cursedbelt 4.1.1 → 4.4.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/dist/react/components/Autocomplete.d.ts.map +1 -1
- package/dist/react/components/Autocomplete.js +1 -1
- package/dist/react/components/Autocomplete.js.map +1 -1
- package/dist/react/components/ComboboxField.d.ts.map +1 -1
- package/dist/react/components/ComboboxField.js +1 -1
- package/dist/react/components/ComboboxField.js.map +1 -1
- package/dist/react/media/CanvasVideoPlayer.d.ts +3 -2
- package/dist/react/media/CanvasVideoPlayer.d.ts.map +1 -1
- package/dist/react/media/CanvasVideoPlayer.js +4 -3
- package/dist/react/media/CanvasVideoPlayer.js.map +1 -1
- package/dist/react/media/FrameGrabber.d.ts +16 -2
- package/dist/react/media/FrameGrabber.d.ts.map +1 -1
- package/dist/react/media/FrameGrabber.js +68 -43
- package/dist/react/media/FrameGrabber.js.map +1 -1
- package/dist/react/media/PictureEditor.d.ts +17 -2
- package/dist/react/media/PictureEditor.d.ts.map +1 -1
- package/dist/react/media/PictureEditor.js +33 -7
- package/dist/react/media/PictureEditor.js.map +1 -1
- package/dist/react/media/VideoChapterEditor.d.ts +17 -2
- package/dist/react/media/VideoChapterEditor.d.ts.map +1 -1
- package/dist/react/media/VideoChapterEditor.js +21 -3
- package/dist/react/media/VideoChapterEditor.js.map +1 -1
- package/dist/react/media/VideoCutEditor.d.ts +16 -2
- package/dist/react/media/VideoCutEditor.d.ts.map +1 -1
- package/dist/react/media/VideoCutEditor.js +21 -3
- package/dist/react/media/VideoCutEditor.js.map +1 -1
- package/dist/react/media/VideoPlayer.d.ts +37 -9
- package/dist/react/media/VideoPlayer.d.ts.map +1 -1
- package/dist/react/media/VideoPlayer.js +130 -39
- package/dist/react/media/VideoPlayer.js.map +1 -1
- package/dist/react/media/hlsSource.d.ts +24 -3
- package/dist/react/media/hlsSource.d.ts.map +1 -1
- package/dist/react/media/hlsSource.js +109 -8
- package/dist/react/media/hlsSource.js.map +1 -1
- package/dist/react/media/pictureEditor.css +2 -0
- package/dist/react/media/sourceFault.d.ts +146 -0
- package/dist/react/media/sourceFault.d.ts.map +1 -0
- package/dist/react/media/sourceFault.js +139 -0
- package/dist/react/media/sourceFault.js.map +1 -0
- package/dist/react/media/videoChapterEditor.css +19 -0
- package/dist/react/media/videoCutEditor.css +22 -0
- package/dist/react/media-gallery/MediaGallery.d.ts +21 -3
- package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
- package/dist/react/media-gallery/MediaGallery.js +178 -17
- package/dist/react/media-gallery/MediaGallery.js.map +1 -1
- package/dist/react/media-gallery/galleryItemMedia.d.ts +4 -0
- package/dist/react/media-gallery/galleryItemMedia.d.ts.map +1 -1
- package/dist/react/media-gallery/galleryItemMedia.js +27 -3
- package/dist/react/media-gallery/galleryItemMedia.js.map +1 -1
- package/dist/react/media-gallery/mediaGalleryModel.d.ts.map +1 -1
- package/dist/react/media-gallery/mediaGalleryModel.js +5 -0
- package/dist/react/media-gallery/mediaGalleryModel.js.map +1 -1
- package/dist/react/media-gallery/types.d.ts +23 -0
- package/dist/react/media-gallery/types.d.ts.map +1 -1
- package/dist/scripts/guardrailsEnforce.d.ts +1 -0
- package/dist/scripts/guardrailsEnforce.d.ts.map +1 -1
- package/dist/styles-areas/analytics.css +1 -1
- package/dist/styles-areas/data-table.css +1 -1
- package/dist/styles-areas/fields.css +1 -1
- package/dist/styles-areas/media-gallery.css +1 -1
- package/dist/styles-areas/media.css +1 -1
- package/dist/styles-areas/wizard.css +1 -1
- package/package.json +51 -6
- package/scripts/demoServer.ts +211 -0
- package/scripts/gate.ts +101 -0
- package/scripts/guardrailsEnforce.spec.ts +313 -1
- package/scripts/guardrailsEnforce.ts +283 -26
- package/scripts/paths.ts +1 -1
- package/src/demoFixture.spec.ts +54 -10
- package/src/demoStaticServer.spec.ts +169 -0
- package/src/publishShape.spec.ts +79 -0
- package/src/react/components/Autocomplete.tsx +11 -1
- package/src/react/components/ComboboxField.spec.tsx +12 -0
- package/src/react/components/ComboboxField.tsx +11 -1
- package/src/react/media/CanvasVideoPlayer.tsx +11 -6
- package/src/react/media/FrameGrabber.spec.tsx +57 -1
- package/src/react/media/FrameGrabber.tsx +138 -84
- package/src/react/media/PictureEditor.spec.tsx +90 -0
- package/src/react/media/PictureEditor.tsx +78 -23
- package/src/react/media/VideoChapterEditor.spec.tsx +86 -0
- package/src/react/media/VideoChapterEditor.tsx +64 -18
- package/src/react/media/VideoCutEditor.spec.tsx +44 -0
- package/src/react/media/VideoCutEditor.tsx +63 -18
- package/src/react/media/VideoPlayer.spec.tsx +101 -0
- package/src/react/media/VideoPlayer.tsx +221 -73
- package/src/react/media/hlsSource.answer.spec.ts +255 -0
- package/src/react/media/hlsSource.ts +136 -8
- package/src/react/media/pictureEditor.css +2 -0
- package/src/react/media/sourceFault.spec.tsx +224 -0
- package/src/react/media/sourceFault.tsx +220 -0
- package/src/react/media/videoChapterEditor.css +19 -0
- package/src/react/media/videoCutEditor.css +22 -0
- package/src/react/media-gallery/MediaGallery.spec.tsx +311 -0
- package/src/react/media-gallery/MediaGallery.tsx +382 -54
- package/src/react/media-gallery/galleryItemMedia.tsx +27 -4
- package/src/react/media-gallery/mediaGalleryModel.spec.ts +36 -0
- package/src/react/media-gallery/mediaGalleryModel.ts +4 -0
- package/src/react/media-gallery/types.ts +23 -0
- package/src/styles-areas/analytics.css +1 -1
- package/src/styles-areas/data-table.css +1 -1
- package/src/styles-areas/fields.css +1 -1
- package/src/styles-areas/media-gallery.css +1 -1
- package/src/styles-areas/media.css +1 -1
- package/src/styles-areas/wizard.css +1 -1
- package/src/testFilesRunInParallel.spec.ts +3 -3
- package/src/typecheckCachesAreSeparate.spec.ts +2 -2
- package/src/verifyGraph.spec.ts +75 -108
- package/scripts/verify.ts +0 -296
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
// 🔴 `package.json`'s `test` script globs `src scripts`, not `src` — this file is the only
|
|
2
|
+
// spec outside `src`, and until 2026-09-18 nothing ran it. The gate was green while every
|
|
3
|
+
// assertion in here (129 of them, the scanner's entire behavioural contract) was inert, so a
|
|
4
|
+
// rule could rot and `bun run verify` would still say ok. If you narrow that glob back, the
|
|
5
|
+
// tests below stop being checks and become a document.
|
|
1
6
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
7
|
import { tmpdir } from 'node:os';
|
|
3
8
|
import { join } from 'node:path';
|
|
4
9
|
import { describe, expect, test } from 'bun:test';
|
|
5
|
-
import { parseFileAllows, RULES, setReimplementedNames, walk } from './guardrailsEnforce';
|
|
10
|
+
import { beltExportedComponentNames, parseFileAllows, RULES, setReimplementedNames, walk } from './guardrailsEnforce';
|
|
6
11
|
|
|
7
12
|
function check(rule: keyof typeof RULES, source: string) {
|
|
8
13
|
return RULES[rule].check('test.tsx', source);
|
|
@@ -107,6 +112,70 @@ describe('opening-tag extraction (comment-aware)', () => {
|
|
|
107
112
|
});
|
|
108
113
|
});
|
|
109
114
|
|
|
115
|
+
// Filed 2026-09-18, found while proving the fix above: the first negative test came back
|
|
116
|
+
// green for the WRONG reason. Making the walker comment-aware widened what it returns, and
|
|
117
|
+
// the opt-out checks then read prose with regexes that look for a real ATTRIBUTE — so a
|
|
118
|
+
// comment merely QUOTING `role="radio"` suppressed no-raw-action-button on a raw button
|
|
119
|
+
// that had no presentational role at all. The rule could not be trusted to read zero in
|
|
120
|
+
// exactly the well-documented files it should be strictest about. The input was wrong, not
|
|
121
|
+
// the pattern: the comment check reads the tag as written, the attribute check reads it
|
|
122
|
+
// comment-blanked.
|
|
123
|
+
describe('a presentational marker must be an ATTRIBUTE, not prose about one', () => {
|
|
124
|
+
test('role="radio" only inside a comment does NOT opt the button out', () => {
|
|
125
|
+
const source = `<button
|
|
126
|
+
// A radio-like list: \`role="radio"\` + \`aria-checked\` says so to a screen reader.
|
|
127
|
+
type="button"
|
|
128
|
+
onClick={() => onPick(id)}
|
|
129
|
+
>Move here</button>`;
|
|
130
|
+
expect(check('no-raw-action-button', source)).toHaveLength(1);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('aria-selected= only inside a block comment does NOT opt the button out', () => {
|
|
134
|
+
const source = `<button
|
|
135
|
+
/* The row is the control here; aria-selected= would be the tab-ish spelling. */
|
|
136
|
+
type="button"
|
|
137
|
+
>Move here</button>`;
|
|
138
|
+
expect(check('no-raw-action-button', source)).toHaveLength(1);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('the guardrails-ignore comment still opts out beside that same prose', () => {
|
|
142
|
+
const source = `<button
|
|
143
|
+
// A radio-like list: \`role="radio"\` + \`aria-checked\` says so to a screen reader.
|
|
144
|
+
// guardrails-ignore no-raw-action-button: presentational row in a radio-like list
|
|
145
|
+
type="button"
|
|
146
|
+
>Move here</button>`;
|
|
147
|
+
expect(check('no-raw-action-button', source)).toHaveLength(0);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('the REAL attributes still pass silently, prose and all', () => {
|
|
151
|
+
const source = `<button
|
|
152
|
+
// A radio-like list: \`role="radio"\` + \`aria-checked\` says so to a screen reader.
|
|
153
|
+
role="radio"
|
|
154
|
+
aria-checked={selected}
|
|
155
|
+
type="button"
|
|
156
|
+
>Move here</button>`;
|
|
157
|
+
expect(check('no-raw-action-button', source)).toHaveLength(0);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// The same shape on no-raw-title-attr, which fires the other way: there the attribute
|
|
161
|
+
// regex is what CREATES the violation, so prose mentioning `title=` invented one.
|
|
162
|
+
test('a title= written only in a comment is not a title attribute', () => {
|
|
163
|
+
const source = `<span
|
|
164
|
+
// Not a title= hover hint — the expansion is rendered beside it.
|
|
165
|
+
className="tabular"
|
|
166
|
+
>UTC</span>`;
|
|
167
|
+
expect(check('no-raw-title-attr', source)).toHaveLength(0);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('a real title attribute below that prose is still flagged', () => {
|
|
171
|
+
const source = `<span
|
|
172
|
+
// Reads as a title= hover hint, and that is the bug.
|
|
173
|
+
title="Coordinated Universal Time"
|
|
174
|
+
>UTC</span>`;
|
|
175
|
+
expect(check('no-raw-title-attr', source)).toHaveLength(1);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
110
179
|
describe('no-raw-overflow', () => {
|
|
111
180
|
test('flags inline style overflow: auto', () => {
|
|
112
181
|
expect(check('no-raw-overflow', '<div style={{ overflow: "auto" }} />')).toHaveLength(1);
|
|
@@ -864,3 +933,246 @@ describe('no-fast-tooltip', () => {
|
|
|
864
933
|
).toHaveLength(0);
|
|
865
934
|
});
|
|
866
935
|
});
|
|
936
|
+
|
|
937
|
+
// ── the CLI's two ratchets, and the flags that let an app use one ──────────────
|
|
938
|
+
// Nine apps each carried an identical ~140-line wrapper (`apps/*/scripts/guardrails.ts`)
|
|
939
|
+
// because this CLI could only read the baseline shipped INSIDE the package. These
|
|
940
|
+
// exercise the flags that replaced them, as a subprocess — the wrappers' only contract
|
|
941
|
+
// was this script's exit code and its verdict lines, and that is what an app's `verify`
|
|
942
|
+
// still depends on.
|
|
943
|
+
|
|
944
|
+
const CLI = join(import.meta.dir, 'guardrailsEnforce.ts');
|
|
945
|
+
|
|
946
|
+
function withFixture(files: Record<string, string>, body: (dir: string) => void): void {
|
|
947
|
+
const root = mkdtempSync(join(tmpdir(), 'guardrails-cli-'));
|
|
948
|
+
try {
|
|
949
|
+
for (const [rel, contents] of Object.entries(files)) {
|
|
950
|
+
const full = join(root, rel);
|
|
951
|
+
mkdirSync(join(full, '..'), { recursive: true });
|
|
952
|
+
writeFileSync(full, contents);
|
|
953
|
+
}
|
|
954
|
+
body(root);
|
|
955
|
+
} finally {
|
|
956
|
+
rmSync(root, { recursive: true, force: true });
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function runCli(cwd: string, args: string[]): { code: number; report: string } {
|
|
961
|
+
const run = Bun.spawnSync([process.execPath, CLI, ...args], { cwd });
|
|
962
|
+
return { code: run.exitCode, report: `${run.stdout.toString()}${run.stderr.toString()}` };
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/** Two hex colours and one raw <input>: counts a baseline row can be wrong about. */
|
|
966
|
+
const FIXTURE = {
|
|
967
|
+
'src/ui/theme.css': '.a { color: #ff0000; }\n.b { background: #00ff00; }\n',
|
|
968
|
+
'src/ui/Form.tsx': 'export const Form = () => <input name="q" />;\n',
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
const rowsFor = (colors: number, inputs: number) =>
|
|
972
|
+
JSON.stringify({ 'no-hardcoded-colors': colors, 'no-raw-inputs': inputs });
|
|
973
|
+
|
|
974
|
+
describe('--baseline (a consuming repo\'s ratchet)', () => {
|
|
975
|
+
test('passes when every row equals the count that is actually there', () => {
|
|
976
|
+
withFixture({ ...FIXTURE, 'scripts/baseline.json': rowsFor(2, 1) }, (dir) => {
|
|
977
|
+
const { code, report } = runCli(dir, [
|
|
978
|
+
'no-hardcoded-colors,no-raw-inputs',
|
|
979
|
+
'--baseline',
|
|
980
|
+
'scripts/baseline.json',
|
|
981
|
+
'src',
|
|
982
|
+
]);
|
|
983
|
+
expect(report).toContain('✓ no-hardcoded-colors: 2 violation(s)');
|
|
984
|
+
expect(report).toContain('✓ no-raw-inputs: 1 violation(s)');
|
|
985
|
+
expect(code).toBe(0);
|
|
986
|
+
});
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
test('🔴 a row ABOVE the real count FAILS, printing the number to paste', () => {
|
|
990
|
+
// The half a one-sided ratchet gets wrong: fix a violation, leave the row where it
|
|
991
|
+
// was, and the slack is available for the next one to grow back into silently.
|
|
992
|
+
withFixture({ ...FIXTURE, 'scripts/baseline.json': rowsFor(9, 1) }, (dir) => {
|
|
993
|
+
const { code, report } = runCli(dir, [
|
|
994
|
+
'no-hardcoded-colors,no-raw-inputs',
|
|
995
|
+
'--baseline',
|
|
996
|
+
'scripts/baseline.json',
|
|
997
|
+
'src',
|
|
998
|
+
]);
|
|
999
|
+
expect(code).toBe(1);
|
|
1000
|
+
expect(report).toContain('✗ no-hardcoded-colors: 2 violation(s), below the baseline of 9');
|
|
1001
|
+
expect(report).toContain('lower "no-hardcoded-colors" to 2 in scripts/baseline.json');
|
|
1002
|
+
});
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
test('a count above its row fails, the way it always did', () => {
|
|
1006
|
+
withFixture({ ...FIXTURE, 'scripts/baseline.json': rowsFor(2, 0) }, (dir) => {
|
|
1007
|
+
const { code, report } = runCli(dir, [
|
|
1008
|
+
'no-hardcoded-colors,no-raw-inputs',
|
|
1009
|
+
'--baseline',
|
|
1010
|
+
'scripts/baseline.json',
|
|
1011
|
+
'src',
|
|
1012
|
+
]);
|
|
1013
|
+
expect(code).toBe(1);
|
|
1014
|
+
expect(report).toContain('✗ no-raw-inputs: 1 violation(s) — exceeds baseline of 0');
|
|
1015
|
+
});
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
test('a row for a rule nobody scans is refused — coverage that is not there', () => {
|
|
1019
|
+
withFixture(
|
|
1020
|
+
{ ...FIXTURE, 'scripts/baseline.json': JSON.stringify({ 'no-hardcoded-colors': 2, 'no-raw-zindex': 0 }) },
|
|
1021
|
+
(dir) => {
|
|
1022
|
+
const { code, report } = runCli(dir, ['no-hardcoded-colors', '--baseline', 'scripts/baseline.json', 'src']);
|
|
1023
|
+
expect(code).toBe(2);
|
|
1024
|
+
expect(report).toContain('row(s) for rules nobody scans: no-raw-zindex');
|
|
1025
|
+
},
|
|
1026
|
+
);
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
test('a scanned rule with no row is refused — a silent 0 demands the whole pile at once', () => {
|
|
1030
|
+
withFixture({ ...FIXTURE, 'scripts/baseline.json': JSON.stringify({ 'no-hardcoded-colors': 2 }) }, (dir) => {
|
|
1031
|
+
const { code, report } = runCli(dir, [
|
|
1032
|
+
'no-hardcoded-colors,no-raw-inputs',
|
|
1033
|
+
'--baseline',
|
|
1034
|
+
'scripts/baseline.json',
|
|
1035
|
+
'src',
|
|
1036
|
+
]);
|
|
1037
|
+
expect(code).toBe(2);
|
|
1038
|
+
expect(report).toContain('no row for rule(s) being scanned: no-raw-inputs');
|
|
1039
|
+
});
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
test('"$comment" carries the why; any OTHER non-number is a typo in a row', () => {
|
|
1043
|
+
withFixture(
|
|
1044
|
+
{
|
|
1045
|
+
...FIXTURE,
|
|
1046
|
+
'scripts/baseline.json': JSON.stringify({ $comment: ['the why', 'over several lines'], 'no-hardcoded-colors': 2 }),
|
|
1047
|
+
},
|
|
1048
|
+
(dir) => {
|
|
1049
|
+
expect(runCli(dir, ['no-hardcoded-colors', '--baseline', 'scripts/baseline.json', 'src']).code).toBe(0);
|
|
1050
|
+
},
|
|
1051
|
+
);
|
|
1052
|
+
withFixture({ ...FIXTURE, 'scripts/baseline.json': JSON.stringify({ 'no-hardcoded-colors': '2' }) }, (dir) => {
|
|
1053
|
+
const { code, report } = runCli(dir, ['no-hardcoded-colors', '--baseline', 'scripts/baseline.json', 'src']);
|
|
1054
|
+
expect(code).toBe(2);
|
|
1055
|
+
expect(report).toContain('"no-hardcoded-colors" is "2", not a violation count');
|
|
1056
|
+
});
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
test('a missing baseline file stops the run rather than defaulting every row to 0', () => {
|
|
1060
|
+
withFixture(FIXTURE, (dir) => {
|
|
1061
|
+
const { code, report } = runCli(dir, ['no-hardcoded-colors', '--baseline', 'scripts/nope.json', 'src']);
|
|
1062
|
+
expect(code).toBe(2);
|
|
1063
|
+
expect(report).toContain('no such file');
|
|
1064
|
+
});
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
test('is not the packaged --scope ratchet, and says so rather than picking one', () => {
|
|
1068
|
+
withFixture({ ...FIXTURE, 'scripts/baseline.json': rowsFor(2, 1) }, (dir) => {
|
|
1069
|
+
const { code, report } = runCli(dir, [
|
|
1070
|
+
'no-hardcoded-colors',
|
|
1071
|
+
'--scope',
|
|
1072
|
+
'cursedbelt',
|
|
1073
|
+
'--baseline',
|
|
1074
|
+
'scripts/baseline.json',
|
|
1075
|
+
'src',
|
|
1076
|
+
]);
|
|
1077
|
+
expect(code).toBe(2);
|
|
1078
|
+
expect(report).toContain('two different ratchets');
|
|
1079
|
+
});
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
test('--scope stays ONE-sided: below its row passes and only suggests', () => {
|
|
1083
|
+
// The packaged baseline lives inside node_modules, so a consumer cannot lower a row
|
|
1084
|
+
// in it. Failing them for a number only a cursedbelt release can change would be a
|
|
1085
|
+
// gate nobody can turn green — which is why only --baseline is two-sided.
|
|
1086
|
+
withFixture(FIXTURE, (dir) => {
|
|
1087
|
+
const { code, report } = runCli(dir, ['no-hardcoded-colors', '--scope', 'cursedbelt', 'src']);
|
|
1088
|
+
expect(code).toBe(1); // 2 found against the packaged cursedbelt row of 0
|
|
1089
|
+
expect(report).toContain('exceeds baseline of 0');
|
|
1090
|
+
});
|
|
1091
|
+
});
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
describe('--reimplements-from-belt', () => {
|
|
1095
|
+
test('flags a local component named after one this package exports', () => {
|
|
1096
|
+
// Read off this package's own react index files, so the list is the truth of the
|
|
1097
|
+
// version an app installed rather than a copy it maintains.
|
|
1098
|
+
withFixture(
|
|
1099
|
+
{
|
|
1100
|
+
'src/Button.tsx': 'export function Button() { return null; }\n',
|
|
1101
|
+
'scripts/baseline.json': JSON.stringify({ 'no-local-reimplementation': 0 }),
|
|
1102
|
+
},
|
|
1103
|
+
(dir) => {
|
|
1104
|
+
const { code, report } = runCli(dir, [
|
|
1105
|
+
'no-local-reimplementation',
|
|
1106
|
+
'--baseline',
|
|
1107
|
+
'scripts/baseline.json',
|
|
1108
|
+
'--reimplements-from-belt',
|
|
1109
|
+
'src',
|
|
1110
|
+
]);
|
|
1111
|
+
expect(code).toBe(1);
|
|
1112
|
+
expect(report).toContain('✗ no-local-reimplementation: 1 violation(s)');
|
|
1113
|
+
expect(report).toContain('src/Button.tsx');
|
|
1114
|
+
},
|
|
1115
|
+
);
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
test('the derived list is this package\'s real exports, not a handful', () => {
|
|
1119
|
+
const names = beltExportedComponentNames();
|
|
1120
|
+
expect(names.length).toBeGreaterThan(100);
|
|
1121
|
+
expect(names).toContain('Button');
|
|
1122
|
+
expect(names).toContain('DataTable');
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
test('🔴 fails CLOSED on an empty list rather than scoring an inert rule 0', () => {
|
|
1126
|
+
// Without this, a moved react tree makes `no-local-reimplementation` print
|
|
1127
|
+
// "0 violation(s)" for a check that never ran, and its 0 row looks earned.
|
|
1128
|
+
const empty = mkdtempSync(join(tmpdir(), 'guardrails-belt-'));
|
|
1129
|
+
try {
|
|
1130
|
+
expect(beltExportedComponentNames(empty)).toEqual([]);
|
|
1131
|
+
} finally {
|
|
1132
|
+
rmSync(empty, { recursive: true, force: true });
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
describe('--exclude', () => {
|
|
1138
|
+
test('drops build output that lives under a source root', () => {
|
|
1139
|
+
// desk's `src/generated/assets.ts` is the compiled browser app inlined as a module:
|
|
1140
|
+
// 282 double-counted violations that move with the bundle, reddening a two-sided
|
|
1141
|
+
// ratchet over work nobody did.
|
|
1142
|
+
const files = {
|
|
1143
|
+
...FIXTURE,
|
|
1144
|
+
'src/generated/assets.css': '.x { color: #123456; }\n.y { color: #654321; }\n',
|
|
1145
|
+
'scripts/baseline.json': rowsFor(2, 1),
|
|
1146
|
+
};
|
|
1147
|
+
withFixture(files, (dir) => {
|
|
1148
|
+
const scanned = runCli(dir, ['no-hardcoded-colors,no-raw-inputs', '--baseline', 'scripts/baseline.json', 'src']);
|
|
1149
|
+
expect(scanned.code).toBe(1); // 4 colours, not 2 — the artifact counted as source
|
|
1150
|
+
|
|
1151
|
+
const { code, report } = runCli(dir, [
|
|
1152
|
+
'no-hardcoded-colors,no-raw-inputs',
|
|
1153
|
+
'--baseline',
|
|
1154
|
+
'scripts/baseline.json',
|
|
1155
|
+
'--exclude',
|
|
1156
|
+
'src/generated',
|
|
1157
|
+
'src',
|
|
1158
|
+
]);
|
|
1159
|
+
expect(report).toContain('✓ no-hardcoded-colors: 2 violation(s)');
|
|
1160
|
+
expect(code).toBe(0);
|
|
1161
|
+
});
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
test('an exclusion matching nothing is refused, never read as handled', () => {
|
|
1165
|
+
withFixture({ ...FIXTURE, 'scripts/baseline.json': rowsFor(2, 1) }, (dir) => {
|
|
1166
|
+
const { code, report } = runCli(dir, [
|
|
1167
|
+
'no-hardcoded-colors,no-raw-inputs',
|
|
1168
|
+
'--baseline',
|
|
1169
|
+
'scripts/baseline.json',
|
|
1170
|
+
'--exclude',
|
|
1171
|
+
'src/generated',
|
|
1172
|
+
'src',
|
|
1173
|
+
]);
|
|
1174
|
+
expect(code).toBe(2);
|
|
1175
|
+
expect(report).toContain('--exclude matched no scanned file');
|
|
1176
|
+
});
|
|
1177
|
+
});
|
|
1178
|
+
});
|