@angular/cdk 21.0.0-next.9 → 21.0.0-rc.1
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/_adev_assets/cdk_drag_drop.json +13 -12
- package/_adev_assets/cdk_testing.json +9 -9
- package/_adev_assets/cdk_testing_protractor.json +1 -1
- package/_adev_assets/cdk_testing_selenium_webdriver.json +1 -1
- package/_adev_assets/cdk_testing_testbed.json +1 -1
- package/fesm2022/_a11y-module-chunk.mjs +755 -869
- package/fesm2022/_a11y-module-chunk.mjs.map +1 -1
- package/fesm2022/_activedescendant-key-manager-chunk.mjs +8 -8
- package/fesm2022/_activedescendant-key-manager-chunk.mjs.map +1 -1
- package/fesm2022/_array-chunk.mjs +1 -1
- package/fesm2022/_array-chunk.mjs.map +1 -1
- package/fesm2022/_breakpoints-observer-chunk.mjs +149 -152
- package/fesm2022/_breakpoints-observer-chunk.mjs.map +1 -1
- package/fesm2022/_css-pixel-value-chunk.mjs +4 -5
- package/fesm2022/_css-pixel-value-chunk.mjs.map +1 -1
- package/fesm2022/_data-source-chunk.mjs +2 -8
- package/fesm2022/_data-source-chunk.mjs.map +1 -1
- package/fesm2022/_directionality-chunk.mjs +54 -54
- package/fesm2022/_directionality-chunk.mjs.map +1 -1
- package/fesm2022/_dispose-view-repeater-strategy-chunk.mjs +25 -36
- package/fesm2022/_dispose-view-repeater-strategy-chunk.mjs.map +1 -1
- package/fesm2022/_element-chunk.mjs +6 -17
- package/fesm2022/_element-chunk.mjs.map +1 -1
- package/fesm2022/_fake-event-detection-chunk.mjs +3 -17
- package/fesm2022/_fake-event-detection-chunk.mjs.map +1 -1
- package/fesm2022/_focus-key-manager-chunk.mjs +10 -14
- package/fesm2022/_focus-key-manager-chunk.mjs.map +1 -1
- package/fesm2022/_focus-monitor-chunk.mjs +376 -566
- package/fesm2022/_focus-monitor-chunk.mjs.map +1 -1
- package/fesm2022/_id-generator-chunk.mjs +37 -27
- package/fesm2022/_id-generator-chunk.mjs.map +1 -1
- package/fesm2022/_keycodes-chunk.mjs +9 -9
- package/fesm2022/_keycodes-chunk.mjs.map +1 -1
- package/fesm2022/_list-key-manager-chunk.mjs +248 -336
- package/fesm2022/_list-key-manager-chunk.mjs.map +1 -1
- package/fesm2022/_overlay-module-chunk.mjs +2535 -2947
- package/fesm2022/_overlay-module-chunk.mjs.map +1 -1
- package/fesm2022/_passive-listeners-chunk.mjs +10 -22
- package/fesm2022/_passive-listeners-chunk.mjs.map +1 -1
- package/fesm2022/_platform-chunk.mjs +42 -65
- package/fesm2022/_platform-chunk.mjs.map +1 -1
- package/fesm2022/_recycle-view-repeater-strategy-chunk.mjs +78 -134
- package/fesm2022/_recycle-view-repeater-strategy-chunk.mjs.map +1 -1
- package/fesm2022/_scrolling-chunk.mjs +44 -85
- package/fesm2022/_scrolling-chunk.mjs.map +1 -1
- package/fesm2022/_selection-model-chunk.mjs +138 -209
- package/fesm2022/_selection-model-chunk.mjs.map +1 -1
- package/fesm2022/_shadow-dom-chunk.mjs +21 -35
- package/fesm2022/_shadow-dom-chunk.mjs.map +1 -1
- package/fesm2022/_style-loader-chunk.mjs +50 -37
- package/fesm2022/_style-loader-chunk.mjs.map +1 -1
- package/fesm2022/_test-environment-chunk.mjs +2 -14
- package/fesm2022/_test-environment-chunk.mjs.map +1 -1
- package/fesm2022/_tree-key-manager-chunk.mjs +229 -308
- package/fesm2022/_tree-key-manager-chunk.mjs.map +1 -1
- package/fesm2022/_typeahead-chunk.mjs +52 -74
- package/fesm2022/_typeahead-chunk.mjs.map +1 -1
- package/fesm2022/_unique-selection-dispatcher-chunk.mjs +43 -40
- package/fesm2022/_unique-selection-dispatcher-chunk.mjs.map +1 -1
- package/fesm2022/a11y.mjs +351 -449
- package/fesm2022/a11y.mjs.map +1 -1
- package/fesm2022/accordion.mjs +254 -192
- package/fesm2022/accordion.mjs.map +1 -1
- package/fesm2022/bidi.mjs +121 -64
- package/fesm2022/bidi.mjs.map +1 -1
- package/fesm2022/cdk.mjs +1 -2
- package/fesm2022/cdk.mjs.map +1 -1
- package/fesm2022/clipboard.mjs +208 -186
- package/fesm2022/clipboard.mjs.map +1 -1
- package/fesm2022/coercion-private.mjs +4 -8
- package/fesm2022/coercion-private.mjs.map +1 -1
- package/fesm2022/coercion.mjs +11 -29
- package/fesm2022/coercion.mjs.map +1 -1
- package/fesm2022/dialog.mjs +660 -808
- package/fesm2022/dialog.mjs.map +1 -1
- package/fesm2022/drag-drop.mjs +3347 -4286
- package/fesm2022/drag-drop.mjs.map +1 -1
- package/fesm2022/keycodes.mjs +4 -8
- package/fesm2022/keycodes.mjs.map +1 -1
- package/fesm2022/layout.mjs +44 -26
- package/fesm2022/layout.mjs.map +1 -1
- package/fesm2022/listbox.mjs +841 -895
- package/fesm2022/listbox.mjs.map +1 -1
- package/fesm2022/menu.mjs +1942 -1858
- package/fesm2022/menu.mjs.map +1 -1
- package/fesm2022/observers-private.mjs +88 -106
- package/fesm2022/observers-private.mjs.map +1 -1
- package/fesm2022/observers.mjs +262 -184
- package/fesm2022/observers.mjs.map +1 -1
- package/fesm2022/overlay.mjs +72 -68
- package/fesm2022/overlay.mjs.map +1 -1
- package/fesm2022/platform.mjs +43 -54
- package/fesm2022/platform.mjs.map +1 -1
- package/fesm2022/portal.mjs +402 -560
- package/fesm2022/portal.mjs.map +1 -1
- package/fesm2022/private.mjs +38 -10
- package/fesm2022/private.mjs.map +1 -1
- package/fesm2022/scrolling.mjs +1323 -1400
- package/fesm2022/scrolling.mjs.map +1 -1
- package/fesm2022/stepper.mjs +758 -590
- package/fesm2022/stepper.mjs.map +1 -1
- package/fesm2022/table.mjs +2327 -2319
- package/fesm2022/table.mjs.map +1 -1
- package/fesm2022/testing-selenium-webdriver.mjs +252 -325
- package/fesm2022/testing-selenium-webdriver.mjs.map +1 -1
- package/fesm2022/testing-testbed.mjs +592 -709
- package/fesm2022/testing-testbed.mjs.map +1 -1
- package/fesm2022/testing.mjs +368 -889
- package/fesm2022/testing.mjs.map +1 -1
- package/fesm2022/text-field.mjs +459 -388
- package/fesm2022/text-field.mjs.map +1 -1
- package/fesm2022/tree.mjs +1483 -1731
- package/fesm2022/tree.mjs.map +1 -1
- package/overlay/_index.scss +30 -0
- package/overlay-prebuilt.css +1 -1
- package/package.json +1 -1
- package/schematics/ng-add/index.js +1 -1
- package/types/_overlay-module-chunk.d.ts +69 -7
- package/types/_portal-directives-chunk.d.ts +2 -18
- package/types/a11y.d.ts +3 -1
- package/types/accordion.d.ts +3 -1
- package/types/dialog.d.ts +1 -1
- package/types/overlay.d.ts +1 -1
- package/types/portal.d.ts +1 -1
|
@@ -4,778 +4,661 @@ import { BehaviorSubject } from 'rxjs';
|
|
|
4
4
|
import { getNoKeysSpecifiedError, _getTextWithExcludedElements, TestKey, HarnessEnvironment, handleAutoChangeDetectionStatus, stopHandlingAutoChangeDetectionStatus } from './testing.mjs';
|
|
5
5
|
import { PERIOD, BACKSPACE, TAB, ENTER, SHIFT, CONTROL, ALT, ESCAPE, PAGE_UP, PAGE_DOWN, END, HOME, LEFT_ARROW, UP_ARROW, RIGHT_ARROW, DOWN_ARROW, INSERT, DELETE, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, META, COMMA } from './_keycodes-chunk.mjs';
|
|
6
6
|
|
|
7
|
-
/** Unique symbol that is used to patch a property to a proxy zone. */
|
|
8
7
|
const stateObservableSymbol = Symbol('ProxyZone_PATCHED#stateObservable');
|
|
9
|
-
/**
|
|
10
|
-
* Interceptor that can be set up in a `ProxyZone` instance. The interceptor
|
|
11
|
-
* will keep track of the task state and emit whenever the state changes.
|
|
12
|
-
*
|
|
13
|
-
* This serves as a workaround for https://github.com/angular/angular/issues/32896.
|
|
14
|
-
*/
|
|
15
8
|
class TaskStateZoneInterceptor {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const zoneSpec = ProxyZoneSpec.assertPresent();
|
|
55
|
-
// If there already is a delegate registered in the proxy zone, and it
|
|
56
|
-
// is type of the custom task state interceptor, we just use that state
|
|
57
|
-
// observable. This allows us to only intercept Zone once per test
|
|
58
|
-
// (similar to how `fakeAsync` or `async` work).
|
|
59
|
-
if (zoneSpec[stateObservableSymbol]) {
|
|
60
|
-
return zoneSpec[stateObservableSymbol];
|
|
61
|
-
}
|
|
62
|
-
// Since we intercept on environment creation and the fixture has been
|
|
63
|
-
// created before, we might have missed tasks scheduled before. Fortunately
|
|
64
|
-
// the proxy zone keeps track of the previous task state, so we can just pass
|
|
65
|
-
// this as initial state to the task zone interceptor.
|
|
66
|
-
const interceptor = new TaskStateZoneInterceptor(zoneSpec.lastTaskState);
|
|
67
|
-
const zoneSpecOnHasTask = zoneSpec.onHasTask.bind(zoneSpec);
|
|
68
|
-
// We setup the task state interceptor in the `ProxyZone`. Note that we cannot register
|
|
69
|
-
// the interceptor as a new proxy zone delegate because it would mean that other zone
|
|
70
|
-
// delegates (e.g. `FakeAsyncTestZone` or `AsyncTestZone`) can accidentally overwrite/disable
|
|
71
|
-
// our interceptor. Since we just intend to monitor the task state of the proxy zone, it is
|
|
72
|
-
// sufficient to just patch the proxy zone. This also avoids that we interfere with the task
|
|
73
|
-
// queue scheduling logic.
|
|
74
|
-
zoneSpec.onHasTask = function (...args) {
|
|
75
|
-
zoneSpecOnHasTask(...args);
|
|
76
|
-
interceptor.onHasTask(...args);
|
|
77
|
-
};
|
|
78
|
-
return (zoneSpec[stateObservableSymbol] = interceptor.state);
|
|
79
|
-
}
|
|
9
|
+
_lastState = null;
|
|
10
|
+
_stateSubject = new BehaviorSubject(this._lastState ? this._getTaskStateFromInternalZoneState(this._lastState) : {
|
|
11
|
+
stable: true
|
|
12
|
+
});
|
|
13
|
+
state = this._stateSubject;
|
|
14
|
+
constructor(lastState) {
|
|
15
|
+
this._lastState = lastState;
|
|
16
|
+
}
|
|
17
|
+
onHasTask(delegate, current, target, hasTaskState) {
|
|
18
|
+
if (current === target) {
|
|
19
|
+
this._stateSubject.next(this._getTaskStateFromInternalZoneState(hasTaskState));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
_getTaskStateFromInternalZoneState(state) {
|
|
23
|
+
return {
|
|
24
|
+
stable: !state.macroTask && !state.microTask
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
static setup() {
|
|
28
|
+
if (Zone === undefined) {
|
|
29
|
+
throw Error('Could not find ZoneJS. For test harnesses running in TestBed, ' + 'ZoneJS needs to be installed.');
|
|
30
|
+
}
|
|
31
|
+
const ProxyZoneSpec = Zone['ProxyZoneSpec'];
|
|
32
|
+
if (!ProxyZoneSpec) {
|
|
33
|
+
throw Error('ProxyZoneSpec is needed for the test harnesses but could not be found. ' + 'Please make sure that your environment includes zone.js/dist/zone-testing.js');
|
|
34
|
+
}
|
|
35
|
+
const zoneSpec = ProxyZoneSpec.assertPresent();
|
|
36
|
+
if (zoneSpec[stateObservableSymbol]) {
|
|
37
|
+
return zoneSpec[stateObservableSymbol];
|
|
38
|
+
}
|
|
39
|
+
const interceptor = new TaskStateZoneInterceptor(zoneSpec.lastTaskState);
|
|
40
|
+
const zoneSpecOnHasTask = zoneSpec.onHasTask.bind(zoneSpec);
|
|
41
|
+
zoneSpec.onHasTask = function (...args) {
|
|
42
|
+
zoneSpecOnHasTask(...args);
|
|
43
|
+
interceptor.onHasTask(...args);
|
|
44
|
+
};
|
|
45
|
+
return zoneSpec[stateObservableSymbol] = interceptor.state;
|
|
46
|
+
}
|
|
80
47
|
}
|
|
81
48
|
|
|
82
|
-
/** Used to generate unique IDs for events. */
|
|
83
|
-
/**
|
|
84
|
-
* Creates a browser MouseEvent with the specified options.
|
|
85
|
-
* @docs-private
|
|
86
|
-
*/
|
|
87
49
|
function createMouseEvent(type, clientX = 0, clientY = 0, offsetX = 0, offsetY = 0, button = 0, modifiers = {}) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
defineReadonlyEventProperty(event, 'offsetX', offsetX);
|
|
116
|
-
}
|
|
117
|
-
if (offsetY != null) {
|
|
118
|
-
defineReadonlyEventProperty(event, 'offsetY', offsetY);
|
|
119
|
-
}
|
|
120
|
-
return event;
|
|
50
|
+
const screenX = clientX;
|
|
51
|
+
const screenY = clientY;
|
|
52
|
+
const event = new MouseEvent(type, {
|
|
53
|
+
bubbles: true,
|
|
54
|
+
cancelable: true,
|
|
55
|
+
composed: true,
|
|
56
|
+
view: window,
|
|
57
|
+
detail: 1,
|
|
58
|
+
relatedTarget: null,
|
|
59
|
+
screenX,
|
|
60
|
+
screenY,
|
|
61
|
+
clientX,
|
|
62
|
+
clientY,
|
|
63
|
+
ctrlKey: modifiers.control,
|
|
64
|
+
altKey: modifiers.alt,
|
|
65
|
+
shiftKey: modifiers.shift,
|
|
66
|
+
metaKey: modifiers.meta,
|
|
67
|
+
button: button,
|
|
68
|
+
buttons: 1
|
|
69
|
+
});
|
|
70
|
+
if (offsetX != null) {
|
|
71
|
+
defineReadonlyEventProperty(event, 'offsetX', offsetX);
|
|
72
|
+
}
|
|
73
|
+
if (offsetY != null) {
|
|
74
|
+
defineReadonlyEventProperty(event, 'offsetY', offsetY);
|
|
75
|
+
}
|
|
76
|
+
return event;
|
|
121
77
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
});
|
|
142
|
-
if (offsetX != null) {
|
|
143
|
-
defineReadonlyEventProperty(event, 'offsetX', offsetX);
|
|
144
|
-
}
|
|
145
|
-
if (offsetY != null) {
|
|
146
|
-
defineReadonlyEventProperty(event, 'offsetY', offsetY);
|
|
147
|
-
}
|
|
148
|
-
return event;
|
|
78
|
+
function createPointerEvent(type, clientX = 0, clientY = 0, offsetX, offsetY, options = {
|
|
79
|
+
isPrimary: true
|
|
80
|
+
}) {
|
|
81
|
+
const event = new PointerEvent(type, {
|
|
82
|
+
bubbles: true,
|
|
83
|
+
cancelable: true,
|
|
84
|
+
composed: true,
|
|
85
|
+
view: window,
|
|
86
|
+
clientX,
|
|
87
|
+
clientY,
|
|
88
|
+
...options
|
|
89
|
+
});
|
|
90
|
+
if (offsetX != null) {
|
|
91
|
+
defineReadonlyEventProperty(event, 'offsetX', offsetX);
|
|
92
|
+
}
|
|
93
|
+
if (offsetY != null) {
|
|
94
|
+
defineReadonlyEventProperty(event, 'offsetY', offsetY);
|
|
95
|
+
}
|
|
96
|
+
return event;
|
|
149
97
|
}
|
|
150
|
-
/**
|
|
151
|
-
* Creates a keyboard event with the specified key and modifiers.
|
|
152
|
-
* @docs-private
|
|
153
|
-
*/
|
|
154
98
|
function createKeyboardEvent(type, keyCode = 0, key = '', modifiers = {}, code = '') {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
99
|
+
return new KeyboardEvent(type, {
|
|
100
|
+
bubbles: true,
|
|
101
|
+
cancelable: true,
|
|
102
|
+
composed: true,
|
|
103
|
+
view: window,
|
|
104
|
+
keyCode,
|
|
105
|
+
key,
|
|
106
|
+
shiftKey: modifiers.shift,
|
|
107
|
+
metaKey: modifiers.meta,
|
|
108
|
+
altKey: modifiers.alt,
|
|
109
|
+
ctrlKey: modifiers.control,
|
|
110
|
+
code
|
|
111
|
+
});
|
|
168
112
|
}
|
|
169
|
-
/**
|
|
170
|
-
* Creates a fake event object with any desired event type.
|
|
171
|
-
* @docs-private
|
|
172
|
-
*/
|
|
173
113
|
function createFakeEvent(type, bubbles = false, cancelable = true, composed = true) {
|
|
174
|
-
|
|
114
|
+
return new Event(type, {
|
|
115
|
+
bubbles,
|
|
116
|
+
cancelable,
|
|
117
|
+
composed
|
|
118
|
+
});
|
|
175
119
|
}
|
|
176
|
-
/**
|
|
177
|
-
* Defines a readonly property on the given event object. Readonly properties on an event object
|
|
178
|
-
* are always set as configurable as that matches default readonly properties for DOM event objects.
|
|
179
|
-
*/
|
|
180
120
|
function defineReadonlyEventProperty(event, propertyName, value) {
|
|
181
|
-
|
|
121
|
+
Object.defineProperty(event, propertyName, {
|
|
122
|
+
get: () => value,
|
|
123
|
+
configurable: true
|
|
124
|
+
});
|
|
182
125
|
}
|
|
183
126
|
|
|
184
|
-
/**
|
|
185
|
-
* Utility to dispatch any event on a Node.
|
|
186
|
-
* @docs-private
|
|
187
|
-
*/
|
|
188
127
|
function dispatchEvent(node, event) {
|
|
189
|
-
|
|
190
|
-
|
|
128
|
+
node.dispatchEvent(event);
|
|
129
|
+
return event;
|
|
191
130
|
}
|
|
192
|
-
/**
|
|
193
|
-
* Shorthand to dispatch a fake event on a specified node.
|
|
194
|
-
* @docs-private
|
|
195
|
-
*/
|
|
196
131
|
function dispatchFakeEvent(node, type, bubbles) {
|
|
197
|
-
|
|
132
|
+
return dispatchEvent(node, createFakeEvent(type, bubbles));
|
|
198
133
|
}
|
|
199
|
-
/**
|
|
200
|
-
* Shorthand to dispatch a keyboard event with a specified key code and
|
|
201
|
-
* optional modifiers.
|
|
202
|
-
* @docs-private
|
|
203
|
-
*/
|
|
204
134
|
function dispatchKeyboardEvent(node, type, keyCode, key, modifiers, code) {
|
|
205
|
-
|
|
135
|
+
return dispatchEvent(node, createKeyboardEvent(type, keyCode, key, modifiers, code));
|
|
206
136
|
}
|
|
207
|
-
/**
|
|
208
|
-
* Shorthand to dispatch a mouse event on the specified coordinates.
|
|
209
|
-
* @docs-private
|
|
210
|
-
*/
|
|
211
137
|
function dispatchMouseEvent(node, type, clientX = 0, clientY = 0, offsetX, offsetY, button, modifiers) {
|
|
212
|
-
|
|
138
|
+
return dispatchEvent(node, createMouseEvent(type, clientX, clientY, offsetX, offsetY, button, modifiers));
|
|
213
139
|
}
|
|
214
|
-
/**
|
|
215
|
-
* Shorthand to dispatch a pointer event on the specified coordinates.
|
|
216
|
-
* @docs-private
|
|
217
|
-
*/
|
|
218
140
|
function dispatchPointerEvent(node, type, clientX = 0, clientY = 0, offsetX, offsetY, options) {
|
|
219
|
-
|
|
141
|
+
return dispatchEvent(node, createPointerEvent(type, clientX, clientY, offsetX, offsetY, options));
|
|
220
142
|
}
|
|
221
143
|
|
|
222
144
|
function triggerFocusChange(element, event) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
145
|
+
let eventFired = false;
|
|
146
|
+
const handler = () => eventFired = true;
|
|
147
|
+
element.addEventListener(event, handler);
|
|
148
|
+
element[event]();
|
|
149
|
+
element.removeEventListener(event, handler);
|
|
150
|
+
if (!eventFired) {
|
|
151
|
+
dispatchFakeEvent(element, event);
|
|
152
|
+
}
|
|
231
153
|
}
|
|
232
|
-
/** @docs-private */
|
|
233
154
|
function triggerFocus(element) {
|
|
234
|
-
|
|
155
|
+
triggerFocusChange(element, 'focus');
|
|
235
156
|
}
|
|
236
|
-
/** @docs-private */
|
|
237
157
|
function triggerBlur(element) {
|
|
238
|
-
|
|
158
|
+
triggerFocusChange(element, 'blur');
|
|
239
159
|
}
|
|
240
160
|
|
|
241
|
-
|
|
242
|
-
const incrementalInputTypes = new Set([
|
|
243
|
-
'text',
|
|
244
|
-
'email',
|
|
245
|
-
'hidden',
|
|
246
|
-
'password',
|
|
247
|
-
'search',
|
|
248
|
-
'tel',
|
|
249
|
-
'url',
|
|
250
|
-
]);
|
|
251
|
-
/**
|
|
252
|
-
* Manual mapping of some common characters to their `code` in a keyboard event. Non-exhaustive, see
|
|
253
|
-
* the tables on MDN for more info: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
|
|
254
|
-
*/
|
|
161
|
+
const incrementalInputTypes = new Set(['text', 'email', 'hidden', 'password', 'search', 'tel', 'url']);
|
|
255
162
|
const charsToCodes = {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
163
|
+
' ': 'Space',
|
|
164
|
+
'.': 'Period',
|
|
165
|
+
',': 'Comma',
|
|
166
|
+
'`': 'Backquote',
|
|
167
|
+
'-': 'Minus',
|
|
168
|
+
'=': 'Equal',
|
|
169
|
+
'[': 'BracketLeft',
|
|
170
|
+
']': 'BracketRight',
|
|
171
|
+
'\\': 'Backslash',
|
|
172
|
+
'/': 'Slash',
|
|
173
|
+
"'": 'Quote',
|
|
174
|
+
'"': 'Quote',
|
|
175
|
+
';': 'Semicolon'
|
|
269
176
|
};
|
|
270
|
-
/**
|
|
271
|
-
* Determines the `KeyboardEvent.key` from a character. See #27034 and
|
|
272
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
|
|
273
|
-
*/
|
|
274
177
|
function getKeyboardEventCode(char) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
return charsToCodes[char] ?? '';
|
|
178
|
+
if (char.length !== 1) {
|
|
179
|
+
return '';
|
|
180
|
+
}
|
|
181
|
+
const charCode = char.charCodeAt(0);
|
|
182
|
+
if (charCode >= 97 && charCode <= 122 || charCode >= 65 && charCode <= 90) {
|
|
183
|
+
return `Key${char.toUpperCase()}`;
|
|
184
|
+
}
|
|
185
|
+
if (48 <= charCode && charCode <= 57) {
|
|
186
|
+
return `Digit${char}`;
|
|
187
|
+
}
|
|
188
|
+
return charsToCodes[char] ?? '';
|
|
288
189
|
}
|
|
289
|
-
/**
|
|
290
|
-
* Checks whether the given Element is a text input element.
|
|
291
|
-
* @docs-private
|
|
292
|
-
*/
|
|
293
190
|
function isTextInput(element) {
|
|
294
|
-
|
|
295
|
-
|
|
191
|
+
const nodeName = element.nodeName.toLowerCase();
|
|
192
|
+
return nodeName === 'input' || nodeName === 'textarea';
|
|
296
193
|
}
|
|
297
194
|
function typeInElement(element, ...modifiersAndKeys) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
// is that for some input types, the browser won't allow for an invalid value to be set via the
|
|
330
|
-
// `value` property which will always be the case when going character-by-character. If we detect
|
|
331
|
-
// such an input, we have to set the value all at once or listeners to the `input` event (e.g.
|
|
332
|
-
// the `ReactiveFormsModule` uses such an approach) won't receive the correct value.
|
|
333
|
-
const enterValueIncrementally = inputType === 'number'
|
|
334
|
-
? // The value can be set character by character in number inputs if it doesn't have any decimals.
|
|
335
|
-
keys.every(key => key.key !== '.' && key.key !== '-' && key.keyCode !== PERIOD)
|
|
336
|
-
: incrementalInputTypes.has(inputType);
|
|
337
|
-
triggerFocus(element);
|
|
338
|
-
// When we aren't entering the value incrementally, assign it all at once ahead
|
|
339
|
-
// of time so that any listeners to the key events below will have access to it.
|
|
340
|
-
if (!enterValueIncrementally) {
|
|
341
|
-
element.value = keys.reduce((value, key) => value + (key.key || ''), '');
|
|
342
|
-
}
|
|
343
|
-
for (const key of keys) {
|
|
344
|
-
dispatchKeyboardEvent(element, 'keydown', key.keyCode, key.key, modifiers, key.code);
|
|
345
|
-
dispatchKeyboardEvent(element, 'keypress', key.keyCode, key.key, modifiers, key.code);
|
|
346
|
-
if (isInput && key.key && key.key.length === 1) {
|
|
347
|
-
if (enterValueIncrementally) {
|
|
348
|
-
element.value += key.key;
|
|
349
|
-
dispatchFakeEvent(element, 'input');
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
dispatchKeyboardEvent(element, 'keyup', key.keyCode, key.key, modifiers, key.code);
|
|
353
|
-
}
|
|
354
|
-
// Since we weren't dispatching `input` events while sending the keys, we have to do it now.
|
|
355
|
-
if (!enterValueIncrementally) {
|
|
195
|
+
const first = modifiersAndKeys[0];
|
|
196
|
+
let modifiers;
|
|
197
|
+
let rest;
|
|
198
|
+
if (first !== undefined && typeof first !== 'string' && first.keyCode === undefined && first.key === undefined) {
|
|
199
|
+
modifiers = first;
|
|
200
|
+
rest = modifiersAndKeys.slice(1);
|
|
201
|
+
} else {
|
|
202
|
+
modifiers = {};
|
|
203
|
+
rest = modifiersAndKeys;
|
|
204
|
+
}
|
|
205
|
+
const isInput = isTextInput(element);
|
|
206
|
+
const inputType = element.getAttribute('type') || 'text';
|
|
207
|
+
const keys = rest.map(k => typeof k === 'string' ? k.split('').map(c => ({
|
|
208
|
+
keyCode: c.toUpperCase().charCodeAt(0),
|
|
209
|
+
key: c,
|
|
210
|
+
code: getKeyboardEventCode(c)
|
|
211
|
+
})) : [k]).reduce((arr, k) => arr.concat(k), []);
|
|
212
|
+
if (keys.length === 0) {
|
|
213
|
+
throw getNoKeysSpecifiedError();
|
|
214
|
+
}
|
|
215
|
+
const enterValueIncrementally = inputType === 'number' ? keys.every(key => key.key !== '.' && key.key !== '-' && key.keyCode !== PERIOD) : incrementalInputTypes.has(inputType);
|
|
216
|
+
triggerFocus(element);
|
|
217
|
+
if (!enterValueIncrementally) {
|
|
218
|
+
element.value = keys.reduce((value, key) => value + (key.key || ''), '');
|
|
219
|
+
}
|
|
220
|
+
for (const key of keys) {
|
|
221
|
+
dispatchKeyboardEvent(element, 'keydown', key.keyCode, key.key, modifiers, key.code);
|
|
222
|
+
dispatchKeyboardEvent(element, 'keypress', key.keyCode, key.key, modifiers, key.code);
|
|
223
|
+
if (isInput && key.key && key.key.length === 1) {
|
|
224
|
+
if (enterValueIncrementally) {
|
|
225
|
+
element.value += key.key;
|
|
356
226
|
dispatchFakeEvent(element, 'input');
|
|
227
|
+
}
|
|
357
228
|
}
|
|
229
|
+
dispatchKeyboardEvent(element, 'keyup', key.keyCode, key.key, modifiers, key.code);
|
|
230
|
+
}
|
|
231
|
+
if (!enterValueIncrementally) {
|
|
232
|
+
dispatchFakeEvent(element, 'input');
|
|
233
|
+
}
|
|
358
234
|
}
|
|
359
|
-
/**
|
|
360
|
-
* Clears the text in an input or textarea element.
|
|
361
|
-
* @docs-private
|
|
362
|
-
*/
|
|
363
235
|
function clearElement(element) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
236
|
+
triggerFocus(element);
|
|
237
|
+
element.value = '';
|
|
238
|
+
dispatchFakeEvent(element, 'input');
|
|
367
239
|
}
|
|
368
240
|
|
|
369
|
-
/** Maps `TestKey` constants to the `keyCode` and `key` values used by native browser events. */
|
|
370
241
|
const keyMap = {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
242
|
+
[TestKey.BACKSPACE]: {
|
|
243
|
+
keyCode: BACKSPACE,
|
|
244
|
+
key: 'Backspace',
|
|
245
|
+
code: 'Backspace'
|
|
246
|
+
},
|
|
247
|
+
[TestKey.TAB]: {
|
|
248
|
+
keyCode: TAB,
|
|
249
|
+
key: 'Tab',
|
|
250
|
+
code: 'Tab'
|
|
251
|
+
},
|
|
252
|
+
[TestKey.ENTER]: {
|
|
253
|
+
keyCode: ENTER,
|
|
254
|
+
key: 'Enter',
|
|
255
|
+
code: 'Enter'
|
|
256
|
+
},
|
|
257
|
+
[TestKey.SHIFT]: {
|
|
258
|
+
keyCode: SHIFT,
|
|
259
|
+
key: 'Shift',
|
|
260
|
+
code: 'ShiftLeft'
|
|
261
|
+
},
|
|
262
|
+
[TestKey.CONTROL]: {
|
|
263
|
+
keyCode: CONTROL,
|
|
264
|
+
key: 'Control',
|
|
265
|
+
code: 'ControlLeft'
|
|
266
|
+
},
|
|
267
|
+
[TestKey.ALT]: {
|
|
268
|
+
keyCode: ALT,
|
|
269
|
+
key: 'Alt',
|
|
270
|
+
code: 'AltLeft'
|
|
271
|
+
},
|
|
272
|
+
[TestKey.ESCAPE]: {
|
|
273
|
+
keyCode: ESCAPE,
|
|
274
|
+
key: 'Escape',
|
|
275
|
+
code: 'Escape'
|
|
276
|
+
},
|
|
277
|
+
[TestKey.PAGE_UP]: {
|
|
278
|
+
keyCode: PAGE_UP,
|
|
279
|
+
key: 'PageUp',
|
|
280
|
+
code: 'PageUp'
|
|
281
|
+
},
|
|
282
|
+
[TestKey.PAGE_DOWN]: {
|
|
283
|
+
keyCode: PAGE_DOWN,
|
|
284
|
+
key: 'PageDown',
|
|
285
|
+
code: 'PageDown'
|
|
286
|
+
},
|
|
287
|
+
[TestKey.END]: {
|
|
288
|
+
keyCode: END,
|
|
289
|
+
key: 'End',
|
|
290
|
+
code: 'End'
|
|
291
|
+
},
|
|
292
|
+
[TestKey.HOME]: {
|
|
293
|
+
keyCode: HOME,
|
|
294
|
+
key: 'Home',
|
|
295
|
+
code: 'Home'
|
|
296
|
+
},
|
|
297
|
+
[TestKey.LEFT_ARROW]: {
|
|
298
|
+
keyCode: LEFT_ARROW,
|
|
299
|
+
key: 'ArrowLeft',
|
|
300
|
+
code: 'ArrowLeft'
|
|
301
|
+
},
|
|
302
|
+
[TestKey.UP_ARROW]: {
|
|
303
|
+
keyCode: UP_ARROW,
|
|
304
|
+
key: 'ArrowUp',
|
|
305
|
+
code: 'ArrowUp'
|
|
306
|
+
},
|
|
307
|
+
[TestKey.RIGHT_ARROW]: {
|
|
308
|
+
keyCode: RIGHT_ARROW,
|
|
309
|
+
key: 'ArrowRight',
|
|
310
|
+
code: 'ArrowRight'
|
|
311
|
+
},
|
|
312
|
+
[TestKey.DOWN_ARROW]: {
|
|
313
|
+
keyCode: DOWN_ARROW,
|
|
314
|
+
key: 'ArrowDown',
|
|
315
|
+
code: 'ArrowDown'
|
|
316
|
+
},
|
|
317
|
+
[TestKey.INSERT]: {
|
|
318
|
+
keyCode: INSERT,
|
|
319
|
+
key: 'Insert',
|
|
320
|
+
code: 'Insert'
|
|
321
|
+
},
|
|
322
|
+
[TestKey.DELETE]: {
|
|
323
|
+
keyCode: DELETE,
|
|
324
|
+
key: 'Delete',
|
|
325
|
+
code: 'Delete'
|
|
326
|
+
},
|
|
327
|
+
[TestKey.F1]: {
|
|
328
|
+
keyCode: F1,
|
|
329
|
+
key: 'F1',
|
|
330
|
+
code: 'F1'
|
|
331
|
+
},
|
|
332
|
+
[TestKey.F2]: {
|
|
333
|
+
keyCode: F2,
|
|
334
|
+
key: 'F2',
|
|
335
|
+
code: 'F2'
|
|
336
|
+
},
|
|
337
|
+
[TestKey.F3]: {
|
|
338
|
+
keyCode: F3,
|
|
339
|
+
key: 'F3',
|
|
340
|
+
code: 'F3'
|
|
341
|
+
},
|
|
342
|
+
[TestKey.F4]: {
|
|
343
|
+
keyCode: F4,
|
|
344
|
+
key: 'F4',
|
|
345
|
+
code: 'F4'
|
|
346
|
+
},
|
|
347
|
+
[TestKey.F5]: {
|
|
348
|
+
keyCode: F5,
|
|
349
|
+
key: 'F5',
|
|
350
|
+
code: 'F5'
|
|
351
|
+
},
|
|
352
|
+
[TestKey.F6]: {
|
|
353
|
+
keyCode: F6,
|
|
354
|
+
key: 'F6',
|
|
355
|
+
code: 'F6'
|
|
356
|
+
},
|
|
357
|
+
[TestKey.F7]: {
|
|
358
|
+
keyCode: F7,
|
|
359
|
+
key: 'F7',
|
|
360
|
+
code: 'F7'
|
|
361
|
+
},
|
|
362
|
+
[TestKey.F8]: {
|
|
363
|
+
keyCode: F8,
|
|
364
|
+
key: 'F8',
|
|
365
|
+
code: 'F8'
|
|
366
|
+
},
|
|
367
|
+
[TestKey.F9]: {
|
|
368
|
+
keyCode: F9,
|
|
369
|
+
key: 'F9',
|
|
370
|
+
code: 'F9'
|
|
371
|
+
},
|
|
372
|
+
[TestKey.F10]: {
|
|
373
|
+
keyCode: F10,
|
|
374
|
+
key: 'F10',
|
|
375
|
+
code: 'F10'
|
|
376
|
+
},
|
|
377
|
+
[TestKey.F11]: {
|
|
378
|
+
keyCode: F11,
|
|
379
|
+
key: 'F11',
|
|
380
|
+
code: 'F11'
|
|
381
|
+
},
|
|
382
|
+
[TestKey.F12]: {
|
|
383
|
+
keyCode: F12,
|
|
384
|
+
key: 'F12',
|
|
385
|
+
code: 'F12'
|
|
386
|
+
},
|
|
387
|
+
[TestKey.META]: {
|
|
388
|
+
keyCode: META,
|
|
389
|
+
key: 'Meta',
|
|
390
|
+
code: 'MetaLeft'
|
|
391
|
+
},
|
|
392
|
+
[TestKey.COMMA]: {
|
|
393
|
+
keyCode: COMMA,
|
|
394
|
+
key: ',',
|
|
395
|
+
code: 'Comma'
|
|
396
|
+
}
|
|
402
397
|
};
|
|
403
|
-
/** A `TestElement` implementation for unit tests. */
|
|
404
398
|
class UnitTestElement {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
/**
|
|
565
|
-
* Dispatches a pointer event on the current element if the browser supports it.
|
|
566
|
-
* @param name Name of the pointer event to be dispatched.
|
|
567
|
-
* @param clientX Coordinate of the user's pointer along the X axis.
|
|
568
|
-
* @param clientY Coordinate of the user's pointer along the Y axis.
|
|
569
|
-
* @param button Mouse button that should be pressed when dispatching the event.
|
|
570
|
-
*/
|
|
571
|
-
_dispatchPointerEventIfSupported(name, clientX, clientY, offsetX, offsetY, button) {
|
|
572
|
-
// The latest versions of all browsers we support have the new `PointerEvent` API.
|
|
573
|
-
// Though since we capture the two most recent versions of these browsers, we also
|
|
574
|
-
// need to support Safari 12 at time of writing. Safari 12 does not have support for this,
|
|
575
|
-
// so we need to conditionally create and dispatch these events based on feature detection.
|
|
576
|
-
if (typeof PointerEvent !== 'undefined' && PointerEvent) {
|
|
577
|
-
dispatchPointerEvent(this.element, name, clientX, clientY, offsetX, offsetY, {
|
|
578
|
-
isPrimary: true,
|
|
579
|
-
button,
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
/**
|
|
584
|
-
* Dispatches all the events that are part of a mouse event sequence
|
|
585
|
-
* and then emits a given primary event at the end, if speciifed.
|
|
586
|
-
*/
|
|
587
|
-
async _dispatchMouseEventSequence(primaryEventName, args, button) {
|
|
588
|
-
let clientX = undefined;
|
|
589
|
-
let clientY = undefined;
|
|
590
|
-
let offsetX = undefined;
|
|
591
|
-
let offsetY = undefined;
|
|
592
|
-
let modifiers = {};
|
|
593
|
-
if (args.length && typeof args[args.length - 1] === 'object') {
|
|
594
|
-
modifiers = args.pop();
|
|
595
|
-
}
|
|
596
|
-
if (args.length) {
|
|
597
|
-
const { left, top, width, height } = await this.getDimensions();
|
|
598
|
-
offsetX = args[0] === 'center' ? width / 2 : args[0];
|
|
599
|
-
offsetY = args[0] === 'center' ? height / 2 : args[1];
|
|
600
|
-
// Round the computed click position as decimal pixels are not
|
|
601
|
-
// supported by mouse events and could lead to unexpected results.
|
|
602
|
-
clientX = Math.round(left + offsetX);
|
|
603
|
-
clientY = Math.round(top + offsetY);
|
|
604
|
-
}
|
|
605
|
-
this._dispatchPointerEventIfSupported('pointerdown', clientX, clientY, offsetX, offsetY, button);
|
|
606
|
-
dispatchMouseEvent(this.element, 'mousedown', clientX, clientY, offsetX, offsetY, button, modifiers);
|
|
607
|
-
this._dispatchPointerEventIfSupported('pointerup', clientX, clientY, offsetX, offsetY, button);
|
|
608
|
-
dispatchMouseEvent(this.element, 'mouseup', clientX, clientY, offsetX, offsetY, button, modifiers);
|
|
609
|
-
// If a primary event name is specified, emit it after the mouse event sequence.
|
|
610
|
-
if (primaryEventName !== null) {
|
|
611
|
-
dispatchMouseEvent(this.element, primaryEventName, clientX, clientY, offsetX, offsetY, button, modifiers);
|
|
612
|
-
}
|
|
613
|
-
// This call to _stabilize should not be needed since the callers will already do that them-
|
|
614
|
-
// selves. Nevertheless it breaks some tests in g3 without it. It needs to be investigated
|
|
615
|
-
// why removing breaks those tests.
|
|
616
|
-
// See: https://github.com/angular/components/pull/20758/files#r520886256.
|
|
617
|
-
await this._stabilize();
|
|
618
|
-
}
|
|
399
|
+
element;
|
|
400
|
+
_stabilize;
|
|
401
|
+
constructor(element, _stabilize) {
|
|
402
|
+
this.element = element;
|
|
403
|
+
this._stabilize = _stabilize;
|
|
404
|
+
}
|
|
405
|
+
async blur() {
|
|
406
|
+
triggerBlur(this.element);
|
|
407
|
+
await this._stabilize();
|
|
408
|
+
}
|
|
409
|
+
async clear() {
|
|
410
|
+
if (!isTextInput(this.element)) {
|
|
411
|
+
throw Error('Attempting to clear an invalid element');
|
|
412
|
+
}
|
|
413
|
+
clearElement(this.element);
|
|
414
|
+
await this._stabilize();
|
|
415
|
+
}
|
|
416
|
+
async click(...args) {
|
|
417
|
+
const isDisabled = this.element.disabled === true;
|
|
418
|
+
await this._dispatchMouseEventSequence(isDisabled ? null : 'click', args, 0);
|
|
419
|
+
await this._stabilize();
|
|
420
|
+
}
|
|
421
|
+
async rightClick(...args) {
|
|
422
|
+
await this._dispatchMouseEventSequence('contextmenu', args, 2);
|
|
423
|
+
await this._stabilize();
|
|
424
|
+
}
|
|
425
|
+
async focus() {
|
|
426
|
+
triggerFocus(this.element);
|
|
427
|
+
await this._stabilize();
|
|
428
|
+
}
|
|
429
|
+
async getCssValue(property) {
|
|
430
|
+
await this._stabilize();
|
|
431
|
+
return getComputedStyle(this.element).getPropertyValue(property);
|
|
432
|
+
}
|
|
433
|
+
async hover() {
|
|
434
|
+
this._dispatchPointerEventIfSupported('pointerenter');
|
|
435
|
+
dispatchMouseEvent(this.element, 'mouseover');
|
|
436
|
+
dispatchMouseEvent(this.element, 'mouseenter');
|
|
437
|
+
await this._stabilize();
|
|
438
|
+
}
|
|
439
|
+
async mouseAway() {
|
|
440
|
+
this._dispatchPointerEventIfSupported('pointerleave');
|
|
441
|
+
dispatchMouseEvent(this.element, 'mouseout');
|
|
442
|
+
dispatchMouseEvent(this.element, 'mouseleave');
|
|
443
|
+
await this._stabilize();
|
|
444
|
+
}
|
|
445
|
+
async sendKeys(...modifiersAndKeys) {
|
|
446
|
+
const args = modifiersAndKeys.map(k => typeof k === 'number' ? keyMap[k] : k);
|
|
447
|
+
typeInElement(this.element, ...args);
|
|
448
|
+
await this._stabilize();
|
|
449
|
+
}
|
|
450
|
+
async text(options) {
|
|
451
|
+
await this._stabilize();
|
|
452
|
+
if (options?.exclude) {
|
|
453
|
+
return _getTextWithExcludedElements(this.element, options.exclude);
|
|
454
|
+
}
|
|
455
|
+
return (this.element.textContent || '').trim();
|
|
456
|
+
}
|
|
457
|
+
async setContenteditableValue(value) {
|
|
458
|
+
const contenteditableAttr = await this.getAttribute('contenteditable');
|
|
459
|
+
if (contenteditableAttr !== '' && contenteditableAttr !== 'true' && contenteditableAttr !== 'plaintext-only') {
|
|
460
|
+
throw new Error('setContenteditableValue can only be called on a `contenteditable` element.');
|
|
461
|
+
}
|
|
462
|
+
await this._stabilize();
|
|
463
|
+
this.element.textContent = value;
|
|
464
|
+
}
|
|
465
|
+
async getAttribute(name) {
|
|
466
|
+
await this._stabilize();
|
|
467
|
+
return this.element.getAttribute(name);
|
|
468
|
+
}
|
|
469
|
+
async hasClass(name) {
|
|
470
|
+
await this._stabilize();
|
|
471
|
+
return this.element.classList.contains(name);
|
|
472
|
+
}
|
|
473
|
+
async getDimensions() {
|
|
474
|
+
await this._stabilize();
|
|
475
|
+
return this.element.getBoundingClientRect();
|
|
476
|
+
}
|
|
477
|
+
async getProperty(name) {
|
|
478
|
+
await this._stabilize();
|
|
479
|
+
return this.element[name];
|
|
480
|
+
}
|
|
481
|
+
async setInputValue(value) {
|
|
482
|
+
this.element.value = value;
|
|
483
|
+
await this._stabilize();
|
|
484
|
+
}
|
|
485
|
+
async selectOptions(...optionIndexes) {
|
|
486
|
+
let hasChanged = false;
|
|
487
|
+
const options = this.element.querySelectorAll('option');
|
|
488
|
+
const indexes = new Set(optionIndexes);
|
|
489
|
+
for (let i = 0; i < options.length; i++) {
|
|
490
|
+
const option = options[i];
|
|
491
|
+
const wasSelected = option.selected;
|
|
492
|
+
option.selected = indexes.has(i);
|
|
493
|
+
if (option.selected !== wasSelected) {
|
|
494
|
+
hasChanged = true;
|
|
495
|
+
dispatchFakeEvent(this.element, 'change');
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (hasChanged) {
|
|
499
|
+
await this._stabilize();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async matchesSelector(selector) {
|
|
503
|
+
await this._stabilize();
|
|
504
|
+
const elementPrototype = Element.prototype;
|
|
505
|
+
return (elementPrototype['matches'] || elementPrototype['msMatchesSelector']).call(this.element, selector);
|
|
506
|
+
}
|
|
507
|
+
async isFocused() {
|
|
508
|
+
await this._stabilize();
|
|
509
|
+
return document.activeElement === this.element;
|
|
510
|
+
}
|
|
511
|
+
async dispatchEvent(name, data) {
|
|
512
|
+
const event = createFakeEvent(name);
|
|
513
|
+
if (data) {
|
|
514
|
+
Object.assign(event, data);
|
|
515
|
+
}
|
|
516
|
+
dispatchEvent(this.element, event);
|
|
517
|
+
await this._stabilize();
|
|
518
|
+
}
|
|
519
|
+
_dispatchPointerEventIfSupported(name, clientX, clientY, offsetX, offsetY, button) {
|
|
520
|
+
if (typeof PointerEvent !== 'undefined' && PointerEvent) {
|
|
521
|
+
dispatchPointerEvent(this.element, name, clientX, clientY, offsetX, offsetY, {
|
|
522
|
+
isPrimary: true,
|
|
523
|
+
button
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
async _dispatchMouseEventSequence(primaryEventName, args, button) {
|
|
528
|
+
let clientX = undefined;
|
|
529
|
+
let clientY = undefined;
|
|
530
|
+
let offsetX = undefined;
|
|
531
|
+
let offsetY = undefined;
|
|
532
|
+
let modifiers = {};
|
|
533
|
+
if (args.length && typeof args[args.length - 1] === 'object') {
|
|
534
|
+
modifiers = args.pop();
|
|
535
|
+
}
|
|
536
|
+
if (args.length) {
|
|
537
|
+
const {
|
|
538
|
+
left,
|
|
539
|
+
top,
|
|
540
|
+
width,
|
|
541
|
+
height
|
|
542
|
+
} = await this.getDimensions();
|
|
543
|
+
offsetX = args[0] === 'center' ? width / 2 : args[0];
|
|
544
|
+
offsetY = args[0] === 'center' ? height / 2 : args[1];
|
|
545
|
+
clientX = Math.round(left + offsetX);
|
|
546
|
+
clientY = Math.round(top + offsetY);
|
|
547
|
+
}
|
|
548
|
+
this._dispatchPointerEventIfSupported('pointerdown', clientX, clientY, offsetX, offsetY, button);
|
|
549
|
+
dispatchMouseEvent(this.element, 'mousedown', clientX, clientY, offsetX, offsetY, button, modifiers);
|
|
550
|
+
this._dispatchPointerEventIfSupported('pointerup', clientX, clientY, offsetX, offsetY, button);
|
|
551
|
+
dispatchMouseEvent(this.element, 'mouseup', clientX, clientY, offsetX, offsetY, button, modifiers);
|
|
552
|
+
if (primaryEventName !== null) {
|
|
553
|
+
dispatchMouseEvent(this.element, primaryEventName, clientX, clientY, offsetX, offsetY, button, modifiers);
|
|
554
|
+
}
|
|
555
|
+
await this._stabilize();
|
|
556
|
+
}
|
|
619
557
|
}
|
|
620
558
|
|
|
621
|
-
/** The default environment options. */
|
|
622
559
|
const defaultEnvironmentOptions = {
|
|
623
|
-
|
|
560
|
+
queryFn: (selector, root) => root.querySelectorAll(selector)
|
|
624
561
|
};
|
|
625
|
-
/** Whether auto change detection is currently disabled. */
|
|
626
562
|
let disableAutoChangeDetection = false;
|
|
627
|
-
/**
|
|
628
|
-
* The set of non-destroyed fixtures currently being used by `TestbedHarnessEnvironment` instances.
|
|
629
|
-
*/
|
|
630
563
|
const activeFixtures = new Set();
|
|
631
|
-
/**
|
|
632
|
-
* Installs a handler for change detection batching status changes for a specific fixture.
|
|
633
|
-
* @param fixture The fixture to handle change detection batching for.
|
|
634
|
-
*/
|
|
635
564
|
function installAutoChangeDetectionStatusHandler(fixture) {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
565
|
+
if (!activeFixtures.size) {
|
|
566
|
+
handleAutoChangeDetectionStatus(({
|
|
567
|
+
isDisabled,
|
|
568
|
+
onDetectChangesNow
|
|
569
|
+
}) => {
|
|
570
|
+
disableAutoChangeDetection = isDisabled;
|
|
571
|
+
if (onDetectChangesNow) {
|
|
572
|
+
Promise.all(Array.from(activeFixtures).map(detectChanges)).then(onDetectChangesNow);
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
activeFixtures.add(fixture);
|
|
645
577
|
}
|
|
646
|
-
/**
|
|
647
|
-
* Uninstalls a handler for change detection batching status changes for a specific fixture.
|
|
648
|
-
* @param fixture The fixture to stop handling change detection batching for.
|
|
649
|
-
*/
|
|
650
578
|
function uninstallAutoChangeDetectionStatusHandler(fixture) {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
579
|
+
activeFixtures.delete(fixture);
|
|
580
|
+
if (!activeFixtures.size) {
|
|
581
|
+
stopHandlingAutoChangeDetectionStatus();
|
|
582
|
+
}
|
|
655
583
|
}
|
|
656
|
-
/** Whether we are currently in the fake async zone. */
|
|
657
584
|
function isInFakeAsyncZone() {
|
|
658
|
-
|
|
585
|
+
return typeof Zone !== 'undefined' && Zone.current.get('FakeAsyncTestZoneSpec') != null;
|
|
659
586
|
}
|
|
660
|
-
/**
|
|
661
|
-
* Triggers change detection for a specific fixture.
|
|
662
|
-
* @param fixture The fixture to trigger change detection for.
|
|
663
|
-
*/
|
|
664
587
|
async function detectChanges(fixture) {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
588
|
+
fixture.detectChanges();
|
|
589
|
+
if (isInFakeAsyncZone()) {
|
|
590
|
+
flush();
|
|
591
|
+
} else {
|
|
592
|
+
await fixture.whenStable();
|
|
593
|
+
}
|
|
672
594
|
}
|
|
673
|
-
/** A `HarnessEnvironment` implementation for Angular's Testbed. */
|
|
674
595
|
class TestbedHarnessEnvironment extends HarnessEnvironment {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
* Waits for all scheduled or running async tasks to complete. This allows harness
|
|
742
|
-
* authors to wait for async tasks outside of the Angular zone.
|
|
743
|
-
*/
|
|
744
|
-
async waitForTasksOutsideAngular() {
|
|
745
|
-
// If we run in the fake async zone, we run "flush" to run any scheduled tasks. This
|
|
746
|
-
// ensures that the harnesses behave inside of the FakeAsyncTestZone similar to the
|
|
747
|
-
// "AsyncTestZone" and the root zone (i.e. neither fakeAsync or async). Note that we
|
|
748
|
-
// cannot just rely on the task state observable to become stable because the state will
|
|
749
|
-
// never change. This is because the task queue will be only drained if the fake async
|
|
750
|
-
// zone is being flushed.
|
|
751
|
-
if (isInFakeAsyncZone()) {
|
|
752
|
-
flush();
|
|
753
|
-
}
|
|
754
|
-
// Wait until the task queue has been drained and the zone is stable. Note that
|
|
755
|
-
// we cannot rely on "fixture.whenStable" since it does not catch tasks scheduled
|
|
756
|
-
// outside of the Angular zone. For test harnesses, we want to ensure that the
|
|
757
|
-
// app is fully stabilized and therefore need to use our own zone interceptor.
|
|
758
|
-
await this._taskState?.pipe(takeWhile(state => !state.stable)).toPromise();
|
|
759
|
-
}
|
|
760
|
-
/** Gets the root element for the document. */
|
|
761
|
-
getDocumentRoot() {
|
|
762
|
-
return document.body;
|
|
763
|
-
}
|
|
764
|
-
/** Creates a `TestElement` from a raw element. */
|
|
765
|
-
createTestElement(element) {
|
|
766
|
-
return new UnitTestElement(element, this._stabilizeCallback);
|
|
767
|
-
}
|
|
768
|
-
/** Creates a `HarnessLoader` rooted at the given raw element. */
|
|
769
|
-
createEnvironment(element) {
|
|
770
|
-
return new TestbedHarnessEnvironment(element, this._fixture, this._options);
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* Gets a list of all elements matching the given selector under this environment's root element.
|
|
774
|
-
*/
|
|
775
|
-
async getAllRawElements(selector) {
|
|
776
|
-
await this.forceStabilize();
|
|
777
|
-
return Array.from(this._options.queryFn(selector, this.rawRootElement));
|
|
778
|
-
}
|
|
596
|
+
_fixture;
|
|
597
|
+
_destroyed = false;
|
|
598
|
+
_taskState;
|
|
599
|
+
_options;
|
|
600
|
+
_stabilizeCallback;
|
|
601
|
+
constructor(rawRootElement, _fixture, options) {
|
|
602
|
+
super(rawRootElement);
|
|
603
|
+
this._fixture = _fixture;
|
|
604
|
+
this._options = {
|
|
605
|
+
...defaultEnvironmentOptions,
|
|
606
|
+
...options
|
|
607
|
+
};
|
|
608
|
+
if (typeof Zone !== 'undefined') {
|
|
609
|
+
this._taskState = TaskStateZoneInterceptor.setup();
|
|
610
|
+
}
|
|
611
|
+
this._stabilizeCallback = () => this.forceStabilize();
|
|
612
|
+
installAutoChangeDetectionStatusHandler(_fixture);
|
|
613
|
+
_fixture.componentRef.onDestroy(() => {
|
|
614
|
+
uninstallAutoChangeDetectionStatusHandler(_fixture);
|
|
615
|
+
this._destroyed = true;
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
static loader(fixture, options) {
|
|
619
|
+
return new TestbedHarnessEnvironment(fixture.nativeElement, fixture, options);
|
|
620
|
+
}
|
|
621
|
+
static documentRootLoader(fixture, options) {
|
|
622
|
+
return new TestbedHarnessEnvironment(document.body, fixture, options);
|
|
623
|
+
}
|
|
624
|
+
static getNativeElement(el) {
|
|
625
|
+
if (el instanceof UnitTestElement) {
|
|
626
|
+
return el.element;
|
|
627
|
+
}
|
|
628
|
+
throw Error('This TestElement was not created by the TestbedHarnessEnvironment');
|
|
629
|
+
}
|
|
630
|
+
static async harnessForFixture(fixture, harnessType, options) {
|
|
631
|
+
const environment = new TestbedHarnessEnvironment(fixture.nativeElement, fixture, options);
|
|
632
|
+
await environment.forceStabilize();
|
|
633
|
+
return environment.createComponentHarness(harnessType, fixture.nativeElement);
|
|
634
|
+
}
|
|
635
|
+
async forceStabilize() {
|
|
636
|
+
if (!disableAutoChangeDetection) {
|
|
637
|
+
if (this._destroyed) {
|
|
638
|
+
throw Error('Harness is attempting to use a fixture that has already been destroyed.');
|
|
639
|
+
}
|
|
640
|
+
await detectChanges(this._fixture);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
async waitForTasksOutsideAngular() {
|
|
644
|
+
if (isInFakeAsyncZone()) {
|
|
645
|
+
flush();
|
|
646
|
+
}
|
|
647
|
+
await this._taskState?.pipe(takeWhile(state => !state.stable)).toPromise();
|
|
648
|
+
}
|
|
649
|
+
getDocumentRoot() {
|
|
650
|
+
return document.body;
|
|
651
|
+
}
|
|
652
|
+
createTestElement(element) {
|
|
653
|
+
return new UnitTestElement(element, this._stabilizeCallback);
|
|
654
|
+
}
|
|
655
|
+
createEnvironment(element) {
|
|
656
|
+
return new TestbedHarnessEnvironment(element, this._fixture, this._options);
|
|
657
|
+
}
|
|
658
|
+
async getAllRawElements(selector) {
|
|
659
|
+
await this.forceStabilize();
|
|
660
|
+
return Array.from(this._options.queryFn(selector, this.rawRootElement));
|
|
661
|
+
}
|
|
779
662
|
}
|
|
780
663
|
|
|
781
664
|
export { TestbedHarnessEnvironment, UnitTestElement };
|