diff --git a/playwright.config.ts b/playwright.config.ts index 2f475c48..d18bca67 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,4 +1,6 @@ import { defineConfig, devices } from '@playwright/test'; +import { PlaywrightTestConfig } from 'playwright/types/test'; +import { BundleTest } from './test-e2e/bundle-test'; /** * Read environment variables from file. @@ -10,7 +12,7 @@ import { defineConfig, devices } from '@playwright/test'; /** * See https://playwright.dev/docs/test-configuration. */ -export default defineConfig({ +const config: PlaywrightTestConfig = { testDir: './test-e2e', /* Run tests in files in parallel */ fullyParallel: true, @@ -21,7 +23,7 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: process.env.CI ? 'dot' : 'list', + reporter: 'dot',//process.env.CI ? 'dot' : 'list', timeout: 2000, /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { @@ -76,4 +78,35 @@ export default defineConfig({ // url: 'http://127.0.0.1:3001', // reuseExistingServer: !process.env.CI, //}, -}); +}; + +const bundles = [ + { + name: '', + bundle: '/assets/scripts/choices.js', + }, + { + name: ' - prod', + bundle: '/assets/scripts/choices.min.js', + }, +]; +const projects = config.projects; +if (config.use.baseURL) { + config.projects = []; + + projects.forEach((project) => { + bundles.forEach(({ name, bundle }) => { + const projectBundle = { + ...project, + name: project.name + name, + use: { + ...project.use, + bundle: config.use.baseURL + bundle, + } + }; + config.projects.push(projectBundle); + }); + }); +} + +export default defineConfig(config); \ No newline at end of file diff --git a/test-e2e/bundle-test.ts b/test-e2e/bundle-test.ts new file mode 100644 index 00000000..e142e0a0 --- /dev/null +++ b/test-e2e/bundle-test.ts @@ -0,0 +1,13 @@ +import { test as base } from '@playwright/test'; + +export { expect } from '@playwright/test'; + +export type BundleTest = { + bundle: string | undefined; +}; + +export const test = base.extend({ + // Define an option and provide a default value. + // We can later override it in the config. + bundle: [undefined, { option: true }], +}); diff --git a/test-e2e/select-test-suit.ts b/test-e2e/select-test-suit.ts index 5c219c73..cd86f07c 100644 --- a/test-e2e/select-test-suit.ts +++ b/test-e2e/select-test-suit.ts @@ -10,8 +10,8 @@ export class SelectTestSuit extends TestSuit { readonly itemsWithPlaceholder: Locator; - constructor(page: Page, baseURL: string | undefined, choicesBundle: string, url: string, testId: string) { - super(page, baseURL, choicesBundle, url, testId); + constructor(page: Page, choicesBundle: string | undefined, url: string, testId: string) { + super(page, choicesBundle, url, testId); this.wrappedSelect = this.group.locator('select'); this.choices = this.dropdown.locator('.choices__item'); diff --git a/test-e2e/test-suit.ts b/test-e2e/test-suit.ts index 03acd50e..539081bf 100644 --- a/test-e2e/test-suit.ts +++ b/test-e2e/test-suit.ts @@ -1,19 +1,6 @@ import { expect, type Locator, type Page } from '@playwright/test'; export class TestSuit { - static testBundles(): { name: string; bundle: string }[] { - return [ - { - name: 'Dev', - bundle: '/assets/scripts/choices.js', - }, - { - name: 'Prod', - bundle: '/assets/scripts/choices.min.js', - }, - ]; - } - readonly testId: string; readonly url: string; @@ -32,10 +19,10 @@ export class TestSuit { readonly dropdown: Locator; - readonly choicesBundle: string; + readonly choicesBundle: string | undefined; - constructor(page: Page, baseURL: string | undefined, choicesBundle: string, url: string, testId: string) { - this.choicesBundle = baseURL ? baseURL + choicesBundle : choicesBundle; + constructor(page: Page, choicesBundle: string | undefined, url: string, testId: string) { + this.choicesBundle = choicesBundle; this.testId = testId; this.url = url; this.page = page; @@ -48,8 +35,10 @@ export class TestSuit { } async start(textInput?: string): Promise { - await this.page.route('/assets/scripts/choices.js', (route) => route.continue({ url: this.choicesBundle })); - await this.page.route('/assets/scripts/choices.min.js', (route) => route.continue({ url: this.choicesBundle })); + if (this.choicesBundle) { + await this.page.route('/assets/scripts/choices.js', (route) => route.continue({ url: this.choicesBundle })); + await this.page.route('/assets/scripts/choices.min.js', (route) => route.continue({ url: this.choicesBundle })); + } // disable google analytics, as it can weirdly fail sometimes await this.page.route('https://www.google-analytics.com/analytics.js', (route) => route.abort('blockedbyresponse')); diff --git a/test-e2e/tests/select-multiple.spec.ts b/test-e2e/tests/select-multiple.spec.ts index 9c031d0d..ff90a1f1 100644 --- a/test-e2e/tests/select-multiple.spec.ts +++ b/test-e2e/tests/select-multiple.spec.ts @@ -1,14 +1,762 @@ -import { test, expect } from '@playwright/test'; +import { test, expect } from '../bundle-test'; import { SelectTestSuit } from '../select-test-suit'; const { describe } = test; -describe.configure({ mode: 'serial', retries: 0 }); +// describe.configure({ mode: 'serial', retries: 0 }); const testUrl = '/test/select-multiple/index.html'; -SelectTestSuit.testBundles().forEach(({ name, bundle }) => { - describe(`Choices - select multiple ${name}`, () => { - describe('scenarios', () => { +describe(`Choices - select multiple`, () => { + describe('scenarios', () => { + describe('basic', () => { + const testId = 'basic'; + const inputValue = 'test'; + describe('focusing on container', () => { + describe('pressing enter key', () => { + test('toggles the dropdown', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.wrapper.focus(); + await suite.enterKey(); + await suite.expectVisibleDropdown(); + await suite.enterKey(); + await suite.expectHiddenDropdown(); + }); + }); + + describe('pressing an alpha-numeric key', () => { + test('opens the dropdown and the input value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.selectByKeyPress(inputValue); + await expect(suite.input).toHaveValue(inputValue); + }); + }); + }); + + describe('selecting choices', () => { + const selectedChoiceText = 'Choice 1'; + + test('allows selecting choices from dropdown', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await suite.choices.first().click(); + await expect(suite.itemList.last()).toHaveText(selectedChoiceText); + }); + + test('does not remove selected choice from dropdown list', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const choice = suite.choices.first(); + await choice.click(); + await expect(choice).toHaveText(selectedChoiceText); + await expect(suite.itemList.last()).toHaveText(selectedChoiceText); + }); + }); + + describe('keys for choice', () => { + test('up/down arrows for selection', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await suite.input.press('ArrowDown'); + await expect(suite.choices.first()).not.toHaveClass(/is-highlighted/); + await expect(suite.choices.nth(1)).toHaveClass(/is-highlighted/); + + await suite.input.press('ArrowUp'); + await expect(suite.choices.first()).toHaveClass(/is-highlighted/); + await expect(suite.choices.nth(1)).not.toHaveClass(/is-highlighted/); + }); + + test('page-up/page-down arrows for selection', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await suite.input.press('PageDown'); + await expect(suite.choices.first()).not.toHaveClass(/is-highlighted/); + await expect(suite.choices.last()).toHaveClass(/is-highlighted/); + + await suite.input.press('PageUp'); + await expect(suite.choices.first()).toHaveClass(/is-highlighted/); + await expect(suite.choices.last()).not.toHaveClass(/is-highlighted/); + }); + }); + + describe('searching choices', () => { + describe('on input', () => { + describe('searching by label', () => { + test('displays choices filtered by inputted value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText('item2'); + + await suite.expectVisibleDropdown('Choice 2'); + }); + }); + + describe('searching by value', () => { + test('displays choices filtered by inputted value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText('find me'); + + await suite.expectVisibleDropdown('Choice 3'); + }); + }); + + describe('no results found', () => { + test('displays "no results found" prompt', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText('faergge'); + + await suite.expectVisibleDropdown('No results found'); + }); + }); + }); + }); + + describe('disabling', () => { + describe('on disable', () => { + test('disables the search input', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.group.locator('button.disable').click(); + await expect(suite.wrapper).toBeDisabled(); + await expect(suite.input).toBeDisabled(); + }); + }); + }); + + describe('enabling', () => { + describe('on enable', () => { + test('enables the search input', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.group.locator('button.disable').click(); + await suite.group.locator('button.enable').click(); + await expect(suite.wrapper).toBeEnabled(); + await expect(suite.input).toBeEnabled(); + }); + }); + }); + }); + + describe('remove button', () => { + const testId = 'remove-button'; + describe('on click', () => { + test('removes default', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.expectHiddenDropdown(); + + await suite.expectedItemCount(1); + await suite.expectedValue('Choice 1'); + + await suite.items.getByRole('button', { name: 'Remove item' }).first().click(); + await suite.advanceClock(); + + await suite.expectedItemCount(0); + await suite.expectedValue(''); + await suite.expectHiddenDropdown(); + }); + + test('removes selected choice', async ({ page, bundle }) => { + const defaultChoice = 'Choice 1'; + const selectedChoice = 'Choice 4'; + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await suite.expectedItemCount(1); + await suite.expectedValue(defaultChoice); + + const choice = suite.choices.last(); + await expect(choice).toHaveText(selectedChoice); + await choice.click(); + + await suite.expectedItemCount(1); + await suite.expectedValue(selectedChoice); + + await suite.items.getByRole('button', { name: 'Remove item' }).first().click(); + + await suite.expectedItemCount(0); + await suite.expectedValue(''); + await suite.expectHiddenDropdown(); + }); + }); + }); + + describe('disabled choice', () => { + const testId = 'disabled-choice'; + const firstChoice = 'Choice 1'; + test('does not change selected choice', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await suite.expectedItemCount(1); + await suite.expectedValue(firstChoice); + + const choice = suite.choices.last(); + await expect(choice).toBeDisabled(); + await choice.click({ force: true }); + + await suite.expectedItemCount(1); + await suite.expectedValue(firstChoice); + }); + }); + + describe('Disabled first choice by options', () => { + const testId = 'disabled-first-choice-via-options'; + const firstChoice = 'Choice 2'; + test('does not change selected choice', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await suite.expectedItemCount(1); + await suite.expectedValue(firstChoice); + + const choice = suite.choices.first(); + await expect(choice).toBeDisabled(); + await choice.click({ force: true }); + + await suite.expectedItemCount(1); + await suite.expectedValue(firstChoice); + }); + }); + + describe('disabled via attribute', () => { + const testId = 'disabled-via-attr'; + test('disables the search input', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.wrapper.click({ force: true }); + + await expect(suite.wrapper).toBeDisabled(); + await suite.expectHiddenDropdown(); + }); + }); + + describe('disabled via fieldset', () => { + const testId = 'disabled-via-fieldset'; + test('disables the search input', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.wrapper.click({ force: true }); + + await expect(suite.wrapper).toBeDisabled(); + await suite.expectHiddenDropdown(); + }); + }); + + describe('prepend/append', () => { + const testId = 'prepend-append'; + const textInput = 'Choice 1'; + test('prepends and appends value to inputted value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const item = suite.items.first(); + await expect(item).toHaveText(textInput); + await expect(item).toHaveAttribute('data-value', `before-${textInput}-after`); + }); + }); + + describe('render choice limit', () => { + const testId = 'render-choice-limit'; + test('only displays given number of choices in the dropdown', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + expect(await suite.choices.count()).toEqual(1); + }); + }); + + describe('search disabled', () => { + const testId = 'search-disabled'; + test('does not display a search input', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await expect(suite.input).toHaveCount(0); + }); + + test('allows selecting choices from dropdown', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const choice = suite.choices.last(); + const text = await choice.innerText(); + await expect(choice).toBeEnabled(); + await choice.click(); + + await suite.expectedItemCount(1); + await suite.expectedValue(text); + await suite.expectHiddenDropdown(); + }); + }); + + describe('search floor', () => { + const testId = 'search-floor'; + describe('on input', () => { + describe('search floor not reached', () => { + test('displays choices not filtered by inputted value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const searchTerm = 'item 2'; + await suite.typeText(searchTerm); + await suite.expectVisibleDropdown(); + await expect(suite.choices.first()).not.toHaveText(searchTerm); + }); + }); + + describe('search floor reached', () => { + test('displays choices filtered by inputted value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const searchTerm = 'Choice 2'; + + await suite.typeText(searchTerm); + await suite.expectVisibleDropdown(); + await expect(suite.choices.first()).toHaveText(searchTerm); + }); + }); + }); + }); + + [ + { + name: 'empty option value', + testId: 'placeholder-via-option-value', + }, + { + name: 'option attribute', + testId: 'placeholder-via-option-attr', + }, + { + name: 'data attribute', + testId: 'placeholder-via-data-attr', + }, + ].forEach((arg) => { + const { testId } = arg; + describe(`Placeholder via ${arg.name}`, () => { + describe('when no choice has been selected', () => { + test('displays a placeholder', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await suite.expectedItemCount(0); + await suite.expectedValue(''); + + const item = suite.itemsWithPlaceholder.first(); + await expect(item).toHaveClass(/choices__placeholder/); + await expect(item).toHaveText('I am a placeholder'); + }); + }); + + describe('when a choice has been selected', () => { + test('does not display a placeholder', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const choice = suite.selectableChoices.first(); + await choice.click(); + + const item = suite.itemsWithPlaceholder.first(); + await expect(item).not.toHaveClass(/choices__placeholder/); + await expect(item).not.toHaveText('I am a placeholder'); + await suite.expectHiddenDropdown(); + }); + }); + + describe('when choice list is open', () => { + if (testId === 'placeholder-via-data-attr') { + test('does not displays the placeholder choice first', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const choice = suite.choices.first(); + await expect(choice).not.toHaveClass(/choices__placeholder/); + await expect(choice).not.toHaveText('I am a placeholder'); + }); + } else { + test('displays the placeholder choice first', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const choice = suite.choices.first(); + await expect(choice).toHaveClass(/choices__placeholder/); + await expect(choice).toHaveText('I am a placeholder'); + }); + } + }); + }); + }); + + describe('remote data', () => { + const testId = 'remote-data'; + describe('when loading data', () => { + test('shows a loading message as a placeholder', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.expectHiddenDropdown(); + await expect(suite.dropdown).toBeDisabled(); + + const placeholder = suite.itemsWithPlaceholder.first(); + await expect(placeholder).toHaveClass(/choices__placeholder/); + await expect(placeholder).toHaveText('Loading...'); + }); + }); + + describe('when data has loaded', () => { + describe('opening the dropdown', () => { + test('displays the loaded data', async ({ page, bundle }) => { + const jsonLoad = page.waitForResponse('**/data.json'); + + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + + await jsonLoad; + await suite.selectByClick(); + + const placeholder = suite.itemsWithPlaceholder.first(); + await expect(placeholder).toHaveClass(/choices__placeholder/); + await expect(placeholder).toHaveText('I am a placeholder'); + await expect(suite.selectableChoices).toHaveCount(10); + }); + }); + }); + }); + + describe('scrolling dropdown', () => { + const testId = 'scrolling-dropdown'; + test('shows partial choices list', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await expect(suite.selectableChoices).toHaveCount(15); + // @todo determine how to assert items + // await expect(suite.selectableChoices.filter({ has: page.locator(':scope:visible') })).toHaveCount(8); + }); + }); + + describe('choice groups', () => { + describe('just groups', () => { + const testId = 'groups'; + test('displays', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await expect(suite.dropdown.locator('.choices__group[data-group]')).toHaveCount(2); + }); + }); + + describe('groups and choices', () => { + const testId = 'mixed-groups'; + test('displays', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await expect(suite.dropdown.locator('.choices__group[data-group]')).toHaveCount(1); + expect( + await suite.selectableChoices.filter({ hasNot: page.locator('[data-group-id]') }).count(), + ).toBeGreaterThan(0); + }); + }); + }); + + describe('parent/child', () => { + const testId = 'parent-child'; + describe('selecting "Parent choice 2"', () => { + test('enables/disables the child Choices instance', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + + const parent = suite.group.locator('.choices').nth(0); + const child = suite.group.locator('.choices').nth(1); + await expect(parent).toBeEnabled(); + await expect(child).toBeDisabled(); + + await parent.click(); + await expect(suite.dropdown.first()).toBeVisible(); + const parentChoices = parent.locator('.choices__item:not(.choices__placeholder)'); + + await parentChoices.filter({ hasText: 'Parent choice 2' }).click(); + + await expect(child).toBeEnabled(); + + await parent.click(); + await expect(suite.dropdown.first()).toBeVisible(); + const choice = parentChoices.filter({ hasText: 'Parent choice 3' }); + await choice.click(); + + await expect(child).toBeDisabled(); + }); + }); + }); + + describe('custom properties via config', () => { + const testId = 'custom-properties'; + describe('on input', () => { + [ + { + country: 'Germany', + city: 'Berlin', + }, + { + country: 'United Kingdom', + city: 'London', + }, + { + country: 'Portugal', + city: 'Lisbon', + }, + ].forEach(({ country, city }) => { + test(`filters choices - ${country} = ${city}`, async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(country); + await suite.expectVisibleDropdown(); + + const choice = suite.selectableChoices.first(); + await expect(choice).toHaveText(city); + }); + }); + }); + }); + + describe('custom properties via html', () => { + const testId = 'custom-properties-html'; + describe('on input', () => { + [ + { + searchText: 'fantastic', + label: 'Label Three', + }, + { + searchText: 'foo', + label: 'Label Four', + }, + ].forEach(({ searchText, label }) => { + test(`filters choices - ${searchText} = ${label}`, async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(searchText); + await suite.expectVisibleDropdown(); + + const choice = suite.selectableChoices.first(); + await expect(choice).toHaveText(label); + }); + }); + }); + }); + + describe('non-string values', () => { + const testId = 'non-string-values'; + test('displays expected amount of choices in dropdown', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await expect(suite.selectableChoices).toHaveCount(4); + }); + + test('allows selecting choices from dropdown', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const choice = suite.selectableChoices.first(); + const choiceText = await choice.textContent(); + expect(choiceText).toBeTruthy(); + await choice.click(); + + await expect(suite.items.first()).toHaveText(choiceText as string); + }); + }); + + describe('within form', () => { + const testId = 'within-form'; + describe('selecting choice', () => { + describe('on enter key', () => { + test('does not submit form', async ({ page, bundle }) => { + let submit = false; + await page.route(page.url(), (route) => { + submit = true; + + return route.abort(); + }); + + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.expectVisibleDropdown(); + + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await suite.selectByClick(); + await suite.expectVisibleDropdown(); + + const choice = suite.choices.first(); + const text = await choice.innerText(); + await expect(choice).toBeEnabled(); + await choice.click(); + await suite.advanceClock(); + + await suite.expectedItemCount(1); + await suite.expectedValue(text); + expect(submit).toEqual(false); + await suite.expectHiddenDropdown(); + }); + }); + }); + }); + + describe('dynamically setting choice by value', () => { + const dynamicallySelectedChoiceValue = 'Choice 2'; + const testId = 'set-choice-by-value'; + test('selects choice', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await expect(suite.items).toHaveText(dynamicallySelectedChoiceValue); + }); + + test('does not remove choice from dropdown list', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await expect(suite.choices.filter({ hasText: dynamicallySelectedChoiceValue })).toHaveCount(1); + }); + + test('updates the value of the original input', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await suite.expectedValue(dynamicallySelectedChoiceValue); + }); + }); + + describe('searching by label only', () => { + const testId = 'search-by-label'; + test('gets zero results when searching by value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText('item2'); + + await suite.expectVisibleDropdown('No results found'); + }); + + test('gets a result when searching by label', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText('label1'); + await suite.expectVisibleDropdown(); + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await expect(suite.items.filter({ hasText: 'label1' })).not.toHaveCount(0); + }); + }); + + describe('html allowed', () => { + const textInput = 'testing'; + const htmlInput = `${textInput}`; + describe('set to undefined', () => { + const testId = 'allowhtml-undefined'; + test('does not show html', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(htmlInput); + await suite.expectVisibleDropdown(); + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await expect(suite.items.first()).toHaveText(htmlInput); + }); + }); + + describe('set to true', () => { + const testId = 'allowhtml-true'; + test('does not show html as text', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(htmlInput); + await suite.expectVisibleDropdown(); + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await expect(suite.items.first()).toHaveText(textInput); + }); + }); + + describe('set to true - except user input', () => { + const testId = 'allowhtml-true-userinput-false'; + test('does not show html as text', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(htmlInput); + await suite.expectVisibleDropdown(); + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await expect(suite.items.first()).toHaveText(htmlInput); + }); + }); + + describe('set to false', () => { + const testId = 'allowhtml-false'; + test('does not show html as text', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(htmlInput); + await suite.expectVisibleDropdown(); + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await expect(suite.items.first()).toHaveText(htmlInput); + }); + }); + }); + + describe('re-initialising a choices instance', () => { + const testId = 'new-destroy-init'; + const testvalue = 'Choice 2'; + test('preserves the choices & items lists', async ({ page, bundle }) => { + let suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(testvalue); + await suite.expectVisibleDropdown(); + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await expect(suite.choices).toHaveCount(3); + + await suite.group.locator('.destroy').click({ force: true }); + + await expect(suite.group.locator('select > option')).toHaveCount(3); + + await suite.group.locator('.init').click({ force: true }); + + suite = new SelectTestSuit(page, bundle, testUrl, testId); + await expect(suite.choices).toHaveCount(3); + await suite.expectedValue(testvalue); + }); + + test('preserves the original select element', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(testvalue); + await suite.expectVisibleDropdown(); + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await expect(suite.choices).toHaveCount(3); + + await expect(suite.group.locator('select > option')).toHaveCount(3); + expect(await suite.getWrappedElement().inputValue()).toEqual(testvalue); + }); }); }); }); diff --git a/test-e2e/tests/select-one.spec.ts b/test-e2e/tests/select-one.spec.ts index acbf91a1..51849ed1 100644 --- a/test-e2e/tests/select-one.spec.ts +++ b/test-e2e/tests/select-one.spec.ts @@ -1,764 +1,762 @@ -import { test, expect } from '@playwright/test'; +import { test, expect } from '../bundle-test'; import { SelectTestSuit } from '../select-test-suit'; const { describe } = test; const testUrl = '/test/select-one/index.html'; -SelectTestSuit.testBundles().forEach(({ name, bundle }) => { - describe(`Choices - select one ${name}`, () => { - describe('scenarios', () => { - describe('basic', () => { - const testId = 'basic'; - const inputValue = 'test'; +describe(`Choices - select one`, () => { + describe('scenarios', () => { + describe('basic', () => { + const testId = 'basic'; + const inputValue = 'test'; - describe('focusing on container', () => { - describe('pressing enter key', () => { - test('toggles the dropdown', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - await suite.wrapper.focus(); - await suite.enterKey(); - await suite.expectVisibleDropdown(); - await suite.enterKey(); - await suite.expectHiddenDropdown(); - }); - }); - - describe('pressing an alpha-numeric key', () => { - test('opens the dropdown and the input value', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - await suite.selectByKeyPress(inputValue); - await expect(suite.input).toHaveValue(inputValue); - }); - }); - }); - - describe('selecting choices', () => { - const selectedChoiceText = 'Choice 1'; - - test('allows selecting choices from dropdown', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - await suite.choices.first().click(); - await expect(suite.itemList.last()).toHaveText(selectedChoiceText); - }); - - test('does not remove selected choice from dropdown list', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - const choice = suite.choices.first(); - await choice.click(); - await expect(choice).toHaveText(selectedChoiceText); - await expect(suite.itemList.last()).toHaveText(selectedChoiceText); - }); - }); - - describe('keys for choice', () => { - test('up/down arrows for selection', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - await suite.input.press('ArrowDown'); - await expect(suite.choices.first()).not.toHaveClass(/is-highlighted/); - await expect(suite.choices.nth(1)).toHaveClass(/is-highlighted/); - - await suite.input.press('ArrowUp'); - await expect(suite.choices.first()).toHaveClass(/is-highlighted/); - await expect(suite.choices.nth(1)).not.toHaveClass(/is-highlighted/); - }); - - test('page-up/page-down arrows for selection', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - await suite.input.press('PageDown'); - await expect(suite.choices.first()).not.toHaveClass(/is-highlighted/); - await expect(suite.choices.last()).toHaveClass(/is-highlighted/); - - await suite.input.press('PageUp'); - await expect(suite.choices.first()).toHaveClass(/is-highlighted/); - await expect(suite.choices.last()).not.toHaveClass(/is-highlighted/); - }); - }); - - describe('searching choices', () => { - describe('on input', () => { - describe('searching by label', () => { - test('displays choices filtered by inputted value', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.typeText('item2'); - - await suite.expectVisibleDropdown('Choice 2'); - }); - }); - - describe('searching by value', () => { - test('displays choices filtered by inputted value', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.typeText('find me'); - - await suite.expectVisibleDropdown('Choice 3'); - }); - }); - - describe('no results found', () => { - test('displays "no results found" prompt', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.typeText('faergge'); - - await suite.expectVisibleDropdown('No results found'); - }); - }); - }); - }); - - describe('disabling', () => { - describe('on disable', () => { - test('disables the search input', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.group.locator('button.disable').click(); - await expect(suite.wrapper).toBeDisabled(); - await expect(suite.input).toBeDisabled(); - }); - }); - }); - - describe('enabling', () => { - describe('on enable', () => { - test('enables the search input', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.group.locator('button.disable').click(); - await suite.group.locator('button.enable').click(); - await expect(suite.wrapper).toBeEnabled(); - await expect(suite.input).toBeEnabled(); - }); - }); - }); - }); - - describe('remove button', () => { - const testId = 'remove-button'; - describe('on click', () => { - test('removes default', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); + describe('focusing on container', () => { + describe('pressing enter key', () => { + test('toggles the dropdown', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); await suite.start(); - await suite.expectHiddenDropdown(); - - await suite.expectedItemCount(1); - await suite.expectedValue('Choice 1'); - - await suite.items.getByRole('button', { name: 'Remove item' }).first().click(); - await suite.advanceClock(); - - await suite.expectedItemCount(0); - await suite.expectedValue(''); + await suite.wrapper.focus(); + await suite.enterKey(); + await suite.expectVisibleDropdown(); + await suite.enterKey(); await suite.expectHiddenDropdown(); }); + }); - test('removes selected choice', async ({ page, baseURL }) => { - const defaultChoice = 'Choice 1'; - const selectedChoice = 'Choice 4'; - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - await suite.expectedItemCount(1); - await suite.expectedValue(defaultChoice); - - const choice = suite.choices.last(); - await expect(choice).toHaveText(selectedChoice); - await choice.click(); - - await suite.expectedItemCount(1); - await suite.expectedValue(selectedChoice); - - await suite.items.getByRole('button', { name: 'Remove item' }).first().click(); - - await suite.expectedItemCount(0); - await suite.expectedValue(''); - await suite.expectHiddenDropdown(); + describe('pressing an alpha-numeric key', () => { + test('opens the dropdown and the input value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.selectByKeyPress(inputValue); + await expect(suite.input).toHaveValue(inputValue); }); }); }); - describe('disabled choice', () => { - const testId = 'disabled-choice'; - const firstChoice = 'Choice 1'; - test('does not change selected choice', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); + describe('selecting choices', () => { + const selectedChoiceText = 'Choice 1'; + + test('allows selecting choices from dropdown', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); await suite.startWithClick(); - await suite.expectedItemCount(1); - await suite.expectedValue(firstChoice); - - const choice = suite.choices.last(); - await expect(choice).toBeDisabled(); - await choice.click({ force: true }); - - await suite.expectedItemCount(1); - await suite.expectedValue(firstChoice); + await suite.choices.first().click(); + await expect(suite.itemList.last()).toHaveText(selectedChoiceText); }); - }); - describe('Disabled first choice by options', () => { - const testId = 'disabled-first-choice-via-options'; - const firstChoice = 'Choice 2'; - test('does not change selected choice', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); + test('does not remove selected choice from dropdown list', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); await suite.startWithClick(); - await suite.expectedItemCount(1); - await suite.expectedValue(firstChoice); - const choice = suite.choices.first(); - await expect(choice).toBeDisabled(); - await choice.click({ force: true }); + await choice.click(); + await expect(choice).toHaveText(selectedChoiceText); + await expect(suite.itemList.last()).toHaveText(selectedChoiceText); + }); + }); + + describe('keys for choice', () => { + test('up/down arrows for selection', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await suite.input.press('ArrowDown'); + await expect(suite.choices.first()).not.toHaveClass(/is-highlighted/); + await expect(suite.choices.nth(1)).toHaveClass(/is-highlighted/); + + await suite.input.press('ArrowUp'); + await expect(suite.choices.first()).toHaveClass(/is-highlighted/); + await expect(suite.choices.nth(1)).not.toHaveClass(/is-highlighted/); + }); + + test('page-up/page-down arrows for selection', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await suite.input.press('PageDown'); + await expect(suite.choices.first()).not.toHaveClass(/is-highlighted/); + await expect(suite.choices.last()).toHaveClass(/is-highlighted/); + + await suite.input.press('PageUp'); + await expect(suite.choices.first()).toHaveClass(/is-highlighted/); + await expect(suite.choices.last()).not.toHaveClass(/is-highlighted/); + }); + }); + + describe('searching choices', () => { + describe('on input', () => { + describe('searching by label', () => { + test('displays choices filtered by inputted value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText('item2'); + + await suite.expectVisibleDropdown('Choice 2'); + }); + }); + + describe('searching by value', () => { + test('displays choices filtered by inputted value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText('find me'); + + await suite.expectVisibleDropdown('Choice 3'); + }); + }); + + describe('no results found', () => { + test('displays "no results found" prompt', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText('faergge'); + + await suite.expectVisibleDropdown('No results found'); + }); + }); + }); + }); + + describe('disabling', () => { + describe('on disable', () => { + test('disables the search input', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.group.locator('button.disable').click(); + await expect(suite.wrapper).toBeDisabled(); + await expect(suite.input).toBeDisabled(); + }); + }); + }); + + describe('enabling', () => { + describe('on enable', () => { + test('enables the search input', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.group.locator('button.disable').click(); + await suite.group.locator('button.enable').click(); + await expect(suite.wrapper).toBeEnabled(); + await expect(suite.input).toBeEnabled(); + }); + }); + }); + }); + + describe('remove button', () => { + const testId = 'remove-button'; + describe('on click', () => { + test('removes default', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.expectHiddenDropdown(); await suite.expectedItemCount(1); - await suite.expectedValue(firstChoice); - }); - }); + await suite.expectedValue('Choice 1'); - describe('disabled via attribute', () => { - const testId = 'disabled-via-attr'; - test('disables the search input', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - await suite.wrapper.click({ force: true }); + await suite.items.getByRole('button', { name: 'Remove item' }).first().click(); + await suite.advanceClock(); - await expect(suite.wrapper).toBeDisabled(); + await suite.expectedItemCount(0); + await suite.expectedValue(''); await suite.expectHiddenDropdown(); }); - }); - describe('disabled via fieldset', () => { - const testId = 'disabled-via-fieldset'; - test('disables the search input', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - await suite.wrapper.click({ force: true }); - - await expect(suite.wrapper).toBeDisabled(); - await suite.expectHiddenDropdown(); - }); - }); - - describe('prepend/append', () => { - const testId = 'prepend-append'; - const textInput = 'Choice 1'; - test('prepends and appends value to inputted value', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); + test('removes selected choice', async ({ page, bundle }) => { + const defaultChoice = 'Choice 1'; + const selectedChoice = 'Choice 4'; + const suite = new SelectTestSuit(page, bundle, testUrl, testId); await suite.startWithClick(); - const item = suite.items.first(); - await expect(item).toHaveText(textInput); - await expect(item).toHaveAttribute('data-value', `before-${textInput}-after`); - }); - }); - - describe('render choice limit', () => { - const testId = 'render-choice-limit'; - test('only displays given number of choices in the dropdown', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - expect(await suite.choices.count()).toEqual(1); - }); - }); - - describe('search disabled', () => { - const testId = 'search-disabled'; - test('does not display a search input', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - await expect(suite.input).toHaveCount(0); - }); - - test('allows selecting choices from dropdown', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); + await suite.expectedItemCount(1); + await suite.expectedValue(defaultChoice); const choice = suite.choices.last(); - const text = await choice.innerText(); - await expect(choice).toBeEnabled(); + await expect(choice).toHaveText(selectedChoice); await choice.click(); await suite.expectedItemCount(1); - await suite.expectedValue(text); + await suite.expectedValue(selectedChoice); + + await suite.items.getByRole('button', { name: 'Remove item' }).first().click(); + + await suite.expectedItemCount(0); + await suite.expectedValue(''); await suite.expectHiddenDropdown(); }); }); + }); - describe('search floor', () => { - const testId = 'search-floor'; - describe('on input', () => { - describe('search floor not reached', () => { - test('displays choices not filtered by inputted value', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); + describe('disabled choice', () => { + const testId = 'disabled-choice'; + const firstChoice = 'Choice 1'; + test('does not change selected choice', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); - const searchTerm = 'item 2'; - await suite.typeText(searchTerm); - await suite.expectVisibleDropdown(); - await expect(suite.choices.first()).not.toHaveText(searchTerm); - }); + await suite.expectedItemCount(1); + await suite.expectedValue(firstChoice); + + const choice = suite.choices.last(); + await expect(choice).toBeDisabled(); + await choice.click({ force: true }); + + await suite.expectedItemCount(1); + await suite.expectedValue(firstChoice); + }); + }); + + describe('Disabled first choice by options', () => { + const testId = 'disabled-first-choice-via-options'; + const firstChoice = 'Choice 2'; + test('does not change selected choice', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await suite.expectedItemCount(1); + await suite.expectedValue(firstChoice); + + const choice = suite.choices.first(); + await expect(choice).toBeDisabled(); + await choice.click({ force: true }); + + await suite.expectedItemCount(1); + await suite.expectedValue(firstChoice); + }); + }); + + describe('disabled via attribute', () => { + const testId = 'disabled-via-attr'; + test('disables the search input', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.wrapper.click({ force: true }); + + await expect(suite.wrapper).toBeDisabled(); + await suite.expectHiddenDropdown(); + }); + }); + + describe('disabled via fieldset', () => { + const testId = 'disabled-via-fieldset'; + test('disables the search input', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.wrapper.click({ force: true }); + + await expect(suite.wrapper).toBeDisabled(); + await suite.expectHiddenDropdown(); + }); + }); + + describe('prepend/append', () => { + const testId = 'prepend-append'; + const textInput = 'Choice 1'; + test('prepends and appends value to inputted value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const item = suite.items.first(); + await expect(item).toHaveText(textInput); + await expect(item).toHaveAttribute('data-value', `before-${textInput}-after`); + }); + }); + + describe('render choice limit', () => { + const testId = 'render-choice-limit'; + test('only displays given number of choices in the dropdown', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + expect(await suite.choices.count()).toEqual(1); + }); + }); + + describe('search disabled', () => { + const testId = 'search-disabled'; + test('does not display a search input', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await expect(suite.input).toHaveCount(0); + }); + + test('allows selecting choices from dropdown', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const choice = suite.choices.last(); + const text = await choice.innerText(); + await expect(choice).toBeEnabled(); + await choice.click(); + + await suite.expectedItemCount(1); + await suite.expectedValue(text); + await suite.expectHiddenDropdown(); + }); + }); + + describe('search floor', () => { + const testId = 'search-floor'; + describe('on input', () => { + describe('search floor not reached', () => { + test('displays choices not filtered by inputted value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const searchTerm = 'item 2'; + await suite.typeText(searchTerm); + await suite.expectVisibleDropdown(); + await expect(suite.choices.first()).not.toHaveText(searchTerm); }); + }); - describe('search floor reached', () => { - test('displays choices filtered by inputted value', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); + describe('search floor reached', () => { + test('displays choices filtered by inputted value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); - const searchTerm = 'Choice 2'; + const searchTerm = 'Choice 2'; - await suite.typeText(searchTerm); - await suite.expectVisibleDropdown(); - await expect(suite.choices.first()).toHaveText(searchTerm); - }); + await suite.typeText(searchTerm); + await suite.expectVisibleDropdown(); + await expect(suite.choices.first()).toHaveText(searchTerm); }); }); }); + }); - [ - { - name: 'empty option value', - testId: 'placeholder-via-option-value', - }, - { - name: 'option attribute', - testId: 'placeholder-via-option-attr', - }, - { - name: 'data attribute', - testId: 'placeholder-via-data-attr', - }, - ].forEach((arg) => { - const { testId } = arg; - describe(`Placeholder via ${arg.name}`, () => { - describe('when no choice has been selected', () => { - test('displays a placeholder', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); + [ + { + name: 'empty option value', + testId: 'placeholder-via-option-value', + }, + { + name: 'option attribute', + testId: 'placeholder-via-option-attr', + }, + { + name: 'data attribute', + testId: 'placeholder-via-data-attr', + }, + ].forEach((arg) => { + const { testId } = arg; + describe(`Placeholder via ${arg.name}`, () => { + describe('when no choice has been selected', () => { + test('displays a placeholder', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); - await suite.expectedItemCount(0); - await suite.expectedValue(''); + await suite.expectedItemCount(0); + await suite.expectedValue(''); - const item = suite.itemsWithPlaceholder.first(); - await expect(item).toHaveClass(/choices__placeholder/); - await expect(item).toHaveText('I am a placeholder'); - }); - }); - - describe('when a choice has been selected', () => { - test('does not display a placeholder', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - const choice = suite.selectableChoices.first(); - await choice.click(); - - const item = suite.itemsWithPlaceholder.first(); - await expect(item).not.toHaveClass(/choices__placeholder/); - await expect(item).not.toHaveText('I am a placeholder'); - await suite.expectHiddenDropdown(); - }); - }); - - describe('when choice list is open', () => { - if (testId === 'placeholder-via-data-attr') { - test('does not displays the placeholder choice first', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - const choice = suite.choices.first(); - await expect(choice).not.toHaveClass(/choices__placeholder/); - await expect(choice).not.toHaveText('I am a placeholder'); - }); - } else { - test('displays the placeholder choice first', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - const choice = suite.choices.first(); - await expect(choice).toHaveClass(/choices__placeholder/); - await expect(choice).toHaveText('I am a placeholder'); - }); - } + const item = suite.itemsWithPlaceholder.first(); + await expect(item).toHaveClass(/choices__placeholder/); + await expect(item).toHaveText('I am a placeholder'); }); }); - }); - describe('remote data', () => { - const testId = 'remote-data'; - describe('when loading data', () => { - test('shows a loading message as a placeholder', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); + describe('when a choice has been selected', () => { + test('does not display a placeholder', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const choice = suite.selectableChoices.first(); + await choice.click(); + + const item = suite.itemsWithPlaceholder.first(); + await expect(item).not.toHaveClass(/choices__placeholder/); + await expect(item).not.toHaveText('I am a placeholder'); await suite.expectHiddenDropdown(); - await expect(suite.dropdown).toBeDisabled(); + }); + }); + + describe('when choice list is open', () => { + if (testId === 'placeholder-via-data-attr') { + test('does not displays the placeholder choice first', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const choice = suite.choices.first(); + await expect(choice).not.toHaveClass(/choices__placeholder/); + await expect(choice).not.toHaveText('I am a placeholder'); + }); + } else { + test('displays the placeholder choice first', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const choice = suite.choices.first(); + await expect(choice).toHaveClass(/choices__placeholder/); + await expect(choice).toHaveText('I am a placeholder'); + }); + } + }); + }); + }); + + describe('remote data', () => { + const testId = 'remote-data'; + describe('when loading data', () => { + test('shows a loading message as a placeholder', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.expectHiddenDropdown(); + await expect(suite.dropdown).toBeDisabled(); + + const placeholder = suite.itemsWithPlaceholder.first(); + await expect(placeholder).toHaveClass(/choices__placeholder/); + await expect(placeholder).toHaveText('Loading...'); + }); + }); + + describe('when data has loaded', () => { + describe('opening the dropdown', () => { + test('displays the loaded data', async ({ page, bundle }) => { + const jsonLoad = page.waitForResponse('**/data.json'); + + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); + + await jsonLoad; + await suite.selectByClick(); const placeholder = suite.itemsWithPlaceholder.first(); await expect(placeholder).toHaveClass(/choices__placeholder/); - await expect(placeholder).toHaveText('Loading...'); - }); - }); - - describe('when data has loaded', () => { - describe('opening the dropdown', () => { - test('displays the loaded data', async ({ page, baseURL }) => { - const jsonLoad = page.waitForResponse('**/data.json'); - - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - - await jsonLoad; - await suite.selectByClick(); - - const placeholder = suite.itemsWithPlaceholder.first(); - await expect(placeholder).toHaveClass(/choices__placeholder/); - await expect(placeholder).toHaveText('I am a placeholder'); - await expect(suite.selectableChoices).toHaveCount(10); - }); + await expect(placeholder).toHaveText('I am a placeholder'); + await expect(suite.selectableChoices).toHaveCount(10); }); }); }); + }); - describe('scrolling dropdown', () => { - const testId = 'scrolling-dropdown'; - test('shows partial choices list', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); + describe('scrolling dropdown', () => { + const testId = 'scrolling-dropdown'; + test('shows partial choices list', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await expect(suite.selectableChoices).toHaveCount(15); + // @todo determine how to assert items + // await expect(suite.selectableChoices.filter({ has: page.locator(':scope:visible') })).toHaveCount(8); + }); + }); + + describe('choice groups', () => { + describe('just groups', () => { + const testId = 'groups'; + test('displays', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); await suite.startWithClick(); - await expect(suite.selectableChoices).toHaveCount(15); - // @todo determine how to assert items - // await expect(suite.selectableChoices.filter({ has: page.locator(':scope:visible') })).toHaveCount(8); + await expect(suite.dropdown.locator('.choices__group[data-group]')).toHaveCount(2); }); }); - describe('choice groups', () => { - describe('just groups', () => { - const testId = 'groups'; - test('displays', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - await expect(suite.dropdown.locator('.choices__group[data-group]')).toHaveCount(2); - }); - }); - - describe('groups and choices', () => { - const testId = 'mixed-groups'; - test('displays', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - await expect(suite.dropdown.locator('.choices__group[data-group]')).toHaveCount(1); - expect( - await suite.selectableChoices.filter({ hasNot: page.locator('[data-group-id]') }).count(), - ).toBeGreaterThan(0); - }); - }); - }); - - describe('parent/child', () => { - const testId = 'parent-child'; - describe('selecting "Parent choice 2"', () => { - test('enables/disables the child Choices instance', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - - const parent = suite.group.locator('.choices').nth(0); - const child = suite.group.locator('.choices').nth(1); - await expect(parent).toBeEnabled(); - await expect(child).toBeDisabled(); - - await parent.click(); - await expect(suite.dropdown.first()).toBeVisible(); - const parentChoices = parent.locator('.choices__item:not(.choices__placeholder)'); - - await parentChoices.filter({ hasText: 'Parent choice 2' }).click(); - - await expect(child).toBeEnabled(); - - await parent.click(); - await expect(suite.dropdown.first()).toBeVisible(); - const choice = parentChoices.filter({ hasText: 'Parent choice 3' }); - await choice.click(); - - await expect(child).toBeDisabled(); - }); - }); - }); - - describe('custom properties via config', () => { - const testId = 'custom-properties'; - describe('on input', () => { - [ - { - country: 'Germany', - city: 'Berlin', - }, - { - country: 'United Kingdom', - city: 'London', - }, - { - country: 'Portugal', - city: 'Lisbon', - }, - ].forEach(({ country, city }) => { - test(`filters choices - ${country} = ${city}`, async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.typeText(country); - await suite.expectVisibleDropdown(); - - const choice = suite.selectableChoices.first(); - await expect(choice).toHaveText(city); - }); - }); - }); - }); - - describe('custom properties via html', () => { - const testId = 'custom-properties-html'; - describe('on input', () => { - [ - { - searchText: 'fantastic', - label: 'Label Three', - }, - { - searchText: 'foo', - label: 'Label Four', - }, - ].forEach(({ searchText, label }) => { - test(`filters choices - ${searchText} = ${label}`, async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.typeText(searchText); - await suite.expectVisibleDropdown(); - - const choice = suite.selectableChoices.first(); - await expect(choice).toHaveText(label); - }); - }); - }); - }); - - describe('non-string values', () => { - const testId = 'non-string-values'; - test('displays expected amount of choices in dropdown', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); + describe('groups and choices', () => { + const testId = 'mixed-groups'; + test('displays', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); await suite.startWithClick(); - await expect(suite.selectableChoices).toHaveCount(4); + await expect(suite.dropdown.locator('.choices__group[data-group]')).toHaveCount(1); + expect( + await suite.selectableChoices.filter({ hasNot: page.locator('[data-group-id]') }).count(), + ).toBeGreaterThan(0); }); + }); + }); - test('allows selecting choices from dropdown', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); + describe('parent/child', () => { + const testId = 'parent-child'; + describe('selecting "Parent choice 2"', () => { + test('enables/disables the child Choices instance', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.start(); - const choice = suite.selectableChoices.first(); - const choiceText = await choice.textContent(); - expect(choiceText).toBeTruthy(); + const parent = suite.group.locator('.choices').nth(0); + const child = suite.group.locator('.choices').nth(1); + await expect(parent).toBeEnabled(); + await expect(child).toBeDisabled(); + + await parent.click(); + await expect(suite.dropdown.first()).toBeVisible(); + const parentChoices = parent.locator('.choices__item:not(.choices__placeholder)'); + + await parentChoices.filter({ hasText: 'Parent choice 2' }).click(); + + await expect(child).toBeEnabled(); + + await parent.click(); + await expect(suite.dropdown.first()).toBeVisible(); + const choice = parentChoices.filter({ hasText: 'Parent choice 3' }); await choice.click(); - await expect(suite.items.first()).toHaveText(choiceText as string); + await expect(child).toBeDisabled(); }); }); + }); - describe('within form', () => { - const testId = 'within-form'; - describe('selecting choice', () => { - describe('on enter key', () => { - test('does not submit form', async ({ page, baseURL }) => { - let submit = false; - await page.route(page.url(), (route) => { - submit = true; + describe('custom properties via config', () => { + const testId = 'custom-properties'; + describe('on input', () => { + [ + { + country: 'Germany', + city: 'Berlin', + }, + { + country: 'United Kingdom', + city: 'London', + }, + { + country: 'Portugal', + city: 'Lisbon', + }, + ].forEach(({ country, city }) => { + test(`filters choices - ${country} = ${city}`, async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(country); + await suite.expectVisibleDropdown(); - return route.abort(); - }); + const choice = suite.selectableChoices.first(); + await expect(choice).toHaveText(city); + }); + }); + }); + }); - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.expectVisibleDropdown(); + describe('custom properties via html', () => { + const testId = 'custom-properties-html'; + describe('on input', () => { + [ + { + searchText: 'fantastic', + label: 'Label Three', + }, + { + searchText: 'foo', + label: 'Label Four', + }, + ].forEach(({ searchText, label }) => { + test(`filters choices - ${searchText} = ${label}`, async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(searchText); + await suite.expectVisibleDropdown(); - await suite.enterKey(); - await suite.expectHiddenDropdown(); + const choice = suite.selectableChoices.first(); + await expect(choice).toHaveText(label); + }); + }); + }); + }); - await suite.selectByClick(); - await suite.expectVisibleDropdown(); + describe('non-string values', () => { + const testId = 'non-string-values'; + test('displays expected amount of choices in dropdown', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); - const choice = suite.choices.first(); - const text = await choice.innerText(); - await expect(choice).toBeEnabled(); - await choice.click(); - await suite.advanceClock(); + await expect(suite.selectableChoices).toHaveCount(4); + }); - await suite.expectedItemCount(1); - await suite.expectedValue(text); - expect(submit).toEqual(false); - await suite.expectHiddenDropdown(); + test('allows selecting choices from dropdown', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + const choice = suite.selectableChoices.first(); + const choiceText = await choice.textContent(); + expect(choiceText).toBeTruthy(); + await choice.click(); + + await expect(suite.items.first()).toHaveText(choiceText as string); + }); + }); + + describe('within form', () => { + const testId = 'within-form'; + describe('selecting choice', () => { + describe('on enter key', () => { + test('does not submit form', async ({ page, bundle }) => { + let submit = false; + await page.route(page.url(), (route) => { + submit = true; + + return route.abort(); }); + + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.expectVisibleDropdown(); + + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await suite.selectByClick(); + await suite.expectVisibleDropdown(); + + const choice = suite.choices.first(); + const text = await choice.innerText(); + await expect(choice).toBeEnabled(); + await choice.click(); + await suite.advanceClock(); + + await suite.expectedItemCount(1); + await suite.expectedValue(text); + expect(submit).toEqual(false); + await suite.expectHiddenDropdown(); }); }); }); + }); - describe('dynamically setting choice by value', () => { - const dynamicallySelectedChoiceValue = 'Choice 2'; - const testId = 'set-choice-by-value'; - test('selects choice', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); + describe('dynamically setting choice by value', () => { + const dynamicallySelectedChoiceValue = 'Choice 2'; + const testId = 'set-choice-by-value'; + test('selects choice', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); - await expect(suite.items).toHaveText(dynamicallySelectedChoiceValue); - }); - - test('does not remove choice from dropdown list', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - await expect(suite.choices.filter({ hasText: dynamicallySelectedChoiceValue })).toHaveCount(1); - }); - - test('updates the value of the original input', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - - await suite.expectedValue(dynamicallySelectedChoiceValue); - }); + await expect(suite.items).toHaveText(dynamicallySelectedChoiceValue); }); - describe('searching by label only', () => { - const testId = 'search-by-label'; - test('gets zero results when searching by value', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.typeText('item2'); + test('does not remove choice from dropdown list', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); - await suite.expectVisibleDropdown('No results found'); - }); + await expect(suite.choices.filter({ hasText: dynamicallySelectedChoiceValue })).toHaveCount(1); + }); - test('gets a result when searching by label', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); + test('updates the value of the original input', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + + await suite.expectedValue(dynamicallySelectedChoiceValue); + }); + }); + + describe('searching by label only', () => { + const testId = 'search-by-label'; + test('gets zero results when searching by value', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText('item2'); + + await suite.expectVisibleDropdown('No results found'); + }); + + test('gets a result when searching by label', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText('label1'); + await suite.expectVisibleDropdown(); + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await expect(suite.items.filter({ hasText: 'label1' })).not.toHaveCount(0); + }); + }); + + describe('html allowed', () => { + const textInput = 'testing'; + const htmlInput = `${textInput}`; + describe('set to undefined', () => { + const testId = 'allowhtml-undefined'; + test('does not show html', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); await suite.startWithClick(); - await suite.typeText('label1'); + await suite.typeText(htmlInput); await suite.expectVisibleDropdown(); await suite.enterKey(); await suite.expectHiddenDropdown(); - await expect(suite.items.filter({ hasText: 'label1' })).not.toHaveCount(0); + await expect(suite.items.first()).toHaveText(htmlInput); }); }); - describe('html allowed', () => { - const textInput = 'testing'; - const htmlInput = `${textInput}`; - describe('set to undefined', () => { - const testId = 'allowhtml-undefined'; - test('does not show html', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.typeText(htmlInput); - await suite.expectVisibleDropdown(); - await suite.enterKey(); - await suite.expectHiddenDropdown(); - - await expect(suite.items.first()).toHaveText(htmlInput); - }); - }); - - describe('set to true', () => { - const testId = 'allowhtml-true'; - test('does not show html as text', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.typeText(htmlInput); - await suite.expectVisibleDropdown(); - await suite.enterKey(); - await suite.expectHiddenDropdown(); - - await expect(suite.items.first()).toHaveText(textInput); - }); - }); - - describe('set to true - except user input', () => { - const testId = 'allowhtml-true-userinput-false'; - test('does not show html as text', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.typeText(htmlInput); - await suite.expectVisibleDropdown(); - await suite.enterKey(); - await suite.expectHiddenDropdown(); - - await expect(suite.items.first()).toHaveText(htmlInput); - }); - }); - - describe('set to false', () => { - const testId = 'allowhtml-false'; - test('does not show html as text', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.startWithClick(); - await suite.typeText(htmlInput); - await suite.expectVisibleDropdown(); - await suite.enterKey(); - await suite.expectHiddenDropdown(); - - await expect(suite.items.first()).toHaveText(htmlInput); - }); - }); - }); - - describe('re-initialising a choices instance', () => { - const testId = 'new-destroy-init'; - const testvalue = 'Choice 2'; - test('preserves the choices & items lists', async ({ page, baseURL }) => { - let suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); + describe('set to true', () => { + const testId = 'allowhtml-true'; + test('does not show html as text', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); await suite.startWithClick(); - await suite.typeText(testvalue); + await suite.typeText(htmlInput); await suite.expectVisibleDropdown(); await suite.enterKey(); await suite.expectHiddenDropdown(); - await expect(suite.choices).toHaveCount(3); - - await suite.group.locator('.destroy').click({ force: true }); - - await expect(suite.group.locator('select > option')).toHaveCount(3); - - await suite.group.locator('.init').click({ force: true }); - - suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); - await expect(suite.choices).toHaveCount(3); - await suite.expectedValue(testvalue); + await expect(suite.items.first()).toHaveText(textInput); }); + }); - test('preserves the original select element', async ({ page, baseURL }) => { - const suite = new SelectTestSuit(page, baseURL, bundle, testUrl, testId); + describe('set to true - except user input', () => { + const testId = 'allowhtml-true-userinput-false'; + test('does not show html as text', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); await suite.startWithClick(); - await suite.typeText(testvalue); + await suite.typeText(htmlInput); await suite.expectVisibleDropdown(); await suite.enterKey(); await suite.expectHiddenDropdown(); - await expect(suite.choices).toHaveCount(3); - - await expect(suite.group.locator('select > option')).toHaveCount(3); - expect(await suite.getWrappedElement().inputValue()).toEqual(testvalue); + await expect(suite.items.first()).toHaveText(htmlInput); }); }); + + describe('set to false', () => { + const testId = 'allowhtml-false'; + test('does not show html as text', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(htmlInput); + await suite.expectVisibleDropdown(); + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await expect(suite.items.first()).toHaveText(htmlInput); + }); + }); + }); + + describe('re-initialising a choices instance', () => { + const testId = 'new-destroy-init'; + const testvalue = 'Choice 2'; + test('preserves the choices & items lists', async ({ page, bundle }) => { + let suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(testvalue); + await suite.expectVisibleDropdown(); + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await expect(suite.choices).toHaveCount(3); + + await suite.group.locator('.destroy').click({ force: true }); + + await expect(suite.group.locator('select > option')).toHaveCount(3); + + await suite.group.locator('.init').click({ force: true }); + + suite = new SelectTestSuit(page, bundle, testUrl, testId); + await expect(suite.choices).toHaveCount(3); + await suite.expectedValue(testvalue); + }); + + test('preserves the original select element', async ({ page, bundle }) => { + const suite = new SelectTestSuit(page, bundle, testUrl, testId); + await suite.startWithClick(); + await suite.typeText(testvalue); + await suite.expectVisibleDropdown(); + await suite.enterKey(); + await suite.expectHiddenDropdown(); + + await expect(suite.choices).toHaveCount(3); + + await expect(suite.group.locator('select > option')).toHaveCount(3); + expect(await suite.getWrappedElement().inputValue()).toEqual(testvalue); + }); }); }); }); diff --git a/test-e2e/tests/text.spec.ts b/test-e2e/tests/text.spec.ts index dabb0e39..8df596e4 100644 --- a/test-e2e/tests/text.spec.ts +++ b/test-e2e/tests/text.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +import { test, expect } from '../bundle-test'; import { TextTestSuit } from '../text-test-suit'; const { describe } = test; @@ -6,345 +6,343 @@ const { describe } = test; const testUrl = '/test/text/index.html'; const textInput = 'testing'; -TextTestSuit.testBundles().forEach(({ name, bundle }) => { - describe(`Choices - text element ${name}`, () => { - describe('scenarios', () => { - describe('basic', () => { - const testId = 'basic'; - describe('adding items', () => { - test('allows me to input items', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(textInput); - await suite.expectHiddenDropdown(); - - await suite.expectedValue(textInput); - }); - - describe('inputting data', () => { - test('shows a dropdown prompt', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - await suite.typeText(textInput); - - await suite.expectVisibleDropdown(`Press Enter to add "${textInput}"`); - }); - }); - }); - }); - - describe('editing items', () => { - const testId = 'edit-items'; - describe('on back space', () => { - test('allows me to change my entry', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(textInput); - await suite.backspaceKey(); - await suite.typeTextAndEnter('-edited'); - await suite.expectHiddenDropdown(); - - await expect(suite.itemList).toHaveText('-edited'); - }); - }); - - describe('on cmd+a', () => { - test('highlights all items', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(textInput); - await suite.ctrlA(); - await suite.expectHiddenDropdown(); - - expect(await suite.itemList.locator('.is-highlighted').count()).toEqual(1); - }); - - describe('on backspace', () => { - test('clears all inputted values', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(textInput); - await suite.ctrlA(); - await suite.backspaceKey(); - await suite.expectHiddenDropdown(); - - expect(await suite.itemList.locator('.is-highlighted').count()).toEqual(0); - }); - }); - }); - }); - - describe('remove button', () => { - const testId = 'remove-button'; - describe('on click', () => { - test('removes respective choice', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(textInput); - await suite.expectHiddenDropdown(); - - await suite.expectedItemCount(1); - - const button = suite.itemList.getByRole('button'); - await button.focus(); - await button.click(); - - await suite.expectedItemCount(0); - await suite.expectedValue(''); - }); - }); - }); - - describe('unique values only', () => { - const testId = 'unique-values'; - describe('unique values', () => { - test('only allows me to input unique values', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(textInput); - await suite.expectHiddenDropdown(); - - await suite.typeTextAndEnter(textInput); - - await suite.expectedItemCount(1); - await suite.expectVisibleDropdown(`Only unique values can be added`); - }); - }); - }); - - describe('html allowed', () => { - const htmlInput = `${textInput}`; - describe('set to undefined', () => { - const testId = 'allowhtml-undefined'; - test('does not show html', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(htmlInput); - await suite.expectHiddenDropdown(); - - await expect(suite.items.first()).toHaveText('Mason Rogers'); - await expect(suite.items.last()).toHaveText(htmlInput); - }); - }); - - describe('set to true', () => { - const testId = 'allowhtml-true'; - test('does not show html as text', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(htmlInput); - await suite.expectHiddenDropdown(); - - await expect(suite.items.first()).toHaveText('Mason Rogers'); - await expect(suite.items.last()).toHaveText(textInput); - }); - }); - - describe('set to true - except user input', () => { - const testId = 'allowhtml-true-userinput-false'; - test('does not show html as text', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(htmlInput); - await suite.expectHiddenDropdown(); - - await expect(suite.items.first()).toHaveText('Mason Rogers'); - await expect(suite.items.last()).toHaveText(htmlInput); - }); - }); - - describe('set to false', () => { - const testId = 'allowhtml-false'; - test('does not show html as text', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(htmlInput); - await suite.expectHiddenDropdown(); - - await expect(suite.items.first()).toHaveText('Mason Rogers'); - await expect(suite.items.last()).toHaveText(htmlInput); - }); - }); - }); - - describe('input limit', () => { - const testId = 'input-limit'; - const inputLimit = 5; - - test('does not let me input more than 5 choices', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - for (let index = 0; index < inputLimit; index++) { - await suite.typeTextAndEnter(textInput); - await suite.expectHiddenDropdown(); - } - await suite.typeText(textInput); - - expect(await suite.items.count()).toEqual(inputLimit); - await suite.expectVisibleDropdown(`Only ${inputLimit} values can be added`); - }); - }); - - describe('add item filter', () => { - const testId = 'add-item-filter'; - describe('inputting a value that satisfies the filter', () => { - const input = 'joe@bloggs.com'; - - test('allows me to add choice', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(input); - await suite.expectHiddenDropdown(); - - await expect(suite.itemList).toHaveText(input); - }); - }); - - describe('inputting a value that does not satisfy the regex', () => { - test('displays dropdown prompt', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(`this is not an email address`); - - await suite.expectVisibleDropdown(`Only values matching specific conditions can be added`); - }); - }); - }); - - describe('adding items disabled', () => { - const testId = 'adding-items-disabled'; - test('does not allow me to input data', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - await suite.expectHiddenDropdown(); - - await expect(suite.input).toBeDisabled(); - }); - }); - - describe('disabled via fieldset', () => { - const testId = 'disabled-via-fieldset'; - test('does not allow me to input data', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - await suite.expectHiddenDropdown(); - - await expect(suite.input).toBeDisabled(); - }); - }); - - describe('disabled via attribute', () => { - const testId = 'disabled-via-attr'; - test('does not allow me to input data', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - await suite.expectHiddenDropdown(); - - await expect(suite.input).toBeDisabled(); - }); - }); - - describe('prepend/append', () => { - const testId = 'prepend-append'; - test('prepends and appends value to inputted value', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); +describe(`Choices - text element`, () => { + describe('scenarios', () => { + describe('basic', () => { + const testId = 'basic'; + describe('adding items', () => { + test('allows me to input items', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); await suite.start(textInput); await suite.expectHiddenDropdown(); - const item = suite.items.first(); - await expect(item).toHaveText(textInput); - await expect(item).toHaveAttribute('data-value', `before-${textInput}-after`); - }); - }); - - describe('pre-populated choices', () => { - const testId = 'prepopulated'; - test('pre-populates choices', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - await suite.expectHiddenDropdown(); - - expect(await suite.items.count()).toEqual(2); - await expect(suite.items.first()).toHaveText('Josh Johnson'); - await expect(suite.items.last()).toHaveText('Joe Bloggs'); - }); - }); - - describe('placeholder', () => { - const testId = 'placeholder'; - describe('when no value has been inputted', () => { - test('displays a placeholder', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - await suite.expectHiddenDropdown(); - - await expect(suite.input).toHaveAttribute('placeholder', 'I am a placeholder'); - }); - }); - }); - - describe('within form', () => { - const testId = 'within-form'; - describe('inputting item', () => { - describe('on enter key', () => { - test('does not submit form', async ({ page, baseURL }) => { - let submit = false; - await page.route(page.url(), (route) => { - submit = true; - - return route.abort(); - }); - - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(textInput); - await suite.expectHiddenDropdown(); - - await suite.expectedValue(textInput); - expect(submit).toEqual(false); - }); - }); - }); - }); - - describe('shadow-dom - basic', () => { - const testId = 'shadow-dom'; - describe('adding items', () => { - test('allows me to input items', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(textInput); - await suite.expectHiddenDropdown(); - - await suite.expectedValue(textInput); - }); - - describe('inputting data', () => { - test('shows a dropdown prompt', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(); - await suite.typeText(textInput); - - await suite.expectVisibleDropdown(`Press Enter to add "${textInput}"`); - }); - }); - }); - }); - - describe('re-initialising a choices instance', () => { - const testId = 'new-destroy-init'; - test('preserves the choices & items lists', async ({ page, baseURL }) => { - let suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await suite.start(textInput); - await suite.expectHiddenDropdown(); - - await expect(suite.items).toHaveCount(1); - - await suite.group.locator('.destroy').click({ force: true }); - - await suite.group.locator('.init').click({ force: true }); - - suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); - await expect(suite.items).toHaveCount(1); await suite.expectedValue(textInput); }); - test('preserves the original select element', async ({ page, baseURL }) => { - const suite = new TextTestSuit(page, baseURL, bundle, testUrl, testId); + describe('inputting data', () => { + test('shows a dropdown prompt', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.typeText(textInput); + + await suite.expectVisibleDropdown(`Press Enter to add "${textInput}"`); + }); + }); + }); + }); + + describe('editing items', () => { + const testId = 'edit-items'; + describe('on back space', () => { + test('allows me to change my entry', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(textInput); + await suite.backspaceKey(); + await suite.typeTextAndEnter('-edited'); + await suite.expectHiddenDropdown(); + + await expect(suite.itemList).toHaveText('-edited'); + }); + }); + + describe('on cmd+a', () => { + test('highlights all items', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(textInput); + await suite.ctrlA(); + await suite.expectHiddenDropdown(); + + expect(await suite.itemList.locator('.is-highlighted').count()).toEqual(1); + }); + + describe('on backspace', () => { + test('clears all inputted values', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(textInput); + await suite.ctrlA(); + await suite.backspaceKey(); + await suite.expectHiddenDropdown(); + + expect(await suite.itemList.locator('.is-highlighted').count()).toEqual(0); + }); + }); + }); + }); + + describe('remove button', () => { + const testId = 'remove-button'; + describe('on click', () => { + test('removes respective choice', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); await suite.start(textInput); await suite.expectHiddenDropdown(); - await expect(suite.items).toHaveCount(1); + await suite.expectedItemCount(1); - expect(await suite.getWrappedElement().inputValue()).toEqual(textInput); + const button = suite.itemList.getByRole('button'); + await button.focus(); + await button.click(); + + await suite.expectedItemCount(0); + await suite.expectedValue(''); }); }); }); + + describe('unique values only', () => { + const testId = 'unique-values'; + describe('unique values', () => { + test('only allows me to input unique values', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(textInput); + await suite.expectHiddenDropdown(); + + await suite.typeTextAndEnter(textInput); + + await suite.expectedItemCount(1); + await suite.expectVisibleDropdown(`Only unique values can be added`); + }); + }); + }); + + describe('html allowed', () => { + const htmlInput = `${textInput}`; + describe('set to undefined', () => { + const testId = 'allowhtml-undefined'; + test('does not show html', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(htmlInput); + await suite.expectHiddenDropdown(); + + await expect(suite.items.first()).toHaveText('Mason Rogers'); + await expect(suite.items.last()).toHaveText(htmlInput); + }); + }); + + describe('set to true', () => { + const testId = 'allowhtml-true'; + test('does not show html as text', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(htmlInput); + await suite.expectHiddenDropdown(); + + await expect(suite.items.first()).toHaveText('Mason Rogers'); + await expect(suite.items.last()).toHaveText(textInput); + }); + }); + + describe('set to true - except user input', () => { + const testId = 'allowhtml-true-userinput-false'; + test('does not show html as text', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(htmlInput); + await suite.expectHiddenDropdown(); + + await expect(suite.items.first()).toHaveText('Mason Rogers'); + await expect(suite.items.last()).toHaveText(htmlInput); + }); + }); + + describe('set to false', () => { + const testId = 'allowhtml-false'; + test('does not show html as text', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(htmlInput); + await suite.expectHiddenDropdown(); + + await expect(suite.items.first()).toHaveText('Mason Rogers'); + await expect(suite.items.last()).toHaveText(htmlInput); + }); + }); + }); + + describe('input limit', () => { + const testId = 'input-limit'; + const inputLimit = 5; + + test('does not let me input more than 5 choices', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(); + for (let index = 0; index < inputLimit; index++) { + await suite.typeTextAndEnter(textInput); + await suite.expectHiddenDropdown(); + } + await suite.typeText(textInput); + + expect(await suite.items.count()).toEqual(inputLimit); + await suite.expectVisibleDropdown(`Only ${inputLimit} values can be added`); + }); + }); + + describe('add item filter', () => { + const testId = 'add-item-filter'; + describe('inputting a value that satisfies the filter', () => { + const input = 'joe@bloggs.com'; + + test('allows me to add choice', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(input); + await suite.expectHiddenDropdown(); + + await expect(suite.itemList).toHaveText(input); + }); + }); + + describe('inputting a value that does not satisfy the regex', () => { + test('displays dropdown prompt', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(`this is not an email address`); + + await suite.expectVisibleDropdown(`Only values matching specific conditions can be added`); + }); + }); + }); + + describe('adding items disabled', () => { + const testId = 'adding-items-disabled'; + test('does not allow me to input data', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.expectHiddenDropdown(); + + await expect(suite.input).toBeDisabled(); + }); + }); + + describe('disabled via fieldset', () => { + const testId = 'disabled-via-fieldset'; + test('does not allow me to input data', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.expectHiddenDropdown(); + + await expect(suite.input).toBeDisabled(); + }); + }); + + describe('disabled via attribute', () => { + const testId = 'disabled-via-attr'; + test('does not allow me to input data', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.expectHiddenDropdown(); + + await expect(suite.input).toBeDisabled(); + }); + }); + + describe('prepend/append', () => { + const testId = 'prepend-append'; + test('prepends and appends value to inputted value', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(textInput); + await suite.expectHiddenDropdown(); + + const item = suite.items.first(); + await expect(item).toHaveText(textInput); + await expect(item).toHaveAttribute('data-value', `before-${textInput}-after`); + }); + }); + + describe('pre-populated choices', () => { + const testId = 'prepopulated'; + test('pre-populates choices', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.expectHiddenDropdown(); + + expect(await suite.items.count()).toEqual(2); + await expect(suite.items.first()).toHaveText('Josh Johnson'); + await expect(suite.items.last()).toHaveText('Joe Bloggs'); + }); + }); + + describe('placeholder', () => { + const testId = 'placeholder'; + describe('when no value has been inputted', () => { + test('displays a placeholder', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.expectHiddenDropdown(); + + await expect(suite.input).toHaveAttribute('placeholder', 'I am a placeholder'); + }); + }); + }); + + describe('within form', () => { + const testId = 'within-form'; + describe('inputting item', () => { + describe('on enter key', () => { + test('does not submit form', async ({ page, bundle }) => { + let submit = false; + await page.route(page.url(), (route) => { + submit = true; + + return route.abort(); + }); + + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(textInput); + await suite.expectHiddenDropdown(); + + await suite.expectedValue(textInput); + expect(submit).toEqual(false); + }); + }); + }); + }); + + describe('shadow-dom - basic', () => { + const testId = 'shadow-dom'; + describe('adding items', () => { + test('allows me to input items', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(textInput); + await suite.expectHiddenDropdown(); + + await suite.expectedValue(textInput); + }); + + describe('inputting data', () => { + test('shows a dropdown prompt', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(); + await suite.typeText(textInput); + + await suite.expectVisibleDropdown(`Press Enter to add "${textInput}"`); + }); + }); + }); + }); + + describe('re-initialising a choices instance', () => { + const testId = 'new-destroy-init'; + test('preserves the choices & items lists', async ({ page, bundle }) => { + let suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(textInput); + await suite.expectHiddenDropdown(); + + await expect(suite.items).toHaveCount(1); + + await suite.group.locator('.destroy').click({ force: true }); + + await suite.group.locator('.init').click({ force: true }); + + suite = new TextTestSuit(page, bundle, testUrl, testId); + await expect(suite.items).toHaveCount(1); + await suite.expectedValue(textInput); + }); + + test('preserves the original select element', async ({ page, bundle }) => { + const suite = new TextTestSuit(page, bundle, testUrl, testId); + await suite.start(textInput); + await suite.expectHiddenDropdown(); + + await expect(suite.items).toHaveCount(1); + + expect(await suite.getWrappedElement().inputValue()).toEqual(textInput); + }); + }); }); }); diff --git a/test-e2e/text-test-suit.ts b/test-e2e/text-test-suit.ts index 2c1529c8..112f636c 100644 --- a/test-e2e/text-test-suit.ts +++ b/test-e2e/text-test-suit.ts @@ -4,8 +4,8 @@ import { TestSuit } from './test-suit'; export class TextTestSuit extends TestSuit { readonly wrappedInput: Locator; - constructor(page: Page, baseURL: string | undefined, choicesBundle: string, url: string, testId: string) { - super(page, baseURL, choicesBundle, url, testId); + constructor(page: Page, choicesBundle: string | undefined, url: string, testId: string) { + super(page, choicesBundle, url, testId); this.wrappedInput = this.group.locator('input[hidden]'); }