Search code examples
unit-testingjestjsjsxstenciljs

In Stencil with Jest, how to test object property on components using template?


I have a stencil component that looks something like this:

    export class MyBadge {
      @Prop() skin: string = 'primary';
      render(){
        return (
        <span class={`skin-${this.skin}`} />
      )

I want to write a unit test that checks that the rendered component applies the given skin property. I have a test that compares the rendered component to my expected html, using expect(page.root).toEqualHtml(´< span class=primary >´). This test works! However, when I try to modify the prop, using template, it fails to run the test.

    import { newSpecPage } from '@stencil/core/testing';
    import { MyBadge } from './my-badge'
    it('should have "secondary" skin', async () => {
     const page = newSpecPage({
            components: [MyBadge],
            template: () => (`
              <my-badge skin=secondary></my-badge>
              `), 
          });
          expect(page.root).toEqualHtml(`
          <span class="skin-secondary">
          ...
          `);
        });

This gives the error expect toEqualHtml() value is "null" and the test fails

I have also tried including the bellow html under the it, but that fails too.

html: `<my-badge></my-badge>`,

Removing the template option makes the test work, however it then does not see the updated prop.

I wanted to use the template option based on this answer: https://github.com/ionic-team/stencil/issues/1923

I've also tried a few different syntaxes, I'm not clear on how to wrap the template string.

I've been looking around for documentation on this but I can't seem to find what I'm looking for. Any help would be appreciated!

Edit: Cleaned up the code based on suggestions from @Simon Hänisch


Solution

  • Your template is returning a string, not JSX. You basically get a document with <badge skin=secondary></badge> as a string in it, not an actual badge element.

    BTW badge is not a valid custom element name, it needs to have a dash (-) in its name. There are also syntax errors in your example code, like your component should

    return (
      <span class={`skin-${this.skin}`} />
    )
    

    (back ticks for the class string were missing and the tag wasn't closed)

    Assuming that all this is just mistakes when writing this question and you actually have a line with newSpecPage in your code (that's also missing here), here's what you need to do:

    1. Change the extension of your spec test file to .tsx.
    2. Add import { h } from '@stencil/core';.
    3. Write some actual JSX as the return value of your template.
    import { h } from '@stencil/core';
    import { newSpecPage } from '@stencil/core/testing';
    import { MyBadge } from './my-badge';
    
    it('should have "secondary" skin', async () => {
      const page = newSpecPage({
        components: [MyBadge],
        template: () => <my-badge skin="secondary" />,
      });
    
      expect(page.root).toEqualHtml('<span class="skin-secondary"></span>');
    });
    

    You can't use both options template and html at the same time (or at least it doesn't make sense, I'm not sure which one will take precedence), because they are different ways of setting the content for the page. If you use template, you can directly use JSX syntax to bind any kind of value to props etc. If you use html, it has to be a string with the html content of your page, and in that case you can only bind string values to props.

    If you wanted to use html in your example instead, it would need to be

    const page = newSpecPage({
      components: [MyBadge],
      html: `<my-badge skin="secondary"></my-badge>`,
    });
    

    I don't think you can use the self-closing syntax in this case because it's not valid for custom elements, see this answer.