basalt-ui 1.20.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -5
- package/CHANGELOG.md +19 -0
- package/MIGRATING.md +147 -49
- package/README.md +42 -8
- package/agent/rules/basalt-app.md +41 -0
- package/agent/rules/basalt-charts.md +15 -12
- package/agent/rules/basalt-content.md +4 -0
- package/agent/rules/basalt-router.md +57 -5
- package/agent/rules/basalt-state.md +21 -3
- package/agent/rules/basalt-tokens.md +73 -16
- package/agent/skills/basalt-app/SKILL.md +4 -1
- package/agent/skills/basalt-charts/SKILL.md +5 -3
- package/agent/templates/CLAUDE-block.md.tpl +11 -5
- package/configs/lefthook.yml +48 -15
- package/configs/oxlint-plugin.js +181 -68
- package/configs/oxlint-plugin.test.ts +215 -9
- package/dist/charts/kinds/Bars.d.ts +1 -1
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.d.ts +1 -1
- package/dist/charts/kinds/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.d.ts +1 -1
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/Heatmap.d.ts +1 -1
- package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
- package/dist/charts/kinds/MultiLine.d.ts +1 -1
- package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.d.ts +1 -1
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/ZonedLine.d.ts +1 -1
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/cli/index.d.ts +48 -14
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +328 -66
- package/dist/cli/waiver-audit.test.d.ts +2 -0
- package/dist/cli/waiver-audit.test.d.ts.map +1 -0
- package/dist/guard/index.d.ts +47 -2
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +176 -31
- package/dist/router-tanstack/multi-search-param-store.d.ts +13 -0
- package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
- package/dist/router-tanstack/multi-search-param-store.js +4 -1
- package/dist/router-tanstack/search-param-store.d.ts +29 -5
- package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
- package/dist/router-tanstack/search-param-store.js +31 -3
- package/dist/router-tanstack/search-param-store.test.d.ts +2 -0
- package/dist/router-tanstack/search-param-store.test.d.ts.map +1 -0
- package/dist/shell/index.d.ts +12 -5
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +24 -6
- package/dist/tokens/index.d.ts +3 -1
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +5 -1
- package/dist/tokens/palette.d.ts +2 -2
- package/dist/tokens/palette.js +2 -2
- package/dist/tokens/text-scale.test.d.ts +2 -0
- package/dist/tokens/text-scale.test.d.ts.map +1 -0
- package/dist/tokens.css +4 -2
- package/dist/vite.d.ts +24 -2
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +54 -13
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/charts/kinds/Bars.tsx +1 -1
- package/src/charts/kinds/Donut.tsx +1 -1
- package/src/charts/kinds/DualPanel.tsx +2 -2
- package/src/charts/kinds/Heatmap.tsx +1 -1
- package/src/charts/kinds/MultiLine.tsx +1 -1
- package/src/charts/kinds/StackedArea.tsx +1 -1
- package/src/charts/kinds/ZonedLine.tsx +1 -1
- package/src/cli/index.ts +607 -128
- package/src/cli/init-sync.test.ts +2 -0
- package/src/cli/template-ledger.test.ts +1 -1
- package/src/cli/toolchain-wiring.test.ts +162 -0
- package/src/cli/waiver-audit.test.ts +131 -0
- package/src/guard/check-source.test.ts +441 -1
- package/src/guard/index.ts +430 -60
- package/src/router-tanstack/multi-search-param-store.ts +16 -1
- package/src/router-tanstack/search-param-store.test.ts +135 -0
- package/src/router-tanstack/search-param-store.ts +90 -8
- package/src/shell/app-mobile-nav.tsx +1 -1
- package/src/shell/index.test.tsx +52 -0
- package/src/shell/index.tsx +64 -13
- package/src/tokens/index.ts +24 -1
- package/src/tokens/palette.ts +2 -2
- package/src/tokens/text-scale.test.ts +51 -0
- package/src/vite.test.ts +125 -6
- package/src/vite.ts +148 -20
|
@@ -151,6 +151,8 @@ describe('doctor — CLI vs installed basalt-ui version', () => {
|
|
|
151
151
|
'node_modules/basalt-ui/package.json',
|
|
152
152
|
JSON.stringify({ name: 'basalt-ui', version: cliVersion }),
|
|
153
153
|
)
|
|
154
|
+
// The `extends` above has to RESOLVE, not merely read correctly — see the oxlint-preset check.
|
|
155
|
+
writeFixture('node_modules/basalt-ui/configs/oxlint.json', '{}')
|
|
154
156
|
const { code, log } = capture(() => doctor(dir))
|
|
155
157
|
expect(code).toBe(0)
|
|
156
158
|
expect(log).toContain('matches the installed basalt-ui')
|
|
@@ -116,7 +116,7 @@ describe('classify ledger: cross-version format-only drift vs a real edit', () =
|
|
|
116
116
|
// older version token — this is what makes the scenario cross-version, not same-version.
|
|
117
117
|
const olderBlock = currentBlock.replace(/basalt-ui/g, 'basalt')
|
|
118
118
|
expect(olderBlock).not.toBe(currentBlock)
|
|
119
|
-
expect(olderBlock).toContain('
|
|
119
|
+
expect(olderBlock).toContain('basalt sync')
|
|
120
120
|
|
|
121
121
|
// What a PRE-1.0.2 CLI's writeUnit actually recorded at that sync: a RAW hash of the exact
|
|
122
122
|
// bytes it wrote — the fix only normalizes hashes written by a CLI carrying it, so a genuine
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
init,
|
|
21
21
|
MANIFEST_PATH,
|
|
22
22
|
normalizeColorFunctions,
|
|
23
|
+
sync,
|
|
23
24
|
tokensCss,
|
|
24
25
|
} from './index.ts'
|
|
25
26
|
|
|
@@ -68,6 +69,9 @@ function installBasalt(at = ''): void {
|
|
|
68
69
|
join(at, 'node_modules/basalt-ui/package.json'),
|
|
69
70
|
JSON.stringify({ name: 'basalt-ui', version: CLI_VERSION }),
|
|
70
71
|
)
|
|
72
|
+
// The shipped preset itself — `doctor`'s oxlint-preset check resolves the `extends` target now,
|
|
73
|
+
// so an install without it is (correctly) a failure.
|
|
74
|
+
write(join(at, 'node_modules/basalt-ui/configs/oxlint.json'), '{}')
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
/** A repo where every framework-profile doctor check passes. */
|
|
@@ -538,6 +542,18 @@ describe('tokens:css — a committable artifact', () => {
|
|
|
538
542
|
expect(css).toContain('rgba(255, 255, 255, 0.6)')
|
|
539
543
|
})
|
|
540
544
|
|
|
545
|
+
it('emits no trailing-zero alpha — the last thing keeping the sheet out of a linter', () => {
|
|
546
|
+
// prettier rewrites `rgba(28, 25, 23, 0.10)` → `0.1`, so the two light shadow tokens failed
|
|
547
|
+
// `format/prettier` while `--fix` put the file straight into `tokens:css --check` drift. The
|
|
548
|
+
// consumer's eslint-ignore entry for it survived two releases.
|
|
549
|
+
const css = emit([])
|
|
550
|
+
expect(css).not.toMatch(/rgba\([^)]*\d\.\d*0\)/)
|
|
551
|
+
expect(css).toContain('rgba(28, 25, 23, 0.1)')
|
|
552
|
+
expect(normalizeColorFunctions('a: rgba(1, 2, 3, 0.10); b: rgba(1, 2, 3, 1.0);')).toBe(
|
|
553
|
+
'a: rgba(1, 2, 3, 0.1); b: rgba(1, 2, 3, 1);',
|
|
554
|
+
)
|
|
555
|
+
})
|
|
556
|
+
|
|
541
557
|
it('changes no token VALUE — the body is buildPaletteCss modulo the formatting', () => {
|
|
542
558
|
const body = emit([]).split('\n').slice(2).join('\n')
|
|
543
559
|
expect(body).toBe(`${normalizeColorFunctions(buildPaletteCss())}\n`)
|
|
@@ -692,3 +708,149 @@ describe('fonts:css — the typeface half of the token layer', () => {
|
|
|
692
708
|
expect(capture(() => fontsCss(['--out', 'fonts.css', '--check'], dir)).code).toBe(1)
|
|
693
709
|
})
|
|
694
710
|
})
|
|
711
|
+
|
|
712
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
713
|
+
// Round 5: the same false-green class, one layer in — inside the checks that
|
|
714
|
+
// were added to prevent it.
|
|
715
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
716
|
+
|
|
717
|
+
describe('doctor — the oxlint-preset check resolves the path, not just the string', () => {
|
|
718
|
+
it('hard-fails when the extends entry names a path that does not exist', () => {
|
|
719
|
+
healthyFixture()
|
|
720
|
+
// Exactly the state a monorepo consumer upgrades into: a well-shaped root-relative entry, and
|
|
721
|
+
// basalt installed beside the package that depends on it. `bunx oxlint .` dies with NotFound
|
|
722
|
+
// here, and 1.20.0's own check printed ✓ in the same tree.
|
|
723
|
+
rmSync(join(dir, 'node_modules/basalt-ui/configs/oxlint.json'))
|
|
724
|
+
const { code, log } = capture(() => doctor(dir))
|
|
725
|
+
expect(code).toBe(1)
|
|
726
|
+
expect(log).toContain('does not exist')
|
|
727
|
+
expect(log).toContain('NotFound')
|
|
728
|
+
expect(log).not.toContain('✓ .oxlintrc.json extends')
|
|
729
|
+
})
|
|
730
|
+
|
|
731
|
+
it('names the RESOLVED install path as the remedy when the entry points somewhere else', () => {
|
|
732
|
+
write(
|
|
733
|
+
'package.json',
|
|
734
|
+
JSON.stringify({
|
|
735
|
+
name: 'root',
|
|
736
|
+
workspaces: ['packages/*'],
|
|
737
|
+
basalt: { roots: ['packages/web/src'] },
|
|
738
|
+
}),
|
|
739
|
+
)
|
|
740
|
+
write('packages/web/package.json', JSON.stringify({ name: 'web' }))
|
|
741
|
+
write('packages/web/src/app.tsx', 'export const App = () => null\n')
|
|
742
|
+
write(MANIFEST_PATH, JSON.stringify({ version: 1, files: {}, basaltVersion: CLI_VERSION }))
|
|
743
|
+
write('.oxlintrc.json', '{ "extends": ["./node_modules/basalt-ui/configs/oxlint.json"] }')
|
|
744
|
+
installBasalt('packages/web')
|
|
745
|
+
const { code, log } = capture(() => doctor(dir))
|
|
746
|
+
expect(code).toBe(1)
|
|
747
|
+
expect(log).toContain('packages/web/node_modules/basalt-ui/configs/oxlint.json')
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
it('passes — and quotes the entry — when the target really is there', () => {
|
|
751
|
+
healthyFixture()
|
|
752
|
+
const { code, log } = capture(() => doctor(dir))
|
|
753
|
+
expect(code).toBe(0)
|
|
754
|
+
expect(log).toContain('extends the shipped basalt-ui oxlint preset')
|
|
755
|
+
})
|
|
756
|
+
})
|
|
757
|
+
|
|
758
|
+
describe('doctor — the lefthook extends target, the seam that fails SILENTLY', () => {
|
|
759
|
+
it('hard-fails on a missing target: lefthook merges it to zero commands and exits 0', () => {
|
|
760
|
+
healthyFixture()
|
|
761
|
+
write('lefthook.yml', 'extends:\n - node_modules/basalt-ui/configs/lefthook.yml\n')
|
|
762
|
+
const { code, log } = capture(() => doctor(dir))
|
|
763
|
+
expect(code).toBe(1)
|
|
764
|
+
expect(log).toContain('ZERO commands')
|
|
765
|
+
})
|
|
766
|
+
|
|
767
|
+
it('passes when the target resolves', () => {
|
|
768
|
+
healthyFixture()
|
|
769
|
+
write('node_modules/basalt-ui/configs/lefthook.yml', 'pre-commit:\n commands: {}\n')
|
|
770
|
+
write('lefthook.yml', 'extends:\n - node_modules/basalt-ui/configs/lefthook.yml\n')
|
|
771
|
+
const { code, log } = capture(() => doctor(dir))
|
|
772
|
+
expect(code).toBe(0)
|
|
773
|
+
expect(log).toContain('extends the shipped lefthook preset')
|
|
774
|
+
})
|
|
775
|
+
|
|
776
|
+
it('warns (never fails) on a lefthook.yml that wires its own hooks instead', () => {
|
|
777
|
+
healthyFixture()
|
|
778
|
+
write('lefthook.yml', 'pre-commit:\n commands:\n mine:\n run: echo hi\n')
|
|
779
|
+
const { code, log } = capture(() => doctor(dir))
|
|
780
|
+
expect(code).toBe(0)
|
|
781
|
+
expect(log).toContain('does not extend the shipped lefthook preset')
|
|
782
|
+
})
|
|
783
|
+
|
|
784
|
+
it('is not applicable when the repo runs no lefthook at all', () => {
|
|
785
|
+
healthyFixture()
|
|
786
|
+
const { code, log } = capture(() => doctor(dir))
|
|
787
|
+
expect(code).toBe(0)
|
|
788
|
+
expect(log).toContain('lefthook-preset: n/a')
|
|
789
|
+
})
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
describe('sync — basalt.roots is drift, and sync is the drift command', () => {
|
|
793
|
+
it('backfills the inferred roots when the key was never written', () => {
|
|
794
|
+
write('package.json', JSON.stringify({ name: 'fixture' }))
|
|
795
|
+
write('src/app.tsx', 'export const App = () => null\n')
|
|
796
|
+
capture(() => init(dir))
|
|
797
|
+
// Undo init's write to reproduce a repo scaffolded before roots existed, then upgrade.
|
|
798
|
+
write('package.json', JSON.stringify({ name: 'fixture' }))
|
|
799
|
+
const { log } = capture(() => sync({}, dir))
|
|
800
|
+
expect(log).toContain('"roots": ["src"]')
|
|
801
|
+
expect((readPkg()['basalt'] as { roots: string[] }).roots).toEqual(['src'])
|
|
802
|
+
})
|
|
803
|
+
|
|
804
|
+
it('never overwrites a declared roots — it names what the declaration does not cover', () => {
|
|
805
|
+
write(
|
|
806
|
+
'package.json',
|
|
807
|
+
JSON.stringify({
|
|
808
|
+
name: 'root',
|
|
809
|
+
workspaces: ['packages/*'],
|
|
810
|
+
basalt: { roots: ['packages/web/src'] },
|
|
811
|
+
}),
|
|
812
|
+
)
|
|
813
|
+
write(
|
|
814
|
+
'packages/web/package.json',
|
|
815
|
+
JSON.stringify({ name: 'web', dependencies: { 'basalt-ui': '*' } }),
|
|
816
|
+
)
|
|
817
|
+
write('packages/web/src/app.tsx', 'export const App = () => null\n')
|
|
818
|
+
write(
|
|
819
|
+
'packages/api/package.json',
|
|
820
|
+
JSON.stringify({ name: 'api', dependencies: { 'basalt-ui': '*' } }),
|
|
821
|
+
)
|
|
822
|
+
write('packages/api/src/server.ts', 'export const server = null\n')
|
|
823
|
+
const { log } = capture(() => sync({}, dir))
|
|
824
|
+
expect((readPkg()['basalt'] as { roots: string[] }).roots).toEqual(['packages/web/src'])
|
|
825
|
+
expect(log).toContain('keeping your "basalt.roots"')
|
|
826
|
+
expect(log).toContain('packages/api/src')
|
|
827
|
+
})
|
|
828
|
+
|
|
829
|
+
it('--check writes nothing, but still says the key is missing', () => {
|
|
830
|
+
write('package.json', JSON.stringify({ name: 'fixture' }))
|
|
831
|
+
write('src/app.tsx', 'export const App = () => null\n')
|
|
832
|
+
const { log } = capture(() => sync({ check: true }, dir))
|
|
833
|
+
expect(readPkg()['basalt']).toBeUndefined()
|
|
834
|
+
expect(log).toContain('"basalt.roots" is not set')
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
it('reports a lefthook extends target that no longer resolves', () => {
|
|
838
|
+
write('package.json', JSON.stringify({ name: 'fixture', basalt: { roots: ['src'] } }))
|
|
839
|
+
write('src/app.tsx', 'export const App = () => null\n')
|
|
840
|
+
write('lefthook.yml', 'extends:\n - node_modules/basalt-ui/configs/lefthook.yml\n')
|
|
841
|
+
const { log } = capture(() => sync({}, dir))
|
|
842
|
+
expect(log).toContain('ZERO commands')
|
|
843
|
+
})
|
|
844
|
+
})
|
|
845
|
+
|
|
846
|
+
describe('init — the seeded lefthook.yml pins the guard to the local bin', () => {
|
|
847
|
+
it('renders a BASALT_BIN env override, the one key an extends target cannot eat', () => {
|
|
848
|
+
write('package.json', JSON.stringify({ name: 'fixture' }))
|
|
849
|
+
write('src/app.tsx', 'export const App = () => null\n')
|
|
850
|
+
installBasalt()
|
|
851
|
+
capture(() => init(dir))
|
|
852
|
+
const seeded = read('lefthook.yml')
|
|
853
|
+
expect(seeded).toContain('BASALT_BIN: node_modules/.bin/basalt-ui')
|
|
854
|
+
expect(seeded).toContain('extends:')
|
|
855
|
+
})
|
|
856
|
+
})
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `check-theme --audit-allows` — the accountability half of the escape hatch.
|
|
3
|
+
*
|
|
4
|
+
* 1.20.0 shipped `theme-allow-unscoped`, which reports a waiver that names no rule. It cannot see
|
|
5
|
+
* the other failure: a perfectly well-written waiver whose finding no longer exists. Two consumers
|
|
6
|
+
* found five of those by hand — editing the token out and re-running the guard — and that manual
|
|
7
|
+
* method is what this command automates, which is why every assertion below is about a VERDICT
|
|
8
|
+
* derived from re-running the scan, never from parsing the annotation's text.
|
|
9
|
+
*
|
|
10
|
+
* Run: bun test packages/basalt-ui/src/cli/waiver-audit.test.ts
|
|
11
|
+
*/
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
|
|
13
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
14
|
+
import { tmpdir } from 'node:os'
|
|
15
|
+
import { join, resolve } from 'node:path'
|
|
16
|
+
|
|
17
|
+
import { checkTheme } from './index.ts'
|
|
18
|
+
|
|
19
|
+
let dir: string
|
|
20
|
+
|
|
21
|
+
function write(relPath: string, content: string): void {
|
|
22
|
+
const abs = join(dir, relPath)
|
|
23
|
+
mkdirSync(resolve(abs, '..'), { recursive: true })
|
|
24
|
+
writeFileSync(abs, content, 'utf8')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function audit(): { code: number; log: string } {
|
|
28
|
+
const originalLog = console.log
|
|
29
|
+
const originalError = console.error
|
|
30
|
+
let log = ''
|
|
31
|
+
const sink = (...args: unknown[]) => {
|
|
32
|
+
log += `${args.join(' ')}\n`
|
|
33
|
+
}
|
|
34
|
+
console.log = sink
|
|
35
|
+
console.error = sink
|
|
36
|
+
try {
|
|
37
|
+
return { code: checkTheme(dir, ['--audit-allows']), log }
|
|
38
|
+
} finally {
|
|
39
|
+
console.log = originalLog
|
|
40
|
+
console.error = originalError
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A repo whose only source file is the one under test. */
|
|
45
|
+
function fixture(source: string, basalt: Record<string, unknown> = {}): void {
|
|
46
|
+
write('package.json', JSON.stringify({ name: 'fixture', basalt: { roots: ['src'], ...basalt } }))
|
|
47
|
+
write('src/app.tsx', source)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
dir = mkdtempSync(resolve(tmpdir(), 'basalt-audit-'))
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
rmSync(dir, { recursive: true, force: true })
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('check-theme --audit-allows — theme-allow annotations', () => {
|
|
59
|
+
it('names what a live waiver still suppresses, and exits 0', () => {
|
|
60
|
+
fixture("export const c = '#ff0000' // theme-allow raw-hex — a deliberate brand literal\n")
|
|
61
|
+
const { code, log } = audit()
|
|
62
|
+
expect(log).toContain('suppresses raw-hex@1')
|
|
63
|
+
expect(code).toBe(0)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('calls a waiver over a line with no finding DEAD, and exits 1', () => {
|
|
67
|
+
fixture('export const c = 1 // theme-allow raw-hex — nothing here fires any more\n')
|
|
68
|
+
const { code, log } = audit()
|
|
69
|
+
expect(log).toContain('SUPPRESSES NOTHING')
|
|
70
|
+
expect(code).toBe(1)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('does not report prose that merely mentions the token', () => {
|
|
74
|
+
// The guard stopped treating a mid-comment mention as an annotation for exactly this reason;
|
|
75
|
+
// an audit that listed them as dead waivers would send someone editing documentation.
|
|
76
|
+
fixture('// Waive it with a `theme-allow raw-hex — why` comment.\nexport const c = 1\n')
|
|
77
|
+
const { code, log } = audit()
|
|
78
|
+
expect(log).toContain('annotations (0)')
|
|
79
|
+
expect(code).toBe(0)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('refuses to judge a waiver scoped to an oxlint plugin rule instead of calling it dead', () => {
|
|
83
|
+
// `hand-rolled-plot` lives in the plugin, so `checkSource` can never see what it suppresses.
|
|
84
|
+
// Reporting "delete it" here would be the command telling someone to remove a live waiver.
|
|
85
|
+
fixture(
|
|
86
|
+
'// theme-allow hand-rolled-plot — DualPanel is not a single cartesian plot\nexport const c = 1\n',
|
|
87
|
+
)
|
|
88
|
+
const { code, log } = audit()
|
|
89
|
+
expect(log).toContain('not a check-theme kind')
|
|
90
|
+
expect(log).not.toContain('SUPPRESSES NOTHING')
|
|
91
|
+
expect(code).toBe(0)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('marks an unaccountable waiver while still reporting what it covers', () => {
|
|
95
|
+
fixture("export const c = '#ff0000' // theme-allow\n")
|
|
96
|
+
const { log } = audit()
|
|
97
|
+
expect(log).toContain('suppresses raw-hex@1')
|
|
98
|
+
expect(log).toContain('no rule id')
|
|
99
|
+
expect(log).toContain('1 unaccountable')
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('check-theme --audit-allows — basalt.exemptRules', () => {
|
|
104
|
+
it('prints the reason from the object form, and says so when there is none', () => {
|
|
105
|
+
fixture("export const c = '#ff0000'\n", {
|
|
106
|
+
exemptRules: { 'raw-hex': { paths: ['src'], reason: 'a vendor asset basalt does not own' } },
|
|
107
|
+
})
|
|
108
|
+
const { log } = audit()
|
|
109
|
+
expect(log).toContain('a vendor asset basalt does not own')
|
|
110
|
+
expect(log).toContain('suppresses findings in src/app.tsx')
|
|
111
|
+
|
|
112
|
+
fixture("export const c = '#ff0000'\n", { exemptRules: { 'raw-hex': ['src'] } })
|
|
113
|
+
expect(audit().log).toContain('[no reason recorded]')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('the object form really does exempt — the reason is accountability, not a second config', () => {
|
|
117
|
+
fixture("export const c = '#ff0000'\n", {
|
|
118
|
+
exemptRules: { 'raw-hex': { paths: ['src'], reason: 'a vendor asset basalt does not own' } },
|
|
119
|
+
})
|
|
120
|
+
expect(checkTheme(dir, [])).toBe(0)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('reports a pattern that matches no scanned file as dead, and exits 1', () => {
|
|
124
|
+
// rollhook wrote a real, correct relative path, it matched nothing, and nothing said so — the
|
|
125
|
+
// finding kept failing the build with the config sitting right there looking like the answer.
|
|
126
|
+
fixture("export const c = '#ff0000'\n", { exemptRules: { 'raw-hex': ['does-not-exist'] } })
|
|
127
|
+
const { code, log } = audit()
|
|
128
|
+
expect(log).toContain('matches no scanned file')
|
|
129
|
+
expect(code).toBe(1)
|
|
130
|
+
})
|
|
131
|
+
})
|