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

Production deploy #3154

Merged
merged 3 commits into from
May 16, 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
161 changes: 92 additions & 69 deletions editor.planx.uk/src/@planx/components/Question/Public.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,75 +52,98 @@ describe("Question component", () => {
QuestionLayout.Images,
QuestionLayout.Descriptions,
].forEach((type) => {
it(`renders the ${QuestionLayout[type]} layout correctly`, async () => {
const handleSubmit = jest.fn();

const { user } = setup(
<Question
text="Best food"
responses={responses[type]}
handleSubmit={handleSubmit}
/>,
);

const continueButton = screen.getByTestId("continue-button");

expect(screen.getByRole("heading")).toHaveTextContent("Best food");

expect(continueButton).toBeDisabled();

await user.click(screen.getByText("Pizza"));

expect(continueButton).not.toBeDisabled();

await user.click(continueButton);

await waitFor(() =>
expect(handleSubmit).toHaveBeenCalledWith({ answers: ["pizza_id"] }),
);
});

it(`should display previously selected answer on back or change in the ${QuestionLayout[type]} layout`, async () => {
const handleSubmit = jest.fn();
const { user } = setup(
<Question
text="Best food"
responses={responses[type]}
previouslySubmittedData={{
answers: ["celery_id"],
auto: false,
}}
handleSubmit={handleSubmit}
/>,
);

expect(screen.getByRole("heading")).toHaveTextContent("Best food");

const celeryRadio = screen.getByRole("radio", { name: /Celery/ });
const pizzaRadio = screen.getByRole("radio", { name: /Pizza/ });

// State is preserved...
expect(celeryRadio).toBeChecked();

// ...and can be updated
await user.click(pizzaRadio);
expect(pizzaRadio).toBeChecked();

const continueButton = screen.getByTestId("continue-button");
expect(continueButton).toBeEnabled();
});

it(`should not have any accessibility violations in the ${QuestionLayout[type]} layout`, async () => {
const handleSubmit = jest.fn();
const { container } = setup(
<Question
text="Best food"
responses={responses[type]}
handleSubmit={handleSubmit}
/>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
describe(`${QuestionLayout[type]} layout`, () => {
it(`renders the layout correctly`, async () => {
const handleSubmit = jest.fn();

const { user, getByTestId, getByRole, getByText } = setup(
<Question
text="Best food"
responses={responses[type]}
handleSubmit={handleSubmit}
/>,
);

const continueButton = getByTestId("continue-button");

expect(getByRole("heading")).toHaveTextContent("Best food");

await user.click(getByText("Pizza"));

await user.click(continueButton);

await waitFor(() =>
expect(handleSubmit).toHaveBeenCalledWith({ answers: ["pizza_id"] }),
);
});

it(`should display previously selected answer on back or change`, async () => {
const handleSubmit = jest.fn();
const { user, getByRole, getByTestId } = setup(
<Question
text="Best food"
responses={responses[type]}
previouslySubmittedData={{
answers: ["celery_id"],
auto: false,
}}
handleSubmit={handleSubmit}
/>,
);

expect(getByRole("heading")).toHaveTextContent("Best food");

const celeryRadio = getByRole("radio", { name: /Celery/ });
const pizzaRadio = getByRole("radio", { name: /Pizza/ });

// State is preserved...
expect(celeryRadio).toBeChecked();

// ...and can be updated
await user.click(pizzaRadio);
expect(pizzaRadio).toBeChecked();

const continueButton = getByTestId("continue-button");
expect(continueButton).toBeEnabled();
});

it(`should not have any accessibility violations`, async () => {
const handleSubmit = jest.fn();
const { container } = setup(
<Question
text="Best food"
responses={responses[type]}
handleSubmit={handleSubmit}
/>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});

it(`should display an error message if no option is selected`, async () => {
const handleSubmit = jest.fn();
const errorMessage = /Select your answer before continuing/;

const { user, getByTestId, getByText, queryByText } = setup(
<Question
text="Best food"
responses={responses[type]}
handleSubmit={handleSubmit}
/>,
);

const continueButton = getByTestId("continue-button");

expect(continueButton).not.toBeDisabled();
expect(queryByText(errorMessage)).not.toBeInTheDocument();

await user.click(continueButton);

await waitFor(() => {
expect(handleSubmit).not.toHaveBeenCalled();
expect(getByText(errorMessage)).toBeVisible();
});
});
});
});
});
143 changes: 74 additions & 69 deletions editor.planx.uk/src/@planx/components/Question/Public/Question.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { handleSubmit } from "pages/Preview/Node";
import React from "react";
import FormWrapper from "ui/public/FormWrapper";
import FullWidthWrapper from "ui/public/FullWidthWrapper";
import ErrorWrapper from "ui/shared/ErrorWrapper";
import { mixed, object, string } from "yup";

export interface IQuestion {
id?: string;
Expand Down Expand Up @@ -57,15 +59,17 @@ const Question: React.FC<IQuestion> = (props) => {
a: previousResponseKey ?? undefined,
},
},
onSubmit: (values) => {
setTimeout(
() => props.handleSubmit({ answers: [values.selected.id] }),
theme.transitions.duration.standard,
);
},
validate: () => {
// do nothing
},
onSubmit: (values) => props.handleSubmit({ answers: [values.selected.id] }),
validateOnBlur: false,
validateOnChange: false,
validationSchema: object({
selected: object({
id: string().required("Select your answer before continuing"),
a: mixed().required().test(value =>
typeof value === "number" ||
typeof value === "string")
})
}),
});

let layout = QuestionLayout.Basic;
Expand All @@ -80,7 +84,6 @@ const Question: React.FC<IQuestion> = (props) => {
return (
<Card
handleSubmit={formik.handleSubmit}
isValid={Boolean(formik.values.selected.id)}
>
<QuestionHeader
title={props.text}
Expand All @@ -100,68 +103,70 @@ const Question: React.FC<IQuestion> = (props) => {
>
{props.text}
</FormLabel>
<RadioGroup
aria-labelledby={`radio-buttons-group-label-${props.id}`}
name={`radio-buttons-group-${props.id}`}
value={formik.values.selected.id}
>
<Grid
container
spacing={layout === QuestionLayout.Basic ? 0 : 2}
alignItems="stretch"
<ErrorWrapper id={props.id} error={formik.errors.selected?.id}>
<RadioGroup
aria-labelledby={`radio-buttons-group-label-${props.id}`}
name={`radio-buttons-group-${props.id}`}
value={formik.values.selected.id}
>
{props.responses?.map((response) => {
const onChange = () => {
formik.setFieldValue("selected.id", response.id);
formik.setFieldValue("selected.a", response.responseKey);
};
const buttonProps = {
onChange,
};
<Grid
container
spacing={layout === QuestionLayout.Basic ? 0 : 2}
alignItems="stretch"
>
{props.responses?.map((response) => {
const onChange = () => {
formik.setFieldValue("selected.id", response.id);
formik.setFieldValue("selected.a", response.responseKey);
};
const buttonProps = {
onChange,
};

switch (layout) {
case QuestionLayout.Basic:
return (
<FormWrapper key={`wrapper-${response.id}`}>
<Grid item xs={12} ml={1} key={`grid-${response.id}`}>
<BasicRadio
{...buttonProps}
{...response}
data-testid="basic-radio"
key={`basic-radio-${response.id}`}
/>
switch (layout) {
case QuestionLayout.Basic:
return (
<FormWrapper key={`wrapper-${response.id}`}>
<Grid item xs={12} ml={1} key={`grid-${response.id}`}>
<BasicRadio
{...buttonProps}
{...response}
data-testid="basic-radio"
key={`basic-radio-${response.id}`}
/>
</Grid>
</FormWrapper>
);
case QuestionLayout.Descriptions:
return (
<Grid
item
xs={12}
sm={6}
contentWrap={4}
key={response.id}
data-testid="description-radio"
>
<DescriptionRadio {...buttonProps} {...response} />
</Grid>
);
case QuestionLayout.Images:
return (
<Grid
item
xs={12}
sm={6}
contentWrap={4}
key={response.id}
>
<ImageRadio {...buttonProps} {...response} />
</Grid>
</FormWrapper>
);
case QuestionLayout.Descriptions:
return (
<Grid
item
xs={12}
sm={6}
contentWrap={4}
key={response.id}
data-testid="description-radio"
>
<DescriptionRadio {...buttonProps} {...response} />
</Grid>
);
case QuestionLayout.Images:
return (
<Grid
item
xs={12}
sm={6}
contentWrap={4}
key={response.id}
>
<ImageRadio {...buttonProps} {...response} />
</Grid>
);
}
})}
</Grid>
</RadioGroup>
);
}
})}
</Grid>
</RadioGroup>
</ErrorWrapper>
</FormControl>
</FullWidthWrapper>
</Card>
Expand Down
10 changes: 8 additions & 2 deletions editor.planx.uk/src/routes/teamMembers.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Role, User } from "@opensystemslab/planx-core/types";
import gql from "graphql-tag";
import { groupBy } from "lodash";
import { compose, mount, route, withData } from "navi";
import { NotFoundError, compose, mount, route, withData } from "navi";
import {
TeamMember,
TeamMembers,
Expand All @@ -22,7 +22,13 @@ const teamMembersRoutes = compose(
})),

mount({
"/": route(async () => {
"/": route(async (req) => {
const isAuthorised = useStore.getState().canUserEditTeam(req.params.team);
if (!isAuthorised)
throw new NotFoundError(
`User does not have access to ${req.originalUrl}`,
);

const teamSlug = useStore.getState().teamSlug;

const {
Expand Down
Loading