1import { afterAll, assert, describe, it } from "@effect/vitest"
2import { Context, Effect, Layer, Ref } from "effect"
3
4interface StateShape {
5 readonly id: number
6 readonly todos: Ref.Ref<Array<string>>
7 readonly migrated: Ref.Ref<boolean>
8}
9
10class State extends Context.Service<State, StateShape>()("State") {}
11
12class TodoService extends Context.Service<TodoService, {
13 readonly stateId: Effect.Effect<number>
14 readonly migrated: Effect.Effect<boolean>
15 readonly add: (title: string) => Effect.Effect<void>
16 readonly list: Effect.Effect<Array<string>>
17}>()("TodoService") {}
18
19describe("top-level it.layer isolation", () => {
20 let nextId = 0
21 const observedStateIds: Array<number> = []
22
23 const baseLayer = Layer.effect(State)(
24 Effect.gen(function*() {
25 const id = ++nextId
26 const todos = yield* Ref.make<Array<string>>([])
27 const migrated = yield* Ref.make(false)
28 return { id, todos, migrated }
29 })
30 )
31
32 const migrationLayer = Layer.effectDiscard(
33 Effect.gen(function*() {
34 const state = yield* State
35 yield* Ref.set(state.migrated, true)
36 })
37 )
38
39 const migratedLayer = Layer.merge(
40 baseLayer,
41 migrationLayer.pipe(Layer.provide(baseLayer))
42 )
43
44 const inMemoryLayer = Layer.effect(TodoService)(
45 Effect.gen(function*() {
46 const state = yield* State
47 return {
48 stateId: Effect.succeed(state.id),
49 migrated: Ref.get(state.migrated),
50 add: (title: string) => Ref.update(state.todos, (todos) => [...todos, title]),
51 list: Ref.get(state.todos)
52 } as const
53 })
54 ).pipe(Layer.provide(migratedLayer))
55
56 it.layer(inMemoryLayer)((it) => {
57 it.effect("first block mutates isolated state", () =>
58 Effect.gen(function*() {
59 const service = yield* TodoService
60 const stateId = yield* service.stateId
61 const migrated = yield* service.migrated
62
63 observedStateIds.push(stateId)
64 yield* service.add("write tests")
65
66 assert.isTrue(migrated)
67 assert.deepStrictEqual(yield* service.list, ["write tests"])
68 }))
69 })
70
71 it.layer(inMemoryLayer)((it) => {
72 it.effect("second block starts fresh", () =>
73 Effect.gen(function*() {
74 const service = yield* TodoService
75 const stateId = yield* service.stateId
76 const migrated = yield* service.migrated
77
78 observedStateIds.push(stateId)
79
80 assert.isTrue(migrated)
81 assert.deepStrictEqual(yield* service.list, [])
82
83 yield* service.add("ship feature")
84 assert.deepStrictEqual(yield* service.list, ["ship feature"])
85 }))
86 })
87
88 it.layer(inMemoryLayer)((it) => {
89 it.effect("third block also starts fresh", () =>
90 Effect.gen(function*() {
91 const service = yield* TodoService
92 const stateId = yield* service.stateId
93 const migrated = yield* service.migrated
94
95 observedStateIds.push(stateId)
96
97 assert.isTrue(migrated)
98 assert.deepStrictEqual(yield* service.list, [])
99 }))
100 })
101
102 afterAll(() => {
103 assert.deepStrictEqual(observedStateIds, [1, 2, 3])
104 })
105})