From 38d0b6216715feb3c3d8e78d9256b5f63ccf5500 Mon Sep 17 00:00:00 2001 From: "Acid Chicken (硫酸鶏)" Date: Tue, 4 Apr 2023 09:38:34 +0900 Subject: build(#10336): Storybook & Chromatic & msw (#10365) * build(#10336): init * fix(#10336): invalid name conversion * build(#10336): load locales and vite config * refactor(#10336): remove unused imports * build(#10336): separate definitions and generated codes * refactor(#10336): remove hatches * refactor(#10336): module semantics * refactor(#10336): remove unused common preferences * fix: typo * build(#10336): mock assets * build(#10336): impl `SatisfiesExpression` * build(#10336): control themes * refactor(#10336): semantics * build(#10336): make .storybook as an individual TypeScript project * style(#10336): use single quote * build(#10336): avoid intrinsic component names * chore: suppress linter * style: typing * build(#10336): update dependencies * docs: note about Storybook * build(#10336): sync * build(#10336): full reload server on change * chore: use defaultStore instead * build(#10336): show popups on Story * refactor(#10336): remove redundant div * docs: fix * build(#10336): interactions * build(#10336): add an interaction test for `` * build(#10336): bump storybook * docs(#10336): mention to pre-build misskey-js * build(#10336): write stories for `MkAcct` * build(#10336): write stories for `MkAd` * build(#10336): fix missing type definition * build(#10336): use `toHaveTextContent` * build(#10336): write some stories * build(#10336): hide internal args * build(#10336): generate `components/global` stories only * build(#10336): write stories for `MkMisskeyFlavoredMarkdown` * fix: conflict errors * build(#10336): subcomponents on sidebar * refactor: restore `SatisfiesExpression` * docs(#10336): note development status * build(#10336): use chokidar-cli * docs(#10336): note chokidar-cli mode * chore(#10336): untrack generated stories files * fix: pointer handling * build(#10336): finalize * chore: add static option to `MkLoading` * refactor(#10336): bind to local args * fix: missing case * revert: restore `SatisfiesExpression` This reverts commit f246699f38a28befbfccc11e9eade22cbaace4f3. * build(#10336): make storybook buildable * build(#10336): staticify assets * build(#10336): staticified directory structure * build(#10336): normalize path for Windows * ci(#10336): create actions * build(#10336): ignore tsc errors * build(#10336): ignore tsc errors * build(#10336): missing dependencies * build(#10336): missing dependencies * build(#10336): use fast-glob * fix: invalid lockfile * ci(#10336): increase heap size * build(#10336): use unpkg for storybook tabler icons * build(#10336): use unpkg for storybook twemojis * build(#10336): disable `ProfilePageCat` * build(#10336): blur `MkA` before interaction ends * ci(#10336): stabilize * ci(#10336): fetch-depth * build(#10336): isChromatic * ci(#10336): notify on changes * ci(#10336): fix typo * ci(#10336): missing working directory * ci(#10336): skip build * ci(#10336): fix path * build(#10336): fails on Windows * build(#10336): available on Windows * ci(#10336): disable animation on chromatic * ci(#10336): add static option to `PageHeader.tabs` * chore: void * ci(#10336): change parameters * docs(#10336): update CONTRIBUTING * docs(#10336): note about meta overriding and etc. * ci(#10336): use Chromatic for checks * ci(#10336): use `pull_request` instead of `pull_request_target` for now * ci(#10336): use `exitOnceUploaded` * ci(#10336): reuse built storybook * ci(#10336): back to `pull_request_target` * chore: unused dependencies * style(#10336): reduce prettier indents * style: note about `TSSatisfiesExpression` --- .../src/components/global/MkA.stories.impl.ts | 47 ++++ .../src/components/global/MkAcct.stories.impl.ts | 43 +++ packages/frontend/src/components/global/MkAcct.vue | 1 - .../src/components/global/MkAd.stories.impl.ts | 120 ++++++++ packages/frontend/src/components/global/MkAd.vue | 2 +- .../src/components/global/MkAvatar.stories.impl.ts | 66 +++++ .../frontend/src/components/global/MkAvatar.vue | 1 + .../global/MkCustomEmoji.stories.impl.ts | 45 +++ .../components/global/MkEllipsis.stories.impl.ts | 32 +++ .../frontend/src/components/global/MkEllipsis.vue | 16 +- .../src/components/global/MkEmoji.stories.impl.ts | 31 ++ .../src/components/global/MkError.stories.meta.ts | 5 + .../components/global/MkLoading.stories.impl.ts | 60 ++++ .../frontend/src/components/global/MkLoading.vue | 8 +- .../MkMisskeyFlavoredMarkdown.stories.impl.ts | 74 +++++ .../components/global/MkPageHeader.stories.impl.ts | 98 +++++++ .../global/MkPageHeader.tabs.stories.impl.ts | 3 + .../src/components/global/MkPageHeader.tabs.vue | 18 +- .../global/MkStickyContainer.stories.impl.ts | 3 + .../src/components/global/MkTime.stories.impl.ts | 312 +++++++++++++++++++++ packages/frontend/src/components/global/MkTime.vue | 6 +- .../src/components/global/MkUrl.stories.impl.ts | 77 +++++ .../components/global/MkUserName.stories.impl.ts | 57 ++++ .../components/global/RouterView.stories.impl.ts | 3 + 24 files changed, 1114 insertions(+), 14 deletions(-) create mode 100644 packages/frontend/src/components/global/MkA.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkAcct.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkAd.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkAvatar.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkEllipsis.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkEmoji.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkError.stories.meta.ts create mode 100644 packages/frontend/src/components/global/MkLoading.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkPageHeader.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkPageHeader.tabs.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkStickyContainer.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkTime.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkUrl.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkUserName.stories.impl.ts create mode 100644 packages/frontend/src/components/global/RouterView.stories.impl.ts (limited to 'packages/frontend/src/components/global') diff --git a/packages/frontend/src/components/global/MkA.stories.impl.ts b/packages/frontend/src/components/global/MkA.stories.impl.ts new file mode 100644 index 0000000000..72d069e853 --- /dev/null +++ b/packages/frontend/src/components/global/MkA.stories.impl.ts @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { expect } from '@storybook/jest'; +import { userEvent, within } from '@storybook/testing-library'; +import { StoryObj } from '@storybook/vue3'; +import MkA from './MkA.vue'; +import { tick } from '@/scripts/test-utils'; +export const Default = { + render(args) { + return { + components: { + MkA, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: 'Text', + }; + }, + async play({ canvasElement }) { + const canvas = within(canvasElement); + const a = canvas.getByRole('link'); + await expect(a.href).toMatch(/^https?:\/\/.*#test$/); + await userEvent.click(a, { button: 2 }); + await tick(); + const menu = canvas.getByRole('menu'); + await expect(menu).toBeInTheDocument(); + await userEvent.click(a, { button: 0 }); + a.blur(); + await tick(); + await expect(menu).not.toBeInTheDocument(); + }, + args: { + to: '#test', + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/global/MkAcct.stories.impl.ts b/packages/frontend/src/components/global/MkAcct.stories.impl.ts new file mode 100644 index 0000000000..7dfa1a14f2 --- /dev/null +++ b/packages/frontend/src/components/global/MkAcct.stories.impl.ts @@ -0,0 +1,43 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { userDetailed } from '../../../.storybook/fakes'; +import MkAcct from './MkAcct.vue'; +export const Default = { + render(args) { + return { + components: { + MkAcct, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + user: { + ...userDetailed, + host: null, + }, + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj; +export const Detail = { + ...Default, + args: { + ...Default.args, + user: userDetailed, + detail: true, + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue index e06ab64e86..2b9f892fc6 100644 --- a/packages/frontend/src/components/global/MkAcct.vue +++ b/packages/frontend/src/components/global/MkAcct.vue @@ -18,4 +18,3 @@ defineProps<{ const host = toUnicode(hostRaw); - diff --git a/packages/frontend/src/components/global/MkAd.stories.impl.ts b/packages/frontend/src/components/global/MkAd.stories.impl.ts new file mode 100644 index 0000000000..7d8a42a03c --- /dev/null +++ b/packages/frontend/src/components/global/MkAd.stories.impl.ts @@ -0,0 +1,120 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { expect } from '@storybook/jest'; +import { userEvent, within } from '@storybook/testing-library'; +import { StoryObj } from '@storybook/vue3'; +import { i18n } from '@/i18n'; +import MkAd from './MkAd.vue'; +const common = { + render(args) { + return { + components: { + MkAd, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + async play({ canvasElement, args }) { + const canvas = within(canvasElement); + const a = canvas.getByRole('link'); + await expect(a.href).toMatch(/^https?:\/\/.*#test$/); + const img = within(a).getByRole('img'); + await expect(img).toBeInTheDocument(); + let buttons = canvas.getAllByRole('button'); + await expect(buttons).toHaveLength(1); + const i = buttons[0]; + await expect(i).toBeInTheDocument(); + await userEvent.click(i); + await expect(a).not.toBeInTheDocument(); + await expect(i).not.toBeInTheDocument(); + buttons = canvas.getAllByRole('button'); + await expect(buttons).toHaveLength(args.__hasReduce ? 2 : 1); + const reduce = args.__hasReduce ? buttons[0] : null; + const back = buttons[args.__hasReduce ? 1 : 0]; + if (reduce) { + await expect(reduce).toBeInTheDocument(); + await expect(reduce).toHaveTextContent(i18n.ts._ad.reduceFrequencyOfThisAd); + } + await expect(back).toBeInTheDocument(); + await expect(back).toHaveTextContent(i18n.ts._ad.back); + await userEvent.click(back); + if (reduce) { + await expect(reduce).not.toBeInTheDocument(); + } + await expect(back).not.toBeInTheDocument(); + const aAgain = canvas.getByRole('link'); + await expect(aAgain).toBeInTheDocument(); + const imgAgain = within(aAgain).getByRole('img'); + await expect(imgAgain).toBeInTheDocument(); + }, + args: { + prefer: [], + specify: { + id: 'someadid', + radio: 1, + url: '#test', + }, + __hasReduce: true, + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj; +export const Square = { + ...common, + args: { + ...common.args, + specify: { + ...common.args.specify, + place: 'square', + imageUrl: + 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true', + }, + }, +} satisfies StoryObj; +export const Horizontal = { + ...common, + args: { + ...common.args, + specify: { + ...common.args.specify, + place: 'horizontal', + imageUrl: + 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true', + }, + }, +} satisfies StoryObj; +export const HorizontalBig = { + ...common, + args: { + ...common.args, + specify: { + ...common.args.specify, + place: 'horizontal-big', + imageUrl: + 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true', + }, + }, +} satisfies StoryObj; +export const ZeroRatio = { + ...Square, + args: { + ...Square.args, + specify: { + ...Square.args.specify, + ratio: 0, + }, + __hasReduce: false, + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue index b8f749bd1c..5799f99d5f 100644 --- a/packages/frontend/src/components/global/MkAd.vue +++ b/packages/frontend/src/components/global/MkAd.vue @@ -20,13 +20,13 @@ + diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts new file mode 100644 index 0000000000..f6811b6747 --- /dev/null +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts @@ -0,0 +1,74 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.vue'; +import { within } from '@storybook/testing-library'; +import { expect } from '@storybook/jest'; +export const Default = { + render(args) { + return { + components: { + MkMisskeyFlavoredMarkdown, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + async play({ canvasElement, args }) { + const canvas = within(canvasElement); + if (args.plain) { + const aiHelloMiskist = canvas.getByText('@ai *Hello*, #Miskist!'); + await expect(aiHelloMiskist).toBeInTheDocument(); + } else { + const ai = canvas.getByText('@ai'); + await expect(ai).toBeInTheDocument(); + await expect(ai.closest('a')).toHaveAttribute('href', '/@ai'); + const hello = canvas.getByText('Hello'); + await expect(hello).toBeInTheDocument(); + await expect(hello.style.fontStyle).toBe('oblique'); + const miskist = canvas.getByText('#Miskist'); + await expect(miskist).toBeInTheDocument(); + await expect(miskist).toHaveAttribute('href', args.isNote ?? true ? '/tags/Miskist' : '/user-tags/Miskist'); + } + const heart = canvas.getByAltText('❤'); + await expect(heart).toBeInTheDocument(); + await expect(heart).toHaveAttribute('src', '/twemoji/2764.svg'); + }, + args: { + text: '@ai *Hello*, #Miskist! ❤', + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj; +export const Plain = { + ...Default, + args: { + ...Default.args, + plain: true, + }, +} satisfies StoryObj; +export const Nowrap = { + ...Default, + args: { + ...Default.args, + nowrap: true, + }, +} satisfies StoryObj; +export const IsNotNote = { + ...Default, + args: { + ...Default.args, + isNote: false, + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts new file mode 100644 index 0000000000..5519d60fc4 --- /dev/null +++ b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts @@ -0,0 +1,98 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import MkPageHeader from './MkPageHeader.vue'; +export const Empty = { + render(args) { + return { + components: { + MkPageHeader, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + static: true, + tabs: [], + }, + parameters: { + layout: 'centered', + chromatic: { + /* This component has animations that are implemented with JavaScript. So it's unstable to take a snapshot. */ + disableSnapshot: true, + }, + }, +} satisfies StoryObj; +export const OneTab = { + ...Empty, + args: { + ...Empty.args, + tab: 'sometabkey', + tabs: [ + { + key: 'sometabkey', + title: 'Some Tab Title', + }, + ], + }, +} satisfies StoryObj; +export const Icon = { + ...OneTab, + args: { + ...OneTab.args, + tabs: [ + { + ...OneTab.args.tabs[0], + icon: 'ti ti-home', + }, + ], + }, +} satisfies StoryObj; +export const IconOnly = { + ...Icon, + args: { + ...Icon.args, + tabs: [ + { + ...Icon.args.tabs[0], + title: undefined, + iconOnly: true, + }, + ], + }, +} satisfies StoryObj; +export const SomeTabs = { + ...Empty, + args: { + ...Empty.args, + tab: 'princess', + tabs: [ + { + key: 'princess', + title: 'Princess', + icon: 'ti ti-crown', + }, + { + key: 'fairy', + title: 'Fairy', + icon: 'ti ti-snowflake', + }, + { + key: 'angel', + title: 'Angel', + icon: 'ti ti-feather', + }, + ], + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.stories.impl.ts b/packages/frontend/src/components/global/MkPageHeader.tabs.stories.impl.ts new file mode 100644 index 0000000000..6d4460d593 --- /dev/null +++ b/packages/frontend/src/components/global/MkPageHeader.tabs.stories.impl.ts @@ -0,0 +1,3 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import MkPageHeader_tabs from './MkPageHeader.tabs.vue'; +void MkPageHeader_tabs; diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue index 42760da08f..9e1da64e61 100644 --- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue +++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue @@ -33,14 +33,18 @@