From 806642ee202fcdd6eeb7a5248c4a554e19251615 Mon Sep 17 00:00:00 2001 From: Misael Alves <112561288+misaelalves99@users.noreply.github.com> Date: Fri, 16 Oct 2026 20:06:14 -0300 Subject: [PATCH 1/2] fix(playground): search instruments by label --- .../Header/InstrumentSelector/InstrumentSelector.tsx | 1 + testing/src/pages/playground/index.page.ts | 6 ++---- testing/src/specs/playground.spec.ts | 9 +++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx b/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx index f301f9d11..1cb7de56c 100644 --- a/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx +++ b/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx @@ -49,6 +49,7 @@ export const InstrumentSelector = () => { {instruments.map((instrument) => ( { setSelectedInstrument(id); diff --git a/testing/src/pages/playground/index.page.ts b/testing/src/pages/playground/index.page.ts index 012ea8ad5..b5a0678d0 100644 --- a/testing/src/pages/playground/index.page.ts +++ b/testing/src/pages/playground/index.page.ts @@ -46,12 +46,10 @@ export class PlaygroundPage { await this.$ref.goto(url); } - /** - * Picks an example or template from the header's instrument list by its label. The list's search - * box matches on instrument ids rather than labels, so the option is clicked without filtering. - */ + /** Picks an example or template from the header's instrument list by its label. */ async selectInstrument(label: string): Promise { await this.$ref.getByRole('combobox', { name: 'Load an instrument...' }).click(); + await this.$ref.getByPlaceholder('Search...').fill(label); await this.$ref.getByRole('option', { exact: true, name: label }).click(); } } diff --git a/testing/src/specs/playground.spec.ts b/testing/src/specs/playground.spec.ts index 2a706b5a7..62b5d2a18 100644 --- a/testing/src/specs/playground.spec.ts +++ b/testing/src/specs/playground.spec.ts @@ -164,6 +164,15 @@ test.describe('playground', () => { test.describe.configure({ mode: 'default' }); test.slow(); + test('should find an instrument by its visible label in the selector', async ({ page }) => { + const playground = new PlaygroundPage(page); + await playground.goto(); + + await page.getByRole('combobox', { name: 'Load an instrument...' }).click(); + await page.getByPlaceholder('Search...').fill('Form Reference'); + await expect(page.getByRole('option', { exact: true, name: 'Form Reference' })).toBeVisible(); + }); + test('should run the preview on an origin other than the editor and render the instrument there @smoke', async ({ page }) => { From 5219572cdac9878df2ea729d497e7ba3c36a2270 Mon Sep 17 00:00:00 2001 From: joshunrau Date: Tue, 3 Nov 2026 09:18:06 -0500 Subject: [PATCH 2/2] fix(playground): stop matching instrument ids in the selector search cmdk's default filter scores an item's value and keywords as one string, so with the UUID as the value and the label as a keyword, any query of hex characters still listed instruments whose hidden id matched it: `4` listed every instrument. The label is now the item's value, and onSelect selects the instrument by its id from the closure. Labels are unique, as upload, clone and share-link import all enforce. The playground page object now owns the selector and search-box locators behind searchInstruments(), and the e2e spec asserts that `4` finds nothing. Co-Authored-By: Claude Opus 5.5 --- .../InstrumentSelector/InstrumentSelector.tsx | 7 +- .../__tests__/InstrumentSelector.test.tsx | 72 +++++++++++++++++++ testing/src/pages/playground/index.page.ts | 22 +++++- testing/src/specs/playground.spec.ts | 14 +++- 4 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 apps/playground/src/components/Header/InstrumentSelector/__tests__/InstrumentSelector.test.tsx diff --git a/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx b/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx index 1cb7de56c..aefb9426b 100644 --- a/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx +++ b/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx @@ -49,10 +49,9 @@ export const InstrumentSelector = () => { {instruments.map((instrument) => ( { - setSelectedInstrument(id); + value={instrument.label} + onSelect={() => { + setSelectedInstrument(instrument.id); setOpen(false); }} > diff --git a/apps/playground/src/components/Header/InstrumentSelector/__tests__/InstrumentSelector.test.tsx b/apps/playground/src/components/Header/InstrumentSelector/__tests__/InstrumentSelector.test.tsx new file mode 100644 index 000000000..5124f5b49 --- /dev/null +++ b/apps/playground/src/components/Header/InstrumentSelector/__tests__/InstrumentSelector.test.tsx @@ -0,0 +1,72 @@ +// @vitest-environment happy-dom + +import { cleanup, fireEvent, render, screen } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import type { InstrumentRepository } from '@/models/instrument-repository.model'; + +import { InstrumentSelector } from '../InstrumentSelector'; + +const store = vi.hoisted(() => { + const instruments: InstrumentRepository[] = [ + { + category: 'Examples', + files: [], + id: '0b5d1c2e-7f3a-4c1d-9e8f-2a6b7c8d9e0f', + kind: 'FORM', + label: 'Form Reference' + }, + { + category: 'Templates', + files: [], + id: 'c3e9a1b2-5d6f-4e7a-8b9c-0d1e2f3a4b5c', + kind: 'FORM', + label: 'Unilingual Form' + } + ]; + return { + state: { + instruments, + selectedInstrument: instruments[1]!, + setSelectedInstrument: (_id: string) => undefined + } + }; +}); + +vi.mock('@/store', () => ({ + useAppStore: (selector: (state: typeof store.state) => TSelected) => selector(store.state) +})); + +function openAndSearch(query: string) { + fireEvent.click(screen.getByRole('combobox', { name: 'Load an instrument...' })); + fireEvent.change(screen.getByPlaceholderText('Search...'), { target: { value: query } }); +} + +describe('InstrumentSelector', () => { + beforeEach(() => { + store.state.setSelectedInstrument = vi.fn(); + render(); + }); + + afterEach(() => { + cleanup(); + }); + + it('should list an instrument whose label matches the search', () => { + openAndSearch('Form Reference'); + expect(screen.getByRole('option', { name: 'Form Reference' })).toBeDefined(); + expect(screen.queryByRole('option', { name: 'Unilingual Form' })).toBeNull(); + }); + + it('should list nothing for a query only an id matches, so the hidden UUIDs are never searched', () => { + openAndSearch('4'); + expect(screen.queryAllByRole('option')).toHaveLength(0); + expect(screen.getByText('No Instruments Found')).toBeDefined(); + }); + + it('should select the chosen instrument by its id, though its label is what is searched', () => { + openAndSearch('Form Reference'); + fireEvent.click(screen.getByRole('option', { name: 'Form Reference' })); + expect(store.state.setSelectedInstrument).toHaveBeenCalledExactlyOnceWith('0b5d1c2e-7f3a-4c1d-9e8f-2a6b7c8d9e0f'); + }); +}); diff --git a/testing/src/pages/playground/index.page.ts b/testing/src/pages/playground/index.page.ts index b5a0678d0..8ac721ed5 100644 --- a/testing/src/pages/playground/index.page.ts +++ b/testing/src/pages/playground/index.page.ts @@ -9,6 +9,10 @@ import { playgroundURL } from '../../support/env'; export class PlaygroundPage { readonly $ref: Page; readonly editorPane: Locator; + /** The instrument list's search box, inside the popover {@link instrumentSelector} opens. */ + readonly instrumentSearch: Locator; + /** The header's button that opens the instrument list. */ + readonly instrumentSelector: Locator; /** The handle between the editor and preview panes, which resizes them. */ readonly paneSeparator: Locator; /** The instrument preview, which runs on another origin than the editor. */ @@ -19,6 +23,8 @@ export class PlaygroundPage { constructor(page: Page) { this.$ref = page; this.editorPane = page.getByTestId('editor-pane'); + this.instrumentSelector = page.getByRole('combobox', { name: 'Load an instrument...' }); + this.instrumentSearch = page.getByPlaceholder('Search...'); this.paneSeparator = page.getByRole('separator'); this.previewFrame = page.getByTestId('preview-frame'); this.preview = page.frameLocator('[data-testid="preview-frame"]'); @@ -46,10 +52,20 @@ export class PlaygroundPage { await this.$ref.goto(url); } + /** The option for the instrument with this label in the header's instrument list. */ + instrumentOption(label: string): Locator { + return this.$ref.getByRole('option', { exact: true, name: label }); + } + + /** Opens the header's instrument list and types `query` into its search box. */ + async searchInstruments(query: string): Promise { + await this.instrumentSelector.click(); + await this.instrumentSearch.fill(query); + } + /** Picks an example or template from the header's instrument list by its label. */ async selectInstrument(label: string): Promise { - await this.$ref.getByRole('combobox', { name: 'Load an instrument...' }).click(); - await this.$ref.getByPlaceholder('Search...').fill(label); - await this.$ref.getByRole('option', { exact: true, name: label }).click(); + await this.searchInstruments(label); + await this.instrumentOption(label).click(); } } diff --git a/testing/src/specs/playground.spec.ts b/testing/src/specs/playground.spec.ts index 62b5d2a18..668ed59e2 100644 --- a/testing/src/specs/playground.spec.ts +++ b/testing/src/specs/playground.spec.ts @@ -168,9 +168,17 @@ test.describe('playground', () => { const playground = new PlaygroundPage(page); await playground.goto(); - await page.getByRole('combobox', { name: 'Load an instrument...' }).click(); - await page.getByPlaceholder('Search...').fill('Form Reference'); - await expect(page.getByRole('option', { exact: true, name: 'Form Reference' })).toBeVisible(); + await playground.searchInstruments('Form Reference'); + await expect(playground.instrumentOption('Form Reference')).toBeVisible(); + }); + + test('should find nothing for a query only the hidden instrument ids match', async ({ page }) => { + const playground = new PlaygroundPage(page); + await playground.goto(); + + // Every v4 UUID carries a 4 in its version digit, and no built-in label contains one. + await playground.searchInstruments('4'); + await expect(page.getByText('No Instruments Found')).toBeVisible(); }); test('should run the preview on an origin other than the editor and render the instrument there @smoke', async ({