Skip to main content

Cypress Component Testing

What we are testing

Snappycart is a UI package. Cypress Component Testing gives us the fastest feedback loop for browser-rendered behaviour without needing to run full end-to-end flows.

This page covers component-level behaviour for Snappycart UI pieces and their mounted integration shape. It does not replace unit tests for reducers or hooks, and it does not replace end-to-end coverage in the demo app.

For the full test inventory and the recommended number of tests across all layers, read the Cart testing plan.

Scope of Cypress Component Testing

Cypress CT is the right place for:

  • rendered UI behaviour
  • component interaction in a real browser DOM
  • focus management
  • keyboard and pointer interaction
  • state transitions visible through mounted components
  • provider-backed component integration when a component depends on cart context

Cypress CT is not the main place for:

  • reducer-only logic
  • hook guard behaviour in isolation
  • full app routing or full storefront journeys

Those belong in unit tests or end-to-end tests.

Where component tests live

Component tests should live next to the component they cover, not in a central cypress/component folder.

Use co-located spec files such as:

packages/snappycart/src/cart/components/CartDrawer.cy.tsx
packages/snappycart/src/cart/components/CartIcon/CartIcon.cy.tsx
packages/snappycart/src/cart/components/CartIntegration.cy.tsx

Selector contract

We use data-cy as a stable test contract. Do not rely on CSS classes for component tests.

Core selectors:

  • cart-icon
  • cart-badge
  • cart-drawer
  • cart-overlay
  • cart-drawer-title
  • cart-close
  • cart-empty
  • cart-subtotal
  • cart-clear

Per-item selectors:

  • cart-item-<id>
  • cart-inc-<id>
  • cart-dec-<id>
  • cart-qty-value-<id>
  • cart-remove-<id>

If a new visible cart interaction is introduced, add a stable data-cy selector as part of the same change.

Starter spec

Start with a co-located file:

packages/snappycart/src/cart/components/CartDrawer.cy.tsx
import CartDrawer from './CartDrawer';
import { CartProvider } from '../context/CartProvider';

const sel = (id: string) => `[data-cy="${id}"]`;

function mountDrawer(params?: { open?: boolean; onClose?: () => void; title?: string }) {
const open = params?.open ?? true;
const onClose = params?.onClose ?? (() => {});
const title = params?.title ?? 'Your Cart';

cy.mount(
<CartProvider>
<CartDrawer open={open} onClose={onClose} title={title} />
</CartProvider>,
);
}

describe('CartDrawer (CT)', () => {
it('does not render when closed', () => {
mountDrawer({ open: false });
cy.get('body').find(sel('cart-drawer')).should('not.exist');
});

it('renders empty state when there are no items', () => {
mountDrawer({ open: true });

cy.get(sel('cart-drawer-title')).should('be.visible');
cy.get(sel('cart-drawer-title')).should('contain', '(0)');
cy.get(sel('cart-empty')).should('be.visible');
});

it('focuses the Close button on open', () => {
mountDrawer({ open: true });

cy.get(sel('cart-close')).should('be.focused');
});

it('calls onClose when pressing Escape', () => {
const onClose = cy.stub().as('onClose');
mountDrawer({ open: true, onClose });

cy.get(sel('cart-close')).should('be.focused');
cy.window().trigger('keydown', { key: 'Escape' });

cy.get('@onClose').should('have.been.calledOnce');
});

it('calls onClose when clicking the overlay', () => {
const onClose = cy.stub().as('onClose');
mountDrawer({ open: true, onClose });

cy.get(sel('cart-overlay')).click({ force: true });

cy.get('@onClose').should('have.been.calledOnce');
});
});

Some component transitions require cart state. In those cases, mount the UI inside a small harness that wraps CartProvider and exposes a controlled way to seed or mutate the cart through public actions.

Typical harness responsibilities:

  • wrap in CartProvider
  • render CartIcon
  • render CartDrawer
  • expose one or two deterministic add-item buttons
  • optionally manage local open state for the drawer

This allows contributors to test real browser-visible transitions without reaching into internal implementation details.

Component state and transition matrix

Use this table to decide what to cover in Cypress Component Testing. Each row describes a browser-visible state transition that should be validated through mounted components.

IDComponentCurrent stateTrigger / eventExpected next stateWhat to assert in CT
CT-01CartDrawerDrawer closed, cart emptyMount with open={false}Drawer remains absent from DOMcart-drawer does not exist
CT-02CartDrawerDrawer open, cart emptyMount with open={true}Empty state is showncart-empty visible, title shows (0)
CT-03CartDrawerDrawer open, cart emptyInitial renderClose button receives focuscart-close is focused
CT-04CartDrawerDrawer openPress EscapeDrawer requests closeonClose called
CT-05CartDrawerDrawer openClick overlayDrawer requests closeonClose called
CT-06CartIconEmpty cartMount inside providerBadge shows empty or zero state according to speccart-badge matches expected empty behaviour
CT-07CartIconCart has 1 itemSeed one item through harnessBadge updates to 1cart-badge contains 1
CT-08CartIconCart has multiple itemsSeed multiple quantities or itemsBadge reflects total item countcart-badge contains expected total
CT-09CartIcon + CartDrawerDrawer closed, cart emptyClick cart iconDrawer opens in empty statecart-drawer visible and cart-empty visible
CT-10CartIcon + CartDrawer + CartProviderEmpty cart, drawer closedAdd first item through harnessCart becomes non-emptyBadge updates and title count updates when opened
CT-11CartDrawer + CartProviderDrawer open, cart has 1 item with qty 1Open drawer after seeding first itemPopulated item row is showncart-item-<id> visible and quantity is 1
CT-12CartDrawer + CartProviderDrawer open, cart has 1 item with qty 1Click incrementQuantity becomes 2cart-qty-value-<id> shows 2, subtotal updates
CT-13CartDrawer + CartProviderDrawer open, cart has 1 item with qty 2Click decrementQuantity becomes 1cart-qty-value-<id> shows 1, subtotal updates
CT-14CartDrawer + CartProviderDrawer open, cart has 1 item with qty 1Click decrement if decrement removes at 1Cart returns to empty stateItem row disappears and empty state is shown
CT-15CartDrawer + CartProviderDrawer open, cart has 1 item with qty 1Click removeItem is removedItem row disappears and empty state is shown
CT-16CartDrawer + CartProviderDrawer open, cart has multiple distinct itemsSeed two different itemsMultiple rows are renderedBoth rows visible and subtotal matches sum
CT-17CartDrawer + CartProviderDrawer open, cart has multiple itemsRemove one itemRemaining item stays visibleRemoved row absent, remaining row visible, subtotal recalculated
CT-18CartDrawer + CartProviderDrawer open, cart has one remaining itemRemove last itemCart returns to empty stateEmpty state shown and subtotal cleared or hidden according to spec
CT-19CartDrawer + CartProviderDrawer open, cart has itemsClick clear buttonCart becomes emptycart-clear empties cart and empty state is shown
CT-20CartDrawer + CartProviderDrawer open, subtotal visibleIncrement or decrement quantitySubtotal recalculates immediatelycart-subtotal updates to expected value
CT-21CartDrawer + CartProviderDrawer open after state changesClose and reopen drawerState remains consistentSame items, same quantities, same subtotal
CT-22CartIcon + CartDrawer + CartProviderCart has items, drawer closedClick icon, then close via overlay or EscapeDrawer closes without losing cart stateDrawer hides and badge still shows current count
CT-23CartDrawer + CartProviderCart contains visible product metadataSeed representative productProduct content renders correctlyExpected product name or visible fields are shown
CT-24CartDrawer + CartProviderCart has enough items for repeated interactionsIncrement, decrement, remove in sequenceUI stays in sync after each transitionCount, rows, and subtotal remain correct after each step

How to use this matrix

When adding or reviewing a Cypress component test:

  • identify the component under test
  • identify the current state
  • identify the visible transition
  • assert the next visible browser state
  • prefer one meaningful transition per test

Every new Cypress CT contribution should map to at least one row in the matrix above.

Coverage guidance for contributors

Prioritise these rows first:

  • empty CartDrawer behaviour
  • CartIcon badge behaviour
  • first item added
  • quantity increase and decrease
  • remove item
  • remove last item
  • subtotal recalculation
  • icon and drawer interaction through provider-backed mounting

Avoid duplicating the same transition in multiple slightly different tests. Prefer meaningful behavioural coverage over repetitive assertions.