fix: stabilize Sudoku workspace layout
This commit is contained in:
@@ -4,6 +4,16 @@ All notable changes are documented here.
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.2.1 - 2026-08-31
|
||||||
|
|
||||||
|
- Fixed compact solver-step cards so their explanations no longer overflow.
|
||||||
|
- Fixed 75% board zoom and removed unnecessary inner scrolling at fitted and
|
||||||
|
reduced board sizes.
|
||||||
|
- Changed the standard digit keypad to a 3-by-3 layout with a full-width Erase
|
||||||
|
key, while retaining a 4-column layout for larger grids.
|
||||||
|
- Kept board and sidebar widths stable across Play, Set, Generate, Solve and
|
||||||
|
Helpers, with helper controls reflowing inside the shared sidebar width.
|
||||||
|
|
||||||
## 0.2.0 - 2026-08-31
|
## 0.2.0 - 2026-08-31
|
||||||
|
|
||||||
- Added staged guided hints that reveal focus, technique, reasoning and effects
|
- Added staged guided hints that reveal focus, technique, reasoning and effects
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ npm run release:artifact
|
|||||||
|
|
||||||
The command verifies the manifest, types, formatting, tests, production build,
|
The command verifies the manifest, types, formatting, tests, production build,
|
||||||
browser workflows and Toolbox contract before creating a deterministic
|
browser workflows and Toolbox contract before creating a deterministic
|
||||||
`release/sudoku-tools-0.2.0.zip` with a matching SHA-256 sidecar.
|
`release/sudoku-tools-0.2.1.zip` with a matching SHA-256 sidecar.
|
||||||
|
|
||||||
## Scope and references
|
## Scope and references
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Corresponding source
|
# Corresponding source
|
||||||
|
|
||||||
The corresponding source for Sudoku Tools 0.2.0 is available at:
|
The corresponding source for Sudoku Tools 0.2.1 is available at:
|
||||||
|
|
||||||
https://git.add-ideas.de/lotobo/sudoku-tools/src/tag/v0.2.0
|
https://git.add-ideas.de/lotobo/sudoku-tools/src/tag/v0.2.1
|
||||||
|
|
||||||
The production archive is generated from that tag with the scripts and exact
|
The production archive is generated from that tag with the scripts and exact
|
||||||
dependency lock contained in the repository. It contains source identity,
|
dependency lock contained in the repository. It contains source identity,
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sudoku-tools",
|
"name": "sudoku-tools",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sudoku-tools",
|
"name": "sudoku-tools",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@add-ideas/toolbox-contract": "0.2.3",
|
"@add-ideas/toolbox-contract": "0.2.3",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sudoku-tools",
|
"name": "sudoku-tools",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"description": "Set, play, solve and analyse Sudoku puzzles locally in the browser.",
|
"description": "Set, play, solve and analyse Sudoku puzzles locally in the browser.",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"author": "Albrecht Degering",
|
"author": "Albrecht Degering",
|
||||||
|
|||||||
@@ -4,6 +4,16 @@ All notable changes are documented here.
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.2.1 - 2026-08-31
|
||||||
|
|
||||||
|
- Fixed compact solver-step cards so their explanations no longer overflow.
|
||||||
|
- Fixed 75% board zoom and removed unnecessary inner scrolling at fitted and
|
||||||
|
reduced board sizes.
|
||||||
|
- Changed the standard digit keypad to a 3-by-3 layout with a full-width Erase
|
||||||
|
key, while retaining a 4-column layout for larger grids.
|
||||||
|
- Kept board and sidebar widths stable across Play, Set, Generate, Solve and
|
||||||
|
Helpers, with helper controls reflowing inside the shared sidebar width.
|
||||||
|
|
||||||
## 0.2.0 - 2026-08-31
|
## 0.2.0 - 2026-08-31
|
||||||
|
|
||||||
- Added staged guided hints that reveal focus, technique, reasoning and effects
|
- Added staged guided hints that reveal focus, technique, reasoning and effects
|
||||||
|
|||||||
+1
-1
@@ -268,7 +268,7 @@ npm run release:artifact
|
|||||||
|
|
||||||
The command verifies the manifest, types, formatting, tests, production build,
|
The command verifies the manifest, types, formatting, tests, production build,
|
||||||
browser workflows and Toolbox contract before creating a deterministic
|
browser workflows and Toolbox contract before creating a deterministic
|
||||||
`release/sudoku-tools-0.2.0.zip` with a matching SHA-256 sidecar.
|
`release/sudoku-tools-0.2.1.zip` with a matching SHA-256 sidecar.
|
||||||
|
|
||||||
## Scope and references
|
## Scope and references
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
# Corresponding source
|
# Corresponding source
|
||||||
|
|
||||||
The corresponding source for Sudoku Tools 0.2.0 is available at:
|
The corresponding source for Sudoku Tools 0.2.1 is available at:
|
||||||
|
|
||||||
https://git.add-ideas.de/lotobo/sudoku-tools/src/tag/v0.2.0
|
https://git.add-ideas.de/lotobo/sudoku-tools/src/tag/v0.2.1
|
||||||
|
|
||||||
The production archive is generated from that tag with the scripts and exact
|
The production archive is generated from that tag with the scripts and exact
|
||||||
dependency lock contained in the repository. It contains source identity,
|
dependency lock contained in the repository. It contains source identity,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"id": "de.add-ideas.sudoku-tools",
|
"id": "de.add-ideas.sudoku-tools",
|
||||||
"name": "Sudoku Tools",
|
"name": "Sudoku Tools",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"description": "Set, play, solve and analyse Sudoku puzzles locally in the browser.",
|
"description": "Set, play, solve and analyse Sudoku puzzles locally in the browser.",
|
||||||
"entry": "./",
|
"entry": "./",
|
||||||
"icon": "./favicon.svg",
|
"icon": "./favicon.svg",
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export function BoardViewport({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className={`board-viewport${panMode ? " is-pan-mode" : ""}`}
|
className={`board-viewport${scale > 1 ? " is-zoomed-in" : ""}${panMode ? " is-pan-mode" : ""}`}
|
||||||
aria-label="Board zoom and pan"
|
aria-label="Board zoom and pan"
|
||||||
onKeyDownCapture={onKeyDownCapture}
|
onKeyDownCapture={onKeyDownCapture}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2335,9 +2335,7 @@ export function Workbench() {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div className="workbench-grid">
|
||||||
className={`workbench-grid${workspace === "helpers" ? " workbench-grid--helpers" : ""}`}
|
|
||||||
>
|
|
||||||
<section className="board-column" aria-label="Puzzle board">
|
<section className="board-column" aria-label="Puzzle board">
|
||||||
<div className="board-toolbar">
|
<div className="board-toolbar">
|
||||||
<div className="toolbar-group">
|
<div className="toolbar-group">
|
||||||
|
|||||||
+14
-13
@@ -632,10 +632,6 @@ body:has(.toolbox-shell[data-toolbox-theme="dark"]) .fatal-error {
|
|||||||
gap: clamp(0.85rem, 1.6vw, 1.35rem);
|
gap: clamp(0.85rem, 1.6vw, 1.35rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.workbench-grid--helpers {
|
|
||||||
grid-template-columns: minmax(24rem, 0.92fr) minmax(31rem, 1.08fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.board-column,
|
.board-column,
|
||||||
.side-panel,
|
.side-panel,
|
||||||
.play-panel,
|
.play-panel,
|
||||||
@@ -738,19 +734,23 @@ body:has(.toolbox-shell[data-toolbox-theme="dark"]) .fatal-error {
|
|||||||
|
|
||||||
.board-viewport__scroller {
|
.board-viewport__scroller {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: min(82dvh, 56rem);
|
max-height: none;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.board-viewport.is-zoomed-in .board-viewport__scroller {
|
||||||
|
max-height: min(82dvh, 56rem);
|
||||||
|
}
|
||||||
|
|
||||||
.board-viewport__scroller:focus-visible {
|
.board-viewport__scroller:focus-visible {
|
||||||
outline: 2px solid var(--toolbox-focus);
|
outline: 2px solid var(--toolbox-focus);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-viewport__canvas {
|
.board-viewport__canvas {
|
||||||
min-width: 100%;
|
min-width: 0;
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2858,12 +2858,12 @@ body:has(.toolbox-shell[data-toolbox-theme="dark"]) .fatal-error {
|
|||||||
|
|
||||||
.digit-pad {
|
.digit-pad {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(2.75rem, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
gap: 0.42rem;
|
gap: 0.42rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.digit-pad--wide {
|
.digit-pad--wide {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(2.5rem, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.digit-pad button {
|
.digit-pad button {
|
||||||
@@ -2875,6 +2875,7 @@ body:has(.toolbox-shell[data-toolbox-theme="dark"]) .fatal-error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.digit-pad .erase-key {
|
.digit-pad .erase-key {
|
||||||
|
grid-column: 1 / -1;
|
||||||
padding-inline: 0.65rem;
|
padding-inline: 0.65rem;
|
||||||
color: var(--toolbox-muted);
|
color: var(--toolbox-muted);
|
||||||
font-size: 0.76rem;
|
font-size: 0.76rem;
|
||||||
@@ -3273,7 +3274,7 @@ body:has(.toolbox-shell[data-toolbox-theme="dark"]) .fatal-error {
|
|||||||
|
|
||||||
.candidate-lab__controls {
|
.candidate-lab__controls {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(12rem, 1.5fr) repeat(3, minmax(8rem, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
align-items: end;
|
align-items: end;
|
||||||
gap: 0.55rem 0.8rem;
|
gap: 0.55rem 0.8rem;
|
||||||
}
|
}
|
||||||
@@ -3510,6 +3511,8 @@ body:has(.toolbox-shell[data-toolbox-theme="dark"]) .fatal-error {
|
|||||||
.solve-steps {
|
.solve-steps {
|
||||||
max-height: 32rem;
|
max-height: 32rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-auto-rows: max-content;
|
||||||
|
align-content: start;
|
||||||
gap: 0.42rem;
|
gap: 0.42rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0.15rem;
|
padding: 0.15rem;
|
||||||
@@ -3518,7 +3521,6 @@ body:has(.toolbox-shell[data-toolbox-theme="dark"]) .fatal-error {
|
|||||||
|
|
||||||
.solve-step {
|
.solve-step {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 0 !important;
|
|
||||||
display: grid !important;
|
display: grid !important;
|
||||||
grid-template-columns: 2rem minmax(0, 1fr);
|
grid-template-columns: 2rem minmax(0, 1fr);
|
||||||
align-items: start !important;
|
align-items: start !important;
|
||||||
@@ -3966,8 +3968,7 @@ body:has(.toolbox-shell[data-toolbox-theme="dark"]) .fatal-error {
|
|||||||
/* Responsive composition */
|
/* Responsive composition */
|
||||||
|
|
||||||
@media (max-width: 67rem) {
|
@media (max-width: 67rem) {
|
||||||
.workbench-grid,
|
.workbench-grid {
|
||||||
.workbench-grid--helpers {
|
|
||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4033,7 +4034,7 @@ body:has(.toolbox-shell[data-toolbox-theme="dark"]) .fatal-error {
|
|||||||
min-height: 2.75rem;
|
min-height: 2.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-viewport__scroller {
|
.board-viewport.is-zoomed-in .board-viewport__scroller {
|
||||||
max-height: min(72dvh, 48rem);
|
max-height: min(72dvh, 48rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"id": "de.add-ideas.sudoku-tools",
|
"id": "de.add-ideas.sudoku-tools",
|
||||||
"name": "Sudoku Tools",
|
"name": "Sudoku Tools",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"description": "Set, play, solve and analyse Sudoku puzzles locally in the browser.",
|
"description": "Set, play, solve and analyse Sudoku puzzles locally in the browser.",
|
||||||
"entry": "./",
|
"entry": "./",
|
||||||
"icon": "./favicon.svg",
|
"icon": "./favicon.svg",
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
export const APPLICATION_VERSION = "0.2.0";
|
export const APPLICATION_VERSION = "0.2.1";
|
||||||
|
|||||||
@@ -65,6 +65,95 @@ test("loads standalone and keeps the core play workflow local", async ({
|
|||||||
expect(runtimeErrors).toEqual([]);
|
expect(runtimeErrors).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("keeps the board, keypad and solver controls within their layouts", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.setViewportSize({ width: 1440, height: 1000 });
|
||||||
|
await page.goto("/deep/nested/sudoku/");
|
||||||
|
|
||||||
|
const frame = page.locator(".sudoku-board-frame");
|
||||||
|
const sidebar = page.locator(".side-panel");
|
||||||
|
const boardColumn = page.locator(".board-column");
|
||||||
|
const scroller = page.locator(".board-viewport__scroller");
|
||||||
|
await expect(frame).toBeVisible();
|
||||||
|
|
||||||
|
const fittedWidth = (await frame.boundingBox())?.width ?? 0;
|
||||||
|
expect(fittedWidth).toBeGreaterThan(0);
|
||||||
|
expect(
|
||||||
|
await scroller.evaluate(
|
||||||
|
(element) => element.scrollHeight <= element.clientHeight + 1,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
const digits = page.getByRole("group", { name: "Digits" });
|
||||||
|
const digitBoxes = await Promise.all(
|
||||||
|
["1", "2", "3", "4", "7", "9"].map(async (digit) =>
|
||||||
|
digits.getByRole("button", { name: digit, exact: true }).boundingBox(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const [one, two, three, four, seven, nine] = digitBoxes;
|
||||||
|
const erase = await digits
|
||||||
|
.getByRole("button", { name: "Erase" })
|
||||||
|
.boundingBox();
|
||||||
|
for (const box of [...digitBoxes, erase]) expect(box).not.toBeNull();
|
||||||
|
expect(Math.abs(one!.y - two!.y)).toBeLessThan(1);
|
||||||
|
expect(Math.abs(two!.y - three!.y)).toBeLessThan(1);
|
||||||
|
expect(Math.abs(one!.x - four!.x)).toBeLessThan(1);
|
||||||
|
expect(Math.abs(four!.x - seven!.x)).toBeLessThan(1);
|
||||||
|
expect(four!.y).toBeGreaterThan(one!.y + one!.height);
|
||||||
|
expect(seven!.y).toBeGreaterThan(four!.y + four!.height);
|
||||||
|
expect(Math.abs(erase!.x - one!.x)).toBeLessThan(1);
|
||||||
|
expect(
|
||||||
|
Math.abs(erase!.x + erase!.width - (three!.x + three!.width)),
|
||||||
|
).toBeLessThan(1);
|
||||||
|
expect(erase!.y).toBeGreaterThan(nine!.y + nine!.height);
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Zoom board out" }).click();
|
||||||
|
await expect(page.getByLabel("Board zoom level")).toHaveText("75%");
|
||||||
|
const reducedWidth = (await frame.boundingBox())?.width ?? 0;
|
||||||
|
expect(reducedWidth / fittedWidth).toBeCloseTo(0.75, 2);
|
||||||
|
await page.getByRole("button", { name: "Fit board" }).click();
|
||||||
|
|
||||||
|
const baselineSidebarWidth = (await sidebar.boundingBox())?.width ?? 0;
|
||||||
|
const baselineBoardWidth = (await boardColumn.boundingBox())?.width ?? 0;
|
||||||
|
for (const workspace of ["Set", "Generate", "Solve", "Helpers"]) {
|
||||||
|
await page.getByRole("button", { name: workspace, exact: true }).click();
|
||||||
|
expect((await sidebar.boundingBox())?.width ?? 0).toBeCloseTo(
|
||||||
|
baselineSidebarWidth,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
expect((await boardColumn.boundingBox())?.width ?? 0).toBeCloseTo(
|
||||||
|
baselineBoardWidth,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole("tab", { name: "Candidate links" }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "Links and houses" }),
|
||||||
|
).toBeVisible();
|
||||||
|
expect(
|
||||||
|
await sidebar.evaluate(
|
||||||
|
(element) => element.scrollWidth <= element.clientWidth + 1,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Solve", exact: true }).click();
|
||||||
|
await page.getByRole("button", { name: "Build logical solve path" }).click();
|
||||||
|
const solveSteps = page.locator(".solve-step");
|
||||||
|
await expect(solveSteps.first()).toBeVisible({ timeout: 60_000 });
|
||||||
|
expect(await solveSteps.count()).toBeGreaterThan(0);
|
||||||
|
expect(
|
||||||
|
await solveSteps.evaluateAll((steps) =>
|
||||||
|
steps.every(
|
||||||
|
(step) =>
|
||||||
|
step.scrollHeight <= step.clientHeight + 1 &&
|
||||||
|
step.scrollWidth <= step.clientWidth + 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
test("replaces setter cages and generates a rated variant", async ({
|
test("replaces setter cages and generates a rated variant", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@@ -9,19 +9,35 @@ describe("BoardViewport", () => {
|
|||||||
|
|
||||||
it("zooms, fits and restores the persisted scale", async () => {
|
it("zooms, fits and restores the persisted scale", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const { unmount } = render(
|
const { container, unmount } = render(
|
||||||
<BoardViewport>
|
<BoardViewport>
|
||||||
<div>Board</div>
|
<div>Board</div>
|
||||||
</BoardViewport>,
|
</BoardViewport>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByLabelText("Board zoom level")).toHaveTextContent("100%");
|
expect(screen.getByLabelText("Board zoom level")).toHaveTextContent("100%");
|
||||||
|
expect(container.querySelector(".board-viewport")).not.toHaveClass(
|
||||||
|
"is-zoomed-in",
|
||||||
|
);
|
||||||
|
await user.click(screen.getByRole("button", { name: "Zoom board out" }));
|
||||||
|
expect(screen.getByLabelText("Board zoom level")).toHaveTextContent("75%");
|
||||||
|
expect(container.querySelector(".board-viewport__canvas")).toHaveStyle({
|
||||||
|
width: "75%",
|
||||||
|
maxWidth: "36rem",
|
||||||
|
});
|
||||||
|
expect(container.querySelector(".board-viewport")).not.toHaveClass(
|
||||||
|
"is-zoomed-in",
|
||||||
|
);
|
||||||
|
await user.click(screen.getByRole("button", { name: "Zoom board in" }));
|
||||||
await user.click(screen.getByRole("button", { name: "Zoom board in" }));
|
await user.click(screen.getByRole("button", { name: "Zoom board in" }));
|
||||||
expect(screen.getByLabelText("Board zoom level")).toHaveTextContent("125%");
|
expect(screen.getByLabelText("Board zoom level")).toHaveTextContent("125%");
|
||||||
|
expect(container.querySelector(".board-viewport")).toHaveClass(
|
||||||
|
"is-zoomed-in",
|
||||||
|
);
|
||||||
expect(localStorage.getItem(BOARD_SCALE_STORAGE_KEY)).toBe("1.25");
|
expect(localStorage.getItem(BOARD_SCALE_STORAGE_KEY)).toBe("1.25");
|
||||||
unmount();
|
unmount();
|
||||||
|
|
||||||
render(
|
const restored = render(
|
||||||
<BoardViewport>
|
<BoardViewport>
|
||||||
<div>Board</div>
|
<div>Board</div>
|
||||||
</BoardViewport>,
|
</BoardViewport>,
|
||||||
@@ -29,6 +45,9 @@ describe("BoardViewport", () => {
|
|||||||
expect(screen.getByLabelText("Board zoom level")).toHaveTextContent("125%");
|
expect(screen.getByLabelText("Board zoom level")).toHaveTextContent("125%");
|
||||||
await user.click(screen.getByRole("button", { name: "Fit board" }));
|
await user.click(screen.getByRole("button", { name: "Fit board" }));
|
||||||
expect(screen.getByLabelText("Board zoom level")).toHaveTextContent("100%");
|
expect(screen.getByLabelText("Board zoom level")).toHaveTextContent("100%");
|
||||||
|
expect(restored.container.querySelector(".board-viewport")).not.toHaveClass(
|
||||||
|
"is-zoomed-in",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("offers explicit pan mode and board-scoped zoom shortcuts", async () => {
|
it("offers explicit pan mode and board-scoped zoom shortcuts", async () => {
|
||||||
|
|||||||
@@ -3,7 +3,42 @@ import userEvent from "@testing-library/user-event";
|
|||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import { NumberPad } from "../../src/components/NumberPad";
|
import { NumberPad } from "../../src/components/NumberPad";
|
||||||
|
|
||||||
describe("NumberPad colour accessibility", () => {
|
describe("NumberPad", () => {
|
||||||
|
it("marks standard and large digit layouts and exposes a full-row erase key", () => {
|
||||||
|
const { container, rerender } = render(
|
||||||
|
<NumberPad
|
||||||
|
size={9}
|
||||||
|
mode="value"
|
||||||
|
onMode={vi.fn()}
|
||||||
|
onValue={vi.fn()}
|
||||||
|
onErase={vi.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole("group", { name: "Digits" })).toHaveClass(
|
||||||
|
"digit-pad",
|
||||||
|
);
|
||||||
|
expect(container.querySelector(".digit-pad")).not.toHaveClass(
|
||||||
|
"digit-pad--wide",
|
||||||
|
);
|
||||||
|
expect(screen.getByRole("button", { name: "Erase" })).toHaveClass(
|
||||||
|
"erase-key",
|
||||||
|
);
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<NumberPad
|
||||||
|
size={16}
|
||||||
|
mode="value"
|
||||||
|
onMode={vi.fn()}
|
||||||
|
onValue={vi.fn()}
|
||||||
|
onErase={vi.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(container.querySelector(".digit-pad")).toHaveClass(
|
||||||
|
"digit-pad--wide",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("names each colour by both hue and pattern", async () => {
|
it("names each colour by both hue and pattern", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const onValue = vi.fn();
|
const onValue = vi.fn();
|
||||||
|
|||||||
Reference in New Issue
Block a user