1import { assert, describe, it } from "@effect/vitest"
2import { Effect, Option, Ref } from "effect"
3
4const current = "value"
5const update = "new value"
6
7type State = Active | Changed | Closed
8
9interface Active {
10 readonly _tag: "Active"
11}
12
13interface Changed {
14 readonly _tag: "Changed"
15}
16
17interface Closed {
18 readonly _tag: "Closed"
19}
20
21export const Active: State = { _tag: "Active" }
22export const Changed: State = { _tag: "Changed" }
23export const Closed: State = { _tag: "Closed" }
24
25const isActive = (self: State): boolean => self._tag === "Active"
26const isChanged = (self: State): boolean => self._tag === "Changed"
27const isClosed = (self: State): boolean => self._tag === "Closed"
28
29describe("Ref", () => {
30 it.effect("get returns the current value", () =>
31 Effect.gen(function*() {
32 const result = yield* Ref.get(Ref.makeUnsafe(current))
33 assert.strictEqual(result, current)
34 }))
35
36 it.effect("getAndSet returns the previous value and stores the replacement", () =>
37 Effect.gen(function*() {
38 const ref = yield* Ref.make(current)
39 const result1 = yield* Ref.getAndSet(ref, update)
40 assert.strictEqual(result1, current)
41 }))
42
43 it.effect("getAndUpdate returns the previous value and stores the computed value", () =>
44 Effect.gen(function*() {
45 const ref = yield* Ref.make(current)
46 const result1 = yield* Ref.getAndUpdate(ref, () => update)
47 assert.strictEqual(result1, current)
48 }))
49
50 it.effect("getAndUpdateSome leaves state unchanged when no transition matches", () =>
51 Effect.gen(function*() {
52 const ref = yield* Ref.make<State>(Active)
53 const result1 = yield* Ref.getAndUpdateSome(
54 ref,
55 (state) => isClosed(state) ? Option.some(Changed) : Option.none()
56 )
57 const result2 = yield* Ref.get(ref)
58 assert.strictEqual(result1, Active)
59 assert.strictEqual(result2, Active)
60 }))
61
62 it.effect("getAndUpdateSome returns each previous state while applying matching transitions", () =>
63 Effect.gen(function*() {
64 const ref = yield* Ref.make<State>(Active)
65 const result1 = yield* Ref.getAndUpdateSome(
66 ref,
67 (state) => isActive(state) ? Option.some(Changed) : Option.none()
68 )
69 const result2 = yield* Ref.getAndUpdateSome(ref, (state) =>
70 isActive(state) ?
71 Option.some(Changed) :
72 isChanged(state) ?
73 Option.some(Closed) :
74 Option.none())
75 const result3 = yield* Ref.get(ref)
76 assert.strictEqual(result1, Active)
77 assert.strictEqual(result2, Changed)
78 assert.strictEqual(result3, Closed)
79 }))
80
81 it.effect("set stores the provided value", () =>
82 Effect.gen(function*() {
83 const ref = yield* Ref.make(current)
84 yield* Ref.set(ref, update)
85 const result = yield* Ref.get(ref)
86 assert.strictEqual(result, update)
87 }))
88
89 it.effect("update stores the computed value", () =>
90 Effect.gen(function*() {
91 const ref = yield* Ref.make(current)
92 yield* Ref.update(ref, () => update)
93 const result = yield* Ref.get(ref)
94 assert.strictEqual(result, update)
95 }))
96
97 it.effect("updateAndGet stores and returns the computed value", () =>
98 Effect.gen(function*() {
99 const ref = yield* Ref.make(current)
100 const result = yield* Ref.updateAndGet(ref, () => update)
101 assert.strictEqual(result, update)
102 }))
103
104 it.effect("updateSome leaves state unchanged when no transition matches", () =>
105 Effect.gen(function*() {
106 const ref = yield* Ref.make<State>(Active)
107 yield* Ref.updateSome(ref, (state) => isClosed(state) ? Option.some(Changed) : Option.none())
108 const result = yield* Ref.get(ref)
109 assert.deepEqual(result, Active)
110 }))
111
112 it.effect("updateSome applies matching transitions to the stored state", () =>
113 Effect.gen(function*() {
114 const ref = yield* Ref.make<State>(Active)
115 yield* Ref.updateSome(ref, (state) => isActive(state) ? Option.some(Changed) : Option.none())
116 const result1 = yield* Ref.get(ref)
117 yield* Ref.updateSome(ref, (state) =>
118 isActive(state) ?
119 Option.some(Changed) :
120 isChanged(state) ?
121 Option.some(Closed) :
122 Option.none())
123 const result2 = yield* Ref.get(ref)
124 assert.deepEqual(result1, Changed)
125 assert.deepEqual(result2, Closed)
126 }))
127
128 it.effect("updateSomeAndGet returns the current state when no transition matches", () =>
129 Effect.gen(function*() {
130 const ref = yield* Ref.make<State>(Active)
131 const result = yield* Ref.updateSomeAndGet(ref, (state) => isClosed(state) ? Option.some(Changed) : Option.none())
132 assert.strictEqual(result, Active)
133 }))
134 it.effect("updateSomeAndGet - twice", () =>
135 Effect.gen(function*() {
136 const ref = yield* Ref.make<State>(Active)
137 const result1 = yield* Ref.updateSomeAndGet(
138 ref,
139 (state) => isActive(state) ? Option.some(Changed) : Option.none()
140 )
141 const result2 = yield* Ref.updateSomeAndGet(ref, (state): Option.Option<State> => {
142 return isActive(state) ?
143 Option.some(Changed) :
144 isChanged(state) ?
145 Option.some(Closed) :
146 Option.none()
147 })
148 assert.deepEqual(result1, Changed)
149 assert.deepEqual(result2, Closed)
150 }))
151
152 it.effect("modify returns a result while storing the new value", () =>
153 Effect.gen(function*() {
154 const ref = yield* Ref.make(current)
155 const result1 = yield* Ref.modify(ref, () => ["hello", update])
156 const result2 = yield* Ref.get(ref)
157 assert.strictEqual(result1, "hello")
158 assert.strictEqual(result2, update)
159 }))
160 it.effect("modifySome - once", () =>
161 Effect.gen(function*() {
162 const ref = yield* Ref.make<State>(Active)
163 const result = yield* Ref.modifySome(ref, (state) =>
164 isClosed(state) ?
165 ["active", Option.some(Active)] :
166 ["state does not change", Option.none()])
167 assert.strictEqual(result, "state does not change")
168 }))
169
170 it.effect("modifySome returns a result while applying matching transitions", () =>
171 Effect.gen(function*() {
172 const ref = yield* Ref.make<State>(Active)
173 const result1 = yield* Ref.modifySome(ref, (state) =>
174 isActive(state) ?
175 ["changed", Option.some(Changed)] :
176 ["state does not change", Option.none()])
177
178 const result2 = yield* Ref.modifySome(ref, (state) =>
179 isActive(state) ?
180 ["changed", Option.some(Changed)] :
181 isChanged(state) ?
182 ["closed", Option.some(Closed)] :
183 ["state does not change", Option.none()])
184 assert.strictEqual(result1, "changed")
185 assert.strictEqual(result2, "closed")
186 }))
187})