source: trip-planner-front/node_modules/@angular/cdk/fesm2015/testing/testbed.js@ 6c1585f

Last change on this file since 6c1585f was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 36.3 KB
Line 
1import { __awaiter } from 'tslib';
2import { TestKey, _getTextWithExcludedElements, handleAutoChangeDetectionStatus, stopHandlingAutoChangeDetectionStatus, HarnessEnvironment } from '@angular/cdk/testing';
3import { flush } from '@angular/core/testing';
4import { takeWhile } from 'rxjs/operators';
5import { BehaviorSubject } from 'rxjs';
6import * as keyCodes from '@angular/cdk/keycodes';
7import { PERIOD } from '@angular/cdk/keycodes';
8
9/**
10 * @license
11 * Copyright Google LLC All Rights Reserved.
12 *
13 * Use of this source code is governed by an MIT-style license that can be
14 * found in the LICENSE file at https://angular.io/license
15 */
16/** Unique symbol that is used to patch a property to a proxy zone. */
17const stateObservableSymbol = Symbol('ProxyZone_PATCHED#stateObservable');
18/**
19 * Interceptor that can be set up in a `ProxyZone` instance. The interceptor
20 * will keep track of the task state and emit whenever the state changes.
21 *
22 * This serves as a workaround for https://github.com/angular/angular/issues/32896.
23 */
24class TaskStateZoneInterceptor {
25 constructor(_lastState) {
26 this._lastState = _lastState;
27 /** Subject that can be used to emit a new state change. */
28 this._stateSubject = new BehaviorSubject(this._lastState ? this._getTaskStateFromInternalZoneState(this._lastState) : { stable: true });
29 /** Public observable that emits whenever the task state changes. */
30 this.state = this._stateSubject;
31 }
32 /** This will be called whenever the task state changes in the intercepted zone. */
33 onHasTask(delegate, current, target, hasTaskState) {
34 if (current === target) {
35 this._stateSubject.next(this._getTaskStateFromInternalZoneState(hasTaskState));
36 }
37 }
38 /** Gets the task state from the internal ZoneJS task state. */
39 _getTaskStateFromInternalZoneState(state) {
40 return { stable: !state.macroTask && !state.microTask };
41 }
42 /**
43 * Sets up the custom task state Zone interceptor in the `ProxyZone`. Throws if
44 * no `ProxyZone` could be found.
45 * @returns an observable that emits whenever the task state changes.
46 */
47 static setup() {
48 if (Zone === undefined) {
49 throw Error('Could not find ZoneJS. For test harnesses running in TestBed, ' +
50 'ZoneJS needs to be installed.');
51 }
52 // tslint:disable-next-line:variable-name
53 const ProxyZoneSpec = Zone['ProxyZoneSpec'];
54 // If there is no "ProxyZoneSpec" installed, we throw an error and recommend
55 // setting up the proxy zone by pulling in the testing bundle.
56 if (!ProxyZoneSpec) {
57 throw Error('ProxyZoneSpec is needed for the test harnesses but could not be found. ' +
58 'Please make sure that your environment includes zone.js/dist/zone-testing.js');
59 }
60 // Ensure that there is a proxy zone instance set up, and get
61 // a reference to the instance if present.
62 const zoneSpec = ProxyZoneSpec.assertPresent();
63 // If there already is a delegate registered in the proxy zone, and it
64 // is type of the custom task state interceptor, we just use that state
65 // observable. This allows us to only intercept Zone once per test
66 // (similar to how `fakeAsync` or `async` work).
67 if (zoneSpec[stateObservableSymbol]) {
68 return zoneSpec[stateObservableSymbol];
69 }
70 // Since we intercept on environment creation and the fixture has been
71 // created before, we might have missed tasks scheduled before. Fortunately
72 // the proxy zone keeps track of the previous task state, so we can just pass
73 // this as initial state to the task zone interceptor.
74 const interceptor = new TaskStateZoneInterceptor(zoneSpec.lastTaskState);
75 const zoneSpecOnHasTask = zoneSpec.onHasTask.bind(zoneSpec);
76 // We setup the task state interceptor in the `ProxyZone`. Note that we cannot register
77 // the interceptor as a new proxy zone delegate because it would mean that other zone
78 // delegates (e.g. `FakeAsyncTestZone` or `AsyncTestZone`) can accidentally overwrite/disable
79 // our interceptor. Since we just intend to monitor the task state of the proxy zone, it is
80 // sufficient to just patch the proxy zone. This also avoids that we interfere with the task
81 // queue scheduling logic.
82 zoneSpec.onHasTask = function (...args) {
83 zoneSpecOnHasTask(...args);
84 interceptor.onHasTask(...args);
85 };
86 return zoneSpec[stateObservableSymbol] = interceptor.state;
87 }
88}
89
90/**
91 * @license
92 * Copyright Google LLC All Rights Reserved.
93 *
94 * Use of this source code is governed by an MIT-style license that can be
95 * found in the LICENSE file at https://angular.io/license
96 */
97/** Used to generate unique IDs for events. */
98let uniqueIds = 0;
99/**
100 * Creates a browser MouseEvent with the specified options.
101 * @docs-private
102 */
103function createMouseEvent(type, clientX = 0, clientY = 0, button = 0, modifiers = {}) {
104 const event = document.createEvent('MouseEvent');
105 const originalPreventDefault = event.preventDefault.bind(event);
106 // Note: We cannot determine the position of the mouse event based on the screen
107 // because the dimensions and position of the browser window are not available
108 // To provide reasonable `screenX` and `screenY` coordinates, we simply use the
109 // client coordinates as if the browser is opened in fullscreen.
110 const screenX = clientX;
111 const screenY = clientY;
112 event.initMouseEvent(type,
113 /* canBubble */ true,
114 /* cancelable */ true,
115 /* view */ window,
116 /* detail */ 0,
117 /* screenX */ screenX,
118 /* screenY */ screenY,
119 /* clientX */ clientX,
120 /* clientY */ clientY,
121 /* ctrlKey */ !!modifiers.control,
122 /* altKey */ !!modifiers.alt,
123 /* shiftKey */ !!modifiers.shift,
124 /* metaKey */ !!modifiers.meta,
125 /* button */ button,
126 /* relatedTarget */ null);
127 // `initMouseEvent` doesn't allow us to pass these properties into the constructor.
128 // Override them to 1, because they're used for fake screen reader event detection.
129 defineReadonlyEventProperty(event, 'buttons', 1);
130 defineReadonlyEventProperty(event, 'offsetX', 1);
131 defineReadonlyEventProperty(event, 'offsetY', 1);
132 // IE won't set `defaultPrevented` on synthetic events so we need to do it manually.
133 event.preventDefault = function () {
134 defineReadonlyEventProperty(event, 'defaultPrevented', true);
135 return originalPreventDefault();
136 };
137 return event;
138}
139/**
140 * Creates a browser `PointerEvent` with the specified options. Pointer events
141 * by default will appear as if they are the primary pointer of their type.
142 * https://www.w3.org/TR/pointerevents2/#dom-pointerevent-isprimary.
143 *
144 * For example, if pointer events for a multi-touch interaction are created, the non-primary
145 * pointer touches would need to be represented by non-primary pointer events.
146 *
147 * @docs-private
148 */
149function createPointerEvent(type, clientX = 0, clientY = 0, options = { isPrimary: true }) {
150 return new PointerEvent(type, Object.assign({ bubbles: true, cancelable: true, view: window, clientX,
151 clientY }, options));
152}
153/**
154 * Creates a browser TouchEvent with the specified pointer coordinates.
155 * @docs-private
156 */
157function createTouchEvent(type, pageX = 0, pageY = 0, clientX = 0, clientY = 0) {
158 // In favor of creating events that work for most of the browsers, the event is created
159 // as a basic UI Event. The necessary details for the event will be set manually.
160 const event = document.createEvent('UIEvent');
161 const touchDetails = { pageX, pageY, clientX, clientY, id: uniqueIds++ };
162 // TS3.6 removes the initUIEvent method and suggests porting to "new UIEvent()".
163 event.initUIEvent(type, true, true, window, 0);
164 // Most of the browsers don't have a "initTouchEvent" method that can be used to define
165 // the touch details.
166 defineReadonlyEventProperty(event, 'touches', [touchDetails]);
167 defineReadonlyEventProperty(event, 'targetTouches', [touchDetails]);
168 defineReadonlyEventProperty(event, 'changedTouches', [touchDetails]);
169 return event;
170}
171/**
172 * Creates a keyboard event with the specified key and modifiers.
173 * @docs-private
174 */
175function createKeyboardEvent(type, keyCode = 0, key = '', modifiers = {}) {
176 const event = document.createEvent('KeyboardEvent');
177 const originalPreventDefault = event.preventDefault.bind(event);
178 // Firefox does not support `initKeyboardEvent`, but supports `initKeyEvent`.
179 // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/initKeyEvent.
180 if (event.initKeyEvent !== undefined) {
181 event.initKeyEvent(type, true, true, window, modifiers.control, modifiers.alt, modifiers.shift, modifiers.meta, keyCode);
182 }
183 else {
184 // `initKeyboardEvent` expects to receive modifiers as a whitespace-delimited string
185 // See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/initKeyboardEvent
186 let modifiersList = '';
187 if (modifiers.control) {
188 modifiersList += 'Control ';
189 }
190 if (modifiers.alt) {
191 modifiersList += 'Alt ';
192 }
193 if (modifiers.shift) {
194 modifiersList += 'Shift ';
195 }
196 if (modifiers.meta) {
197 modifiersList += 'Meta ';
198 }
199 // TS3.6 removed the `initKeyboardEvent` method and suggested porting to
200 // `new KeyboardEvent()` constructor. We cannot use that as we support IE11.
201 // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/initKeyboardEvent.
202 event.initKeyboardEvent(type, true, /* canBubble */ true, /* cancelable */ window, /* view */ 0, /* char */ key, /* key */ 0, /* location */ modifiersList.trim(), /* modifiersList */ false /* repeat */);
203 }
204 // Webkit Browsers don't set the keyCode when calling the init function.
205 // See related bug https://bugs.webkit.org/show_bug.cgi?id=16735
206 defineReadonlyEventProperty(event, 'keyCode', keyCode);
207 defineReadonlyEventProperty(event, 'key', key);
208 defineReadonlyEventProperty(event, 'ctrlKey', !!modifiers.control);
209 defineReadonlyEventProperty(event, 'altKey', !!modifiers.alt);
210 defineReadonlyEventProperty(event, 'shiftKey', !!modifiers.shift);
211 defineReadonlyEventProperty(event, 'metaKey', !!modifiers.meta);
212 // IE won't set `defaultPrevented` on synthetic events so we need to do it manually.
213 event.preventDefault = function () {
214 defineReadonlyEventProperty(event, 'defaultPrevented', true);
215 return originalPreventDefault();
216 };
217 return event;
218}
219/**
220 * Creates a fake event object with any desired event type.
221 * @docs-private
222 */
223function createFakeEvent(type, canBubble = false, cancelable = true) {
224 const event = document.createEvent('Event');
225 event.initEvent(type, canBubble, cancelable);
226 return event;
227}
228/**
229 * Defines a readonly property on the given event object. Readonly properties on an event object
230 * are always set as configurable as that matches default readonly properties for DOM event objects.
231 */
232function defineReadonlyEventProperty(event, propertyName, value) {
233 Object.defineProperty(event, propertyName, { get: () => value, configurable: true });
234}
235
236/**
237 * @license
238 * Copyright Google LLC All Rights Reserved.
239 *
240 * Use of this source code is governed by an MIT-style license that can be
241 * found in the LICENSE file at https://angular.io/license
242 */
243/**
244 * Utility to dispatch any event on a Node.
245 * @docs-private
246 */
247function dispatchEvent(node, event) {
248 node.dispatchEvent(event);
249 return event;
250}
251/**
252 * Shorthand to dispatch a fake event on a specified node.
253 * @docs-private
254 */
255function dispatchFakeEvent(node, type, canBubble) {
256 return dispatchEvent(node, createFakeEvent(type, canBubble));
257}
258/**
259 * Shorthand to dispatch a keyboard event with a specified key code and
260 * optional modifiers.
261 * @docs-private
262 */
263function dispatchKeyboardEvent(node, type, keyCode, key, modifiers) {
264 return dispatchEvent(node, createKeyboardEvent(type, keyCode, key, modifiers));
265}
266/**
267 * Shorthand to dispatch a mouse event on the specified coordinates.
268 * @docs-private
269 */
270function dispatchMouseEvent(node, type, clientX = 0, clientY = 0, button, modifiers) {
271 return dispatchEvent(node, createMouseEvent(type, clientX, clientY, button, modifiers));
272}
273/**
274 * Shorthand to dispatch a pointer event on the specified coordinates.
275 * @docs-private
276 */
277function dispatchPointerEvent(node, type, clientX = 0, clientY = 0, options) {
278 return dispatchEvent(node, createPointerEvent(type, clientX, clientY, options));
279}
280/**
281 * Shorthand to dispatch a touch event on the specified coordinates.
282 * @docs-private
283 */
284function dispatchTouchEvent(node, type, pageX = 0, pageY = 0, clientX = 0, clientY = 0) {
285 return dispatchEvent(node, createTouchEvent(type, pageX, pageY, clientX, clientY));
286}
287
288/**
289 * @license
290 * Copyright Google LLC All Rights Reserved.
291 *
292 * Use of this source code is governed by an MIT-style license that can be
293 * found in the LICENSE file at https://angular.io/license
294 */
295function triggerFocusChange(element, event) {
296 let eventFired = false;
297 const handler = () => eventFired = true;
298 element.addEventListener(event, handler);
299 element[event]();
300 element.removeEventListener(event, handler);
301 if (!eventFired) {
302 dispatchFakeEvent(element, event);
303 }
304}
305/**
306 * Patches an elements focus and blur methods to emit events consistently and predictably.
307 * This is necessary, because some browsers, like IE11, will call the focus handlers asynchronously,
308 * while others won't fire them at all if the browser window is not focused.
309 * @docs-private
310 */
311function patchElementFocus(element) {
312 element.focus = () => dispatchFakeEvent(element, 'focus');
313 element.blur = () => dispatchFakeEvent(element, 'blur');
314}
315/** @docs-private */
316function triggerFocus(element) {
317 triggerFocusChange(element, 'focus');
318}
319/** @docs-private */
320function triggerBlur(element) {
321 triggerFocusChange(element, 'blur');
322}
323
324/**
325 * @license
326 * Copyright Google LLC All Rights Reserved.
327 *
328 * Use of this source code is governed by an MIT-style license that can be
329 * found in the LICENSE file at https://angular.io/license
330 */
331/** Input types for which the value can be entered incrementally. */
332const incrementalInputTypes = new Set(['text', 'email', 'hidden', 'password', 'search', 'tel', 'url']);
333/**
334 * Checks whether the given Element is a text input element.
335 * @docs-private
336 */
337function isTextInput(element) {
338 const nodeName = element.nodeName.toLowerCase();
339 return nodeName === 'input' || nodeName === 'textarea';
340}
341function typeInElement(element, ...modifiersAndKeys) {
342 const first = modifiersAndKeys[0];
343 let modifiers;
344 let rest;
345 if (typeof first !== 'string' && first.keyCode === undefined && first.key === undefined) {
346 modifiers = first;
347 rest = modifiersAndKeys.slice(1);
348 }
349 else {
350 modifiers = {};
351 rest = modifiersAndKeys;
352 }
353 const isInput = isTextInput(element);
354 const inputType = element.getAttribute('type') || 'text';
355 const keys = rest
356 .map(k => typeof k === 'string' ?
357 k.split('').map(c => ({ keyCode: c.toUpperCase().charCodeAt(0), key: c })) : [k])
358 .reduce((arr, k) => arr.concat(k), []);
359 // We simulate the user typing in a value by incrementally assigning the value below. The problem
360 // is that for some input types, the browser won't allow for an invalid value to be set via the
361 // `value` property which will always be the case when going character-by-character. If we detect
362 // such an input, we have to set the value all at once or listeners to the `input` event (e.g.
363 // the `ReactiveFormsModule` uses such an approach) won't receive the correct value.
364 const enterValueIncrementally = inputType === 'number' && keys.length > 0 ?
365 // The value can be set character by character in number inputs if it doesn't have any decimals.
366 keys.every(key => key.key !== '.' && key.keyCode !== PERIOD) :
367 incrementalInputTypes.has(inputType);
368 triggerFocus(element);
369 // When we aren't entering the value incrementally, assign it all at once ahead
370 // of time so that any listeners to the key events below will have access to it.
371 if (!enterValueIncrementally) {
372 element.value = keys.reduce((value, key) => value + (key.key || ''), '');
373 }
374 for (const key of keys) {
375 dispatchKeyboardEvent(element, 'keydown', key.keyCode, key.key, modifiers);
376 dispatchKeyboardEvent(element, 'keypress', key.keyCode, key.key, modifiers);
377 if (isInput && key.key && key.key.length === 1) {
378 if (enterValueIncrementally) {
379 element.value += key.key;
380 dispatchFakeEvent(element, 'input');
381 }
382 }
383 dispatchKeyboardEvent(element, 'keyup', key.keyCode, key.key, modifiers);
384 }
385 // Since we weren't dispatching `input` events while sending the keys, we have to do it now.
386 if (!enterValueIncrementally) {
387 dispatchFakeEvent(element, 'input');
388 }
389}
390/**
391 * Clears the text in an input or textarea element.
392 * @docs-private
393 */
394function clearElement(element) {
395 triggerFocus(element);
396 element.value = '';
397 dispatchFakeEvent(element, 'input');
398}
399
400/**
401 * @license
402 * Copyright Google LLC All Rights Reserved.
403 *
404 * Use of this source code is governed by an MIT-style license that can be
405 * found in the LICENSE file at https://angular.io/license
406 */
407
408/**
409 * @license
410 * Copyright Google LLC All Rights Reserved.
411 *
412 * Use of this source code is governed by an MIT-style license that can be
413 * found in the LICENSE file at https://angular.io/license
414 */
415/** Maps `TestKey` constants to the `keyCode` and `key` values used by native browser events. */
416const keyMap = {
417 [TestKey.BACKSPACE]: { keyCode: keyCodes.BACKSPACE, key: 'Backspace' },
418 [TestKey.TAB]: { keyCode: keyCodes.TAB, key: 'Tab' },
419 [TestKey.ENTER]: { keyCode: keyCodes.ENTER, key: 'Enter' },
420 [TestKey.SHIFT]: { keyCode: keyCodes.SHIFT, key: 'Shift' },
421 [TestKey.CONTROL]: { keyCode: keyCodes.CONTROL, key: 'Control' },
422 [TestKey.ALT]: { keyCode: keyCodes.ALT, key: 'Alt' },
423 [TestKey.ESCAPE]: { keyCode: keyCodes.ESCAPE, key: 'Escape' },
424 [TestKey.PAGE_UP]: { keyCode: keyCodes.PAGE_UP, key: 'PageUp' },
425 [TestKey.PAGE_DOWN]: { keyCode: keyCodes.PAGE_DOWN, key: 'PageDown' },
426 [TestKey.END]: { keyCode: keyCodes.END, key: 'End' },
427 [TestKey.HOME]: { keyCode: keyCodes.HOME, key: 'Home' },
428 [TestKey.LEFT_ARROW]: { keyCode: keyCodes.LEFT_ARROW, key: 'ArrowLeft' },
429 [TestKey.UP_ARROW]: { keyCode: keyCodes.UP_ARROW, key: 'ArrowUp' },
430 [TestKey.RIGHT_ARROW]: { keyCode: keyCodes.RIGHT_ARROW, key: 'ArrowRight' },
431 [TestKey.DOWN_ARROW]: { keyCode: keyCodes.DOWN_ARROW, key: 'ArrowDown' },
432 [TestKey.INSERT]: { keyCode: keyCodes.INSERT, key: 'Insert' },
433 [TestKey.DELETE]: { keyCode: keyCodes.DELETE, key: 'Delete' },
434 [TestKey.F1]: { keyCode: keyCodes.F1, key: 'F1' },
435 [TestKey.F2]: { keyCode: keyCodes.F2, key: 'F2' },
436 [TestKey.F3]: { keyCode: keyCodes.F3, key: 'F3' },
437 [TestKey.F4]: { keyCode: keyCodes.F4, key: 'F4' },
438 [TestKey.F5]: { keyCode: keyCodes.F5, key: 'F5' },
439 [TestKey.F6]: { keyCode: keyCodes.F6, key: 'F6' },
440 [TestKey.F7]: { keyCode: keyCodes.F7, key: 'F7' },
441 [TestKey.F8]: { keyCode: keyCodes.F8, key: 'F8' },
442 [TestKey.F9]: { keyCode: keyCodes.F9, key: 'F9' },
443 [TestKey.F10]: { keyCode: keyCodes.F10, key: 'F10' },
444 [TestKey.F11]: { keyCode: keyCodes.F11, key: 'F11' },
445 [TestKey.F12]: { keyCode: keyCodes.F12, key: 'F12' },
446 [TestKey.META]: { keyCode: keyCodes.META, key: 'Meta' }
447};
448/** A `TestElement` implementation for unit tests. */
449class UnitTestElement {
450 constructor(element, _stabilize) {
451 this.element = element;
452 this._stabilize = _stabilize;
453 }
454 /** Blur the element. */
455 blur() {
456 return __awaiter(this, void 0, void 0, function* () {
457 triggerBlur(this.element);
458 yield this._stabilize();
459 });
460 }
461 /** Clear the element's input (for input and textarea elements only). */
462 clear() {
463 return __awaiter(this, void 0, void 0, function* () {
464 if (!isTextInput(this.element)) {
465 throw Error('Attempting to clear an invalid element');
466 }
467 clearElement(this.element);
468 yield this._stabilize();
469 });
470 }
471 click(...args) {
472 return __awaiter(this, void 0, void 0, function* () {
473 yield this._dispatchMouseEventSequence('click', args, 0);
474 yield this._stabilize();
475 });
476 }
477 rightClick(...args) {
478 return __awaiter(this, void 0, void 0, function* () {
479 yield this._dispatchMouseEventSequence('contextmenu', args, 2);
480 yield this._stabilize();
481 });
482 }
483 /** Focus the element. */
484 focus() {
485 return __awaiter(this, void 0, void 0, function* () {
486 triggerFocus(this.element);
487 yield this._stabilize();
488 });
489 }
490 /** Get the computed value of the given CSS property for the element. */
491 getCssValue(property) {
492 return __awaiter(this, void 0, void 0, function* () {
493 yield this._stabilize();
494 // TODO(mmalerba): Consider adding value normalization if we run into common cases where its
495 // needed.
496 return getComputedStyle(this.element).getPropertyValue(property);
497 });
498 }
499 /** Hovers the mouse over the element. */
500 hover() {
501 return __awaiter(this, void 0, void 0, function* () {
502 this._dispatchPointerEventIfSupported('pointerenter');
503 dispatchMouseEvent(this.element, 'mouseenter');
504 yield this._stabilize();
505 });
506 }
507 /** Moves the mouse away from the element. */
508 mouseAway() {
509 return __awaiter(this, void 0, void 0, function* () {
510 this._dispatchPointerEventIfSupported('pointerleave');
511 dispatchMouseEvent(this.element, 'mouseleave');
512 yield this._stabilize();
513 });
514 }
515 sendKeys(...modifiersAndKeys) {
516 return __awaiter(this, void 0, void 0, function* () {
517 const args = modifiersAndKeys.map(k => typeof k === 'number' ? keyMap[k] : k);
518 typeInElement(this.element, ...args);
519 yield this._stabilize();
520 });
521 }
522 /**
523 * Gets the text from the element.
524 * @param options Options that affect what text is included.
525 */
526 text(options) {
527 return __awaiter(this, void 0, void 0, function* () {
528 yield this._stabilize();
529 if (options === null || options === void 0 ? void 0 : options.exclude) {
530 return _getTextWithExcludedElements(this.element, options.exclude);
531 }
532 return (this.element.textContent || '').trim();
533 });
534 }
535 /** Gets the value for the given attribute from the element. */
536 getAttribute(name) {
537 return __awaiter(this, void 0, void 0, function* () {
538 yield this._stabilize();
539 return this.element.getAttribute(name);
540 });
541 }
542 /** Checks whether the element has the given class. */
543 hasClass(name) {
544 return __awaiter(this, void 0, void 0, function* () {
545 yield this._stabilize();
546 return this.element.classList.contains(name);
547 });
548 }
549 /** Gets the dimensions of the element. */
550 getDimensions() {
551 return __awaiter(this, void 0, void 0, function* () {
552 yield this._stabilize();
553 return this.element.getBoundingClientRect();
554 });
555 }
556 /** Gets the value of a property of an element. */
557 getProperty(name) {
558 return __awaiter(this, void 0, void 0, function* () {
559 yield this._stabilize();
560 return this.element[name];
561 });
562 }
563 /** Sets the value of a property of an input. */
564 setInputValue(value) {
565 return __awaiter(this, void 0, void 0, function* () {
566 this.element.value = value;
567 yield this._stabilize();
568 });
569 }
570 /** Selects the options at the specified indexes inside of a native `select` element. */
571 selectOptions(...optionIndexes) {
572 return __awaiter(this, void 0, void 0, function* () {
573 let hasChanged = false;
574 const options = this.element.querySelectorAll('option');
575 const indexes = new Set(optionIndexes); // Convert to a set to remove duplicates.
576 for (let i = 0; i < options.length; i++) {
577 const option = options[i];
578 const wasSelected = option.selected;
579 // We have to go through `option.selected`, because `HTMLSelectElement.value` doesn't
580 // allow for multiple options to be selected, even in `multiple` mode.
581 option.selected = indexes.has(i);
582 if (option.selected !== wasSelected) {
583 hasChanged = true;
584 dispatchFakeEvent(this.element, 'change');
585 }
586 }
587 if (hasChanged) {
588 yield this._stabilize();
589 }
590 });
591 }
592 /** Checks whether this element matches the given selector. */
593 matchesSelector(selector) {
594 return __awaiter(this, void 0, void 0, function* () {
595 yield this._stabilize();
596 const elementPrototype = Element.prototype;
597 return (elementPrototype['matches'] || elementPrototype['msMatchesSelector'])
598 .call(this.element, selector);
599 });
600 }
601 /** Checks whether the element is focused. */
602 isFocused() {
603 return __awaiter(this, void 0, void 0, function* () {
604 yield this._stabilize();
605 return document.activeElement === this.element;
606 });
607 }
608 /**
609 * Dispatches an event with a particular name.
610 * @param name Name of the event to be dispatched.
611 */
612 dispatchEvent(name, data) {
613 return __awaiter(this, void 0, void 0, function* () {
614 const event = createFakeEvent(name);
615 if (data) {
616 // tslint:disable-next-line:ban Have to use `Object.assign` to preserve the original object.
617 Object.assign(event, data);
618 }
619 dispatchEvent(this.element, event);
620 yield this._stabilize();
621 });
622 }
623 /**
624 * Dispatches a pointer event on the current element if the browser supports it.
625 * @param name Name of the pointer event to be dispatched.
626 * @param clientX Coordinate of the user's pointer along the X axis.
627 * @param clientY Coordinate of the user's pointer along the Y axis.
628 * @param button Mouse button that should be pressed when dispatching the event.
629 */
630 _dispatchPointerEventIfSupported(name, clientX, clientY, button) {
631 // The latest versions of all browsers we support have the new `PointerEvent` API.
632 // Though since we capture the two most recent versions of these browsers, we also
633 // need to support Safari 12 at time of writing. Safari 12 does not have support for this,
634 // so we need to conditionally create and dispatch these events based on feature detection.
635 if (typeof PointerEvent !== 'undefined' && PointerEvent) {
636 dispatchPointerEvent(this.element, name, clientX, clientY, { isPrimary: true, button });
637 }
638 }
639 /** Dispatches all the events that are part of a mouse event sequence. */
640 _dispatchMouseEventSequence(name, args, button) {
641 return __awaiter(this, void 0, void 0, function* () {
642 let clientX = undefined;
643 let clientY = undefined;
644 let modifiers = {};
645 if (args.length && typeof args[args.length - 1] === 'object') {
646 modifiers = args.pop();
647 }
648 if (args.length) {
649 const { left, top, width, height } = yield this.getDimensions();
650 const relativeX = args[0] === 'center' ? width / 2 : args[0];
651 const relativeY = args[0] === 'center' ? height / 2 : args[1];
652 // Round the computed click position as decimal pixels are not
653 // supported by mouse events and could lead to unexpected results.
654 clientX = Math.round(left + relativeX);
655 clientY = Math.round(top + relativeY);
656 }
657 this._dispatchPointerEventIfSupported('pointerdown', clientX, clientY, button);
658 dispatchMouseEvent(this.element, 'mousedown', clientX, clientY, button, modifiers);
659 this._dispatchPointerEventIfSupported('pointerup', clientX, clientY, button);
660 dispatchMouseEvent(this.element, 'mouseup', clientX, clientY, button, modifiers);
661 dispatchMouseEvent(this.element, name, clientX, clientY, button, modifiers);
662 // This call to _stabilize should not be needed since the callers will already do that them-
663 // selves. Nevertheless it breaks some tests in g3 without it. It needs to be investigated
664 // why removing breaks those tests.
665 yield this._stabilize();
666 });
667 }
668}
669
670/**
671 * @license
672 * Copyright Google LLC All Rights Reserved.
673 *
674 * Use of this source code is governed by an MIT-style license that can be
675 * found in the LICENSE file at https://angular.io/license
676 */
677/** The default environment options. */
678const defaultEnvironmentOptions = {
679 queryFn: (selector, root) => root.querySelectorAll(selector)
680};
681/** Whether auto change detection is currently disabled. */
682let disableAutoChangeDetection = false;
683/**
684 * The set of non-destroyed fixtures currently being used by `TestbedHarnessEnvironment` instances.
685 */
686const activeFixtures = new Set();
687/**
688 * Installs a handler for change detection batching status changes for a specific fixture.
689 * @param fixture The fixture to handle change detection batching for.
690 */
691function installAutoChangeDetectionStatusHandler(fixture) {
692 if (!activeFixtures.size) {
693 handleAutoChangeDetectionStatus(({ isDisabled, onDetectChangesNow }) => {
694 disableAutoChangeDetection = isDisabled;
695 if (onDetectChangesNow) {
696 Promise.all(Array.from(activeFixtures).map(detectChanges)).then(onDetectChangesNow);
697 }
698 });
699 }
700 activeFixtures.add(fixture);
701}
702/**
703 * Uninstalls a handler for change detection batching status changes for a specific fixture.
704 * @param fixture The fixture to stop handling change detection batching for.
705 */
706function uninstallAutoChangeDetectionStatusHandler(fixture) {
707 activeFixtures.delete(fixture);
708 if (!activeFixtures.size) {
709 stopHandlingAutoChangeDetectionStatus();
710 }
711}
712/** Whether we are currently in the fake async zone. */
713function isInFakeAsyncZone() {
714 return Zone.current.get('FakeAsyncTestZoneSpec') != null;
715}
716/**
717 * Triggers change detection for a specific fixture.
718 * @param fixture The fixture to trigger change detection for.
719 */
720function detectChanges(fixture) {
721 return __awaiter(this, void 0, void 0, function* () {
722 fixture.detectChanges();
723 if (isInFakeAsyncZone()) {
724 flush();
725 }
726 else {
727 yield fixture.whenStable();
728 }
729 });
730}
731/** A `HarnessEnvironment` implementation for Angular's Testbed. */
732class TestbedHarnessEnvironment extends HarnessEnvironment {
733 constructor(rawRootElement, _fixture, options) {
734 super(rawRootElement);
735 this._fixture = _fixture;
736 /** Whether the environment has been destroyed. */
737 this._destroyed = false;
738 this._options = Object.assign(Object.assign({}, defaultEnvironmentOptions), options);
739 this._taskState = TaskStateZoneInterceptor.setup();
740 installAutoChangeDetectionStatusHandler(_fixture);
741 _fixture.componentRef.onDestroy(() => {
742 uninstallAutoChangeDetectionStatusHandler(_fixture);
743 this._destroyed = true;
744 });
745 }
746 /** Creates a `HarnessLoader` rooted at the given fixture's root element. */
747 static loader(fixture, options) {
748 return new TestbedHarnessEnvironment(fixture.nativeElement, fixture, options);
749 }
750 /**
751 * Creates a `HarnessLoader` at the document root. This can be used if harnesses are
752 * located outside of a fixture (e.g. overlays appended to the document body).
753 */
754 static documentRootLoader(fixture, options) {
755 return new TestbedHarnessEnvironment(document.body, fixture, options);
756 }
757 /** Gets the native DOM element corresponding to the given TestElement. */
758 static getNativeElement(el) {
759 if (el instanceof UnitTestElement) {
760 return el.element;
761 }
762 throw Error('This TestElement was not created by the TestbedHarnessEnvironment');
763 }
764 /**
765 * Creates an instance of the given harness type, using the fixture's root element as the
766 * harness's host element. This method should be used when creating a harness for the root element
767 * of a fixture, as components do not have the correct selector when they are created as the root
768 * of the fixture.
769 */
770 static harnessForFixture(fixture, harnessType, options) {
771 return __awaiter(this, void 0, void 0, function* () {
772 const environment = new TestbedHarnessEnvironment(fixture.nativeElement, fixture, options);
773 yield environment.forceStabilize();
774 return environment.createComponentHarness(harnessType, fixture.nativeElement);
775 });
776 }
777 /**
778 * Flushes change detection and async tasks captured in the Angular zone.
779 * In most cases it should not be necessary to call this manually. However, there may be some edge
780 * cases where it is needed to fully flush animation events.
781 */
782 forceStabilize() {
783 return __awaiter(this, void 0, void 0, function* () {
784 if (!disableAutoChangeDetection) {
785 if (this._destroyed) {
786 throw Error('Harness is attempting to use a fixture that has already been destroyed.');
787 }
788 yield detectChanges(this._fixture);
789 }
790 });
791 }
792 /**
793 * Waits for all scheduled or running async tasks to complete. This allows harness
794 * authors to wait for async tasks outside of the Angular zone.
795 */
796 waitForTasksOutsideAngular() {
797 return __awaiter(this, void 0, void 0, function* () {
798 // If we run in the fake async zone, we run "flush" to run any scheduled tasks. This
799 // ensures that the harnesses behave inside of the FakeAsyncTestZone similar to the
800 // "AsyncTestZone" and the root zone (i.e. neither fakeAsync or async). Note that we
801 // cannot just rely on the task state observable to become stable because the state will
802 // never change. This is because the task queue will be only drained if the fake async
803 // zone is being flushed.
804 if (isInFakeAsyncZone()) {
805 flush();
806 }
807 // Wait until the task queue has been drained and the zone is stable. Note that
808 // we cannot rely on "fixture.whenStable" since it does not catch tasks scheduled
809 // outside of the Angular zone. For test harnesses, we want to ensure that the
810 // app is fully stabilized and therefore need to use our own zone interceptor.
811 yield this._taskState.pipe(takeWhile(state => !state.stable)).toPromise();
812 });
813 }
814 /** Gets the root element for the document. */
815 getDocumentRoot() {
816 return document.body;
817 }
818 /** Creates a `TestElement` from a raw element. */
819 createTestElement(element) {
820 return new UnitTestElement(element, () => this.forceStabilize());
821 }
822 /** Creates a `HarnessLoader` rooted at the given raw element. */
823 createEnvironment(element) {
824 return new TestbedHarnessEnvironment(element, this._fixture, this._options);
825 }
826 /**
827 * Gets a list of all elements matching the given selector under this environment's root element.
828 */
829 getAllRawElements(selector) {
830 return __awaiter(this, void 0, void 0, function* () {
831 yield this.forceStabilize();
832 return Array.from(this._options.queryFn(selector, this.rawRootElement));
833 });
834 }
835}
836
837/**
838 * @license
839 * Copyright Google LLC All Rights Reserved.
840 *
841 * Use of this source code is governed by an MIT-style license that can be
842 * found in the LICENSE file at https://angular.io/license
843 */
844
845/**
846 * @license
847 * Copyright Google LLC All Rights Reserved.
848 *
849 * Use of this source code is governed by an MIT-style license that can be
850 * found in the LICENSE file at https://angular.io/license
851 */
852
853export { TestbedHarnessEnvironment, UnitTestElement };
854//# sourceMappingURL=testbed.js.map
Note: See TracBrowser for help on using the repository browser.