| 1 | import { describe, it } from "@effect/vitest" |
| 2 | import { deepStrictEqual, strictEqual } from "@effect/vitest/utils" |
| 3 | import { Deferred, Effect, Exit, Fiber, Option, pipe, SynchronizedRef } from "effect" |
| 4 | |
| 5 | const current = "value" |
| 6 | const update = "new value" |
| 7 | const failure = "failure" |
| 8 | |
| 9 | type State = Active | Changed | Closed |
| 10 | |
| 11 | interface Active { |
| 12 | readonly _tag: "Active" |
| 13 | } |
| 14 | |
| 15 | interface Changed { |
| 16 | readonly _tag: "Changed" |
| 17 | } |
| 18 | |
| 19 | interface Closed { |
| 20 | readonly _tag: "Closed" |
| 21 | } |
| 22 | |
| 23 | export const Active: State = { _tag: "Active" } |
| 24 | export const Changed: State = { _tag: "Changed" } |
| 25 | export const Closed: State = { _tag: "Closed" } |
| 26 | |
| 27 | const isActive = (self: State): boolean => self._tag === "Active" |
| 28 | const isChanged = (self: State): boolean => self._tag === "Changed" |
| 29 | const isClosed = (self: State): boolean => self._tag === "Closed" |
| 30 | |
| 31 | describe("SynchronizedRef", () => { |
| 32 | it.effect("get", () => |
| 33 | Effect.gen(function*() { |
| 34 | const result = yield* pipe(SynchronizedRef.make(current), Effect.flatMap(SynchronizedRef.get)) |
| 35 | strictEqual(result, current) |
| 36 | })) |
| 37 | it.effect("getAndUpdateEffect - happy path", () => |
| 38 | Effect.gen(function*() { |
| 39 | const ref = yield* SynchronizedRef.make(current) |
| 40 | const result1 = yield* SynchronizedRef.getAndUpdateEffect(ref, () => Effect.succeed(update)) |
| 41 | const result2 = yield* SynchronizedRef.get(ref) |
| 42 | strictEqual(result1, current) |
| 43 | strictEqual(result2, update) |
| 44 | })) |
| 45 | it.effect("getAndUpdateEffect - with failure", () => |
| 46 | Effect.gen(function*() { |
| 47 | const ref = yield* SynchronizedRef.make(current) |
| 48 | const result = yield* pipe(SynchronizedRef.getAndUpdateEffect(ref, (_) => Effect.fail(failure)), Effect.exit) |
| 49 | deepStrictEqual(result, Exit.fail(failure)) |
| 50 | })) |
| 51 | it.effect("getAndUpdateSomeEffect - happy path", () => |
| 52 | Effect.gen(function*() { |
| 53 | const ref = yield* SynchronizedRef.make<State>(Active) |
| 54 | const result1 = yield* (SynchronizedRef.getAndUpdateSomeEffect(ref, (state) => |
| 55 | isClosed(state) ? |
| 56 | Effect.succeedSome(Changed) : |
| 57 | Effect.succeedNone)) |
| 58 | const result2 = yield* SynchronizedRef.get(ref) |
| 59 | deepStrictEqual(result1, Active) |
| 60 | deepStrictEqual(result2, Active) |
| 61 | })) |
| 62 | it.effect("getAndUpdateSomeEffect - twice", () => |
| 63 | Effect.gen(function*() { |
| 64 | const ref = yield* SynchronizedRef.make<State>(Active) |
| 65 | const result1 = yield* SynchronizedRef.getAndUpdateSomeEffect(ref, (state) => |
| 66 | isActive(state) ? |
| 67 | Effect.succeedSome(Changed) : |
| 68 | Effect.succeedNone) |
| 69 | const result2 = yield* SynchronizedRef.getAndUpdateSomeEffect(ref, (state) => |
| 70 | isClosed(state) |
| 71 | ? Effect.succeedSome(Active) |
| 72 | : isChanged(state) |
| 73 | ? Effect.succeedSome(Closed) |
| 74 | : Effect.succeedNone) |
| 75 | const result3 = yield* SynchronizedRef.get(ref) |
| 76 | deepStrictEqual(result1, Active) |
| 77 | deepStrictEqual(result2, Changed) |
| 78 | deepStrictEqual(result3, Closed) |
| 79 | })) |
| 80 | it.effect("getAndUpdateSomeEffect - with failure", () => |
| 81 | Effect.gen(function*() { |
| 82 | const ref = yield* SynchronizedRef.make<State>(Active) |
| 83 | const result = yield* pipe( |
| 84 | SynchronizedRef.getAndUpdateSomeEffect(ref, (state) => |
| 85 | isActive(state) ? |
| 86 | Effect.fail(failure) : |
| 87 | Effect.succeedNone), |
| 88 | Effect.exit |
| 89 | ) |
| 90 | deepStrictEqual(result, Exit.fail(failure)) |
| 91 | })) |
| 92 | it.effect("getAndUpdateSomeEffect - interrupt parent fiber and update", () => |
| 93 | Effect.gen(function*() { |
| 94 | const deferred = yield* Deferred.make<SynchronizedRef.SynchronizedRef<State>>() |
| 95 | const latch = yield* Deferred.make<void>() |
| 96 | const makeAndWait = Deferred.complete(deferred, SynchronizedRef.make<State>(Active)).pipe( |
| 97 | Effect.andThen(Deferred.await(latch)) |
| 98 | ) |
| 99 | const fiber = yield* Effect.forkChild(makeAndWait) |
| 100 | const ref = yield* Deferred.await(deferred) |
| 101 | yield* Fiber.interrupt(fiber) |
| 102 | const result = yield* SynchronizedRef.updateAndGetEffect(ref, (_) => Effect.succeed(Closed)) |
| 103 | deepStrictEqual(result, Closed) |
| 104 | })) |
| 105 | it.effect("getAndUpdateSome updates the backing ref", () => |
| 106 | Effect.gen(function*() { |
| 107 | const ref = yield* SynchronizedRef.make(1) |
| 108 | const previous = yield* SynchronizedRef.getAndUpdateSome(ref, (n) => Option.some(n + 1)) |
| 109 | strictEqual(previous, 1) |
| 110 | strictEqual(yield* SynchronizedRef.get(ref), 2) |
| 111 | })) |
| 112 | }) |