Explicitly test for console warnings & errors (#6502)

# Description of Changes
Disallow warnings and errors from being thrown in the browser console
during tests unless explicitly expected in the test. Also adds a
Playwright test to prod around some main UI areas and checks that no
warnings/errors have been thrown.
This commit is contained in:
James Brunton
2026-06-09 08:34:02 +00:00
committed by GitHub
parent 002de06411
commit 0e3cbb3cf2
28 changed files with 544 additions and 213 deletions
@@ -1,5 +1,6 @@
import { renderHook } from "@testing-library/react";
import { act, renderHook } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { expectConsole } from "@app/tests/failOnConsole";
// ── Mocks (hoisted by vi.mock) ──────────────────────────────────────────────
const invokeMock = vi.fn();
@@ -46,13 +47,21 @@ async function flushMicrotasks() {
const AUTO_FAILURE_KEY = "stirling-pdf-updater:autoFailedAt";
/** Run the hook through its startup timer + async chain. */
/**
* Run the hook through its startup timer + async chain. The post-render
* timer-driven async work is wrapped in act() so the state updates it
* triggers don't surface as React "not wrapped in act" warnings.
* renderHook already wraps the initial render in act internally, so it
* stays outside.
*/
async function runStartup() {
renderHook(() => useDesktopUpdatePopup());
await vi.advanceTimersByTimeAsync(16_000);
await flushMicrotasks();
await vi.advanceTimersByTimeAsync(0);
await flushMicrotasks();
await act(async () => {
await vi.advanceTimersByTimeAsync(16_000);
await flushMicrotasks();
await vi.advanceTimersByTimeAsync(0);
await flushMicrotasks();
});
}
describe("useDesktopUpdatePopup — auto mode", () => {
@@ -83,6 +92,7 @@ describe("useDesktopUpdatePopup — auto mode", () => {
});
it("does NOT call restart_app when download_and_install_update fails", async () => {
expectConsole.error(/\[useDesktopInstall\] Install failed/);
invokeMock.mockImplementation((cmd: string) => {
if (cmd === "check_for_update") {
return Promise.resolve({
@@ -132,6 +142,9 @@ describe("useDesktopUpdatePopup — auto mode", () => {
});
it("skips the install entirely when a recent failure is within the backoff window", async () => {
expectConsole.warn(
/\[DesktopUpdatePopup\] auto-update skipped: recent failure within backoff window/,
);
// Recorded 1 hour ago — well inside the 6-hour backoff.
const oneHourAgo = Date.now() - 60 * 60 * 1000;
window.localStorage.setItem(AUTO_FAILURE_KEY, String(oneHourAgo));
@@ -1,6 +1,7 @@
import type { AxiosInstance } from "axios";
import { describe, expect, test, vi, beforeEach, afterEach } from "vitest";
import { OPEN_SIGN_IN_EVENT } from "@app/constants/signInEvents";
import { expectConsole } from "@app/tests/failOnConsole";
// Exercise the error-interceptor logic against a hand-rolled axios-like
// client; transitive imports are mocked out so this is a pure unit test.
@@ -120,6 +121,7 @@ describe("desktop apiClientSetup - 401 silent-path", () => {
});
test("POST 401 without Authorization dispatches sign-in modal event", async () => {
expectConsole.warn(/\[apiClientSetup\] 401 on path/);
const { client, handlers } = makeMockClient();
setupApiInterceptors(client as unknown as AxiosInstance);
await triggerErrorInterceptor(handlers, {
@@ -135,6 +137,7 @@ describe("desktop apiClientSetup - 401 silent-path", () => {
});
test("GET 401 without Authorization stays silent (background probe)", async () => {
expectConsole.warn(/\[apiClientSetup\] 401 on path/);
const { client, handlers } = makeMockClient();
setupApiInterceptors(client as unknown as AxiosInstance);
await triggerErrorInterceptor(handlers, {
@@ -159,6 +162,7 @@ describe("desktop apiClientSetup - 401 silent-path", () => {
});
test("401 with skipAuthRedirect set never dispatches the modal", async () => {
expectConsole.warn(/\[apiClientSetup\] 401 on path/);
const { client, handlers } = makeMockClient();
setupApiInterceptors(client as unknown as AxiosInstance);
await triggerErrorInterceptor(handlers, {