tewip-ai 0.70.9 → 0.70.11

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.
@@ -62,12 +62,25 @@ export function impliedLandmark(layerName) {
62
62
  return null;
63
63
  }
64
64
  const PLACEHOLDER = /^(?:lorem ipsum|placeholder|label|text|body|paragraph|xxx+|\.\.\.|…)$/i;
65
- /** Text worth checking for: real words a person would notice missing. */
65
+ /**
66
+ * Text worth checking for: real words a person would notice missing - **or a number**.
67
+ *
68
+ * This required two consecutive letters, which filters the noise it was written for (a lone glyph,
69
+ * an ellipsis, "Label") and also silently discarded every number in the design. Field-tested: a
70
+ * checkout frame specifying **£54.99** against a page serving **£59.99** produced a byte-identical
71
+ * report - "5 check(s) from the design, 3 met, 2 different" - because the price was never one of the
72
+ * five. A wrong price on a checkout screen is the drift a design review exists to catch, and it was
73
+ * the one kind that could not be seen.
74
+ *
75
+ * A run of two or more digits now counts, so a price, a total, a quantity or a rate becomes a check.
76
+ * A single character still does not, nor does punctuation on its own: `3`, `—` and `...` are noise,
77
+ * `£54.99` and `20%` are the design speaking.
78
+ */
66
79
  function meaningful(text) {
67
80
  const t = text.trim();
68
81
  if (t.length < 2 || t.length > 120)
69
82
  return false;
70
- if (!/[A-Za-z]{2}/.test(t))
83
+ if (!/[A-Za-z]{2}/.test(t) && !/\d{2}/.test(t))
71
84
  return false;
72
85
  return !PLACEHOLDER.test(t);
73
86
  }
@@ -19,6 +19,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
19
19
  import { existsSync, readFileSync, readdirSync, statSync, watch } from 'node:fs';
20
20
  import { join, resolve } from 'node:path';
21
21
  import { spawnSync } from 'node:child_process';
22
+ import { detectFrameworks } from '../quality/frameworks.js';
22
23
  /** Where test runners in this project put their reports, in the order they are usually found. */
23
24
  export const REPORT_PLACES = [
24
25
  'blob-report',
@@ -103,6 +104,40 @@ const SKIP_DIR = /(?:^|[\\/])(?:node_modules|\.git|\.tewip|dist|build|target|cov
103
104
  * Watches the project and explains failures as they appear. Returns when `once` is set; otherwise
104
105
  * it runs until the person stops it, which is what a terminal beside your editor is for.
105
106
  */
107
+ /**
108
+ * What to change so the next run can be explained - named for the runner this project actually uses.
109
+ *
110
+ * The advice was a fixed list of three examples: pytest, jest, dotnet. Field-tested on a project
111
+ * using **`node --test`**, Node's own built-in runner, the suite failed with a perfectly clear
112
+ * assertion - `26 !== 25`, the test named, the line numbered - and the answer was a list that did
113
+ * not mention the runner failing in front of it. Telling somebody to configure jest when they are
114
+ * not using jest is the kind of advice that gets a tool closed.
115
+ *
116
+ * The frameworks this project has are already detectable, so the first line is the one command that
117
+ * will work here, and the examples stay underneath for anyone whose runner was not found.
118
+ */
119
+ export function howToWriteAReport(root) {
120
+ const JUNIT = {
121
+ node: 'node --test --test-reporter=junit --test-reporter-destination=report.xml',
122
+ jest: 'npx jest --reporters=default --reporters=jest-junit',
123
+ vitest: 'npx vitest run --reporter=junit --outputFile=report.xml',
124
+ mocha: 'npx mocha --reporter mocha-junit-reporter',
125
+ pytest: 'pytest --junitxml=report.xml',
126
+ go: 'gotestsum --junitfile report.xml',
127
+ dotnet: 'dotnet test --logger trx',
128
+ maven: 'mvn test (Surefire already writes XML to target/surefire-reports)',
129
+ gradle: './gradlew test (already writes XML to build/test-results)',
130
+ playwright: 'npx playwright test --reporter=blob',
131
+ };
132
+ const here = detectFrameworks(root)
133
+ .map((f) => JUNIT[f.id])
134
+ .filter((line, i, all) => Boolean(line) && all.indexOf(line) === i);
135
+ const head = 'The tests failed but wrote no report Tewip can read.';
136
+ if (here.length === 0) {
137
+ return `${head} Make the runner write JUnit XML (for example: pytest --junitxml=report.xml, jest --reporters=jest-junit, dotnet test --logger trx).`;
138
+ }
139
+ return [`${head} For the runner${here.length === 1 ? '' : 's'} this project has, that is:`, ...here.map((line) => ` ${line}`)].join(String.fromCharCode(10));
140
+ }
106
141
  export async function runWatch(cmd) {
107
142
  const root = resolve(cmd.root);
108
143
  const explain = cmd.explain ?? explainReport;
@@ -124,9 +159,7 @@ export async function runWatch(cmd) {
124
159
  }
125
160
  const found = cmd.report ? { path: resolve(root, cmd.report), at: Date.now() } : newestReport(root);
126
161
  if (!found || !existsSync(found.path)) {
127
- console.log(cmd.cmd
128
- ? 'The tests failed but wrote no report Tewip can read. Make the runner write JUnit XML (for example: pytest --junitxml=report.xml, jest --reporters=jest-junit, dotnet test --logger trx).'
129
- : 'No test report here yet. Run your tests, or use --cmd "npm test" to let Tewip run them.');
162
+ console.log(cmd.cmd ? howToWriteAReport(root) : 'No test report here yet. Run your tests, or use --cmd "npm test" to let Tewip run them.');
130
163
  return;
131
164
  }
132
165
  if (!cmd.cmd && found.at === seen)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tewip-ai",
3
- "version": "0.70.9",
3
+ "version": "0.70.11",
4
4
  "mcpName": "io.github.hmamut39/tewip",
5
5
  "description": "Tewip finds why each failing test failed - real bug, flaky test, environment, test data or changed selector - for any stack, from the command line, CI, or your editor.",
6
6
  "keywords": [