Skip to content

Commit

Permalink
feat(PUPIL-442): add OakQuizMatch
Browse files Browse the repository at this point in the history
  • Loading branch information
carlmw committed Mar 8, 2024
1 parent f9d4c3a commit d439f33
Show file tree
Hide file tree
Showing 7 changed files with 1,236 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/components/molecules/OakDroppable/OakDroppable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export const OakDroppable: FC<
$flexBasis="100%"
$width="100%"
$alignSelf="center"
data-testid="label"
>
{labelSlot}
</OakFlex>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from "react";
import { Meta, StoryObj } from "@storybook/react";

import { OakQuizMatch } from "./OakQuizMatch";

const meta: Meta<typeof OakQuizMatch> = {
component: OakQuizMatch,
tags: ["autodocs"],
title: "components/organisms/pupil/OakQuizMatch",
parameters: {
controls: {
include: [],
},
backgrounds: {
default: "light",
},
},
render: (args) => <OakQuizMatch {...args} />,
};
export default meta;

type Story = StoryObj<typeof OakQuizMatch>;

export const Default: Story = {
args: {
initialOptions: [
{ id: "1", label: "Comma" },
{ id: "2", label: "Apostrophe" },
{ id: "3", label: "Question mark" },
{ id: "4", label: "Full stop" },
{ id: "5", label: "Exclamation mark" },
],
initialSlots: [
{ id: "1", label: "conveys intense emotion" },
{ id: "2", label: "poses a question" },
{ id: "3", label: "ends a declarative sentence" },
{ id: "4", label: "separates a main clause and a subordinate clause" },
{ id: "5", label: "shows belonging" },
],
},
};

export const WithManyOptions: Story = {
args: {
initialOptions: [
{ id: "1", label: "Book" },
{ id: "2", label: "Bicycle" },
{ id: "3", label: "Guitar" },
{ id: "4", label: "Camera" },
{ id: "5", label: "Paintbrush" },
{ id: "6", label: "Football" },
{ id: "7", label: "Cooking pot" },
{ id: "8", label: "Telescope" },
{ id: "9", label: "Headphones" },
{ id: "10", label: "Passport" },
],
initialSlots: [
{ id: "1", label: "a source of knowledge and adventure" },
{ id: "2", label: "a mode of transportation and exercise" },
{
id: "3",
label: "a musical instrument for creating melodies and rhythms",
},
{ id: "4", label: "captures memories and moments in time" },
{ id: "5", label: "used to express creativity on canvas" },
{ id: "6", label: "a tool for teamwork and athletic skill" },
{ id: "7", label: "essential for preparing delicious meals" },
{ id: "8", label: "explores the wonders of the cosmos" },
{ id: "9", label: "provides immersive audio experiences" },
{
id: "10",
label: "gateway to new destinations and cultural experiences",
},
],
},
};
169 changes: 169 additions & 0 deletions src/components/organisms/pupil/OakQuizMatch/OakQuizMatch.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import assert from "assert";

import { act, create } from "react-test-renderer";
import { ThemeProvider } from "styled-components";
import React from "react";
import { DndContext, DndContextProps, UniqueIdentifier } from "@dnd-kit/core";
import {
getAllByRole as getAllByRoleWithin,
getByTestId as getByTestIdWithin,
getByRole as getByRoleWithin,
queryByRole as queryByRoleWithin,
} from "@testing-library/react";

import { OakQuizMatch } from "./OakQuizMatch";

import { oakDefaultTheme } from "@/styles";
import renderWithTheme from "@/test-helpers/renderWithTheme";
import { injectDndContext } from "@/components/atoms/InternalDndContext/InternalDndContext";

window.matchMedia = jest.fn().mockReturnValue({
matches: false,
});

const options = [
{ id: "1", label: "Exclamation mark" },
{ id: "2", label: "Full stop" },
{ id: "3", label: "Question mark" },
];
const slots = [
{ id: "1", label: "conveys intense emotion" },
{ id: "2", label: "poses a question" },
{ id: "3", label: "ends a declarative sentence" },
];

describe(OakQuizMatch, () => {
it("matches snapshot", () => {
const tree = create(
<ThemeProvider theme={oakDefaultTheme}>
<OakQuizMatch initialOptions={options} initialSlots={slots} />
</ThemeProvider>,
).toJSON();

expect(tree).toMatchSnapshot();
});

it("allows an option to be dropped into a slot", async () => {
let dndProps: DndContextProps = {};
const MockDndContext = (props: DndContextProps) => {
dndProps = props;
return <DndContext {...props} />;
};
const onChangeSpy = jest.fn();

const { getByTestId, getAllByTestId } = renderWithTheme(
<injectDndContext.Provider value={MockDndContext}>
<OakQuizMatch
initialOptions={options}
initialSlots={slots}
onChange={onChangeSpy}
/>
</injectDndContext.Provider>,
);
const [firstSlot] = getAllByTestId("slot");
assert(firstSlot);

// All items should start in the holding pen
expect(
getAllByRoleWithin(getByTestId("holding-pen"), "option").map(
(item) => item.textContent,
),
).toEqual(["Exclamation mark", "Full stop", "Question mark"]);

// Drag the first item into the first slot
act(() => {
dndProps?.onDragStart?.(mockDragEvent("1"));
dndProps?.onDragOver?.(mockDragEvent("1", "1"));
dndProps?.onDragEnd?.(mockDragEvent("1", "1"));
});

// The first item should be removed from the holding pen and placed in the first slot
expect(
getAllByRoleWithin(getByTestId("holding-pen"), "option").map(
(item) => item.textContent,
),
).toEqual(["Full stop", "Question mark"]);
expect(getByTestIdWithin(firstSlot, "label").textContent).toEqual(
"conveys intense emotion",
);
expect(getByRoleWithin(firstSlot, "option").textContent).toEqual(
"Exclamation mark",
);

// Replace the first item
act(() => {
dndProps?.onDragStart?.(mockDragEvent("2"));
dndProps?.onDragOver?.(mockDragEvent("2", "1"));
dndProps?.onDragEnd?.(mockDragEvent("2", "1"));
});

// The first option should be returned to the holding pen
expect(
getAllByRoleWithin(getByTestId("holding-pen"), "option").map(
(item) => item.textContent,
),
).toEqual(["Exclamation mark", "Question mark"]);
// The first slot should now contain the second option
expect(getByRoleWithin(firstSlot, "option").textContent).toEqual(
"Full stop",
);

// Drag the option back into the holding pen
act(() => {
dndProps?.onDragStart?.(mockDragEvent("2"));
dndProps?.onDragOver?.(mockDragEvent("2", "holding-pen"));
dndProps?.onDragEnd?.(mockDragEvent("2", "holding-pen"));
});

// All options should be back in the holding pen
expect(
getAllByRoleWithin(getByTestId("holding-pen"), "option").map(
(item) => item.textContent,
),
).toEqual(["Exclamation mark", "Full stop", "Question mark"]);
expect(getByTestIdWithin(firstSlot, "label").textContent).toEqual(
"conveys intense emotion",
);
expect(queryByRoleWithin(firstSlot, "option")).toBeNull();
});
});

function mockDragEvent(optionId: UniqueIdentifier, slotId?: UniqueIdentifier) {
return {
active: {
id: optionId,
data: {
current: options.find((option) => option.id === optionId),
},
rect: {
current: {
initial: null,
translated: null,
},
},
},
over: slotId
? {
id: slotId,
rect: {
width: 0,
height: 0,
top: 0,
left: 0,
right: 0,
bottom: 0,
},
disabled: false,
data: {
current: slots.find((slot) => slot.id === slotId),
},
}
: null,
activatorEvent: new Event("mock-pointer"),
collisions: null,
delta: {
x: 0,
y: 0,
},
};
}
Loading

0 comments on commit d439f33

Please sign in to comment.