Skip to content

Commit

Permalink
feat: auto-answer one at a time, not into the future (#3764)
Browse files Browse the repository at this point in the history
Co-authored-by: Dafydd Llŷr Pearson <[email protected]>
  • Loading branch information
jessicamcinchak and DafyddLlyr authored Oct 29, 2024
1 parent dee6870 commit 02cbaf0
Show file tree
Hide file tree
Showing 31 changed files with 2,562 additions and 2,041 deletions.
24 changes: 10 additions & 14 deletions editor.planx.uk/src/@planx/components/Calculate/logic.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { ComponentType as TYPES } from "@opensystemslab/planx-core/types";
import { clickContinue, visitedNodes } from "pages/FlowEditor/lib/__tests__/utils";
import { Store, useStore } from "pages/FlowEditor/lib/store";

const { getState, setState } = useStore;
const { upcomingCardIds, resetPreview, record } = getState();

// Helper method
const visitedNodes = () => Object.keys(getState().breadcrumbs);
const { upcomingCardIds, resetPreview, autoAnswerableOptions } = getState();

beforeEach(() => {
resetPreview();
Expand All @@ -17,13 +15,12 @@ test("When formatOutputForAutomations is true, Calculate writes an array and fut
expect(upcomingCardIds()).toEqual(["Calculate", "Question"]);

// Step forwards through the Calculate
record("Calculate", { data: { testGroup: ["2"] }, auto: true });
upcomingCardIds();

// The Question has been auto-answered
expect(visitedNodes()).toEqual(["Calculate", "Question"]);
clickContinue("Calculate", { data: { testGroup: ["2"] }, auto: true });

expect(upcomingCardIds()).toEqual(["Group2Notice"]);
// The Question can be auto-answered
expect(visitedNodes()).toEqual(["Calculate"]);
expect(upcomingCardIds()).toEqual(["Question"])
expect(autoAnswerableOptions("Question")).toEqual(["Group2Response"]);
});

test("When formatOutputForAutomations is false, Calculate writes a number and future questions are not auto-answered", () => {
Expand All @@ -32,13 +29,12 @@ test("When formatOutputForAutomations is false, Calculate writes a number and fu
expect(upcomingCardIds()).toEqual(["Calculate", "Question"]);

// Step forwards through the Calculate
record("Calculate", { data: { testGroup: 2 }, auto: true });
upcomingCardIds();
clickContinue("Calculate", { data: { testGroup: 2 }, auto: true });

// The Question has NOT been auto-answered
// The Question cannot be auto-answered
expect(visitedNodes()).toEqual(["Calculate"]);

expect(upcomingCardIds()).toEqual(["Question"]);
expect(autoAnswerableOptions("Question")).toBeUndefined();
});

const flowWithAutomation: Store.Flow = {
Expand Down
18 changes: 18 additions & 0 deletions editor.planx.uk/src/@planx/components/Checklist/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface ChecklistProps extends Checklist {
node?: {
data?: {
allRequired?: boolean;
neverAutoAnswer?: boolean;
categories?: Array<Category>;
description?: string;
fn?: string;
Expand Down Expand Up @@ -285,6 +286,7 @@ export const ChecklistComponent: React.FC<ChecklistProps> = (props) => {
const formik = useFormik<Checklist>({
initialValues: {
allRequired: props.node?.data?.allRequired || false,
neverAutoAnswer: props.node?.data?.neverAutoAnswer || false,
description: props.node?.data?.description || "",
fn: props.node?.data?.fn || "",
groupedOptions: props.groupedOptions,
Expand Down Expand Up @@ -422,6 +424,22 @@ export const ChecklistComponent: React.FC<ChecklistProps> = (props) => {
label="All required"
/>
</InputRow>
<InputRow>
<FormControlLabel
control={
<Switch
checked={formik.values.neverAutoAnswer}
onChange={() =>
formik.setFieldValue(
"neverAutoAnswer",
!formik.values.neverAutoAnswer,
)
}
/>
}
label="Always put to user (forgo automation)"
/>
</InputRow>
</InputGroup>
</ModalSectionContent>

Expand Down
42 changes: 38 additions & 4 deletions editor.planx.uk/src/@planx/components/Checklist/Public.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import ImageButton from "@planx/components/shared/Buttons/ImageButton";
import Card from "@planx/components/shared/Preview/Card";
import { CardHeader } from "@planx/components/shared/Preview/CardHeader/CardHeader";
import { getIn, useFormik } from "formik";
import React, { useState } from "react";
import { useStore } from "pages/FlowEditor/lib/store";
import React, { useEffect, useState } from "react";
import { ExpandableList, ExpandableListItem } from "ui/public/ExpandableList";
import FormWrapper from "ui/public/FormWrapper";
import FullWidthWrapper from "ui/public/FullWidthWrapper";
Expand All @@ -38,6 +39,40 @@ function toggleInArray<T>(value: T, arr: Array<T>): Array<T> {
}

const ChecklistComponent: React.FC<Props> = (props) => {
if (props.neverAutoAnswer) {
return <VisibleChecklist {...props} />;
}

const autoAnswerableOptions = useStore(
(state) => state.autoAnswerableOptions,
);

let idsThatCanBeAutoAnswered: string[] | undefined;
if (props.id) idsThatCanBeAutoAnswered = autoAnswerableOptions(props.id);
if (idsThatCanBeAutoAnswered) {
return (
<AutoAnsweredChecklist {...props} answerIds={idsThatCanBeAutoAnswered} />
);
}

return <VisibleChecklist {...props} />;
};

// An auto-answered Checklist won't be seen by the user, but still leaves a breadcrumb
const AutoAnsweredChecklist: React.FC<Props & { answerIds: string[] }> = (
props,
) => {
useEffect(() => {
props.handleSubmit?.({
answers: props.answerIds,
auto: true,
});
}, []);

return null;
};

const VisibleChecklist: React.FC<Props> = (props) => {
const {
description = "",
groupedOptions,
Expand Down Expand Up @@ -168,9 +203,8 @@ const ChecklistComponent: React.FC<Props> = (props) => {
pb={2}
aria-labelledby={`group-${index}-heading`}
id={`group-${index}-content`}
data-testid={`group-${index}${
isExpanded ? "-expanded" : ""
}`}
data-testid={`group-${index}${isExpanded ? "-expanded" : ""
}`}
>
{group.children.map((option) => (
<ChecklistItem
Expand Down
1 change: 1 addition & 0 deletions editor.planx.uk/src/@planx/components/Checklist/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface Checklist extends BaseNodeData {
img?: string;
allRequired?: boolean;
categories?: Array<Category>;
neverAutoAnswer?: boolean;
}

interface ChecklistExpandableProps {
Expand Down
3 changes: 1 addition & 2 deletions editor.planx.uk/src/@planx/components/Filter/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,12 @@ const Filter: React.FC<Props> = (props) => {
through the left-most matching flag option only.
</Typography>
</ModalSectionContent>
<ModalSectionContent title="Pick a flagset category (coming soon)">
<ModalSectionContent title="Pick a flagset category">
<select
data-testid="flagset-category-select"
name="category"
value={formik.values.category}
onChange={formik.handleChange}
disabled
>
{Array.from(categories).map((category) => (
<option key={category} value={category}>
Expand Down
26 changes: 26 additions & 0 deletions editor.planx.uk/src/@planx/components/Filter/Public.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { PublicProps } from "@planx/components/ui";
import { useStore } from "pages/FlowEditor/lib/store";
import { useEffect } from "react";

import type { Props as Filter } from "./Editor";

export type Props = PublicProps<Filter>;

// Filters are always auto-answered and never seen by a user, but should still leave a breadcrumb
export default function Component(props: Props) {
const autoAnswerableFlag = useStore(
(state) => state.autoAnswerableFlag,
);

let idThatCanBeAutoAnswered: string | undefined;
if (props.id) idThatCanBeAutoAnswered = autoAnswerableFlag(props.id);

useEffect(() => {
props.handleSubmit?.({
answers: [idThatCanBeAutoAnswered],
auto: true,
});
}, []);

return null;
}
24 changes: 20 additions & 4 deletions editor.planx.uk/src/@planx/components/Question/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import { ComponentType as TYPES } from "@opensystemslab/planx-core/types";
import { useFormik } from "formik";
import React, { useEffect, useRef } from "react";
Expand All @@ -24,6 +26,7 @@ interface Props {
img?: string;
text: string;
type?: string;
neverAutoAnswer?: boolean;
} & BaseNodeData;
};
options?: Option[];
Expand Down Expand Up @@ -131,6 +134,7 @@ export const Question: React.FC<Props> = (props) => {
img: props.node?.data?.img || "",
options: props.options || [],
text: props.node?.data?.text || "",
neverAutoAnswer: props.node?.data?.neverAutoAnswer || false,
...parseBaseNodeData(props.node?.data),
},
onSubmit: ({ options, ...values }) => {
Expand Down Expand Up @@ -175,15 +179,13 @@ export const Question: React.FC<Props> = (props) => {
onChange={formik.handleChange}
inputRef={focusRef}
/>

<ImgInput
img={formik.values.img}
onChange={(newUrl) => {
formik.setFieldValue("img", newUrl);
}}
/>
</InputRow>

<InputRow>
<RichTextInput
name="description"
Expand All @@ -192,7 +194,6 @@ export const Question: React.FC<Props> = (props) => {
onChange={formik.handleChange}
/>
</InputRow>

<InputRow>
<Input
// required
Expand All @@ -203,6 +204,22 @@ export const Question: React.FC<Props> = (props) => {
onChange={formik.handleChange}
/>
</InputRow>
<InputRow>
<FormControlLabel
control={
<Switch
checked={formik.values.neverAutoAnswer}
onChange={() =>
formik.setFieldValue(
"neverAutoAnswer",
!formik.values.neverAutoAnswer,
)
}
/>
}
label="Always put to user (forgo automation)"
/>
</InputRow>
</InputGroup>
</ModalSectionContent>

Expand All @@ -227,7 +244,6 @@ export const Question: React.FC<Props> = (props) => {
/>
</ModalSectionContent>
</ModalSection>

<MoreInformation
changeField={formik.handleChange}
definitionImg={formik.values.definitionImg}
Expand Down
46 changes: 44 additions & 2 deletions editor.planx.uk/src/@planx/components/Question/Public.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,50 @@
import { waitFor } from "@testing-library/react";
import { act, waitFor } from "@testing-library/react";
import React from "react";
import { setup } from "testUtils";
import { vi } from "vitest";
import { axe } from "vitest-axe";

import { Store, useStore } from "pages/FlowEditor/lib/store";
import type { Question } from "./model";
import QuestionComponent, { QuestionLayout } from "./Public";

const { setState } = useStore;

// Setup a basic single component flow so that we're testing the "VisibleQuestion" throughout (eg wrapper checks `flow[props.id].edges`)
const flow: Store.Flow = {
"_root": {
"edges": [
"qustion_id"
]
},
"celery_id": {
"data": {
"text": "celery"
},
"type": 200
},
"pizza_id": {
"data": {
"text": "pizza"
},
"type": 200
},
"question_id": {
"data": {
"text": "Best food",
},
"type": 100,
"edges": [
"pizza_id",
"celery_id",
]
},
};

beforeEach(() => {
act(() => setState({ flow }));
});

const responses: { [key in QuestionLayout]: Question["responses"] } = {
[QuestionLayout.Basic]: [
{
Expand Down Expand Up @@ -58,9 +96,10 @@ describe("Question component", () => {
describe(`${QuestionLayout[type]} layout`, () => {
it(`renders the layout correctly`, async () => {
const handleSubmit = vi.fn();

const { user, getByTestId, getByRole, getByText } = setup(
<QuestionComponent
id="question_id"
text="Best food"
responses={responses[type]}
handleSubmit={handleSubmit}
Expand All @@ -84,6 +123,7 @@ describe("Question component", () => {
const handleSubmit = vi.fn();
const { user, getByRole, getByTestId } = setup(
<QuestionComponent
id="question_id"
text="Best food"
responses={responses[type]}
previouslySubmittedData={{
Expand Down Expand Up @@ -114,6 +154,7 @@ describe("Question component", () => {
const handleSubmit = vi.fn();
const { container } = setup(
<QuestionComponent
id="question_id"
text="Best food"
responses={responses[type]}
handleSubmit={handleSubmit}
Expand All @@ -129,6 +170,7 @@ describe("Question component", () => {

const { user, getByTestId, getByText, queryByText } = setup(
<QuestionComponent
id="question_id"
text="Best food"
responses={responses[type]}
handleSubmit={handleSubmit}
Expand Down
Loading

0 comments on commit 02cbaf0

Please sign in to comment.