1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @emails react-core
8
+ */
9
+
10
+'use strict';
11
+
12
+import {
13
+ buttonsType,
14
+ createEventTarget,
15
+ describeWithPointerEvent,
16
+ setPointerEvent,
17
+ testWithPointerType,
18
+} from '../testing-library';
19
+
20
+let React;
21
+let ReactFeatureFlags;
22
+let ReactDOM;
23
+let useTap;
24
+
25
+function initializeModules(hasPointerEvents) {
26
+ jest.resetModules();
27
+ setPointerEvent(hasPointerEvents);
28
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
29
+ ReactFeatureFlags.enableFlareAPI = true;
30
+ React = require('react');
31
+ ReactDOM = require('react-dom');
32
+ useTap = require('react-events/tap').useTap;
33
+}
34
+
35
+const coordinatesInside = {x: 51, y: 51};
36
+const coordinatesOutside = {x: 49, y: 49};
37
+
38
+function tapAndMoveOutside({
39
+ hasPointerEvents,
40
+ pointerType,
41
+ downTarget,
42
+ upTarget,
43
+}) {
44
+ downTarget.setBoundingClientRect({width: 100, height: 100, x: 50, y: 50});
45
+ downTarget.pointerdown({pointerType, ...coordinatesInside});
46
+ downTarget.pointermove({pointerType, ...coordinatesInside});
47
+ // NOTE: this assumes the PointerEvent implementation calls
48
+ // 'releasePointerCapture' for touch pointers
49
+ if (!hasPointerEvents && pointerType === 'touch') {
50
+ downTarget.pointermove({pointerType, ...coordinatesOutside});
51
+ document.elementFromPoint = () => upTarget.node;
52
+ } else {
53
+ upTarget.pointermove({pointerType, ...coordinatesOutside});
54
+ }
55
+}
56
+
57
+function tapAndReleaseOutside({
58
+ hasPointerEvents,
59
+ pointerType,
60
+ downTarget,
61
+ upTarget,
62
+}) {
63
+ tapAndMoveOutside({hasPointerEvents, pointerType, downTarget, upTarget});
64
+ if (!hasPointerEvents && pointerType === 'touch') {
65
+ downTarget.pointerup({pointerType, ...coordinatesOutside});
66
+ } else {
67
+ upTarget.pointerup({pointerType, ...coordinatesOutside});
68
+ }
69
+}
70
+
71
+describeWithPointerEvent('Tap responder', hasPointerEvents => {
72
+ let container;
73
+
74
+ beforeEach(() => {
75
+ initializeModules(hasPointerEvents);
76
+ container = document.createElement('div');
77
+ document.body.appendChild(container);
78
+ });
79
+
80
+ afterEach(() => {
81
+ ReactDOM.render(null, container);
82
+ document.body.removeChild(container);
83
+ container = null;
84
+ });
85
+
86
+ test('supports repeated use', () => {
87
+ const ref = React.createRef();
88
+ const Component = () => {
89
+ const listener = useTap();
90
+ return <button ref={ref} listeners={listener} />;
91
+ };
92
+ ReactDOM.render(<Component />, container);
93
+
94
+ const target = createEventTarget(ref.current);
95
+ function interact() {
96
+ target.pointerdown();
97
+ target.pointermove();
98
+ target.pointerup();
99
+ }
100
+ expect(() => {
101
+ interact();
102
+ interact();
103
+ }).not.toThrow();
104
+ });
105
+
106
+ describe('disabled', () => {
107
+ let onTapStart, onTapChange, onTapUpdate, onTapCancel, onTapEnd, ref;
108
+
109
+ beforeEach(() => {
110
+ onTapStart = jest.fn();
111
+ onTapChange = jest.fn();
112
+ onTapUpdate = jest.fn();
113
+ onTapCancel = jest.fn();
114
+ onTapEnd = jest.fn();
115
+ ref = React.createRef();
116
+ const Component = () => {
117
+ const listener = useTap({
118
+ disabled: true,
119
+ onTapStart,
120
+ onTapChange,
121
+ onTapUpdate,
122
+ onTapCancel,
123
+ onTapEnd,
124
+ });
125
+ return <div ref={ref} listeners={listener} />;
126
+ };
127
+ ReactDOM.render(<Component />, container);
128
+ });
129
+
130
+ test('does not call callbacks', () => {
131
+ const target = createEventTarget(ref.current);
132
+ target.pointerdown();
133
+ target.pointerup();
134
+ expect(onTapStart).not.toBeCalled();
135
+ expect(onTapChange).not.toBeCalled();
136
+ expect(onTapUpdate).not.toBeCalled();
137
+ expect(onTapCancel).not.toBeCalled();
138
+ expect(onTapEnd).not.toBeCalled();
139
+ });
140
+ });
141
+
142
+ describe('onTapStart', () => {
143
+ let onTapStart, ref;
144
+
145
+ beforeEach(() => {
146
+ onTapStart = jest.fn();
147
+ ref = React.createRef();
148
+ const Component = () => {
149
+ const listener = useTap({onTapStart});
150
+ return <div ref={ref} listeners={listener} />;
151
+ };
152
+ ReactDOM.render(<Component />, container);
153
+ document.elementFromPoint = () => ref.current;
154
+ });
155
+
156
+ testWithPointerType('pointer down', pointerType => {
157
+ const target = createEventTarget(ref.current);
158
+ const nativeEvent = {
159
+ pageX: 10,
160
+ pageY: 10,
161
+ pointerType,
162
+ x: 10,
163
+ y: 10,
164
+ };
165
+ target.pointerdown(nativeEvent);
166
+ // 'pointerup' is only for the MouseEvent/TouchEvent fallback
167
+ // implementation. We also dispatch 'pointerup' so that this test covers
168
+ // the case where browsers dispatch an emulated mousedown (and mouseup)
169
+ // event *after* a touch ends.
170
+ target.pointerup(nativeEvent);
171
+ expect(onTapStart).toHaveBeenCalledTimes(1);
172
+ expect(onTapStart).toHaveBeenCalledWith(
173
+ expect.objectContaining({
174
+ altKey: false,
175
+ buttons: 1,
176
+ ctrlKey: false,
177
+ height: pointerType === 'mouse' ? 1 : 23,
178
+ metaKey: false,
179
+ pageX: 10,
180
+ pageY: 10,
181
+ pointerType,
182
+ pressure: pointerType === 'touch' ? 1 : 0.5,
183
+ screenX: 10,
184
+ screenY: 60,
185
+ shiftKey: false,
186
+ tangentialPressure: 0,
187
+ target: target.node,
188
+ tiltX: 0,
189
+ tiltY: 0,
190
+ timeStamp: expect.any(Number),
191
+ twist: 0,
192
+ type: 'tap:start',
193
+ width: pointerType === 'mouse' ? 1 : 23,
194
+ x: 10,
195
+ y: 10,
196
+ }),
197
+ );
198
+ });
199
+
200
+ test('second pointer down', () => {
201
+ const pointerType = 'touch';
202
+ const target = createEventTarget(ref.current);
203
+ const buttons = buttonsType.primary;
204
+ target.pointerdown({buttons, pointerId: 1, pointerType});
205
+ expect(onTapStart).toHaveBeenCalledTimes(1);
206
+ if (hasPointerEvents) {
207
+ target.pointerdown({buttons, pointerId: 2, pointerType});
208
+ } else {
209
+ // TouchEvents
210
+ target.pointerdown([
211
+ {pointerId: 1, pointerType},
212
+ {pointerId: 2, pointerType},
213
+ ]);
214
+ }
215
+ expect(onTapStart).toHaveBeenCalledTimes(1);
216
+ });
217
+
218
+ test('primary-button pointer down', () => {
219
+ const pointerType = 'mouse';
220
+ const buttons = buttonsType.primary;
221
+ const target = createEventTarget(ref.current);
222
+ target.pointerdown({buttons, pointerType});
223
+ expect(onTapStart).toHaveBeenCalledTimes(1);
224
+ expect(onTapStart).toHaveBeenCalledWith(
225
+ expect.objectContaining({buttons, pointerType}),
226
+ );
227
+ });
228
+
229
+ test('middle-button pointer down', () => {
230
+ const pointerType = 'mouse';
231
+ const buttons = buttonsType.middle;
232
+ const target = createEventTarget(ref.current);
233
+ target.pointerdown({buttons, pointerType});
234
+ expect(onTapStart).toHaveBeenCalledTimes(1);
235
+ expect(onTapStart).toHaveBeenCalledWith(
236
+ expect.objectContaining({buttons, pointerType}),
237
+ );
238
+ });
239
+
240
+ test('unsupported buttons', () => {
241
+ const target = createEventTarget(ref.current);
242
+ // right-click
243
+ target.pointerdown({buttons: buttonsType.secondary});
244
+ target.pointerup();
245
+ // pen eraser
246
+ target.pointerdown({buttons: buttonsType.eraser});
247
+ target.pointerup();
248
+ expect(onTapStart).toHaveBeenCalledTimes(0);
249
+ });
250
+ });
251
+
252
+ describe('onTapEnd', () => {
253
+ let onTapEnd, ref;
254
+
255
+ beforeEach(() => {
256
+ onTapEnd = jest.fn();
257
+ ref = React.createRef();
258
+ const Component = () => {
259
+ const listener = useTap({onTapEnd});
260
+ return <div ref={ref} listeners={listener} />;
261
+ };
262
+ ReactDOM.render(<Component />, container);
263
+ document.elementFromPoint = () => ref.current;
264
+ });
265
+
266
+ testWithPointerType('pointer up', pointerType => {
267
+ const target = createEventTarget(ref.current);
268
+ target.pointerdown({pointerType});
269
+ target.pointerup({
270
+ pageX: 10,
271
+ pageY: 10,
272
+ pointerType,
273
+ x: 10,
274
+ y: 10,
275
+ });
276
+ expect(onTapEnd).toHaveBeenCalledTimes(1);
277
+ expect(onTapEnd).toHaveBeenCalledWith(
278
+ expect.objectContaining({
279
+ altKey: false,
280
+ buttons: 1,
281
+ ctrlKey: false,
282
+ height: pointerType === 'mouse' ? 1 : 23,
283
+ metaKey: false,
284
+ pageX: 10,
285
+ pageY: 10,
286
+ pointerType,
287
+ pressure: 0,
288
+ screenX: 10,
289
+ screenY: 60,
290
+ shiftKey: false,
291
+ tangentialPressure: 0,
292
+ target: target.node,
293
+ tiltX: 0,
294
+ tiltY: 0,
295
+ timeStamp: expect.any(Number),
296
+ twist: 0,
297
+ type: 'tap:end',
298
+ width: pointerType === 'mouse' ? 1 : 23,
299
+ x: 10,
300
+ y: 10,
301
+ }),
302
+ );
303
+ });
304
+
305
+ test('primary-button pointer up', () => {
306
+ const pointerType = 'mouse';
307
+ const buttons = buttonsType.primary;
308
+ const target = createEventTarget(ref.current);
309
+ target.pointerdown({buttons, pointerType});
310
+ target.pointerup({pointerType});
311
+ expect(onTapEnd).toHaveBeenCalledTimes(1);
312
+ expect(onTapEnd).toHaveBeenCalledWith(
313
+ expect.objectContaining({buttons, pointerType}),
314
+ );
315
+ });
316
+
317
+ test('middle-button pointer up', () => {
318
+ const pointerType = 'mouse';
319
+ const buttons = buttonsType.middle;
320
+ const target = createEventTarget(ref.current);
321
+ target.pointerdown({buttons, pointerType});
322
+ target.pointerup({pointerType});
323
+ expect(onTapEnd).toHaveBeenCalledTimes(1);
324
+ expect(onTapEnd).toHaveBeenCalledWith(
325
+ expect.objectContaining({buttons, pointerType}),
326
+ );
327
+ });
328
+
329
+ testWithPointerType('zero-dimension hit rect', pointerType => {
330
+ const targetRef = React.createRef();
331
+ const innerRef = React.createRef();
332
+
333
+ const Component = () => {
334
+ const listener = useTap({onTapEnd});
335
+ return (
336
+ <div ref={targetRef} listeners={listener}>
337
+ <button ref={innerRef} />
338
+ </div>
339
+ );
340
+ };
341
+ ReactDOM.render(<Component />, container);
342
+ document.elementFromPoint = () => innerRef.current;
343
+
344
+ const target = createEventTarget(targetRef.current);
345
+ target.setBoundingClientRect({x: 0, y: 0, width: 0, height: 0});
346
+ const innerTarget = createEventTarget(innerRef.current);
347
+ innerTarget.pointerdown({pointerType});
348
+ innerTarget.pointerup({pointerType});
349
+ expect(onTapEnd).toBeCalled();
350
+ });
351
+
352
+ testWithPointerType('pointer up outside target', pointerType => {
353
+ const downTarget = createEventTarget(ref.current);
354
+ const upTarget = createEventTarget(container);
355
+ tapAndReleaseOutside({
356
+ hasPointerEvents,
357
+ downTarget,
358
+ upTarget,
359
+ pointerType,
360
+ });
361
+ expect(onTapEnd).not.toBeCalled();
362
+ });
363
+ });
364
+
365
+ describe('onTapUpdate', () => {
366
+ let onTapUpdate, ref;
367
+ const rect = {x: 0, y: 0, width: 100, height: 100};
368
+ const coordinates = {x: 10, y: 10};
369
+
370
+ beforeEach(() => {
371
+ onTapUpdate = jest.fn();
372
+ ref = React.createRef();
373
+ const Component = () => {
374
+ const listener = useTap({onTapUpdate});
375
+ return <div ref={ref} listeners={listener} />;
376
+ };
377
+ ReactDOM.render(<Component />, container);
378
+ document.elementFromPoint = () => ref.current;
379
+ });
380
+
381
+ testWithPointerType('requires activation', pointerType => {
382
+ const target = createEventTarget(ref.current);
383
+ target.setBoundingClientRect(rect);
384
+ target.pointerhover({pointerType, ...coordinates});
385
+ target.pointermove({pointerType, ...coordinates});
386
+ expect(onTapUpdate).not.toBeCalled();
387
+ });
388
+
389
+ testWithPointerType('pointer move', pointerType => {
390
+ const target = createEventTarget(ref.current);
391
+ target.setBoundingClientRect(rect);
392
+ target.pointerdown({pointerType});
393
+
394
+ target.pointermove({pointerType, x: 10, y: 10});
395
+ expect(onTapUpdate).toHaveBeenCalledWith(
396
+ expect.objectContaining({
397
+ altKey: false,
398
+ buttons: 1,
399
+ ctrlKey: false,
400
+ height: pointerType === 'mouse' ? 1 : 23,
401
+ metaKey: false,
402
+ pageX: 10,
403
+ pageY: 10,
404
+ pointerType,
405
+ pressure: pointerType === 'touch' ? 1 : 0.5,
406
+ screenX: 10,
407
+ screenY: 60,
408
+ shiftKey: false,
409
+ tangentialPressure: 0,
410
+ target: target.node,
411
+ tiltX: 0,
412
+ tiltY: 0,
413
+ timeStamp: expect.any(Number),
414
+ twist: 0,
415
+ type: 'tap:update',
416
+ width: pointerType === 'mouse' ? 1 : 23,
417
+ x: 10,
418
+ y: 10,
419
+ }),
420
+ );
421
+
422
+ target.pointermove({pointerType, x: 20, y: 20});
423
+ expect(onTapUpdate).toHaveBeenCalledWith(
424
+ expect.objectContaining({pointerType, x: 20, y: 20}),
425
+ );
426
+
427
+ expect(onTapUpdate).toHaveBeenCalledTimes(2);
428
+ });
429
+
430
+ testWithPointerType('pointer moves outside target', pointerType => {
431
+ const target = createEventTarget(ref.current);
432
+ const containerTarget = createEventTarget(container);
433
+ target.setBoundingClientRect(rect);
434
+ target.pointerdown({pointerType});
435
+ target.pointermove({pointerType, x: 10, y: 10});
436
+ expect(onTapUpdate).toHaveBeenCalledTimes(1);
437
+
438
+ // NOTE: this assumes the PointerEvent implementation calls
439
+ // 'releasePointerCapture' for touch pointers
440
+ if (!hasPointerEvents && pointerType === 'touch') {
441
+ document.elementFromPoint = () => containerTarget.node;
442
+ target.pointermove({pointerType, x: 101, y: 101});
443
+ } else {
444
+ containerTarget.pointermove({pointerType, x: 101, y: 101});
445
+ }
446
+
447
+ // No extra 'onTapUpdate' calls when the pointer is outside the target
448
+ expect(onTapUpdate).toHaveBeenCalledTimes(1);
449
+ });
450
+ });
451
+
452
+ describe('onTapChange', () => {
453
+ let onTapChange, ref;
454
+
455
+ beforeEach(() => {
456
+ onTapChange = jest.fn();
457
+ ref = React.createRef();
458
+ const Component = () => {
459
+ const listener = useTap({onTapChange});
460
+ return <div ref={ref} listeners={listener} />;
461
+ };
462
+ ReactDOM.render(<Component />, container);
463
+ });
464
+
465
+ testWithPointerType('pointer down/up', pointerType => {
466
+ const target = createEventTarget(ref.current);
467
+ target.pointerdown({pointerType});
468
+ expect(onTapChange).toHaveBeenCalledTimes(1);
469
+ expect(onTapChange).toHaveBeenCalledWith(true);
470
+ target.pointerup({pointerType});
471
+ expect(onTapChange).toHaveBeenCalledTimes(2);
472
+ expect(onTapChange).toHaveBeenCalledWith(false);
473
+ });
474
+
475
+ testWithPointerType('pointer cancel', pointerType => {
476
+ const target = createEventTarget(ref.current);
477
+ target.pointerdown({pointerType});
478
+ expect(onTapChange).toHaveBeenCalledTimes(1);
479
+ expect(onTapChange).toHaveBeenCalledWith(true);
480
+ target.pointercancel({pointerType});
481
+ expect(onTapChange).toHaveBeenCalledTimes(2);
482
+ expect(onTapChange).toHaveBeenCalledWith(false);
483
+ });
484
+
485
+ testWithPointerType('pointer move outside target', pointerType => {
486
+ const downTarget = createEventTarget(ref.current);
487
+ const upTarget = createEventTarget(container);
488
+ tapAndMoveOutside({
489
+ hasPointerEvents,
490
+ downTarget,
491
+ upTarget,
492
+ pointerType,
493
+ });
494
+ expect(onTapChange).toHaveBeenCalledTimes(2);
495
+ });
496
+ });
497
+
498
+ describe('onTapCancel', () => {
499
+ let onTapCancel, parentRef, ref, siblingRef;
500
+
501
+ beforeEach(() => {
502
+ onTapCancel = jest.fn();
503
+ parentRef = React.createRef();
504
+ ref = React.createRef();
505
+ siblingRef = React.createRef();
506
+ const Component = () => {
507
+ const listener = useTap({onTapCancel});
508
+ return (
509
+ <div ref={parentRef}>
510
+ <div ref={ref} listeners={listener} />
511
+ <span ref={siblingRef} />
512
+ </div>
513
+ );
514
+ };
515
+ ReactDOM.render(<Component />, container);
516
+ });
517
+
518
+ testWithPointerType('pointer cancel', pointerType => {
519
+ const target = createEventTarget(ref.current);
520
+ target.pointerdown({pointerType});
521
+ target.pointercancel({pointerType});
522
+ expect(onTapCancel).toHaveBeenCalledTimes(1);
523
+ expect(onTapCancel).toHaveBeenCalledWith(
524
+ expect.objectContaining({
525
+ altKey: false,
526
+ buttons: buttonsType.primary,
527
+ ctrlKey: false,
528
+ height: 1,
529
+ metaKey: false,
530
+ pageX: 0,
531
+ pageY: 0,
532
+ pointerType,
533
+ pressure: 0,
534
+ screenX: 0,
535
+ screenY: 0,
536
+ shiftKey: false,
537
+ tangentialPressure: 0,
538
+ target: target.node,
539
+ tiltX: 0,
540
+ tiltY: 0,
541
+ timeStamp: expect.any(Number),
542
+ twist: 0,
543
+ type: 'tap:cancel',
544
+ width: 1,
545
+ x: 0,
546
+ y: 0,
547
+ }),
548
+ );
549
+ });
550
+
551
+ test('long press context menu', () => {
552
+ const target = createEventTarget(ref.current);
553
+ target.contextmenu({}, {pointerType: 'touch'});
554
+ expect(onTapCancel).toHaveBeenCalledTimes(1);
555
+ });
556
+
557
+ test('parent scroll (non-mouse)', () => {
558
+ const target = createEventTarget(ref.current);
559
+ const parentTarget = createEventTarget(parentRef.current);
560
+ target.pointerdown({pointerType: 'touch'});
561
+ parentTarget.scroll();
562
+ expect(onTapCancel).toHaveBeenCalledTimes(1);
563
+ });
564
+
565
+ test('sibling scroll', () => {
566
+ const target = createEventTarget(ref.current);
567
+ const siblingTarget = createEventTarget(siblingRef.current);
568
+ target.pointerdown();
569
+ siblingTarget.scroll();
570
+ expect(onTapCancel).not.toBeCalled();
571
+ });
572
+
573
+ test('document scroll (non-mouse)', () => {
574
+ const target = createEventTarget(ref.current);
575
+ const documentTarget = createEventTarget(document);
576
+ target.pointerdown({pointerType: 'touch'});
577
+ documentTarget.scroll();
578
+ expect(onTapCancel).toHaveBeenCalledTimes(1);
579
+ });
580
+
581
+ // Scroll on an element not managed by React
582
+ test('root container scroll (non-mouse)', () => {
583
+ const target = createEventTarget(ref.current);
584
+ const containerTarget = createEventTarget(container);
585
+ target.pointerdown({pointerType: 'touch'});
586
+ containerTarget.scroll();
587
+ expect(onTapCancel).toHaveBeenCalledTimes(1);
588
+ });
589
+
590
+ testWithPointerType('pointer move outside target', pointerType => {
591
+ const downTarget = createEventTarget(ref.current);
592
+ const upTarget = createEventTarget(container);
593
+ tapAndMoveOutside({
594
+ hasPointerEvents,
595
+ downTarget,
596
+ upTarget,
597
+ pointerType,
598
+ });
599
+ expect(onTapCancel).toBeCalled();
600
+ });
601
+ });
602
+
603
+ describe('preventDefault', () => {
604
+ let onTapEnd, ref, innerRef, preventDefault, remount;
605
+
606
+ beforeEach(() => {
607
+ remount = function(shouldPreventDefault) {
608
+ onTapEnd = jest.fn();
609
+ preventDefault = jest.fn();
610
+ ref = React.createRef();
611
+ innerRef = React.createRef();
612
+ const Component = () => {
613
+ const listener = useTap({
614
+ onTapEnd,
615
+ preventDefault: shouldPreventDefault,
616
+ });
617
+ return (
618
+ <a href="#" ref={ref} listeners={listener}>
619
+ <div ref={innerRef} />
620
+ </a>
621
+ );
622
+ };
623
+ ReactDOM.render(<Component />, container);
624
+ };
625
+ remount();
626
+ });
627
+
628
+ test('prevents native behavior by default', () => {
629
+ const target = createEventTarget(ref.current);
630
+ target.pointerdown();
631
+ target.pointerup({preventDefault});
632
+ expect(preventDefault).toBeCalled();
633
+ expect(onTapEnd).toBeCalled();
634
+ });
635
+
636
+ test('prevents native behaviour by default (inner target)', () => {
637
+ const innerTarget = createEventTarget(innerRef.current);
638
+ innerTarget.pointerdown();
639
+ innerTarget.pointerup({preventDefault});
640
+ expect(preventDefault).toBeCalled();
641
+ expect(onTapEnd).toBeCalled();
642
+ });
643
+
644
+ test('allows native behaviour by default (modifier keys)', () => {
645
+ ['metaKey', 'ctrlKey', 'shiftKey'].forEach(modifierKey => {
646
+ const target = createEventTarget(ref.current);
647
+ target.pointerdown({[modifierKey]: true});
648
+ target.pointerup({[modifierKey]: true, preventDefault});
649
+ expect(preventDefault).not.toBeCalled();
650
+ expect(onTapEnd).toBeCalled();
651
+ });
652
+ });
653
+
654
+ test('allows native behaviour if false', () => {
655
+ remount(false);
656
+
657
+ const target = createEventTarget(ref.current);
658
+ target.pointerdown();
659
+ target.pointerup({preventDefault});
660
+ expect(preventDefault).not.toBeCalled();
661
+ expect(onTapEnd).toBeCalled();
662
+ });
663
+ });
664
+});