fix(storybook): prevent infinite remount of component (#14101)
* fix(storybook): prevent infinite remount of component * fix: disable flaky `.toMatch()` test
This commit is contained in:
parent
a6edd50a5d
commit
f1b1e2a7cc
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FORCE_REMOUNT } from '@storybook/core-events';
|
import { FORCE_RE_RENDER, FORCE_REMOUNT } from '@storybook/core-events';
|
||||||
import { addons } from '@storybook/preview-api';
|
import { addons } from '@storybook/preview-api';
|
||||||
import { type Preview, setup } from '@storybook/vue3';
|
import { type Preview, setup } from '@storybook/vue3';
|
||||||
import isChromatic from 'chromatic/isChromatic';
|
import isChromatic from 'chromatic/isChromatic';
|
||||||
|
@ -16,7 +16,7 @@ import '../src/style.scss';
|
||||||
|
|
||||||
const appInitialized = Symbol();
|
const appInitialized = Symbol();
|
||||||
|
|
||||||
let lastStory = null;
|
let lastStory: string | null = null;
|
||||||
let moduleInitialized = false;
|
let moduleInitialized = false;
|
||||||
let unobserve = () => {};
|
let unobserve = () => {};
|
||||||
let misskeyOS = null;
|
let misskeyOS = null;
|
||||||
|
@ -110,7 +110,7 @@ const preview = {
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
Promise.all([resetIndexedDBPromise, resetDefaultStorePromise]).then(() => {
|
Promise.all([resetIndexedDBPromise, resetDefaultStorePromise]).then(() => {
|
||||||
initLocalStorage();
|
initLocalStorage();
|
||||||
channel.emit(FORCE_REMOUNT, { storyId: context.id });
|
channel.emit(FORCE_RE_RENDER, { storyId: context.id });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const story = Story();
|
const story = Story();
|
||||||
|
|
|
@ -12,14 +12,12 @@ import { expect, userEvent, within } from '@storybook/test';
|
||||||
import { channel } from '../../.storybook/fakes.js';
|
import { channel } from '../../.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import MkChannelFollowButton from './MkChannelFollowButton.vue';
|
import MkChannelFollowButton from './MkChannelFollowButton.vue';
|
||||||
import { semaphore } from '@/scripts/test-utils.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
function sleep(ms: number) {
|
function sleep(ms: number) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
const s = semaphore();
|
|
||||||
export const Default = {
|
export const Default = {
|
||||||
render(args) {
|
render(args) {
|
||||||
return {
|
return {
|
||||||
|
@ -46,17 +44,13 @@ export const Default = {
|
||||||
full: true,
|
full: true,
|
||||||
},
|
},
|
||||||
async play({ canvasElement }) {
|
async play({ canvasElement }) {
|
||||||
await s.acquire();
|
|
||||||
await sleep(1000);
|
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const buttonElement = canvas.getByRole<HTMLButtonElement>('button');
|
const buttonElement = canvas.getByRole<HTMLButtonElement>('button');
|
||||||
await expect(buttonElement).toHaveTextContent(i18n.ts.follow);
|
await expect(buttonElement).toHaveTextContent(i18n.ts.follow);
|
||||||
await userEvent.click(buttonElement);
|
await userEvent.click(buttonElement);
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
await expect(buttonElement).toHaveTextContent(i18n.ts.unfollow);
|
await expect(buttonElement).toHaveTextContent(i18n.ts.unfollow);
|
||||||
await sleep(100);
|
|
||||||
await userEvent.click(buttonElement);
|
await userEvent.click(buttonElement);
|
||||||
s.release();
|
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: 'centered',
|
layout: 'centered',
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import { StoryObj } from '@storybook/vue3';
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import { expect, within } from '@storybook/test';
|
import { expect, userEvent, within } from '@storybook/test';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import MkClickerGame from './MkClickerGame.vue';
|
import MkClickerGame from './MkClickerGame.vue';
|
||||||
|
|
||||||
|
@ -41,12 +41,10 @@ export const Default = {
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const count = canvas.getByTestId('count');
|
const count = canvas.getByTestId('count');
|
||||||
// NOTE: flaky なので N/A も通しておく
|
await expect(count).toHaveTextContent('0');
|
||||||
await expect(count).toHaveTextContent(/^(0|N\/A)$/);
|
const buttonElement = canvas.getByRole<HTMLButtonElement>('button');
|
||||||
// FIXME: flaky
|
await userEvent.click(buttonElement);
|
||||||
// const buttonElement = canvas.getByRole<HTMLButtonElement>('button');
|
await expect(count).toHaveTextContent('1');
|
||||||
// await userEvent.click(buttonElement);
|
|
||||||
// await expect(count).toHaveTextContent('1');
|
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: 'centered',
|
layout: 'centered',
|
||||||
|
|
|
@ -11,13 +11,6 @@ import { expect, userEvent, within } from '@storybook/test';
|
||||||
import { file } from '../../.storybook/fakes.js';
|
import { file } from '../../.storybook/fakes.js';
|
||||||
import MkCwButton from './MkCwButton.vue';
|
import MkCwButton from './MkCwButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { semaphore } from '@/scripts/test-utils.js';
|
|
||||||
|
|
||||||
function sleep(ms: number) {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
const s = semaphore();
|
|
||||||
|
|
||||||
export const Default = {
|
export const Default = {
|
||||||
render(args) {
|
render(args) {
|
||||||
|
@ -54,8 +47,6 @@ export const Default = {
|
||||||
text: 'Some CW content',
|
text: 'Some CW content',
|
||||||
},
|
},
|
||||||
async play({ canvasElement }) {
|
async play({ canvasElement }) {
|
||||||
await s.acquire();
|
|
||||||
await sleep(1000);
|
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const buttonElement = canvas.getByRole<HTMLButtonElement>('button');
|
const buttonElement = canvas.getByRole<HTMLButtonElement>('button');
|
||||||
await expect(buttonElement).toHaveTextContent(i18n.ts._cw.show);
|
await expect(buttonElement).toHaveTextContent(i18n.ts._cw.show);
|
||||||
|
@ -63,7 +54,6 @@ export const Default = {
|
||||||
await userEvent.click(buttonElement);
|
await userEvent.click(buttonElement);
|
||||||
await expect(buttonElement).toHaveTextContent(i18n.ts._cw.hide);
|
await expect(buttonElement).toHaveTextContent(i18n.ts._cw.hide);
|
||||||
await userEvent.click(buttonElement);
|
await userEvent.click(buttonElement);
|
||||||
s.release();
|
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
chromatic: {
|
chromatic: {
|
||||||
|
|
|
@ -35,12 +35,10 @@ export const Default = {
|
||||||
// FIXME: 通るけどその後落ちるのでコメントアウト
|
// FIXME: 通るけどその後落ちるのでコメントアウト
|
||||||
// await expect(a.href).toMatch(/^https?:\/\/.*#test$/);
|
// await expect(a.href).toMatch(/^https?:\/\/.*#test$/);
|
||||||
await userEvent.pointer({ keys: '[MouseRight]', target: a });
|
await userEvent.pointer({ keys: '[MouseRight]', target: a });
|
||||||
await tick();
|
|
||||||
const menu = canvas.getByRole('menu');
|
const menu = canvas.getByRole('menu');
|
||||||
await expect(menu).toBeInTheDocument();
|
await expect(menu).toBeInTheDocument();
|
||||||
await userEvent.click(a);
|
await userEvent.click(a);
|
||||||
a.blur();
|
a.blur();
|
||||||
await tick();
|
|
||||||
await expect(menu).not.toBeInTheDocument();
|
await expect(menu).not.toBeInTheDocument();
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
|
|
|
@ -9,12 +9,6 @@ import { StoryObj } from '@storybook/vue3';
|
||||||
import MkAd from './MkAd.vue';
|
import MkAd from './MkAd.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
let lock: Promise<undefined> | undefined;
|
|
||||||
|
|
||||||
function sleep(ms: number) {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
const common = {
|
const common = {
|
||||||
render(args) {
|
render(args) {
|
||||||
return {
|
return {
|
||||||
|
@ -37,56 +31,41 @@ const common = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async play({ canvasElement, args }) {
|
async play({ canvasElement, args }) {
|
||||||
if (lock) {
|
const canvas = within(canvasElement);
|
||||||
console.warn('This test is unexpectedly running twice in parallel, fix it!');
|
const a = canvas.getByRole<HTMLAnchorElement>('link');
|
||||||
console.warn('See also: https://github.com/misskey-dev/misskey/issues/11267');
|
// FIXME: 通るけどその後落ちるのでコメントアウト
|
||||||
await lock;
|
// await expect(a.href).toMatch(/^https?:\/\/.*#test$/);
|
||||||
|
const img = within(a).getByRole('img');
|
||||||
|
await expect(img).toBeInTheDocument();
|
||||||
|
let buttons = canvas.getAllByRole<HTMLButtonElement>('button');
|
||||||
|
await expect(buttons).toHaveLength(1);
|
||||||
|
const i = buttons[0];
|
||||||
|
await expect(i).toBeInTheDocument();
|
||||||
|
await userEvent.click(i);
|
||||||
|
await expect(canvasElement).toHaveTextContent(i18n.ts._ad.back);
|
||||||
|
await expect(a).not.toBeInTheDocument();
|
||||||
|
await expect(i).not.toBeInTheDocument();
|
||||||
|
buttons = canvas.getAllByRole<HTMLButtonElement>('button');
|
||||||
|
const hasReduceFrequency = args.specify?.ratio !== 0;
|
||||||
|
await expect(buttons).toHaveLength(hasReduceFrequency ? 2 : 1);
|
||||||
|
const reduce = hasReduceFrequency ? buttons[0] : null;
|
||||||
|
const back = buttons[hasReduceFrequency ? 1 : 0];
|
||||||
|
if (reduce) {
|
||||||
|
await expect(reduce).toBeInTheDocument();
|
||||||
|
await expect(reduce).toHaveTextContent(i18n.ts._ad.reduceFrequencyOfThisAd);
|
||||||
}
|
}
|
||||||
|
await expect(back).toBeInTheDocument();
|
||||||
let resolve: (value?: any) => void;
|
await expect(back).toHaveTextContent(i18n.ts._ad.back);
|
||||||
lock = new Promise(r => resolve = r);
|
await userEvent.click(back);
|
||||||
|
await waitFor(() => expect(canvas.queryByRole('img')).toBeTruthy());
|
||||||
try {
|
if (reduce) {
|
||||||
// NOTE: sleep しないと何故か落ちる
|
await expect(reduce).not.toBeInTheDocument();
|
||||||
await sleep(100);
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const a = canvas.getByRole<HTMLAnchorElement>('link');
|
|
||||||
// await expect(a.href).toMatch(/^https?:\/\/.*#test$/);
|
|
||||||
const img = within(a).getByRole('img');
|
|
||||||
await expect(img).toBeInTheDocument();
|
|
||||||
let buttons = canvas.getAllByRole<HTMLButtonElement>('button');
|
|
||||||
await expect(buttons).toHaveLength(1);
|
|
||||||
const i = buttons[0];
|
|
||||||
await expect(i).toBeInTheDocument();
|
|
||||||
await userEvent.click(i);
|
|
||||||
await expect(canvasElement).toHaveTextContent(i18n.ts._ad.back);
|
|
||||||
await expect(a).not.toBeInTheDocument();
|
|
||||||
await expect(i).not.toBeInTheDocument();
|
|
||||||
buttons = canvas.getAllByRole<HTMLButtonElement>('button');
|
|
||||||
const hasReduceFrequency = args.specify?.ratio !== 0;
|
|
||||||
await expect(buttons).toHaveLength(hasReduceFrequency ? 2 : 1);
|
|
||||||
const reduce = hasReduceFrequency ? buttons[0] : null;
|
|
||||||
const back = buttons[hasReduceFrequency ? 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);
|
|
||||||
await waitFor(() => expect(canvas.queryByRole('img')).toBeTruthy());
|
|
||||||
if (reduce) {
|
|
||||||
await expect(reduce).not.toBeInTheDocument();
|
|
||||||
}
|
|
||||||
await expect(back).not.toBeInTheDocument();
|
|
||||||
const aAgain = canvas.getByRole<HTMLAnchorElement>('link');
|
|
||||||
await expect(aAgain).toBeInTheDocument();
|
|
||||||
const imgAgain = within(aAgain).getByRole('img');
|
|
||||||
await expect(imgAgain).toBeInTheDocument();
|
|
||||||
} finally {
|
|
||||||
resolve!();
|
|
||||||
lock = undefined;
|
|
||||||
}
|
}
|
||||||
|
await expect(back).not.toBeInTheDocument();
|
||||||
|
const aAgain = canvas.getByRole<HTMLAnchorElement>('link');
|
||||||
|
await expect(aAgain).toBeInTheDocument();
|
||||||
|
const imgAgain = within(aAgain).getByRole('img');
|
||||||
|
await expect(imgAgain).toBeInTheDocument();
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
prefer: [],
|
prefer: [],
|
||||||
|
|
|
@ -7,13 +7,3 @@ export async function tick(): Promise<void> {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
await new Promise((globalThis.requestIdleCallback ?? setTimeout) as never);
|
await new Promise((globalThis.requestIdleCallback ?? setTimeout) as never);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://github.com/misskey-dev/misskey/issues/11267
|
|
||||||
*/
|
|
||||||
export function semaphore(counter = 0, waiting: (() => void)[] = []) {
|
|
||||||
return {
|
|
||||||
acquire: () => ++counter > 1 && new Promise<void>(resolve => waiting.push(resolve)),
|
|
||||||
release: () => --counter && waiting.pop()?.(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue