Files
actual/packages/desktop-client/src/components/FatalError.test.tsx
425db2d94d [AI] Recover from BackendInitFailure and show a meaningful error (#7761)
* [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>
2026-05-11 17:06:01 +00:00

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();
});
});