Cross-framework web components library 📚 using Lit (Part II)

fstbraz

Fausto Braz

Posted on August 31, 2022

Cross-framework web components library 📚 using Lit (Part II)

Testing

After we conclude our card component is time to test it. I have used the testing package from Open Web Components, and you can find the documentation here in conjunction with jasmine and karma.

We start describing what component will be tested and defining what we will mock as component properties:

import { Card } from './card';

describe('Card Component', () => {
    let el: Card;

    const card = {
        altText: 'Je suis a image',
        ctaText: 'Click me',
        image: 'http://example.com/image/',
        link: 'http://example.com/link/',
        text: 'amazing text',
        textDesc: 'other amazing text for test',
        textDescLink: 'http://example.com/author/',
        title: 'amazing title',
    };

}

Enter fullscreen mode Exit fullscreen mode

After that, before the test runs, we boot a fixture that will pass a Card component with our custom configuration, and we check if the element is defined, checking the instanceOf:

...

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

describe('Card Component', () => {
    ...

    beforeEach(async () => {
        el = await fixture<Card>(html` <card-image .card=${card}></card-image> `);
    });

    it('is defined', () => {
        assert.instanceOf(el, Card);
    });

}

Enter fullscreen mode Exit fullscreen mode

If everything is well configured, we run the test, and we should have one successful test:

Testing console

Let's have a bit more coverage on that component:

...

describe('Card Component', () => {
      ...

      it('renders the image', () => {
        const image = el.shadowRoot?.querySelector('img');
        expect(image).to.exist;
        expect(image?.alt).to.equal(card.altText);
        expect(image?.src).to.equal(card.image);
      });

      it('renders the title', () => {
        const title = el.shadowRoot?.querySelector('#card-link');
        expect(title).to.exist;
        expect(title?.getAttribute('href')).to.equal(card.link);
        expect(title?.textContent).to.equal(card.title);
      });

      it('renders the text', () => {
        const text = el.shadowRoot?.querySelector('p');
        expect(text).to.exist;
        expect(text?.textContent).to.equal(card.text);
      });

      it('renders the cta text', () => {
        const ctaText = el.shadowRoot?.querySelector('.cta a');
        expect(ctaText).to.exist;
        expect(ctaText?.textContent).to.equal(card.ctaText);
      });

      it('renders the description link', () => {
        const textDesc = el.shadowRoot?.querySelector('small a');
        expect(textDesc).to.exist;
        expect(textDesc?.getAttribute('href')).to.equal(card.textDescLink);
        expect(textDesc?.textContent).to.equal(card.textDesc);
      });

      it('dispatch the mousedown event', () => {
        el.mouseDown();
        expect(el.down).to.be.a('number');
      });

}

Enter fullscreen mode Exit fullscreen mode

This should leverage the coverage to more than 80%.

Cards component

We will be doing a second component where we can pass the configuration to have multiple cards.
This exposes the ability to reuse one internal component and do another with other purposes that can have a different configuration. Also, we will be using lit directives.

We start by doing a new component at the same level as card.ts:

...

import { LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';

@customElement('cards-images')
export class Cards extends LitElement {}

Enter fullscreen mode Exit fullscreen mode

After we define the external @property, that basically will receive an array of CardConfig objects:

...

import { customElement, property } from 'lit/decorators.js';

@customElement('cards-images')
export class Cards extends LitElement {

    @property({ type: Array }) cards!: CardConfig[];

}

Enter fullscreen mode Exit fullscreen mode

We can now render the cards container and use the repeat directive to generate various instances of the card based on the configuration we passed in the @property:

...

import { html, LitElement } from 'lit';
import { repeat } from 'lit/directives/repeat.js';

@customElement('cards-images')
export class Cards extends LitElement {

    ...

    render() {
    return html` <div class="cards">
      <ul>
        ${repeat(this.cards, (current) => html` <card-image .card=${current}></card-image> `)}
      </ul>
    </div>`;
  }

}

Enter fullscreen mode Exit fullscreen mode

To finalize, we only need to add the component styles and the shared ones:

...

import { css, html, LitElement } from 'lit';
import { sharedStyles } from './shared/style';

const componentStyle = css`
  .cards > ul {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  @supports (display: grid) {
    .cards > ul {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
      grid-column-gap: 1.5rem;
      grid-row-gap: 1.5rem;
    }
  }
`;


@customElement('cards-images')
export class Cards extends LitElement {

  static styles = [sharedStyles, componentStyle];

    ...

}

Enter fullscreen mode Exit fullscreen mode

And now, you should have a component that will render like this:

Web component cards

In the next part, we will cover distributing the lib via a node package using the npm registry and how to use the components in different frameworks/libs environments.

💖 💪 🙅 🚩
fstbraz
Fausto Braz

Posted on August 31, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related