From bad5d3952e097a4e79b3b897586719b43d02f8ab Mon Sep 17 00:00:00 2001 From: Yx01-me Date: Fri, 28 Aug 2026 01:02:21 +0800 Subject: [PATCH 1/2] test(desktop): expand Storybook smoke theme coverage Generated-by: OpenAI Codex --- .github/workflows/ci.yml | 4 +- scripts/storybook-visual-smoke.mjs | 49 +++++++--- scripts/storybook-visual-smoke.test.mjs | 122 ++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 scripts/storybook-visual-smoke.test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8e9cc17fd..bb5ca139db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,8 +78,8 @@ jobs: - name: Test the epoch guard run: node --test --test-concurrency=1 scripts/protocol-epoch-check.test.mjs - - name: Test AX tree audit contract - run: node --test scripts/ax-tree-audit.test.mjs + - name: Test Storybook audit contracts + run: node --test scripts/ax-tree-audit.test.mjs scripts/storybook-visual-smoke.test.mjs - name: Verify ASF npm preflight policy run: npm run check:asf-npm diff --git a/scripts/storybook-visual-smoke.mjs b/scripts/storybook-visual-smoke.mjs index 9069a06211..4d5604f903 100644 --- a/scripts/storybook-visual-smoke.mjs +++ b/scripts/storybook-visual-smoke.mjs @@ -26,6 +26,8 @@ import { auditAxTree } from './ax-tree-audit.mjs'; const RENDER_VIEWPORT = Object.freeze({ width: 1280, height: 900 }); const NARROW_RENDER_VIEWPORT = Object.freeze({ width: 720, height: 900 }); +const COLOR_SCHEMES = Object.freeze(['light', 'dark']); +const FULL_PALETTE_STORY_IDS = new Set(['product-shell-official-appshell--native-conversation']); const REQUIRED_COMPUTER_USE_STORY_IDS = new Set([ 'product-accessibility-dialogs--create-scheduled-task', 'product-accessibility-dialogs--mermaid-fullscreen', @@ -119,32 +121,54 @@ function installStorybookRenderProbe({ storyId }) { connect(); } -export function catalogJobs(storyIndex) { +export function catalogJobs( + storyIndex, + { themePalettes = ['default'], fullPaletteStoryIds = FULL_PALETTE_STORY_IDS } = {}, +) { const entries = storyIndex?.entries; if (!entries || typeof entries !== 'object' || Array.isArray(entries)) { throw new Error('Built Storybook index has no entries'); } + if (!Array.isArray(themePalettes) || themePalettes.length === 0) { + throw new Error('Storybook smoke requires at least one theme palette'); + } + const palettes = [...new Set(themePalettes)]; + if (!palettes.includes('default')) { + throw new Error('Storybook smoke theme palettes must include default'); + } const jobs = Object.values(entries) .filter((entry) => entry?.type === 'story' && typeof entry.id === 'string') - .map((entry) => ({ storyId: entry.id })); + .flatMap((entry) => + (fullPaletteStoryIds.has(entry.id) ? palettes : ['default']).flatMap((palette) => + COLOR_SCHEMES.map((colorScheme) => ({ + storyId: entry.id, + colorScheme, + palette, + })), + ), + ); if (jobs.length === 0) throw new Error('Built Storybook index has no stories'); return jobs; } -export function storyUrl(baseUrl, storyId) { +export function storyUrl(baseUrl, job) { const url = new URL('/iframe.html', baseUrl); - url.searchParams.set('id', storyId); + url.searchParams.set('id', job.storyId); url.searchParams.set('viewMode', 'story'); - url.searchParams.set('globals', 'colorScheme:light'); + url.searchParams.set('globals', `colorScheme:${job.colorScheme};palette:${job.palette}`); return url.href; } +function jobLabel(job) { + return `${job.storyId} [${job.colorScheme}/${job.palette}]`; +} + export function storyViewport(storyId) { return storyId.includes('narrow') ? NARROW_RENDER_VIEWPORT : RENDER_VIEWPORT; } async function smokeStory(page, baseUrl, job, options = {}) { - const prefix = `[${job.storyId}]`; + const prefix = `[${job.storyId}][${job.colorScheme}/${job.palette}]`; const browserFailures = []; const onConsole = (message) => { if (message.type() === 'error') browserFailures.push(`console.error: ${message.text()}`); @@ -158,7 +182,7 @@ async function smokeStory(page, baseUrl, job, options = {}) { try { await page.addInitScript(installStorybookRenderProbe, { storyId: job.storyId }); await page.setViewportSize(storyViewport(job.storyId)); - await page.goto(storyUrl(baseUrl, job.storyId), { waitUntil: 'load' }); + await page.goto(storyUrl(baseUrl, job), { waitUntil: 'load' }); try { await page.waitForFunction( @@ -242,10 +266,10 @@ async function runJobs(browser, baseUrl, jobs, concurrency) { const page = await browser.newPage(); try { await smokeStory(page, baseUrl, job); - process.stdout.write(`✓ ${job.storyId}\n`); + process.stdout.write(`✓ ${jobLabel(job)}\n`); } catch (error) { failures.push(error instanceof Error ? error.message : String(error)); - process.stdout.write(`✗ ${job.storyId}\n`); + process.stdout.write(`✗ ${jobLabel(job)}\n`); } finally { await page.close(); } @@ -312,7 +336,8 @@ async function runCli() { const repoRoot = resolve(scriptDir, '..'); const staticDir = resolve(process.argv[2] ?? join(repoRoot, 'apps/desktop/storybook-static')); const storyIndex = await readFile(join(staticDir, 'index.json'), 'utf8').then(JSON.parse); - const jobs = catalogJobs(storyIndex); + const { THEME_PALETTES } = await import('@maka/core/settings'); + const jobs = catalogJobs(storyIndex, { themePalettes: THEME_PALETTES }); const storyIds = new Set(jobs.map((job) => job.storyId)); const missingRequiredStories = [...REQUIRED_COMPUTER_USE_STORY_IDS].filter( (storyId) => !storyIds.has(storyId), @@ -335,7 +360,9 @@ async function runCli() { if (problems.length > 0) { throw new Error(`${problems.length} story render(s) failed:\n${problems.join('\n')}`); } - process.stdout.write(`Storybook render smoke passed (${jobs.length} stories).\n`); + process.stdout.write( + `Storybook render smoke passed (${jobs.length} renders across ${storyIds.size} stories).\n`, + ); } if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) { diff --git a/scripts/storybook-visual-smoke.test.mjs b/scripts/storybook-visual-smoke.test.mjs new file mode 100644 index 0000000000..3eddcd2019 --- /dev/null +++ b/scripts/storybook-visual-smoke.test.mjs @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { catalogJobs, storyUrl } from './storybook-visual-smoke.mjs'; + +const REFERENCE_STORY_ID = 'product-shell-official-appshell--native-conversation'; +const THEME_PALETTES = [ + 'default', + ...Array.from({ length: 10 }, (_, index) => `test-palette-${index + 1}`), +]; + +function storyIndex(...storyIds) { + return { + entries: Object.fromEntries( + storyIds.map((storyId) => [storyId, { id: storyId, type: 'story' }]), + ), + }; +} + +test('ordinary catalog stories render the default palette in both colour schemes', () => { + assert.deepEqual( + catalogJobs(storyIndex('product-settings--memory'), { themePalettes: THEME_PALETTES }), + [ + { + storyId: 'product-settings--memory', + colorScheme: 'light', + palette: 'default', + }, + { + storyId: 'product-settings--memory', + colorScheme: 'dark', + palette: 'default', + }, + ], + ); +}); + +test('the reference story renders every palette in both colour schemes', () => { + const jobs = catalogJobs(storyIndex(REFERENCE_STORY_ID), { + themePalettes: THEME_PALETTES, + }); + + assert.equal(jobs.length, 22); + assert.equal(new Set(jobs.map((job) => `${job.colorScheme}/${job.palette}`)).size, 22); + assert.deepEqual(jobs.slice(0, 4), [ + { storyId: REFERENCE_STORY_ID, colorScheme: 'light', palette: 'default' }, + { storyId: REFERENCE_STORY_ID, colorScheme: 'dark', palette: 'default' }, + { + storyId: REFERENCE_STORY_ID, + colorScheme: 'light', + palette: 'test-palette-1', + }, + { + storyId: REFERENCE_STORY_ID, + colorScheme: 'dark', + palette: 'test-palette-1', + }, + ]); +}); + +test('a mixed catalog adds only twenty renders for full palette coverage', () => { + const storyIds = ['product-settings--memory', REFERENCE_STORY_ID, 'design-system--button']; + const jobs = catalogJobs(storyIndex(...storyIds), { themePalettes: THEME_PALETTES }); + + assert.equal(jobs.length, 2 * storyIds.length + 20); + assert.deepEqual(new Set(jobs.map((job) => job.storyId)), new Set(storyIds)); +}); + +test('duplicate palette ids do not duplicate render jobs', () => { + const jobs = catalogJobs(storyIndex(REFERENCE_STORY_ID), { + themePalettes: ['default', 'onedark', 'default', 'onedark'], + }); + + assert.equal(jobs.length, 4); +}); + +test('catalog jobs require a non-empty palette inventory containing default', () => { + assert.throws( + () => catalogJobs(storyIndex('product-settings--memory'), { themePalettes: [] }), + /at least one theme palette/, + ); + assert.throws( + () => + catalogJobs(storyIndex('product-settings--memory'), { + themePalettes: ['onedark'], + }), + /must include default/, + ); +}); + +test('story URLs encode the selected colour scheme and palette', () => { + const url = new URL( + storyUrl('http://127.0.0.1:6006', { + storyId: REFERENCE_STORY_ID, + colorScheme: 'dark', + palette: 'tokyo-night', + }), + ); + + assert.equal(url.pathname, '/iframe.html'); + assert.equal(url.searchParams.get('id'), REFERENCE_STORY_ID); + assert.equal(url.searchParams.get('viewMode'), 'story'); + assert.equal(url.searchParams.get('globals'), 'colorScheme:dark;palette:tokyo-night'); +}); From 19705c601ad99b1503aa7996f8104d598625682d Mon Sep 17 00:00:00 2001 From: Yx01-me Date: Sun, 30 Aug 2026 00:57:51 +0800 Subject: [PATCH 2/2] fix(desktop): build core before Storybook smoke --- apps/desktop/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 7551ebbc5c..2a29cfa96c 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -15,6 +15,7 @@ "dev:hmr": "node scripts/dev.mjs", "storybook": "storybook dev -p 6006 -c .storybook", "build-storybook": "storybook build -c .storybook --output-dir storybook-static", + "presmoke:storybook": "npm --workspace @maka/core run build", "smoke:storybook": "node ../../scripts/storybook-visual-smoke.mjs", "build": "npm run build:resources && npm run build:main && npm run build:preload && npm run build:overlay && npm run build:renderer", "build:resources": "node scripts/copy-runtime-filesystem-worker.mjs",