diff --git a/docs/reference/routing/useIsRouteMatchProtected.md b/docs/reference/routing/useIsRouteMatchProtected.md index 250b14e45..6e2c06951 100644 --- a/docs/reference/routing/useIsRouteMatchProtected.md +++ b/docs/reference/routing/useIsRouteMatchProtected.md @@ -18,10 +18,12 @@ const isProtected = useIsRouteMatchProtected(locationArg) ### Parameters - `locationArg`: The location to match the route paths against. +- `options`: An optional object literal of options: + - `throwWhenThereIsNoMatch`: Whether or not to throw an `Error` if no route match `locationArg`. ### Returns -A `boolean` value indicating whether or not the matching route is `protected`. If no route match the given location, an `Error` is thrown. +A `boolean` value indicating whether or not the matching route is `protected`. If `throwWhenThereIsNoMatch` is enabled and no route match the given location, an `Error` is thrown. ## Usage diff --git a/packages/core/src/federation/registerLocalModules.ts b/packages/core/src/federation/registerLocalModules.ts index 8587b3f55..f72e88a3e 100644 --- a/packages/core/src/federation/registerLocalModules.ts +++ b/packages/core/src/federation/registerLocalModules.ts @@ -19,8 +19,7 @@ export interface LocalModuleRegistrationError { export class LocalModuleRegistry { #registrationStatus: ModuleRegistrationStatus = "none"; - - readonly #deferredRegistrations: DeferredRegistration[] = []; + #deferredRegistrations: DeferredRegistration[] = []; async registerModules(registerFunctions: ModuleRegisterFunction[], runtime: TRuntime, { context }: RegisterLocalModulesOptions = {}) { const errors: LocalModuleRegistrationError[] = []; @@ -109,6 +108,12 @@ export class LocalModuleRegistry { get registrationStatus() { return this.#registrationStatus; } + + // Strictly for Jest tests, this is NOT ideal. + __reset() { + this.#registrationStatus = "none"; + this.#deferredRegistrations = []; + } } const localModuleRegistry = new LocalModuleRegistry(); @@ -124,3 +129,8 @@ export function completeLocalModuleRegistrations" }) - } + }, + setupFilesAfterEnv: ["/jest-setup.js"] }; export default config; diff --git a/packages/firefly/package.json b/packages/firefly/package.json index 62c3e97da..06c355e18 100644 --- a/packages/firefly/package.json +++ b/packages/firefly/package.json @@ -47,6 +47,7 @@ "@swc/helpers": "0.5.3", "@swc/jest": "0.2.29", "@testing-library/react": "14.0.0", + "@testing-library/jest-dom": "6.1.4", "@types/jest": "29.5.6", "@types/react": "18.2.33", "@types/react-dom": "18.2.14", diff --git a/packages/firefly/src/AppRouter.tsx b/packages/firefly/src/AppRouter.tsx index 04ad40676..bc33160e1 100644 --- a/packages/firefly/src/AppRouter.tsx +++ b/packages/firefly/src/AppRouter.tsx @@ -5,17 +5,17 @@ import { cloneElement, useCallback, useEffect, useMemo, useState, type ReactElem import { ErrorBoundary, useErrorBoundary } from "react-error-boundary"; import { Outlet, RouterProvider, createBrowserRouter, useLocation, type RouterProviderProps } from "react-router-dom"; -export type OnLoadPublicDataFunction = (signal: AbortSignal) => Promise; +export type OnLoadPublicDataFunction = () => Promise; -export type OnLoadProtectedDataFunction = (signal: AbortSignal) => Promise; +export type OnLoadProtectedDataFunction = () => Promise; -export type OnCompleteRegistrationFunction = () => Promise; +export type OnCompleteRegistrationsFunction = () => Promise; interface BootstrappingRouteProps { fallbackElement: ReactElement; onLoadPublicData?: OnLoadPublicDataFunction; onLoadProtectedData?: OnLoadProtectedDataFunction; - onCompleteRegistration?: OnCompleteRegistrationFunction; + onCompleteRegistrations?: OnCompleteRegistrationsFunction; waitForMsw: boolean; areModulesRegistered: boolean; areModulesReady: boolean; @@ -28,7 +28,7 @@ export function BootstrappingRoute(props: BootstrappingRouteProps) { fallbackElement, onLoadPublicData, onLoadProtectedData, - onCompleteRegistration, + onCompleteRegistrations, waitForMsw, areModulesRegistered, areModulesReady @@ -66,9 +66,7 @@ export function BootstrappingRoute(props: BootstrappingRouteProps) { if (!isPublicDataLoaded) { logger.debug("[shell] Loading public data."); - const abordController = new AbortController(); - - onLoadPublicData(abordController.signal) + onLoadPublicData() .then(() => { setIsPublicDataLoaded(true); @@ -77,16 +75,13 @@ export function BootstrappingRoute(props: BootstrappingRouteProps) { .catch(error => { showBoundary(error); }); - - return () => { - abordController.abort(); - }; } } } }, [logger, areModulesRegistered, areModulesReady, isMswStarted, isPublicDataLoaded, onLoadPublicData]); - const isActiveRouteProtected = useIsRouteMatchProtected(location); + // Only throw when there's no match if the modules has been registered, otherwise it's expected that there are no registered routes. + const isActiveRouteProtected = useIsRouteMatchProtected(location, { throwWhenThereIsNoMatch: areModulesReady }); useEffect(() => { // Don't go further if no handler has been provided to load protected data. @@ -96,9 +91,7 @@ export function BootstrappingRoute(props: BootstrappingRouteProps) { if (!isProtectedDataLoaded) { logger.debug(`[shell] Loading protected data as "${location.pathname}" is a protected route.`); - const abordController = new AbortController(); - - onLoadProtectedData(abordController.signal) + onLoadProtectedData() .then(() => { setIsProtectedDataLoaded(true); @@ -107,10 +100,6 @@ export function BootstrappingRoute(props: BootstrappingRouteProps) { .catch(error => { showBoundary(error); }); - - return () => { - abordController.abort(); - }; } } else { logger.debug(`[shell] Not loading protected data as "${location.pathname}" is a public route.`); @@ -121,14 +110,14 @@ export function BootstrappingRoute(props: BootstrappingRouteProps) { useEffect(() => { // Don't go further if no handler has been provided to complete the registration. - if (onCompleteRegistration) { + if (onCompleteRegistrations) { if (areModulesRegistered && isMswStarted && isPublicDataLoaded) { if (!areModulesReady) { - onCompleteRegistration(); + onCompleteRegistrations(); } } } - }, [areModulesRegistered, areModulesReady, isMswStarted, isPublicDataLoaded, onCompleteRegistration]); + }, [areModulesRegistered, areModulesReady, isMswStarted, isPublicDataLoaded, onCompleteRegistrations]); if (!areModulesReady || !isMswStarted || !isPublicDataLoaded || (isActiveRouteProtected && !isProtectedDataLoaded)) { return fallbackElement; @@ -144,7 +133,7 @@ export interface AppRouterProps { errorElement: ReactElement; onLoadPublicData?: OnLoadPublicDataFunction; onLoadProtectedData?: OnLoadProtectedDataFunction; - onCompleteRegistration?: OnCompleteRegistrationFunction; + onCompleteRegistrations?: OnCompleteRegistrationsFunction; waitForMsw: boolean; routerProvidersProps?: RouterProviderProps; } @@ -155,7 +144,7 @@ export function AppRouter(props: AppRouterProps) { errorElement, onLoadPublicData, onLoadProtectedData, - onCompleteRegistration, + onCompleteRegistrations, waitForMsw, routerProvidersProps = {} } = props; @@ -183,7 +172,7 @@ export function AppRouter(props: AppRouterProps) { fallbackElement={fallbackElement} onLoadPublicData={onLoadPublicData} onLoadProtectedData={onLoadProtectedData} - onCompleteRegistration={onCompleteRegistration} + onCompleteRegistrations={onCompleteRegistrations} waitForMsw={waitForMsw} areModulesRegistered={areModulesRegistered} areModulesReady={areModulesReady} @@ -193,7 +182,7 @@ export function AppRouter(props: AppRouterProps) { children: routes } ]); - }, [areModulesRegistered, areModulesReady, routes, onLoadPublicData, onLoadProtectedData, onCompleteRegistration, waitForMsw]); + }, [areModulesRegistered, areModulesReady, routes, onLoadPublicData, onLoadProtectedData, onCompleteRegistrations, waitForMsw]); return ( diff --git a/packages/firefly/tests/AppRouter.test.tsx b/packages/firefly/tests/AppRouter.test.tsx new file mode 100644 index 000000000..0a04c7e6b --- /dev/null +++ b/packages/firefly/tests/AppRouter.test.tsx @@ -0,0 +1,399 @@ +import { __resetMswStatus, setMswAsStarted } from "@squide/msw"; +import { Runtime, RuntimeContext, __resetLocalModuleRegistrations, registerLocalModules } from "@squide/react-router"; +import { completeModuleRegistrations } from "@squide/webpack-module-federation"; +import { render, screen } from "@testing-library/react"; +import type { ReactElement, ReactNode } from "react"; +import { AppRouter } from "../src/AppRouter.tsx"; + +// Not all permutations are testedbecause there are simply too many. The code path that we deem the most important to test +// has been handled and additional tests will be added once bugs are discovered. + +function Loading() { + return ( +
Loading...
+ ); +} + +function ErrorBoundary() { + return ( +
An error occured!
+ ); +} + +function renderWithRuntime(runtime: Runtime, ui: ReactElement) { + return render(ui, { + wrapper: ({ children }: { children?: ReactNode }) => { + return ( + + {children} + + ); + } + }); +} + +beforeEach(() => { + __resetLocalModuleRegistrations(); + __resetMswStatus(); +}); + +test("when no data handlers are provided, msw is disabled, there's no deferred registrations, and modules are not registered yet, render the fallback", async () => { + const runtime = new Runtime(); + + // Must add at least a route otherwise useRouteMatchProtected will throw. + runtime.registerRoute({ + path: "*", + element:
A wildcard route
+ }, { + hoist: true + }); + + // Never resolving Promise object. + registerLocalModules([() => new Promise(() => {})], runtime); + + renderWithRuntime(runtime, } + errorElement={} + waitForMsw={false} + />); + + expect(await screen.findByRole("loading")).toBeInTheDocument(); +}); + +test("when no data handlers are provided, msw is disabled, there's no deferred registrations, and modules are registered, render the router", async () => { + const runtime = new Runtime(); + + // Must add at least a route otherwise useRouteMatchProtected will throw. + runtime.registerRoute({ + path: "*", + element:
A wildcard route
+ }, { + hoist: true + }); + + await registerLocalModules([() => { + runtime.registerRoute({ + index: true, + element:
A route
+ }, { + hoist: true + }); + }], runtime); + + renderWithRuntime(runtime, } + errorElement={} + waitForMsw={false} + />); + + expect(await screen.findByRole("module-route")).toBeInTheDocument(); +}); + +test("when no data handlers are provided, msw is disabled, modules are registered but there's uncompleted deferred registrations, render the fallback", async () => { + const runtime = new Runtime(); + + // Must add at least a route otherwise useRouteMatchProtected will throw. + runtime.registerRoute({ + path: "*", + element:
A wildcard route
+ }, { + hoist: true + }); + + await registerLocalModules([() => () => Promise.resolve()], runtime); + + renderWithRuntime(runtime, } + errorElement={} + waitForMsw={false} + />); + + expect(await screen.findByRole("loading")).toBeInTheDocument(); +}); + +test("when a onLoadPublicData handler is provided and the public data is not loaded, render the fallback", async () => { + const runtime = new Runtime(); + + // Must add at least a route otherwise useRouteMatchProtected will throw. + runtime.registerRoute({ + $visibility: "public", + path: "*", + element:
A wildcard route
+ }, { + hoist: true + }); + + await registerLocalModules([() => {}], runtime); + + renderWithRuntime(runtime, } + errorElement={} + // Never resolving Promise object. + onLoadPublicData={() => new Promise(() => {})} + waitForMsw={false} + />); + + expect(await screen.findByRole("loading")).toBeInTheDocument(); +}); + +test("when a onLoadPublicData handler is provided and the public data is loaded, render the router", async () => { + const runtime = new Runtime(); + + runtime.registerRoute({ + $visibility: "public", + index: true, + element:
A route
+ }, { + hoist: true + }); + + await registerLocalModules([() => {}], runtime); + + renderWithRuntime(runtime, } + errorElement={} + onLoadPublicData={() => Promise.resolve()} + waitForMsw={false} + />); + + expect(await screen.findByRole("route")).toBeInTheDocument(); +}); + +test("when a onLoadProtectedData handler is provided and the protected data is not loaded, render the fallback", async () => { + const runtime = new Runtime(); + + // Must add at least a route otherwise React Router complains the router has no routes. + runtime.registerRoute({ + path: "*", + element:
A wildcard route
+ }, { + hoist: true + }); + + await registerLocalModules([() => {}], runtime); + + renderWithRuntime(runtime, } + errorElement={} + // Never resolving Promise object. + onLoadProtectedData={() => new Promise(() => {})} + waitForMsw={false} + />); + + expect(await screen.findByRole("loading")).toBeInTheDocument(); +}); + +test("when a onLoadProtectedData handler is provided and the protected data is loaded, render the router", async () => { + const runtime = new Runtime(); + + runtime.registerRoute({ + index: true, + element:
A route
+ }, { + hoist: true + }); + + await registerLocalModules([() => {}], runtime); + + renderWithRuntime(runtime, } + errorElement={} + onLoadProtectedData={() => Promise.resolve()} + waitForMsw={false} + />); + + expect(await screen.findByRole("route")).toBeInTheDocument(); +}); + +test("when msw is enabled and msw is not started, render the fallback", async () => { + const runtime = new Runtime(); + + // Must add at least a route otherwise React Router complains the router has no routes. + runtime.registerRoute({ + path: "*", + element:
A wildcard route
+ }, { + hoist: true + }); + + await registerLocalModules([() => {}], runtime); + + renderWithRuntime(runtime, } + errorElement={} + waitForMsw={true} + />); + + expect(await screen.findByRole("loading")).toBeInTheDocument(); +}); + +test("when msw is enabled and msw is started, render the router", async () => { + const runtime = new Runtime(); + + runtime.registerRoute({ + index: true, + element:
A route
+ }, { + hoist: true + }); + + await registerLocalModules([() => {}], runtime); + + setMswAsStarted(); + + renderWithRuntime(runtime, } + errorElement={} + waitForMsw={true} + />); + + expect(await screen.findByRole("route")).toBeInTheDocument(); +}); + +test("when a onCompleteRegistrations handler is provided and there's no deferred registrations, render the router", async () => { + const runtime = new Runtime(); + + runtime.registerRoute({ + index: true, + element:
A route
+ }, { + hoist: true + }); + + await registerLocalModules([() => {}], runtime); + + renderWithRuntime(runtime, } + errorElement={} + onCompleteRegistrations={() => Promise.resolve()} + waitForMsw={false} + />); + + expect(await screen.findByRole("route")).toBeInTheDocument(); +}); + +test("when a onCompleteRegistrations handler is provided and the deferred registrations are not completed, render the fallback", async () => { + const runtime = new Runtime(); + + // Must add at least a route otherwise React Router complains the router has no routes. + runtime.registerRoute({ + path: "*", + element:
A wildcard route
+ }, { + hoist: true + }); + + // Never resolving Promise object. + await registerLocalModules([() => () => new Promise(() => {})], runtime); + + renderWithRuntime(runtime, } + errorElement={} + onCompleteRegistrations={() => completeModuleRegistrations(runtime, {})} + waitForMsw={false} + />); + + expect(await screen.findByRole("loading")).toBeInTheDocument(); +}); + +test("when a onCompleteRegistrations handler is provided and the deferred registrations are completed, render the router", async () => { + const runtime = new Runtime(); + + // Must add at least a route otherwise React Router complains the router has no routes. + runtime.registerRoute({ + path: "*", + element:
A wildcard route
+ }, { + hoist: true + }); + + await registerLocalModules([() => { + return () => { + runtime.registerRoute({ + index: true, + element:
A deferred route
+ }, { + hoist: true + }); + }; + }], runtime); + + function handleCompleteRegistration() { + return completeModuleRegistrations(runtime, {}); + } + + const { rerender } = renderWithRuntime(runtime, } + errorElement={} + onCompleteRegistrations={handleCompleteRegistration} + waitForMsw={false} + />); + + rerender(} + errorElement={} + onCompleteRegistrations={handleCompleteRegistration} + waitForMsw={false} + />); + + expect(await screen.findByRole("deferred-route")).toBeInTheDocument(); +}); + +test("when an error occurs while loading the public data, show the error element", async () => { + // An error log is expected because it will hit the ErrorBoundary, see: https://github.com/facebook/react/issues/11098. + const spy = jest.spyOn(console, "error"); + spy.mockImplementation(() => {}); + + const runtime = new Runtime(); + + // Must add at least a route otherwise useRouteMatchProtected will throw. + runtime.registerRoute({ + $visibility: "public", + path: "*", + element:
A wildcard route
+ }, { + hoist: true + }); + + await registerLocalModules([() => {}], runtime); + + renderWithRuntime(runtime, } + errorElement={} + onLoadPublicData={() => Promise.reject("Dummy error")} + waitForMsw={false} + />); + + expect(await screen.findByRole("error")).toBeInTheDocument(); + + spy.mockRestore(); +}); + +test("when an error occurs while loading the protected data, show the error element", async () => { + // An error log is expected because it will hit the ErrorBoundary, see: https://github.com/facebook/react/issues/11098. + const spy = jest.spyOn(console, "error"); + spy.mockImplementation(() => {}); + + const runtime = new Runtime(); + + // Must add at least a route otherwise useRouteMatchProtected will throw. + runtime.registerRoute({ + path: "*", + element:
A wildcard route
+ }, { + hoist: true + }); + + await registerLocalModules([() => {}], runtime); + + renderWithRuntime(runtime, } + errorElement={} + onLoadProtectedData={() => Promise.reject("Dummy error")} + waitForMsw={false} + />); + + expect(await screen.findByRole("error")).toBeInTheDocument(); + + spy.mockRestore(); +}); diff --git a/packages/msw/src/setMswAsStarted.ts b/packages/msw/src/setMswAsStarted.ts index 50ebe011a..65509611f 100644 --- a/packages/msw/src/setMswAsStarted.ts +++ b/packages/msw/src/setMswAsStarted.ts @@ -7,3 +7,8 @@ export function setMswAsStarted() { export function isMswStarted() { return isStarted; } + +// Strictly for Jest tests, this is NOT ideal. +export function __resetMswStatus() { + isStarted = false; +} diff --git a/packages/react-router/src/index.ts b/packages/react-router/src/index.ts index e88150f37..3c28a3bb6 100644 --- a/packages/react-router/src/index.ts +++ b/packages/react-router/src/index.ts @@ -4,6 +4,7 @@ export * from "./navigationItemRegistry.ts"; export * from "./outlets.ts"; export * from "./routeRegistry.ts"; export * from "./runtime.ts"; +export * from "./useIsRouteMatchProtected.ts"; export * from "./useNavigationItems.ts"; export * from "./useRenderedNavigationItems.tsx"; export * from "./useRouteMatch.ts"; diff --git a/packages/react-router/src/useIsRouteMatchProtected.ts b/packages/react-router/src/useIsRouteMatchProtected.ts new file mode 100644 index 000000000..d022d9115 --- /dev/null +++ b/packages/react-router/src/useIsRouteMatchProtected.ts @@ -0,0 +1,19 @@ +import { useRouteMatch } from "./useRouteMatch.ts"; + +export interface UseIsRouteMatchProtectedOptions { + throwWhenThereIsNoMatch?: boolean; +} + +export function useIsRouteMatchProtected(locationArg: Partial, { throwWhenThereIsNoMatch = true } = {}) { + const activeRoute = useRouteMatch(locationArg); + + if (!activeRoute) { + if (throwWhenThereIsNoMatch) { + throw new Error(`[squide] There's no matching route for the location: "${locationArg.pathname}". Did you add routes to React Router without using the runtime.registerRoute() function?`); + } + + return false; + } + + return activeRoute.$visibility === "protected"; +} diff --git a/packages/react-router/src/useRouteMatch.ts b/packages/react-router/src/useRouteMatch.ts index 609610b72..70ed98e56 100644 --- a/packages/react-router/src/useRouteMatch.ts +++ b/packages/react-router/src/useRouteMatch.ts @@ -14,13 +14,3 @@ export function useRouteMatch(locationArg: Partial) { return undefined; } - -export function useIsRouteMatchProtected(locationArg: Partial) { - const activeRoute = useRouteMatch(locationArg); - - if (!activeRoute) { - throw new Error(`[squide] There's no matching route for the location: "${locationArg.pathname}". Did you add routes to React Router without using the runtime.registerRoute() function?`); - } - - return activeRoute.$visibility === "protected"; -} diff --git a/packages/webpack-module-federation/src/registerRemoteModules.ts b/packages/webpack-module-federation/src/registerRemoteModules.ts index bf9541183..1928dbfb9 100644 --- a/packages/webpack-module-federation/src/registerRemoteModules.ts +++ b/packages/webpack-module-federation/src/registerRemoteModules.ts @@ -183,8 +183,3 @@ export function completeRemoteModuleRegistrations { - const intervalId = setInterval(() => { - if (areModulesReady(getLocalModuleRegistrationStatus(), getRemoteModuleRegistrationStatus())) { - // Must clear interval before calling "_completeRegistration" in case there's an error. - clearInterval(intervalId); - - runtime._completeRegistration(); - - setAreModulesReady(true); - } - }, interval); - - return () => { - if (intervalId) { - clearInterval(intervalId); - } - }; + if (!value) { + const intervalId = setInterval(() => { + if (areModulesReady(getLocalModuleRegistrationStatus(), getRemoteModuleRegistrationStatus())) { + // Must clear interval before calling "_completeRegistration" in case there's an error. + clearInterval(intervalId); + + runtime._completeRegistration(); + + setAreModulesReady(true); + } + }, interval); + + return () => { + if (intervalId) { + clearInterval(intervalId); + } + }; + } }, []); return value; diff --git a/packages/webpack-module-federation/src/useAreModulesRegistered.ts b/packages/webpack-module-federation/src/useAreModulesRegistered.ts index 69d10f6f3..414d4964d 100644 --- a/packages/webpack-module-federation/src/useAreModulesRegistered.ts +++ b/packages/webpack-module-federation/src/useAreModulesRegistered.ts @@ -21,23 +21,25 @@ export function areModulesRegistered(localModuleRegistrationStatus: ModuleRegist export function useAreModulesRegistered({ interval = 10 }: UseAreModulesRegisteredOptions = {}) { // Using a state hook to force a rerender once registered. - const [value, setAreModulesRegistered] = useState(false); + const [value, setAreModulesRegistered] = useState(areModulesRegistered(getLocalModuleRegistrationStatus(), getRemoteModuleRegistrationStatus())); // Perform a reload once the modules are registered. useEffect(() => { - const intervalId = setInterval(() => { - if (areModulesRegistered(getLocalModuleRegistrationStatus(), getRemoteModuleRegistrationStatus())) { - clearInterval(intervalId); - - setAreModulesRegistered(true); - } - }, interval); - - return () => { - if (intervalId) { - clearInterval(intervalId); - } - }; + if (!value) { + const intervalId = setInterval(() => { + if (areModulesRegistered(getLocalModuleRegistrationStatus(), getRemoteModuleRegistrationStatus())) { + clearInterval(intervalId); + + setAreModulesRegistered(true); + } + }, interval); + + return () => { + if (intervalId) { + clearInterval(intervalId); + } + }; + } }, []); return value; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23b4ead9f..155833883 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -152,6 +152,9 @@ importers: '@swc/jest': specifier: 0.2.29 version: 0.2.29(@swc/core@1.3.95) + '@testing-library/jest-dom': + specifier: 6.1.4 + version: 6.1.4(@types/jest@29.5.6)(jest@29.7.0) '@testing-library/react': specifier: 14.0.0 version: 14.0.0(react-dom@18.2.0)(react@18.2.0) @@ -1298,6 +1301,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /@adobe/css-tools@4.3.1: + resolution: {integrity: sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==} + dev: true + /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} @@ -5734,6 +5741,36 @@ packages: pretty-format: 27.5.1 dev: true + /@testing-library/jest-dom@6.1.4(@types/jest@29.5.6)(jest@29.7.0): + resolution: {integrity: sha512-wpoYrCYwSZ5/AxcrjLxJmCU6I5QAJXslEeSiMQqaWmP2Kzpd1LvF/qxmAIW2qposULGWq2gw30GgVNFLSc2Jnw==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + peerDependencies: + '@jest/globals': '>= 28' + '@types/jest': '>= 28' + jest: '>= 28' + vitest: '>= 0.32' + peerDependenciesMeta: + '@jest/globals': + optional: true + '@types/jest': + optional: true + jest: + optional: true + vitest: + optional: true + dependencies: + '@adobe/css-tools': 4.3.1 + '@babel/runtime': 7.23.2 + '@types/jest': 29.5.6 + aria-query: 5.3.0 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.5.16 + jest: 29.7.0(@types/node@20.8.9)(ts-node@10.9.1) + lodash: 4.17.21 + redent: 3.0.0 + dev: true + /@testing-library/react@14.0.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==} engines: {node: '>=14'} @@ -7928,6 +7965,14 @@ packages: supports-color: 5.5.0 dev: true + /chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -8618,6 +8663,10 @@ packages: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} + /css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + dev: true + /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} diff --git a/samples/endpoints/shell/src/AppRouter.tsx b/samples/endpoints/shell/src/AppRouter.tsx index 7bea755f9..d3217a764 100644 --- a/samples/endpoints/shell/src/AppRouter.tsx +++ b/samples/endpoints/shell/src/AppRouter.tsx @@ -18,12 +18,11 @@ export interface AppRouterProps { function fetchPublicData( setFeatureFlags: (featureFlags: FeatureFlags) => void, - logger: Logger, - signal: AbortSignal + logger: Logger ) { // React Query "queryClient.fetchQuery" could be used instead of using axios directly. // https://tanstack.com/query/latest/docs/react/reference/QueryClient#queryclientfetchquery - const featureFlagsPromise = axios.get("/api/feature-flags", { signal }) + const featureFlagsPromise = axios.get("/api/feature-flags") .then(({ data }) => { const featureFlags: FeatureFlags = { featureA: data.featureA, @@ -42,12 +41,11 @@ function fetchPublicData( function fetchProtectedData( setSession: (session: Session) => void, setSubscription: (subscription: Subscription) => void, - logger: Logger, - signal: AbortSignal + logger: Logger ) { // React Query "queryClient.fetchQuery" could be used instead of using axios directly. // https://tanstack.com/query/latest/docs/react/reference/QueryClient#queryclientfetchquery - const sessionPromise = axios.get("/api/session", { signal }) + const sessionPromise = axios.get("/api/session") .then(({ data }) => { const session: Session = { user: { @@ -63,7 +61,7 @@ function fetchProtectedData( // React Query "queryClient.fetchQuery" could be used instead of using axios directly. // https://tanstack.com/query/latest/docs/react/reference/QueryClient#queryclientfetchquery - const subscriptionPromise = axios.get("/api/subscription", { signal }) + const subscriptionPromise = axios.get("/api/subscription") .then(({ data }) => { const subscription: Subscription = { company: data.company, @@ -102,19 +100,19 @@ export function AppRouter({ waitForMsw, sessionManager, telemetryService }: AppR const logger = useLogger(); const runtime = useRuntime(); - const handleLoadPublicData = useCallback((signal: AbortSignal) => { - return fetchPublicData(setFeatureFlags, logger, signal); + const handleLoadPublicData = useCallback(() => { + return fetchPublicData(setFeatureFlags, logger); }, [logger]); - const handleLoadProtectedData = useCallback((signal: AbortSignal) => { + const handleLoadProtectedData = useCallback(() => { const setSession = (session: Session) => { sessionManager.setSession(session); }; - return fetchProtectedData(setSession, setSubscription, logger, signal); + return fetchProtectedData(setSession, setSubscription, logger); }, [logger, sessionManager]); - const handleCompleteRegistration = useCallback(() => { + const handleCompleteRegistrations = useCallback(() => { return completeModuleRegistrations(runtime, { featureFlags }); @@ -130,7 +128,7 @@ export function AppRouter({ waitForMsw, sessionManager, telemetryService }: AppR waitForMsw={waitForMsw} onLoadPublicData={handleLoadPublicData} onLoadProtectedData={handleLoadProtectedData} - onCompleteRegistration={handleCompleteRegistration} + onCompleteRegistrations={handleCompleteRegistrations} />