Skip to content
This repository was archived by the owner on Sep 9, 2025. It is now read-only.

Commit 352eeb3

Browse files
authored
Merge pull request #1964 from skaut/unknown-error-detail
Printing unknown error messages
2 parents 01117dc + 43a4d1b commit 352eeb3

File tree

7 files changed

+126
-12
lines changed

7 files changed

+126
-12
lines changed

eslint.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ export default tseslint.config(
209209
parser: tseslint.parser,
210210
},
211211
},
212+
rules: {
213+
"@typescript-eslint/no-confusing-void-expression": "off",
214+
},
212215
},
213216
{
214217
files: ["tests/backend/**/*.ts"],

src/backend/move.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,17 @@ export function move(
6363
}
6464
state.destroyState();
6565
return { response: { errors }, status: "success" };
66-
} catch (e) {
67-
return { status: "error", type: "DriveAPIError" };
66+
} catch (e: unknown) {
67+
return {
68+
detail:
69+
typeof e === "object" &&
70+
e !== null &&
71+
"message" in e &&
72+
typeof e.message === "string"
73+
? e.message
74+
: undefined,
75+
status: "error",
76+
type: "DriveAPIError",
77+
};
6878
}
6979
}

src/frontend/App.svelte

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import BackButton from "./BackButton.svelte";
1717
import Confirmation from "./Confirmation.svelte";
1818
import ContinueButton from "./ContinueButton.svelte";
19+
import Details from "./Details.svelte";
1920
import Done from "./Done.svelte";
2021
import FolderSelection from "./FolderSelection.svelte";
2122
import Introduction from "./Introduction.svelte";
@@ -44,7 +45,8 @@
4445
let moving = $state(false);
4546
let movingComponent: Moving | undefined = $state();
4647
let errorDialogOpen = $state(false);
47-
let errorMessage = $state("");
48+
let errorDialogMessage = $state("");
49+
let errorDialogDetail: string | undefined = $state(undefined);
4850
4951
let progress = $derived(
5052
currentTab === "introduction"
@@ -66,8 +68,9 @@
6668
let destination: NamedRecord | null = $state(null);
6769
let errors: Array<MoveError> | null = $state(null);
6870
69-
function showErrorDialog(message: string): void {
70-
errorMessage = message;
71+
function showErrorDialog(message: string, details?: string): void {
72+
errorDialogMessage = message;
73+
errorDialogDetail = details;
7174
errorDialogOpen = true;
7275
}
7376
@@ -77,7 +80,7 @@
7780
switch (response.type) {
7881
case "DriveAPIError":
7982
currentTab = "confirmation";
80-
showErrorDialog(m.errorDialog_DriveAPIError());
83+
showErrorDialog(m.errorDialog_DriveAPIError(), response.detail);
8184
break;
8285
case "invalidParameter":
8386
currentTab = "confirmation";
@@ -244,7 +247,16 @@
244247
{m.errorDialog_title()}
245248
</DialogTitle>
246249
<Content id="errorDialogContent">
247-
{errorMessage}
250+
{errorDialogMessage}
251+
{#if errorDialogDetail !== undefined}
252+
<br />
253+
<br />
254+
<Details>
255+
<span>
256+
{errorDialogDetail}
257+
</span>
258+
</Details>
259+
{/if}
248260
</Content>
249261
<Actions>
250262
<Button>
@@ -264,6 +276,10 @@
264276
--mdc-theme-secondary: #ff5252; /* Red A200 */
265277
}
266278
279+
span {
280+
white-space: pre-line;
281+
}
282+
267283
.tab {
268284
margin: 50px;
269285
}

src/frontend/Details.svelte

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script lang="ts">
2+
import type { Snippet } from "svelte";
3+
4+
import Button, { Icon, Label } from "@smui/button";
5+
6+
interface Props {
7+
children: Snippet;
8+
}
9+
let { children }: Props = $props();
10+
11+
let open = $state(false);
12+
</script>
13+
14+
<Button
15+
onclick={(): void => {
16+
open = !open;
17+
}}
18+
>
19+
<Icon class="material-icons">
20+
{#if open}
21+
expand_more
22+
{:else}
23+
chevron_right
24+
{/if}
25+
</Icon>
26+
<Label>Details</Label>
27+
</Button>
28+
<br />
29+
{#if open}
30+
{@render children()}
31+
{/if}

src/interfaces/Response.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
export type Response<RawResponse, ErrorType extends string> =
22
| {
3-
response: RawResponse;
4-
status: "success";
5-
}
6-
| {
3+
detail?: string;
74
status: "error";
85
type: ErrorType;
6+
}
7+
| {
8+
response: RawResponse;
9+
status: "success";
910
};

tests/backend/move.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,11 @@ test("move detects and reports source matching destination", () => {
294294

295295
test("move fails gracefully on error when checking folder emptiness", () => {
296296
vi.mocked(folderManagement).isFolderEmpty_.mockImplementationOnce(() => {
297-
throw new Error();
297+
throw new Error("ERROR_DETAIL");
298298
});
299299

300300
expect(move("SRC_ID", "DEST_ID", false, false, false)).toStrictEqual({
301+
detail: "ERROR_DETAIL",
301302
status: "error",
302303
type: "DriveAPIError",
303304
});

tests/frontend/move-api-error.spec.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,55 @@ test("works with an API error", async ({ page }) => {
4747
page.getByText("An error occurred in Google Drive"),
4848
).toBeVisible();
4949
});
50+
51+
test("works with an API error with detail", async ({ page }) => {
52+
await page.goto("/");
53+
await setup(page);
54+
55+
await page.evaluate(() => {
56+
window._endpointStubs.listSharedDrives = [
57+
{
58+
status: "success",
59+
value: { response: [], status: "success" },
60+
},
61+
{
62+
status: "success",
63+
value: { response: [], status: "success" },
64+
},
65+
];
66+
window._endpointStubs.move = [
67+
{
68+
status: "success",
69+
value: {
70+
detail: "Error detail\nSecond line",
71+
status: "error",
72+
type: "DriveAPIError",
73+
},
74+
},
75+
];
76+
});
77+
78+
await expect(
79+
page.getByText("Shared drive mover", { exact: true }),
80+
).toBeVisible();
81+
await page.getByText("Continue").click();
82+
await page.getByText("My Drive").click();
83+
await page.getByText("Continue").click();
84+
await page.getByText("My Drive").click();
85+
await page.getByText("Continue").click();
86+
await expect(
87+
page.getByText(
88+
'contents of the folder "My Drive" into the folder "My Drive"',
89+
),
90+
).toBeVisible();
91+
await page.getByText("Move", { exact: true }).click();
92+
await expect(page.getByText("Confirmation", { exact: true })).toBeVisible();
93+
await expect(
94+
page.getByText("An error occurred", { exact: true }),
95+
).toBeVisible();
96+
await expect(
97+
page.getByText("An error occurred in Google Drive"),
98+
).toBeVisible();
99+
await page.getByText("Details").click();
100+
await expect(page.getByText("Error detail")).toBeVisible();
101+
});

0 commit comments

Comments
 (0)