feat(webdav): add robust auto sync with failure feedback

(cherry picked from commit bb6760124a62a964b36902c004e173534910728f)
This commit is contained in:
saladday
2026-02-14 18:34:05 +08:00
parent 7b20c17ea4
commit ff29b939a8
17 changed files with 495 additions and 18 deletions
@@ -34,6 +34,17 @@ vi.mock("@/components/ui/input", () => ({
Input: (props: any) => <input {...props} />,
}));
vi.mock("@/components/ui/switch", () => ({
Switch: ({ checked, onCheckedChange, ...props }: any) => (
<button
role="switch"
aria-checked={checked}
onClick={() => onCheckedChange?.(!checked)}
{...props}
/>
),
}));
vi.mock("@/components/ui/select", () => ({
Select: ({ value, onValueChange, children }: any) => (
<select
@@ -82,6 +93,7 @@ const baseConfig: WebDavSyncSettings = {
password: "secret",
remoteRoot: "cc-switch-sync",
profile: "default",
autoSync: false,
status: {},
};
@@ -128,6 +140,21 @@ describe("WebdavSyncSection", () => {
settingsApiMock.webdavSyncDownload.mockResolvedValue({ status: "downloaded" });
});
it("shows auto sync error callout when last auto sync failed", () => {
renderSection({
...baseConfig,
status: {
lastError: "network timeout",
lastErrorSource: "auto",
},
});
expect(
screen.getByText("settings.webdavSync.autoSyncLastErrorTitle"),
).toBeInTheDocument();
expect(screen.getByText("network timeout")).toBeInTheDocument();
});
it("shows validation error when saving without base url", async () => {
renderSection({ ...baseConfig, baseUrl: "" });
@@ -150,6 +177,7 @@ describe("WebdavSyncSection", () => {
baseUrl: "https://dav.example.com/dav/",
username: "alice",
password: "secret",
autoSync: false,
}),
false,
);
@@ -166,6 +194,24 @@ describe("WebdavSyncSection", () => {
);
});
it("saves auto sync as true after toggle", async () => {
renderSection(baseConfig);
fireEvent.click(
screen.getByRole("switch", { name: "settings.webdavSync.autoSync" }),
);
fireEvent.click(screen.getByRole("button", { name: "settings.webdavSync.save" }));
await waitFor(() => {
expect(settingsApiMock.webdavSyncSaveSettings).toHaveBeenCalledWith(
expect.objectContaining({
autoSync: true,
}),
false,
);
});
});
it("blocks upload when there are unsaved changes", async () => {
renderSection(baseConfig);
+21
View File
@@ -209,4 +209,25 @@ describe("App integration with MSW", () => {
expect(toastErrorMock).not.toHaveBeenCalled();
expect(toastSuccessMock).toHaveBeenCalled();
});
it("shows toast when auto sync fails in background", async () => {
const { default: App } = await import("@/App");
renderApp(App);
await waitFor(() =>
expect(screen.getByTestId("provider-list").textContent).toContain(
"claude-1",
),
);
emitTauriEvent("webdav-sync-status-updated", {
source: "auto",
status: "error",
error: "network timeout",
});
await waitFor(() => {
expect(toastErrorMock).toHaveBeenCalled();
});
});
});