Skip to content

Commit

Permalink
improve examples
Browse files Browse the repository at this point in the history
  • Loading branch information
yannbf committed Nov 22, 2024
1 parent 75c2c5f commit 1619bdf
Show file tree
Hide file tree
Showing 15 changed files with 328 additions and 236 deletions.
4 changes: 1 addition & 3 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ const config: StorybookConfig = {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
docs: {},
}
export default config
Binary file modified .yarn/install-state.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
28 changes: 20 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@
"storybook": "storybook dev -p 6006",
"test-storybook": "vitest --project=storybook"
},
"imports": {
"#*": [
"./src/*",
"./src/*.ts",
"./src/*.tsx"
],
"#utils/date": {
"storybook": "./src/utils/date.mock.ts",
"default": "./src/utils/date.ts"
}
},
"dependencies": {
"@reduxjs/toolkit": "^2.0.1",
"react": "^18.2.0",
Expand All @@ -30,13 +41,13 @@
},
"devDependencies": {
"@chromatic-com/storybook": "^3.2.2",
"@storybook/addon-a11y": "^8.4.0",
"@storybook/addon-essentials": "^8.4.0",
"@storybook/blocks": "^8.4.0",
"@storybook/experimental-addon-test": "^8.4.4",
"@storybook/react": "^8.4.0",
"@storybook/react-vite": "^8.4.0",
"@storybook/test": "^8.4.0",
"@storybook/addon-a11y": "^8.5.0-alpha.9",
"@storybook/addon-essentials": "^8.5.0-alpha.9",
"@storybook/blocks": "^8.5.0-alpha.9",
"@storybook/experimental-addon-test": "^8.5.0-alpha.9",
"@storybook/react": "^8.5.0-alpha.9",
"@storybook/react-vite": "^8.5.0-alpha.9",
"@storybook/test": "^8.5.0-alpha.9",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",
Expand All @@ -48,11 +59,12 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"eslint-plugin-storybook": "^0.8.0",
"mockdate": "^3.0.5",
"msw": "^2.3.0",
"msw-storybook-addon": "^2.0.3",
"playwright": "^1.49.0",
"prop-types": "^15.8.1",
"storybook": "^8.4.0",
"storybook": "^8.5.0-alpha.9",
"ts-migrate": "^0.1.35",
"typescript": "^5.6.3",
"vite": "^5.2.0",
Expand Down
28 changes: 24 additions & 4 deletions src/components/InboxScreen.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { Meta, StoryObj } from '@storybook/react'
import { HttpResponse, http, delay } from 'msw'
import { Provider } from 'react-redux'
import MockDate from 'mockdate'

import store from '../lib/store'
import InboxScreen from './InboxScreen'
import { MockedState } from './TaskList.stories'
import * as mocks from '../mocks/data'

import {
userEvent,
waitFor,
within,
waitForElementToBeRemoved,
} from '@storybook/test'
import { getFormattedDate } from '#utils/date.mock.ts'

const meta = {
component: InboxScreen,
Expand All @@ -29,22 +31,26 @@ export const Default: Story = {
http.get(
'https://jsonplaceholder.typicode.com/todos',
() => {
return HttpResponse.json(MockedState.tasks)
return HttpResponse.json(mocks.tasks)
}
),
],
},
},
}

export const PinnedTasks: Story = {
...Default,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
// Waits for the component to transition from the loading state
await waitForElementToBeRemoved(await canvas.findByTestId('loading'))
// Waits for the component to be updated based on the store
await waitFor(async () => {
// Simulates pinning the first task
await userEvent.click(canvas.getByLabelText('pinTask-1'))
await userEvent.click(canvas.getByLabelText('Pin Learn more about Storybook'))
// Simulates pinning the third task
await userEvent.click(canvas.getByLabelText('pinTask-3'))
await userEvent.click(canvas.getByLabelText('Pin Schedule annual health check-up'))
})
},
}
Expand Down Expand Up @@ -72,3 +78,17 @@ export const Loading: Story = {
},
},
}

export const MockedDateWithModuleMocking: Story = {
...Default,
beforeEach: async () => {
getFormattedDate.mockReturnValue('August 23, 1993')
}
}

export const MockedDateWithDateMocking: Story = {
...Default,
beforeEach: async () => {
MockDate.set('2000-01-01T12:24:02Z')
}
}
7 changes: 5 additions & 2 deletions src/components/InboxScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useEffect } from "react";
import { useEffect, useMemo } from "react";

import { useDispatch, useSelector } from "../lib/hooks";
import { fetchTasks } from "../lib/store";
import { selectTaskbox } from "../lib/selectors";
import TaskList from "./TaskList";
import { getFormattedDate } from "#utils/date";

export default function InboxScreen() {
const dispatch = useDispatch();
Expand All @@ -14,6 +15,8 @@ export default function InboxScreen() {
dispatch(fetchTasks());
}, []);

const today = useMemo(() => getFormattedDate(new Date()), []);

if (error) {
return (
<div className="page lists-show">
Expand All @@ -28,7 +31,7 @@ export default function InboxScreen() {
return (
<div className="page lists-show">
<nav>
<h1 className="title-page">Taskbox</h1>
<h1 className="title-page">Taskbox - {today}</h1>
</nav>
<TaskList />
</div>
Expand Down
25 changes: 16 additions & 9 deletions src/components/Task.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Meta, StoryObj } from '@storybook/react'
import { fn } from '@storybook/test';
import { expect, fn, userEvent } from '@storybook/test';
import * as mocks from '../mocks/data'

import Task from "./Task";

Expand All @@ -18,11 +19,7 @@ type Story = StoryObj<typeof meta>

export const Default: Story = {
args: {
task: {
id: "1",
title: "Test Task",
state: "TASK_INBOX",
},
task: mocks.task,
},
};

Expand All @@ -44,13 +41,23 @@ export const Archived: Story = {
},
};

const longTitleString = `This task's name is absurdly large. In fact, I think if I keep going I might end up with content overflow. What will happen? The star that represents a pinned task could have text overlapping. The text could cut-off abruptly when it reaches the star. I hope not!`;

export const LongTitle: Story = {
args: {
task: {
...Default.args.task,
title: longTitleString,
title: `This task's name is absurdly large. In fact, I think if I keep going I might end up with content overflow. What will happen? The star that represents a pinned task could have text overlapping. The text could cut-off abruptly when it reaches the star. I hope not!`,
},
},
};

export const Test: Story = {
...Default,
play: async({ canvas, args }) => {
await userEvent.click(canvas.getByLabelText('Pin Learn more about Storybook'))
await expect(args.onPinTask).toHaveBeenCalledWith(mocks.task.id)

await userEvent.click(canvas.getByLabelText('Archive Learn more about Storybook'))
await expect(args.onArchiveTask).toHaveBeenCalledWith(mocks.task.id)
},
tags: ['!autodocs']
}
20 changes: 7 additions & 13 deletions src/components/Task.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ export default function TaskComponent({
onPinTask,
}: TaskProps) {
return (
<div className={`list-item ${state}`}>
<li className={`list-item ${state}`}>
<label
htmlFor="checked"
aria-label={`archiveTask-${id}`}
aria-label={`Archive ${title}`}
className="checkbox"
onClick={() => onArchiveTask(id)}
>
<input
type="checkbox"
Expand All @@ -25,31 +26,24 @@ export default function TaskComponent({
id={`archiveTask-${id}`}
checked={state === "TASK_ARCHIVED"}
/>
<span className="checkbox-custom" onClick={() => onArchiveTask(id)} />
<span className="checkbox-custom"/>
</label>

<label htmlFor="title" aria-label={title} className="title">
<input
type="text"
value={title}
readOnly={true}
name="title"
placeholder="Input title"
style={{ textOverflow: "ellipsis" }}
/>
{title}
</label>

{state !== "TASK_ARCHIVED" && (
<button
className="pin-button"
onClick={() => onPinTask(id)}
id={`pinTask-${id}`}
aria-label={`pinTask-${id}`}
aria-label={`Pin ${title}`}
key={`pinTask-${id}`}
>
<span className={`icon-star`} />
</button>
)}
</div>
</li>
);
}
55 changes: 37 additions & 18 deletions src/components/TaskList.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import { Meta, StoryObj } from '@storybook/react'
import type { ReactElement } from 'react';
import type { Meta, StoryObj } from '@storybook/react'
import { Provider } from "react-redux";
import { configureStore, createSlice } from "@reduxjs/toolkit";

import TaskList from "./TaskList";
import * as TaskStories from "./Task.stories";
import { State } from '../lib/store';
import { ReactElement } from 'react';
import { Task } from '../types';
import * as mocks from "../mocks/data";
import { expect, userEvent } from '@storybook/test';

// A super-simple mock of the state of the store
export const MockedState: State = {
tasks: [
{ ...TaskStories.Default.args.task, id: "1", title: "Task 1" },
{ ...TaskStories.Default.args.task, id: "2", title: "Task 2" },
{ ...TaskStories.Default.args.task, id: "3", title: "Task 3" },
{ ...TaskStories.Default.args.task, id: "4", title: "Task 4" },
{ ...TaskStories.Default.args.task, id: "5", title: "Task 5" },
{ ...TaskStories.Default.args.task, id: "6", title: "Task 6" },
],
const defaultTaskboxState: State = {
tasks: mocks.tasks,
status: "idle",
error: null,
};
Expand Down Expand Up @@ -52,30 +46,29 @@ const meta = {
title: "TaskList",
tags: ["autodocs"],
decorators: [(story) => <div style={{ padding: "3rem" }}>{story()}</div>],
excludeStories: /.*MockedState$/,
} satisfies Meta<typeof TaskList>
export default meta;

type Story = StoryObj<typeof meta>

export const Default: Story = {
decorators: [
(story) => <Mockstore taskboxState={MockedState}>{story()}</Mockstore>,
(story) => <Mockstore taskboxState={defaultTaskboxState}>{story()}</Mockstore>,
],
};

export const WithPinnedTasks: Story = {
decorators: [
(story) => {
const pinnedtasks: Task[] = [
...MockedState.tasks.slice(0, 5),
...defaultTaskboxState.tasks.slice(0, 5),
{ id: "6", title: "Task 6 (pinned)", state: "TASK_PINNED" },
];

return (
<Mockstore
taskboxState={{
...MockedState,
...defaultTaskboxState,
tasks: pinnedtasks,
}}
>
Expand All @@ -91,7 +84,7 @@ export const Loading: Story = {
(story) => (
<Mockstore
taskboxState={{
...MockedState,
...defaultTaskboxState,
status: "loading",
}}
>
Expand All @@ -106,7 +99,7 @@ export const Empty: Story = {
(story) => (
<Mockstore
taskboxState={{
...MockedState,
...defaultTaskboxState,
tasks: [],
}}
>
Expand All @@ -115,3 +108,29 @@ export const Empty: Story = {
),
],
};

export const TestPinBehavior: Story = {
...Default,
play: async ({ canvas, step }) => {

await step('Ensure tasks are rendered in the initial order', async () => {
const listItems = canvas.getAllByRole("listitem");
await expect(listItems[0]).toHaveTextContent("Learn more about Storybook");
await expect(listItems[1]).toHaveTextContent("Go to the gym");
})

await step('Pin "Go to the gym" task', async () => {
// Pin Learn more about Storybook and verify it moves to the top
const pinButton = canvas.getByLabelText("Pin Go to the gym");
await userEvent.click(pinButton);
});

await step('Ensure tasks order is changed', async () => {
const updatedListItems = canvas.getAllByRole("listitem");
await expect(updatedListItems[0]).toHaveTextContent("Go to the gym");
await expect(updatedListItems[1]).toHaveTextContent("Learn more about Storybook");
});
},
// hide the story from autodocs page as it's intended for test purposes only
tags: ['!autodocs']
};
Loading

0 comments on commit 1619bdf

Please sign in to comment.