Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: List should record val, not text, in passport and calculate total units by development type when applicable #3263

Merged
merged 4 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions editor.planx.uk/src/@planx/components/List/Public/Context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import {
Schema,
UserData,
} from "../model";
import { flatten } from "../utils";
import {
flatten,
sumIdenticalUnits,
sumIdenticalUnitsByDevelopmentType,
} from "../utils";

interface ListContextValue {
schema: Schema;
Expand Down Expand Up @@ -132,7 +136,7 @@ export const ListProvider: React.FC<ListProviderProps> = (props) => {
userData: getInitialValues(),
},
onSubmit: (values) => {
// defaultPassportData is used when coming "back"
// defaultPassportData (array) is used when coming "back"
const defaultPassportData = makeData(props, values.userData)?.["data"];

// flattenedPassportData makes individual list items compatible with Calculate components
Expand All @@ -141,16 +145,22 @@ export const ListProvider: React.FC<ListProviderProps> = (props) => {
// basic example of general summary stats we can add onSubmit:
// 1. count of items/responses
// 2. if the schema includes a field that sets fn = "identicalUnits", sum of total units
let sumIdenticalUnits = 0;
defaultPassportData[`${props.fn}`].map(
(item) => (sumIdenticalUnits += parseInt(item?.identicalUnits)),
// 3. if the schema includes a field that sets fn = "development" & fn = "identicalUnits", sum of total units by development "val"
const totalUnits = sumIdenticalUnits(props.fn, defaultPassportData);
const totalUnitsByDevelopmentType = sumIdenticalUnitsByDevelopmentType(
props.fn,
defaultPassportData,
);

const summaries = {
[`${props.fn}.total.listItems`]:
defaultPassportData[`${props.fn}`].length,
...(sumIdenticalUnits > 0 && {
[`${props.fn}.total.units`]: sumIdenticalUnits,
...(totalUnits > 0 && {
[`${props.fn}.total.units`]: totalUnits,
}),
...(totalUnits > 0 &&
Object.keys(totalUnitsByDevelopmentType).length > 0 &&
totalUnitsByDevelopmentType),
};

handleSubmit?.({
Expand Down
10 changes: 6 additions & 4 deletions editor.planx.uk/src/@planx/components/List/Public/Fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export const RadioFieldInput: React.FC<Props<QuestionField>> = (props) => {
{data.options.map(({ id, data }) => (
<BasicRadio
key={id}
id={data.text}
id={data.val || data.text}
title={data.text}
onChange={formik.handleChange}
/>
Expand All @@ -159,9 +159,11 @@ export const SelectFieldInput: React.FC<Props<QuestionField>> = (props) => {

const existingValues = formik.values.userData
.map((response) => response[data.fn])
.filter((value) => value === option.data.text);
.filter(
(value) => value === option.data.val || value === option.data.text,
);

return existingValues.includes(option.data.text);
return existingValues.includes(option.data.val || option.data.text);
};

return (
Expand All @@ -185,7 +187,7 @@ export const SelectFieldInput: React.FC<Props<QuestionField>> = (props) => {
{data.options.map((option) => (
<MenuItem
key={option.id}
value={option.data.text}
value={option.data.val || option.data.text}
disabled={isDisabled(option)}
>
{option.data.text}
Expand Down
95 changes: 94 additions & 1 deletion editor.planx.uk/src/@planx/components/List/Public/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ import { cloneDeep, merge } from "lodash";
import React from "react";
import { axe, setup } from "testUtils";

import { UserResponse } from "../model";
import ListComponent, { Props } from "../Public";
import { GenericUnitsTest } from "../schemas/GenericUnitsTest";
import { Zoo } from "../schemas/Zoo";
import {
flatten,
sumIdenticalUnits,
sumIdenticalUnitsByDevelopmentType,
} from "../utils";

const mockProps: Props = {
fn: "mockFn",
Expand Down Expand Up @@ -47,6 +54,48 @@ const mockPayload = {
},
};

const mockPropsUnits: Props = {
fn: "proposal.units.residential",
schema: GenericUnitsTest,
schemaName: "Generic residential units",
title: "Describe residential units",
};

const mockPayloadUnits = {
data: {
"proposal.units.residential": [
{
development: "newBuild",
garden: "Yes",
identicalUnits: 1,
},
{
development: "newBuild",
garden: "No",
identicalUnits: 2,
},
{
development: "changeOfUseTo",
garden: "No",
identicalUnits: 2,
},
],
"proposal.units.residential.one.development": "newBuild",
"proposal.units.residential.one.garden": "Yes",
"proposal.units.residential.one.identicalUnits": 1,
"proposal.units.residential.two.development": "newBuild",
"proposal.units.residential.two.garden": "No",
"proposal.units.residential.two.identicalUnits": 2,
"proposal.units.residential.three.development": "changeOfUseTo",
"proposal.units.residential.three.garden": "No",
"proposal.units.residential.three.identicalUnits": 2,
"proposal.units.residential.total.listItems": 3,
"proposal.units.residential.total.units": 5,
"proposal.units.residential.total.units.newBuid": 3,
"proposal.units.residential.total.units.changeOfUseTo": 2,
},
};

jest.setTimeout(20_000);

describe("Basic UI", () => {
Expand Down Expand Up @@ -378,7 +427,7 @@ describe("Form validation and error handling", () => {
});

describe("Payload generation", () => {
it("generates a valid payload on submission", async () => {
it("generates a valid payload on submission (Zoo)", async () => {
const handleSubmit = jest.fn();
const { getByTestId, user } = setup(
<ListComponent {...mockProps} handleSubmit={handleSubmit} />,
Expand All @@ -395,6 +444,50 @@ describe("Payload generation", () => {
expect(handleSubmit).toHaveBeenCalled();
expect(handleSubmit.mock.calls[0][0]).toMatchObject(mockPayload);
});

it.skip("generates a valid payload with summary stats on submission (Units)", async () => {
const handleSubmit = jest.fn();
const { getByTestId, user } = setup(
<ListComponent {...mockPropsUnits} handleSubmit={handleSubmit} />,
);

const saveButton = screen.getByRole("button", { name: /Save/ });
const addItemButton = getByTestId("list-add-button");
const developmentSelect = screen.getByRole("combobox");
const gardenYesRadio = screen.getAllByRole("radio")[0];
const gardenNoRadio = screen.getAllByRole("radio")[1];
const unitsNumberInput = screen.getByLabelText(/identical units/);

// Response 1
await user.click(developmentSelect);
await user.click(screen.getByRole("option", { name: /New build/ }));
await user.click(gardenYesRadio);
await user.type(unitsNumberInput, "1");
await user.click(saveButton);

// Response 2
await user.click(addItemButton);
await user.click(developmentSelect);
await user.click(screen.getByRole("option", { name: /New build/ }));
await user.click(gardenNoRadio);
await user.type(unitsNumberInput, "2");
await user.click(saveButton);

// Response 3
await user.click(addItemButton);
await user.click(developmentSelect);
await user.click(
screen.getByRole("option", { name: /Change of use to a home/ }),
);
await user.click(gardenNoRadio);
await user.type(unitsNumberInput, "2");
await user.click(saveButton);

await user.click(screen.getByTestId("continue-button"));

expect(handleSubmit).toHaveBeenCalled();
expect(handleSubmit.mock.calls[0][0]).toMatchObject(mockPayloadUnits);
});
});

describe("Navigating back", () => {
Expand Down
8 changes: 7 additions & 1 deletion editor.planx.uk/src/@planx/components/List/Public/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import InputRow from "ui/shared/InputRow";
import Card from "../../shared/Preview/Card";
import CardHeader from "../../shared/Preview/CardHeader";
import type { Field, List } from "../model";
import { formatSchemaDisplayValue } from "../utils";
import { ListProvider, useListContext } from "./Context";
import {
NumberFieldInput,
Expand Down Expand Up @@ -111,7 +112,12 @@ const InactiveListCard: React.FC<{
<TableCell sx={{ fontWeight: FONT_WEIGHT_SEMI_BOLD }}>
{field.data.title}
</TableCell>
<TableCell>{formik.values.userData[i][field.data.fn]}</TableCell>
<TableCell>
{formatSchemaDisplayValue(
formik.values.userData[i][field.data.fn],
schema,
)}
</TableCell>
</TableRow>
))}
</TableBody>
Expand Down
4 changes: 2 additions & 2 deletions editor.planx.uk/src/@planx/components/List/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ interface QuestionInput {
*/
const questionInputValidationSchema = (data: QuestionInput) =>
string()
.oneOf(data.options.map((option) => option.data.text))
.oneOf(data.options.map((option) => option.data.val || option.data.text))
.required("Select your answer before continuing");

// TODO: Add summary fields for inactive view?
Expand Down Expand Up @@ -62,7 +62,7 @@ export interface Schema {
max?: number;
}

type UserResponse = Record<Field["data"]["fn"], string>;
export type UserResponse = Record<Field["data"]["fn"], string>;

export type UserData = { userData: UserResponse[] };

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Schema } from "@planx/components/List/model";

export const GenericUnitsTest: Schema = {
type: "Unit",
fields: [
// fn = "development" triggers summary stat and options set "val"
{
type: "question",
data: {
title: "What development does this unit result from?",
fn: "development",
options: [
{ id: "newBuild", data: { text: "New build", val: "newBuild" } },
{
id: "changeOfUseFrom",
data: {
text: "Change of use of existing single home",
val: "changeOfUseFrom",
},
},
{
id: "changeOfUseTo",
data: { text: "Change of use to a home", val: "changeOfUseTo" },
},
],
},
},
// options set "text" only
{
type: "question",
data: {
title: "Is this unit built on garden land?",
fn: "garden",
options: [
{ id: "true", data: { text: "Yes" } },
{ id: "false", data: { text: "No" } },
],
},
},
// fn = "identicalUnits" triggers summary stat
{
type: "number",
data: {
title: "How many identical units does the description above apply to?",
fn: "identicalUnits",
allowNegatives: false,
},
},
],
min: 1,
} as const;
Loading
Loading