From f6c622e4f2fe710867d288736f51b3ad3fde9203 Mon Sep 17 00:00:00 2001 From: Zlodej43sm <8863227+Zlodej43sm@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:26:31 +0200 Subject: [PATCH 1/3] refactor: add Components parent tier to Web Components Storybook structure Nest Atoms, Molecules, and Layout & Structure under a Components parent category in Web Components Storybook. Add migration utilities for redirecting legacy and interim tier URLs to the new nested structure. Update story paths, link validation, smoke tests, and documentation to reflect the Components/tier/component hierarchy. --- bin/storybook/check-links.mjs | 6 +- bin/storybook/link-validation.test.mjs | 22 +++++-- bin/storybook/native-checks.mjs | 65 ++++++++++--------- bin/storybook/routes.d.mts | 3 + bin/storybook/routes.mjs | 38 ++++++++++- bin/storybook/routes.test.mjs | 79 +++++++++++++++++++++-- bin/storybook/smoke-test.mjs | 6 +- bin/storybook/story-parity.mjs | 8 +-- libs/ui/.storybook/main.ts | 17 ++++- libs/ui/.storybook/preview.ts | 7 +- libs/ui/llms.txt | 2 +- libs/ui/src/ai/README.md | 2 +- libs/web-components/.storybook/main.ts | 11 +++- libs/web-components/.storybook/preview.ts | 2 +- libs/web-components/README.md | 66 +++++++++---------- 15 files changed, 236 insertions(+), 98 deletions(-) diff --git a/bin/storybook/check-links.mjs b/bin/storybook/check-links.mjs index 52752f4..fb341cb 100644 --- a/bin/storybook/check-links.mjs +++ b/bin/storybook/check-links.mjs @@ -3,7 +3,7 @@ import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from 'node import { tmpdir } from 'node:os'; import { extname, join, relative, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { legacyReactIds, sharedDocIds } from './routes.mjs'; +import { legacyReactIds, sharedDocIds, migratedReactId } from './routes.mjs'; import { validateLinks } from './link-validation.mjs'; const root = fileURLToPath(new URL('../../', import.meta.url)); @@ -49,7 +49,7 @@ if (built) { const errors = []; for (const id of legacyReactIds) { - const canonical = sharedDocIds.includes(id) ? id : `react-${id}`; + const canonical = sharedDocIds.includes(id) ? id : migratedReactId(id); if (!indexes.react.entries[canonical]) errors.push(`Missing migrated entry: ${canonical}`); } for (const id of sharedDocIds) { @@ -74,7 +74,7 @@ for (const [group, names] of [ for (const name of names) { const primaryStory = name === 'input' ? 'primary-default-with-label-and-helper-text' : 'default'; for (const variant of ['docs', primaryStory]) { - const id = `${group}-${name}--${variant}`; + const id = `components-${group}-${name}--${variant}`; if (!indexes.webComponents.entries[id]) errors.push(`Missing native Web Components entry: ${id}`); } } diff --git a/bin/storybook/link-validation.test.mjs b/bin/storybook/link-validation.test.mjs index fb8bc5d..abec66f 100644 --- a/bin/storybook/link-validation.test.mjs +++ b/bin/storybook/link-validation.test.mjs @@ -5,35 +5,43 @@ import { validateLinks } from './link-validation.mjs'; const indexes = { react: { entries: { - 'react-atoms-button--docs': { type: 'docs' }, + 'react-components-atoms-button--docs': { type: 'docs' }, 'theme-tokens-colors--docs': { type: 'docs' }, }, }, - webComponents: { entries: { 'introduction-overview--overview': { type: 'story' } } }, + webComponents: { + entries: { + 'introduction-overview--overview': { type: 'story' }, + 'components-atoms-button--default': { type: 'story' }, + }, + }, }; test('validates canonical, composed and child deep links', () => { const result = validateLinks( ` - [Button](?path=/docs/react-atoms-button--docs) + [Button](?path=/docs/react-components-atoms-button--docs) [Colors](?path=/docs/theme-tokens-colors--docs) https://storybook.cto-rnd-system-design.griddynamics.net/web-components/?path=/story/introduction-overview--overview ?path=/story/web-components_introduction-overview--overview - iframe.html?id=react-atoms-button--docs&viewMode=docs + ?path=/story/web-components_components-atoms-button--default + iframe.html?id=react-components-atoms-button--docs&viewMode=docs `, indexes ); - assert.equal(result.checked, 5); + assert.equal(result.checked, 6); assert.deepEqual(result.errors, []); }); test('catches stale, nonexistent, incorrectly typed and bare routes', () => { for (const href of [ '?path=/docs/atoms-button--docs', + '?path=/docs/react-atoms-button--docs', + '?path=/story/web-components_atoms-button--default', '?path=/docs/react-nonexistent--docs', '?path=/docs/react-theme-tokens-colors--docs', - '?path=/story/react-atoms-button--docs', - '/docs/react-atoms-button--docs', + '?path=/story/react-components-atoms-button--docs', + '/docs/react-components-atoms-button--docs', ]) assert.ok(validateLinks(href, indexes).errors.length > 0, href); }); diff --git a/bin/storybook/native-checks.mjs b/bin/storybook/native-checks.mjs index d37fbf7..58db51d 100644 --- a/bin/storybook/native-checks.mjs +++ b/bin/storybook/native-checks.mjs @@ -6,7 +6,8 @@ export async function checkNativeStories(page, base) { const stories = Object.values(index.entries).filter( (entry) => entry.type === 'story' && - (/^(Atoms|Molecules)\//.test(entry.title) || entry.title.startsWith('Layout & Structure/')) && + (/^Components\/(Atoms|Molecules)\//.test(entry.title) || + entry.title.startsWith('Components/Layout & Structure/')) && !entry.id.endsWith('--default-tokens') ); const names = new Set(); @@ -17,7 +18,7 @@ export async function checkNativeStories(page, base) { sliderdots: 'slider-dots', }; for (const story of stories) { - const name = story.title.split('/')[1].toLowerCase(); + const name = story.title.split('/')[2].toLowerCase(); names.add(name); await page.goto(`${base}/web-components/iframe.html?id=${story.id}&viewMode=story`); const tag = `gd-${tags[name] ?? name}`; @@ -105,7 +106,7 @@ export async function checkNativeStories(page, base) { }), detail ); - await open('layout-structure-chatcontainer--interactive-demo'); + await open('components-layout-structure-chatcontainer--interactive-demo'); const chatContainer = page.locator('gd-chat-container'); await page.waitForFunction(() => document.querySelector('gd-chat-container')?.isOpen === true); await chatContainer.evaluate((element) => element.close()); @@ -120,7 +121,7 @@ export async function checkNativeStories(page, base) { }) ); assert.deepEqual(await toggleEvent, { open: false }); - await open('layout-structure-column--default'); + await open('components-layout-structure-column--default'); assert.equal( await page .locator('gd-column') @@ -128,16 +129,16 @@ export async function checkNativeStories(page, base) { .evaluate((element) => getComputedStyle(element.shadowRoot.querySelector('[part="root"]')).flexDirection), 'column' ); - await open('layout-structure-row--default'); + await open('components-layout-structure-row--default'); assert.equal(await page.locator('gd-row').evaluate((element) => element.getAttribute('display')), 'inline-flex'); - await open('layout-structure-flexcontainer--default'); + await open('components-layout-structure-flexcontainer--default'); assert.equal( await page .locator('gd-flex-container') .evaluate((element) => getComputedStyle(element.shadowRoot.querySelector('[part="root"]')).display), 'flex' ); - await open('layout-structure-portal--rendering-in-target'); + await open('components-layout-structure-portal--rendering-in-target'); await page.getByText('Show Portal', { exact: true }).click(); assert.equal( await page @@ -147,7 +148,7 @@ export async function checkNativeStories(page, base) { 1 ); assert.equal(await page.locator('gd-portal').evaluate((element) => element.portalRoot), null); - await open('layout-structure-portal--with-wrapper-as-tag-main'); + await open('components-layout-structure-portal--with-wrapper-as-tag-main'); await page.getByText('Show Portal', { exact: true }).click(); assert.equal(await page.locator('gd-portal').evaluate((element) => element.portalRoot?.tagName), 'MAIN'); assert.deepEqual( @@ -159,7 +160,7 @@ export async function checkNativeStories(page, base) { ); await page.getByText('Hide', { exact: true }).click(); assert.equal(await page.locator('gd-portal').count(), 0); - await open('layout-structure-scroll--interactive-example'); + await open('components-layout-structure-scroll--interactive-example'); const scroll = page.locator('gd-scroll'); await page.waitForFunction(() => { const element = document.querySelector('gd-scroll'); @@ -226,7 +227,7 @@ export async function checkNativeStories(page, base) { await page.mouse.move(trackBox.x + trackBox.width / 2, trackBox.y + trackBox.height - 2); await page.mouse.up(); await page.waitForFunction(() => document.querySelector('gd-scroll').scrollTop > 100); - await open('layout-structure-scroll--vertical-scroll-only'); + await open('components-layout-structure-scroll--vertical-scroll-only'); await page.waitForFunction(() => document.querySelector('gd-scroll')?.scrollHeight > 198); assert.deepEqual( await page.locator('gd-scroll').evaluate((element) => ({ @@ -236,7 +237,7 @@ export async function checkNativeStories(page, base) { })), { extent: [298, 500], vertical: true, horizontal: false } ); - await open('layout-structure-scroll--horizontal-scroll-only'); + await open('components-layout-structure-scroll--horizontal-scroll-only'); await page.waitForFunction(() => document.querySelector('gd-scroll')?.scrollWidth > 298); assert.deepEqual( await page.locator('gd-scroll').evaluate((element) => ({ @@ -246,7 +247,7 @@ export async function checkNativeStories(page, base) { })), { extent: [1020, 198], vertical: false, horizontal: true } ); - await open('layout-structure-scroll--auto-hide-scrollbars'); + await open('components-layout-structure-scroll--auto-hide-scrollbars'); await page.waitForFunction(() => document.querySelector('gd-scroll')?.shadowRoot?.querySelector('[part~="vertical-scrollbar"]') ); @@ -268,7 +269,7 @@ export async function checkNativeStories(page, base) { ); await page.waitForTimeout(1250); assert.equal(await autoHideScrollbar.evaluate((element) => getComputedStyle(element).opacity), '0'); - await open('layout-structure-scroll--auto-hide-vertical-only'); + await open('components-layout-structure-scroll--auto-hide-vertical-only'); await page.waitForFunction(() => document.querySelector('gd-scroll')?.scrollHeight === 980); assert.deepEqual( await page.locator('gd-scroll').evaluate((element) => ({ @@ -278,10 +279,10 @@ export async function checkNativeStories(page, base) { })), { extent: [298, 980], vertical: true, horizontal: false } ); - await open('atoms-button--disabled-button'); + await open('components-atoms-button--disabled-button'); await page.getByRole('button', { name: 'Disabled Button', exact: true }).waitFor(); assert.ok(await page.getByRole('button', { name: 'Disabled Button', exact: true }).isDisabled()); - await open('atoms-input--primary-default-with-label-and-helper-text'); + await open('components-atoms-input--primary-default-with-label-and-helper-text'); // The story's play function types "hello" after render; wait for it so it cannot overwrite the fill below. await page.waitForFunction( () => document.querySelector('gd-input')?.shadowRoot?.querySelector('input')?.value === 'hello' @@ -289,14 +290,14 @@ export async function checkNativeStories(page, base) { // The wrapping