4

I recently converted a lit web component over to Typescript and can't seem to figure out why my tests are failing now.. everything was working fine before the conversion.

These are my dependencies for testing:

"@open-wc/testing": "^3.1.2",
"@web/dev-server-esbuild": "^0.2.16",
"@web/test-runner": "^0.13.27"

So I run "test": "web-test-runner", with the following config in web-test-runner.config.mjs (Also got the same error using tsc to transpile the code):

import { esbuildPlugin } from '@web/dev-server-esbuild';

const filteredLogs = ['Running in dev mode', 'Lit is in dev mode'];

export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({
  files: 'lib/**/*.test.ts',
  nodeResolve: {
    exportConditions: ['browser', 'development'],
  },
  plugins: [esbuildPlugin({ ts: true })],
  filterBrowserLogs(log) {
    for (const arg of log.args) {
      if (typeof arg === 'string' && filteredLogs.some(l => arg.includes(l))) {
        return false;
      }
    }
    return true;
  }
});

and get this error:

components: > web-test-runner
components: Chrome: |██████████████████████████████| 0/1 test files | 0 passed, 0 failed
components: Running tests...
lib/MyElement/index.test.ts:
components:  ❌ MyElement > has a default title "World" and count 0
components:       AssertionError: expected undefined to equal 'World'
components:         at o.<anonymous> (lib/MyElement/index.test.ts:11:23)
components: Chrome: |██████████████████████████████| 1/1 test files | 0 passed, 1 failed
components: Finished running tests in 2.7s with 1 failed tests.
components: npm ERR! code ELIFECYCLE
components: npm ERR! errno 1
components: npm ERR! components@1.0.0 test: `web-test-runner`
components: npm ERR! Exit status 1
components: npm ERR! 
components: npm ERR! Failed at the components@1.0.0 test script.
components: npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
components: npm ERR! A complete log of this run can be found in:
components: npm ERR!     /Users/shawn/.npm/_logs/2022-03-21T22_11_54_084Z-debug.log
lerna ERR! npm run test exited 1 in 'components'

This is the component code:

import {LitElement, html, css} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';

@customElement('my-element')
 export class MyElement extends LitElement {
   static styles = css`
     :host {
       display: block;
       border: solid 1px gray;
       padding: 16px;
       max-width: 800px;
     }
   `;

   @property() name: string = 'World';

   @state() count: number = 0;

   _onClick() {
    this.count++;
    this.dispatchEvent(new CustomEvent('count-changed'));
  }

  sayHello(name: string) {
    return `Hello, ${name}`;
  }
 
  render() {
    return html`
      <h1>${this.sayHello(this.name)}!</h1>
      <button @click=${this._onClick} part="button">
        Click Count: ${this.count}
      </button>
      <slot></slot>
    `;
  }
 }

And finally, the test code:

import { html, fixture, expect } from '@open-wc/testing';

import { MyElement } from '.';

describe('MyElement', () => {
  it('has a default title "World" and count 0', async () => {
    const el = await fixture<MyElement>(html`
      <my-element></my-element>
    `);

    expect(el.name).to.equal('World');
    expect(el.count).to.equal(0);
  });
});

So I believe it's something related to transpiling the typescript, but I have not been successful in finding out what exactly it is. Anybody notice anything wrong that would cause these properties to be undefined now?

EDIT:

This is the original JS implementation to show the diff between this and the TS one.

import {LitElement, html, css} from 'lit';

 export class MyElement extends LitElement {
   static get styles() {
     return css`
       :host {
         display: block;
         border: solid 1px gray;
         padding: 16px;
         max-width: 800px;
       }
     `;
   }
 
   static get properties() {
     return {
       name: {type: String},
       count: {type: Number},
     };
   }
 
   constructor() {
     super();
     this.name = 'World';
     this.count = 0;
   }
 
   render() {
     return html`
       <h1>${this.sayHello(this.name)}!</h1>
       <button @click=${this._onClick} part="button">
         Click Count: ${this.count}
       </button>
       <slot></slot>
     `;
   }
 
   _onClick() {
     this.count++;
     this.dispatchEvent(new CustomEvent('count-changed'));
   }

   sayHello(name) {
     return `Hello, ${name}`;
   }
 }
 
 window.customElements.define('my-element', MyElement);
Shawn
  • 2,355
  • 14
  • 48
  • 98
  • would you please share if you were able to get around this issue? I am currently facing exactly the same. It seems the fixture is putting the tags in DOM but the Lit Element is not being instantiated. I wonder if the fixture has access to JavaScript code required to register the web component. @Shawn – yuva Jun 16 '22 at 13:06
  • Sorry this question might seem irrelevant. But how do you get the `describe` statements in your test? I can't figure out where the declaration of that is coming from. – Eddie Lam Mar 16 '23 at 07:08

2 Answers2

0

Can you try

@property({type: String}) name = 'World';

https://lit.dev/docs/api/decorators/#property

Michel Lamsoul
  • 232
  • 2
  • 4
  • thanks for the fast reponse! That didn't seem to work, still got the same error message. I even tried to set both values in the constructor in the class and they are still undefined in the test. It's weird, it seems to not be able to read off `this` anymore. – Shawn Mar 21 '22 at 22:46
  • Mmm... when using constructor, you called `super()` first right ? – Michel Lamsoul Mar 21 '22 at 23:06
  • I did indeed, I uploaded the original JS implementation so we can see the diff between the two as well. – Shawn Mar 22 '22 at 00:34
0

The issue is that you're not using the instance of MyElement in your test file but only its type and tag. Thus the compile process will throw out the import statement import { MyElement } from '.';. If you would compile your test file using tsc you would see that the import statement is missing in the .js-output.

Fix it by importing the whole file in your test:

import './index.js';

Note: In the test file of the lit-element-starter-ts repo (find it here) this scenario is not appearing since they are using the Element's instance in their first test case, thus the named import won't be removed by the tsc/esbuild:

  import {MyElement} from '../my-element.js';

  // ...

  test('is defined', () => {
    const el = document.createElement('my-element');
    assert.instanceOf(el, MyElement);
  });
noChance
  • 418
  • 2
  • 13