mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-09 00:12:21 +02:00
108 lines
5.4 KiB
JavaScript
108 lines
5.4 KiB
JavaScript
const { test, expect } = require('@playwright/test');
|
|
const { editorState, openBlankEditor, reopenDraft, waitForDraft } = require('./helpers.js');
|
|
|
|
test('group drag reorders the complete subtree with undo and server persistence', async ({ page, request }) => {
|
|
await openBlankEditor(page, { width: 420, height: 300 }, 'Group reorder E2E');
|
|
await page.locator('.ge-layer-item[data-layer-id]').filter({ hasText: 'Background' }).first().click({ modifiers: ['Control'] });
|
|
await page.locator('#ge-group-selected').click();
|
|
let current = await editorState(page);
|
|
const group = current.groups[0];
|
|
const originalOrder = current.layers.map(layer => layer.id);
|
|
|
|
await page.locator('#ge-add-layer').click();
|
|
await page.locator('.ge-add-layer-menu [data-layer-kind="raster"]').click();
|
|
current = await editorState(page);
|
|
const looseId = current.activeLayerId;
|
|
expect(current.layers.map(layer => layer.id)).toEqual([...originalOrder, looseId]);
|
|
|
|
const handle = page.locator(`.ge-layer-group-row[data-group-id="${group.id}"] .ge-layer-group-drag`);
|
|
const looseRow = page.locator(`.ge-layer-item[data-layer-id="${looseId}"]`);
|
|
const handleBox = await handle.boundingBox();
|
|
const looseBox = await looseRow.boundingBox();
|
|
const dragReadiness = await page.evaluate(async ({ groupId, point }) => {
|
|
const { state } = await import('/static/js/editor/state.js');
|
|
const { groupSiblingUnits } = await import('/static/js/editor/layer-groups.js');
|
|
const group = state.layerGroups.find(item => item.id === groupId);
|
|
const hit = document.elementFromPoint(point.x, point.y);
|
|
return {
|
|
siblingIds: groupSiblingUnits(state, group.parentId || null).map(unit => unit.id),
|
|
hitHandle: !!hit?.closest('.ge-layer-group-drag'),
|
|
};
|
|
}, { groupId: group.id, point: { x: handleBox.x + handleBox.width / 2, y: handleBox.y + handleBox.height / 2 } });
|
|
expect(dragReadiness.hitHandle, JSON.stringify(dragReadiness)).toBe(true);
|
|
expect(dragReadiness.siblingIds).toEqual([group.id, looseId]);
|
|
await page.mouse.move(handleBox.x + handleBox.width / 2, handleBox.y + handleBox.height / 2);
|
|
await page.mouse.down();
|
|
await page.mouse.move(looseBox.x + looseBox.width / 2, looseBox.y + 1, { steps: 8 });
|
|
await expect(page.locator('.ge-group-drop-line')).toBeVisible();
|
|
await page.mouse.up();
|
|
|
|
current = await editorState(page);
|
|
expect(current.layers.map(layer => layer.id)).toEqual([looseId, ...originalOrder]);
|
|
expect(current.groups[0].layerIds).toEqual(group.layerIds);
|
|
|
|
await page.locator('#ge-undo').click();
|
|
expect((await editorState(page)).layers.map(layer => layer.id)).toEqual([...originalOrder, looseId]);
|
|
await page.locator('#ge-redo').click();
|
|
expect((await editorState(page)).layers.map(layer => layer.id)).toEqual([looseId, ...originalOrder]);
|
|
|
|
const draftId = await waitForDraft(page);
|
|
const beforeReopen = await editorState(page);
|
|
await reopenDraft(page, draftId);
|
|
current = await editorState(page);
|
|
expect(current.layers.map(layer => layer.id)).toEqual(beforeReopen.layers.map(layer => layer.id));
|
|
expect(current.groups).toEqual(beforeReopen.groups);
|
|
await request.delete(`/api/editor-drafts/${encodeURIComponent(draftId)}`);
|
|
});
|
|
|
|
test('mobile layer drag handle supports long-press reorder', async ({ browser, browserName }) => {
|
|
test.skip(browserName !== 'chromium', 'Uses Chromium CDP touch injection');
|
|
const context = await browser.newContext({
|
|
viewport: { width: 390, height: 844 },
|
|
isMobile: true,
|
|
hasTouch: true,
|
|
serviceWorkers: 'block',
|
|
});
|
|
const page = await context.newPage();
|
|
try {
|
|
await openBlankEditor(page, { width: 320, height: 240 }, 'Mobile layer reorder');
|
|
await page.locator('.ge-layers-header').click();
|
|
await expect(page.locator('.ge-right-panel')).toHaveClass(/expanded/);
|
|
const before = await editorState(page);
|
|
// State is bottom-to-top; the panel is top-to-bottom.
|
|
const dragId = before.layers[0].id;
|
|
const targetId = before.layers[before.layers.length - 1].id;
|
|
const bottomHandle = page.locator(`.ge-layer-item[data-layer-id="${dragId}"] .ge-layer-drag`);
|
|
const topRow = page.locator(`.ge-layer-item[data-layer-id="${targetId}"]`);
|
|
await bottomHandle.scrollIntoViewIfNeeded();
|
|
const handleBox = await bottomHandle.boundingBox();
|
|
const topBox = await topRow.boundingBox();
|
|
expect(handleBox.width).toBeGreaterThanOrEqual(24);
|
|
const point = { x: handleBox.x + handleBox.width / 2, y: handleBox.y + handleBox.height / 2 };
|
|
const hitHandle = await page.evaluate(({ x, y }) => {
|
|
const hit = document.elementFromPoint(x, y);
|
|
return !!hit?.closest('.ge-layer-drag');
|
|
}, point);
|
|
expect(hitHandle).toBe(true);
|
|
const cdp = await page.context().newCDPSession(page);
|
|
await cdp.send('Input.dispatchTouchEvent', {
|
|
type: 'touchStart',
|
|
touchPoints: [{ x: point.x, y: point.y, id: 1, radiusX: 8, radiusY: 8 }],
|
|
});
|
|
await page.waitForTimeout(450);
|
|
await expect(page.locator(`.ge-layer-item[data-layer-id="${dragId}"]`)).toHaveClass(/dragging/);
|
|
await cdp.send('Input.dispatchTouchEvent', {
|
|
type: 'touchMove',
|
|
touchPoints: [{ x: point.x, y: topBox.y + 1, id: 1, radiusX: 8, radiusY: 8 }],
|
|
});
|
|
await cdp.send('Input.dispatchTouchEvent', {
|
|
type: 'touchEnd',
|
|
touchPoints: [],
|
|
});
|
|
await page.waitForTimeout(120);
|
|
expect((await editorState(page)).layers.map(layer => layer.id)).toEqual([targetId, dragId]);
|
|
} finally {
|
|
await context.close();
|
|
}
|
|
});
|