@cevek/screentest 0.3.2 → 0.3.3

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
@@ -159,12 +159,15 @@ Or pass `--worker-url` / `--token` to `screentest` directly.
159
159
  ## CI
160
160
 
161
161
  ```yaml
162
- - run: npx screentest serve # builds image + starts daemon
162
+ - run: npx screentest serve # build image + start daemon
163
163
  - run: CI=1 npx vitest run --config vitest.screentest.config.ts
164
164
  ```
165
165
 
166
- In `CI=1` the orchestrator exits with vitest's code and never tries to
167
- open the UI.
166
+ In `CI=1` the globalSetup exits with vitest's code and never tries to
167
+ open the UI. Full recipe (GitHub Actions example, docker layer caching
168
+ to skip the ~2 min cold-start build, downloading actuals for offline
169
+ review, pnpm/yarn variants) is in the [CI guide on
170
+ GitHub](https://github.com/x-cevek/screentest/blob/main/docs/ci.md).
168
171
 
169
172
  ## Input format reference
170
173
 
@@ -83,7 +83,8 @@ function countLeaves(doc) {
83
83
  for (const g of doc.groups) walk(g.items);
84
84
  return n;
85
85
  }
86
- async function generateDoc(projectRoot, snapshotFile, actualDir, docFile, cacheDir) {
86
+ async function generateDoc(projectRoot, snapshotFile, actualDir, docFile, cacheDir, opts = {}) {
87
+ const detectDeletions = opts.detectDeletions ?? true;
87
88
  const snap = await readSnapshot(snapshotFile);
88
89
  const expectedByPath = indexSnapshot(snap);
89
90
  const actuals = await walkActual(actualDir);
@@ -121,21 +122,24 @@ async function generateDoc(projectRoot, snapshotFile, actualDir, docFile, cacheD
121
122
  });
122
123
  summary.change++;
123
124
  }
124
- for (const [key, hash] of expectedByPath) {
125
- if (seen.has(key) || !hash) continue;
126
- insertLeaf(doc, key.split("/"), {
127
- type: "deleted",
128
- expectedHash: hash,
129
- patchSnapshotJsonFile: snapshotFile
130
- });
131
- summary.deleted++;
125
+ if (detectDeletions) {
126
+ for (const [key, hash] of expectedByPath) {
127
+ if (seen.has(key) || !hash) continue;
128
+ insertLeaf(doc, key.split("/"), {
129
+ type: "deleted",
130
+ expectedHash: hash,
131
+ patchSnapshotJsonFile: snapshotFile
132
+ });
133
+ summary.deleted++;
134
+ }
132
135
  }
133
136
  await fs.mkdir(cacheDir, { recursive: true });
134
137
  await fs.writeFile(docFile, JSON.stringify(doc, null, 2));
135
138
  const total = countLeaves(doc);
139
+ const deletedSummary = detectDeletions ? `deleted: ${summary.deleted}, ` : "";
136
140
  process.stdout.write(
137
141
  `Wrote ${docFile}
138
- ${total} diff${total === 1 ? "" : "s"} to review (new: ${summary.new}, change: ${summary.change}, deleted: ${summary.deleted}, unchanged: ${summary.unchanged})
142
+ ${total} diff${total === 1 ? "" : "s"} to review (new: ${summary.new}, change: ${summary.change}, ${deletedSummary}unchanged: ${summary.unchanged}${detectDeletions ? "" : "; deletion detection disabled \u2014 subset run"})
139
143
  `
140
144
  );
141
145
  return total;
@@ -208,6 +212,40 @@ async function resolveWsEndpoint() {
208
212
  );
209
213
  return ws;
210
214
  }
215
+ function detectVitestFilter() {
216
+ if (process.env.SCREENTEST_FORCE_DELETION_DETECTION) {
217
+ return { filtered: false, reason: null };
218
+ }
219
+ const argv = process.argv;
220
+ for (let i = 0; i < argv.length; i++) {
221
+ const a = argv[i];
222
+ if (a === "-t" || a === "--testNamePattern") return { filtered: true, reason: a };
223
+ if (a?.startsWith("--testNamePattern=")) return { filtered: true, reason: a };
224
+ }
225
+ const runIdx = argv.indexOf("run");
226
+ if (runIdx === -1) return { filtered: false, reason: null };
227
+ const flagsTakingValue = /* @__PURE__ */ new Set([
228
+ "--config",
229
+ "-c",
230
+ "--reporter",
231
+ "--root",
232
+ "-r",
233
+ "--mode",
234
+ "--inspect-brk",
235
+ "--inspect",
236
+ "--coverage.reporter",
237
+ "--shard"
238
+ ]);
239
+ for (let i = runIdx + 1; i < argv.length; i++) {
240
+ const a = argv[i];
241
+ if (a.startsWith("-")) {
242
+ if (flagsTakingValue.has(a)) i += 1;
243
+ continue;
244
+ }
245
+ return { filtered: true, reason: a };
246
+ }
247
+ return { filtered: false, reason: null };
248
+ }
211
249
  function spawnUI(docFile) {
212
250
  const here = dirname(fileURLToPath(import.meta.url));
213
251
  const bin = join3(here, "index.js");
@@ -229,6 +267,15 @@ async function setup(project) {
229
267
  return async () => {
230
268
  if (server) await server.close().catch(() => {
231
269
  });
270
+ const filter = detectVitestFilter();
271
+ if (filter.filtered) {
272
+ process.stderr.write(
273
+ `[screentest] subset run detected (${filter.reason}) \u2014 skipping deletion
274
+ [screentest] detection so untouched tests in snapshot.json are preserved.
275
+ [screentest] Run the full suite without filters to see deletions.
276
+ `
277
+ );
278
+ }
232
279
  let total = 0;
233
280
  try {
234
281
  total = await generateDoc(
@@ -236,7 +283,8 @@ async function setup(project) {
236
283
  paths.snapshotFile,
237
284
  paths.actualDir,
238
285
  paths.docFile,
239
- paths.cacheDir
286
+ paths.cacheDir,
287
+ { detectDeletions: !filter.filtered }
240
288
  );
241
289
  } catch (err) {
242
290
  const msg = err instanceof Error ? err.message : String(err);
package/dist/index.js CHANGED
@@ -15243,7 +15243,8 @@ function countLeaves(doc) {
15243
15243
  for (const g of doc.groups) walk(g.items);
15244
15244
  return n;
15245
15245
  }
15246
- async function generateDoc(projectRoot, snapshotFile, actualDir, docFile, cacheDir) {
15246
+ async function generateDoc(projectRoot, snapshotFile, actualDir, docFile, cacheDir, opts = {}) {
15247
+ const detectDeletions = opts.detectDeletions ?? true;
15247
15248
  const snap = await readSnapshot(snapshotFile);
15248
15249
  const expectedByPath = indexSnapshot(snap);
15249
15250
  const actuals = await walkActual(actualDir);
@@ -15281,21 +15282,24 @@ async function generateDoc(projectRoot, snapshotFile, actualDir, docFile, cacheD
15281
15282
  });
15282
15283
  summary.change++;
15283
15284
  }
15284
- for (const [key, hash2] of expectedByPath) {
15285
- if (seen.has(key) || !hash2) continue;
15286
- insertLeaf(doc, key.split("/"), {
15287
- type: "deleted",
15288
- expectedHash: hash2,
15289
- patchSnapshotJsonFile: snapshotFile
15290
- });
15291
- summary.deleted++;
15285
+ if (detectDeletions) {
15286
+ for (const [key, hash2] of expectedByPath) {
15287
+ if (seen.has(key) || !hash2) continue;
15288
+ insertLeaf(doc, key.split("/"), {
15289
+ type: "deleted",
15290
+ expectedHash: hash2,
15291
+ patchSnapshotJsonFile: snapshotFile
15292
+ });
15293
+ summary.deleted++;
15294
+ }
15292
15295
  }
15293
15296
  await fs4.mkdir(cacheDir, { recursive: true });
15294
15297
  await fs4.writeFile(docFile, JSON.stringify(doc, null, 2));
15295
15298
  const total = countLeaves(doc);
15299
+ const deletedSummary = detectDeletions ? `deleted: ${summary.deleted}, ` : "";
15296
15300
  process.stdout.write(
15297
15301
  `Wrote ${docFile}
15298
- ${total} diff${total === 1 ? "" : "s"} to review (new: ${summary.new}, change: ${summary.change}, deleted: ${summary.deleted}, unchanged: ${summary.unchanged})
15302
+ ${total} diff${total === 1 ? "" : "s"} to review (new: ${summary.new}, change: ${summary.change}, ${deletedSummary}unchanged: ${summary.unchanged}${detectDeletions ? "" : "; deletion detection disabled \u2014 subset run"})
15299
15303
  `
15300
15304
  );
15301
15305
  return total;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.3.2",
6
+ "version": "0.3.3",
7
7
  "description": "Local desktop tool for visual screenshot-test review — CLI, runner helpers, and review UI shipped as a single package.",
8
8
  "license": "MIT",
9
9
  "type": "module",