Platform
Shared utilities, components, layouts, API infrastructure
Reading amount values
Plain numbers show correctly on screen
Amounts written with ₹ symbol or commas (e.g. "₹1,25,000") display correctly
Blank or invalid amount fields show ₹0 instead of breaking the page
Blank fields that should stay blank do not fill in with 0
Valid numeric values pass through unchanged
Rupee amount formatting
Whole-rupee amounts show without ".00" decimals (e.g. ₹500 not ₹500.00)
Amounts with paise show exactly two decimal digits (₹99.50)
Large amounts use Indian comma grouping (1,00,000 — not 100,000)
Invoices / accounting screens can force ".00" on whole amounts when needed
Name & text tidy-up
Names typed in lowercase show as Title Case ("john doe" → "John Doe")
Names typed in ALL CAPS show as Title Case
Extra spaces inside a name are removed and trimmed at both ends
Profile avatar shows "U" placeholder when the user has no name
Profile avatar shows first letters of first two words (e.g. "John Doe" → "JD")
Short YouTube share links (youtu.be/...) play correctly in product videos
Full YouTube links (watch?v=... and embed/...) play correctly
Broken YouTube links do not crash the product page
Non-YouTube URLs entered in the video field are quietly ignored
Seller address does not repeat the same line when city equals address line 1
Discount & savings display
Discount percentage shown next to a product is a whole rounded number
No discount label appears when the strike price is missing or lower than the selling price
Total savings on the cart page match unit savings times quantity
Registration form defaults
Sample data is available for customer, dealer, and supplier registration forms
Selecting a role loads the matching sample profile fields
Date rendering across the app
Empty or missing dates show "N/A" instead of a blank space or an odd value
Order and transaction dates show padded day and 12-hour AM/PM time (e.g. "05 Sep 2026 · 03:45 PM")
Midnight timestamps show as "12:00 AM" (not "00:00")
Noon timestamps show as "12:00 PM" (not "00:00 PM")
Broken date values from the server show the original text instead of crashing the row
Numeric timestamps from the server are formatted correctly
Compact date labels show as "DD Mon YYYY" (e.g. "05 Sep 2026")
Broken date values show at least the date portion, not a blank cell
How forms flag invalid input
Valid form submission passes without any error messages
Each invalid field shows its own inline error message
General form errors that are not tied to a specific field do not attach to any field
Nested fields (e.g. "billing address city") show errors on the correct sub-field
Errors on list rows (e.g. product variant 1 MRP) attach to the correct row
Two invalid fields in the same section both show their errors independently
When the same field has multiple errors, only the first is shown to the user
Errors on deeply nested fields replace parent-level errors correctly
CSV export from reports and tables
Downloaded filename includes today's date (YYYY-MM-DD format)
Filename uses today's date automatically when none is passed
Empty tables do not trigger a download (no blank file appears in Downloads)
Populated tables trigger a CSV download when Export is clicked
Only visible / meaningful columns are included in the CSV
Blank cells stay blank in CSV (no "null" or "undefined" text)
Date columns export in a standard readable format
Complex cell values (badges, tags) export as readable text
Columns without a plain-text header fall back to a sensible label
HTML tags inside data are escaped in the CSV (no broken output, no injection risk)
Popup blocked by the browser does not crash the page — user just sees no download
Table rendering and empty / error states
Column headers and data rows render correctly
Empty tables show a friendly "no data" message
Loading skeleton appears while data is being fetched
Fetch errors show an error message with a Retry button
Typing in the search box filters rows across all searchable columns
Clearing the search returns all rows
Search input inside tables
Search box shows helpful placeholder text
Typing in the search box updates results as you type
Clear (×) button hides when the search field is empty
Clear (×) button appears once you start typing
Clicking clear (×) empties the search and resets the results
Bank account cell display
Bank name and account number appear on one line in the compact view
Long bank names truncate cleanly without breaking the row
Detailed view shows a header row above the bank details
Header falls back to "Bank Account" when no custom label is provided
Info (i) button opens a bank details popup when available
Info (i) button is hidden when there are no additional bank details to show
Column layout defaults
Columns marked hidden do not appear in the table
Column labels, sortable and searchable defaults apply as configured
A column can be sortable but excluded from search (and vice versa)
Currency, number and percentage columns align right by default
Status badges and yes/no columns align center by default
Text columns align left by default
Buttons
Buttons show the correct label passed to them
Segmented toggle (tabs)
Every tab option shows its label
Count badge appears only on tabs that have a count
Currently selected tab is visibly highlighted
Clicking a tab switches to it and updates the page
Disabled tabs cannot be clicked
Screen readers announce the tab group correctly
Light / dark mode toggle
Toggle button reads "Switch to dark mode" or "Switch to light mode" correctly
Clicking the toggle in light mode switches the whole app to dark mode
Clicking the toggle in dark mode switches the whole app back to light mode
Selected theme is remembered across page reloads and new visits
Screen readers announce the same label that sighted users see
Tree / hierarchy view (e.g. sub-dealer network)
Root row appears when the tree first loads
Top-level rows appear expanded on first render
Grandchild rows stay hidden until you expand their parent
Clicking a row expands it to show its children
"Expand All" reveals every row in the tree
"Collapse All" hides every branch back to root
Cart merge conflict popup
No popup appears when there is no cart conflict
Popup appears when guest cart and server cart differ after login
Popup correctly labels which cart is Online Order and which is Store Pickup
Labels swap correctly when guest and server carts have opposite shop types
Popup lists every server cart item with name, size, qty and price
Popup summary uses correct plural form (e.g. "3 items", not "3 item")
Browser tab titles
Home page shows the default site title in the browser tab
About page shows its own title in the browser tab
Contact page shows its own title in the browser tab
Dashboard reports pages show their own titles
Unknown pages auto-generate a title from the page name plus "| SPARSS"
Deep pages (e.g. /dashboard/settings/security) show a sensible title fallback
Overall page layout
Every logged-in page shows the top navigation and footer
Top navigation shows the SPARSS logo and main menu links
On mobile, top header collapses into a compact menu bar
Footer shows brand, copyright, and social links on every page
Server requests
Every request goes to the correct backend URL from the environment configuration
Logged-in requests include the user's session token so the server recognises them
Public / logged-out requests do not send a session token
Response handling
Standard server responses are unwrapped correctly for pages to consume
Server errors show a readable error message (not a raw error stack)
Session-expired (401) responses log the user out and send them to the login page
List response parsing
Lists load correctly from the standard server response format
Alternate response shapes from older endpoints still show lists correctly
Empty or missing lists show an empty state instead of crashing the page
Session storage on device
Login stores the user's session so they stay logged in across page reloads
Reopening the app restores the previous session automatically
Logout clears the stored session completely
App still works in private / incognito browsing when storage is not allowed
App startup and data caching
App starts with a single shared data cache used by every page
Logout clears the cache so the next user does not see previous user's data
Fresh data is fetched when cached data becomes stale (after ~1 minute)
Role-based page access
Users with the correct role can open the page normally
Logged-out users trying to open a protected page are sent to /login
Users opening a page not meant for their role are redirected or shown a fallback