From a43df3a818c7f9cd3994772cd7ac15a81de4cfa8 Mon Sep 17 00:00:00 2001 From: Jay Giang Date: Sun, 23 Feb 2025 14:13:29 -0800 Subject: [PATCH 01/27] fix: Add debug log in service worker installation method --- packages/radfish/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/radfish/index.js b/packages/radfish/index.js index 62d8fc21..dc71767e 100644 --- a/packages/radfish/index.js +++ b/packages/radfish/index.js @@ -83,6 +83,7 @@ export class Application { async _installServiceWorker(handlers, url) { if (!url) return null; console.info("Installing service worker"); + console.log("something"); const worker = setupWorker(...((await handlers)?.default || [])); const onUnhandledRequest = "bypass"; From 6881ccfaf177a2f32a9bbf6f238e48050109ab5b Mon Sep 17 00:00:00 2001 From: "Jay Giang (aider)" Date: Sun, 23 Feb 2025 14:13:31 -0800 Subject: [PATCH 02/27] feat: Add network status handler and event listeners to Application --- packages/radfish/index.js | 35 +++++++++++++++---- .../useOfflineStatus/useOfflineStatus.js | 16 +++++---- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/packages/radfish/index.js b/packages/radfish/index.js index dc71767e..30793a6b 100644 --- a/packages/radfish/index.js +++ b/packages/radfish/index.js @@ -9,12 +9,21 @@ export class Application { this.serviceWorker = null; this.isOnline = navigator.onLine; this._options = options; + this._networkHandler = options.network?.setIsOnline; this._registerEventListeners(); this._dispatch("init"); } + addEventListener(event, callback) { + return this.emitter.addEventListener(event, callback); + } + + removeEventListener(event, callback) { + return this.emitter.removeEventListener(event, callback); + } + get storage() { if (!this._options.storage) { return null; @@ -67,15 +76,29 @@ export class Application { this._dispatch("ready", { worker }); }); - const handleOnline = (event) => { - this.isOnline = true; - this._dispatch("online", { event }); + const handleOnline = async (event) => { + if (this._networkHandler) { + await this._networkHandler(navigator.connection, (status) => { + this.isOnline = status; + this._dispatch("online", { event }); + }); + } else { + this.isOnline = true; + this._dispatch("online", { event }); + } }; window.addEventListener("online", handleOnline, true); - const handleOffline = (event) => { - this.isOnline = false; - this._dispatch("offline", { event }); + const handleOffline = async (event) => { + if (this._networkHandler) { + await this._networkHandler(navigator.connection, (status) => { + this.isOnline = status; + this._dispatch("offline", { event }); + }); + } else { + this.isOnline = false; + this._dispatch("offline", { event }); + } }; window.addEventListener("offline", handleOffline, true); } diff --git a/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.js b/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.js index 2837fbc1..471221a8 100644 --- a/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.js +++ b/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.js @@ -1,23 +1,25 @@ import { useState, useEffect } from "react"; +import { useApplication } from "../useApplication"; export const useOfflineStatus = () => { - const [isOffline, setIsOffline] = useState(!navigator.onLine); + const application = useApplication(); + const [isOffline, setIsOffline] = useState(!application.isOnline); const updateOnlineStatus = () => { - setIsOffline(!navigator.onLine); + setIsOffline(!application.isOnline); }; useEffect(() => { updateOnlineStatus(); - window.addEventListener("online", updateOnlineStatus); - window.addEventListener("offline", updateOnlineStatus); + application.addEventListener("online", updateOnlineStatus); + application.addEventListener("offline", updateOnlineStatus); return () => { - window.removeEventListener("online", updateOnlineStatus); - window.removeEventListener("offline", updateOnlineStatus); + application.removeEventListener("online", updateOnlineStatus); + application.removeEventListener("offline", updateOnlineStatus); }; - }, []); + }, [application]); return { isOffline }; }; From 4a280fa67988107de47531026012396032fd081c Mon Sep 17 00:00:00 2001 From: Jay Giang Date: Sun, 23 Feb 2025 14:18:24 -0800 Subject: [PATCH 03/27] feat: Add useOfflineStatus hook import to Home component --- examples/react-query/src/pages/Home.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/react-query/src/pages/Home.jsx b/examples/react-query/src/pages/Home.jsx index 5568ef39..7d476000 100644 --- a/examples/react-query/src/pages/Home.jsx +++ b/examples/react-query/src/pages/Home.jsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Button, Alert, Label, Link, TextInput } from "@trussworks/react-uswds"; -import { dispatchToast } from "@nmfs-radfish/react-radfish"; +import { dispatchToast, useOfflineStatus } from "@nmfs-radfish/react-radfish"; import { Table } from "@nmfs-radfish/react-radfish"; const HomePage = () => { From 0a8b2293849737770201162bc0fb985eeb78d63e Mon Sep 17 00:00:00 2001 From: "Jay Giang (aider)" Date: Sun, 23 Feb 2025 14:18:26 -0800 Subject: [PATCH 04/27] feat: Add offline status warning to Home page using useOfflineStatus hook --- examples/react-query/src/pages/Home.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/react-query/src/pages/Home.jsx b/examples/react-query/src/pages/Home.jsx index 7d476000..a272dbeb 100644 --- a/examples/react-query/src/pages/Home.jsx +++ b/examples/react-query/src/pages/Home.jsx @@ -5,6 +5,7 @@ import { dispatchToast, useOfflineStatus } from "@nmfs-radfish/react-radfish"; import { Table } from "@nmfs-radfish/react-radfish"; const HomePage = () => { + const { isOffline } = useOfflineStatus(); const queryClient = useQueryClient(); /** @@ -44,6 +45,11 @@ const HomePage = () => { return ( <>

React Query Example

+ {isOffline && ( + + You are currently offline. Some features may be limited. + + )}
-

Network Status: {isOffline ? "Offline ❌" : "Online ✅"}

+ + {/* Top row - Network Status */} + + + + +

Network Status

+
+ +
+ Current Status: {getNetworkStatusTag()} +
+ + {isFlapping && flappingDetails && ( + + Network is flapping! Changed {flappingDetails.flappingCount} times in the last{" "} + {Math.round(flappingDetails.timeSinceLastChange / 1000)} seconds. + + )} + +

+ The application automatically detects unstable network connections where the status + rapidly fluctuates between online and offline. +

+
+
+
+
+ + {/* Bottom row - Resilient Network Features and Network Flapping */} + + + + +

Resilient Network Features

+
+ +

Test network resilience features with retry logic and exponential backoff:

+
+
+ +
+
+
+
+ + + + +

Network Flapping

+
+ +

+ Simulate network flapping to test the application's ability to detect unstable 190 + connections. Flapping threshold is set to {app._networkFlappingThreshold}. +

+
+
+ +
+ + + +
); }; diff --git a/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.spec.js b/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.spec.js index 44d3d498..4dea3ee2 100644 --- a/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.spec.js +++ b/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.spec.js @@ -1,11 +1,31 @@ import { renderHook, act } from "@testing-library/react"; import { useOfflineStatus } from "./useOfflineStatus"; +import { useApplication } from "../../Application"; -describe("useToast", () => { - it("should trigger when naviagator online switches", async () => { - let dispatchEventSpy = vi.spyOn(window, "dispatchEvent"); - const onLineSpy = vi.spyOn(window.navigator, "onLine", "get"); +// Mock the useApplication hook +vi.mock("../../Application", () => ({ + useApplication: vi.fn() +})); +describe("useOfflineStatus", () => { + // Setup mock application + const mockApplication = { + isOnline: true, + _networkTimeout: 5000, + fetch: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn() + }; + + beforeEach(() => { + vi.resetAllMocks(); + mockApplication.isOnline = true; + mockApplication.fetch.mockReset(); + useApplication.mockReturnValue(mockApplication); + }); + + it("should trigger when navigator online switches", async () => { + const onLineSpy = vi.spyOn(window.navigator, "onLine", "get"); const { result, rerender } = renderHook(() => useOfflineStatus()); onLineSpy.mockReturnValue(true); @@ -20,4 +40,113 @@ describe("useToast", () => { expect(result.current.isOffline).toBe(true); }); + + it("should add event listeners for network events", () => { + renderHook(() => useOfflineStatus()); + + expect(mockApplication.addEventListener).toHaveBeenCalledWith( + "online", + expect.any(Function) + ); + expect(mockApplication.addEventListener).toHaveBeenCalledWith( + "offline", + expect.any(Function) + ); + expect(mockApplication.addEventListener).toHaveBeenCalledWith( + "networkFlapping", + expect.any(Function) + ); + }); + + it("should cleanup event listeners on unmount", () => { + const { unmount } = renderHook(() => useOfflineStatus()); + unmount(); + + expect(mockApplication.removeEventListener).toHaveBeenCalledWith( + "online", + expect.any(Function) + ); + expect(mockApplication.removeEventListener).toHaveBeenCalledWith( + "offline", + expect.any(Function) + ); + expect(mockApplication.removeEventListener).toHaveBeenCalledWith( + "networkFlapping", + expect.any(Function) + ); + }); + + it("should handle network flapping events", async () => { + const { result } = renderHook(() => useOfflineStatus()); + + // Initially not flapping + expect(result.current.isFlapping).toBe(false); + + // Simulate flapping event + const flappingHandler = mockApplication.addEventListener.mock.calls.find( + call => call[0] === "networkFlapping" + )[1]; + + await act(async () => { + flappingHandler({ detail: { flappingCount: 3, timeSinceLastChange: 1000 } }); + }); + + // Now should be flapping + expect(result.current.isFlapping).toBe(true); + expect(result.current.flappingDetails).toEqual({ + flappingCount: 3, + timeSinceLastChange: 1000 + }); + }); + + it("should correctly check endpoint connectivity", async () => { + const { result } = renderHook(() => useOfflineStatus()); + + // Test successful fetch + mockApplication.fetch.mockResolvedValueOnce({}); + + let isReachable = await result.current.checkEndpoint("https://example.com"); + expect(isReachable).toBe(true); + expect(mockApplication.fetch).toHaveBeenCalledWith( + "https://example.com", + { method: "HEAD" } + ); + + // Test failed fetch + mockApplication.fetch.mockRejectedValueOnce(new Error("Network error")); + + isReachable = await result.current.checkEndpoint("https://example.com"); + expect(isReachable).toBe(false); + }); + + it("should check multiple endpoints correctly", async () => { + const { result } = renderHook(() => useOfflineStatus()); + + // Setup different responses for different endpoints + mockApplication.fetch + .mockImplementationOnce((url) => { + if (url === "https://working.example.com") { + return Promise.resolve({}); + } + return Promise.reject(new Error("Failed")); + }) + .mockImplementationOnce((url) => { + if (url === "https://api.example.com") { + return Promise.resolve({}); + } + return Promise.reject(new Error("Failed")); + }); + + const endpoints = [ + "https://working.example.com", + "https://down.example.com" + ]; + + const results = await result.current.checkMultipleEndpoints(endpoints); + + expect(results).toEqual({ + "https://working.example.com": true, + "https://down.example.com": false + }); + }); }); From 7d0c836406c2a64a3b483b7b9c068cc4e4de46ac Mon Sep 17 00:00:00 2001 From: Jay Giang Date: Tue, 4 Mar 2025 21:26:35 -0800 Subject: [PATCH 19/27] Update readme for network status --- examples/network-status/README.md | 147 +++++++++++++++++++++++++----- 1 file changed, 126 insertions(+), 21 deletions(-) diff --git a/examples/network-status/README.md b/examples/network-status/README.md index e67c01ae..bee9ed9f 100644 --- a/examples/network-status/README.md +++ b/examples/network-status/README.md @@ -1,41 +1,146 @@ -# Network Status Example +# Enhanced Network Status Example -This example demonstrates how to detect a user's network connectivity. If the user is offline, the built-in status indicator will display `Offline ❌`. If online, it will show `Online ✅`. +This example demonstrates advanced network status handling with sophisticated features for modern web applications that need to be resilient to network issues. -The `useOfflineStatus` hook provides an `isOffline` utility that detects the user's network connectivity. It uses the `navigator` browser API. Refer to the [MDN Navigator Docs](https://developer.mozilla.org/en-US/docs/Web/API/Navigator) for limitations. +## Key Features + +- **Real-time Network Status Detection**: Monitors online/offline state using browser APIs and custom network checks +- **Network Flapping Detection**: Identifies unstable connections that rapidly switch between online and offline states +- **Resilient Fetching**: Implements automatic retries with exponential backoff for failed network requests +- **Request Timeout Control**: Configurable timeout settings to prevent hanging requests +- **Fallback URLs**: Automatic redirection to alternative endpoints when primary endpoints fail +- **Custom Network Testing Tools**: Tools to simulate various network conditions for testing Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples. ## Preview -This example will render as shown in this screenshot: +This example renders as shown in this screenshot: -![Network Status](./src/assets/network-status.png) +![Enhanced Network Status](./src/assets/network-status.png) -## Steps +## Implementation -### 1. Import Required Dependencies +### 1. Configuring Enhanced Network Features -Import the following libraries in the `App.jsx` file: +Set up the Application with advanced network handling options: ```jsx -import React, { useEffect } from "react"; -import { useOfflineStatus } from "@nmfs-radfish/react-radfish"; -import { Alert } from "@trussworks/react-uswds"; +const app = new Application({ + network: { + // Custom timeout in milliseconds (default is 30000) + timeout: 5000, + + // Fallback URLs to use when primary endpoints fail + fallbackUrls: { + "https://nonexistent-endpoint.example.com": "https://jsonplaceholder.typicode.com/users" + }, + + // Threshold for detecting network flapping (default is 3) + flappingThreshold: 2, + + // Optional custom network status handler + setIsOnline: async (networkInfo, callback) => { + // Custom logic to determine network status + try { + const response = await fetch("https://api.github.com/users", { + method: "HEAD", + signal: AbortSignal.timeout(3000) + }); + callback(response.ok); + } catch (error) { + callback(false); + } + } + } +}); ``` -### 2. Use `useOfflineStatus` to Access Network State +### 2. Using Enhanced Network Status Features -Within the `HomePage` component, use `useOfflineStatus` to retrieve the `isOffline` property, which indicates whether the application is currently offline: +Access the enhanced network status features through hooks: ```jsx const HomePage = () => { -const { isOffline } = useOfflineStatus(); // Retrieve the isOffline state - -return ( -
-

Network Status Example

-

Network Status: {isOffline ? "Offline ❌" : "Online ✅"}

-
-); + const { isOffline, isFlapping, flappingDetails } = useOfflineStatus(); + const app = useApplication(); + + // Network status tag with flapping detection + const getNetworkStatusTag = () => { + if (isFlapping) { + return Unstable; + } else if (isOffline) { + return Offline; + } else { + return Online; + } + }; + + // Using fetch with retry and fallback capabilities + const fetchWithRetry = async () => { + try { + const response = await app.fetchWithRetry( + "https://nonexistent-endpoint.example.com", + {}, + { + retries: 2, + retryDelay: 1000, + exponentialBackoff: true, + } + ); + const data = await response.json(); + // Handle successful response + } catch (error) { + // Handle failure after all retries + } + }; + + return ( +
+
Current Status: {getNetworkStatusTag()}
+ + {isFlapping && flappingDetails && ( + + Network is flapping! Changed {flappingDetails.flappingCount} times + in the last {Math.round(flappingDetails.timeSinceLastChange/1000)} seconds. + + )} + + +
+ ); +}; ``` + +## Testing with Browser DevTools + +To fully test the network resilience features, you can use browser DevTools: + +### Viewing Console Logs + +1. Open DevTools in your browser: + - **Chrome/Edge**: Press F12 or right-click and select "Inspect" + - **Firefox**: Press F12 or right-click and select "Inspect Element" + - **Safari**: Enable "Developer Tools" in preferences, then press Option+Command+I + +2. Go to the "Console" tab to view logs: + - Network status changes appear as color-coded logs + - Network flapping events are highlighted with yellow backgrounds + - Retry attempts are logged with blue backgrounds + +### Simulating Offline/Online States + +1. In DevTools, go to the "Network" tab +2. Look for the "Online" dropdown (may appear as "No throttling" in some browsers) +3. Select "Offline" to simulate a disconnected state +4. Return to "Online" or "No throttling" to restore connectivity + +### Testing Network Flapping + +Instead of manually toggling network state, you can use the "Simulate Network Flapping" button in the example interface to trigger a rapid sequence of online/offline state changes. + +### Testing Fetch with Retry and Fallbacks + +1. Click the "Test Fetch with Retry" button while watching the Console +2. You'll see logs of retry attempts and eventual success or failure +3. If using a fallback URL, you'll see the fallback request after the primary URL fails From cf3444cd0ae262d45e8c75a1885cf5fde513d2d1 Mon Sep 17 00:00:00 2001 From: Jay Giang Date: Thu, 6 Mar 2025 23:30:40 -0800 Subject: [PATCH 20/27] update: Remove network flapping --- examples/network-status/README.md | 39 ++++-------- examples/network-status/src/pages/Home.jsx | 59 ++----------------- packages/radfish/Application.spec.js | 27 ++------- packages/radfish/index.js | 31 ++-------- packages/react-radfish/Application/index.jsx | 28 ++------- .../useOfflineStatus/useOfflineStatus.js | 19 +----- .../useOfflineStatus/useOfflineStatus.spec.js | 31 ---------- 7 files changed, 32 insertions(+), 202 deletions(-) diff --git a/examples/network-status/README.md b/examples/network-status/README.md index bee9ed9f..1614864e 100644 --- a/examples/network-status/README.md +++ b/examples/network-status/README.md @@ -1,11 +1,10 @@ -# Enhanced Network Status Example +# Network Status Example -This example demonstrates advanced network status handling with sophisticated features for modern web applications that need to be resilient to network issues. +This example demonstrates network status handling with sophisticated features for modern web applications that need to be resilient to network issues. ## Key Features - **Real-time Network Status Detection**: Monitors online/offline state using browser APIs and custom network checks -- **Network Flapping Detection**: Identifies unstable connections that rapidly switch between online and offline states - **Resilient Fetching**: Implements automatic retries with exponential backoff for failed network requests - **Request Timeout Control**: Configurable timeout settings to prevent hanging requests - **Fallback URLs**: Automatic redirection to alternative endpoints when primary endpoints fail @@ -17,13 +16,13 @@ Learn more about RADFish examples at the official [documentation](https://nmfs-r This example renders as shown in this screenshot: -![Enhanced Network Status](./src/assets/network-status.png) +![Network Status](./src/assets/network-status.png) ## Implementation -### 1. Configuring Enhanced Network Features +### 1. Configuring Network Features -Set up the Application with advanced network handling options: +Set up the Application with network handling options: ```jsx const app = new Application({ @@ -36,9 +35,6 @@ const app = new Application({ "https://nonexistent-endpoint.example.com": "https://jsonplaceholder.typicode.com/users" }, - // Threshold for detecting network flapping (default is 3) - flappingThreshold: 2, - // Optional custom network status handler setIsOnline: async (networkInfo, callback) => { // Custom logic to determine network status @@ -56,20 +52,18 @@ const app = new Application({ }); ``` -### 2. Using Enhanced Network Status Features +### 2. Using Network Status Features -Access the enhanced network status features through hooks: +Access the network status features through hooks: ```jsx const HomePage = () => { - const { isOffline, isFlapping, flappingDetails } = useOfflineStatus(); + const { isOffline } = useOfflineStatus(); const app = useApplication(); - // Network status tag with flapping detection + // Network status tag const getNetworkStatusTag = () => { - if (isFlapping) { - return Unstable; - } else if (isOffline) { + if (isOffline) { return Offline; } else { return Online; @@ -98,14 +92,6 @@ const HomePage = () => { return (
Current Status: {getNetworkStatusTag()}
- - {isFlapping && flappingDetails && ( - - Network is flapping! Changed {flappingDetails.flappingCount} times - in the last {Math.round(flappingDetails.timeSinceLastChange/1000)} seconds. - - )} -
); @@ -125,7 +111,6 @@ To fully test the network resilience features, you can use browser DevTools: 2. Go to the "Console" tab to view logs: - Network status changes appear as color-coded logs - - Network flapping events are highlighted with yellow backgrounds - Retry attempts are logged with blue backgrounds ### Simulating Offline/Online States @@ -135,10 +120,6 @@ To fully test the network resilience features, you can use browser DevTools: 3. Select "Offline" to simulate a disconnected state 4. Return to "Online" or "No throttling" to restore connectivity -### Testing Network Flapping - -Instead of manually toggling network state, you can use the "Simulate Network Flapping" button in the example interface to trigger a rapid sequence of online/offline state changes. - ### Testing Fetch with Retry and Fallbacks 1. Click the "Test Fetch with Retry" button while watching the Console diff --git a/examples/network-status/src/pages/Home.jsx b/examples/network-status/src/pages/Home.jsx index 3dc2fc81..54d620ae 100644 --- a/examples/network-status/src/pages/Home.jsx +++ b/examples/network-status/src/pages/Home.jsx @@ -12,7 +12,7 @@ import { import { useOfflineStatus, useApplication } from "@nmfs-radfish/react-radfish"; const HomePage = () => { - const { isOffline, isFlapping, flappingDetails } = useOfflineStatus(); + const { isOffline } = useOfflineStatus(); const app = useApplication(); const [loading, setLoading] = useState(false); @@ -55,9 +55,7 @@ const HomePage = () => { }; const getNetworkStatusTag = () => { - if (isFlapping) { - return Unstable; - } else if (isOffline) { + if (isOffline) { return Offline; } else { return Online; @@ -69,8 +67,7 @@ const HomePage = () => {

Network Status Example

- This example demonstrates network status handling with support for network flapping - detection, request retries with exponential backoff, and request timeouts and fallback URLs. + This example demonstrates network status handling with support for request retries with exponential backoff, and request timeouts and fallback URLs. { Current Status: {getNetworkStatusTag()} - {isFlapping && flappingDetails && ( - - Network is flapping! Changed {flappingDetails.flappingCount} times in the last{" "} - {Math.round(flappingDetails.timeSinceLastChange / 1000)} seconds. - - )} -

- The application automatically detects unstable network connections where the status - rapidly fluctuates between online and offline. + The application automatically detects network connection status based on the browser's online/offline events.

- {/* Bottom row - Resilient Network Features and Network Flapping */} + {/* Bottom row - Resilient Network Features */} - +

Resilient Network Features

@@ -135,42 +124,6 @@ const HomePage = () => {
- - - - -

Network Flapping

-
- -

- Simulate network flapping to test the application's ability to detect unstable 190 - connections. Flapping threshold is set to {app._networkFlappingThreshold}. -

-
-
- -
- - -
); diff --git a/packages/radfish/Application.spec.js b/packages/radfish/Application.spec.js index 2d62ff95..88d6b4c6 100644 --- a/packages/radfish/Application.spec.js +++ b/packages/radfish/Application.spec.js @@ -89,7 +89,6 @@ describe ('Application', () => { const app = new Application(); expect(app._networkTimeout).toBe(30000); expect(app._fallbackUrls).toEqual({}); - expect(app._networkFlappingThreshold).toBe(3); }); it('should initialize with custom network settings', () => { @@ -98,8 +97,7 @@ describe ('Application', () => { timeout: 5000, fallbackUrls: { "https://primary.com": "https://fallback.com" - }, - flappingThreshold: 5 + } } }); @@ -107,35 +105,22 @@ describe ('Application', () => { expect(app._fallbackUrls).toEqual({ "https://primary.com": "https://fallback.com" }); - expect(app._networkFlappingThreshold).toBe(5); }); - it('should detect network flapping', () => { - const app = new Application({ - network: { - flappingThreshold: 2 - } - }); + it('should handle network status changes', () => { + const app = new Application(); const dispatchSpy = vi.spyOn(app, '_dispatch'); // First status change app._handleNetworkStatusChange(false); expect(app.isOnline).toBe(false); - expect(dispatchSpy).toHaveBeenCalledWith("offline", { isFlapping: false }); + expect(dispatchSpy).toHaveBeenCalledWith("offline"); - // Fast status change (within 5000ms) - count: 1 - vi.advanceTimersByTime(1000); + // Change to online app._handleNetworkStatusChange(true); expect(app.isOnline).toBe(true); - expect(dispatchSpy).toHaveBeenCalledWith("online", { isFlapping: false }); - - // Another fast status change - count: 2, should trigger flapping - vi.advanceTimersByTime(1000); - app._handleNetworkStatusChange(false); - - // Now we hit the threshold (2), so should emit flapping event - expect(dispatchSpy).toHaveBeenCalledWith("networkFlapping", expect.any(Object)); + expect(dispatchSpy).toHaveBeenCalledWith("online"); }); it('should handle fetch with timeout', async () => { diff --git a/packages/radfish/index.js b/packages/radfish/index.js index 91d359ac..ccdd5105 100644 --- a/packages/radfish/index.js +++ b/packages/radfish/index.js @@ -12,9 +12,6 @@ export class Application { this._networkHandler = options.network?.setIsOnline; this._networkTimeout = options.network?.timeout || 30000; // Default 30s timeout this._fallbackUrls = options.network?.fallbackUrls || {}; - this._networkFlappingThreshold = options.network?.flappingThreshold || 3; // Default count for flapping detection - this._flappingCounter = 0; - this._lastStatusChange = Date.now(); this._registerEventListeners(); @@ -240,35 +237,15 @@ export class Application { } /** - * Handle network status changes with flapping detection + * Handle network status changes * @param {boolean} isOnline - Current network status * @private */ _handleNetworkStatusChange(isOnline) { - const now = Date.now(); - const timeSinceLastChange = now - this._lastStatusChange; + this.isOnline = isOnline; - // If status changed rapidly, increment flapping counter - if (timeSinceLastChange < 5000 && this.isOnline !== isOnline) { - this._flappingCounter++; - } else { - this._flappingCounter = 0; - } - - // Only update status if not flapping or if forced by threshold - if (this._flappingCounter < this._networkFlappingThreshold) { - this._lastStatusChange = now; - this.isOnline = isOnline; - - // Dispatch appropriate event - this._dispatch(isOnline ? "online" : "offline", { isFlapping: false }); - } else if (this._flappingCounter === this._networkFlappingThreshold) { - // Dispatch flapping event when threshold is reached - this._dispatch("networkFlapping", { - flappingCount: this._flappingCounter, - timeSinceLastChange - }); - } + // Dispatch appropriate event + this._dispatch(isOnline ? "online" : "offline"); } } diff --git a/packages/react-radfish/Application/index.jsx b/packages/react-radfish/Application/index.jsx index cccbcbbe..df64ec3b 100644 --- a/packages/react-radfish/Application/index.jsx +++ b/packages/react-radfish/Application/index.jsx @@ -7,42 +7,22 @@ const ApplicationContext = createContext(); function ApplicationComponent(props) { const { toasts } = useToasts(); - const { isOffline, isFlapping } = useOfflineStatus(); + const { isOffline } = useOfflineStatus(); const prevIsOffline = useRef(null); - const prevIsFlapping = useRef(false); useEffect(() => { if (prevIsOffline.current === null) { prevIsOffline.current = isOffline; - } else if (prevIsOffline.current && !isOffline && !isFlapping) { + } else if (prevIsOffline.current && !isOffline) { dispatchToast({ message: "Application is online", status: "info", duration: 3000 }); } prevIsOffline.current = isOffline; - }, [isOffline, isFlapping]); - - useEffect(() => { - if (!prevIsFlapping.current && isFlapping) { - dispatchToast({ - message: "Network connection is unstable", - status: "warning", - duration: 5000 - }); - } - prevIsFlapping.current = isFlapping; - }, [isFlapping]); + }, [isOffline]); return (
- {isFlapping && ( - - )} - {isOffline && !isFlapping && ( + {isOffline && ( { const application = useApplication(); const [isOffline, setIsOffline] = useState(!application.isOnline); - const [isFlapping, setIsFlapping] = useState(false); - const [flappingDetails, setFlappingDetails] = useState(null); - const updateOnlineStatus = (event) => { + const updateOnlineStatus = () => { setIsOffline(!application.isOnline); - // Reset flapping if we get a stable online/offline state - if (event?.detail?.isFlapping === false) { - setIsFlapping(false); - } - }; - - const handleNetworkFlapping = (event) => { - setIsFlapping(true); - setFlappingDetails(event.detail); }; useEffect(() => { @@ -25,12 +14,10 @@ export const useOfflineStatus = () => { application.addEventListener("online", updateOnlineStatus); application.addEventListener("offline", updateOnlineStatus); - application.addEventListener("networkFlapping", handleNetworkFlapping); return () => { application.removeEventListener("online", updateOnlineStatus); application.removeEventListener("offline", updateOnlineStatus); - application.removeEventListener("networkFlapping", handleNetworkFlapping); }; }, [application]); @@ -58,9 +45,7 @@ export const useOfflineStatus = () => { }; return { - isOffline, - isFlapping, - flappingDetails, + isOffline, checkEndpoint, checkMultipleEndpoints }; diff --git a/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.spec.js b/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.spec.js index 4dea3ee2..489027d2 100644 --- a/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.spec.js +++ b/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.spec.js @@ -52,10 +52,6 @@ describe("useOfflineStatus", () => { "offline", expect.any(Function) ); - expect(mockApplication.addEventListener).toHaveBeenCalledWith( - "networkFlapping", - expect.any(Function) - ); }); it("should cleanup event listeners on unmount", () => { @@ -70,33 +66,6 @@ describe("useOfflineStatus", () => { "offline", expect.any(Function) ); - expect(mockApplication.removeEventListener).toHaveBeenCalledWith( - "networkFlapping", - expect.any(Function) - ); - }); - - it("should handle network flapping events", async () => { - const { result } = renderHook(() => useOfflineStatus()); - - // Initially not flapping - expect(result.current.isFlapping).toBe(false); - - // Simulate flapping event - const flappingHandler = mockApplication.addEventListener.mock.calls.find( - call => call[0] === "networkFlapping" - )[1]; - - await act(async () => { - flappingHandler({ detail: { flappingCount: 3, timeSinceLastChange: 1000 } }); - }); - - // Now should be flapping - expect(result.current.isFlapping).toBe(true); - expect(result.current.flappingDetails).toEqual({ - flappingCount: 3, - timeSinceLastChange: 1000 - }); }); it("should correctly check endpoint connectivity", async () => { From 41de1a9908845c633b02921dace4da97f3148dd9 Mon Sep 17 00:00:00 2001 From: Jay Giang Date: Fri, 7 Mar 2025 16:19:59 -0800 Subject: [PATCH 21/27] fix: Change fetch method to request method --- examples/network-status/src/index.jsx | 17 ++--- examples/network-status/src/pages/Home.jsx | 18 ++--- packages/radfish/Application.spec.js | 79 ++++++++++++++++------ packages/radfish/index.js | 42 +++++++++--- templates/react-javascript/src/index.jsx | 23 ++++++- 5 files changed, 127 insertions(+), 52 deletions(-) diff --git a/examples/network-status/src/index.jsx b/examples/network-status/src/index.jsx index ca270ada..584ecf0f 100644 --- a/examples/network-status/src/index.jsx +++ b/examples/network-status/src/index.jsx @@ -14,17 +14,19 @@ const app = new Application({ }, network: { - // Custom timeout in milliseconds (default is 30000) - timeout: 5000, + // Health check configuration + health: { + // Endpoint URL for health checks + endpointUrl: "https://api.github.com/users", + // Custom timeout in milliseconds (default is 30000) + timeout: 5000 + }, // Fallback URLs to use when primary endpoints fail fallbackUrls: { "https://nonexistent-endpoint.example.com": "https://jsonplaceholder.typicode.com/users" }, - // Threshold for detecting network flapping (default is 3) - flappingThreshold: 2, - // Optional custom network status handler setIsOnline: async (networkInfo, callback) => { console.log("Checking network with custom handler..."); @@ -61,11 +63,6 @@ app.addEventListener("offline", (event) => { console.log("%c NETWORK OFFLINE ", "background: #F44336; color: #fff; font-weight: bold; padding: 4px;"); }); -// Listen for flapping and retry events with more prominent logging -app.addEventListener("networkFlapping", (event) => { - console.warn("%c NETWORK FLAPPING DETECTED ", "background: #FFC107; color: #000; font-weight: bold; padding: 4px;", event.detail); -}); - app.addEventListener("networkRetry", (event) => { console.warn("%c NETWORK RETRY ", "background: #3F51B5; color: #fff; font-weight: bold; padding: 4px;", event.detail); diff --git a/examples/network-status/src/pages/Home.jsx b/examples/network-status/src/pages/Home.jsx index 54d620ae..caec0e67 100644 --- a/examples/network-status/src/pages/Home.jsx +++ b/examples/network-status/src/pages/Home.jsx @@ -16,16 +16,16 @@ const HomePage = () => { const app = useApplication(); const [loading, setLoading] = useState(false); - const testFetchWithRetry = async () => { + const testRequestWithRetry = async () => { setLoading(true); console.log( - "%c STARTING FETCH WITH RETRY TEST ", + "%c STARTING REQUEST WITH RETRY TEST ", "background: #4CAF50; color: #fff; font-weight: bold; padding: 4px;", ); try { // Simulate an endpoint that will fail initially but succeed on retry - const response = await app.fetchWithRetry( + const response = await app.requestWithRetry( "https://nonexistent-endpoint.example.com", {}, { @@ -37,18 +37,18 @@ const HomePage = () => { const data = await response.json(); console.log( - "%c FETCH SUCCESSFUL ", + "%c REQUEST SUCCESSFUL ", "background: #4CAF50; color: #fff; font-weight: bold; padding: 4px;", data, ); - alert(`Fetch successful with retry! Retrieved ${data.length} users.`); + alert(`Request successful with retry! Retrieved ${data.length} users.`); } catch (error) { console.error( - "%c FETCH FAILED ", + "%c REQUEST FAILED ", "background: #F44336; color: #fff; font-weight: bold; padding: 4px;", error, ); - alert(`Fetch failed after all retries: ${error.message}`); + alert(`Request failed after all retries: ${error.message}`); } finally { setLoading(false); } @@ -114,11 +114,11 @@ const HomePage = () => {
diff --git a/packages/radfish/Application.spec.js b/packages/radfish/Application.spec.js index 88d6b4c6..43bc13b3 100644 --- a/packages/radfish/Application.spec.js +++ b/packages/radfish/Application.spec.js @@ -85,23 +85,28 @@ describe ('Application', () => { }); describe('network', () => { - it('should initialize with default network settings', () => { + it('should initialize with default network health settings', () => { const app = new Application(); - expect(app._networkTimeout).toBe(30000); + expect(app._networkHealth.timeout).toBe(30000); + expect(app._networkHealth.endpointUrl).toBeNull(); expect(app._fallbackUrls).toEqual({}); }); - it('should initialize with custom network settings', () => { + it('should initialize with custom network health settings', () => { const app = new Application({ network: { - timeout: 5000, + health: { + timeout: 5000, + endpointUrl: "https://api.example.com/health" + }, fallbackUrls: { "https://primary.com": "https://fallback.com" } } }); - expect(app._networkTimeout).toBe(5000); + expect(app._networkHealth.timeout).toBe(5000); + expect(app._networkHealth.endpointUrl).toBe("https://api.example.com/health"); expect(app._fallbackUrls).toEqual({ "https://primary.com": "https://fallback.com" }); @@ -123,17 +128,19 @@ describe ('Application', () => { expect(dispatchSpy).toHaveBeenCalledWith("online"); }); - it('should handle fetch with timeout', async () => { + it('should handle HTTP request with timeout', async () => { const app = new Application({ network: { - timeout: 5000 + health: { + timeout: 5000 + } } }); // Mock successful fetch global.fetch.mockResolvedValueOnce("response"); - const result = await app.fetch("https://example.com"); + const result = await app.request("https://example.com"); expect(result).toBe("response"); expect(global.fetch).toHaveBeenCalledWith("https://example.com", expect.objectContaining({ signal: expect.any(Object) @@ -156,7 +163,7 @@ describe ('Application', () => { global.fetch.mockImplementationOnce(() => Promise.reject(new Error("Failed"))) .mockResolvedValueOnce("fallback response"); - const result = await app.fetch("https://primary.com"); + const result = await app.request("https://primary.com"); expect(result).toBe("fallback response"); expect(global.fetch).toHaveBeenCalledTimes(2); @@ -164,23 +171,23 @@ describe ('Application', () => { expect(global.fetch).toHaveBeenNthCalledWith(2, "https://backup.com", expect.any(Object)); }); - it('should handle fetch with retry logic', async () => { + it('should handle HTTP request with retry logic', async () => { const app = new Application(); - const fetchSpy = vi.spyOn(app, 'fetch'); + const requestSpy = vi.spyOn(app, 'request'); const dispatchSpy = vi.spyOn(app, '_dispatch'); // Mock failure on first attempt, success on second - fetchSpy.mockRejectedValueOnce(new Error("Failed")) - .mockResolvedValueOnce("success"); + requestSpy.mockRejectedValueOnce(new Error("Failed")) + .mockResolvedValueOnce("success"); - const result = await app.fetchWithRetry("https://example.com", {}, { + const result = await app.requestWithRetry("https://example.com", {}, { retries: 2, retryDelay: 1000 }); expect(result).toBe("success"); - expect(fetchSpy).toHaveBeenCalledTimes(2); + expect(requestSpy).toHaveBeenCalledTimes(2); expect(dispatchSpy).toHaveBeenCalledWith("networkRetry", expect.objectContaining({ attempt: 1, maxRetries: 2 @@ -193,22 +200,22 @@ describe ('Application', () => { it('should use exponential backoff for retries', async () => { const app = new Application(); - const fetchSpy = vi.spyOn(app, 'fetch'); + const requestSpy = vi.spyOn(app, 'request'); // Mock failures - fetchSpy.mockRejectedValueOnce(new Error("Failed")) - .mockRejectedValueOnce(new Error("Failed again")) - .mockResolvedValueOnce("success"); + requestSpy.mockRejectedValueOnce(new Error("Failed")) + .mockRejectedValueOnce(new Error("Failed again")) + .mockResolvedValueOnce("success"); // Use fake timers to advance through delays - const result = await app.fetchWithRetry("https://example.com", {}, { + const result = await app.requestWithRetry("https://example.com", {}, { retries: 3, retryDelay: 1000, exponentialBackoff: true }); expect(result).toBe("success"); - expect(fetchSpy).toHaveBeenCalledTimes(3); + expect(requestSpy).toHaveBeenCalledTimes(3); // First retry should use 1000ms delay expect(setTimeout).toHaveBeenNthCalledWith(1, expect.any(Function), 1000); @@ -216,5 +223,35 @@ describe ('Application', () => { // Second retry should use 2000ms delay (2^1 * 1000) expect(setTimeout).toHaveBeenNthCalledWith(2, expect.any(Function), 2000); }); + + it('should check network health', async () => { + const app = new Application({ + network: { + health: { + endpointUrl: "https://api.example.com/health" + } + } + }); + + const requestSpy = vi.spyOn(app, 'request'); + + // Mock successful health check + requestSpy.mockResolvedValueOnce({ ok: true }); + + const result = await app.checkNetworkHealth(); + + expect(result).toBe(true); + expect(requestSpy).toHaveBeenCalledWith( + "https://api.example.com/health", + { method: "HEAD" } + ); + + // Test failed health check + requestSpy.mockRejectedValueOnce(new Error("Network error")); + + const failedResult = await app.checkNetworkHealth(); + + expect(failedResult).toBe(false); + }); }); }); \ No newline at end of file diff --git a/packages/radfish/index.js b/packages/radfish/index.js index ccdd5105..266b65df 100644 --- a/packages/radfish/index.js +++ b/packages/radfish/index.js @@ -10,7 +10,14 @@ export class Application { this.isOnline = navigator.onLine; this._options = options; this._networkHandler = options.network?.setIsOnline; - this._networkTimeout = options.network?.timeout || 30000; // Default 30s timeout + + // Network health check configuration + this._networkHealth = { + timeout: options.network?.health?.timeout || 30000, // Default 30s timeout + endpointUrl: options.network?.health?.endpointUrl || null + }; + + // Fallback URLs configuration this._fallbackUrls = options.network?.fallbackUrls || {}; this._registerEventListeners(); @@ -129,16 +136,16 @@ export class Application { } /** - * Fetch with fallback and timeout support + * Make HTTP request with fallback and timeout support * @param {string|Request} resource - The URL or Request object - * @param {Object} [options] - Fetch options + * @param {Object} [options] - Request options * @returns {Promise} - Response from primary or fallback URL */ - async fetch(resource, options = {}) { + async request(resource, options = {}) { const url = resource instanceof Request ? resource.url : resource; const fallbackUrl = this._fallbackUrls[url]; const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), this._networkTimeout); + const timeoutId = setTimeout(() => controller.abort(), this._networkHealth.timeout); // Clone options and add abort signal const fetchOptions = { @@ -164,7 +171,7 @@ export class Application { try { // Create a new controller for the fallback request const fallbackController = new AbortController(); - const fallbackTimeoutId = setTimeout(() => fallbackController.abort(), this._networkTimeout); + const fallbackTimeoutId = setTimeout(() => fallbackController.abort(), this._networkHealth.timeout); const fallbackOptions = { ...options, @@ -188,16 +195,16 @@ export class Application { } /** - * Fetch with automatic retry capability + * Make HTTP request with automatic retry capability * @param {string|Request} resource - The URL or Request object - * @param {Object} [options] - Fetch options + * @param {Object} [options] - Request options * @param {Object} [retryOptions] - Retry configuration * @param {number} [retryOptions.retries=3] - Maximum number of retries * @param {number} [retryOptions.retryDelay=1000] - Delay between retries in ms * @param {boolean} [retryOptions.exponentialBackoff=true] - Whether to use exponential backoff * @returns {Promise} - Response from successful request */ - async fetchWithRetry(resource, options = {}, retryOptions = {}) { + async requestWithRetry(resource, options = {}, retryOptions = {}) { const { retries = 3, retryDelay = 1000, @@ -208,7 +215,7 @@ export class Application { for (let attempt = 0; attempt <= retries; attempt++) { try { - return await this.fetch(resource, options); + return await this.request(resource, options); } catch (error) { lastError = error; @@ -236,6 +243,21 @@ export class Application { throw lastError; } + /** + * Check network health by making a request to the configured health endpoint + * @returns {Promise} - True if the health check succeeds, false otherwise + */ + async checkNetworkHealth() { + const url = this._networkHealth.endpointUrl || 'https://api.github.com/users'; + + try { + const response = await this.request(url, { method: 'HEAD' }); + return response.ok; + } catch (error) { + return false; + } + } + /** * Handle network status changes * @param {boolean} isOnline - Current network status diff --git a/templates/react-javascript/src/index.jsx b/templates/react-javascript/src/index.jsx index 7b1d65bc..97b7b48c 100644 --- a/templates/react-javascript/src/index.jsx +++ b/templates/react-javascript/src/index.jsx @@ -17,6 +17,21 @@ const app = new Application({ handlers: import("../mocks/browser.js"), }, network: { + // Health check configuration + health: { + // Endpoint URL for health checks + endpointUrl: "https://api.github.com/zen", + // Custom timeout in milliseconds + timeout: 3000 + }, + + // Example fallback URLs for resilient requests + fallbackUrls: { + // Map primary URLs to fallback URLs + "https://api.example.com/primary": "https://api.example.com/backup" + }, + + // Custom network status handler setIsOnline: async (networkInformation, callback) => { try { // First check basic connection status @@ -25,11 +40,15 @@ const app = new Application({ } // Simple ping test to verify actual connectivity + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 3000); + const response = await fetch('https://api.github.com/zen', { method: 'HEAD', - timeout: 3000 + signal: controller.signal }); - + + clearTimeout(timeoutId); return callback(response.ok); } catch (error) { console.warn('Network check failed:', error); From 0de5e24e1c25258a7dda4c0f6e8e22434615a087 Mon Sep 17 00:00:00 2001 From: Tony Gaskell Date: Tue, 12 Aug 2025 09:16:47 -1000 Subject: [PATCH 22/27] fix merge conflict --- packages/radfish/Application.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/radfish/Application.spec.js b/packages/radfish/Application.spec.js index 192a8fa7..43bc13b3 100644 --- a/packages/radfish/Application.spec.js +++ b/packages/radfish/Application.spec.js @@ -18,7 +18,6 @@ describe ('Application', () => { vi.useRealTimers(); }); -describe('Application', () => { describe('storage', () => { it('should return the storage method', () => { // IndexedDB Storage application From a8d0bad197e1071253e37d796e8884b79703957b Mon Sep 17 00:00:00 2001 From: Tony Gaskell Date: Thu, 11 Sep 2025 11:18:46 -1000 Subject: [PATCH 23/27] Check if networkHandler is a function before calling --- packages/radfish/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/radfish/index.js b/packages/radfish/index.js index 1d06e8a8..b46d66a7 100644 --- a/packages/radfish/index.js +++ b/packages/radfish/index.js @@ -43,7 +43,7 @@ export class Application { this._registerEventListeners(); // Check initial network status if handler provided - if (this._networkHandler) { + if (typeof this._networkHandler === 'function') { this._networkHandler(navigator.connection, (status) => { this._handleNetworkStatusChange(status); }); From 714129f0fd21f205e68374045a3d97882e73d4ae Mon Sep 17 00:00:00 2001 From: Tony Gaskell Date: Tue, 23 Sep 2025 08:41:20 -1000 Subject: [PATCH 24/27] fix: event dispatch in react-radfish unit tests --- package-lock.json | 239 +++++++++++++++++- .../useOfflineStatus/useOfflineStatus.js | 11 +- .../useOfflineStatus/useOfflineStatus.spec.js | 80 ++++-- .../hooks/useToast/useToast.spec.js | 6 +- packages/react-radfish/package.json | 5 +- 5 files changed, 310 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7491319d..d5636481 100644 --- a/package-lock.json +++ b/package-lock.json @@ -724,6 +724,21 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", + "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==", + "peer": true + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz", + "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==", + "peer": true, + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.4.0" + } + }, "node_modules/@nmfs-radfish/radfish": { "resolved": "packages/radfish", "link": true @@ -1107,6 +1122,7 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/@trussworks/react-uswds/-/react-uswds-9.1.0.tgz", "integrity": "sha512-vQsr73oMtDIzLHVtkgD81tL7YxzygTyH9e1P3Lv/C1tGlqoNEUmUgVEmUVzo/IwOvMN0XxxSkNkOpnM9rDzRMg==", + "peer": true, "engines": { "node": ">= 18" }, @@ -1176,6 +1192,59 @@ "integrity": "sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA==", "dev": true }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@types/react": { + "version": "18.3.24", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz", + "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "optional": true, + "peer": true, + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "peer": true + }, + "node_modules/@uswds/uswds": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@uswds/uswds/-/uswds-3.13.0.tgz", + "integrity": "sha512-8P494gmXv/0sm09ExSdj8wAMjGLnM7UMRY/XgsMIRKnWfDXG+TyuCOKIuD4lqs+gLvSmi1nTQKyd0c0/A7VWJQ==", + "peer": true, + "dependencies": { + "lit": "^3.2.1", + "receptor": "1.0.0" + }, + "engines": { + "node": ">= 4" + }, + "optionalDependencies": { + "sass-embedded-linux-x64": "^1.89.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.2.tgz", @@ -1574,6 +1643,14 @@ "node": ">=18" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", @@ -1663,6 +1740,15 @@ "integrity": "sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ==", "dev": true }, + "node_modules/element-closest": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/element-closest/-/element-closest-2.0.2.tgz", + "integrity": "sha512-QCqAWP3kwj8Gz9UXncVXQGdrhnWxD8SQBSeZp5pOsyCcQ6RpL738L1/tfuwBiMi6F1fYkxqPnBrFBR4L+f49Cg==", + "peer": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -1780,6 +1866,30 @@ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, + "node_modules/focus-trap": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.5.tgz", + "integrity": "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==", + "peer": true, + "dependencies": { + "tabbable": "^6.2.0" + } + }, + "node_modules/focus-trap-react": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-10.3.1.tgz", + "integrity": "sha512-PN4Ya9xf9nyj/Nd9VxBNMuD7IrlRbmaG6POAQ8VLqgtc6IY/Ln1tYakow+UIq4fihYYYFM70/2oyidE6bbiPgw==", + "peer": true, + "dependencies": { + "focus-trap": "^7.6.1", + "tabbable": "^6.2.0" + }, + "peerDependencies": { + "prop-types": "^15.8.1", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -2038,6 +2148,43 @@ "node": ">=6" } }, + "node_modules/keyboardevent-key-polyfill": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keyboardevent-key-polyfill/-/keyboardevent-key-polyfill-1.1.0.tgz", + "integrity": "sha512-NTDqo7XhzL1fqmUzYroiyK2qGua7sOMzLav35BfNA/mPUSCtw8pZghHFMTYR9JdnJ23IQz695FcaM6EE6bpbFQ==", + "peer": true + }, + "node_modules/lit": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.1.tgz", + "integrity": "sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==", + "peer": true, + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.1.tgz", + "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==", + "peer": true, + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.4.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.1.tgz", + "integrity": "sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==", + "peer": true, + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/local-pkg": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", @@ -2064,7 +2211,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -2105,6 +2251,12 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/matches-selector": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/matches-selector/-/matches-selector-1.2.0.tgz", + "integrity": "sha512-c4vLwYWyl+Ji+U43eU/G5FwxWd4ZH0ePUsFs5y0uwD9HUEFBXUQ1zUUan+78IpRD+y4pUfG0nAzNM292K7ItvA==", + "peer": true + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -2226,6 +2378,15 @@ "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==", "dev": true }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -2351,6 +2512,23 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "peer": true + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -2404,6 +2582,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -2419,6 +2609,18 @@ "node": ">=0.10.0" } }, + "node_modules/receptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/receptor/-/receptor-1.0.0.tgz", + "integrity": "sha512-yvVEqVQDNzEmGkluCkEdbKSXqZb3WGxotI/VukXIQ+4/BXEeXVjWtmC6jWaR1BIsmEAGYQy3OTaNgDj2Svr01w==", + "peer": true, + "dependencies": { + "element-closest": "^2.0.1", + "keyboardevent-key-polyfill": "^1.0.2", + "matches-selector": "^1.0.0", + "object-assign": "^4.1.0" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -2524,6 +2726,22 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.92.1", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.92.1.tgz", + "integrity": "sha512-xuiK5Jp5NldW4bvlC7AuX1Wf7o0gLZ3md/hNg+bkTvxtCDgnUHtfdo8Q+xWP11bD9QX31xXFWpmUB8UDLi6XQQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -2536,6 +2754,14 @@ "node": ">=v12.22.7" } }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2667,6 +2893,12 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "peer": true + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -3620,8 +3852,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@tanstack/react-table": "^8.16.0", - "@trussworks/react-uswds": "^9.0.0" + "@tanstack/react-table": "^8.16.0" }, "devDependencies": { "@testing-library/dom": "10.4.0", @@ -3629,11 +3860,13 @@ "@testing-library/react": "16.0.0", "@vitejs/plugin-react": "^4.3.1", "jsdom": "25.0.1", + "react-dom": "^18.3.1", "vite-plugin-css-injected-by-js": "^3.5.1", "vitest": "2.1.2" }, "peerDependencies": { "@nmfs-radfish/radfish": "^1.1.0", + "@trussworks/react-uswds": "^9.0.0", "react": "^18.2.0" } } diff --git a/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.js b/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.js index a171935d..3748bf88 100644 --- a/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.js +++ b/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.js @@ -12,12 +12,15 @@ export const useOfflineStatus = () => { useEffect(() => { updateOnlineStatus(); - application.addEventListener("online", updateOnlineStatus); - application.addEventListener("offline", updateOnlineStatus); + const setIsOfflineTrue = () => setIsOffline(true); + const setIsOfflineFalse = () => setIsOffline(false); + + application.addEventListener("online", setIsOfflineFalse); + application.addEventListener("offline", setIsOfflineTrue); return () => { - application.removeEventListener("online", updateOnlineStatus); - application.removeEventListener("offline", updateOnlineStatus); + application.removeEventListener("online", setIsOfflineFalse); + application.removeEventListener("offline", setIsOfflineTrue); }; }, [application]); diff --git a/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.spec.js b/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.spec.js index 489027d2..75bb33bc 100644 --- a/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.spec.js +++ b/packages/react-radfish/hooks/useOfflineStatus/useOfflineStatus.spec.js @@ -8,37 +8,77 @@ vi.mock("../../Application", () => ({ })); describe("useOfflineStatus", () => { - // Setup mock application - const mockApplication = { - isOnline: true, - _networkTimeout: 5000, - fetch: vi.fn(), - addEventListener: vi.fn(), - removeEventListener: vi.fn() - }; - + let mockApplication; beforeEach(() => { vi.resetAllMocks(); - mockApplication.isOnline = true; - mockApplication.fetch.mockReset(); + const emitter = new EventTarget(); + mockApplication = { + isOnline: false, + emitter, + _networkTimeout: 5000, + fetch: vi.fn(), + addEventListener: vi.fn((event, callback) => { + emitter.addEventListener(event, callback); + }), + dispatchEvent: vi.fn((event) => { + emitter.dispatchEvent(event); + }), + removeEventListener: vi.fn((event, callback) => { + emitter.removeEventListener(event, callback); + }) + }; useApplication.mockReturnValue(mockApplication); }); + it("should initialize with offline status", () => { + const { result } = renderHook(() => useOfflineStatus()); + expect(result.current.isOffline).toBe(true); + }); + + it('should mount and unmount handlers', () => { + const {unmount } = renderHook(() => useOfflineStatus()); + + expect(mockApplication.addEventListener).toHaveBeenCalledWith( + "online", + expect.any(Function) + ); + expect(mockApplication.addEventListener).toHaveBeenCalledWith( + "offline", + expect.any(Function) + ); + + unmount(); + + expect(mockApplication.removeEventListener).toHaveBeenCalledWith( + "online", + expect.any(Function) + ); + expect(mockApplication.removeEventListener).toHaveBeenCalledWith( + "offline", + expect.any(Function) + ); + + }); + it("should trigger when navigator online switches", async () => { - const onLineSpy = vi.spyOn(window.navigator, "onLine", "get"); - const { result, rerender } = renderHook(() => useOfflineStatus()); + const { result } = renderHook(() => useOfflineStatus()); - onLineSpy.mockReturnValue(true); - window.dispatchEvent(new window.Event("online")); - rerender(); + // Must be wrapped in act callback to allow callstack to resolve + // before asserting value + act(() => { + mockApplication.dispatchEvent(new CustomEvent("online")); + }); expect(result.current.isOffline).toBe(false); - onLineSpy.mockReturnValue(false); - window.dispatchEvent(new window.Event("offline")); - rerender(); + // Must be wrapped in act callback to allow callstack to resolve + // before asserting value + act(() => { + mockApplication.dispatchEvent(new CustomEvent("offline")); + }); expect(result.current.isOffline).toBe(true); + }); it("should add event listeners for network events", () => { @@ -78,7 +118,7 @@ describe("useOfflineStatus", () => { expect(isReachable).toBe(true); expect(mockApplication.fetch).toHaveBeenCalledWith( "https://example.com", - { method: "HEAD" } + { method: "HEAD", timeout: 5000 } ); // Test failed fetch diff --git a/packages/react-radfish/hooks/useToast/useToast.spec.js b/packages/react-radfish/hooks/useToast/useToast.spec.js index 493df398..5e36d12d 100644 --- a/packages/react-radfish/hooks/useToast/useToast.spec.js +++ b/packages/react-radfish/hooks/useToast/useToast.spec.js @@ -1,4 +1,4 @@ -import { renderHook } from "@testing-library/react"; +import { renderHook, act } from "@testing-library/react"; import { useToasts, dispatchToast } from "./useToast"; describe("useToast", () => { @@ -11,7 +11,9 @@ describe("useToast", () => { }, } = renderHook(() => useToasts()); - dispatchToast({ message: "Hello", status: "ok" }); + act(() => { + dispatchToast({ message: "Hello", status: "ok" }); + }); let dispatchedEvent = dispatchEventSpy.mock.calls[0][0]; expect(dispatchEventSpy).toHaveBeenCalledWith(expect.any(CustomEvent)); diff --git a/packages/react-radfish/package.json b/packages/react-radfish/package.json index 0f55e3e1..ba842926 100644 --- a/packages/react-radfish/package.json +++ b/packages/react-radfish/package.json @@ -25,11 +25,11 @@ "registry": "https://registry.npmjs.org/" }, "dependencies": { - "@tanstack/react-table": "^8.16.0", - "@trussworks/react-uswds": "^9.0.0" + "@tanstack/react-table": "^8.16.0" }, "peerDependencies": { "@nmfs-radfish/radfish": "^1.1.0", + "@trussworks/react-uswds": "^9.0.0", "react": "^18.2.0" }, "devDependencies": { @@ -38,6 +38,7 @@ "@testing-library/react": "16.0.0", "@vitejs/plugin-react": "^4.3.1", "jsdom": "25.0.1", + "react-dom": "^18.3.1", "vite-plugin-css-injected-by-js": "^3.5.1", "vitest": "2.1.2" } From 7381f00339c682792d1cdefc585977297fccb118 Mon Sep 17 00:00:00 2001 From: Tony Gaskell Date: Mon, 29 Sep 2025 09:31:27 -1000 Subject: [PATCH 25/27] Update depenendency lock file --- package-lock.json | 245 +--------------------------------------------- 1 file changed, 5 insertions(+), 240 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5636481..a69bc25c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -724,21 +724,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", - "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==", - "peer": true - }, - "node_modules/@lit/reactive-element": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz", - "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==", - "peer": true, - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.4.0" - } - }, "node_modules/@nmfs-radfish/radfish": { "resolved": "packages/radfish", "link": true @@ -1118,21 +1103,6 @@ } } }, - "node_modules/@trussworks/react-uswds": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@trussworks/react-uswds/-/react-uswds-9.1.0.tgz", - "integrity": "sha512-vQsr73oMtDIzLHVtkgD81tL7YxzygTyH9e1P3Lv/C1tGlqoNEUmUgVEmUVzo/IwOvMN0XxxSkNkOpnM9rDzRMg==", - "peer": true, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@uswds/uswds": "^3.7.1", - "focus-trap-react": "^10.2.3", - "react": "^16.x || ^17.x || ^18.x", - "react-dom": "^16.x || ^17.x || ^18.x" - } - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -1192,59 +1162,6 @@ "integrity": "sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA==", "dev": true }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@types/react": { - "version": "18.3.24", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz", - "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, - "optional": true, - "peer": true, - "peerDependencies": { - "@types/react": "^18.0.0" - } - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "peer": true - }, - "node_modules/@uswds/uswds": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@uswds/uswds/-/uswds-3.13.0.tgz", - "integrity": "sha512-8P494gmXv/0sm09ExSdj8wAMjGLnM7UMRY/XgsMIRKnWfDXG+TyuCOKIuD4lqs+gLvSmi1nTQKyd0c0/A7VWJQ==", - "peer": true, - "dependencies": { - "lit": "^3.2.1", - "receptor": "1.0.0" - }, - "engines": { - "node": ">= 4" - }, - "optionalDependencies": { - "sass-embedded-linux-x64": "^1.89.0" - } - }, "node_modules/@vitejs/plugin-react": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.2.tgz", @@ -1643,14 +1560,6 @@ "node": ">=18" } }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", @@ -1740,15 +1649,6 @@ "integrity": "sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ==", "dev": true }, - "node_modules/element-closest": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/element-closest/-/element-closest-2.0.2.tgz", - "integrity": "sha512-QCqAWP3kwj8Gz9UXncVXQGdrhnWxD8SQBSeZp5pOsyCcQ6RpL738L1/tfuwBiMi6F1fYkxqPnBrFBR4L+f49Cg==", - "peer": true, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -1866,30 +1766,6 @@ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, - "node_modules/focus-trap": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.5.tgz", - "integrity": "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==", - "peer": true, - "dependencies": { - "tabbable": "^6.2.0" - } - }, - "node_modules/focus-trap-react": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-10.3.1.tgz", - "integrity": "sha512-PN4Ya9xf9nyj/Nd9VxBNMuD7IrlRbmaG6POAQ8VLqgtc6IY/Ln1tYakow+UIq4fihYYYFM70/2oyidE6bbiPgw==", - "peer": true, - "dependencies": { - "focus-trap": "^7.6.1", - "tabbable": "^6.2.0" - }, - "peerDependencies": { - "prop-types": "^15.8.1", - "react": ">=16.3.0", - "react-dom": ">=16.3.0" - } - }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -2070,7 +1946,8 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/jsdom": { "version": "25.0.1", @@ -2148,43 +2025,6 @@ "node": ">=6" } }, - "node_modules/keyboardevent-key-polyfill": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/keyboardevent-key-polyfill/-/keyboardevent-key-polyfill-1.1.0.tgz", - "integrity": "sha512-NTDqo7XhzL1fqmUzYroiyK2qGua7sOMzLav35BfNA/mPUSCtw8pZghHFMTYR9JdnJ23IQz695FcaM6EE6bpbFQ==", - "peer": true - }, - "node_modules/lit": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.1.tgz", - "integrity": "sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==", - "peer": true, - "dependencies": { - "@lit/reactive-element": "^2.1.0", - "lit-element": "^4.2.0", - "lit-html": "^3.3.0" - } - }, - "node_modules/lit-element": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.1.tgz", - "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==", - "peer": true, - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.4.0", - "@lit/reactive-element": "^2.1.0", - "lit-html": "^3.3.0" - } - }, - "node_modules/lit-html": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.1.tgz", - "integrity": "sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==", - "peer": true, - "dependencies": { - "@types/trusted-types": "^2.0.2" - } - }, "node_modules/local-pkg": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", @@ -2211,6 +2051,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -2251,12 +2092,6 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/matches-selector": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/matches-selector/-/matches-selector-1.2.0.tgz", - "integrity": "sha512-c4vLwYWyl+Ji+U43eU/G5FwxWd4ZH0ePUsFs5y0uwD9HUEFBXUQ1zUUan+78IpRD+y4pUfG0nAzNM292K7ItvA==", - "peer": true - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -2378,15 +2213,6 @@ "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==", "dev": true }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -2512,23 +2338,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "peer": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "peer": true - }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -2570,22 +2379,11 @@ } ] }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -2609,18 +2407,6 @@ "node": ">=0.10.0" } }, - "node_modules/receptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/receptor/-/receptor-1.0.0.tgz", - "integrity": "sha512-yvVEqVQDNzEmGkluCkEdbKSXqZb3WGxotI/VukXIQ+4/BXEeXVjWtmC6jWaR1BIsmEAGYQy3OTaNgDj2Svr01w==", - "peer": true, - "dependencies": { - "element-closest": "^2.0.1", - "keyboardevent-key-polyfill": "^1.0.2", - "matches-selector": "^1.0.0", - "object-assign": "^4.1.0" - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -2726,22 +2512,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "node_modules/sass-embedded-linux-x64": { - "version": "1.92.1", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.92.1.tgz", - "integrity": "sha512-xuiK5Jp5NldW4bvlC7AuX1Wf7o0gLZ3md/hNg+bkTvxtCDgnUHtfdo8Q+xWP11bD9QX31xXFWpmUB8UDLi6XQQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -2758,6 +2528,7 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -2893,12 +2664,6 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "peer": true - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", From 5abdf168ecec7d43fe818b69c8a5e5fad4db84ed Mon Sep 17 00:00:00 2001 From: Tony Gaskell Date: Mon, 29 Sep 2025 09:59:34 -1000 Subject: [PATCH 26/27] Correctly add dev dependencies for ci environment --- package-lock.json | 87 +++++++++++++++++++++++++++++ packages/react-radfish/package.json | 6 +- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index a69bc25c..106121ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1103,6 +1103,21 @@ } } }, + "node_modules/@trussworks/react-uswds": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@trussworks/react-uswds/-/react-uswds-9.1.0.tgz", + "integrity": "sha512-vQsr73oMtDIzLHVtkgD81tL7YxzygTyH9e1P3Lv/C1tGlqoNEUmUgVEmUVzo/IwOvMN0XxxSkNkOpnM9rDzRMg==", + "dev": true, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@uswds/uswds": "^3.7.1", + "focus-trap-react": "^10.2.3", + "react": "^16.x || ^17.x || ^18.x", + "react-dom": "^16.x || ^17.x || ^18.x" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -1766,6 +1781,30 @@ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, + "node_modules/focus-trap": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.5.tgz", + "integrity": "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==", + "dev": true, + "dependencies": { + "tabbable": "^6.2.0" + } + }, + "node_modules/focus-trap-react": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-10.3.1.tgz", + "integrity": "sha512-PN4Ya9xf9nyj/Nd9VxBNMuD7IrlRbmaG6POAQ8VLqgtc6IY/Ln1tYakow+UIq4fihYYYFM70/2oyidE6bbiPgw==", + "dev": true, + "dependencies": { + "focus-trap": "^7.6.1", + "tabbable": "^6.2.0" + }, + "peerDependencies": { + "prop-types": "^15.8.1", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -2213,6 +2252,15 @@ "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==", "dev": true }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -2338,6 +2386,23 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -2379,6 +2444,18 @@ } ] }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -2664,6 +2741,12 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -3623,8 +3706,12 @@ "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "16.0.0", + "@trussworks/react-uswds": "^9.0.0", "@vitejs/plugin-react": "^4.3.1", + "focus-trap-react": "^10.3.1", "jsdom": "25.0.1", + "prop-types": "^15.8.1", + "react": "^18.2.0", "react-dom": "^18.3.1", "vite-plugin-css-injected-by-js": "^3.5.1", "vitest": "2.1.2" diff --git a/packages/react-radfish/package.json b/packages/react-radfish/package.json index ba842926..30bf0920 100644 --- a/packages/react-radfish/package.json +++ b/packages/react-radfish/package.json @@ -36,10 +36,14 @@ "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "16.0.0", + "@trussworks/react-uswds": "^9.0.0", "@vitejs/plugin-react": "^4.3.1", + "focus-trap-react": "^10.3.1", "jsdom": "25.0.1", + "prop-types": "^15.8.1", + "react": "^18.2.0", "react-dom": "^18.3.1", "vite-plugin-css-injected-by-js": "^3.5.1", "vitest": "2.1.2" } -} \ No newline at end of file +} From e34c0686c6b2e96b4ef9caabf87ed31e73e7a21b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 29 Sep 2025 20:01:14 +0000 Subject: [PATCH 27/27] chore(release): bump @nmfs-radfish/react-radfish to 1.1.0-rc.0 --- packages/react-radfish/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-radfish/package.json b/packages/react-radfish/package.json index 30bf0920..382f62b4 100644 --- a/packages/react-radfish/package.json +++ b/packages/react-radfish/package.json @@ -1,6 +1,6 @@ { "name": "@nmfs-radfish/react-radfish", - "version": "1.0.0", + "version": "1.1.0-rc.0", "type": "module", "main": "./dist/index.umd.js", "module": "./dist/index.es.js",