Search code examples
typescriptjestjshrefrouterstenciljs

Error: "Router must be defined in href" stenciljs router-v2 testing


I am trying to test a stencil component. I use "stencil-router-v2". I have a link in my component's render method with "href" function: <a {...href(`/news/${this.page}`)} class="btn btn--primary btn--medium btn--view">View</a> When I try to test it, I see the next error: "Router must be defined in href". Can you please explain to me how to test the "href" function correctly? What test code do I need to write in order for the test to pass?

My component code:

import { Component, h, Prop, Event, EventEmitter } from '@stencil/core';
import { href } from 'stencil-router-v2';
import state from '../store/store';

@Component({
  tag: 'my-news-page',
  styleUrl: 'my-news-page.scss'
})

export class MyNewsPage {  
  @Prop() page: string;
  @Prop() news;

  @Event() newsDeleted: EventEmitter;

  deleteNewsHandler = id => {
    this.newsDeleted.emit(id);
  }

  componentWillRender() {
    this.news = state.newsList.find(news => news.id === +this.page);
  }

  render() {
    return (
      <section>
        <h3>{this.news.title}</h3>
        <p>{this.news.body}</p>
        <button class="btn btn--primary btn--medium btn--delete" onClick={() => this.deleteNewsHandler(this.news.id)}>Delete</button>
        <a {...href(`/news/${this.page}`)} class="btn btn--primary btn--medium btn--view">View</a>
      </section>
    )
  }
}

My test code:

import { newSpecPage } from '@stencil/core/testing';
import { MyNewsPage } from '../my-news-page';
import state from '../../store/store';

state.newsList = [
  { id: 1, title: 'title1', body: 'content1' },
  { id: 2, title: 'title2', body: 'content2' },
  { id: 3, title: 'title3', body: 'content3' }
];

const ID = '1';

describe('my-news-page', () => {
  it('should display remaining items', () => {
    let page = new MyNewsPage();
    page.news = state.newsList;
    expect(page.news.length).toEqual(3);
  });

  it('renders', async () => {
    const page = await newSpecPage({
      components: [MyNewsPage],
      html: '<div></div>'
    });
    let cmp = page.doc.createElement('my-news-page');
    (cmp as any).page = ID;
    (cmp as any).news = state.newsList.find(news => news.id === +cmp.page);
    page.root.appendChild(cmp);
    await page.waitForChanges();
    expect(page.root).toMatchSnapshot();
  });
});

Terminal:

  my-news-page
    ✓ should display remaining items (7 ms)
    ✕ renders (37 ms)

  ● my-news-page › renders

    Router must be defined in href

      28 |         <p>{this.news.body}</p>
      29 |         <button class="btn btn--primary btn--medium btn--delete" onClick={() => this.deleteNewsHandler(this.news.id)}>Delete</button>
    > 30 |         {<a {...href(`/news/${this.page}`)} class="btn btn--primary btn--medium btn--view">View</a>}
         |                 ^
      31 |       </section>
      32 |     )
      33 |   }

      at Object.href (node_modules/stencil-router-v2/dist/index.js:106:15)
      at MyNewsPage.render (src/components/my-news-page/my-news-page.tsx:30:17)
      at callRender (node_modules/@stencil/core/internal/testing/index.js:421:46)
      at updateComponent (node_modules/@stencil/core/internal/testing/index.js:402:247)
      at node_modules/@stencil/core/internal/testing/index.js:397:22
      at then (node_modules/@stencil/core/internal/testing/index.js:459:47)
      at dispatchHooks (node_modules/@stencil/core/internal/testing/index.js:397:7)
      at a (node_modules/@stencil/core/internal/testing/index.js:387:18)
      at a (node_modules/@stencil/core/internal/testing/index.js:25:17)```

Solution

  • Thanks to Thomas and Simon, I found the solution.

    I should add const Router = createRouter(); to my code here:

    import { createRouter } from 'stencil-router-v2';
    import { MyNewsPage } from '../my-news-page';
    import state from '../../store/store';
    
    state.newsList = [
      { id: 1, title: 'title1', body: 'content1' },
      { id: 2, title: 'title2', body: 'content2' },
      { id: 3, title: 'title3', body: 'content3' }
    ];
    
    const ID = '1';
    
    describe('my-news-page', () => {
      it('should display remaining items', () => {
        let page = new MyNewsPage();
        page.news = state.newsList;
        expect(page.news.length).toEqual(3);
      });
    
      it('renders', async () => {
        const page = await newSpecPage({
          components: [MyNewsPage],
          html: '<div></div>'
        });
        const Router = createRouter();
        let cmp = page.doc.createElement('my-news-page');
        (cmp as any).page = ID;
        (cmp as any).news = state.newsList.find(news => news.id === +cmp.page);
        page.root.appendChild(cmp);
    
        await page.waitForChanges();
        expect(page.root).toMatchSnapshot();
      });
    });
    

    Now my test is working correctly.