Skip to content
Open
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
64 changes: 60 additions & 4 deletions src/components/mui/__tests__/mui-qr-badge-popup.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,35 @@ jest.mock("../formik-inputs/mui-formik-async-select", () => {

jest.mock(
"openstack-uicore-foundation/lib/components/extra-questions-mui",
() => ({
__esModule: true,
default: () => <div data-testid="extra-questions" />
})
() => {
const React = require("react");
const { toSlug } = require("../../../utils/extra-questions");
return {
__esModule: true,
default: ({ extraQuestions, formik }) => (
<div data-testid="extra-questions">
{extraQuestions
.filter((q) => q.type === "CheckBox")
.map((q) => {
const slug = toSlug(q.name, q.id);
return (
<label key={slug} htmlFor={slug}>
{q.name}
<input
id={slug}
type="checkbox"
checked={!!formik.values[slug]}
onChange={(e) =>
formik.setFieldValue(slug, e.target.checked)
}
/>
</label>
);
})}
</div>
)
};
}
);

const mockErrorMessage = jest.fn();
Expand Down Expand Up @@ -361,4 +386,35 @@ describe("MuiQrBadgePopup", () => {
expect(screen.queryByTestId("extra-questions")).not.toBeInTheDocument();
});
});

describe("Extra questions submission", () => {
const checkboxQuestion = { id: 5, name: "Opted In", type: "CheckBox" };

it("should submit an unchecked CheckBox answer as the string 'false' instead of dropping it", async () => {
const onSave = jest.fn();
renderComponent({
isAdmin: true,
extraQuestions: [checkboxQuestion],
onSave
});

await userEvent.click(
screen.getByRole("radio", {
name: "sponsor_badge_scans.scan_popup.scan_qr"
})
);
await userEvent.click(screen.getByText("Simulate Scan"));
await userEvent.click(
screen.getByRole("button", { name: "general.save" })
);

await waitFor(() => {
expect(onSave).toHaveBeenCalledWith(
expect.objectContaining({
extra_questions: [{ question_id: 5, answer: "false" }]
})
);
});
});
});
});
22 changes: 14 additions & 8 deletions src/components/mui/mui-qr-badge-popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ import CloseIcon from "@mui/icons-material/Close";
import { useSnackbarMessage } from "openstack-uicore-foundation/lib/components/mui/snackbar-notification";
import MuiFormikTextField from "openstack-uicore-foundation/lib/components/mui/formik-inputs/textfield";
import QrReader from "../qr-reader";
import { getTypeValue, toSlug } from "../../utils/extra-questions";
import {
getTypeValue,
toSlug,
formatAnswerForSubmit
} from "../../utils/extra-questions";
import MuiFormikAsyncAutocomplete from "./formik-inputs/mui-formik-async-select";
import { queryAttendeesWithTickets } from "../../actions/attendee-actions";

Expand Down Expand Up @@ -68,13 +72,15 @@ const MuiQrBadgePopup = ({
const { attendee_email, notes, ...extraValues } = values;

const extra_questions = Object.entries(extraValues)
.map(([slug, value]) => ({
question_id: parseInt(slug.split("_").pop()),
answer: Array.isArray(value)
? value.filter((v) => v !== "").join(",")
: value
}))
.filter((q) => q.answer);
.map(([slug, value]) => {
const question_id = parseInt(slug.split("_").pop());
const question = extraQuestions.find((q) => q.id === question_id);
return {
question_id,
answer: formatAnswerForSubmit(value, question?.type)
};
})
.filter((q) => q.answer !== "");

const entity = {
...(scanMode === BADGE_SCAN_MODE_QR
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// edit-badge-scan-popup.test.js
import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";
import EditBadgeScanPopup from "../edit-badge-scan-popup";

jest.mock("i18n-react/dist/i18n-react", () => ({
__esModule: true,
default: { translate: (key) => key }
}));

jest.mock(
"openstack-uicore-foundation/lib/components/extra-questions-mui",
() => {
const React = require("react");
const { toSlug } = require("../../../../../../utils/extra-questions");
return {
__esModule: true,
default: ({ extraQuestions, formik }) => (
<div data-testid="extra-questions">
{extraQuestions
.filter((q) => q.type === "CheckBox")
.map((q) => {
const slug = toSlug(q.name, q.id);
return (
<label key={slug} htmlFor={slug}>
{q.name}
<input
id={slug}
type="checkbox"
checked={!!formik.values[slug]}
onChange={(e) =>
formik.setFieldValue(slug, e.target.checked)
}
/>
</label>
);
})}
</div>
)
};
}
);

const buildBadgeScan = (overrides = {}) => ({
id: 1,
attendee_full_name: "John Doe",
attendee_company: "Acme",
notes: "",
extra_questions: [],
sponsor_extra_questions: [
{ id: 5, name: "Opted In", type: "CheckBox", order: 1 }
],
...overrides
});

const renderComponent = (props = {}) =>
render(
<EditBadgeScanPopup
badgeScan={buildBadgeScan()}
onClose={jest.fn()}
onSubmit={jest.fn()}
{...props}
/>
);

describe("EditBadgeScanPopup", () => {
beforeEach(() => {
jest.clearAllMocks();
});

describe("Extra questions submission", () => {
it("should submit an unchecked CheckBox answer as the string 'false' instead of dropping it", async () => {
const onSubmit = jest.fn();
renderComponent({ onSubmit });

await userEvent.click(
screen.getByRole("button", { name: "general.save" })
);

await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith(
expect.objectContaining({
extra_questions: [{ question_id: 5, answer: "false" }]
})
);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import ExtraQuestionsMUI from "openstack-uicore-foundation/lib/components/extra-

import MuiFormikTextField from "openstack-uicore-foundation/lib/components/mui/formik-inputs/textfield";
import useScrollToError from "../../../../../hooks/useScrollToError";
import { getTypeValue, toSlug } from "../../../../../utils/extra-questions";
import {
getTypeValue,
toSlug,
formatAnswerForSubmit
} from "../../../../../utils/extra-questions";

const formatExtraQuestions = (extraQuestions, sponsorQuestions) => {
const values = {};
Expand Down Expand Up @@ -67,17 +71,17 @@ const EditBadgeScanPopup = ({ badgeScan, onClose, onSubmit }) => {
// formatting extra questions before submit, and omit empty answers
const extra_questions = Object.entries(extraValues)
.map(([slug, value]) => {
const parts = slug.split("_").pop();
const question_id = parseInt(parts);
const question_id = parseInt(slug.split("_").pop());
const question = badgeScan.sponsor_extra_questions.find(
(q) => q.id === question_id
);

return {
question_id,
answer: Array.isArray(value)
? value.filter((v) => v !== "").join(",")
: value
answer: formatAnswerForSubmit(value, question?.type)
};
})
.filter((q) => q.answer);
.filter((q) => q.answer !== "");

onSubmit({
id,
Expand Down
34 changes: 34 additions & 0 deletions src/utils/__tests__/extra-questions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { formatAnswerForSubmit } from "../extra-questions";

describe("formatAnswerForSubmit", () => {
describe("CheckBox questions", () => {
it("should format an unchecked (false) answer as the string 'false' instead of dropping it", () => {
expect(formatAnswerForSubmit(false, "CheckBox")).toBe("false");
});

it("should format a checked (true) answer as the string 'true'", () => {
expect(formatAnswerForSubmit(true, "CheckBox")).toBe("true");
});
});

describe("CheckBoxList questions", () => {
it("should join selected values, filtering out empty entries", () => {
expect(formatAnswerForSubmit(["1", "", "3"], "CheckBoxList")).toBe("1,3");
});

it("should return an empty string for no selection", () => {
expect(formatAnswerForSubmit([], "CheckBoxList")).toBe("");
});
});

describe("other question types", () => {
it("should stringify a numeric 0 answer instead of treating it as empty", () => {
expect(formatAnswerForSubmit(0, "ComboBox")).toBe("0");
});

it("should return an empty string for a null or undefined answer", () => {
expect(formatAnswerForSubmit(null, "RadioButtonList")).toBe("");
expect(formatAnswerForSubmit(undefined, "Text")).toBe("");
});
});
});
8 changes: 8 additions & 0 deletions src/utils/extra-questions.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,11 @@ export const getTypeValue = (ans, type) => {
return ans;
}
};

// reverse of getTypeValue: formats a typed form value back into the string
// the API expects for extra_questions[].answer
export const formatAnswerForSubmit = (value, type) => {
if (Array.isArray(value)) return value.filter((v) => v !== "").join(",");
if (type === QuestionType_Checkbox) return value ? "true" : "false";
return value === null || value === undefined ? "" : `${value}`;
};
Loading