Add Effect v4 support, Fast Refresh tooling, and revamped docs #56

Merged
Thilawyn merged 133 commits from next into master 2026-07-26 02:32:59 +02:00
3 changed files with 23 additions and 11 deletions
Showing only changes of commit 2b677f8810 - Show all commits
@@ -312,7 +312,7 @@ describe("Component", () => {
yield* Effect.addFinalizer(() => Effect.sync(() => unmounts())) yield* Effect.addFinalizer(() => Effect.sync(() => unmounts()))
})) }))
return <button onClick={() => setCount(value => value + 1)}>{`${service.value}:${count}`}</button> return <button type="button" onClick={() => setCount(value => value + 1)}>{`${service.value}:${count}`}</button>
}).pipe( }).pipe(
Component.withOptions({ Component.withOptions({
nonReactiveTags: [...Component.defaultOptions.nonReactiveTags, ValueService], nonReactiveTags: [...Component.defaultOptions.nonReactiveTags, ValueService],
+11 -5
View File
@@ -18,6 +18,12 @@ const makeRuntime = async () => {
} }
} }
const expectDefined = <A,>(value: A | undefined): A => {
if (value === undefined)
throw new Error("Expected value to be defined")
return value
}
describe("Lens", () => { describe("Lens", () => {
it("useState stays in sync with lens updates in both directions", async () => { it("useState stays in sync with lens updates in both directions", async () => {
const { runtime, effectRuntime, dispose } = await makeRuntime() const { runtime, effectRuntime, dispose } = await makeRuntime()
@@ -30,7 +36,7 @@ describe("Lens", () => {
return ( return (
<> <>
<div>{value}</div> <div>{value}</div>
<button onClick={() => setValue(previous => previous + 1)}>increment</button> <button type="button" onClick={() => setValue(previous => previous + 1)}>increment</button>
</> </>
) )
}).pipe( }).pipe(
@@ -102,7 +108,7 @@ describe("Lens", () => {
lens = reactLens lens = reactLens
})) }))
return <button onClick={() => setValue(previous => `${previous}!`)}>{value}</button> return <button type="button" onClick={() => setValue(previous => `${previous}!`)}>{value}</button>
}).pipe( }).pipe(
Component.withContext(runtime.context) Component.withContext(runtime.context)
) )
@@ -118,7 +124,7 @@ describe("Lens", () => {
fireEvent.click(screen.getByRole("button", { name: "hello" })) fireEvent.click(screen.getByRole("button", { name: "hello" }))
await screen.findByText("hello!") await screen.findByText("hello!")
await waitFor(async () => expect(await Effect.runPromise(Lens.get(lens!))).toBe("hello!")) await waitFor(async () => expect(await Effect.runPromise(Lens.get(expectDefined(lens)))).toBe("hello!"))
view.unmount() view.unmount()
await dispose() await dispose()
@@ -152,11 +158,11 @@ describe("Lens", () => {
await screen.findByText("first") await screen.findByText("first")
await waitFor(() => expect(lens).toBeDefined()) await waitFor(() => expect(lens).toBeDefined())
await Effect.runPromise(Lens.set(lens!, { id: 1, label: "ignored" })) await Effect.runPromise(Lens.set(expectDefined(lens), { id: 1, label: "ignored" }))
await waitFor(() => expect(screen.getByText("first")).toBeTruthy()) await waitFor(() => expect(screen.getByText("first")).toBeTruthy())
expect(screen.queryByText("ignored")).toBeNull() expect(screen.queryByText("ignored")).toBeNull()
await Effect.runPromise(Lens.set(lens!, { id: 2, label: "updated" })) await Effect.runPromise(Lens.set(expectDefined(lens), { id: 2, label: "updated" }))
await screen.findByText("updated") await screen.findByText("updated")
view.unmount() view.unmount()
+11 -5
View File
@@ -18,6 +18,12 @@ const makeRuntime = async () => {
} }
} }
const expectDefined = <A,>(value: A | undefined): A => {
if (value === undefined)
throw new Error("Expected value to be defined")
return value
}
describe("Lens", () => { describe("Lens", () => {
it("useState stays in sync with lens updates in both directions", async () => { it("useState stays in sync with lens updates in both directions", async () => {
const { runtime, effectRuntime, dispose } = await makeRuntime() const { runtime, effectRuntime, dispose } = await makeRuntime()
@@ -30,7 +36,7 @@ describe("Lens", () => {
return ( return (
<> <>
<div>{value}</div> <div>{value}</div>
<button onClick={() => setValue(previous => previous + 1)}>increment</button> <button type="button" onClick={() => setValue(previous => previous + 1)}>increment</button>
</> </>
) )
}).pipe( }).pipe(
@@ -102,7 +108,7 @@ describe("Lens", () => {
lens = reactLens lens = reactLens
})) }))
return <button onClick={() => setValue(previous => `${previous}!`)}>{value}</button> return <button type="button" onClick={() => setValue(previous => `${previous}!`)}>{value}</button>
}).pipe( }).pipe(
Component.withRuntime(runtime.context) Component.withRuntime(runtime.context)
) )
@@ -118,7 +124,7 @@ describe("Lens", () => {
fireEvent.click(screen.getByRole("button", { name: "hello" })) fireEvent.click(screen.getByRole("button", { name: "hello" }))
await screen.findByText("hello!") await screen.findByText("hello!")
await waitFor(async () => expect(await Effect.runPromise(Lens.get(lens!))).toBe("hello!")) await waitFor(async () => expect(await Effect.runPromise(Lens.get(expectDefined(lens)))).toBe("hello!"))
view.unmount() view.unmount()
await dispose() await dispose()
@@ -152,11 +158,11 @@ describe("Lens", () => {
await screen.findByText("first") await screen.findByText("first")
await waitFor(() => expect(lens).toBeDefined()) await waitFor(() => expect(lens).toBeDefined())
await Effect.runPromise(Lens.set(lens!, { id: 1, label: "ignored" })) await Effect.runPromise(Lens.set(expectDefined(lens), { id: 1, label: "ignored" }))
await waitFor(() => expect(screen.getByText("first")).toBeTruthy()) await waitFor(() => expect(screen.getByText("first")).toBeTruthy())
expect(screen.queryByText("ignored")).toBeNull() expect(screen.queryByText("ignored")).toBeNull()
await Effect.runPromise(Lens.set(lens!, { id: 2, label: "updated" })) await Effect.runPromise(Lens.set(expectDefined(lens), { id: 2, label: "updated" }))
await screen.findByText("updated") await screen.findByText("updated")
view.unmount() view.unmount()