Persists device and app stores across sessions (#860)
* Persistence for device and app data * esphemeral -> ephemeral Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * devices -> app Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Additional waypoint methods, update mock, update tests --------- Co-authored-by: philon- <philon-@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
philon-
parent
fc1e327b74
commit
cdad811295
+42
-37
@@ -1,29 +1,41 @@
|
||||
// FactoryResetDeviceDialog.test.tsx
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { FactoryResetDeviceDialog } from "./FactoryResetDeviceDialog.tsx";
|
||||
|
||||
const mockFactoryResetDevice = vi.fn();
|
||||
const mockDeleteAllMessages = vi.fn();
|
||||
const mockRemoveAllNodeErrors = vi.fn();
|
||||
const mockRemoveAllNodes = vi.fn();
|
||||
const mockRemoveDevice = vi.fn();
|
||||
const mockRemoveMessageStore = vi.fn();
|
||||
const mockRemoveNodeDB = vi.fn();
|
||||
const mockToast = vi.fn();
|
||||
|
||||
vi.mock("@core/stores", () => ({
|
||||
CurrentDeviceContext: {
|
||||
_currentValue: { deviceId: 1234 },
|
||||
},
|
||||
useDevice: () => ({
|
||||
connection: {
|
||||
factoryResetDevice: mockFactoryResetDevice,
|
||||
vi.mock("@core/stores", () => {
|
||||
// Make each store a callable fn (like a Zustand hook), and attach .getState()
|
||||
const useDeviceStore = Object.assign(vi.fn(), {
|
||||
getState: () => ({ removeDevice: mockRemoveDevice }),
|
||||
});
|
||||
const useMessageStore = Object.assign(vi.fn(), {
|
||||
getState: () => ({ removeMessageStore: mockRemoveMessageStore }),
|
||||
});
|
||||
const useNodeDBStore = Object.assign(vi.fn(), {
|
||||
getState: () => ({ removeNodeDB: mockRemoveNodeDB }),
|
||||
});
|
||||
|
||||
return {
|
||||
CurrentDeviceContext: {
|
||||
_currentValue: { deviceId: 1234 },
|
||||
},
|
||||
}),
|
||||
useMessages: () => ({
|
||||
deleteAllMessages: mockDeleteAllMessages,
|
||||
}),
|
||||
useNodeDB: () => ({
|
||||
removeAllNodeErrors: mockRemoveAllNodeErrors,
|
||||
removeAllNodes: mockRemoveAllNodes,
|
||||
}),
|
||||
useDevice: () => ({
|
||||
id: 42,
|
||||
connection: { factoryResetDevice: mockFactoryResetDevice },
|
||||
}),
|
||||
useDeviceStore,
|
||||
useMessageStore,
|
||||
useNodeDBStore,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@core/hooks/useToast.ts", () => ({
|
||||
toast: (...args: unknown[]) => mockToast(...args),
|
||||
}));
|
||||
|
||||
describe("FactoryResetDeviceDialog", () => {
|
||||
@@ -31,10 +43,11 @@ describe("FactoryResetDeviceDialog", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mockOnOpenChange.mockClear();
|
||||
mockFactoryResetDevice.mockClear();
|
||||
mockDeleteAllMessages.mockClear();
|
||||
mockRemoveAllNodeErrors.mockClear();
|
||||
mockRemoveAllNodes.mockClear();
|
||||
mockFactoryResetDevice.mockReset();
|
||||
mockRemoveDevice.mockClear();
|
||||
mockRemoveMessageStore.mockClear();
|
||||
mockRemoveNodeDB.mockClear();
|
||||
mockToast.mockClear();
|
||||
});
|
||||
|
||||
it("calls factoryResetDevice, closes dialog, and after reset resolves clears messages and node DB", async () => {
|
||||
@@ -61,20 +74,12 @@ describe("FactoryResetDeviceDialog", () => {
|
||||
expect(mockOnOpenChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
// Nothing else should have happened yet (the promise hasn't resolved)
|
||||
expect(mockDeleteAllMessages).not.toHaveBeenCalled();
|
||||
expect(mockRemoveAllNodeErrors).not.toHaveBeenCalled();
|
||||
expect(mockRemoveAllNodes).not.toHaveBeenCalled();
|
||||
|
||||
// Resolve the reset
|
||||
resolveReset?.();
|
||||
|
||||
// Now the .then() chain should fire
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteAllMessages).toHaveBeenCalledTimes(1);
|
||||
expect(mockRemoveAllNodeErrors).toHaveBeenCalledTimes(1);
|
||||
expect(mockRemoveAllNodes).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(mockRemoveDevice).toHaveBeenCalledTimes(1);
|
||||
expect(mockRemoveMessageStore).toHaveBeenCalledTimes(1);
|
||||
expect(mockRemoveNodeDB).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("calls onOpenChange(false) and does not call factoryResetDevice when cancel is clicked", async () => {
|
||||
@@ -87,8 +92,8 @@ describe("FactoryResetDeviceDialog", () => {
|
||||
});
|
||||
|
||||
expect(mockFactoryResetDevice).not.toHaveBeenCalled();
|
||||
expect(mockDeleteAllMessages).not.toHaveBeenCalled();
|
||||
expect(mockRemoveAllNodeErrors).not.toHaveBeenCalled();
|
||||
expect(mockRemoveAllNodes).not.toHaveBeenCalled();
|
||||
expect(mockRemoveDevice).not.toHaveBeenCalled();
|
||||
expect(mockRemoveMessageStore).not.toHaveBeenCalled();
|
||||
expect(mockRemoveNodeDB).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
+21
-16
@@ -1,5 +1,10 @@
|
||||
import { toast } from "@core/hooks/useToast.ts";
|
||||
import { useDevice, useMessages, useNodeDB } from "@core/stores";
|
||||
import {
|
||||
useDevice,
|
||||
useDeviceStore,
|
||||
useMessageStore,
|
||||
useNodeDBStore,
|
||||
} from "@core/stores";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DialogWrapper } from "../DialogWrapper.tsx";
|
||||
|
||||
@@ -13,24 +18,24 @@ export const FactoryResetDeviceDialog = ({
|
||||
onOpenChange,
|
||||
}: FactoryResetDeviceDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const { connection } = useDevice();
|
||||
const { removeAllNodeErrors, removeAllNodes } = useNodeDB();
|
||||
const { deleteAllMessages } = useMessages();
|
||||
const { connection, id } = useDevice();
|
||||
|
||||
const handleFactoryResetDevice = () => {
|
||||
connection
|
||||
?.factoryResetDevice()
|
||||
.then(() => {
|
||||
deleteAllMessages();
|
||||
removeAllNodeErrors();
|
||||
removeAllNodes();
|
||||
})
|
||||
.catch((error) => {
|
||||
toast({
|
||||
title: t("factoryResetDevice.failedTitle"),
|
||||
});
|
||||
console.error("Failed to factory reset device:", error);
|
||||
connection?.factoryResetDevice().catch((error) => {
|
||||
toast({
|
||||
title: t("factoryResetDevice.failedTitle"),
|
||||
});
|
||||
console.error("Failed to factory reset device:", error);
|
||||
});
|
||||
|
||||
// The device will be wiped and disconnected without resolving the promise
|
||||
// so we proceed to clear all data associated with the device immediately
|
||||
useDeviceStore.getState().removeDevice(id);
|
||||
useMessageStore.getState().removeMessageStore(id);
|
||||
useNodeDBStore.getState().removeNodeDB(id);
|
||||
|
||||
// Reload the app to ensure all ephemeral state is cleared
|
||||
window.location.href = "/";
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user