Skip to content

Commit

Permalink
test: render with events utility refactoring (#554)
Browse files Browse the repository at this point in the history
# Motivation

Using an `$on` callback for testing is deprecated in Svelte v5 (PR
#548). Instead, we will now use the `events` option of `render`, which
itself will eventually be replaced by the library. To minimize changes
in the Svelte v5 PR, we refactored the test to use a utility function
that handles rendering and event binding.

# Changes

- Introduce and use `renderWithEvents` utilitiy.

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
peterpeterparker and github-actions[bot] authored Dec 17, 2024
1 parent 78b8864 commit e4ada9a
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 74 deletions.
11 changes: 6 additions & 5 deletions src/tests/lib/components/Back.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import Back from "$lib/components/Back.svelte";
import { fireEvent, render } from "@testing-library/svelte";
import { fireEvent } from "@testing-library/svelte";
import { render } from "../../utils/render.test-utils";

describe("Back", () => {
it("should forward the click event", () =>
new Promise<void>((done) => {
const { getByTestId, component } = render(Back);

component.$on("nnsBack", () => {
done();
const { getByTestId } = render(Back, {
events: {
nnsBack: () => done(),
},
});

const button = getByTestId("back") as HTMLButtonElement;
Expand Down
11 changes: 6 additions & 5 deletions src/tests/lib/components/Card.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Card from "$lib/components/Card.svelte";
import { fireEvent, render } from "@testing-library/svelte";
import { fireEvent } from "@testing-library/svelte";
import { render } from "../../utils/render.test-utils";

describe("Card", () => {
it("should render an article", () => {
Expand Down Expand Up @@ -34,10 +35,10 @@ describe("Card", () => {

it("should forward the click event", () =>
new Promise<void>((done) => {
const { container, component } = render(Card);

component.$on("click", () => {
done();
const { container } = render(Card, {
events: {
click: () => done(),
},
});

const article = container.querySelector("article");
Expand Down
45 changes: 26 additions & 19 deletions src/tests/lib/components/Checkbox.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Checkbox from "$lib/components/Checkbox.svelte";
import { fireEvent, render } from "@testing-library/svelte";
import { fireEvent } from "@testing-library/svelte";
import { render } from "../../utils/render.test-utils";

describe("Checkbox", () => {
const props: { inputId: string; checked: boolean } = {
Expand Down Expand Up @@ -63,12 +64,11 @@ describe("Checkbox", () => {

it("should trigger select on container", () =>
new Promise<void>((done) => {
const { container, component } = render(Checkbox, {
const { container } = render(Checkbox, {
props,
});

component.$on("nnsChange", () => {
done();
events: {
nnsChange: () => done(),
},
});

const div: HTMLDivElement | null =
Expand All @@ -79,12 +79,11 @@ describe("Checkbox", () => {

it("should trigger select on input", () =>
new Promise<void>((done) => {
const { container, component } = render(Checkbox, {
const { container } = render(Checkbox, {
props,
});

component.$on("nnsChange", () => {
done();
events: {
nnsChange: () => done(),
},
});

const input: HTMLInputElement | null = container.querySelector("input");
Expand Down Expand Up @@ -114,25 +113,33 @@ describe("Checkbox", () => {

it("should not trigger nnsChange event when disabled and clicked", async () => {
const mockChange = vi.fn();
const { container, component } = render(Checkbox, {
props: { ...props, disabled: true },
const { container } = render(Checkbox, {
props: {
...props,
disabled: true,
},
events: {
nnsChange: mockChange,
},
});

component.$on("nnsChange", mockChange);

await fireEvent.click(container.querySelector("div.checkbox") as Element);

expect(mockChange).not.toHaveBeenCalled();
});

it("should not trigger nnsChange event when disabled and key pressed", async () => {
const mockChange = vi.fn();
const { container, component } = render(Checkbox, {
props: { ...props, disabled: true },
const { container } = render(Checkbox, {
props: {
...props,
disabled: true,
},
events: {
nnsChange: mockChange,
},
});

component.$on("nnsChange", mockChange);

await fireEvent.keyPress(
container.querySelector("div.checkbox") as Element,
{
Expand Down
35 changes: 21 additions & 14 deletions src/tests/lib/components/Collapsible.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fireEvent } from "@testing-library/dom";
import { render } from "@testing-library/svelte";
import { tick } from "svelte";
import { render } from "../../utils/render.test-utils";
import CollapsibleTest from "./CollapsibleTest.svelte";

// props
Expand Down Expand Up @@ -111,13 +111,14 @@ describe("Collapsible", () => {
});

it("should not toggle if external toggle", async () => {
const { getByTestId, container, component } = render(
CollapsibleTest,
props({ externalToggle: true }),
);

const spyToggle = vi.fn();
component.$on("nnsToggle", spyToggle);

const { getByTestId, container } = render(CollapsibleTest, {
...props({ externalToggle: true }),
events: {
nnsToggle: spyToggle,
},
});

fireEvent.click(getByTestId("collapsible-header"));
await tick();
Expand All @@ -128,20 +129,26 @@ describe("Collapsible", () => {
expect(spyToggle).not.toHaveBeenCalled();
});

it("should emit state update", async () => {
const { getByTestId, component } = render(CollapsibleTest);
await new Promise((resolve) => {
it("should emit state update", () =>
new Promise<void>((done) => {
let callIndex = 0;

component.$on("nnsToggle", ({ detail }) => {
const onToggle = ({ detail }: CustomEvent<{ expanded: boolean }>) => {
expect(detail.expanded).toBe(callIndex++ % 2 === 0);
if (callIndex >= 4) resolve(undefined);
if (callIndex >= 4) {
done();
}
};

const { getByTestId } = render(CollapsibleTest, {
events: {
nnsToggle: onToggle,
},
});

fireEvent.click(getByTestId("collapsible-header"));
fireEvent.click(getByTestId("collapsible-header"));
fireEvent.click(getByTestId("collapsible-header"));
fireEvent.click(getByTestId("collapsible-header"));
});
});
}));
});
53 changes: 27 additions & 26 deletions src/tests/lib/components/Modal.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { startBusy } from "$lib";
import Modal from "$lib/components/Modal.svelte";
import { fireEvent, render } from "@testing-library/svelte";
import { fireEvent } from "@testing-library/svelte";
import { render } from "../../utils/render.test-utils";
import ModalTest from "./ModalTest.svelte";

describe("Modal", () => {
Expand Down Expand Up @@ -109,12 +110,11 @@ describe("Modal", () => {

it("should trigger close modal on click on backdrop", () =>
new Promise<void>((done) => {
const { container, component } = render(Modal, {
const { container } = render(Modal, {
props,
});

component.$on("nnsClose", () => {
done();
events: {
nnsClose: () => done(),
},
});

const backdrop: HTMLDivElement | null =
Expand All @@ -124,41 +124,42 @@ describe("Modal", () => {

it("should trigger close modal on Esc", () =>
new Promise<void>((done) => {
const { container, component } = render(Modal, {
const { container } = render(Modal, {
props,
});

component.$on("nnsClose", () => {
done();
events: {
nnsClose: () => done(),
},
});

fireEvent.keyDown(container, { key: "Escape" });
}));

it("should not close modal on not Esc keypress", () => {
const { container, component } = render(Modal, {
const { container } = render(Modal, {
props,
});

component.$on("nnsClose", () => {
throw new Error("Should not close modal");
events: {
nnsClose: () => {
throw new Error("Should not close modal");
},
},
});

fireEvent.keyDown(container, { key: "Enter" });
fireEvent.keyDown(container, { key: "Backspace" });
});

it("should not close modal on Esc when busy = true", () => {
const { container, component } = render(Modal, {
const { container } = render(Modal, {
props,
events: {
nnsClose: () => {
throw new Error("Should not close modal");
},
},
});

startBusy({ initiator: "stake-neuron" });

component.$on("nnsClose", () => {
throw new Error("Should not close modal");
});

fireEvent.keyDown(container, { key: "Escape" });
});

Expand Down Expand Up @@ -191,12 +192,12 @@ describe("Modal", () => {

it("should trigger close modal on click on close button", () =>
new Promise<void>((done) => {
const { getByTestId, component } = render(ModalTest, {
const { getByTestId } = render(ModalTest, {
props,
});

component.$on("nnsClose", () => {
done();
// TODO: remove once events is migrated to props
events: {
nnsClose: () => done(),
},
});

const button: HTMLElement | null = getByTestId("close-modal");
Expand Down
15 changes: 10 additions & 5 deletions src/tests/lib/components/Toggle.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Toggle from "$lib/components/Toggle.svelte";
import { fireEvent, render } from "@testing-library/svelte";
import { fireEvent } from "@testing-library/svelte";
import { render } from "../../utils/render.test-utils";

describe("Toggle", () => {
const props = {
Expand Down Expand Up @@ -49,12 +50,16 @@ describe("Toggle", () => {
});

it("should toggle checked", () => {
const { component, container } = render(Toggle, { props });
const onToggle = vi.fn();

const input = container.querySelector("input") as HTMLInputElement;
const { container } = render(Toggle, {
props,
events: {
nnsToggle: onToggle,
},
});

const onToggle = vi.fn();
component.$on("nnsToggle", onToggle);
const input = container.querySelector("input") as HTMLInputElement;

fireEvent.click(input);

Expand Down
23 changes: 23 additions & 0 deletions src/tests/utils/render.test-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
render as svelteRender,
type RenderResult,
} from "@testing-library/svelte";
import type { ComponentProps, ComponentType, SvelteComponent } from "svelte";

export const render = <C extends SvelteComponent>(
cmp: ComponentType<C>,
options?: {
props?: ComponentProps<C>;
events?: Record<string, ($event: CustomEvent) => void>;
},
): RenderResult<C> => {
const { component, ...rest } = svelteRender(cmp, { props: options?.props });

const events = Object.entries(options?.events ?? {});

events.forEach(([event, fn]) => {
component.$on(event, fn);
});

return { component, ...rest };
};

0 comments on commit e4ada9a

Please sign in to comment.