Files
edgeever/tests/web-memo-list-cache.test.ts
T
tianma-if df7931e2f6 fix(desktop): keep one new note from appearing twice
Cmd+N and a second click could both create before isPending flipped.
Sync also left the local id in the list beside the durable id, and
autofocus stopped before the editor was editable.
2026-09-16 18:18:59 +08:00

93 lines
4.0 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { QueryClient } from "@tanstack/react-query";
import type { MemoSummary } from "@edgeever/shared";
import { remapMemoIdsInLists, updateMemoSummaryInLists, type MemoListQueryData } from "../apps/web/src/lib/memo-list-cache";
const memo = (overrides: Partial<MemoSummary> = {}): MemoSummary => ({
id: "memo-apple",
notebookId: "nb-inbox",
title: "Apple developer enrollment",
excerpt: "Apple Developer Program",
tags: [],
isPinned: false,
isArchived: false,
isDeleted: false,
revision: 1,
createdAt: "2026-07-16T12:00:00.000Z",
updatedAt: "2026-07-16T12:00:00.000Z",
deletedAt: null,
...overrides,
});
const queryData = (pages: MemoSummary[][], totalCount = pages.flat().length): MemoListQueryData => ({
pages: pages.map((memos) => ({ memos, totalCount, nextCursor: null })),
pageParams: pages.map(() => null),
});
describe("memo list cache updates", () => {
test("keeps an edited note in an active full-text search until the server refetch resolves membership", () => {
const queryClient = new QueryClient();
const queryKey = ["memos", "notebook", null, "Apple", "all", "updated-desc"] as const;
queryClient.setQueryData(queryKey, queryData([[memo()]]));
updateMemoSummaryInLists(queryClient, memo({ title: "Updated title", revision: 2 }));
const cached = queryClient.getQueryData<MemoListQueryData>(queryKey);
expect(cached?.pages[0]?.memos).toHaveLength(1);
expect(cached?.pages[0]?.memos[0]?.title).toBe("Updated title");
expect(cached?.pages[0]?.memos[0]?.revision).toBe(2);
});
test("does not optimistically insert an unseen note into a full-text search", () => {
const queryClient = new QueryClient();
const queryKey = ["memos", "notebook", null, "Apple", "all", "updated-desc"] as const;
queryClient.setQueryData(queryKey, queryData([[]], 0));
updateMemoSummaryInLists(queryClient, memo());
expect(queryClient.getQueryData<MemoListQueryData>(queryKey)?.pages[0]?.memos).toEqual([]);
});
test("deduplicates the same note ID if overlapping infinite-query pages contain it", () => {
const queryClient = new QueryClient();
const queryKey = ["memos", "notebook", null, "", "all", "updated-desc"] as const;
queryClient.setQueryData(queryKey, queryData([[memo()], [memo({ revision: 2 })]], 1));
updateMemoSummaryInLists(queryClient, memo({ revision: 3 }));
const cached = queryClient.getQueryData<MemoListQueryData>(queryKey);
const flattened = cached?.pages.flatMap((page) => page.memos) ?? [];
expect(flattened).toHaveLength(1);
expect(flattened[0]?.revision).toBe(3);
expect(cached?.pages[0]?.totalCount).toBe(1);
});
test("replaces a temporary create id so the list does not show two empty notes", () => {
const queryClient = new QueryClient();
const queryKey = ["memos", "notebook", "nb-inbox", "", "all", "updated-desc"] as const;
queryClient.setQueryData(queryKey, queryData([
[memo({ id: "memo_remote_1", title: "", excerpt: "" }), memo({ id: "memo_local_1", title: "", excerpt: "" })],
], 2));
remapMemoIdsInLists(queryClient, new Map([["memo_local_1", "memo_remote_1"]]));
const cached = queryClient.getQueryData<MemoListQueryData>(queryKey);
const flattened = cached?.pages.flatMap((page) => page.memos) ?? [];
expect(flattened).toHaveLength(1);
expect(flattened[0]?.id).toBe("memo_remote_1");
expect(cached?.pages[0]?.totalCount).toBe(1);
});
test("removes a note when an edit no longer matches the selected exact tag", () => {
const queryClient = new QueryClient();
const queryKey = ["memos", "notebook", null, "", "all", "updated-desc", [], "demo"] as const;
queryClient.setQueryData(queryKey, queryData([[memo({ tags: ["Demo"] })]], 1));
updateMemoSummaryInLists(queryClient, memo({ tags: ["demo-extra"], revision: 2 }));
const cached = queryClient.getQueryData<MemoListQueryData>(queryKey);
expect(cached?.pages[0]?.memos).toEqual([]);
expect(cached?.pages[0]?.totalCount).toBe(0);
});
});