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

137 lines
3.9 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'
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()
const createTemporary = vi.fn(
(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')
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])
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()
})