-
Notifications
You must be signed in to change notification settings - Fork 0
test: harden project guidance, coverage, and story states #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
62d5add
docs: align project guidance and test configuration
sepaseh e51cc81
test: expand coverage and story states
sepaseh 9160c33
test: harden Storybook and workflow validation
sepaseh fddec61
fix: address automated review feedback
sepaseh 04ec6c1
refactor: derive theme palette from Ant Design tokens
sepaseh 76e560e
fix: preserve loading story user state
sepaseh 25bc6cf
fix: synchronize Playwright lockfile
sepaseh 0588424
test: scope Storybook contrast checks to product palettes
sepaseh 616d374
test: align E2E accessibility with starter palette
sepaseh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,4 +33,4 @@ blob-report | |
| *.sw? | ||
|
|
||
| # AI assistant local settings | ||
| .ai/settings.local.json | ||
| .agents/settings.local.json | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # AI assistant entry point | ||
|
|
||
| Before changing this repository, read `.agents/README.md` and the relevant files | ||
| under `.agents/rules/`. Treat `.agents/settings.json` as the machine-readable | ||
| index of those rules and optional hooks. | ||
|
|
||
| Use repository scripts for validation, preserve user changes, and never commit, | ||
| push, edit secrets, or contact production services without explicit user | ||
| authorization. Start with the smallest relevant check and follow | ||
| `CONTRIBUTING.md` before preparing a pull request. | ||
|
|
||
| The documentation map in `docs/README.md` identifies the source of truth for | ||
| each project subsystem. Update the corresponding document whenever behavior, | ||
| configuration, test coverage, or operational workflow changes. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # Documentation map | ||
|
|
||
| This page maps every maintained project area to its source of truth. A change | ||
| that introduces a new subsystem, workflow, environment variable, or public | ||
| contract must add or update an entry here. | ||
|
|
||
| | Area | Primary documentation | Implementation and configuration | | ||
| | -------------------------------------------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------- | | ||
| | Setup, scripts, and local development | [Development](development.md) | `package.json`, `.nvmrc`, `.env.example`, Vite and TypeScript configs | | ||
| | Application structure and boundaries | [Architecture](architecture.md) | `src/app`, `src/features`, `src/layouts`, `src/shared`, `eslint.config.ts` | | ||
| | Routes, permissions, localization, theme, fonts, and storage | [Architecture](architecture.md) | `src/app`, `src/shared/config`, `src/shared/i18n`, `src/shared/storage`, `src/assets` | | ||
| | HTTP, authentication, token refresh, and endpoint contracts | [API client](api-client.md) | `src/shared/api`, feature `api.ts` and `types.ts` files | | ||
| | Unit, component, integration, browser, accessibility, visual, and Storybook testing | [Testing](testing.md) | Vitest, Playwright, `.storybook`, `e2e`, and `src/test` | | ||
| | Consumer contracts and mutation testing | [Mutation and contract testing](contract-testing.md) | `contract`, `vitest.contract.config.ts`, `stryker.config.json` | | ||
| | Build, environment validation, nginx, security headers, observability, and performance | [Deployment](deployment.md) | `vite.config.ts`, `nginx.conf`, `scripts`, `smoke` | | ||
| | Staging validation | [Staging](staging.md) | `staging`, `staging.config.ts`, staging workflow | | ||
| | CI checks and merge policy | [Branch protection](branch-protection.md) | CI and CodeQL workflows, `CODEOWNERS`, GitHub branch settings | | ||
| | Versioning and release automation | [Versioning and releases](releasing.md) | Release workflow, Release Please config, changelog and manifest | | ||
| | Production approval, promotion, rollback, and incident follow-up template | [Release operations](release-operations.md) | Deployment-smoke workflow and downstream environment controls | | ||
| | Static analysis | [SonarQube](sonarqube.md) | `sonar-project.properties` and advisory CI scan | | ||
| | Security policy and assurance | [Security policy](../SECURITY.md) and `security/` reviews | CodeQL and authorized DAST workflows, threat model | | ||
| | AI assistant rules and safety hooks | [AI guidance](../.agents/README.md) | `AGENTS.md`, `.agents/settings.json`, `.agents/rules`, `.agents/hooks` | | ||
|
|
||
| Generated output directories such as `dist`, `coverage`, `pacts`, Storybook, | ||
| Lighthouse, mutation, and Playwright reports are intentionally excluded from | ||
| version control. Their producers and retention behavior are documented in the | ||
| testing, contract-testing, deployment, and workflow documents above. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,45 +1,68 @@ | ||
| import console from "node:console"; | ||
| import { readdir, stat } from "node:fs/promises"; | ||
| import path from "node:path"; | ||
| import process from "node:process"; | ||
| import { fileURLToPath } from "node:url"; | ||
|
|
||
| const assetsDirectory = path.resolve("dist/assets"); | ||
| const maximumChunkBytes = 450_000; | ||
| const maximumTotalBytes = 1_600_000; | ||
| export const maximumChunkBytes = 450_000; | ||
| export const maximumTotalBytes = 1_600_000; | ||
|
|
||
| const assetNames = await readdir(assetsDirectory); | ||
| const javascriptAssets = assetNames.filter((name) => name.endsWith(".js")); | ||
| export const evaluateBundleAssets = (assets) => { | ||
| if (assets.length === 0) throw new Error("No JavaScript assets found"); | ||
|
|
||
| if (javascriptAssets.length === 0) { | ||
| throw new Error(`No JavaScript assets found in ${assetsDirectory}`); | ||
| } | ||
|
|
||
| const assets = await Promise.all( | ||
| javascriptAssets.map(async (name) => ({ | ||
| bytes: (await stat(path.join(assetsDirectory, name))).size, | ||
| name, | ||
| })), | ||
| ); | ||
| const oversizedAssets = assets.filter(({ bytes }) => bytes > maximumChunkBytes); | ||
| const totalBytes = assets.reduce((total, { bytes }) => total + bytes, 0); | ||
|
|
||
| if (oversizedAssets.length > 0 || totalBytes > maximumTotalBytes) { | ||
| const failures = oversizedAssets.map( | ||
| ({ bytes, name }) => | ||
| `${name} is ${bytes.toLocaleString()} bytes (limit ${maximumChunkBytes.toLocaleString()})`, | ||
| const oversizedAssets = assets.filter( | ||
| ({ bytes }) => bytes > maximumChunkBytes, | ||
| ); | ||
| const totalBytes = assets.reduce((total, { bytes }) => total + bytes, 0); | ||
|
|
||
| if (totalBytes > maximumTotalBytes) { | ||
| failures.push( | ||
| `Total JavaScript is ${totalBytes.toLocaleString()} bytes (limit ${maximumTotalBytes.toLocaleString()})`, | ||
| if (oversizedAssets.length > 0 || totalBytes > maximumTotalBytes) { | ||
| const failures = oversizedAssets.map( | ||
| ({ bytes, name }) => | ||
| `${name} is ${bytes.toLocaleString()} bytes (limit ${maximumChunkBytes.toLocaleString()})`, | ||
| ); | ||
|
|
||
| if (totalBytes > maximumTotalBytes) { | ||
| failures.push( | ||
| `Total JavaScript is ${totalBytes.toLocaleString()} bytes (limit ${maximumTotalBytes.toLocaleString()})`, | ||
| ); | ||
| } | ||
|
|
||
| throw new Error(`Bundle size budget exceeded:\n${failures.join("\n")}`); | ||
| } | ||
|
|
||
| throw new Error(`Bundle size budget exceeded:\n${failures.join("\n")}`); | ||
| } | ||
| return { | ||
| largestAsset: assets.toSorted((a, b) => b.bytes - a.bytes)[0], | ||
| totalBytes, | ||
| }; | ||
| }; | ||
|
|
||
| const largestAsset = assets.toSorted((a, b) => b.bytes - a.bytes)[0]; | ||
| if (process.argv[1] === fileURLToPath(import.meta.url)) { | ||
| const assetNames = await readdir(assetsDirectory); | ||
| const javascriptAssets = assetNames.filter((name) => name.endsWith(".js")); | ||
| const assets = await Promise.all( | ||
| javascriptAssets.map(async (name) => ({ | ||
| bytes: (await stat(path.join(assetsDirectory, name))).size, | ||
| name, | ||
| })), | ||
| ); | ||
| let result; | ||
|
|
||
| console.log( | ||
| `Bundle budgets passed: ${assets.length} chunks, ${totalBytes.toLocaleString()} bytes total, ` + | ||
| `${largestAsset.name} largest at ${largestAsset.bytes.toLocaleString()} bytes.`, | ||
| ); | ||
| try { | ||
| result = evaluateBundleAssets(assets); | ||
| } catch (error) { | ||
| if (error.message === "No JavaScript assets found") { | ||
| throw new Error(`No JavaScript assets found in ${assetsDirectory}`, { | ||
| cause: error, | ||
| }); | ||
| } | ||
| throw error; | ||
| } | ||
|
|
||
| const { largestAsset, totalBytes } = result; | ||
|
|
||
| console.log( | ||
| `Bundle budgets passed: ${assets.length} chunks, ${totalBytes.toLocaleString()} bytes total, ` + | ||
| `${largestAsset.name} largest at ${largestAsset.bytes.toLocaleString()} bytes.`, | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
|
|
||
| import { | ||
| evaluateBundleAssets, | ||
| maximumChunkBytes, | ||
| maximumTotalBytes, | ||
| } from "./check-bundle-size.mjs"; | ||
|
|
||
| describe("bundle size budgets", () => { | ||
| it("returns the total and largest JavaScript asset", () => { | ||
| expect( | ||
| evaluateBundleAssets([ | ||
| { bytes: 100, name: "small.js" }, | ||
| { bytes: 200, name: "large.js" }, | ||
| ]), | ||
| ).toEqual({ | ||
| largestAsset: { bytes: 200, name: "large.js" }, | ||
| totalBytes: 300, | ||
| }); | ||
| }); | ||
|
|
||
| it("rejects an empty asset list", () => { | ||
| expect(() => evaluateBundleAssets([])).toThrow("No JavaScript assets"); | ||
| }); | ||
|
|
||
| it("rejects an oversized chunk", () => { | ||
| expect(() => | ||
| evaluateBundleAssets([{ bytes: maximumChunkBytes + 1, name: "app.js" }]), | ||
| ).toThrow("app.js"); | ||
| }); | ||
|
|
||
| it("rejects an oversized total", () => { | ||
| expect(() => | ||
| evaluateBundleAssets([ | ||
| { bytes: maximumTotalBytes / 2 + 1, name: "a.js" }, | ||
| { bytes: maximumTotalBytes / 2, name: "b.js" }, | ||
| ]), | ||
| ).toThrow("Total JavaScript"); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.