Update storybook to v9 (#4645)

* chore: Update storybook to v9

* Pin next-export-i18n + remove outdated mock library

* Replace old mocking library with MSW

* Resolve knip unused code warnings
This commit is contained in:
Gabe Kangas
2025-10-20 21:26:32 -07:00
committed by GitHub
parent 72d5ee8754
commit cc9286416c
68 changed files with 40881 additions and 44059 deletions
@@ -1,5 +1,5 @@
import { StoryFn, Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { StoryFn, Meta } from '@storybook/nextjs';
import { action } from 'storybook/actions';
import { ActionButton } from './ActionButton';
const meta = {
@@ -1,5 +1,5 @@
import { StoryFn, Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { StoryFn, Meta } from '@storybook/nextjs';
import { action } from 'storybook/actions';
import { ActionButtonMenu } from './ActionButtonMenu';
const meta = {
@@ -1,5 +1,5 @@
import { StoryFn, Meta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { StoryFn, Meta } from '@storybook/nextjs';
import { action } from 'storybook/actions';
import { ActionButtonRow } from './ActionButtonRow';
import { ActionButton } from '../ActionButton/ActionButton';
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { ChatActionMessage } from './ChatActionMessage';
import Mock from '../../../stories/assets/mocks/chatmessage-action.png';
@@ -1,5 +1,5 @@
import { useState } from 'react';
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { ChatContainer } from './ChatContainer';
import { ChatMessage } from '../../../interfaces/chat-message.model';
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { ChatJoinMessage } from './ChatJoinMessage';
import Mock from '../../../stories/assets/mocks/chatmessage-action.png';
@@ -1,23 +1,9 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { http, HttpResponse } from 'msw';
import { ChatModerationActionMenu } from './ChatModerationActionMenu';
const mocks = {
mocks: [
{
// The "matcher" determines if this
// mock should respond to the current
// call to fetch().
matcher: {
name: 'response',
url: 'glob:/api/moderation/chat/user/*',
},
// If the "matcher" matches the current
// fetch() call, the fetch response is
// built using this "response".
response: {
status: 200,
body: {
const mockUserData = {
user: {
id: 'hjFPU967R',
displayName: 'focused-snyder',
@@ -57,17 +43,19 @@ const mocks = {
body: 'test message 1',
},
],
},
},
},
],
};
};
const meta = {
title: 'owncast/Chat/Moderation menu',
component: ChatModerationActionMenu,
parameters: {
fetchMock: mocks,
msw: {
handlers: [
http.get('/api/moderation/chat/user/*', () => {
return HttpResponse.json(mockUserData);
}),
],
},
docs: {
description: {
component: `This should be a popup that is activated from a user's chat message. It should have actions to:
@@ -1,23 +1,9 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { http, HttpResponse } from 'msw';
import { ChatModerationDetailsModal } from './ChatModerationDetailsModal';
const mocks = {
mocks: [
{
// The "matcher" determines if this
// mock should respond to the current
// call to fetch().
matcher: {
name: 'response',
url: 'glob:/api/moderation/chat/user/*',
},
// If the "matcher" matches the current
// fetch() call, the fetch response is
// built using this "response".
response: {
status: 200,
body: {
const mockUserData = {
user: {
id: 'hjFPU967R',
displayName: 'focused-snyder',
@@ -57,17 +43,19 @@ const mocks = {
body: 'test message 1',
},
],
},
},
},
],
};
};
const meta = {
title: 'owncast/Chat/Moderation modal',
component: ChatModerationDetailsModal,
parameters: {
fetchMock: mocks,
msw: {
handlers: [
http.get('/api/moderation/chat/user/*', () => {
return HttpResponse.json(mockUserData);
}),
],
},
docs: {
description: {
component: `This should be a modal that gives the moderator more details about the user such as:
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { ChatModeratorNotification } from './ChatModeratorNotification';
const meta = {
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { ChatNameChangeMessage } from './ChatNameChangeMessage';
const meta = {
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { ChatPartMessage } from './ChatPartMessage';
import Mock from '../../../stories/assets/mocks/chatmessage-action.png';
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { ChatSocialMessage } from './ChatSocialMessage';
const meta = {
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { ChatSystemMessage } from './ChatSystemMessage';
import Mock from '../../../stories/assets/mocks/chatmessage-system.png';
import { ChatMessage } from '../../../interfaces/chat-message.model';
@@ -1,37 +1,24 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { http, HttpResponse } from 'msw';
import { ChatTextField } from './ChatTextField';
import Mockup from '../../../stories/assets/mocks/chatinput-mock.png';
const mockResponse = JSON.parse(
`[{"name":"Reaper-gg.png","url":"https://ui-avatars.com/api/?background=0D8ABC&color=fff&name=OC"},{"name":"Reaper-hi.png","url":"https://ui-avatars.com/api/?background=0D8ABC&color=fff&name=XX"},{"name":"Reaper-hype.png","url":"https://ui-avatars.com/api/?background=0D8ABC&color=fff&name=TX"},{"name":"Reaper-lol.png","url":"https://ui-avatars.com/api/?background=0D8ABC&color=fff&name=CA"},{"name":"Reaper-love.png","url":"https://ui-avatars.com/api/?background=0D8ABC&color=fff&name=OK"}]`,
);
const mocks = {
mocks: [
{
// The "matcher" determines if this
// mock should respond to the current
// call to fetch().
matcher: {
name: 'response',
url: 'glob:/api/emoji',
},
// If the "matcher" matches the current
// fetch() call, the fetch response is
// built using this "response".
response: {
status: 200,
body: mockResponse,
},
},
],
};
const meta = {
title: 'owncast/Chat/Input text field',
component: ChatTextField,
parameters: {
fetchMock: mocks,
msw: {
handlers: [
http.get('/api/emoji', () => {
return HttpResponse.json(mockResponse);
}),
],
},
chromatic: { diffThreshold: 0.9 },
design: {
@@ -1,4 +1,4 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { ChatUserBadge } from './ChatUserBadge';
import { ModerationBadge } from './ModerationBadge';
import { AuthedUserBadge } from './AuthedUserBadge';
@@ -1,4 +1,4 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { ChatUserMessage } from './ChatUserMessage';
import { ChatMessage } from '../../../interfaces/chat-message.model';
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { ContentHeader } from './ContentHeader';
const meta = {
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { OwncastLogo } from './OwncastLogo';
const meta = {
@@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot, useSetRecoilState } from 'recoil';
import { UserDropdown } from './UserDropdown';
import { CurrentUser } from '../../../interfaces/current-user';
+17 -13
View File
@@ -1,4 +1,4 @@
import { Meta, StoryFn, StoryObj } from '@storybook/react';
import { Meta, StoryFn, StoryObj } from '@storybook/nextjs';
import { MutableSnapshot, RecoilRoot } from 'recoil';
import { makeEmptyClientConfig } from '../../../interfaces/client-config.model';
import { ServerStatus, makeEmptyServerStatus } from '../../../interfaces/server-status.model';
@@ -129,20 +129,22 @@ export const OfflineMobile: StoryObj<typeof Template> = {
},
},
parameters: {
globals: {
viewport: {
defaultViewport: 'mobile1',
},
value: 'mobile1',
isRotated: false
}
},
};
export const OfflineTablet: StoryObj<typeof Template> = {
render: Template,
parameters: {
globals: {
viewport: {
defaultViewport: 'tablet',
},
value: 'tablet',
isRotated: false
}
},
};
@@ -168,10 +170,11 @@ export const OnlineMobile: StoryObj<typeof Template> = {
},
},
parameters: {
globals: {
viewport: {
defaultViewport: 'mobile1',
},
value: 'mobile1',
isRotated: false
}
},
};
@@ -182,9 +185,10 @@ export const OnlineTablet: StoryObj<typeof Template> = {
ServerStatusServiceMock: OnlineServerStatusServiceMock,
},
parameters: {
globals: {
viewport: {
defaultViewport: 'tablet',
},
value: 'tablet',
isRotated: false
}
},
};
@@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot, useSetRecoilState } from 'recoil';
import { AuthModal } from './AuthModal';
import { currentUserAtom } from '../../stores/ClientConfigStore';
@@ -1,4 +1,4 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { BrowserNotifyModal } from './BrowserNotifyModal';
import BrowserNotifyModalMock from '../../../stories/assets/mocks/notify-modal.png';
@@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot, useSetRecoilState } from 'recoil';
import { ChatModal, ChatModalProps } from './ChatModal';
import { ChatMessage } from '../../../interfaces/chat-message.model';
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { FatalErrorStateModal } from './FatalErrorStateModal';
const meta = {
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { FediAuthModal } from './FediAuthModal';
import FediAuthModalMock from '../../../stories/assets/mocks/fediauth-modal.png';
@@ -1,4 +1,4 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { FollowModal } from './FollowModal';
import FollowModalMock from '../../../stories/assets/mocks/follow-modal.png';
@@ -1,4 +1,4 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { IndieAuthModal } from './IndieAuthModal';
import Mock from '../../../stories/assets/mocks/indieauth-modal.png';
@@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot, useSetRecoilState } from 'recoil';
import { NameChangeModal } from './NameChangeModal';
import { CurrentUser } from '../../../interfaces/current-user';
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { ComponentError } from './ComponentError';
const meta = {
@@ -1,5 +1,5 @@
/* eslint-disable no-useless-escape */
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { CustomPageContent } from './CustomPageContent';
+1 -1
View File
@@ -1,4 +1,4 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { Footer } from './Footer';
+1 -1
View File
@@ -1,4 +1,4 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { Header } from './Header';
+1 -1
View File
@@ -1,4 +1,4 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { Modal } from './Modal';
const meta = {
@@ -1,5 +1,5 @@
/* eslint-disable no-alert */
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { NotifyReminderPopup } from './NotifyReminderPopup';
import Mock from '../../../stories/assets/mocks/notify-popup.png';
@@ -1,4 +1,4 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { OfflineBanner } from './OfflineBanner';
import OfflineState from '../../../stories/assets/mocks/offline-state.png';
@@ -1,4 +1,4 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { OfflineEmbed } from './OfflineEmbed';
import OfflineState from '../../../stories/assets/mocks/offline-state.png';
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { SocialLinks } from './SocialLinks';
const meta = {
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { subHours, subMinutes, subSeconds } from 'date-fns';
import { Statusbar } from './Statusbar';
@@ -1,24 +1,9 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { action } from '@storybook/addon-actions';
import { http, HttpResponse } from 'msw';
import { FollowerCollection } from './FollowerCollection';
const mocks = {
mocks: [
{
// The "matcher" determines if this
// mock should respond to the current
// call to fetch().
matcher: {
name: 'response',
url: 'glob:/api/followers*',
},
// If the "matcher" matches the current
// fetch() call, the fetch response is
// built using this "response".
response: {
status: 200,
body: {
const mockFollowersData = {
total: 100,
results: [
{
@@ -227,34 +212,11 @@ const mocks = {
disabledAt: null,
},
],
},
},
},
],
};
};
const noFollowersMock = {
mocks: [
{
// The "matcher" determines if this
// mock should respond to the current
// call to fetch().
matcher: {
name: 'response',
url: 'glob:/api/followers*',
},
// If the "matcher" matches the current
// fetch() call, the fetch response is
// built using this "response".
response: {
status: 200,
body: {
total: 0,
results: [],
},
},
},
],
const noFollowersData = {
total: 0,
results: [],
};
const meta = {
@@ -270,9 +232,7 @@ export default meta;
const Template: StoryFn<typeof FollowerCollection> = (args: object) => (
<RecoilRoot>
<FollowerCollection
onFollowButtonClick={() => {
action('Follow button clicked');
}}
onFollowButtonClick={() => {}}
name="Example stream name"
{...args}
/>
@@ -283,7 +243,13 @@ export const NoFollowers = {
render: Template,
parameters: {
fetchMock: noFollowersMock,
msw: {
handlers: [
http.get('/api/followers*', () => {
return HttpResponse.json(noFollowersData);
}),
],
},
},
};
@@ -291,6 +257,12 @@ export const Example = {
render: Template,
parameters: {
fetchMock: mocks,
msw: {
handlers: [
http.get('/api/followers*', () => {
return HttpResponse.json(mockFollowersData);
}),
],
},
},
};
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { SingleFollower } from './SingleFollower';
import SingleFollowerMock from '../../../../stories/assets/mocks/single-follower.png';
@@ -1,4 +1,4 @@
import { StoryFn, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/nextjs';
import { RecoilRoot } from 'recoil';
import { OwncastPlayer } from './OwncastPlayer';
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/react';
import { Meta } from '@storybook/nextjs';
import { VideoPoster } from './VideoPoster';
const meta = {