diff --git a/backend/spec/requests/counts_table_spec.rb b/backend/spec/requests/counts_table_spec.rb index d45d67e..8b4a115 100644 --- a/backend/spec/requests/counts_table_spec.rb +++ b/backend/spec/requests/counts_table_spec.rb @@ -155,7 +155,7 @@ RSpec.describe 'Counts table API', type: :request do result = query(tags: 'renamed-alpha') expect(result['versions'].first['tags'].first['name']).to eq('alpha') expect(versions(query(tags: 'renamed-alpha', definition: 'latest', - at: '2024-01-31T16:00:00Z', current: '0'))).to eq([2]) + at: '2024-01-31T16:00:00Z'))).to eq([2]) expect(versions(query(tags: 'not:alpha-alias'))).to eq([3]) expect(versions(query(tags: 'renamed-alpha nico:beta'))).to eq([1]) expect(versions(query(tags: 'renamed-alpha nico:beta', match: 'any'))).to eq([1, 2, 4]) diff --git a/frontend/src/components/common/SortableColumns.tsx b/frontend/src/components/common/SortableColumns.tsx index ba87d3a..e8c5306 100644 --- a/frontend/src/components/common/SortableColumns.tsx +++ b/frontend/src/components/common/SortableColumns.tsx @@ -2,7 +2,7 @@ import { DndContext, DragOverlay, KeyboardSensor, MouseSensor, TouchSensor, closestCenter, useSensor, useSensors } from '@dnd-kit/core' import { SortableContext, arrayMove, horizontalListSortingStrategy, sortableKeyboardCoordinates } from '@dnd-kit/sortable' -import { useEffect, useRef, useState } from 'react' +import { useEffect, useId, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { CSS } from '@dnd-kit/utilities' @@ -16,6 +16,7 @@ const SortableColumns = ({ ids, fixed = [], name, onChange, children }: { onChange: (ids: string[]) => void; children: (ids: string[]) => ReactNode }, ) => { const [mounted, setMounted] = useState (false) + const scope = useId () useEffect (() => setMounted (true), []) const [active, setActive] = useState (null) const drag = useRef<{ id: string; ids: string[] } | null> (null) @@ -35,7 +36,7 @@ const SortableColumns = ({ ids, fixed = [], name, onChange, children }: { drag.current = null setActive (null) } - return name (String (event.active.id)).text, onDragOver: event => event.over == null ? undefined : name (String (event.over.id)).text, diff --git a/frontend/src/components/common/SortableList.tsx b/frontend/src/components/common/SortableList.tsx index f5e2be7..73025f7 100644 --- a/frontend/src/components/common/SortableList.tsx +++ b/frontend/src/components/common/SortableList.tsx @@ -92,6 +92,7 @@ const SortableList = ( } return ( { const hits = pointerWithin (args) diff --git a/frontend/src/pages/CountsResourcePage.test.tsx b/frontend/src/pages/CountsResourcePage.test.tsx index e79856d..5fa7aa4 100644 --- a/frontend/src/pages/CountsResourcePage.test.tsx +++ b/frontend/src/pages/CountsResourcePage.test.tsx @@ -4,7 +4,7 @@ import { hydrateRoot } from 'react-dom/client' import { renderToString } from 'react-dom/server' import { HelmetProvider } from 'react-helmet-async' import { createMemoryRouter, RouterProvider, useLocation } from 'react-router-dom' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import CountsResourcePage from '@/pages/CountsResourcePage' import DialogueProvider from '@/components/dialogues/DialogueProvider' @@ -37,7 +37,7 @@ const execute = () => fireEvent.submit ( const openConditions = () => fireEvent.click ( screen.getByText ('集計条件').closest ('button')!) const resourceInput = () => within ( - screen.getByText ('抽出条件', { selector: 'legend' }).closest ('fieldset')!, + screen.getByRole ('group', { name: '抽出条件' }), ).getByRole ('textbox', { name: 'Id.' }) const renderPage = (extra = '') => renderWithProviders (<> @@ -62,6 +62,10 @@ beforeEach (() => { fetchCountsDefinition.mockReset ().mockResolvedValue (postDefinition) }) +const hydrationCleanup = new Set<() => void> () +afterEach (() => { + for (const dispose of hydrationCleanup) dispose () +}) describe ('Counts execution and URL state', () => { it ('renders sortable shells on the server even when JSDOM provides document', () => { @@ -102,23 +106,42 @@ describe ('Counts execution and URL state', () => { const container = document.createElement ('div') container.innerHTML = html document.body.appendChild (container) + const handles = [...container.querySelectorAll ('[aria-roledescription="sortable"]')] + .map (node => ({ node, description: node.getAttribute ('aria-describedby') })) const recoverable = vi.fn () const root = hydrateRoot (container, page, { onRecoverableError: recoverable }) + const dispose = () => { + if (!hydrationCleanup.delete (dispose)) return + try + { + act (() => root.unmount ()) + } + finally + { + container.remove () + router.dispose () + client.clear () + } + } + hydrationCleanup.add (dispose) try { + expect (handles.length).toBeGreaterThan (0) await waitForResult (client) expect (fetchCounts).toHaveBeenCalledTimes (1) expect (fetchCounts.mock.calls[0][1].get ('resource_id')).toBe ('7') await waitFor (() => expect (currentParams ().get ('execution_at')).toBe (executionAt)) expect (currentParams ().get ('resource_id')).toBe ('7') + for (const { node, description } of handles) + { + expect (node.getAttribute ('aria-describedby')).toBe (description) + expect (document.getElementById (description!)).not.toBeNull () + } expect (recoverable).not.toHaveBeenCalled () } finally { - await act (async () => root.unmount ()) - container.remove () - router.dispose () - client.clear () + dispose () } }) diff --git a/frontend/src/pages/help/HelpChangesPage.test.tsx b/frontend/src/pages/help/HelpChangesPage.test.tsx index 872fdf8..acfa22e 100644 --- a/frontend/src/pages/help/HelpChangesPage.test.tsx +++ b/frontend/src/pages/help/HelpChangesPage.test.tsx @@ -1,9 +1,10 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { act, render, screen, within } from '@testing-library/react' +import { act, render, screen, waitFor, within } from '@testing-library/react' import { HelmetProvider } from 'react-helmet-async' import { beforeEach, describe, expect, it, vi } from 'vitest' import { apiGet } from '@/lib/api' +import { helpKeys } from '@/lib/queryKeys' import App, { pageRoutes } from '@/App' import { helpFixture } from '@/test/helpFixtures' @@ -37,6 +38,13 @@ describe ('Help global edition history', () => { ) + // Wait for the query started by the actual route, separately from DOM discovery. + await waitFor (() => expect (client.getQueryCache ().find ({ + queryKey: helpKeys.changes ('ja') })).toBeDefined ()) + const changes = client.getQueryCache ().find ({ queryKey: helpKeys.changes ('ja') }) + await act (async () => { await changes!.promise }) + expect (changes!.state.status).toBe ('success') + expect (changes!.state.data).toHaveLength (6) const table = await screen.findByRole ('table') const links = within (table).getAllByRole ('link') const editions = links.map (link => link.getAttribute ('href'))