import { afterAll, assert, beforeAll, describe, expect, layer } from "@effect/vitest" import { Context, Effect, Layer, Ref } from "effect" describe("nested sibling layers", () => { let nextChildId = 0 let firstChildId = -1 let secondChildId = -1 const releasedChildIds: Array = [] class Parent extends Context.Service()("Parent") { static readonly Live = Layer.succeed(Parent)("parent") } class Child extends Context.Service()("Child") { static readonly Live = Layer.effect(Child)( Effect.flatMap(Parent, () => { const id = ++nextChildId return Effect.acquireRelease( Effect.succeed({ id }), () => Effect.sync(() => { releasedChildIds.push(id) }) ) }) ) } layer(Parent.Live)("parent", (it) => { it.layer(Child.Live)("first sibling", (it) => { it.effect("allocates child", () => Effect.gen(function*() { const child = yield* Child firstChildId = child.id assert.strictEqual(child.id, 1) assert.deepStrictEqual(releasedChildIds, []) })) }) it.layer(Child.Live)("second sibling", (it) => { beforeAll(() => { expect(releasedChildIds).toEqual([firstChildId]) }) it.effect("allocates a fresh child", () => Effect.gen(function*() { const child = yield* Child secondChildId = child.id assert.strictEqual(child.id, 2) assert.isTrue(child.id !== firstChildId) assert.deepStrictEqual(releasedChildIds, [firstChildId]) })) }) afterAll(() => { expect(firstChildId).toEqual(1) expect(secondChildId).toEqual(2) expect(releasedChildIds).toEqual([1, 2]) }) }) }) describe.concurrent("nested sibling layers in concurrent suites", () => { let nextSharedId = 0 let firstSharedId: number | undefined let secondSharedId: number | undefined const releasedSharedIds: Array = [] class Parent extends Context.Service()("ConcurrentParent") { static readonly Live = Layer.succeed(Parent)("parent") } class SharedChild extends Context.Service()("SharedChild") { static readonly Live = Layer.effect(SharedChild)( Effect.flatMap(Parent, () => Effect.gen(function*() { yield* Effect.promise(() => new Promise((resolve) => setTimeout(resolve, 50))) const id = ++nextSharedId return yield* Effect.acquireRelease( Effect.succeed({ id }), () => Effect.sync(() => { releasedSharedIds.push(id) }) ) })) ) } layer(Parent.Live)("parent", (it) => { describe.concurrent("concurrent siblings", () => { it.layer(SharedChild.Live)("first sibling", (it) => { it.effect("captures shared child", () => Effect.gen(function*() { const child = yield* SharedChild firstSharedId = child.id assert.isTrue(child.id === 1 || child.id === 2) })) }) it.layer(SharedChild.Live)("second sibling", (it) => { it.effect("allocates an isolated child", () => Effect.gen(function*() { const child = yield* SharedChild secondSharedId = child.id assert.isTrue(child.id === 1 || child.id === 2) })) }) }) afterAll(() => { expect(firstSharedId).not.toEqual(secondSharedId) expect(nextSharedId).toEqual(2) expect([...releasedSharedIds].sort((a, b) => a - b)).toEqual([1, 2]) }) }) }) describe("nested sibling isolation with provided state graph", () => { interface StateShape { readonly id: number readonly todos: Ref.Ref> readonly migrated: Ref.Ref } class Parent extends Context.Service()("ProvidedParent") { static readonly Live = Layer.succeed(Parent)("parent") } class State extends Context.Service()("ProvidedState") {} class TodoService extends Context.Service readonly migrated: Effect.Effect readonly add: (title: string) => Effect.Effect readonly list: Effect.Effect> }>()("ProvidedTodoService") {} let nextId = 0 let firstStateId = -1 let secondStateId = -1 const baseLayer = Layer.effect(State)( Effect.gen(function*() { const id = ++nextId const todos = yield* Ref.make>([]) const migrated = yield* Ref.make(false) return { id, todos, migrated } }) ) const migrationLayer = Layer.effectDiscard( Effect.gen(function*() { const state = yield* State yield* Ref.set(state.migrated, true) }) ) const migratedLayer = Layer.merge( baseLayer, migrationLayer.pipe(Layer.provide(baseLayer)) ) const inMemoryLayer = Layer.effect(TodoService)( Effect.gen(function*() { const state = yield* State return { stateId: Effect.succeed(state.id), migrated: Ref.get(state.migrated), add: (title: string) => Ref.update(state.todos, (todos) => [...todos, title]), list: Ref.get(state.todos) } as const }) ).pipe(Layer.provide(migratedLayer)) layer(Parent.Live)("parent", (it) => { it.layer(inMemoryLayer)("first sibling", (it) => { it.effect("mutates isolated provided state", () => Effect.gen(function*() { const service = yield* TodoService firstStateId = yield* service.stateId assert.isTrue(yield* service.migrated) yield* service.add("write tests") assert.deepStrictEqual(yield* service.list, ["write tests"]) })) }) it.layer(inMemoryLayer)("second sibling", (it) => { it.effect("starts fresh with a new provided state", () => Effect.gen(function*() { const service = yield* TodoService secondStateId = yield* service.stateId assert.isTrue(yield* service.migrated) assert.deepStrictEqual(yield* service.list, []) yield* service.add("ship feature") assert.deepStrictEqual(yield* service.list, ["ship feature"]) })) }) afterAll(() => { expect(firstStateId).toEqual(1) expect(secondStateId).toEqual(2) expect(nextId).toEqual(2) }) }) })