Implement ScopeRegistry
Lint / lint (push) Failing after 42s

This commit is contained in:
Julien Valverdé
2026-07-23 01:22:14 +02:00
parent 8fed17789a
commit cb62639829
4 changed files with 253 additions and 68 deletions
@@ -1,5 +1,5 @@
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"
import { Context, Effect, HashMap, Layer, Ref } from "effect"
import { Context, Effect, HashMap, Layer, SubscriptionRef } from "effect"
import * as React from "react"
import { afterEach, describe, expect, it, vi } from "vitest"
import * as Component from "../src/Component.js"
@@ -362,7 +362,7 @@ describe("Component", () => {
await runtime.runtime.dispose()
})
it.fails("does not commit effects or retain registered scopes for a discarded Suspense render", async () => {
it("does not commit effects or retain registered scopes for a discarded Suspense render", async () => {
const setup = vi.fn()
const runtime = ReactRuntime.make(Layer.empty)
const effectRuntime = await runtime.runtime.context()
@@ -391,7 +391,7 @@ describe("Component", () => {
try {
await screen.findByText("fallback")
expect(setup).not.toHaveBeenCalled()
expect(HashMap.size(Effect.runSync(Ref.get(scopeRegistry.ref)))).toBe(0)
await waitFor(() => expect(HashMap.size(Effect.runSync(SubscriptionRef.get(scopeRegistry.ref)))).toBe(0))
}
finally {
view.unmount()
@@ -399,7 +399,45 @@ describe("Component", () => {
}
})
it.fails("clears ScopeRegistry after a suspended render retries, commits, and unmounts", async () => {
it("keeps committed scopes alive without heartbeats", async () => {
const acquisitions = vi.fn()
const releases = vi.fn()
const runtime = ReactRuntime.make(Layer.empty)
const effectRuntime = await runtime.runtime.context()
const scopeRegistry = Context.get(effectRuntime, ScopeRegistry.ScopeRegistry)
const Probe = Component.makeUntraced("CommittedScopeProbe")(function*() {
yield* Component.useOnMount(() => Effect.gen(function*() {
yield* Effect.sync(acquisitions)
yield* Effect.addFinalizer(() => Effect.sync(releases))
}))
return <div>committed</div>
}).pipe(
Component.withOptions({ finalizerExecutionDebounce: "10 millis" }),
Component.withContext(runtime.context),
)
const view = render(
<runtime.context.Provider value={effectRuntime}>
<Probe />
</runtime.context.Provider>
)
await screen.findByText("committed")
await waitFor(() => expect(releases).toHaveBeenCalledTimes(acquisitions.mock.calls.length - 1))
await new Promise(resolve => setTimeout(resolve, 30))
expect(HashMap.size(Effect.runSync(SubscriptionRef.get(scopeRegistry.ref)))).toBe(1)
expect(releases).toHaveBeenCalledTimes(acquisitions.mock.calls.length - 1)
view.unmount()
await waitFor(() => expect(releases).toHaveBeenCalledTimes(acquisitions.mock.calls.length))
await waitFor(() => expect(HashMap.size(Effect.runSync(SubscriptionRef.get(scopeRegistry.ref)))).toBe(0))
await runtime.runtime.dispose()
})
it("clears ScopeRegistry after a suspended render retries, commits, and unmounts", async () => {
const lifecycle = vi.fn<(message: string) => void>()
const runtime = ReactRuntime.make(Layer.empty)
const effectRuntime = await runtime.runtime.context()
@@ -441,7 +479,7 @@ describe("Component", () => {
await waitFor(() => expect(lifecycle).toHaveBeenCalledWith("mount"))
view.unmount()
await waitFor(() => expect(HashMap.size(Effect.runSync(Ref.get(scopeRegistry.ref)))).toBe(0))
await waitFor(() => expect(HashMap.size(Effect.runSync(SubscriptionRef.get(scopeRegistry.ref)))).toBe(0))
expect(lifecycle.mock.calls.filter(([message]) => message === "mount")).toHaveLength(2)
expect(lifecycle.mock.calls.filter(([message]) => message === "cleanup")).toHaveLength(2)
}