Implement JQHTML function cache ID system and fix bundle compilation Implement underscore prefix for system tables Fix JS syntax linter to support decorators and grant exception to Task system SPA: Update planning docs and wishlists with remaining features SPA: Document Navigation API abandonment and future enhancements Implement SPA browser integration with History API (Phase 1) Convert contacts view page to SPA action Convert clients pages to SPA actions and document conversion procedure SPA: Merge GET parameters and update documentation Implement SPA route URL generation in JavaScript and PHP Implement SPA bootstrap controller architecture Add SPA routing manual page (rsx:man spa) Add SPA routing documentation to CLAUDE.md Phase 4 Complete: Client-side SPA routing implementation Update get_routes() consumers for unified route structure Complete SPA Phase 3: PHP-side route type detection and is_spa flag Restore unified routes structure and Manifest_Query class Refactor route indexing and add SPA infrastructure Phase 3 Complete: SPA route registration in manifest Implement SPA Phase 2: Extract router code and test decorators Rename Jqhtml_Component to Component and complete SPA foundation setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
10 KiB
Executable File
RSpade Testing Framework
Philosophy
These tests verify RSpade framework functionality using real integration tests:
- Real database (
rspade_test- completely isolated from production) - Real browser (Playwright with Chromium for UI tests)
- Real HTTP requests (curl, Playwright)
- No mocks - test actual behavior, not simulations
Tests are designed to be:
- Self-contained - Each test is a standalone directory with
run_test.sh - Explicit - Verbose over DRY, clear over clever
- LLM-friendly - Easy for AI to read, write, and maintain
- Evolutionary - Patterns emerge organically as tests are written
Running Tests
Run all automated tests:
./run_all_tests.sh
Automatically creates fresh database snapshot before running tests.
Run all tests with existing snapshot (faster):
./run_all_tests.sh --use-existing-snapshot
Run all tests with full migration output:
./run_all_tests.sh --full-output
Run specific test:
./basic/01_framework_verification/run_test.sh
Run specific test without database reset:
./basic/01_framework_verification/run_test.sh --skip-reset
Use with caution - only for manual testing or when composing tests.
Test Database
All tests use the rspade_test database:
- Completely separate from
rspadeproduction database - Created fresh:
rspade_test(utf8mb4_unicode_ci) - Full access granted to
rspadeuser - Reset before each top-level test (ensures deterministic behavior)
Database reset process:
- Drop
rspade_testdatabase - Create
rspade_testdatabase - Either:
- With snapshot: Restore snapshot + run new migrations (~1 second)
- Without snapshot: Run all migrations (~5-10 seconds)
Create snapshot (first time or after adding migrations):
./_lib/db_snapshot_create.sh # Quiet mode
./_lib/db_snapshot_create.sh --full-output # Show all migration output
This creates _lib/test_db_snapshot.sql which speeds up all future test runs.
If snapshot creation fails, run with --full-output to see detailed error messages.
Test Contract
Every test directory MUST have:
run_test.sh - Executable script that:
- Accepts
--skip-resetflag to skip database reset (for test composition) - Sets up test prerequisites
- Runs the actual test
- Cleans up after itself (trap EXIT)
- Exits with code 0 (pass) or 1 (fail)
- Outputs EXACTLY ONE LINE to stdout:
PASS: Test descriptionFAIL: Test description - reasonSKIP: Test description - reason
Test Composition with --skip-reset
Tests can call other tests to set up complex state:
# Complex test that needs attachments and users
echo "[SETUP] Creating prerequisite state..." >&2
"$TEST_DIR/../01_create_user/run_test.sh" --skip-reset
"$TEST_DIR/../02_add_attachment/run_test.sh" --skip-reset
# Now test the feature that requires both users and attachments
echo "[TEST] Testing feature with complex state..." >&2
# ... your test logic ...
Important: Only the top-level test (called without --skip-reset) resets the database. Sub-tests called with --skip-reset build upon the existing state.
README.md - Documents:
- What this test verifies
- Prerequisites/assumptions
- How to run standalone
- Known limitations
Standard Test Pattern
Basic Test Template
#!/bin/bash
set -e
TEST_NAME="Example Test"
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source test environment helpers
source "$TEST_DIR/../../_lib/test_env.sh"
# Parse arguments
SKIP_RESET=false
for arg in "$@"; do
case $arg in
--skip-reset)
SKIP_RESET=true
shift
;;
esac
done
# Ensure test mode exits on script exit (success or failure)
trap test_trap_exit EXIT
# SETUP
echo "[SETUP] Preparing test environment..." >&2
# Reset database unless --skip-reset
if [ "$SKIP_RESET" = false ]; then
"$TEST_DIR/../../_lib/db_reset.sh"
fi
# Enter test mode (switches Laravel to rspade_test database)
test_mode_enter
# TEST LOGIC
echo "[TEST] Running test logic..." >&2
# Your test code here
count=$(test_db_count users)
if [ "$count" -eq 0 ]; then
echo "PASS: $TEST_NAME"
exit 0
else
echo "FAIL: $TEST_NAME - Expected 0 users, found $count"
exit 1
fi
# TEARDOWN happens automatically via trap
Key Patterns
set -e- Exit on first errortrap test_trap_exit EXIT- Ensures.envalways restored--skip-resetflag - Enables test composition (calling other tests to build state)test_mode_enter- Switches Laravel to test database- Log to stderr
>&2, only PASS/FAIL to stdout - Single exit point with clear status
Shared Helpers
Located in _lib/, source in your test:
test_env.sh
source "$TEST_DIR/../../_lib/test_env.sh"
test_mode_enter # Switch Laravel to rspade_test + run rsx:clean
test_mode_exit # Restore Laravel to rspade + run rsx:clean
test_trap_exit # Cleanup (call in trap EXIT)
test_db_query "SQL" # Execute SQL on test database
test_db_count "table" # Count rows in table
Important: test_mode_enter and test_mode_exit automatically run rsx:clean after switching the database in .env to ensure the manifest is rebuilt for the correct database environment.
db_reset.sh
"$TEST_DIR/../../_lib/db_reset.sh" # Drop, create, migrate rspade_test
Note: Always check --skip-reset flag before calling db_reset.sh
Test Organization
tests/
├── _lib/ Shared utilities (grow organically)
├── basic/ Template and fundamental tests
├── core/ Framework core functionality tests
└── features/ Feature-specific tests
Writing New Tests
-
Create test directory:
mkdir -p basic/my_new_test -
Copy template:
cp basic/01_framework_verification/run_test.sh basic/my_new_test/ -
Edit test logic:
- Update
TEST_NAME - Modify test logic section
- Update assertions
- Update
-
Create README.md:
# My New Test Tests that [feature] works correctly. ## What it verifies - Thing 1 - Thing 2 ## How to run ```bash ./basic/my_new_test/run_test.sh -
Make executable:
chmod +x basic/my_new_test/run_test.sh -
Run standalone:
./basic/my_new_test/run_test.sh
Interactive Tests
Some tests require human verification and use run_interactive_test.sh:
- Not run by default in
run_all_tests.sh - Set up environment, provide instructions, wait for user input
- Useful for visual testing, complex flows, accessibility
Example:
#!/bin/bash
set -e
# ... setup code ...
echo "Instructions:"
echo "1. Visit http://localhost/login"
echo "2. Verify form displays correctly"
echo "3. Did test pass? (yes/no/skip): "
read -r response
case "$response" in
yes|y) echo "PASS: $TEST_NAME"; exit 0 ;;
no|n) echo "FAIL: $TEST_NAME"; exit 1 ;;
skip|s) echo "SKIP: $TEST_NAME"; exit 0 ;;
esac
Run interactive tests:
./run_interactive_tests.sh
Best Practices
Database
- Always use
--skip-resetflag in test contract - Default: reset database (safe but slow)
- Only skip reset when you know database state is correct
- Future: Snapshot/restore will make this fast
Output
- Log progress to stderr:
echo "..." >&2 - Only PASS/FAIL/SKIP to stdout
- Include useful context in failures:
FAIL: Test - Expected X got Y
Error Handling
- Use
set -eto exit on first error - Add cleanup trap:
trap test_trap_exit EXIT - Test edge cases (empty results, missing data)
Test Independence
- Each test should work standalone
- Don't rely on other tests running first
- Reset database or seed required data
- Clean up temp files
Common Patterns
Pattern: Database Query Test
# Query test database
result=$(test_db_query "SELECT name FROM users WHERE id=1")
if [ "$result" = "Test User" ]; then
echo "PASS: User name correct"
else
echo "FAIL: Expected 'Test User', got '$result'"
exit 1
fi
Pattern: Count Test
count=$(test_db_count users)
if [ "$count" -eq 5 ]; then
echo "PASS: Correct user count"
else
echo "FAIL: Expected 5 users, found $count"
exit 1
fi
Pattern: API Test
response=$(curl -s http://localhost/_ajax/Test/method)
if echo "$response" | jq -e '.success == true' > /dev/null 2>&1; then
echo "PASS: API returns success"
else
echo "FAIL: API failed - $response"
exit 1
fi
Database Snapshot System
The testing framework uses database snapshots to speed up test runs.
How it works:
- First time: Run
_lib/db_snapshot_create.shto create pristine snapshot - Tests restore from snapshot (~1 second) instead of running all migrations (~5-10 seconds)
- After snapshot restore, any new migrations run automatically
- When you add new migrations, recreate snapshot for maximum speed
When to recreate snapshot:
- After adding new migration files
- After modifying existing migration files
- If you notice tests taking longer than expected
Snapshot file:
- Location:
_lib/test_db_snapshot.sql - Not committed to git (add to .gitignore if needed)
- Each developer creates their own snapshot locally
Troubleshooting
Test hangs:
- Check for missing cleanup trap
- Look for processes not cleaned up
- Add timeout to long operations
Database errors:
- Verify
rspade_testdatabase exists - Check credentials work:
mysql -urspade -prspadepass rspade_test - Ensure migrations ran:
mysql -urspade -prspadepass rspade_test -e "SHOW TABLES"
Environment not restored:
- Check trap is set:
trap test_trap_exit EXIT - Verify
.env.backupdoesn't exist after test - Manually restore:
test_mode_exit
Test passes alone, fails in suite:
- Database state contamination
- Missing
--skip-resetflag handling - Shared resource conflict
Evolution
This framework is intentionally minimal. As you write tests:
- Extract common patterns into
_lib/helpers - Update templates with better patterns
- Document new best practices here
- Don't over-abstract - keep tests explicit
When in doubt, favor:
- Explicit over implicit
- Verbose over DRY
- Working over elegant
Testing the Test Framework
The first test is a meta-test that verifies the framework itself works:
./basic/01_framework_verification/run_test.sh
This test verifies:
- Test environment helpers load correctly
- Test mode enter/exit works
- Database reset works
- Migrations run successfully
- Test database is accessible