mirror of
https://github.com/actualbudget/actual.git
synced 2026-07-23 03:50:52 -05:00
* [AI] Recover from BackendInitFailure and show a meaningful error When the backend Worker fails to load (e.g., the hashed kcab.worker asset can't be fetched), the SharedWorker would cache the app-init-failure and replay it to every subsequent tab forever, while the FatalError modal showed a misleading "browser version" message. - Retry importScripts in production (3 attempts) so a transient blip doesn't brick the SharedWorker. - Clear lastAppInitFailure when the client acknowledges the failure, when a backend later connects successfully (centralized in broadcastConnect), and when a fresh init arrives with no active groups (the failed leader is gone). - Add a BackendInitFailure branch to FatalError's RenderSimple with a message that points the user at reload / hard refresh. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Remove support contact message from FatalError Removed support contact message from FatalError component. * [AI] Fix error propagation in importScriptsWithRetry - Change Promise executor to accept both resolve and reject - Properly propagate errors using .then(resolve).catch(reject) - Fixes issue where errors from recursive retry calls were swallowed Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Matiss Janis Aboltins <MatissJanis@users.noreply.github.com>
76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
import { LazyLoadFailedError } from '@actual-app/core/shared/errors';
|
|
import { render, screen } from '@testing-library/react';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
import { TestProviders } from '#mocks';
|
|
|
|
import { FatalError } from './FatalError';
|
|
|
|
describe('FatalError', () => {
|
|
it('renders the SharedArrayBuffer message for a non-Error AppError payload', () => {
|
|
// matches what browser-server.js posts and what initAll().catch passes
|
|
const error = {
|
|
type: 'app-init-failure',
|
|
SharedArrayBufferMissing: true,
|
|
};
|
|
|
|
render(<FatalError error={error} />, { wrapper: TestProviders });
|
|
|
|
expect(screen.getAllByText(/SharedArrayBuffer/).length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('renders the IndexedDB message for a non-Error AppError payload', () => {
|
|
const error = {
|
|
type: 'app-init-failure',
|
|
IDBFailure: true,
|
|
};
|
|
|
|
render(<FatalError error={error} />, { wrapper: TestProviders });
|
|
|
|
expect(screen.getByText(/IndexedDB/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders a backend-worker message for a BackendInitFailure', () => {
|
|
const error = {
|
|
type: 'app-init-failure',
|
|
BackendInitFailure: true,
|
|
};
|
|
|
|
render(<FatalError error={error} />, { wrapper: TestProviders });
|
|
|
|
expect(
|
|
screen.getByText(/couldn't load a critical backend worker/i),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders the generic simple message for an app-init-failure without a specific cause', () => {
|
|
const error = { type: 'app-init-failure' };
|
|
|
|
render(<FatalError error={error} />, { wrapper: TestProviders });
|
|
|
|
expect(
|
|
screen.getByText(/problem loading the app in this browser version/i),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders the UI error message for a generic Error', () => {
|
|
render(<FatalError error={new Error('boom')} />, {
|
|
wrapper: TestProviders,
|
|
});
|
|
|
|
expect(
|
|
screen.getByText(/unrecoverable error in the UI/i),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders the lazy load message for a LazyLoadFailedError', () => {
|
|
render(<FatalError error={new LazyLoadFailedError('SomeModule', null)} />, {
|
|
wrapper: TestProviders,
|
|
});
|
|
|
|
expect(
|
|
screen.getByText(/problem loading one of the chunks/i),
|
|
).toBeInTheDocument();
|
|
});
|
|
});
|