@syncerauk/test-rules 1.0.3 → 1.0.4

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/README.md CHANGED
@@ -49,7 +49,9 @@ Both in the repo's `scripts/test-rules/`:
49
49
 
50
50
  - `config.json`: what differs between repos. `fail` (false is report only), `tests` and
51
51
  `ignore` (which files are test files), `fixtures` (folders a test may read), `wording` (the
52
- one list of what a person reads), `reads` (the reads register: `"comment"`, or
52
+ one list of what a person reads), `style_contract` (`{ "<test file>": ["<.css or .scss path or
53
+ glob>"] }`: the one test that may read those stylesheets; style rules are checked there, once),
54
+ `reads` (the reads register: `"comment"`, or
53
55
  `{ "json": "<path>" }` for the map under `"tests"` in that file, `"key": "<name>"` for another
54
56
  key, `"key": null` when the file itself is the map). The defaults are `CONFIG_DEFAULTS` in the
55
57
  script.
@@ -67,6 +69,10 @@ A repo starts with `"fail": false`: breaches are printed and the exit is 0. One
67
69
 
68
70
  ## Versions
69
71
 
72
+ - **1.0.4** — `style_contract`: a repo may name one test that reads its stylesheets (`.css`,
73
+ `.scss`), the way wording is read once from the wording list. Those files are not a
74
+ `T4:source-text` breach in that test, and only in that test. Nothing changes for a repo that
75
+ does not set it.
70
76
  - **1.0.3** — A JSON reads register no longer turns off the scan of the test's code. A source
71
77
  file the test builds a path to from its own place, in a test that reads files, is a
72
78
  `T4:source-text` breach unless the test declares it `// runs:`. A repo with a JSON register
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncerauk/test-rules",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "The static check of Syncera's test-writing rules (T1, T4, T6 and the line limit of R7). One file, no dependency. Published to npm by the orchestrator's publish-test-rules workflow; each repo pins an exact version.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/test-rules.mjs CHANGED
@@ -39,7 +39,8 @@
39
39
  // reads the file system and declares it neither `// reads:`, `// runs:` nor
40
40
  // in its JSON register entry (a file that is only run is declared `// runs:`).
41
41
  // A JSON register is what a test says it reads; it does not stop the code
42
- // being read for what it does read (1.0.3).
42
+ // being read for what it does read (1.0.3). A stylesheet named for a test in
43
+ // `style_contract` is not a breach in that test, and only in that test (1.0.4).
43
44
  // T6:timer setTimeout( / setInterval( / sleep( / timers/promises with no fake timers in the file
44
45
  // T6:clock Date.now( / new Date() / performance.now( with no fake timers in the file
45
46
  // T6:outside homedir(), process.env.HOME, a relative path that climbs out of the repo,
@@ -71,6 +72,7 @@ export const CONFIG_DEFAULTS = Object.freeze({
71
72
  ignore: [], // test files the rules do not judge (a real-time tier 3 suite)
72
73
  fixtures: [], // folders a test may read: `a/fixtures/`
73
74
  wording: [], // the one list of what a person reads (a built page, a legal text): files, globs, folders
75
+ style_contract: {}, // { "<test file>": ["<.css/.scss path or glob>", …] }: the one test that may read those stylesheets
74
76
  reads: null, // the reads register: null, "comment" (`// reads:` lines) or { json: "<path>", key: "tests" } (key null: the file itself is the map)
75
77
  source_ext: ['mjs', 'cjs', 'js', 'jsx', 'ts', 'tsx', 'mts', 'cts', 'sql', 'css', 'scss', 'html', 'sh', 'yml', 'yaml', 'md', 'mdx'],
76
78
  max_lines: 1500,
@@ -273,7 +275,9 @@ function envLine({ code, bare }, allowed) {
273
275
  /** T4: the line of the first read of a source file as text, or 0. */
274
276
  function sourceTextLine({ path, text, code, bare, strings }, { config, register, tracked }) {
275
277
  const isSource = (p) => config.source_ext.includes(String(p).split('.').pop().toLowerCase());
276
- const allowed = (p) => under(config.fixtures, p) || under(config.wording, p);
278
+ // A style contract is the one test that may read the stylesheets it names: style rules are
279
+ // checked once, there, the way wording is checked once from the wording list.
280
+ const allowed = (p) => under(config.fixtures, p) || under(config.wording, p) || under(config.style_contract?.[path], p);
277
281
  const isFolderOrGlob = (p) => (/[*?]/.test(p) ? !/\.[A-Za-z0-9]+$/.test(p) || isSource(p) : String(p).endsWith('/'));
278
282
  const offends = (p) => !allowed(p) && (isFolderOrGlob(p) || isSource(p));
279
283
 
@@ -397,6 +401,11 @@ export function withDefaults(raw) {
397
401
  for (const k of ['tests', 'ignore', 'fixtures', 'wording', 'source_ext', 'task_names', 'fake_timers', 'env_allowed']) {
398
402
  if (!Array.isArray(config[k]) || config[k].some((x) => typeof x !== 'string')) throw fail(EXIT.FAILED, `${CONFIG_FILE}: ${k} must be a list of strings`);
399
403
  }
404
+ const sc = config.style_contract;
405
+ const styleOnly = (g) => typeof g === 'string' && /\.s?css$/.test(g);
406
+ if (!sc || typeof sc !== 'object' || Array.isArray(sc) || Object.values(sc).some((v) => !Array.isArray(v) || !v.length || !v.every(styleOnly))) {
407
+ throw fail(EXIT.FAILED, `${CONFIG_FILE}: style_contract must be { "<test file>": ["<path or glob ending .css or .scss>", …] }`);
408
+ }
400
409
  const r = config.reads;
401
410
  const json = r && typeof r === 'object' && !Array.isArray(r) && typeof r.json === 'string'
402
411
  && Object.keys(r).every((k) => k === 'json' || k === 'key') && (r.key === undefined || r.key === null || typeof r.key === 'string');