Add button-driven search filters

This commit is contained in:
2026-08-10 04:17:54 +09:00
parent 079aaa1664
commit 8eb299f46d
12 changed files with 984 additions and 72 deletions
+30 -1
View File
@@ -157,13 +157,42 @@ describe('post feed route wiring', () => {
await waitFor(() =>
expect(usePostFeed).toHaveBeenCalledWith({
kind: 'search',
query: 'AI lang:ja',
query: 'AI lang:ja filter:follows',
product: 'Top',
following: true,
}),
)
})
it('compiles button-selected search conditions from the URL', async () => {
await renderRoute(
'/search?q=TypeScript&from=%40tan_stack&since=2026-08-04&until=2026-08-11&lang=ja&content=images&excludeReplies=true&excludeReposts=true&product=Latest&following=false',
)
await waitFor(() =>
expect(usePostFeed).toHaveBeenCalledWith({
kind: 'search',
query:
'TypeScript from:tan_stack since:2026-08-04 until:2026-08-11 lang:ja filter:images -filter:replies -filter:retweets',
product: 'Latest',
following: false,
}),
)
expect(
screen.getByRole('button', { name: '投稿者 @tan_stack を解除' }),
).toBeVisible()
})
it('keeps contradictory date parameters local and idle', async () => {
await renderRoute(
'/search?q=TypeScript&since=2026-08-11&until=2026-08-10&product=Latest&following=false',
)
fireEvent.click(screen.getByRole('button', { name: '期間' }))
expect(await screen.findByLabelText('開始日')).toHaveValue('2026-08-11')
expect(usePostFeed).toHaveBeenCalledWith(undefined)
})
it('turns a decimal status path into a deliberate thread request', async () => {
await renderRoute('/yuta/status/123')
+44 -8
View File
@@ -1,34 +1,70 @@
import { createFileRoute } from '@tanstack/react-router'
import { AppShell } from '#/components/app-shell'
import { PostFeed } from '#/features/posts/components/post-feed'
import { SearchForm } from '#/features/posts/components/search-form'
import { postSearchRouteSchema } from '#/features/posts/inputs'
import {
SearchForm,
type SearchValues,
} from '#/features/posts/components/search-form'
import {
buildFilteredSearchQuery,
postSearchRouteSchema,
} from '#/features/posts/inputs'
export const Route = createFileRoute('/search')({
validateSearch: (search) => postSearchRouteSchema.parse(search),
component: SearchRoute,
})
function compileQuery(values: SearchValues): string | undefined {
if (!values.q && !values.from) return undefined
try {
return buildFilteredSearchQuery(values, values.following)
} catch {
return undefined
}
}
function SearchRoute() {
const values = Route.useSearch()
const navigate = Route.useNavigate()
const formValues = {
...values,
from: values.from ?? '',
since: values.since ?? '',
until: values.until ?? '',
lang: values.lang ?? 'all',
content: values.content ?? 'all',
excludeReplies: values.excludeReplies ?? false,
excludeReposts: values.excludeReposts ?? false,
} satisfies SearchValues
const query = compileQuery(formValues)
const formKey = [
formValues.q,
formValues.from,
formValues.since,
formValues.until,
formValues.lang,
formValues.content,
formValues.excludeReplies,
formValues.excludeReposts,
formValues.product,
formValues.following,
].join(':')
return (
<AppShell active="search">
<h1>何を確認しますか?</h1>
<p className="intro">入力した条件だけを検索します。</p>
<SearchForm
initialFollowing={values.following}
initialProduct={values.product}
initialQuery={values.q}
key={`${values.q}:${values.product}:${values.following}`}
initialValues={formValues}
key={formKey}
onSubmit={(search) => navigate({ search })}
/>
<PostFeed
request={
values.q
query
? {
kind: 'search',
query: values.q,
query,
product: values.product,
following: values.following,
}