-
Creating Web Component
1.1 How to create a web component ?
1.2 How to import component in file ?
1.3 How to set a web component attribute ?
1.4 How to create a custom event and dispatch it ? 1.5 How to listen and handle an event ? -
Testing Web component
2.1 How to load my web component ?
2.2 How to access to the DOM of my web component ?
2.3 How to create a story with controls
2.4 How to do snapshot test with Jest ?
2.5 How to create a custom event and dispatch it ?
2.6 How to create a stub of a function ?
2.7 How to set attributes to a web component ? -
Utilities & Cheat sheet
3.1 DOM selectors cheat sheet
3.2 Jest cheat sheet
3.3 Functions
3.4 Playwrigth cheat sheet -
Exercices Helpers
4.1 Create a Footer - Exercice n°2
4.2 Testing Betting-list - Exercice n°4
import { CustomHTMLElement } from '../../utils'
/* Create template */
const template = document.createElement('template');
function createTemplate(name: string): string {
return `
<div>Hello ${name} !</div>
`
}
export class MyComponent extends CustomHTMLElement {
constructor() {
super()
/* Attach template to shadow DOM */
this.attachShadow({ mode: 'open' })
.appendChild(template.content.cloneNode(true))
}
connectedCallback() {
/* Create template with variable on the first rendering */
const name = 'Maxime'
const newTemplate = createTemplate(name)
this.renderComponent(newTemplate)
}
}
/* Define tag with 'arl' prefix */
customElements.define('arl-my-component', MyComponent);
You can call now your component with the tag <arl-my-component></arl-my-component>
In order to import a component "Header" in App.ts
for example use
import './src/components/header/header'
<!-- /!\ attributes must be in lowercase -->
<arl-footer is-user-connected="false"></arl-footer>
class MyComponent extends CustomHTMLElement {
...
static get observedAttributes() {
return ['is-user-connected']
}
attributeChangedCallback(attributeName: string, _oldValue: string, newValue: string) {
// catch any attributes changes
}
}
const objectToPass = { key: 'value' }
const event = new CustomEvent('eventName', { detail: objectToPass }) /* 'detail' is mandatory in customEvent */
window.dispatchEvent(event)
window.addEventListener('eventName', callback())
class MyComponent extends CustomHTMLElement {}
const myComponent: HTMLElement = render(MyComponent)
// myComponent is a HTMLElement you can access to the DOM with classical js **methods**
myComponent.shadowRoot.querySelector(...)
import { StorybookControls } from '../../models'
import './footer'
export default {
title: 'Components/Footer',
argTypes: {
isUserConnected: {
control: 'boolean',
defaultValue: false
},
}
}
type ArgTypes = {
isUserConnected: StorybookControls,
}
export const Default = (argTypes: ArgTypes) => `<arl-footer is-user-connected="${argTypes.isUserConnected}"></arl-footer>`
expect(...).toMatchSnapshot();
const spyDispatchEvent = jest.spyOn(window, 'dispatchEvent');
const expectedParameters = (spyDispatchEvent.mock.calls[nthCall][nthParameter] as CustomEvent).detail /* nthCall: represent the nth call that we want to watch, nthParameter represent the nth parameter sent that we want to watch */
expect(expectedBetChoice).toEqual({ ... })
const stub = { key: 'value' }
jest.mock('../../services', () => ({
fetchGameOdds: jest.fn().mockResolvedValue(stub)
}))
const objectToPass = { key: 'value' }
const myComponent: HTMLElement = render(MyComponent)
myComponent.setAttribute('my-prop-key', JSON.stringify(objectToPass))
/* Return an array of requested DOM elements */
myComponent.querySelectorAll(' CSS selectors ')
/* Return the requested DOM element */
myComponent.querySelector(' CSS selectors ')
/* Return true/false if class exists or not in requested element */
myComponent.classList.contains(' class name')
/* Simulate a click */
myComponent.click()
/* Check if value exists or is true */
expect(value).toBeTruthy()
/* Check if value does NOT exist or is false */
expect(value).toBeFalsy()
/* Check equality with object/array */
expect(value).toEqual(expectedValue)
/* Check equality with literal type */
expect(value).toBe(expectedValue)
/* Check if value contains a expected value */
expect(value).toContain(expectedValue)
/* Check if method have been called (true / false) */
expect(method).toHaveBeenCalled(expectedValue)
/* Create or check validity of snapshot */
expect(...).toMatchSnapshot()
/* Instanciate a component and launch 'connectedCallback()' method if exists */
const myComponent: HTMLElement = render(MyComponent)
/* Stringify any type of variable in order to pass it as a prop */
myComponent.setAttribute('key-attribute', stringify(object))
/* Parse any stringified attribute value in order to manipulate it */
attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
const parsedValue = parse(newValue)
}
const browser: Browser = await chromium.launch({
headless: true,
slowMo: 0,
devtools: false
})
const context: BrowserContext = await browser.newContext()
const page: Page = await context.newPage()
await myComponent.isBlockDisplayed('tag or CSS selectors')
await input.type('100')
await input.press('Backspace')
await myComponent.getSummaryContent()
Update template depending on the recieved attributes.
import { CustomHTMLElement } from '../../utils'
function createTemplate(text: string): string {
return `
<footer>
<h3>Besoin d'aide ?</h3>
<p>${text}</p>
</footer>
`
}
export class Footer extends CustomHTMLElement {
constructor() {
...
this.render()
}
static get observedAttributes() {
return ['is-user-connected']
}
attributeChangedCallback(attributeName: string, _oldValue: string, newValue: string) {
if (attributeName !== 'is-user-connected') {
return
}
this.render(newValue)
}
render(isUserConnected?: string) {
const footerText = (isUserConnected === 'true')
? 'Contact | Plan | Deconnexion'
: 'Contact | Plan | Connexion'
const newTemplate = createTemplate(footerText)
this.renderComponent(newTemplate)
}
}
Display a list of game odds using component Betting Item
.
function createTemplate(gameOddsList: GameOdds[]) {
return `
<div class="betting-list">
<h3>Liste des paris - Football</h3>
${gameOddsList
.map((gameOdds: GameOdds) => `<arl-betting-item game-odds='${JSON.stringify(gameOdds)}'></arl-betting-item>`)
.join('')
}
</div>
`
}