Skip to content

arolla/crafting-frontend-exercices

 
 

Repository files navigation

📝 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>`

About

Crafting frontend training project

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • HTML 50.8%
  • TypeScript 35.9%
  • JavaScript 13.1%
  • SCSS 0.2%