spectator icon indicating copy to clipboard operation
spectator copied to clipboard

activatedRoute.queryParams subscription not being called on route navigation

Open AnthonyLenglet opened this issue 3 years ago • 4 comments

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

AnthonyLenglet avatar Oct 07 '21 11:10 AnthonyLenglet

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 :/

AnthonyLenglet avatar Oct 07 '21 12:10 AnthonyLenglet

can you make a stackblitz reproduction of your issue?

rodcisal avatar Oct 13 '21 09:10 rodcisal

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

AnthonyLenglet avatar Oct 14 '21 10:10 AnthonyLenglet

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

AnthonyLenglet avatar Oct 14 '21 13:10 AnthonyLenglet