Get some automatic tests working again

This commit is contained in:
nathan 2023-09-20 14:24:01 -06:00
parent 5349c7a966
commit e2d3c5f1e8
7 changed files with 110 additions and 139 deletions

View File

@ -82,11 +82,21 @@ export function inputBasicTests(before : Function, test_value : string, value_se
{
element = await before();
});
it("no value gives empty string", () =>
it("no value gives empty string", async() =>
{
element.set_value("");
await elementUpdated(element);
// Shows as empty / no value
let value = (<Element><unknown>element).querySelector(value_selector) || (<Element><unknown>element).shadowRoot.querySelector(value_selector);
assert.isDefined(value, "Bad value selector '" + value_selector + "'");
debugger;
assert.equal(value.textContent.trim(), "", "Displaying something when there is no value");
if(element.multiple)
{
assert.isEmpty(element.get_value());
return;
}
// Gives no value
assert.equal(element.get_value(), "", "Value mismatch");
});
@ -94,7 +104,7 @@ export function inputBasicTests(before : Function, test_value : string, value_se
it("value out matches value in", async() =>
{
element.set_value(test_value);
debugger;
// wait for asychronous changes to the DOM
await elementUpdated(<Element><unknown>element);

View File

@ -14,7 +14,6 @@ import {Et2WidgetWithSelectMixin} from "./Et2WidgetWithSelectMixin";
import {SelectOption} from "./FindSelectOptions";
import shoelace from "../Styles/shoelace";
import {RowLimitedMixin} from "../Layout/RowLimitedMixin";
import {Et2Tag} from "./Tag/Et2Tag";
import {Et2WithSearchMixin} from "./SearchMixin";
import {property} from "lit/decorators/property.js";
import {SlChangeEvent, SlOption, SlSelect} from "@shoelace-style/shoelace";
@ -582,61 +581,6 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
return literal`et2-tag`;
}
/**
* Customise how tags are rendered. This overrides what SlSelect
* does in syncItemsFromValue().
* This is a copy+paste from SlSelect.syncItemsFromValue().
*
* @param item
* @protected
*/
protected _createTagNode(item)
{
console.warn("Deprecated");
debugger;
let tag;
if(typeof super._createTagNode == "function")
{
tag = super._createTagNode(item);
}
else
{
tag = <Et2Tag>document.createElement(this.tagTag);
}
tag.value = item.value;
tag.textContent = item?.getTextLabel()?.trim();
tag.class = item.classList.value + " search_tag";
tag.setAttribute("exportparts", "icon");
if(this.size)
{
tag.size = this.size;
}
if(this.readonly || item.option && typeof (item.option.disabled) != "undefined" && item.option.disabled)
{
tag.removable = false;
tag.readonly = true;
}
else
{
tag.addEventListener("dblclick", this._handleDoubleClick);
tag.addEventListener("click", this.handleTagInteraction);
tag.addEventListener("keydown", this.handleTagInteraction);
tag.addEventListener("sl-remove", (event : CustomEvent) => this.handleTagRemove(event, item));
}
// Allow click handler even if read only
if(typeof this.onTagClick == "function")
{
tag.addEventListener("click", (e) => this.onTagClick(e, e.target));
}
let image = this._createImage(item);
if(image)
{
tag.prepend(image);
}
return tag;
}
blur()
{
if(typeof super.blur == "function")
@ -727,37 +671,6 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
}
/**
* Get the icon for the select option
*
* @param option
* @protected
*/
protected _iconTemplate(option)
{
if(!option.icon)
{
return html``;
}
return html`
<et2-image slot="prefix" part="icon" style="width: var(--icon-width)"
src="${option.icon}"></et2-image>`
}
protected _createImage(item)
{
let image = item?.querySelector ? item.querySelector("et2-image") || item.querySelector("[slot='prefix']") : null;
if(image)
{
image = image.clone();
image.slot = "prefix";
image.class = "tag_image";
return image;
}
return "";
}
/** Shows the listbox. */
async show()
{
@ -865,6 +778,24 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
</sl-option>`;
}
/**
* Get the icon for the select option
*
* @param option
* @protected
*/
protected _iconTemplate(option)
{
if(!option.icon)
{
return html``;
}
return html`
<et2-image slot="prefix" part="icon" style="width: var(--icon-width)"
src="${option.icon}"></et2-image>`
}
/**
* Custom tag
@ -881,7 +812,7 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
{
const readonly = (this.readonly || option && typeof (option.disabled) != "undefined" && option.disabled);
const isEditable = this.editModeEnabled && !readonly;
const image = this._createImage(option);
const image = this._iconTemplate(option);
const tagName = this.tagTag;
return html`
<${tagName}
@ -900,6 +831,7 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
?readonly=${readonly}
?editable=${isEditable}
.value=${option.value.replaceAll("___", " ")}
@change=${this.handleTagEdit}
@dblclick=${this._handleDoubleClick}
@click=${typeof this.onTagClick == "function" ? (e) => this.onTagClick(e, e.target) : nothing}
>

View File

@ -751,8 +751,10 @@ export const Et2WithSearchMixin = dedupeMixin(<T extends Constructor<LitElement>
focus()
{
this.show();
this._searchInputNode.focus();
this.show().then(() =>
{
this._searchInputNode?.focus();
});
}
_handleMenuHide()
@ -1303,20 +1305,16 @@ export const Et2WithSearchMixin = dedupeMixin(<T extends Constructor<LitElement>
}
// Make sure not to double-add, but wait until the option is there
this.updateComplete.then(() =>
if(this.multiple && this.getValueAsArray().indexOf(text) == -1)
{
if(this.multiple && this.getValueAsArray().indexOf(text) == -1)
{
let value = this.getValueAsArray();
value.push(text);
this.value = value;
}
else if(!this.multiple && this.value !== text)
{
this.value = text;
}
this.requestUpdate("value");
});
let value = this.getValueAsArray();
value.push(text);
this.value = value;
}
else if(!this.multiple && this.value !== text)
{
this.value = text;
}
// If we were overlapping edit inputbox with the value display, reset
if(!this.readonly && this._activeControls?.classList.contains("novalue"))
@ -1370,7 +1368,6 @@ export const Et2WithSearchMixin = dedupeMixin(<T extends Constructor<LitElement>
{
this.value = value;
}
this.querySelector("[value='" + original.replace(/'/g, "\\\'") + "']")?.remove();
this.__select_options = this.__select_options.filter(v => v.value !== original);
}
}

View File

@ -13,6 +13,7 @@ window.egw = {
};
let element : Et2Select;
const tag_name = "et2-tag";
async function before(editable = true)
{
@ -20,16 +21,19 @@ async function before(editable = true)
// @ts-ignore
element = await fixture<Et2Select>(html`
<et2-select label="I'm a select" value="one" multiple="true" .editModeEnabled=${editable}>
<sl-option value="one">One</sl-option>
<sl-option value="two">Two</sl-option>
<option value="one">One</option>
<option value="two">Two</option>
</et2-select>
`);
// Need to call loadFromXML() explicitly to read the options
element.loadFromXML(element);
// Stub egw()
sinon.stub(element, "egw").returns(window.egw);
await element.updateComplete;
let tags = [];
element.shadowRoot.querySelectorAll(element.tagTag).forEach((t : Et2Tag) => tags.push(t.updateComplete));
element.shadowRoot.querySelectorAll(tag_name).forEach((t : Et2Tag) => tags.push(t.updateComplete));
await Promise.all(tags);
return element;
@ -48,30 +52,29 @@ describe("Editable tag", () =>
it("Tag editable matches editModeEnabled", async() =>
{
let tag = element.shadowRoot.querySelectorAll(element.tagTag);
let tag = element.select.combobox.querySelectorAll(tag_name);
assert.isAbove(tag.length, 0, "No tags found");
assert.isTrue(tag[0].editable);
// Change it to false & force immediate update
element.editModeEnabled = false;
element.syncItemsFromValue();
element.requestUpdate();
await element.updateComplete;
tag = element.shadowRoot.querySelectorAll(element.tagTag);
tag = element.select.combobox.querySelectorAll(tag_name);
assert.isAbove(tag.length, 0, "No tags found");
assert.isFalse(tag[0].editable);
});
it("Has edit button when editable ", async() =>
{
let tag = element.shadowRoot.querySelectorAll(element.tagTag);
let tag = element.select.combobox.querySelectorAll(tag_name);
assert.isAbove(tag.length, 0, "No tags found");
assert.exists(tag[0].shadowRoot.querySelector("et2-button-icon[label='edit*']"), "No edit button");
});
it("Shows input when edit button is clicked", async() =>
{
let tag = element.shadowRoot.querySelectorAll(element.tagTag)[0];
let tag = element.select.combobox.querySelectorAll(tag_name)[0];
let edit_button = tag.shadowRoot.querySelector("et2-button-icon");
edit_button.click();
@ -81,7 +84,7 @@ describe("Editable tag", () =>
});
it("Changes value when edited", async() =>
{
let tag = <Et2Tag>element.shadowRoot.querySelectorAll(element.tagTag)[0];
let tag = <Et2Tag>element.select.combobox.querySelectorAll(tag_name)[0];
tag.isEditing = true;
tag.requestUpdate();
await tag.updateComplete;
@ -119,7 +122,7 @@ describe("Editable tag", () =>
await listener2;
assert.equal(tag.value, "change select too");
// Haven't turned on allow free entries, so no change here
// Have turned on allow free entries, so it should change here
assert.equal(element.value, "change select too", "Tag change did not cause value change in parent select (allowFreeEntries was on)");
});
@ -129,7 +132,7 @@ describe("Editable tag", () =>
element.readonly = true;
await element.updateComplete;
let tag = element.shadowRoot.querySelectorAll(element.tagTag);
let tag = element.select.combobox.querySelectorAll(tag_name);
assert.isAbove(tag.length, 0, "No tags found");
let wait = [];
@ -146,7 +149,7 @@ describe("Select is not editable", () =>
it("Does not have edit button when not editable", async() =>
{
let tag = element.shadowRoot.querySelectorAll(element.tagTag);
let tag = element.select.combobox.querySelectorAll(tag_name);
assert.isAbove(tag.length, 0, "No tags found");
assert.isNull(tag[0].shadowRoot.querySelector("et2-button-icon[label='edit*']"), "Unexpected edit button");

View File

@ -9,6 +9,18 @@
import {assert, fixture, html} from '@open-wc/testing';
import {Et2EmailTag} from "../Tag/Et2EmailTag";
import * as sinon from 'sinon';
// Stub global egw
// @ts-ignore
window.egw = {
tooltipUnbind: () => {},
lang: i => i + "*",
image: () => "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDMyIDMyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsPSIjNjk2OTY5IiBkPSJNNi45NDMsMjguNDUzDQoJYzAuOTA2LDAuNzY1LDIuMDk3LDEuMTI3LDMuMjg2LDEuMTA5YzAuNDMsMC4wMTQsMC44NTItMC4wNjgsMS4yNjUtMC4yMDdjMC42NzktMC4xOCwxLjMyOC0wLjQ1LDEuODY2LTAuOTAyTDI5LjQwMywxNC45DQoJYzEuNzcyLTEuNDk4LDEuNzcyLTMuOTI1LDAtNS40MjJjLTEuNzcyLTEuNDk3LTQuNjQ2LTEuNDk3LTYuNDE4LDBMMTAuMTE5LDIwLjM0OWwtMi4zODktMi40MjRjLTEuNDQtMS40NTctMy43NzItMS40NTctNS4yMTIsMA0KCWMtMS40MzgsMS40Ni0xLjQzOCwzLjgyNSwwLDUuMjgxQzIuNTE4LDIzLjIwNiw1LjQ3NCwyNi45NDcsNi45NDMsMjguNDUzeiIvPg0KPC9zdmc+DQo=",
webserverUrl: "",
app: (_app) => _app,
jsonq: () => Promise.resolve({})
};
describe('Et2EmailTag', () =>
{
@ -18,7 +30,13 @@ describe('Et2EmailTag', () =>
{
component = await fixture<Et2EmailTag>(html`
<et2-email-tag value="test@example.com"></et2-email-tag>`);
// Stub egw()
// @ts-ignore
sinon.stub(component, "egw").returns(window.egw);
await component.updateComplete;
// Asserting this instanceOf forces class loading
assert.instanceOf(component, Et2EmailTag);
});
it('should be defined', () =>
@ -48,7 +66,8 @@ describe('Et2EmailTag', () =>
it('should open addressbook with email preset on (+) click', () =>
{
component.egw = () => ({
window.egw.open = () =>
{
open: (url, app, mode, extra) =>
{
assert.equal(url, '');
@ -56,7 +75,7 @@ describe('Et2EmailTag', () =>
assert.equal(mode, 'add');
assert.equal(extra['presets[email]'], 'test@example.com');
}
});
};
component.handleMouseDown(new MouseEvent('click'));
});
@ -70,7 +89,8 @@ describe('Et2EmailTag', () =>
};
component.value = 'test@example.com';
component.checkContact = async(email) => contact;
component.egw = () => ({
component.egw.open = () =>
{
open: (id, app, mode, extra) =>
{
assert.equal(id, contact.id);
@ -78,7 +98,7 @@ describe('Et2EmailTag', () =>
assert.equal(mode, 'view');
assert.deepEqual(extra, {title: contact.n_fn, icon: contact.photo});
}
});
};
await component.handleContactMouseDown(new MouseEvent('click'));
});
});

View File

@ -25,11 +25,13 @@ async function before()
// Create an element to test with, and wait until it's ready
// @ts-ignore
element = await fixture<Et2Select>(html`
<et2-select label="I'm a select"/>
<et2-select label="I'm a select">
</et2-select>
`);
// Stub egw()
sinon.stub(element, "egw").returns(window.egw);
await elementUpdated(element);
return element;
}
@ -48,7 +50,6 @@ describe("Select widget basics", () =>
it('has a label', async() =>
{
element.set_label("Label set");
// @ts-ignore TypeScript doesn't recognize widgets as Elements
await elementUpdated(element);
assert.equal(element.querySelector("[slot='label']").textContent, "Label set");
@ -64,28 +65,30 @@ describe("Select widget basics", () =>
{
// WIP
const blurSpy = sinon.spy();
element.addEventListener('blur', blurSpy);
element.addEventListener('sl-hide', blurSpy);
const showPromise = new Promise(resolve =>
{
element.addEventListener("sl-after-show", resolve);
});
const hidePromise = new Promise(resolve =>
{
element.addEventListener("blur", resolve);
element.addEventListener("sl-hide", resolve);
});
await elementUpdated(element);
element.focus();
await showPromise;
await elementUpdated(element);
element.blur();
await elementUpdated(element);
await hidePromise;
sinon.assert.calledOnce(blurSpy);
// Check that it actually closed dropdown
assert.isFalse(element.dropdown?.hasAttribute("open"));
assert.isFalse(element.select?.hasAttribute("open"));
})
});
@ -97,10 +100,11 @@ describe("Multiple", () =>
// @ts-ignore
element = await fixture<Et2Select>(html`
<et2-select label="I'm a select" multiple="true">
<sl-option value="one">One</sl-option>
<sl-option value="two">Two</sl-option>
<option value="one">One</option>
<option value="two">Two</option>
</et2-select>
`);
element.loadFromXML(element);
element.set_value("one,two");
// Stub egw()
@ -111,14 +115,14 @@ describe("Multiple", () =>
it("Can remove tags", async() =>
{
assert.equal(element.querySelectorAll("sl-option").length, 2, "Did not find options");
assert.equal(element.select.querySelectorAll("sl-option").length, 2, "Did not find options");
assert.sameMembers(element.value, ["one", "two"]);
let tags = element.shadowRoot.querySelectorAll('.select__tags > *');
let tags = element.select.combobox.querySelectorAll('.select__tags et2-tag');
// Await tags to render
let tag_updates = []
element.shadowRoot.querySelectorAll(element.tagTag).forEach((t : Et2Tag) => tag_updates.push(t.updateComplete));
element.select.combobox.querySelectorAll("et2-tag").forEach((t : Et2Tag) => tag_updates.push(t.updateComplete));
await Promise.all(tag_updates);
assert.equal(tags.length, 2);
@ -138,15 +142,21 @@ describe("Multiple", () =>
// Wait for widget to update
await element.updateComplete;
tag_updates = []
element.shadowRoot.querySelectorAll(element.tagTag).forEach((t : Et2Tag) => tag_updates.push(t.updateComplete));
element.select.combobox.querySelectorAll('et2-tag').forEach((t : Et2Tag) => tag_updates.push(t.updateComplete));
await Promise.all(tag_updates);
// Check
assert.sameMembers(element.value, ["two"], "Removing tag did not remove value");
tags = element.shadowRoot.querySelectorAll('.select__tags > *');
tags = element.select.combobox.querySelectorAll('.select__tags et2-tag');
assert.equal(tags.length, 1, "Removed tag is still there");
});
});
inputBasicTests(before, "", "select");
inputBasicTests(async() =>
{
const element = await before();
element.noLang = true;
element.select_options = [{value: "", label: ""}];
return element
}, "", "sl-select");

View File

@ -1,8 +1,9 @@
import {assert, elementUpdated, fixture, html} from '@open-wc/testing';
import {Et2Box} from "../../Layout/Et2Box/Et2Box";
import {Et2Select, SelectOption} from "../Et2Select";
import {Et2Select} from "../Et2Select";
import * as sinon from "sinon";
import {et2_arrayMgr} from "../../et2_core_arrayMgr";
import {SelectOption} from "../FindSelectOptions";
let parser = new window.DOMParser();
@ -30,7 +31,6 @@ describe("Select widget", () =>
beforeEach(async() =>
{
// This stuff because otherwise Et2Select isn't actually loaded when testing
// @ts-ignore TypeScript is not recognizing that this widget is a LitElement
element = await fixture<Et2Select>(html`
<et2-select></et2-select>
`);
@ -39,7 +39,6 @@ describe("Select widget", () =>
assert.instanceOf(element, Et2Select);
element.remove();
// @ts-ignore TypeScript is not recognizing that this widget is a LitElement
container = await fixture<Et2Box>(html`
<et2-box/>
`);