From 4392053b3eda56ecdc2b9d5b9174fa949c838f2a Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Fri, 3 Jan 2025 13:43:38 +0100 Subject: [PATCH] feat: autocomplete data fields in the editor (#4062) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dafydd Llŷr Pearson --- .../src/create-flow-with-geospatial.spec.ts | 4 + e2e/tests/ui-driven/src/create-flow.spec.ts | 12 +- .../ui-driven/src/helpers/addComponent.ts | 43 ++- .../ui-driven/src/helpers/userActions.ts | 4 +- .../@planx/components/AddressInput/Editor.tsx | 19 +- .../@planx/components/Calculate/Editor.tsx | 17 +- .../components/Checklist/Editor/Editor.tsx | 20 +- .../components/Checklist/Editor/Options.tsx | 17 +- .../Checklist/Editor/OptionsEditor.tsx | 2 + .../@planx/components/ContactInput/Editor.tsx | 17 +- .../@planx/components/DateInput/Editor.tsx | 15 +- .../@planx/components/FileUpload/Editor.tsx | 30 ++- .../components/FileUploadAndLabel/Editor.tsx | 29 +- .../src/@planx/components/List/Editor.tsx | 16 +- .../@planx/components/MapAndLabel/Editor.tsx | 16 +- .../@planx/components/NumberInput/Editor.tsx | 16 +- .../src/@planx/components/Page/Editor.tsx | 16 +- .../src/@planx/components/Question/Editor.tsx | 28 +- .../components/Question/OptionsEditor.tsx | 2 + .../src/@planx/components/SetValue/Editor.tsx | 16 +- .../@planx/components/TextInput/Editor.tsx | 17 +- .../components/shared/BaseOptionsEditor.tsx | 31 ++- .../shared/DataFieldAutocomplete.tsx | 101 +++++++ .../src/@planx/components/shared/utils.ts | 19 ++ .../lib/__tests__/getFlowSchema.test.ts | 255 ++++++++++++++++++ .../src/pages/FlowEditor/lib/store/editor.ts | 38 ++- .../src/ui/shared/AutocompleteInput.tsx | 122 +++++++++ 27 files changed, 733 insertions(+), 189 deletions(-) create mode 100644 editor.planx.uk/src/@planx/components/shared/DataFieldAutocomplete.tsx create mode 100644 editor.planx.uk/src/pages/FlowEditor/lib/__tests__/getFlowSchema.test.ts create mode 100644 editor.planx.uk/src/ui/shared/AutocompleteInput.tsx diff --git a/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts b/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts index 16b46b755e..098c914a85 100644 --- a/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts @@ -62,6 +62,8 @@ test.describe("Flow creation, publish and preview", () => { }); test("Create a flow", async ({ browser }) => { + test.setTimeout(60_000); + const page = await getTeamPage({ browser, userId: context.user!.id!, @@ -107,6 +109,8 @@ test.describe("Flow creation, publish and preview", () => { test("Publish and preview flow with geospatial components", async ({ browser, }) => { + test.setTimeout(60_000); + const page = await createAuthenticatedSession({ browser, userId: context.user!.id!, diff --git a/e2e/tests/ui-driven/src/create-flow.spec.ts b/e2e/tests/ui-driven/src/create-flow.spec.ts index 921e2a54a9..ffb4419353 100644 --- a/e2e/tests/ui-driven/src/create-flow.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow.spec.ts @@ -58,6 +58,8 @@ test.describe("Flow creation, publish and preview", () => { }); test("Create a flow", async ({ browser }) => { + test.setTimeout(60_000); + const page = await getTeamPage({ browser, userId: context.user!.id!, @@ -231,7 +233,7 @@ test.describe("Flow creation, publish and preview", () => { await publishService(page); }); - test("Can preview a published flow", async ({ + test("Can preview a published flow with an external portal", async ({ browser, }: { browser: Browser; @@ -241,12 +243,17 @@ test.describe("Flow creation, publish and preview", () => { userId: context.user!.id!, }); - await page.goto(`/${context.team.slug}/${serviceProps.slug}`); + await navigateToService(page, serviceProps.slug); await expect( page.getByRole("link", { name: "E2E/an-external-portal-service" }), ).toBeVisible(); + const previewLink = page.getByRole("link", { + name: "Open published service", + }); + await expect(previewLink).toBeVisible(); + await page.goto( `/${context.team.slug}/${serviceProps.slug}/published?analytics=false`, ); @@ -273,6 +280,7 @@ test.describe("Flow creation, publish and preview", () => { }); await clickContinue({ page }); + // The external portal question has been flattened into the overall flow data structure and can be successfully navigated through await answerQuestion({ page, title: externalPortalFlowData.title, diff --git a/e2e/tests/ui-driven/src/helpers/addComponent.ts b/e2e/tests/ui-driven/src/helpers/addComponent.ts index 7f9c6f10b3..b3140c7f92 100644 --- a/e2e/tests/ui-driven/src/helpers/addComponent.ts +++ b/e2e/tests/ui-driven/src/helpers/addComponent.ts @@ -55,11 +55,19 @@ const createBaseComponent = async ( break; case ComponentType.AddressInput: await page.getByPlaceholder("Title").fill(title || ""); - await page.getByPlaceholder("Data Field").fill(options?.[0] || ""); + await page.getByRole("combobox", { name: "Data field" }).click(); + await page + .getByRole("combobox", { name: "Data field" }) + .fill(options?.[0] || "proposal.address"); + await page.getByRole("combobox", { name: "Data field" }).press("Enter"); break; case ComponentType.ContactInput: await page.getByPlaceholder("Title").fill(title || ""); - await page.getByPlaceholder("Data Field").fill(options?.[0] || ""); + await page.getByRole("combobox", { name: "Data field" }).click(); + await page + .getByRole("combobox", { name: "Data field" }) + .fill(options?.[0] || "proposal.contact"); + await page.getByRole("combobox", { name: "Data field" }).press("Enter"); break; case ComponentType.TaskList: await page.getByPlaceholder("Main Title").fill(title || ""); @@ -112,15 +120,27 @@ const createBaseComponent = async ( } break; case ComponentType.FileUpload: - await page.getByPlaceholder("Data Field").fill(options?.[0] || ""); + await page.getByRole("combobox", { name: "Data field" }).click(); + await page + .getByRole("combobox", { name: "Data field" }) + .fill(options?.[0] || "otherDocument"); + await page.getByRole("combobox", { name: "Data field" }).press("Enter"); break; case ComponentType.FileUploadAndLabel: await page.getByPlaceholder("File type").fill(options?.[0] || ""); - await page.getByPlaceholder("Data Field").fill(options?.[1] || ""); + await page.getByRole("combobox", { name: "Data field" }).click(); + await page + .getByRole("combobox", { name: "Data field" }) + .fill(options?.[1] || "otherDocument"); + await page.getByRole("combobox", { name: "Data field" }).press("Enter"); break; case ComponentType.List: await page.getByPlaceholder("Title").fill(title || ""); - await page.getByPlaceholder("Data Field").fill(options?.[0] || ""); + await page.getByRole("combobox", { name: "Data field" }).click(); + await page + .getByRole("combobox", { name: "Data field" }) + .fill(options?.[0] || "proposal.list"); + await page.getByRole("combobox", { name: "Data field" }).press("Enter"); break; case ComponentType.Content: await page @@ -176,7 +196,9 @@ export const createQuestionWithDataFieldOptions = async ( await locatingNode.click(); await page.getByRole("dialog").waitFor(); await page.getByPlaceholder("Text").fill(questionText); - await page.getByPlaceholder("Data Field").fill(dataField); + await page.getByRole("combobox", { name: "Data field" }).click(); + await page.getByRole("combobox", { name: "Data field" }).fill(dataField); + await page.getByRole("combobox", { name: "Data field" }).press("Enter"); await createComponentOptionsWithDataValues(page, options); await page.locator('button[form="modal"][type="submit"]').click(); }; @@ -377,12 +399,13 @@ async function createComponentOptionsWithDataValues( page: Page, options: OptionWithDataValues[], ) { - let index = 0; for (const option of options) { await page.locator("button").filter({ hasText: "add new" }).click(); - await page.getByPlaceholder("Option").nth(index).fill(option.optionText); - await page.getByPlaceholder("Data Value").nth(index).fill(option.dataValue); - index++; + await page.getByPlaceholder("Option").last().fill(option.optionText); + await page.getByRole("combobox", { name: "Data field" }).last().click(); + await page + .getByRole("option", { name: option.dataValue, exact: true }) + .click(); } } diff --git a/e2e/tests/ui-driven/src/helpers/userActions.ts b/e2e/tests/ui-driven/src/helpers/userActions.ts index 6229183c02..f70a4d3bb3 100644 --- a/e2e/tests/ui-driven/src/helpers/userActions.ts +++ b/e2e/tests/ui-driven/src/helpers/userActions.ts @@ -328,14 +328,14 @@ export async function answerListInput( await page .getByRole("combobox", { name: "What best describes this unit?" }) .click(); - await page.getByRole("option", { name: unitType }).click(); + await page.getByRole("option", { name: unitType, exact: true }).click(); await page .getByRole("combobox", { name: "What best describes the tenure of this unit?", }) .click(); - await page.getByRole("option", { name: tenure }).click(); + await page.getByRole("option", { name: tenure, exact: true }).click(); await page .getByLabel("How many bedrooms does this unit have?") diff --git a/editor.planx.uk/src/@planx/components/AddressInput/Editor.tsx b/editor.planx.uk/src/@planx/components/AddressInput/Editor.tsx index 35a9a643a1..839f45a51c 100644 --- a/editor.planx.uk/src/@planx/components/AddressInput/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/AddressInput/Editor.tsx @@ -9,6 +9,7 @@ import RichTextInput from "ui/editor/RichTextInput/RichTextInput"; import Input from "ui/shared/Input/Input"; import InputRow from "ui/shared/InputRow"; +import { DataFieldAutocomplete } from "../shared/DataFieldAutocomplete"; import { ICONS } from "../shared/icons"; import { AddressInput, parseAddressInput } from "./model"; @@ -25,8 +26,9 @@ export default function AddressInputComponent(props: Props): FCReturn { }); } }, - validate: () => {}, + validate: () => { }, }); + return (