From 67b33d9b8f488191adecb8ebe354a0a7487f4c6a Mon Sep 17 00:00:00 2001 From: Alex Yang Date: Tue, 15 Aug 2023 15:34:02 -0500 Subject: [PATCH] feat(storybook): preview app (#3765) --- .github/workflows/build.yml | 11 ++- apps/core/package.json | 5 ++ apps/core/src/bootstrap/setup.ts | 7 ++ .../block-suite-mode-switch/switch-items.tsx | 6 +- apps/core/src/router.ts | 76 +++++++++---------- apps/core/tsconfig.json | 2 +- apps/storybook/.storybook/main.ts | 1 + apps/storybook/.storybook/preview.tsx | 32 +++----- apps/storybook/package.json | 6 +- apps/storybook/src/stories/core.stories.tsx | 55 ++++++++++++++ .../src/stories/page-list.stories.tsx | 10 +-- apps/storybook/tsconfig.json | 3 + apps/storybook/tsconfig.node.json | 3 +- tsconfig.json | 1 + yarn.lock | 37 ++++++++- 15 files changed, 179 insertions(+), 76 deletions(-) create mode 100644 apps/storybook/src/stories/core.stories.tsx diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d099d6edb9..d1f0e0dd92 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -147,10 +147,13 @@ jobs: uses: ./.github/actions/setup-node with: electron-install: false - - run: yarn chromatic - working-directory: apps/storybook - env: - CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + - name: Publish to Chromatic + uses: chromaui/action@v1 + with: + workingDir: apps/storybook + buildScriptName: build + onlyStoryNames: Preview/Core + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} build-core: name: Build @affine/core diff --git a/apps/core/package.json b/apps/core/package.json index dbdac75902..10d2a22728 100644 --- a/apps/core/package.json +++ b/apps/core/package.json @@ -8,6 +8,11 @@ "dev": "yarn -T run dev-core", "static-server": "ts-node-esm ./server.mts" }, + "exports": { + "./app": "./src/app.tsx", + "./router": "./src/router.ts", + "./bootstrap/setup": "./src/bootstrap/setup.ts" + }, "dependencies": { "@affine-test/fixtures": "workspace:*", "@affine/component": "workspace:*", diff --git a/apps/core/src/bootstrap/setup.ts b/apps/core/src/bootstrap/setup.ts index e7b9f096e5..3005e7ba5f 100644 --- a/apps/core/src/bootstrap/setup.ts +++ b/apps/core/src/bootstrap/setup.ts @@ -169,7 +169,14 @@ function createFirstAppData() { rootStore.set(rootWorkspacesMetadataAtom, result); } +let isSetup = false; + export async function setup() { + if (isSetup) { + console.warn('already setup'); + return; + } + isSetup = true; rootStore.set( workspaceAdaptersAtom, WorkspaceAdapters as Record< diff --git a/apps/core/src/components/blocksuite/block-suite-mode-switch/switch-items.tsx b/apps/core/src/components/blocksuite/block-suite-mode-switch/switch-items.tsx index 8244f8973f..7d738acce1 100644 --- a/apps/core/src/components/blocksuite/block-suite-mode-switch/switch-items.tsx +++ b/apps/core/src/components/blocksuite/block-suite-mode-switch/switch-items.tsx @@ -3,6 +3,8 @@ import type { HTMLAttributes } from 'react'; import type React from 'react'; import { cloneElement, useState } from 'react'; +import edgelessHover from './animation-data/edgeless-hover.json'; +import pageHover from './animation-data/page-hover.json'; import { StyledSwitchItem } from './style'; type HoverAnimateControllerProps = { @@ -52,7 +54,7 @@ export const PageSwitchItem = ( options={{ loop: false, autoplay: false, - animationData: require('./animation-data/page-hover.json'), + animationData: pageHover, rendererSettings: { preserveAspectRatio: 'xMidYMid slice', }, @@ -71,7 +73,7 @@ export const EdgelessSwitchItem = ( options={{ loop: false, autoplay: false, - animationData: require('./animation-data/edgeless-hover.json'), + animationData: edgelessHover, rendererSettings: { preserveAspectRatio: 'xMidYMid slice', }, diff --git a/apps/core/src/router.ts b/apps/core/src/router.ts index 0d438ede0d..672bcbf5e2 100644 --- a/apps/core/src/router.ts +++ b/apps/core/src/router.ts @@ -1,41 +1,41 @@ +import type { RouteObject } from 'react-router-dom'; import { createBrowserRouter } from 'react-router-dom'; -export const router = createBrowserRouter( - [ - { - path: '/', - lazy: () => import('./pages/index'), - }, - { - path: '/workspace/:workspaceId', - lazy: () => import('./pages/workspace/index'), - children: [ - { - path: 'all', - lazy: () => import('./pages/workspace/all-page'), - }, - { - path: 'trash', - lazy: () => import('./pages/workspace/trash-page'), - }, - { - path: ':pageId', - lazy: () => import('./pages/workspace/detail-page'), - }, - ], - }, - { - path: '/404', - lazy: () => import('./pages/404'), - }, - { - path: '*', - lazy: () => import('./pages/404'), - }, - ], +export const routes = [ { - future: { - v7_normalizeFormMethod: true, - }, - } -); + path: '/', + lazy: () => import('./pages/index'), + }, + { + path: '/workspace/:workspaceId', + lazy: () => import('./pages/workspace/index'), + children: [ + { + path: 'all', + lazy: () => import('./pages/workspace/all-page'), + }, + { + path: 'trash', + lazy: () => import('./pages/workspace/trash-page'), + }, + { + path: ':pageId', + lazy: () => import('./pages/workspace/detail-page'), + }, + ], + }, + { + path: '/404', + lazy: () => import('./pages/404'), + }, + { + path: '*', + lazy: () => import('./pages/404'), + }, +] satisfies [RouteObject, ...RouteObject[]]; + +export const router = createBrowserRouter(routes, { + future: { + v7_normalizeFormMethod: true, + }, +}); diff --git a/apps/core/tsconfig.json b/apps/core/tsconfig.json index eb0b796783..1809b66040 100644 --- a/apps/core/tsconfig.json +++ b/apps/core/tsconfig.json @@ -5,7 +5,7 @@ "typeRoots": ["../../node_modules", "../../node_modules/@types"], "types": ["webpack-env", "ses", "affine__env"] }, - "include": ["src/**/*.ts", "src/**/*.tsx"], + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.json"], "exclude": ["node_modules"], "references": [ { diff --git a/apps/storybook/.storybook/main.ts b/apps/storybook/.storybook/main.ts index 817bb1865c..aa5d7ef2c6 100644 --- a/apps/storybook/.storybook/main.ts +++ b/apps/storybook/.storybook/main.ts @@ -28,6 +28,7 @@ export default { '@storybook/addon-interactions', '@storybook/addon-storysource', 'storybook-dark-mode', + 'storybook-addon-react-router-v6', ], framework: { name: '@storybook/react-vite', diff --git a/apps/storybook/.storybook/preview.tsx b/apps/storybook/.storybook/preview.tsx index 4c70a7ce5f..89525d8bee 100644 --- a/apps/storybook/.storybook/preview.tsx +++ b/apps/storybook/.storybook/preview.tsx @@ -1,13 +1,16 @@ import '@affine/component/theme/global.css'; import '@affine/component/theme/theme.css'; -import { LOCALES, createI18n } from '@affine/i18n'; +import '@toeverything/components/style.css'; +import { createI18n } from '@affine/i18n'; import { ThemeProvider, useTheme } from 'next-themes'; -import { setupGlobal } from '@affine/env/global'; import type { ComponentType } from 'react'; import { useEffect } from 'react'; import { useDarkMode } from 'storybook-dark-mode'; +import { setup } from '@affine/core/bootstrap/setup'; +import { AffineContext } from '@affine/component/context'; +import { use } from 'foxact/use'; -setupGlobal(); +const setupPromise = setup(); export const parameters = { backgrounds: { disable: true }, @@ -20,22 +23,6 @@ export const parameters = { }, }; -export const globalTypes = { - locale: { - name: 'Locale', - description: 'Internationalization locale', - defaultValue: 'en', - toolbar: { - icon: 'globe', - items: LOCALES.map(locale => ({ - title: locale.originalName, - value: locale.tag, - right: locale.flagEmoji, - })), - }, - }, -}; - const createI18nDecorator = () => { const i18n = createI18n(); const withI18n = (Story: any, context: any) => { @@ -59,10 +46,13 @@ const Component = () => { export const decorators = [ (Story: ComponentType) => { + use(setupPromise); return ( - - + + + + ); }, diff --git a/apps/storybook/package.json b/apps/storybook/package.json index d3824e99b0..8c958d820d 100644 --- a/apps/storybook/package.json +++ b/apps/storybook/package.json @@ -4,8 +4,7 @@ "scripts": { "dev": "storybook dev -p 6006", "build": "storybook build", - "test": "test-storybook", - "chromatic": "npx chromatic --build-script-name build" + "test": "test-storybook" }, "dependencies": { "@affine/component": "workspace:*", @@ -40,7 +39,8 @@ "@blocksuite/store": "0.0.0-20230814155455-ceb5d5d8-nightly", "chromatic": "^6.22.0", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "storybook-addon-react-router-v6": "^2.0.4" }, "peerDependencies": { "@blocksuite/blocks": "*", diff --git a/apps/storybook/src/stories/core.stories.tsx b/apps/storybook/src/stories/core.stories.tsx new file mode 100644 index 0000000000..e8023e3e8a --- /dev/null +++ b/apps/storybook/src/stories/core.stories.tsx @@ -0,0 +1,55 @@ +import { routes } from '@affine/core/router'; +import { expect } from '@storybook/jest'; +import type { StoryContext, StoryFn } from '@storybook/react'; +import { userEvent } from '@storybook/testing-library'; +import { Outlet, useLocation } from 'react-router-dom'; +import { + reactRouterOutlets, + reactRouterParameters, + withRouter, +} from 'storybook-addon-react-router-v6'; + +const withCleanLocalStorage = (Story: StoryFn, context: StoryContext) => { + localStorage.clear(); + return ; +}; + +const FakeApp = () => { + const location = useLocation(); + // fixme: `key` is a hack to force the storybook to re-render the outlet + return ; +}; + +export default { + title: 'Preview/Core', +}; + +export const Index: StoryFn = () => { + return ; +}; + +Index.decorators = [withRouter, withCleanLocalStorage]; +Index.parameters = { + reactRouter: reactRouterParameters({ + routing: reactRouterOutlets(routes), + }), +}; + +export const SettingPage: StoryFn = () => { + return ; +}; + +SettingPage.play = async ({ canvasElement }) => { + await new Promise(resolve => setTimeout(resolve, 1000)); + const settingModalBtn = canvasElement.querySelector( + '[data-testid="settings-modal-trigger"]' + ) as Element; + expect(settingModalBtn).not.toBeNull(); + await userEvent.click(settingModalBtn); +}; +SettingPage.decorators = [withRouter, withCleanLocalStorage]; +SettingPage.parameters = { + reactRouter: reactRouterParameters({ + routing: reactRouterOutlets(routes), + }), +}; diff --git a/apps/storybook/src/stories/page-list.stories.tsx b/apps/storybook/src/stories/page-list.stories.tsx index 116e07e19e..7a9086dee3 100644 --- a/apps/storybook/src/stories/page-list.stories.tsx +++ b/apps/storybook/src/stories/page-list.stories.tsx @@ -34,7 +34,7 @@ AffineOperationCell.play = async ({ canvasElement }) => { '[data-testid="page-list-operation-button"]' ) as HTMLButtonElement; expect(button).not.toBeNull(); - userEvent.click(button); + await userEvent.click(button); } }; @@ -51,7 +51,7 @@ AffineNewPageButton.play = async ({ canvasElement }) => { expect(button).not.toBeNull(); const dropdown = button.querySelector('svg') as SVGSVGElement; expect(dropdown).not.toBeNull(); - userEvent.click(dropdown); + await userEvent.click(dropdown); }; export const AffineAllPageList: StoryFn = ({ ...props }) => ( @@ -69,11 +69,11 @@ AffineAllPageList.args = { favorite: false, icon: , isPublicPage: true, - title: 'Today Page', + title: 'Last Page', tags: [], preview: 'this is page preview', - createDate: new Date(), - updatedDate: new Date(), + createDate: new Date('2021-01-01'), + updatedDate: new Date('2023-08-15'), bookmarkPage: () => toast('Bookmark page'), onClickPage: () => toast('Click page'), onDisablePublicSharing: () => toast('Disable public sharing'), diff --git a/apps/storybook/tsconfig.json b/apps/storybook/tsconfig.json index 5bc6d833a6..c1f080046c 100644 --- a/apps/storybook/tsconfig.json +++ b/apps/storybook/tsconfig.json @@ -9,6 +9,9 @@ "outDir": "lib" }, "references": [ + { + "path": "../../apps/core" + }, { "path": "../../packages/component" }, diff --git a/apps/storybook/tsconfig.node.json b/apps/storybook/tsconfig.node.json index b6c1c2b1ac..2c5839ca72 100644 --- a/apps/storybook/tsconfig.node.json +++ b/apps/storybook/tsconfig.node.json @@ -4,7 +4,7 @@ "composite": true, "module": "ESNext", "jsx": "react-jsx", - "moduleResolution": "Node", + "moduleResolution": "bundler", "esModuleInterop": true, "allowSyntheticDefaultImports": true, "noEmit": false, @@ -13,6 +13,7 @@ "include": [".storybook/**/*"], "exclude": ["lib"], "references": [ + { "path": "../../apps/core" }, { "path": "../../packages/i18n" }, { "path": "../../packages/env" diff --git a/tsconfig.json b/tsconfig.json index 83e4fc568b..e1317741ae 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -55,6 +55,7 @@ "skipLibCheck": true, // skip all type checks for .d.ts files "paths": { + "@affine/core/*": ["./packages/core/src/*"], "@affine/cli/*": ["./packages/cli/src/*"], "@affine/component": ["./packages/component/src/index"], "@affine/component/*": [ diff --git a/yarn.lock b/yarn.lock index 2448ce5d13..2fcf4b5a52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -709,6 +709,7 @@ __metadata: react-dom: 18.2.0 serve: ^14.2.0 storybook: ^7.2.3 + storybook-addon-react-router-v6: ^2.0.4 storybook-dark-mode: ^3.0.1 wait-on: ^7.0.1 peerDependencies: @@ -15874,6 +15875,13 @@ __metadata: languageName: node linkType: hard +"compare-versions@npm:^6.0.0": + version: 6.1.0 + resolution: "compare-versions@npm:6.1.0" + checksum: d4e2a45706a023d8d0b6680338b66b79e20bd02d1947f0ac6531dab634cbed89fa373b3f03d503c5e489761194258d6e1bae67a07f88b1efc61648454f2d47e7 + languageName: node + linkType: hard + "component-emitter@npm:^1.3.0": version: 1.3.0 resolution: "component-emitter@npm:1.3.0" @@ -27724,7 +27732,7 @@ __metadata: languageName: node linkType: hard -"react-inspector@npm:^6.0.0": +"react-inspector@npm:6.0.2, react-inspector@npm:^6.0.0": version: 6.0.2 resolution: "react-inspector@npm:6.0.2" peerDependencies: @@ -29775,6 +29783,33 @@ __metadata: languageName: node linkType: hard +"storybook-addon-react-router-v6@npm:^2.0.4": + version: 2.0.4 + resolution: "storybook-addon-react-router-v6@npm:2.0.4" + dependencies: + compare-versions: ^6.0.0 + react-inspector: 6.0.2 + peerDependencies: + "@storybook/blocks": ^7.0.0 + "@storybook/channels": ^7.0.0 + "@storybook/components": ^7.0.0 + "@storybook/core-events": ^7.0.0 + "@storybook/manager-api": ^7.0.0 + "@storybook/preview-api": ^7.0.0 + "@storybook/theming": ^7.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-router: ^6.4.0 + react-router-dom: ^6.4.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + checksum: 2920eff2f15b84bc57c0260286d656373130dfdd25c4299319da94e427e2dcf45bf8a73221b47818d470e029d2588d60d498b0163b50b7d6db17253ffa36d11f + languageName: node + linkType: hard + "storybook-dark-mode@npm:^3.0.1": version: 3.0.1 resolution: "storybook-dark-mode@npm:3.0.1"