spectator
spectator copied to clipboard
activatedRoute.queryParams subscription not being called on route navigation
Is this a regression?
No
Description
Context:
I have a tab bar, that filters the displayed elements in the list below
Implementation wise, a click on each tab changes a query parameter in the url, this then gets caught via a subscription in the component, that changes filters the list of displayed items
ngOnInit(): void {
this.handleQueryParams();
}
handleQueryParams() {
this.activatedRoute.queryParams.subscribe((params) => {
if (params.pipeline === 'todo') {
this.items = this.itemService.ItemsTodo$;
} else if (params.pipeline === 'under-review') {
this.items = this.itemService.ItemsUnderReview$;
} else if (params.pipeline === 'reviewed') {
this.items = this.itemService.ItemsReviewed$;
} else {
this.router.navigate([], { queryParams: { pipeline: 'todo' } });
}
});
}
<header>
<nav>
<ul>
<li mat-ripple routerLinkActive="active">
<a routerLink="." [queryParams]="{ pipeline: 'todo' }">
To do
<span class="count">
{{ (itemService.itemsTodo$ | async).length }}
</span>
</a>
</li>
<li mat-ripple routerLinkActive="active">
<a routerLink="." [queryParams]="{ pipeline: 'under-review' }">
Under Review
<span class="count">
{{
(itemService.itemsUnderReview$ | async).length
}}
</span>
</a>
</li>
<li mat-ripple routerLinkActive="active">
<a routerLink="." [queryParams]="{ pipeline: 'reviewed' }">
Reviewed
<span class="count">
{{ (itemService.itemsReviewed$ | async).length }}
</span>
</a>
</li>
</ul>
</nav>
</header>
<section>
<p *ngIf="(items | async).length === 0">No items to handle.</p>
<article *ngFor="let item of items | async">
<item-component [item]="item"></item-component>
</article>
</section>
The tests currently look like this:
const createComponent = createRoutingFactory({
component: ViewComponent,
providers: [
mockProvider(itemService, {
itemsTodo$: of([
item.noImplementation,
item.noImplementation,
]),
itemsUnderReview$: of([
item.answer,
item.answer,
item.answer,
]),
itemsReviewed$: of([item.validated]),
}),
],
shallow: true,
});
beforeEach(() => (spectator = createComponent()));
it('should have 2 items in the "todo" tab', () => {
spectator.setRouteQueryParam('pipeline', 'todo');
expect(spectator.queryAll('item-component').length).toBe(2);
});
it('should have 3 items in the "under review" tab', () => {
spectator.setRouteQueryParam('pipeline', 'under-review');
expect(spectator.queryAll('item-component').length).toBe(3);
});
it('should have 1 item in the "reviewed" tab', () => {
spectator.setRouteQueryParam('pipeline', 'reviewed');
expect(spectator.queryAll('item-component').length).toBe(1);
});
Problem:
as it stands, all of those tests are working completely fine, however, I feel like I would ideally want to do the following tests instead:
> click on the tab X
> get Y amount of items
that way my tests become completely decoupled from the implementation of the feature, and only cares about it working correctly
so I tried doing this:
it('should have 3 items in the "under review" tab', fakeAsync(() => {
spectator.click(spectator.queryAll('a')[1]);
tick();
expect(spectator.queryAll('item-component').length).toBe(3);
}));
however, this does not work, and just never redirects anywhere.
After playing around with it, I realised that disabling stubs but keeping the activatedRoute stub that spectator uses (and thus effectively removing the Router stub added when stubs are enabled), seems to get me closer to the solution to this issue:
const createComponent = createRoutingFactory({
component: ViewComponent,
+ stubsEnabled: false,
providers: [
mockProvider(itemService, {
itemsTodo$: of([
item.noImplementation,
item.noImplementation,
]),
itemsUnderReview$: of([
item.answer,
item.answer,
item.answer,
]),
itemsReviewed$: of([item.validated]),
}),
+ {
+ provide: ActivatedRoute,
+ useExisting: ActivatedRouteStub,
+ },
],
+ routes: [
+ {
+ path: '',
+ component: ViewComponent,
+ },
+ ],
shallow: true,
});
When this is used, the router seems to change the url properly:
it('should have 3 items in the "under review" tab', fakeAsync(() => {
spectator.click(spectator.queryAll('a')[1]);
tick();
const router = spectator.inject(Router);
console.log(router.url); // <- this gives "/?pipeline=under-review" as expected
expect(spectator.queryAll('oney-rule').length).toBe(3);
}));
However my subscription to activatedRoute.queryParams
still never actually gets triggered
At that point I am lost as to what I could do to get around this
Please provide a link to a minimal reproduction of the bug
https://github.com/AnthonyLenglet/spectator-testing
Please provide the exception or error you saw
no error shown, only a lack of response from the `queryParams` subscriber
Please provide the environment you discovered this bug in
`@ngneat/spectator`: "^8.1.0"
Anything else?
No response
Do you want to create a pull request?
No
an additional test for this component, not directly linked to the subscription issue but does seem to hint towards the Router mock possibly being the cause of the issue
it('should navigate to the "todo" tab by default', () => {
spectator = createComponent({
providers: [
mockProvider(Router, {
events: new Subject<Event>(),
serializeUrl(): string {
return '/';
},
}),
],
});
const router = TestBed.inject(Router);
expect(router.navigate).toHaveBeenCalledWith([], {
queryParams: { pipeline: 'todo' },
});
});
in this test case I need to actually pretty much just add the Router mock back in order to spy on Router functions
It kinda looks like the router mock is incomplete and thats what's causing the issues, I don't really know how the router class works though :/
can you make a stackblitz reproduction of your issue?
tried getting a stackblitz up, but I can't get it to work fully, I think part of it is caused by spectator being on version 8.0.4, and version 8.1.0 cannot be found apparently
https://stackblitz.com/edit/angular-testing-spectator-vuqbw1?file=app%2Fitems%2Fitems.component.spec.ts
Here's a repo for the issue instead, every test is configured, just need to install and run npm test
!
https://github.com/AnthonyLenglet/spectator-testing