Skip to content

Commit 9fbc840

Browse files
author
Johannes Weber
committed
chore: Refactor - extract logic from item-container to separate file including tests
1 parent c31b715 commit 9fbc840

File tree

3 files changed

+464
-53
lines changed

3 files changed

+464
-53
lines changed
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import { beforeEach, describe, expect, Mock, test, vi } from "vitest";
4+
5+
import { getLogicalClientX as originalGetLogicalClientX } from "@cloudscape-design/component-toolkit/internal";
6+
7+
import type { Transition } from "../../../../lib/components/internal/item-container";
8+
import { determineHandleActiveState } from "../../../../lib/components/internal/item-container/utils";
9+
import type { Operation } from "../../dnd-controller/controller";
10+
import { Coordinates } from "../../utils/coordinates";
11+
import { calculateInitialPointerData, DetermineHandleActiveStateArgs, getDndOperationType } from "../utils";
12+
13+
const mockRect = {
14+
insetInlineStart: 10,
15+
insetBlockStart: 20,
16+
insetInlineEnd: 110, // 10 + 100 (width)
17+
insetBlockEnd: 120, // 20 + 100 (height)
18+
inlineSize: 100,
19+
blockSize: 100,
20+
left: 10,
21+
right: 110,
22+
top: 20,
23+
bottom: 120,
24+
width: 100,
25+
height: 100,
26+
x: 10,
27+
y: 20,
28+
};
29+
30+
const mockPointerEvent = (clientX: number, clientY: number): Partial<PointerEvent> => ({
31+
clientX,
32+
clientY,
33+
});
34+
35+
vi.mock("@cloudscape-design/component-toolkit/internal", async (importOriginal) => {
36+
const actual = (await importOriginal()) as any;
37+
return {
38+
...actual,
39+
getLogicalClientX: vi.fn(),
40+
};
41+
});
42+
const mockGetLogicalClientX = originalGetLogicalClientX as Mock;
43+
44+
describe("getDndOperationType", () => {
45+
interface TestCases {
46+
operation: "drag" | "resize";
47+
isPlaced: boolean;
48+
expected: Operation;
49+
description: string;
50+
}
51+
52+
const testCases: Array<TestCases> = [
53+
{ operation: "resize", isPlaced: true, expected: "resize", description: "resize when placed" },
54+
{
55+
operation: "resize",
56+
isPlaced: false,
57+
expected: "resize",
58+
description: "resize when not placed (should still be resize)",
59+
},
60+
{ operation: "drag", isPlaced: true, expected: "reorder", description: "reorder when drag and placed" },
61+
{ operation: "drag", isPlaced: false, expected: "insert", description: "insert when drag and not placed" },
62+
];
63+
64+
test.each(testCases)('should return "$expected" for $description', ({ operation, isPlaced, expected }) => {
65+
expect(getDndOperationType(operation, isPlaced)).toBe<Operation>(expected);
66+
});
67+
});
68+
69+
describe("calculateInitialPointerData", () => {
70+
const getMinSizeMock = vi.fn();
71+
const MOCK_DOCUMENT_CLIENT_WIDTH = 1000; // For RTL simulation
72+
73+
beforeEach(() => {
74+
getMinSizeMock.mockReset();
75+
mockGetLogicalClientX.mockReset();
76+
});
77+
78+
describe('when operation is "drag"', () => {
79+
test("should calculate pointerOffset from top-left and null boundaries for LTR", () => {
80+
mockGetLogicalClientX.mockImplementation((event: PointerEvent) => event.clientX);
81+
const event = mockPointerEvent(50, 60) as PointerEvent;
82+
const result = calculateInitialPointerData({
83+
event,
84+
operation: "drag",
85+
rect: mockRect,
86+
getMinSize: getMinSizeMock,
87+
isRtl: false,
88+
});
89+
expect(mockGetLogicalClientX).toHaveBeenCalledWith(event, false);
90+
91+
const expectedPointerOffsetX = event.clientX - mockRect.insetInlineStart; // 50 - 10 = 40
92+
const expectedPointerOffsetY = event.clientY - mockRect.insetBlockStart; // 60 - 20 = 40
93+
expect(result.pointerOffset).toEqual(new Coordinates({ x: expectedPointerOffsetX, y: expectedPointerOffsetY }));
94+
expect(result.pointerBoundaries).toBeNull();
95+
expect(getMinSizeMock).not.toHaveBeenCalled();
96+
});
97+
98+
test("should calculate pointerOffset from top-left and null boundaries for RTL", () => {
99+
mockGetLogicalClientX.mockImplementation((event: PointerEvent) => MOCK_DOCUMENT_CLIENT_WIDTH - event.clientX);
100+
const event = mockPointerEvent(950, 60) as PointerEvent;
101+
const result = calculateInitialPointerData({
102+
event,
103+
operation: "drag",
104+
rect: mockRect,
105+
getMinSize: getMinSizeMock,
106+
isRtl: true,
107+
});
108+
expect(mockGetLogicalClientX).toHaveBeenCalledWith(event, true);
109+
110+
const logicalClientX = MOCK_DOCUMENT_CLIENT_WIDTH - event.clientX; // 1000 - 950 = 50
111+
const expectedPointerOffsetX = logicalClientX - mockRect.insetInlineStart; // 50 - 10 = 40
112+
const expectedPointerOffsetY = event.clientY - mockRect.insetBlockStart; // 60 - 20 = 40
113+
expect(result.pointerOffset).toEqual(new Coordinates({ x: expectedPointerOffsetX, y: expectedPointerOffsetY }));
114+
expect(result.pointerBoundaries).toBeNull();
115+
expect(getMinSizeMock).not.toHaveBeenCalled();
116+
});
117+
});
118+
119+
describe('when operation is "resize"', () => {
120+
const minWidth = 50;
121+
const minHeight = 50;
122+
123+
beforeEach(() => {
124+
getMinSizeMock.mockReturnValue({ minWidth, minHeight });
125+
});
126+
127+
test("should calculate pointerOffset from bottom-right and boundaries for LTR", () => {
128+
mockGetLogicalClientX.mockImplementation((event: PointerEvent) => event.clientX);
129+
const event = mockPointerEvent(150, 160) as PointerEvent; // Pointer beyond item
130+
const result = calculateInitialPointerData({
131+
event,
132+
operation: "resize",
133+
rect: mockRect,
134+
getMinSize: getMinSizeMock,
135+
isRtl: false,
136+
});
137+
138+
expect(mockGetLogicalClientX).toHaveBeenCalledWith(event, false);
139+
expect(getMinSizeMock).toHaveBeenCalledTimes(1);
140+
141+
const expectedPointerOffsetX = event.clientX - mockRect.insetInlineEnd; // 150 - 110 = 40
142+
const expectedPointerOffsetY = event.clientY - mockRect.insetBlockEnd; // 160 - 120 = 40
143+
expect(result.pointerOffset).toEqual(new Coordinates({ x: expectedPointerOffsetX, y: expectedPointerOffsetY }));
144+
145+
const expectedBoundaryX = event.clientX - mockRect.inlineSize + minWidth; // 150 - 100 + 50 = 100
146+
const expectedBoundaryY = event.clientY - mockRect.blockSize + minHeight; // 160 - 100 + 50 = 110
147+
expect(result.pointerBoundaries).toEqual(new Coordinates({ x: expectedBoundaryX, y: expectedBoundaryY }));
148+
});
149+
150+
test("should calculate pointerOffset from bottom-right and boundaries for RTL", () => {
151+
mockGetLogicalClientX.mockImplementation((event: PointerEvent) => MOCK_DOCUMENT_CLIENT_WIDTH - event.clientX);
152+
const event = mockPointerEvent(850, 160) as PointerEvent;
153+
const result = calculateInitialPointerData({
154+
event,
155+
operation: "resize",
156+
rect: mockRect,
157+
getMinSize: getMinSizeMock,
158+
isRtl: true,
159+
});
160+
161+
expect(mockGetLogicalClientX).toHaveBeenCalledWith(event, true);
162+
expect(getMinSizeMock).toHaveBeenCalledTimes(1);
163+
164+
const logicalClientX = MOCK_DOCUMENT_CLIENT_WIDTH - event.clientX; // 1000 - 850 = 150
165+
const expectedPointerOffsetX = logicalClientX - mockRect.insetInlineEnd; // 150 - 110 = 40
166+
const expectedPointerOffsetY = event.clientY - mockRect.insetBlockEnd; // 160 - 120 = 40
167+
expect(result.pointerOffset).toEqual(new Coordinates({ x: expectedPointerOffsetX, y: expectedPointerOffsetY }));
168+
169+
const expectedBoundaryX = logicalClientX - mockRect.inlineSize + minWidth; // 150 - 100 + 50 = 100
170+
const expectedBoundaryY = event.clientY - mockRect.blockSize + minHeight; // 160 - 100 + 50 = 110
171+
expect(result.pointerBoundaries).toEqual(new Coordinates({ x: expectedBoundaryX, y: expectedBoundaryY }));
172+
});
173+
});
174+
});
175+
176+
describe("determineHandleActiveState", () => {
177+
const mockTransition = (operation: Operation): Transition => ({
178+
itemId: "test-item",
179+
operation: operation,
180+
interactionType: "pointer", // Default value while testing, doesn't affect function's logic
181+
sizeTransform: null,
182+
positionTransform: null,
183+
});
184+
185+
type InteractionHookValue = DetermineHandleActiveStateArgs["interactionHookValue"];
186+
187+
const activeStateTestCases: Array<{
188+
description: string;
189+
args: Partial<DetermineHandleActiveStateArgs>;
190+
expected: "pointer" | "uap" | null;
191+
targetOperation?: Operation;
192+
}> = [
193+
// "pointer" states
194+
{
195+
description: 'return "pointer" if globally active, resize transition, dnd-start',
196+
args: {
197+
isHandleActive: true,
198+
currentTransition: mockTransition("resize"),
199+
interactionHookValue: "dnd-start",
200+
targetOperation: "resize",
201+
},
202+
expected: "pointer",
203+
},
204+
{
205+
description: 'return "pointer" if globally active, reorder transition, dnd-start',
206+
args: {
207+
isHandleActive: true,
208+
currentTransition: mockTransition("reorder"),
209+
interactionHookValue: "dnd-start",
210+
targetOperation: "reorder",
211+
},
212+
expected: "pointer",
213+
},
214+
// "uap" states
215+
{
216+
description: 'return "uap" if globally active, resize transition, uap-action-start',
217+
args: {
218+
isHandleActive: true,
219+
currentTransition: mockTransition("resize"),
220+
interactionHookValue: "uap-action-start",
221+
targetOperation: "resize",
222+
},
223+
expected: "uap",
224+
},
225+
{
226+
description: 'return "uap" if globally active, reorder transition, uap-action-start',
227+
args: {
228+
isHandleActive: true,
229+
currentTransition: mockTransition("reorder"),
230+
interactionHookValue: "uap-action-start",
231+
targetOperation: "reorder",
232+
},
233+
expected: "uap",
234+
},
235+
// Null states
236+
{
237+
description: "return null if not globally active",
238+
args: {
239+
isHandleActive: false,
240+
currentTransition: mockTransition("resize"),
241+
interactionHookValue: "dnd-start",
242+
targetOperation: "resize",
243+
},
244+
expected: null,
245+
},
246+
{
247+
description: "return null if no current transition",
248+
args: {
249+
isHandleActive: true,
250+
currentTransition: null,
251+
interactionHookValue: "dnd-start",
252+
targetOperation: "resize",
253+
},
254+
expected: null,
255+
},
256+
{
257+
description: "return null if current transition operation mismatches target",
258+
args: {
259+
isHandleActive: true,
260+
currentTransition: mockTransition("reorder"),
261+
interactionHookValue: "dnd-start",
262+
targetOperation: "resize",
263+
},
264+
expected: null,
265+
},
266+
{
267+
description: 'return null if interaction hook is not "dnd-start" or "uap-action-start" (e.g., "dnd-active")',
268+
args: {
269+
isHandleActive: true,
270+
currentTransition: mockTransition("resize"),
271+
interactionHookValue: "dnd-active",
272+
targetOperation: "resize",
273+
},
274+
expected: null,
275+
},
276+
{
277+
description: "return null if interaction hook is null",
278+
args: {
279+
isHandleActive: true,
280+
currentTransition: mockTransition("resize"),
281+
interactionHookValue: null,
282+
targetOperation: "resize",
283+
},
284+
expected: null,
285+
},
286+
{
287+
description: "return null if interaction hook is undefined",
288+
args: {
289+
isHandleActive: true,
290+
currentTransition: mockTransition("resize"),
291+
interactionHookValue: undefined as unknown as InteractionHookValue,
292+
targetOperation: "resize",
293+
},
294+
expected: null,
295+
},
296+
{
297+
description: "return null if interaction hook is an arbitrary string",
298+
args: {
299+
isHandleActive: true,
300+
currentTransition: mockTransition("resize"),
301+
interactionHookValue: "some-other-state" as InteractionHookValue,
302+
targetOperation: "resize",
303+
},
304+
expected: null,
305+
},
306+
// Combined null conditions
307+
{
308+
description: 'return null if not globally active, even if other conditions match for "pointer"',
309+
args: {
310+
isHandleActive: false,
311+
currentTransition: mockTransition("resize"),
312+
interactionHookValue: "dnd-start",
313+
targetOperation: "resize",
314+
},
315+
expected: null,
316+
},
317+
{
318+
description: 'return null if not globally active, even if other conditions match for "uap"',
319+
args: {
320+
isHandleActive: false,
321+
currentTransition: mockTransition("resize"),
322+
interactionHookValue: "uap-action-start",
323+
targetOperation: "resize",
324+
},
325+
expected: null,
326+
},
327+
];
328+
329+
test.each(activeStateTestCases)("should $description", ({ args, expected }) => {
330+
expect(determineHandleActiveState(args as DetermineHandleActiveStateArgs)).toBe(expected);
331+
});
332+
});

0 commit comments

Comments
 (0)