mirror of https://github.com/mastodon/mastodon
feat: Add Storybook for component documentation, testing, and development (#34907)
Co-authored-by: Echo <ChaosExAnima@users.noreply.github.com> Co-authored-by: Renaud Chaput <renchap@gmail.com>pull/34959/head
parent
989ca63b59
commit
f2cfa4f482
@ -0,0 +1,40 @@
|
|||||||
|
name: 'Chromatic'
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- renovate/*
|
||||||
|
- stable-*
|
||||||
|
paths:
|
||||||
|
- 'package.json'
|
||||||
|
- 'yarn.lock'
|
||||||
|
- '**/*.js'
|
||||||
|
- '**/*.jsx'
|
||||||
|
- '**/*.ts'
|
||||||
|
- '**/*.tsx'
|
||||||
|
- '**/*.css'
|
||||||
|
- '**/*.scss'
|
||||||
|
- '.github/workflows/chromatic.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
chromatic:
|
||||||
|
name: Run Chromatic
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set up Javascript environment
|
||||||
|
uses: ./.github/actions/setup-javascript
|
||||||
|
|
||||||
|
- name: Build Storybook
|
||||||
|
run: yarn build-storybook
|
||||||
|
|
||||||
|
- name: Run Chromatic
|
||||||
|
uses: chromaui/action@v12
|
||||||
|
with:
|
||||||
|
# ⚠️ Make sure to configure a `CHROMATIC_PROJECT_TOKEN` repository secret
|
||||||
|
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||||
|
zip: true
|
||||||
|
storybookBuildDir: 'storybook-static'
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import type { StorybookConfig } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ['../app/javascript/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||||
|
addons: [
|
||||||
|
'@storybook/addon-docs',
|
||||||
|
'@storybook/addon-a11y',
|
||||||
|
'@storybook/addon-vitest',
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/react-vite',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { addons } from 'storybook/manager-api';
|
||||||
|
|
||||||
|
import theme from './storybook-theme';
|
||||||
|
|
||||||
|
addons.setConfig({
|
||||||
|
theme,
|
||||||
|
});
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
<style>
|
||||||
|
/* Increase docs font size */
|
||||||
|
.sbdocs.sbdocs-content :where(p:not(.sb-anchor, .sb-unstyled, .sb-unstyled p)),
|
||||||
|
.sbdocs.sbdocs-content :where(li:not(.sb-anchor, .sb-unstyled, .sb-unstyled li)) {
|
||||||
|
font-size: 1.0666rem; /* 17px */
|
||||||
|
line-height: 1.585; /* 27px */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sbdocs.sbdocs-content :where(p:not(.sb-anchor, .sb-unstyled, .sb-unstyled p)) code,
|
||||||
|
.sbdocs.sbdocs-content :where(li:not(.sb-anchor, .sb-unstyled, .sb-unstyled li)) code {
|
||||||
|
font-size: 0.875rem; /* ~15px */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bring numbers back for ordered lists */
|
||||||
|
ol {
|
||||||
|
list-style: revert !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
import type { Preview } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
// If you want to run the dark theme during development,
|
||||||
|
// you can change the below to `/application.scss`
|
||||||
|
import '../app/javascript/styles/mastodon-light.scss';
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
// Auto-generate docs: https://storybook.js.org/docs/writing-docs/autodocs
|
||||||
|
tags: ['autodocs'],
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
a11y: {
|
||||||
|
// 'todo' - show a11y violations in the test UI only
|
||||||
|
// 'error' - fail CI on a11y violations
|
||||||
|
// 'off' - skip a11y checks entirely
|
||||||
|
test: 'todo',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
// The addon package.json incorrectly exports types, so we need to override them here.
|
||||||
|
// See: https://github.com/storybookjs/storybook/blob/v9.0.4/code/addons/vitest/package.json#L70-L76
|
||||||
|
declare module '@storybook/addon-vitest/vitest-plugin' {
|
||||||
|
export * from '@storybook/addon-vitest/dist/vitest-plugin/index';
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { create } from 'storybook/theming';
|
||||||
|
|
||||||
|
export default create({
|
||||||
|
base: 'light',
|
||||||
|
brandTitle: 'Mastodon Storybook',
|
||||||
|
brandImage: 'https://joinmastodon.org/logos/wordmark-black-text.svg',
|
||||||
|
});
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import * as a11yAddonAnnotations from '@storybook/addon-a11y/preview';
|
||||||
|
import { setProjectAnnotations } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
import * as projectAnnotations from './preview';
|
||||||
|
|
||||||
|
// This is an important step to apply the right configuration when testing your stories.
|
||||||
|
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
|
||||||
|
setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
import { fn, expect } from 'storybook/test';
|
||||||
|
|
||||||
|
import { Button } from '.';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Button',
|
||||||
|
component: Button,
|
||||||
|
args: {
|
||||||
|
secondary: false,
|
||||||
|
compact: false,
|
||||||
|
dangerous: false,
|
||||||
|
disabled: false,
|
||||||
|
onClick: fn(),
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
text: {
|
||||||
|
control: 'text',
|
||||||
|
type: 'string',
|
||||||
|
description:
|
||||||
|
'Alternative way of specifying the button label. Will override `children` if provided.',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
control: 'text',
|
||||||
|
table: {
|
||||||
|
type: { summary: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: ['test'],
|
||||||
|
} satisfies Meta<typeof Button>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
const buttonTest: Story['play'] = async ({ args, canvas, userEvent }) => {
|
||||||
|
await userEvent.click(canvas.getByRole('button'));
|
||||||
|
await expect(args.onClick).toHaveBeenCalled();
|
||||||
|
};
|
||||||
|
|
||||||
|
const disabledButtonTest: Story['play'] = async ({
|
||||||
|
args,
|
||||||
|
canvas,
|
||||||
|
userEvent,
|
||||||
|
}) => {
|
||||||
|
await userEvent.click(canvas.getByRole('button'));
|
||||||
|
await expect(args.onClick).not.toHaveBeenCalled();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Primary: Story = {
|
||||||
|
args: {
|
||||||
|
children: 'Primary button',
|
||||||
|
},
|
||||||
|
play: buttonTest,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Secondary: Story = {
|
||||||
|
args: {
|
||||||
|
secondary: true,
|
||||||
|
children: 'Secondary button',
|
||||||
|
},
|
||||||
|
play: buttonTest,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Compact: Story = {
|
||||||
|
args: {
|
||||||
|
compact: true,
|
||||||
|
children: 'Compact button',
|
||||||
|
},
|
||||||
|
play: buttonTest,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Dangerous: Story = {
|
||||||
|
args: {
|
||||||
|
dangerous: true,
|
||||||
|
children: 'Dangerous button',
|
||||||
|
},
|
||||||
|
play: buttonTest,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PrimaryDisabled: Story = {
|
||||||
|
args: {
|
||||||
|
...Primary.args,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
play: disabledButtonTest,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SecondaryDisabled: Story = {
|
||||||
|
args: {
|
||||||
|
...Secondary.args,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
play: disabledButtonTest,
|
||||||
|
};
|
||||||
@ -1,26 +1,69 @@
|
|||||||
import { configDefaults, defineConfig } from 'vitest/config';
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
|
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import svgr from 'vite-plugin-svgr';
|
||||||
|
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||||
|
import {
|
||||||
|
configDefaults,
|
||||||
|
defineConfig,
|
||||||
|
TestProjectInlineConfiguration,
|
||||||
|
} from 'vitest/config';
|
||||||
|
|
||||||
import { config as viteConfig } from './vite.config.mjs';
|
import { config as viteConfig } from './vite.config.mjs';
|
||||||
|
|
||||||
|
const storybookTests: TestProjectInlineConfiguration = {
|
||||||
|
plugins: [
|
||||||
|
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
|
||||||
|
storybookTest({
|
||||||
|
configDir: '.storybook',
|
||||||
|
storybookScript: 'yarn run storybook',
|
||||||
|
}),
|
||||||
|
react(),
|
||||||
|
svgr(),
|
||||||
|
tsconfigPaths(),
|
||||||
|
],
|
||||||
|
test: {
|
||||||
|
name: 'storybook',
|
||||||
|
browser: {
|
||||||
|
enabled: true,
|
||||||
|
headless: true,
|
||||||
|
provider: 'playwright',
|
||||||
|
instances: [{ browser: 'chromium' }],
|
||||||
|
},
|
||||||
|
setupFiles: [resolve(__dirname, '.storybook/vitest.setup.ts')],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const legacyTests: TestProjectInlineConfiguration = {
|
||||||
|
extends: true,
|
||||||
|
test: {
|
||||||
|
name: 'legacy-tests',
|
||||||
|
environment: 'jsdom',
|
||||||
|
include: [
|
||||||
|
...configDefaults.include,
|
||||||
|
'**/__tests__/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
|
||||||
|
],
|
||||||
|
exclude: [
|
||||||
|
...configDefaults.exclude,
|
||||||
|
'**/node_modules/**',
|
||||||
|
'vendor/**',
|
||||||
|
'config/**',
|
||||||
|
'log/**',
|
||||||
|
'public/**',
|
||||||
|
'tmp/**',
|
||||||
|
],
|
||||||
|
globals: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default defineConfig(async (context) => {
|
export default defineConfig(async (context) => {
|
||||||
|
const baseConfig = await viteConfig(context);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...(await viteConfig(context)),
|
...baseConfig,
|
||||||
test: {
|
test: {
|
||||||
environment: 'jsdom',
|
projects: [legacyTests, storybookTests],
|
||||||
include: [
|
|
||||||
...configDefaults.include,
|
|
||||||
'**/__tests__/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
|
|
||||||
],
|
|
||||||
exclude: [
|
|
||||||
...configDefaults.exclude,
|
|
||||||
'**/node_modules/**',
|
|
||||||
'vendor/**',
|
|
||||||
'config/**',
|
|
||||||
'log/**',
|
|
||||||
'public/**',
|
|
||||||
'tmp/**',
|
|
||||||
],
|
|
||||||
globals: true,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="@vitest/browser/providers/playwright" />
|
||||||
Loading…
Reference in New Issue