mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: detect CSS preload errors and keep banner visible during error states
- Add "unable to preload" pattern to isChunkError for Vite CSS preload failures - Move ConnectionMonitor and ConnectionBanner outside ErrorBoundary so they remain visible when the error boundary catches a render crash - Add test for CSS preload error retry
This commit is contained in:
+34
-29
@@ -167,36 +167,41 @@ function ConnectionMonitor() {
|
|||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<>
|
||||||
<ConnectionMonitor />
|
<ConnectionMonitor />
|
||||||
<ConnectionBanner />
|
<ConnectionBanner />
|
||||||
<Toaster position="bottom-right" />
|
<ErrorBoundary>
|
||||||
<BrowserRouter>
|
<Toaster position="bottom-right" />
|
||||||
<KeyboardShortcutProvider>
|
<BrowserRouter>
|
||||||
<AuthGuard>
|
<KeyboardShortcutProvider>
|
||||||
<Suspense fallback={<PageLoader />}>
|
<AuthGuard>
|
||||||
<Routes>
|
<Suspense fallback={<PageLoader />}>
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Routes>
|
||||||
<Route path="/change-password" element={<ChangePasswordPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
<Route path="/automate" element={<AutomatePage />} />
|
<Route path="/change-password" element={<ChangePasswordPage />} />
|
||||||
<Route path="/files" element={<FilesPage />} />
|
<Route path="/automate" element={<AutomatePage />} />
|
||||||
<Route path="/fullscreen" element={<FullscreenGridPage />} />
|
<Route path="/files" element={<FilesPage />} />
|
||||||
<Route path="/privacy" element={<PrivacyPolicyPage />} />
|
<Route path="/fullscreen" element={<FullscreenGridPage />} />
|
||||||
{/* Redirects: old color tools consolidated into adjust-colors */}
|
<Route path="/privacy" element={<PrivacyPolicyPage />} />
|
||||||
<Route
|
{/* Redirects: old color tools consolidated into adjust-colors */}
|
||||||
path="/brightness-contrast"
|
<Route
|
||||||
element={<Navigate to="/adjust-colors" replace />}
|
path="/brightness-contrast"
|
||||||
/>
|
element={<Navigate to="/adjust-colors" replace />}
|
||||||
<Route path="/saturation" element={<Navigate to="/adjust-colors" replace />} />
|
/>
|
||||||
<Route path="/color-channels" element={<Navigate to="/adjust-colors" replace />} />
|
<Route path="/saturation" element={<Navigate to="/adjust-colors" replace />} />
|
||||||
<Route path="/color-effects" element={<Navigate to="/adjust-colors" replace />} />
|
<Route
|
||||||
<Route path="/:toolId" element={<ToolPage />} />
|
path="/color-channels"
|
||||||
<Route path="/" element={<HomePage />} />
|
element={<Navigate to="/adjust-colors" replace />}
|
||||||
</Routes>
|
/>
|
||||||
</Suspense>
|
<Route path="/color-effects" element={<Navigate to="/adjust-colors" replace />} />
|
||||||
</AuthGuard>
|
<Route path="/:toolId" element={<ToolPage />} />
|
||||||
</KeyboardShortcutProvider>
|
<Route path="/" element={<HomePage />} />
|
||||||
</BrowserRouter>
|
</Routes>
|
||||||
</ErrorBoundary>
|
</Suspense>
|
||||||
|
</AuthGuard>
|
||||||
|
</KeyboardShortcutProvider>
|
||||||
|
</BrowserRouter>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ export function isChunkError(error: unknown): boolean {
|
|||||||
msg.includes("dynamically imported module") ||
|
msg.includes("dynamically imported module") ||
|
||||||
msg.includes("loading chunk") ||
|
msg.includes("loading chunk") ||
|
||||||
msg.includes("loading css chunk") ||
|
msg.includes("loading css chunk") ||
|
||||||
msg.includes("failed to fetch")
|
msg.includes("failed to fetch") ||
|
||||||
|
msg.includes("unable to preload")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,19 @@ describe("retryDynamicImport", () => {
|
|||||||
expect(importFn).toHaveBeenCalledTimes(3);
|
expect(importFn).toHaveBeenCalledTimes(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("retries CSS preload errors", async () => {
|
||||||
|
const mod = { default: () => null };
|
||||||
|
const importFn = vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValueOnce(
|
||||||
|
new TypeError("Unable to preload CSS for /assets/tool-page-DDbXBANV.css"),
|
||||||
|
)
|
||||||
|
.mockResolvedValue(mod);
|
||||||
|
const result = await retryDynamicImport(importFn, 3, 0);
|
||||||
|
expect(result).toBe(mod);
|
||||||
|
expect(importFn).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
it("only retries chunk-related errors, not other errors", async () => {
|
it("only retries chunk-related errors, not other errors", async () => {
|
||||||
const err = new Error("Some other error");
|
const err = new Error("Some other error");
|
||||||
const importFn = vi.fn().mockRejectedValue(err);
|
const importFn = vi.fn().mockRejectedValue(err);
|
||||||
|
|||||||
Reference in New Issue
Block a user