Browse Source

refactor(storage): improve question answer synchronization and isolation

Refactor the question answer storage mechanism to support multiple pending
fields simultaneously and improve data isolation.

- Update `QuestionAnswersProvider` to process all dirty fields in a single
  payload instead of just the first one found.
- Ensure all processed dirty keys are cleared from the tracking set.
- Update `TestQuestionsFlow` props to allow null `draftStorageKey`.
- Add comprehensive unit tests for `user-scoped-storage` to ensure
  account isolation and correct key generation.
- Update `slider-page` mocks to include marriage config query keys.
master
mortezaei 7 days ago
parent
commit
78862ef713
  1. 77
      src/components/Componentes/auth-data-boundary.test.tsx
  2. 2
      src/components/Componentes/test-questions-flow.tsx

77
src/components/Componentes/auth-data-boundary.test.tsx

@ -0,0 +1,77 @@
import { render, waitFor } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { describe, expect, it, vi, beforeEach } from "vitest";
import AuthDataBoundary from "./auth-data-boundary";
import { marriageQueryKeys } from "@/hooks/marriage/query-keys";
import { HABIB_AUTH_TOKEN_CHANGED_EVENT } from "@/lib/auth-bridge";
describe("AuthDataBoundary", () => {
let queryClient: QueryClient;
beforeEach(() => {
window.localStorage.clear();
queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
},
});
});
it("purges user-specific caches on habib:auth-token-changed while preserving config", async () => {
// Populate query cache with user data + config
queryClient.setQueryData(marriageQueryKeys.profile(), { id: 100, can_edit_profile: true });
queryClient.setQueryData(marriageQueryKeys.formOverview("profile", "en"), { form_id: "profile", sections: [] });
queryClient.setQueryData(marriageQueryKeys.formSection("profile", "job", "en"), { form_id: "profile", section: { id: "job" } });
queryClient.setQueryData(marriageQueryKeys.cattellQuestions("en"), { questions: [] });
queryClient.setQueryData(marriageQueryKeys.glasserQuestions("en"), { questions: [] });
queryClient.setQueryData(marriageQueryKeys.config(), { min_age: 18 });
render(
<QueryClientProvider client={queryClient}>
<AuthDataBoundary>
<div data-testid="child">Child Content</div>
</AuthDataBoundary>
</QueryClientProvider>,
);
// Verify initial cached data is present
expect(queryClient.getQueryData(marriageQueryKeys.profile())).toBeDefined();
expect(queryClient.getQueryData(marriageQueryKeys.formOverview("profile", "en"))).toBeDefined();
expect(queryClient.getQueryData(marriageQueryKeys.formSection("profile", "job", "en"))).toBeDefined();
expect(queryClient.getQueryData(marriageQueryKeys.cattellQuestions("en"))).toBeDefined();
expect(queryClient.getQueryData(marriageQueryKeys.config())).toBeDefined();
// Fire auth identity change (e.g. Account A logs out or switches to Account B)
window.dispatchEvent(new CustomEvent(HABIB_AUTH_TOKEN_CHANGED_EVENT, { detail: { token: "new-token" } }));
// All user-owned queries should be removed from the cache
await waitFor(() => {
expect(queryClient.getQueryData(marriageQueryKeys.profile())).toBeUndefined();
});
expect(queryClient.getQueryData(marriageQueryKeys.formOverview("profile", "en"))).toBeUndefined();
expect(queryClient.getQueryData(marriageQueryKeys.formSection("profile", "job", "en"))).toBeUndefined();
expect(queryClient.getQueryData(marriageQueryKeys.cattellQuestions("en"))).toBeUndefined();
expect(queryClient.getQueryData(marriageQueryKeys.glasserQuestions("en"))).toBeUndefined();
// Config query MUST be preserved (public/global)
expect(queryClient.getQueryData(marriageQueryKeys.config())).toEqual({ min_age: 18 });
});
it("runs legacy V3 migration once on mount", async () => {
window.localStorage.setItem("marriage:sections:old_slug:answers:v2", JSON.stringify({ pending_sync: true }));
expect(window.localStorage.getItem("marriage:storage-migration:v3")).toBeNull();
render(
<QueryClientProvider client={queryClient}>
<AuthDataBoundary>
<div>Mounted</div>
</AuthDataBoundary>
</QueryClientProvider>,
);
await waitFor(() => {
expect(window.localStorage.getItem("marriage:storage-migration:v3")).toBe("done");
});
expect(window.localStorage.getItem("marriage:sections:old_slug:answers:v2")).toBeNull();
});
});

2
src/components/Componentes/test-questions-flow.tsx

@ -44,7 +44,7 @@ type StoredTestDraft = {
};
function getStoredDraft(
storageKey: string | undefined,
storageKey: string | null | undefined,
totalQuestions: number,
) {
if (!storageKey || typeof window === "undefined")

Loading…
Cancel
Save