Testing a storefront
A storefront test has two jobs: prove the page renders when the server is healthy, and prove it still says something useful when the server is not.
The SDK's read contract makes the second job testable: a failed read resolves to an empty value instead of throwing.
Test the contracts, not the markup
Start with a fake server. Point the client at a test origin and replace
globalThis.fetch:
Code
The first test proves the page's empty state is reachable. The second proves the operator can see the degradation the shopper cannot.
Do the same for the reads each page depends on:
| Read | Failure value to assert |
|---|---|
config.get | null |
products.get | null |
products.page, products.search | { items: [], hasNext: false, total: 0 } |
categories.list | [] |
collections.get | null |
menus.get | null |
pages.get | null |
shipping.rates | {} |
geo.communes | [] |
orders.tracking | null |
A page test then asserts the visible result: a 404 rewrite for a missing product, a retry for empty communes, a hidden review form, no conversion event.
Test the writes
A write returns a result; it does not throw. Cover three branches:
Code
Also cover:
- a validation failure with
fieldIssues; - a coupon refusal (
couponRefusalOf(result)is not null); - a transport failure (
fetchrejects), where the result is still aClientResult, not an exception.
The checkout UI test should assert that a failed submit keeps the shopper's input.
Test the browser helpers directly
Pure helpers need no server:
Code
Test the fallback paths too: no WhatsApp, no product, no order, a merchant's
text in another language arriving as null, an unknown locale, and an image on
a direct delivery (mediaImageUrl answers null, so the original URL
shows).
createBasketStore accepts a memory adapter, so it tests without a browser:
Code
Test the routes
The route factory is easiest to test as an HTTP boundary: call the exported
handler with an APIContext and a fake client, then assert the status and
body.
At minimum, assert:
- a cross-origin POST is 403;
- a bad body is 400;
- a review upload without
X-Review-Grantis refused; - a server business refusal maps to 422;
- a rate limit maps to 429;
- a failed read route keeps its envelope shape.
The route's status mapping is part of the public contract. It should not change between a test and production.
Build-time checks
Run these in the theme before shipping:
Code
A build catches the failures unit tests cannot:
- a server-only import in a browser island;
- a missing Astro action or API route export;
- a page that is prerendered while reading request data;
- a broken environment schema.
A build can succeed with an empty store
A missing STORE_API_KEY makes the client throw at construction; an unreachable
server makes reads empty. Neither fails a static build. Run at least one build
or preview against a real store before calling the theme done.
A live smoke test
Use a staging store, not production, for:
- Home, catalogue, category, collection and a product page.
- A product with variants: pick each option, confirm the price changes.
- Add to basket, change quantity, remove a line.
- Quote with a wilaya, then change the wilaya and confirm the fee changes.
- Apply a valid and an invalid coupon.
- Submit a product order and a basket order.
- A landing page with a linked product.
- A store page and a menu.
- A review: wrong order number, correct order, a photo, a voice note.
- Checkout with OTP on and off.
- Thank-you: the order appears; the pixel fires once with
eventID = orderId.
Do not run this suite in CI against a shared store; it writes orders.
Definition of done
A storefront using the SDK is ready when:
- every page renders its empty and error state without an exception;
- every internal link goes through
localizeHref; - the primary language has no prefix and other languages do;
- no store key or Zod schema is in the browser bundle;
- all writes go through the Astro routes or actions;
- an order is repriced by the server, not by the browser;
- the basket is capped and the quote is authoritative;
- the thank-you page fires no event when
orders.trackingreturnsnull; - preview is excluded from caching and revalidation drops the page tag;
npx astro check,npx vitest run,npm run buildandnpm run build:vercelpass.
Compatibility
The SDK follows semantic versioning:
- a patch fixes behaviour without changing the API;
- a minor adds backwards-compatible features;
- a major may remove or change exports.
Pin the exact version in the theme. Read CHANGELOG.md before upgrading; a
newer theme may assume a new SDK export while an older storefront build is
still running.
A major of the SDK follows a major of the platform's storefront payloads.
@wocart/storefront 3.x reads platform v7. A storefront on 2.x cannot read a
v7 store's config and renders every page as an unreadable store, so a store
updates its platform and its theme together.
The theme key is part of the contract. defineTheme({ key }) and the client's
theme option must agree; changing the key orphans the merchant's text edits
and page documents for that theme.
Deep imports are not public
Import only from @wocart/storefront, /server, /astro and /review-media.
A deep path into dist/ or src/ may move in any release. If the public
surface is missing something a storefront needs, that is an SDK change, not a
deep import.