diff --git a/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx b/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx index f301f9d11..aefb9426b 100644 --- a/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx +++ b/apps/playground/src/components/Header/InstrumentSelector/InstrumentSelector.tsx @@ -49,9 +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 012ea8ad5..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,12 +52,20 @@ 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. - */ + /** 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.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 2a706b5a7..668ed59e2 100644 --- a/testing/src/specs/playground.spec.ts +++ b/testing/src/specs/playground.spec.ts @@ -164,6 +164,23 @@ 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 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 ({ page }) => {