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 (), apiGet: vi.fn ().mockResolvedValue ([]) })) const appliedParams = (extra: Record = {}) => normaliseCountsParams ('posts', new URLSearchParams ({ mode: 'details', timezone: 'UTC', ...extra }), postDefinition) const renderControls = (params: URLSearchParams, labelsParams = params.toString ()) => { const execute = vi.fn () const result = renderWithProviders ( , { 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 setParams (previous => { const next = new URLSearchParams (previous) next.set (key, value) return next })}/> {params.toString ()} } it ('derives mixed parent selection and permits selecting all children again after clearing', () => { renderWithProviders (, { 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 () })