Two"}}/>',
+ ],
+ ['apps/desktop/src/renderer/a.js', 'node.innerHTML="Hello"', 'node.innerHTML="Welcome"'],
+ ['tailwind.config.js', 'export default {plugins:[]}', 'export default {plugins:[customPlugin]}'],
+ [
+ 'apps/desktop/src/main/window.ts',
+ 'win.setBackgroundColor("red")',
+ 'win.setBackgroundColor("blue")',
+ ],
+ [
+ 'apps/desktop/src/main/window.ts',
+ 'nativeTheme.themeSource="light"',
+ 'nativeTheme.themeSource="dark"',
+ ],
+ [
+ 'apps/desktop/src/main/terminal-themes.ts',
+ 'const script = "foreground:red"',
+ 'const script = "foreground:blue"',
+ ],
+])('covers %s', async (file, before, after) => {
+ const report = await compareFiles({ [file]: before }, { [file]: after })
+ expect(report.flagged).toBe(
+ ![
+ 'apps/sim/app/a.tsx',
+ 'apps/desktop/src/renderer/a.js',
+ 'tailwind.config.js',
+ 'apps/desktop/src/main/terminal-themes.ts',
+ ].includes(file)
+ )
+})
+
+it('preserves declaration and selector precedence', async () => {
+ const file = 'apps/sim/a.css'
+ const report = await compareFiles(
+ { [file]: '.a {color:red}.a {color:blue}' },
+ { [file]: '.a {color:blue}.a {color:red}' }
+ )
+ expect(report.flagged).toBe(true)
+})
+
+it('ignores CSS comments and formatting', async () => {
+ const file = 'apps/sim/a.css'
+ const report = await compareFiles(
+ { [file]: '.a{color:red}' },
+ { [file]: '/** comment */\n.a {\n color: red;\n}' }
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it('exempts content-only HTML whitespace', async () => {
+ const file = 'apps/desktop/a.html'
+ const report = await compareFiles({ [file]: '
a b
' }, { [file]: '
a b
' })
+ expect(report.flagged).toBe(false)
+})
diff --git a/scripts/design-diff/tests/fixtures/visual-cases.json b/scripts/design-diff/tests/fixtures/visual-cases.json
new file mode 100644
index 00000000000..040ecf827bb
--- /dev/null
+++ b/scripts/design-diff/tests/fixtures/visual-cases.json
@@ -0,0 +1,62 @@
+[
+ {
+ "name": "local colour",
+ "before": "export const Button = () =>
",
+ "after": "export const Button = () =>
",
+ "category": "colour"
+ },
+ {
+ "name": "padding",
+ "before": "export const Button = () =>
",
+ "after": "export const Button = () =>
",
+ "category": "dimensions"
+ },
+ {
+ "name": "width",
+ "before": "export const Button = () =>
",
+ "after": "export const Button = () =>
",
+ "category": "dimensions"
+ },
+ {
+ "name": "font",
+ "before": "export const Button = () =>
",
+ "after": "export const Button = () =>
",
+ "category": "typography"
+ },
+ {
+ "name": "shape",
+ "before": "export const Button = () =>
",
+ "after": "export const Button = () =>
",
+ "category": "shape-effects"
+ },
+ {
+ "name": "wrapping",
+ "before": "export const Button = () =>
",
+ "after": "export const Button = () =>
",
+ "category": "layout"
+ },
+ {
+ "name": "visibility",
+ "before": "export const Button = () =>
",
+ "after": "export const Button = () =>
",
+ "category": "visibility"
+ },
+ {
+ "name": "visible copy",
+ "before": "export const Button = () =>
",
+ "after": "export const Button = () =>
",
+ "category": "content"
+ },
+ {
+ "name": "motion",
+ "before": "export const Button = () =>
Go",
+ "after": "export const Button = () =>
Go",
+ "category": "motion"
+ },
+ {
+ "name": "inline SVG",
+ "before": "export const Icon = () =>
",
+ "after": "export const Icon = () =>
",
+ "category": "content"
+ }
+]
diff --git a/scripts/design-diff/tests/git.test.ts b/scripts/design-diff/tests/git.test.ts
new file mode 100644
index 00000000000..51967a83690
--- /dev/null
+++ b/scripts/design-diff/tests/git.test.ts
@@ -0,0 +1,79 @@
+import { execFileSync } from 'node:child_process'
+import { mkdtempSync, rmSync } from 'node:fs'
+import os from 'node:os'
+import path from 'node:path'
+import { expect, it } from 'vitest'
+import { analyze } from '#design-diff/analyze'
+import { GitReader } from '#design-diff/git'
+import { compareFiles, config, FixtureRepo } from '#design-diff/tests/helpers'
+
+it('compares the merge-base after staging diverges', async () => {
+ const repo = new FixtureRepo()
+ try {
+ const common = repo.commit({ 'apps/sim/a.tsx': 'export const A=()=>
Original
' })
+ repo.git('checkout', '-b', 'feature')
+ const head = repo.commit({ 'README.md': 'A nonvisual change' })
+ repo.git('checkout', 'staging')
+ const base = repo.commit({ 'apps/sim/a.tsx': 'export const A=()=>
Staging edit
' })
+ const report = await analyze(repo.cwd, base, head, config)
+ expect(report.commits?.mergeBase).toBe(common)
+ expect(report.flagged).toBe(false)
+ } finally {
+ repo.close()
+ }
+})
+
+it('handles binary assets, deletions and filenames containing spaces, tabs and newlines', async () => {
+ const file = 'apps/sim/public/icon space\tline\n.png'
+ const changed = await compareFiles(
+ { [file]: Buffer.from([0, 1, 255]) },
+ { [file]: Buffer.from([0, 2, 255]) }
+ )
+ expect(changed.flagged).toBe(false)
+ expect(changed.status).toBe('completed')
+ const removed = await compareFiles({ [file]: Buffer.from([0, 1, 255]) }, { [file]: null })
+ expect(removed.flagged).toBe(false)
+ const styled = 'apps/sim/component space\tline\n.tsx'
+ const appearance = await compareFiles(
+ { [styled]: 'export const A=()=>
' },
+ { [styled]: null }
+ )
+ expect(appearance.flagged).toBe(true)
+ expect(appearance.findings[0].before?.location.file).toBe(styled)
+ expect(appearance.findings[0].after).toBeNull()
+})
+
+it('handles renames and modifications without losing their old location', async () => {
+ const repo = new FixtureRepo()
+ try {
+ const base = repo.commit({
+ 'apps/sim/old.tsx': 'export const A=()=>
\n',
+ })
+ repo.git('mv', 'apps/sim/old.tsx', 'apps/sim/new name.tsx')
+ const rename = repo.commit({})
+ expect((await analyze(repo.cwd, base, rename, config)).flagged).toBe(false)
+ const head = repo.commit({
+ 'apps/sim/new name.tsx':
+ 'export const A=()=>
\n',
+ })
+ expect((await analyze(repo.cwd, base, head, config)).flagged).toBe(true)
+ } finally {
+ repo.close()
+ }
+})
+
+it('fails explicitly for unreadable revisions and missing history', async () => {
+ const repo = new FixtureRepo()
+ const shallow = mkdtempSync(path.join(os.tmpdir(), 'design-diff-shallow-'))
+ try {
+ const base = repo.commit({ 'README.md': 'first' })
+ repo.commit({ 'README.md': 'second' })
+ expect(() => new GitReader(repo.cwd).compare('--help', 'HEAD')).toThrow()
+ await expect(analyze(repo.cwd, 'not-a-ref', 'HEAD', config)).rejects.toThrow()
+ execFileSync('git', ['clone', '--depth=1', `file://${repo.cwd}`, shallow], { stdio: 'pipe' })
+ await expect(analyze(shallow, base, 'HEAD', config)).rejects.toThrow()
+ } finally {
+ repo.close()
+ rmSync(shallow, { recursive: true, force: true })
+ }
+})
diff --git a/scripts/design-diff/tests/group.test.ts b/scripts/design-diff/tests/group.test.ts
new file mode 100644
index 00000000000..63b84671735
--- /dev/null
+++ b/scripts/design-diff/tests/group.test.ts
@@ -0,0 +1,151 @@
+import { expect, it } from 'vitest'
+import { allChanges, compareFiles, config } from '#design-diff/tests/helpers'
+
+const button = 'packages/emcn/src/button.tsx'
+const icon = 'packages/emcn/src/icon.tsx'
+const index = 'packages/emcn/src/index.ts'
+const consumer = 'apps/sim/page.tsx'
+const unrelated = 'apps/sim/icon-only.tsx'
+const settings = { ...config, themes: [] }
+const fixture = {
+ 'packages/emcn/package.json': '{"name":"@sim/emcn","exports":"./src/index.ts"}',
+ [button]: 'export const Button=(props)=>
',
+ [icon]: 'export const Icon=()=>
',
+ [index]: 'export * from "./button";export * from "./icon"',
+ [consumer]:
+ 'import {Button} from "@sim/emcn";export const A=()=>
',
+ [unrelated]:
+ 'import {Icon} from "@sim/emcn";export const A=()=>
',
+}
+
+it('retains a nearby visual consumer before a distant opaque consumer', async () => {
+ const token = 'apps/sim/token.ts'
+ const direct = 'apps/sim/z-title.tsx'
+ const report = await compareFiles(
+ {
+ [token]: 'export const title="red"',
+ [direct]:
+ 'import {title} from "./token";export const Title=()=> ',
+ 'apps/sim/helper.ts': 'import {title} from "./token";export const config=unknown(title)',
+ 'apps/sim/a-distant.tsx':
+ 'import {config} from "./helper";export const View=()=> ',
+ },
+ { [token]: 'export const title="blue"' },
+ settings
+ )
+ expect(report.findings[0].example?.change.after?.location.file).toBe(direct)
+ expect(report.findings[0].example?.change.category).toBe('colour')
+})
+
+it('keeps direct findings and declares partial indirect coverage after qualification', async () => {
+ const token = 'apps/sim/token.ts'
+ const other = 'apps/sim/other.tsx'
+ const files = {
+ [token]: 'export const colour="red"',
+ [consumer]: 'export const View=()=> ',
+ [other]: 'import {colour} from "./token";export const Other=()=> ',
+ 'apps/sim/z-extra.tsx':
+ 'import {colour} from "./token";export const Extra=()=>
',
+ }
+ const report = await compareFiles(
+ files,
+ {
+ [token]: 'export const colour="blue"',
+ [consumer]: files[consumer].replace('p-2', 'p-4'),
+ },
+ settings
+ )
+ expect(report.flagged).toBe(true)
+ expect(report.findings.some((finding) => finding.source.after?.file === consumer)).toBe(true)
+ expect(report.limitations.join(' ')).toContain(
+ 'additional indirect effects are not exhaustively catalogued'
+ )
+ expect(
+ (await compareFiles(files, { [token]: 'export const colour="blue"' }, settings)).flagged
+ ).toBe(true)
+})
+
+it('groups a shared change once and counts only references to its defining module', async () => {
+ const report = await compareFiles(
+ fixture,
+ { [button]: fixture[button].replace('rounded-md', 'rounded-none') },
+ settings
+ )
+ expect(report.findings).toHaveLength(1)
+ const finding = report.findings[0]
+ expect(finding.decision).toBe('flag')
+ expect(finding.source.after?.file).toBe(button)
+ expect(finding.changes).toHaveLength(1)
+ expect(finding.category).toBe('shape-effects')
+ expect(finding.impact.before.referenceCount).toBe(2)
+ expect(finding.impact.after.referenceCount).toBe(2)
+ expect(finding.impact.after.fileCount).toBe(1)
+ expect(finding.consumers).toEqual([consumer])
+ expect(finding.example?.change.after?.location.file).toBe(consumer)
+ expect(finding.limitations.join(' ')).toContain('not confirmed visual changes')
+ expect(JSON.stringify(report.findings)).not.toContain(unrelated)
+})
+
+it('retains multiple direct changes under one source without dropping their categories', async () => {
+ const report = await compareFiles(
+ { [consumer]: 'export const A=()=> ' },
+ { [consumer]: 'export const A=()=> ' },
+ settings
+ )
+ expect(report.findings).toHaveLength(1)
+ expect(report.findings[0].changes).toHaveLength(2)
+ expect(report.findings[0].categories).toEqual(['colour', 'dimensions'])
+})
+
+it('anchors imported token changes at their source and retains one changed consumer example', async () => {
+ const token = 'apps/sim/token.ts'
+ const second = 'apps/sim/second.tsx'
+ const view = 'import {colour} from "./token";export const A=()=> '
+ const report = await compareFiles(
+ { [token]: 'export const colour="red"', [consumer]: view, [second]: view },
+ { [token]: 'export const colour="blue"' },
+ settings
+ )
+ expect(report.findings).toHaveLength(1)
+ const finding = report.findings[0]
+ expect(finding.source.before?.file).toBe(token)
+ expect(finding.source.after?.file).toBe(token)
+ expect(finding.changes).toEqual([])
+ expect(finding.example?.basis).toBe('changed-definition')
+ expect(finding.example?.change.before?.value).toBe('red')
+ expect(finding.example?.change.after?.value).toBe('blue')
+ expect(finding.consumers).toEqual([consumer, second].sort())
+ expect(finding.impact.after.referenceCount).toBe(2)
+})
+
+it('keeps independent changed sources separate, including shared consumers', async () => {
+ const a = 'apps/sim/a.ts'
+ const b = 'apps/sim/b.ts'
+ const report = await compareFiles(
+ {
+ [a]: 'export const colour="red"',
+ [b]: 'export const padding=4',
+ [consumer]:
+ 'import {colour} from "./a";import {padding} from "./b";export const A=()=> ',
+ },
+ { [a]: 'export const colour="blue"', [b]: 'export const padding=8' },
+ settings
+ )
+ expect(report.findings.map((finding) => finding.source.after?.file)).toEqual([a, b])
+ expect(report.findings.every((finding) => finding.decision === 'flag')).toBe(true)
+})
+
+it('uses only binary decisions while retaining uncertainty and operational failure separately', async () => {
+ const report = await compareFiles(
+ { [consumer]: 'export const A=()=>
' },
+ { [consumer]: 'export const A=()=>
' },
+ settings
+ )
+ expect(report.schemaVersion).toBe('3.0.0')
+ expect(report.policyVersion).toBe('5.0.0')
+ expect(report.status).toBe('completed')
+ expect(report.flagged).toBe(false)
+ expect(report.findings.every((finding) => finding.decision === 'flag')).toBe(true)
+ expect(allChanges(report).every((change) => change.decision === 'flag')).toBe(true)
+ expect(report.limitations).toContain('Function call is not executed')
+})
diff --git a/scripts/design-diff/tests/helpers.ts b/scripts/design-diff/tests/helpers.ts
new file mode 100644
index 00000000000..956db2d2584
--- /dev/null
+++ b/scripts/design-diff/tests/helpers.ts
@@ -0,0 +1,63 @@
+import { execFileSync } from 'node:child_process'
+import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
+import os from 'node:os'
+import path from 'node:path'
+import { analyze } from '#design-diff/analyze'
+import type { Change, Config, Report } from '#design-diff/types'
+
+export const config: Config = JSON.parse(
+ readFileSync(new URL('../../../design-diff.config.json', import.meta.url), 'utf8')
+)
+export type Files = Record
+
+/** Extracts retained source evidence and the representative consumer from grouped reports. */
+export function allChanges(report: Report): Change[] {
+ return report.findings.flatMap((finding) => [
+ ...finding.changes,
+ ...(finding.example ? [finding.example.change] : []),
+ ])
+}
+
+export class FixtureRepo {
+ readonly cwd = mkdtempSync(path.join(os.tmpdir(), 'design-diff-'))
+ constructor() {
+ this.git('init', '--initial-branch=staging')
+ this.git('config', 'user.email', 'fixture@example.invalid')
+ this.git('config', 'user.name', 'Design diff fixture')
+ this.git('config', 'commit.gpgsign', 'false')
+ }
+ git(...args: string[]): string {
+ return execFileSync('git', args, {
+ cwd: this.cwd,
+ encoding: 'utf8',
+ stdio: ['pipe', 'pipe', 'pipe'],
+ }).trim()
+ }
+ commit(files: Files): string {
+ for (const [file, contents] of Object.entries(files)) {
+ const target = path.join(this.cwd, file)
+ if (contents === null) rmSync(target, { force: true })
+ else {
+ mkdirSync(path.dirname(target), { recursive: true })
+ writeFileSync(target, contents)
+ }
+ }
+ this.git('add', '--all')
+ this.git('commit', '--allow-empty', '-m', 'fixture')
+ return this.git('rev-parse', 'HEAD')
+ }
+ close() {
+ rmSync(this.cwd, { force: true, recursive: true })
+ }
+}
+
+export async function compareFiles(before: Files, after: Files, settings: Config = config) {
+ const repo = new FixtureRepo()
+ try {
+ const base = repo.commit(before)
+ const head = repo.commit(after)
+ return await analyze(repo.cwd, base, head, settings)
+ } finally {
+ repo.close()
+ }
+}
diff --git a/scripts/design-diff/tests/infrastructure.test.ts b/scripts/design-diff/tests/infrastructure.test.ts
new file mode 100644
index 00000000000..b81afd6c1e9
--- /dev/null
+++ b/scripts/design-diff/tests/infrastructure.test.ts
@@ -0,0 +1,34 @@
+import { expect, it } from 'vitest'
+import { renderingLock } from '#design-diff/infrastructure'
+import { compareFiles, config } from '#design-diff/tests/helpers'
+
+const lock = (react: string, scheduler: string, backend: string) =>
+ JSON.stringify({
+ packages: {
+ react: [`react@${react}`, '', { dependencies: { scheduler: '1' } }, 'hash'],
+ scheduler: [`scheduler@${scheduler}`, '', {}, 'hash'],
+ redis: [`redis@${backend}`, '', {}, 'hash'],
+ },
+ })
+
+it('retains rendering dependency diagnostics without notifying on version changes alone', async () => {
+ const a = lock('19', '1', '1')
+ expect(renderingLock(a, config.renderingDependencies)).toEqual(
+ renderingLock(lock('19', '1', '2'), config.renderingDependencies)
+ )
+ expect(renderingLock(a, config.renderingDependencies)).not.toEqual(
+ renderingLock(lock('19', '2', '1'), config.renderingDependencies)
+ )
+ expect(
+ (await compareFiles({ 'bun.lock': a }, { 'bun.lock': lock('19', '1', '2') })).flagged
+ ).toBe(false)
+ expect(
+ (await compareFiles({ 'bun.lock': a }, { 'bun.lock': lock('19', '2', '1') })).flagged
+ ).toBe(false)
+})
+
+it('retains uncertainty for unsupported lockfile formats without executing content', () => {
+ expect(
+ renderingLock('throw new Error("do not execute")', config.renderingDependencies)
+ ).toMatchObject({ $unresolved: 'Malformed or unsupported Bun lockfile' })
+})
diff --git a/scripts/design-diff/tests/inputs.test.ts b/scripts/design-diff/tests/inputs.test.ts
new file mode 100644
index 00000000000..01cbbe05e92
--- /dev/null
+++ b/scripts/design-diff/tests/inputs.test.ts
@@ -0,0 +1,44 @@
+import { expect, it } from 'vitest'
+import { allChanges, compareFiles, config, type Files } from '#design-diff/tests/helpers'
+
+const list = 'apps/docs/lib/openapi-specs.ts'
+const renderer = 'apps/docs/lib/openapi.ts'
+const spec = 'apps/docs/openapi.json'
+const files = {
+ [list]: 'export const OPENAPI_SPEC_FILES=["openapi.json"] as const',
+ [renderer]: 'export const renderer = 1',
+ [spec]: '{"description":"First","enum":["a","b"]}',
+}
+
+it('exempts valid file-loaded API documentation content', async () => {
+ const report = await compareFiles(files, { [spec]: '{"description":"Second","enum":["a","b"]}' })
+ expect(report.flagged).toBe(false)
+ expect(report.findings).toEqual([])
+})
+
+it('exempts formatting and semantic API-reference data edits', async () => {
+ expect(
+ (await compareFiles(files, { [spec]: '{ "enum": ["a", "b"], "description": "First" }' }))
+ .flagged
+ ).toBe(false)
+ expect(
+ (await compareFiles(files, { [spec]: '{"description":"First","enum":["b","a"]}' })).flagged
+ ).toBe(false)
+})
+
+it.each([
+ { [spec]: 'broken' },
+ { [spec]: null },
+ { [list]: 'export const OPENAPI_SPEC_FILES=load()' },
+ { [renderer]: null },
+])('records unresolved configured inputs without designer notifications: %j', async (change) => {
+ const report = await compareFiles(files, change, config)
+ expect(report.flagged).toBe(false)
+ expect(allChanges(report).some((change) => change.limitations.length)).toBe(true)
+})
+
+it('clears diagnostics when file-loaded documentation is repaired', async () => {
+ const report = await compareFiles({ ...files, [spec]: 'broken' }, { [spec]: files[spec] })
+ expect(report.flagged).toBe(false)
+ expect(report.findings).toEqual([])
+})
diff --git a/scripts/design-diff/tests/movement.test.ts b/scripts/design-diff/tests/movement.test.ts
new file mode 100644
index 00000000000..48925079c75
--- /dev/null
+++ b/scripts/design-diff/tests/movement.test.ts
@@ -0,0 +1,47 @@
+import { expect, it } from 'vitest'
+import { allChanges, compareFiles } from '#design-diff/tests/helpers'
+
+const file = 'apps/sim/geometry.tsx'
+const svg = (x: number, fill = 'red', extra = '') =>
+ `export const A=()=> `
+
+it('exempts movement within SVG media', async () => {
+ const report = await compareFiles({ [file]: svg(20) }, { [file]: svg(30) })
+ expect(report.flagged).toBe(false)
+ expect(allChanges(report)).toEqual([])
+})
+
+it('exempts SVG appearance as media', async () => {
+ const report = await compareFiles({ [file]: svg(20) }, { [file]: svg(30, 'blue') })
+ expect(report.flagged).toBe(false)
+ expect(allChanges(report)).toEqual([])
+})
+
+it('exempts SVG media regardless of movement proof', async () => {
+ const report = await compareFiles(
+ { [file]: svg(20), 'apps/sim/global.css': 'rect {width:90px}' },
+ { [file]: svg(30) }
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it.each([
+ ['clipping', svg(99)],
+ ['effects', svg(30, 'red', 'stroke="black"')],
+ ['context', svg(30, 'red', 'className="custom"')],
+])('exempts media %s', async (_name, after) => {
+ const report = await compareFiles({ [file]: svg(20) }, { [file]: after })
+ expect(report.flagged).toBe(false)
+})
+
+it.each(['marginLeft', 'gap', 'justifyContent', 'position', 'transform'])(
+ 'does not blanket-exempt %s',
+ async (property) => {
+ const report = await compareFiles(
+ { [file]: `export const A=()=> ` },
+ { [file]: `export const A=()=>
` }
+ )
+ expect(report.flagged).toBe(true)
+ expect(allChanges(report)[0].decision).toBe('flag')
+ }
+)
diff --git a/scripts/design-diff/tests/precision.test.ts b/scripts/design-diff/tests/precision.test.ts
new file mode 100644
index 00000000000..6a499c0c703
--- /dev/null
+++ b/scripts/design-diff/tests/precision.test.ts
@@ -0,0 +1,597 @@
+import { expect, it } from 'vitest'
+import { semanticSource } from '#design-diff/ast'
+import { allChanges, compareFiles, config } from '#design-diff/tests/helpers'
+
+const settings = { ...config, themes: [] }
+const view = 'apps/sim/page.tsx'
+const data = 'apps/sim/data.ts'
+
+it('does not trace type-only factory inputs through opaque helpers', async () => {
+ const source = (count: number) => `export const backend={batch:${count}}`
+ const files = {
+ [data]: source(4),
+ 'apps/sim/client.ts':
+ 'import type {backend} from "./data";export const client=createClient
({name:"same"})',
+ [view]:
+ 'import {client} from "./client";export const Page=()=> {client.session()}',
+ }
+ expect((await compareFiles(files, { [data]: source(8) }, settings)).flagged).toBe(false)
+})
+
+it('exempts event-only and content-only custom props', async () => {
+ const component = (visible: boolean) => `export function Menu({editing=false}){
+ return }`
+ const files = {
+ [data]: component(false),
+ [view]:
+ 'import {Menu} from "./data";export const Page=({show})=> {show && }',
+ }
+ const changed = {
+ [view]:
+ 'import {Menu} from "./data";export const Page=({show})=> {show && ',
+ }
+ expect((await compareFiles(files, changed, settings)).flagged).toBe(false)
+ expect(
+ (await compareFiles({ ...files, [data]: component(true) }, changed, settings)).flagged
+ ).toBe(false)
+})
+
+it.each([1, 4, 24])(
+ 'exempts captured wording changes at resolution depth %s',
+ async (resolutionDepth) => {
+ const source = (title: string) =>
+ `const titles={fill:'${title}'};export function title(name){return titles[name]}`
+ const report = await compareFiles(
+ {
+ [data]: source('Filling'),
+ [view]:
+ 'import {title} from "./data";export const Page=({name})=> {title(name)}',
+ },
+ { [data]: source('Filling form') },
+ { ...settings, limits: { ...settings.limits, resolutionDepth } }
+ )
+ expect(report.flagged).toBe(false)
+ }
+)
+
+it.each([
+ [
+ 'nonempty accumulator',
+ 'Object.entries(input).reduce((acc,[key,value])=>({...acc,[key]:value}),{extra:1})',
+ ],
+ [
+ 'accumulator read',
+ 'Object.entries(input).reduce((acc,[key,value])=>({...acc,[key]:acc.previous}),{})',
+ ],
+ ['changed key', 'Object.entries(input).reduce((acc,[key,value])=>({...acc,[value]:value}),{})'],
+])('exempts opaque record changes without authored styles: %s', async (_name, expression) => {
+ const source = (value: string) =>
+ `export function Page({input}){return }`
+ const report = await compareFiles(
+ { [view]: source(expression) },
+ { [view]: source('Object.fromEntries(Object.entries(input).map(([key,value])=>[key,value]))') },
+ settings
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it.each([
+ [
+ 'SQL validation',
+ 'import {sql} from "drizzle-orm";export const query=sql`x = 1`',
+ 'import {sql} from "drizzle-orm";export const query=sql`x = 2`',
+ ],
+ [
+ 'telemetry',
+ 'import {trace} from "@opentelemetry/api";const span=trace.getTracer("a").startSpan("x");span.setAttribute("warm",false)',
+ 'import {trace} from "@opentelemetry/api";const span=trace.getTracer("a").startSpan("x");span.setAttribute("warm",true)',
+ ],
+])('does not classify %s as standalone rendering', async (_name, before, after) => {
+ const report = await compareFiles(
+ {
+ [data]: before,
+ [view]:
+ 'import {query} from "./data";export const Page=()=>
Go',
+ },
+ { [data]: after },
+ settings
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it('isolates nested properties and unrelated imported environment settings', async () => {
+ const source = (count: number) =>
+ `import {createEnv} from '@t3-oss/env-nextjs';export const env=createEnv({server:{BATCH:rule(${count})},client:{NEXT_PUBLIC_DISABLED:rule(false)}});export const design={button:{colour:'red'},count:${count}}`
+ const report = await compareFiles(
+ {
+ [data]: source(4),
+ [view]:
+ 'import {env,design} from "./data";export const Page=()=>
',
+ },
+ { [data]: source(8) },
+ settings
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it.each([
+ [
+ 'unused props',
+ 'export function Page({colour="red",unused=false}){return }',
+ 'export function Page({colour="red"}){return }',
+ ],
+ [
+ 'constant hoisting',
+ 'export function Page(){const padding={top:4};return }',
+ 'const PADDING={top:4};export function Page(){return }',
+ ],
+ [
+ 'local renames',
+ 'export function Page(){const colour=unknown();return }',
+ 'export function Page(){const paint=unknown();return }',
+ ],
+])('preserves equivalent %s', async (_name, before, after) => {
+ expect((await compareFiles({ [view]: before }, { [view]: after }, settings)).flagged).toBe(false)
+})
+
+it('traces a helper return through an alias and excludes its unrelated export', async () => {
+ const source = (n: number, colour: string) =>
+ `export const options={button:{colour:'${colour}'},other:${n}};export function paint(){return options.button.colour};export const unused=${n}`
+ const files = {
+ [data]: source(4, 'red'),
+ [view]:
+ 'import {paint as colour} from "./data";export const Page=()=> ',
+ }
+ expect((await compareFiles(files, { [data]: source(8, 'red') }, settings)).flagged).toBe(false)
+ const report = await compareFiles(files, { [data]: source(4, 'blue') }, settings)
+ expect(report.flagged).toBe(true)
+ expect(allChanges(report).some((change) => change.after?.location.file === view)).toBe(true)
+})
+
+it('exempts changed option visibility without appearance changes', async () => {
+ const source = (tool: string) =>
+ `const hidden=new Set(['${tool}']);export function visible(name){return !hidden.has(name)}`
+ const report = await compareFiles(
+ {
+ [data]: source('read'),
+ [view]:
+ 'import {visible} from "./data";export function Page({tool}){return visible(tool) && Tool}',
+ },
+ { [data]: source('write') },
+ settings
+ )
+ expect(report.flagged).toBe(false)
+ expect(allChanges(report).some((change) => change.after?.location.file === view)).toBe(false)
+})
+
+it('detects supported DOM styling and exempts canvas content', async () => {
+ for (const [index, source] of [
+ (colour: string) =>
+ `const button=document.createElement('button');button.setAttribute('class','bg-${colour}-500')`,
+ (colour: string) =>
+ `function draw(canvas: HTMLCanvasElement){const ctx=canvas.getContext('2d');ctx.fillStyle='${colour}';ctx.fillRect(0,0,20,20)}`,
+ ].entries())
+ expect(
+ (await compareFiles({ [data]: source('red') }, { [data]: source('blue') }, settings)).flagged
+ ).toBe(index === 0)
+})
+
+it('does not propagate dead re-exports into unrelated unresolved JSX', async () => {
+ const report = await compareFiles(
+ {
+ [data]: 'export const dead=4;export const live=8',
+ 'apps/sim/index.ts': 'export {dead,live} from "./data"',
+ [view]:
+ 'import {live} from "./index";export const Page=()=> ',
+ },
+ { 'apps/sim/index.ts': 'export {live} from "./data"' },
+ settings
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it('keeps a destructured local property independent of removed siblings and names', async () => {
+ const before =
+ 'const options={colour:"red",unused:4};export function Page(){const {colour,unused}=options;return }'
+ const after =
+ 'const options={colour:"red",unused:4};export function Page(){const {colour:paint}=options;return }'
+ expect((await compareFiles({ [view]: before }, { [view]: after }, settings)).flagged).toBe(false)
+})
+
+it('does not re-fingerprint unused component parameters at an imported consumer', async () => {
+ const component = (unused: string) =>
+ `export function Button({colour="red"${unused}}){return }`
+ const report = await compareFiles(
+ {
+ [data]: component(',unused=false'),
+ [view]:
+ 'import {Button} from "./data";export const Page=()=> {ready && }
',
+ },
+ { [data]: component('') },
+ settings
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it('recognizes an aliased class helper by its imported binding', async () => {
+ const source = (colour: string) =>
+ `import {clsx as classes} from "clsx";export const Page=()=> `
+ const report = await compareFiles(
+ { [view]: source('text-red-500') },
+ { [view]: source('text-blue-500') },
+ settings
+ )
+ expect(report.flagged).toBe(true)
+ expect(allChanges(report)[0].category).toBe('colour')
+})
+
+it('exempts unsupported canvas rendering', async () => {
+ const source = (shader: string) =>
+ `function draw(canvas:HTMLCanvasElement){const gl=canvas.getContext('webgl');gl.shaderSource(shader,'${shader}')}`
+ expect(
+ (await compareFiles({ [data]: source('before') }, { [data]: source('after') }, settings))
+ .flagged
+ ).toBe(false)
+})
+
+it('exempts helper conditions with unchanged appearance values', async () => {
+ const source = (mode: string) =>
+ `export function colour(mode){switch(mode){case '${mode}':return 'red';default:return 'blue'}}`
+ const report = await compareFiles(
+ {
+ [data]: source('a'),
+ [view]:
+ 'import {colour} from "./data";export const Page=()=> ',
+ },
+ { [data]: source('b') },
+ settings
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it.each([1, 24])(
+ 'preserves the audited record-map refactor at resolution depth %s',
+ async (resolutionDepth) => {
+ const before =
+ 'export const copy=(blocks)=>Object.entries(blocks).reduce((acc,[id,block])=>({...acc,[id]:{...block,value:structuredClone(block.value)}}),{})'
+ const after =
+ 'export const copy=(blocks)=>Object.fromEntries(Object.entries(blocks).map(([id,block])=>[id,{...block,value:structuredClone(block.value)}]))'
+ const report = await compareFiles(
+ {
+ [data]: before,
+ [view]: 'import {copy} from "./data";export const Page=()=> ',
+ },
+ { [data]: after },
+ { ...settings, limits: { ...settings.limits, resolutionDepth } }
+ )
+ expect(report.flagged).toBe(false)
+ }
+)
+
+it('keeps selected environment evidence narrow after exhausting expression depth', async () => {
+ const source = (size: number) =>
+ `import {createEnv} from '@t3-oss/env-nextjs';export const env=createEnv({server:{GITHUB_TOKEN:rule('same'),BATCH:rule(${size})}})`
+ const report = await compareFiles(
+ {
+ [data]: source(4),
+ [view]:
+ 'import {env} from "./data";export const Page=()=> ',
+ },
+ { [data]: source(8) },
+ { ...settings, limits: { ...settings.limits, resolutionDepth: 2 } }
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it('exempts capability visibility changes without appearance values', async () => {
+ const adapter = settings.environmentAdapters![0]
+ const configured = {
+ ...settings,
+ aliases: [
+ ...settings.aliases,
+ { from: 'apps/sim/', prefix: '@capability/', target: 'apps/sim/lib/core/config/' },
+ ],
+ }
+ const environment = (batch: number, email: boolean) =>
+ `import {createEnv} from '@t3-oss/env-nextjs';export const env=createEnv({server:{BATCH:rule(${batch}),EMAIL:rule(${email})}})`
+ const files = {
+ [adapter.environmentModule]: environment(4, true),
+ [adapter.module]: `import {env} from './env';import {wireFallback} from '@capability/env-capabilities';export function wireServerFallback(options){return wireFallback({...options,values:env})}`,
+ [adapter.implementationModule]:
+ 'export function wireFallback({values,definition}){return {providers:[]}}',
+ [data]: `import {wireServerFallback} from '@/lib/core/config/env-capabilities.server';const CAPABILITY=defineCapability({providers:[{activation:{keys:['EMAIL']}}]});export const providers=wireServerFallback({definition:CAPABILITY,factories:{}}).providers`,
+ [view]:
+ 'import {providers} from "./data";export const Page=()=> ',
+ }
+ expect(
+ (await compareFiles(files, { [adapter.environmentModule]: environment(8, true) }, configured))
+ .flagged
+ ).toBe(false)
+ expect(
+ (await compareFiles(files, { [adapter.environmentModule]: environment(4, false) }, configured))
+ .flagged
+ ).toBe(false)
+ expect(
+ (
+ await compareFiles(
+ files,
+ {
+ [adapter.implementationModule]:
+ 'export function wireFallback({values,definition}){return {providers:[1]}}',
+ },
+ configured
+ )
+ ).flagged
+ ).toBe(false)
+})
+
+it('leaves type queries over literal constants erased and parseable', async () => {
+ const source = (kind: string) =>
+ `const KINDS=['one','two'] as const;type Kind = typeof KINDS[number];export const Page=()=>
`
+ const report = await compareFiles({ [view]: source('a') }, { [view]: source('b') }, settings)
+ expect(report.flagged).toBe(true)
+ expect(
+ report.findings
+ .flatMap((finding) => finding.limitations)
+ .some((reason) => /parser|extraction failed/i.test(reason))
+ ).toBe(false)
+})
+
+it('preserves value/type names shared by generated schema declarations', async () => {
+ const source = (text: string) =>
+ `const Kind={ONE:1};type Kind=typeof Kind;export const Page=()=> `
+ const report = await compareFiles({ [view]: source('a') }, { [view]: source('b') }, settings)
+ expect(report.flagged).toBe(true)
+ expect(
+ report.findings
+ .flatMap((finding) => finding.limitations)
+ .some((reason) => /parser|extraction failed/i.test(reason))
+ ).toBe(false)
+})
+
+it.each([
+ (colour: string) =>
+ `export function colours(){const values=[];values.push('${colour}');return values}`,
+ (colour: string) =>
+ `export function colours(){const values={colour:'red'};values.colour='${colour}';return values.colour}`,
+])('exempts collections supplied to unknown component props', async (source) => {
+ const report = await compareFiles(
+ {
+ [data]: source('red'),
+ [view]: 'import {colours} from "./data";export const Page=()=>
',
+ },
+ { [data]: source('blue') },
+ settings
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it('exempts conditions around unknown presentation collections', async () => {
+ const source = (enabled: boolean) =>
+ `export function colours(){const values=[];if(${enabled})values.push('red');return values}`
+ const report = await compareFiles(
+ {
+ [data]: source(true),
+ [view]: 'import {colours} from "./data";export const Page=()=> ',
+ },
+ { [data]: source(false) },
+ settings
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it('isolates unrelated mutable object fields such as telemetry warmup state', async () => {
+ const source = (warm: boolean) =>
+ `const state={client:null,warmup:false};state.client=connect();state.warmup=${warm};export function client(){return state.client}`
+ const report = await compareFiles(
+ {
+ [data]: source(false),
+ [view]: 'import {client} from "./data";export const Page=()=> ',
+ },
+ { [data]: source(true) },
+ settings
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it.each(['[live, unrelated]', 'await Promise.all([live, unrelated])'])(
+ 'projects an individual array result from %s',
+ async (expression) => {
+ const source = (colour: string, telemetry: number) =>
+ `export const live='${colour}';export const unrelated=${telemetry}`
+ const files = {
+ [data]: source('red', 1),
+ [view]: `import {live,unrelated} from './data';export async function Page(){const [colour]=${expression};return }`,
+ }
+ expect((await compareFiles(files, { [data]: source('red', 2) }, settings)).flagged).toBe(false)
+ expect((await compareFiles(files, { [data]: source('blue', 1) }, settings)).flagged).toBe(true)
+ }
+)
+
+it('does not notify on shadowed Promise.all uncertainty', async () => {
+ const source = (n: number) => `export const unrelated=${n}`
+ const report = await compareFiles(
+ {
+ [data]: source(1),
+ [view]: `import {unrelated} from './data';export async function Page({Promise}){const [colour]=await Promise.all(['red',unrelated]);return }`,
+ },
+ { [data]: source(2) },
+ settings
+ )
+ expect(report.flagged).toBe(false)
+})
+
+it('does not notify on opaque namespace consumers at their resolution limit', async () => {
+ const source = (colour: string, unused: number) =>
+ `export const colour='${colour}';export const unused=${unused}`
+ const files = {
+ [data]: source('red', 1),
+ [view]: `import * as palette from './data';export const Page=()=>