Skip to main content

Cart state transition matrix

This page defines the state transition model for snappycart.

It exists to support test design for contributors working on cart logic, provider behaviour, and UI interactions.

The goal is not to generate random test cases. The goal is to map the cart states, valid transitions, guarded transitions, derived value checks, and key visual behaviours in a structured way.

Why this matrix matters

snappycart behaviour is driven by more than one moving part:

  • cart data state
  • drawer UI state
  • derived values such as totalItems and subtotal

A single action can look correct in isolation and still break the contract when state transitions are considered as a whole.

State model

Cart data states

IDStateDescription
C0EmptyNo items in the cart
C1SingleLineQty1One line item with quantity equal to 1
C2SingleLineQtyGT1One line item with quantity greater than 1
C3MultiLineMore than one line item in the cart

Drawer UI states

IDStateDescription
D0DrawerClosedDrawer is closed
D1DrawerOpenDrawer is open

Combined states

IDCombinationDescription
S0D0 + C0Drawer closed, cart empty
S1D1 + C0Drawer open, cart empty
S2D0 + C1Drawer closed, one line item, quantity = 1
S3D1 + C1Drawer open, one line item, quantity = 1
S4D0 + C2Drawer closed, one line item, quantity > 1
S5D1 + C2Drawer open, one line item, quantity > 1
S6D0 + C3Drawer closed, multiple line items
S7D1 + C3Drawer open, multiple line items

Core assumptions

These assumptions should be treated as the intended contract unless package behaviour changes deliberately:

  • adding the same product merges by product.id
  • increment increases quantity by one
  • decrement reduces quantity by one
  • decrement from one removes the line item
  • clear() empties the cart data
  • drawer visibility is controlled separately from cart data
  • invalid quantity input should not create a broken cart state

Main valid transitions

IDFromEventGuardToExpected result
T01S0INITInitial renderS0Empty cart, badge = 0, subtotal = 0
T02S0OPEN_DRAWERUser clicks cart iconS1Drawer becomes visible
T03S1CLOSE_DRAWERClose action firedS0Drawer becomes hidden
T04S0ADD_NEW_ITEMValid productS2First line item added
T05S1ADD_NEW_ITEMValid productS3First line item added, drawer remains open
T06S2ADD_SAME_ITEMSame product.idS4Quantity increases from 1 to more than 1
T07S3ADD_SAME_ITEMSame product.idS5Quantity increases from 1 to more than 1
T08S2ADD_DIFFERENT_ITEMDifferent product.idS6Second line item added
T09S3ADD_DIFFERENT_ITEMDifferent product.idS7Second line item added, drawer remains open
T10S4INCREMENT_EXISTINGExisting line itemS4Quantity increases, same cart shape
T11S5INCREMENT_EXISTINGExisting line itemS5Quantity increases, drawer remains open
T12S4DECREMENT_EXISTINGQuantity becomes 1S2Single line item remains with quantity 1
T13S5DECREMENT_EXISTINGQuantity becomes 1S3Single line item remains with quantity 1
T14S2REMOVE_LINEOnly line item removedS0Cart becomes empty
T15S3REMOVE_LINEOnly line item removedS1Cart becomes empty, drawer remains open
T16S4REMOVE_LINEOnly line item removedS0Cart becomes empty
T17S5REMOVE_LINEOnly line item removedS1Cart becomes empty, drawer remains open
T18S6OPEN_DRAWERUser clicks cart iconS7Drawer becomes visible
T19S7CLOSE_DRAWERClose action firedS6Drawer becomes hidden
T20S6ADD_SAME_ITEMExisting line itemS6Quantity increases, line count unchanged
T21S7ADD_SAME_ITEMExisting line itemS7Quantity increases, line count unchanged
T22S6ADD_DIFFERENT_ITEMNew line itemS6Line count increases
T23S7ADD_DIFFERENT_ITEMNew line itemS7Line count increases, drawer remains open
T24S6REMOVE_NON_LAST_LINEMore than one line remainsS6One line removed, cart still multi-line
T25S7REMOVE_NON_LAST_LINEMore than one line remainsS7One line removed, cart still multi-line
T26S6REMOVE_LINEOne line remains with quantity 1S2Cart collapses to single-line qty 1
T27S7REMOVE_LINEOne line remains with quantity 1S3Cart collapses to single-line qty 1
T28S6REMOVE_LINEOne line remains with quantity > 1S4Cart collapses to single-line qty > 1
T29S7REMOVE_LINEOne line remains with quantity > 1S5Cart collapses to single-line qty > 1
T30S2CLEARClear invokedS0Cart becomes empty
T31S3CLEARClear invokedS1Cart becomes empty, drawer remains open
T32S4CLEARClear invokedS0Cart becomes empty
T33S5CLEARClear invokedS1Cart becomes empty, drawer remains open
T34S6CLEARClear invokedS0Cart becomes empty
T35S7CLEARClear invokedS1Cart becomes empty, drawer remains open

Guarded and invalid transitions

IDFromEventExpected result
N01S0 or S1REMOVE_LINENo state change
N02S0 or S1INCREMENT_EXISTINGNo state change
N03S0 or S1DECREMENT_EXISTINGNo state change
N04S0 or S1CLEARNo crash, state remains empty
N05S0CLOSE_DRAWERNo state change
N06S1OPEN_DRAWERNo state change
N07Any non-empty stateSET_QUANTITY(0)Line item removed or request rejected, but no broken state
N08Any non-empty stateSET_QUANTITY(-1)Request rejected or normalised safely
N09Any non-empty stateSET_QUANTITY(NaN)Request rejected, no corrupted state
N10Any non-empty stateSET_QUANTITY(decimal)Request rejected or normalised consistently
N11Any stateADD_NEW_ITEM with invalid payloadRequest rejected, no corrupted state
N12Any stateRapid repeated actionsNo broken totals, no duplicate inconsistent state

Derived values matrix

Every mutating transition must validate more than the line items list.

EventDistinct linestotalItemssubtotalBadgeEmpty state
Initial empty state0000Visible when drawer is open
Add new itemIncreasesIncreasesIncreasesUpdatesHidden
Add same itemUnchangedIncreasesIncreasesUpdatesHidden
Add different itemIncreasesIncreasesIncreasesUpdatesHidden
IncrementUnchangedIncreases by 1IncreasesUpdatesHidden
DecrementUnchanged or reducedDecreases by 1DecreasesUpdatesVisible only if cart becomes empty
Remove lineDecreasesDecreasesDecreasesUpdatesVisible only if last line is removed
Clear0000Visible when drawer is open

What to validate after each transition

After every valid cart mutation, contributors should verify:

  • items.length
  • individual line item quantity
  • totalItems
  • subtotal
  • visible badge value
  • empty state visibility
  • drawer visibility state
  • correct rendering of item names, prices, and controls when applicable

Visual state references

The screenshots below should show the stable visual outcome for the most important cart states.

S0 Cart Empty, Drawer Closed

S0 Empty, drawer closed

Purpose

  • State S0: drawer closed, cart empty.

Start state

  • Fresh page load, no items in cart.

What should be visible

  • cart icon
  • badge shows 0 if your demo shows empty badge, or no badge if hidden by design
  • drawer is closed

Do not show

  • open drawer
  • any added item

S1 Cart Empty, Drawer Open

S1 Empty, drawer open

Purpose

  • State S1: drawer open, cart empty.

Start state

  • Fresh page load, empty cart.

Action

  • click cart icon

What should be visible

  • open drawer
  • empty cart message
  • subtotal at zero if shown
  • no line items

Capture tip

  • Make sure the empty state text is readable and centered properly.

S2 Single line item, quantity = 1

S2 Single line item, quantity = 1

Purpose

  • State S2: one line item, quantity = 1.

Start state

  • empty cart
  • drawer closed

Action

  • add Apple once
  • keep drawer closed if possible for this shot

What should be visible

  • badge = 1
  • drawer closed
  • cart has one item in state, but only badge is visible externally

S4 Single line item, quantity > 1

S4 Single line item, quantity greater than 1

Purpose

  • State S4: one line item, quantity greater than 1.

Start state

  • Apple already in cart once

Action

  • add Apple again or increment quantity to 2
  • keep drawer open for readability if needed

What should be visible

  • one line item only
  • quantity displayed as 2
  • subtotal = £1.20
  • no duplicate Apple rows

Important

  • This state should clearly prove that same-item add merges quantity instead of creating a second line.

S6 Multi-line cart, drawer closed

S6 Multi-line cart, drawer closed

Purpose

  • State S6: multi-line cart, drawer closed.

Start state

  • Apple quantity = 2
  • Banana not yet added
  • drawer closed

Action

  • add Banana once
  • keep drawer closed

What should be visible

  • badge = 3
  • drawer closed

Optional

  • If closed state does not visually communicate enough, just make sure the badge count is readable.

S7 Multi-line cart, drawer open

S7 Multi-line cart, drawer open

Purpose

  • State S7: multi-line cart, drawer open.

Start state

  • Apple quantity = 2
  • Banana quantity = 1

Action

  • open the drawer

What should be visible

  • two line items
  • Apple line with quantity 2
  • Banana line with quantity 1
  • subtotal = £1.60
  • clear cart action visible if your UI supports it

Transition clips

The clips below should stay short and focused. Each one should show a single transition only.

T02 Open drawer

This clip shows the drawer opening from the empty closed state without mutating cart data.

Transition

  • S0 -> S1

Start state

  • empty cart
  • drawer closed

Action

  • click cart icon
  • drawer opens

End state

  • empty open drawer

Clip goal

  • Show that opening the drawer changes UI state only and does not mutate cart contents.

T04 Add first item

This clip shows the cart moving from empty to a single line item state.

Transition

  • S0 -> S2 or visually into open equivalent if your UI opens drawer

Start state

  • empty cart
  • drawer closed

Action

  • click add-to-cart for Apple once

End state

  • one item added
  • badge updates to 1

Clip goal

  • Show the first successful add.

Important

  • If your host demo auto-opens the drawer on add, that is fine, but the clip still clearly shows the first-item transition.

T06 Add same item and merge quantity

This clip shows quantity increasing on an existing line item instead of creating a duplicate line.

Transition

  • S2 -> S4

Start state

  • Apple already in cart once
  • drawer open if needed for visibility

Action

  • click add Apple again or use increment on Apple

End state

  • Apple remains a single line item
  • quantity changes from 1 to 2
  • subtotal updates

Clip goal

  • Show merge-by-product-id behaviour, not duplicate-line behaviour.

T08 Add different item and create multi-line cart

This clip shows the cart transitioning from a single-line state to a multi-line state.

Transition

  • S2 -> S6

Start state

  • Apple quantity = 1

Action

  • add Banana once

End state

  • cart now has two distinct lines

Clip goal

  • Show transition from single-line cart to multi-line cart.

T12 Decrement quantity back to one

This clip shows a single line item moving from quantity greater than one back to quantity equal to one.

Transition

  • S4 -> S2

Start state

  • Apple quantity = 2
  • drawer open

Action

  • click decrement once

End state

  • Apple quantity becomes 1
  • subtotal decreases correctly

Clip goal

  • Show decrement above one without removing the line.

T14 Remove last line item

This clip shows the cart returning to the empty state after the last line item is removed.

Transition

  • S2 -> S0 or S3 -> S1

Start state

  • exactly one line item in cart
  • quantity = 1
  • drawer open is best for this clip

Action

  • click decrement if quantity-1 removes line, or click remove button

End state

  • cart becomes empty
  • empty state appears

Clip goal

  • Show last-line removal and return to empty cart.

T30 Clear cart

This clip shows the cart being cleared from a non-empty state.

Transition

  • non-empty -> empty

Start state

  • multi-line cart
  • drawer open
  • clear cart button visible

Action

  • click clear cart

End state

  • all items removed
  • empty state visible
  • subtotal reset to zero

Clip goal

  • Show bulk reset behavior clearly.

Cross-matrix view

Cart data transitions

From \ ToC0 EmptyC1 SingleQty1C2 SingleQtyGT1C3 MultiLine
C0 EmptyINIT, CLEAR(empty)ADD_NEW_ITEMNot directNot direct
C1 SingleQty1REMOVE_LAST, CLEARNo-op or invalid actionsADD_SAME, INCREMENTADD_DIFFERENT
C2 SingleQtyGT1REMOVE_LAST, CLEARDECREMENT to 1INCREMENT, SET_QUANTITY(n > 1)ADD_DIFFERENT
C3 MultiLineCLEAR, remove until emptyRemove until one line remains at qty 1Remove until one line remains at qty > 1Add, increment, remove non-last

Drawer transitions

From \ EventOPEN_DRAWERCLOSE_DRAWERCART_MUTATION
D0 ClosedD1D0Should remain D0 unless host app changes it
D1 OpenD1D0Should remain D1 unless host app changes it

P0

  • add first item
  • add same item
  • add different item
  • decrement from quantity greater than 1 to quantity equal to 1
  • remove last line item
  • clear the cart
  • open and close the drawer without data corruption

P1

  • invalid quantity handling
  • invalid product payload handling
  • subtotal recalculation
  • stale action handling after clear
  • repeated user clicks

P2

  • keyboard close behaviour
  • image fallback rendering
  • host-controlled integration edge cases

Practical contributor notes

This matrix should guide:

  • reducer unit tests
  • provider and hook integration tests
  • component tests for CartDrawer
  • component tests for CartIcon
  • demo end-to-end smoke coverage

It should not be treated as a requirement to write one test per row in the same layer.

The point is to make sure the contract is fully represented and covered at the right level.

Summary

snappycart has two important state dimensions:

  • cart data state
  • drawer visibility state

The project also relies on derived values such as totalItems and subtotal.

That makes state transition testing a strong fit for the package.

This matrix gives contributors a complete model for reasoning about valid behaviour, invalid behaviour, and risk-based test coverage.