Skip to content

Commit 5e15a2b

Browse files
authored
Merge pull request #3389 from adumesny/master
unit test coverage
2 parents e270a00 + 7c900fd commit 5e15a2b

11 files changed

Lines changed: 566 additions & 303 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
.npmrc
55
.yarnrc
66
coverage
7+
test-results
78
dist
89
dist_save
910
node_modules

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# gridstack.js
22

33
[![NPM version](https://img.shields.io/npm/v/gridstack.svg)](https://www.npmjs.com/package/gridstack)
4-
[![Coverage Status](https://coveralls.io/repos/github/gridstack/gridstack.js/badge.svg?branch=develop)](https://coveralls.io/github/gridstack/gridstack.js?branch=develop)
4+
[![Coverage Status](https://coveralls.io/repos/github/gridstack/gridstack.js/badge.svg?branch=master)](https://coveralls.io/github/gridstack/gridstack.js?branch=master)
55
[![downloads](https://img.shields.io/npm/dm/gridstack.svg)](https://www.npmjs.com/package/gridstack)
66

7-
Mobile-friendly modern Typescript library for dashboard layout and creation. Making a drag-and-drop, multi-column responsive dashboard has never been easier. Has multiple bindings and works great with [Angular](./angular/README.md) (included), [React](./react/README.md) (included), [Vue](./vue/README.md) (included), [Knockout.js](http://knockoutjs.com), [Ember](https://www.emberjs.com/) and others (see [frameworks](#specific-frameworks) section).
7+
Mobile-friendly modern pure Typescript library (no external dependencies) for dashboard layout and creation. Making a drag-and-drop, multi-column responsive dashboard has never been easier. Has multiple bindings and works great with [Angular](./angular/README.md) (included), [React](./react/README.md) (included), [Vue](./vue/README.md) (included), [Knockout.js](http://knockoutjs.com), [Ember](https://www.emberjs.com/) and others (see [frameworks](#specific-frameworks) section).
88

99
Inspired by no-longer maintained gridster, built with love.
1010

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@
8080
"@testing-library/jest-dom": "^6.4.8",
8181
"@typescript-eslint/eslint-plugin": "^5.58.0",
8282
"@typescript-eslint/parser": "^5.58.0",
83-
"@vitest/coverage-v8": "^2.0.5",
84-
"@vitest/ui": "^2.0.5",
83+
"@vitest/coverage-v8": "^4.1.0",
84+
"@vitest/ui": "^4.1.0",
8585
"connect": "^3.7.0",
8686
"core-js": "^3.30.1",
8787
"coveralls": "^3.1.1",

spec/dd-simple-integration-spec.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DDDraggable } from '../src/dd-draggable';
22
import { DDDroppable } from '../src/dd-droppable';
33
import { DDResizable } from '../src/dd-resizable';
44
import { DDElement } from '../src/dd-element';
5+
import { DDManager } from '../src/dd-manager';
56
import { GridItemHTMLElement } from '../src/types';
67

78
describe('DD Integration Tests', () => {
@@ -229,6 +230,141 @@ describe('DD Integration Tests', () => {
229230
});
230231
});
231232

233+
describe('drag gesture', () => {
234+
/** dispatch a real mouse event - the drag code listens on document for move/up */
235+
const mouse = (type: string, x: number, y: number, target: EventTarget = document) => {
236+
target.dispatchEvent(new MouseEvent(type, {bubbles: true, cancelable: true, button: 0, clientX: x, clientY: y}));
237+
};
238+
239+
it('should fire dragstart/drag/dragstop as the mouse moves and releases', () => {
240+
const start = vi.fn(), drag = vi.fn(), stop = vi.fn();
241+
const draggable = new DDDraggable(element, {start, drag, stop});
242+
243+
mouse('mousedown', 10, 10, element);
244+
expect(start).not.toHaveBeenCalled(); // a click alone isn't a drag
245+
246+
mouse('mousemove', 30, 30);
247+
expect(start).toHaveBeenCalled();
248+
expect(DDManager.dragElement).toBe(draggable);
249+
250+
mouse('mousemove', 50, 60);
251+
expect(drag).toHaveBeenCalled();
252+
253+
mouse('mouseup', 50, 60);
254+
expect(stop).toHaveBeenCalled();
255+
expect(DDManager.dragElement).toBeUndefined();
256+
257+
draggable.destroy();
258+
});
259+
260+
it('should not start dragging for a tiny mouse move (#3px threshold)', () => {
261+
const start = vi.fn();
262+
const draggable = new DDDraggable(element, {start});
263+
264+
mouse('mousedown', 10, 10, element);
265+
mouse('mousemove', 11, 11);
266+
expect(start).not.toHaveBeenCalled();
267+
expect(DDManager.dragElement).toBeUndefined();
268+
269+
mouse('mouseup', 11, 11);
270+
draggable.destroy();
271+
});
272+
273+
it('should ignore a non left click', () => {
274+
const start = vi.fn();
275+
const draggable = new DDDraggable(element, {start});
276+
277+
element.dispatchEvent(new MouseEvent('mousedown', {bubbles: true, button: 2, clientX: 10, clientY: 10}));
278+
mouse('mousemove', 40, 40);
279+
280+
expect(start).not.toHaveBeenCalled();
281+
draggable.destroy();
282+
});
283+
284+
it('should cancel the drag on Escape', () => {
285+
const stop = vi.fn();
286+
const draggable = new DDDraggable(element, {stop});
287+
288+
mouse('mousedown', 10, 10, element);
289+
mouse('mousemove', 40, 40);
290+
expect(DDManager.dragElement).toBe(draggable);
291+
292+
document.dispatchEvent(new KeyboardEvent('keydown', {key: 'Escape', bubbles: true}));
293+
294+
expect(stop).toHaveBeenCalled();
295+
expect(DDManager.dragElement).toBeUndefined();
296+
draggable.destroy();
297+
});
298+
299+
it('should only let one item handle a drag at a time', () => {
300+
const other = document.createElement('div');
301+
document.body.appendChild(other);
302+
const start1 = vi.fn(), start2 = vi.fn();
303+
const d1 = new DDDraggable(element, {start: start1});
304+
const d2 = new DDDraggable(other, {start: start2});
305+
306+
mouse('mousedown', 10, 10, element);
307+
mouse('mousedown', 10, 10, other); // second one must be ignored
308+
mouse('mousemove', 40, 40);
309+
310+
expect(start1).toHaveBeenCalled();
311+
expect(start2).not.toHaveBeenCalled();
312+
313+
mouse('mouseup', 40, 40);
314+
d1.destroy(); d2.destroy(); other.remove();
315+
});
316+
});
317+
318+
describe('resize gesture', () => {
319+
const mouse = (type: string, x: number, y: number, target: EventTarget = document) => {
320+
target.dispatchEvent(new MouseEvent(type, {bubbles: true, cancelable: true, button: 0, clientX: x, clientY: y}));
321+
};
322+
323+
it('should fire resizestart/resize/resizestop when dragging a handle', () => {
324+
const start = vi.fn(), resize = vi.fn(), stop = vi.fn();
325+
const resizable = new DDResizable(element, {handles: 'se', start, resize, stop});
326+
const handle = element.querySelector('.ui-resizable-se')!;
327+
328+
mouse('mousedown', 100, 100, handle);
329+
expect(start).not.toHaveBeenCalled();
330+
331+
mouse('mousemove', 140, 150);
332+
expect(start).toHaveBeenCalled();
333+
expect(resize).toHaveBeenCalled();
334+
335+
mouse('mouseup', 140, 150);
336+
expect(stop).toHaveBeenCalled();
337+
338+
resizable.destroy();
339+
});
340+
341+
it('should not resize for a tiny move', () => {
342+
const start = vi.fn();
343+
const resizable = new DDResizable(element, {handles: 'se', start});
344+
const handle = element.querySelector('.ui-resizable-se')!;
345+
346+
mouse('mousedown', 100, 100, handle);
347+
mouse('mousemove', 101, 101);
348+
expect(start).not.toHaveBeenCalled();
349+
350+
mouse('mouseup', 101, 101);
351+
resizable.destroy();
352+
});
353+
354+
it('should cancel the resize on Escape', () => {
355+
const stop = vi.fn();
356+
const resizable = new DDResizable(element, {handles: 'se', stop});
357+
const handle = element.querySelector('.ui-resizable-se')!;
358+
359+
mouse('mousedown', 100, 100, handle);
360+
mouse('mousemove', 140, 150);
361+
document.dispatchEvent(new KeyboardEvent('keydown', {key: 'Escape', bubbles: true}));
362+
363+
expect(stop).toHaveBeenCalled();
364+
resizable.destroy();
365+
});
366+
});
367+
232368
describe('Event handling', () => {
233369
it('should support event listeners on DDElement', () => {
234370
const ddElement = DDElement.init(element);

spec/gridstack-engine-spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,31 @@ describe('gridstack engine:', () => {
2222
expect(typeof GridStackEngine).toBe('function');
2323
});
2424

25+
describe('non-finite positions >', () => {
26+
it('should clamp a non-finite position to a usable one', () => {
27+
// pixel math against a zero cell size (collapsed/hidden grid) yields Infinity/NaN,
28+
// which can't be packed or collision checked
29+
e = new GridStackEngine({column: 12});
30+
const n = e.addNode({id: 'bad', x: Infinity, y: Infinity, w: NaN, h: Infinity});
31+
32+
expect(Number.isFinite(n.x!)).toBe(true);
33+
expect(Number.isFinite(n.y!)).toBe(true);
34+
expect(n.w).toBe(1);
35+
expect(n.h).toBe(1);
36+
});
37+
38+
it('should keep the layout valid when a move goes non-finite', () => {
39+
e = new GridStackEngine({column: 12});
40+
e.addNode({id: 'a', x: 0, y: 0, w: 2, h: 1});
41+
const b = e.addNode({id: 'b', x: 2, y: 0, w: 2, h: 1});
42+
43+
e.moveNode(b, {y: Infinity});
44+
45+
expect(Number.isFinite(b.y!)).toBe(true);
46+
expect(overlaps(e)).toEqual([]);
47+
});
48+
});
49+
2550
describe('test constructor >', () => {
2651

2752
it('should be setup properly', () => {

0 commit comments

Comments
 (0)