feat: add authenticated list reading

This commit is contained in:
2026-07-26 12:58:35 +09:00
parent 83992bd962
commit 604c7f16e2
28 changed files with 947 additions and 29 deletions
+54 -1
View File
@@ -4,11 +4,12 @@ import {
createRouter,
RouterProvider,
} from '@tanstack/react-router'
import { render, screen, waitFor } from '@testing-library/react'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { routeTree } from '../routeTree.gen'
const usePostFeed = vi.hoisted(() => vi.fn())
const useListChoices = vi.hoisted(() => vi.fn())
vi.mock('../features/posts/use-post-feed', async (importOriginal) => {
const original =
@@ -16,6 +17,12 @@ vi.mock('../features/posts/use-post-feed', async (importOriginal) => {
return { ...original, usePostFeed }
})
vi.mock('../features/posts/use-list-choices', async (importOriginal) => {
const original =
await importOriginal<typeof import('../features/posts/use-list-choices')>()
return { ...original, useListChoices }
})
vi.mock('./__root', async () => {
const { createRootRouteWithContext, Outlet } = await import(
'@tanstack/react-router'
@@ -41,6 +48,7 @@ describe('post feed route wiring', () => {
beforeEach(() => {
vi.stubGlobal('scrollTo', vi.fn())
usePostFeed.mockReset()
useListChoices.mockReset()
usePostFeed.mockReturnValue({
data: undefined,
error: null,
@@ -52,6 +60,13 @@ describe('post feed route wiring', () => {
isPending: false,
refetch: vi.fn(),
} as never)
useListChoices.mockReturnValue({
data: [{ id: '1234567890', name: 'My List', memberCount: 10 }],
error: null,
isError: false,
isPending: false,
refetch: vi.fn(),
} as never)
})
afterEach(() => vi.unstubAllGlobals())
@@ -76,6 +91,44 @@ describe('post feed route wiring', () => {
)
})
it('keeps an empty list URL disabled', async () => {
await renderRoute('/list?target=')
expect(
await screen.findByRole('heading', { name: 'どのリストを読みますか?' }),
).toBeVisible()
expect(usePostFeed).toHaveBeenCalledWith(undefined)
})
it('turns a manually entered list URL into a deliberate request', async () => {
await renderRoute('/list?target=1234567890')
await waitFor(() =>
expect(usePostFeed).toHaveBeenCalledWith({
kind: 'list',
target: '1234567890',
}),
)
expect(
await screen.findByRole('link', { name: 'リスト', current: 'page' }),
).toBeVisible()
})
it('turns a selected authenticated list into a deliberate request', async () => {
await renderRoute('/list?target=')
fireEvent.click(
await screen.findByRole('link', { name: /My List.*10 メンバー/ }),
)
await waitFor(() =>
expect(usePostFeed).toHaveBeenCalledWith({
kind: 'list',
target: '1234567890',
}),
)
})
it('forwards every manually entered search control', async () => {
await renderRoute('/search?q=AI%20lang%3Aja&product=Top&following=true')
+31
View File
@@ -0,0 +1,31 @@
import { createFileRoute } from '@tanstack/react-router'
import { AppShell } from '#/components/app-shell'
import { ListForm } from '#/features/posts/components/list-form'
import { ListPicker } from '#/features/posts/components/list-picker'
import { PostFeed } from '#/features/posts/components/post-feed'
import { listRouteSearchSchema } from '#/features/posts/inputs'
export const Route = createFileRoute('/list')({
validateSearch: (search) => listRouteSearchSchema.parse(search),
component: ListRoute,
})
function ListRoute() {
const { target } = Route.useSearch()
const navigate = Route.useNavigate()
return (
<AppShell active="list">
<h1>どのリストを読みますか?</h1>
<p className="intro">
自分のリストを選ぶか、リストURLを入力してください。
</p>
<ListPicker selectedId={target || undefined} />
<ListForm
initialTarget={target}
key={target}
onSubmit={(nextTarget) => navigate({ search: { target: nextTarget } })}
/>
<PostFeed request={target ? { kind: 'list', target } : undefined} />
</AppShell>
)
}