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.
Files changed (36) hide show
  1. package/README.md +403 -42
  2. package/esm2020/jasmine-matchers.mjs +2 -0
  3. package/esm2020/lib/component-tester.mjs +147 -0
  4. package/esm2020/lib/matchers.mjs +283 -0
  5. package/esm2020/lib/mock.mjs +25 -0
  6. package/esm2020/lib/route.mjs +319 -0
  7. package/{esm2015/lib/test-button.js → esm2020/lib/test-button.mjs} +0 -0
  8. package/esm2020/lib/test-element-querier.mjs +140 -0
  9. package/esm2020/lib/test-element.mjs +142 -0
  10. package/{esm2015/lib/test-html-element.js → esm2020/lib/test-html-element.mjs} +0 -0
  11. package/{esm2015/lib/test-input.js → esm2020/lib/test-input.mjs} +0 -0
  12. package/{esm2015/lib/test-select.js → esm2020/lib/test-select.mjs} +0 -0
  13. package/{esm2015/lib/test-textarea.js → esm2020/lib/test-textarea.mjs} +0 -0
  14. package/{esm2015/ngx-speculoos.js → esm2020/ngx-speculoos.mjs} +0 -0
  15. package/{esm2015/public_api.js → esm2020/public_api.mjs} +2 -1
  16. package/fesm2015/ngx-speculoos.mjs +1257 -0
  17. package/fesm2015/ngx-speculoos.mjs.map +1 -0
  18. package/{fesm2015/ngx-speculoos.js → fesm2020/ngx-speculoos.mjs} +436 -22
  19. package/fesm2020/ngx-speculoos.mjs.map +1 -0
  20. package/jasmine-matchers.d.ts +5 -0
  21. package/lib/component-tester.d.ts +87 -49
  22. package/lib/mock.d.ts +10 -0
  23. package/lib/route.d.ts +149 -0
  24. package/lib/test-element-querier.d.ts +15 -15
  25. package/lib/test-element.d.ts +87 -49
  26. package/package.json +24 -11
  27. package/public_api.d.ts +1 -0
  28. package/bundles/ngx-speculoos.umd.js +0 -1259
  29. package/bundles/ngx-speculoos.umd.js.map +0 -1
  30. package/esm2015/jasmine-matchers.js +0 -2
  31. package/esm2015/lib/component-tester.js +0 -96
  32. package/esm2015/lib/matchers.js +0 -244
  33. package/esm2015/lib/route.js +0 -81
  34. package/esm2015/lib/test-element-querier.js +0 -129
  35. package/esm2015/lib/test-element.js +0 -91
  36. package/fesm2015/ngx-speculoos.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { ComponentFixture, TestBed } from '@angular/core/testing';
2
2
  import { By } from '@angular/platform-browser';
3
- import { convertToParamMap } from '@angular/router';
4
- import { map } from 'rxjs/operators';
3
+ import { convertToParamMap, ActivatedRouteSnapshot, ActivatedRoute } from '@angular/router';
4
+ import { map, BehaviorSubject } from 'rxjs';
5
5
 
6
6
  /**
7
7
  * A wrapped DOM element, providing additional methods and attributes helping with writing tests
@@ -60,7 +60,7 @@ class TestElement {
60
60
  }
61
61
  /**
62
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 selector
63
+ * @param selector a CSS or directive selector
64
64
  * @returns the wrapped input, or null if no element was matched
65
65
  */
66
66
  input(selector) {
@@ -68,7 +68,7 @@ class TestElement {
68
68
  }
69
69
  /**
70
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 selector
71
+ * @param selector a CSS or directive selector
72
72
  * @returns the wrapped select, or null if no element was matched
73
73
  */
74
74
  select(selector) {
@@ -76,7 +76,7 @@ class TestElement {
76
76
  }
77
77
  /**
78
78
  * Gets the first textarea matched by the given selector
79
- * @param selector a CSS selector
79
+ * @param selector a CSS or directive selector
80
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
81
  * @throws {Error} if the matched element isn't actually a textarea
82
82
  */
@@ -85,12 +85,63 @@ class TestElement {
85
85
  }
86
86
  /**
87
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 selector
88
+ * @param selector a CSS or directive selector
89
89
  * @returns the wrapped button, or null if no element was matched
90
90
  */
91
91
  button(selector) {
92
92
  return this.querier.button(selector);
93
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
+ return this.querier.element(selector)?.debugElement?.componentInstance ?? null;
100
+ }
101
+ /**
102
+ * Gets the directives matching the given component directive selector and returns their component instance
103
+ * @param selector the selector of a component directive
104
+ */
105
+ components(selector) {
106
+ return this.querier.elements(selector).map(e => e.debugElement.componentInstance);
107
+ }
108
+ /**
109
+ * Gets the first element matching the given selector, then gets the given token from its injector, or null if there is no such token
110
+ * @param selector a CSS or directive selector
111
+ * @param token the token to get from the matched element injector
112
+ */
113
+ token(selector, token) {
114
+ return this.querier.element(selector)?.debugElement?.injector?.get(token, null) ?? null;
115
+ }
116
+ /**
117
+ * Gets the elements matching the given selector, then gets their given token from their injector, or null if there is no such token
118
+ * @param selector a CSS or directive selector
119
+ * @param token the token to get from the matched element injector
120
+ */
121
+ tokens(selector, token) {
122
+ return this.querier.elements(selector).map(e => e.debugElement.injector.get(token, null) ?? null);
123
+ }
124
+ /**
125
+ * Gets the element matching the given selector, and if found, creates and returns a custom TestElement of the provided
126
+ * type. This is useful to create custom higher-level abstractions similar to TestInput, TestSelect, etc. for
127
+ * custom elements or components.
128
+ * @param selector a CSS or directive selector
129
+ * @param customTestElementType the type of the TestElement subclass that will wrap the found element
130
+ */
131
+ custom(selector, customTestElementType) {
132
+ const element = this.querier.element(selector);
133
+ return element && new customTestElementType(this.tester, element.debugElement);
134
+ }
135
+ /**
136
+ * Gets the elements matching the given selector, and creates and returns custom TestElements of the provided
137
+ * type. This is useful to create custom higher-level abstractions similar to TestInput, TestSelect, etc. for
138
+ * custom elements or components.
139
+ * @param selector a CSS or directive selector
140
+ * @param customTestElementType the type of the TestElement subclass that will wrap the found elements
141
+ */
142
+ customs(selector, customTestElementType) {
143
+ return this.querier.elements(selector).map(element => new customTestElementType(this.tester, element.debugElement));
144
+ }
94
145
  }
95
146
 
96
147
  /**
@@ -296,6 +347,7 @@ class TestInput extends TestHtmlElement {
296
347
  }
297
348
  }
298
349
 
350
+ /* eslint-disable @typescript-eslint/no-explicit-any */
299
351
  /**
300
352
  * @internal
301
353
  */
@@ -326,11 +378,11 @@ class TestElementQuerier {
326
378
  }
327
379
  }
328
380
  /**
329
- * Gets the first element matching the given CSS selector and wraps it into a TestElement. The actual type
381
+ * Gets the first element matching the given selector and wraps it into a TestElement. The actual type
330
382
  * of the returned value is the TestElement subclass matching the type of the found element. So, if the
331
383
  * matched element is an input for example, the method will return a TestInput. You can thus use
332
384
  * `tester.element('#some-input') as TestInput`.
333
- * @param selector a CSS selector
385
+ * @param selector a CSS or directive selector
334
386
  * @returns the wrapped element, or null if no element matches the selector.
335
387
  */
336
388
  element(selector) {
@@ -338,11 +390,11 @@ class TestElementQuerier {
338
390
  return childElement && TestElementQuerier.wrap(childElement, this.tester);
339
391
  }
340
392
  /**
341
- * Gets all the elements matching the given CSS selector and wraps them into a TestElement. The actual type
393
+ * Gets all the elements matching the given selector and wraps them into a TestElement. The actual type
342
394
  * of the returned elements is the TestElement subclass matching the type of the found element. So, if the
343
395
  * matched elements are inputs for example, the method will return an array of TestInput. You can thus use
344
396
  * `tester.elements('input') as Array<TestInput>`.
345
- * @param selector a CSS selector
397
+ * @param selector a CSS or directive selector
346
398
  * @returns the array of matched elements, empty if no element was matched
347
399
  */
348
400
  elements(selector) {
@@ -351,7 +403,7 @@ class TestElementQuerier {
351
403
  }
352
404
  /**
353
405
  * Gets the first input matched by the given selector. Throws an Error if the matched element isn't actually an input.
354
- * @param selector a CSS selector
406
+ * @param selector a CSS or directive selector
355
407
  * @returns the wrapped input, or null if no element was matched
356
408
  */
357
409
  input(selector) {
@@ -366,7 +418,7 @@ class TestElementQuerier {
366
418
  }
367
419
  /**
368
420
  * Gets the first select matched by the given selector. Throws an Error if the matched element isn't actually a select.
369
- * @param selector a CSS selector
421
+ * @param selector a CSS or directive selector
370
422
  * @returns the wrapped select, or null if no element was matched
371
423
  */
372
424
  select(selector) {
@@ -381,7 +433,7 @@ class TestElementQuerier {
381
433
  }
382
434
  /**
383
435
  * Gets the first textarea matched by the given selector
384
- * @param selector a CSS selector
436
+ * @param selector a CSS or directive selector
385
437
  * @returns the wrapped textarea, or null if no element was matched. Throws an Error if the matched element isn't actually a textarea.
386
438
  * @throws {Error} if the matched element isn't actually a textarea
387
439
  */
@@ -397,7 +449,7 @@ class TestElementQuerier {
397
449
  }
398
450
  /**
399
451
  * Gets the first button matched by the given selector. Throws an Error if the matched element isn't actually a button.
400
- * @param selector a CSS selector
452
+ * @param selector a CSS or directive selector
401
453
  * @returns the wrapped button, or null if no element was matched
402
454
  */
403
455
  button(selector) {
@@ -411,10 +463,20 @@ class TestElementQuerier {
411
463
  return new TestButton(this.tester, childElement);
412
464
  }
413
465
  query(selector) {
414
- return this.root.query(By.css(selector));
466
+ if (typeof selector === 'string') {
467
+ return this.root.query(By.css(selector));
468
+ }
469
+ else {
470
+ return this.root.query(By.directive(selector));
471
+ }
415
472
  }
416
473
  queryAll(selector) {
417
- return this.root.queryAll(By.css(selector));
474
+ if (typeof selector === 'string') {
475
+ return this.root.queryAll(By.css(selector));
476
+ }
477
+ else {
478
+ return this.root.queryAll(By.directive(selector));
479
+ }
418
480
  }
419
481
  }
420
482
 
@@ -473,7 +535,7 @@ class ComponentTester {
473
535
  }
474
536
  /**
475
537
  * Gets the first input matched by the given selector. Throws an Error if the matched element isn't actually an input.
476
- * @param selector a CSS selector
538
+ * @param selector a CSS or directive selector
477
539
  * @returns the wrapped input, or null if no element was matched
478
540
  */
479
541
  input(selector) {
@@ -481,7 +543,7 @@ class ComponentTester {
481
543
  }
482
544
  /**
483
545
  * Gets the first select matched by the given selector. Throws an Error if the matched element isn't actually a select.
484
- * @param selector a CSS selector
546
+ * @param selector a CSS or directive selector
485
547
  * @returns the wrapped select, or null if no element was matched
486
548
  */
487
549
  select(selector) {
@@ -489,7 +551,7 @@ class ComponentTester {
489
551
  }
490
552
  /**
491
553
  * Gets the first textarea matched by the given selector
492
- * @param selector a CSS selector
554
+ * @param selector a CSS or directive selector
493
555
  * @returns the wrapped textarea, or null if no element was matched. Throws an Error if the matched element isn't actually a textarea.
494
556
  * @throws {Error} if the matched element isn't actually a textarea
495
557
  */
@@ -498,12 +560,62 @@ class ComponentTester {
498
560
  }
499
561
  /**
500
562
  * Gets the first button matched by the given selector. Throws an Error if the matched element isn't actually a button.
501
- * @param selector a CSS selector
563
+ * @param selector a CSS or directive selector
502
564
  * @returns the wrapped button, or null if no element was matched
503
565
  */
504
566
  button(selector) {
505
567
  return this.testElement.button(selector);
506
568
  }
569
+ /**
570
+ * Gets the first directive matching the given component directive selector and returns its component instance
571
+ * @param selector the selector of a component directive
572
+ */
573
+ component(selector) {
574
+ return this.testElement.component(selector);
575
+ }
576
+ /**
577
+ * Gets the directives matching the given component directive selector and returns their component instance
578
+ * @param selector the selector of a component directive
579
+ */
580
+ components(selector) {
581
+ return this.testElement.components(selector);
582
+ }
583
+ /**
584
+ * Gets the first element matching the given selector, then gets the given token from its injector, or null if there is no such token
585
+ * @param selector a CSS or directive selector
586
+ * @param token the token to get from the matched element injector
587
+ */
588
+ token(selector, token) {
589
+ return this.testElement.token(selector, token);
590
+ }
591
+ /**
592
+ * Gets the elements matching the given selector, then gets their given token from their injector, or null if there is no such token
593
+ * @param selector a CSS or directive selector
594
+ * @param token the token to get from the matched element injector
595
+ */
596
+ tokens(selector, token) {
597
+ return this.testElement.tokens(selector, token);
598
+ }
599
+ /**
600
+ * Gets the element matching the given selector, and if found, creates and returns a custom TestElement of the provided
601
+ * type. This is useful to create custom higher-level abstractions similar to TestInput, TestSelect, etc. for
602
+ * custom elements or components.
603
+ * @param selector a CSS or directive selector
604
+ * @param customTestElementType the type of the TestElement subclass that will wrap the found element
605
+ */
606
+ custom(selector, customTestElementType) {
607
+ return this.testElement.custom(selector, customTestElementType);
608
+ }
609
+ /**
610
+ * Gets the elements matching the given selector, and creates and returns custom TestElements of the provided
611
+ * type. This is useful to create custom higher-level abstractions similar to TestInput, TestSelect, etc. for
612
+ * custom elements or components.
613
+ * @param selector a CSS or directive selector
614
+ * @param customTestElementType the type of the TestElement subclass that will wrap the found elements
615
+ */
616
+ customs(selector, customTestElementType) {
617
+ return this.testElement.customs(selector, customTestElementType);
618
+ }
507
619
  /**
508
620
  * Triggers a change detection using the wrapped fixture
509
621
  */
@@ -527,6 +639,7 @@ class ComponentTester {
527
639
  * `route.parent.snapshot` or `route.snapshot.parent`.
528
640
  *
529
641
  * @returns a partially populated, fake ActivatedRoute, depending on what you passed in
642
+ * @deprecated favor stubRoute, which creates an easier to use and more logical stub
530
643
  */
531
644
  function fakeRoute(options) {
532
645
  const result = {
@@ -549,12 +662,14 @@ function fakeRoute(options) {
549
662
  };
550
663
  for (let route = result; route; route = route.parent) {
551
664
  if (route.parent && route.parent.snapshot && !route.snapshot) {
665
+ // eslint-disable-next-line deprecation/deprecation
552
666
  route.snapshot = fakeSnapshot({});
553
667
  }
554
668
  if (route.parent && route.parent.snapshot && !route.snapshot.parent) {
555
669
  route.snapshot.parent = route.parent.snapshot;
556
670
  }
557
671
  if (route.snapshot && route.snapshot.parent && !route.parent) {
672
+ // eslint-disable-next-line deprecation/deprecation
558
673
  route.parent = fakeRoute({});
559
674
  }
560
675
  if (route.snapshot && route.snapshot.parent && route.parent && !route.parent.snapshot) {
@@ -570,6 +685,7 @@ function fakeRoute(options) {
570
685
  * The same goes for queryParams and queryParamMap.
571
686
  *
572
687
  * @returns a partially populated, fake ActivatedRoute, depending on what you passed in
688
+ * @deprecated favor stubRoute, which creates an easier to use and more logical stub for both the route and its snapshot
573
689
  */
574
690
  function fakeSnapshot(options) {
575
691
  return {
@@ -590,6 +706,240 @@ function fakeSnapshot(options) {
590
706
  pathFromRoot: options.pathFromRoot
591
707
  };
592
708
  }
709
+ class ActivatedRouteSnapshotStub extends ActivatedRouteSnapshot {
710
+ constructor() {
711
+ super();
712
+ this._parent = null;
713
+ this._firstChild = null;
714
+ this._children = [];
715
+ this._pathFromRoot = [];
716
+ this._routeConfig = null;
717
+ this._root = this;
718
+ }
719
+ get parent() {
720
+ return this._parent;
721
+ }
722
+ set parent(value) {
723
+ this._parent = value;
724
+ }
725
+ get root() {
726
+ return this._root;
727
+ }
728
+ set root(value) {
729
+ this._root = value;
730
+ }
731
+ get firstChild() {
732
+ return this._firstChild;
733
+ }
734
+ set firstChild(value) {
735
+ this._firstChild = value;
736
+ }
737
+ get children() {
738
+ return this._children;
739
+ }
740
+ set children(value) {
741
+ this._children = value;
742
+ }
743
+ get pathFromRoot() {
744
+ return this._pathFromRoot;
745
+ }
746
+ set pathFromRoot(value) {
747
+ this._pathFromRoot = value;
748
+ }
749
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
750
+ // @ts-ignore
751
+ get routeConfig() {
752
+ return this._routeConfig;
753
+ }
754
+ set routeConfig(route) {
755
+ this._routeConfig = route;
756
+ }
757
+ }
758
+ /**
759
+ * A stub for ActivatedRoute. It behaves almost the same way as the actual ActivatedRoute, exposing a snapshot
760
+ * and observables for the params, query params etc., which are kept in sync.
761
+ *
762
+ * In addition, this stub allows simulating a navigation by changing the params, the query params, the fragment, etc.
763
+ * When that happens, the snapshot is modified, then the relevant observables emit the new values.
764
+ *
765
+ * There are some things that don't really work the same way as the real ActivatedRoute though:
766
+ * - the handling of the firstChild and of the children is entirely under the tester's responsibility. Setting the parent
767
+ * of a route stub does not add this route to the children of its parent, for example.
768
+ * - when changing the params, query params, fragment, etc., their associated observable emits unconditionally, instead of
769
+ * first checking if the value is actually different from before. It's thus the responsibility of the tester to not
770
+ * change the values if they're the same as before.
771
+ */
772
+ class ActivatedRouteStub extends ActivatedRoute {
773
+ /**
774
+ * Constructs a new instance, based on the given options.
775
+ * If an option is not provided (or if no option is provided at all), then the route has a default value for this option
776
+ * (empty parameters for example, null fragment, etc.)
777
+ * If no parent is passed, then this route has no parent and is thus set as the root. Otherwise, the root and the path
778
+ * from root are created based on the root and path from root of the given parent route.
779
+ */
780
+ constructor(options) {
781
+ super();
782
+ const snapshot = new ActivatedRouteSnapshotStub();
783
+ this.snapshot = snapshot;
784
+ this._firstChild = options?.firstChild ?? null;
785
+ this._children = options?.children ?? [];
786
+ this._parent = options?.parent ?? null;
787
+ this._root = this.parent?.root ?? this;
788
+ this._pathFromRoot = this.parent ? [...this.parent.pathFromRoot, this] : [this];
789
+ snapshot.params = options?.params ?? {};
790
+ snapshot.queryParams = options?.queryParams ?? {};
791
+ snapshot.data = options?.data ?? {};
792
+ snapshot.fragment = options?.fragment ?? null;
793
+ snapshot.url = options?.url ?? [];
794
+ snapshot.routeConfig = options?.routeConfig ?? null;
795
+ snapshot.firstChild = this.firstChild?.snapshot ?? null;
796
+ snapshot.children = this.children?.map(route => route.snapshot) ?? [];
797
+ snapshot.parent = this.parent?.snapshot ?? null;
798
+ snapshot.root = this.root.snapshot;
799
+ snapshot.pathFromRoot = this.pathFromRoot.map(route => route.snapshot);
800
+ this.paramsSubject = new BehaviorSubject(this.snapshot.params);
801
+ this.queryParamsSubject = new BehaviorSubject(this.snapshot.queryParams);
802
+ this.dataSubject = new BehaviorSubject(this.snapshot.data);
803
+ this.fragmentSubject = new BehaviorSubject(this.snapshot.fragment);
804
+ this.urlSubject = new BehaviorSubject(this.snapshot.url);
805
+ this.params = this.paramsSubject.asObservable();
806
+ this.queryParams = this.queryParamsSubject.asObservable();
807
+ this.data = this.dataSubject.asObservable();
808
+ this.fragment = this.fragmentSubject.asObservable();
809
+ this.url = this.urlSubject.asObservable();
810
+ }
811
+ get root() {
812
+ return this._root;
813
+ }
814
+ get parent() {
815
+ return this._parent;
816
+ }
817
+ get pathFromRoot() {
818
+ return this._pathFromRoot;
819
+ }
820
+ get firstChild() {
821
+ return this._firstChild;
822
+ }
823
+ get children() {
824
+ return this._children;
825
+ }
826
+ get routeConfig() {
827
+ return this.snapshot.routeConfig;
828
+ }
829
+ /**
830
+ * Triggers a navigation with the given new parameters. All the other parts (query params etc.) stay as the are.
831
+ * This is a shortcut to `triggerNavigation` that can be used to only change the parameters.
832
+ */
833
+ setParams(params) {
834
+ this.triggerNavigation({ params });
835
+ }
836
+ /**
837
+ * Triggers a navigation with the given new parameter. The other parameters, as well as all the other parts (query params etc.)
838
+ * stay as the are.
839
+ * This is a shortcut to `triggerNavigation` that can be used to only change one parameter.
840
+ */
841
+ setParam(name, value) {
842
+ this.setParams({ ...this.snapshot.params, [name]: value });
843
+ }
844
+ /**
845
+ * Triggers a navigation with the given new query parameters. All the other parts (params etc.) stay as the are.
846
+ * This is a shortcut to `triggerNavigation` that can be used to only change the query parameters.
847
+ */
848
+ setQueryParams(queryParams) {
849
+ this.triggerNavigation({ queryParams });
850
+ }
851
+ /**
852
+ * Triggers a navigation with the given new parameter. The other query parameters, as well as all the other parts (params etc.)
853
+ * stay as the are.
854
+ * This is a shortcut to `triggerNavigation` that can be used to only change one query parameter.
855
+ */
856
+ setQueryParam(name, value) {
857
+ this.setQueryParams({ ...this.snapshot.queryParams, [name]: value });
858
+ }
859
+ /**
860
+ * Triggers a navigation with the given new data. The other parameters, as well as all the other parts (params etc.)
861
+ * stay as the are.
862
+ * This is a shortcut to `triggerNavigation` that can be used to only change the data.
863
+ */
864
+ setData(data) {
865
+ this.triggerNavigation({ data });
866
+ }
867
+ /**
868
+ * Triggers a navigation with the given new data item. The other data, as well as all the other parts (params etc.)
869
+ * stay as the are.
870
+ * This is a shortcut to `triggerNavigation` that can be used to only change one data item.
871
+ */
872
+ setDataItem(name, value) {
873
+ this.setData({ ...this.snapshot.data, [name]: value });
874
+ }
875
+ /**
876
+ * Triggers a navigation with the given new fragment. The other parts (params etc.) stay as the are.
877
+ * This is a shortcut to `triggerNavigation` that can be used to only change the fragment.
878
+ */
879
+ setFragment(fragment) {
880
+ this.triggerNavigation({ fragment });
881
+ }
882
+ /**
883
+ * Triggers a navigation with the given new url. The other parts (params etc.) stay as the are.
884
+ * This is a shortcut to `triggerNavigation` that can be used to only change the url.
885
+ */
886
+ setUrl(url) {
887
+ this.triggerNavigation({ url });
888
+ }
889
+ /**
890
+ * Triggers a navigation based on the given options. If an option is undefined or null, it's ignored. Except for fragment, which is only
891
+ * ignored if it's undefined, because null is a valid value for a fragment.
892
+ *
893
+ * The non-ignored values are used to change the snapshot of the route. Once the snapshot has been modified,
894
+ * the observables corresponding to the updated parts emit the new value.
895
+ *
896
+ * So, setting params and query params will make the params and queryParams observables emit, but not the fragment, data and
897
+ * url observables for example. This is consistent to how the router behaves.
898
+ */
899
+ triggerNavigation(options) {
900
+ // set the snapshot first
901
+ if (options.params) {
902
+ this.snapshot.params = options.params;
903
+ }
904
+ if (options.queryParams) {
905
+ this.snapshot.queryParams = options.queryParams;
906
+ }
907
+ if (options.fragment !== undefined) {
908
+ this.snapshot.fragment = options.fragment;
909
+ }
910
+ if (options.data) {
911
+ this.snapshot.data = options.data;
912
+ }
913
+ if (options.url) {
914
+ this.snapshot.url = options.url;
915
+ }
916
+ // then emit everything that has changed
917
+ if (options.params) {
918
+ this.paramsSubject.next(this.snapshot.params);
919
+ }
920
+ if (options.queryParams) {
921
+ this.queryParamsSubject.next(this.snapshot.queryParams);
922
+ }
923
+ if (options.fragment !== undefined) {
924
+ this.fragmentSubject.next(this.snapshot.fragment);
925
+ }
926
+ if (options.data) {
927
+ this.dataSubject.next(this.snapshot.data);
928
+ }
929
+ if (options.url) {
930
+ this.urlSubject.next(this.snapshot.url);
931
+ }
932
+ }
933
+ toString() {
934
+ return 'ActivatedRouteStub';
935
+ }
936
+ }
937
+ /**
938
+ * Creates a new ActivatedRouteStub, by calling its constructor.
939
+ */
940
+ function stubRoute(options) {
941
+ return new ActivatedRouteStub(options);
942
+ }
593
943
 
594
944
  const speculoosMatchers = {
595
945
  /**
@@ -671,6 +1021,45 @@ const speculoosMatchers = {
671
1021
  }
672
1022
  };
673
1023
  },
1024
+ /**
1025
+ * Checks that the receiver is a TestElement wrapping a DOM element and has the given textContent, after both have been trimmed.
1026
+ * So, An element such as
1027
+ * ```
1028
+ * <h1>
1029
+ * Some title
1030
+ * </h1>
1031
+ * ```
1032
+ * will pass the test
1033
+ * ```
1034
+ * expect(tester.title).toHaveTrimmedText('Some title')
1035
+ * ```
1036
+ */
1037
+ toHaveTrimmedText: () => {
1038
+ const assert = (isNegative, el, expected) => {
1039
+ const trimmedExpected = expected.trim();
1040
+ if (!el) {
1041
+ return { pass: false, message: `Expected to check trimmed text '${trimmedExpected}' on element, but element was falsy` };
1042
+ }
1043
+ if (!(el instanceof TestElement)) {
1044
+ return {
1045
+ pass: false,
1046
+ message: `Expected to check trimmed text '${trimmedExpected}' on element, but element was not a TestElement`
1047
+ };
1048
+ }
1049
+ const actual = el.textContent?.trim();
1050
+ const pass = actual === trimmedExpected;
1051
+ const message = `Expected element to ${isNegative ? 'not ' : ''}have trimmed text '${trimmedExpected}', but had '${actual}'`;
1052
+ return { pass: isNegative ? !pass : pass, message };
1053
+ };
1054
+ return {
1055
+ compare: (el, expected) => {
1056
+ return assert(false, el, expected);
1057
+ },
1058
+ negativeCompare: (el, expected) => {
1059
+ return assert(true, el, expected);
1060
+ }
1061
+ };
1062
+ },
674
1063
  /**
675
1064
  * Checks that the receiver is a TestElement wrapping a DOM element and contains the given textContent
676
1065
  */
@@ -829,11 +1218,36 @@ const speculoosMatchers = {
829
1218
  }
830
1219
  };
831
1220
 
1221
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1222
+ function collectMethodNames(proto) {
1223
+ if (!proto || proto === Object.prototype) {
1224
+ return [];
1225
+ }
1226
+ const methodNames = [];
1227
+ for (const key of Object.getOwnPropertyNames(proto)) {
1228
+ const descriptor = Object.getOwnPropertyDescriptor(proto, key);
1229
+ if (descriptor && typeof descriptor.value === 'function' && key !== 'constructor') {
1230
+ methodNames.push(key);
1231
+ }
1232
+ }
1233
+ return [...methodNames, ...collectMethodNames(Object.getPrototypeOf(proto))];
1234
+ }
1235
+ /**
1236
+ * Creates a spy object for a class where all the methods of the class (and of its superclasses) are spies.
1237
+ * I.e., for a class `UserService` with methods `get()`, `create()`, `update()` and `delete()`, calling
1238
+ * `createMock(UserService)` is equivalent to calling
1239
+ * `jasmine.createSpyObj<UserService>('UserService', ['get', 'create', 'update', 'delete'])`.
1240
+ * @param type the type to mock (usually a service class)
1241
+ */
1242
+ function createMock(type) {
1243
+ return jasmine.createSpyObj(type.name, collectMethodNames(type.prototype));
1244
+ }
1245
+
832
1246
  /* eslint-disable */
833
1247
 
834
1248
  /**
835
1249
  * Generated bundle index. Do not edit.
836
1250
  */
837
1251
 
838
- export { ComponentTester, TestButton, TestElement, TestHtmlElement, TestInput, TestSelect, TestTextArea, fakeRoute, fakeSnapshot, speculoosMatchers };
839
- //# sourceMappingURL=ngx-speculoos.js.map
1252
+ export { ActivatedRouteStub, ComponentTester, TestButton, TestElement, TestHtmlElement, TestInput, TestSelect, TestTextArea, createMock, fakeRoute, fakeSnapshot, speculoosMatchers, stubRoute };
1253
+ //# sourceMappingURL=ngx-speculoos.mjs.map