paperlint 10.2.0 → 10.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/.github/workflows/ci.yml +15 -4
- package/README.md +1 -0
- package/dist/adapters/git/index.d.ts +10 -0
- package/dist/adapters/git/index.d.ts.map +1 -0
- package/dist/adapters/git/index.js +151 -0
- package/dist/adapters/git/index.js.map +1 -0
- package/dist/adapters/memory/index.d.ts +6 -0
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +8 -0
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/node/index.d.ts +1 -0
- package/dist/adapters/node/index.d.ts.map +1 -1
- package/dist/adapters/node/index.js +1 -0
- package/dist/adapters/node/index.js.map +1 -1
- package/dist/adapters/node/list-entries.io.d.ts +3 -0
- package/dist/adapters/node/list-entries.io.d.ts.map +1 -0
- package/dist/adapters/node/list-entries.io.js +24 -0
- package/dist/adapters/node/list-entries.io.js.map +1 -0
- package/dist/adapters/study-registry/index.d.ts +412 -1
- package/dist/adapters/study-registry/index.d.ts.map +1 -1
- package/dist/adapters/study-registry/index.js +245 -8
- package/dist/adapters/study-registry/index.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +38 -6
- package/dist/cli.js.map +1 -1
- package/dist/domain/decide-first.d.ts +100 -0
- package/dist/domain/decide-first.d.ts.map +1 -0
- package/dist/domain/decide-first.js +102 -0
- package/dist/domain/decide-first.js.map +1 -0
- package/dist/domain/index-columns.d.ts +45 -0
- package/dist/domain/index-columns.d.ts.map +1 -0
- package/dist/domain/index-columns.js +62 -0
- package/dist/domain/index-columns.js.map +1 -0
- package/dist/domain/study-mode.d.ts +37 -0
- package/dist/domain/study-mode.d.ts.map +1 -0
- package/dist/domain/study-mode.js +45 -0
- package/dist/domain/study-mode.js.map +1 -0
- package/dist/domain/study-registry.d.ts +253 -10
- package/dist/domain/study-registry.d.ts.map +1 -1
- package/dist/domain/study-registry.js +325 -27
- package/dist/domain/study-registry.js.map +1 -1
- package/dist/outputs-before-decision-rule.d.ts +17 -0
- package/dist/outputs-before-decision-rule.d.ts.map +1 -0
- package/dist/outputs-before-decision-rule.js +90 -0
- package/dist/outputs-before-decision-rule.js.map +1 -0
- package/dist/ports/git-history.d.ts +44 -0
- package/dist/ports/git-history.d.ts.map +1 -0
- package/dist/ports/git-history.js +2 -0
- package/dist/ports/git-history.js.map +1 -0
- package/dist/ports/list-entries.d.ts +17 -0
- package/dist/ports/list-entries.d.ts.map +1 -0
- package/dist/ports/list-entries.js +2 -0
- package/dist/ports/list-entries.js.map +1 -0
- package/dist/ports/study-registry-codec.d.ts +9 -2
- package/dist/ports/study-registry-codec.d.ts.map +1 -1
- package/dist/studies.d.ts +4 -0
- package/dist/studies.d.ts.map +1 -1
- package/dist/studies.js +55 -24
- package/dist/studies.js.map +1 -1
- package/dist/study-gate.d.ts +14 -0
- package/dist/study-gate.d.ts.map +1 -0
- package/dist/study-gate.js +95 -0
- package/dist/study-gate.js.map +1 -0
- package/dist/study-index-input.d.ts +5 -0
- package/dist/study-index-input.d.ts.map +1 -0
- package/dist/study-index-input.js +13 -0
- package/dist/study-index-input.js.map +1 -0
- package/dist/study-modes.d.ts +45 -0
- package/dist/study-modes.d.ts.map +1 -0
- package/dist/study-modes.js +70 -0
- package/dist/study-modes.js.map +1 -0
- package/dist/study-registry-rule.d.ts +29 -6
- package/dist/study-registry-rule.d.ts.map +1 -1
- package/dist/study-registry-rule.js +91 -40
- package/dist/study-registry-rule.js.map +1 -1
- package/dist/study-run.d.ts +19 -0
- package/dist/study-run.d.ts.map +1 -0
- package/dist/study-run.js +9 -0
- package/dist/study-run.js.map +1 -0
- package/docs/design/study-modes.md +247 -0
- package/docs/design/study-registry.md +137 -15
- package/docs/e2e.md +8 -4
- package/docs/layout.md +26 -21
- package/docs/rules/repro/outputs-before-decision.md +119 -0
- package/docs/rules/repro/study-plan-name.md +70 -0
- package/docs/rules/repro/study-registered.md +206 -15
- package/docs/rules.md +54 -52
- package/docs/testing.md +1 -0
- package/package.json +2 -1
- package/scripts/check.ts +6 -0
- package/skills/build-benchmark/SKILL.md +24 -12
- package/skills/build-benchmark/SKILL.md.spec.ts +23 -11
- package/skills/build-label-page/SKILL.md +9 -7
- package/skills/build-label-page/SKILL.md.spec.ts +8 -6
- package/skills/camera-ready/SKILL.md +6 -1
- package/skills/camera-ready/SKILL.md.spec.ts +5 -0
- package/skills/draft-paper/SKILL.md +11 -3
- package/skills/draft-paper/SKILL.md.spec.ts +10 -2
- package/skills/harden-paper/SKILL.md +12 -2
- package/skills/harden-paper/SKILL.md.spec.ts +11 -1
- package/skills/osf-artifact-upload/SKILL.md +7 -1
- package/skills/osf-artifact-upload/SKILL.md.spec.ts +6 -0
- package/skills/pc-panel-review/SKILL.md +10 -3
- package/skills/pc-panel-review/SKILL.md.spec.ts +9 -2
- package/skills/run-agent-grid/SKILL.md +10 -5
- package/skills/run-agent-grid/SKILL.md.spec.ts +9 -4
- package/skills/tighten-paper/SKILL.md +5 -1
- package/skills/tighten-paper/SKILL.md.spec.ts +4 -0
- package/src/adapters/git/history.test.ts +121 -0
- package/src/adapters/git/index.test.ts +268 -0
- package/src/adapters/git/index.ts +227 -0
- package/src/adapters/memory/index.test.ts +15 -1
- package/src/adapters/memory/index.ts +13 -0
- package/src/adapters/node/index.ts +1 -0
- package/src/adapters/node/list-entries.io.ts +26 -0
- package/src/adapters/node/list-entries.test.ts +43 -0
- package/src/adapters/study-registry/index.test.ts +343 -0
- package/src/adapters/study-registry/index.ts +374 -10
- package/src/adapters/study-registry/mode.test.ts +162 -0
- package/src/cli.ts +52 -6
- package/src/domain/decide-first.test.ts +258 -0
- package/src/domain/decide-first.ts +243 -0
- package/src/domain/index-columns.test.ts +216 -0
- package/src/domain/index-columns.ts +105 -0
- package/src/domain/study-measurements.test.ts +633 -0
- package/src/domain/study-mode.test.ts +55 -0
- package/src/domain/study-mode.ts +78 -0
- package/src/domain/study-registry.test.ts +48 -32
- package/src/domain/study-registry.ts +621 -36
- package/src/new-paper.test.ts +6 -1
- package/src/outputs-before-decision-rule.test.ts +206 -0
- package/src/outputs-before-decision-rule.ts +124 -0
- package/src/ports/git-history.ts +44 -0
- package/src/ports/list-entries.ts +18 -0
- package/src/ports/study-registry-codec.ts +13 -2
- package/src/rule-cases.test.ts +103 -1
- package/src/studies-cli.test.ts +2 -2
- package/src/studies-index-modes.test.ts +129 -0
- package/src/studies.test.ts +39 -9
- package/src/studies.ts +99 -29
- package/src/study-gate.test.ts +316 -0
- package/src/study-gate.ts +161 -0
- package/src/study-index-input.ts +23 -0
- package/src/study-modes.ts +120 -0
- package/src/study-registry-rule.test.ts +333 -7
- package/src/study-registry-rule.ts +145 -41
- package/src/study-run.ts +21 -0
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
6
6
|
import {
|
|
7
|
+
planTemplate,
|
|
8
|
+
planText,
|
|
7
9
|
registryText,
|
|
8
10
|
type StudyRegistry,
|
|
9
11
|
} from "../../domain/study-registry.ts";
|
|
@@ -142,3 +144,344 @@ describe("refusing a row that is not a study", () => {
|
|
|
142
144
|
});
|
|
143
145
|
});
|
|
144
146
|
});
|
|
147
|
+
|
|
148
|
+
/** A registry with every key above the studies. */
|
|
149
|
+
const FULL = {
|
|
150
|
+
prefix: "P1",
|
|
151
|
+
author: "A. Author",
|
|
152
|
+
requirements: [
|
|
153
|
+
{
|
|
154
|
+
id: "R1",
|
|
155
|
+
text: "Every sample is a random draw from all rules found.",
|
|
156
|
+
date: "2026-10-10",
|
|
157
|
+
source: "chat",
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
measurements: [
|
|
161
|
+
{
|
|
162
|
+
id: "M1",
|
|
163
|
+
title: "Rule census",
|
|
164
|
+
rq: "RQ1",
|
|
165
|
+
role: "headline",
|
|
166
|
+
approval: {
|
|
167
|
+
by: "A. Author",
|
|
168
|
+
date: "2026-10-12",
|
|
169
|
+
quote: "This is the one.",
|
|
170
|
+
source: "review comment",
|
|
171
|
+
},
|
|
172
|
+
requirements: ["R1"],
|
|
173
|
+
frame: "all rules found",
|
|
174
|
+
},
|
|
175
|
+
{ id: "M2", title: "Baseline", role: "baseline" },
|
|
176
|
+
],
|
|
177
|
+
studies: [{ ...ROW, measurement: "M1", plan: "plan.json" }],
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
describe("the keys above the studies: author, requirements, measurements", () => {
|
|
181
|
+
it("a registry with all of them parses to itself, a measurement's extra fields kept", () => {
|
|
182
|
+
expect(parse(text(FULL))).toEqual({ ok: true, registry: FULL });
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("refusing what is not a measurement, a requirement or a plan path", () => {
|
|
187
|
+
it.each([
|
|
188
|
+
[
|
|
189
|
+
"a role that is neither",
|
|
190
|
+
{ ...FULL, measurements: [{ id: "M1", title: "x", role: "main" }] },
|
|
191
|
+
'measurements.0.role: Invalid option: expected one of "headline"|"baseline"',
|
|
192
|
+
],
|
|
193
|
+
[
|
|
194
|
+
"an approval without the author's words",
|
|
195
|
+
{
|
|
196
|
+
...FULL,
|
|
197
|
+
measurements: [
|
|
198
|
+
{
|
|
199
|
+
id: "M1",
|
|
200
|
+
title: "x",
|
|
201
|
+
role: "headline",
|
|
202
|
+
approval: { by: "A", date: "2026-10-12" },
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
"measurements.0.approval.quote: Invalid input: expected string, received undefined",
|
|
207
|
+
],
|
|
208
|
+
[
|
|
209
|
+
"a requirement with a key of its own",
|
|
210
|
+
{ ...FULL, requirements: [{ ...FULL.requirements[0], note: "x" }] },
|
|
211
|
+
'requirements.0: Unrecognized key: "note"',
|
|
212
|
+
],
|
|
213
|
+
[
|
|
214
|
+
"a row's plan that is not a path",
|
|
215
|
+
{ ...FULL, studies: [{ ...ROW, plan: true }] },
|
|
216
|
+
"studies.0.plan: Invalid input: expected string, received boolean",
|
|
217
|
+
],
|
|
218
|
+
])("refuses %s", (_why, v, why) => {
|
|
219
|
+
expect(parse(text(v))).toEqual({ ok: false, why });
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
/** A filled plan of a seeded draw, with keys of the paper's own in `extra`. */
|
|
224
|
+
const parsePlan = studyRegistryCodec.parsePlan;
|
|
225
|
+
const PLAN = {
|
|
226
|
+
question: "How many rules can a linter check?",
|
|
227
|
+
population: {
|
|
228
|
+
source: "the crawl of 2026-10-01",
|
|
229
|
+
draw: { kind: "seeded", method: "random", seed: 7 },
|
|
230
|
+
},
|
|
231
|
+
arms: [{ name: "with the linter" }, { name: "without" }],
|
|
232
|
+
primary_outcome: "the share of rules checked",
|
|
233
|
+
author_check: { by: "A. Author", date: "2026-10-14", quote: "Go." },
|
|
234
|
+
extra: { hypotheses: ["H1"], draw_script: "tools/draw.py" },
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/** `PLAN` with `over` merged in, as the text of a file. */
|
|
238
|
+
const planWith = (over: Readonly<Record<string, unknown>>): string =>
|
|
239
|
+
JSON.stringify({ ...PLAN, ...over });
|
|
240
|
+
|
|
241
|
+
describe("parsing a study's plan: ready or draft", () => {
|
|
242
|
+
it("a filled plan is ready, its `extra` kept as it is", () => {
|
|
243
|
+
expect(parsePlan(JSON.stringify(PLAN), "spike")).toEqual({
|
|
244
|
+
kind: "ready",
|
|
245
|
+
plan: PLAN,
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("a census draws all: no seed", () => {
|
|
250
|
+
const census = {
|
|
251
|
+
...PLAN,
|
|
252
|
+
population: {
|
|
253
|
+
source: "every rule found",
|
|
254
|
+
draw: { kind: "all", method: "census" },
|
|
255
|
+
},
|
|
256
|
+
arms: [],
|
|
257
|
+
};
|
|
258
|
+
expect(parsePlan(JSON.stringify(census), "spike")).toEqual({
|
|
259
|
+
kind: "ready",
|
|
260
|
+
plan: census,
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("the template studies new writes is a draft of a census, naming every empty text", () => {
|
|
265
|
+
expect(planTemplate("spike", "x").population.draw).toEqual({
|
|
266
|
+
kind: "all",
|
|
267
|
+
method: "",
|
|
268
|
+
});
|
|
269
|
+
expect(parsePlan(planText(planTemplate("spike", "x")), "spike")).toEqual({
|
|
270
|
+
kind: "draft",
|
|
271
|
+
plan: planTemplate("spike", "x"),
|
|
272
|
+
empty: [
|
|
273
|
+
"question",
|
|
274
|
+
"population.source",
|
|
275
|
+
"population.draw.method",
|
|
276
|
+
"primary_outcome",
|
|
277
|
+
],
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("a text of white space only is empty", () => {
|
|
282
|
+
const plan = { ...PLAN, primary_outcome: " ", author_check: null };
|
|
283
|
+
expect(parsePlan(JSON.stringify(plan), "spike")).toEqual({
|
|
284
|
+
kind: "draft",
|
|
285
|
+
plan,
|
|
286
|
+
empty: ["primary_outcome"],
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe("parsing a study's plan: no contradictory state", () => {
|
|
292
|
+
it.each([
|
|
293
|
+
[
|
|
294
|
+
"an author_check on a draft: the author approved a plan with empty texts",
|
|
295
|
+
planWith({ question: "", primary_outcome: " " }),
|
|
296
|
+
"author_check: is set, and the plan leaves `question`, `primary_outcome` empty — the author approves a complete plan: fill them, or set author_check to null",
|
|
297
|
+
],
|
|
298
|
+
[
|
|
299
|
+
"a seeded draw without its seed",
|
|
300
|
+
planWith({
|
|
301
|
+
population: {
|
|
302
|
+
source: "x",
|
|
303
|
+
draw: { kind: "seeded", method: "random" },
|
|
304
|
+
},
|
|
305
|
+
}),
|
|
306
|
+
"population.draw.seed: Invalid input",
|
|
307
|
+
],
|
|
308
|
+
[
|
|
309
|
+
"a seeded draw whose seed is null",
|
|
310
|
+
planWith({
|
|
311
|
+
population: {
|
|
312
|
+
source: "x",
|
|
313
|
+
draw: { kind: "seeded", method: "random", seed: null },
|
|
314
|
+
},
|
|
315
|
+
}),
|
|
316
|
+
"population.draw.seed: Invalid input",
|
|
317
|
+
],
|
|
318
|
+
[
|
|
319
|
+
"a draw of all that carries a seed",
|
|
320
|
+
planWith({
|
|
321
|
+
population: {
|
|
322
|
+
source: "x",
|
|
323
|
+
draw: { kind: "all", method: "census", seed: 7 },
|
|
324
|
+
},
|
|
325
|
+
}),
|
|
326
|
+
'population.draw: Unrecognized key: "seed"',
|
|
327
|
+
],
|
|
328
|
+
[
|
|
329
|
+
"a draw of no known kind",
|
|
330
|
+
planWith({
|
|
331
|
+
population: { source: "x", draw: { method: "census" } },
|
|
332
|
+
}),
|
|
333
|
+
"population.draw.kind: Invalid discriminator value. Expected 'seeded' | 'all'",
|
|
334
|
+
],
|
|
335
|
+
])("refuses %s", (_why, t, why) => {
|
|
336
|
+
expect(parsePlan(t, "spike")).toEqual({ kind: "unreadable", why });
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe("parsing a study's plan: no name or fact said twice", () => {
|
|
341
|
+
it.each([
|
|
342
|
+
[
|
|
343
|
+
"two arms of one name",
|
|
344
|
+
planWith({ arms: [{ name: "a" }, { name: "b" }, { name: "a" }] }),
|
|
345
|
+
"arms: more than one arm is named «a» — an arm's name is unique",
|
|
346
|
+
],
|
|
347
|
+
[
|
|
348
|
+
"a key in `extra` that is a field of the plan",
|
|
349
|
+
planWith({ extra: { hypotheses: ["H1"], question: "x" } }),
|
|
350
|
+
"extra.question: is a field of the plan itself — set it there, not in extra",
|
|
351
|
+
],
|
|
352
|
+
[
|
|
353
|
+
"a population size: it is derived, never stored",
|
|
354
|
+
planWith({ population: { ...PLAN.population, size: 4210 } }),
|
|
355
|
+
'population: Unrecognized key: "size"',
|
|
356
|
+
],
|
|
357
|
+
])("refuses %s", (_why, t, why) => {
|
|
358
|
+
expect(parsePlan(t, "spike")).toEqual({ kind: "unreadable", why });
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
describe("parsing a study's plan: strict at every level", () => {
|
|
363
|
+
it.each([
|
|
364
|
+
["text that is not JSON", "{", "not JSON"],
|
|
365
|
+
[
|
|
366
|
+
"a misspelt key at the top",
|
|
367
|
+
planWith({ primary_outcom: "x" }),
|
|
368
|
+
'(top): Unrecognized key: "primary_outcom"',
|
|
369
|
+
],
|
|
370
|
+
[
|
|
371
|
+
"a misspelt key in the draw",
|
|
372
|
+
planWith({
|
|
373
|
+
population: {
|
|
374
|
+
...PLAN.population,
|
|
375
|
+
draw: { kind: "seeded", method: "random", seed: 7, sed: 7 },
|
|
376
|
+
},
|
|
377
|
+
}),
|
|
378
|
+
'population.draw: Unrecognized key: "sed"',
|
|
379
|
+
],
|
|
380
|
+
[
|
|
381
|
+
"a key of the paper's own outside `extra`",
|
|
382
|
+
planWith({ hypotheses: ["H1"] }),
|
|
383
|
+
'(top): Unrecognized key: "hypotheses"',
|
|
384
|
+
],
|
|
385
|
+
[
|
|
386
|
+
"no author_check: null is the one way to say none yet",
|
|
387
|
+
planWith({ author_check: undefined }),
|
|
388
|
+
"author_check: Invalid input: expected object, received undefined",
|
|
389
|
+
],
|
|
390
|
+
[
|
|
391
|
+
"an author_check with an empty quote",
|
|
392
|
+
planWith({ author_check: { ...PLAN.author_check, quote: "" } }),
|
|
393
|
+
"author_check.quote: Too small: expected string to have >=1 characters",
|
|
394
|
+
],
|
|
395
|
+
[
|
|
396
|
+
"an arm with a key besides its name",
|
|
397
|
+
planWith({ arms: [{ name: "a", model: "m" }] }),
|
|
398
|
+
'arms.0: Unrecognized key: "model"',
|
|
399
|
+
],
|
|
400
|
+
])("refuses %s", (_why, t, why) => {
|
|
401
|
+
expect(parsePlan(t, "spike")).toEqual({ kind: "unreadable", why });
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
/** A plan under its old name, written by hand: keys of its own, a size, no seed, no check. */
|
|
406
|
+
const parseLegacyPlan = studyRegistryCodec.parseLegacyPlan;
|
|
407
|
+
const OLD = {
|
|
408
|
+
question: "How many rules can a linter check?",
|
|
409
|
+
population: {
|
|
410
|
+
source: "the crawl of 2026-10-01",
|
|
411
|
+
about: "rules in instruction files",
|
|
412
|
+
size: 4210,
|
|
413
|
+
draw: { method: "random", script: "tools/draw.py" },
|
|
414
|
+
},
|
|
415
|
+
arms: [{ name: "with the linter", model: "m" }],
|
|
416
|
+
primary_outcome: "",
|
|
417
|
+
hypotheses: ["H1"],
|
|
418
|
+
strata: { by: "type" },
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
describe("parsing a plan under its old name, prereg.json: loose", () => {
|
|
422
|
+
it("keys of its own accepted, the top-level ones kept in `extra`; size dropped; no seed and no check: a draw of all, null", () => {
|
|
423
|
+
expect(parseLegacyPlan(JSON.stringify(OLD))).toEqual({
|
|
424
|
+
kind: "draft",
|
|
425
|
+
plan: {
|
|
426
|
+
question: "How many rules can a linter check?",
|
|
427
|
+
population: {
|
|
428
|
+
source: "the crawl of 2026-10-01",
|
|
429
|
+
draw: { kind: "all", method: "random" },
|
|
430
|
+
},
|
|
431
|
+
arms: [{ name: "with the linter" }],
|
|
432
|
+
primary_outcome: "",
|
|
433
|
+
author_check: null,
|
|
434
|
+
extra: { hypotheses: ["H1"], strata: { by: "type" } },
|
|
435
|
+
},
|
|
436
|
+
empty: ["primary_outcome"],
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
describe("parsing a plan under its old name, prereg.json: its draw", () => {
|
|
442
|
+
it("a seed, and a check: a seeded draw, the check kept", () => {
|
|
443
|
+
const old = {
|
|
444
|
+
...OLD,
|
|
445
|
+
population: {
|
|
446
|
+
source: "s",
|
|
447
|
+
draw: { method: "random", seed: "a1" },
|
|
448
|
+
},
|
|
449
|
+
primary_outcome: "share",
|
|
450
|
+
author_check: { by: "A. Author", date: "2026-10-14", quote: "Go." },
|
|
451
|
+
};
|
|
452
|
+
expect(parseLegacyPlan(JSON.stringify(old))).toMatchObject({
|
|
453
|
+
kind: "ready",
|
|
454
|
+
plan: {
|
|
455
|
+
population: {
|
|
456
|
+
source: "s",
|
|
457
|
+
draw: { kind: "seeded", method: "random", seed: "a1" },
|
|
458
|
+
},
|
|
459
|
+
author_check: { by: "A. Author", date: "2026-10-14", quote: "Go." },
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it("a seed of null is a draw of all", () => {
|
|
465
|
+
const old = {
|
|
466
|
+
...OLD,
|
|
467
|
+
population: { source: "s", draw: { method: "census", seed: null } },
|
|
468
|
+
};
|
|
469
|
+
expect(parseLegacyPlan(JSON.stringify(old))).toMatchObject({
|
|
470
|
+
plan: { population: { draw: { kind: "all", method: "census" } } },
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("still needs the plan's own fields", () => {
|
|
475
|
+
expect(parseLegacyPlan(JSON.stringify({ hypotheses: ["H1"] }))).toEqual({
|
|
476
|
+
kind: "unreadable",
|
|
477
|
+
why: "question: Invalid input: expected string, received undefined; population: Invalid input: expected object, received undefined; arms: Invalid input: expected array, received undefined; primary_outcome: Invalid input: expected string, received undefined",
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("text that is not JSON", () => {
|
|
482
|
+
expect(parseLegacyPlan("{")).toEqual({
|
|
483
|
+
kind: "unreadable",
|
|
484
|
+
why: "not JSON",
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
});
|