Skip to content

Commit

Permalink
test(misskey-dev#10336): add components/MkC.* stories (misskey-dev#…
Browse files Browse the repository at this point in the history
…13830)

* test(storybook): add `components/MkC.*` stories

* test(storybook): add some tests

* test: add sleep

* test: comment-out flaky test

* test(storybook): add test for `MkChannelFollowButton`

* chore(storybook): tweak sleep duration in `MkChannelFollowButton` story test

* fix(chromatic): add delay to `MkChannelList`

* chore: replace `mswDecorator` with `mswLoader`

* fix(storybook): tweak some parameters

* chore: serve static files

* fix(chromatic): add delay to `MkCwButton`

* chore: delete logging for debug

* fix: add right click in `MkContextMenu` play

* refactor: remove unused imports
  • Loading branch information
zyoshoka authored Jun 8, 2024
1 parent 61fae45 commit 9849aab
Show file tree
Hide file tree
Showing 28 changed files with 1,083 additions and 86 deletions.
60 changes: 60 additions & 0 deletions packages/frontend/.storybook/fakes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,66 @@ export function abuseUserReport() {
};
}

export function channel(id = 'somechannelid', name = 'Some Channel', bannerUrl: string | null = 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true'): entities.Channel {
return {
id,
createdAt: '2016-12-28T22:49:51.000Z',
lastNotedAt: '2016-12-28T22:49:51.000Z',
name,
description: null,
userId: null,
bannerUrl,
pinnedNoteIds: [],
color: '#000',
isArchived: false,
usersCount: 1,
notesCount: 1,
isSensitive: false,
allowRenoteToExternal: false,
};
}

export function clip(id = 'someclipid', name = 'Some Clip'): entities.Clip {
return {
id,
createdAt: '2016-12-28T22:49:51.000Z',
lastClippedAt: null,
userId: 'someuserid',
user: {
id: 'someuserid',
name: 'Misskey User',
username: 'miskist',
host: 'misskey-hub.net',
avatarUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true',
avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay',
avatarDecorations: [],
emojis: {},
badgeRoles: [],
onlineStatus: 'unknown',
},
notesCount: undefined,
name,
description: 'Some clip description',
isPublic: false,
favoritedCount: 0,
};
}

export function emojiDetailed(id = 'someemojiid', name = 'some_emoji'): entities.EmojiDetailed {
return {
id,
aliases: ['alias1', 'alias2'],
name,
category: 'emojiCategory',
host: null,
url: '/client-assets/about-icon.png',
license: null,
isSensitive: false,
localOnly: false,
roleIdsThatCanBeUsedThisEmojiAsReaction: ['roleId1', 'roleId2'],
};
}

export function galleryPost(isSensitive = false) {
return {
id: 'somepostid',
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/.storybook/generate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ function toStories(component: string): Promise<string> {
const globs = await Promise.all([
glob('src/components/global/Mk*.vue'),
glob('src/components/global/RouterView.vue'),
glob('src/components/Mk{A,B}*.vue'),
glob('src/components/Mk[A-C]*.vue'),
glob('src/components/MkDigitalClock.vue'),
glob('src/components/MkGalleryPostPreview.vue'),
glob('src/components/MkSignupServerRules.vue'),
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const _dirname = fileURLToPath(new URL('.', import.meta.url));

const config = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
staticDirs: [{ from: '../assets', to: '/client-assets' }],
addons: [
getAbsolutePath('@storybook/addon-essentials'),
getAbsolutePath('@storybook/addon-interactions'),
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend/.storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { FORCE_REMOUNT } from '@storybook/core-events';
import { addons } from '@storybook/preview-api';
import { type Preview, setup } from '@storybook/vue3';
import isChromatic from 'chromatic/isChromatic';
import { initialize, mswDecorator } from 'msw-storybook-addon';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { userDetailed } from './fakes.js';
import locale from './locale.js';
import { commonHandlers, onUnhandledRequest } from './mocks.js';
Expand Down Expand Up @@ -122,7 +122,6 @@ const preview = {
}
return story;
},
mswDecorator,
(Story, context) => {
return {
setup() {
Expand All @@ -137,6 +136,7 @@ const preview = {
};
},
],
loaders: [mswLoader],
parameters: {
controls: {
exclude: /^__/,
Expand Down
2 changes: 2 additions & 0 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"@types/node": "20.12.7",
"@types/punycode": "2.1.4",
"@types/sanitize-html": "2.11.0",
"@types/seedrandom": "3.0.8",
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@types/uuid": "9.0.8",
Expand All @@ -128,6 +129,7 @@
"prettier": "3.2.5",
"react": "18.3.1",
"react-dom": "18.3.1",
"seedrandom": "3.0.5",
"start-server-and-test": "2.0.3",
"storybook": "8.0.9",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
import { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { expect, userEvent, within } from '@storybook/test';
import { channel } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkChannelFollowButton from './MkChannelFollowButton.vue';
import { semaphore } from '@/scripts/test-utils.js';
import { i18n } from '@/i18n.js';

function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}

const s = semaphore();
export const Default = {
render(args) {
return {
components: {
MkChannelFollowButton,
},
setup() {
return {
args,
};
},
computed: {
props() {
return {
...this.args,
};
},
},
template: '<MkChannelFollowButton v-bind="props" />',
};
},
args: {
channel: channel(),
full: true,
},
async play({ canvasElement }) {
await s.acquire();
await sleep(1000);
const canvas = within(canvasElement);
const buttonElement = canvas.getByRole<HTMLButtonElement>('button');
await expect(buttonElement).toHaveTextContent(i18n.ts.follow);
await userEvent.click(buttonElement);
await sleep(1000);
await expect(buttonElement).toHaveTextContent(i18n.ts.unfollow);
await sleep(100);
await userEvent.click(buttonElement);
s.release();
},
parameters: {
layout: 'centered',
msw: {
handlers: [
...commonHandlers,
http.post('/api/channels/follow', async ({ request }) => {
action('POST /api/channels/follow')(await request.json());
return HttpResponse.json({});
}),
http.post('/api/channels/unfollow', async ({ request }) => {
action('POST /api/channels/unfollow')(await request.json());
return HttpResponse.json({});
}),
],
},
},
} satisfies StoryObj<typeof MkChannelFollowButton>;
5 changes: 3 additions & 2 deletions packages/frontend/src/components/MkChannelFollowButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,18 @@ SPDX-License-Identifier: AGPL-3.0-only

<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';

const props = withDefaults(defineProps<{
channel: Record<string, any>;
channel: Misskey.entities.Channel;
full?: boolean;
}>(), {
full: false,
});

const isFollowing = ref<boolean>(props.channel.isFollowing);
const isFollowing = ref(props.channel.isFollowing);
const wait = ref(false);

async function onClick() {
Expand Down
65 changes: 65 additions & 0 deletions packages/frontend/src/components/MkChannelList.stories.impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
import { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { channel } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkChannelList from './MkChannelList.vue';
export const Default = {
render(args) {
return {
components: {
MkChannelList,
},
setup() {
return {
args,
};
},
computed: {
props() {
return {
...this.args,
};
},
},
template: '<MkChannelList v-bind="props" />',
};
},
args: {
pagination: {
endpoint: 'channels/search',
limit: 10,
},
},
parameters: {
chromatic: {
// NOTE: ロードが終わるまで待つ
delay: 3000,
},
layout: 'fullscreen',
msw: {
handlers: [
...commonHandlers,
http.post('/api/channels/search', async ({ request, params }) => {
action('POST /api/channels/search')(await request.json());
return HttpResponse.json(params.untilId === 'lastchannel' ? [] : [
channel(),
channel('lastchannel', 'Last Channel', null),
]);
}),
],
},
},
decorators: [
() => ({
template: '<div style="display: flex; align-items: center; justify-content: center; height: 100vh"><div style="max-width: 700px; width: 100%; margin: 3rem"><story/></div></div>',
}),
],
} satisfies StoryObj<typeof MkChannelList>;
43 changes: 43 additions & 0 deletions packages/frontend/src/components/MkChannelPreview.stories.impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
import { StoryObj } from '@storybook/vue3';
import { channel } from '../../.storybook/fakes.js';
import MkChannelPreview from './MkChannelPreview.vue';
export const Default = {
render(args) {
return {
components: {
MkChannelPreview,
},
setup() {
return {
args,
};
},
computed: {
props() {
return {
...this.args,
};
},
},
template: '<MkChannelPreview v-bind="props" />',
};
},
args: {
channel: channel(),
},
parameters: {
layout: 'fullscreen',
},
decorators: [
() => ({
template: '<div style="display: flex; align-items: center; justify-content: center; height: 100vh"><div style="max-width: 700px; width: 100%; margin: 3rem"><story/></div></div>',
}),
],
} satisfies StoryObj<typeof MkChannelPreview>;
Loading

1 comment on commit 9849aab

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chromatic detects changes. Please review the changes on Chromatic.

Please sign in to comment.