Cross-framework web components library 📚 using Lit (Part II)
Fausto Braz
Posted on August 31, 2022
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',
};
}
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);
});
}
If everything is well configured, we run the test, and we should have one successful test:
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');
});
}
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 {}
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[];
}
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>`;
}
}
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];
...
}
And now, you should have a component that will render like this:
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.
Posted on August 31, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.