129 行
6.0 KiB
TypeScript
129 行
6.0 KiB
TypeScript
import { fireEvent, screen } from '@testing-library/react'
|
|
import { useState } from 'react'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
import CountsControls from '@/components/counts/CountsControls'
|
|
import CountsFieldTree from '@/components/counts/CountsFieldTree'
|
|
import { CountsDefinitionContext } from '@/lib/countsDefinitions'
|
|
import { normaliseCountsParams } from '@/lib/counts'
|
|
import { readCountState, setCountState, encodeCountState } from '@/lib/queryState'
|
|
import { postDefinition } from '@/test/fixtures/counts'
|
|
import { renderWithProviders } from '@/test/render'
|
|
|
|
vi.mock ('@/lib/api', async original => ({
|
|
...await original<typeof import ('@/lib/api')> (), apiGet: vi.fn ().mockResolvedValue ([]) }))
|
|
|
|
const appliedParams = (extra: Record<string, string> = {}) => normaliseCountsParams ('posts',
|
|
new URLSearchParams ({ mode: 'details', timezone: 'UTC', ...extra }), postDefinition)
|
|
|
|
const renderControls = (params: URLSearchParams, labelsParams = params.toString ()) => {
|
|
const execute = vi.fn ()
|
|
const result = renderWithProviders (
|
|
<CountsDefinitionContext.Provider value={postDefinition}>
|
|
<CountsControls resource="posts" applied={params} open setOpen={vi.fn ()}
|
|
execute={execute} reorder={vi.fn ()} displayJSON={vi.fn ()}
|
|
displayRowNumbers={vi.fn ()} displayThumbnail={vi.fn ()}
|
|
conditionLabels={{ tags: { 'internal:7': 'Resolved tag' } }}
|
|
conditionLabelsParams={labelsParams}/>
|
|
</CountsDefinitionContext.Provider>, { route: '/ja/counts/posts' })
|
|
return { ...result, execute }
|
|
}
|
|
const submit = () => fireEvent.submit (
|
|
screen.getByRole ('button', { name: '集計実行' }).closest ('form')!)
|
|
|
|
|
|
describe ('Counts filter draft', () => {
|
|
it ('uses labels only for the executed condition set', () => {
|
|
const params = appliedParams ()
|
|
setCountState (params, 'conditions', encodeCountState ('conditions', [
|
|
{ field: 'tags', operator: 'axis_eq', value: 'internal:7' }]))
|
|
const { execute } = renderControls (params)
|
|
expect (screen.getByText ('Resolved tag')).toBeInTheDocument ()
|
|
fireEvent.change (screen.getByRole ('textbox', { name: 'Id.' }), { target: { value: '9' } })
|
|
expect (screen.queryByText ('Resolved tag')).not.toBeInTheDocument ()
|
|
expect (screen.getByText ('…')).toBeInTheDocument ()
|
|
expect (screen.queryByText ('internal:7')).not.toBeInTheDocument ()
|
|
submit ()
|
|
expect (readCountState (execute.mock.lastCall![0], 'conditions', []))
|
|
.toEqual ([{ field: 'tags', operator: 'axis_eq', value: 'internal:7' }])
|
|
})
|
|
|
|
it ('removes a visible drill condition from the submitted URL state', () => {
|
|
const params = appliedParams ()
|
|
setCountState (params, 'conditions', encodeCountState ('conditions', [
|
|
{ field: 'created_at', grain: 'month', operator: 'axis_eq', value: '2026-10' }]))
|
|
const { execute } = renderControls (params)
|
|
expect (screen.getByText (/令和8年10月/)).toBeInTheDocument ()
|
|
fireEvent.click (screen.getByRole ('button', { name: '削除' }))
|
|
submit ()
|
|
expect (readCountState (execute.mock.lastCall![0], 'conditions', [])).toEqual ([])
|
|
})
|
|
|
|
it ('opens the existing add dialogue without inserting a condition immediately', () => {
|
|
const { execute } = renderControls (appliedParams ())
|
|
fireEvent.click (screen.getByRole ('button', { name: '追加' }))
|
|
expect (screen.getByRole ('dialog')).toBeInTheDocument ()
|
|
expect (execute).not.toHaveBeenCalled ()
|
|
expect (document.querySelectorAll ('[role="dialog"] select.h-11').length).toBeGreaterThan (0)
|
|
expect (screen.queryByText ('pattern.input')).not.toBeInTheDocument ()
|
|
})
|
|
|
|
it ('submits only the edited tag names', () => {
|
|
const { execute } = renderControls (appliedParams ({ tags: 'alpha beta' }))
|
|
const tags = screen.getByDisplayValue ('alpha beta')
|
|
fireEvent.change (tags, { target: { value: 'not:beta gamma alpha' } })
|
|
submit ()
|
|
expect (execute.mock.lastCall![0].get ('tags')).toBe ('not:beta gamma alpha')
|
|
fireEvent.change (tags, { target: { value: 'gamma' } })
|
|
submit ()
|
|
expect (execute.mock.lastCall![0].get ('tags')).toBe ('gamma')
|
|
})
|
|
|
|
})
|
|
|
|
|
|
const FieldTree = () => {
|
|
const [params, setParams] = useState (() => appliedParams ({ columns: 'tags',
|
|
children: 'tags.name_ja' }))
|
|
return <CountsDefinitionContext.Provider value={postDefinition}>
|
|
<CountsFieldTree resource="posts" params={params} update={(key, value) => setParams (previous => {
|
|
const next = new URLSearchParams (previous)
|
|
next.set (key, value)
|
|
return next
|
|
})}/>
|
|
<output data-testid="fields">{params.toString ()}</output>
|
|
</CountsDefinitionContext.Provider>
|
|
}
|
|
|
|
it ('derives mixed parent selection and permits selecting all children again after clearing', () => {
|
|
renderWithProviders (<FieldTree/>, { route: '/ja/counts/posts' })
|
|
const parent = screen.getByRole ('checkbox', { name: 'タグ' })
|
|
expect (parent).toHaveAttribute ('aria-checked', 'mixed')
|
|
expect (parent).toBeEnabled ()
|
|
fireEvent.click (parent)
|
|
expect (parent).toBeChecked ()
|
|
fireEvent.click (parent)
|
|
expect (parent).not.toBeChecked ()
|
|
expect (parent).toBeEnabled ()
|
|
fireEvent.click (parent)
|
|
expect (parent).toBeChecked ()
|
|
const params = new URLSearchParams (screen.getByTestId ('fields').textContent ?? '')
|
|
expect (params.get ('children')?.split (' ')).toEqual ([
|
|
'tags.name_ja', 'tags.name_ain', 'tags.aliases_ja', 'tags.aliases_ain'])
|
|
expect (params.get ('columns')).toBe ('tags')
|
|
})
|
|
|
|
|
|
it ('offers row numbers only in details and keeps limit in display settings', () => {
|
|
const view = renderControls (appliedParams ({ mode: 'summary', row_numbers: '1' }))
|
|
expect (screen.queryByRole ('checkbox', { name: '行番号' })).not.toBeInTheDocument ()
|
|
const limit = screen.getByRole ('combobox', { name: '表示件数' })
|
|
expect (Array.from (limit.querySelectorAll ('option')).map (option => option.value))
|
|
.toEqual (['20', '50', '100'])
|
|
expect (limit).toHaveValue ('100')
|
|
expect (limit.querySelector ('span')).toBeNull ()
|
|
view.unmount ()
|
|
renderControls (appliedParams ({ row_numbers: '1' }))
|
|
expect (screen.getByRole ('checkbox', { name: '行番号' })).toBeChecked ()
|
|
})
|