Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(testing): support to test web components #198

Merged
merged 11 commits into from
May 15, 2024
6 changes: 4 additions & 2 deletions packages/brisa/src/__fixtures__/lib/foo.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export default function Foo() {
return <div>Foo</div>;
import type { WebContext } from "brisa";

export default function Foo({}, { i18n }: WebContext) {
return <div>Foo {i18n.t("hello-world")}</div>;
}
16 changes: 16 additions & 0 deletions packages/brisa/src/__fixtures__/web-components/custom-counter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { WebContext } from "brisa";

export default function Counter(
{ initialValue = 0 }: { initialValue: number },
{ state }: WebContext,
) {
const count = state(initialValue);

return (
<div>
<button onClick={() => count.value++}>+</button>
{count.value}
<button onClick={() => count.value--}>-</button>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { WebContext } from "brisa";

export default function Counter({ children }: { children: JSX.Element }) {
return <div id="children-container">{children}</div>;
}
312 changes: 299 additions & 13 deletions packages/brisa/src/core/test/api/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import path from "node:path";
import { debug, render, serveRoute, waitFor, userEvent } from "@/core/test/api";
import {
debug,
render,
serveRoute,
waitFor,
userEvent,
cleanup,
} from "@/core/test/api";
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import {
describe,
Expand All @@ -13,6 +20,7 @@ import {
} from "bun:test";
import { getConstants } from "@/constants";
import { blueLog, cyanLog, greenLog } from "@/utils/log/log-color";
import type { RequestContext } from "brisa";

const BUILD_DIR = path.join(import.meta.dir, "..", "..", "..", "__fixtures__");
const PAGES_DIR = path.join(BUILD_DIR, "pages");
Expand All @@ -26,6 +34,7 @@ describe("test api", () => {
afterEach(() => {
jest.restoreAllMocks();
GlobalRegistrator.unregister();
globalThis.mockConstants = undefined;
});
describe("render", () => {
it("should render the element", async () => {
Expand Down Expand Up @@ -74,7 +83,7 @@ describe("test api", () => {
return <div>Foo</div>;
}
const parent = document.createElement("div");
const { container } = await render(<Foo />, parent);
const { container } = await render(<Foo />, { baseElement: parent });
expect(parent.contains(container)).toBeTrue();
});

Expand Down Expand Up @@ -134,19 +143,256 @@ describe("test api", () => {
expect(mockLog).toHaveBeenCalledWith("foo");
});

it.todo("should render a web component", async () => {});
it("should render a web component", async () => {
globalThis.mockConstants = {
...(getConstants() ?? {}),
SRC_DIR: BUILD_DIR,
BUILD_DIR,
};
// Register DOM and web components from __fixtures__/web-components
const runWebComponents = await import(
"@/core/test/run-web-components"
).then((m) => m.default);
await runWebComponents();

// @ts-ignore
const { container } = await render(<custom-counter />);
const customCounter =
container.querySelector("custom-counter")!.shadowRoot!;

expect(customCounter).toContainTextContent("0");
});

it.todo("should render a web component with props", async () => {
globalThis.mockConstants = {
...(getConstants() ?? {}),
SRC_DIR: BUILD_DIR,
BUILD_DIR,
};
// Register DOM and web components from __fixtures__/web-components
const runWebComponents = await import(
"@/core/test/run-web-components"
).then((m) => m.default);
await runWebComponents();

// @ts-ignore
const { container } = await render(<custom-counter initialValue={5} />);
const customCounter =
container.querySelector("custom-counter")?.shadowRoot!;

expect(customCounter.innerHTML).toBe("5");
});

it("should render a web component with slots", async () => {
globalThis.mockConstants = {
...(getConstants() ?? {}),
SRC_DIR: BUILD_DIR,
BUILD_DIR,
};
// Register DOM and web components from __fixtures__/web-components
const runWebComponents = await import(
"@/core/test/run-web-components"
).then((m) => m.default);
await runWebComponents();

const { container } = await render(
// @ts-ignore
<custom-slot>
<div slot="header">Header</div>
<div slot="footer">Footer</div>
{/* @ts-ignore */}
</custom-slot>,
);
const customSlot = container.querySelector("custom-slot")?.shadowRoot!;

expect(customSlot).toHaveElementByNodeName("slot");
expect(container).toContainTextContent("Header");
expect(container).toContainTextContent("Footer");
});

it("should be possible to interact with a web component", async () => {
globalThis.mockConstants = {
...(getConstants() ?? {}),
SRC_DIR: BUILD_DIR,
BUILD_DIR,
};
// Register DOM and web components from __fixtures__/web-components
const runWebComponents = await import(
"@/core/test/run-web-components"
).then((m) => m.default);
await runWebComponents();

// @ts-ignore
const { container } = await render(<custom-counter />);
const customCounter =
container.querySelector("custom-counter")!.shadowRoot!;
const [increment, decrement] = customCounter.querySelectorAll("button");

expect(customCounter).toContainTextContent("0");

userEvent.click(increment);

expect(customCounter).toContainTextContent("1");

userEvent.click(increment);

expect(customCounter).toContainTextContent("2");

userEvent.click(decrement);

expect(customCounter).toContainTextContent("1");

userEvent.click(decrement);

expect(customCounter).toContainTextContent("0");
});

it("should be possible to render a server component with a web component inside", async () => {
globalThis.mockConstants = {
...(getConstants() ?? {}),
SRC_DIR: BUILD_DIR,
BUILD_DIR,
};
// Register DOM and web components from __fixtures__/web-components
const runWebComponents = await import(
"@/core/test/run-web-components"
).then((m) => m.default);
await runWebComponents();

function ServerComponent() {
return (
<div>
{/* @ts-ignore */}
<custom-counter />
</div>
);
}

// @ts-ignore
const { container } = await render(<ServerComponent />);
const customCounter =
container.querySelector("custom-counter")?.shadowRoot!;
const [increment] = customCounter.querySelectorAll("button");

expect(customCounter).toContainTextContent("0");
userEvent.click(increment);
expect(customCounter).toContainTextContent("1");
});

it("should render server component using i18n", async () => {
globalThis.mockConstants = {
...(getConstants() ?? {}),
SRC_DIR: BUILD_DIR,
BUILD_DIR,
I18N_CONFIG: {
defaultLocale: "en",
locales: ["en", "es"],
messages: {
en: {
"hello-world": "Hello World",
},
},
},
};

function ServerComponent({}, { i18n }: RequestContext) {
return (
<div>
<span>{i18n.t("hello-world")}</span>
</div>
);
}

// @ts-ignore
const { container } = await render(<ServerComponent />, { locale: "en" });
const span = container.querySelector("span")!;

expect(span.innerHTML).toBe("Hello World");
});

it("should be possible to use overrideMessages inside a server component", async () => {
globalThis.mockConstants = {
...(getConstants() ?? {}),
SRC_DIR: BUILD_DIR,
BUILD_DIR,
I18N_CONFIG: {
defaultLocale: "en",
locales: ["en", "es"],
messages: {
en: {
"hello-world": "Hello World",
},
},
},
};

function ServerComponent({}, { i18n }: RequestContext) {
i18n.overrideMessages(() => ({
hello: "Hi {{name}}",
}));

return (
<div>
<span>{i18n.t("hello", { name: "Foo" })}</span>
</div>
);
}

// @ts-ignore
const { container } = await render(<ServerComponent />, { locale: "en" });
const span = container.querySelector("span")!;

expect(span.innerHTML).toBe("Hi Foo");
});

it("should render the web component foo-component using i18n", async () => {
globalThis.mockConstants = {
...(getConstants() ?? {}),
SRC_DIR: BUILD_DIR,
BUILD_DIR,
I18N_CONFIG: {
defaultLocale: "en",
locales: ["en", "es"],
messages: {
en: {
"hello-world": "Hello World",
},
},
},
};
// Register DOM and web components from __fixtures__/web-components
const runWebComponents = await import(
"@/core/test/run-web-components"
).then((m) => m.default);
await runWebComponents();

// @ts-ignore
const { container } = await render(<foo-component />, { locale: "en" });
const fooComponent =
container.querySelector("foo-component")?.shadowRoot!;

it.todo("should render a web component with props", async () => {});
expect(fooComponent).toContainTextContent("Foo Hello World");
});
});

it.todo(
"should be possible to interact with a web component",
async () => {},
);
describe("cleanup", () => {
it("should cleanup the registed actions", () => {
globalThis.REGISTERED_ACTIONS = [() => {}];
cleanup();
expect(globalThis.REGISTERED_ACTIONS).toBeEmpty();
});

it.todo(
"should be possible to render a server component with a web component inside",
async () => {},
);
it("should cleanup the document body", async () => {
document.body.innerHTML = "<div>Foo</div>";
cleanup();
expect(document.body.innerHTML).toBeEmpty();
});

it("should cleanup the document head", async () => {
document.head.innerHTML = "<title>Foo</title>";
cleanup();
expect(document.head.innerHTML).toBeEmpty();
});
});

describe("serveRoute", () => {
Expand Down Expand Up @@ -246,7 +492,7 @@ describe("test api", () => {
"\n " +
blueLog("<div") +
blueLog(">") +
"\n " +
"\n " +
"Foo\n " +
blueLog("</div>") +
"\n " +
Expand All @@ -270,6 +516,46 @@ describe("test api", () => {
greenLog('"test"'),
);
});

it("should be possible to log a shadow root", () => {
const mockLog = spyOn(console, "log");
const shadowRoot = document
.createElement("div")
.attachShadow({ mode: "open" });
shadowRoot.innerHTML = "<div>Foo</div>";
debug(shadowRoot);
expect(mockLog.mock.calls[0][0]).toBe(
blueLog("<div") + blueLog(">") + "\n " + "Foo\n" + blueLog("</div>"),
);
});

it("should be possible to log a document fragment", () => {
const mockLog = spyOn(console, "log");
const fragment = document.createDocumentFragment();
const div = document.createElement("div");
div.innerHTML = "Foo<div>Bar</div>";
fragment.appendChild(div);
debug(fragment);
expect(mockLog.mock.calls[0][0]).toBe(
blueLog("<div") +
blueLog(">") +
"\n " +
"Foo\n " +
blueLog("<div") +
blueLog(">") +
"\n " +
"Bar\n " +
blueLog("</div>") +
"\n" +
blueLog("</div>"),
);
});

it("should be possible to log null element an see an empty fragment", () => {
const mockLog = spyOn(console, "log");
debug(null);
expect(mockLog.mock.calls[0][0]).toBe(blueLog("<>\n</>"));
});
});

describe("userEvent", () => {
Expand Down
Loading
Loading