Skip to content

Commit

Permalink
Improve interaction test handling, test utils
Browse files Browse the repository at this point in the history
  • Loading branch information
hansottowirtz committed Dec 31, 2022
1 parent 6421bc2 commit 8850a55
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 45 deletions.
45 changes: 24 additions & 21 deletions src/stories/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ import { within, fireEvent } from "@storybook/testing-library";
import { expect } from "@storybook/jest";
import Button from "../components/Button";
import { ContextualStoryDecorator } from "./StoryWrapper";
import { createSpyableActions } from "./common";
import { createSpyableActions, delay, doneTrackerUtils } from "./common";
import { action } from "@storybook/addon-actions";

const { actions, actionsMockReset } = createSpyableActions();
const { actions, actionsMockClear: actionsMockReset } = createSpyableActions({
onDone: action("done"),
onAbort: action("abort"),
onError: action("error"),
onPending: action("pending"),
});

export default {
title: "Contextual API/Button",
Expand All @@ -20,51 +26,48 @@ export const Primary: Meta = {
export const InteractionTestNotPersisted: Meta = {
args: { children: "Click me", persistDone: false },
play: async ({ canvasElement }) => {
await Promise.resolve();
await delay(500);

const canvas = within(canvasElement);
const { status, refresh } = await doneTrackerUtils(canvas);
actionsMockReset();

const canvas = within(canvasElement);
const button = canvas.getByText("Click me", { selector: "button" });
fireEvent.click(button);
const text = canvas.getByTestId("root-state");
expect(text.innerText).toBe("pending");
expect(status()).toBe("pending");
await Promise.resolve();
// it is resolved in 1 microtask
expect(text.innerText).toBe("done");
const refreshButton = canvas.getByTestId("new-root-done-tracker");
fireEvent.click(refreshButton);
expect(status()).toBe("done");
refresh();
await Promise.resolve();
expect(text.innerText).toBe("pending");
expect(status()).toBe("pending");
fireEvent.click(button);
expect(text.innerText).toBe("pending");
expect(status()).toBe("pending");
await Promise.resolve();
expect(text.innerText).toBe("done");
expect(status()).toBe("done");
},
};

export const InteractionTestPersisted: Meta = {
args: { children: "Click me", persistDone: true },
play: async ({ canvasElement }) => {
await Promise.resolve();

actionsMockReset();
await delay(500);

const canvas = within(canvasElement);
const text = await canvas.findByTestId("root-state");
const refreshButton = await canvas.findByTestId("new-root-done-tracker");
const { status, refresh } = await doneTrackerUtils(canvas);
actionsMockReset();

const button = canvas.getByText("Click me", { selector: "button" });
fireEvent.click(button);
expect(text.innerText).toBe("pending");
expect(status()).toBe("pending");
await Promise.resolve();
// it is resolved in 1 microtask
expect(text.innerText).toBe("done");
expect(status()).toBe("done");
expect(actions.onDone).toBeCalledTimes(1);
actions.onDone.mockReset();
fireEvent.click(refreshButton);
refresh();
await Promise.resolve();
expect(text.innerText).toBe("done");
expect(status()).toBe("done");
expect(actions.onPending).not.toBeCalled();
expect(actions.onDone).toBeCalledTimes(1);
},
Expand Down
64 changes: 55 additions & 9 deletions src/stories/Image.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,76 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { action } from "@storybook/addon-actions";
import type { Meta } from "@storybook/react";
import { waitFor, within } from "@storybook/testing-library";
import { expect } from "@storybook/jest";
import Image from "../components/Image";
import { createSpyableActions, delay, doneTrackerUtils } from "./common";
import { ContextualStoryDecorator } from "./StoryWrapper";
import { action } from "@storybook/addon-actions";

const { actions, actionsMockClear } = createSpyableActions({
onDone: action("done"),
onAbort: action("abort"),
onError: action("error"),
onPending: action("pending"),
});

export default {
title: "Contextual API/Image",
component: Image,
decorators: [
ContextualStoryDecorator({
onDone: action("done"),
onAbort: action("abort"),
onError: action("error"),
onPending: action("pending"),
}),
ContextualStoryDecorator(actions),
],
} as Meta;

export const Primary = {
export const Primary: Meta = {
args: {
src: "https://picsum.photos/200/300",
}
};

export const InteractionTest: Meta = {
args: {
src: "https://picsum.photos/200/300",
},
play: async ({ canvasElement }) => {
actionsMockClear();

await delay(500);

const canvas = within(canvasElement);
const { status, refresh } = await doneTrackerUtils(canvas);

await waitFor(() => expect(status()).toBe("done"), { timeout: 1000 });
await delay(100);
expect(actions.onDone).toBeCalledTimes(1);
refresh();
await delay(100);
expect(status()).toBe("done");
expect(actions.onDone).toBeCalledTimes(2);
}
};

export const Error: Meta = {
args: {
src: "https://example.qwkeinasc",
}
};

export const Error = {
export const InteractionTestError: Meta = {
args: {
src: "https://example.qwkeinasc",
},
play: async ({ canvasElement }) => {
await delay(500);

const canvas = within(canvasElement);
const { status, refresh } = await doneTrackerUtils(canvas);
console.log('b');
actionsMockClear();

await waitFor(() => expect(status()).toBe("error"), { timeout: 1000 });
refresh();
await delay(100);
expect(status()).toBe("error");
}
};
37 changes: 22 additions & 15 deletions src/stories/common.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
import { action } from "@storybook/addon-actions";
import { action as actionFn } from "@storybook/addon-actions";
import { jest } from "@storybook/jest";
import { fireEvent } from "@storybook/testing-library";

export const actions = {
onDone: action("done"),
onAbort: action("abort"),
onError: action("error"),
onPending: action("pending"),
};

export function createSpyableActions() {
export function createSpyableActions<
A extends Record<string, ReturnType<typeof actionFn>>
>(actions: A) {
const spyableActions: Record<
keyof typeof actions,
keyof A,
jest.Mock<void, []>
> = Object.fromEntries(
Object.entries(actions).map(([k, v]) => {
const fn = jest.fn(v);
Object.entries(actions).map(([k, actionFn]) => {
const fn = jest.fn(actionFn);
// set function name for storybook interaction view
Object.defineProperty(fn, "name", { value: k });
return [k as any, fn] as const;
})
);

const actionsMockReset = () => {
Object.values(spyableActions).forEach((action) => action.mockReset());
const actionsMockClear = () => {
Object.values(spyableActions).forEach((action) => action.mockClear());
};

return { actions: spyableActions, actionsMockReset };
return { actions: spyableActions, actionsMockClear };
}

export async function doneTrackerUtils(canvas: any) {
const stateText = await canvas.findByTestId("root-state");
const refreshButton = await canvas.findByTestId("new-root-done-tracker");
return {
status: () => stateText.innerHTML,
refresh: () => fireEvent.click(refreshButton),
};
}

export const delay = (n: number) => new Promise((res) => setTimeout(res, n));

0 comments on commit 8850a55

Please sign in to comment.