Changes
7 changed files (+195/-20)
-
-
-
@@ -0,0 +1,29 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { type Decorator } from "@storybook/react"; import { idbBackend } from "@yamori/idb_backend"; import { ProtoRPCProvider, type ProtoRPC } from "../../src/lib.ts"; export function withIDBBackend<Args>(): Decorator<Args> { const backend = idbBackend(); const rpc = { async send(service, method, data) { return (await backend).handle({ service, method, data, }); }, } satisfies ProtoRPC; return function (Story) { return ( <ProtoRPCProvider rpc={rpc}> <Story /> </ProtoRPCProvider> ); }; }
-
-
-
@@ -0,0 +1,60 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { type DescMethod, type DescMessage, type MessageInitShape, type MessageShape, create, fromBinary, toBinary, } from "@bufbuild/protobuf"; import { type Decorator } from "@storybook/react"; import { ProtoRPCProvider, type ProtoRPC } from "../../src/lib.ts"; export interface MethodMock { service: string; method: string; handler(request: Uint8Array): Promise<Uint8Array> | Uint8Array; } export function mock<Input extends DescMessage, Output extends DescMessage>( method: { input: Input; output: Output } & DescMethod, handler: ( request: MessageShape<Input>, ) => Promise<MessageInitShape<Output>> | MessageInitShape<Output>, ): MethodMock { return { service: method.parent.typeName, method: method.name, async handler(data) { const request = fromBinary(method.input, data); return toBinary(method.output, create(method.output, await handler(request))); }, }; } export function withMockedBackend<Args>(mocks: readonly MethodMock[]): Decorator<Args> { const rpc = { async send(service, method, data) { for (const mock of mocks) { if (mock.service === service && mock.method === method) { return mock.handler(data); } } throw new Error(`[withMockedBackend] No mock set to "${service}.${method}"`); }, } satisfies ProtoRPC; return function (Story) { return ( <ProtoRPCProvider rpc={rpc}> <Story /> </ProtoRPCProvider> ); }; }
-
-
-
@@ -1,30 +1,20 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { Preview } from "@storybook/react"; import { type Preview } from "@storybook/react"; import { ProtoRPCProvider, type ProtoRPC, ThemeProvider } from "../src/lib.ts"; import { ThemeProvider } from "../src/lib.ts"; import css from "./preview.module.css"; const rpc = { send() { // TODO: Implement return new Promise((_resolve, reject) => { setTimeout(() => { reject(new Error("Not implemented")); }, 1_000); }); }, } satisfies ProtoRPC; import { withIDBBackend } from "./decorators/withIDBBackend.tsx"; export default { decorators: [ (Story) => ( <ProtoRPCProvider rpc={rpc}> <ThemeProvider className={css.theme}> <Story /> </ThemeProvider> </ProtoRPCProvider> <ThemeProvider className={css.theme}> <Story /> </ThemeProvider> ), withIDBBackend(), ], } satisfies Preview;
-
-
-
@@ -25,6 +25,7 @@ "@bufbuild/protobuf": "^2.2.2","@radix-ui/react-icons": "^1.3.2", "@radix-ui/themes": "^3.1.6", "@tanstack/react-query": "^5.62.8", "@yamori/idb_backend": "workspace:*", "@yamori/proto": "workspace:*", "i": "^0.3.7", "npm": "^11.0.0"
-
-
-
@@ -1,8 +1,16 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { create } from "@bufbuild/protobuf"; import { ListResponseSchema } from "@yamori/proto/yamori/workspace/v1/list_response_pb.js"; import { KeyValueStorageBasedWorkspaceService } from "@yamori/proto/yamori/workspace/v1/key_value_storage_based_workspace_service_pb.js"; import type { Meta, StoryObj } from "@storybook/react"; import { withMockedBackend, mock, } from "../../.storybook/decorators/withMockedBackend.tsx"; import { WorkspaceSelectionPage } from "./WorkspaceSelectionPage.tsx"; export default {
-
@@ -11,4 +19,91 @@ } satisfies Meta<typeof WorkspaceSelectionPage>;type Story = StoryObj<typeof WorkspaceSelectionPage>; export const Default: Story = {}; const success = create(ListResponseSchema, { result: { case: "ok", value: { workspaces: [ { id: { value: "ws-foo" }, displayName: "株式会社あああ", }, { id: { value: "ws-bar" }, displayName: "有限会社いいい", }, ], }, }, }); export const Success: Story = { decorators: [ withMockedBackend([ mock(KeyValueStorageBasedWorkspaceService.method.list, () => success), ]), ], }; export const SlowLoad: Story = { decorators: [ withMockedBackend([ mock(KeyValueStorageBasedWorkspaceService.method.list, async () => { await new Promise<void>((resolve) => { setTimeout(() => { resolve(); }, 1_000); }); return success; }), ]), ], }; export const NoWorkspaces: Story = { decorators: [ withMockedBackend([ mock(KeyValueStorageBasedWorkspaceService.method.list, () => { return create(ListResponseSchema, { result: { case: "ok", value: { workspaces: [], }, }, }); }), ]), ], }; const systemError = create(ListResponseSchema, { result: { case: "systemError", value: { code: "SAMPLE_ERR", message: "This is sample error message", }, }, }); export const ListLoadError: Story = { decorators: [ withMockedBackend([ mock(KeyValueStorageBasedWorkspaceService.method.list, () => systemError), ]), ], }; export const FlakyListLoading: Story = { decorators: [ withMockedBackend([ mock(KeyValueStorageBasedWorkspaceService.method.list, () => Math.random() >= 0.5 ? success : systemError, ), ]), ], }; export const Playground: Story = {};
-
-
-
@@ -9,8 +9,8 @@ "jsx": "react-jsx"}, "include": [ "*.ts", ".storybook/*.ts", ".storybook/*.tsx", ".storybook/**/*.ts", ".storybook/**/*.tsx", "src/**/*.ts", "src/**/*.tsx" ]
-