Skip to content

Commit

Permalink
Merge pull request #529 from foretagsplatsen/40698660/Make_asJquery_w…
Browse files Browse the repository at this point in the history
…ork_within_a_shadow_DOM

40698660: Make asJquery() work within a shadow DOM
  • Loading branch information
DamienCassou authored Jan 30, 2025
2 parents d9f9347 + 5a2ad83 commit 21ca184
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 44 deletions.
15 changes: 14 additions & 1 deletion src/Widget2.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
58 changes: 58 additions & 0 deletions src/test/Widget2Test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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();
});

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();
});
});
});

function makeWidget() {
class Foo extends Widget2 {
renderContentOn(html) {
html.p("content");
}
}

return new Foo();
}

function makeHtml() {
jQuery("BODY").append('<div id="sandbox"></div>');
const sandbox = jQuery("#sandbox");

return htmlCanvas(sandbox);
}
97 changes: 55 additions & 42 deletions src/test/widgetTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -34,8 +31,6 @@ function withCanvas(callback) {
sandbox.remove();
}

// actual tests

describe("function", () => {
it("widgets are assigned unique identifiers", () => {
expect.assertions(1000);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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");
Expand Down Expand Up @@ -161,7 +148,6 @@ describe("function", () => {
expect.assertions(1);

withCanvas((html) => {
// Arrange: a widget
const aWidget = (function () {
const that = widgetSubclass();

Expand All @@ -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());
});
});
Expand All @@ -190,7 +173,6 @@ describe("function", () => {
expect.assertions(2);

withCanvas((html) => {
// Arrange: a widget
const aWidget = (function () {
const that = widgetSubclass();

Expand All @@ -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());
});
Expand All @@ -220,7 +199,6 @@ describe("function", () => {
expect.assertions(1);

withCanvas((html) => {
// Arrange: a widget
const aWidget = (function () {
const that = widgetSubclass();

Expand All @@ -231,36 +209,77 @@ 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();
});
});

it("isRendered()", () => {
expect.assertions(2);
describe("isRendered()", () => {
it("returns false before render", () => {
expect.assertions(1);

withCanvas(() => {
const aWidget = (function () {
const that = widgetSubclass();

that.renderContentOn = function (html) {
html.div("div").addClass("aDiv");
};

return that;
})();

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);

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);

withCanvas((html) => {
// Arrange: a widget
const aWidget = (function () {
const that = widgetSubclass();

that.renderContentOn = function (html) {
html.div("div").addClass("aDiv");
html.div("div");
};

return that;
})();

// Assert: false before render
expect(!aWidget.isRendered()).toBeTruthy();
const host = document.createElement("div");
host.setAttribute("widgetjs-shadow", "document");

// Act: render widget
html.render(aWidget);
document.body.appendChild(host);

const shadowRoot = host.attachShadow({ mode: "open" });

htmlCanvas(jQuery(shadowRoot)).render(aWidget);

// Assert: true ehrn rendered
expect(aWidget.isRendered()).toBeTruthy();
});
});
Expand All @@ -269,8 +288,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);
Expand All @@ -282,10 +299,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(),
);
Expand Down Expand Up @@ -314,10 +329,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();
});
Expand Down
15 changes: 14 additions & 1 deletion src/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

/**
Expand Down

0 comments on commit 21ca184

Please sign in to comment.