diff --git a/editor.planx.uk/src/@planx/components/Question/Public.test.tsx b/editor.planx.uk/src/@planx/components/Question/Public.test.tsx index 34ad08ae52..f9ae0170fb 100644 --- a/editor.planx.uk/src/@planx/components/Question/Public.test.tsx +++ b/editor.planx.uk/src/@planx/components/Question/Public.test.tsx @@ -1,12 +1,50 @@ -import { waitFor } from "@testing-library/react"; +import { act, waitFor } from "@testing-library/react"; import React from "react"; import { setup } from "testUtils"; import { vi } from "vitest"; import { axe } from "vitest-axe"; +import { Store, useStore } from "pages/FlowEditor/lib/store"; import type { Question } from "./model"; import QuestionComponent, { QuestionLayout } from "./Public"; +const { setState } = useStore; + +// Setup a basic single component flow so that we're testing the "VisibleQuestion" throughout (eg wrapper checks `flow[props.id].edges`) +const flow: Store.Flow = { + "_root": { + "edges": [ + "qustion_id" + ] + }, + "celery_id": { + "data": { + "text": "celery" + }, + "type": 200 + }, + "pizza_id": { + "data": { + "text": "pizza" + }, + "type": 200 + }, + "question_id": { + "data": { + "text": "Best food", + }, + "type": 100, + "edges": [ + "pizza_id", + "celery_id", + ] + }, +}; + +beforeEach(() => { + act(() => setState({ flow })); +}); + const responses: { [key in QuestionLayout]: Question["responses"] } = { [QuestionLayout.Basic]: [ { @@ -58,9 +96,10 @@ describe("Question component", () => { describe(`${QuestionLayout[type]} layout`, () => { it(`renders the layout correctly`, async () => { const handleSubmit = vi.fn(); - + const { user, getByTestId, getByRole, getByText } = setup( { const handleSubmit = vi.fn(); const { user, getByRole, getByTestId } = setup( { const handleSubmit = vi.fn(); const { container } = setup( { const { user, getByTestId, getByText, queryByText } = setup( = (props) => { state.autoAnswerableOptions, ]); + console.log("HERE flow", props); + // Questions without edges act like "sticky notes" in the graph for editors only & can be immediately auto-answered let edges: Edges | undefined; - if (props.id) edges = flow[props.id]?.edges; + if (props.id) edges = flow[props.id]?.edges if (!edges || edges.length === 0) { return ; } diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/advancedAutomations.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/advancedAutomations.test.ts deleted file mode 100644 index f9774e5d9b..0000000000 --- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/advancedAutomations.test.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; - -import { Store, useStore } from "../store"; - -const { getState, setState } = useStore; - -const flow: Store.Flow = { - _root: { - edges: [ - "Imks7j68BD", - "HV0gV8DOil", - "2PT6bTPTqj", - "3H2bGdzpIN", - "AFX3QwbOCd", - ], - }, - "0LzMSk4JTO": { - data: { - val: "food.bread", - text: "bread", - }, - type: TYPES.Answer, - }, - "0vojjvJ6rP": { - data: { - val: "food", - text: "food", - }, - type: TYPES.Answer, - edges: ["mOPogpQa7V"], - }, - "2PT6bTPTqj": { - data: { - fn: "item", - text: "contains", - }, - type: TYPES.Question, - edges: ["oB2vfxQs4D", "ykhO0drpaY", "U9S73zxy9n", "LwozLZdXCA"], - }, - "3H2bGdzpIN": { - data: { - fn: "item", - text: "Does the basket contain apples?", - }, - type: TYPES.Question, - edges: ["BJpKurp49I", "hKebzlFQDa"], - }, - "4JPWSgnGtI": { - data: { - val: "tool", - text: "tools", - }, - type: TYPES.Answer, - edges: ["KcLGMm3UWw"], - }, - "52ZNXBMLDP": { - data: { - color: "#EFEFEF", - title: "?, so must be a 🍌 or 🔧", - resetButton: false, - }, - type: TYPES.Notice, - }, - "6RR1J1lmrM": { - data: { - color: "#EFEFEF", - title: "🍏", - resetButton: false, - }, - type: TYPES.Notice, - }, - "7tV1uvR9ng": { - data: { - val: "tool.spanner", - text: "spanner", - }, - type: TYPES.Answer, - }, - AFX3QwbOCd: { - data: { - fn: "item", - text: "Which does the basket contain?", - }, - type: TYPES.Question, - edges: ["4JPWSgnGtI", "0vojjvJ6rP"], - }, - BJpKurp49I: { - data: { - val: "food.fruit.apple", - text: "Yes", - }, - type: TYPES.Answer, - }, - BloOMLvLJK: { - data: { - val: "food.fruit.banana", - text: "banana", - }, - type: TYPES.Answer, - }, - EqfqaqZ6CH: { - data: { - val: "food.fruit.apple", - text: "apple", - }, - type: TYPES.Answer, - }, - HV0gV8DOil: { - data: { - fn: "item", - text: "shopping trolley (should be skipped)", - allRequired: false, - }, - type: TYPES.Checklist, - edges: ["lTosE7Xo1j", "BloOMLvLJK", "0LzMSk4JTO", "OvNhSiRfdL"], - }, - I8DznYCKVg: { - data: { - val: "food.fruit.banana", - text: "banana", - }, - type: TYPES.Answer, - }, - Imks7j68BD: { - data: { - fn: "item", - text: "shopping trolley", - allRequired: false, - }, - type: TYPES.Checklist, - edges: ["EqfqaqZ6CH", "I8DznYCKVg", "pXFKKRG6lE", "7tV1uvR9ng"], - }, - KcLGMm3UWw: { - data: { - color: "#EFEFEF", - title: "🔧", - resetButton: false, - }, - type: TYPES.Notice, - }, - LwozLZdXCA: { - data: { - text: "neither apples nor bread", - }, - type: TYPES.Answer, - edges: ["52ZNXBMLDP"], - }, - OvNhSiRfdL: { - data: { - val: "tool.spanner", - text: "spanner", - }, - type: TYPES.Answer, - }, - U9S73zxy9n: { - data: { - val: "food.fruit.apple,food.bread", - text: "apples and bread", - }, - type: TYPES.Answer, - edges: ["t3SCqQKeUK"], - }, - g0IAKsBVPQ: { - data: { - color: "#EFEFEF", - title: "🥖", - resetButton: false, - }, - type: TYPES.Notice, - }, - hKebzlFQDa: { - data: { - text: "No", - }, - type: TYPES.Answer, - }, - lTosE7Xo1j: { - data: { - val: "food.fruit.apple", - text: "apple", - }, - type: TYPES.Answer, - }, - mOPogpQa7V: { - data: { - color: "#EFEFEF", - title: "🍌🍏🥖", - resetButton: false, - }, - type: TYPES.Notice, - }, - oB2vfxQs4D: { - data: { - val: "food.fruit.apple", - text: "apples", - }, - type: TYPES.Answer, - edges: ["6RR1J1lmrM"], - }, - pXFKKRG6lE: { - data: { - val: "food.bread", - text: "bread", - }, - type: TYPES.Answer, - }, - t3SCqQKeUK: { - data: { - color: "#EFEFEF", - title: "🍏🥖", - resetButton: false, - }, - type: TYPES.Notice, - }, - ykhO0drpaY: { - data: { - val: "food.bread", - text: "bread", - }, - type: TYPES.Answer, - edges: ["g0IAKsBVPQ"], - }, -}; - -beforeEach(() => { - getState().resetPreview(); -}); - -test("apple", () => { - setState({ - flow, - }); - - expect(getState().upcomingCardIds()).toEqual([ - "Imks7j68BD", - "HV0gV8DOil", - "2PT6bTPTqj", - "3H2bGdzpIN", - "AFX3QwbOCd", - ]); - - // record apple - getState().record("Imks7j68BD", { answers: ["EqfqaqZ6CH"] }); - - expect(getState().upcomingCardIds()).toEqual(["6RR1J1lmrM", "mOPogpQa7V"]); -}); - -test("apple and spanner", () => { - setState({ - flow, - }); - - // record apple and spanner - getState().record("Imks7j68BD", { answers: ["EqfqaqZ6CH", "7tV1uvR9ng"] }); - - expect(getState().upcomingCardIds()).toEqual(["6RR1J1lmrM", "KcLGMm3UWw"]); -}); - -test("apple and bread", () => { - setState({ - flow, - }); - - // record apple and bread - getState().record("Imks7j68BD", { answers: ["EqfqaqZ6CH", "pXFKKRG6lE"] }); - - expect(getState().upcomingCardIds()).toEqual(["t3SCqQKeUK", "mOPogpQa7V"]); -}); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/automations.blanks.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/automations.blanks.test.ts new file mode 100644 index 0000000000..8d0095ec2c --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/automations.blanks.test.ts @@ -0,0 +1,15 @@ +import { Store, useStore } from "../store"; + +const { getState, setState } = useStore; +const { resetPreview } = getState(); + +describe("Auto-answering blanks", () => { + beforeEach(() => { + resetPreview(); + setState({ flow }); + }); + + test.todo("TODO"); +}) + +const flow: Store.Flow = {}; diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/automations.parentChild.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/automations.parentChild.test.ts new file mode 100644 index 0000000000..1631e5dda7 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/automations.parentChild.test.ts @@ -0,0 +1,206 @@ +import { Store, useStore } from "../store"; +import { clickContinue } from "./utils"; + +const { getState, setState } = useStore; +const { upcomingCardIds, resetPreview, autoAnswerableOptions, computePassport } = getState(); + +describe("Parent-child automations and granularity", () => { + beforeEach(() => { + resetPreview(); + setState({ flow }); + }); + + test("Selecting `a` and `a.x` only auto-answers `a.x`", () => { + // Manually answer the first Checklist + expect(upcomingCardIds()).toEqual(["Checklist1", "Checklist2", "Question"]); + clickContinue("Checklist1", { answers: ["Checklist1ParentA", "Checklist1ChildAX"], auto: false }); + + // Only the most granular value is retained in the passport and queued up for auto-answering the subsequent Checklist & Question + expect(upcomingCardIds()?.[0]).toEqual("Checklist2"); + expect(computePassport()?.data).toEqual({ "values": ["a.x"] }); + expect(autoAnswerableOptions("Checklist2")).toEqual(["Checklist2ChildAX"]); + expect(autoAnswerableOptions("Checklist2")).not.toContain("Checklist2ParentA"); + clickContinue("Checklist2", { answers: autoAnswerableOptions("Checklist2"), auto: true }); + + expect(upcomingCardIds()?.[0]).toEqual("Question"); + expect(computePassport()?.data).toEqual({ "values": ["a.x"] }); + expect(autoAnswerableOptions("Question")).toEqual(["QuestionChildAX"]); + expect(autoAnswerableOptions("Question")).toHaveLength(1); + }); + + test("Selecting `a` and `b` auto-answers both", () => { + // Manually answer the first Checklist + expect(upcomingCardIds()).toEqual(["Checklist1", "Checklist2", "Question"]); + clickContinue("Checklist1", { answers: ["Checklist1ParentA", "Checklist1ParentB"], auto: false }); + + // Both values are queued up for auto-answering the subsequent Checklist because they are the same granularity + expect(upcomingCardIds()?.[0]).toEqual("Checklist2"); + expect(computePassport()?.data).toEqual({ "values": ["a", "b"] }); + expect(autoAnswerableOptions("Checklist2")).toEqual(["Checklist2ParentA", "Checklist2ParentB"]); + clickContinue("Checklist2", { answers: autoAnswerableOptions("Checklist2"), auto: true }); + + // Only the left-most value is queued up for auto-answering the Question + expect(upcomingCardIds()?.[0]).toEqual("Question"); + expect(computePassport()?.data).toEqual({ "values": ["a", "b"] }); + expect(autoAnswerableOptions("Question")).toEqual(["QuestionParentA"]); + expect(autoAnswerableOptions("Question")).toHaveLength(1); + }); + + test("Selecting `a`, `a.x` and `b` auto-answers `a.x` and `b` if a Checklist and only `a.x` if a Question", () => { + // Manually answer the first Checklist + expect(upcomingCardIds()).toEqual(["Checklist1", "Checklist2", "Question"]); + clickContinue("Checklist1", { answers: ["Checklist1ParentA", "Checklist1ChildAX", "Checklist1ParentB"], auto: false }); + + // Only the most granular value _per_ parent category is retained in the passport and queued up for auto-answering the subsequent Checklist + expect(upcomingCardIds()?.[0]).toEqual("Checklist2"); + expect(computePassport()?.data).toEqual({ "values": ["a.x", "b"] }); + expect(autoAnswerableOptions("Checklist2")).toEqual(["Checklist2ChildAX", "Checklist2ParentB"]); + expect(autoAnswerableOptions("Checklist2")).not.toContain("Checklist2ParentA"); + clickContinue("Checklist2", { answers: autoAnswerableOptions("Checklist2"), auto: true }); + + // Only the most granular, left-most value is queued up for auto-answering the Question + expect(upcomingCardIds()?.[0]).toEqual("Question"); + expect(computePassport()?.data).toEqual({ "values": ["a.x", "b"] }); + expect(autoAnswerableOptions("Question")).toEqual(["QuestionChildAX"]); + expect(autoAnswerableOptions("Question")).toHaveLength(1); + }); +}); + +const flow: Store.Flow = { + "_root": { + "edges": [ + "Checklist1", + "Checklist2", + "Question" + ] + }, + "QuestionParentB": { + "data": { + "val": "b", + "text": "b parent" + }, + "type": 200 + }, + "Checklist1ParentA": { + "data": { + "val": "a", + "text": "a parent" + }, + "type": 200 + }, + "Checklist2ChildAX": { + "data": { + "val": "a.x", + "text": "a child" + }, + "type": 200 + }, + "Checklist1": { + "data": { + "fn": "values", + "tags": [], + "text": "Pick many", + "allRequired": false, + "forceSelection": false + }, + "type": 105, + "edges": [ + "Checklist1ParentA", + "Checklist1ChildAX", + "Checklist1ParentB" + ] + }, + "Checklist2": { + "data": { + "fn": "values", + "tags": [], + "text": "Pick many", + "allRequired": false, + "forceSelection": false + }, + "type": 105, + "edges": [ + "Checklist2ParentA", + "Checklist2ChildAX", + "Checklist2ParentB", + "Checklist2ParentC", + "Checklist2Blank" + ] + }, + "QuestionChildAX": { + "data": { + "val": "a.x", + "text": "a child" + }, + "type": 200 + }, + "QuestionParentA": { + "data": { + "val": "a", + "text": "a parent" + }, + "type": 200 + }, + "Checklist2ParentC": { + "data": { + "val": "c", + "text": "c parent" + }, + "type": 200 + }, + "QuestionBlank": { + "data": { + "text": "blank" + }, + "type": 200 + }, + "Checklist2Blank": { + "data": { + "text": "blank" + }, + "type": 200 + }, + "Checklist2ParentA": { + "data": { + "val": "a", + "text": "a parent" + }, + "type": 200 + }, + "Question": { + "data": { + "fn": "values", + "tags": [], + "text": "Pick one", + "forceSelection": false + }, + "type": 100, + "edges": [ + "QuestionParentA", + "QuestionChildAX", + "QuestionParentB", + "QuestionBlank" + ] + }, + "Checklist1ParentB": { + "data": { + "val": "b", + "text": "b parent" + }, + "type": 200 + }, + "Checklist2ParentB": { + "data": { + "val": "b", + "text": "b parent" + }, + "type": 200 + }, + "Checklist1ChildAX": { + "data": { + "val": "a.x", + "text": "a child" + }, + "type": 200 + } +}; diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/automations.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/automations.test.ts deleted file mode 100644 index 54fc776b78..0000000000 --- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/automations.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; -import shuffle from "lodash/shuffle"; - -import { useStore } from "../store"; - -const { getState, setState } = useStore; - -beforeEach(() => { - getState().resetPreview(); -}); - -describe("(basic) if the passport contains", () => { - [ - ["food.fruit", "food"], - ["hardware", "hardware"], - ["stationary", "other"], - ].forEach(([item, expected]) => { - test(`[${item}] it should go down the [${expected}] path`, () => { - setState({ - flow: { - _root: { - edges: ["_setter", "item"], - }, - _setter: {}, - item: { - type: TYPES.Question, - data: { fn: "item" }, - edges: ["food", "hardware", "other"], - }, - food: { - type: TYPES.Answer, - data: { val: "food" }, - }, - hardware: { - type: TYPES.Answer, - data: { val: "hardware" }, - }, - other: { - type: TYPES.Answer, - }, - }, - }); - - const defaultPassportData = { - data: { item: [item] }, - }; - getState().record("_setter", defaultPassportData); - - getState().upcomingCardIds(); - - expect(getState().breadcrumbs).toEqual({ - _setter: { - auto: false, - ...defaultPassportData, - }, - item: { - answers: [expected], - auto: true, - }, - }); - }); - }); -}); - -describe("(more advanced) if the passport contains", () => { - [ - ["food.fruit", "food.fruit"], - ["food.dairy", "food"], - ["clothes", "other"], - ].forEach(([item, expected]) => { - test(`[${item}] it should go down the [${expected}] path`, () => { - setState({ - flow: { - _root: { - edges: ["_setter", "item"], - }, - _setter: {}, - item: { - type: TYPES.Question, - data: { fn: "item" }, - edges: ["food.fruit", "food", "other"], - }, - "food.fruit": { - type: TYPES.Answer, - data: { val: "food.fruit" }, - }, - food: { - type: TYPES.Answer, - data: { val: "food" }, - }, - other: { - type: TYPES.Answer, - }, - }, - }); - - const defaultPassportData = { - data: { item: [item] }, - }; - getState().record("_setter", defaultPassportData); - - getState().upcomingCardIds(); - - expect(getState().breadcrumbs).toEqual({ - _setter: { - auto: false, - ...defaultPassportData, - }, - item: { - answers: [expected], - auto: true, - }, - }); - }); - }); -}); - -describe("(advanced) if the passport contains", () => { - const data: Array<[string[], string]> = [ - [["food.fruit.banana"], "neither_apples_nor_bread"], - - [["food.bread"], "bread"], - - [["food.fruit.apple"], "apples"], - - [["food.fruit.apple", "food.fruit.banana"], "apples"], - [["food.fruit.apple", "food.bread"], "apples_and_bread"], - - [ - ["food.fruit.apple", "food.fruit.banana", "food.bread"], - "apples_and_bread", - ], - - [["food.fruit.banana", "food.bread"], "bread"], - ]; - data.forEach(([item, expected]: [string[], string]) => { - test(`[${item.join( - " & ", - )}] it should go down the [${expected}] path`, () => { - setState({ - flow: { - _root: { - edges: ["_setter", "contains"], - }, - _setter: {}, - contains: { - type: TYPES.Question, - data: { fn: "item" }, - edges: shuffle([ - "apples", - "bread", - "apples_and_bread", - "neither_apples_nor_bread", - ]), - }, - apples: { - type: TYPES.Answer, - data: { val: "food.fruit.apple" }, - }, - bread: { - type: TYPES.Answer, - data: { val: "food.bread" }, - }, - apples_and_bread: { - type: TYPES.Answer, - data: { val: "food.fruit.apple,food.bread" }, - }, - neither_apples_nor_bread: { - type: TYPES.Answer, - }, - }, - }); - - const defaultPassportData = { data: { item: shuffle(item) } }; - - getState().record("_setter", defaultPassportData); - - getState().upcomingCardIds(); - - expect(getState().breadcrumbs).toEqual({ - _setter: { - auto: false, - ...defaultPassportData, - }, - contains: { - answers: [expected], - auto: true, - }, - }); - }); - }); -}); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/autoAnswerableFlag.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/autoAnswerableFlag.test.ts new file mode 100644 index 0000000000..def2d8a812 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/autoAnswerableFlag.test.ts @@ -0,0 +1,80 @@ +import { Store, useStore } from "../../store"; + +const { getState, setState } = useStore; +const { resetPreview, autoAnswerableFlag } = getState(); + +beforeEach(() => { + resetPreview(); +}); + +describe("Returns undefined and does not auto-answer any flag paths", () => { + test("If the node is not a Filter type", () => { + setState({ flow: { + "_root": { "edges": ["SetValue"] }, + "SetValue": { "type": 380, "data": { "fn": "projectType", "val": "alter", "operation": "replace" } }, + }}); + + expect(autoAnswerableFlag("SetValue")).not.toBeDefined(); + }); + + test("If the node does not set a `fn`", () => { + const alteredFlow = structuredClone(flowWithFilter); + delete alteredFlow["Filter"].data?.fn; + setState({ flow: alteredFlow }); + + expect(autoAnswerableFlag("Filter")).not.toBeDefined(); + }); + + test("If the node does not have any flag paths (aka options)"); + const alteredFlow = structuredClone(flowWithFilter); + delete alteredFlow["Filter"].edges; + setState({ flow: alteredFlow }); + + expect(autoAnswerableFlag("Filter")).not.toBeDefined(); +}); + +describe("Filters", () => { + test.todo("Auto-answer the single highest order flag path when many flags are collected"); + + test.todo("Auto-answer the blank path (no flag result) when no matching flags have been collected"); +}); + +const flowWithFilter: Store.Flow = { + "_root": { + "edges": [ + "Filter" + ] + }, + "Filter": { + "type": 500, + "data": { + "fn": "flag", + "category": "Material change of use" + }, + "edges": [ + "Flag1", + "Flag2", + "Flag3" + ] + }, + "Flag1": { + "type": 200, + "data": { + "text": "Material change of use", + "val": "MCOU_TRUE" + } + }, + "Flag2": { + "type": 200, + "data": { + "text": "Not material change of use", + "val": "MCOU_FALSE" + } + }, + "Flag3": { + "type": 200, + "data": { + "text": "No flag result" + } + } +}; diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/autoAnswerableOptions.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/autoAnswerableOptions.test.ts index 5ca9ff542e..32901e1f8b 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/autoAnswerableOptions.test.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/autoAnswerableOptions.test.ts @@ -1,32 +1,53 @@ import { useStore } from "../../store"; -const { getState } = useStore; -const { resetPreview } = getState(); +const { getState, setState } = useStore; +const { resetPreview, autoAnswerableOptions } = getState(); beforeEach(() => { resetPreview(); }); +describe("Returns undefined and does not auto-answer any options", () => { + test("If the node is not a Question or Checklist type", () => { + setState({ + flow: { + "_root": { "edges": ["SetValue"] }, + "SetValue": { "type": 380, "data": { "fn": "projectType", "val": "alter", "operation": "replace" } }, + }, + }); + + expect(autoAnswerableOptions("SetValue")).not.toBeDefined(); + }); + + test.todo("If the node is a 'sticky note' Question without edges"); + + test.todo("If the node does not set a `fn`"); + + test.todo("If we've never seen another node with this `fn` before"); +}); + describe("Questions", () => { - test.todo("Correctly auto-answers the option that exactly matches a passport value"); + test.todo("Auto-answer the option that exactly matches a passport value"); + + test.todo("Auto-answer the less granular option when there's a single more granular passport value"); - test.todo("Correctly auto-answers the less granular option when there's a single more granular passport value"); + test.todo("Auto-answer the single most granular, left-most option when there are many matching passport values"); - test.todo("Correctly auto-answers the single left-most option when there are many matching passport values"); + test.todo("Auto-answer through the blank path when we have seen this node `fn` but there are no matching passport values"); - test.todo("Correctly auto-answers through the blank path when there are no matching passport values but we've seen this passport fn before"); + test.todo("Auto-answer through the blank path when we have not seen this node `fn` but we have seen all possible option `val`"); }); describe("Checklists", () => { - test.todo("Correctly auto-answers all options that exactly match passport values"); + test.todo("Auto-answer all options that exactly match passport values"); - test.todo("Correctly auto-answers all less granular options when there are more granular passport values"); + test.todo("Auto-answer all less granular options when there are more granular passport values"); - test.todo("Correctly auto-answers through the blank path when there are no matching passport values but we've seen this passport fn before"); -}); + test.todo("Auto-answer through the blank path when we have seen thsi node `fn` but there are no matching passport values"); -describe("Filters", () => { - test.todo("Correctly auto-answers the single highest order flag path when many flags are collected"); + test.todo("Auto-answer through the blank path when we have not seen this node `fn` but we have seen all possible option `val`"); +}); - test.todo("Correctly auto-answers the through the blank path (no flag result) when no matching flags have been collected"); +describe("Blanks and `_nots`", () => { + test.todo("TODO"); }); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/unseen.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/unseen.test.ts deleted file mode 100644 index b8bb32b230..0000000000 --- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/unseen.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Store, useStore } from "../store"; - -const { getState, setState } = useStore; - -// https://github.com/theopensystemslab/planx-new/pull/430#issue-625111571 -const flow: Store.Flow = { - _root: { - edges: ["Dq7qLvn9If", "GZAmDGuV3J"], - }, - Dq7qLvn9If: { - type: 100, - data: { - fn: "test", - text: "first", - }, - edges: ["to4BQeRpOn", "6CTQDoPZPQ", "gxrGcPJCqi"], - }, - to4BQeRpOn: { - type: 200, - data: { - text: "1", - val: "1", - }, - }, - "6CTQDoPZPQ": { - type: 200, - data: { - text: "2", - val: "2", - }, - }, - GZAmDGuV3J: { - type: 100, - data: { - fn: "test", - text: "second", - }, - edges: ["R4N2rp5nXt", "tX0BQy3QcA"], - }, - R4N2rp5nXt: { - type: 200, - data: { - text: "1", - val: "1", - }, - }, - tX0BQy3QcA: { - type: 200, - data: { - text: "empty", - }, - edges: ["pws2AF5whV"], - }, - gxrGcPJCqi: { - type: 200, - data: { - text: "empty", - }, - }, - pws2AF5whV: { - type: 100, - data: { - fn: "test", - text: "inner", - }, - edges: ["aH3SQ3Agsi", "WRSytUiGsr"], - }, - aH3SQ3Agsi: { - type: 200, - data: { - text: "1", - val: "1", - }, - }, - WRSytUiGsr: { - type: 200, - data: { - text: "unseen", - val: "unseen", - }, - }, -}; - -it("always shows a question when has a response(value) that hasn't been seen before", () => { - setState({ flow }); - getState().record("Dq7qLvn9If", { answers: ["6CTQDoPZPQ"] }); - expect(getState().upcomingCardIds()).toEqual(["pws2AF5whV"]); -}); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/useNotValues.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/useNotValues.test.ts deleted file mode 100644 index 7de640f186..0000000000 --- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/useNotValues.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { Store, useStore } from "../store"; - -// flow preview: https://i.imgur.com/nCov5CE.png - -const flow: Store.Flow = { - _root: { - edges: ["NS7QFc7Cjc", "3cNtq1pLmt", "eTBHJsbJKc"], - }, - "3cNtq1pLmt": { - data: { - fn: "animal", - text: "is it a lion?", - }, - type: 100, - edges: ["TDIbLrdTdd", "TnvmCtle0s"], - }, - BecasKrIhI: { - data: { - text: "neither", - }, - type: 200, - }, - NS7QFc7Cjc: { - data: { - fn: "animal", - text: "which wild cat is it?", - }, - type: 100, - edges: ["sv0hklWPX1", "UqZo0rGwcY", "BecasKrIhI"], - }, - Nrf7BHDJvO: { - data: { - text: "neither", - }, - type: 200, - edges: ["UOefNWg6uf"], - }, - Sd38UCC8Cg: { - data: { - content: "

it's a lion

\n", - }, - type: 250, - }, - TDIbLrdTdd: { - data: { - val: "lion", - text: "yes", - }, - type: 200, - }, - TnvmCtle0s: { - data: { - text: "no", - }, - type: 200, - }, - UOefNWg6uf: { - data: { - content: "

it's a tiger or something else

\n", - }, - type: 250, - }, - UqZo0rGwcY: { - data: { - val: "tiger", - text: "tiger", - }, - type: 200, - }, - eOoDvdKjWf: { - data: { - val: "lion", - text: "lion", - }, - type: 200, - edges: ["Sd38UCC8Cg"], - }, - eTBHJsbJKc: { - data: { - fn: "animal", - text: "ok, so which animal is it?", - }, - type: 100, - edges: ["eOoDvdKjWf", "nR15Tl0lhC", "Nrf7BHDJvO"], - }, - nR15Tl0lhC: { - data: { - val: "gazelle", - text: "gazelle", - }, - type: 200, - edges: ["pqZK1mpn23"], - }, - pqZK1mpn23: { - data: { - content: "

it's a gazelle

\n", - }, - type: 250, - }, - sv0hklWPX1: { - data: { - val: "lion", - text: "lion", - }, - type: 200, - }, -}; - -const { getState, setState } = useStore; - -describe("if I initially pick", () => { - beforeEach(() => { - getState().resetPreview(); - setState({ flow }); - }); - - test("lion, it should display 'lion'", () => { - getState().record("NS7QFc7Cjc", { answers: ["TDIbLrdTdd"] }); - expect(getState().upcomingCardIds()).toEqual(["Sd38UCC8Cg"]); - }); - - test("tiger, it should display 'tiger or something else'", () => { - getState().record("NS7QFc7Cjc", { answers: ["UqZo0rGwcY"] }); - expect(getState().upcomingCardIds()).toEqual(["UOefNWg6uf"]); - }); - - test("gazelle, it should ask which animal it is", () => { - getState().record("NS7QFc7Cjc", { answers: ["BecasKrIhI"] }); - expect(getState().upcomingCardIds()).toEqual(["eTBHJsbJKc"]); - getState().record("eTBHJsbJKc", { answers: ["nR15Tl0lhC"] }); - expect(getState().upcomingCardIds()).toEqual(["pqZK1mpn23"]); - }); -}); - -test("back button works as expected", () => { - getState().resetPreview(); - setState({ - flow, - breadcrumbs: { - NS7QFc7Cjc: { - answers: ["BecasKrIhI"], - auto: false, - }, - "3cNtq1pLmt": { - answers: ["TnvmCtle0s"], - auto: true, - }, - eTBHJsbJKc: { - answers: ["nR15Tl0lhC"], - auto: false, - }, - }, - }); - - getState().record("eTBHJsbJKc"); - expect(getState().upcomingCardIds()).toEqual(["eTBHJsbJKc"]); -}); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/store/preview.ts b/editor.planx.uk/src/pages/FlowEditor/lib/store/preview.ts index 8f286e6c98..4b9a07146c 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/store/preview.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/store/preview.ts @@ -667,36 +667,6 @@ export const previewStore: StateCreator< getCurrentCard: () => get().currentCard, }); -const knownNots = ( - flow: Store.Flow, - breadcrumbs: Store.Breadcrumbs, - nots = {}, -) => - Object.entries(breadcrumbs).reduce( - (acc, [id, { answers = [] }]) => { - if (!flow[id]) return acc; - - const _knownNotVals = difference( - flow[id].edges, - answers as Array, - ); - - if (flow[id].data?.fn) { - acc[flow[id].data.fn] = uniq( - flatten([ - ...(acc[flow[id].data?.fn] || []), - _knownNotVals.flatMap((n) => flow[n].data?.val), - ]), - ).filter(Boolean) as Array; - } - - return acc; - }, - { - ...nots, - } as Record>, - ); - interface RemoveOrphansFromBreadcrumbsProps { id: string; flow: Store.Flow;