Skip to content

Latest commit

 

History

History
264 lines (191 loc) · 5.5 KB

README.md

File metadata and controls

264 lines (191 loc) · 5.5 KB

📝 Crafting Front-End Formation - Cheat sheet


Summary

  1. Creating Web Component
    1.1 basic component
    1.2 components inputs by attributes
    1.3 components inputs/outputs by events\

  2. Testing
    2.1 first test
    2.2 snapshot
    2.3 test with test doubles
    2.4 playwright\

  3. Storybook
    3.2 controls\


📗 1. Creating Web component

1.1 basic component

In new .ts file :

  1. Create template either
const template = `<template><div>Hello !</div></template>`

/!\ this one cannot be changed after the first rendering

or

const template = document.createElement('template');
function createTemplate(name: string): string {
    return `<div>Hello ${name} !</div>`
}
  1. Create component by Extending CustomHTMLElement
export class MyComponent extends CustomHTMLElement {
  constructor() {
    super()

    /* Attach template to shadow DOM */
    this.attachShadow({ mode: 'open' }).appendChild(
      template.content.cloneNode(true),
    )
  }

  /* connectedCallback() is automatically called once on first rendering */
  connectedCallback() {
    /* Create template with variable */
    const name = 'Maxime'
    const newTemplate = createTemplate(name)
    this.renderComponent(newTemplate)
  }
}
  1. Define your new tag
/* Define tag with 'arl' prefix */
customElements.define('arl-my-component', MyComponent)

/!\ prefix is mandatory, prevent collision of tag names

  1. Where you want to use it In HTML template
 <arl-my-component></arl-my-component>
In TS file
import './src/components/MyComponent/MyComponent'

1.2 components inputs by attributes

@see example

in HTLM

<arl-footer is-user-logged-in="false"></arl-footer>

/!\ attributes must be in lowercase

in TS

class MyComponent extends CustomHTMLElement {
  //...

  static get observedAttributes() {
    return ['is-user-logged-in']
  }

  // called an attribute value of the tag changes
  attributeChangedCallback(
    attributeName: string,
    _oldValue: string,
    newValue: string,
  ) {
    //attributeName = 'is-user-logged-in' (if this is the attribute that just changed)
    //newValue = 'false' (in this example)
    //oldValue is the previous value
  }
}

1.3 components inputs/outputs by events

@see example

In HTML

<body>
  <custom-element-imperative></custom-element-imperative>
</body>

output : to send data out of a component

window.dispatchEvent(
  new CustomEvent('MY_EVENT', { detail: { payload: 'Toto' } }),
)

input : to get data from outside another component

window.addEventListener(
  'MY_EVENT',
  callback((event: CustomEvent) => console.log(event.detail.payload)),
)

📙 2. Testing

2.1 first test

in myComponent.spec.ts

//import your component
import { MyComponent } from './my-component'

it('title should be the right thing', () => {
  //given
  const component = new MyComponent()

  //when
  // call the connectedCallback to mimic the rendering of the component (=init)
  component.connectedCallback()

  //then

  //get dom element
  const title = component.shadowRoot?.querySelector('.title')?.textContent

  //assertion
  expect(title).toEqual('the right thing')
})

2.2 snapshot test with Jest

in myComponent.spec.ts

const component = new MyComponent()
component.connectedCallback()
expect(component.shadowRoot?.querySelector('#component-id')).toMatchSnapshot()
  • The first time you run the test, it creates a file with the name of your test file that is the snapshot template of the component in "snapshots" folder,

  • next runs, this automatically generated file will be used to compare the snapshot of the component with the one generated by the test

2.3 test with test doubles

SPY

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({ ... })

STUB

const stub = { key: 'value' }

jest.mock('../../services', () => ({
  fetchGameOdds: jest.fn().mockResolvedValue(stub),
}))

3 Storybook

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()

3.2 controls

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-logged-in="${argTypes.isUserConnected}"></arl-footer>`