-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
1,236 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
src/components/organisms/pupil/OakQuizMatch/OakQuizMatch.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
169
src/components/organisms/pupil/OakQuizMatch/OakQuizMatch.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
}; | ||
} |
Oops, something went wrong.