Et2Email: Some automatic tests

This commit is contained in:
nathan 2023-12-20 15:22:14 -07:00 committed by ralf
parent 5e42dc34de
commit 3746e07276
4 changed files with 152 additions and 33 deletions

View File

@ -89,16 +89,7 @@ export class Et2Email extends Et2InputWidget(LitElement) implements SearchMixinI
// Parse string into array
if(typeof value === 'string' && value.indexOf(',') !== -1)
{
let val = value.split(',');
for(let n = 0; n < val.length - 1; n++)
{
while(val[n].indexOf('@') === -1 && n < val.length - 1)
{
val[n] += ',' + val[n + 1];
val.splice(n + 1, 1);
}
}
return val;
return parseEmailsString(value, false);
}
return value;
},
@ -676,6 +667,47 @@ export class Et2Email extends Et2InputWidget(LitElement) implements SearchMixinI
}
}
<<<<<<< HEAD
=======
/**
* Sometimes users paste multiple comma separated values at once. Split them then handle normally.
* Overridden here to handle email addresses that may have commas using the regex from the validator.
*
* @param {ClipboardEvent} event
* @protected
*/
protected handlePaste(event : ClipboardEvent)
{
event.preventDefault();
let paste = event.clipboardData.getData('text');
if(!paste)
{
return;
}
const selection = window.getSelection();
if(selection.rangeCount)
{
selection.deleteFromDocument();
}
let values = parseEmailsString(paste, this.allowPlaceholder);
if(values)
{
values.forEach(v =>
{
this.addAddress(v.trim());
});
this.hide();
// Update key to force Lit to redraw tags
this._valueUID = this.egw()?.uid() ?? new Date().toISOString();
this.dispatchEvent(new Event("change", {bubbles: true}));
}
}
>>>>>>> f68faa7941 (Et2Email: Some automatic tests)
private handleSearchFocus()
{
this.hasFocus = true;
@ -693,6 +725,26 @@ export class Et2Email extends Et2InputWidget(LitElement) implements SearchMixinI
this.hasFocus = false;
// Should not be needed, but not firing the update
this.requestUpdate("hasFocus");
<<<<<<< HEAD
=======
// If they had something OK typed, use it, but only if focus went outside Et2Email
// because maybe they clicked an option which took focus
if(event.composedPath().includes(this))
{
if(this.addAddress(this._search.value.trim()))
{
this._search.value = "";
this.dispatchEvent(new Event("change", {bubbles: true}));
}
else if(this._search.value)
{
// Invalid input, show message. Not part of the value, so normal validation doesn't apply
// Can't just call this.validate(), it will get cleared immediately
this.set_validation_error(this.egw().lang("Invalid email") + ' "' + this._search.value + '"')
}
}
>>>>>>> f68faa7941 (Et2Email: Some automatic tests)
}
handleSearchKeyDown(event)
@ -742,6 +794,7 @@ export class Et2Email extends Et2InputWidget(LitElement) implements SearchMixinI
{
this.open = false;
this._search.value = "";
this.dispatchEvent(new Event("change", {bubbles: true}));
}
if(event.key == "Tab")
{
@ -927,6 +980,7 @@ export class Et2Email extends Et2InputWidget(LitElement) implements SearchMixinI
this._search.value = "";
this._search.focus();
this.requestUpdate("value");
this.dispatchEvent(new Event("change", {bubbles: true}));
if(this._close_on_select)
{
this.open = false;
@ -941,6 +995,7 @@ export class Et2Email extends Et2InputWidget(LitElement) implements SearchMixinI
let index = this.value.indexOf(event.originalValue);
this.value[index] = event.target.value;
this.requestUpdate();
this.dispatchEvent(new Event("change", {bubbles: true}));
}
if(event.target.current)
{
@ -954,6 +1009,7 @@ export class Et2Email extends Et2InputWidget(LitElement) implements SearchMixinI
const index = this.value.indexOf(value);
this.value.splice(index, 1);
this.requestUpdate("value");
this.dispatchEvent(new Event("change", {bubbles: true}));
}
tagsTemplate()
@ -1143,6 +1199,7 @@ export class Et2Email extends Et2InputWidget(LitElement) implements SearchMixinI
}
}
// @ts-ignore TypeScript is not recognizing that this widget is a LitElement
customElements.define("et2-email", Et2Email);
/**

View File

@ -2,6 +2,8 @@ import {assert, elementUpdated, fixture, html, oneEvent} from '@open-wc/testing'
import * as sinon from 'sinon';
import {inputBasicTests} from "../../Et2InputWidget/test/InputBasicTests";
import {Et2Email} from "../Et2Email";
import {Et2EmailTag} from "../../Et2Select/Tag/Et2EmailTag";
import {waitForEvent} from "../../Et2Widget/event";
/**
* Test file for Etemplate webComponent Select
@ -10,11 +12,24 @@ import {Et2Email} from "../Et2Email";
*/
// Stub global egw for cssImage to find
// @ts-ignore
let uid = 0;
const testSuggestions = [
{value: "suggestion.1@example.com", label: "Suggestion 1"},
{value: "suggestion.2@example.com", label: "Suggestion 2"}
];
window.egw = {
ajaxUrl: () => "",
app: () => "addressbook",
decodePath: (_path : string) => _path,
image: () => "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDMyIDMyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsPSIjNjk2OTY5IiBkPSJNNi45NDMsMjguNDUzDQoJYzAuOTA2LDAuNzY1LDIuMDk3LDEuMTI3LDMuMjg2LDEuMTA5YzAuNDMsMC4wMTQsMC44NTItMC4wNjgsMS4yNjUtMC4yMDdjMC42NzktMC4xOCwxLjMyOC0wLjQ1LDEuODY2LTAuOTAyTDI5LjQwMywxNC45DQoJYzEuNzcyLTEuNDk4LDEuNzcyLTMuOTI1LDAtNS40MjJjLTEuNzcyLTEuNDk3LTQuNjQ2LTEuNDk3LTYuNDE4LDBMMTAuMTE5LDIwLjM0OWwtMi4zODktMi40MjRjLTEuNDQtMS40NTctMy43NzItMS40NTctNS4yMTIsMA0KCWMtMS40MzgsMS40Ni0xLjQzOCwzLjgyNSwwLDUuMjgxQzIuNTE4LDIzLjIwNiw1LjQ3NCwyNi45NDcsNi45NDMsMjguNDUzeiIvPg0KPC9zdmc+DQo=",
jsonq: () => Promise.resolve({}),
lang: i => i + "*",
link: i => i,
preference: i => "",
request: () => Promise.resolve(testSuggestions),
tooltipUnbind: () => {},
webserverUrl: ""
webserverUrl: "",
uid: () => {return "" + (uid++);}
};
let element : Et2Email;
@ -52,7 +67,13 @@ describe("Email widget basics", () =>
await elementUpdated(element);
assert.equal(element.querySelector("[slot='label']").textContent, "Label set");
})
});
it("textbox gets focus when widget is focused", async() =>
{
element.focus();
assert.equal(element.shadowRoot.activeElement, element._search, "Search textbox did not get focus when widget got focus");
});
it("closes when losing focus", async() =>
{
@ -67,8 +88,9 @@ describe("Email widget basics", () =>
{
element.addEventListener("sl-hide", resolve);
});
await elementUpdated(element);
element.focus();
element.show();
await showPromise;
await elementUpdated(element);
@ -82,7 +104,57 @@ describe("Email widget basics", () =>
// Check that it actually closed dropdown
assert.isFalse(element.hasAttribute("open"));
})
});
it("blurring widget accepts current text", async() =>
{
const value = "valid@example.com";
element.focus();
element._search.value = value;
element.blur();
await elementUpdated(element);
assert.sameMembers(element.value, [value], "Valid email was not accepted on blur");
});
});
describe("Suggestions", () =>
{ // Setup run before each test
beforeEach(before);
it("clicking accepts suggestion", async() =>
{
await elementUpdated(element);
// Start the search
element.focus();
element.startSearch();
debugger;
await waitForEvent(element, "sl-after-show");
// Click the first one
element._listbox.querySelector('sl-option').dispatchEvent(new MouseEvent("mouseup", {bubbles: true}))
await elementUpdated(element);
// Check the value
assert.sameMembers(element.value, [testSuggestions[0].value]);
});
it("tab accepts top suggestion", async() =>
{
element.focus();
element.startSearch();
await waitForEvent(element, "sl-after-show");
// No match between what they typed and the suggestion - no
element._search.dispatchEvent(new KeyboardEvent("keydown", {key: "Tab"}));
await elementUpdated(element);
assert.sameMembers(element.value, []);
// Partial match with current suggestion, take it
element.focus();
element._search.value = "sugg";
element._search.dispatchEvent(new KeyboardEvent("keydown", {key: "Tab"}));
await elementUpdated(element);
assert.sameMembers(element.value, [testSuggestions[0].value]);
});
});
describe("Tags", () =>
@ -107,21 +179,11 @@ describe("Tags", () =>
{
assert.equal(element._tags.length, 2, "Did not find tags");
// Await tags to render
/* TODO
let tag_updates = []
element.select.combobox.querySelectorAll("et2-tag").forEach((t : Et2Tag) => tag_updates.push(t.updateComplete));
await Promise.all(tag_updates);
assert.equal(tags.length, 2);
assert.equal(tags[0].value, "one");
assert.equal(tags[1].value, "two");
*/
// Set up listener
const listener = oneEvent(element, "change");
// Click to remove first tag
let removeButton = tags[0].shadowRoot.querySelector("[part='remove-button']");
let removeButton = element._tags[0].shadowRoot.querySelector("[part='remove-button']");
assert.exists(removeButton, "Could not find tag remove button");
removeButton.dispatchEvent(new Event("click"));
@ -129,14 +191,13 @@ describe("Tags", () =>
// Wait for widget to update
await element.updateComplete;
tag_updates = []
element.select.combobox.querySelectorAll('et2-tag').forEach((t : Et2Tag) => tag_updates.push(t.updateComplete));
let tag_updates = []
element._tags.forEach((t : Et2EmailTag) => tag_updates.push(t.updateComplete));
await Promise.all(tag_updates);
// Check
assert.sameMembers(element.value, ["two"], "Removing tag did not remove value");
tags = element.select.combobox.querySelectorAll('.select__tags et2-tag');
assert.equal(tags.length, 1, "Removed tag is still there");
assert.sameMembers(element.value, ["two@example.com"], "Removing tag did not remove value");
assert.equal(element._tags.length, 1, "Removed tag is still there");
});
});
@ -146,4 +207,4 @@ inputBasicTests(async() =>
const element = await before();
element.noLang = true;
return element
}, "", "sl-select");
}, "", "input");

View File

@ -181,7 +181,7 @@ export class Et2EmailTag extends Et2Tag
e.stopPropagation();
let extra = {
'presets[email]': this.value
'presets[email]': this.value ?? ""
};
this.egw().open('', 'addressbook', 'add', extra);

View File

@ -76,7 +76,8 @@ describe('Et2EmailTag', () =>
assert.equal(extra['presets[email]'], 'test@example.com');
}
};
component.handleMouseDown(new MouseEvent('click'));
debugger;
component.shadowRoot.querySelector("et2-button-icon").dispatchEvent(new MouseEvent('click'));
});
it('should open addressbook CRM on avatar click', async() =>