ca26af9e9d
Security: - Clear the offline IndexedDB cache on sign-in (not only logout) so a previous user's persisted query cache and Yjs page bodies cannot leak to the next user on a shared device when the prior session ended without an explicit logout. Regressions: - Remove the double Yjs title write from the AI title-generation path: the title editor is bound to the Yjs `title` fragment and the server REST update reseeds it, so the local setContent raced that reseed and doubled/garbled the title. Conventions / i18n / docs: - Remove the unused showAiMenuAtom. - Register the 3 offline-fallback strings in en-US and ru-RU. - Fix the 5 broken links to the nonexistent docs/offline-sync-plan.md. Stability / simplification: - warmInfiniteAll now reports truncation (returns false) when it hits maxPages with a cursor still pending instead of silently succeeding. - space-tree make-offline catch logs the raw error and surfaces the real cause. - Move the Offline/Mobile/CORS CHANGELOG entries from the released 0.93.0 section into [Unreleased] (CORS is a documented breaking change). - Drop the pass-through sync-flag forwarders in use-page-collab-providers; set the atoms directly. - Collapse the three isSwaggerEnabled true-cases into it.each. Tests / architecture: - Extract collabTokenNeedsRefresh (pure) and cover all four token states. - Extract shouldPropagateTitleChange and cover the collab-origin skip; add a TitleEditor render test for the static-h1 vs collaborative-editor switch. - Add a use-auth test asserting the sign-in cache purge runs before login. - Add an OFFLINE_PERSIST_ROOTS guard test asserting every persisted root maps to an exported query-key factory; route make-offline's currentUser warm through a new userKeys factory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
49 lines
1.8 KiB
TypeScript
49 lines
1.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
|
|
// The query modules transitively import the app entry (@/main.tsx) for the
|
|
// shared queryClient; mock it so importing the key factories has no side effects.
|
|
import { vi } from "vitest";
|
|
vi.mock("@/main.tsx", () => ({
|
|
queryClient: { setQueryData: vi.fn(), getQueryData: vi.fn() },
|
|
}));
|
|
|
|
import { OFFLINE_PERSIST_ROOTS } from "./query-persister";
|
|
import { pageKeys } from "@/features/page/queries/page-query";
|
|
import { spaceKeys } from "@/features/space/queries/space-query";
|
|
import { RQ_KEY } from "@/features/comment/queries/comment-query";
|
|
import { userKeys } from "@/features/user/hooks/use-current-user";
|
|
|
|
/**
|
|
* Architecture guard (#13): every string persisted via OFFLINE_PERSIST_ROOTS
|
|
* must be the ROOT (queryKey[0]) of some exported query-key factory. If a
|
|
* factory's root is renamed without updating the persist registry — or vice
|
|
* versa — offline persist/warm silently breaks (persisted keys never match the
|
|
* live queries). This turns that silent regression into a red build.
|
|
*
|
|
* Each factory is invoked with throwaway args; only queryKey[0] is inspected.
|
|
*/
|
|
function rootOf(key: readonly unknown[]): string {
|
|
return String(key[0]);
|
|
}
|
|
|
|
const FACTORY_ROOTS = new Set<string>([
|
|
rootOf(pageKeys.detail("x")),
|
|
rootOf(pageKeys.sidebar({})),
|
|
rootOf(pageKeys.rootSidebar("x")),
|
|
rootOf(pageKeys.breadcrumbs("x")),
|
|
rootOf(pageKeys.recentChanges("x")),
|
|
rootOf(spaceKeys.detail("x")),
|
|
rootOf(spaceKeys.list()),
|
|
rootOf(RQ_KEY("x")),
|
|
rootOf(userKeys.currentUser()),
|
|
]);
|
|
|
|
describe("OFFLINE_PERSIST_ROOTS is backed by real query-key factories", () => {
|
|
it("maps every persisted root to an exported factory root", () => {
|
|
const unbacked = [...OFFLINE_PERSIST_ROOTS].filter(
|
|
(root) => !FACTORY_ROOTS.has(root),
|
|
);
|
|
expect(unbacked).toEqual([]);
|
|
});
|
|
});
|