Files
twitter-lite/src/features/research/use-citation-navigation.test.ts
T

133 lines
4.1 KiB
TypeScript

import { act, renderHook } from "@testing-library/react";
import { expect, it, vi } from "vitest";
import type { ColumnRegistry } from "../decks/column-tools";
import type { DeckColumn, DeckView, Workspace } from "../decks/model";
import type { ResearchCitation } from "./model";
import { useCitationNavigation } from "./use-citation-navigation";
vi.mock("../navigation/use-url-state", () => ({
useUrlState: () => [{ citation: "twitter:123" }, vi.fn<() => void>()],
}));
const column: DeckColumn = {
id: "column",
title: "Research",
connectionId: "twitter",
source: {
platform: "twitter",
kind: "search",
query: "WebMCP",
product: "Latest",
following: false,
},
};
const citation: ResearchCitation = {
column,
post: {
key: "twitter:123",
nativeId: "123",
platform: "twitter",
url: "https://x.com/alice/status/123",
text: "Snapshot",
author: { name: "Alice", handle: "alice" },
},
};
const deck: DeckView = {
id: "saved",
title: "Saved",
columns: [column],
persisted: true,
revision: 1,
};
function setup(decks: DeckView[], registry: ColumnRegistry = new Map()) {
const workspace: Workspace = { decks, activeDeckId: decks[0]?.id ?? "" };
const select = vi.fn<Parameters<typeof useCitationNavigation>[0]["select"]>();
const createTemporary = vi.fn<Parameters<typeof useCitationNavigation>[0]["createTemporary"]>(
(title: string, columns: DeckColumn[]): DeckView => ({
id: "created",
title,
columns,
persisted: false,
}),
);
return {
...renderHook(() =>
useCitationNavigation({
registry,
getWorkspace: () => workspace,
select,
createTemporary,
}),
),
select,
createTemporary,
};
}
it("prefers a live loaded card over its old research snapshot", () => {
const live = { ...citation.post, text: "Latest live post" };
const registry = new Map([
[column.id, { current: { column, query: { data: { pages: [{ posts: [live] }] } } } }],
]) as ColumnRegistry;
const { result, select, createTemporary } = setup([deck], registry);
act(() => {
expect(result.current.navigate("https://mobile.twitter.com/alice/status/123", [citation])).toBe(
true,
);
});
expect(result.current.target?.post.text).toBe("Latest live post");
expect(select).not.toHaveBeenCalled();
expect(createTemporary).not.toHaveBeenCalled();
});
it("selects another deck with the same source and connection without changing saved data", () => {
const otherColumn = { ...column, id: "other-column" };
const otherDeck = { ...deck, id: "other", columns: [otherColumn] };
const { result, select, createTemporary } = setup([{ ...deck, columns: [] }, otherDeck]);
act(() => {
result.current.navigate(citation.post.url, [citation]);
});
expect(select).toHaveBeenCalledWith("other", false);
expect(result.current.target).toMatchObject({
deckId: "other",
column: otherColumn,
post: citation.post,
});
expect(otherDeck.columns).toEqual([otherColumn]);
expect(createTemporary).not.toHaveBeenCalled();
});
it.each([
{ ...column, connectionId: "different-account" },
{
...column,
source: {
...column.source,
kind: "search" as const,
platform: "twitter" as const,
query: "Changed",
product: "Latest" as const,
following: false,
},
},
])(
"creates a one-column temporary deck when an old column ID now means another source",
(changed) => {
const { result, createTemporary } = setup([{ ...deck, columns: [changed] }]);
act(() => {
expect(result.current.navigate(citation.post.url, [citation])).toBe(true);
});
expect(createTemporary).toHaveBeenCalledWith("Cited posts", [column], false);
expect(result.current.target?.deckId).toBe("created");
},
);
it("leaves an unknown URL to the normal external link handler", () => {
const { result, select, createTemporary } = setup([deck]);
act(() => {
expect(result.current.navigate("https://outside.invalid/post", [citation])).toBe(false);
});
expect(result.current.target).toBeUndefined();
expect(select).not.toHaveBeenCalled();
expect(createTemporary).not.toHaveBeenCalled();
});