From 07171d0f354ebcb68e4836ed9988abd64ed71a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Wed, 20 Nov 2024 13:29:34 +0000 Subject: [PATCH 01/11] feat: Update model, add optional fee breakdown section in Editor modal --- .../src/@planx/components/Pay/Editor/Editor.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx b/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx index 450c13a4d5..6befd655e0 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx @@ -15,6 +15,7 @@ import { Switch } from "ui/shared/Switch"; import { ICONS } from "../../shared/icons"; import { EditorProps } from "../../shared/types"; import { FeeBreakdownSection } from "./FeeBreakdownSection"; +import { FeeBreakdownSection } from "./FeeBreakdownSection"; import { GovPayMetadataSection } from "./GovPayMetadataSection"; import { InviteToPaySection } from "./InviteToPaySection"; @@ -102,9 +103,9 @@ const Component: React.FC = (props: Props) => { - - - + + + Date: Wed, 27 Nov 2024 09:38:48 +0000 Subject: [PATCH 02/11] chore: Fix imports --- .../src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx b/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx index 9109276931..61b495a398 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Editor/FeeBreakdownSection.tsx @@ -1,5 +1,5 @@ import ReceiptLongIcon from "@mui/icons-material/ReceiptLong"; -import { hasFeatureFlag } from 'lib/featureFlags'; +import { hasFeatureFlag } from "lib/featureFlags"; import React from "react"; import { FeaturePlaceholder } from "ui/editor/FeaturePlaceholder"; import ModalSection from "ui/editor/ModalSection"; From 5b600ed3d72130c1105fd66100f275562162c93e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Fri, 29 Nov 2024 13:40:20 +0000 Subject: [PATCH 03/11] refactor: Restructure type to nest amount object --- .../Pay/Public/FeeBreakdown/FeeBreakdown.tsx | 10 ++-- .../Pay/Public/FeeBreakdown/types.ts | 20 ++++---- .../FeeBreakdown/useFeeBreakdown.test.ts | 20 ++++---- .../Public/FeeBreakdown/useFeeBreakdown.tsx | 7 ++- .../Pay/Public/FeeBreakdown/utils.test.ts | 46 +++++++++++-------- .../Pay/Public/FeeBreakdown/utils.ts | 36 ++++++++++----- 6 files changed, 87 insertions(+), 52 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx index 8298e553c6..42ba5e7a1b 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx @@ -90,6 +90,8 @@ export const FeeBreakdown: React.FC = () => { const breakdown = useFeeBreakdown(); if (!breakdown) return null; + const { amount, exemptions: _exemptions, reductions: _reductions } = breakdown; + return ( @@ -102,10 +104,10 @@ export const FeeBreakdown: React.FC = () => {
- - - - + + + + diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/types.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/types.ts index e0967d4bc3..479ed2d72e 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/types.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/types.ts @@ -1,12 +1,16 @@ export interface FeeBreakdown { - applicationFee: number; - total: number; - reduction: number; - vat: number | undefined; + amount: { + applicationFee: number; + total: number; + reduction: number; + vat: number | undefined; + }; } export interface PassportFeeFields { - "application.fee.calculated": number; - "application.fee.payable": number; - "application.fee.payable.vat": number; -} + amount: { + "application.fee.calculated": number; + "application.fee.payable": number; + "application.fee.payable.vat": number; + }, +}; \ No newline at end of file diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts index 4e4c042d5a..8ab1c2ff77 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts @@ -28,10 +28,12 @@ describe("useFeeBreakdown() hook", () => { const result = useFeeBreakdown(); expect(result).toEqual({ - applicationFee: 1000, - total: 800, - reduction: 200, - vat: 160, + amount: { + applicationFee: 1000, + total: 800, + reduction: 200, + vat: 160, + }, }); }); @@ -47,10 +49,12 @@ describe("useFeeBreakdown() hook", () => { const result = useFeeBreakdown(); expect(result).toEqual({ - applicationFee: 1000, - total: 800, - reduction: 200, - vat: 160, + amount: { + applicationFee: 1000, + total: 800, + reduction: 200, + vat: 160, + } }); }); }); diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.tsx index 216ce36f53..8ec4eb3aa3 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.tsx @@ -2,7 +2,7 @@ import { logger } from "airbrake"; import { useStore } from "pages/FlowEditor/lib/store"; import { FeeBreakdown } from "./types"; -import { createPassportSchema } from "./utils"; +import { createPassportSchema, preProcessPassport } from "./utils"; /** * Parses the users's Passport for data variables associated with their fee @@ -16,8 +16,11 @@ export const useFeeBreakdown = (): FeeBreakdown | undefined => { state.computePassport().data, state.sessionId, ]); + if (!passportData) return + const schema = createPassportSchema(); - const result = schema.safeParse(passportData); + const processedPassport = preProcessPassport(passportData); + const result = schema.safeParse(processedPassport); if (!result.success) { logger.notify( diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.test.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.test.ts index 25e52596b9..20277c610b 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.test.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.test.ts @@ -20,9 +20,11 @@ describe("toNumber() helper function", () => { describe("calculateReduction() helper function", () => { it("correctly outputs the reduction when a calculated value is provided", () => { const input: PassportFeeFields = { - "application.fee.calculated": 100, - "application.fee.payable": 50, - "application.fee.payable.vat": 0, + amount: { + "application.fee.calculated": 100, + "application.fee.payable": 50, + "application.fee.payable.vat": 0, + } }; const reduction = calculateReduction(input); @@ -31,9 +33,11 @@ describe("calculateReduction() helper function", () => { it("defaults to 0 when calculated is 0", () => { const input: PassportFeeFields = { - "application.fee.calculated": 0, - "application.fee.payable": 100, - "application.fee.payable.vat": 0, + amount: { + "application.fee.calculated": 0, + "application.fee.payable": 100, + "application.fee.payable.vat": 0, + } }; const reduction = calculateReduction(input); @@ -44,28 +48,32 @@ describe("calculateReduction() helper function", () => { describe("toFeeBreakdown() helper function", () => { it("correctly maps fields", () => { const input: PassportFeeFields = { - "application.fee.calculated": 100, - "application.fee.payable": 50, - "application.fee.payable.vat": 10, + amount: { + "application.fee.calculated": 100, + "application.fee.payable": 50, + "application.fee.payable.vat": 10, + } }; - const output = toFeeBreakdown(input); + const { amount } = toFeeBreakdown(input); - expect(output.applicationFee).toEqual(input["application.fee.calculated"]); - expect(output.total).toEqual(input["application.fee.payable"]); - expect(output.vat).toEqual(input["application.fee.payable.vat"]); - expect(output.reduction).toEqual(50); + expect(amount.applicationFee).toEqual(input.amount["application.fee.calculated"]); + expect(amount.total).toEqual(input.amount["application.fee.payable"]); + expect(amount.vat).toEqual(input.amount["application.fee.payable.vat"]); + expect(amount.reduction).toEqual(50); }); it("sets applicationFee to payable amount if no calculated value is provided", () => { const input: PassportFeeFields = { - "application.fee.calculated": 0, - "application.fee.payable": 50, - "application.fee.payable.vat": 10, + amount : { + "application.fee.calculated": 0, + "application.fee.payable.vat": 10, + "application.fee.payable": 50, + } }; - const output = toFeeBreakdown(input); + const { amount } = toFeeBreakdown(input); - expect(output.applicationFee).toEqual(input["application.fee.payable"]); + expect(amount.applicationFee).toEqual(input.amount["application.fee.payable"]); }); }); diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts index 6afa520dfc..8c4924d8d8 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts @@ -9,20 +9,29 @@ export const toNumber = (input: number | [number]) => * A "reduction" is the sum of the difference between calculated and payable * This is not currently broken down further into component parts, or as exemptions or reductions */ -export const calculateReduction = (data: PassportFeeFields) => - data["application.fee.calculated"] - ? data["application.fee.calculated"] - data["application.fee.payable"] +export const calculateReduction = ({ amount }: PassportFeeFields) => + amount["application.fee.calculated"] + ? amount["application.fee.calculated"] - amount["application.fee.payable"] : 0; /** * Transform Passport data to a FeeBreakdown shape */ export const toFeeBreakdown = (data: PassportFeeFields): FeeBreakdown => ({ - applicationFee: - data["application.fee.calculated"] || data["application.fee.payable"], - total: data["application.fee.payable"], - vat: data["application.fee.payable.vat"], - reduction: calculateReduction(data), + amount: { + applicationFee: + data.amount["application.fee.calculated"] || data.amount["application.fee.payable"], + total: data.amount["application.fee.payable"], + vat: data.amount["application.fee.payable.vat"], + reduction: calculateReduction(data), + } +}); + +const filterByKey = (data: Record, key: string) => + Object.fromEntries(Object.entries(data).filter(([k, _v]) => k.startsWith(key))) + +export const preProcessPassport = (data: Record) => ({ + amount: filterByKey(data, "application.fee"), }); export const createPassportSchema = () => { @@ -32,13 +41,18 @@ export const createPassportSchema = () => { .union([questionSchema, setValueSchema]) .transform(toNumber); - const schema = z + const amountsSchema = z .object({ "application.fee.calculated": feeSchema.optional().default(0), "application.fee.payable": feeSchema, "application.fee.payable.vat": feeSchema.optional().default(0), - }) - .transform(toFeeBreakdown); + }); + + const schema = z + .object({ + amount: amountsSchema, + + }).transform(toFeeBreakdown); return schema; }; From 36c5d07440d09a88ca5a1b877389d57e52f75076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Fri, 29 Nov 2024 14:32:32 +0000 Subject: [PATCH 04/11] feat: Add reductions and exemptions --- .../Pay/Public/FeeBreakdown/types.ts | 4 ++ .../Pay/Public/FeeBreakdown/utils.ts | 37 +++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/types.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/types.ts index 479ed2d72e..f8c85944bb 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/types.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/types.ts @@ -5,6 +5,8 @@ export interface FeeBreakdown { reduction: number; vat: number | undefined; }; + reductions: string[]; + exemptions: string[]; } export interface PassportFeeFields { @@ -13,4 +15,6 @@ export interface PassportFeeFields { "application.fee.payable": number; "application.fee.payable.vat": number; }, + reductions?: Record + exemptions?: Record }; \ No newline at end of file diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts index 8c4924d8d8..066e178c14 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts @@ -2,6 +2,9 @@ import { z } from "zod"; import { FeeBreakdown, PassportFeeFields } from "./types"; +const reductionKeys = /^application\.fee\.reduction\..+$/; +const exemptionKeys = /^application\.fee\.exemption\..+$/; + export const toNumber = (input: number | [number]) => Array.isArray(input) ? input[0] : input; @@ -20,11 +23,18 @@ export const calculateReduction = ({ amount }: PassportFeeFields) => export const toFeeBreakdown = (data: PassportFeeFields): FeeBreakdown => ({ amount: { applicationFee: - data.amount["application.fee.calculated"] || data.amount["application.fee.payable"], + data.amount["application.fee.calculated"] || + data.amount["application.fee.payable"], total: data.amount["application.fee.payable"], vat: data.amount["application.fee.payable.vat"], reduction: calculateReduction(data), - } + }, + reductions: Object.entries(data.reductions || {}) + .filter(([_key, value]) => value) + .map(([key, _value]) => key.replace("application.fee.reduction.", "")), + exemptions: Object.entries(data.exemptions || {}) + .filter(([_key, value]) => value) + .map(([key, _value]) => key.replace("application.fee.exemption.", "")), }); const filterByKey = (data: Record, key: string) => @@ -32,6 +42,8 @@ const filterByKey = (data: Record, key: string) => export const preProcessPassport = (data: Record) => ({ amount: filterByKey(data, "application.fee"), + reductions: filterByKey(data, "application.fee.reduction"), + exemptions: filterByKey(data, "application.fee.exemption"), }); export const createPassportSchema = () => { @@ -48,11 +60,28 @@ export const createPassportSchema = () => { "application.fee.payable.vat": feeSchema.optional().default(0), }); + const reductionsSchema = z.record( + z.string().regex(reductionKeys), + z.array(z.string()) + .max(1) + .transform((val) => val[0].toLowerCase() === "true"), + ).optional(); + + const exemptionsSchema = z + .record( + z.string().regex(exemptionKeys), + z.array(z.string()) + .max(1) + .transform((val) => val[0].toLowerCase() === "true") + ) + .optional(); + const schema = z .object({ amount: amountsSchema, - + reductions: reductionsSchema, + exemptions: exemptionsSchema, }).transform(toFeeBreakdown); return schema; -}; +}; \ No newline at end of file From 935883ee5cd47587e20022e9ba2c810d8f4e6256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Fri, 29 Nov 2024 16:37:01 +0000 Subject: [PATCH 05/11] test: useFeeBreakdown() --- .../FeeBreakdown/useFeeBreakdown.test.ts | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts index 8ab1c2ff77..47434fc94e 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts @@ -34,6 +34,8 @@ describe("useFeeBreakdown() hook", () => { reduction: 200, vat: 160, }, + exemptions: [], + reductions: [], }); }); @@ -54,9 +56,81 @@ describe("useFeeBreakdown() hook", () => { total: 800, reduction: 200, vat: 160, - } + }, + exemptions: [], + reductions: [], }); }); + + it("parses 'true' reduction values to a list of keys", () => { + const mockPassportData = { + "application.fee.calculated": 1000, + "application.fee.payable": 800, + "application.fee.payable.vat": 160, + "application.fee.reduction.reasonOne": ["true"], + "application.fee.reduction.reasonTwo": ["true"], + }; + + vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); + + const result = useFeeBreakdown(); + + expect(result?.reductions).toHaveLength(2); + expect(result?.reductions).toEqual( + expect.arrayContaining(["reasonOne", "reasonTwo"]) + ); + }); + + it("does not parse 'false' reduction values to a list of keys", () => { + const mockPassportData = { + "application.fee.calculated": 1000, + "application.fee.payable": 800, + "application.fee.payable.vat": 160, + "application.fee.reduction.reasonOne": ["false"], + "application.fee.reduction.reasonTwo": ["false"], + }; + + vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); + + const result = useFeeBreakdown(); + + expect(result?.reductions).toHaveLength(0); + }); + + it("parses 'true' exemption values to a list of keys", () => { + const mockPassportData = { + "application.fee.calculated": 1000, + "application.fee.payable": 800, + "application.fee.payable.vat": 160, + "application.fee.exemption.reasonOne": ["true"], + "application.fee.exemption.reasonTwo": ["true"], + }; + + vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); + + const result = useFeeBreakdown(); + + expect(result?.exemptions).toHaveLength(2); + expect(result?.exemptions).toEqual( + expect.arrayContaining(["reasonOne", "reasonTwo"]) + ); + }); + + it("does not parse 'false' exemption values to a list of keys", () => { + const mockPassportData = { + "application.fee.calculated": 1000, + "application.fee.payable": 800, + "application.fee.payable.vat": 160, + "application.fee.exemption.reasonOne": ["false"], + "application.fee.exemption.reasonTwo": ["false"], + }; + + vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); + + const result = useFeeBreakdown(); + + expect(result?.exemptions).toHaveLength(0); + }); }); describe("invalid inputs", () => { From d5e49915bdd2b62cb52d0fac57e04e1970e35e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Fri, 29 Nov 2024 17:35:19 +0000 Subject: [PATCH 06/11] refactor: Tidy up --- .../Pay/Public/FeeBreakdown/utils.ts | 79 +++++++++++-------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts index 066e178c14..13585428d6 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts @@ -2,15 +2,33 @@ import { z } from "zod"; import { FeeBreakdown, PassportFeeFields } from "./types"; -const reductionKeys = /^application\.fee\.reduction\..+$/; -const exemptionKeys = /^application\.fee\.exemption\..+$/; - export const toNumber = (input: number | [number]) => Array.isArray(input) ? input[0] : input; +/** + * Convert a Passport value to an actual boolean + */ +const toBoolean = (val: ["true" | "false"]) => val[0] === "true"; + +const filterByKeyPrefix = (data: Record, prefix: string) => + Object.fromEntries( + Object.entries(data).filter(([k, _v]) => k.startsWith(prefix)) + ); + +/** + * Iterate over exemptions or reductions to find matches, returning the granular keys + */ + const getGranularKeys = ( + data: Record | undefined = {}, + prefix: "application.fee.reduction" | "application.fee.exemption" +) => { + const keys = Object.keys(data).filter((key) => data[key]); + const granularKeys = keys.map((key) => key.replace(prefix + ".", "")); + return granularKeys; +}; + /** * A "reduction" is the sum of the difference between calculated and payable - * This is not currently broken down further into component parts, or as exemptions or reductions */ export const calculateReduction = ({ amount }: PassportFeeFields) => amount["application.fee.calculated"] @@ -18,7 +36,7 @@ export const calculateReduction = ({ amount }: PassportFeeFields) => : 0; /** - * Transform Passport data to a FeeBreakdown shape + * Transform Passport data to a FeeBreakdown */ export const toFeeBreakdown = (data: PassportFeeFields): FeeBreakdown => ({ amount: { @@ -29,21 +47,14 @@ export const toFeeBreakdown = (data: PassportFeeFields): FeeBreakdown => ({ vat: data.amount["application.fee.payable.vat"], reduction: calculateReduction(data), }, - reductions: Object.entries(data.reductions || {}) - .filter(([_key, value]) => value) - .map(([key, _value]) => key.replace("application.fee.reduction.", "")), - exemptions: Object.entries(data.exemptions || {}) - .filter(([_key, value]) => value) - .map(([key, _value]) => key.replace("application.fee.exemption.", "")), + reductions: getGranularKeys(data.reductions, "application.fee.reduction"), + exemptions: getGranularKeys(data.exemptions, "application.fee.exemption"), }); -const filterByKey = (data: Record, key: string) => - Object.fromEntries(Object.entries(data).filter(([k, _v]) => k.startsWith(key))) - export const preProcessPassport = (data: Record) => ({ - amount: filterByKey(data, "application.fee"), - reductions: filterByKey(data, "application.fee.reduction"), - exemptions: filterByKey(data, "application.fee.exemption"), + amount: filterByKeyPrefix(data, "application.fee"), + reductions: filterByKeyPrefix(data, "application.fee.reduction"), + exemptions: filterByKeyPrefix(data, "application.fee.exemption"), }); export const createPassportSchema = () => { @@ -53,26 +64,23 @@ export const createPassportSchema = () => { .union([questionSchema, setValueSchema]) .transform(toNumber); - const amountsSchema = z - .object({ - "application.fee.calculated": feeSchema.optional().default(0), - "application.fee.payable": feeSchema, - "application.fee.payable.vat": feeSchema.optional().default(0), - }); + const amountsSchema = z.object({ + "application.fee.calculated": feeSchema.optional().default(0), + "application.fee.payable": feeSchema, + "application.fee.payable.vat": feeSchema.optional().default(0), + }); - const reductionsSchema = z.record( - z.string().regex(reductionKeys), - z.array(z.string()) - .max(1) - .transform((val) => val[0].toLowerCase() === "true"), - ).optional(); + const reductionsSchema = z + .record( + z.string(), + z.tuple([z.enum(["true", "false"])]).transform(toBoolean) + ) + .optional(); const exemptionsSchema = z .record( - z.string().regex(exemptionKeys), - z.array(z.string()) - .max(1) - .transform((val) => val[0].toLowerCase() === "true") + z.string(), + z.tuple([z.enum(["true", "false"])]).transform(toBoolean) ) .optional(); @@ -81,7 +89,8 @@ export const createPassportSchema = () => { amount: amountsSchema, reductions: reductionsSchema, exemptions: exemptionsSchema, - }).transform(toFeeBreakdown); + }) + .transform(toFeeBreakdown); return schema; -}; \ No newline at end of file +}; From 27487a01ffbcf8317bfde171601be15b4e8dc47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Fri, 29 Nov 2024 17:36:33 +0000 Subject: [PATCH 07/11] feat: Basic UI --- .../Pay/Public/FeeBreakdown/FeeBreakdown.tsx | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx index 42ba5e7a1b..2fcccf850b 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx @@ -49,16 +49,52 @@ const ApplicationFee: React.FC<{ amount: number }> = ({ amount }) => ( ); -const Reductions: React.FC<{ amount?: number }> = ({ amount }) => { +const Reductions: React.FC<{ amount?: number, reductions: string[] }> = ({ amount, reductions }) => { if (!amount) return null; return ( + <> Reductions {formattedPriceWithCurrencySymbol(-amount)} + { + reductions.map((reduction) => ( + + + {reduction} + + + )) + } + + ); +}; + +// TODO: This won't show as if a fee is 0, we hide the whole Pay component from the user +const Exemptions: React.FC<{ amount: number, exemptions: string[] }> = ({ amount, exemptions }) => { + if (!exemptions.length) return null; + + return ( + <> + + Exemptions + + {formattedPriceWithCurrencySymbol(-amount)} + + + { + exemptions.map((exemption) => ( + + + {exemption} + + + )) + } + ); }; @@ -90,7 +126,7 @@ export const FeeBreakdown: React.FC = () => { const breakdown = useFeeBreakdown(); if (!breakdown) return null; - const { amount, exemptions: _exemptions, reductions: _reductions } = breakdown; + const { amount, reductions, exemptions } = breakdown; return ( @@ -105,7 +141,8 @@ export const FeeBreakdown: React.FC = () => {
- + + From 33f5a4dafc35352c8b7ce46099625616f25f6953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Sun, 1 Dec 2024 13:42:02 +0000 Subject: [PATCH 08/11] test: Ensure a realistic mock passport is used --- .../Public/FeeBreakdown/useFeeBreakdown.test.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts index 47434fc94e..b0282477b1 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts @@ -21,6 +21,7 @@ describe("useFeeBreakdown() hook", () => { "application.fee.calculated": 1000, "application.fee.payable": 800, "application.fee.payable.vat": 160, + "some.other.fields": ["abc", "xyz"], }; vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); @@ -44,6 +45,7 @@ describe("useFeeBreakdown() hook", () => { "application.fee.calculated": [1000], "application.fee.payable": [800], "application.fee.payable.vat": [160], + "some.other.fields": ["abc", "xyz"], }; vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); @@ -69,6 +71,7 @@ describe("useFeeBreakdown() hook", () => { "application.fee.payable.vat": 160, "application.fee.reduction.reasonOne": ["true"], "application.fee.reduction.reasonTwo": ["true"], + "some.other.fields": ["abc", "xyz"], }; vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); @@ -88,6 +91,7 @@ describe("useFeeBreakdown() hook", () => { "application.fee.payable.vat": 160, "application.fee.reduction.reasonOne": ["false"], "application.fee.reduction.reasonTwo": ["false"], + "some.other.fields": ["abc", "xyz"], }; vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); @@ -104,6 +108,7 @@ describe("useFeeBreakdown() hook", () => { "application.fee.payable.vat": 160, "application.fee.exemption.reasonOne": ["true"], "application.fee.exemption.reasonTwo": ["true"], + "some.other.fields": ["abc", "xyz"], }; vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); @@ -123,6 +128,7 @@ describe("useFeeBreakdown() hook", () => { "application.fee.payable.vat": 160, "application.fee.exemption.reasonOne": ["false"], "application.fee.exemption.reasonTwo": ["false"], + "some.other.fields": ["abc", "xyz"], }; vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); @@ -135,7 +141,9 @@ describe("useFeeBreakdown() hook", () => { describe("invalid inputs", () => { it("returns undefined for missing data", () => { - const mockPassportData = {}; + const mockPassportData = { + "some.other.fields": ["abc", "xyz"], + }; vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); @@ -148,6 +156,7 @@ describe("useFeeBreakdown() hook", () => { const mockPassportData = { "application.fee.calculated": [1000], "application.fee.payable.vat": [160], + "some.other.fields": ["abc", "xyz"], }; vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); @@ -162,6 +171,7 @@ describe("useFeeBreakdown() hook", () => { "application.fee.calculated": "some string", "application.fee.payable": [800, 700], "application.fee.payable.vat": false, + "some.other.fields": ["abc", "xyz"], }; vi.mocked(useStore).mockReturnValue([mockPassportData, "test-session"]); @@ -172,7 +182,9 @@ describe("useFeeBreakdown() hook", () => { }); it("calls Airbrake if invalid inputs are provided", () => { - const mockPassportData = {}; + const mockPassportData = { + "some.other.fields": ["abc", "xyz"], + }; const mockSessionId = "test-session"; vi.mocked(useStore).mockReturnValue([mockPassportData, mockSessionId]); From 84338ad90fdd60b7b9ea78e21b1b7b2d619a8208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Wed, 4 Dec 2024 16:08:35 +0000 Subject: [PATCH 09/11] refactor: Use discrete list of passport variables only --- .../Pay/Public/FeeBreakdown/types.ts | 15 ++-- .../FeeBreakdown/useFeeBreakdown.test.ts | 70 +++++++++++++--- .../Public/FeeBreakdown/useFeeBreakdown.tsx | 5 +- .../Pay/Public/FeeBreakdown/utils.test.ts | 60 ++++++++------ .../Pay/Public/FeeBreakdown/utils.ts | 83 ++++++++----------- 5 files changed, 141 insertions(+), 92 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/types.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/types.ts index f8c85944bb..176cf87724 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/types.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/types.ts @@ -10,11 +10,12 @@ export interface FeeBreakdown { } export interface PassportFeeFields { - amount: { - "application.fee.calculated": number; - "application.fee.payable": number; - "application.fee.payable.vat": number; - }, - reductions?: Record - exemptions?: Record + "application.fee.calculated": number; + "application.fee.payable": number; + "application.fee.payable.vat": number; + "application.fee.reduction.alternative": boolean; + "application.fee.reduction.parishCouncil": boolean; + "application.fee.reduction.sports": boolean; + "application.fee.exemption.disability": boolean; + "application.fee.exemption.resubmission": boolean; }; \ No newline at end of file diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts index b0282477b1..6f4158028c 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts @@ -69,8 +69,8 @@ describe("useFeeBreakdown() hook", () => { "application.fee.calculated": 1000, "application.fee.payable": 800, "application.fee.payable.vat": 160, - "application.fee.reduction.reasonOne": ["true"], - "application.fee.reduction.reasonTwo": ["true"], + "application.fee.reduction.alternative": ["true"], + "application.fee.reduction.parishCouncil": ["true"], "some.other.fields": ["abc", "xyz"], }; @@ -80,7 +80,7 @@ describe("useFeeBreakdown() hook", () => { expect(result?.reductions).toHaveLength(2); expect(result?.reductions).toEqual( - expect.arrayContaining(["reasonOne", "reasonTwo"]) + expect.arrayContaining(["alternative", "parishCouncil"]) ); }); @@ -89,8 +89,8 @@ describe("useFeeBreakdown() hook", () => { "application.fee.calculated": 1000, "application.fee.payable": 800, "application.fee.payable.vat": 160, - "application.fee.reduction.reasonOne": ["false"], - "application.fee.reduction.reasonTwo": ["false"], + "application.fee.reduction.alternative": ["false"], + "application.fee.reduction.parishCouncil": ["false"], "some.other.fields": ["abc", "xyz"], }; @@ -101,13 +101,36 @@ describe("useFeeBreakdown() hook", () => { expect(result?.reductions).toHaveLength(0); }); + it("does not parse non-schema reduction values", () => { + const mockPassportData = { + "application.fee.calculated": 1000, + "application.fee.payable": 800, + "application.fee.payable.vat": 160, + "application.fee.reduction.alternative": ["true"], + "application.fee.reduction.parishCouncil": ["false"], + "application.fee.reduction.someReason": ["true"], + "application.fee.reduction.someOtherReason": ["false"], + "some.other.fields": ["abc", "xyz"], + }; + + vi.mocked(useStore).mockReturnValue([ + mockPassportData, + "test-session", + ]); + + const result = useFeeBreakdown(); + + expect(result?.reductions).toEqual(expect.not.arrayContaining(["someReason"])) + expect(result?.reductions).toEqual(expect.not.arrayContaining(["someOtherReason"])) + }); + it("parses 'true' exemption values to a list of keys", () => { const mockPassportData = { "application.fee.calculated": 1000, "application.fee.payable": 800, "application.fee.payable.vat": 160, - "application.fee.exemption.reasonOne": ["true"], - "application.fee.exemption.reasonTwo": ["true"], + "application.fee.exemption.disability": ["true"], + "application.fee.exemption.resubmission": ["true"], "some.other.fields": ["abc", "xyz"], }; @@ -117,7 +140,7 @@ describe("useFeeBreakdown() hook", () => { expect(result?.exemptions).toHaveLength(2); expect(result?.exemptions).toEqual( - expect.arrayContaining(["reasonOne", "reasonTwo"]) + expect.arrayContaining(["disability", "resubmission"]) ); }); @@ -126,8 +149,8 @@ describe("useFeeBreakdown() hook", () => { "application.fee.calculated": 1000, "application.fee.payable": 800, "application.fee.payable.vat": 160, - "application.fee.exemption.reasonOne": ["false"], - "application.fee.exemption.reasonTwo": ["false"], + "application.fee.exemption.disability": ["false"], + "application.fee.exemption.resubmission": ["false"], "some.other.fields": ["abc", "xyz"], }; @@ -137,6 +160,33 @@ describe("useFeeBreakdown() hook", () => { expect(result?.exemptions).toHaveLength(0); }); + + it("does not parse non-schema exemption values", () => { + const mockPassportData = { + "application.fee.calculated": 1000, + "application.fee.payable": 800, + "application.fee.payable.vat": 160, + "application.fee.exemption.disability": ["false"], + "application.fee.exemption.resubmission": ["false"], + "application.fee.exemption.someReason": ["true"], + "application.fee.exemption.someOtherReason": ["false"], + "some.other.fields": ["abc", "xyz"], + }; + + vi.mocked(useStore).mockReturnValue([ + mockPassportData, + "test-session", + ]); + + const result = useFeeBreakdown(); + + expect(result?.exemption).toEqual( + expect.not.arrayContaining(["someReason"]) + ); + expect(result?.exemption).toEqual( + expect.not.arrayContaining(["someOtherReason"]) + ); + }); }); describe("invalid inputs", () => { diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.tsx index 8ec4eb3aa3..b1e6b10c1d 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.tsx @@ -2,7 +2,7 @@ import { logger } from "airbrake"; import { useStore } from "pages/FlowEditor/lib/store"; import { FeeBreakdown } from "./types"; -import { createPassportSchema, preProcessPassport } from "./utils"; +import { createPassportSchema } from "./utils"; /** * Parses the users's Passport for data variables associated with their fee @@ -19,8 +19,7 @@ export const useFeeBreakdown = (): FeeBreakdown | undefined => { if (!passportData) return const schema = createPassportSchema(); - const processedPassport = preProcessPassport(passportData); - const result = schema.safeParse(processedPassport); + const result = schema.safeParse(passportData); if (!result.success) { logger.notify( diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.test.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.test.ts index 20277c610b..935c8cb066 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.test.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.test.ts @@ -20,11 +20,14 @@ describe("toNumber() helper function", () => { describe("calculateReduction() helper function", () => { it("correctly outputs the reduction when a calculated value is provided", () => { const input: PassportFeeFields = { - amount: { - "application.fee.calculated": 100, - "application.fee.payable": 50, - "application.fee.payable.vat": 0, - } + "application.fee.calculated": 100, + "application.fee.payable": 50, + "application.fee.payable.vat": 0, + "application.fee.reduction.alternative": false, + "application.fee.reduction.parishCouncil": false, + "application.fee.reduction.sports": false, + "application.fee.exemption.disability": false, + "application.fee.exemption.resubmission": false, }; const reduction = calculateReduction(input); @@ -33,11 +36,14 @@ describe("calculateReduction() helper function", () => { it("defaults to 0 when calculated is 0", () => { const input: PassportFeeFields = { - amount: { - "application.fee.calculated": 0, - "application.fee.payable": 100, - "application.fee.payable.vat": 0, - } + "application.fee.calculated": 0, + "application.fee.payable": 100, + "application.fee.payable.vat": 0, + "application.fee.reduction.alternative": false, + "application.fee.reduction.parishCouncil": false, + "application.fee.reduction.sports": false, + "application.fee.exemption.disability": false, + "application.fee.exemption.resubmission": false, }; const reduction = calculateReduction(input); @@ -48,32 +54,38 @@ describe("calculateReduction() helper function", () => { describe("toFeeBreakdown() helper function", () => { it("correctly maps fields", () => { const input: PassportFeeFields = { - amount: { - "application.fee.calculated": 100, - "application.fee.payable": 50, - "application.fee.payable.vat": 10, - } + "application.fee.calculated": 100, + "application.fee.payable": 50, + "application.fee.payable.vat": 10, + "application.fee.reduction.alternative": false, + "application.fee.reduction.parishCouncil": false, + "application.fee.reduction.sports": false, + "application.fee.exemption.disability": false, + "application.fee.exemption.resubmission": false, }; const { amount } = toFeeBreakdown(input); - expect(amount.applicationFee).toEqual(input.amount["application.fee.calculated"]); - expect(amount.total).toEqual(input.amount["application.fee.payable"]); - expect(amount.vat).toEqual(input.amount["application.fee.payable.vat"]); + expect(amount.applicationFee).toEqual(input["application.fee.calculated"]); + expect(amount.total).toEqual(input["application.fee.payable"]); + expect(amount.vat).toEqual(input["application.fee.payable.vat"]); expect(amount.reduction).toEqual(50); }); it("sets applicationFee to payable amount if no calculated value is provided", () => { const input: PassportFeeFields = { - amount : { - "application.fee.calculated": 0, - "application.fee.payable.vat": 10, - "application.fee.payable": 50, - } + "application.fee.calculated": 0, + "application.fee.payable.vat": 10, + "application.fee.payable": 50, + "application.fee.reduction.alternative": false, + "application.fee.reduction.parishCouncil": false, + "application.fee.reduction.sports": false, + "application.fee.exemption.disability": false, + "application.fee.exemption.resubmission": false, }; const { amount } = toFeeBreakdown(input); - expect(amount.applicationFee).toEqual(input.amount["application.fee.payable"]); + expect(amount.applicationFee).toEqual(input["application.fee.payable"]); }); }); diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts index 13585428d6..759c5438db 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/utils.ts @@ -10,29 +10,31 @@ export const toNumber = (input: number | [number]) => */ const toBoolean = (val: ["true" | "false"]) => val[0] === "true"; -const filterByKeyPrefix = (data: Record, prefix: string) => - Object.fromEntries( - Object.entries(data).filter(([k, _v]) => k.startsWith(prefix)) - ); /** * Iterate over exemptions or reductions to find matches, returning the granular keys */ const getGranularKeys = ( - data: Record | undefined = {}, - prefix: "application.fee.reduction" | "application.fee.exemption" -) => { - const keys = Object.keys(data).filter((key) => data[key]); - const granularKeys = keys.map((key) => key.replace(prefix + ".", "")); - return granularKeys; -}; + data: PassportFeeFields, + prefix: "application.fee.reduction" | "application.fee.exemption" + ) => { + const keys = Object.keys(data) as (keyof PassportFeeFields)[]; + const intersectingKeys = keys.filter( + (key) => key.startsWith(prefix) && Boolean(data[key]) + ); + const granularKeys = intersectingKeys.map((key) => + key.replace(prefix + ".", "") + ); + + return granularKeys; + }; /** * A "reduction" is the sum of the difference between calculated and payable */ -export const calculateReduction = ({ amount }: PassportFeeFields) => - amount["application.fee.calculated"] - ? amount["application.fee.calculated"] - amount["application.fee.payable"] +export const calculateReduction = (data: PassportFeeFields) => + data["application.fee.calculated"] + ? data["application.fee.calculated"] - data["application.fee.payable"] : 0; /** @@ -41,20 +43,14 @@ export const calculateReduction = ({ amount }: PassportFeeFields) => export const toFeeBreakdown = (data: PassportFeeFields): FeeBreakdown => ({ amount: { applicationFee: - data.amount["application.fee.calculated"] || - data.amount["application.fee.payable"], - total: data.amount["application.fee.payable"], - vat: data.amount["application.fee.payable.vat"], + data["application.fee.calculated"] || + data["application.fee.payable"], + total: data["application.fee.payable"], + vat: data["application.fee.payable.vat"], reduction: calculateReduction(data), }, - reductions: getGranularKeys(data.reductions, "application.fee.reduction"), - exemptions: getGranularKeys(data.exemptions, "application.fee.exemption"), -}); - -export const preProcessPassport = (data: Record) => ({ - amount: filterByKeyPrefix(data, "application.fee"), - reductions: filterByKeyPrefix(data, "application.fee.reduction"), - exemptions: filterByKeyPrefix(data, "application.fee.exemption"), + reductions: getGranularKeys(data, "application.fee.reduction"), + exemptions: getGranularKeys(data, "application.fee.exemption"), }); export const createPassportSchema = () => { @@ -64,31 +60,22 @@ export const createPassportSchema = () => { .union([questionSchema, setValueSchema]) .transform(toNumber); - const amountsSchema = z.object({ - "application.fee.calculated": feeSchema.optional().default(0), - "application.fee.payable": feeSchema, - "application.fee.payable.vat": feeSchema.optional().default(0), - }); - - const reductionsSchema = z - .record( - z.string(), - z.tuple([z.enum(["true", "false"])]).transform(toBoolean) - ) - .optional(); - - const exemptionsSchema = z - .record( - z.string(), - z.tuple([z.enum(["true", "false"])]).transform(toBoolean) - ) - .optional(); + /** Describes how boolean values are set via PlanX components */ + const booleanSchema = z + .tuple([z.enum(["true", "false"])]) + .default(["false"]) + .transform(toBoolean) const schema = z .object({ - amount: amountsSchema, - reductions: reductionsSchema, - exemptions: exemptionsSchema, + "application.fee.calculated": feeSchema.optional().default(0), + "application.fee.payable": feeSchema, + "application.fee.payable.vat": feeSchema.optional().default(0), + "application.fee.reduction.alternative": booleanSchema, + "application.fee.reduction.parishCouncil": booleanSchema, + "application.fee.reduction.sports": booleanSchema, + "application.fee.exemption.disability": booleanSchema, + "application.fee.exemption.resubmission": booleanSchema, }) .transform(toFeeBreakdown); From 9e5dbea303813a016934eaea26e79c7b36af131d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Thu, 5 Dec 2024 09:08:45 +0000 Subject: [PATCH 10/11] fix: Type fixes after rebase --- editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx | 1 - .../Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx b/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx index 6befd655e0..82f05da942 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Editor/Editor.tsx @@ -15,7 +15,6 @@ import { Switch } from "ui/shared/Switch"; import { ICONS } from "../../shared/icons"; import { EditorProps } from "../../shared/types"; import { FeeBreakdownSection } from "./FeeBreakdownSection"; -import { FeeBreakdownSection } from "./FeeBreakdownSection"; import { GovPayMetadataSection } from "./GovPayMetadataSection"; import { InviteToPaySection } from "./InviteToPaySection"; diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts index 6f4158028c..5312cb12c2 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/useFeeBreakdown.test.ts @@ -180,10 +180,10 @@ describe("useFeeBreakdown() hook", () => { const result = useFeeBreakdown(); - expect(result?.exemption).toEqual( + expect(result?.exemptions).toEqual( expect.not.arrayContaining(["someReason"]) ); - expect(result?.exemption).toEqual( + expect(result?.exemptions).toEqual( expect.not.arrayContaining(["someOtherReason"]) ); }); From c083ade5980516e188277d8371d44d8e6ecb382b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Thu, 5 Dec 2024 12:17:50 +0000 Subject: [PATCH 11/11] fix: Typo in exemptions - should be total post reductions --- .../@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx index 2fcccf850b..5ef73fc59f 100644 --- a/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx +++ b/editor.planx.uk/src/@planx/components/Pay/Public/FeeBreakdown/FeeBreakdown.tsx @@ -142,7 +142,7 @@ export const FeeBreakdown: React.FC = () => { - +