Skip to content

Commit e32d2d1

Browse files
committed
yarn test unit test fixes
* all unit tests are working again (forgot to update, not regressions)
1 parent 61e358b commit e32d2d1

8 files changed

Lines changed: 112 additions & 103 deletions

File tree

angular/projects/lib/src/lib/base-widget.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,16 @@ export abstract class BaseWidget {
9393
public deserialize(w: NgGridStackWidget) {
9494
// save full description for meta data
9595
this.widgetItem = w;
96-
if (!w?.props) return;
96+
const props = w?.props;
97+
if (!props) return;
9798

98-
if (this._compRef) {
99+
const compRef = this._compRef;
100+
if (compRef) {
99101
// Use setInput() to correctly handle both @Input() decorator and signal-based inputs (Angular 17+).
100102
// Direct Object.assign overwrites signal functions with plain values, breaking signal inputs.
101-
Object.keys(w.props).forEach(key => this._compRef!.setInput(key, (w.props as any)[key]));
103+
Object.keys(props).forEach(key => compRef.setInput(key, props[key]));
102104
} else {
103-
Object.assign(this, w.props);
105+
Object.assign(this, props);
104106
}
105107
}
106108
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"test:e2e": "playwright test",
5050
"test:e2e:ui": "playwright test --ui",
5151
"test:e2e:headed": "playwright test --headed",
52-
"lint": "tsc --project tsconfig.build.json --noEmit && eslint src/*.ts angular/projects/lib/src/**/*.ts react/projects/lib/src/**/*.{ts,tsx}",
52+
"lint": "tsc --project tsconfig.build.json --noEmit && eslint src/*.ts angular/projects/lib/src/**/*.ts && cd react && yarn lint",
5353
"reset": "rm -rf dist node_modules",
5454
"prepublishOnly": "yarn build"
5555
},

react/eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import reactRefresh from 'eslint-plugin-react-refresh'
55
import tseslint from 'typescript-eslint'
66

77
export default tseslint.config(
8-
{ ignores: ['dist'] },
8+
{ ignores: ['dist', 'lib/_old'] }, // _old is dead code, nothing imports it
99
{
1010
extends: [js.configs.recommended, ...tseslint.configs.recommended],
1111
files: ['**/*.{ts,tsx}'],

react/projects/lib/src/gridstack.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ export const GridStackComponent = forwardRef<GridStackHandle, GridStackProps>(
214214
}
215215
});
216216

217-
gridRef.current = GridStack.init(options, el); // eslint-disable-line react-hooks/exhaustive-deps
217+
gridRef.current = GridStack.init(options, el);
218218
prevOptsSig.current = optsSig;
219219
setLayoutVersion((v) => v + 1);
220220
setGridSession((s) => s + 1);
@@ -226,13 +226,13 @@ export const GridStackComponent = forwardRef<GridStackHandle, GridStackProps>(
226226
gridRef.current = null;
227227
prevOptsSig.current = null;
228228
};
229-
}, []); // eslint-disable-line react-hooks/exhaustive-deps intentionally init once
229+
}, []); // eslint-disable-line react-hooks/exhaustive-deps -- intentionally init once
230230

231231
// Options update — calls GS updateOptions when content changes without recreating the grid.
232232
useLayoutEffect(() => {
233233
if (!gridRef.current || prevOptsSig.current === null || prevOptsSig.current === optsSig)
234234
return;
235-
gridRef.current.updateOptions(options); // eslint-disable-line react-hooks/exhaustive-deps
235+
gridRef.current.updateOptions(options);
236236
prevOptsSig.current = optsSig;
237237
}, [optsSig]); // eslint-disable-line react-hooks/exhaustive-deps
238238

spec/dd-droppable-spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ describe('DDDroppable', () => {
157157
ui: vi.fn().mockReturnValue({
158158
helper: document.createElement('div'),
159159
position: { left: 0, top: 0 }
160-
})
160+
}),
161+
_stopScrolling: vi.fn()
161162
};
162163
DDManager.dragElement = mockDraggable as any;
163164
});

spec/dd-touch-spec.ts

Lines changed: 66 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {
55
touchend,
66
pointerdown,
77
pointerenter,
8-
pointerleave
8+
pointerleave,
9+
cancelPendingTouchDrag,
10+
DDTouch
911
} from '../src/dd-touch';
1012
import { DDManager } from '../src/dd-manager';
1113
import { Utils } from '../src/utils';
@@ -120,43 +122,37 @@ function createMockPointerEvent(type: string, pointerType: string, options: Part
120122
return mockEvent as PointerEvent;
121123
}
122124

125+
/** touch drags only start after the user pauses on the item - see #2781 */
126+
const TOUCH_DRAG_DELAY = 300;
127+
const pauseToDrag = () => vi.advanceTimersByTime(TOUCH_DRAG_DELAY);
128+
123129
describe('dd-touch', () => {
124130
let mockUtils: any;
125131
let mockDDManager: any;
126132

133+
/** touchstart + wait out the pause, which is what actually starts the drag */
134+
function startTouchDrag(touch: Touch): void {
135+
touchstart(createMockTouchEvent('touchstart', [touch]));
136+
pauseToDrag();
137+
}
138+
127139
beforeEach(() => {
140+
vi.useFakeTimers(); // drags are now delayed, see #2781
128141
mockUtils = vi.mocked(Utils);
129142
mockDDManager = vi.mocked(DDManager);
130143

131144
// Reset mocks
132145
mockUtils.simulateMouseEvent.mockClear();
133146
mockDDManager.dragElement = null;
134147

135-
// Mock window.clearTimeout and setTimeout
136-
vi.spyOn(window, 'clearTimeout');
137-
vi.spyOn(window, 'setTimeout').mockImplementation((callback: Function, delay: number) => {
138-
return setTimeout(callback, delay) as any;
139-
});
140-
141-
// Reset DDTouch state by calling touchend to reset touchHandled flag
142-
// This is a workaround since we can't access DDTouch directly
143-
const resetTouch = {
144-
pageX: 0, pageY: 0, clientX: 0, clientY: 0, screenX: 0, screenY: 0,
145-
identifier: 0, target: document.createElement('div'),
146-
radiusX: 0, radiusY: 0, rotationAngle: 0, force: 0
147-
} as Touch;
148-
const resetEvent = createMockTouchEvent('touchend', [], { changedTouches: [resetTouch] });
149-
150-
// Call touchstart then touchend to reset state
151-
const startEvent = createMockTouchEvent('touchstart', [resetTouch]);
152-
touchstart(startEvent);
153-
touchend(resetEvent);
154-
155-
// Clear any calls made during reset
156-
mockUtils.simulateMouseEvent.mockClear();
148+
// reset leftover state from the previous gesture
149+
cancelPendingTouchDrag();
150+
DDTouch.touchHandled = false;
151+
delete DDTouch.pointerLeaveTimeout;
157152
});
158153

159154
afterEach(() => {
155+
vi.useRealTimers();
160156
vi.restoreAllMocks();
161157
});
162158

@@ -192,18 +188,46 @@ describe('dd-touch', () => {
192188
} as Touch;
193189
});
194190

195-
it('should simulate mousedown for single touch', () => {
191+
it('should simulate mousedown only after pausing on the item (#2781)', () => {
196192
const mockTouchEvent = createMockTouchEvent('touchstart', [mockTouch]);
197193

198194
touchstart(mockTouchEvent);
195+
expect(mockUtils.simulateMouseEvent).not.toHaveBeenCalled(); // still could be a page scroll
199196

197+
pauseToDrag();
200198
expect(mockUtils.simulateMouseEvent).toHaveBeenCalledWith(mockTouch, 'mousedown');
201199
});
202200

201+
it('should let a quick swipe scroll the page instead of dragging (#2781)', () => {
202+
const target = document.createElement('div');
203+
touchstart(createMockTouchEvent('touchstart', [mockTouch], { currentTarget: target } as never));
204+
205+
// finger moves away before the delay elapses -> this is a scroll, not a drag
206+
const move: Event & { touches?: unknown } = new Event('touchmove');
207+
move.touches = [{ ...mockTouch, clientX: mockTouch.clientX + 50 }];
208+
target.dispatchEvent(move);
209+
pauseToDrag();
210+
211+
expect(mockUtils.simulateMouseEvent).not.toHaveBeenCalled();
212+
expect(DDTouch.touchHandled).toBeFalsy();
213+
});
214+
215+
it('should not drag when releasing before the pause elapses (tap)', () => {
216+
const target = document.createElement('div');
217+
touchstart(createMockTouchEvent('touchstart', [mockTouch], { currentTarget: target } as never));
218+
219+
target.dispatchEvent(new Event('touchend'));
220+
pauseToDrag();
221+
222+
expect(mockUtils.simulateMouseEvent).not.toHaveBeenCalled();
223+
expect(DDTouch.touchHandled).toBeFalsy();
224+
});
225+
203226
it('should prevent default on cancelable events', () => {
204227
const mockTouchEvent = createMockTouchEvent('touchstart', [mockTouch], { cancelable: true });
205228

206229
touchstart(mockTouchEvent);
230+
pauseToDrag();
207231

208232
expect(mockTouchEvent.preventDefault).toHaveBeenCalled();
209233
});
@@ -212,6 +236,7 @@ describe('dd-touch', () => {
212236
const mockTouchEvent = createMockTouchEvent('touchstart', [mockTouch], { cancelable: false });
213237

214238
touchstart(mockTouchEvent);
239+
pauseToDrag();
215240

216241
expect(mockTouchEvent.preventDefault).not.toHaveBeenCalled();
217242
});
@@ -221,6 +246,7 @@ describe('dd-touch', () => {
221246
const mockTouchEvent = createMockTouchEvent('touchstart', [mockTouch, secondTouch]);
222247

223248
touchstart(mockTouchEvent);
249+
pauseToDrag();
224250

225251
expect(mockUtils.simulateMouseEvent).not.toHaveBeenCalled();
226252
});
@@ -247,10 +273,7 @@ describe('dd-touch', () => {
247273
});
248274

249275
it('should simulate mousemove for single touch when touch is handled', () => {
250-
// First call touchstart to set DDTouch.touchHandled = true
251-
const startEvent = createMockTouchEvent('touchstart', [mockTouch]);
252-
touchstart(startEvent);
253-
276+
startTouchDrag(mockTouch);
254277
mockUtils.simulateMouseEvent.mockClear(); // Clear previous calls
255278

256279
const mockTouchEvent = createMockTouchEvent('touchmove', [mockTouch]);
@@ -269,10 +292,7 @@ describe('dd-touch', () => {
269292
});
270293

271294
it('should ignore multi-touch events', () => {
272-
// First call touchstart to set DDTouch.touchHandled = true
273-
const startEvent = createMockTouchEvent('touchstart', [mockTouch]);
274-
touchstart(startEvent);
275-
295+
startTouchDrag(mockTouch);
276296
mockUtils.simulateMouseEvent.mockClear(); // Clear previous calls
277297

278298
const secondTouch = { ...mockTouch, identifier: 2 };
@@ -305,10 +325,7 @@ describe('dd-touch', () => {
305325
});
306326

307327
it('should simulate mouseup when touch is handled', () => {
308-
// First call touchstart to set DDTouch.touchHandled = true
309-
const startEvent = createMockTouchEvent('touchstart', [mockTouch]);
310-
touchstart(startEvent);
311-
328+
startTouchDrag(mockTouch);
312329
mockUtils.simulateMouseEvent.mockClear(); // Clear previous calls
313330

314331
const mockTouchEvent = createMockTouchEvent('touchend', [], { changedTouches: [mockTouch] });
@@ -318,10 +335,7 @@ describe('dd-touch', () => {
318335
});
319336

320337
it('should simulate click when not dragging', () => {
321-
// First call touchstart to set DDTouch.touchHandled = true
322-
const startEvent = createMockTouchEvent('touchstart', [mockTouch]);
323-
touchstart(startEvent);
324-
338+
startTouchDrag(mockTouch);
325339
mockUtils.simulateMouseEvent.mockClear(); // Clear previous calls
326340
mockDDManager.dragElement = null; // Not dragging
327341

@@ -333,10 +347,7 @@ describe('dd-touch', () => {
333347
});
334348

335349
it('should not simulate click when dragging', () => {
336-
// First call touchstart to set DDTouch.touchHandled = true
337-
const startEvent = createMockTouchEvent('touchstart', [mockTouch]);
338-
touchstart(startEvent);
339-
350+
startTouchDrag(mockTouch);
340351
mockUtils.simulateMouseEvent.mockClear(); // Clear previous calls
341352
mockDDManager.dragElement = {}; // Dragging
342353

@@ -355,29 +366,16 @@ describe('dd-touch', () => {
355366
expect(mockUtils.simulateMouseEvent).not.toHaveBeenCalled();
356367
});
357368

358-
it('should clear pointerLeaveTimeout when it exists', () => {
359-
// First set up a pointerleave timeout
369+
it('should cancel the pending mouseleave when releasing over ourself', () => {
370+
startTouchDrag(mockTouch);
360371
mockDDManager.dragElement = {};
361-
const pointerEvent = createMockPointerEvent('pointerleave', 'touch');
362-
363-
let timeoutId: number;
364-
vi.mocked(window.setTimeout).mockImplementation((callback: Function, delay: number) => {
365-
timeoutId = 123;
366-
return timeoutId as any;
367-
});
368-
369-
pointerleave(pointerEvent);
370-
371-
// Now call touchstart and touchend to trigger the timeout clearing
372-
const startEvent = createMockTouchEvent('touchstart', [mockTouch]);
373-
touchstart(startEvent);
374-
372+
pointerleave(createMockPointerEvent('pointerleave', 'touch')); // leave we get right before the release
375373
mockUtils.simulateMouseEvent.mockClear();
376-
377-
const mockTouchEvent = createMockTouchEvent('touchend', [], { changedTouches: [mockTouch] });
378-
touchend(mockTouchEvent);
379374

380-
expect(window.clearTimeout).toHaveBeenCalledWith(123);
375+
touchend(createMockTouchEvent('touchend', [], { changedTouches: [mockTouch] }));
376+
vi.advanceTimersByTime(50);
377+
378+
expect(mockUtils.simulateMouseEvent).not.toHaveBeenCalledWith(expect.anything(), 'mouseleave');
381379
});
382380
});
383381

@@ -476,8 +474,8 @@ describe('dd-touch', () => {
476474
const mockPointerEvent = createMockPointerEvent('pointerleave', 'touch');
477475

478476
pointerleave(mockPointerEvent);
477+
vi.advanceTimersByTime(50);
479478

480-
expect(window.setTimeout).not.toHaveBeenCalled();
481479
expect(mockUtils.simulateMouseEvent).not.toHaveBeenCalled();
482480
});
483481

@@ -486,29 +484,19 @@ describe('dd-touch', () => {
486484
const mockPointerEvent = createMockPointerEvent('pointerleave', 'mouse');
487485

488486
pointerleave(mockPointerEvent);
487+
vi.advanceTimersByTime(50);
489488

490-
expect(window.setTimeout).not.toHaveBeenCalled();
491489
expect(mockUtils.simulateMouseEvent).not.toHaveBeenCalled();
492490
});
493491

494492
it('should delay mouseleave simulation for touch events when dragging', () => {
495493
mockDDManager.dragElement = {};
496494
const mockPointerEvent = createMockPointerEvent('pointerleave', 'touch');
497-
498-
// Mock setTimeout to capture the callback
499-
let timeoutCallback: Function;
500-
vi.mocked(window.setTimeout).mockImplementation((callback: Function, delay: number) => {
501-
timeoutCallback = callback;
502-
return 123 as any;
503-
});
504495

505496
pointerleave(mockPointerEvent);
497+
expect(mockUtils.simulateMouseEvent).not.toHaveBeenCalled(); // delayed so a release on ourself can cancel it
506498

507-
expect(window.setTimeout).toHaveBeenCalledWith(expect.any(Function), 10);
508-
509-
// Execute the timeout callback
510-
timeoutCallback!();
511-
499+
vi.advanceTimersByTime(50);
512500
expect(mockUtils.simulateMouseEvent).toHaveBeenCalledWith(mockPointerEvent, 'mouseleave');
513501
});
514502
});

0 commit comments

Comments
 (0)