From bba8d838b78650e9e59302f0c7f1b8651b00945b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 22 Mar 2026 01:41:30 +0100 Subject: [PATCH] Add tests --- packages/effect-fc/src/PropertyPath.test.ts | 73 +++++++ .../effect-fc/src/SubscriptionSubRef.test.ts | 183 ++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 packages/effect-fc/src/PropertyPath.test.ts create mode 100644 packages/effect-fc/src/SubscriptionSubRef.test.ts diff --git a/packages/effect-fc/src/PropertyPath.test.ts b/packages/effect-fc/src/PropertyPath.test.ts new file mode 100644 index 0000000..aef2cf0 --- /dev/null +++ b/packages/effect-fc/src/PropertyPath.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, test } from "bun:test" +import { Option } from "effect" +import * as PropertyPath from "./PropertyPath.ts" + + +describe("immutableSet with arrays", () => { + test("sets a top-level array element", () => { + const arr = [1, 2, 3] + const result = PropertyPath.immutableSet(arr, [1], 99) + expect(result).toEqual(Option.some([1, 99, 3])) + }) + + test("does not mutate the original array", () => { + const arr = [1, 2, 3] + PropertyPath.immutableSet(arr, [0], 42) + expect(arr).toEqual([1, 2, 3]) + }) + + test("sets the first element of an array", () => { + const arr = ["a", "b", "c"] + const result = PropertyPath.immutableSet(arr, [0], "z") + expect(result).toEqual(Option.some(["z", "b", "c"])) + }) + + test("sets the last element of an array", () => { + const arr = [10, 20, 30] + const result = PropertyPath.immutableSet(arr, [2], 99) + expect(result).toEqual(Option.some([10, 20, 99])) + }) + + test("sets a nested array element inside an object", () => { + const obj = { tags: ["foo", "bar", "baz"] } + const result = PropertyPath.immutableSet(obj, ["tags", 1], "qux") + expect(result).toEqual(Option.some({ tags: ["foo", "qux", "baz"] })) + }) + + test("sets a deeply nested value inside an array of objects", () => { + const obj = { items: [{ name: "alice" }, { name: "bob" }] } + const result = PropertyPath.immutableSet(obj, ["items", 0, "name"], "charlie") + expect(result).toEqual(Option.some({ items: [{ name: "charlie" }, { name: "bob" }] })) + }) + + test("sets a value in a nested array", () => { + const matrix = [[1, 2], [3, 4]] + const result = PropertyPath.immutableSet(matrix, [1, 0], 99) + expect(result).toEqual(Option.some([[1, 2], [99, 4]])) + }) + + test("returns Option.none() for an out-of-bounds index", () => { + const arr = [1, 2, 3] + const result = PropertyPath.immutableSet(arr, [5], 99) + expect(result).toEqual(Option.none()) + }) + + test("returns Option.none() for a non-numeric key on an array", () => { + const arr = [1, 2, 3] + // @ts-expect-error intentionally wrong key type + const result = PropertyPath.immutableSet(arr, ["length"], 0) + expect(result).toEqual(Option.none()) + }) + + test("curried form works with arrays", () => { + const setSecond = PropertyPath.immutableSet([1], 42) + const result = setSecond([10, 20, 30]) + expect(result).toEqual(Option.some([10, 42, 30])) + }) + + test("empty path returns Option.some of the value itself", () => { + const arr = [1, 2, 3] + const result = PropertyPath.immutableSet(arr, [], [9, 9, 9] as any) + expect(result).toEqual(Option.some([9, 9, 9])) + }) +}) diff --git a/packages/effect-fc/src/SubscriptionSubRef.test.ts b/packages/effect-fc/src/SubscriptionSubRef.test.ts new file mode 100644 index 0000000..e0a8a9d --- /dev/null +++ b/packages/effect-fc/src/SubscriptionSubRef.test.ts @@ -0,0 +1,183 @@ +import { describe, expect, test } from "bun:test" +import { Chunk, Effect, Ref, SubscriptionRef } from "effect" +import * as SubscriptionSubRef from "./SubscriptionSubRef.ts" + + +describe("SubscriptionSubRef with array refs", () => { + test("creates a subref for a single array element using path", async () => { + const value = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make([{ name: "alice" }, { name: "bob" }, { name: "charlie" }]), + parent => { + const subref = SubscriptionSubRef.makeFromPath(parent, [1, "name"]) + return subref.get + }, + ), + ) + + expect(value).toBe("bob") + }) + + test("modifies a single array element via subref", async () => { + const result = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make([{ name: "alice" }, { name: "bob" }, { name: "charlie" }]), + parent => { + const subref = SubscriptionSubRef.makeFromPath(parent, [1, "name"]) + return Effect.flatMap( + Ref.set(subref, "bob-updated"), + () => Ref.get(parent), + ) + }, + ), + ) + + expect(result).toEqual([{ name: "alice" }, { name: "bob-updated" }, { name: "charlie" }]) + }) + + test("modifies array element at index 0", async () => { + const result = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make([10, 20, 30]), + parent => { + const subref = SubscriptionSubRef.makeFromPath(parent, [0]) + return Effect.flatMap( + Ref.set(subref, 99), + () => Ref.get(parent), + ) + }, + ), + ) + + expect(result).toEqual([99, 20, 30]) + }) + + test("modifies array element at last index", async () => { + const result = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make(["a", "b", "c"]), + parent => { + const subref = SubscriptionSubRef.makeFromPath(parent, [2]) + return Effect.flatMap( + Ref.set(subref, "z"), + () => Ref.get(parent), + ) + }, + ), + ) + + expect(result).toEqual(["a", "b", "z"]) + }) + + test("modifies nested array element", async () => { + const result = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make([[1, 2], [3, 4], [5, 6]]), + parent => { + const subref = SubscriptionSubRef.makeFromPath(parent, [1, 0]) + return Effect.flatMap( + Ref.set(subref, 99), + () => Ref.get(parent), + ) + }, + ), + ) + + expect(result).toEqual([[1, 2], [99, 4], [5, 6]]) + }) + + test("uses modifyEffect to transform array element", async () => { + const result = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make([{ count: 1 }, { count: 2 }, { count: 3 }]), + parent => { + const subref = SubscriptionSubRef.makeFromPath(parent, [1, "count"]) + return Effect.flatMap( + Ref.update(subref, count => count + 100), + () => Effect.map(Ref.get(parent), parentValue => ({ result: 102, parentValue })), + ) + }, + ), + ) + + expect(result.result).toBe(102) // count + 100 + expect(result.parentValue).toEqual([{ count: 1 }, { count: 102 }, { count: 3 }]) // count + 100 + }) + + test("uses modify to transform array element", async () => { + const result = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make([10, 20, 30]), + parent => { + const subref = SubscriptionSubRef.makeFromPath(parent, [1]) + return Effect.flatMap( + Ref.update(subref, x => x + 5), + () => Effect.map(Ref.get(parent), parentValue => ({ result: 25, parentValue })), + ) + }, + ), + ) + + expect(result.result).toBe(25) // 20 + 5 + expect(result.parentValue).toEqual([10, 25, 30]) // 20 + 5 + }) + + test("makeFromChunkIndex modifies chunk element", async () => { + const result = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make(Chunk.make(100, 200, 300)), + parent => { + const subref = SubscriptionSubRef.makeFromChunkIndex(parent, 1) + return Effect.flatMap( + Ref.set(subref, 999), + () => Ref.get(parent), + ) + }, + ), + ) + + expect(Chunk.toReadonlyArray(result)).toEqual([100, 999, 300]) + }) + + test("makeFromGetSet with custom getter/setter for array element", async () => { + const result = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make([{ id: 1, value: "a" }, { id: 2, value: "b" }]), + parent => { + const subref = SubscriptionSubRef.makeFromGetSet(parent, { + get: arr => arr[0].value, + set: (arr, newValue) => [ + { ...arr[0], value: newValue }, + ...arr.slice(1), + ], + }) + return Effect.flatMap( + Ref.set(subref, "updated"), + () => Ref.get(parent), + ) + }, + ), + ) + + expect(result).toEqual([{ id: 1, value: "updated" }, { id: 2, value: "b" }]) + }) + + test("does not mutate original array when modifying via subref", async () => { + const original = [{ name: "alice" }, { name: "bob" }] + const result = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make(original), + parent => { + const subref = SubscriptionSubRef.makeFromPath(parent, [0, "name"]) + return Effect.flatMap( + Ref.set(subref, "alice-updated"), + () => Ref.get(parent), + ) + }, + ), + ) + + expect(original).toEqual([{ name: "alice" }, { name: "bob" }]) // original unchanged + expect(result).toEqual([{ name: "alice-updated" }, { name: "bob" }]) // new value in ref + }) +})