UNPKG

use-async-effekt-hooks

Version:

React hooks for async effects and memoization with proper dependency tracking and linting support

600 lines (599 loc) 37 kB
var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime"; import React, { Suspense, startTransition } from "react"; import { render, screen, waitFor, act as rtlAct } from "@testing-library/react"; import { act } from "./test-utils"; import { useAsyncMemoSuspense } from "../useAsyncMemoSuspense"; function TestComponent(_a) { var factory = _a.factory, deps = _a.deps, scope = _a.scope, testId = _a.testId; var value = useAsyncMemoSuspense(factory, deps, { scope: scope }); return (_jsxs("div", { "data-testid": testId || "test-result", children: ["Value: ", String(value)] })); } function UserProfile(_a) { var _this = this; var userId = _a.userId, _b = _a.includeDetails, includeDetails = _b === void 0 ? false : _b; var user = useAsyncMemoSuspense(function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: // Simulate realistic API call return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 10); })]; case 1: // Simulate realistic API call _a.sent(); return [2 /*return*/, { id: userId, name: "User ".concat(userId), email: "user".concat(userId, "@example.com"), details: includeDetails ? "Details for ".concat(userId) : undefined, }]; } }); }); }, [userId, includeDetails], // Realistic dependency usage { scope: "user-profile-".concat(userId, "-").concat(includeDetails) } // Proper scoping ); return (_jsxs("div", { "data-testid": "user-profile", children: [_jsx("h1", { children: user === null || user === void 0 ? void 0 : user.name }), _jsx("p", { children: user === null || user === void 0 ? void 0 : user.email }), (user === null || user === void 0 ? void 0 : user.details) && _jsx("p", { children: user.details })] })); } var ErrorBoundary = /** @class */ (function (_super) { __extends(ErrorBoundary, _super); function ErrorBoundary() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.state = { hasError: false, error: null }; return _this; } ErrorBoundary.getDerivedStateFromError = function (error) { return { hasError: true, error: error }; }; ErrorBoundary.prototype.render = function () { var _a; if (this.state.hasError) { return (_jsxs("div", { "data-testid": "error-boundary", children: ["Error: ", (_a = this.state.error) === null || _a === void 0 ? void 0 : _a.message] })); } return this.props.children; }; return ErrorBoundary; }(React.Component)); describe("useAsyncMemoSuspense - Core Functionality", function () { var consoleErrorSpy; beforeEach(function () { consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(function () { }); jest.clearAllMocks(); jest.clearAllTimers(); jest.useFakeTimers(); }); afterEach(function () { consoleErrorSpy.mockRestore(); jest.runOnlyPendingTimers(); jest.useRealTimers(); }); describe("Basic async operations", function () { it("should suspend and then render resolved value", function () { return __awaiter(void 0, void 0, void 0, function () { var factory; return __generator(this, function (_a) { switch (_a.label) { case 0: factory = jest .fn() .mockImplementation(function () { return new Promise(function (resolve) { return setTimeout(function () { return resolve("async-result"); }, 50); }); }); render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestComponent, { factory: factory, deps: [], scope: "basic-async-test", testId: "async-result" }) })); expect(screen.getByTestId("loading")).toBeInTheDocument(); expect(factory).toHaveBeenCalledTimes(1); return [4 /*yield*/, act(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { jest.advanceTimersByTime(50); return [2 /*return*/]; }); }); })]; case 1: _a.sent(); return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("async-result")).toHaveTextContent("Value: async-result"); })]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }); it("should handle synchronous values without suspending", function () { return __awaiter(void 0, void 0, void 0, function () { var factory; return __generator(this, function (_a) { switch (_a.label) { case 0: factory = jest.fn().mockReturnValue("sync-result"); render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestComponent, { factory: factory, deps: [], scope: "sync-test", testId: "sync-result" }) })); // Wait a tick to ensure React has processed the synchronous result return [4 /*yield*/, waitFor(function () { expect(screen.queryByTestId("loading")).not.toBeInTheDocument(); expect(screen.getByTestId("sync-result")).toHaveTextContent("Value: sync-result"); })]; case 1: // Wait a tick to ensure React has processed the synchronous result _a.sent(); expect(factory).toHaveBeenCalledTimes(1); return [2 /*return*/]; } }); }); }); }); describe("Error handling", function () { it("should throw async errors to error boundary", function () { return __awaiter(void 0, void 0, void 0, function () { var error, factory; return __generator(this, function (_a) { switch (_a.label) { case 0: error = new Error("Async operation failed"); factory = jest.fn().mockRejectedValue(error); render(_jsx(ErrorBoundary, { children: _jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestComponent, { factory: factory, deps: [], scope: "async-error-test" }) }) })); return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("error-boundary")).toHaveTextContent("Error: Async operation failed"); }, { timeout: 1000 })]; case 1: _a.sent(); expect(factory).toHaveBeenCalledTimes(1); return [2 /*return*/]; } }); }); }); it("should throw sync errors to error boundary", function () { var error = new Error("Sync operation failed"); var factory = jest.fn().mockImplementation(function () { throw error; }); render(_jsx(ErrorBoundary, { children: _jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestComponent, { factory: factory, deps: [], scope: "sync-error-test" }) }) })); expect(screen.getByTestId("error-boundary")).toHaveTextContent("Error: Sync operation failed"); }); }); describe("Dependency management", function () { it("should recompute when dependencies change", function () { return __awaiter(void 0, void 0, void 0, function () { var rerender; return __generator(this, function (_a) { switch (_a.label) { case 0: rerender = render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(UserProfile, { userId: "1" }) })).rerender; return [4 /*yield*/, waitFor(function () { return expect(screen.getByText("User 1")).toBeInTheDocument(); })]; case 1: _a.sent(); rerender(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(UserProfile, { userId: "2" }) })); return [4 /*yield*/, waitFor(function () { return expect(screen.getByText("User 2")).toBeInTheDocument(); })]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }); it("should handle boolean dependency changes", function () { return __awaiter(void 0, void 0, void 0, function () { var rerender; return __generator(this, function (_a) { switch (_a.label) { case 0: rerender = render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(UserProfile, { userId: "1", includeDetails: false }) })).rerender; return [4 /*yield*/, waitFor(function () { return expect(screen.getByText("User 1")).toBeInTheDocument(); })]; case 1: _a.sent(); expect(screen.queryByText("Details for 1")).not.toBeInTheDocument(); rerender(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(UserProfile, { userId: "1", includeDetails: true }) })); return [4 /*yield*/, waitFor(function () { return expect(screen.getByText("Details for 1")).toBeInTheDocument(); })]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }); }); describe("Scoping", function () { it("should differentiate caches with different scopes", function () { return __awaiter(void 0, void 0, void 0, function () { var factory1, factory2; return __generator(this, function (_a) { switch (_a.label) { case 0: factory1 = jest.fn().mockResolvedValue("scoped-value-1"); factory2 = jest.fn().mockResolvedValue("scoped-value-2"); render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsxs("div", { children: [_jsx(TestComponent, { factory: factory1, deps: [], scope: "scope-1", testId: "result-1" }), _jsx(TestComponent, { factory: factory2, deps: [], scope: "scope-2", testId: "result-2" })] }) })); return [4 /*yield*/, waitFor(function () { expect(screen.getByTestId("result-1")).toHaveTextContent("Value: scoped-value-1"); expect(screen.getByTestId("result-2")).toHaveTextContent("Value: scoped-value-2"); })]; case 1: _a.sent(); expect(factory1).toHaveBeenCalledTimes(1); expect(factory2).toHaveBeenCalledTimes(1); return [2 /*return*/]; } }); }); }); }); describe("React 18 concurrent features", function () { it("should work with startTransition", function () { return __awaiter(void 0, void 0, void 0, function () { var resolver, factory, App, rerender; return __generator(this, function (_a) { switch (_a.label) { case 0: factory = jest .fn() .mockImplementationOnce(function () { return new Promise(function (res) { resolver = res; }); }) .mockImplementationOnce(function () { return new Promise(function (resolve) { return setTimeout(function () { return resolve("transition-value-2"); }, 10); }); }); App = function (_a) { var dep = _a.dep; var value = useAsyncMemoSuspense(factory, [dep], { scope: "transition-test-".concat(dep), }); return _jsxs("div", { "data-testid": "transition-result", children: ["Value: ", value] }); }; rerender = render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(App, { dep: 1 }) })).rerender; expect(screen.getByTestId("loading")).toBeInTheDocument(); return [4 /*yield*/, rtlAct(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { resolver("transition-value-1"); return [2 /*return*/]; }); }); })]; case 1: _a.sent(); return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("transition-result")).toHaveTextContent("Value: transition-value-1"); })]; case 2: _a.sent(); startTransition(function () { rerender(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(App, { dep: 2 }) })); }); // With startTransition, should show old value while loading expect(screen.getByTestId("transition-result")).toHaveTextContent("Value: transition-value-1"); return [4 /*yield*/, act(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { jest.advanceTimersByTime(10); return [2 /*return*/]; }); }); })]; case 3: _a.sent(); return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("transition-result")).toHaveTextContent("Value: transition-value-2"); })]; case 4: _a.sent(); expect(factory).toHaveBeenCalledTimes(2); return [2 /*return*/]; } }); }); }); }); describe("Cache behavior and edge cases", function () { it("should reuse cache when dependencies haven't changed", function () { return __awaiter(void 0, void 0, void 0, function () { var factory, TestCacheComponent, rerender; return __generator(this, function (_a) { switch (_a.label) { case 0: factory = jest.fn().mockResolvedValue("cached-result"); TestCacheComponent = function (_a) { var rerender = _a.rerender; var value = useAsyncMemoSuspense(factory, ["static-dep"], { scope: "cache-test", }); return (_jsxs("div", { "data-testid": "cache-result", children: ["Value: ", value, " - Render: ", rerender ? "second" : "first"] })); }; rerender = render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestCacheComponent, { rerender: false }) })).rerender; return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("cache-result")).toHaveTextContent("Value: cached-result - Render: first"); })]; case 1: _a.sent(); expect(factory).toHaveBeenCalledTimes(1); // Rerender with same dependencies - should use cache rerender(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestCacheComponent, { rerender: true }) })); return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("cache-result")).toHaveTextContent("Value: cached-result - Render: second"); })]; case 2: _a.sent(); // Factory should still only be called once due to caching expect(factory).toHaveBeenCalledTimes(1); return [2 /*return*/]; } }); }); }); it("should handle multiple components with same cache key", function () { return __awaiter(void 0, void 0, void 0, function () { var factory, ComponentA, ComponentB; return __generator(this, function (_a) { switch (_a.label) { case 0: factory = jest.fn().mockResolvedValue("shared-result"); ComponentA = function () { var value = useAsyncMemoSuspense(factory, ["shared"], { scope: "shared-scope", }); return _jsxs("div", { "data-testid": "component-a", children: ["A: ", value] }); }; ComponentB = function () { var value = useAsyncMemoSuspense(factory, ["shared"], { scope: "shared-scope", }); return _jsxs("div", { "data-testid": "component-b", children: ["B: ", value] }); }; render(_jsxs(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: [_jsx(ComponentA, {}), _jsx(ComponentB, {})] })); return [4 /*yield*/, waitFor(function () { expect(screen.getByTestId("component-a")).toHaveTextContent("A: shared-result"); expect(screen.getByTestId("component-b")).toHaveTextContent("B: shared-result"); })]; case 1: _a.sent(); // Factory should only be called once since both components share the same cache expect(factory).toHaveBeenCalledTimes(1); return [2 /*return*/]; } }); }); }); it("should handle complex dependency types", function () { return __awaiter(void 0, void 0, void 0, function () { var complexObj, factory, TestComplexDeps, rerender; return __generator(this, function (_a) { switch (_a.label) { case 0: complexObj = { nested: { value: 42 }, array: [1, 2, 3] }; factory = jest.fn().mockResolvedValue("complex-deps-result"); TestComplexDeps = function (_a) { var obj = _a.obj, num = _a.num, str = _a.str, bool = _a.bool, nullVal = _a.nullVal, undefinedVal = _a.undefinedVal; var value = useAsyncMemoSuspense(factory, [obj, num, str, bool, nullVal, undefinedVal], { scope: "complex-deps" }); return _jsxs("div", { "data-testid": "complex-result", children: ["Value: ", value] }); }; rerender = render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestComplexDeps, { obj: complexObj, num: 42, str: "test", bool: true, nullVal: null, undefinedVal: undefined }) })).rerender; return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("complex-result")).toHaveTextContent("Value: complex-deps-result"); })]; case 1: _a.sent(); expect(factory).toHaveBeenCalledTimes(1); // Rerender with same complex dependencies - should use cache rerender(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestComplexDeps, { obj: complexObj, num: 42, str: "test", bool: true, nullVal: null, undefinedVal: undefined }) })); // Should still use cache expect(factory).toHaveBeenCalledTimes(1); // Change one dependency - should invalidate cache rerender(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestComplexDeps, { obj: complexObj, num: 43, str: "test", bool: true, nullVal: null, undefinedVal: undefined }) })); return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("complex-result")).toHaveTextContent("Value: complex-deps-result"); })]; case 2: _a.sent(); expect(factory).toHaveBeenCalledTimes(2); return [2 /*return*/]; } }); }); }); it("should handle empty dependencies array", function () { return __awaiter(void 0, void 0, void 0, function () { var factory, TestEmptyDeps; return __generator(this, function (_a) { switch (_a.label) { case 0: factory = jest.fn().mockResolvedValue("empty-deps-result"); TestEmptyDeps = function () { var value = useAsyncMemoSuspense(factory, [], { scope: "empty-deps-test", }); return _jsxs("div", { "data-testid": "empty-deps-result", children: ["Value: ", value] }); }; render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestEmptyDeps, {}) })); return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("empty-deps-result")).toHaveTextContent("Value: empty-deps-result"); })]; case 1: _a.sent(); expect(factory).toHaveBeenCalledTimes(1); return [2 /*return*/]; } }); }); }); it("should handle no dependencies parameter (default empty array)", function () { return __awaiter(void 0, void 0, void 0, function () { var factory, TestNoDeps; return __generator(this, function (_a) { switch (_a.label) { case 0: factory = jest.fn().mockResolvedValue("no-deps-result"); TestNoDeps = function () { // Test calling without deps parameter var value = useAsyncMemoSuspense(factory, undefined, { scope: "no-deps-test", }); return _jsxs("div", { "data-testid": "no-deps-result", children: ["Value: ", value] }); }; render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestNoDeps, {}) })); return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("no-deps-result")).toHaveTextContent("Value: no-deps-result"); })]; case 1: _a.sent(); expect(factory).toHaveBeenCalledTimes(1); return [2 /*return*/]; } }); }); }); it("should handle zero and negative zero in dependencies", function () { return __awaiter(void 0, void 0, void 0, function () { var factory, TestZero, rerender; return __generator(this, function (_a) { switch (_a.label) { case 0: factory = jest.fn().mockResolvedValue("zero-result"); TestZero = function (_a) { var zeroValue = _a.zeroValue; var value = useAsyncMemoSuspense(factory, [zeroValue], { scope: "zero-test", }); return _jsxs("div", { "data-testid": "zero-result", children: ["Value: ", value] }); }; rerender = render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestZero, { zeroValue: 0 }) })).rerender; return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("zero-result")).toHaveTextContent("Value: zero-result"); })]; case 1: _a.sent(); expect(factory).toHaveBeenCalledTimes(1); // Rerender with -0 - should be treated as different due to Object.is(0, -0) being false rerender(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestZero, { zeroValue: -0 }) })); return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("zero-result")).toHaveTextContent("Value: zero-result"); })]; case 2: _a.sent(); // Should be called twice since Object.is(0, -0) is false expect(factory).toHaveBeenCalledTimes(2); return [2 /*return*/]; } }); }); }); it("should handle functions with identical string representations but different behavior", function () { return __awaiter(void 0, void 0, void 0, function () { var factory1, factory2, TestIdenticalFunctions, rerender; return __generator(this, function (_a) { switch (_a.label) { case 0: factory1 = jest.fn().mockResolvedValue("factory1-result"); factory2 = jest.fn().mockResolvedValue("factory2-result"); TestIdenticalFunctions = function (_a) { var useFirst = _a.useFirst; var factory = useFirst ? factory1 : factory2; var value = useAsyncMemoSuspense(factory, [useFirst]); return _jsxs("div", { "data-testid": "identical-result", children: ["Value: ", value] }); }; rerender = render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestIdenticalFunctions, { useFirst: true }) })).rerender; return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("identical-result")).toHaveTextContent("Value: factory1-result"); })]; case 1: _a.sent(); expect(factory1).toHaveBeenCalledTimes(1); rerender(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestIdenticalFunctions, { useFirst: false }) })); return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("identical-result")).toHaveTextContent("Value: factory2-result"); })]; case 2: _a.sent(); expect(factory2).toHaveBeenCalledTimes(1); return [2 /*return*/]; } }); }); }); }); // Note: Error recovery is already tested in the main error handling tests describe("Performance and memory considerations", function () { it("should handle rapid dependency changes efficiently", function () { return __awaiter(void 0, void 0, void 0, function () { var factory, TestRapidChanges, rerender, _loop_1, i; return __generator(this, function (_a) { switch (_a.label) { case 0: factory = jest .fn() .mockImplementation(function (dep) { return Promise.resolve("rapid-".concat(dep)); }); TestRapidChanges = function (_a) { var dep = _a.dep; var value = useAsyncMemoSuspense(function () { return factory(dep); }, [dep], { scope: "rapid-test", }); return _jsxs("div", { "data-testid": "rapid-result", children: ["Value: ", value] }); }; rerender = render(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestRapidChanges, { dep: 1 }) })).rerender; return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("rapid-result")).toHaveTextContent("Value: rapid-1"); })]; case 1: _a.sent(); _loop_1 = function (i) { return __generator(this, function (_b) { switch (_b.label) { case 0: rerender(_jsx(Suspense, { fallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), children: _jsx(TestRapidChanges, { dep: i }) })); return [4 /*yield*/, waitFor(function () { return expect(screen.getByTestId("rapid-result")).toHaveTextContent("Value: rapid-".concat(i)); })]; case 1: _b.sent(); return [2 /*return*/]; } }); }; i = 2; _a.label = 2; case 2: if (!(i <= 5)) return [3 /*break*/, 5]; return [5 /*yield**/, _loop_1(i)]; case 3: _a.sent(); _a.label = 4; case 4: i++; return [3 /*break*/, 2]; case 5: expect(factory).toHaveBeenCalledTimes(5); return [2 /*return*/]; } }); }); }); }); });