@neo4j-nvl/interaction-handlers 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -1
- package/README.md +8 -0
- package/lib/constants.d.ts +1 -1
- package/lib/constants.js +4 -2
- package/lib/index.d.ts +10 -20
- package/lib/index.js +10 -10
- package/lib/interaction-handlers/base.d.ts +67 -67
- package/lib/interaction-handlers/base.js +98 -104
- package/lib/interaction-handlers/box-select-interaction.d.ts +44 -44
- package/lib/interaction-handlers/box-select-interaction.js +133 -127
- package/lib/interaction-handlers/click-interaction.d.ts +111 -111
- package/lib/interaction-handlers/click-interaction.js +116 -131
- package/lib/interaction-handlers/drag-node-interaction.d.ts +40 -40
- package/lib/interaction-handlers/drag-node-interaction.js +102 -111
- package/lib/interaction-handlers/draw-interaction.d.ts +37 -37
- package/lib/interaction-handlers/draw-interaction.js +245 -278
- package/lib/interaction-handlers/hover-interaction.d.ts +34 -30
- package/lib/interaction-handlers/hover-interaction.js +83 -89
- package/lib/interaction-handlers/keyboard-interaction.d.ts +149 -97
- package/lib/interaction-handlers/keyboard-interaction.js +255 -201
- package/lib/interaction-handlers/lasso-interaction.d.ts +45 -45
- package/lib/interaction-handlers/lasso-interaction.js +174 -196
- package/lib/interaction-handlers/pan-interaction.d.ts +62 -61
- package/lib/interaction-handlers/pan-interaction.js +111 -100
- package/lib/interaction-handlers/utils.d.ts +2 -2
- package/lib/interaction-handlers/utils.js +39 -38
- package/lib/interaction-handlers/zoom-interaction.d.ts +52 -48
- package/lib/interaction-handlers/zoom-interaction.js +73 -71
- package/lib/overlay-renderer/overlay-renderer.d.ts +13 -13
- package/lib/overlay-renderer/overlay-renderer.js +96 -111
- package/package.json +19 -6
- package/lib/__tests__/box-select-interaction.test.d.ts +0 -1
- package/lib/__tests__/box-select-interaction.test.js +0 -149
- package/lib/__tests__/click-interaction.test.d.ts +0 -1
- package/lib/__tests__/click-interaction.test.js +0 -505
- package/lib/__tests__/drag-node-interaction.test.d.ts +0 -1
- package/lib/__tests__/drag-node-interaction.test.js +0 -247
- package/lib/__tests__/hover-interaction.test.d.ts +0 -1
- package/lib/__tests__/hover-interaction.test.js +0 -374
- package/lib/__tests__/keyboard-interaction.test.d.ts +0 -1
- package/lib/__tests__/keyboard-interaction.test.js +0 -322
- package/lib/__tests__/lasso-interaction.test.d.ts +0 -1
- package/lib/__tests__/lasso-interaction.test.js +0 -219
- package/lib/__tests__/pan-interaction.test.d.ts +0 -1
- package/lib/__tests__/pan-interaction.test.js +0 -223
- package/lib/__tests__/utils.test.d.ts +0 -1
- package/lib/__tests__/utils.test.js +0 -19
- package/lib/__tests__/zoom-interaction.test.d.ts +0 -1
- package/lib/__tests__/zoom-interaction.test.js +0 -185
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import NVL from '@neo4j-nvl/base';
|
|
2
|
-
import '@testing-library/jest-dom';
|
|
3
|
-
import { KeyboardInteraction } from '../interaction-handlers/keyboard-interaction';
|
|
4
|
-
jest.mock('@neo4j-nvl/layout-workers');
|
|
5
|
-
describe('KeyboardInteraction', () => {
|
|
6
|
-
let keyboardInteraction;
|
|
7
|
-
let myNVL;
|
|
8
|
-
const callbackMock = jest.fn();
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
myNVL = new NVL(document.createElement('div'), [
|
|
11
|
-
{ id: '0', x: 100, y: 100 },
|
|
12
|
-
{ id: '1', x: 200, y: 200 }
|
|
13
|
-
], [{ id: '10', from: '0', to: '1' }], { disableWebGL: true, initialZoom: 1, layout: 'free', renderer: 'webgl' });
|
|
14
|
-
keyboardInteraction = new KeyboardInteraction(myNVL);
|
|
15
|
-
});
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
keyboardInteraction.destroy();
|
|
18
|
-
myNVL.destroy();
|
|
19
|
-
callbackMock.mockReset();
|
|
20
|
-
});
|
|
21
|
-
test('sets tabindex on the container if not already present', () => {
|
|
22
|
-
expect(myNVL.getContainer().getAttribute('tabindex')).toBe('0');
|
|
23
|
-
});
|
|
24
|
-
test('does not override existing tabindex', () => {
|
|
25
|
-
keyboardInteraction.destroy();
|
|
26
|
-
const container = myNVL.getContainer();
|
|
27
|
-
container.setAttribute('tabindex', '-1');
|
|
28
|
-
keyboardInteraction = new KeyboardInteraction(myNVL);
|
|
29
|
-
expect(container.getAttribute('tabindex')).toBe('-1');
|
|
30
|
-
});
|
|
31
|
-
test('removes tabindex on destroy only if it was added by the interaction', () => {
|
|
32
|
-
keyboardInteraction.destroy();
|
|
33
|
-
expect(myNVL.getContainer().getAttribute('tabindex')).toBeNull();
|
|
34
|
-
});
|
|
35
|
-
test('does not remove tabindex on destroy if it was pre-existing', () => {
|
|
36
|
-
keyboardInteraction.destroy();
|
|
37
|
-
const container = myNVL.getContainer();
|
|
38
|
-
container.setAttribute('tabindex', '-1');
|
|
39
|
-
const interaction = new KeyboardInteraction(myNVL);
|
|
40
|
-
interaction.destroy();
|
|
41
|
-
expect(container.getAttribute('tabindex')).toBe('-1');
|
|
42
|
-
});
|
|
43
|
-
test('keydown event triggers onKeyDown callback', () => {
|
|
44
|
-
keyboardInteraction.updateCallback('onKeyDown', callbackMock);
|
|
45
|
-
const event = new KeyboardEvent('keydown', { key: 'a' });
|
|
46
|
-
myNVL.getContainer().dispatchEvent(event);
|
|
47
|
-
expect(callbackMock).toHaveBeenCalledWith(event, undefined);
|
|
48
|
-
expect(callbackMock).toHaveBeenCalledTimes(1);
|
|
49
|
-
});
|
|
50
|
-
test('keyup event triggers onKeyUp callback', () => {
|
|
51
|
-
keyboardInteraction.updateCallback('onKeyUp', callbackMock);
|
|
52
|
-
const event = new KeyboardEvent('keyup', { key: 'a' });
|
|
53
|
-
myNVL.getContainer().dispatchEvent(event);
|
|
54
|
-
expect(callbackMock).toHaveBeenCalledWith(event, undefined);
|
|
55
|
-
expect(callbackMock).toHaveBeenCalledTimes(1);
|
|
56
|
-
});
|
|
57
|
-
test('focusin event triggers onCanvasFocus callback', () => {
|
|
58
|
-
keyboardInteraction.updateCallback('onCanvasFocus', callbackMock);
|
|
59
|
-
const event = new FocusEvent('focusin');
|
|
60
|
-
myNVL.getContainer().dispatchEvent(event);
|
|
61
|
-
expect(callbackMock).toHaveBeenCalledWith(event);
|
|
62
|
-
expect(callbackMock).toHaveBeenCalledTimes(1);
|
|
63
|
-
});
|
|
64
|
-
test('focusout event triggers onCanvasBlur callback', () => {
|
|
65
|
-
keyboardInteraction.updateCallback('onCanvasBlur', callbackMock);
|
|
66
|
-
const event = new FocusEvent('focusout');
|
|
67
|
-
myNVL.getContainer().dispatchEvent(event);
|
|
68
|
-
expect(callbackMock).toHaveBeenCalledWith(event);
|
|
69
|
-
expect(callbackMock).toHaveBeenCalledTimes(1);
|
|
70
|
-
});
|
|
71
|
-
describe('automatic navigation via key mappings', () => {
|
|
72
|
-
test('Enter key enters the graph and focuses the first element', () => {
|
|
73
|
-
keyboardInteraction.updateCallback('onNodeFocus', callbackMock);
|
|
74
|
-
const event = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true });
|
|
75
|
-
myNVL.getContainer().dispatchEvent(event);
|
|
76
|
-
expect(callbackMock).toHaveBeenCalledTimes(1);
|
|
77
|
-
expect(callbackMock).toHaveBeenCalledWith(expect.objectContaining({ id: '0' }), event);
|
|
78
|
-
});
|
|
79
|
-
test('Enter key does nothing when an element is already focused', () => {
|
|
80
|
-
const nodeFocusMock = jest.fn();
|
|
81
|
-
keyboardInteraction.updateCallback('onNodeFocus', nodeFocusMock);
|
|
82
|
-
const container = myNVL.getContainer();
|
|
83
|
-
// Enter the graph first
|
|
84
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
85
|
-
expect(nodeFocusMock).toHaveBeenCalledTimes(1);
|
|
86
|
-
// Press Enter again — should not navigate
|
|
87
|
-
nodeFocusMock.mockClear();
|
|
88
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
89
|
-
expect(nodeFocusMock).not.toHaveBeenCalled();
|
|
90
|
-
});
|
|
91
|
-
test('Tab does nothing when the graph has not been entered', () => {
|
|
92
|
-
keyboardInteraction.updateCallback('onNodeFocus', callbackMock);
|
|
93
|
-
myNVL.getContainer().dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
94
|
-
expect(callbackMock).not.toHaveBeenCalled();
|
|
95
|
-
});
|
|
96
|
-
test('Tab key navigates focus forward after entering the graph', () => {
|
|
97
|
-
const nodeFocusMock = jest.fn();
|
|
98
|
-
keyboardInteraction.updateCallback('onNodeFocus', nodeFocusMock);
|
|
99
|
-
const container = myNVL.getContainer();
|
|
100
|
-
// Enter the graph first
|
|
101
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
102
|
-
expect(nodeFocusMock).toHaveBeenLastCalledWith(expect.objectContaining({ id: '0' }), expect.any(KeyboardEvent));
|
|
103
|
-
// Tab to next element
|
|
104
|
-
const event = new KeyboardEvent('keydown', { key: 'Tab', bubbles: true });
|
|
105
|
-
container.dispatchEvent(event);
|
|
106
|
-
expect(nodeFocusMock).toHaveBeenLastCalledWith(expect.objectContaining({ id: '1' }), event);
|
|
107
|
-
});
|
|
108
|
-
test('Shift+Tab does nothing when the graph has not been entered', () => {
|
|
109
|
-
keyboardInteraction.updateCallback('onRelationshipFocus', callbackMock);
|
|
110
|
-
myNVL.getContainer().dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true, bubbles: true }));
|
|
111
|
-
expect(callbackMock).not.toHaveBeenCalled();
|
|
112
|
-
});
|
|
113
|
-
test('Shift+Tab navigates focus backward after entering the graph', () => {
|
|
114
|
-
const nodeFocusMock = jest.fn();
|
|
115
|
-
const relFocusMock = jest.fn();
|
|
116
|
-
keyboardInteraction.updateCallback('onNodeFocus', nodeFocusMock);
|
|
117
|
-
keyboardInteraction.updateCallback('onRelationshipFocus', relFocusMock);
|
|
118
|
-
const container = myNVL.getContainer();
|
|
119
|
-
// Enter the graph first
|
|
120
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
121
|
-
// Shift+Tab to go backward
|
|
122
|
-
const event = new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true, bubbles: true });
|
|
123
|
-
container.dispatchEvent(event);
|
|
124
|
-
expect(relFocusMock).toHaveBeenCalledWith(expect.objectContaining({ id: '10' }), event);
|
|
125
|
-
});
|
|
126
|
-
test('Tab cycles through nodes and relationships', () => {
|
|
127
|
-
const nodeFocusMock = jest.fn();
|
|
128
|
-
const relFocusMock = jest.fn();
|
|
129
|
-
keyboardInteraction.updateCallback('onNodeFocus', nodeFocusMock);
|
|
130
|
-
keyboardInteraction.updateCallback('onRelationshipFocus', relFocusMock);
|
|
131
|
-
const container = myNVL.getContainer();
|
|
132
|
-
// Enter the graph: node '0'
|
|
133
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
134
|
-
expect(nodeFocusMock).toHaveBeenLastCalledWith(expect.objectContaining({ id: '0' }), expect.any(KeyboardEvent));
|
|
135
|
-
// First Tab: node '1'
|
|
136
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
137
|
-
expect(nodeFocusMock).toHaveBeenLastCalledWith(expect.objectContaining({ id: '1' }), expect.any(KeyboardEvent));
|
|
138
|
-
// Second Tab: relationship '10'
|
|
139
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
140
|
-
expect(relFocusMock).toHaveBeenCalledWith(expect.objectContaining({ id: '10' }), expect.any(KeyboardEvent));
|
|
141
|
-
});
|
|
142
|
-
test('Tab wraps around from last to first element', () => {
|
|
143
|
-
const nodeFocusMock = jest.fn();
|
|
144
|
-
keyboardInteraction.updateCallback('onNodeFocus', nodeFocusMock);
|
|
145
|
-
keyboardInteraction.updateCallback('onRelationshipFocus', jest.fn());
|
|
146
|
-
const container = myNVL.getContainer();
|
|
147
|
-
// Enter the graph, then navigate to the last element (relationship '10')
|
|
148
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
149
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
150
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
151
|
-
// Wrap around to first element
|
|
152
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
153
|
-
expect(nodeFocusMock).toHaveBeenLastCalledWith(expect.objectContaining({ id: '0' }), expect.any(KeyboardEvent));
|
|
154
|
-
});
|
|
155
|
-
test('Tab fires blur callback when moving away from a node', () => {
|
|
156
|
-
const nodeBlurMock = jest.fn();
|
|
157
|
-
keyboardInteraction.updateCallback('onNodeFocus', jest.fn());
|
|
158
|
-
keyboardInteraction.updateCallback('onNodeBlur', nodeBlurMock);
|
|
159
|
-
const container = myNVL.getContainer();
|
|
160
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
161
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
162
|
-
expect(nodeBlurMock).toHaveBeenCalledWith(expect.objectContaining({ id: '0' }), expect.any(KeyboardEvent));
|
|
163
|
-
});
|
|
164
|
-
test('Tab fires onRelationshipBlur when moving away from a relationship', () => {
|
|
165
|
-
const relBlurMock = jest.fn();
|
|
166
|
-
keyboardInteraction.updateCallback('onNodeFocus', jest.fn());
|
|
167
|
-
keyboardInteraction.updateCallback('onRelationshipFocus', jest.fn());
|
|
168
|
-
keyboardInteraction.updateCallback('onRelationshipBlur', relBlurMock);
|
|
169
|
-
const container = myNVL.getContainer();
|
|
170
|
-
// Enter the graph, then navigate to relationship
|
|
171
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
172
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
173
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
174
|
-
// Move away from relationship
|
|
175
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
176
|
-
expect(relBlurMock).toHaveBeenCalledWith(expect.objectContaining({ id: '10' }), expect.any(KeyboardEvent));
|
|
177
|
-
});
|
|
178
|
-
test('does nothing when there are no elements', () => {
|
|
179
|
-
keyboardInteraction.destroy();
|
|
180
|
-
myNVL.destroy();
|
|
181
|
-
myNVL = new NVL(document.createElement('div'), [], [], {
|
|
182
|
-
disableWebGL: true,
|
|
183
|
-
initialZoom: 1,
|
|
184
|
-
layout: 'free',
|
|
185
|
-
renderer: 'webgl'
|
|
186
|
-
});
|
|
187
|
-
keyboardInteraction = new KeyboardInteraction(myNVL);
|
|
188
|
-
keyboardInteraction.updateCallback('onNodeFocus', callbackMock);
|
|
189
|
-
myNVL.getContainer().dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
190
|
-
expect(callbackMock).not.toHaveBeenCalled();
|
|
191
|
-
});
|
|
192
|
-
test('Escape clears element focus but keeps canvas focused', () => {
|
|
193
|
-
const nodeBlurMock = jest.fn();
|
|
194
|
-
keyboardInteraction.updateCallback('onNodeFocus', jest.fn());
|
|
195
|
-
keyboardInteraction.updateCallback('onNodeBlur', nodeBlurMock);
|
|
196
|
-
const container = myNVL.getContainer();
|
|
197
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
198
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
199
|
-
expect(nodeBlurMock).toHaveBeenCalledWith(expect.objectContaining({ id: '0' }), expect.any(KeyboardEvent));
|
|
200
|
-
expect(keyboardInteraction.getFocused()).toBeUndefined();
|
|
201
|
-
});
|
|
202
|
-
test('Escape does nothing when nothing is focused', () => {
|
|
203
|
-
keyboardInteraction.updateCallback('onNodeBlur', callbackMock);
|
|
204
|
-
keyboardInteraction.updateCallback('onRelationshipBlur', callbackMock);
|
|
205
|
-
myNVL.getContainer().dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
206
|
-
expect(callbackMock).not.toHaveBeenCalled();
|
|
207
|
-
});
|
|
208
|
-
test('Shift+F10 fires onContextMenu with focused element', () => {
|
|
209
|
-
const contextMenuMock = jest.fn();
|
|
210
|
-
keyboardInteraction.updateCallback('onNodeFocus', jest.fn());
|
|
211
|
-
keyboardInteraction.updateCallback('onContextMenu', contextMenuMock);
|
|
212
|
-
const container = myNVL.getContainer();
|
|
213
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
214
|
-
const event = new KeyboardEvent('keydown', { key: 'F10', shiftKey: true, bubbles: true });
|
|
215
|
-
container.dispatchEvent(event);
|
|
216
|
-
expect(contextMenuMock).toHaveBeenCalledWith(expect.objectContaining({ id: '0' }), event);
|
|
217
|
-
});
|
|
218
|
-
test('Shift+F10 fires onContextMenu with undefined when nothing is focused', () => {
|
|
219
|
-
const contextMenuMock = jest.fn();
|
|
220
|
-
keyboardInteraction.updateCallback('onContextMenu', contextMenuMock);
|
|
221
|
-
const event = new KeyboardEvent('keydown', { key: 'F10', shiftKey: true, bubbles: true });
|
|
222
|
-
myNVL.getContainer().dispatchEvent(event);
|
|
223
|
-
expect(contextMenuMock).toHaveBeenCalledWith(undefined, event);
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
describe('custom key mappings', () => {
|
|
227
|
-
test('respects custom enterGraphKey', () => {
|
|
228
|
-
keyboardInteraction.destroy();
|
|
229
|
-
keyboardInteraction = new KeyboardInteraction(myNVL, { enterGraphKey: ['Space'] });
|
|
230
|
-
keyboardInteraction.updateCallback('onNodeFocus', callbackMock);
|
|
231
|
-
// Default Enter should not enter the graph
|
|
232
|
-
myNVL.getContainer().dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
233
|
-
expect(callbackMock).not.toHaveBeenCalled();
|
|
234
|
-
// Custom key should enter the graph
|
|
235
|
-
myNVL.getContainer().dispatchEvent(new KeyboardEvent('keydown', { key: 'Space', bubbles: true }));
|
|
236
|
-
expect(callbackMock).toHaveBeenCalledWith(expect.objectContaining({ id: '0' }), expect.any(KeyboardEvent));
|
|
237
|
-
});
|
|
238
|
-
test('respects custom navigateForwardKey', () => {
|
|
239
|
-
keyboardInteraction.destroy();
|
|
240
|
-
keyboardInteraction = new KeyboardInteraction(myNVL, { navigateForwardKey: ['ArrowDown'] });
|
|
241
|
-
keyboardInteraction.updateCallback('onNodeFocus', callbackMock);
|
|
242
|
-
const container = myNVL.getContainer();
|
|
243
|
-
// Enter the graph first
|
|
244
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
245
|
-
// Custom forward key should navigate to the next element
|
|
246
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
|
|
247
|
-
expect(callbackMock).toHaveBeenLastCalledWith(expect.objectContaining({ id: '1' }), expect.any(KeyboardEvent));
|
|
248
|
-
});
|
|
249
|
-
test('respects custom navigateBackwardKey', () => {
|
|
250
|
-
keyboardInteraction.destroy();
|
|
251
|
-
keyboardInteraction = new KeyboardInteraction(myNVL, { navigateBackwardKey: ['ArrowUp'] });
|
|
252
|
-
keyboardInteraction.updateCallback('onNodeFocus', jest.fn());
|
|
253
|
-
keyboardInteraction.updateCallback('onRelationshipFocus', callbackMock);
|
|
254
|
-
const container = myNVL.getContainer();
|
|
255
|
-
// Enter the graph first
|
|
256
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
257
|
-
// Custom backward key should navigate to the previous (last) element
|
|
258
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }));
|
|
259
|
-
expect(callbackMock).toHaveBeenCalledWith(expect.objectContaining({ id: '10' }), expect.any(KeyboardEvent));
|
|
260
|
-
});
|
|
261
|
-
test('respects custom exitGraphKey', () => {
|
|
262
|
-
keyboardInteraction.destroy();
|
|
263
|
-
keyboardInteraction = new KeyboardInteraction(myNVL, { exitGraphKey: ['q'] });
|
|
264
|
-
keyboardInteraction.updateCallback('onNodeFocus', jest.fn());
|
|
265
|
-
keyboardInteraction.updateCallback('onNodeBlur', callbackMock);
|
|
266
|
-
const container = myNVL.getContainer();
|
|
267
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
268
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'q', bubbles: true }));
|
|
269
|
-
expect(callbackMock).toHaveBeenCalledWith(expect.objectContaining({ id: '0' }), expect.any(KeyboardEvent));
|
|
270
|
-
});
|
|
271
|
-
test('respects custom contextMenuKey', () => {
|
|
272
|
-
keyboardInteraction.destroy();
|
|
273
|
-
keyboardInteraction = new KeyboardInteraction(myNVL, { contextMenuKey: ['Ctrl', 'm'] });
|
|
274
|
-
const contextMenuMock = jest.fn();
|
|
275
|
-
keyboardInteraction.updateCallback('onNodeFocus', jest.fn());
|
|
276
|
-
keyboardInteraction.updateCallback('onContextMenu', contextMenuMock);
|
|
277
|
-
const container = myNVL.getContainer();
|
|
278
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
279
|
-
const event = new KeyboardEvent('keydown', { key: 'm', ctrlKey: true, bubbles: true });
|
|
280
|
-
container.dispatchEvent(event);
|
|
281
|
-
expect(contextMenuMock).toHaveBeenCalledWith(expect.objectContaining({ id: '0' }), event);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
test('getFocused returns the currently focused node', () => {
|
|
285
|
-
keyboardInteraction.updateCallback('onNodeFocus', jest.fn());
|
|
286
|
-
expect(keyboardInteraction.getFocused()).toBeUndefined();
|
|
287
|
-
myNVL.getContainer().dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
288
|
-
expect(keyboardInteraction.getFocused()).toEqual(expect.objectContaining({ id: '0' }));
|
|
289
|
-
});
|
|
290
|
-
test('getFocused returns the currently focused relationship', () => {
|
|
291
|
-
keyboardInteraction.updateCallback('onNodeFocus', jest.fn());
|
|
292
|
-
keyboardInteraction.updateCallback('onRelationshipFocus', jest.fn());
|
|
293
|
-
const container = myNVL.getContainer();
|
|
294
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
295
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
296
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
297
|
-
expect(keyboardInteraction.getFocused()).toEqual(expect.objectContaining({ id: '10' }));
|
|
298
|
-
});
|
|
299
|
-
test('onKeyDown callback receives the currently focused element', () => {
|
|
300
|
-
keyboardInteraction.updateCallback('onNodeFocus', jest.fn());
|
|
301
|
-
keyboardInteraction.updateCallback('onKeyDown', callbackMock);
|
|
302
|
-
const container = myNVL.getContainer();
|
|
303
|
-
container.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
304
|
-
const event = new KeyboardEvent('keydown', { key: 'a', bubbles: true });
|
|
305
|
-
container.dispatchEvent(event);
|
|
306
|
-
expect(callbackMock).toHaveBeenLastCalledWith(event, expect.objectContaining({ id: '0' }));
|
|
307
|
-
});
|
|
308
|
-
test('destroy removes event listeners', () => {
|
|
309
|
-
keyboardInteraction.updateCallback('onKeyDown', callbackMock);
|
|
310
|
-
keyboardInteraction.destroy();
|
|
311
|
-
const event = new KeyboardEvent('keydown', { key: 'Tab' });
|
|
312
|
-
myNVL.getContainer().dispatchEvent(event);
|
|
313
|
-
expect(callbackMock).not.toHaveBeenCalled();
|
|
314
|
-
});
|
|
315
|
-
test('destroy clears focus state', () => {
|
|
316
|
-
keyboardInteraction.updateCallback('onNodeFocus', jest.fn());
|
|
317
|
-
myNVL.getContainer().dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
318
|
-
expect(keyboardInteraction.getFocused()).toBeDefined();
|
|
319
|
-
keyboardInteraction.destroy();
|
|
320
|
-
expect(keyboardInteraction.getFocused()).toBeUndefined();
|
|
321
|
-
});
|
|
322
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import '@testing-library/jest-dom';
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import NVL from '@neo4j-nvl/base';
|
|
2
|
-
import '@testing-library/jest-dom';
|
|
3
|
-
import { LassoInteraction, checkIntersection, checkLinesCrossing, checkPointInside } from '../interaction-handlers/lasso-interaction';
|
|
4
|
-
jest.mock('@neo4j-nvl/layout-workers');
|
|
5
|
-
const testNodes = [
|
|
6
|
-
{ id: '0', x: 25, y: 25 },
|
|
7
|
-
{ id: '1', x: 200, y: 200 }
|
|
8
|
-
];
|
|
9
|
-
const testRels = [{ id: '10', from: '0', to: '1' }];
|
|
10
|
-
const createMouseEvent = (type, x, y) => {
|
|
11
|
-
return new MouseEvent(type, { button: 0, buttons: 1, clientX: x, clientY: y });
|
|
12
|
-
};
|
|
13
|
-
describe('LassoInteraction', () => {
|
|
14
|
-
let lassoInteraction;
|
|
15
|
-
let myNVL;
|
|
16
|
-
const startCallback = jest.fn();
|
|
17
|
-
const selectCallback = jest.fn();
|
|
18
|
-
const lassoSelectCallbackMock = jest.fn();
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
myNVL = new NVL(document.createElement('div'), testNodes, testRels, {
|
|
21
|
-
disableWebGL: true,
|
|
22
|
-
initialZoom: 1,
|
|
23
|
-
layout: 'free'
|
|
24
|
-
});
|
|
25
|
-
lassoInteraction = new LassoInteraction(myNVL);
|
|
26
|
-
});
|
|
27
|
-
afterEach(() => {
|
|
28
|
-
lassoInteraction.destroy();
|
|
29
|
-
myNVL.destroy();
|
|
30
|
-
startCallback.mockReset();
|
|
31
|
-
selectCallback.mockReset();
|
|
32
|
-
lassoSelectCallbackMock.mockReset();
|
|
33
|
-
});
|
|
34
|
-
test('can select individual nodes', () => {
|
|
35
|
-
lassoInteraction.updateCallback('onLassoStarted', startCallback);
|
|
36
|
-
lassoInteraction.updateCallback('onLassoSelect', selectCallback);
|
|
37
|
-
expect(myNVL.getSelectedNodes()).toHaveLength(0);
|
|
38
|
-
const gesture1 = [
|
|
39
|
-
createMouseEvent('mousedown', 0, 0),
|
|
40
|
-
createMouseEvent('mousemove', 50, 0),
|
|
41
|
-
createMouseEvent('mousemove', 20, 50),
|
|
42
|
-
createMouseEvent('mouseup', 0, 0)
|
|
43
|
-
];
|
|
44
|
-
const gesture2 = [
|
|
45
|
-
createMouseEvent('mousedown', 200, 150),
|
|
46
|
-
createMouseEvent('mousemove', 210, 210),
|
|
47
|
-
createMouseEvent('mouseup', 190, 210)
|
|
48
|
-
];
|
|
49
|
-
const container = myNVL.getContainer();
|
|
50
|
-
gesture1.forEach((e) => container.dispatchEvent(e));
|
|
51
|
-
gesture2.forEach((e) => container.dispatchEvent(e));
|
|
52
|
-
return new Promise((resolve) => {
|
|
53
|
-
expect(startCallback).toHaveBeenCalledTimes(2);
|
|
54
|
-
expect(selectCallback).toHaveBeenCalledTimes(2);
|
|
55
|
-
expect(selectCallback).toHaveBeenNthCalledWith(1, { nodes: [testNodes[0]], rels: [] }, gesture1[3]);
|
|
56
|
-
expect(selectCallback).toHaveBeenNthCalledWith(2, { nodes: [testNodes[1]], rels: [] }, gesture2[2]);
|
|
57
|
-
resolve();
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
test('can select relationships', () => {
|
|
61
|
-
lassoInteraction.updateCallback('onLassoStarted', startCallback);
|
|
62
|
-
lassoInteraction.updateCallback('onLassoSelect', selectCallback);
|
|
63
|
-
expect(myNVL.getSelectedNodes()).toHaveLength(0);
|
|
64
|
-
const gesture = [
|
|
65
|
-
createMouseEvent('mousedown', 0, 0),
|
|
66
|
-
createMouseEvent('mousemove', 250, 0),
|
|
67
|
-
createMouseEvent('mousemove', 240, 240),
|
|
68
|
-
createMouseEvent('mousemove', 100, 200),
|
|
69
|
-
createMouseEvent('mousemove', 0, 50),
|
|
70
|
-
createMouseEvent('mouseup', 0, 0)
|
|
71
|
-
];
|
|
72
|
-
const container = myNVL.getContainer();
|
|
73
|
-
gesture.forEach((e) => container.dispatchEvent(e));
|
|
74
|
-
return new Promise((resolve) => {
|
|
75
|
-
expect(startCallback).toHaveBeenCalledTimes(1);
|
|
76
|
-
expect(selectCallback).toHaveBeenCalledTimes(1);
|
|
77
|
-
expect(selectCallback).toHaveBeenCalledWith({ nodes: [testNodes[0], testNodes[1]], rels: [testRels[0]] }, gesture[5]);
|
|
78
|
-
resolve();
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
test('does not fire started or select callbacks when movement stays below drag threshold', () => {
|
|
82
|
-
lassoInteraction.updateCallback('onLassoStarted', startCallback);
|
|
83
|
-
lassoInteraction.updateCallback('onLassoSelect', selectCallback);
|
|
84
|
-
const container = myNVL.getContainer();
|
|
85
|
-
container.dispatchEvent(createMouseEvent('mousedown', 100, 100));
|
|
86
|
-
container.dispatchEvent(createMouseEvent('mousemove', 100, 103));
|
|
87
|
-
container.dispatchEvent(createMouseEvent('mouseup', 100, 103));
|
|
88
|
-
expect(startCallback).not.toHaveBeenCalled();
|
|
89
|
-
expect(selectCallback).not.toHaveBeenCalled();
|
|
90
|
-
});
|
|
91
|
-
test('can start lasso when only a relationship is under the pointer', () => {
|
|
92
|
-
lassoInteraction.updateCallback('onLassoStarted', startCallback);
|
|
93
|
-
lassoInteraction.updateCallback('onLassoSelect', selectCallback);
|
|
94
|
-
const gesture = [
|
|
95
|
-
createMouseEvent('mousedown', 100, 100),
|
|
96
|
-
createMouseEvent('mousemove', 150, 100),
|
|
97
|
-
createMouseEvent('mousemove', 50, 150),
|
|
98
|
-
createMouseEvent('mouseup', 100, 100)
|
|
99
|
-
];
|
|
100
|
-
const container = myNVL.getContainer();
|
|
101
|
-
gesture.forEach((e) => container.dispatchEvent(e));
|
|
102
|
-
return new Promise((resolve) => {
|
|
103
|
-
expect(startCallback).toHaveBeenCalledTimes(1);
|
|
104
|
-
expect(selectCallback).toHaveBeenCalledTimes(1);
|
|
105
|
-
resolve();
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
test('will not start when node is below pointer when dragging', () => {
|
|
109
|
-
lassoInteraction.updateCallback('onLassoStarted', startCallback);
|
|
110
|
-
lassoInteraction.updateCallback('onLassoSelect', selectCallback);
|
|
111
|
-
expect(myNVL.getSelectedNodes()).toHaveLength(0);
|
|
112
|
-
const gesture1 = [
|
|
113
|
-
createMouseEvent('mousedown', 25, 25),
|
|
114
|
-
createMouseEvent('mousemove', 50, 0),
|
|
115
|
-
createMouseEvent('mousemove', 20, 50),
|
|
116
|
-
createMouseEvent('mouseup', 0, 0)
|
|
117
|
-
];
|
|
118
|
-
const container = myNVL.getContainer();
|
|
119
|
-
gesture1.forEach((e) => container.dispatchEvent(e));
|
|
120
|
-
return new Promise((resolve) => {
|
|
121
|
-
expect(startCallback).not.toHaveBeenCalled();
|
|
122
|
-
resolve();
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
test('does not fire callbacks when clicking directly on a relationship without dragging', () => {
|
|
126
|
-
lassoInteraction.updateCallback('onLassoStarted', startCallback);
|
|
127
|
-
lassoInteraction.updateCallback('onLassoSelect', selectCallback);
|
|
128
|
-
const container = myNVL.getContainer();
|
|
129
|
-
container.dispatchEvent(createMouseEvent('mousedown', 112, 112));
|
|
130
|
-
container.dispatchEvent(createMouseEvent('mouseup', 112, 112));
|
|
131
|
-
expect(startCallback).not.toHaveBeenCalled();
|
|
132
|
-
expect(selectCallback).not.toHaveBeenCalled();
|
|
133
|
-
});
|
|
134
|
-
test('test for line crossing utility function', () => {
|
|
135
|
-
const res1 = checkLinesCrossing([0, 0], [10, 10], [10, 0], [0, 10]);
|
|
136
|
-
expect(res1).toBeTruthy();
|
|
137
|
-
const res2 = checkLinesCrossing([10, 10], [227, 182], [390, 10], [100, 100]);
|
|
138
|
-
expect(res2).toBeTruthy();
|
|
139
|
-
const res3 = checkLinesCrossing([5, 5], [5, 20], [10, 10], [20, 10]);
|
|
140
|
-
expect(res3).toBeFalsy();
|
|
141
|
-
const res4 = checkLinesCrossing([10, 10], [20, 10], [15, 10], [25, 10]);
|
|
142
|
-
expect(res4).toBeFalsy();
|
|
143
|
-
const res5 = checkLinesCrossing([880, 416], [872, 404], [868, 392], [856, 378]);
|
|
144
|
-
expect(res5).toBeFalsy();
|
|
145
|
-
});
|
|
146
|
-
test('test for line segment crossing utility function', () => {
|
|
147
|
-
const res1 = checkIntersection([
|
|
148
|
-
[0, 0],
|
|
149
|
-
[10, 0],
|
|
150
|
-
[100, 10],
|
|
151
|
-
[50, 10],
|
|
152
|
-
[50, -10]
|
|
153
|
-
]);
|
|
154
|
-
expect(res1).toBeTruthy();
|
|
155
|
-
const res2 = checkIntersection([
|
|
156
|
-
[10, 10],
|
|
157
|
-
[227, 182],
|
|
158
|
-
[390, 10],
|
|
159
|
-
[500, 100]
|
|
160
|
-
]);
|
|
161
|
-
expect(res2).toBeTruthy();
|
|
162
|
-
const res3 = checkIntersection([
|
|
163
|
-
[50, 10],
|
|
164
|
-
[10, 10],
|
|
165
|
-
[10, 50],
|
|
166
|
-
[50, 50]
|
|
167
|
-
]);
|
|
168
|
-
expect(res3).toBeFalsy();
|
|
169
|
-
});
|
|
170
|
-
test('test for point inside polygon utility function', () => {
|
|
171
|
-
const res1 = checkPointInside(10, 10, [
|
|
172
|
-
{ x: 0, y: 0 },
|
|
173
|
-
{ x: 10, y: -10 },
|
|
174
|
-
{ x: 20, y: 0 },
|
|
175
|
-
{ x: 10, y: 20 }
|
|
176
|
-
]);
|
|
177
|
-
expect(res1).toBeTruthy();
|
|
178
|
-
const res2 = checkPointInside(10, 10, [
|
|
179
|
-
{ x: 0, y: 0 },
|
|
180
|
-
{ x: 10, y: -10 },
|
|
181
|
-
{ x: 20, y: 0 },
|
|
182
|
-
{ x: 10, y: 5 }
|
|
183
|
-
]);
|
|
184
|
-
expect(res2).toBeFalsy();
|
|
185
|
-
});
|
|
186
|
-
test('lasso selection should disable text selection until mouse release', () => {
|
|
187
|
-
const container = myNVL.getContainer();
|
|
188
|
-
lassoInteraction.updateCallback('onLassoSelect', lassoSelectCallbackMock);
|
|
189
|
-
const gesture = [createMouseEvent('mousedown', 100, 100), createMouseEvent('mousemove', 150, 150)];
|
|
190
|
-
gesture.forEach((e) => container.dispatchEvent(e));
|
|
191
|
-
expect(document.body.style.getPropertyValue('user-select')).toBe('none');
|
|
192
|
-
document.body.dispatchEvent(createMouseEvent('mouseup', 150, 150));
|
|
193
|
-
expect(document.body.style.getPropertyValue('user-select')).toBe('');
|
|
194
|
-
});
|
|
195
|
-
test('lasso selection should work after leaving and re-entering the app area', () => {
|
|
196
|
-
const container = myNVL.getContainer();
|
|
197
|
-
lassoInteraction.updateCallback('onLassoStarted', startCallback);
|
|
198
|
-
lassoInteraction.updateCallback('onLassoSelect', selectCallback);
|
|
199
|
-
const exitGesture = [
|
|
200
|
-
createMouseEvent('mousedown', 0, 0),
|
|
201
|
-
createMouseEvent('mousemove', 50, 0),
|
|
202
|
-
createMouseEvent('mouseleave', 99, 99)
|
|
203
|
-
];
|
|
204
|
-
exitGesture.forEach((e) => container.dispatchEvent(e));
|
|
205
|
-
const moveOutsideEvent = createMouseEvent('mousemove', 100, 100);
|
|
206
|
-
document.body.dispatchEvent(moveOutsideEvent);
|
|
207
|
-
const reEnterGesture = [createMouseEvent('mousemove', 150, 150), createMouseEvent('mouseup', 0, 0)];
|
|
208
|
-
reEnterGesture.forEach((e) => container.dispatchEvent(e));
|
|
209
|
-
return new Promise((resolve) => {
|
|
210
|
-
expect(startCallback).toHaveBeenCalledTimes(1);
|
|
211
|
-
expect(selectCallback).toHaveBeenCalledTimes(1);
|
|
212
|
-
expect(selectCallback).toHaveBeenCalledWith({
|
|
213
|
-
nodes: [testNodes[0]],
|
|
214
|
-
rels: []
|
|
215
|
-
}, reEnterGesture[1]);
|
|
216
|
-
resolve();
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import '@testing-library/jest-dom';
|