diff --git a/packages/toolpad-components/src/Select.tsx b/packages/toolpad-components/src/Select.tsx index cc8d407566c..748f1daee7a 100644 --- a/packages/toolpad-components/src/Select.tsx +++ b/packages/toolpad-components/src/Select.tsx @@ -22,6 +22,8 @@ function Select({ options, value, onChange, defaultValue, fullWidth, sx, ...rest [onChange], ); + const id = React.useId(); + return ( - {options.map((option) => { - const parsedOption: SelectOption = typeof option === 'string' ? { value: option } : option; + {options.map((option, i) => { + const parsedOption: SelectOption = + option && typeof option === 'object' ? option : { value: String(option) }; return ( - - {parsedOption.label ?? parsedOption.value} + + {String(parsedOption.label ?? parsedOption.value)} ); })} diff --git a/test/integration/components/componentsDom.json b/test/integration/components/componentsDom.json index 0fd4e346ea3..b2378f080dc 100644 --- a/test/integration/components/componentsDom.json +++ b/test/integration/components/componentsDom.json @@ -22,6 +22,22 @@ "parentProp": "children", "parentIndex": "a0" }, + "1m13ljr": { + "id": "1m13ljr", + "name": "codeComponent_eb03t9a", + "type": "element", + "props": {}, + "layout": {}, + "parentId": "rx23l68", + "attributes": { + "component": { + "type": "const", + "value": "codeComponent.eb03t9a" + } + }, + "parentProp": "children", + "parentIndex": "a0" + }, "2m53pfl": { "id": "2m53pfl", "name": "pageRow5", @@ -173,7 +189,7 @@ "attributes": { "component": { "type": "const", - "value": "Typography" + "value": "Text" } }, "parentProp": "children", @@ -240,6 +256,22 @@ "parentProp": "children", "parentIndex": "a0" }, + "rx23l68": { + "id": "rx23l68", + "name": "pageRow", + "type": "element", + "props": {}, + "layout": {}, + "parentId": "f703ps3", + "attributes": { + "component": { + "type": "const", + "value": "PageRow" + } + }, + "parentProp": "children", + "parentIndex": "a1V" + }, "vkowdut": { "id": "vkowdut", "name": "Application", @@ -281,7 +313,21 @@ "parentProp": "children", "parentIndex": "a0" }, - "rx23l68": { + "ly03uc8": { + "name": "select", + "attributes": { + "title": { + "type": "const", + "value": "select" + } + }, + "id": "ly03uc8", + "type": "page", + "parentId": "vkowdut", + "parentProp": "pages", + "parentIndex": "a2" + }, + "vy33um7": { "name": "pageRow", "props": {}, "attributes": { @@ -291,28 +337,41 @@ } }, "layout": {}, - "id": "rx23l68", + "id": "vy33um7", "type": "element", - "parentId": "f703ps3", + "parentId": "ly03uc8", "parentProp": "children", - "parentIndex": "a1V" + "parentIndex": "a0" }, - "1m13ljr": { - "name": "codeComponent_eb03t9a", - "props": {}, + "q813u5k": { + "name": "selectOptions", + "props": { + "options": { + "type": "jsExpression", + "value": "[\"one\", 2, { value: 3, label: \"three\" }, { value: 4 }, { noValue: \"foo\" }]\n" + }, + "label": { + "type": "const", + "value": "select with options" + }, + "fullWidth": { + "type": "const", + "value": true + } + }, "attributes": { "component": { "type": "const", - "value": "codeComponent.eb03t9a" + "value": "Select" } }, "layout": {}, - "id": "1m13ljr", + "id": "q813u5k", "type": "element", - "parentId": "rx23l68", + "parentId": "vy33um7", "parentProp": "children", "parentIndex": "a0" } }, - "version": 2 + "version": 5 } diff --git a/test/integration/components/index.spec.ts b/test/integration/components/index.spec.ts index 8effcf1228a..bdb75e47437 100644 --- a/test/integration/components/index.spec.ts +++ b/test/integration/components/index.spec.ts @@ -1,21 +1,35 @@ import * as path from 'path'; import { ToolpadEditor } from '../../models/ToolpadEditor'; import { ToolpadRuntime } from '../../models/ToolpadRuntime'; -import { FrameLocator, Page, test } from '../../playwright/test'; +import { FrameLocator, Page, test, expect } from '../../playwright/test'; +import clickCenter from '../../utils/clickCenter'; import { readJsonFile } from '../../utils/fs'; import generateId from '../../utils/generateId'; -async function waitForComponents(page: Page | FrameLocator) { - await page.locator('text="foo button"').waitFor({ state: 'visible' }); - await page.locator('img[alt="foo image"]').waitFor({ state: 'attached' }); - await page.locator('text="foo datagrid column"').waitFor({ state: 'visible' }); - await page.locator('text="custom component 1"').waitFor({ state: 'visible' }); - await page.locator('label:has-text("foo textfield")').waitFor({ state: 'visible' }); - await page.locator('text="foo typography"').waitFor({ state: 'visible' }); - await page.locator('label:has-text("foo select")').waitFor({ state: 'visible' }); +async function waitForComponents(page: Page, frame: Page | FrameLocator = page) { + const button = frame.locator('text="foo button"'); + await button.waitFor({ state: 'visible' }); + + const image = frame.locator('img[alt="foo image"]'); + await image.waitFor({ state: 'attached' }); + + const datagrid = frame.locator('text="foo datagrid column"'); + await datagrid.waitFor({ state: 'visible' }); + + const customComponent = frame.locator('text="custom component 1"'); + await customComponent.waitFor({ state: 'visible' }); + + const textField = frame.locator('label:has-text("foo textfield")'); + await textField.waitFor({ state: 'visible' }); + + const text = frame.locator('text="foo typography"'); + await text.waitFor({ state: 'visible' }); + + const select = frame.locator('label:has-text("foo select")'); + await select.waitFor({ state: 'visible' }); } -test('components', async ({ page, browserName, api }) => { +test('rendering components in the app runtime', async ({ page, api }) => { const dom = await readJsonFile(path.resolve(__dirname, './componentsDom.json')); const app = await api.mutation.createApp(`App ${generateId()}`, { @@ -26,9 +40,37 @@ test('components', async ({ page, browserName, api }) => { await runtimeModel.gotoPage(app.id, 'components'); await waitForComponents(page); +}); + +test('rendering components in the app editor', async ({ page, browserName, api }) => { + const dom = await readJsonFile(path.resolve(__dirname, './componentsDom.json')); + + const app = await api.mutation.createApp(`App ${generateId()}`, { + from: { kind: 'dom', dom }, + }); const editorModel = new ToolpadEditor(page, browserName); editorModel.goto(app.id); - await waitForComponents(editorModel.appCanvas); + await waitForComponents(page, editorModel.appCanvas, true); +}); + +test('select component behavior', async ({ page, api }) => { + const dom = await readJsonFile(path.resolve(__dirname, './componentsDom.json')); + + const app = await api.mutation.createApp(`App ${generateId()}`, { + from: { kind: 'dom', dom }, + }); + + const runtimeModel = new ToolpadRuntime(page); + await runtimeModel.gotoPage(app.id, 'select'); + + const optionsSelect = page.getByRole('button', { name: /select with options/ }); + await optionsSelect.scrollIntoViewIfNeeded(); + await clickCenter(page, optionsSelect); + await expect(page.getByRole('option', { name: 'one' })).toBeVisible(); + await expect(page.getByRole('option', { name: '2' })).toBeVisible(); + await expect(page.getByRole('option', { name: 'three' })).toBeVisible(); + await expect(page.getByRole('option', { name: '4' })).toBeVisible(); + await expect(page.getByRole('option', { name: 'undefined' })).toBeVisible(); });