@peter.naydenov/shortcuts 3.3.1 → 3.4.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 +45 -1
- package/dist/shortcuts.cjs +1 -1
- package/dist/shortcuts.esm.mjs +1 -1
- package/dist/shortcuts.umd.js +1 -1
- package/jsconfig.json +10 -0
- package/package.json +12 -6
- package/src/main.js +26 -10
- package/src/methods/_readShortcutWithPlugins.js +2 -1
- package/src/methods/listShortcuts.js +2 -1
- package/src/methods/load.js +6 -4
- package/src/plugins/click/_listenDOM.js +12 -2
- package/src/plugins/click/index.js +6 -6
- package/src/plugins/form/index.js +4 -4
- package/src/plugins/key/_listenDOM.js +13 -0
- package/src/plugins/key/index.js +1 -5
- package/test/01-general.test.js +97 -249
- package/test/02-key.test.js +174 -0
- package/test/03-click.test.js +320 -0
- package/test/04-form.test.js +90 -0
- package/test-helpers/setup.js +18 -0
- package/test-helpers/wait.js +8 -0
- package/vitest.config.js +21 -0
- package/vitest-example/HelloWorld.js +0 -9
- package/vitest-example/HelloWorld.test.js +0 -11
- package/vitest.workspace.js +0 -19
- /package/{test-components → test-helpers}/Block.jsx +0 -0
- /package/{test-components → test-helpers}/style.css +0 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { beforeEach, afterEach, describe, it, expect } from 'vitest'
|
|
2
|
+
import { userEvent } from '@vitest/browser/context'
|
|
3
|
+
import {
|
|
4
|
+
getByLabelText,
|
|
5
|
+
getByText,
|
|
6
|
+
getByTestId,
|
|
7
|
+
queryByTestId,
|
|
8
|
+
// Tip: all queries are also exposed on an object
|
|
9
|
+
// called "queries" which you could import here as well
|
|
10
|
+
waitFor
|
|
11
|
+
} from '@testing-library/dom'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import '../test-helpers/style.css'
|
|
16
|
+
import Block from '../test-helpers/Block.jsx'
|
|
17
|
+
import VisaulController from '@peter.naydenov/visual-controller-for-react'
|
|
18
|
+
import wait from '../test-helpers/wait.js'
|
|
19
|
+
import {
|
|
20
|
+
shortcuts
|
|
21
|
+
, pluginKey
|
|
22
|
+
, pluginClick
|
|
23
|
+
, pluginForm
|
|
24
|
+
} from '../src/main.js'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
const html = new VisaulController ();
|
|
29
|
+
let
|
|
30
|
+
a = false
|
|
31
|
+
, b = false
|
|
32
|
+
, c = null
|
|
33
|
+
;
|
|
34
|
+
|
|
35
|
+
const contextDefinition = {
|
|
36
|
+
general : {
|
|
37
|
+
' key : shift+a': [
|
|
38
|
+
() => a = true,
|
|
39
|
+
() => c = 'triggered'
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
, touch : {
|
|
43
|
+
// Single click with left button
|
|
44
|
+
' click: left-1': ({ target }) => {
|
|
45
|
+
b = true
|
|
46
|
+
// Named argument 'target' should be available
|
|
47
|
+
c = target.dataset.click
|
|
48
|
+
},
|
|
49
|
+
// Double click with left button
|
|
50
|
+
'click: left-2': ({ target }) => {
|
|
51
|
+
b = true
|
|
52
|
+
c = target.dataset.click
|
|
53
|
+
},
|
|
54
|
+
// Single click with right button
|
|
55
|
+
'click: right-1': () => c = 'right'
|
|
56
|
+
}
|
|
57
|
+
, extra : {
|
|
58
|
+
'key : p,r,o,b,a': () => b = true
|
|
59
|
+
}
|
|
60
|
+
, extend : {
|
|
61
|
+
'form : watch' : () => 'input'
|
|
62
|
+
, 'form : define' : () => 'input'
|
|
63
|
+
, 'form : action' : () => [
|
|
64
|
+
{
|
|
65
|
+
fn : (e) => console.log ( e.target )
|
|
66
|
+
, type : 'input'
|
|
67
|
+
, mode : 'in'
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
let short = shortcuts ();
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
describe ( 'Click plugin', () => {
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
beforeEach ( async () => {
|
|
83
|
+
short.load ( contextDefinition )
|
|
84
|
+
let container = document.createElement ( 'div' );
|
|
85
|
+
container.id = 'app'
|
|
86
|
+
document.body.appendChild ( container )
|
|
87
|
+
await html.publish ( Block, {}, 'app' )
|
|
88
|
+
a = false, b = false
|
|
89
|
+
}) // beforeEach
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
afterEach ( async () => {
|
|
94
|
+
short.reset ();
|
|
95
|
+
a = false, b = false, c = null;
|
|
96
|
+
}) // afterEach
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
it ( 'No "click" plugin installed', async () => {
|
|
101
|
+
let r = short.listShortcuts ('touch');
|
|
102
|
+
// Shortcuts are untouched if plugin is not installed
|
|
103
|
+
expect ( r[0]).to.equal ( ' click: left-1' )
|
|
104
|
+
}) // it no 'click' plugin installed
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
it ( 'Click plugin installed', async () => {
|
|
109
|
+
short.enablePlugin ( pluginClick )
|
|
110
|
+
let r = short.listShortcuts ( 'touch' );
|
|
111
|
+
// Shortcuts are normalized
|
|
112
|
+
expect ( r[0]).to.equal ( 'CLICK:LEFT-1' )
|
|
113
|
+
}) // it click plugin installed
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
it ( 'Single left click', async () => {
|
|
118
|
+
expect ( b ).to.equal ( false )
|
|
119
|
+
short.enablePlugin ( pluginClick )
|
|
120
|
+
short.changeContext ( 'touch' )
|
|
121
|
+
await userEvent.click ( document.querySelector ( '#rspan' ) )
|
|
122
|
+
await wait ( 330 )
|
|
123
|
+
await waitFor ( () => {
|
|
124
|
+
expect ( b ).to.equal ( true )
|
|
125
|
+
// Target is a element that contains data-click property!
|
|
126
|
+
expect ( c ).to.equal ( 'red' )
|
|
127
|
+
// We clicked on span, but target is the parent element
|
|
128
|
+
// that contains data-click property
|
|
129
|
+
}, { timeout: 1000, interval: 12 })
|
|
130
|
+
}) // it single left click
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
it ( 'Double left click', async () => {
|
|
135
|
+
expect ( b ).to.equal ( false )
|
|
136
|
+
short.enablePlugin ( pluginClick )
|
|
137
|
+
short.changeContext ( 'touch' )
|
|
138
|
+
await userEvent.dblClick ( document.querySelector ( '#rspan' ) )
|
|
139
|
+
await wait ( 20 )
|
|
140
|
+
// Default wait mouse timeout is 320 ms, but maxClicks is set to 2,
|
|
141
|
+
// so we don't need to wait for timeout
|
|
142
|
+
await waitFor ( () => {
|
|
143
|
+
expect ( b ).to.equal ( true )
|
|
144
|
+
// Target is a element that contains data-click property!
|
|
145
|
+
expect ( c ).to.equal ( 'red' )
|
|
146
|
+
// We clicked on span, but target is the parent element
|
|
147
|
+
// that contains data-click property
|
|
148
|
+
}, { timeout: 1000, interval: 12 })
|
|
149
|
+
}) // it double left click
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
it ( 'Triple left click', async () => {
|
|
154
|
+
short.enablePlugin ( pluginClick )
|
|
155
|
+
const hitItem = document.querySelector ( '#rspan' );
|
|
156
|
+
expect ( a ).to.equal ( false )
|
|
157
|
+
short.changeContext ( 'touch' )
|
|
158
|
+
// Load will restart the selected context
|
|
159
|
+
short.load ({
|
|
160
|
+
// load will overwrite existing 'touch' context definition
|
|
161
|
+
'touch' : {
|
|
162
|
+
'click: left-3' : () => a = true
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
await wait ( 12 )
|
|
166
|
+
await userEvent.tripleClick ( hitItem )
|
|
167
|
+
// Default wait mouse timeout is 320 ms, but maxClicks is set to 3,
|
|
168
|
+
// so we don't need to wait for timeout
|
|
169
|
+
await waitFor ( () => {
|
|
170
|
+
expect ( a ).to.equal ( true )
|
|
171
|
+
expect ( b ).to.equal ( false )
|
|
172
|
+
}, { timeout: 1000, interval: 12 })
|
|
173
|
+
}) // it triple left click
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
it ( 'Single right click', async () => {
|
|
178
|
+
short.enablePlugin ( pluginClick )
|
|
179
|
+
// Context 'touch' was changed during previous test
|
|
180
|
+
// Return to original context.
|
|
181
|
+
short.load ( contextDefinition )
|
|
182
|
+
short.changeContext ( 'touch' )
|
|
183
|
+
let find = null
|
|
184
|
+
await waitFor ( () => {
|
|
185
|
+
find = document.querySelector ( '#rspan' )
|
|
186
|
+
},{ timeout: 1000, interval: 12 })
|
|
187
|
+
if ( find ) await userEvent.click ( find , { button:'right' })
|
|
188
|
+
// Default wait mouse timeout is 320 ms
|
|
189
|
+
await wait ( 320 )
|
|
190
|
+
await waitFor ( () => {
|
|
191
|
+
expect ( c ).to.equal ( 'right' )
|
|
192
|
+
}, { timeout: 1000, interval: 12 })
|
|
193
|
+
}) // it single right click
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
it ( 'Arguments of click handler', async () => {
|
|
198
|
+
/**
|
|
199
|
+
* Need to know arguments for 'click' handler
|
|
200
|
+
* function myMouseHandler ({
|
|
201
|
+
* context // (string) Name of the current context;
|
|
202
|
+
* , note // (string) Name of the note or null if note isn't set;
|
|
203
|
+
* , dependencies // (object) Object with dependencies that you have set by calling `setDependencies` method;
|
|
204
|
+
* , target // (DOM element). Target element of the mouse event;
|
|
205
|
+
* , targetProps // (object). Coordinates of the target element (top, left, right, bottom, width, height) or null if target element is not available;
|
|
206
|
+
* , x // (number). X coordinate of the target element;
|
|
207
|
+
* , y // (number). Y coordinate of the target element;
|
|
208
|
+
* , event // (object). Original mouse event object;
|
|
209
|
+
* }) {
|
|
210
|
+
* // Body of the handler. Do something...
|
|
211
|
+
* }
|
|
212
|
+
*/
|
|
213
|
+
// Ensure clean state for this test
|
|
214
|
+
let megaBtn = document.querySelector ( '[data-click="mega"]' )
|
|
215
|
+
let test = [];
|
|
216
|
+
let i = 0;
|
|
217
|
+
short.enablePlugin ( pluginClick )
|
|
218
|
+
short.setDependencies ({ test })
|
|
219
|
+
short.load ({
|
|
220
|
+
'local' : {
|
|
221
|
+
'click: left-1' : ({
|
|
222
|
+
dependencies
|
|
223
|
+
, target
|
|
224
|
+
, x
|
|
225
|
+
, y
|
|
226
|
+
, targetProps
|
|
227
|
+
, context
|
|
228
|
+
}) => {
|
|
229
|
+
const
|
|
230
|
+
{ test } = dependencies
|
|
231
|
+
, result = {
|
|
232
|
+
x
|
|
233
|
+
, y
|
|
234
|
+
, targetProps
|
|
235
|
+
, context
|
|
236
|
+
}
|
|
237
|
+
;
|
|
238
|
+
result.target = target.dataset.click
|
|
239
|
+
test.push ( result )
|
|
240
|
+
i++
|
|
241
|
+
}
|
|
242
|
+
} // local
|
|
243
|
+
})
|
|
244
|
+
short.changeContext ( 'local' )
|
|
245
|
+
expect ( megaBtn ).to.not.be.null
|
|
246
|
+
await userEvent.click ( megaBtn )
|
|
247
|
+
await wait ( 50 ) // Wait for click processing
|
|
248
|
+
await waitFor ( () => {
|
|
249
|
+
expect ( i ).to.be.equal ( 1 )
|
|
250
|
+
let result = test[0];
|
|
251
|
+
expect ( result.target ).to.be.equal ( 'mega' )
|
|
252
|
+
expect ( result.context ).to.be.equal ( 'local' )
|
|
253
|
+
}, { timeout: 1000, interval: 12 })
|
|
254
|
+
}) // it arguments of click handler
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
it ( 'Click on anchor', async () => {
|
|
259
|
+
// Click on anchor that don't have click-data attribute.
|
|
260
|
+
let result = 'none';
|
|
261
|
+
short.enablePlugin ( pluginClick )
|
|
262
|
+
short.load ({ 'extra' : {
|
|
263
|
+
'click: 1 - left' : ({target, context, event }) => { // Order of button name and number of click is not important
|
|
264
|
+
event.preventDefault ()
|
|
265
|
+
expect ( context ).to.be.equal ( 'extra' )
|
|
266
|
+
expect ( target.nodeName ).to.be.equal ( 'A' )
|
|
267
|
+
result = target.nodeName
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
})
|
|
271
|
+
short.changeContext ( 'extra' )
|
|
272
|
+
let loc = document.querySelector ( '#anchor' ) || false;
|
|
273
|
+
if ( loc ) await userEvent.click ( loc )
|
|
274
|
+
expect ( result ).to.be.equal ( 'A' )
|
|
275
|
+
}) // it click on anchor
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
it ( 'Mute click plugin', async () => {
|
|
280
|
+
const
|
|
281
|
+
result = []
|
|
282
|
+
, trg = document.querySelector ( '#rspan' )
|
|
283
|
+
;
|
|
284
|
+
|
|
285
|
+
let i = 0;
|
|
286
|
+
result.push ( 'init' )
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
short.load ({
|
|
290
|
+
'local' : {
|
|
291
|
+
'click: left-1 ' : ({dependencies}) => {
|
|
292
|
+
let { result } = dependencies;
|
|
293
|
+
result.push ( i++ )
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
short.setDependencies ({ result })
|
|
298
|
+
short.enablePlugin ( pluginClick )
|
|
299
|
+
short.changeContext ( 'local' )
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
await userEvent.click ( trg )
|
|
303
|
+
await wait( 330 )
|
|
304
|
+
await waitFor ( () => {
|
|
305
|
+
// We checking if the shortcut works
|
|
306
|
+
expect ( result ).to.have.lengthOf ( 2 )
|
|
307
|
+
expect ( i ).to.equal ( 1 )
|
|
308
|
+
}, { timeout: 1000, interval: 12 })
|
|
309
|
+
|
|
310
|
+
short.mutePlugin ( 'click' )
|
|
311
|
+
|
|
312
|
+
await userEvent.click ( trg )
|
|
313
|
+
await waitFor ( () => {
|
|
314
|
+
// Plugin is muted, so we don't expect any changes
|
|
315
|
+
expect ( result ).to.have.lengthOf ( 2 )
|
|
316
|
+
expect ( i ).to.equal ( 1 )
|
|
317
|
+
}, { timeout: 1000, interval: 12 })
|
|
318
|
+
}) // it mute click plugin
|
|
319
|
+
|
|
320
|
+
}) // describe
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { beforeEach, afterEach, describe, it, test, expect } from 'vitest'
|
|
2
|
+
import { userEvent } from '@vitest/browser/context'
|
|
3
|
+
import {
|
|
4
|
+
getByLabelText,
|
|
5
|
+
getByText,
|
|
6
|
+
getByTestId,
|
|
7
|
+
queryByTestId,
|
|
8
|
+
// Tip: all queries are also exposed on an object
|
|
9
|
+
// called "queries" which you could import here as well
|
|
10
|
+
waitFor
|
|
11
|
+
} from '@testing-library/dom'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import '../test-helpers/style.css'
|
|
16
|
+
import Block from '../test-helpers/Block.jsx'
|
|
17
|
+
import VisaulController from '@peter.naydenov/visual-controller-for-react'
|
|
18
|
+
import wait from '../test-helpers/wait.js'
|
|
19
|
+
import {
|
|
20
|
+
shortcuts
|
|
21
|
+
, pluginClick
|
|
22
|
+
, pluginKey
|
|
23
|
+
, pluginForm
|
|
24
|
+
} from '../src/main.js'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
const html = new VisaulController ();
|
|
29
|
+
let
|
|
30
|
+
a = false
|
|
31
|
+
, b = false
|
|
32
|
+
, c = null
|
|
33
|
+
;
|
|
34
|
+
|
|
35
|
+
const contextDefinition = {
|
|
36
|
+
general : {
|
|
37
|
+
' key : shift+a': [
|
|
38
|
+
() => a = true,
|
|
39
|
+
() => c = 'triggered'
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
, touch : {
|
|
43
|
+
// Single click with left button
|
|
44
|
+
'click: left-1': () => b = true,
|
|
45
|
+
// Double click with left button
|
|
46
|
+
'click: left-2': () => b = true,
|
|
47
|
+
// Single click with right button
|
|
48
|
+
'click: right-1': () => b = true
|
|
49
|
+
}
|
|
50
|
+
, extra : {
|
|
51
|
+
'key : p,r,o,b,a': () => b = true
|
|
52
|
+
}
|
|
53
|
+
, extend : {
|
|
54
|
+
'form : watch' : () => 'input'
|
|
55
|
+
, 'form : define' : () => 'input'
|
|
56
|
+
, 'form : action' : () => [
|
|
57
|
+
{
|
|
58
|
+
fn : (e) => console.log ( e.target )
|
|
59
|
+
, type : 'input'
|
|
60
|
+
, mode : 'in'
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let short = shortcuts ();
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
describe.skip ( 'Form plugin', () => {
|
|
71
|
+
|
|
72
|
+
beforeEach ( async () => {
|
|
73
|
+
short.load ( contextDefinition )
|
|
74
|
+
let container = document.createElement ( 'div' )
|
|
75
|
+
container.id = 'app'
|
|
76
|
+
document.body.appendChild ( container )
|
|
77
|
+
await html.publish ( Block, {}, 'app' )
|
|
78
|
+
a = false, b = false
|
|
79
|
+
}) // beforeEach
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
afterEach ( async () => {
|
|
84
|
+
short.reset ()
|
|
85
|
+
a = false, b = false, c = null;
|
|
86
|
+
}) // afterEach
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
}) // describe
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
async function setup () {
|
|
2
|
+
let st = document.createElement ( 'style' )
|
|
3
|
+
st.textContent = cssCode
|
|
4
|
+
document.head.appendChild ( st )
|
|
5
|
+
await waitFor ( () => {
|
|
6
|
+
expect ( document.head ).to.have.property ( 'style' )
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
let container = document.createElement ( 'div' )
|
|
11
|
+
container.id = 'app'
|
|
12
|
+
document.body.appendChild ( container )
|
|
13
|
+
html.publish ( Block, {}, 'app' )
|
|
14
|
+
a = false, b = false
|
|
15
|
+
} // setup func.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export default setup
|
package/vitest.config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
|
|
4
|
+
export default defineConfig ({
|
|
5
|
+
plugins: [react()],
|
|
6
|
+
test: {
|
|
7
|
+
coverage: {
|
|
8
|
+
reporter: ['lcov', 'text-summary']
|
|
9
|
+
},
|
|
10
|
+
browser: {
|
|
11
|
+
enabled: true,
|
|
12
|
+
headless: true,
|
|
13
|
+
provider: 'playwright',
|
|
14
|
+
instances: [
|
|
15
|
+
{
|
|
16
|
+
browser: 'chromium'
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
})
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { expect, test } from 'vitest'
|
|
2
|
-
import { getByText } from '@testing-library/dom'
|
|
3
|
-
import HelloWorld from './HelloWorld.js'
|
|
4
|
-
|
|
5
|
-
test('renders name', () => {
|
|
6
|
-
const parent = HelloWorld({ name: 'Vitest' })
|
|
7
|
-
document.body.appendChild(parent)
|
|
8
|
-
|
|
9
|
-
const element = getByText(parent, 'Hello Vitest!')
|
|
10
|
-
expect(element).toBeInTheDocument()
|
|
11
|
-
})
|
package/vitest.workspace.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { defineWorkspace } from 'vitest/config'
|
|
2
|
-
|
|
3
|
-
export default defineWorkspace([
|
|
4
|
-
// If you want to keep running your existing tests in Node.js, uncomment the next line.
|
|
5
|
-
// 'vite.config.js',
|
|
6
|
-
{
|
|
7
|
-
extends: 'vite.config.js',
|
|
8
|
-
test: {
|
|
9
|
-
environment: 'browser',
|
|
10
|
-
browser: {
|
|
11
|
-
enabled: true,
|
|
12
|
-
name: 'chromium',
|
|
13
|
-
provider: 'playwright',
|
|
14
|
-
// https://vitest.dev/guide/browser/playwright
|
|
15
|
-
configs: [],
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
])
|
|
File without changes
|
|
File without changes
|