diff --git a/api/js/etemplate/Et2InputWidget/test/InputBasicTests.ts b/api/js/etemplate/Et2InputWidget/test/InputBasicTests.ts index 9714989652..fce0593e0d 100644 --- a/api/js/etemplate/Et2InputWidget/test/InputBasicTests.ts +++ b/api/js/etemplate/Et2InputWidget/test/InputBasicTests.ts @@ -84,8 +84,10 @@ export function inputBasicTests(before : Function, test_value : string, value_se }); it("no value gives empty string", () => { - assert.equal((element).querySelector(value_selector).textContent, ""); - assert.equal(element.get_value(), ""); + // Shows as empty / no value + assert.equal((element).querySelector(value_selector).textContent.trim(), "", "Displaying something when there is no value"); + // Gives no value + assert.equal(element.get_value(), "", "Value mismatch"); }); it("value out matches value in", async() => diff --git a/api/js/etemplate/Et2Select/Et2Select.ts b/api/js/etemplate/Et2Select/Et2Select.ts index a9feef29cc..7fb127d825 100644 --- a/api/js/etemplate/Et2Select/Et2Select.ts +++ b/api/js/etemplate/Et2Select/Et2Select.ts @@ -90,11 +90,14 @@ export class Et2Select extends Et2InputWidget(LionSelect) { super.connectedCallback(); - // Add in actual options as children to select - render(html`${this._emptyLabelTemplate()} - ${repeat(this.get_select_options(), (option : SelectOption) => option.value, this._optionTemplate)}`, - this._inputNode - ); + // Add in actual options as children to select, if not already there + if(this._inputNode.children.length == 0) + { + render(html`${this._emptyLabelTemplate()} + ${repeat(this.get_select_options(), (option : SelectOption) => option.value, this._optionTemplate)}`, + this._inputNode + ); + } } /** @@ -125,6 +128,12 @@ export class Et2Select extends Et2InputWidget(LionSelect) return this._widget_id; } + getValue() + { + return this.readOnly ? null : this.value; + } + + /** * Set the select options * @@ -145,6 +154,14 @@ export class Et2Select extends Et2InputWidget(LionSelect) { this._options = new_options; } + // Add in actual options as children to select + if(this._inputNode) + { + render(html`${this._emptyLabelTemplate()} + ${repeat(this.get_select_options(), (option : SelectOption) => option.value, this._optionTemplate)}`, + this._inputNode + ); + } } get_select_options() diff --git a/api/js/etemplate/Et2Select/test/Et2SelectBasic.test.ts b/api/js/etemplate/Et2Select/test/Et2SelectBasic.test.ts new file mode 100644 index 0000000000..61e1b61870 --- /dev/null +++ b/api/js/etemplate/Et2Select/test/Et2SelectBasic.test.ts @@ -0,0 +1,61 @@ +/** + * Test file for Etemplate webComponent Select + * + * In here we test just the simple, basic widget stuff. + */ +// Stub global egw for cssImage to find +// @ts-ignore +window.egw = { + image: () => "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDMyIDMyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsPSIjNjk2OTY5IiBkPSJNNi45NDMsMjguNDUzDQoJYzAuOTA2LDAuNzY1LDIuMDk3LDEuMTI3LDMuMjg2LDEuMTA5YzAuNDMsMC4wMTQsMC44NTItMC4wNjgsMS4yNjUtMC4yMDdjMC42NzktMC4xOCwxLjMyOC0wLjQ1LDEuODY2LTAuOTAyTDI5LjQwMywxNC45DQoJYzEuNzcyLTEuNDk4LDEuNzcyLTMuOTI1LDAtNS40MjJjLTEuNzcyLTEuNDk3LTQuNjQ2LTEuNDk3LTYuNDE4LDBMMTAuMTE5LDIwLjM0OWwtMi4zODktMi40MjRjLTEuNDQtMS40NTctMy43NzItMS40NTctNS4yMTIsMA0KCWMtMS40MzgsMS40Ni0xLjQzOCwzLjgyNSwwLDUuMjgxQzIuNTE4LDIzLjIwNiw1LjQ3NCwyNi45NDcsNi45NDMsMjguNDUzeiIvPg0KPC9zdmc+DQo=" +}; + +import {assert, fixture} from '@open-wc/testing'; +import {html} from "lit-element"; +import * as sinon from 'sinon'; +import {inputBasicTests} from "../../Et2InputWidget/test/InputBasicTests"; +import {Et2Select} from "../Et2Select"; + +let element : Et2Select; + +async function before() +{ + // Create an element to test with, and wait until it's ready + // @ts-ignore + element = await fixture(html` + + `); + + // Stub egw() + sinon.stub(element, "egw").returns({ + tooltipUnbind: () => {}, + // Image always give check mark. Use data URL to avoid having to serve an actual image + }); + + return element; +} + +describe("Select widget basics", () => +{ + // Setup run before each test + beforeEach(before); + + // Make sure it works + it('is defined', () => + { + assert.instanceOf(element, Et2Select); + }); + + it('has a label', () => + { + element.set_label("Label set"); + + assert.equal(element.querySelector("[slot='label']").textContent, "Label set"); + }) + + it("starts empty", () => + { + assert.notExists(element.querySelector("option"), "Static option not found in DOM"); + assert.deepEqual(element.get_select_options(), [], "Unexpected option(s)"); + }) +}); +inputBasicTests(before, "", "select"); \ No newline at end of file diff --git a/api/js/etemplate/Et2Select/test/Et2SelectOptions.test.ts b/api/js/etemplate/Et2Select/test/Et2SelectOptions.test.ts new file mode 100644 index 0000000000..6bd952370a --- /dev/null +++ b/api/js/etemplate/Et2Select/test/Et2SelectOptions.test.ts @@ -0,0 +1,143 @@ +import {assert, elementUpdated, fixture} from '@open-wc/testing'; +import {html} from "lit-element"; +import {Et2Box} from "../../Et2Box/Et2Box"; +import {Et2Select, SelectOption} from "../Et2Select"; +import * as sinon from "sinon"; +import {et2_arrayMgr} from "../../et2_core_arrayMgr"; + +let parser = new window.DOMParser(); + +// Use this to load the select as a child +let container : Et2Box; + +// Element under test +let element : Et2Select; + +// Stub global egw +// @ts-ignore +window.egw = { + tooltipUnbind: () => {}, + lang: i => i + "*", + image: () => "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDMyIDMyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsPSIjNjk2OTY5IiBkPSJNNi45NDMsMjguNDUzDQoJYzAuOTA2LDAuNzY1LDIuMDk3LDEuMTI3LDMuMjg2LDEuMTA5YzAuNDMsMC4wMTQsMC44NTItMC4wNjgsMS4yNjUtMC4yMDdjMC42NzktMC4xOCwxLjMyOC0wLjQ1LDEuODY2LTAuOTAyTDI5LjQwMywxNC45DQoJYzEuNzcyLTEuNDk4LDEuNzcyLTMuOTI1LDAtNS40MjJjLTEuNzcyLTEuNDk3LTQuNjQ2LTEuNDk3LTYuNDE4LDBMMTAuMTE5LDIwLjM0OWwtMi4zODktMi40MjRjLTEuNDQtMS40NTctMy43NzItMS40NTctNS4yMTIsMA0KCWMtMS40MzgsMS40Ni0xLjQzOCwzLjgyNSwwLDUuMjgxQzIuNTE4LDIzLjIwNiw1LjQ3NCwyNi45NDcsNi45NDMsMjguNDUzeiIvPg0KPC9zdmc+DQo=" +}; + +let options = [ + {value: "1", label: "Option 1"}, + {value: "2", label: "Option 2"} +]; +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(html` + + `); + assert.instanceOf(element, Et2Select); + element.remove(); + + // @ts-ignore TypeScript is not recognizing that this widget is a LitElement + container = await fixture(html` + + `); + + assert.instanceOf(container, Et2Box); + // Stub egw() + sinon.stub(container, "egw").returns(window.egw); + }); + + describe("Finds options", () => + { + it("static", async() => + { + /** SETUP **/ + // Create an element to test with, and wait until it's ready + let node = ''; + + container.loadFromXML(parser.parseFromString(node, "text/xml")); + + // wait for asychronous changes to the DOM + // @ts-ignore TypeScript is not recognizing that this widget is a LitElement + await elementUpdated(container); + element = container.getWidgetById('select'); + + /** TESTING **/ + assert.equal(element.querySelector("select").children.length, 1, "Missing static option"); + }); + + it("directly in sel_options", async() => + { + /** SETUP **/ + // Create an element to test with, and wait until it's ready + let node = ''; + container.setArrayMgr("sel_options", new et2_arrayMgr({ + select: options + })); + container.loadFromXML(parser.parseFromString(node, "text/xml")); + + // wait for asychronous changes to the DOM + // @ts-ignore TypeScript is not recognizing that this widget is a LitElement + await elementUpdated(container); + element = container.getWidgetById('select'); + + /** TESTING **/ + assert.equal(element.querySelector("select").children.length, 2); + }); + + it("merges static options with sel_options", async() => + { + /** SETUP **/ + + // Create an element to test with, and wait until it's ready + let node = ''; + container.setArrayMgr("sel_options", new et2_arrayMgr({ + select: options + })); + container.loadFromXML(parser.parseFromString(node, "text/xml")); + + // wait for asychronous changes to the DOM + // @ts-ignore TypeScript is not recognizing that this widget is a LitElement + await elementUpdated(container); + element = container.getWidgetById('select'); + + /** TESTING **/ + + // @ts-ignore o.value isn't known by TypeScript, but it's there + let option_keys = Object.values(element.querySelector("select").children).map(o => o.value); + assert.include(option_keys, "option", "Static option missing"); + assert.includeMembers(option_keys, ["1", "2", "option"], "Option mis-match"); + assert.equal(options.length, 3); + }); + }); + + describe("Value tests", () => + { + it("set_value()", async() => + { + /** SETUP **/ + // Create an element to test with, and wait until it's ready + let node = ''; + let test_value = "2"; + container.setArrayMgr("sel_options", new et2_arrayMgr({ + select: options + })); + container.loadFromXML(parser.parseFromString(node, "text/xml")); + + // wait for asychronous changes to the DOM + // @ts-ignore TypeScript is not recognizing that this widget is a LitElement + await elementUpdated(container); + element = container.getWidgetById('select'); + + /** TESTING **/ + element.set_value(test_value); + // wait for asychronous changes to the DOM + // @ts-ignore TypeScript is not recognizing that this widget is a LitElement + await elementUpdated(element); + + // Now check + assert.equal(element.get_value(), test_value, "Wrong value from widget"); + assert.equal(element.querySelector("select").value, test_value, "Wrong value in DOM") + }); + }); +}); \ No newline at end of file diff --git a/api/js/etemplate/Et2Widget/Et2Widget.ts b/api/js/etemplate/Et2Widget/Et2Widget.ts index 10566b8595..e93e309f7f 100644 --- a/api/js/etemplate/Et2Widget/Et2Widget.ts +++ b/api/js/etemplate/Et2Widget/Et2Widget.ts @@ -1067,7 +1067,7 @@ const Et2WidgetMixin = (superClass) => egw() : IegwAppLocal { - if(this.getParent() != null && !(this.getParent() instanceof HTMLElement)) + if(this.getParent() != null && typeof this.getParent().egw === "function") { return (this.getParent()).egw(); } @@ -1236,7 +1236,7 @@ function transformAttributes(widget, mgr : et2_arrayMgr, attributes) */ export function cssImage(image_name : string, app_name? : string) { - let url = egw.image(image_name, app_name); + let url = egw?.image(image_name, app_name); if(url) { return css`url(${unsafeCSS(url)})`;