ngx-speculoos 6.0.0 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +403 -42
- package/esm2020/jasmine-matchers.mjs +2 -0
- package/esm2020/lib/component-tester.mjs +147 -0
- package/esm2020/lib/matchers.mjs +283 -0
- package/esm2020/lib/mock.mjs +25 -0
- package/esm2020/lib/route.mjs +319 -0
- package/{esm2015/lib/test-button.js → esm2020/lib/test-button.mjs} +0 -0
- package/esm2020/lib/test-element-querier.mjs +140 -0
- package/esm2020/lib/test-element.mjs +142 -0
- package/{esm2015/lib/test-html-element.js → esm2020/lib/test-html-element.mjs} +0 -0
- package/{esm2015/lib/test-input.js → esm2020/lib/test-input.mjs} +0 -0
- package/{esm2015/lib/test-select.js → esm2020/lib/test-select.mjs} +0 -0
- package/{esm2015/lib/test-textarea.js → esm2020/lib/test-textarea.mjs} +0 -0
- package/{esm2015/ngx-speculoos.js → esm2020/ngx-speculoos.mjs} +0 -0
- package/{esm2015/public_api.js → esm2020/public_api.mjs} +2 -1
- package/fesm2015/ngx-speculoos.mjs +1257 -0
- package/fesm2015/ngx-speculoos.mjs.map +1 -0
- package/{fesm2015/ngx-speculoos.js → fesm2020/ngx-speculoos.mjs} +436 -22
- package/fesm2020/ngx-speculoos.mjs.map +1 -0
- package/jasmine-matchers.d.ts +5 -0
- package/lib/component-tester.d.ts +87 -49
- package/lib/mock.d.ts +10 -0
- package/lib/route.d.ts +149 -0
- package/lib/test-element-querier.d.ts +15 -15
- package/lib/test-element.d.ts +87 -49
- package/package.json +24 -11
- package/public_api.d.ts +1 -0
- package/bundles/ngx-speculoos.umd.js +0 -1259
- package/bundles/ngx-speculoos.umd.js.map +0 -1
- package/esm2015/jasmine-matchers.js +0 -2
- package/esm2015/lib/component-tester.js +0 -96
- package/esm2015/lib/matchers.js +0 -244
- package/esm2015/lib/route.js +0 -81
- package/esm2015/lib/test-element-querier.js +0 -129
- package/esm2015/lib/test-element.js +0 -91
- package/fesm2015/ngx-speculoos.js.map +0 -1
|
@@ -0,0 +1,1257 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
import { By } from '@angular/platform-browser';
|
|
3
|
+
import { convertToParamMap, ActivatedRouteSnapshot, ActivatedRoute } from '@angular/router';
|
|
4
|
+
import { map, BehaviorSubject } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A wrapped DOM element, providing additional methods and attributes helping with writing tests
|
|
8
|
+
*/
|
|
9
|
+
class TestElement {
|
|
10
|
+
constructor(tester,
|
|
11
|
+
/**
|
|
12
|
+
* the wrapped debug element
|
|
13
|
+
*/
|
|
14
|
+
debugElement) {
|
|
15
|
+
this.tester = tester;
|
|
16
|
+
this.debugElement = debugElement;
|
|
17
|
+
this.querier = new TestElementQuerier(tester, debugElement);
|
|
18
|
+
}
|
|
19
|
+
get nativeElement() {
|
|
20
|
+
return this.debugElement.nativeElement;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* the text content of this element
|
|
24
|
+
*/
|
|
25
|
+
get textContent() {
|
|
26
|
+
return this.nativeElement.textContent;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* dispatches an event of the given type from the wrapped element, then triggers a change detection
|
|
30
|
+
*/
|
|
31
|
+
dispatchEventOfType(type) {
|
|
32
|
+
this.nativeElement.dispatchEvent(new Event(type));
|
|
33
|
+
this.tester.detectChanges();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* dispatches the given event from the wrapped element, then triggers a change detection
|
|
37
|
+
*/
|
|
38
|
+
dispatchEvent(event) {
|
|
39
|
+
this.nativeElement.dispatchEvent(event);
|
|
40
|
+
this.tester.detectChanges();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Gets the CSS classes of the wrapped element, as an array
|
|
44
|
+
*/
|
|
45
|
+
get classes() {
|
|
46
|
+
return Array.prototype.slice.call(this.nativeElement.classList);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Gets the attribute of the wrapped element with the given name
|
|
50
|
+
* @param name the name of the attribute to get
|
|
51
|
+
*/
|
|
52
|
+
attr(name) {
|
|
53
|
+
return this.nativeElement.getAttribute(name);
|
|
54
|
+
}
|
|
55
|
+
element(selector) {
|
|
56
|
+
return this.querier.element(selector);
|
|
57
|
+
}
|
|
58
|
+
elements(selector) {
|
|
59
|
+
return this.querier.elements(selector);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Gets the first input matched by the given selector. Throws an Error if the matched element isn't actually an input.
|
|
63
|
+
* @param selector a CSS or directive selector
|
|
64
|
+
* @returns the wrapped input, or null if no element was matched
|
|
65
|
+
*/
|
|
66
|
+
input(selector) {
|
|
67
|
+
return this.querier.input(selector);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Gets the first select matched by the given selector. Throws an Error if the matched element isn't actually a select.
|
|
71
|
+
* @param selector a CSS or directive selector
|
|
72
|
+
* @returns the wrapped select, or null if no element was matched
|
|
73
|
+
*/
|
|
74
|
+
select(selector) {
|
|
75
|
+
return this.querier.select(selector);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Gets the first textarea matched by the given selector
|
|
79
|
+
* @param selector a CSS or directive selector
|
|
80
|
+
* @returns the wrapped textarea, or null if no element was matched. Throws an Error if the matched element isn't actually a textarea.
|
|
81
|
+
* @throws {Error} if the matched element isn't actually a textarea
|
|
82
|
+
*/
|
|
83
|
+
textarea(selector) {
|
|
84
|
+
return this.querier.textarea(selector);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Gets the first button matched by the given selector. Throws an Error if the matched element isn't actually a button.
|
|
88
|
+
* @param selector a CSS or directive selector
|
|
89
|
+
* @returns the wrapped button, or null if no element was matched
|
|
90
|
+
*/
|
|
91
|
+
button(selector) {
|
|
92
|
+
return this.querier.button(selector);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Gets the first directive matching the given component directive selector and returns its component instance
|
|
96
|
+
* @param selector the selector of a component directive
|
|
97
|
+
*/
|
|
98
|
+
component(selector) {
|
|
99
|
+
var _a, _b, _c;
|
|
100
|
+
return (_c = (_b = (_a = this.querier.element(selector)) === null || _a === void 0 ? void 0 : _a.debugElement) === null || _b === void 0 ? void 0 : _b.componentInstance) !== null && _c !== void 0 ? _c : null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Gets the directives matching the given component directive selector and returns their component instance
|
|
104
|
+
* @param selector the selector of a component directive
|
|
105
|
+
*/
|
|
106
|
+
components(selector) {
|
|
107
|
+
return this.querier.elements(selector).map(e => e.debugElement.componentInstance);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Gets the first element matching the given selector, then gets the given token from its injector, or null if there is no such token
|
|
111
|
+
* @param selector a CSS or directive selector
|
|
112
|
+
* @param token the token to get from the matched element injector
|
|
113
|
+
*/
|
|
114
|
+
token(selector, token) {
|
|
115
|
+
var _a, _b, _c, _d;
|
|
116
|
+
return (_d = (_c = (_b = (_a = this.querier.element(selector)) === null || _a === void 0 ? void 0 : _a.debugElement) === null || _b === void 0 ? void 0 : _b.injector) === null || _c === void 0 ? void 0 : _c.get(token, null)) !== null && _d !== void 0 ? _d : null;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Gets the elements matching the given selector, then gets their given token from their injector, or null if there is no such token
|
|
120
|
+
* @param selector a CSS or directive selector
|
|
121
|
+
* @param token the token to get from the matched element injector
|
|
122
|
+
*/
|
|
123
|
+
tokens(selector, token) {
|
|
124
|
+
return this.querier.elements(selector).map(e => { var _a; return (_a = e.debugElement.injector.get(token, null)) !== null && _a !== void 0 ? _a : null; });
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Gets the element matching the given selector, and if found, creates and returns a custom TestElement of the provided
|
|
128
|
+
* type. This is useful to create custom higher-level abstractions similar to TestInput, TestSelect, etc. for
|
|
129
|
+
* custom elements or components.
|
|
130
|
+
* @param selector a CSS or directive selector
|
|
131
|
+
* @param customTestElementType the type of the TestElement subclass that will wrap the found element
|
|
132
|
+
*/
|
|
133
|
+
custom(selector, customTestElementType) {
|
|
134
|
+
const element = this.querier.element(selector);
|
|
135
|
+
return element && new customTestElementType(this.tester, element.debugElement);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Gets the elements matching the given selector, and creates and returns custom TestElements of the provided
|
|
139
|
+
* type. This is useful to create custom higher-level abstractions similar to TestInput, TestSelect, etc. for
|
|
140
|
+
* custom elements or components.
|
|
141
|
+
* @param selector a CSS or directive selector
|
|
142
|
+
* @param customTestElementType the type of the TestElement subclass that will wrap the found elements
|
|
143
|
+
*/
|
|
144
|
+
customs(selector, customTestElementType) {
|
|
145
|
+
return this.querier.elements(selector).map(element => new customTestElementType(this.tester, element.debugElement));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* A wrapped DOM HTML element, providing additional methods and attributes helping with writing tests
|
|
151
|
+
*/
|
|
152
|
+
class TestHtmlElement extends TestElement {
|
|
153
|
+
constructor(tester, debugElement) {
|
|
154
|
+
super(tester, debugElement);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Clicks on the wrapped element, then triggers a change detection
|
|
158
|
+
*/
|
|
159
|
+
click() {
|
|
160
|
+
this.nativeElement.click();
|
|
161
|
+
this.tester.detectChanges();
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Tests if the element is visible, in the same meaning (and implementation) as in jQuery, i.e.
|
|
165
|
+
* present anywhere in the DOM, and visible.
|
|
166
|
+
* An element is not visible typically, if its display style or any of its ancestors display style is none.
|
|
167
|
+
*/
|
|
168
|
+
get visible() {
|
|
169
|
+
return !!(this.nativeElement.offsetWidth || this.nativeElement.offsetHeight || this.nativeElement.getClientRects().length);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* A wrapped button element, providing additional methods and attributes helping with writing tests
|
|
175
|
+
*/
|
|
176
|
+
class TestButton extends TestHtmlElement {
|
|
177
|
+
constructor(tester, debugElement) {
|
|
178
|
+
super(tester, debugElement);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* the disabled flag of the button
|
|
182
|
+
*/
|
|
183
|
+
get disabled() {
|
|
184
|
+
return this.nativeElement.disabled;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* A wrapped DOM HTML select element, providing additional methods and attributes helping with writing tests
|
|
190
|
+
*/
|
|
191
|
+
class TestSelect extends TestHtmlElement {
|
|
192
|
+
constructor(tester, debugElement) {
|
|
193
|
+
super(tester, debugElement);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Selects the option at the given index, then dispatches an event of type change and triggers a change detection
|
|
197
|
+
*/
|
|
198
|
+
selectIndex(index) {
|
|
199
|
+
this.nativeElement.selectedIndex = index;
|
|
200
|
+
this.dispatchEventOfType('change');
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Selects the first option with the given value, then dispatches an event of type change and triggers a change detection.
|
|
204
|
+
* If there is no option with the given value, then does nothing
|
|
205
|
+
* TODO should it throw instead?
|
|
206
|
+
*/
|
|
207
|
+
selectValue(value) {
|
|
208
|
+
const index = this.optionValues.indexOf(value);
|
|
209
|
+
if (index >= 0) {
|
|
210
|
+
this.selectIndex(index);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Selects the first option with the given label (or text), then dispatches an event of type change and triggers a change detection.
|
|
215
|
+
* If there is no option with the given label, then does nothing
|
|
216
|
+
* TODO should it throw instead?
|
|
217
|
+
*/
|
|
218
|
+
selectLabel(label) {
|
|
219
|
+
const index = this.optionLabels.indexOf(label);
|
|
220
|
+
if (index >= 0) {
|
|
221
|
+
this.selectIndex(index);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* the selected index of the wrapped select
|
|
226
|
+
*/
|
|
227
|
+
get selectedIndex() {
|
|
228
|
+
return this.nativeElement.selectedIndex;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* the value of the selected option of the wrapped select, or null if there is no selected option
|
|
232
|
+
*/
|
|
233
|
+
get selectedValue() {
|
|
234
|
+
if (this.selectedIndex < 0) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return this.nativeElement.options[this.selectedIndex].value;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* the label (or text if no label) of the selected option of the wrapped select, or null if there is no selected option
|
|
241
|
+
*/
|
|
242
|
+
get selectedLabel() {
|
|
243
|
+
if (this.selectedIndex < 0) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
return this.nativeElement.options[this.selectedIndex].label;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* the values of the options, as an array
|
|
250
|
+
*/
|
|
251
|
+
get optionValues() {
|
|
252
|
+
return Array.prototype.slice.call(this.nativeElement.options).map(option => option.value);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* the labels (or texts if no label) of the options, as an array
|
|
256
|
+
*/
|
|
257
|
+
get optionLabels() {
|
|
258
|
+
return Array.prototype.slice.call(this.nativeElement.options).map(option => option.label);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* the number of options in the select
|
|
262
|
+
*/
|
|
263
|
+
get size() {
|
|
264
|
+
return this.nativeElement.options.length;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* the disabled property of the wrapped select
|
|
268
|
+
*/
|
|
269
|
+
get disabled() {
|
|
270
|
+
return this.nativeElement.disabled;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* A wrapped DOM HTML textarea element, providing additional methods and attributes helping with writing tests
|
|
276
|
+
*/
|
|
277
|
+
class TestTextArea extends TestHtmlElement {
|
|
278
|
+
constructor(tester, debugElement) {
|
|
279
|
+
super(tester, debugElement);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Sets the value of the wrapped textarea, then dispatches an event of type input and triggers a change detection
|
|
283
|
+
* @param value the new value of the textarea
|
|
284
|
+
*/
|
|
285
|
+
fillWith(value) {
|
|
286
|
+
this.nativeElement.value = value;
|
|
287
|
+
this.dispatchEventOfType('input');
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* the value of the wrapped textarea
|
|
291
|
+
*/
|
|
292
|
+
get value() {
|
|
293
|
+
return this.nativeElement.value;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* the disabled property of the wrapped textarea
|
|
297
|
+
*/
|
|
298
|
+
get disabled() {
|
|
299
|
+
return this.nativeElement.disabled;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* A wrapped DOM HTML input element, providing additional methods and attributes helping with writing tests
|
|
305
|
+
*/
|
|
306
|
+
class TestInput extends TestHtmlElement {
|
|
307
|
+
constructor(tester, debugElement) {
|
|
308
|
+
super(tester, debugElement);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Sets the value of the wrapped input, then dispatches an event of type input and triggers a change detection
|
|
312
|
+
* @param value the new value of the input
|
|
313
|
+
*/
|
|
314
|
+
fillWith(value) {
|
|
315
|
+
this.nativeElement.value = value;
|
|
316
|
+
this.dispatchEventOfType('input');
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* the value of the wrapped input
|
|
320
|
+
*/
|
|
321
|
+
get value() {
|
|
322
|
+
return this.nativeElement.value;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* the checked property of the wrapped input
|
|
326
|
+
*/
|
|
327
|
+
get checked() {
|
|
328
|
+
return this.nativeElement.checked;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* the disabled property of the wrapped input
|
|
332
|
+
*/
|
|
333
|
+
get disabled() {
|
|
334
|
+
return this.nativeElement.disabled;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Checks the wrapped input, then dispatches an event of type change and triggers a change detection
|
|
338
|
+
*/
|
|
339
|
+
check() {
|
|
340
|
+
this.nativeElement.checked = true;
|
|
341
|
+
this.dispatchEventOfType('change');
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Unchecks the wrapped input, then dispatches an event of type change and triggers a change detection
|
|
345
|
+
*/
|
|
346
|
+
uncheck() {
|
|
347
|
+
this.nativeElement.checked = false;
|
|
348
|
+
this.dispatchEventOfType('change');
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
353
|
+
/**
|
|
354
|
+
* @internal
|
|
355
|
+
*/
|
|
356
|
+
class TestElementQuerier {
|
|
357
|
+
constructor(tester, root) {
|
|
358
|
+
this.tester = tester;
|
|
359
|
+
this.root = root;
|
|
360
|
+
}
|
|
361
|
+
static wrap(childDebugElement, tester) {
|
|
362
|
+
const childElement = childDebugElement.nativeElement;
|
|
363
|
+
if (childElement instanceof HTMLButtonElement) {
|
|
364
|
+
return new TestButton(tester, childDebugElement);
|
|
365
|
+
}
|
|
366
|
+
else if (childElement instanceof HTMLInputElement) {
|
|
367
|
+
return new TestInput(tester, childDebugElement);
|
|
368
|
+
}
|
|
369
|
+
else if (childElement instanceof HTMLSelectElement) {
|
|
370
|
+
return new TestSelect(tester, childDebugElement);
|
|
371
|
+
}
|
|
372
|
+
else if (childElement instanceof HTMLTextAreaElement) {
|
|
373
|
+
return new TestTextArea(tester, childDebugElement);
|
|
374
|
+
}
|
|
375
|
+
else if (childElement instanceof HTMLElement) {
|
|
376
|
+
return new TestHtmlElement(tester, childDebugElement);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
return new TestElement(tester, childDebugElement);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Gets the first element matching the given selector and wraps it into a TestElement. The actual type
|
|
384
|
+
* of the returned value is the TestElement subclass matching the type of the found element. So, if the
|
|
385
|
+
* matched element is an input for example, the method will return a TestInput. You can thus use
|
|
386
|
+
* `tester.element('#some-input') as TestInput`.
|
|
387
|
+
* @param selector a CSS or directive selector
|
|
388
|
+
* @returns the wrapped element, or null if no element matches the selector.
|
|
389
|
+
*/
|
|
390
|
+
element(selector) {
|
|
391
|
+
const childElement = this.query(selector);
|
|
392
|
+
return childElement && TestElementQuerier.wrap(childElement, this.tester);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Gets all the elements matching the given selector and wraps them into a TestElement. The actual type
|
|
396
|
+
* of the returned elements is the TestElement subclass matching the type of the found element. So, if the
|
|
397
|
+
* matched elements are inputs for example, the method will return an array of TestInput. You can thus use
|
|
398
|
+
* `tester.elements('input') as Array<TestInput>`.
|
|
399
|
+
* @param selector a CSS or directive selector
|
|
400
|
+
* @returns the array of matched elements, empty if no element was matched
|
|
401
|
+
*/
|
|
402
|
+
elements(selector) {
|
|
403
|
+
const childElements = this.queryAll(selector);
|
|
404
|
+
return childElements.map(debugElement => TestElementQuerier.wrap(debugElement, this.tester));
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Gets the first input matched by the given selector. Throws an Error if the matched element isn't actually an input.
|
|
408
|
+
* @param selector a CSS or directive selector
|
|
409
|
+
* @returns the wrapped input, or null if no element was matched
|
|
410
|
+
*/
|
|
411
|
+
input(selector) {
|
|
412
|
+
const childElement = this.query(selector);
|
|
413
|
+
if (!childElement) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
else if (!(childElement.nativeElement instanceof HTMLInputElement)) {
|
|
417
|
+
throw new Error(`Element with selector ${selector} is not an HTMLInputElement`);
|
|
418
|
+
}
|
|
419
|
+
return new TestInput(this.tester, childElement);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Gets the first select matched by the given selector. Throws an Error if the matched element isn't actually a select.
|
|
423
|
+
* @param selector a CSS or directive selector
|
|
424
|
+
* @returns the wrapped select, or null if no element was matched
|
|
425
|
+
*/
|
|
426
|
+
select(selector) {
|
|
427
|
+
const childElement = this.query(selector);
|
|
428
|
+
if (!childElement) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
else if (!(childElement.nativeElement instanceof HTMLSelectElement)) {
|
|
432
|
+
throw new Error(`Element with selector ${selector} is not an HTMLSelectElement`);
|
|
433
|
+
}
|
|
434
|
+
return new TestSelect(this.tester, childElement);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Gets the first textarea matched by the given selector
|
|
438
|
+
* @param selector a CSS or directive selector
|
|
439
|
+
* @returns the wrapped textarea, or null if no element was matched. Throws an Error if the matched element isn't actually a textarea.
|
|
440
|
+
* @throws {Error} if the matched element isn't actually a textarea
|
|
441
|
+
*/
|
|
442
|
+
textarea(selector) {
|
|
443
|
+
const childElement = this.query(selector);
|
|
444
|
+
if (!childElement) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
else if (!(childElement.nativeElement instanceof HTMLTextAreaElement)) {
|
|
448
|
+
throw new Error(`Element with selector ${selector} is not an HTMLTextAreaElement`);
|
|
449
|
+
}
|
|
450
|
+
return new TestTextArea(this.tester, childElement);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Gets the first button matched by the given selector. Throws an Error if the matched element isn't actually a button.
|
|
454
|
+
* @param selector a CSS or directive selector
|
|
455
|
+
* @returns the wrapped button, or null if no element was matched
|
|
456
|
+
*/
|
|
457
|
+
button(selector) {
|
|
458
|
+
const childElement = this.query(selector);
|
|
459
|
+
if (!childElement) {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
else if (!(childElement.nativeElement instanceof HTMLButtonElement)) {
|
|
463
|
+
throw new Error(`Element with selector ${selector} is not an HTMLButtonElement`);
|
|
464
|
+
}
|
|
465
|
+
return new TestButton(this.tester, childElement);
|
|
466
|
+
}
|
|
467
|
+
query(selector) {
|
|
468
|
+
if (typeof selector === 'string') {
|
|
469
|
+
return this.root.query(By.css(selector));
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
return this.root.query(By.directive(selector));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
queryAll(selector) {
|
|
476
|
+
if (typeof selector === 'string') {
|
|
477
|
+
return this.root.queryAll(By.css(selector));
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
return this.root.queryAll(By.directive(selector));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
486
|
+
/**
|
|
487
|
+
* The main entry point of the API. It wraps an Angular ComponentFixture<T>, and gives access to its
|
|
488
|
+
* most used properties and methods. It also allows getting elements wrapped in TestElement (and its subclasses)
|
|
489
|
+
* @param <C> the type of the component to test
|
|
490
|
+
*/
|
|
491
|
+
class ComponentTester {
|
|
492
|
+
/**
|
|
493
|
+
* Creates a ComponentFixture for the given component type using the TestBed, and creates a ComponentTester
|
|
494
|
+
* wrapping (and delegating) to this fixture. If a fixture is passed, then delegates to this fixture directly.
|
|
495
|
+
*
|
|
496
|
+
* Note that no `detectChanges()` call is made by this constructor. It's up to the subclass constructor,
|
|
497
|
+
* or to the user of the created ComponentTester, to call `detectChanges()` at least once to trigger change
|
|
498
|
+
* detection. This is necessary because some component templates can only be evaluated once inputs
|
|
499
|
+
* have been set on the component instance.
|
|
500
|
+
*
|
|
501
|
+
* @param arg the type of the component to wrap, or a component fixture to wrap
|
|
502
|
+
*/
|
|
503
|
+
constructor(arg) {
|
|
504
|
+
this.fixture = arg instanceof ComponentFixture ? arg : TestBed.createComponent(arg);
|
|
505
|
+
this.testElement = TestElementQuerier.wrap(this.debugElement, this);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Creates a component fixture of the given type with the TestBed and wraps it into a ComponentTester
|
|
509
|
+
*/
|
|
510
|
+
static create(componentType) {
|
|
511
|
+
const fixture = TestBed.createComponent(componentType);
|
|
512
|
+
return new ComponentTester(fixture);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* The native DOM host element of the component
|
|
516
|
+
*/
|
|
517
|
+
get nativeElement() {
|
|
518
|
+
return this.fixture.nativeElement;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Gets the instance of the tested component from the wrapped fixture
|
|
522
|
+
*/
|
|
523
|
+
get componentInstance() {
|
|
524
|
+
return this.fixture.componentInstance;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Gets the debug element from the wrapped fixture
|
|
528
|
+
*/
|
|
529
|
+
get debugElement() {
|
|
530
|
+
return this.fixture.debugElement;
|
|
531
|
+
}
|
|
532
|
+
element(selector) {
|
|
533
|
+
return this.testElement.element(selector);
|
|
534
|
+
}
|
|
535
|
+
elements(selector) {
|
|
536
|
+
return this.testElement.elements(selector);
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Gets the first input matched by the given selector. Throws an Error if the matched element isn't actually an input.
|
|
540
|
+
* @param selector a CSS or directive selector
|
|
541
|
+
* @returns the wrapped input, or null if no element was matched
|
|
542
|
+
*/
|
|
543
|
+
input(selector) {
|
|
544
|
+
return this.testElement.input(selector);
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Gets the first select matched by the given selector. Throws an Error if the matched element isn't actually a select.
|
|
548
|
+
* @param selector a CSS or directive selector
|
|
549
|
+
* @returns the wrapped select, or null if no element was matched
|
|
550
|
+
*/
|
|
551
|
+
select(selector) {
|
|
552
|
+
return this.testElement.select(selector);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Gets the first textarea matched by the given selector
|
|
556
|
+
* @param selector a CSS or directive selector
|
|
557
|
+
* @returns the wrapped textarea, or null if no element was matched. Throws an Error if the matched element isn't actually a textarea.
|
|
558
|
+
* @throws {Error} if the matched element isn't actually a textarea
|
|
559
|
+
*/
|
|
560
|
+
textarea(selector) {
|
|
561
|
+
return this.testElement.textarea(selector);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Gets the first button matched by the given selector. Throws an Error if the matched element isn't actually a button.
|
|
565
|
+
* @param selector a CSS or directive selector
|
|
566
|
+
* @returns the wrapped button, or null if no element was matched
|
|
567
|
+
*/
|
|
568
|
+
button(selector) {
|
|
569
|
+
return this.testElement.button(selector);
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Gets the first directive matching the given component directive selector and returns its component instance
|
|
573
|
+
* @param selector the selector of a component directive
|
|
574
|
+
*/
|
|
575
|
+
component(selector) {
|
|
576
|
+
return this.testElement.component(selector);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Gets the directives matching the given component directive selector and returns their component instance
|
|
580
|
+
* @param selector the selector of a component directive
|
|
581
|
+
*/
|
|
582
|
+
components(selector) {
|
|
583
|
+
return this.testElement.components(selector);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Gets the first element matching the given selector, then gets the given token from its injector, or null if there is no such token
|
|
587
|
+
* @param selector a CSS or directive selector
|
|
588
|
+
* @param token the token to get from the matched element injector
|
|
589
|
+
*/
|
|
590
|
+
token(selector, token) {
|
|
591
|
+
return this.testElement.token(selector, token);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Gets the elements matching the given selector, then gets their given token from their injector, or null if there is no such token
|
|
595
|
+
* @param selector a CSS or directive selector
|
|
596
|
+
* @param token the token to get from the matched element injector
|
|
597
|
+
*/
|
|
598
|
+
tokens(selector, token) {
|
|
599
|
+
return this.testElement.tokens(selector, token);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Gets the element matching the given selector, and if found, creates and returns a custom TestElement of the provided
|
|
603
|
+
* type. This is useful to create custom higher-level abstractions similar to TestInput, TestSelect, etc. for
|
|
604
|
+
* custom elements or components.
|
|
605
|
+
* @param selector a CSS or directive selector
|
|
606
|
+
* @param customTestElementType the type of the TestElement subclass that will wrap the found element
|
|
607
|
+
*/
|
|
608
|
+
custom(selector, customTestElementType) {
|
|
609
|
+
return this.testElement.custom(selector, customTestElementType);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Gets the elements matching the given selector, and creates and returns custom TestElements of the provided
|
|
613
|
+
* type. This is useful to create custom higher-level abstractions similar to TestInput, TestSelect, etc. for
|
|
614
|
+
* custom elements or components.
|
|
615
|
+
* @param selector a CSS or directive selector
|
|
616
|
+
* @param customTestElementType the type of the TestElement subclass that will wrap the found elements
|
|
617
|
+
*/
|
|
618
|
+
customs(selector, customTestElementType) {
|
|
619
|
+
return this.testElement.customs(selector, customTestElementType);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Triggers a change detection using the wrapped fixture
|
|
623
|
+
*/
|
|
624
|
+
detectChanges(checkNoChanges) {
|
|
625
|
+
this.fixture.detectChanges(checkNoChanges);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Creates a fake partial ActivatedRoute for tests.
|
|
631
|
+
*
|
|
632
|
+
* If you pass params, then the created route's paramMap will contain the same values.
|
|
633
|
+
* The same goes for queryParams and queryParamMap.
|
|
634
|
+
*
|
|
635
|
+
* If you pass a parent route and a snapshot, and the passed snapshot doesn't have a parent, then the snapshot's
|
|
636
|
+
* parent will be set to the parent route snapshot. This allows the code under test to use
|
|
637
|
+
* `route.parent.snapshot` or `route.snapshot.parent`.
|
|
638
|
+
*
|
|
639
|
+
* If you pass a snapshot with a parent, but don't pass a parent or pass a parent without snapshot, then the route's
|
|
640
|
+
* parent snapshot will be set to the given snapshot's parent. This allows the code under test to use
|
|
641
|
+
* `route.parent.snapshot` or `route.snapshot.parent`.
|
|
642
|
+
*
|
|
643
|
+
* @returns a partially populated, fake ActivatedRoute, depending on what you passed in
|
|
644
|
+
* @deprecated favor stubRoute, which creates an easier to use and more logical stub
|
|
645
|
+
*/
|
|
646
|
+
function fakeRoute(options) {
|
|
647
|
+
const result = {
|
|
648
|
+
url: options.url,
|
|
649
|
+
params: options.params,
|
|
650
|
+
paramMap: options.params && options.params.pipe(map(params => convertToParamMap(params))),
|
|
651
|
+
queryParams: options.queryParams,
|
|
652
|
+
queryParamMap: options.queryParams && options.queryParams.pipe(map(params => convertToParamMap(params))),
|
|
653
|
+
fragment: options.fragment,
|
|
654
|
+
data: options.data,
|
|
655
|
+
outlet: options.outlet,
|
|
656
|
+
component: options.component,
|
|
657
|
+
snapshot: options.snapshot,
|
|
658
|
+
routeConfig: options.routeConfig,
|
|
659
|
+
root: options.root,
|
|
660
|
+
parent: options.parent,
|
|
661
|
+
firstChild: options.firstChild,
|
|
662
|
+
children: options.children,
|
|
663
|
+
pathFromRoot: options.pathFromRoot
|
|
664
|
+
};
|
|
665
|
+
for (let route = result; route; route = route.parent) {
|
|
666
|
+
if (route.parent && route.parent.snapshot && !route.snapshot) {
|
|
667
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
668
|
+
route.snapshot = fakeSnapshot({});
|
|
669
|
+
}
|
|
670
|
+
if (route.parent && route.parent.snapshot && !route.snapshot.parent) {
|
|
671
|
+
route.snapshot.parent = route.parent.snapshot;
|
|
672
|
+
}
|
|
673
|
+
if (route.snapshot && route.snapshot.parent && !route.parent) {
|
|
674
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
675
|
+
route.parent = fakeRoute({});
|
|
676
|
+
}
|
|
677
|
+
if (route.snapshot && route.snapshot.parent && route.parent && !route.parent.snapshot) {
|
|
678
|
+
route.parent.snapshot = route.snapshot.parent;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Creates a fake partial ActivatedRouteSnapshot for tests.
|
|
685
|
+
*
|
|
686
|
+
* If you pass params, then the created snapshot's paramMap will contain the same values.
|
|
687
|
+
* The same goes for queryParams and queryParamMap.
|
|
688
|
+
*
|
|
689
|
+
* @returns a partially populated, fake ActivatedRoute, depending on what you passed in
|
|
690
|
+
* @deprecated favor stubRoute, which creates an easier to use and more logical stub for both the route and its snapshot
|
|
691
|
+
*/
|
|
692
|
+
function fakeSnapshot(options) {
|
|
693
|
+
return {
|
|
694
|
+
url: options.url,
|
|
695
|
+
params: options.params,
|
|
696
|
+
paramMap: options.params && convertToParamMap(options.params),
|
|
697
|
+
queryParams: options.queryParams,
|
|
698
|
+
queryParamMap: options.queryParams && convertToParamMap(options.queryParams),
|
|
699
|
+
fragment: options.fragment,
|
|
700
|
+
data: options.data,
|
|
701
|
+
outlet: options.outlet,
|
|
702
|
+
component: options.component,
|
|
703
|
+
routeConfig: options.routeConfig,
|
|
704
|
+
root: options.root,
|
|
705
|
+
parent: options.parent,
|
|
706
|
+
firstChild: options.firstChild,
|
|
707
|
+
children: options.children,
|
|
708
|
+
pathFromRoot: options.pathFromRoot
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
class ActivatedRouteSnapshotStub extends ActivatedRouteSnapshot {
|
|
712
|
+
constructor() {
|
|
713
|
+
super();
|
|
714
|
+
this._parent = null;
|
|
715
|
+
this._firstChild = null;
|
|
716
|
+
this._children = [];
|
|
717
|
+
this._pathFromRoot = [];
|
|
718
|
+
this._routeConfig = null;
|
|
719
|
+
this._root = this;
|
|
720
|
+
}
|
|
721
|
+
get parent() {
|
|
722
|
+
return this._parent;
|
|
723
|
+
}
|
|
724
|
+
set parent(value) {
|
|
725
|
+
this._parent = value;
|
|
726
|
+
}
|
|
727
|
+
get root() {
|
|
728
|
+
return this._root;
|
|
729
|
+
}
|
|
730
|
+
set root(value) {
|
|
731
|
+
this._root = value;
|
|
732
|
+
}
|
|
733
|
+
get firstChild() {
|
|
734
|
+
return this._firstChild;
|
|
735
|
+
}
|
|
736
|
+
set firstChild(value) {
|
|
737
|
+
this._firstChild = value;
|
|
738
|
+
}
|
|
739
|
+
get children() {
|
|
740
|
+
return this._children;
|
|
741
|
+
}
|
|
742
|
+
set children(value) {
|
|
743
|
+
this._children = value;
|
|
744
|
+
}
|
|
745
|
+
get pathFromRoot() {
|
|
746
|
+
return this._pathFromRoot;
|
|
747
|
+
}
|
|
748
|
+
set pathFromRoot(value) {
|
|
749
|
+
this._pathFromRoot = value;
|
|
750
|
+
}
|
|
751
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
752
|
+
// @ts-ignore
|
|
753
|
+
get routeConfig() {
|
|
754
|
+
return this._routeConfig;
|
|
755
|
+
}
|
|
756
|
+
set routeConfig(route) {
|
|
757
|
+
this._routeConfig = route;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* A stub for ActivatedRoute. It behaves almost the same way as the actual ActivatedRoute, exposing a snapshot
|
|
762
|
+
* and observables for the params, query params etc., which are kept in sync.
|
|
763
|
+
*
|
|
764
|
+
* In addition, this stub allows simulating a navigation by changing the params, the query params, the fragment, etc.
|
|
765
|
+
* When that happens, the snapshot is modified, then the relevant observables emit the new values.
|
|
766
|
+
*
|
|
767
|
+
* There are some things that don't really work the same way as the real ActivatedRoute though:
|
|
768
|
+
* - the handling of the firstChild and of the children is entirely under the tester's responsibility. Setting the parent
|
|
769
|
+
* of a route stub does not add this route to the children of its parent, for example.
|
|
770
|
+
* - when changing the params, query params, fragment, etc., their associated observable emits unconditionally, instead of
|
|
771
|
+
* first checking if the value is actually different from before. It's thus the responsibility of the tester to not
|
|
772
|
+
* change the values if they're the same as before.
|
|
773
|
+
*/
|
|
774
|
+
class ActivatedRouteStub extends ActivatedRoute {
|
|
775
|
+
/**
|
|
776
|
+
* Constructs a new instance, based on the given options.
|
|
777
|
+
* If an option is not provided (or if no option is provided at all), then the route has a default value for this option
|
|
778
|
+
* (empty parameters for example, null fragment, etc.)
|
|
779
|
+
* If no parent is passed, then this route has no parent and is thus set as the root. Otherwise, the root and the path
|
|
780
|
+
* from root are created based on the root and path from root of the given parent route.
|
|
781
|
+
*/
|
|
782
|
+
constructor(options) {
|
|
783
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
|
|
784
|
+
super();
|
|
785
|
+
const snapshot = new ActivatedRouteSnapshotStub();
|
|
786
|
+
this.snapshot = snapshot;
|
|
787
|
+
this._firstChild = (_a = options === null || options === void 0 ? void 0 : options.firstChild) !== null && _a !== void 0 ? _a : null;
|
|
788
|
+
this._children = (_b = options === null || options === void 0 ? void 0 : options.children) !== null && _b !== void 0 ? _b : [];
|
|
789
|
+
this._parent = (_c = options === null || options === void 0 ? void 0 : options.parent) !== null && _c !== void 0 ? _c : null;
|
|
790
|
+
this._root = (_e = (_d = this.parent) === null || _d === void 0 ? void 0 : _d.root) !== null && _e !== void 0 ? _e : this;
|
|
791
|
+
this._pathFromRoot = this.parent ? [...this.parent.pathFromRoot, this] : [this];
|
|
792
|
+
snapshot.params = (_f = options === null || options === void 0 ? void 0 : options.params) !== null && _f !== void 0 ? _f : {};
|
|
793
|
+
snapshot.queryParams = (_g = options === null || options === void 0 ? void 0 : options.queryParams) !== null && _g !== void 0 ? _g : {};
|
|
794
|
+
snapshot.data = (_h = options === null || options === void 0 ? void 0 : options.data) !== null && _h !== void 0 ? _h : {};
|
|
795
|
+
snapshot.fragment = (_j = options === null || options === void 0 ? void 0 : options.fragment) !== null && _j !== void 0 ? _j : null;
|
|
796
|
+
snapshot.url = (_k = options === null || options === void 0 ? void 0 : options.url) !== null && _k !== void 0 ? _k : [];
|
|
797
|
+
snapshot.routeConfig = (_l = options === null || options === void 0 ? void 0 : options.routeConfig) !== null && _l !== void 0 ? _l : null;
|
|
798
|
+
snapshot.firstChild = (_o = (_m = this.firstChild) === null || _m === void 0 ? void 0 : _m.snapshot) !== null && _o !== void 0 ? _o : null;
|
|
799
|
+
snapshot.children = (_q = (_p = this.children) === null || _p === void 0 ? void 0 : _p.map(route => route.snapshot)) !== null && _q !== void 0 ? _q : [];
|
|
800
|
+
snapshot.parent = (_s = (_r = this.parent) === null || _r === void 0 ? void 0 : _r.snapshot) !== null && _s !== void 0 ? _s : null;
|
|
801
|
+
snapshot.root = this.root.snapshot;
|
|
802
|
+
snapshot.pathFromRoot = this.pathFromRoot.map(route => route.snapshot);
|
|
803
|
+
this.paramsSubject = new BehaviorSubject(this.snapshot.params);
|
|
804
|
+
this.queryParamsSubject = new BehaviorSubject(this.snapshot.queryParams);
|
|
805
|
+
this.dataSubject = new BehaviorSubject(this.snapshot.data);
|
|
806
|
+
this.fragmentSubject = new BehaviorSubject(this.snapshot.fragment);
|
|
807
|
+
this.urlSubject = new BehaviorSubject(this.snapshot.url);
|
|
808
|
+
this.params = this.paramsSubject.asObservable();
|
|
809
|
+
this.queryParams = this.queryParamsSubject.asObservable();
|
|
810
|
+
this.data = this.dataSubject.asObservable();
|
|
811
|
+
this.fragment = this.fragmentSubject.asObservable();
|
|
812
|
+
this.url = this.urlSubject.asObservable();
|
|
813
|
+
}
|
|
814
|
+
get root() {
|
|
815
|
+
return this._root;
|
|
816
|
+
}
|
|
817
|
+
get parent() {
|
|
818
|
+
return this._parent;
|
|
819
|
+
}
|
|
820
|
+
get pathFromRoot() {
|
|
821
|
+
return this._pathFromRoot;
|
|
822
|
+
}
|
|
823
|
+
get firstChild() {
|
|
824
|
+
return this._firstChild;
|
|
825
|
+
}
|
|
826
|
+
get children() {
|
|
827
|
+
return this._children;
|
|
828
|
+
}
|
|
829
|
+
get routeConfig() {
|
|
830
|
+
return this.snapshot.routeConfig;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Triggers a navigation with the given new parameters. All the other parts (query params etc.) stay as the are.
|
|
834
|
+
* This is a shortcut to `triggerNavigation` that can be used to only change the parameters.
|
|
835
|
+
*/
|
|
836
|
+
setParams(params) {
|
|
837
|
+
this.triggerNavigation({ params });
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Triggers a navigation with the given new parameter. The other parameters, as well as all the other parts (query params etc.)
|
|
841
|
+
* stay as the are.
|
|
842
|
+
* This is a shortcut to `triggerNavigation` that can be used to only change one parameter.
|
|
843
|
+
*/
|
|
844
|
+
setParam(name, value) {
|
|
845
|
+
this.setParams(Object.assign(Object.assign({}, this.snapshot.params), { [name]: value }));
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Triggers a navigation with the given new query parameters. All the other parts (params etc.) stay as the are.
|
|
849
|
+
* This is a shortcut to `triggerNavigation` that can be used to only change the query parameters.
|
|
850
|
+
*/
|
|
851
|
+
setQueryParams(queryParams) {
|
|
852
|
+
this.triggerNavigation({ queryParams });
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Triggers a navigation with the given new parameter. The other query parameters, as well as all the other parts (params etc.)
|
|
856
|
+
* stay as the are.
|
|
857
|
+
* This is a shortcut to `triggerNavigation` that can be used to only change one query parameter.
|
|
858
|
+
*/
|
|
859
|
+
setQueryParam(name, value) {
|
|
860
|
+
this.setQueryParams(Object.assign(Object.assign({}, this.snapshot.queryParams), { [name]: value }));
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Triggers a navigation with the given new data. The other parameters, as well as all the other parts (params etc.)
|
|
864
|
+
* stay as the are.
|
|
865
|
+
* This is a shortcut to `triggerNavigation` that can be used to only change the data.
|
|
866
|
+
*/
|
|
867
|
+
setData(data) {
|
|
868
|
+
this.triggerNavigation({ data });
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Triggers a navigation with the given new data item. The other data, as well as all the other parts (params etc.)
|
|
872
|
+
* stay as the are.
|
|
873
|
+
* This is a shortcut to `triggerNavigation` that can be used to only change one data item.
|
|
874
|
+
*/
|
|
875
|
+
setDataItem(name, value) {
|
|
876
|
+
this.setData(Object.assign(Object.assign({}, this.snapshot.data), { [name]: value }));
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Triggers a navigation with the given new fragment. The other parts (params etc.) stay as the are.
|
|
880
|
+
* This is a shortcut to `triggerNavigation` that can be used to only change the fragment.
|
|
881
|
+
*/
|
|
882
|
+
setFragment(fragment) {
|
|
883
|
+
this.triggerNavigation({ fragment });
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Triggers a navigation with the given new url. The other parts (params etc.) stay as the are.
|
|
887
|
+
* This is a shortcut to `triggerNavigation` that can be used to only change the url.
|
|
888
|
+
*/
|
|
889
|
+
setUrl(url) {
|
|
890
|
+
this.triggerNavigation({ url });
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Triggers a navigation based on the given options. If an option is undefined or null, it's ignored. Except for fragment, which is only
|
|
894
|
+
* ignored if it's undefined, because null is a valid value for a fragment.
|
|
895
|
+
*
|
|
896
|
+
* The non-ignored values are used to change the snapshot of the route. Once the snapshot has been modified,
|
|
897
|
+
* the observables corresponding to the updated parts emit the new value.
|
|
898
|
+
*
|
|
899
|
+
* So, setting params and query params will make the params and queryParams observables emit, but not the fragment, data and
|
|
900
|
+
* url observables for example. This is consistent to how the router behaves.
|
|
901
|
+
*/
|
|
902
|
+
triggerNavigation(options) {
|
|
903
|
+
// set the snapshot first
|
|
904
|
+
if (options.params) {
|
|
905
|
+
this.snapshot.params = options.params;
|
|
906
|
+
}
|
|
907
|
+
if (options.queryParams) {
|
|
908
|
+
this.snapshot.queryParams = options.queryParams;
|
|
909
|
+
}
|
|
910
|
+
if (options.fragment !== undefined) {
|
|
911
|
+
this.snapshot.fragment = options.fragment;
|
|
912
|
+
}
|
|
913
|
+
if (options.data) {
|
|
914
|
+
this.snapshot.data = options.data;
|
|
915
|
+
}
|
|
916
|
+
if (options.url) {
|
|
917
|
+
this.snapshot.url = options.url;
|
|
918
|
+
}
|
|
919
|
+
// then emit everything that has changed
|
|
920
|
+
if (options.params) {
|
|
921
|
+
this.paramsSubject.next(this.snapshot.params);
|
|
922
|
+
}
|
|
923
|
+
if (options.queryParams) {
|
|
924
|
+
this.queryParamsSubject.next(this.snapshot.queryParams);
|
|
925
|
+
}
|
|
926
|
+
if (options.fragment !== undefined) {
|
|
927
|
+
this.fragmentSubject.next(this.snapshot.fragment);
|
|
928
|
+
}
|
|
929
|
+
if (options.data) {
|
|
930
|
+
this.dataSubject.next(this.snapshot.data);
|
|
931
|
+
}
|
|
932
|
+
if (options.url) {
|
|
933
|
+
this.urlSubject.next(this.snapshot.url);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
toString() {
|
|
937
|
+
return 'ActivatedRouteStub';
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Creates a new ActivatedRouteStub, by calling its constructor.
|
|
942
|
+
*/
|
|
943
|
+
function stubRoute(options) {
|
|
944
|
+
return new ActivatedRouteStub(options);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const speculoosMatchers = {
|
|
948
|
+
/**
|
|
949
|
+
* Checks that the receiver is a TestElement wrapping a DOM element and as the given CSS class
|
|
950
|
+
*/
|
|
951
|
+
toHaveClass: () => {
|
|
952
|
+
const assert = (isNegative, el, expected) => {
|
|
953
|
+
if (!el) {
|
|
954
|
+
return { pass: false, message: `Expected to check class '${expected}' on element, but element was falsy` };
|
|
955
|
+
}
|
|
956
|
+
if (!(el instanceof TestElement)) {
|
|
957
|
+
return { pass: false, message: `Expected to check class '${expected}' on element, but element was not a TestElement` };
|
|
958
|
+
}
|
|
959
|
+
const actual = el.classes;
|
|
960
|
+
const pass = actual.indexOf(expected) !== -1;
|
|
961
|
+
const message = `Expected element to ${isNegative ? 'not ' : ''}have class '${expected}', ` +
|
|
962
|
+
`but had ${actual.length ? "'" + actual.join(', ') + "'" : 'none'}`;
|
|
963
|
+
return { pass: isNegative ? !pass : pass, message };
|
|
964
|
+
};
|
|
965
|
+
return {
|
|
966
|
+
compare: (el, expected) => {
|
|
967
|
+
return assert(false, el, expected);
|
|
968
|
+
},
|
|
969
|
+
negativeCompare: (el, expected) => {
|
|
970
|
+
return assert(true, el, expected);
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
},
|
|
974
|
+
/**
|
|
975
|
+
* Checks that the receiver is a TestInput or a TestTextArea and has the given value
|
|
976
|
+
*/
|
|
977
|
+
toHaveValue: () => {
|
|
978
|
+
const assert = (isNegative, el, expected) => {
|
|
979
|
+
if (!el) {
|
|
980
|
+
return { pass: false, message: `Expected to check value '${expected}' on element, but element was falsy` };
|
|
981
|
+
}
|
|
982
|
+
if (!(el instanceof TestInput) && !(el instanceof TestTextArea)) {
|
|
983
|
+
return {
|
|
984
|
+
pass: false,
|
|
985
|
+
message: `Expected to check value '${expected}' on element, but element was neither a TestInput nor a TestTextArea`
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
const actual = el.value;
|
|
989
|
+
const pass = actual === expected;
|
|
990
|
+
const message = `Expected element to ${isNegative ? 'not ' : ''}have value '${expected}', but had value '${actual}'`;
|
|
991
|
+
return { pass: isNegative ? !pass : pass, message };
|
|
992
|
+
};
|
|
993
|
+
return {
|
|
994
|
+
compare: (el, expected) => {
|
|
995
|
+
return assert(false, el, expected);
|
|
996
|
+
},
|
|
997
|
+
negativeCompare: (el, expected) => {
|
|
998
|
+
return assert(true, el, expected);
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
},
|
|
1002
|
+
/**
|
|
1003
|
+
* Checks that the receiver is a TestElement wrapping a DOM element and has the exact given textContent
|
|
1004
|
+
*/
|
|
1005
|
+
toHaveText: () => {
|
|
1006
|
+
const assert = (isNegative, el, expected) => {
|
|
1007
|
+
if (!el) {
|
|
1008
|
+
return { pass: false, message: `Expected to check text '${expected}' on element, but element was falsy` };
|
|
1009
|
+
}
|
|
1010
|
+
if (!(el instanceof TestElement)) {
|
|
1011
|
+
return { pass: false, message: `Expected to check text '${expected}' on element, but element was not a TestElement` };
|
|
1012
|
+
}
|
|
1013
|
+
const actual = el.textContent;
|
|
1014
|
+
const pass = actual === expected;
|
|
1015
|
+
const message = `Expected element to ${isNegative ? 'not ' : ''}have text '${expected}', but had '${actual}'`;
|
|
1016
|
+
return { pass: isNegative ? !pass : pass, message };
|
|
1017
|
+
};
|
|
1018
|
+
return {
|
|
1019
|
+
compare: (el, expected) => {
|
|
1020
|
+
return assert(false, el, expected);
|
|
1021
|
+
},
|
|
1022
|
+
negativeCompare: (el, expected) => {
|
|
1023
|
+
return assert(true, el, expected);
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
},
|
|
1027
|
+
/**
|
|
1028
|
+
* Checks that the receiver is a TestElement wrapping a DOM element and has the given textContent, after both have been trimmed.
|
|
1029
|
+
* So, An element such as
|
|
1030
|
+
* ```
|
|
1031
|
+
* <h1>
|
|
1032
|
+
* Some title
|
|
1033
|
+
* </h1>
|
|
1034
|
+
* ```
|
|
1035
|
+
* will pass the test
|
|
1036
|
+
* ```
|
|
1037
|
+
* expect(tester.title).toHaveTrimmedText('Some title')
|
|
1038
|
+
* ```
|
|
1039
|
+
*/
|
|
1040
|
+
toHaveTrimmedText: () => {
|
|
1041
|
+
const assert = (isNegative, el, expected) => {
|
|
1042
|
+
var _a;
|
|
1043
|
+
const trimmedExpected = expected.trim();
|
|
1044
|
+
if (!el) {
|
|
1045
|
+
return { pass: false, message: `Expected to check trimmed text '${trimmedExpected}' on element, but element was falsy` };
|
|
1046
|
+
}
|
|
1047
|
+
if (!(el instanceof TestElement)) {
|
|
1048
|
+
return {
|
|
1049
|
+
pass: false,
|
|
1050
|
+
message: `Expected to check trimmed text '${trimmedExpected}' on element, but element was not a TestElement`
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
const actual = (_a = el.textContent) === null || _a === void 0 ? void 0 : _a.trim();
|
|
1054
|
+
const pass = actual === trimmedExpected;
|
|
1055
|
+
const message = `Expected element to ${isNegative ? 'not ' : ''}have trimmed text '${trimmedExpected}', but had '${actual}'`;
|
|
1056
|
+
return { pass: isNegative ? !pass : pass, message };
|
|
1057
|
+
};
|
|
1058
|
+
return {
|
|
1059
|
+
compare: (el, expected) => {
|
|
1060
|
+
return assert(false, el, expected);
|
|
1061
|
+
},
|
|
1062
|
+
negativeCompare: (el, expected) => {
|
|
1063
|
+
return assert(true, el, expected);
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
},
|
|
1067
|
+
/**
|
|
1068
|
+
* Checks that the receiver is a TestElement wrapping a DOM element and contains the given textContent
|
|
1069
|
+
*/
|
|
1070
|
+
toContainText: () => {
|
|
1071
|
+
const assert = (isNegative, el, expected) => {
|
|
1072
|
+
if (!el) {
|
|
1073
|
+
return { pass: false, message: `Expected to check text '${expected}' on element, but element was falsy` };
|
|
1074
|
+
}
|
|
1075
|
+
if (!(el instanceof TestElement)) {
|
|
1076
|
+
return { pass: false, message: `Expected to check text '${expected}' on element, but element was not a TestElement` };
|
|
1077
|
+
}
|
|
1078
|
+
const actual = el.textContent;
|
|
1079
|
+
if (!actual) {
|
|
1080
|
+
return {
|
|
1081
|
+
pass: isNegative,
|
|
1082
|
+
message: `Expected element to ${isNegative ? 'not ' : ''}contain text '${expected}', but had no text`
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
const pass = actual.indexOf(expected) !== -1;
|
|
1086
|
+
const message = `Expected element to ${isNegative ? 'not ' : ''}contain text '${expected}', but had text '${actual}'`;
|
|
1087
|
+
return { pass: isNegative ? !pass : pass, message };
|
|
1088
|
+
};
|
|
1089
|
+
return {
|
|
1090
|
+
compare: (el, expected) => {
|
|
1091
|
+
return assert(false, el, expected);
|
|
1092
|
+
},
|
|
1093
|
+
negativeCompare: (el, expected) => {
|
|
1094
|
+
return assert(true, el, expected);
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
},
|
|
1098
|
+
/**
|
|
1099
|
+
* Checks that the receiver is a TestElement wrapping a DOM element and contains the given textContent
|
|
1100
|
+
*/
|
|
1101
|
+
toBeChecked: () => {
|
|
1102
|
+
const assert = (isNegative, el) => {
|
|
1103
|
+
if (!el) {
|
|
1104
|
+
return { pass: false, message: `Expected to check if element was checked, but element was falsy` };
|
|
1105
|
+
}
|
|
1106
|
+
if (!(el instanceof TestInput)) {
|
|
1107
|
+
return { pass: false, message: `Expected to check if element was checked, but element was not a TestInput` };
|
|
1108
|
+
}
|
|
1109
|
+
const pass = el.checked;
|
|
1110
|
+
const message = `Expected element to be ${isNegative ? 'not ' : ''}checked, but was${!isNegative ? ' not' : ''}`;
|
|
1111
|
+
return { pass: isNegative ? !pass : pass, message };
|
|
1112
|
+
};
|
|
1113
|
+
return {
|
|
1114
|
+
compare: (el) => {
|
|
1115
|
+
return assert(false, el);
|
|
1116
|
+
},
|
|
1117
|
+
negativeCompare: (el) => {
|
|
1118
|
+
return assert(true, el);
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
},
|
|
1122
|
+
/**
|
|
1123
|
+
* Checks that the receiver is a TestSelect wrapping a DOM element and has the given selected index
|
|
1124
|
+
*/
|
|
1125
|
+
toHaveSelectedIndex: () => {
|
|
1126
|
+
const assert = (isNegative, el, expected) => {
|
|
1127
|
+
if (!el) {
|
|
1128
|
+
return { pass: false, message: `Expected to check selected index ${expected} on element, but element was falsy` };
|
|
1129
|
+
}
|
|
1130
|
+
if (!(el instanceof TestSelect)) {
|
|
1131
|
+
return { pass: false, message: `Expected to check selected index ${expected} on element, but element was not a TestSelect` };
|
|
1132
|
+
}
|
|
1133
|
+
const actual = el.selectedIndex;
|
|
1134
|
+
const pass = actual === expected;
|
|
1135
|
+
const message = `Expected element to ${isNegative ? 'not ' : ''}have selected index ${expected}, but had ${actual}`;
|
|
1136
|
+
return { pass: isNegative ? !pass : pass, message };
|
|
1137
|
+
};
|
|
1138
|
+
return {
|
|
1139
|
+
compare: (el, expected) => {
|
|
1140
|
+
return assert(false, el, expected);
|
|
1141
|
+
},
|
|
1142
|
+
negativeCompare: (el, expected) => {
|
|
1143
|
+
return assert(true, el, expected);
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
},
|
|
1147
|
+
/**
|
|
1148
|
+
* Checks that the receiver is a TestSelect wrapping a DOM element with the selected option's value equal to the given value
|
|
1149
|
+
*/
|
|
1150
|
+
toHaveSelectedValue: () => {
|
|
1151
|
+
const assert = (isNegative, el, expected) => {
|
|
1152
|
+
if (!el) {
|
|
1153
|
+
return { pass: false, message: `Expected to check selected value '${expected}' on element, but element was falsy` };
|
|
1154
|
+
}
|
|
1155
|
+
if (!(el instanceof TestSelect)) {
|
|
1156
|
+
return { pass: false, message: `Expected to check selected value '${expected}' on element, but element was not a TestSelect` };
|
|
1157
|
+
}
|
|
1158
|
+
const actual = el.selectedValue;
|
|
1159
|
+
const pass = actual === expected;
|
|
1160
|
+
const message = `Expected element to ${isNegative ? 'not ' : ''}have selected value '${expected}', but had '${actual}'`;
|
|
1161
|
+
return { pass: isNegative ? !pass : pass, message };
|
|
1162
|
+
};
|
|
1163
|
+
return {
|
|
1164
|
+
compare: (el, expected) => {
|
|
1165
|
+
return assert(false, el, expected);
|
|
1166
|
+
},
|
|
1167
|
+
negativeCompare: (el, expected) => {
|
|
1168
|
+
return assert(true, el, expected);
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
},
|
|
1172
|
+
/**
|
|
1173
|
+
* Checks that the receiver is a TestSelect wrapping a DOM element with the selected option's label equal to the given label
|
|
1174
|
+
*/
|
|
1175
|
+
toHaveSelectedLabel: () => {
|
|
1176
|
+
const assert = (isNegative, el, expected) => {
|
|
1177
|
+
if (!el) {
|
|
1178
|
+
return { pass: false, message: `Expected to check selected label '${expected}' on element, but element was falsy` };
|
|
1179
|
+
}
|
|
1180
|
+
if (!(el instanceof TestSelect)) {
|
|
1181
|
+
return { pass: false, message: `Expected to check selected label '${expected}' on element, but element was not a TestSelect` };
|
|
1182
|
+
}
|
|
1183
|
+
const actual = el.selectedLabel;
|
|
1184
|
+
const pass = actual === expected;
|
|
1185
|
+
const message = `Expected element to ${isNegative ? 'not ' : ''}have selected label '${expected}', but had '${actual}'`;
|
|
1186
|
+
return { pass: isNegative ? !pass : pass, message };
|
|
1187
|
+
};
|
|
1188
|
+
return {
|
|
1189
|
+
compare: (el, expected) => {
|
|
1190
|
+
return assert(false, el, expected);
|
|
1191
|
+
},
|
|
1192
|
+
negativeCompare: (el, expected) => {
|
|
1193
|
+
return assert(true, el, expected);
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
},
|
|
1197
|
+
/**
|
|
1198
|
+
* Checks that the receiver is a TestHtmlElement which is visible
|
|
1199
|
+
*/
|
|
1200
|
+
toBeVisible: () => {
|
|
1201
|
+
const assert = (isNegative, el) => {
|
|
1202
|
+
const expectedState = `${isNegative ? 'in' : ''}visible`;
|
|
1203
|
+
const inverseState = `${isNegative ? '' : 'in'}visible`;
|
|
1204
|
+
if (!el) {
|
|
1205
|
+
return { pass: false, message: `Expected to check if element was ${expectedState}, but element was falsy` };
|
|
1206
|
+
}
|
|
1207
|
+
if (!(el instanceof TestHtmlElement)) {
|
|
1208
|
+
return { pass: false, message: `Expected to check if element was ${expectedState}, but element was not a TestHtmlElement` };
|
|
1209
|
+
}
|
|
1210
|
+
const pass = el.visible;
|
|
1211
|
+
const message = `Expected element to be ${expectedState}, but was ${inverseState}`;
|
|
1212
|
+
return { pass: isNegative ? !pass : pass, message };
|
|
1213
|
+
};
|
|
1214
|
+
return {
|
|
1215
|
+
compare: (el) => {
|
|
1216
|
+
return assert(false, el);
|
|
1217
|
+
},
|
|
1218
|
+
negativeCompare: (el) => {
|
|
1219
|
+
return assert(true, el);
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1226
|
+
function collectMethodNames(proto) {
|
|
1227
|
+
if (!proto || proto === Object.prototype) {
|
|
1228
|
+
return [];
|
|
1229
|
+
}
|
|
1230
|
+
const methodNames = [];
|
|
1231
|
+
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
1232
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
|
|
1233
|
+
if (descriptor && typeof descriptor.value === 'function' && key !== 'constructor') {
|
|
1234
|
+
methodNames.push(key);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return [...methodNames, ...collectMethodNames(Object.getPrototypeOf(proto))];
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Creates a spy object for a class where all the methods of the class (and of its superclasses) are spies.
|
|
1241
|
+
* I.e., for a class `UserService` with methods `get()`, `create()`, `update()` and `delete()`, calling
|
|
1242
|
+
* `createMock(UserService)` is equivalent to calling
|
|
1243
|
+
* `jasmine.createSpyObj<UserService>('UserService', ['get', 'create', 'update', 'delete'])`.
|
|
1244
|
+
* @param type the type to mock (usually a service class)
|
|
1245
|
+
*/
|
|
1246
|
+
function createMock(type) {
|
|
1247
|
+
return jasmine.createSpyObj(type.name, collectMethodNames(type.prototype));
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/* eslint-disable */
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* Generated bundle index. Do not edit.
|
|
1254
|
+
*/
|
|
1255
|
+
|
|
1256
|
+
export { ActivatedRouteStub, ComponentTester, TestButton, TestElement, TestHtmlElement, TestInput, TestSelect, TestTextArea, createMock, fakeRoute, fakeSnapshot, speculoosMatchers, stubRoute };
|
|
1257
|
+
//# sourceMappingURL=ngx-speculoos.mjs.map
|