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: export our own async version of fireEvent #3

Merged
merged 2 commits into from
Jul 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 52 additions & 35 deletions src/__tests__/render.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ test("renders interactive content in the document", async () => {
const { getByText } = await render(Counter);
expect(getByText(/Value: 0/)).toBeInTheDocument();

fireEvent.click(getByText("Increment"));
await fireEvent.click(getByText("Increment"));

await wait(() => expect(getByText("Value: 1")));
expect(getByText("Value: 1")).toBeInTheDocument();
});

test("renders interactive content in the document with Marko 3", async () => {
const { getByText } = await render(LegacyCounter);
expect(getByText(/Value: 0/)).toBeInTheDocument();

fireEvent.click(getByText("Increment"));
await fireEvent.click(getByText("Increment"));

await wait(() => expect(getByText("Value: 1")));
expect(getByText("Value: 1")).toBeInTheDocument();
});

test("can be rerendered with new input", async () => {
Expand Down Expand Up @@ -63,54 +63,56 @@ test("records user events", async () => {
const { getByText, emitted } = await render(Clickable);
const button = getByText("Click");

fireEvent.click(button);
fireEvent.click(button);
await fireEvent.click(button);
await fireEvent.click(button);

expect(emitted("unknown-event")).toHaveProperty("length", 0);

expect(emitted("button-click")).toMatchInlineSnapshot(`
Array [
Array [
Object {
"count": 0,
},
],
Array [
Object {
"count": 1,
},
],
]
`);

expect(emitted().map(({ type }) => type)).toMatchInlineSnapshot(`
Array [
"button-click",
"button-click",
Array [
Object {
"count": 0,
},
],
Array [
Object {
"count": 1,
},
],
]
`);

fireEvent.click(button);
expect(emitted().map(({ type }) => type)).toMatchInlineSnapshot(`
Array [
"button-click",
"button-click",
]
`);

await fireEvent.click(button);

expect(emitted("button-click")).toMatchInlineSnapshot(`
Array [
Array [
Object {
"count": 2,
},
],
]
`);
expect(emitted().map(({ type }) => type)).toMatchInlineSnapshot(`
Array [
"button-click",
Array [
Object {
"count": 2,
},
],
]
`);
expect(emitted().map(({ type }) => type)).toMatchInlineSnapshot(`
Array [
"button-click",
]
`);
});

test("errors when trying to record internal events", async () => {
const { emitted } = await render(Clickable);
expect(() => emitted("mount" as string)).toThrow(/internal events/);
expect(() => emitted("mount" as string)).toThrowErrorMatchingInlineSnapshot(
`"The emitted helper cannot be used to listen to internal events."`
);
});

test("cleanup removes content from the document", async () => {
Expand All @@ -126,3 +128,18 @@ test("can render into a different container", async () => {
const { getByText } = await render(HelloWorld, null, { container });
expect(getByText("Hello World")).toHaveProperty("parentNode", container);
});

test("fireEvent waits for pending updates", async () => {
const { getByText } = await render(Counter);
expect(getByText(/Value: 0/)).toBeInTheDocument();

await fireEvent(
getByText("Increment"),
new MouseEvent("click", {
bubbles: true,
cancelable: true
})
);

expect(getByText("Value: 1")).toBeInTheDocument();
});
16 changes: 12 additions & 4 deletions src/__tests__/render.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,25 @@ test("renders static content from a Marko 3 component", async () => {

test("fails when rerendering", async () => {
const { rerender } = await render(HelloName, { name: "Michael" });
await expect(rerender({ name: "Dylan" })).rejects.toThrow(/cannot re-render/);
await expect(
rerender({ name: "Dylan" })
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Components cannot re-render on the server side"`
);
});

test("fails when checking emitted events", async () => {
const { emitted } = await render(Clickable);
expect(() => emitted("button-click")).toThrow(/should not emit events/);
expect(() => emitted("button-click")).toThrowErrorMatchingInlineSnapshot(
`"Components should not emit events on the server side"`
);
});

test("fails when emitting events", async () => {
const { getByText } = await render(Counter);
expect(() => fireEvent.click(getByText("Increment"))).toThrow(
/fireEvent currently supports/
await expect(
fireEvent.click(getByText("Increment"))
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Unable to find the \\"window\\" object for the given node. fireEvent currently supports firing events on DOM nodes, document, and window. Please file an issue with the code that's causing you to see this error: https://github.com/testing-library/dom-testing-library/issues/new"`
);
});
12 changes: 6 additions & 6 deletions src/index-browser.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { within, prettyDOM } from "@testing-library/dom";
import {
AsyncReturnValue,
RenderOptions,
Template,
EventRecord,
InternalEventNames,
INTERNAL_EVENTS
} from "./types";
} from "./shared";

const mountedComponents = new Set();

export * from "@testing-library/dom";
export { FireFunction, FireObject, fireEvent } from "./shared";

const mountedComponents = new Set();
export type RenderResult = AsyncReturnValue<typeof render>;

export async function render<T extends Template>(
template: T,
Expand Down Expand Up @@ -114,10 +118,6 @@ export function cleanup() {
mountedComponents.forEach(destroyComponent);
}

export type RenderResult = Parameters<
NonNullable<Parameters<ReturnType<typeof render>["then"]>[0]>
>[0];

function destroyComponent(component) {
const { instance, container, isDefaultContainer } = component;

Expand Down
14 changes: 7 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { JSDOM } from "jsdom";
import { within, prettyDOM } from "@testing-library/dom";
import {
AsyncReturnValue,
RenderOptions,
Template,
EventRecord,
InternalEventNames
} from "./types";
} from "./shared";

export * from "@testing-library/dom";
export { FireFunction, FireObject, fireEvent } from "./shared";

export type RenderResult = AsyncReturnValue<typeof render>;

export async function render<T extends Template>(
template: T,
Expand All @@ -31,11 +35,11 @@ export async function render<T extends Template>(
emitted<N extends string = "*">(
type?: N extends InternalEventNames ? never : N
): NonNullable<EventRecord[N]> {
throw new Error("Component's should not emit events on the server side");
throw new Error("Components should not emit events on the server side");
},
rerender(newInput?: typeof input): Promise<void> {
return Promise.reject(
new Error("Component's cannot re-render on the server side")
new Error("Components cannot re-render on the server side")
);
},
// eslint-disable-next-line no-console
Expand All @@ -56,7 +60,3 @@ export async function render<T extends Template>(

/* istanbul ignore next: There is no cleanup for SSR. */
export function cleanup() {}

export type RenderResult = Parameters<
NonNullable<Parameters<ReturnType<typeof render>["then"]>[0]>
>[0];
68 changes: 68 additions & 0 deletions src/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
fireEvent as originalFireEvent,
wait,
EventType,
FireFunction as originalFireFunction,
FireObject as originalFireObject
} from "@testing-library/dom";

export interface EventRecord {
"*"?: Array<{ type: string; args: unknown[] }>;
[x: string]: unknown[] | undefined;
}

export interface RenderOptions {
container?: Element | DocumentFragment;
}

export interface Template {
renderToString(
input: unknown,
cb: (err: Error | null, result: any) => void
): any;
render(input: unknown, cb: (err: Error | null, result: any) => void): any;
}

export const INTERNAL_EVENTS = [
"create",
"input",
"render",
"mount",
"update",
"destroy"
] as const;

export type InternalEventNames = typeof INTERNAL_EVENTS[number];

export type FireFunction = (
...params: Parameters<originalFireFunction>
) => Promise<void>;

export type FireObject = {
[K in EventType]: (
...params: Parameters<originalFireObject[keyof originalFireObject]>
) => Promise<void>;
};

export const fireEvent = (async (...params) => {
originalFireEvent(...params);
await wait();
}) as FireFunction & FireObject;

Object.keys(originalFireEvent).forEach((eventName: EventType) => {
const fire = originalFireEvent[eventName];
fireEvent[eventName] = async (...params) => {
fire(...params);

// TODO: this waits for a possible update using setTimeout(0) which should
// be sufficient, but ideally we would hook into the Marko lifecycle to
// determine when all pending updates are complete.
await wait();
};
});

export type AsyncReturnValue<
AsyncFunction extends (...args: any) => Promise<any>
> = Parameters<
NonNullable<Parameters<ReturnType<AsyncFunction>["then"]>[0]>
>[0];
27 changes: 0 additions & 27 deletions src/types.ts

This file was deleted.