import { fireEvent, render, screen, waitFor } from "@testing-library/react" import { Effect, Layer, SubscriptionRef } from "effect" import * as React from "react" import { describe, expect, it } from "vitest" import * as Component from "../src/Component.js" import * as Lens from "../src/Lens.js" import * as ReactRuntime from "../src/ReactRuntime.js" const makeRuntime = async () => { const runtime = ReactRuntime.make(Layer.empty) const effectRuntime = await runtime.runtime.context() return { runtime, effectRuntime, dispose: () => runtime.runtime.dispose(), } } describe("Lens", () => { it("useState stays in sync with lens updates in both directions", async () => { const { runtime, effectRuntime, dispose } = await makeRuntime() const ref = await Effect.runPromise(SubscriptionRef.make(0)) const lens = Lens.fromSubscriptionRef(ref) const Probe = Component.makeUntraced("LensUseStateProbe")(function*() { const [value, setValue] = yield* Lens.useState(lens) return ( <>
{value}
) }).pipe( Component.withRuntime(runtime.context) ) const view = render( ) await screen.findByText("0") await Effect.runPromise(Lens.set(lens, 5)) await screen.findByText("5") fireEvent.click(screen.getByRole("button", { name: "increment" })) await screen.findByText("6") expect(await Effect.runPromise(Lens.get(lens))).toBe(6) view.unmount() await dispose() }) it("useState respects the provided equivalence when subscribing to lens changes", async () => { const { runtime, effectRuntime, dispose } = await makeRuntime() const ref = await Effect.runPromise(SubscriptionRef.make({ id: 1, label: "first" })) const lens = Lens.fromSubscriptionRef(ref) const Probe = Component.makeUntraced("LensUseStateEquivalenceProbe")(function*() { const [value] = yield* Lens.useState(lens, { equivalence: (self, that) => self.id === that.id, }) return
{value.label}
}).pipe( Component.withRuntime(runtime.context) ) const view = render( ) await screen.findByText("first") await Effect.runPromise(Lens.set(lens, { id: 1, label: "ignored" })) await waitFor(() => expect(screen.getByText("first")).toBeTruthy()) expect(screen.queryByText("ignored")).toBeNull() await Effect.runPromise(Lens.set(lens, { id: 2, label: "updated" })) await screen.findByText("updated") view.unmount() await dispose() }) it("useFromReactState writes React state changes into the returned lens", async () => { const { runtime, effectRuntime, dispose } = await makeRuntime() let lens: Lens.Lens | undefined const Probe = Component.makeUntraced("LensUseFromReactStateProbe")(function*() { const [value, setValue] = React.useState("hello") const reactLens = yield* Lens.useFromReactState([value, setValue]) yield* Component.useOnMount(() => Effect.sync(() => { lens = reactLens })) return }).pipe( Component.withRuntime(runtime.context) ) const view = render( ) await screen.findByText("hello") await waitFor(() => expect(lens).toBeDefined()) fireEvent.click(screen.getByRole("button", { name: "hello" })) await screen.findByText("hello!") await waitFor(async () => expect(await Effect.runPromise(Lens.get(lens!))).toBe("hello!")) view.unmount() await dispose() }) it("useFromReactState respects equivalence when lens updates flow back into React state", async () => { const { runtime, effectRuntime, dispose } = await makeRuntime() let lens: Lens.Lens<{ readonly id: number; readonly label: string }, never, never, never, never> | undefined const Probe = Component.makeUntraced("LensUseFromReactStateEquivalenceProbe")(function*() { const [value, setValue] = React.useState({ id: 1, label: "first" }) const reactLens = yield* Lens.useFromReactState([value, setValue], { equivalence: (self, that) => self.id === that.id, }) yield* Component.useOnMount(() => Effect.sync(() => { lens = reactLens })) return
{value.label}
}).pipe( Component.withRuntime(runtime.context) ) const view = render( ) await screen.findByText("first") await waitFor(() => expect(lens).toBeDefined()) await Effect.runPromise(Lens.set(lens!, { id: 1, label: "ignored" })) await waitFor(() => expect(screen.getByText("first")).toBeTruthy()) expect(screen.queryByText("ignored")).toBeNull() await Effect.runPromise(Lens.set(lens!, { id: 2, label: "updated" })) await screen.findByText("updated") view.unmount() await dispose() }) })