/** * `scoped` — a component-lifetime resource: an `acquireRelease` yielded with * `scoped(...)` is acquired once per component and its finalizer runs exactly once * when the component unmounts (StrictMode-safe), extending the cancellation the * query/suspend fibers already get to arbitrary scoped resources. */ import { StrictMode, act } from 'react'; import { createRoot } from 'react-dom/client'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import * as Effect from 'effect/Effect'; import * as Layer from 'effect/Layer'; import { mount, rec, scoped } from '../../index.client.ts'; Reflect.set(globalThis, 'IS_REACT_ACT_ENVIRONMENT', true); const flush = (): Promise => act(async () => { await Promise.resolve(); await Promise.resolve(); }); let container: HTMLDivElement; beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(() => container.remove()); describe('scoped — component-lifetime resources (#50)', () => { it('acquires once and releases exactly once on unmount, under StrictMode', async () => { let acquired = 0; let released = 0; const resource = Effect.acquireRelease( Effect.sync(() => { acquired += 1; return 'socket'; }), () => Effect.sync(() => void (released += 1)), ); const Comp = rec(function* () { const handle = yield* scoped(resource); return {handle}; }).suspense(loading); const root = createRoot(container); await act(async () => root.render({mount(Layer.empty, Comp)})); await flush(); await act(async () => { await new Promise((r) => setTimeout(r, 20)); }); expect(container.querySelector('[data-x="r"]')?.textContent).toBe('socket'); expect(acquired).toBe(1); // acquired once despite StrictMode's double-render expect(released).toBe(0); // still mounted — not released await act(async () => root.unmount()); // The scope close is deferred a microtask (StrictMode-safe) and its finalizer // is async; give it a moment. await act(async () => { await new Promise((r) => setTimeout(r, 40)); }); expect(released).toBe(1); // released exactly once when the component is gone }); });