@shivaedev/test-story 0.0.0-stage → 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.
Files changed (86) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +248 -2
  4. package/dist/genreTags.d.ts +6 -0
  5. package/dist/genreTags.d.ts.map +1 -0
  6. package/dist/genreTags.js +5 -0
  7. package/dist/genreTags.js.map +1 -0
  8. package/dist/internal/callSite.d.ts +3 -0
  9. package/dist/internal/callSite.d.ts.map +1 -0
  10. package/dist/internal/callSite.js +16 -0
  11. package/dist/internal/callSite.js.map +1 -0
  12. package/dist/internal/engineSnapshot.d.ts +4 -0
  13. package/dist/internal/engineSnapshot.d.ts.map +1 -0
  14. package/dist/internal/engineSnapshot.js +35 -0
  15. package/dist/internal/engineSnapshot.js.map +1 -0
  16. package/dist/internal/errors.d.ts +5 -0
  17. package/dist/internal/errors.d.ts.map +1 -0
  18. package/dist/internal/errors.js +15 -0
  19. package/dist/internal/errors.js.map +1 -0
  20. package/dist/internal/failureReport.d.ts +8 -0
  21. package/dist/internal/failureReport.d.ts.map +1 -0
  22. package/dist/internal/failureReport.js +40 -0
  23. package/dist/internal/failureReport.js.map +1 -0
  24. package/dist/internal/genre.d.ts +7 -0
  25. package/dist/internal/genre.d.ts.map +1 -0
  26. package/dist/internal/genre.js +8 -0
  27. package/dist/internal/genre.js.map +1 -0
  28. package/dist/internal/hooked.d.ts +7 -0
  29. package/dist/internal/hooked.d.ts.map +1 -0
  30. package/dist/internal/hooked.js +11 -0
  31. package/dist/internal/hooked.js.map +1 -0
  32. package/dist/internal/narration.d.ts +25 -0
  33. package/dist/internal/narration.d.ts.map +1 -0
  34. package/dist/internal/narration.js +34 -0
  35. package/dist/internal/narration.js.map +1 -0
  36. package/dist/internal/render.d.ts +8 -0
  37. package/dist/internal/render.d.ts.map +1 -0
  38. package/dist/internal/render.js +56 -0
  39. package/dist/internal/render.js.map +1 -0
  40. package/dist/internal/runUntil.d.ts +17 -0
  41. package/dist/internal/runUntil.d.ts.map +1 -0
  42. package/dist/internal/runUntil.js +24 -0
  43. package/dist/internal/runUntil.js.map +1 -0
  44. package/dist/internal/seed.d.ts +26 -0
  45. package/dist/internal/seed.d.ts.map +1 -0
  46. package/dist/internal/seed.js +27 -0
  47. package/dist/internal/seed.js.map +1 -0
  48. package/dist/internal/start.d.ts +7 -0
  49. package/dist/internal/start.d.ts.map +1 -0
  50. package/dist/internal/start.js +23 -0
  51. package/dist/internal/start.js.map +1 -0
  52. package/dist/internal/storyIt.d.ts +16 -0
  53. package/dist/internal/storyIt.d.ts.map +1 -0
  54. package/dist/internal/storyIt.js +46 -0
  55. package/dist/internal/storyIt.js.map +1 -0
  56. package/dist/internal/traits.d.ts +12 -0
  57. package/dist/internal/traits.d.ts.map +1 -0
  58. package/dist/internal/traits.js +8 -0
  59. package/dist/internal/traits.js.map +1 -0
  60. package/dist/internal/wrap.d.ts +8 -0
  61. package/dist/internal/wrap.d.ts.map +1 -0
  62. package/dist/internal/wrap.js +49 -0
  63. package/dist/internal/wrap.js.map +1 -0
  64. package/dist/storyKit.d.ts +65 -0
  65. package/dist/storyKit.d.ts.map +1 -0
  66. package/dist/storyKit.js +11 -0
  67. package/dist/storyKit.js.map +1 -0
  68. package/docs/north-star.md +35 -0
  69. package/docs/roadmap.md +18 -0
  70. package/package.json +72 -3
  71. package/src/genreTags.ts +10 -0
  72. package/src/internal/callSite.ts +18 -0
  73. package/src/internal/engineSnapshot.ts +47 -0
  74. package/src/internal/errors.ts +39 -0
  75. package/src/internal/failureReport.ts +54 -0
  76. package/src/internal/genre.ts +14 -0
  77. package/src/internal/hooked.ts +22 -0
  78. package/src/internal/narration.ts +57 -0
  79. package/src/internal/render.ts +69 -0
  80. package/src/internal/runUntil.ts +43 -0
  81. package/src/internal/seed.ts +53 -0
  82. package/src/internal/start.ts +24 -0
  83. package/src/internal/storyIt.ts +63 -0
  84. package/src/internal/traits.ts +20 -0
  85. package/src/internal/wrap.ts +50 -0
  86. package/src/storyKit.ts +126 -0
@@ -0,0 +1,126 @@
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, hooked, type ServicesOf } from "#internal/hooked.ts";
5
+ import type { LooseDefinition, LooseTrait } from "#internal/seed.ts";
6
+ import { type LooseIt, storyIt } from "#internal/storyIt.ts";
7
+ import { type Parts, trait, traits } from "#internal/traits.ts";
8
+
9
+ export type Trait<TEngine, TStage extends string, R = never> = Parts<TStage, (engine: TEngine) => Effect.Effect<void, unknown, R>>;
10
+
11
+ type TraitServices<TTrait> = TTrait extends Parts<string, (engine: never) => Effect.Effect<void, unknown, infer R>> ? R : never;
12
+
13
+ type Needs<TProvided> = Effect.Effect<unknown, unknown, TProvided>;
14
+
15
+ // A plain after hook yields nothing to infer, so TypeScript leaves its effect at the AnyEffect constraint.
16
+ type AfterEffects<TAfter> = { readonly [TKey in keyof TAfter]-?: AnyEffect extends TAfter[TKey] ? never : TAfter[TKey] }[keyof TAfter];
17
+
18
+ type LayerFor<THooks, TProvided> = [ServicesOf<THooks>] extends [TProvided]
19
+ ? unknown
20
+ : { readonly layer: Layer.Layer<ServicesOf<THooks>, unknown, never> };
21
+
22
+ export interface RunUntilOptions {
23
+ readonly maxSteps?: number | undefined;
24
+ }
25
+
26
+ export interface Story<TEngine, TRun extends AnyEffect = never> {
27
+ readonly engine: TEngine;
28
+ readonly lines: readonly string[];
29
+ readonly runUntil: <TUntil extends AnyEffect = never>(
30
+ until: Hook<[engine: TEngine], TUntil, boolean>,
31
+ options?: RunUntilOptions,
32
+ ) => Effect.Effect<void, ErrorOf<TRun | TUntil>, ServicesOf<TRun | TUntil>>;
33
+ readonly tell: (line: string) => void;
34
+ }
35
+
36
+ export interface RunHooks<TEngine, TStep extends AnyEffect, TFailed extends AnyEffect, TDiagnose extends AnyEffect> {
37
+ readonly diagnose?: Hook<[engine: TEngine], TDiagnose, string>;
38
+ readonly failed?: Hook<[engine: TEngine], TFailed, string | undefined>;
39
+ readonly maxSteps: number;
40
+ readonly step: Hook<[engine: TEngine, tell: (line: string) => void], TStep, void>;
41
+ }
42
+
43
+ export interface StoryKitDefinition<
44
+ TEngine,
45
+ TStage extends string,
46
+ TVerbs,
47
+ TProvided,
48
+ TCreate extends AnyEffect,
49
+ TAfter extends { readonly [TKey in TStage]?: AnyEffect },
50
+ TStep extends AnyEffect,
51
+ TFailed extends AnyEffect,
52
+ TDiagnose extends AnyEffect,
53
+ > {
54
+ readonly after?: { readonly [TKey in keyof TAfter]: Hook<[engine: TEngine], TAfter[TKey] & AnyEffect, void> };
55
+ readonly create: Hook<[], TCreate, TEngine>;
56
+ readonly inspect?: (engine: TEngine) => unknown;
57
+ readonly layer?: Layer.Layer<TProvided, unknown, never>;
58
+ readonly name: string;
59
+ readonly run?: RunHooks<TEngine, TStep, TFailed, TDiagnose>;
60
+ readonly stages: readonly [TStage, ...TStage[]];
61
+ readonly verbs: (engine: TEngine, story: Story<TEngine, TStep | TFailed | TDiagnose>) => TVerbs;
62
+ }
63
+
64
+ export type Told<TEngine, TVerbs, TRun extends AnyEffect> = TVerbs & { readonly story: Story<TEngine, TRun> };
65
+
66
+ export type StoryBody<TEngine, TVerbs, TRun extends AnyEffect, TEffect extends AnyEffect> = (
67
+ told: Told<TEngine, TVerbs, TRun>,
68
+ context: TestContext,
69
+ ) => Generator<TEffect, unknown, never> | undefined;
70
+
71
+ export type StoryTest<TEngine, TStage extends string, TVerbs, TRun extends AnyEffect, TProvided> = <TEffect extends Needs<TProvided> = never>(
72
+ name: string,
73
+ given: readonly Trait<TEngine, TStage, TProvided>[],
74
+ body?: StoryBody<TEngine, TVerbs, TRun, TEffect>,
75
+ options?: number | EffectTestOptions,
76
+ ) => void;
77
+
78
+ export interface StoryIt<TEngine, TStage extends string, TVerbs, TRun extends AnyEffect, TProvided>
79
+ extends StoryTest<TEngine, TStage, TVerbs, TRun, TProvided> {
80
+ readonly fails: StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
81
+ readonly only: StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
82
+ readonly runIf: (condition: unknown) => StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
83
+ readonly skip: StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
84
+ readonly skipIf: (condition: unknown) => StoryTest<TEngine, TStage, TVerbs, TRun, TProvided>;
85
+ }
86
+
87
+ export interface StoryKit<TEngine, TStage extends string, TVerbs, TRun extends AnyEffect, TProvided> {
88
+ readonly it: StoryIt<TEngine, TStage, TVerbs, TRun, TProvided>;
89
+ readonly trait: <TEffect extends AnyEffect = never>(
90
+ stage: TStage,
91
+ line: string,
92
+ apply: Hook<[engine: TEngine], TEffect, void>,
93
+ ) => Trait<TEngine, TStage, ServicesOf<TEffect>>;
94
+ readonly traits: <const TGiven extends readonly Trait<TEngine, TStage, unknown>[]>(
95
+ ...given: TGiven
96
+ ) => Trait<TEngine, TStage, TraitServices<TGiven[number]>>;
97
+ }
98
+
99
+ interface LooseKit {
100
+ readonly it: LooseIt;
101
+ readonly trait: (stage: string, line: string, apply: Hook<[engine: unknown], AnyEffect, void>) => LooseTrait;
102
+ readonly traits: (...given: readonly LooseTrait[]) => LooseTrait;
103
+ }
104
+
105
+ // The implementation works on loose types; the overload states what the definition infers.
106
+ export function storyKit<
107
+ TEngine,
108
+ const TStage extends string,
109
+ TVerbs extends object,
110
+ TProvided = never,
111
+ TCreate extends AnyEffect = never,
112
+ TAfter extends { readonly [TKey in TStage]?: AnyEffect } = Record<never, never>,
113
+ TStep extends AnyEffect = never,
114
+ TFailed extends AnyEffect = never,
115
+ TDiagnose extends AnyEffect = never,
116
+ >(
117
+ definition: StoryKitDefinition<TEngine, TStage, TVerbs, TProvided, TCreate, TAfter, TStep, TFailed, TDiagnose> &
118
+ LayerFor<TCreate | AfterEffects<TAfter> | TStep | TFailed | TDiagnose, TProvided>,
119
+ ): StoryKit<TEngine, TStage, TVerbs, TStep | TFailed | TDiagnose, TProvided>;
120
+ export function storyKit(definition: LooseDefinition): LooseKit {
121
+ return {
122
+ it: storyIt(definition),
123
+ trait: (stage, line, apply) => trait(stage, line, (engine) => hooked(() => apply(engine))),
124
+ traits,
125
+ };
126
+ }