@shivaedev/test-story 0.0.0 → 0.1.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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +247 -1
- package/dist/genreTags.d.ts +6 -0
- package/dist/genreTags.d.ts.map +1 -0
- package/dist/genreTags.js +5 -0
- package/dist/genreTags.js.map +1 -0
- package/dist/internal/callSite.d.ts +3 -0
- package/dist/internal/callSite.d.ts.map +1 -0
- package/dist/internal/callSite.js +16 -0
- package/dist/internal/callSite.js.map +1 -0
- package/dist/internal/engineSnapshot.d.ts +4 -0
- package/dist/internal/engineSnapshot.d.ts.map +1 -0
- package/dist/internal/engineSnapshot.js +35 -0
- package/dist/internal/engineSnapshot.js.map +1 -0
- package/dist/internal/errors.d.ts +5 -0
- package/dist/internal/errors.d.ts.map +1 -0
- package/dist/internal/errors.js +15 -0
- package/dist/internal/errors.js.map +1 -0
- package/dist/internal/failureReport.d.ts +8 -0
- package/dist/internal/failureReport.d.ts.map +1 -0
- package/dist/internal/failureReport.js +40 -0
- package/dist/internal/failureReport.js.map +1 -0
- package/dist/internal/genre.d.ts +7 -0
- package/dist/internal/genre.d.ts.map +1 -0
- package/dist/internal/genre.js +8 -0
- package/dist/internal/genre.js.map +1 -0
- package/dist/internal/hooked.d.ts +7 -0
- package/dist/internal/hooked.d.ts.map +1 -0
- package/dist/internal/hooked.js +11 -0
- package/dist/internal/hooked.js.map +1 -0
- package/dist/internal/narration.d.ts +25 -0
- package/dist/internal/narration.d.ts.map +1 -0
- package/dist/internal/narration.js +34 -0
- package/dist/internal/narration.js.map +1 -0
- package/dist/internal/render.d.ts +8 -0
- package/dist/internal/render.d.ts.map +1 -0
- package/dist/internal/render.js +56 -0
- package/dist/internal/render.js.map +1 -0
- package/dist/internal/runUntil.d.ts +17 -0
- package/dist/internal/runUntil.d.ts.map +1 -0
- package/dist/internal/runUntil.js +24 -0
- package/dist/internal/runUntil.js.map +1 -0
- package/dist/internal/seed.d.ts +26 -0
- package/dist/internal/seed.d.ts.map +1 -0
- package/dist/internal/seed.js +27 -0
- package/dist/internal/seed.js.map +1 -0
- package/dist/internal/start.d.ts +7 -0
- package/dist/internal/start.d.ts.map +1 -0
- package/dist/internal/start.js +23 -0
- package/dist/internal/start.js.map +1 -0
- package/dist/internal/storyIt.d.ts +16 -0
- package/dist/internal/storyIt.d.ts.map +1 -0
- package/dist/internal/storyIt.js +46 -0
- package/dist/internal/storyIt.js.map +1 -0
- package/dist/internal/traits.d.ts +12 -0
- package/dist/internal/traits.d.ts.map +1 -0
- package/dist/internal/traits.js +8 -0
- package/dist/internal/traits.js.map +1 -0
- package/dist/internal/wrap.d.ts +8 -0
- package/dist/internal/wrap.d.ts.map +1 -0
- package/dist/internal/wrap.js +49 -0
- package/dist/internal/wrap.js.map +1 -0
- package/dist/storyKit.d.ts +65 -0
- package/dist/storyKit.d.ts.map +1 -0
- package/dist/storyKit.js +11 -0
- package/dist/storyKit.js.map +1 -0
- package/docs/north-star.md +35 -0
- package/docs/roadmap.md +18 -0
- package/package.json +67 -3
- package/src/genreTags.ts +10 -0
- package/src/internal/callSite.ts +18 -0
- package/src/internal/engineSnapshot.ts +47 -0
- package/src/internal/errors.ts +39 -0
- package/src/internal/failureReport.ts +54 -0
- package/src/internal/genre.ts +14 -0
- package/src/internal/hooked.ts +22 -0
- package/src/internal/narration.ts +57 -0
- package/src/internal/render.ts +69 -0
- package/src/internal/runUntil.ts +43 -0
- package/src/internal/seed.ts +53 -0
- package/src/internal/start.ts +24 -0
- package/src/internal/storyIt.ts +63 -0
- package/src/internal/traits.ts +20 -0
- package/src/internal/wrap.ts +50 -0
- package/src/storyKit.ts +126 -0
- package/login.log +0 -26
- package/publish.log +0 -0
- package/run.out +0 -26
- package/run.sh +0 -10
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function describeValue(value: unknown): string;
|
|
2
|
+
export declare function describeFailure(failure: {
|
|
3
|
+
readonly _tag?: unknown;
|
|
4
|
+
readonly message: string;
|
|
5
|
+
readonly name?: string;
|
|
6
|
+
}): string;
|
|
7
|
+
export declare function toJson(value: unknown, indent?: number): string;
|
|
8
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/internal/render.ts"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQpD;AAOD,wBAAgB,eAAe,CAAC,OAAO,EAAE;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAO9H;AAYD,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CA6B9D"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const OWN_ERROR_KEYS = new Set(["_tag", "cause", "message", "name", "stack"]);
|
|
2
|
+
// Vitest serializes a failure's class into these keys.
|
|
3
|
+
const SERIALIZED_KEYS = new Set(["constructor", "toString"]);
|
|
4
|
+
export function describeValue(value) {
|
|
5
|
+
if (typeof value === "string") {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
if (value instanceof Error) {
|
|
9
|
+
return describeError(value);
|
|
10
|
+
}
|
|
11
|
+
return typeof value === "object" && value !== null ? toJson(value) : String(value);
|
|
12
|
+
}
|
|
13
|
+
function describeError(error) {
|
|
14
|
+
const tag = "_tag" in error && typeof error._tag === "string" ? error._tag : error.name;
|
|
15
|
+
return describeTagged(tag, error.message, Object.entries(error));
|
|
16
|
+
}
|
|
17
|
+
export function describeFailure(failure) {
|
|
18
|
+
const tag = typeof failure._tag === "string" ? failure._tag : (failure.name ?? "Error");
|
|
19
|
+
return describeTagged(tag, failure.message, Object.entries(failure).filter(([key]) => !SERIALIZED_KEYS.has(key)));
|
|
20
|
+
}
|
|
21
|
+
function describeTagged(tag, message, entries) {
|
|
22
|
+
const fields = Object.fromEntries(entries.filter(([key]) => !OWN_ERROR_KEYS.has(key)));
|
|
23
|
+
const label = tag === "Error" ? "" : tag;
|
|
24
|
+
const detail = Object.keys(fields).length === 0 ? "" : toJson(fields);
|
|
25
|
+
if (message === "") {
|
|
26
|
+
return [label, detail].filter((part) => part !== "").join(" ") || "Error";
|
|
27
|
+
}
|
|
28
|
+
return [label === "" ? message : `${label}: ${message}`, detail].filter((part) => part !== "").join(" ");
|
|
29
|
+
}
|
|
30
|
+
export function toJson(value, indent) {
|
|
31
|
+
const seen = new WeakSet();
|
|
32
|
+
const json = JSON.stringify(value, (_key, item) => {
|
|
33
|
+
if (typeof item === "bigint") {
|
|
34
|
+
return `${item}n`;
|
|
35
|
+
}
|
|
36
|
+
if (typeof item === "symbol") {
|
|
37
|
+
return item.toString();
|
|
38
|
+
}
|
|
39
|
+
if (item instanceof Error) {
|
|
40
|
+
return describeError(item);
|
|
41
|
+
}
|
|
42
|
+
if (typeof item !== "object" || item === null) {
|
|
43
|
+
return item;
|
|
44
|
+
}
|
|
45
|
+
if (seen.has(item)) {
|
|
46
|
+
return "[shown above]";
|
|
47
|
+
}
|
|
48
|
+
seen.add(item);
|
|
49
|
+
if (item instanceof Map) {
|
|
50
|
+
return Object.fromEntries([...item].map(([key, entry]) => [describeValue(key), entry]));
|
|
51
|
+
}
|
|
52
|
+
return item instanceof Set ? [...item] : item;
|
|
53
|
+
}, indent);
|
|
54
|
+
return json ?? String(value);
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/internal/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { type AnyEffect, type Hook } from "#internal/hooked.ts";
|
|
3
|
+
import type { RunUntilOptions } from "#storyKit.ts";
|
|
4
|
+
export interface LooseRunHooks<TEngine> {
|
|
5
|
+
readonly diagnose?: Hook<[engine: TEngine], AnyEffect, string>;
|
|
6
|
+
readonly failed?: Hook<[engine: TEngine], AnyEffect, string | undefined>;
|
|
7
|
+
readonly maxSteps: number;
|
|
8
|
+
readonly step: Hook<[engine: TEngine, tell: (line: string) => void], AnyEffect, void>;
|
|
9
|
+
}
|
|
10
|
+
export interface Running<TEngine> {
|
|
11
|
+
readonly engine: TEngine;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly run: LooseRunHooks<TEngine> | undefined;
|
|
14
|
+
readonly tell: (line: string) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare const runUntil: <TEngine>(args_0: Running<TEngine>, until: Hook<[engine: TEngine], AnyEffect, boolean>, options: RunUntilOptions | undefined) => Effect.Effect<undefined, unknown, unknown>;
|
|
17
|
+
//# sourceMappingURL=runUntil.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runUntil.d.ts","sourceRoot":"","sources":["../../src/internal/runUntil.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,IAAI,EAAU,MAAM,qBAAqB,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,aAAa,CAAC,OAAO;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IAC/D,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;CACtF;AAED,MAAM,WAAW,OAAO,CAAC,OAAO;IAC/B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,eAAO,MAAM,QAAQ,GAAgC,OAAO,mKAuB1D,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { broken, cannotRun, unfinished } from "#internal/errors.ts";
|
|
3
|
+
import { hooked } from "#internal/hooked.ts";
|
|
4
|
+
export const runUntil = Effect.fnUntraced(function* ({ engine, name, run, tell }, until, options) {
|
|
5
|
+
if (run === undefined) {
|
|
6
|
+
return yield* Effect.die(cannotRun(name));
|
|
7
|
+
}
|
|
8
|
+
const { diagnose, failed, step } = run;
|
|
9
|
+
const maxSteps = options?.maxSteps ?? run.maxSteps;
|
|
10
|
+
for (let steps = 0;; steps += 1) {
|
|
11
|
+
const reason = failed === undefined ? undefined : yield* hooked(() => failed(engine));
|
|
12
|
+
if (reason !== undefined) {
|
|
13
|
+
return yield* Effect.die(broken(name, steps, reason));
|
|
14
|
+
}
|
|
15
|
+
if (yield* hooked(() => until(engine))) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (steps >= maxSteps) {
|
|
19
|
+
return yield* Effect.die(unfinished(name, steps, diagnose === undefined ? undefined : yield* hooked(() => diagnose(engine))));
|
|
20
|
+
}
|
|
21
|
+
yield* hooked(() => step(engine, tell));
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=runUntil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runUntil.js","sourceRoot":"","sources":["../../src/internal/runUntil.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAA6B,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAiBxE,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAClD,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAoB,EAC7C,KAAkD,EAClD,OAAoC;IAEpC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3C,CAAC;IACD,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC;IACvC,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,GAAG,CAAC,QAAQ,CAAC;IACnD,KAAK,IAAI,KAAK,GAAG,CAAC,GAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACtF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;QACvD,CAAC;QACD,IAAI,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;YACxC,OAAO;QACR,CAAC;QACD,IAAI,KAAK,IAAI,QAAQ,EAAE,CAAC;YACvB,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/H,CAAC;QACD,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;IACzC,CAAC;AACF,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { AnyTestLayer } from "@shivaedev/effect-test/any-test-layer.ts";
|
|
3
|
+
import { type AnyEffect, type Hook } from "#internal/hooked.ts";
|
|
4
|
+
import { type Narration } from "#internal/narration.ts";
|
|
5
|
+
import type { LooseRunHooks } from "#internal/runUntil.ts";
|
|
6
|
+
import type { Parts } from "#internal/traits.ts";
|
|
7
|
+
import type { RunUntilOptions } from "#storyKit.ts";
|
|
8
|
+
export type LooseTrait = Parts<string, (engine: unknown) => Effect.Effect<void, unknown, unknown>>;
|
|
9
|
+
export interface LooseStory {
|
|
10
|
+
readonly engine: unknown;
|
|
11
|
+
readonly lines: readonly string[];
|
|
12
|
+
readonly runUntil: (until: Hook<[engine: unknown], AnyEffect, boolean>, options?: RunUntilOptions) => Effect.Effect<void, unknown, unknown>;
|
|
13
|
+
readonly tell: (line: string) => void;
|
|
14
|
+
}
|
|
15
|
+
export interface LooseDefinition {
|
|
16
|
+
readonly after?: Readonly<Partial<Record<string, Hook<[engine: unknown], AnyEffect, void>>>>;
|
|
17
|
+
readonly create: Hook<[], AnyEffect, unknown>;
|
|
18
|
+
readonly inspect?: (engine: unknown) => unknown;
|
|
19
|
+
readonly layer?: AnyTestLayer;
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly run?: LooseRunHooks<unknown>;
|
|
22
|
+
readonly stages: readonly string[];
|
|
23
|
+
readonly verbs: (engine: unknown, story: LooseStory) => object;
|
|
24
|
+
}
|
|
25
|
+
export declare const seed: (definition: LooseDefinition, engine: unknown, narration: Narration, given: readonly LooseTrait[]) => Effect.Effect<void, unknown, unknown>;
|
|
26
|
+
//# sourceMappingURL=seed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../src/internal/seed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAC;AAE7E,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,IAAI,EAAU,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,KAAK,SAAS,EAAa,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAEnG,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5I,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7F,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAC9C,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC;CAC/D;AAED,eAAO,MAAM,IAAI,6IAuBf,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Cause, Effect } from "effect";
|
|
2
|
+
import { refusal } from "#internal/errors.ts";
|
|
3
|
+
import { hooked } from "#internal/hooked.ts";
|
|
4
|
+
import { tellGiven } from "#internal/narration.ts";
|
|
5
|
+
export const seed = Effect.fnUntraced(function* (definition, engine, narration, given) {
|
|
6
|
+
const parts = given.flatMap((each) => each.parts);
|
|
7
|
+
tellGiven(narration, parts);
|
|
8
|
+
for (const stage of definition.stages) {
|
|
9
|
+
for (const [index, part] of parts.entries()) {
|
|
10
|
+
if (part.stage === stage) {
|
|
11
|
+
yield* Effect.suspend(() => part.apply(engine)).pipe(Effect.catchCause((cause) => {
|
|
12
|
+
if (Cause.hasInterruptsOnly(cause)) {
|
|
13
|
+
return Effect.interrupt;
|
|
14
|
+
}
|
|
15
|
+
const thrown = Cause.squash(cause);
|
|
16
|
+
narration.refused = { cause: thrown, index };
|
|
17
|
+
return Effect.die(refusal(definition.name, part.line, thrown));
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const after = definition.after?.[stage];
|
|
22
|
+
if (after !== undefined) {
|
|
23
|
+
yield* hooked(() => after(engine));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=seed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../../src/internal/seed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEvC,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAA6B,MAAM,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAkB,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAyBnE,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,UAA2B,EAAE,MAAe,EAAE,SAAoB,EAAE,KAA4B;IAChJ,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClD,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;QACvC,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;YAC7C,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;gBAC1B,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CACnD,MAAM,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,EAAE;oBAC3B,IAAI,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;wBACpC,OAAO,MAAM,CAAC,SAAS,CAAC;oBACzB,CAAC;oBACD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBACnC,SAAS,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;oBAC7C,OAAO,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;gBAChE,CAAC,CAAC,CACF,CAAC;YACH,CAAC;QACF,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACzB,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QACpC,CAAC;IACF,CAAC;AACF,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { TestContext } from "vitest";
|
|
3
|
+
import { type LooseDefinition, type LooseStory, type LooseTrait } from "#internal/seed.ts";
|
|
4
|
+
export declare const start: (definition: LooseDefinition, given: readonly LooseTrait[], context: TestContext) => Effect.Effect<{
|
|
5
|
+
story: LooseStory;
|
|
6
|
+
}, unknown, unknown>;
|
|
7
|
+
//# sourceMappingURL=start.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/internal/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAK1C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAQ,MAAM,mBAAmB,CAAC;AAEjG,eAAO,MAAM,KAAK;;oBAehB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { callSite } from "#internal/callSite.ts";
|
|
3
|
+
import { hooked } from "#internal/hooked.ts";
|
|
4
|
+
import { narrate } from "#internal/narration.ts";
|
|
5
|
+
import { runUntil } from "#internal/runUntil.ts";
|
|
6
|
+
import { seed } from "#internal/seed.ts";
|
|
7
|
+
export const start = Effect.fnUntraced(function* (definition, given, context) {
|
|
8
|
+
const engine = yield* hooked(definition.create);
|
|
9
|
+
const narration = narrate({ engine, inspect: definition.inspect, name: definition.name }, context);
|
|
10
|
+
yield* seed(definition, engine, narration, given);
|
|
11
|
+
const story = {
|
|
12
|
+
engine,
|
|
13
|
+
lines: narration.lines,
|
|
14
|
+
runUntil: (until, options) => {
|
|
15
|
+
const site = callSite();
|
|
16
|
+
narration.at = site;
|
|
17
|
+
return runUntil({ engine, name: definition.name, run: definition.run, tell: (line) => narration.tellAt(line, site) }, until, options);
|
|
18
|
+
},
|
|
19
|
+
tell: narration.tell,
|
|
20
|
+
};
|
|
21
|
+
return { ...definition.verbs(engine, story), story };
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=start.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start.js","sourceRoot":"","sources":["../../src/internal/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAA0D,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEjG,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,UAA2B,EAAE,KAA4B,EAAE,OAAoB;IAChI,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IACnG,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAClD,MAAM,KAAK,GAAe;QACzB,MAAM;QACN,KAAK,EAAE,SAAS,CAAC,KAAK;QACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;YAC5B,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;YACxB,SAAS,CAAC,EAAE,GAAG,IAAI,CAAC;YACpB,OAAO,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QACvI,CAAC;QACD,IAAI,EAAE,SAAS,CAAC,IAAI;KACpB,CAAC;IACF,OAAO,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;AACtD,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TestContext } from "@effect/vitest";
|
|
2
|
+
import type { EffectTestOptions } from "@shivaedev/effect-test/types.ts";
|
|
3
|
+
import { type AnyEffect } from "#internal/hooked.ts";
|
|
4
|
+
import type { LooseDefinition, LooseTrait } from "#internal/seed.ts";
|
|
5
|
+
type LooseBody = (told: object, context: TestContext) => Generator<AnyEffect, unknown, never> | undefined;
|
|
6
|
+
type LooseTest = (name: string, given: readonly LooseTrait[], body?: LooseBody, options?: number | EffectTestOptions) => void;
|
|
7
|
+
export interface LooseIt extends LooseTest {
|
|
8
|
+
readonly fails: LooseTest;
|
|
9
|
+
readonly only: LooseTest;
|
|
10
|
+
readonly runIf: (condition: unknown) => LooseTest;
|
|
11
|
+
readonly skip: LooseTest;
|
|
12
|
+
readonly skipIf: (condition: unknown) => LooseTest;
|
|
13
|
+
}
|
|
14
|
+
export declare function storyIt(definition: LooseDefinition): LooseIt;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=storyIt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storyIt.d.ts","sourceRoot":"","sources":["../../src/internal/storyIt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGrF,OAAO,EAAE,KAAK,SAAS,EAAU,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGrE,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,KAAK,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,SAAS,CAAC;AAE1G,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,iBAAiB,KAAK,IAAI,CAAC;AAE9H,MAAM,WAAW,OAAQ,SAAQ,SAAS;IACzC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,SAAS,CAAC;IAClD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,SAAS,CAAC;CACnD;AAiCD,wBAAgB,OAAO,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAU5D"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Effect, Layer } from "effect";
|
|
2
|
+
import { makeEffectIt } from "@shivaedev/effect-test/vitest.ts";
|
|
3
|
+
import { genreOf } from "#internal/genre.ts";
|
|
4
|
+
import { hooked } from "#internal/hooked.ts";
|
|
5
|
+
import { start } from "#internal/start.ts";
|
|
6
|
+
function withTags(options, tags) {
|
|
7
|
+
if (typeof options === "number") {
|
|
8
|
+
return { tags: [...tags], timeout: options };
|
|
9
|
+
}
|
|
10
|
+
const own = options?.tags ?? [];
|
|
11
|
+
return { ...options, tags: [...tags, ...(typeof own === "string" ? [own] : own)] };
|
|
12
|
+
}
|
|
13
|
+
function storyOf(definition, given, body) {
|
|
14
|
+
return function* story(_harness, context) {
|
|
15
|
+
const told = yield* start(definition, given, context);
|
|
16
|
+
return yield* hooked(() => body?.(told, context));
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// Vitest refuses a tag its config does not declare, so a genre the config leaves out names the test without tagging it.
|
|
20
|
+
function declaring(definition, genre, declare) {
|
|
21
|
+
return (name, given, body, options) => {
|
|
22
|
+
const title = `${genre.title}: ${name}`;
|
|
23
|
+
const story = storyOf(definition, given, body);
|
|
24
|
+
try {
|
|
25
|
+
declare(title, story, withTags(options, [genre.tag.name]));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (!(error instanceof Error && error.message.includes(`"${genre.tag.name}"`))) {
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
declare(title, story, withTags(options, []));
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function storyIt(definition) {
|
|
36
|
+
const genre = genreOf(definition.name);
|
|
37
|
+
const { effectApp } = makeEffectIt({ layer: definition.layer ?? Layer.empty, makeHarness: () => Effect.void });
|
|
38
|
+
return Object.assign(declaring(definition, genre, effectApp), {
|
|
39
|
+
fails: declaring(definition, genre, effectApp.fails),
|
|
40
|
+
only: declaring(definition, genre, effectApp.only),
|
|
41
|
+
runIf: (condition) => declaring(definition, genre, effectApp.runIf(condition)),
|
|
42
|
+
skip: declaring(definition, genre, effectApp.skip),
|
|
43
|
+
skipIf: (condition) => declaring(definition, genre, effectApp.skipIf(condition)),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=storyIt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storyIt.js","sourceRoot":"","sources":["../../src/internal/storyIt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface TraitPart<TStage extends string, TApply> {
|
|
2
|
+
readonly apply: TApply;
|
|
3
|
+
readonly line: string;
|
|
4
|
+
readonly site: Error;
|
|
5
|
+
readonly stage: TStage;
|
|
6
|
+
}
|
|
7
|
+
export interface Parts<TStage extends string, TApply> {
|
|
8
|
+
readonly parts: readonly TraitPart<TStage, TApply>[];
|
|
9
|
+
}
|
|
10
|
+
export declare function trait<TStage extends string, TApply>(stage: TStage, line: string, apply: TApply): Parts<TStage, TApply>;
|
|
11
|
+
export declare function traits<TStage extends string, TApply>(...given: readonly Parts<TStage, TApply>[]): Parts<TStage, TApply>;
|
|
12
|
+
//# sourceMappingURL=traits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traits.d.ts","sourceRoot":"","sources":["../../src/internal/traits.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,KAAK,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;CACrD;AAED,wBAAgB,KAAK,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAEtH;AAED,wBAAgB,MAAM,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,SAAS,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAEvH"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { callSite } from "#internal/callSite.ts";
|
|
2
|
+
export function trait(stage, line, apply) {
|
|
3
|
+
return { parts: [{ apply, line, site: callSite(), stage }] };
|
|
4
|
+
}
|
|
5
|
+
export function traits(...given) {
|
|
6
|
+
return { parts: given.flatMap((each) => each.parts) };
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=traits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traits.js","sourceRoot":"","sources":["../../src/internal/traits.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAajD,MAAM,UAAU,KAAK,CAAgC,KAAa,EAAE,IAAY,EAAE,KAAa;IAC9F,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,MAAM,CAAgC,GAAG,KAAuC;IAC/F,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const WIDTH = 120;
|
|
2
|
+
export declare function wrap(text: string, { first, rest }?: {
|
|
3
|
+
readonly first?: string;
|
|
4
|
+
readonly rest?: string;
|
|
5
|
+
}): string[];
|
|
6
|
+
export declare function explained(what: string, help: string): string;
|
|
7
|
+
export declare function wrapJson(json: string): string[];
|
|
8
|
+
//# sourceMappingURL=wrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../../src/internal/wrap.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK,MAAM,CAAC;AAEzB,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAU,EAAE,IAAS,EAAE,GAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,EAAE,CAahI;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5D;AAYD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAkB/C"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const WIDTH = 120;
|
|
2
|
+
export function wrap(text, { first = "", rest = "" } = {}) {
|
|
3
|
+
const lines = [];
|
|
4
|
+
let line = first;
|
|
5
|
+
for (const word of text.split(" ")) {
|
|
6
|
+
const prefix = lines.length === 0 ? first : rest;
|
|
7
|
+
if (line.length > prefix.length && line.length + 1 + word.length > WIDTH) {
|
|
8
|
+
lines.push(line);
|
|
9
|
+
line = `${rest}${word}`;
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
line = line.length > prefix.length ? `${line} ${word}` : `${line}${word}`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return [...lines, line];
|
|
16
|
+
}
|
|
17
|
+
export function explained(what, help) {
|
|
18
|
+
return [...wrap(what, { rest: " " }), ...wrap(help, { first: "help: ", rest: " " })].join("\n");
|
|
19
|
+
}
|
|
20
|
+
function members(json) {
|
|
21
|
+
const value = JSON.parse(json);
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return value.map((item) => JSON.stringify(item));
|
|
24
|
+
}
|
|
25
|
+
return typeof value === "object" && value !== null
|
|
26
|
+
? Object.entries(value).map(([key, item]) => `${JSON.stringify(key)}:${JSON.stringify(item)}`)
|
|
27
|
+
: [json];
|
|
28
|
+
}
|
|
29
|
+
export function wrapJson(json) {
|
|
30
|
+
const [open, close] = json.startsWith("[") ? ["[", "]"] : ["{", "}"];
|
|
31
|
+
const pieces = members(json);
|
|
32
|
+
if (pieces.length < 2) {
|
|
33
|
+
return [json];
|
|
34
|
+
}
|
|
35
|
+
const lines = [];
|
|
36
|
+
let line = open;
|
|
37
|
+
for (const [index, piece] of pieces.entries()) {
|
|
38
|
+
const next = `${piece}${index === pieces.length - 1 ? close : ","}`;
|
|
39
|
+
if (line.length > 1 && line.length + next.length > WIDTH) {
|
|
40
|
+
lines.push(line);
|
|
41
|
+
line = next;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
line += next;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return [...lines, line];
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=wrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrap.js","sourceRoot":"","sources":["../../src/internal/wrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { TestContext } from "@effect/vitest";
|
|
2
|
+
import type { Effect, Layer } from "effect";
|
|
3
|
+
import type { EffectTestOptions } from "@shivaedev/effect-test/types.ts";
|
|
4
|
+
import { type AnyEffect, type ErrorOf, type Hook, type ServicesOf } from "#internal/hooked.ts";
|
|
5
|
+
import { type Parts } from "#internal/traits.ts";
|
|
6
|
+
export type Trait<TEngine, TStage extends string, R = never> = Parts<TStage, (engine: TEngine) => Effect.Effect<void, unknown, R>>;
|
|
7
|
+
type TraitServices<TTrait> = TTrait extends Parts<string, (engine: never) => Effect.Effect<void, unknown, infer R>> ? R : never;
|
|
8
|
+
type Needs<TProvided> = Effect.Effect<unknown, unknown, TProvided>;
|
|
9
|
+
type AfterEffects<TAfter> = {
|
|
10
|
+
readonly [TKey in keyof TAfter]-?: AnyEffect extends TAfter[TKey] ? never : TAfter[TKey];
|
|
11
|
+
}[keyof TAfter];
|
|
12
|
+
type LayerFor<THooks, TProvided> = [ServicesOf<THooks>] extends [TProvided] ? unknown : {
|
|
13
|
+
readonly layer: Layer.Layer<ServicesOf<THooks>, unknown, never>;
|
|
14
|
+
};
|
|
15
|
+
export interface RunUntilOptions {
|
|
16
|
+
readonly maxSteps?: number | undefined;
|
|
17
|
+
}
|
|
18
|
+
export interface Story<TEngine, TRun extends AnyEffect = never> {
|
|
19
|
+
readonly engine: TEngine;
|
|
20
|
+
readonly lines: readonly string[];
|
|
21
|
+
readonly runUntil: <TUntil extends AnyEffect = never>(until: Hook<[engine: TEngine], TUntil, boolean>, options?: RunUntilOptions) => Effect.Effect<void, ErrorOf<TRun | TUntil>, ServicesOf<TRun | TUntil>>;
|
|
22
|
+
readonly tell: (line: string) => void;
|
|
23
|
+
}
|
|
24
|
+
export interface RunHooks<TEngine, TStep extends AnyEffect, TFailed extends AnyEffect, TDiagnose extends AnyEffect> {
|
|
25
|
+
readonly diagnose?: Hook<[engine: TEngine], TDiagnose, string>;
|
|
26
|
+
readonly failed?: Hook<[engine: TEngine], TFailed, string | undefined>;
|
|
27
|
+
readonly maxSteps: number;
|
|
28
|
+
readonly step: Hook<[engine: TEngine, tell: (line: string) => void], TStep, void>;
|
|
29
|
+
}
|
|
30
|
+
export interface StoryKitDefinition<TEngine, TStage extends string, TVerbs, TProvided, TCreate extends AnyEffect, TAfter extends {
|
|
31
|
+
readonly [TKey in TStage]?: AnyEffect;
|
|
32
|
+
}, TStep extends AnyEffect, TFailed extends AnyEffect, TDiagnose extends AnyEffect> {
|
|
33
|
+
readonly after?: {
|
|
34
|
+
readonly [TKey in keyof TAfter]: Hook<[engine: TEngine], TAfter[TKey] & AnyEffect, void>;
|
|
35
|
+
};
|
|
36
|
+
readonly create: Hook<[], TCreate, TEngine>;
|
|
37
|
+
readonly inspect?: (engine: TEngine) => unknown;
|
|
38
|
+
readonly layer?: Layer.Layer<TProvided, unknown, never>;
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly run?: RunHooks<TEngine, TStep, TFailed, TDiagnose>;
|
|
41
|
+
readonly stages: readonly [TStage, ...TStage[]];
|
|
42
|
+
readonly verbs: (engine: TEngine, story: Story<TEngine, TStep | TFailed | TDiagnose>) => TVerbs;
|
|
43
|
+
}
|
|
44
|
+
export type Told<TEngine, TVerbs, TRun extends AnyEffect> = TVerbs & {
|
|
45
|
+
readonly story: Story<TEngine, TRun>;
|
|
46
|
+
};
|
|
47
|
+
export type StoryBody<TEngine, TVerbs, TRun extends AnyEffect, TEffect extends AnyEffect> = (told: Told<TEngine, TVerbs, TRun>, context: TestContext) => Generator<TEffect, unknown, never> | undefined;
|
|
48
|
+
export type StoryTest<TEngine, TStage extends string, TVerbs, TRun extends AnyEffect, TProvided> = <TEffect extends Needs<TProvided> = never>(name: string, given: readonly Trait<TEngine, TStage, TProvided>[], body?: StoryBody<TEngine, TVerbs, TRun, TEffect>, options?: number | EffectTestOptions) => void;
|
|
49
|
+
export interface StoryIt<TEngine, TStage extends string, TVerbs, TRun extends AnyEffect, TProvided> extends StoryTest<TEngine, TStage, TVerbs, TRun, TProvided> {
|
|
50
|
+
readonly fails: StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
|
|
51
|
+
readonly only: StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
|
|
52
|
+
readonly runIf: (condition: unknown) => StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
|
|
53
|
+
readonly skip: StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
|
|
54
|
+
readonly skipIf: (condition: unknown) => StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
|
|
55
|
+
}
|
|
56
|
+
export interface StoryKit<TEngine, TStage extends string, TVerbs, TRun extends AnyEffect, TProvided> {
|
|
57
|
+
readonly it: StoryIt<TEngine, TStage, TVerbs, TRun, TProvided>;
|
|
58
|
+
readonly trait: <TEffect extends AnyEffect = never>(stage: TStage, line: string, apply: Hook<[engine: TEngine], TEffect, void>) => Trait<TEngine, TStage, ServicesOf<TEffect>>;
|
|
59
|
+
readonly traits: <const TGiven extends readonly Trait<TEngine, TStage, unknown>[]>(...given: TGiven) => Trait<TEngine, TStage, TraitServices<TGiven[number]>>;
|
|
60
|
+
}
|
|
61
|
+
export declare function storyKit<TEngine, const TStage extends string, TVerbs extends object, TProvided = never, TCreate extends AnyEffect = never, TAfter extends {
|
|
62
|
+
readonly [TKey in TStage]?: AnyEffect;
|
|
63
|
+
} = Record<never, never>, TStep extends AnyEffect = never, TFailed extends AnyEffect = never, TDiagnose extends AnyEffect = never>(definition: StoryKitDefinition<TEngine, TStage, TVerbs, TProvided, TCreate, TAfter, TStep, TFailed, TDiagnose> & LayerFor<TCreate | AfterEffects<TAfter> | TStep | TFailed | TDiagnose, TProvided>): StoryKit<TEngine, TStage, TVerbs, TStep | TFailed | TDiagnose, TProvided>;
|
|
64
|
+
export {};
|
|
65
|
+
//# sourceMappingURL=storyKit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storyKit.d.ts","sourceRoot":"","sources":["../src/storyKit.ts"],"names":[],"mappings":"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"}
|
package/dist/storyKit.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { hooked } from "#internal/hooked.ts";
|
|
2
|
+
import { storyIt } from "#internal/storyIt.ts";
|
|
3
|
+
import { trait, traits } from "#internal/traits.ts";
|
|
4
|
+
export function storyKit(definition) {
|
|
5
|
+
return {
|
|
6
|
+
it: storyIt(definition),
|
|
7
|
+
trait: (stage, line, apply) => trait(stage, line, (engine) => hooked(() => apply(engine))),
|
|
8
|
+
traits,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=storyKit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storyKit.js","sourceRoot":"","sources":["../src/storyKit.ts"],"names":[],"mappings":"AAGA,OAAO,EAA2C,MAAM,EAAmB,MAAM,qBAAqB,CAAC;AAEvG,OAAO,EAAgB,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAc,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAiHhE,MAAM,UAAU,QAAQ,CAAC,UAA2B;IACnD,OAAO;QACN,EAAE,EAAE,OAAO,CAAC,UAAU,CAAC;QACvB,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1F,MAAM;KACN,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# North star
|
|
2
|
+
|
|
3
|
+
## The problem
|
|
4
|
+
|
|
5
|
+
Agents now write most of the code, tests included, and a test file an agent writes tends to be hundreds of lines long. Each test rebuilds its setup by hand, a little differently from the last, and where rebuilding a complex setup is too much work, it mocks the part that was hard to build. Nobody reads these files. A human could ask an agent what a suite actually tests, but nobody asks consistently enough for that to be a safety net, so we lose sight of what we test exactly when more code is written than ever.
|
|
6
|
+
|
|
7
|
+
## The ideal
|
|
8
|
+
|
|
9
|
+
A test reads like a short English story: what exists, what someone does, and what is true afterwards. Its setup is a list of traits in the domain's words, such as `ovenIsLit()` and `hasDough(3)`, its actions go through the application's real verbs, and the real engine runs between them. Everything that is not specific to this one test lives elsewhere, written once:
|
|
10
|
+
|
|
11
|
+
- once per engine, a story kit that knows how to create the engine, seed it stage by stage, step it and show its state;
|
|
12
|
+
- once per feature, the traits and verbs that name that feature in domain words;
|
|
13
|
+
- in each test, only the story.
|
|
14
|
+
|
|
15
|
+
This changes the cost and benefit of a good test for both readers. For an agent, a story test is the cheaper one to write and to read on every later visit, because it is a handful of lines that reuse what already exists. Because every story runs the whole engine, each test also checks, almost by accident, the parts its author never thought about and would have mocked. For a human, a spec that reads as English leaves no excuse not to read it. With so little noise around the signal, an agent that writes a sloppy or pointless test produces something a reviewer sees at a glance.
|
|
16
|
+
|
|
17
|
+
## What good looks like
|
|
18
|
+
|
|
19
|
+
- A spec is a few lines of domain words. If a spec needs a helper, a loop or hand-built state, a trait or verb is missing from the kit.
|
|
20
|
+
- An impossible setup refuses loudly instead of seeding a state the application could never reach.
|
|
21
|
+
- A failure is a console version of stopping in a debugger: the whole story, where it stopped and the spec line there, and the engine's state at that moment, written so an agent with no knowledge of this package can fix the test, and complete enough that a CI log alone is enough to reproduce it.
|
|
22
|
+
- The kit author writes hooks and the package infers every type from them. There is one kit, built on Effect: a hook is a plain function, or a generator function when it needs a service or fails with a typed error, so no definition writes `Effect.gen` and a plain engine needs no Effect at all.
|
|
23
|
+
- A spec writes only its story. The kit hands it an `it` that already knows the engine's services, so a spec never builds a test harness, and names every test after its genre, so a report shows which engine it ran and a run can pick one engine's stories.
|
|
24
|
+
|
|
25
|
+
## Trade-offs
|
|
26
|
+
|
|
27
|
+
- **Real engine over speed.** A story is slower than a unit test with a mock. We accept that, and keep engines fast enough to step in a test instead of faking them.
|
|
28
|
+
- **Hooks over freedom.** The kit fixes the shape of an engine: create, stages, after-stage hooks, a step, a state to inspect, verbs. An engine that does not fit should change its kit's hooks, not bypass the kit.
|
|
29
|
+
- **Cheap locations over complete ones.** The failure shows one spec line, where the story stopped, found by matching the spec's path in the failure's stack or in the call site of the story's last line. A failure raised deep inside an Effect verb may have none; finding one would take machinery we do not want.
|
|
30
|
+
|
|
31
|
+
## What it will not do
|
|
32
|
+
|
|
33
|
+
- Run without Vitest, or print a failure anywhere but the failing test's own error.
|
|
34
|
+
- Own any application's traits, verbs or steps; they belong to the application's `test-support/`.
|
|
35
|
+
- Mock the application's own code, or help a test reach into an engine's internals. A fake belongs only at a true external boundary, and the kit's `create` is where it is wired in.
|
package/docs/roadmap.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
## Built
|
|
4
|
+
|
|
5
|
+
- [x] `storyKit(definition)`: one Effect kit per engine, with every type and the services its layer provides inferred from its hooks. Plain engines plug in with plain functions.
|
|
6
|
+
- [x] Traits seeded stage by stage, with a hook after each stage, and refusal of an impossible setup.
|
|
7
|
+
- [x] The kit's own `it`, built on `@shivaedev/effect-test`, which hands the body the verbs and the story and names every test after the kit's genre. Vitest tags per genre are opt-in through `genreTags(...kitNames)` in the Vitest config.
|
|
8
|
+
- [x] `story.runUntil(condition)`: steps the engine until the condition holds, stops on what `run.failed` reports, and stops after a step budget with a diagnosis, keeping every hook's typed failure in its error channel.
|
|
9
|
+
- [x] A failed test prints a short guide, its story, where it stopped with the spec line there, and the engine's state, inline or in a file under `node_modules/.cache/test-story/`, broken at 120 columns.
|
|
10
|
+
|
|
11
|
+
## Next
|
|
12
|
+
|
|
13
|
+
- [ ] Move a first application's hand-written story DSL onto the kit and let what it needs decide the next hook.
|
|
14
|
+
|
|
15
|
+
## Open questions
|
|
16
|
+
|
|
17
|
+
- A long story prints in full. Should a story of thousands of lines print its traits and its last lines, and write the rest to a file the way a large engine state is written?
|
|
18
|
+
- `inspect` runs synchronously when the test has failed. An engine whose state lives behind an Effect service cannot be read then. Is that worth an Effect inspect, captured when the failure happens?
|