fix(playlists): what the first review of bulk actions found
An Esc during a pointer drag is spent on the drag and leaves the selection. A wheel over the rail re-reads the target each frame, so a drop lands on the row under the pointer. After a bulk removal the focus goes to the next row left, and to the list's menu when the list is emptied. remove-bulk names the videos that went, and its Undo brings back only those. An Undo of a move keeps each row (and when it was saved), leaves out a video that moved on from the target, closes up its place, and says how many came back. Delete with every tick hidden removes nothing and says why. A list gone before the lock answers 404, and a body without its videos 422. One removal path now: the single row goes through the bulk one, the same toast and undo, which also retires the add-at-position route. railDrop reuses the store and scrollParent, the drag image the overlay and the z-index token, and the sensors keep their number. The count is read out from a region that outlives the band. Tests fill the gaps the review named, each seen failing.
This commit is contained in:
@@ -94,6 +94,50 @@ async function pinOnTouch(page: Page, project: string) {
|
||||
});
|
||||
}
|
||||
|
||||
test.describe("acting on the selected rows", { tag: "@playlists" }, () => {
|
||||
test.afterEach(async ({ page }) => {
|
||||
for (const id of made.splice(0)) await page.request.delete(`/api/playlists/${String(id)}`);
|
||||
});
|
||||
|
||||
const box = (page: Page, title: string) =>
|
||||
page.getByRole("checkbox", { name: `Select “${title}”` });
|
||||
|
||||
test("a Shift range, the band's Remove, and its Undo back to the places", async ({
|
||||
page,
|
||||
}, info) => {
|
||||
const { videos, from } = await setUp(page, `band ${info.project.name}`, info.project.name);
|
||||
await box(page, videos[0]!.title).click();
|
||||
await box(page, videos[1]!.title).click({ modifiers: ["Shift"] });
|
||||
await expect(box(page, videos[1]!.title)).toBeChecked();
|
||||
await expect(box(page, videos[2]!.title)).not.toBeChecked();
|
||||
await page.getByRole("button", { name: "Remove", exact: true }).click();
|
||||
await expect(page.locator("tbody tr")).toHaveCount(1);
|
||||
await expect.poll(() => contents(page, from)).toEqual([videos[2]!.id]);
|
||||
await page.getByRole("button", { name: "Undo", exact: true }).last().click();
|
||||
await expect.poll(() => contents(page, from)).toEqual(videos.map((v) => v.id));
|
||||
await expect(page.locator("tbody tr")).toHaveCount(3);
|
||||
});
|
||||
|
||||
test("x ticks the walked row, Esc clears, and Delete takes the selection and goes on", async ({
|
||||
page,
|
||||
}, info) => {
|
||||
const { videos, from } = await setUp(page, `keys ${info.project.name}`, info.project.name);
|
||||
const firstRow = page.locator("tbody tr").first();
|
||||
await firstRow.focus();
|
||||
await page.keyboard.press("x");
|
||||
await expect(box(page, videos[0]!.title)).toBeChecked();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(box(page, videos[0]!.title)).not.toBeChecked();
|
||||
await page.keyboard.press("x");
|
||||
await page.keyboard.press("j");
|
||||
await page.keyboard.press("x");
|
||||
await page.keyboard.press("Delete");
|
||||
await expect.poll(() => contents(page, from)).toEqual([videos[2]!.id]);
|
||||
await expect(page.locator("tbody tr")).toHaveCount(1);
|
||||
await expect(firstRow).toBeFocused();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("dragging onto a list", { tag: "@playlists" }, () => {
|
||||
test.afterEach(async ({ page }) => {
|
||||
await page.mouse.up();
|
||||
@@ -139,6 +183,8 @@ test.describe("dragging onto a list", { tag: "@playlists" }, () => {
|
||||
await carryToRail(page, grip, target);
|
||||
const image = page.getByText("2 videos", { exact: true });
|
||||
await expect(image).toBeVisible();
|
||||
// Above every layer, by the tooltip's token: the overlay wrapping it takes the level.
|
||||
expect(await image.evaluate((el) => getComputedStyle(el.parentElement!).zIndex)).toBe("100");
|
||||
// Below the pointer, so it leaves the list's name to read.
|
||||
const { y } = await centre(target);
|
||||
expect((await image.boundingBox())!.y).toBeGreaterThan(y);
|
||||
@@ -184,6 +230,9 @@ test.describe("dragging onto a list", { tag: "@playlists" }, () => {
|
||||
`drag cancel ${info.project.name}`,
|
||||
info.project.name,
|
||||
);
|
||||
// A tick on another row: the Esc is the drag's, and leaves the selection alone.
|
||||
const ticked = page.getByRole("checkbox", { name: `Select “${videos[2]!.title}”` });
|
||||
await ticked.click();
|
||||
const grip = page.getByRole("button", { name: `Reorder “${videos[0]!.title}”`, exact: false });
|
||||
await carryToRail(page, grip, target);
|
||||
await expect(target).toHaveClass(/ring-2/);
|
||||
@@ -192,6 +241,7 @@ test.describe("dragging onto a list", { tag: "@playlists" }, () => {
|
||||
await page.mouse.up();
|
||||
expect(await contents(page, from)).toEqual(videos.map((v) => v.id));
|
||||
expect(await contents(page, to)).toEqual([]);
|
||||
await expect(ticked).toBeChecked();
|
||||
});
|
||||
|
||||
test("a key still reorders a row, alone", async ({ page }, info) => {
|
||||
|
||||
Reference in New Issue
Block a user