From 80b88d3360af485e158c52446470ce619fb01bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Wed, 11 Sep 2024 10:45:50 +0100 Subject: [PATCH] feat: Data value search (#3646) --- editor.planx.uk/src/hooks/useSearch.ts | 22 +- .../Sidebar/Search/NodeSearchResults.tsx | 46 +- .../SearchResultCard/DataDisplayMap.test.ts | 126 ++++ .../SearchResultCard/DataDisplayMap.tsx | 116 +++ .../index.tsx} | 47 +- .../Sidebar/Search/mocks/DataDisplayMap.ts | 679 ++++++++++++++++++ .../components/Sidebar/Search/mocks/simple.ts | 2 + 7 files changed, 962 insertions(+), 76 deletions(-) create mode 100644 editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard/DataDisplayMap.test.ts create mode 100644 editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard/DataDisplayMap.tsx rename editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/{SearchResultCard.tsx => SearchResultCard/index.tsx} (57%) create mode 100644 editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/mocks/DataDisplayMap.ts diff --git a/editor.planx.uk/src/hooks/useSearch.ts b/editor.planx.uk/src/hooks/useSearch.ts index 16dbe22f37..a186fe707f 100644 --- a/editor.planx.uk/src/hooks/useSearch.ts +++ b/editor.planx.uk/src/hooks/useSearch.ts @@ -9,7 +9,8 @@ interface UseSearchProps { export interface SearchResult { item: T; key: string; - matchIndices?: [number, number][]; + matchIndices: [number, number][]; + refIndex: number; } export type SearchResults = SearchResult[]; @@ -39,13 +40,18 @@ export const useSearch = ({ useEffect(() => { const fuseResults = fuse.search(pattern); setResults( - fuseResults.map((result) => ({ - item: result.item, - key: result.matches?.[0].key || "", - // We only display the first match - matchIndices: - (result.matches?.[0].indices as [number, number][]) || undefined, - })), + fuseResults.map((result) => { + // Required type narrowing for FuseResult + if (!result.matches) throw Error("Matches missing from FuseResults"); + + return { + item: result.item, + key: result.matches?.[0].key || "", + // We only display the first match + matchIndices: result.matches[0].indices as [number, number][], + refIndex: result.matches[0]?.refIndex || 0, + }; + }), ); }, [pattern, fuse]); diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/NodeSearchResults.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/NodeSearchResults.tsx index c8dfedceaf..7672884536 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/NodeSearchResults.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/NodeSearchResults.tsx @@ -2,7 +2,7 @@ import List from "@mui/material/List"; import ListItem from "@mui/material/ListItem"; import { styled } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; -import { ComponentType, IndexedNode } from "@opensystemslab/planx-core/types"; +import { IndexedNode } from "@opensystemslab/planx-core/types"; import type { SearchResults } from "hooks/useSearch"; import React from "react"; @@ -17,32 +17,20 @@ export const Root = styled(List)(({ theme }) => ({ export const NodeSearchResults: React.FC<{ results: SearchResults; -}> = ({ results }) => { - /** Temporary guard function to filter out component types not yet supported by SearchResultCard */ - const isSupportedNodeType = ( - result: SearchResults[number], - ): boolean => - ![ - ComponentType.FileUploadAndLabel, - ComponentType.Calculate, - ComponentType.List, - ].includes(result.item.type); +}> = ({ results }) => ( + <> + + {!results.length && "No matches found"} + {results.length === 1 && "1 result:"} + {results.length > 1 && `${results.length} results:`} + - return ( - <> - - {!results.length && "No matches found"} - {results.length === 1 && "1 result:"} - {results.length > 1 && `${results.length} results:`} - - - - {results.filter(isSupportedNodeType).map((result) => ( - - - - ))} - - - ); -}; + + {results.map((result) => ( + + + + ))} + + +); diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard/DataDisplayMap.test.ts b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard/DataDisplayMap.test.ts new file mode 100644 index 0000000000..d8e07894e3 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard/DataDisplayMap.test.ts @@ -0,0 +1,126 @@ +import { ComponentType } from "@opensystemslab/planx-core/types"; +import { useStore } from "pages/FlowEditor/lib/store"; + +import { + mockAnswerResult, + mockCalculateFormulaResult, + mockCalculateRootResult, + mockFileUploadAndLabelResult, + mockFlow, + mockListAnswerResult, + mockListDataResult, + mockListRootResult, + mockQuestionResult, +} from "../mocks/DataDisplayMap"; +import { getDisplayDetailsForResult } from "./DataDisplayMap"; + +type Output = ReturnType; + +// Setup flow so that it can be referenced by SearchResults (e.g. getting parent nodes) +beforeAll(() => useStore.setState({ flow: mockFlow })); + +describe("Question component", () => { + it("returns the expected display values", () => { + const output = getDisplayDetailsForResult(mockQuestionResult); + + expect(output).toStrictEqual({ + key: "Data", + iconKey: ComponentType.Question, + componentType: "Question", + title: "This is a question component", + headline: "colour", + }); + }); +}); + +describe("Answer component", () => { + it("returns the expected display values", () => { + const output = getDisplayDetailsForResult(mockAnswerResult); + + expect(output).toStrictEqual({ + key: "Option (data)", + iconKey: ComponentType.Question, + componentType: "Question", + title: "This is a question component", + headline: "red", + }); + }); +}); + +describe("List component", () => { + it("handles the root data value", () => { + const output = getDisplayDetailsForResult(mockListRootResult); + + expect(output).toStrictEqual({ + componentType: "List", + headline: "listRoot", + iconKey: ComponentType.List, + key: "Data", + title: "This is a list component", + }); + }); + + it("handles nested data variables", () => { + const output = getDisplayDetailsForResult(mockListDataResult); + + expect(output).toStrictEqual({ + componentType: "List", + headline: "tenure", + iconKey: ComponentType.List, + key: "Data", + title: "This is a list component", + }); + }); + + it("handles nested data variables in Answers", () => { + const output = getDisplayDetailsForResult(mockListAnswerResult); + + expect(output).toStrictEqual({ + componentType: "List", + headline: "selfCustomBuild", + iconKey: ComponentType.List, + key: "Option (data)", + title: "This is a list component", + }); + }); +}); + +describe("Calculate component", () => { + it("handles the output data variables", () => { + const output = getDisplayDetailsForResult(mockCalculateRootResult); + + expect(output).toStrictEqual({ + componentType: "Calculate", + headline: "calculateOutput", + iconKey: ComponentType.Calculate, + key: "Output (data)", + title: "This is a calculate component", + }); + }); + + it("handles the formula data variables", () => { + const output = getDisplayDetailsForResult(mockCalculateFormulaResult); + + expect(output).toStrictEqual({ + componentType: "Calculate", + headline: "formulaOne + formulaTwo", + iconKey: ComponentType.Calculate, + key: "Formula", + title: "This is a calculate component", + }); + }); +}); + +describe("FileUploadAndLabel component", () => { + it("handles the data variables nested in FileTypes", () => { + const output = getDisplayDetailsForResult(mockFileUploadAndLabelResult); + + expect(output).toStrictEqual({ + componentType: "File upload and label", + headline: "floorplan", + iconKey: ComponentType.FileUploadAndLabel, + key: "File type (data)", + title: "This is a FileUploadAndLabel component", + }); + }); +}); diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard/DataDisplayMap.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard/DataDisplayMap.tsx new file mode 100644 index 0000000000..ea0b2b4953 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard/DataDisplayMap.tsx @@ -0,0 +1,116 @@ +import { ComponentType, IndexedNode } from "@opensystemslab/planx-core/types"; +import { Calculate } from "@planx/components/Calculate/model"; +import { FileUploadAndLabel } from "@planx/components/FileUploadAndLabel/model"; +import { List } from "@planx/components/List/model"; +import { SearchResult } from "hooks/useSearch"; +import { capitalize, get } from "lodash"; +import { SLUGS } from "pages/FlowEditor/data/types"; +import { useStore } from "pages/FlowEditor/lib/store"; + +interface DataDisplayValues { + displayKey: string; + getIconKey: (result: SearchResult) => ComponentType; + getTitle: (result: SearchResult) => string; + getHeadline: (result: SearchResult) => string; + getComponentType: (result: SearchResult) => string; +} + +/** + * Map of data keys to their associated display values + * Uses Partial as not all values are unique, we later apply defaults + */ +type DataKeyMap = Record>; + +/** + * Map of ComponentTypes to their associated data keys + */ +type ComponentMap = Record; + +/** + * Map of ComponentTypes which need specific overrides in order to display their data values + */ +const DISPLAY_DATA: Partial = { + // Answers are mapped to their parent questions + [ComponentType.Answer]: { + default: { + getIconKey: () => ComponentType.Question, + displayKey: "Option (data)", + getTitle: ({ item }) => { + const parentNode = useStore.getState().flow[item.parentId]; + return parentNode.data.text; + }, + getHeadline: ({ item, key }) => get(item, key)?.toString(), + }, + }, + // FileUploadAndLabel has data values nested in FileTypes + [ComponentType.FileUploadAndLabel]: { + default: { + displayKey: "File type (data)", + getHeadline: ({ item, refIndex }) => + (item["data"] as unknown as FileUploadAndLabel)["fileTypes"][refIndex][ + "fn" + ], + }, + }, + // Calculate contains both input and output data values + [ComponentType.Calculate]: { + formula: { + displayKey: "Formula", + getHeadline: ({ item }) => (item.data as unknown as Calculate).formula, + }, + "data.output": { + displayKey: "Output (data)", + getHeadline: ({ item }) => (item.data as unknown as Calculate).output, + }, + }, + // List contains data variables nested within its schema + [ComponentType.List]: { + "data.schema.fields.data.fn": { + getHeadline: ({ item, refIndex }) => + (item.data as unknown as List).schema.fields[refIndex].data.fn, + }, + "data.schema.fields.data.options.data.val": { + displayKey: "Option (data)", + getHeadline: ({ item, refIndex }) => { + // Fuse.js flattens deeply nested arrays when using refIndex + const options = (item.data as unknown as List).schema.fields + .filter((field) => field.type === "question") + .flatMap((field) => field.data.options); + return options[refIndex].data.val || ""; + }, + }, + }, +}; + +/** + * Default values for all ComponentTypes not listed in DISPLAY_DATA + */ +const DEFAULT_DISPLAY_DATA: DataDisplayValues = { + displayKey: "Data", + getIconKey: ({ item }) => item.type, + getTitle: ({ item }) => + (item.data?.title as string) || (item.data?.text as string) || "", + getHeadline: ({ item, key }) => get(item, key)?.toString() || "", + getComponentType: ({ item }) => + capitalize(SLUGS[item.type].replaceAll("-", " ")), +}; + +export const getDisplayDetailsForResult = ( + result: SearchResult, +) => { + const componentMap = DISPLAY_DATA[result.item.type]; + const keyMap = componentMap?.[result.key] || componentMap?.default || {}; + + const data: DataDisplayValues = { + ...DEFAULT_DISPLAY_DATA, + ...keyMap, + }; + + return { + iconKey: data.getIconKey(result), + componentType: data.getComponentType(result), + title: data.getTitle(result), + key: data.displayKey, + headline: data.getHeadline(result), + }; +}; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard/index.tsx similarity index 57% rename from editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard.tsx rename to editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard/index.tsx index 30f5621d2a..8478f41819 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/SearchResultCard/index.tsx @@ -2,16 +2,14 @@ import Box from "@mui/material/Box"; import ListItemButton from "@mui/material/ListItemButton"; import { styled } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; -import { ComponentType, IndexedNode } from "@opensystemslab/planx-core/types"; +import { IndexedNode } from "@opensystemslab/planx-core/types"; import { ICONS } from "@planx/components/ui"; import type { SearchResult } from "hooks/useSearch"; -import { capitalize, get } from "lodash"; -import { SLUGS } from "pages/FlowEditor/data/types"; -import { useStore } from "pages/FlowEditor/lib/store"; import React from "react"; import { FONT_WEIGHT_SEMI_BOLD } from "theme"; -import { Headline } from "./Headline"; +import { Headline } from "../Headline"; +import { getDisplayDetailsForResult } from "./DataDisplayMap"; export const Root = styled(ListItemButton)(({ theme }) => ({ padding: theme.spacing(1), @@ -22,42 +20,13 @@ export const Root = styled(ListItemButton)(({ theme }) => ({ export const SearchResultCard: React.FC<{ result: SearchResult; }> = ({ result }) => { - const getDisplayDetailsForResult = ({ - item, - key, - }: SearchResult) => { - const componentType = capitalize( - SLUGS[result.item.type].replaceAll("-", " "), - ); - let title = (item.data?.title as string) || (item.data?.text as string); - let Icon = ICONS[item.type]; // TODO: Generate display key from key - - let displayKey = "Data"; - const headline = get(item, key).toString() || ""; - - // For Answer nodes, update display values to match the parent question - if (item.type === ComponentType.Answer) { - const parentNode = useStore.getState().flow[item.parentId]; - Icon = ICONS[ComponentType.Question]; - title = parentNode!.data.text!; - displayKey = "Option (data)"; - } - - return { - Icon, - componentType, - title, - key: displayKey, - headline, - }; - }; - - const { Icon, componentType, title, key, headline } = - getDisplayDetailsForResult(result); // TODO - display portal wrapper + // TODO - display portal wrapper + const { iconKey, componentType, title, key, headline } = + getDisplayDetailsForResult(result); + const Icon = ICONS[iconKey]; const handleClick = () => { - console.log("todo!"); - console.log({ nodeId: result.item.id }); + console.log({ result }); // get path for node // generate url from path // navigate to url diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/mocks/DataDisplayMap.ts b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/mocks/DataDisplayMap.ts new file mode 100644 index 0000000000..44f4efeb23 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/mocks/DataDisplayMap.ts @@ -0,0 +1,679 @@ +import { FlowGraph, IndexedNode } from "@opensystemslab/planx-core/types"; + +import { SearchResult } from "../../../../../../hooks/useSearch"; + +/** Simple flow which contains an example of each component which has unique rules for finding data values and displaying these as search results */ +export const mockFlow: FlowGraph = { + _root: { + edges: ["UMJi4q9zud", "Xj4E14wvd6", "zryBH8H7vD", "Flfg7UnuhH"], + }, + "3W0WyymBuj": { + data: { + val: "blue", + text: "Blue", + }, + type: 200, + }, + Flfg7UnuhH: { + data: { + title: "This is a FileUploadAndLabel component", + fileTypes: [ + { + fn: "floorplan", + name: "Floorplan", + rule: { + condition: "AlwaysRequired", + }, + }, + ], + hideDropZone: false, + }, + type: 145, + }, + UMJi4q9zud: { + data: { + fn: "colour", + text: "This is a question component", + }, + type: 100, + edges: ["th2EEQ03a7", "3W0WyymBuj"], + }, + Xj4E14wvd6: { + data: { + fn: "listRoot", + title: "This is a list component", + schema: { + min: 1, + type: "Existing residential unit type", + fields: [ + { + data: { + fn: "type", + title: "What best describes the type of this unit?", + options: [ + { + id: "house", + data: { + val: "house", + text: "House", + }, + }, + { + id: "flat", + data: { + val: "flat", + text: "Flat, apartment or maisonette", + }, + }, + { + id: "sheltered", + data: { + val: "sheltered", + text: "Sheltered housing", + }, + }, + { + id: "studio", + data: { + val: "studio", + text: "Studio or bedsit", + }, + }, + { + id: "cluster", + data: { + val: "cluster", + text: "Cluster flat", + }, + }, + { + id: "other", + data: { + val: "other", + text: "Other", + }, + }, + ], + }, + type: "question", + }, + { + data: { + fn: "tenure", + title: "What best describes the tenure of this unit?", + options: [ + { + id: "MH", + data: { + val: "MH", + text: "Market housing", + }, + }, + { + id: "SAIR", + data: { + val: "SAIR", + text: "Social, affordable or interim rent", + }, + }, + { + id: "AHO", + data: { + val: "AHO", + text: "Affordable home ownership", + }, + }, + { + id: "SH", + data: { + val: "SH", + text: "Starter homes", + }, + }, + { + id: "selfCustomBuild", + data: { + val: "selfCustomBuild", + text: "Self-build and custom build", + }, + }, + { + id: "other", + data: { + val: "other", + text: "Other", + }, + }, + ], + }, + type: "question", + }, + { + data: { + fn: "bedrooms", + title: "How many bedrooms does this unit have?", + allowNegatives: false, + }, + type: "number", + }, + { + data: { + fn: "identicalUnits", + title: + "How many units of the type described above exist on the site?", + allowNegatives: false, + }, + type: "number", + }, + ], + }, + schemaName: "Residential units - Existing", + }, + type: 800, + }, + th2EEQ03a7: { + data: { + val: "red", + text: "Red", + }, + type: 200, + }, + zryBH8H7vD: { + data: { + title: "This is a calculate component", + output: "calculateOutput", + formula: "formulaOne + formulaTwo", + defaults: { + formulaOne: "1", + formulaTwo: "1", + }, + formatOutputForAutomations: false, + samples: {}, + }, + type: 700, + }, +}; + +export const mockQuestionResult: SearchResult = { + item: { + id: "UMJi4q9zud", + parentId: "_root", + type: 100, + edges: ["th2EEQ03a7", "3W0WyymBuj"], + data: { + fn: "colour", + text: "This is a question component", + }, + }, + key: "data.fn", + matchIndices: [[0, 3]], + refIndex: 0, +}; + +export const mockAnswerResult: SearchResult = { + item: { + id: "th2EEQ03a7", + parentId: "UMJi4q9zud", + type: 200, + data: { + text: "Red", + val: "red", + }, + }, + key: "data.val", + matchIndices: [[0, 2]], + refIndex: 0, +}; + +export const mockListRootResult: SearchResult = { + item: { + id: "Xj4E14wvd6", + parentId: "_root", + type: 800, + data: { + fn: "listRoot", + title: "This is a list component", + schema: { + min: 1, + type: "Tree type", + fields: [ + { + data: { + fn: "species", + type: "short", + title: "Species", + }, + type: "text", + }, + { + data: { + fn: "work", + type: "short", + title: "Proposed work", + }, + type: "text", + }, + { + data: { + fn: "justification", + type: "short", + title: "Justification", + }, + type: "text", + }, + { + data: { + fn: "urgency", + title: "Urgency", + options: [ + { + id: "low", + data: { + val: "low", + text: "Low", + }, + }, + { + id: "moderate", + data: { + val: "moderate", + text: "Moderate", + }, + }, + { + id: "high", + data: { + val: "high", + text: "High", + }, + }, + { + id: "urgent", + data: { + val: "urgent", + text: "Urgent", + }, + }, + ], + }, + type: "question", + }, + { + data: { + fn: "completionDate", + title: "Expected completion date", + }, + type: "date", + }, + { + data: { + fn: "features", + title: "Where is it? Plot as many as apply", + mapOptions: { + basemap: "MapboxSatellite", + drawMany: true, + drawType: "Point", + drawColor: "#66ff00", + }, + }, + type: "map", + }, + ], + }, + schemaName: "Trees", + }, + }, + key: "data.fn", + matchIndices: [[0, 7]], + refIndex: 0, +}; + +export const mockListDataResult: SearchResult = { + item: { + id: "Xj4E14wvd6", + parentId: "_root", + type: 800, + data: { + fn: "listRoot", + title: "This is a list component", + schema: { + min: 1, + type: "Existing residential unit type", + fields: [ + { + data: { + fn: "type", + title: "What best describes the type of this unit?", + options: [ + { + id: "house", + data: { + val: "house", + text: "House", + }, + }, + { + id: "flat", + data: { + val: "flat", + text: "Flat, apartment or maisonette", + }, + }, + { + id: "sheltered", + data: { + val: "sheltered", + text: "Sheltered housing", + }, + }, + { + id: "studio", + data: { + val: "studio", + text: "Studio or bedsit", + }, + }, + { + id: "cluster", + data: { + val: "cluster", + text: "Cluster flat", + }, + }, + { + id: "other", + data: { + val: "other", + text: "Other", + }, + }, + ], + }, + type: "question", + }, + { + data: { + fn: "tenure", + title: "What best describes the tenure of this unit?", + options: [ + { + id: "MH", + data: { + val: "MH", + text: "Market housing", + }, + }, + { + id: "SAIR", + data: { + val: "SAIR", + text: "Social, affordable or interim rent", + }, + }, + { + id: "AHO", + data: { + val: "AHO", + text: "Affordable home ownership", + }, + }, + { + id: "SH", + data: { + val: "SH", + text: "Starter homes", + }, + }, + { + id: "selfCustomBuild", + data: { + val: "selfCustomBuild", + text: "Self-build and custom build", + }, + }, + { + id: "other", + data: { + val: "other", + text: "Other", + }, + }, + ], + }, + type: "question", + }, + { + data: { + fn: "bedrooms", + title: "How many bedrooms does this unit have?", + allowNegatives: false, + }, + type: "number", + }, + { + data: { + fn: "identicalUnits", + title: + "How many units of the type described above exist on the site?", + allowNegatives: false, + }, + type: "number", + }, + ], + }, + schemaName: "Residential units - Existing", + }, + }, + key: "data.schema.fields.data.fn", + matchIndices: [[0, 5]], + refIndex: 1, +}; + +export const mockListAnswerResult: SearchResult = { + item: { + id: "Xj4E14wvd6", + parentId: "_root", + type: 800, + data: { + fn: "listRoot", + title: "This is a list component", + schema: { + min: 1, + type: "Existing residential unit type", + fields: [ + { + data: { + fn: "type", + title: "What best describes the type of this unit?", + options: [ + { + id: "house", + data: { + val: "house", + text: "House", + }, + }, + { + id: "flat", + data: { + val: "flat", + text: "Flat, apartment or maisonette", + }, + }, + { + id: "sheltered", + data: { + val: "sheltered", + text: "Sheltered housing", + }, + }, + { + id: "studio", + data: { + val: "studio", + text: "Studio or bedsit", + }, + }, + { + id: "cluster", + data: { + val: "cluster", + text: "Cluster flat", + }, + }, + { + id: "other", + data: { + val: "other", + text: "Other", + }, + }, + ], + }, + type: "question", + }, + { + data: { + fn: "tenure", + title: "What best describes the tenure of this unit?", + options: [ + { + id: "MH", + data: { + val: "MH", + text: "Market housing", + }, + }, + { + id: "SAIR", + data: { + val: "SAIR", + text: "Social, affordable or interim rent", + }, + }, + { + id: "AHO", + data: { + val: "AHO", + text: "Affordable home ownership", + }, + }, + { + id: "SH", + data: { + val: "SH", + text: "Starter homes", + }, + }, + { + id: "selfCustomBuild", + data: { + val: "selfCustomBuild", + text: "Self-build and custom build", + }, + }, + { + id: "other", + data: { + val: "other", + text: "Other", + }, + }, + ], + }, + type: "question", + }, + { + data: { + fn: "bedrooms", + title: "How many bedrooms does this unit have?", + allowNegatives: false, + }, + type: "number", + }, + { + data: { + fn: "identicalUnits", + title: + "How many units of the type described above exist on the site?", + allowNegatives: false, + }, + type: "number", + }, + ], + }, + schemaName: "Residential units - Existing", + }, + }, + key: "data.schema.fields.data.options.data.val", + matchIndices: [[0, 14]], + refIndex: 10, +}; + +export const mockCalculateRootResult: SearchResult = { + item: { + id: "zryBH8H7vD", + parentId: "_root", + type: 700, + data: { + title: "This is a calculate component", + output: "calculateOutput", + formula: "formulaOne + formulaTwo", + defaults: { + formulaOne: "1", + formulaTwo: "1", + }, + formatOutputForAutomations: false, + samples: {}, + }, + }, + key: "data.output", + matchIndices: [[0, 14]], + refIndex: 0, +}; + +export const mockCalculateFormulaResult: SearchResult = { + item: { + id: "zryBH8H7vD", + parentId: "_root", + type: 700, + data: { + title: "This is a calculate component", + output: "calculateOutput", + formula: "formulaOne + formulaTwo", + defaults: { + formulaOne: "1", + formulaTwo: "1", + }, + formatOutputForAutomations: false, + samples: {}, + }, + }, + key: "formula", + matchIndices: [[0, 6]], + refIndex: 1, +}; + +export const mockFileUploadAndLabelResult: SearchResult = { + item: { + id: "Flfg7UnuhH", + parentId: "_root", + type: 145, + data: { + title: "This is a FileUploadAndLabel component", + fileTypes: [ + { + fn: "floorplan", + name: "Floorplan", + rule: { + condition: "AlwaysRequired", + }, + }, + ], + hideDropZone: false, + }, + }, + key: "data.fileTypes.fn", + matchIndices: [[0, 8]], + refIndex: 0, +}; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/mocks/simple.ts b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/mocks/simple.ts index 275c397f34..391b4bec02 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/mocks/simple.ts +++ b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search/mocks/simple.ts @@ -49,6 +49,7 @@ export const results: SearchResults = [ }, key: "data.val", matchIndices: [[0, 2]], + refIndex: 0, }, { item: { @@ -62,5 +63,6 @@ export const results: SearchResults = [ }, key: "data.val", matchIndices: [[0, 2]], + refIndex: 0, }, ];