From adb96f143a8491ce51789d1a65bccfa597ef8a9e Mon Sep 17 00:00:00 2001 From: Damien Cassou Date: Wed, 29 Jan 2025 16:48:53 +0100 Subject: [PATCH 1/5] 40698660: Cleanup widgetTest.js --- src/test/widgetTest.js | 40 +++------------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/src/test/widgetTest.js b/src/test/widgetTest.js index 90365360..6a511c44 100644 --- a/src/test/widgetTest.js +++ b/src/test/widgetTest.js @@ -10,17 +10,14 @@ const widgetSubclass = widget.subclass((that) => { }); function withWidget(callback) { - // create a widget const my = {}; const aWidget = widgetSubclass({}, my); aWidget.appendTo(jQuery("body")); - // execute test callback(aWidget, my); - // clean-up : remove widget aWidget.asJQuery().remove(); } @@ -34,8 +31,6 @@ function withCanvas(callback) { sandbox.remove(); } -// actual tests - describe("function", () => { it("widgets are assigned unique identifiers", () => { expect.assertions(1000); @@ -56,8 +51,6 @@ describe("function", () => { it("widgets supports events", () => { expect.assertions(1); - // Arrange: a widget with a public method - // that triggers an event when executed. const aWidget = (function () { const my = {}; const that = widgetSubclass({}, my); @@ -69,18 +62,14 @@ describe("function", () => { return that; })(); - // Assert: that callback is executed when aWidget.register("anEvent", () => { expect(true).toBeTruthy(); }); - // event is triggered aWidget.aMethod(); }); it("widgets supports event methods", () => { - // Arrange: a widget with a public method - // that triggers an event when executed. const aWidget = (function () { const my = {}; const that = widgetSubclass({}, my); @@ -96,24 +85,22 @@ describe("function", () => { const spy = vi.fn(); - // Assert: that callback is executed when aWidget.anEvent.register(spy); - // event is triggered aWidget.aMethod(); expect(spy).toHaveBeenCalledWith(); }); it("linkTo() creates links to paths in app", () => { - const my = {}; // reference to protected methods using "my"; + const my = {}; widgetSubclass({}, my); expect(my.linkTo("foo/bar")).toBe("#!/foo/bar"); }); it("redirectTo() redirects to paths in app", () => { - const my = {}; // reference to protected methods using "my"; + const my = {}; widgetSubclass({}, my); my.redirectTo("foo/bar"); @@ -161,7 +148,6 @@ describe("function", () => { expect.assertions(1); withCanvas((html) => { - // Arrange: a widget const aWidget = (function () { const that = widgetSubclass(); @@ -172,16 +158,13 @@ describe("function", () => { return that; })(); - // and a DIV with existing content const divQuery = html .div(html.span("content")) .id("aDiv") .asJQuery(); - // Act: append widget to DIV aWidget.appendTo(divQuery); - // Assert: that widget was appended last to DIV expect(divQuery.children().get(1).id).toBe(aWidget.id()); }); }); @@ -190,7 +173,6 @@ describe("function", () => { expect.assertions(2); withCanvas((html) => { - // Arrange: a widget const aWidget = (function () { const that = widgetSubclass(); @@ -201,16 +183,13 @@ describe("function", () => { return that; })(); - // and a DIV with existing content const divQuery = html .div(html.span("content")) .id("aDiv") .asJQuery(); - // Act: replace content with jQuery aWidget.replace(divQuery); - // Assert: that widget was appended to DIV expect(divQuery.children()).toHaveLength(1); expect(divQuery.children().get(0).id).toBe(aWidget.id()); }); @@ -220,7 +199,6 @@ describe("function", () => { expect.assertions(1); withCanvas((html) => { - // Arrange: a widget const aWidget = (function () { const that = widgetSubclass(); @@ -231,10 +209,8 @@ describe("function", () => { return that; })(); - // Act: append widget to canvas html.render(aWidget); - // Assert: that widget was rendered in canvas expect(html.root.asJQuery().find(".aDiv").get(0)).toBeTruthy(); }); }); @@ -243,7 +219,6 @@ describe("function", () => { expect.assertions(2); withCanvas((html) => { - // Arrange: a widget const aWidget = (function () { const that = widgetSubclass(); @@ -254,13 +229,10 @@ describe("function", () => { return that; })(); - // Assert: false before render - expect(!aWidget.isRendered()).toBeTruthy(); + expect(aWidget.isRendered()).toBeFalsy(); - // Act: render widget html.render(aWidget); - // Assert: true ehrn rendered expect(aWidget.isRendered()).toBeTruthy(); }); }); @@ -269,8 +241,6 @@ describe("function", () => { expect.assertions(1); withCanvas((html) => { - // Arrange: a widget that renders it"s root as - // form instead of DIV const aWidget = (function () { const my = {}; const that = widgetSubclass({}, my); @@ -282,10 +252,8 @@ describe("function", () => { return that; })(); - // Act: render widget html.render(aWidget); - // Assert: that form is rendered with id expect(html.root.asJQuery().find("FORM").get(0).id).toBe( aWidget.id(), ); @@ -314,10 +282,8 @@ describe("function", () => { return that; })(); - // Act: render widget html.render(aWidget); - // Assert: that form is rendered with id expect(aWidget.willAttachCalled).toBeTruthy(); expect(aWidget.didAttachCalled).toBeTruthy(); }); From ef9cb162697f3d5d7b53399042ef51b6493b85f5 Mon Sep 17 00:00:00 2001 From: Damien Cassou Date: Wed, 29 Jan 2025 16:44:48 +0100 Subject: [PATCH 2/5] 40698660: Surround a test with a describe() block --- src/test/widgetTest.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/test/widgetTest.js b/src/test/widgetTest.js index 6a511c44..6531f531 100644 --- a/src/test/widgetTest.js +++ b/src/test/widgetTest.js @@ -215,25 +215,27 @@ describe("function", () => { }); }); - it("isRendered()", () => { - expect.assertions(2); + describe("isRendered()", () => { + it("returns false before render", () => { + expect.assertions(2); - withCanvas((html) => { - const aWidget = (function () { - const that = widgetSubclass(); + withCanvas((html) => { + const aWidget = (function () { + const that = widgetSubclass(); - that.renderContentOn = function (html) { - html.div("div").addClass("aDiv"); - }; + that.renderContentOn = function (html) { + html.div("div").addClass("aDiv"); + }; - return that; - })(); + return that; + })(); - expect(aWidget.isRendered()).toBeFalsy(); + expect(aWidget.isRendered()).toBeFalsy(); - html.render(aWidget); + html.render(aWidget); - expect(aWidget.isRendered()).toBeTruthy(); + expect(aWidget.isRendered()).toBeTruthy(); + }); }); }); From 82c9a000f1f57077fbdfbbff4ea28dcd85d098fe Mon Sep 17 00:00:00 2001 From: Damien Cassou Date: Wed, 29 Jan 2025 16:46:04 +0100 Subject: [PATCH 3/5] 40698660: Split a test in 2 --- src/test/widgetTest.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/test/widgetTest.js b/src/test/widgetTest.js index 6531f531..682c2158 100644 --- a/src/test/widgetTest.js +++ b/src/test/widgetTest.js @@ -217,9 +217,9 @@ describe("function", () => { describe("isRendered()", () => { it("returns false before render", () => { - expect.assertions(2); + expect.assertions(1); - withCanvas((html) => { + withCanvas(() => { const aWidget = (function () { const that = widgetSubclass(); @@ -231,6 +231,24 @@ describe("function", () => { })(); expect(aWidget.isRendered()).toBeFalsy(); + }); + }); + + it("returns true after render", () => { + // Make sure the function passed to `withCanvas()` is + // called: + expect.assertions(1); + + withCanvas((html) => { + const aWidget = (function () { + const that = widgetSubclass(); + + that.renderContentOn = function (html) { + html.div("div").addClass("aDiv"); + }; + + return that; + })(); html.render(aWidget); From 0317d1cecedf0f3bcff5a5b65d7ead3a6f469edb Mon Sep 17 00:00:00 2001 From: Damien Cassou Date: Wed, 29 Jan 2025 16:35:32 +0100 Subject: [PATCH 4/5] 40698660: Add initial tests to Widget2.isRendered() --- src/test/Widget2Test.js | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/test/Widget2Test.js diff --git a/src/test/Widget2Test.js b/src/test/Widget2Test.js new file mode 100644 index 00000000..681cdb7a --- /dev/null +++ b/src/test/Widget2Test.js @@ -0,0 +1,43 @@ +import { beforeEach, describe, expect, it } from "vitest"; +import htmlCanvas from "../htmlCanvas.js"; +import jQuery from "jquery"; +import Widget2 from "../Widget2.js"; + +describe("Widget", () => { + beforeEach(() => { + document.body.replaceChildren(); + }); + + describe("isRendered()", () => { + it("returns false if the widget isn't attached to the DOM", () => { + const widget = makeWidget(); + + expect(widget.isRendered()).toBeFalsy(); + }); + + it("returns true if the widget is attached to the DOM", () => { + const widget = makeWidget(); + + makeHtml().render(widget); + + expect(widget.isRendered()).toBeTruthy(); + }); + }); +}); + +function makeWidget() { + class Foo extends Widget2 { + renderContentOn(html) { + html.p("content"); + } + } + + return new Foo(); +} + +function makeHtml() { + jQuery("BODY").append('
'); + const sandbox = jQuery("#sandbox"); + + return htmlCanvas(sandbox); +} From 5a2ad837124bf782f6c571820ef8afc10659591b Mon Sep 17 00:00:00 2001 From: Damien Cassou Date: Wed, 29 Jan 2025 16:36:32 +0100 Subject: [PATCH 5/5] 40698660: Make Widget.asJQuery() find nodes in specially-crafted shadow trees In the previous version, widget.asJQuery() would only return something useful if the widget is directly attached to the document. If the widget is within a shadow tree, asJQuery() wouldn't work. This commit makes asJQuery() work even if widget is within a shadow tree if and only if: - this tree is attached to a host node with widgetjs-shadow=document, - and there is only one such node in the DOM This limitation on the host avoids impacting too much the performance. --- src/Widget2.js | 15 ++++++++++++++- src/test/Widget2Test.js | 15 +++++++++++++++ src/test/widgetTest.js | 27 +++++++++++++++++++++++++++ src/widget.js | 15 ++++++++++++++- 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/Widget2.js b/src/Widget2.js index 600fa2e9..3ce13a3a 100644 --- a/src/Widget2.js +++ b/src/Widget2.js @@ -160,7 +160,20 @@ export default class Widget2 { * See "renderOn". */ asJQuery() { - return jQuery(`#${this.getId()}`); + const elementQuery = `#${this.getId()}`; + const element = jQuery(elementQuery); + + if (element.length !== 0) return element; + + const documentShadowRoot = jQuery(`[widgetjs-shadow="document"]`); + + if ( + documentShadowRoot.length !== 1 || + !documentShadowRoot[0].shadowRoot + ) + return element; + + return jQuery(elementQuery, documentShadowRoot[0].shadowRoot); } /** diff --git a/src/test/Widget2Test.js b/src/test/Widget2Test.js index 681cdb7a..fff8a841 100644 --- a/src/test/Widget2Test.js +++ b/src/test/Widget2Test.js @@ -22,6 +22,21 @@ describe("Widget", () => { expect(widget.isRendered()).toBeTruthy(); }); + + it("returns true if the widget is attached to the DOM under a special shadow host", () => { + const host = document.createElement("div"); + host.setAttribute("widgetjs-shadow", "document"); + + document.body.appendChild(host); + + const shadowRoot = host.attachShadow({ mode: "open" }); + + const widget = makeWidget(); + + htmlCanvas(jQuery(shadowRoot)).render(widget); + + expect(widget.isRendered()).toBeTruthy(); + }); }); }); diff --git a/src/test/widgetTest.js b/src/test/widgetTest.js index 682c2158..4c38ef23 100644 --- a/src/test/widgetTest.js +++ b/src/test/widgetTest.js @@ -255,6 +255,33 @@ describe("function", () => { expect(aWidget.isRendered()).toBeTruthy(); }); }); + + it("returns true if the widget is attached to the DOM under a special shadow host", () => { + // Make sure the function passed to `withCanvas()` is + // called: + expect.assertions(1); + + const aWidget = (function () { + const that = widgetSubclass(); + + that.renderContentOn = function (html) { + html.div("div"); + }; + + return that; + })(); + + const host = document.createElement("div"); + host.setAttribute("widgetjs-shadow", "document"); + + document.body.appendChild(host); + + const shadowRoot = host.attachShadow({ mode: "open" }); + + htmlCanvas(jQuery(shadowRoot)).render(aWidget); + + expect(aWidget.isRendered()).toBeTruthy(); + }); }); it("renderRoot() can be overridden in widget", () => { diff --git a/src/widget.js b/src/widget.js index 6938b8fe..ec1d8bc5 100644 --- a/src/widget.js +++ b/src/widget.js @@ -159,7 +159,20 @@ const widget = object.subclass((that, my) => { * @returns {*} */ that.asJQuery = function () { - return jQuery(`#${that.getId()}`); + const elementQuery = `#${that.getId()}`; + const element = jQuery(elementQuery); + + if (element.length !== 0) return element; + + const documentShadowRoot = jQuery(`[widgetjs-shadow="document"]`); + + if ( + documentShadowRoot.length !== 1 || + !documentShadowRoot[0].shadowRoot + ) + return element; + + return jQuery(elementQuery, documentShadowRoot[0].shadowRoot); }; /**