|
11 | 11 | * workaround is `correctParentTransform(ref)` via `MotionConfig |
12 | 12 | * transformPagePoint`. These tests pin that workaround so future drag / |
13 | 13 | * projection refactors can't silently regress it. |
| 14 | + * |
| 15 | + * Pointer coords are element-relative (same pattern as `drag-scaled-parent.ts`). |
| 16 | + * PanSession applies pointermove on the next animation frame, so end-state |
| 17 | + * checks use `.should()` — a single `.then()` after a fixed wait races RAF |
| 18 | + * on slow CI. |
14 | 19 | */ |
15 | | -function domOrder(): Cypress.Chainable<string[]> { |
16 | | - return cy.get("#reorder-group [data-testid]").then(($items) => |
17 | | - Cypress._.map($items, (el) => el.getAttribute("data-testid") as string) |
| 20 | +function itemOrder($items: any): string[] { |
| 21 | + return Cypress._.map( |
| 22 | + $items, |
| 23 | + (el) => el.getAttribute("data-testid") as string |
18 | 24 | ) |
19 | 25 | } |
20 | 26 |
|
21 | 27 | describe("Reorder inside a scaled parent (#2449 / #2750)", () => { |
22 | 28 | it("dragged item tracks the cursor with correctParentTransform (scale 0.5)", () => { |
23 | 29 | cy.visit("?test=reorder-scaled-parent&corrected=true&scale=0.5") |
24 | | - .wait(300) |
25 | | - .get("[data-testid='item-0']") |
26 | | - .then(($el: any) => { |
27 | | - const start = $el[0].getBoundingClientRect() |
28 | | - const startMidX = start.left + start.width / 2 |
29 | | - const startMidY = start.top + start.height / 2 |
30 | | - |
31 | | - cy.wrap($el) |
32 | | - .trigger("pointerdown", startMidX, startMidY, { |
33 | | - force: true, |
34 | | - }) |
35 | | - .wait(50) |
36 | | - // Move past the drag threshold. |
37 | | - .trigger("pointermove", startMidX, startMidY + 5, { |
38 | | - force: true, |
39 | | - }) |
40 | | - .wait(50) |
41 | | - // Move the pointer 80px down the screen. |
42 | | - .trigger("pointermove", startMidX, startMidY + 80, { |
43 | | - force: true, |
44 | | - }) |
45 | | - .wait(80) |
46 | | - .then(([item]: any) => { |
47 | | - const moved = item.getBoundingClientRect() |
48 | | - const movedMidY = moved.top + moved.height / 2 |
49 | | - const screenDelta = movedMidY - startMidY |
50 | | - |
51 | | - // The element must follow the cursor (~80px on screen), |
52 | | - // not 80 / scale (160px) as it would without the |
53 | | - // transformPagePoint correction. |
54 | | - expect(screenDelta).to.be.greaterThan(60) |
55 | | - expect(screenDelta).to.be.lessThan(100) |
56 | | - }) |
57 | | - .trigger("pointerup", { force: true }) |
58 | | - }) |
59 | | - }) |
60 | | - |
61 | | - it("reorders correctly and settles aligned with correctParentTransform", () => { |
62 | | - cy.visit("?test=reorder-scaled-parent&corrected=true&scale=0.5") |
63 | | - .wait(300) |
64 | | - |
65 | | - domOrder().should("deep.equal", [ |
66 | | - "item-0", |
67 | | - "item-1", |
68 | | - "item-2", |
69 | | - "item-3", |
70 | | - ]) |
| 30 | + cy.get("[data-testid='item-0']").should("be.visible") |
71 | 31 |
|
72 | 32 | cy.get("[data-testid='item-0']").then(($el: any) => { |
73 | 33 | const start = $el[0].getBoundingClientRect() |
74 | | - const startMidX = start.left + start.width / 2 |
75 | 34 | const startMidY = start.top + start.height / 2 |
76 | 35 |
|
77 | | - let chain = cy |
78 | | - .wrap($el) |
79 | | - .trigger("pointerdown", startMidX, startMidY, { force: true }) |
| 36 | + cy.wrap($el) |
| 37 | + .trigger("pointerdown", 20, 10, { force: true }) |
80 | 38 | .wait(50) |
81 | | - // Move past the drag threshold first. |
82 | | - .trigger("pointermove", startMidX, startMidY + 6, { |
83 | | - force: true, |
84 | | - }) |
| 39 | + // Move past the drag threshold. |
| 40 | + .trigger("pointermove", 20, 16, { force: true }) |
85 | 41 | .wait(50) |
| 42 | + // Move the pointer 80px down the screen. |
| 43 | + .trigger("pointermove", 20, 90, { force: true }) |
86 | 44 |
|
87 | | - // Steady drag downward. With scale 0.5 the offset is corrected |
88 | | - // into local space, so the on-screen distance must be ~2x the |
89 | | - // local row pitch to cross item-1's centre. Incremental moves |
90 | | - // keep a non-zero velocity so checkReorder engages. |
91 | | - for (let i = 1; i <= 6; i++) { |
92 | | - chain = chain |
93 | | - .trigger("pointermove", startMidX, startMidY + i * 14, { |
94 | | - force: true, |
95 | | - }) |
96 | | - .wait(60) |
97 | | - } |
| 45 | + // Drag tracking is 1:1, not a tween: retry until the frame loop |
| 46 | + // applies the pointermove. A wrong scale (e.g. 40px uncorrected) |
| 47 | + // stays wrong and still fails. |
| 48 | + cy.wrap($el).should(($item: any) => { |
| 49 | + const moved = $item[0].getBoundingClientRect() |
| 50 | + const screenDelta = moved.top + moved.height / 2 - startMidY |
98 | 51 |
|
99 | | - chain.trigger("pointerup", { force: true }).wait(700) |
| 52 | + // The element must follow the cursor (~80px on screen), |
| 53 | + // not 80 / scale (160px) or 80 * scale (40px) as it would |
| 54 | + // without the transformPagePoint correction. |
| 55 | + expect(screenDelta).to.be.greaterThan(60) |
| 56 | + expect(screenDelta).to.be.lessThan(100) |
| 57 | + }) |
| 58 | + |
| 59 | + cy.wrap($el).trigger("pointerup", { force: true }) |
| 60 | + }) |
| 61 | + }) |
| 62 | + |
| 63 | + it("reorders correctly and settles aligned with correctParentTransform", () => { |
| 64 | + cy.visit("?test=reorder-scaled-parent&corrected=true&scale=0.5") |
| 65 | + cy.get("[data-testid='item-0']").should("be.visible") |
| 66 | + |
| 67 | + cy.get("#reorder-group [data-testid]").should(($items) => { |
| 68 | + expect(itemOrder($items)).to.deep.equal([ |
| 69 | + "item-0", |
| 70 | + "item-1", |
| 71 | + "item-2", |
| 72 | + "item-3", |
| 73 | + ]) |
100 | 74 | }) |
101 | 75 |
|
102 | | - // item-0 should have moved down past item-1 (a swap occurred) ... |
103 | | - domOrder().then((order) => { |
| 76 | + // Element-relative moves. Scale 0.5 → ~40px screen is ~80px local, |
| 77 | + // enough to cross item-1's centre (needs >35px local) without |
| 78 | + // racing through the whole list. |
| 79 | + cy.get("[data-testid='item-0']") |
| 80 | + .trigger("pointerdown", 20, 10, { force: true }) |
| 81 | + .wait(50) |
| 82 | + .trigger("pointermove", 20, 16, { force: true }) |
| 83 | + .wait(50) |
| 84 | + .trigger("pointermove", 20, 35, { force: true }) |
| 85 | + .wait(50) |
| 86 | + .trigger("pointermove", 20, 50, { force: true }) |
| 87 | + .wait(50) |
| 88 | + .trigger("pointerup", { force: true }) |
| 89 | + |
| 90 | + cy.get("#reorder-group [data-testid]").should(($items) => { |
| 91 | + const order = itemOrder($items) |
104 | 92 | expect(order.indexOf("item-0")).to.be.greaterThan( |
105 | 93 | order.indexOf("item-1"), |
106 | 94 | `expected item-0 to sit below item-1, got ${order.join(",")}` |
107 | 95 | ) |
108 | 96 | }) |
109 | 97 |
|
110 | | - // ... and the released item should settle flush in the list, i.e. no |
111 | | - // stranded drag transform (translateY ≈ 0 in local space). |
112 | | - cy.get("[data-testid='item-0']").then(($el: any) => { |
113 | | - const transform = getComputedStyle($el[0]).transform |
114 | | - if (transform && transform !== "none") { |
115 | | - const parts = transform.match(/matrix\(([^)]+)\)/) |
116 | | - if (parts) { |
117 | | - const translateY = parseFloat(parts[1].split(",")[5]) |
118 | | - expect(Math.abs(translateY)).to.be.lessThan(10) |
119 | | - } |
| 98 | + // List sits flush: 10px local gap × 0.5 scale = 5px screen, no |
| 99 | + // leftover drag/layout transform. Retry until springs finish. |
| 100 | + cy.get("#reorder-group [data-testid]").should(($items) => { |
| 101 | + const boxes = [...$items].map((el) => el.getBoundingClientRect()) |
| 102 | + for (let i = 1; i < boxes.length; i++) { |
| 103 | + expect(boxes[i].top).to.be.closeTo(boxes[i - 1].bottom + 5, 4) |
120 | 104 | } |
121 | 105 | }) |
122 | 106 | }) |
|
0 commit comments