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
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { expect, test } from "bun:test";

test("root failures retain real translation context and report the original error", () => {
const result = Bun.spawnSync({
cmd: [process.execPath, `${import.meta.dir}/fixtures/route-errors.tsx`],
env: { ...process.env, NODE_ENV: "test" },
});
expect(result.exitCode, result.stderr.toString()).toBe(0);
expect(result.stdout.toString().trim()).toBe("passed");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { I18nProvider } from "@lingui/react";
import { i18n, initI18n } from "@superset/i18n";
import { type AnyRouter, RouterProvider } from "@tanstack/react-router";

if (!i18n.locale) initI18n();

export function RendererRouter({ router }: { router: AnyRouter }) {
return (
<I18nProvider i18n={i18n}>
<RouterProvider router={router} />
</I18nProvider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { mock } from "bun:test";
import { transformAsync } from "@babel/core";
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import linguiMacro from "@lingui/babel-plugin-lingui-macro";
import { plugin } from "bun";
import type { ReactNode } from "react";

plugin({
name: "real-lingui-react-macros",
setup(build) {
build.onLoad({ filter: /\/(error|not-found)\.tsx$/ }, async ({ path }) => {
if (path.includes("/node_modules/")) return;
const code = await Bun.file(path).text();
if (!/@lingui\/(react|core)\/macro/.test(code)) return;
const result = await transformAsync(code, {
filename: path,
babelrc: false,
configFile: false,
parserOpts: { plugins: ["typescript", "jsx"] },
plugins: [linguiMacro],
});
if (!result?.code) throw new Error(`Could not compile ${path}`);
return { contents: result.code, loader: "tsx" };
});
},
});

GlobalRegistrator.register({ url: "http://localhost" });
(
globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;

const captured: unknown[] = [];
mock.module("@sentry/electron/renderer", () => ({
captureException: (error: unknown) => captured.push(error),
}));
const originalError = new Error("Original route failure");
let failLayout = false;
mock.module("../../../routes/-layout", () => ({
RootLayout: ({ children }: { children: ReactNode }) => {
if (failLayout) throw originalError;
return children;
},
}));

const { act, cleanup, fireEvent, render, waitFor } = await import(
"@testing-library/react"
);
const { createMemoryHistory, createRoute, createRouter } = await import(
"@tanstack/react-router"
);
const { QueryClient } = await import("@tanstack/react-query");
const { i18n, initI18nAsync } = await import("@superset/i18n");
const { RendererRouter } = await import("../RendererRouter");
const { Route } = await import("../../../routes/__root");

const home = createRoute({
getParentRoute: () => Route,
path: "/",
component: () => <div data-testid="recovered-home" />,
});
const broken = createRoute({
getParentRoute: () => Route,
path: "/broken",
loader: () => {
throw originalError;
},
});
const routeTree = Route.addChildren([home, broken]);
const consoleErrors: unknown[][] = [];
console.error = (...args: unknown[]) => consoleErrors.push(args);
console.warn = () => {};

function check(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}

try {
for (const failure of ["layout", "loader", "not-found"] as const) {
captured.length = 0;
consoleErrors.length = 0;
failLayout = failure === "layout";
await initI18nAsync("fr");
const router = createRouter({
routeTree,
history: createMemoryHistory({
initialEntries: [
failure === "loader"
? "/broken"
: failure === "not-found"
? "/missing"
: "/",
],
}),
context: { queryClient: new QueryClient() },
});
await act(async () => {
render(<RendererRouter router={router} />);
await router.load();
});
const heading =
failure === "not-found" ? "Page introuvable" : "Une erreur est survenue";
await waitFor(() =>
check(
document.body.textContent?.includes(heading),
`${failure}: missing translated fallback`,
),
);
check(i18n.locale === "fr", `${failure}: fallback reset the active locale`);
if (failure !== "not-found") {
await waitFor(() =>
check(
captured.includes(originalError),
`${failure}: original error not reported`,
),
);
await act(async () => {
fireEvent.click(
document.querySelector(
"button[aria-controls='error-details']",
) as Element,
);
});
check(
document
.querySelector("pre")
?.textContent?.includes(originalError.stack ?? originalError.message),
`${failure}: original error details missing`,
);
} else {
check(captured.length === 0, "404 was reported as a crash");
}
check(
!consoleErrors.some((args) =>
args.some((arg) => /useLingui|I18nProvider/.test(String(arg))),
),
`${failure}: translation context failed`,
);
if (failure !== "layout") {
await act(async () => {
fireEvent.click(document.querySelector("a") as Element);
});
await waitFor(() =>
check(
document.querySelector('[data-testid="recovered-home"]'),
"Go home did not recover",
),
);
}
cleanup();
}
console.log("passed");
} finally {
cleanup();
await GlobalRegistrator.unregister();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { RendererRouter } from "./RendererRouter";
5 changes: 3 additions & 2 deletions apps/desktop/src/renderer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { initSentry } from "./lib/sentry";

initSentry();

import { createRouter, RouterProvider } from "@tanstack/react-router";
import { createRouter } from "@tanstack/react-router";
import ReactDom from "react-dom/client";
import { BootErrorBoundary } from "./components/BootErrorBoundary";
import { RendererRouter } from "./components/RendererRouter";
import {
cleanupBootErrorHandling,
initBootErrorHandling,
Expand Down Expand Up @@ -85,7 +86,7 @@ if (!rootElement) {
<BootErrorBoundary
onError={(error) => reportBootError("Render failed", error)}
>
<RouterProvider router={router} />
<RendererRouter router={router} />
</BootErrorBoundary>,
);
markBootMounted();
Expand Down
Loading