diff options
| author | Acid Chicken (硫酸鶏) <root@acid-chicken.com> | 2023-04-13 12:20:39 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-13 12:20:39 +0900 |
| commit | 9bb6c536c0cdf6c5fa5e9bda7ceba36f007ef4eb (patch) | |
| tree | 43682137b867ebc3dddc3a12e3bb5f50098f4f56 /packages/frontend/src | |
| parent | [ci skip] improve readability (diff) | |
| download | sharkey-9bb6c536c0cdf6c5fa5e9bda7ceba36f007ef4eb.tar.gz sharkey-9bb6c536c0cdf6c5fa5e9bda7ceba36f007ef4eb.tar.bz2 sharkey-9bb6c536c0cdf6c5fa5e9bda7ceba36f007ef4eb.zip | |
test(#10336): add `components/Mk[A-B].*` stories (#10475)
* chore(#10336): register snippets
* test(#10336): add `components/Mk[A-B].*` stories
* build: desynced lockfile
* ci(#10336): preload assets
* ci(#10336): use pull_request
* build: update lockfile
* fix: reactivity transform
* chore: track upstream changes
* refactor: avoid temporary previous tapping declarations
* revert: avoid temporary previous tapping declarations
This reverts commit e649b1b1e6771bee674f2dfb044e0efd72d0be5d.
* test: flaky snapshots
* style: import
Diffstat (limited to 'packages/frontend/src')
14 files changed, 495 insertions, 9 deletions
diff --git a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts new file mode 100644 index 0000000000..7d27adeb04 --- /dev/null +++ b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts @@ -0,0 +1,49 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { action } from '@storybook/addon-actions'; +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { abuseUserReport } from '../../.storybook/fakes'; +import { commonHandlers } from '../../.storybook/mocks'; +import MkAbuseReport from './MkAbuseReport.vue'; +export const Default = { + render(args) { + return { + components: { + MkAbuseReport, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + events() { + return { + resolved: action('resolved'), + }; + }, + }, + template: '<MkAbuseReport v-bind="props" v-on="events" />', + }; + }, + args: { + report: abuseUserReport(), + }, + parameters: { + layout: 'fullscreen', + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/admin/resolve-abuse-user-report', async (req, res, ctx) => { + action('POST /api/admin/resolve-abuse-user-report')(await req.json()); + return res(ctx.json({})); + }), + ], + }, + }, +} satisfies StoryObj<typeof MkAbuseReport>; diff --git a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts new file mode 100644 index 0000000000..d0877ffd3b --- /dev/null +++ b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts @@ -0,0 +1,49 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { action } from '@storybook/addon-actions'; +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { userDetailed } from '../../.storybook/fakes'; +import { commonHandlers } from '../../.storybook/mocks'; +import MkAbuseReportWindow from './MkAbuseReportWindow.vue'; +export const Default = { + render(args) { + return { + components: { + MkAbuseReportWindow, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + events() { + return { + 'closed': action('closed'), + }; + }, + }, + template: '<MkAbuseReportWindow v-bind="props" v-on="events" />', + }; + }, + args: { + user: userDetailed(), + }, + parameters: { + layout: 'centered', + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users/report-abuse', async (req, res, ctx) => { + action('POST /api/users/report-abuse')(await req.json()); + return res(ctx.json({})); + }), + ], + }, + }, +} satisfies StoryObj<typeof MkAbuseReportWindow>; diff --git a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts new file mode 100644 index 0000000000..bed9d94311 --- /dev/null +++ b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts @@ -0,0 +1,33 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { userDetailed } from '../../.storybook/fakes'; +import MkAccountMoved from './MkAccountMoved.vue'; +export const Default = { + render(args) { + return { + components: { + MkAccountMoved, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkAccountMoved v-bind="props" />', + }; + }, + args: { + username: userDetailed().username, + host: userDetailed().host, + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj<typeof MkAccountMoved>; diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue index fd472de6c1..98979de236 100644 --- a/packages/frontend/src/components/MkAccountMoved.vue +++ b/packages/frontend/src/components/MkAccountMoved.vue @@ -2,7 +2,7 @@ <div :class="$style.root"> <i class="ti ti-plane-departure" style="margin-right: 8px;"></i> {{ i18n.ts.accountMoved }} - <MkMention :class="$style.link" :username="acct" :host="host ?? localHost"/> + <MkMention :class="$style.link" :username="username" :host="host ?? localHost"/> </div> </template> @@ -12,7 +12,7 @@ import { i18n } from '@/i18n'; import { host as localHost } from '@/config'; defineProps<{ - acct: string; + username: string; host: string; }>(); </script> diff --git a/packages/frontend/src/components/MkAchievements.stories.impl.ts b/packages/frontend/src/components/MkAchievements.stories.impl.ts new file mode 100644 index 0000000000..477152a47b --- /dev/null +++ b/packages/frontend/src/components/MkAchievements.stories.impl.ts @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { userDetailed } from '../../.storybook/fakes'; +import { commonHandlers } from '../../.storybook/mocks'; +import MkAchievements from './MkAchievements.vue'; +import { ACHIEVEMENT_TYPES } from '@/scripts/achievements'; +export const Empty = { + render(args) { + return { + components: { + MkAchievements, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkAchievements v-bind="props" />', + }; + }, + args: { + user: userDetailed(), + }, + parameters: { + layout: 'fullscreen', + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users/achievements', (req, res, ctx) => { + return res(ctx.json([])); + }), + ], + }, + }, +} satisfies StoryObj<typeof MkAchievements>; +export const All = { + ...Empty, + parameters: { + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users/achievements', (req, res, ctx) => { + return res(ctx.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 })))); + }), + ], + }, + }, +} satisfies StoryObj<typeof MkAchievements>; diff --git a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts index 05190aa268..e7fbb47284 100644 --- a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts +++ b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import MkAnalogClock from './MkAnalogClock.vue'; +import isChromatic from 'chromatic'; export const Default = { render(args) { return { @@ -22,6 +23,14 @@ export const Default = { template: '<MkAnalogClock v-bind="props" />', }; }, + args: { + now: isChromatic() ? () => new Date('2023-01-01T10:10:30') : undefined, + }, + decorators: [ + () => ({ + template: '<div style="container-type:inline-size;height:100%"><div style="height:100cqmin;margin:auto;width:100cqmin"><story/></div></div>', + }), + ], parameters: { layout: 'fullscreen', }, diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue index 1218202616..f12020f810 100644 --- a/packages/frontend/src/components/MkAnalogClock.vue +++ b/packages/frontend/src/components/MkAnalogClock.vue @@ -99,6 +99,7 @@ const props = withDefaults(defineProps<{ graduations?: 'none' | 'dots' | 'numbers'; fadeGraduations?: boolean; sAnimation?: 'none' | 'elastic' | 'easeOut'; + now?: () => Date; }>(), { numbers: false, thickness: 0.1, @@ -107,6 +108,7 @@ const props = withDefaults(defineProps<{ graduations: 'dots', fadeGraduations: true, sAnimation: 'elastic', + now: () => new Date(), }); const graduationsMajor = computed(() => { @@ -145,11 +147,17 @@ let disableSAnimate = $ref(false); let sOneRound = false; function tick() { - const now = new Date(); - now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + props.offset)); + const now = props.now(); + now.setMinutes(now.getMinutes() + now.getTimezoneOffset() + props.offset); + const previousS = s; + const previousM = m; + const previousH = h; s = now.getSeconds(); m = now.getMinutes(); h = now.getHours(); + if (previousS === s && previousM === m && previousH === h) { + return; + } hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6); mAngle = Math.PI * (m + s / 60) / 30; if (sOneRound) { // 秒針が一周した際のアニメーションをよしなに処理する(これが無いと秒が59->0になったときに期待したアニメーションにならない) diff --git a/packages/frontend/src/components/MkAsUi.stories.impl.ts b/packages/frontend/src/components/MkAsUi.stories.impl.ts new file mode 100644 index 0000000000..b67c0e679d --- /dev/null +++ b/packages/frontend/src/components/MkAsUi.stories.impl.ts @@ -0,0 +1,2 @@ +import MkAsUi from './MkAsUi.vue'; +void MkAsUi; diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts new file mode 100644 index 0000000000..075904d6a3 --- /dev/null +++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts @@ -0,0 +1,176 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { action } from '@storybook/addon-actions'; +import { expect } from '@storybook/jest'; +import { userEvent, waitFor, within } from '@storybook/testing-library'; +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { userDetailed } from '../../.storybook/fakes'; +import { commonHandlers } from '../../.storybook/mocks'; +import MkAutocomplete from './MkAutocomplete.vue'; +import MkInput from './MkInput.vue'; +import { tick } from '@/scripts/test-utils'; +const common = { + render(args) { + return { + components: { + MkAutocomplete, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + events() { + return { + open: action('open'), + closed: action('closed'), + }; + }, + }, + template: '<MkAutocomplete v-bind="props" v-on="events" :textarea="textarea" />', + }; + }, + args: { + close: action('close'), + x: 0, + y: 0, + }, + decorators: [ + (_, context) => ({ + components: { + MkInput, + }, + data() { + return { + q: context.args.q, + textarea: null, + }; + }, + methods: { + inputMounted() { + this.textarea = this.$refs.input.$refs.inputEl; + }, + }, + template: '<MkInput v-model="q" ref="input" @vue:mounted="inputMounted"/><story v-if="textarea" :q="q" :textarea="textarea"/>', + }), + ], + parameters: { + controls: { + exclude: ['textarea'], + }, + layout: 'centered', + chromatic: { + // FIXME: flaky + disableSnapshot: true, + }, + }, +} satisfies StoryObj<typeof MkAutocomplete>; +export const User = { + ...common, + args: { + ...common.args, + type: 'user', + }, + async play({ canvasElement }) { + const canvas = within(canvasElement); + const input = canvas.getByRole('combobox'); + await waitFor(() => userEvent.hover(input)); + await waitFor(() => userEvent.click(input)); + await waitFor(() => userEvent.type(input, 'm')); + await waitFor(async () => { + await userEvent.type(input, ' ', { delay: 256 }); + await tick(); + return await expect(canvas.getByRole('list')).toBeInTheDocument(); + }, { timeout: 16384 }); + }, + parameters: { + ...common.parameters, + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users/search-by-username-and-host', (req, res, ctx) => { + return res(ctx.json([ + userDetailed('44', 'mizuki', 'misskey-hub.net', 'Mizuki'), + userDetailed('49', 'momoko', 'misskey-hub.net', 'Momoko'), + ])); + }), + ], + }, + }, +}; +export const Hashtag = { + ...common, + args: { + ...common.args, + type: 'hashtag', + }, + async play({ canvasElement }) { + const canvas = within(canvasElement); + const input = canvas.getByRole('combobox'); + await waitFor(() => userEvent.hover(input)); + await waitFor(() => userEvent.click(input)); + await waitFor(() => userEvent.type(input, '気象')); + await waitFor(async () => { + await userEvent.type(input, ' ', { delay: 256 }); + await tick(); + return await expect(canvas.getByRole('list')).toBeInTheDocument(); + }, { interval: 256, timeout: 16384 }); + }, + parameters: { + ...common.parameters, + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/hashtags/search', (req, res, ctx) => { + return res(ctx.json([ + '気象警報注意報', + '気象警報', + '気象情報', + ])); + }), + ], + }, + }, +}; +export const Emoji = { + ...common, + args: { + ...common.args, + type: 'emoji', + }, + async play({ canvasElement }) { + const canvas = within(canvasElement); + const input = canvas.getByRole('combobox'); + await waitFor(() => userEvent.hover(input)); + await waitFor(() => userEvent.click(input)); + await waitFor(() => userEvent.type(input, 'smile')); + await waitFor(async () => { + await userEvent.type(input, ' ', { delay: 256 }); + await tick(); + return await expect(canvas.getByRole('list')).toBeInTheDocument(); + }, { interval: 256, timeout: 16384 }); + }, +} satisfies StoryObj<typeof MkAutocomplete>; +export const MfmTag = { + ...common, + args: { + ...common.args, + type: 'mfmTag', + }, + async play({ canvasElement }) { + const canvas = within(canvasElement); + const input = canvas.getByRole('combobox'); + await waitFor(() => userEvent.hover(input)); + await waitFor(() => userEvent.click(input)); + await waitFor(async () => { + await tick(); + return await expect(canvas.getByRole('list')).toBeInTheDocument(); + }, { interval: 256, timeout: 16384 }); + }, +} satisfies StoryObj<typeof MkAutocomplete>; diff --git a/packages/frontend/src/components/MkAvatars.stories.impl.ts b/packages/frontend/src/components/MkAvatars.stories.impl.ts new file mode 100644 index 0000000000..14052c7343 --- /dev/null +++ b/packages/frontend/src/components/MkAvatars.stories.impl.ts @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { userDetailed } from '../../.storybook/fakes'; +import { commonHandlers } from '../../.storybook/mocks'; +import MkAvatars from './MkAvatars.vue'; +export const Default = { + render(args) { + return { + components: { + MkAvatars, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkAvatars v-bind="props" />', + }; + }, + args: { + userIds: ['17', '20', '18'], + }, + parameters: { + layout: 'centered', + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users/show', (req, res, ctx) => { + return res(ctx.json([ + userDetailed('17'), + userDetailed('20'), + userDetailed('18'), + ])); + }), + ], + }, + }, +} satisfies StoryObj<typeof MkAvatars>; diff --git a/packages/frontend/src/components/MkButton.stories.impl.ts b/packages/frontend/src/components/MkButton.stories.impl.ts index e1c1c54d10..982a8b3be1 100644 --- a/packages/frontend/src/components/MkButton.stories.impl.ts +++ b/packages/frontend/src/components/MkButton.stories.impl.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable import/no-default-export */ -/* eslint-disable import/no-duplicates */ +import { action } from '@storybook/addon-actions'; import { StoryObj } from '@storybook/vue3'; import MkButton from './MkButton.vue'; export const Default = { @@ -20,11 +20,60 @@ export const Default = { ...this.args, }; }, + events() { + return { + click: action('click'), + }; + }, }, - template: '<MkButton v-bind="props">Text</MkButton>', + template: '<MkButton v-bind="props" v-on="events">Text</MkButton>', }; }, + args: { + }, parameters: { layout: 'centered', }, } satisfies StoryObj<typeof MkButton>; +export const Primary = { + ...Default, + args: { + ...Default.args, + primary: true, + }, +} satisfies StoryObj<typeof MkButton>; +export const Gradate = { + ...Default, + args: { + ...Default.args, + gradate: true, + }, +} satisfies StoryObj<typeof MkButton>; +export const Rounded = { + ...Default, + args: { + ...Default.args, + rounded: true, + }, +} satisfies StoryObj<typeof MkButton>; +export const Danger = { + ...Default, + args: { + ...Default.args, + danger: true, + }, +} satisfies StoryObj<typeof MkButton>; +export const Small = { + ...Default, + args: { + ...Default.args, + small: true, + }, +} satisfies StoryObj<typeof MkButton>; +export const Large = { + ...Default, + args: { + ...Default.args, + large: true, + }, +} satisfies StoryObj<typeof MkButton>; diff --git a/packages/frontend/src/components/global/MkError.stories.impl.ts b/packages/frontend/src/components/global/MkError.stories.impl.ts index 60ac5c91ad..8252a4d76e 100644 --- a/packages/frontend/src/components/global/MkError.stories.impl.ts +++ b/packages/frontend/src/components/global/MkError.stories.impl.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { action } from '@storybook/addon-actions'; import { expect } from '@storybook/jest'; import { waitFor } from '@storybook/testing-library'; import { StoryObj } from '@storybook/vue3'; @@ -20,14 +21,21 @@ export const Default = { ...this.args, }; }, + events() { + return { + retry: action('retry'), + }; + }, }, - template: '<MkError v-bind="props" />', + template: '<MkError v-bind="props" v-on="events" />', }; }, async play({ canvasElement }) { await expect(canvasElement.firstElementChild).not.toBeNull(); await waitFor(async () => expect(canvasElement.firstElementChild?.classList).not.toContain('_transition_zoom-enter-active')); }, + args: { + }, parameters: { layout: 'centered', }, diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue index 99169512db..261cc0ee18 100644 --- a/packages/frontend/src/components/global/MkTime.vue +++ b/packages/frontend/src/components/global/MkTime.vue @@ -8,6 +8,7 @@ </template> <script lang="ts" setup> +import isChromatic from 'chromatic/isChromatic'; import { onUnmounted } from 'vue'; import { i18n } from '@/i18n'; import { dateTimeFormat } from '@/scripts/intl-const'; @@ -17,7 +18,7 @@ const props = withDefaults(defineProps<{ origin?: Date | null; mode?: 'relative' | 'absolute' | 'detail'; }>(), { - origin: null, + origin: isChromatic() ? new Date('2023-04-01T00:00:00Z') : null, mode: 'relative', }); diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 8c3478d8f2..69fb4b93be 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -7,7 +7,7 @@ <!-- <div class="punished" v-if="user.isSilenced"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i> {{ i18n.ts.userSilenced }}</div> --> <div class="profile _gaps"> - <MkAccountMoved v-if="user.movedToUri" :host="user.movedToUri.host" :acct="user.movedToUri.username"/> + <MkAccountMoved v-if="user.movedToUri" :host="user.movedToUri.host" :username="user.movedToUri.username"/> <MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/> <div :key="user.id" class="main _panel"> |