From 2d12b7380192f663016c9cd6108826bc8b1b8466 Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Fri, 1 Dec 2023 13:59:56 +0100 Subject: [PATCH] split up preview.test.ts by method --- .../FlowEditor/lib/__tests__/flags.test.ts | 22 +- .../FlowEditor/lib/__tests__/ordering.test.ts | 100 ++++-- .../__tests__/{ => preview}/canGoBack.test.ts | 70 ++-- .../lib/__tests__/preview/changeAnswer.ts | 71 ++++ .../{ => preview}/getResultData.test.ts | 12 +- .../lib/__tests__/preview/hasPaid.test.ts | 40 +++ .../{ => preview}/overrideAnswer.test.ts | 2 +- .../{ => preview}/previousCard.test.ts | 12 +- .../lib/__tests__/preview/record.test.ts | 68 ++++ .../removeNodesDependentOnPassport.test.ts} | 325 +----------------- .../removeOrphansFromBreadcrumbs.test.ts | 158 +++++++++ .../__tests__/preview/resetPreview.test.ts | 27 ++ .../__tests__/preview/upcomingCardIds.test.ts | 151 ++++++++ 13 files changed, 657 insertions(+), 401 deletions(-) rename editor.planx.uk/src/pages/FlowEditor/lib/__tests__/{ => preview}/canGoBack.test.ts (70%) create mode 100644 editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/changeAnswer.ts rename editor.planx.uk/src/pages/FlowEditor/lib/__tests__/{ => preview}/getResultData.test.ts (56%) create mode 100644 editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/hasPaid.test.ts rename editor.planx.uk/src/pages/FlowEditor/lib/__tests__/{ => preview}/overrideAnswer.test.ts (99%) rename editor.planx.uk/src/pages/FlowEditor/lib/__tests__/{ => preview}/previousCard.test.ts (71%) create mode 100644 editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/record.test.ts rename editor.planx.uk/src/pages/FlowEditor/lib/__tests__/{preview.test.ts => preview/removeNodesDependentOnPassport.test.ts} (59%) create mode 100644 editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/removeOrphansFromBreadcrumbs.test.ts create mode 100644 editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/resetPreview.test.ts create mode 100644 editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/upcomingCardIds.test.ts diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/flags.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/flags.test.ts index 8b7af0b0ec..b5b1ccc625 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/flags.test.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/flags.test.ts @@ -94,33 +94,33 @@ describe("changing flag inside flag filter doesn't affect the filter's behaviour val: "MISSING_INFO", text: "Missing information", }, - type: 200, + type: TYPES.Response, edges: ["missing_info_content"], }, q2: { data: { text: "another", }, - type: 100, + type: TYPES.Statement, edges: ["missing_2", "nothing_2"], }, missing_info_content: { data: { content: "missing info", }, - type: 250, + type: TYPES.Content, }, nothing_2: { data: { text: "nothing", }, - type: 200, + type: TYPES.Response, }, no_result: { data: { text: "(No Result)", }, - type: 200, + type: TYPES.Response, edges: ["q2"], }, missing_2: { @@ -128,38 +128,38 @@ describe("changing flag inside flag filter doesn't affect the filter's behaviour flag: "MISSING_INFO", text: "missing", }, - type: 200, + type: TYPES.Response, }, immune: { data: { val: "IMMUNE", text: "Immune", }, - type: 200, + type: TYPES.Response, }, filter: { data: { fn: "flag", }, - type: 500, + type: TYPES.Filter, edges: ["missing_info", "immune", "no_result"], }, q1: { - type: 100, + type: TYPES.Statement, data: { text: "q", }, edges: ["missing_1", "nothing_1"], }, missing_1: { - type: 200, + type: TYPES.Response, data: { text: "missing", flag: "MISSING_INFO", }, }, nothing_1: { - type: 200, + type: TYPES.Response, data: { text: "nothing", }, diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/ordering.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/ordering.test.ts index 288efb5d2e..2cc7c77c8b 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/ordering.test.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/ordering.test.ts @@ -6,108 +6,112 @@ const { getState, setState } = vanillaStore; const flow: Store.flow = { _root: { - edges: ["P36QH99kvZ", "uYvBtZO8AN"], + edges: ["QuestionTrolley", "ChecklistTrolley"], }, - uYvBtZO8AN: { + ChecklistTrolley: { type: TYPES.Checklist, data: { allRequired: false, text: "shopping trolley 2", fn: "item", }, - edges: ["6J02AGJpgH", "9wMrjIHzW4"], + edges: ["AppleChecklistResponse", "BananaChecklistResponse"], }, - "6J02AGJpgH": { + AppleChecklistResponse: { data: { text: "apple", val: "food.fruit.apple", }, type: TYPES.Response, - edges: ["J1u7Gpf8S1", "vR40DW4oAn", "mNzbrbhCsT"], + edges: [ + "AppleQuestionWithoutFn", + "AutoAnsweredBananaQuestion", + "FinalContent", + ], }, - "9wMrjIHzW4": { + BananaChecklistResponse: { data: { text: "banana", val: "food.fruit.banana", }, type: TYPES.Response, }, - vR40DW4oAn: { + AutoAnsweredBananaQuestion: { type: TYPES.Statement, data: { text: "did you choose the banana?", fn: "item", }, - edges: ["y9sfwmwRG3", "ojPenHnK0K"], + edges: ["YesBanana", "NoBanana"], }, - y9sfwmwRG3: { + YesBanana: { type: TYPES.Response, data: { text: "yes", val: "food.fruit.banana", }, }, - ojPenHnK0K: { + NoBanana: { type: TYPES.Response, data: { text: "no", }, - edges: ["bpbEbD6fHo"], + edges: ["BananaQuestionWithoutFn"], }, - mNzbrbhCsT: { + FinalContent: { type: TYPES.Content, data: { content: "

last thing

\n", }, }, - bpbEbD6fHo: { + BananaQuestionWithoutFn: { type: TYPES.Statement, data: { text: "will you be eating the banana today?", }, - edges: ["jUDIdyRnl3", "gDIoLjLoFW"], + edges: ["YesEatingBanana", "NoEatingBanana"], }, - jUDIdyRnl3: { + YesEatingBanana: { type: TYPES.Response, data: { text: "yes", }, }, - gDIoLjLoFW: { + NoEatingBanana: { type: TYPES.Response, data: { text: "no", }, }, - J1u7Gpf8S1: { + AppleQuestionWithoutFn: { type: TYPES.Statement, data: { text: "you chose apple", }, - edges: ["ij94v25xVZ"], + edges: ["YesApple"], }, - ij94v25xVZ: { + YesApple: { type: TYPES.Response, data: { text: "i did", }, }, - P36QH99kvZ: { + QuestionTrolley: { type: TYPES.Statement, data: { fn: "item", text: "shopping trolley 1", }, - edges: ["TMRY4IGTwG", "3dCm8g4wBY"], + edges: ["AppleQuestionResponse", "BananaQuestionResponse"], }, - TMRY4IGTwG: { + AppleQuestionResponse: { type: TYPES.Response, data: { text: "apple", val: "food.fruit.apple", }, }, - "3dCm8g4wBY": { + BananaQuestionResponse: { type: TYPES.Response, data: { text: "banana", @@ -120,24 +124,50 @@ beforeEach(() => { getState().resetPreview(); }); -test("order", () => { +test("Nodes are asked in the expected order", () => { setState({ flow, }); - expect(getState().upcomingCardIds()).toEqual(["P36QH99kvZ", "uYvBtZO8AN"]); - getState().record("P36QH99kvZ", { answers: ["TMRY4IGTwG"] }); + // Root nodes are immediately queued up in upcomingCardIds() expect(getState().upcomingCardIds()).toEqual([ - "J1u7Gpf8S1", - "bpbEbD6fHo", - "mNzbrbhCsT", + "QuestionTrolley", + "ChecklistTrolley", ]); - getState().record("J1u7Gpf8S1", { answers: ["ij94v25xVZ"] }); // you chose apple - i did - expect(getState().upcomingCardIds()).toEqual(["bpbEbD6fHo", "mNzbrbhCsT"]); + + // Proceed through first Question and answer "Apple" + getState().record("QuestionTrolley", { answers: ["AppleQuestionResponse"] }); + getState().upcomingCardIds(); // mimic "Continue" + + // New upcoming cards + expect(getState().upcomingCardIds()).toEqual([ + "AppleQuestionWithoutFn", + "BananaQuestionWithoutFn", + "FinalContent", + ]); + + // Two nodes have been auto-answered based on Question response + expect(getState().breadcrumbs).toEqual({ + AutoAnsweredBananaQuestion: { answers: ["NoBanana"], auto: true }, + ChecklistTrolley: { answers: ["AppleChecklistResponse"], auto: true }, + QuestionTrolley: { answers: ["AppleQuestionResponse"], auto: false }, + }); + + // Manually answer a branched Question + getState().record("AppleQuestionWithoutFn", { answers: ["YesApple"] }); + getState().upcomingCardIds(); + + // Updated upcoming cards + expect(getState().upcomingCardIds()).toEqual([ + "BananaQuestionWithoutFn", + "FinalContent", + ]); + + // Updated breadcrumbs expect(getState().breadcrumbs).toEqual({ - J1u7Gpf8S1: { answers: ["ij94v25xVZ"], auto: false }, - P36QH99kvZ: { answers: ["TMRY4IGTwG"], auto: false }, - uYvBtZO8AN: { answers: ["6J02AGJpgH"], auto: true }, - vR40DW4oAn: { answers: ["ojPenHnK0K"], auto: true }, + AppleQuestionWithoutFn: { answers: ["YesApple"], auto: false }, + QuestionTrolley: { answers: ["AppleQuestionResponse"], auto: false }, + ChecklistTrolley: { answers: ["AppleChecklistResponse"], auto: true }, + AutoAnsweredBananaQuestion: { answers: ["NoBanana"], auto: true }, }); }); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/canGoBack.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/canGoBack.test.ts similarity index 70% rename from editor.planx.uk/src/pages/FlowEditor/lib/__tests__/canGoBack.test.ts rename to editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/canGoBack.test.ts index e5abaee960..6a2c20e97a 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/canGoBack.test.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/canGoBack.test.ts @@ -1,54 +1,56 @@ -import { Store, vanillaStore } from "../store"; +import { TYPES } from "@planx/components/types"; + +import { Store, vanillaStore } from "../../store"; const { getState, setState } = vanillaStore; // https://imgur.com/VFV64ax const flow: Store.flow = { _root: { - edges: ["XYoJeox7F0", "8ZSxuIfFYE", "bmsSl3ScbV", "ltuI9xrBHk"], + edges: ["Question", "Pay", "Content", "Confirmation"], }, - XYoJeox7F0: { - type: 100, + Question: { + type: TYPES.Statement, data: { text: "first question", }, - edges: ["VfJAj7agvC", "YQXjsVsGqf"], + edges: ["NoFeeAnswerPath", "FeeAnswerPath"], }, - VfJAj7agvC: { - type: 200, + NoFeeAnswerPath: { + type: TYPES.Response, data: { text: "no fee", }, }, - YQXjsVsGqf: { - type: 200, + FeeAnswerPath: { + type: TYPES.Response, data: { text: "fee", }, - edges: ["DlgsufM3OK"], + edges: ["Calculate"], }, - DlgsufM3OK: { - type: 700, + Calculate: { + type: TYPES.Calculate, data: { output: "fee", formula: "10", }, }, - "8ZSxuIfFYE": { - type: 400, + Pay: { + type: TYPES.Pay, data: { title: "Pay for your application", fn: "fee", }, }, - ltuI9xrBHk: { - type: 725, + Confirmation: { + type: TYPES.Confirmation, data: { heading: "Application sent", }, }, - bmsSl3ScbV: { - type: 250, + Content: { + type: TYPES.Content, data: { content: "

after payment

\n", }, @@ -66,9 +68,9 @@ describe("can go back if", () => { test("the previous component was manually answered", () => { setState({ breadcrumbs: { - XYoJeox7F0: { + Question: { auto: false, - answers: ["VfJAj7agvC"], + answers: ["NoFeeAnswerPath"], }, }, }); @@ -78,11 +80,11 @@ describe("can go back if", () => { test("the user skipped the payment component", () => { setState({ breadcrumbs: { - XYoJeox7F0: { + Question: { auto: false, - answers: ["VfJAj7agvC"], + answers: ["NoFeeAnswerPath"], }, - "8ZSxuIfFYE": { + Pay: { auto: true, }, }, @@ -99,9 +101,9 @@ describe("cannot go back if", () => { test("the only previous component was auto-answered", () => { setState({ breadcrumbs: { - XYoJeox7F0: { + Question: { auto: true, - answers: ["VfJAj7agvC"], + answers: ["NoFeeAnswerPath"], }, }, }); @@ -111,17 +113,17 @@ describe("cannot go back if", () => { test("the applicant made a payment", () => { setState({ breadcrumbs: { - XYoJeox7F0: { + Question: { auto: false, - answers: ["YQXjsVsGqf"], + answers: ["FeeAnswerPath"], }, - DlgsufM3OK: { + Calculate: { auto: true, data: { fee: 10, }, }, - "8ZSxuIfFYE": { + Pay: { auto: false, }, }, @@ -132,21 +134,21 @@ describe("cannot go back if", () => { test("changing a component's answer", () => { setState({ breadcrumbs: { - XYoJeox7F0: { + Question: { auto: false, - answers: ["YQXjsVsGqf"], + answers: ["FeeAnswerPath"], }, - DlgsufM3OK: { + Calculate: { auto: true, data: { fee: 10, }, }, - "8ZSxuIfFYE": { + Pay: { auto: false, }, }, - changedNode: "ltuI9xrBHk", + changedNode: "Confirmation", }); expect(getState().canGoBack(getState().currentCard())).toStrictEqual(false); }); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/changeAnswer.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/changeAnswer.ts new file mode 100644 index 0000000000..03cc472c81 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/changeAnswer.ts @@ -0,0 +1,71 @@ +import cloneDeep from "lodash/cloneDeep"; + +import { Store, vanillaStore } from "../../store"; +import flowWithAutoAnswersMock from "../mocks/flowWithAutoAnswers.json"; + +const { getState, setState } = vanillaStore; + +const flowWithAutoAnswers = cloneDeep(flowWithAutoAnswersMock) as Store.flow; + +const { record, changeAnswer, computePassport } = getState(); + +describe("changeAnswer", () => { + test("should auto-answer future nodes with the updated passport variable correctly", () => { + // See https://trello.com/c/B8xMMJLo/1930-changes-from-the-review-page-that-affect-the-fee-do-not-update-the-fee + // and https://editor.planx.uk/testing/autoanswer-change-test + const flow = { ...flowWithAutoAnswers }; + + // Mock initial state as if we've initially answered "Yes" to the question and reached the Review component, about to click "change" + const breadcrumbs = { + rCjETwjwE3: { + auto: false, + answers: ["b0qdvLAxIL"], + }, + vgj2UNYK9r: { + answers: ["X9JjnbPpnd"], + auto: true, + }, + } as Store.breadcrumbs; + const cachedBreadcrumbs = {} as Store.cachedBreadcrumbs; + + setState({ + flow, + breadcrumbs, + cachedBreadcrumbs, + }); + + // Assert our initial passport state is correct + expect(computePassport()).toEqual({ + data: { + "application.fee.exemption.disability": ["true"], + }, + }); + + // Change the question answer from "Yes" to "No" + changeAnswer("rCjETwjwE3"); + record("rCjETwjwE3", { + answers: ["ykNZocRJtQ"], + auto: false, + }); + + expect(getState().changedNode).toEqual("rCjETwjwE3"); + + // Confirm the passport has updated to reflect new answer and has not retained previous answer + expect(computePassport()).toEqual({ + data: { + "application.fee.exemption.disability": ["false"], + }, + }); + + const originalAnswer = { + vgj2UNYK9r: { + answers: ["X9JjnbPpnd"], + auto: true, + }, + } as Store.cachedBreadcrumbs; + + // Confirm that our original answer is still preserved in cachedBreadcrumbs, but not included in current breadcrumbs + expect(getState().breadcrumbs).not.toContain(originalAnswer); + expect(getState().cachedBreadcrumbs).toStrictEqual(originalAnswer); + }); +}); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/getResultData.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/getResultData.test.ts similarity index 56% rename from editor.planx.uk/src/pages/FlowEditor/lib/__tests__/getResultData.test.ts rename to editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/getResultData.test.ts index 06968ce916..47f4342b22 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/getResultData.test.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/getResultData.test.ts @@ -1,6 +1,6 @@ -import { getResultData } from "../store/preview"; +import { getResultData } from "../../store/preview"; -test("result data", () => { +test("Default result data", () => { const result = getResultData({}, {}); expect(result).toEqual({ @@ -18,3 +18,11 @@ test("result data", () => { }, }); }); + +test.todo("Returns correct result based on collected flags"); + +test.todo( + "Returns correct, custom text based on collected flags and overrides", +); + +test.todo("Returns result data for flagsets beyond `Planning permission`"); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/hasPaid.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/hasPaid.test.ts new file mode 100644 index 0000000000..e82bf9e5ae --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/hasPaid.test.ts @@ -0,0 +1,40 @@ +import { TYPES } from "@planx/components/types"; + +import { vanillaStore } from "../../store"; + +const { getState, setState } = vanillaStore; +const { record, hasPaid } = getState(); + +test("hasPaid is updated if a Pay component has been recorded", () => { + setState({ + flow: { + _root: { + edges: ["a", "b"], + }, + a: { + type: TYPES.Statement, + edges: ["c"], + }, + b: { + type: TYPES.Statement, + }, + c: { + type: TYPES.Pay, + }, + + d: { type: TYPES.Response }, + e: { type: TYPES.Review }, + }, + }); + + record("a", { answers: ["c"] }); + expect(hasPaid()).toBe(false); + + record("c", {}); + expect(getState().breadcrumbs).toEqual({ + a: { answers: ["c"], auto: false }, + c: { auto: false }, + }); + + expect(hasPaid()).toBe(true); +}); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/overrideAnswer.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/overrideAnswer.test.ts similarity index 99% rename from editor.planx.uk/src/pages/FlowEditor/lib/__tests__/overrideAnswer.test.ts rename to editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/overrideAnswer.test.ts index 9757bf9bfc..e0f53e3992 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/overrideAnswer.test.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/overrideAnswer.test.ts @@ -1,4 +1,4 @@ -import { vanillaStore } from "../store"; +import { vanillaStore } from "../../store"; const { getState, setState } = vanillaStore; diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/previousCard.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/previousCard.test.ts similarity index 71% rename from editor.planx.uk/src/pages/FlowEditor/lib/__tests__/previousCard.test.ts rename to editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/previousCard.test.ts index 63fb3c4c14..5ccedeeb2b 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/previousCard.test.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/previousCard.test.ts @@ -1,9 +1,11 @@ -import { vanillaStore } from "../store"; +import { vanillaStore } from "../../store"; const { getState, setState } = vanillaStore; +const { resetPreview, previousCard, currentCard } = getState(); + beforeEach(() => { - getState().resetPreview(); + resetPreview(); }); const setup = (args = {}) => @@ -22,7 +24,7 @@ const setup = (args = {}) => describe("store.previousCard is", () => { test("undefined when there are no breadcrumbs", () => { setup(); - expect(getState().previousCard(getState().currentCard())).toBeUndefined(); + expect(previousCard(currentCard())).toBeUndefined(); }); test("undefined when cards were automatically answered", () => { @@ -32,7 +34,7 @@ describe("store.previousCard is", () => { b: { auto: true }, }, }); - expect(getState().previousCard(getState().currentCard())).toBeUndefined(); + expect(previousCard(currentCard())).toBeUndefined(); }); test("the most recent human-answered card id", () => { @@ -43,6 +45,6 @@ describe("store.previousCard is", () => { }, }); - expect(getState().previousCard(getState().currentCard())).toEqual("a"); + expect(previousCard(currentCard())).toEqual("a"); }); }); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/record.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/record.test.ts new file mode 100644 index 0000000000..7444a7c312 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/record.test.ts @@ -0,0 +1,68 @@ +import { TYPES } from "@planx/components/types"; + +import { vanillaStore } from "../../store"; + +const { getState, setState } = vanillaStore; +const { record } = getState(); + +describe("error handling", () => { + test("cannot record id that doesn't exist", () => { + setState({ + flow: { + _root: { + edges: ["a", "b"], + }, + a: { + type: TYPES.InternalPortal, + edges: ["c"], + }, + b: { + edges: ["d"], + }, + c: { + edges: ["d"], + }, + d: {}, + }, + }); + + expect(() => record("x", {})).toThrow("id not found"); + }); +}); + +test("record(id, undefined) clears up breadcrumbs", () => { + setState({ + flow: { + _root: { + edges: ["a", "b"], + }, + a: { + type: TYPES.Statement, + edges: ["c"], + }, + b: { + type: TYPES.Statement, + }, + c: { + type: TYPES.Response, + edges: ["d"], + }, + d: { + type: TYPES.Statement, + edges: ["e", "f"], + }, + e: { type: TYPES.Response }, + f: { type: TYPES.Response }, + }, + }); + record("a", { answers: ["c"] }); + record("d", { answers: ["e", "f"] }); + expect(getState().breadcrumbs).toEqual({ + a: { answers: ["c"], auto: false }, + d: { answers: ["e", "f"], auto: false }, + }); + + record("a"); + + expect(getState().breadcrumbs).toEqual({}); +}); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/removeNodesDependentOnPassport.test.ts similarity index 59% rename from editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview.test.ts rename to editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/removeNodesDependentOnPassport.test.ts index c9338e4e25..092553572a 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview.test.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/removeNodesDependentOnPassport.test.ts @@ -1,34 +1,22 @@ -import { TYPES } from "@planx/components/types"; import cloneDeep from "lodash/cloneDeep"; -import { Store, vanillaStore } from "../store"; -import { - removeNodesDependentOnPassport, - removeOrphansFromBreadcrumbs, -} from "../store/preview"; -import breadcrumbsDependentOnPassportMock from "./mocks/breadcrumbsDependentOnPassport.json"; -import flowWithAutoAnswersMock from "./mocks/flowWithAutoAnswers.json"; -import flowWithPassportComponentsMock from "./mocks/flowWithPassportComponents.json"; +import { Store, vanillaStore } from "../../store"; +import { removeNodesDependentOnPassport } from "../../store/preview"; +import breadcrumbsDependentOnPassportMock from "../mocks/breadcrumbsDependentOnPassport.json"; +import flowWithPassportComponentsMock from "../mocks/flowWithPassportComponents.json"; const { getState, setState } = vanillaStore; + let breadcrumbsDependentOnPassport = cloneDeep( breadcrumbsDependentOnPassportMock, ) as Store.breadcrumbs; + let flowWithPassportComponents = cloneDeep( flowWithPassportComponentsMock, ) as Store.flow; -const flowWithAutoAnswers = cloneDeep(flowWithAutoAnswersMock) as Store.flow; - -const { - record, - upcomingCardIds, - currentCard, - resetPreview, - hasPaid, - previousCard, - changeAnswer, - computePassport, -} = getState(); + +const { record, resetPreview, previousCard, currentCard, changeAnswer } = + getState(); beforeEach(() => { resetPreview(); @@ -40,214 +28,6 @@ beforeEach(() => { ) as Store.flow; }); -test("it lists upcoming cards", () => { - setState({ - flow: { - _root: { - edges: ["a", "b"], - }, - a: { - type: TYPES.Statement, - edges: ["c"], - }, - b: { - type: TYPES.Statement, - }, - c: { - type: TYPES.Response, - edges: ["d"], - }, - d: { - type: TYPES.Statement, - edges: ["e", "f"], - }, - e: { type: TYPES.Response }, - f: { type: TYPES.Response }, - }, - }); - - expect(upcomingCardIds()).toEqual(["a"]); - - record("a", { answers: ["c"] }); - - expect(upcomingCardIds()).toEqual(["d"]); - - record("d", { answers: ["e", "f"] }); - - expect(upcomingCardIds()).toEqual([]); -}); - -test("notice", () => { - setState({ - flow: { - _root: { - edges: ["a"], - }, - a: { - type: TYPES.Notice, - }, - }, - }); - - expect(upcomingCardIds()).toEqual(["a"]); -}); - -test("crawling with portals", () => { - setState({ - flow: { - _root: { - edges: ["a", "b"], - }, - a: { - type: TYPES.InternalPortal, - edges: ["c"], - }, - b: { - edges: ["d"], - }, - c: { - edges: ["d"], - }, - d: {}, - }, - }); - - expect(upcomingCardIds()).toEqual(["c", "b"]); -}); - -describe("error handling", () => { - test("cannot record id that doesn't exist", () => { - setState({ - flow: { - _root: { - edges: ["a", "b"], - }, - a: { - type: TYPES.InternalPortal, - edges: ["c"], - }, - b: { - edges: ["d"], - }, - c: { - edges: ["d"], - }, - d: {}, - }, - }); - - expect(() => record("x", {})).toThrow("id not found"); - }); -}); - -test("record(id, undefined) clears up breadcrumbs", () => { - setState({ - flow: { - _root: { - edges: ["a", "b"], - }, - a: { - type: TYPES.Statement, - edges: ["c"], - }, - b: { - type: TYPES.Statement, - }, - c: { - type: TYPES.Response, - edges: ["d"], - }, - d: { - type: TYPES.Statement, - edges: ["e", "f"], - }, - e: { type: TYPES.Response }, - f: { type: TYPES.Response }, - }, - }); - record("a", { answers: ["c"] }); - record("d", { answers: ["e", "f"] }); - expect(getState().breadcrumbs).toEqual({ - a: { answers: ["c"], auto: false }, - d: { answers: ["e", "f"], auto: false }, - }); - - record("a"); - - expect(getState().breadcrumbs).toEqual({}); -}); - -test("hasPaid is updated if a Pay component has been recorded", () => { - setState({ - flow: { - _root: { - edges: ["a", "b"], - }, - a: { - type: TYPES.Statement, - edges: ["c"], - }, - b: { - type: TYPES.Statement, - }, - c: { - type: TYPES.Pay, - }, - - d: { type: TYPES.Response }, - e: { type: TYPES.Review }, - }, - }); - - record("a", { answers: ["c"] }); - expect(hasPaid()).toBe(false); - - record("c", {}); - expect(getState().breadcrumbs).toEqual({ - a: { answers: ["c"], auto: false }, - c: { auto: false }, - }); - - expect(hasPaid()).toBe(true); -}); - -describe("removeOrphansFromBreadcrumbs", () => { - test("Deletes orphans from breadcrumbs when changing answers", () => { - const payload = { - ...mockFlowData, - userData: { - auto: false, - answers: ["4FRZMfNlXf"], - }, - }; - - const actual = removeOrphansFromBreadcrumbs(payload); - - const expected = { - mBFPszBssY: mockBreadcrumbs["mBFPszBssY"], - OjcsvOxVum: mockBreadcrumbs["OjcsvOxVum"], - }; - - expect(actual).toEqual(expected); - }); - - test("Should keep breadcrumbs when tree is not changed", () => { - const payload = { - ...mockFlowData, - userData: { - auto: false, - answers: ["4FRZMfNlXf", "IzT93uCmyF"], - }, - }; - - const actual = removeOrphansFromBreadcrumbs(payload); - - const expected = mockBreadcrumbs; - - expect(actual).toEqual(expected); - }); -}); - describe("removeNodesDependentOnPassport", () => { test("Deletes nodes that are dependent on passport", () => { const breadcrumbs = { ...breadcrumbsDependentOnPassport }; @@ -280,7 +60,7 @@ describe("removeNodesDependentOnPassport", () => { }); }); -describe("record", () => { +describe("nodesDependentOnPassport with record", () => { test("should remove Draw Boundary and Planning contraints from cachedBreadcrumbs", () => { const cachedBreadcrumbs = { ...breadcrumbsDependentOnPassport, @@ -434,7 +214,7 @@ describe("record", () => { }); }); -describe("previousCard", () => { +describe("nodesDependentOnPassport with previousCard", () => { test("To be the card before the current one", () => { const breadcrumbs = { findProperty: breadcrumbsDependentOnPassport.findProperty, @@ -471,7 +251,7 @@ describe("previousCard", () => { }); }); -describe("changeAnswer", () => { +describe("nodesDependentOnPassport with changeAnswer", () => { test("should set state correctly", () => { const breadcrumbs = { text: breadcrumbsDependentOnPassport.text, @@ -518,87 +298,6 @@ describe("changeAnswer", () => { const originalBreadcrumbKeys = Object.keys(breadcrumbsDependentOnPassport); expect(changedBreadcrumbKeys).toStrictEqual(originalBreadcrumbKeys); }); - - test("should auto-answer future nodes with the updated passport variable correctly", () => { - // See https://trello.com/c/B8xMMJLo/1930-changes-from-the-review-page-that-affect-the-fee-do-not-update-the-fee - // and https://editor.planx.uk/testing/autoanswer-change-test - const flow = { ...flowWithAutoAnswers }; - - // Mock initial state as if we've initially answered "Yes" to the question and reached the Review component, about to click "change" - const breadcrumbs = { - rCjETwjwE3: { - auto: false, - answers: ["b0qdvLAxIL"], - }, - vgj2UNYK9r: { - answers: ["X9JjnbPpnd"], - auto: true, - }, - } as Store.breadcrumbs; - const cachedBreadcrumbs = {} as Store.cachedBreadcrumbs; - - setState({ - flow, - breadcrumbs, - cachedBreadcrumbs, - }); - - // Assert our initial passport state is correct - expect(computePassport()).toEqual({ - data: { - "application.fee.exemption.disability": ["true"], - }, - }); - - // Change the question answer from "Yes" to "No" - changeAnswer("rCjETwjwE3"); - record("rCjETwjwE3", { - answers: ["ykNZocRJtQ"], - auto: false, - }); - - expect(getState().changedNode).toEqual("rCjETwjwE3"); - - // Confirm the passport has updated to reflect new answer and has not retained previous answer - expect(computePassport()).toEqual({ - data: { - "application.fee.exemption.disability": ["false"], - }, - }); - - const originalAnswer = { - vgj2UNYK9r: { - answers: ["X9JjnbPpnd"], - auto: true, - }, - } as Store.cachedBreadcrumbs; - - // Confirm that our original answer is still preserved in cachedBreadcrumbs, but not included in current breadcrumbs - expect(getState().breadcrumbs).not.toContain(originalAnswer); - expect(getState().cachedBreadcrumbs).toStrictEqual(originalAnswer); - }); -}); - -describe("resetPreview", () => { - test("should reset preview state correctly for a local storage session", async () => { - setState({ - sessionId: "123", - }); - - resetPreview(); - expect(getState().sessionId).toBe(""); - }); - - test("should reset preview state correctly for a save & return session", async () => { - setState({ - sessionId: "123", - saveToEmail: "test@council.gov.uk", - }); - - resetPreview(); - expect(getState().sessionId).toBe(""); - expect(getState().saveToEmail).toBe(""); - }); }); const mockBreadcrumbs = { diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/removeOrphansFromBreadcrumbs.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/removeOrphansFromBreadcrumbs.test.ts new file mode 100644 index 0000000000..1c9ca91f93 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/removeOrphansFromBreadcrumbs.test.ts @@ -0,0 +1,158 @@ +import { removeOrphansFromBreadcrumbs } from "../../store/preview"; + +describe("removeOrphansFromBreadcrumbs", () => { + test("Deletes orphans from breadcrumbs when changing answers", () => { + const payload = { + ...mockFlowData, + userData: { + auto: false, + answers: ["4FRZMfNlXf"], + }, + }; + + const actual = removeOrphansFromBreadcrumbs(payload); + + const expected = { + mBFPszBssY: mockBreadcrumbs["mBFPszBssY"], + OjcsvOxVum: mockBreadcrumbs["OjcsvOxVum"], + }; + + expect(actual).toEqual(expected); + }); + + test("Should keep breadcrumbs when tree is not changed", () => { + const payload = { + ...mockFlowData, + userData: { + auto: false, + answers: ["4FRZMfNlXf", "IzT93uCmyF"], + }, + }; + + const actual = removeOrphansFromBreadcrumbs(payload); + + const expected = mockBreadcrumbs; + + expect(actual).toEqual(expected); + }); +}); + +const mockBreadcrumbs = { + mBFPszBssY: { + auto: false, + answers: ["IzT93uCmyF", "4FRZMfNlXf"], + }, + "1eJjMmhGBU": { + auto: false, + answers: ["GxcDrNTW26"], + }, + J5SvQgzuK0: { + auto: false, + answers: ["DTXNs02JmU"], + }, + AHOdMRaRGK: { + auto: false, + data: { + AHOdMRaRGK: "Answer", + }, + }, + OjcsvOxVum: { + auto: false, + data: { + OjcsvOxVum: "Test", + }, + }, +}; + +const mockFlowData = { + id: "mBFPszBssY", + flow: { + _root: { + edges: ["mBFPszBssY", "fCg1EeibAD"], + }, + "1eJjMmhGBU": { + data: { + text: "Question", + }, + type: 100, + edges: ["Om0CWNHoDs", "GxcDrNTW26"], + }, + "4FRZMfNlXf": { + data: { + flag: "PP-NOT_DEVELOPMENT", + text: "Not development", + }, + type: 200, + edges: ["OjcsvOxVum"], + }, + AHOdMRaRGK: { + data: { + title: "Question text", + }, + type: 110, + }, + DTXNs02JmU: { + data: { + text: "A2", + }, + type: 200, + edges: ["AHOdMRaRGK"], + }, + GM8yVE4Fgm: { + data: { + title: "Prior approval ", + }, + type: 110, + }, + GxcDrNTW26: { + data: { + text: "path2", + }, + type: 200, + edges: ["J5SvQgzuK0"], + }, + IzT93uCmyF: { + data: { + flag: "PRIOR_APPROVAL", + text: "Prior", + }, + type: 200, + edges: ["1eJjMmhGBU"], + }, + J5SvQgzuK0: { + data: { + text: "Question 2", + }, + type: 100, + edges: ["fSN4QxmM2w", "DTXNs02JmU"], + }, + OjcsvOxVum: { + data: { + title: "Non development text", + }, + type: 110, + }, + Om0CWNHoDs: { + data: { + text: "path1", + }, + type: 200, + edges: ["GM8yVE4Fgm"], + }, + fSN4QxmM2w: { + data: { + text: "A1", + }, + type: 200, + }, + mBFPszBssY: { + data: { + text: "Checklist for review", + allRequired: false, + }, + type: 105, + edges: ["IzT93uCmyF", "4FRZMfNlXf"], + }, + }, + breadcrumbs: mockBreadcrumbs, +}; diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/resetPreview.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/resetPreview.test.ts new file mode 100644 index 0000000000..c212c43630 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/resetPreview.test.ts @@ -0,0 +1,27 @@ +import { vanillaStore } from "../../store"; + +const { getState, setState } = vanillaStore; + +const { resetPreview } = getState(); + +describe("resetPreview", () => { + test("should reset preview state correctly for a local storage session", async () => { + setState({ + sessionId: "123", + }); + + resetPreview(); + expect(getState().sessionId).toBe(""); + }); + + test("should reset preview state correctly for a save & return session", async () => { + setState({ + sessionId: "123", + saveToEmail: "test@council.gov.uk", + }); + + resetPreview(); + expect(getState().sessionId).toBe(""); + expect(getState().saveToEmail).toBe(""); + }); +}); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/upcomingCardIds.test.ts b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/upcomingCardIds.test.ts new file mode 100644 index 0000000000..d25dc63df9 --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/lib/__tests__/preview/upcomingCardIds.test.ts @@ -0,0 +1,151 @@ +import { TYPES } from "@planx/components/types"; + +import { Store, vanillaStore } from "../../store"; + +const { getState, setState } = vanillaStore; +const { upcomingCardIds, resetPreview, record, currentCard } = getState(); + +const flow: Store.flow = { + _root: { + edges: ["SetValue", "Content", "AutomatedQuestion"], + }, + ResponseApple: { + data: { + val: "apple", + text: "Apple", + }, + type: TYPES.Response, + }, + ResponsePear: { + data: { + val: "pear", + text: "Pear", + }, + type: TYPES.Response, + }, + SetValue: { + data: { + fn: "fruit", + val: "apple", + }, + type: TYPES.SetValue, + }, + AutomatedQuestion: { + data: { + fn: "fruit", + text: "Which fruit?", + }, + type: TYPES.Statement, + edges: ["ResponseApple", "ResponsePear"], + }, + Content: { + data: { + content: "

Pause

", + }, + type: TYPES.Content, + }, +}; + +beforeEach(() => { + resetPreview(); + setState({ flow }); +}); + +test("Root nodes are immediately queued up", () => { + expect(upcomingCardIds()).toEqual([ + "SetValue", + "Content", + "AutomatedQuestion", + ]); +}); + +test.skip("A node is only auto-answered when it is the first upcomingCardId(), not when its' `fn` is first added to the breadcrumbs/passport", () => { + const visitedNodes = () => Object.keys(getState().breadcrumbs); + + // mimic "Continue" button and properly set visitedNodes() + const clickContinue = () => upcomingCardIds(); + + expect(upcomingCardIds()).toEqual([ + "SetValue", + "Content", + "AutomatedQuestion", + ]); + + // Step forwards through the SetValue + record("SetValue", { data: { fruit: ["apple"] }, auto: true }); + clickContinue(); + + expect(currentCard()?.id).toBe("Content"); + + // "AutomatedQuestion" should still be queued up, not already answered based on SetValue + expect(visitedNodes()).not.toContain("AutomatedQuestion"); + expect(upcomingCardIds()).toContain("AutomatedQuestion"); + + // Step forwards through Content + record("Content", { data: {}, auto: false }); + clickContinue(); + + // "AutomatedQuestion" has now been auto-answered now, end of flow + expect(visitedNodes()).toContain("AutomatedQuestion"); + expect(upcomingCardIds()).toEqual([]); +}); + +test("it lists upcoming cards", () => { + setState({ + flow: { + _root: { + edges: ["a", "b"], + }, + a: { + type: TYPES.Statement, + edges: ["c"], + }, + b: { + type: TYPES.Statement, + }, + c: { + type: TYPES.Response, + edges: ["d"], + }, + d: { + type: TYPES.Statement, + edges: ["e", "f"], + }, + e: { type: TYPES.Response }, + f: { type: TYPES.Response }, + }, + }); + + expect(upcomingCardIds()).toEqual(["a"]); + + record("a", { answers: ["c"] }); + + expect(upcomingCardIds()).toEqual(["d"]); + + record("d", { answers: ["e", "f"] }); + + expect(upcomingCardIds()).toEqual([]); +}); + +test("crawling with portals", () => { + setState({ + flow: { + _root: { + edges: ["a", "b"], + }, + a: { + type: TYPES.InternalPortal, + edges: ["c"], + }, + b: { + edges: ["d"], + }, + c: { + edges: ["d"], + }, + d: {}, + }, + }); + + expect(upcomingCardIds()).toEqual(["c", "b"]); +});