docs: add comprehensive test behavior documentation for all pages

Add behavior documentation covering all SSR and legacy SPA pages:
- Testing strategy and type definitions (unit/integration/UI)
- Dashboard, Invoice, Payment, Transaction, Ledger pages
- Company/Settings, POS, Admin, Search, Auth pages
- Legacy SPA behavior docs (no UI tests until migrated)
- Edge cases, test data requirements, and dependencies per subsystem

Total: 3,600+ lines of behavior documentation to guide test authorship.
This commit is contained in:
2026-05-04 12:15:20 -07:00
parent 2993da5c82
commit b499d460f3
13 changed files with 3785 additions and 0 deletions

View File

@@ -0,0 +1,547 @@
# Legacy SPA Behaviors
## Overview
These pages are rendered client-side via Reagent/Re-frame. They use GraphQL for data fetching. They are being migrated to HTMX SSR. Behavior docs exist for reference but **NO UI tests should be written for these pages until migrated**.
### Architecture
- **Routing**: Bidi client-side routes (`src/cljc/auto_ap/client_routes.cljc`)
- **State**: Re-frame subscriptions and events
- **Data**: GraphQL via custom `graphql` effect (`auto-ap.effects`)
- **Permissions**: `auto-ap.permissions/can?` with role-based checks
### Role Permissions Summary
| Role | Transaction Page | Ledger Page | Vendor Create/Edit |
|------|-----------------|-------------|-------------------|
| admin | full | full | full |
| power-user | full | full | full |
| manager | full | blocked | full |
| user | full | full | full |
| read-only | blocked | full | blocked |
---
## Pages
### Home (`/`)
#### Purpose
Dashboard showing financial overview for the selected client: top expense categories (pie chart), upcoming bills (bar chart), and cash flow projection (interactive bar chart with table).
#### User Flows
1. User lands on `/`
2. Page loads data for currently selected client
3. If user has access to multiple clients, shows note: "these reports are for [client]. Please choose a specific customer for their report."
4. User can switch cash flow range: 7/30/60/90/120/150/180 days
5. Cash flow bars are clickable and redirect to unpaid invoices for that date
#### GraphQL Queries Used
```graphql
query HomeDashboard($clientId: id) {
expense_account_stats(client_id: $clientId) {
account { id name }
total
}
invoice_stats(client_id: $clientId) {
name
paid
unpaid
}
cash_flow(client_id: $clientId) {
beginning_balance
outstanding_payments
invoices_due_soon { due outstanding_balance invoice_number vendor { id name } }
upcoming_credits { date amount identifier }
upcoming_debits { date amount identifier }
}
}
```
#### Behaviors to Test (when migrated)
- **Happy path**: Dashboard loads with all three chart sections
- **Multi-client note**: Displays when user hasn't selected a specific client
- **Cash flow ranges**: Switching between 7-180 day views updates chart and table
- **Cash flow table**: Shows invoices, upcoming debits/credits with days-until
- **Edge case**: No data for client shows empty charts gracefully
- **Error states**: GraphQL errors show loading state appropriately
---
### Login (`/login`)
#### Purpose
Authentication page with Google OAuth login.
#### User Flows
1. User visits `/login`
2. Shows "Login with Google" button
3. Button links to Google OAuth with optional `redirect-to` query param
4. After auth failure/logout, may show logout reason notification
#### GraphQL Queries/Mutations Used
None.
#### Behaviors to Test (when migrated)
- **Happy path**: Shows login button linking to Google OAuth
- **Redirect**: Preserves `redirect-to` query parameter in OAuth URL
- **Logout reason**: Displays warning notification when `logout-reason` is set
- **Edge case**: Already authenticated user visiting login page
---
### Needs Activation (`/needs-activation`)
#### Purpose
Shown when authenticated user's account is not yet activated.
#### User Flows
1. User logs in but account is inactive
2. Shows message: "Sorry, your user is not activated yet. Please have Ben Skinner enable your account."
3. "here" link clears user state and redirects to login
#### GraphQL Queries/Mutations Used
None.
#### Behaviors to Test (when migrated)
- **Happy path**: Shows activation required message
- **Relogin link**: Clears user state and redirects to login
---
### Transactions (`/transactions/`, `/transactions/unapproved`, `/transactions/approved`, `/transactions/requires-feedback`, `/transactions/excluded`)
#### Purpose
Transaction list with filtering, editing, and bulk admin operations. Different routes show different approval statuses.
#### User Flows
1. User navigates to transactions page
2. Default date filter: last 1 month
3. Side bar allows filtering by: vendor, account, bank account, date range, amount range, location, import batch, description, linked status
4. Table shows transactions with checkbox selection (admin only)
5. Clicking row opens edit side bar
6. Admin can: bulk code, bulk delete, suppress, or manual Yodlee import
#### GraphQL Queries/Mutations Used
```graphql
query TransactionPage($filters: transaction_filters) {
transaction_page(filters: $filters) {
data {
id amount memo location approval_status check_number is_locked
matched_rule { note id }
vendor { name id }
accounts { id amount location account { name id location numeric_code } }
date yodlee_merchant { name yodlee_id id } plaid_merchant { name id }
post_date expected_deposit { id date } forecast_match { id identifier }
status description_original
payment { check_number s3_url id date }
client { name id }
bank_account { name yodlee_account_id current_balance }
}
total start end count
}
}
mutation EditTransaction($transaction: edit_transaction) {
edit_transaction(transaction: $transaction) { ... }
}
mutation DeleteTransactions($filters: transaction_filters, $ids: [id], $suppress: Boolean) {
delete_transactions(filters: $filters, ids: $ids, suppress: $suppress) { message }
}
mutation BulkCodeTransactions($filters: transaction_filters, $ids: [id], $vendor: id, $approval_status: transaction_approval_status, $accounts: [edit_percentage_account]) {
bulk_code_transactions(filters: $filters, ids: $ids, vendor: $vendor, approval_status: $approval_status, accounts: $accounts) { message }
}
```
#### Behaviors to Test (when migrated)
- **Happy path**: Loads transaction list with default 1-month filter
- **Route variants**: Each approval-status route applies correct default filter
- **Filtering**: Side bar filters trigger debounced data refresh (800ms)
- **Pagination**: Start/per-page params work correctly
- **Edit transaction**: Update vendor, approval status, memo, expense accounts
- **Edit validation**: Account total must equal transaction amount; locations must be valid
- **Bulk delete (admin)**: Delete selected transactions or all visible transactions
- **Bulk suppress (admin)**: Mark transactions as suppressed instead of deleting
- **Bulk code (admin)**: Apply vendor/account rules to multiple transactions
- **Manual import (admin)**: Import transactions via manual Yodlee import dialog
- **Error states**: Locked transactions cannot be edited/deleted; validation errors display inline
- **Permissions**: Non-admin users don't see checkboxes or action buttons
---
### Ledger (`/ledger/`)
#### Purpose
General ledger showing journal entries with line items. Supports filtering and CSV export.
#### User Flows
1. User navigates to `/ledger/`
2. Default date filter: last 1 month
3. Side bar filters: vendor, account, bank account, date range, amount range, location
4. Table shows journal entries with expandable line items
5. Admin can export to CSV
#### GraphQL Queries/Mutations Used
```graphql
query LedgerPage($filters: ledger_filters) {
ledger_page(filters: $filters) {
journal_entries {
id source original_entity amount note cleared_against alternate_description
vendor { name id } client { name id }
line_items { id debit credit location running_balance account { id name } }
date
}
total start end
}
}
query LedgerCsv($filters: ledger_filters) {
ledger_csv(filters: $filters) { csv_content_b64 }
}
```
#### Behaviors to Test (when migrated)
- **Happy path**: Loads ledger with default 1-month filter
- **Filtering**: Date range, vendor, account, location filters work
- **CSV export**: Admin can export filtered results as base64 CSV download
- **Pagination**: Virtual pagination controls
- **Permissions**: Manager role sees "Not authorized"
---
### Profit and Loss (`/ledger/profit-and-loss`)
#### Purpose
Financial report showing revenue and expenses over configurable periods. Supports multi-company and PDF export.
#### User Flows
1. User selects companies (multi-select typeahead)
2. User selects period preset (13 periods, 12 months, last week, week-to-date, last month, month-to-date, year-to-date, last calendar year, full year)
3. Optional: Include deltas, Column per location
4. Click "Run" to generate report
5. Admin can "Export" to PDF
6. Clicking report cells opens ledger detail side bar
#### GraphQL Queries/Mutations Used
```graphql
query ProfitAndLoss($clientIds: [id], $periods: [date_range], $includeDeltas: Boolean, $columnPerLocation: Boolean) {
profit_and_loss(client_ids: $clientIds, periods: $periods, include_deltas: $includeDeltas, column_per_location: $columnPerLocation) {
periods { accounts { name amount client_id account_type id count numeric_code location } }
}
}
query ProfitAndLossPdf($clientIds: [id], $periods: [date_range], $includeDeltas: Boolean, $columnPerLocation: Boolean) {
profit_and_loss_pdf(...) { url name }
}
```
#### Behaviors to Test (when migrated)
- **Happy path**: Generate P&L for single client with default period
- **Multi-company**: Select multiple clients and generate combined report
- **Period presets**: All preset buttons populate correct date ranges
- **Advanced mode**: Custom period date pickers
- **Include deltas**: Shows period-over-period changes
- **Column per location**: Breaks out by location (mutually exclusive with deltas)
- **PDF export**: Admin can generate and download PDF; single-client shows email link
- **Ledger drill-down**: Clicking cells opens ledger entries side bar for that account/period
- **Permissions**: Manager role sees "Not authorized"
---
### Cash Flows (`/ledger/cash-flows`)
#### Purpose
Statement of cash flows report. Same control structure as P&L but different report format.
#### User Flows
Same as Profit and Loss but generates cash flow statement.
#### GraphQL Queries/Mutations Used
```graphql
query CashFlows($clientIds: [id], $periods: [date_range], $includeDeltas: Boolean, $columnPerLocation: Boolean) {
profit_and_loss(client_ids: $clientIds, periods: $periods, include_deltas: $includeDeltas, column_per_location: $columnPerLocation) {
periods { accounts { name amount debits credits client_id account_type id count numeric_code location } }
}
}
query CashFlowsPdf($clientIds: [id], $periods: [date_range], $includeDeltas: Boolean, $columnPerLocation: Boolean) {
cash_flows_pdf(...) { url name }
}
```
#### Behaviors to Test (when migrated)
- **Happy path**: Generate cash flows report
- **Same controls as P&L**: Companies, periods, deltas, location columns
- **PDF export**: Admin-only export button
- **Ledger drill-down**: Clicking cells opens ledger detail
- **Permissions**: Manager role sees "Not authorized"
---
### Profit and Loss Detail (`/ledger/profit-and-loss-detail`)
#### Purpose
Detailed journal entry report broken down by category (sales, COGS, payroll, controllable, fixed overhead, ownership controllable).
#### User Flows
1. Select companies
2. Select date range (start/end date pickers)
3. Click "Run" to generate detail report
4. Shows journal entries grouped by category and account with running balances
5. Admin can export to PDF
#### GraphQL Queries/Mutations Used
```graphql
query JournalDetailReport($clientIds: [id], $dateRange: date_range, $categories: [category]) {
journal_detail_report(client_ids: $clientIds, date_range: $dateRange, categories: $categories) {
categories {
category client_id location
account { numeric_code name }
journal_entries { description date debit credit location running_balance account { id name } }
total
}
}
}
query JournalDetailReportPdf($clientIds: [id], $dateRange: date_range, $categories: [category]) {
journal_detail_report_pdf(...) { url name }
}
```
#### Behaviors to Test (when migrated)
- **Happy path**: Generate detail report with default 2-week range
- **Category breakdown**: Report includes all 6 category sections plus Gross Profit, Overhead, Net Profit summaries
- **Date range filtering**: Start/end dates filter journal entries
- **PDF export**: Admin can export to PDF with email link for single client
- **Permissions**: Manager role sees "Not authorized"
---
### Balance Sheet (`/ledger/balance-sheet`)
#### Purpose
Balance sheet report as of a specific date, with optional prior-year comparison.
#### User Flows
1. Select companies
2. Select report date
3. Optionally enable "Include comparison" and select comparison date
4. Click "Run" to generate balance sheet
5. Admin can export to PDF
6. Clicking cells opens ledger detail side bar
#### GraphQL Queries/Mutations Used
```graphql
query BalanceSheet($clientIds: [id], $date: iso_date, $comparisonDate: iso_date, $includeComparison: Boolean) {
balance_sheet(client_ids: $clientIds, date: $date, comparison_date: $comparisonDate, include_comparison: $includeComparison) {
balance_sheet_accounts { name amount account_type id numeric_code client_id }
comparable_balance_sheet_accounts { name amount account_type id client_id numeric_code }
}
}
query BalanceSheetPdf($clientIds: [id], $date: iso_date, $comparisonDate: iso_date, $includeComparison: Boolean) {
balance_sheet_pdf(...) { url name }
}
```
#### Behaviors to Test (when migrated)
- **Happy path**: Generate balance sheet for current date
- **Comparison mode**: Shows prior period side-by-side
- **Multi-company**: Combines multiple clients
- **PDF export**: Admin-only with email composition link for single client
- **Ledger drill-down**: Clicking cells opens ledger entries filtered by account and date range
- **Permissions**: Manager role sees "Not authorized"
---
### External Ledger (`/ledger/external`)
#### Purpose
Shows only externally-imported journal entries. Admin can delete entries.
#### User Flows
1. Admin navigates to `/ledger/external`
2. Shows external journal entries with external_id
3. Can select entries and delete them
4. Admin can export to CSV
#### GraphQL Queries/Mutations Used
```graphql
query ExternalLedger($filters: ledger_filters) {
ledger_page(filters: $filters) {
journal_entries { id external_id source ... }
total start end
}
}
mutation DeleteExternalLedger($filters: ledger_filters, $ids: [id]) {
delete_external_ledger(filters: $filters, ids: $ids) { message }
}
```
#### Behaviors to Test (when migrated)
- **Happy path**: Loads external-only ledger entries
- **Delete**: Admin can delete selected entries (max 1000 at once)
- **CSV export**: Same as main ledger
- **Permissions**: Admin-only; non-admin sees "Not authorized"
---
### External Import (`/ledger/external-import`)
#### Purpose
Manual import of ledger entries via tab-separated text paste.
#### User Flows
1. Admin pastes tab-separated data into textarea
2. Optional: indicates whether first row is header
3. Click "Parse" to convert to table
4. Table shows parsed rows: Id, Client, Source, Vendor, Date, Account, Location, Debit, Credit, Note, Cleared against
5. Click "Import" to submit
6. Results show: success count, ignored count, error count
7. Errors show inline with dropdown explanations
8. "Only show errors" filter available
#### GraphQL Queries/Mutations Used
```graphql
mutation ImportLedger($entries: [ledger_entry_input]) {
import_ledger(entries: $entries) {
successful { external_id }
existing { external_id }
ignored { external_id }
errors { external_id error }
}
}
```
#### Behaviors to Test (when migrated)
- **Happy path**: Paste TSV data, parse, import successfully
- **Header row**: Checkbox to skip first row
- **Validation**: Client code must exist, vendor must exist, date must be MM/dd/yyyy, debits=credits, amounts > 0
- **Lock check**: Entries must be after client's locked-until date
- **Location validation**: Location must belong to client or be "A"
- **Account validation**: Account must exist and location must match account's required location
- **Error display**: Errors show per-row with dropdown explanation
- **Success/ignored/existing**: Status icons show import result per row
- **Permissions**: Admin-only
---
### Payments (`/payments/`)
#### Purpose
**Already migrated to SSR.** Legacy client route exists for navbar highlighting only. Actual payments page is served by `payment` SSR routes at `/payment/`.
#### GraphQL Queries/Mutations Used
N/A (SSR page)
#### Behaviors to Test (when migrated)
Already migrated. Test via SSR routes.
---
### Reports (`/reports/`)
#### Purpose
**Already migrated to SSR.** Legacy client route exists for navbar highlighting only. Actual reports page is served by `company/reports` SSR routes.
#### GraphQL Queries/Mutations Used
N/A (SSR page)
#### Behaviors to Test (when migrated)
Already migrated. Test via SSR routes.
---
### Admin Vendors (`/admin/vendors`)
#### Purpose
**Already migrated to SSR.** Legacy client route exists for navbar highlighting only. Actual vendor management is served by `admin/vendor` SSR routes.
#### GraphQL Queries/Mutations Used
N/A (SSR page)
#### Behaviors to Test (when migrated)
Already migrated. Test via SSR routes.
---
### New Vendor (`/vendor/new`)
#### Purpose
Opens the home dashboard with the vendor creation dialog pre-opened.
#### User Flows
1. User navigates to `/vendor/new`
2. Loads home dashboard
3. Opens vendor dialog if user has `:vendor :create` permission
4. Dialog allows creating new vendor with name, terms, address, contacts, default account, etc.
#### GraphQL Queries/Mutations Used
```graphql
mutation UpsertVendor($vendor: add_vendor) {
upsert_vendor(vendor: $vendor) { id name ... }
}
```
#### Behaviors to Test (when migrated)
- **Happy path**: Home loads with vendor dialog open
- **Permission check**: Dialog only opens if user can `:vendor :create`
- **Vendor creation**: Form submission creates vendor via GraphQL mutation
- **Validation**: Only one terms override, schedule payment DOM override, and account override per client
---
## Cross-Cutting Behaviors
### GraphQL Query Patterns
- All data fetching uses re-frame `graphql` effect with JWT token from `:user` in app-db
- Queries use `owns-state` to track loading status per page
- Results flow through `::data-page/received` event which stores data and syncs URL query params
- Filter changes are debounced (800ms) via `dispatch-debounce`
### Client-Side State Management (Re-frame)
- **Data pages**: `data-page` namespace provides reusable pagination/filtering state
- `::data-page/params` - merged filters + table params + query params
- `::data-page/data` - GraphQL response data
- `::data-page/checked` - selected rows for bulk operations
- **Forms**: `forms` namespace manages edit dialogs with `start-form`, `change-handler`, `save-succeeded`
- **Status**: `status` namespace tracks async operation states (`:loading`, `:complete`, `:error`)
### Navigation Between Legacy Pages
- Navbar uses Bidi `path-for` with `routes/routes` for legacy SPA links
- Some navbar items (Payments, POS, Invoices) now link to `ssr-routes/only-routes` instead
- Page components dispatch `::mounted` on mount and `::unmounted` on unmount to set up/tear down:
- Data subscriptions
- Forward event listeners
- Track subscriptions (parameter change watchers)
---
## Migration Notes
### Actively Migrated / Already SSR
- **Payments** (`/payments/`) - Fully migrated to `/payment/` SSR routes
- **Reports** (`/reports/`) - Fully migrated to `/company/reports` SSR routes
- **Admin Vendors** (`/admin/vendors`) - Fully migrated to `/admin/vendor` SSR routes
### Still Legacy SPA (prioritized by complexity)
1. **Transactions** - High complexity (filters, edit form, bulk operations, manual import)
2. **Home/Dashboard** - Medium complexity (charts, cash flow calculations)
3. **Ledger** - Medium complexity (filters, CSV export)
4. **External Ledger** - Low-medium complexity (subset of ledger + delete)
5. **External Import** - Medium complexity (TSV parsing, validation, batch import)
6. **Profit and Loss** - High complexity (multi-company, periods, PDF export)
7. **Cash Flows** - High complexity (shares P&L infrastructure)
8. **Profit and Loss Detail** - Medium complexity (category filtering, running balances)
9. **Balance Sheet** - Medium complexity (comparison mode, drill-down)
10. **Login** - Low complexity (static page)
11. **Needs Activation** - Low complexity (static page)
12. **New Vendor** - Low complexity (dialog on home page)
### Migration Risks
- **Chart libraries**: Home page uses Recharts (React). Replacement needed for SSR.
- **PDF generation**: P&L, Cash Flows, Balance Sheet, P&L Detail all support PDF export via server-side generation.
- **Bulk operations**: Transactions page has complex bulk coding/deletion with validation.
- **External import**: TSV parsing and validation logic lives entirely in SPA; server-side equivalent needed.