Test grouped drag constraints and orientation
All checks were successful
Deploy Sokko G / deploy (push) Successful in 19s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 19s
This commit is contained in:
parent
62138120ee
commit
6de016ec7b
4 changed files with 124 additions and 3 deletions
|
|
@ -45,3 +45,4 @@
|
||||||
{"id":"int-37bf322d7d6ae9d06e033d9f16b7f057","kind":"field_change","created_at":"2026-09-12T21:33:50.250082802Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
{"id":"int-37bf322d7d6ae9d06e033d9f16b7f057","kind":"field_change","created_at":"2026-09-12T21:33:50.250082802Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
||||||
{"id":"int-cc87d51843f7ee62beb6445939aa30d5","kind":"field_change","created_at":"2026-09-13T06:35:52.804580049Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
{"id":"int-cc87d51843f7ee62beb6445939aa30d5","kind":"field_change","created_at":"2026-09-13T06:35:52.804580049Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
||||||
{"id":"int-e554bed2958d709f2965a55fea9aa956","kind":"field_change","created_at":"2026-09-13T06:53:17.211928645Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
{"id":"int-e554bed2958d709f2965a55fea9aa956","kind":"field_change","created_at":"2026-09-13T06:53:17.211928645Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
||||||
|
{"id":"int-6f399629fa8cc8212d36ca21853eaa52","kind":"field_change","created_at":"2026-09-13T17:46:00.430152831Z","actor":"Shinuwa","issue_id":"sokko-g-zsd","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
||||||
|
|
|
||||||
49
e2e/fixtures/grouped-reorder-probe.js
Normal file
49
e2e/fixtures/grouped-reorder-probe.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Rôle : expose la politique réelle de réorganisation dans une géométrie stable sous StrictMode.
|
||||||
|
import { createElement as h, StrictMode } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { flushSync } from "react-dom";
|
||||||
|
import { useGroupedReorder } from "../../website/src/hooks/useGroupedReorder.js";
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ id: "a", group: "A", parent: "p" }, { id: "b", group: "A", parent: "p" },
|
||||||
|
{ id: "c", group: "B", parent: "p" }, { id: "d", group: "B", parent: "q" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export function mountGroupedReorderProbe(element) {
|
||||||
|
const root = createRoot(element);
|
||||||
|
const events = [];
|
||||||
|
let options = {
|
||||||
|
orientation: "vertical", hierarchy: { enabled: true, stickyParents: true },
|
||||||
|
reorderFeatures: { item: { groupChange: true }, group: { reorder: true } }, veto: false
|
||||||
|
};
|
||||||
|
function Probe({ config }) {
|
||||||
|
const reorder = useGroupedReorder({
|
||||||
|
...config, namespace: "probe", items,
|
||||||
|
getItemGroup: (item) => item.group, getParentId: (item) => item.parent,
|
||||||
|
canMoveItem: () => !config.veto, canMoveGroup: () => !config.veto,
|
||||||
|
onItemMove: (operation) => events.push(operation), onGroupMove: (operation) => events.push(operation)
|
||||||
|
});
|
||||||
|
const tile = (id, props, child) => h("div", {
|
||||||
|
...props, key: id, "data-testid": id,
|
||||||
|
style: { height: 96, width: 220, border: "1px solid" }
|
||||||
|
}, child);
|
||||||
|
return h("section", null,
|
||||||
|
h("output", { "aria-label": "Dragging", style: { display: "block", height: 24 } },
|
||||||
|
reorder.itemReorder.draggingId || reorder.groupReorder.draggingId ? "active" : ""),
|
||||||
|
h("div", { style: { display: "grid", gridTemplateColumns: "repeat(3, 222px)", gap: 12 } },
|
||||||
|
...items.map((item) => tile(item.id, reorder.getItemProps({ itemId: item.id, groupId: item.group, parentId: item.parent }),
|
||||||
|
h("button", { type: "button", onPointerDown: (event) => reorder.itemReorder.startDrag(event, item.id) }, `Item ${item.id}`))),
|
||||||
|
...["A", "B"].map((groupId) => tile(`group-${groupId}`, reorder.getGroupProps({ groupId, parentId: "p" }),
|
||||||
|
h("button", { type: "button", onPointerDown: (event) => reorder.groupReorder.startDrag(event, { groupId, parentId: "p" }) }, `Group ${groupId}`))),
|
||||||
|
tile("boundary", reorder.getGroupBoundaryProps({ groupId: "B", parentId: "p", placement: "after" }), "Boundary"),
|
||||||
|
tile("ungrouped", reorder.getUngroupedDropProps({ parentId: "p" }), "Ungrouped"),
|
||||||
|
tile("alien", { ...reorder.getItemProps({ itemId: "alien", groupId: "A", parentId: "p" }), "data-reorder-namespace": "other" }, "Other namespace")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
function update(patch = {}) {
|
||||||
|
options = { ...options, ...patch };
|
||||||
|
flushSync(() => root.render(h(StrictMode, null, h(Probe, { config: options }))));
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
return { events, update, unmount: () => root.unmount() };
|
||||||
|
}
|
||||||
74
e2e/grouped-reorder.spec.js
Normal file
74
e2e/grouped-reorder.spec.js
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Rôle : vérifie les contraintes de groupes, de parents et d'orientation avec de vrais gestes pointeur.
|
||||||
|
/* global window, document */
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { fileURLToPath, URL } from "node:url";
|
||||||
|
|
||||||
|
const probeUrl = `/@fs/${fileURLToPath(new URL("./fixtures/grouped-reorder-probe.js", import.meta.url))}`;
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.evaluate(async (url) => {
|
||||||
|
const { mountGroupedReorderProbe } = await import(url);
|
||||||
|
document.getElementById("app").style.display = "none";
|
||||||
|
const element = document.createElement("div");
|
||||||
|
document.body.prepend(element);
|
||||||
|
window.groupedProbe = mountGroupedReorderProbe(element);
|
||||||
|
}, probeUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function configure(page, options) {
|
||||||
|
await page.evaluate((options) => window.groupedProbe.update(options), options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function drop(page, source, target, x = 0.2, y = 0.8) {
|
||||||
|
await page.getByRole("button", { name: source, exact: true }).hover();
|
||||||
|
await page.mouse.down();
|
||||||
|
await expect(page.getByLabel("Dragging")).toHaveText("active");
|
||||||
|
const box = await page.getByTestId(target).boundingBox();
|
||||||
|
await page.mouse.move(box.x + box.width * x, box.y + box.height * y, { steps: 3 });
|
||||||
|
await page.mouse.up();
|
||||||
|
await expect(page.getByLabel("Dragging")).toBeEmpty();
|
||||||
|
return page.evaluate(() => window.groupedProbe.events.splice(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
test("item moves obey parent boundaries, group changes, namespace isolation and custom vetoes", async ({ page }) => {
|
||||||
|
expect(await drop(page, "Item a", "b")).toEqual([{
|
||||||
|
type: "item", sourceId: "a", targetId: "b", targetType: "item", placement: "after",
|
||||||
|
sourceGroup: "A", targetGroup: "A", sourceParentId: "p", targetParentId: "p"
|
||||||
|
}]);
|
||||||
|
expect(await drop(page, "Item a", "d")).toEqual([]);
|
||||||
|
expect(await drop(page, "Item a", "alien")).toEqual([]);
|
||||||
|
await configure(page, { reorderFeatures: { item: { groupChange: false } } });
|
||||||
|
expect(await drop(page, "Item a", "c")).toEqual([]);
|
||||||
|
await configure(page, { reorderFeatures: { item: { groupChange: true } } });
|
||||||
|
expect(await drop(page, "Item a", "c")).toMatchObject([{ targetId: "c", targetGroup: "B" }]);
|
||||||
|
await configure(page, { hierarchy: { enabled: true, stickyParents: false } });
|
||||||
|
expect(await drop(page, "Item a", "d")).toMatchObject([{ sourceParentId: "p", targetParentId: "q" }]);
|
||||||
|
await configure(page, { veto: true });
|
||||||
|
expect(await drop(page, "Item a", "b")).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("orientation changes placement while explicit boundaries and ungrouped drops require permission", async ({ page }) => {
|
||||||
|
expect(await drop(page, "Item a", "b")).toMatchObject([{ placement: "after" }]);
|
||||||
|
await configure(page, { orientation: "horizontal" });
|
||||||
|
expect(await drop(page, "Item a", "b")).toMatchObject([{ placement: "before" }]);
|
||||||
|
expect(await drop(page, "Item a", "b", 0.8, 0.2)).toMatchObject([{ placement: "after" }]);
|
||||||
|
expect(await drop(page, "Item a", "boundary")).toEqual([]);
|
||||||
|
expect(await drop(page, "Item a", "ungrouped")).toEqual([]);
|
||||||
|
await configure(page, { reorderFeatures: { item: { boundaryDrop: true, ungroupedDrop: true } } });
|
||||||
|
expect(await drop(page, "Item a", "boundary", 0.1, 0.1)).toMatchObject([{ targetType: "boundary", targetGroup: "B", placement: "after" }]);
|
||||||
|
expect(await drop(page, "Item a", "ungrouped")).toMatchObject([{ targetType: "ungrouped", targetGroup: "" }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("group dragging rejects itself, disabled and root-only moves and respects custom vetoes", async ({ page }) => {
|
||||||
|
expect(await drop(page, "Group A", "group-A")).toEqual([]);
|
||||||
|
expect(await drop(page, "Group A", "group-B")).toEqual([{
|
||||||
|
type: "group", sourceId: "A", targetId: "B", targetType: "group", placement: "after",
|
||||||
|
sourceGroup: "A", targetGroup: "B", sourceParentId: "p", targetParentId: "p"
|
||||||
|
}]);
|
||||||
|
await configure(page, { reorderFeatures: { group: { reorder: false } } });
|
||||||
|
expect(await drop(page, "Group A", "group-B")).toEqual([]);
|
||||||
|
await configure(page, { reorderFeatures: { group: { reorder: true, rootOnly: true } } });
|
||||||
|
expect(await drop(page, "Group A", "group-B")).toEqual([]);
|
||||||
|
await configure(page, { reorderFeatures: { group: { reorder: true } }, veto: true });
|
||||||
|
expect(await drop(page, "Group A", "group-B")).toEqual([]);
|
||||||
|
});
|
||||||
|
|
@ -29,7 +29,6 @@ test("toolbox cards and pages retain their public UI conventions", async () => {
|
||||||
const storageQuota = await readFile("website/src/components/StorageQuota.jsx", "utf8");
|
const storageQuota = await readFile("website/src/components/StorageQuota.jsx", "utf8");
|
||||||
const importButton = await readFile("website/src/components/ImportButton.jsx", "utf8");
|
const importButton = await readFile("website/src/components/ImportButton.jsx", "utf8");
|
||||||
const guidedTooltipTour = await readFile("website/src/components/GuidedTooltipTour.jsx", "utf8");
|
const guidedTooltipTour = await readFile("website/src/components/GuidedTooltipTour.jsx", "utf8");
|
||||||
const groupedReorderHook = await readFile("website/src/hooks/useGroupedReorder.js", "utf8");
|
|
||||||
|
|
||||||
assert.match(toolboxActions, /creationTemplate/);
|
assert.match(toolboxActions, /creationTemplate/);
|
||||||
assert.match(toolboxActions, /type: "memos"/);
|
assert.match(toolboxActions, /type: "memos"/);
|
||||||
|
|
@ -99,8 +98,6 @@ test("toolbox cards and pages retain their public UI conventions", async () => {
|
||||||
assert.match(storageQuota, /role="progressbar"/);
|
assert.match(storageQuota, /role="progressbar"/);
|
||||||
assert.match(importButton, /export function ImportButton/);
|
assert.match(importButton, /export function ImportButton/);
|
||||||
assert.match(importButton, /accept="application\/json"/);
|
assert.match(importButton, /accept="application\/json"/);
|
||||||
assert.match(groupedReorderHook, /data-reorder-orientation/);
|
|
||||||
assert.match(groupedReorderHook, /operation\.sourceParentId === operation\.targetParentId/);
|
|
||||||
assert.match(toolboxCoverEditor, /image\/webp/);
|
assert.match(toolboxCoverEditor, /image\/webp/);
|
||||||
assert.match(toolboxCoverEditor, /OUTPUT_WIDTH = 620/);
|
assert.match(toolboxCoverEditor, /OUTPUT_WIDTH = 620/);
|
||||||
assert.match(toolboxCoverEditor, /OUTPUT_HEIGHT = 248/);
|
assert.match(toolboxCoverEditor, /OUTPUT_HEIGHT = 248/);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue