Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cap-error-list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solidjs/start-devtools': patch
---

The toolbar keeps the 200 newest errors, so an error thrown in a loop no longer grows the list without end.
19 changes: 19 additions & 0 deletions src/dev-toolbar/error-list.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, expect, it } from 'vitest';
import { addError, MAX_ERRORS } from './error-list.js';

describe('addError', () => {
it('puts the new error first', () => {
expect(addError(['b', 'a'], 'c')).toEqual(['c', 'b', 'a']);
});

it('drops the oldest past the limit', () => {
expect(addError(['c', 'b', 'a'], 'd', 3)).toEqual(['d', 'c', 'b']);
});

it('keeps 200 by default', () => {
let list: number[] = [];
for (let i = 0; i < MAX_ERRORS + 5; i++) list = addError(list, i);
expect(list).toHaveLength(MAX_ERRORS);
expect(list[0]).toBe(MAX_ERRORS + 4);
});
});
12 changes: 12 additions & 0 deletions src/dev-toolbar/error-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** Most errors the toolbar keeps. An error thrown in a loop drops the oldest. */
export const MAX_ERRORS = 200;

/** The list with `error` first, keeping at most `max` of the newest. */
export function addError<T>(list: readonly T[], error: T, max = MAX_ERRORS): T[] {
const next = [error];
for (const item of list) {
if (next.length >= max) break;
next.push(item);
}
return next;
}
3 changes: 2 additions & 1 deletion src/dev-toolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { installElementTracking, type InspectedComponent } from './inspector/loo
import { InspectorOverlay } from './inspector/overlay.js';
import { addIssues, captureIssues, type IssueEntry } from './issues/tracker.js';
import { isExtensionError } from './extension-errors.js';
import { addError } from './error-list.js';
import { openInEditor } from './open-in-editor.js';
import { CopyReportButton } from './report/CopyReportButton.js';
import { pageRoute } from './report/markdown.js';
Expand Down Expand Up @@ -353,7 +354,7 @@ export function DevToolbar(props: DevToolbarProps) {

function pushError(error: unknown) {
console.error(error);
setErrors((current) => [error, ...current]);
setErrors((current) => addError(current, error));

show('err');
}
Expand Down
20 changes: 20 additions & 0 deletions tests/e2e/devtools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,26 @@ test('leaves out errors thrown by browser extensions', async ({ page }) => {
);
});

test('keeps the newest errors when an error repeats in a loop', async ({ page }) => {
await page.goto('/');

// An error thrown on every frame of a loop would grow the list without end.
await page.evaluate(() => {
for (let i = 1; i <= 205; i++) {
const error = new Error(`frame ${i}`);
window.dispatchEvent(new ErrorEvent('error', { error, message: error.message }));
}
});

const tab = page.getByRole('tab', { name: 'Errors', exact: true });
await expect(tab).toHaveAttribute('aria-selected', 'true');
await expect(tab).toContainText('200');
// The newest is shown first, and the oldest five are gone.
await expect(page.locator('[data-solid-error-viewer-error-info-message]')).toHaveText(
'frame 205',
);
});

test('captures client errors', async ({ page }) => {
await page.goto('/');
await page.locator('#throw-client-error').click();
Expand Down
Loading