Project: workspace-doctolib · Branch: HEAD · Mode: ` · **Model:**claude-sonnet-4-6`
Stats: 1 user messages, 6 tool calls, tools used: Read.
Conversation
Turn 1 — User
Analyze test coverage for the NhrDocumentCategoryFilter multi-select implementation (FHIR-2219) in treatment-plan/packages/tpf-national-health-records-ui.
Read these test files:
1. /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.test.tsx
2. /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDocumentCategoryFilterConfig.test.tsx
And the implementation files:
1. /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.tsx
2. /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.hooks.ts
3. /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.helpers.ts
4. /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDocumentCategoryFilterConfig.tsx
Report only genuine gaps in coverage — behaviors that exist in the implementation but are not tested. Skip suggestions that are already covered.
Turn 1 — Assistant
I'll read all the files in parallel to analyze the implementation and test coverage.
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.test.tsx
Tool results:
→ result (ok): 1 import { setup } from '@doctolib/test-helpers'
2 import { screen } from '@testing-library/react'
3 import { afterEach, describe, expect, it, vi } from 'vitest'
4
5 import { NhrDocumentCategoryFilter } from './NhrDocumentCategoryFilter'
6
7 const sampleOptions = [
8 { value: 'A', label: 'Report A' },
9 { value: 'B', label: 'Report B' },
10 { value: 'C', label: 'Report C' },
11 ]
12
13 describe('NhrDocumentCategoryFilter', () => {
14 afterEach(() => {
15 vi.clearAllMocks()
16
…(truncated, 14044 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDocumentCategoryFilterConfig.test.tsx
Tool results:
→ result (ok): 1 import { setup } from '@doctolib/test-helpers'
2 import { screen } from '@testing-library/react'
3 import { afterEach, describe, expect, it, vi } from 'vitest'
4
5 import { mockPatientInfo } from '../../../test-helpers'
6 import { createNhrDocumentCategoryFilterConfig } from './createNhrDocumentCategoryFilterConfig'
7
8 const ctx = { patient: mockPatientInfo }
9
10 const sampleOptions = [
11 { value: 'A', label: 'Report A' },
12 { value: 'B', label: 'Report B' },
13 { value: 'C', labe
…(truncated, 4677 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.tsx
Tool results:
→ result (ok): 1 import { ChevronDownRegularIcon } from '@doctolib/design-system-icons/ChevronDownRegularIcon'
2 import { Checkbox } from '@doctolib/design-system/Checkbox/v2'
3 import { IconButton } from '@doctolib/design-system/IconButton/v2'
4 import type { Option } from '@doctolib/design-system/Option'
5 import { useId, useMemo, useRef, useState } from 'react'
6
7 import { normalize } from './NhrDocumentCategoryFilter.helpers'
8 import { useNhrDocumentCategoryClose } from './NhrDocumentCategoryFilter.hook
…(truncated, 4075 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.hooks.ts
Tool results:
→ result (ok): 1 import { type RefObject, useEffect, useRef } from 'react'
2
3 export const useNhrDocumentCategoryClose = (
4 containerRef: RefObject<HTMLDivElement | null>,
5 onClose: () => void,
6 isOpen: boolean
7 ) => {
8 const onCloseRef = useRef(onClose)
9 // Sync ref every render so the stable event listeners always call the latest onClose.
10 useEffect(() => {
11 onCloseRef.current = onClose
12 })
13
14 useEffect(() => {
15 if (!isOpen) return undefined
16
17 const handle
…(truncated, 638 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.helpers.ts
Tool results:
→ result (ok): 1 export const normalize = (s: string) => 2 s 3 .normalize('NFD') 4 .replace(/\p{Diacritic}/gu, '') 5 .toLowerCase() 6
Tools used:
Read:/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDocumentCategoryFilterConfig.tsx
Tool results:
→ result (ok): 1 import type { Option } from '@doctolib/design-system/Option'
2
3 import { NhrDocumentCategoryFilter } from '../../../components/NhrDocumentList/NhrDocumentCategoryFilter'
4 import type { FilterConfig } from '../../../types'
5
6 export const createNhrDocumentCategoryFilterConfig = ({
7 options = [],
8 label = 'Document Category',
9 placeholder,
10 }: {
11 options?: Option[]
12 label?: string
13 placeholder?: string
14 } = {}): FilterConfig => ({
15 key: 'typeCodes',
16 render:
…(truncated, 653 more chars)
Here is the coverage analysis.
Summary
The test suite for the NhrDocumentCategoryFilter multi-select implementation is comprehensive and of high quality. The tests consistently test behavior rather than implementation details, follow DAMP principles, and cover the vast majority of meaningful user-facing scenarios. The gaps identified below are genuine behavioral paths in the implementation that currently have no corresponding test.
Critical Gaps
None. There are no gaps that could cause data loss, security issues, or system failures.
Important Improvements
1. The displayLabel count in NhrDocumentCategoryFilter includes unknown values in +N
Rating: 7
The displayLabel computation in /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.tsx (line 43) is:
return values.length === 1 ? firstLabel : `${firstLabel}, +${values.length - 1}`
values.length counts all values passed in, including ones that have no matching option. Test line 67–71 in NhrDocumentCategoryFilter.test.tsx covers ['UNKNOWN', 'A', 'B'] and expects 'Report A, +2'. That test verifies the current behavior — the unknown value is counted in the +N suffix — but the scenario where values = ['A', 'UNKNOWN'] (one valid, one unknown) is not tested. The test labeled "should show the first matching label with count when some selected values do not match any option" only covers the case where the unknown value comes first. The inverse ordering (unknown value appended after a valid one) should be confirmed to produce 'Report A, +1' rather than something unexpected.
What regression this catches: A future fix that attempts to exclude unknown values from the count would silently change the +N number for users who had unknowns appended after a known value.
2. useNhrDocumentCategoryClose does not clean up listeners when isOpen becomes false mid-render cycle
Rating: 6
The hook in /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.hooks.ts adds pointerdown and keydown listeners on document when isOpen is true and removes them in the cleanup of that same effect. No test directly asserts that event listeners are removed when the component unmounts while the dropdown is open. If a parent component unmounts the filter while the dropdown is open, the pointerdown and keydown listeners attached to document should be removed; currently no test verifies this.
What regression this catches: A refactor that breaks the effect cleanup return (e.g., accidentally placing cleanup outside the if (!isOpen) return guard) would leak global document listeners, causing ghost interactions on unrelated subsequent renders or memory leaks in long-lived pages.
A test sketch:
it('should remove document event listeners when unmounted while open', async () => {
const addSpy = vi.spyOn(document, 'addEventListener')
const removeSpy = vi.spyOn(document, 'removeEventListener')
const { user, unmount } = renderComponent()
await user.click(screen.getByRole('combobox', { name: 'Category' }))
unmount()
const addedListeners = addSpy.mock.calls.map(([type]) => type)
const removedListeners = removeSpy.mock.calls.map(([type]) => type)
expect(addedListeners).toContain('pointerdown')
expect(addedListeners).toContain('keydown')
expect(removedListeners).toContain('pointerdown')
expect(removedListeners).toContain('keydown')
})
3. createNhrDocumentCategoryFilterConfig — render function ignores the ctx parameter
Rating: 5
In /Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/business/factories/filters/createNhrDocumentCategoryFilterConfig.tsx, the render function signature includes a ctx parameter (line 16: (filters, onChange) => {; the ctx argument passed by callers is ignored — the signature on the FilterConfig type presumably carries it). No test verifies what happens when ctx is undefined or null, which could happen if a consumer passes an incomplete context object. While the current implementation ignores ctx entirely, a future maintainer extending the factory to consume patient context would have no test guarding the undefined path.
What regression this catches: Any future use of ctx inside the factory without a corresponding guard would produce a runtime error that no test would catch in advance.
4. Search query is cleared when handleInputClick is called while closed, but not when isOpen is already true on the same click
Rating: 5
In NhrDocumentCategoryFilter.tsx (lines 56–60):
const handleInputClick = () => {
if (!isOpen) {
setSearchQuery('')
setIsOpen(true)
}
}
When isOpen is already true (user clicks the input while the dropdown is open), setSearchQuery('') is intentionally not called, so the current typed query is preserved. The existing test "should not close the dropdown when the trigger is clicked again while open" (line 327) correctly verifies the dropdown stays open, but it does not verify that a previously typed search query is preserved when the user clicks the input again while open.
What regression this catches: A change that unconditionally resets searchQuery on every click would make it impossible for users to re-focus the input to continue typing without their partial query disappearing.
5. normalize helper — behavior on empty string input
Rating: 4
/Users/USER/workspace/doctolib/treatment-plan/packages/tpf-national-health-records-ui/src/components/NhrDocumentList/NhrDocumentCategoryFilter/NhrDocumentCategoryFilter.helpers.ts is a trivial one-liner and the component tests implicitly cover its behavior via the filter tests. However, the normalize function has no dedicated unit test. The component correctly handles an empty search (line 47: q ? options.filter(...) : options), but the fact that normalize('') returns '' — and therefore the falsy branch is taken — is never explicitly asserted. This is low risk but means a refactor of the guard condition that depends on normalize returning a falsy value for empty input would not be caught.
Test Quality Issues
None found. The tests avoid snapshot testing, implementation-detail coupling, CSS selectors, and arbitrary waits. The use of screen.getByRole throughout is correct and aligned with project testing conventions.
Positive Observations
- The display-label ordering tests (line 61–65 and 67–71 in
NhrDocumentCategoryFilter.test.tsx) correctly distinguish between selection order invaluesvs. options order — this is a non-obvious behavior that is well-guarded. - Diacritic normalization is tested with both a French (
Médecin traitant) and a German (über den Dächern) fixture — good internationalisation coverage for the target markets. - The "should show all options when reopened after closing with an active search query" test (line 337) is exactly the kind of UX regression test that prevents product bugs; it tests the
close()function clearingsearchQuerywhich is easy to miss. - The
createNhrDocumentCategoryFilterConfigtests correctly cover the defensiveArray.isArrayguard fortypeCodeswith a non-array input (line 121–125). - All tests use
setup()from@doctolib/test-helpers,screen.getByRole, anduser.*interactions consistently with the project's RTL conventions.