feat: add URL-driven workspace state and streamed Codex research
This commit is contained in:
@@ -1,51 +1,54 @@
|
||||
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'
|
||||
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',
|
||||
id: "column",
|
||||
title: "Research",
|
||||
connectionId: "twitter",
|
||||
source: {
|
||||
platform: 'twitter',
|
||||
kind: 'search',
|
||||
query: 'WebMCP',
|
||||
product: 'Latest',
|
||||
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' },
|
||||
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',
|
||||
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()
|
||||
const createTemporary = vi.fn(
|
||||
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',
|
||||
id: "created",
|
||||
title,
|
||||
columns,
|
||||
persisted: false,
|
||||
}),
|
||||
)
|
||||
);
|
||||
return {
|
||||
...renderHook(() =>
|
||||
useCitationNavigation({
|
||||
@@ -57,80 +60,73 @@ function setup(decks: DeckView[], registry: ColumnRegistry = new Map()) {
|
||||
),
|
||||
select,
|
||||
createTemporary,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
it('prefers a live loaded card over its old research snapshot', () => {
|
||||
const live = { ...citation.post, text: 'Latest live post' }
|
||||
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)
|
||||
[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()
|
||||
})
|
||||
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,
|
||||
])
|
||||
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')
|
||||
result.current.navigate(citation.post.url, [citation]);
|
||||
});
|
||||
expect(select).toHaveBeenCalledWith("other", false);
|
||||
expect(result.current.target).toMatchObject({
|
||||
deckId: 'other',
|
||||
deckId: "other",
|
||||
column: otherColumn,
|
||||
post: citation.post,
|
||||
})
|
||||
expect(otherDeck.columns).toEqual([otherColumn])
|
||||
expect(createTemporary).not.toHaveBeenCalled()
|
||||
})
|
||||
});
|
||||
expect(otherDeck.columns).toEqual([otherColumn]);
|
||||
expect(createTemporary).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ ...column, connectionId: 'different-account' },
|
||||
{ ...column, connectionId: "different-account" },
|
||||
{
|
||||
...column,
|
||||
source: {
|
||||
...column.source,
|
||||
kind: 'search' as const,
|
||||
platform: 'twitter' as const,
|
||||
query: 'Changed',
|
||||
product: 'Latest' as const,
|
||||
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])
|
||||
expect(result.current.target?.deckId).toBe('created')
|
||||
})
|
||||
])(
|
||||
"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])
|
||||
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()
|
||||
})
|
||||
expect(result.current.navigate("https://outside.invalid/post", [citation])).toBe(false);
|
||||
});
|
||||
expect(result.current.target).toBeUndefined();
|
||||
expect(select).not.toHaveBeenCalled();
|
||||
expect(createTemporary).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user