From 6ac367a4d3f47e0f7809bd3cdf79d2c0bb52744c Mon Sep 17 00:00:00 2001 From: Misael Alves <112561288+misaelalves99@users.noreply.github.com> Date: Fri, 16 Oct 2026 09:32:02 -0300 Subject: [PATCH] fix(web): preserve instrument showcase tag options --- .../InstrumentShowcase/InstrumentShowcase.tsx | 15 ++++----- .../__tests__/InstrumentShowcase.test.tsx | 19 +++++++++++ testing/src/specs/remote-assignment.spec.ts | 32 +++++++++++++++++++ 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/apps/web/src/components/InstrumentShowcase/InstrumentShowcase.tsx b/apps/web/src/components/InstrumentShowcase/InstrumentShowcase.tsx index ba880d54c..c99f8e480 100644 --- a/apps/web/src/components/InstrumentShowcase/InstrumentShowcase.tsx +++ b/apps/web/src/components/InstrumentShowcase/InstrumentShowcase.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ListboxDropdown, SearchBar } from '@douglasneuroinformatics/libui/components'; import type { ListboxDropdownOption } from '@douglasneuroinformatics/libui/components'; @@ -21,7 +21,6 @@ export const InstrumentShowcase: React.FC<{ const [filteredInstruments, setFilteredInstruments] = useState( availableInstruments.toSorted((a, b) => a.details.title.localeCompare(b.details.title)) ); - const [tagOptions, setTagOptions] = useState([]); const [selectedKinds, setSelectedKinds] = useState([]); const [selectedLanguages, setSelectedLanguages] = useState([]); const [selectedTags, setSelectedTags] = useState([]); @@ -50,16 +49,16 @@ export const InstrumentShowcase: React.FC<{ setHighlightedIndex(0); }, [availableInstruments, selectedKinds, selectedLanguages, selectedTags, searchTerm]); - useEffect(() => { - setTagOptions( - Array.from(new Set(filteredInstruments.flatMap((item) => item.tags))) + const tagOptions = useMemo( + () => + Array.from(new Set(availableInstruments.flatMap((item) => item.tags))) .map((item) => ({ key: item, label: item })) - .sort((a, b) => a.label.localeCompare(b.label)) - ); - }, [availableInstruments]); + .sort((a, b) => a.label.localeCompare(b.label)), + [availableInstruments] + ); useEffect(() => { const items = listRef.current?.querySelectorAll('[data-instrument-index]'); diff --git a/apps/web/src/components/InstrumentShowcase/__tests__/InstrumentShowcase.test.tsx b/apps/web/src/components/InstrumentShowcase/__tests__/InstrumentShowcase.test.tsx index 43ec6cd01..db862155f 100644 --- a/apps/web/src/components/InstrumentShowcase/__tests__/InstrumentShowcase.test.tsx +++ b/apps/web/src/components/InstrumentShowcase/__tests__/InstrumentShowcase.test.tsx @@ -185,6 +185,25 @@ describe('InstrumentShowcase', () => { ]); }); + it('should offer every available tag after the data is passed again, so an active search cannot shrink the tag filter', async () => { + const onSelect = vi.fn(); + const { rerender } = render(); + + search('Alpha'); + await expectCardIds(['alpha']); + // The parent re-renders with a new array instance, as Remote Assignment does on dialog open. + rerender(); + search(''); + await expectCardIds(['alpha', 'beta', 'gamma']); + + openFilter('instrument-tag-filter'); + expect(screen.getAllByRole('menuitemcheckbox').map((option) => option.textContent)).toEqual([ + 'Anxiety', + 'Memory', + 'Mood' + ]); + }); + it('should highlight the first instrument, so Enter has something to open', () => { renderShowcase(); expect(isHighlighted(alpha)).toBe(true); diff --git a/testing/src/specs/remote-assignment.spec.ts b/testing/src/specs/remote-assignment.spec.ts index f63e5654d..6f41a86cc 100644 --- a/testing/src/specs/remote-assignment.spec.ts +++ b/testing/src/specs/remote-assignment.spec.ts @@ -29,6 +29,38 @@ test.describe('remote assignment', () => { await expect(remoteAssignmentPage.instrumentShowcase).toBeVisible(); }); + test('should retain all tag options after searching, opening a remote assignment dialog, and clearing the search', async ({ + getPageModel, + page, + uniqueId + }) => { + await startSession(getPageModel, `Tag${uniqueId}`, `Subject${uniqueId}`, 'Male'); + + await page.getByTestId('nav-button-/session/remote-assignment').click(); + await page.waitForURL('**/session/remote-assignment'); + + const remoteAssignmentPage = new RemoteAssignmentPage(page); + await expect(remoteAssignmentPage.instrumentShowcase).toBeVisible(); + const tagFilter = page.getByTestId('instrument-tag-filter'); + const tagButton = tagFilter.getByRole('button'); + await tagButton.click(); + await expect(page.getByRole('menuitemcheckbox').first()).toBeVisible(); + const initialOptions = await page.getByRole('menuitemcheckbox').allTextContents(); + expect(initialOptions.length).toBeGreaterThan(1); + await page.keyboard.press('Escape'); + + const searchInput = remoteAssignmentPage.instrumentShowcase.getByRole('searchbox'); + await searchInput.fill('Happiness Questionnaire'); + await remoteAssignmentPage.selectInstrument('Happiness Questionnaire'); + await expect(remoteAssignmentPage.createDialog).toBeVisible(); + await page.keyboard.press('Escape'); + await expect(remoteAssignmentPage.createDialog).not.toBeVisible(); + + await searchInput.fill(''); + await tagButton.click(); + await expect.poll(() => page.getByRole('menuitemcheckbox').allTextContents()).toEqual(initialOptions); + }); + test('should create a remote assignment and display the link', async ({ getPageModel, page, uniqueId }) => { await startSession(getPageModel, `Assign${uniqueId}`, `User${uniqueId}`, 'Female');