From bcbf6bb29716239e4200aff93f2ea1dbd4fffcc9 Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Thu, 26 Jun 2025 16:20:17 +0800 Subject: [PATCH] add storybook tests for pages --- next-ui/src/mocks/api/handlers.ts | 3 +- .../src/mocks/api/handlers/announcements.ts | 46 +++++++++++++ next-ui/src/mocks/api/handlers/releases.ts | 8 +++ .../src/pages/server/announcements.stories.ts | 69 +++++++++++++++++++ next-ui/src/pages/server/announcements.vue | 1 + next-ui/src/pages/server/updates.stories.ts | 58 ++++++++++++++++ 6 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 next-ui/src/mocks/api/handlers/announcements.ts create mode 100644 next-ui/src/pages/server/announcements.stories.ts create mode 100644 next-ui/src/pages/server/updates.stories.ts diff --git a/next-ui/src/mocks/api/handlers.ts b/next-ui/src/mocks/api/handlers.ts index de8a6ca17..0b5f388d5 100644 --- a/next-ui/src/mocks/api/handlers.ts +++ b/next-ui/src/mocks/api/handlers.ts @@ -1,3 +1,4 @@ +import { announcementHandlers } from '@/mocks/api/handlers/announcements' import { releasesHandlers } from '@/mocks/api/handlers/releases' import { fromOpenApi } from '@mswjs/source/open-api' import type { OpenAPIV3 } from 'openapi-types' @@ -10,7 +11,7 @@ const doc = { } as unknown as OpenAPIV3.Document // manually defined handlers need to be before fromOpenApi -export const handlers = [...releasesHandlers, ...(await fromOpenApi(doc))] +export const handlers = [...announcementHandlers, ...releasesHandlers, ...(await fromOpenApi(doc))] export const response401Unauthorized = () => HttpResponse.json({ error: 'Unauthorized' }, { status: 401 }) diff --git a/next-ui/src/mocks/api/handlers/announcements.ts b/next-ui/src/mocks/api/handlers/announcements.ts new file mode 100644 index 000000000..deeeb40ed --- /dev/null +++ b/next-ui/src/mocks/api/handlers/announcements.ts @@ -0,0 +1,46 @@ +import { httpTyped } from '@/mocks/api/httpTyped' + +export const announcementsAllRead = { + version: 'https://jsonfeed.org/version/1', + title: 'Announcements', + home_page_url: 'https://komga.org/blog', + description: 'Latest Komga announcements', + items: [ + { + id: 'https://komga.org/blog/ebook-drop2', + url: 'https://komga.org/blog/ebook-drop2', + title: 'eBook drop 2', + summary: 'Version 1.9.0 contains the second feature drop for Ebooks support.', + content_html: '

A longer text…

', + date_modified: new Date('2023-12-15T00:00:00Z'), + author: { + name: 'gotson', + url: 'https://github.com/gotson', + }, + tags: ['upgrade', 'komga'], + _komga: { + read: true, + }, + }, + { + id: 'https://komga.org/blog/ebook-support', + url: 'https://komga.org/blog/ebook-support', + title: 'eBook support', + summary: 'Version 1.8.0 is bringing a long awaited feature: proper eBook support!', + content_html: '

A longer text…

', + date_modified: new Date('2023-11-29T00:00:00Z'), + author: { + name: 'gotson', + url: 'https://github.com/gotson', + }, + tags: ['upgrade', 'komga'], + _komga: { + read: true, + }, + }, + ], +} + +export const announcementHandlers = [ + httpTyped.put('/api/v1/announcements', ({ response }) => response(204).empty()), +] diff --git a/next-ui/src/mocks/api/handlers/releases.ts b/next-ui/src/mocks/api/handlers/releases.ts index 5405cf500..6bfd4aa03 100644 --- a/next-ui/src/mocks/api/handlers/releases.ts +++ b/next-ui/src/mocks/api/handlers/releases.ts @@ -28,6 +28,14 @@ export const releasesResponseOkNotLatest = [ preRelease: false, description: 'Truncated', }, + { + version: '1.21.2', + releaseDate: new Date('2025-03-12T04:19:30Z'), + url: 'https://github.com/gotson/komga/releases/tag/1.21.2', + latest: false, + preRelease: false, + description: 'Truncated', + }, ] export const releasesHandlers = [ diff --git a/next-ui/src/pages/server/announcements.stories.ts b/next-ui/src/pages/server/announcements.stories.ts new file mode 100644 index 000000000..edc24f231 --- /dev/null +++ b/next-ui/src/pages/server/announcements.stories.ts @@ -0,0 +1,69 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import announcements from './announcements.vue' +import { http, delay } from 'msw' + +import { response401Unauthorized } from '@/mocks/api/handlers' +import { httpTyped } from '@/mocks/api/httpTyped' +import { announcementsAllRead } from '@/mocks/api/handlers/announcements' +import { expect, waitFor } from 'storybook/test' + +const meta = { + component: announcements, + render: (args: object) => ({ + components: { announcements }, + setup() { + return { args } + }, + template: '', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Unread: Story = { + args: {}, + play: async ({ canvas }) => { + await waitFor(() => expect(canvas.getByText('eBook drop 2')).not.toBeNull()) + + await expect(canvas.getByRole('button', { name: 'mark all read' })).toBeEnabled() + }, +} + +export const NoUnread: Story = { + parameters: { + msw: { + handlers: [ + httpTyped.get('/api/v1/announcements', ({ response }) => + response(200).json(announcementsAllRead), + ), + ], + }, + }, + play: async ({ canvas }) => { + await waitFor(() => expect(canvas.getByText('eBook drop 2')).not.toBeNull()) + + await expect(canvas.queryByRole('button', { name: 'mark all read' })).toBeNull() + }, +} + +export const Loading: Story = { + parameters: { + msw: { + handlers: [http.all('*', async () => await delay(5_000))], + }, + }, +} + +export const Error: Story = { + parameters: { + msw: { + handlers: [http.all('*', response401Unauthorized)], + }, + }, +} diff --git a/next-ui/src/pages/server/announcements.vue b/next-ui/src/pages/server/announcements.vue index 620cfd681..d8bcc3d99 100644 --- a/next-ui/src/pages/server/announcements.vue +++ b/next-ui/src/pages/server/announcements.vue @@ -50,6 +50,7 @@ color="success" size="x-large" icon="i-mdi:check-all" + aria-label="mark all read" /> diff --git a/next-ui/src/pages/server/updates.stories.ts b/next-ui/src/pages/server/updates.stories.ts new file mode 100644 index 000000000..d29e87e30 --- /dev/null +++ b/next-ui/src/pages/server/updates.stories.ts @@ -0,0 +1,58 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import updates from './updates.vue' +import { http, delay } from 'msw' + +import { response401Unauthorized } from '@/mocks/api/handlers' +import { httpTyped } from '@/mocks/api/httpTyped' +import { releasesResponseOkNotLatest } from '@/mocks/api/handlers/releases' + +const meta = { + component: updates, + render: (args: object) => ({ + components: { updates }, + setup() { + return { args } + }, + template: '', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Latest: Story = { + args: {}, +} + +export const Outdated: Story = { + parameters: { + msw: { + handlers: [ + httpTyped.get('/api/v1/releases', ({ response }) => + response(200).json(releasesResponseOkNotLatest), + ), + ], + }, + }, +} + +export const Loading: Story = { + parameters: { + msw: { + handlers: [http.all('*', async () => await delay(5_000))], + }, + }, +} + +export const Error: Story = { + parameters: { + msw: { + handlers: [http.all('*', response401Unauthorized)], + }, + }, +}