Cheat SheetsJavaScript & TypeScriptQuality

Quality — Cheat Sheet

JavaScript & TypeScript · 5 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Quality
JavaScript & TypeScript5 topicsQuick revision reference
1

Testing Fundamentals — Vitest and Jest

Arrange, act, assert — the structure is the same as JUnit or pytest. What differs is how mocking works and how async assertions behave.

  • toBe compares identity, toEqual compares deeply — using toBe on objects is the classic false failure
  • An async test must await, or it passes without ever running the assertion
  • Use rejects.toThrow with await, and expect.assertions(n) to guard against silently skipped assertions
  • vi.mock replaces a module; MSW intercepts the network so your real fetch code is still exercised
  • Coverage shows what is untested — chase meaningful branches, not the percentage
describe/it, and toBe vs toEqual
import { describe, it, expect, beforeEach, vi } from 'vitest'

describe('calculateReadiness', () => {
  let attempts

  beforeEach(() => { attempts = [] })      // fresh state per test

  it('returns provisional below five attempts', () => {
    const result = calculateReadiness(attempts)      // act
    expect(result.provisional).toBe(true)            // assert
  })

  it.each([
    [0, 'not started'],
    [45, 'developing'],
    [85, 'ready'],
  ])('labels %i as %s', (score, label) => {
    expect(labelFor(score)).toBe(label)
  })
})

// Matchers that matter
expect(x).toBe(1)                    // Object.is — primitives, identity
expect(obj).toEqual({ a: 1 })        // deep equality, ignores undefined keys
expect(obj).toStrictEqual({ a: 1 })  // deep, and does not ignore them
expect(arr).toHaveLength(3)
expect(arr).toContainEqual({ id: 1 })
expect(fn).toThrow(ValidationError)
expect(obj).toMatchObject({ id: 1 })  // subset match
2

Testing React Components

Testing Library's whole philosophy is to interact with a component the way a user does. Query by role and text, not by class name or component internals.

  • Query by role and accessible name first; needing getByTestId often signals an accessibility gap
  • getBy throws, queryBy returns null for absence assertions, findBy waits for an async appearance
  • userEvent reproduces the full event sequence a real user generates, unlike fireEvent
  • Missing awaits — not slow machines — cause most flaky component tests
  • Test conditional rendering, gates, error and empty states; do not test internal state or class names
getByRole first, getByTestId last
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

render(<ProblemCard problem={problem} onOpen={onOpen} />)

// Priority order
screen.getByRole('button', { name: /solve/i })     // best
screen.getByLabelText('Email')                      // form fields
screen.getByPlaceholderText('Search problems')
screen.getByText(/two sum/i)
screen.getByTestId('problem-card')                  // last resort

// If getByRole cannot find your button, a screen reader cannot
// either. The failing test is telling you about a real defect.

// get / query / find
getByRole(...)     // throws if missing — asserts presence
queryByRole(...)   // null if missing — the only way to assert absence
await findByRole(...)  // waits for it to appear — async

expect(screen.queryByText('Error')).not.toBeInTheDocument()
3

End-to-End Testing with Playwright

E2E tests run the real app in a real browser. They catch what unit tests structurally cannot, and they cost more — so pick the few flows worth that cost.

  • E2E tests catch integration, routing, auth and CSP failures that unit tests structurally cannot
  • Playwright locators and assertions auto-retry — an explicit waitForTimeout hides real timing bugs
  • Log in once and reuse the saved storage state instead of signing in in every test
  • Tests must not depend on each other or on data another test created; stub third-party services
  • Cover only the flows whose breakage costs money or trust — keep the rest in fast component tests
Locators retry; sleeps do not
import { test, expect } from '@playwright/test'

test('a signed-in user can run a solution', async ({ page }) => {
  await page.goto('/dsa/problems/two-sum')

  await page.getByRole('button', { name: 'Run' }).click()
  await expect(page.getByText('3 of 3 tests passed')).toBeVisible()
})

// A locator is lazy and auto-retries: it waits for the element to
// exist, be visible, be stable and be enabled before acting.
// So this is never needed and hides real timing bugs:
await page.waitForTimeout(2000)      // avoid

// Assertions retry too, up to the timeout
await expect(page.getByRole('alert')).toHaveText(/saved/i)

// Locator priority mirrors Testing Library
page.getByRole('button', { name: 'Submit' })
page.getByLabel('Email')
page.getByTestId('editor')           // last resort
4

Linting, Formatting and CI Gates

ESLint catches bugs, Prettier ends formatting arguments, and CI is what makes either of them matter. A rule nobody enforces is a suggestion.

  • Prettier owns formatting and ESLint owns correctness — configure them so they never fight
  • no-floating-promises and react-hooks/exhaustive-deps catch real bugs, not style preferences
  • Enforce in three layers: format on save, lint-staged on commit, and a CI job that fails the build
  • Run eslint with --max-warnings=0, or warnings accumulate until the output is ignored
  • Introduce new rules as warnings with a ratchet rather than flipping on 900 errors at once
Type-aware rules earn their setup cost
// Prettier — formatting only, no correctness opinions
{ "semi": false, "singleQuote": true, "printWidth": 100 }

// ESLint — correctness and consistency (flat config)
export default [
  js.configs.recommended,
  ...tseslint.configs.recommendedTypeChecked,     // needs a tsconfig
  {
    rules: {
      '@typescript-eslint/no-floating-promises': 'error',
      '@typescript-eslint/no-explicit-any': 'warn',
      'react-hooks/exhaustive-deps': 'warn',
      'no-console': ['warn', { allow: ['warn', 'error'] }],
    },
  },
]

// no-floating-promises is the highest-value rule here:
saveProgress()      // forgot await — the error vanishes silently
// It catches more production bugs than every formatting rule combined.

// exhaustive-deps is the second: a stale closure in a useEffect
// is one of the hardest React bugs to find by reading.
5

Accessibility and Frontend Security

Semantic HTML gets you most of accessibility for free, and most XSS comes from one habit. Both are asked in interviews and both are visible in code review.

  • A native button, label or nav brings keyboard support, focus and screen-reader semantics for free
  • Every interactive control must be reachable and operable by keyboard, with a visible focus style
  • ARIA is a last resort — incorrect ARIA is worse than none, and automated tools catch only about a third of issues
  • React escapes interpolated text; innerHTML and dangerouslySetInnerHTML are the XSS entry points, so sanitise with DOMPurify
  • SameSite cookies plus a CSRF token cover CSRF, and a Content-Security-Policy header blocks whole classes of injection
Use the element that already behaves correctly
// A clickable div needs all of this to match a button
<div onClick={h} role="button" tabIndex={0}
     onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && h()}>

// A button just works
<button onClick={h}>Run</button>

// Elements that carry meaning for free
<nav> <main> <header> <footer> <aside>
<button> <a href> <label> <fieldset> <table>

// Labels — every input needs one
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" />
// An icon-only control needs an accessible name
<button aria-label="Close dialog"><X aria-hidden="true" /></button>

// Images: alt describes purpose; decorative images take alt=""
<img src={cover} alt="" />

// Headings are a document outline, not font sizes.
// One h1, no skipped levels — a screen-reader user navigates by them.
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/javascript