Skip to content

Documentation

Dashboard Tests — Control Map (Widget-Agnostic)

100% coverage plan for /dashboard shell. Widget catalogs vary per install — never hard-code a fixed widget list.

8/4/2026
4 min read Edit on GitHub
Important

Widgets are install-specific. Core ships a default set of package folders under src/routes/(app)/dashboard/widgets/<folder>/; plugins may inject more via slots or additional files.
Tests and docs assert shell contracts and API shapes, never “must show CPU + Memory + Logs”.

Architecture (brief)

Piece Role
+page.server.ts Auth, dashboard:read gate, compile-time widget discovery (import.meta.glob)
+page.svelte Toolbar, add/search/reset, grid, DnD reorder, lazy load, AI generative mode
base-widget.svelte Shared fetch/poll/cache/refresh chrome for individual widgets
dashboard-preferences.svelte.ts Persist layout to /api/system-preferences
widgets/<folder>/<component>.svelte Per-install catalog — package folders, each exports widgetMeta + widget.json
Slot name="dashboard" Plugin injection zone

Control → test matrix (shell = 100%)

Page shell

Control Unit Integration E2E Notes
Load + title page.server dashboard.spec AdminPageShell
dashboard:read deny page.server 403 RBAC suite Non-admin without perm
Empty state Install with no saved layout
Widget grid When preferences non-empty
Plugin slot attached May be empty

Toolbar (install-agnostic)

Control Unit E2E
Add Widget button / menu ✅ opens menu when catalog has free widgets
Widget search ✅ nonsense query → “No widgets found”
Reset all ✅ after add → empty state
AI toggle present ✅ does not require live MCP
Pointer drag-reorder (≥2 widgets) ✅ header-band mouse drag
Keyboard Ctrl+Arrow reorder ✅ accessibility path

Layout persistence

Control Unit / integration E2E
Save layout POST system-preferences API (integration elsewhere) add+reset exercises store
CSRF on save preferences store sends header covered by store code
Secure widget ids generateUUID (no Math.random) code review + unit path

Per-widget content

Policy How we test
Do not assert widget names E2E only counts [data-widget-id]
Do test APIs widgets call tests/integration/api/dashboard.test.ts (health, metrics, logs, …)
Optional deep unit per widget Opt-in; not required for shell 100%

Dashboard API (shared by many widgets)

Covered by tests/integration/api/dashboard.test.ts:

  • GET /api/dashboard/health
  • GET /api/dashboard/metrics (+ detailed)
  • system-info, logs, last5-content/media, online-user, system-messages, cache-metrics

Licensing gate (server-side, premium widgets)

Covered by tests/unit/api/dashboard-license-gate.test.ts:

  • Endpoint → widget-id map (DASHBOARD_ENDPOINT_LICENSE) covers all premium endpoints
  • Free endpoints (health, system-info, last5-*, system-messages, tenant-analytics) are ungated — checkExtensionLicense never called
  • Active trial / valid key → 200 pass-through
  • Expired trial without key → 403 LICENSE_REQUIRED
  • requireDashboardWidgetLicense direct gate (used by /api/database/pool-diagnostics)

Premium endpoints return 200 during the install-wide 14-day trial; the gate only blocks after trial expiry without a license.


Product hardening (2026-07-18)

  • Replace Math.random widget ids with generateUUID
  • CSRF headers on layout save + AI generate-layout
  • Empty-state copy notes install-varying widgets
  • Stable data-testids for shell automation
  • Replace skipped “not implemented” E2E with real shell suite
  • Pointer drag-reorder + keyboard Ctrl+Arrow E2E (widget-agnostic, ≥2 widgets)

Associated files

E2E

Unit

Integration


Run

bun run test:unit -- tests/unit/dashboard tests/unit/routes/dashboard-page-server.test.ts
bun x playwright test tests/e2e/routes/dashboard

Definition of done (shell 100%)

  • Shell loads; empty or grid
  • Add / search / reset without fixed widget list
  • Permission gate unit-tested
  • Dashboard APIs integration-tested
  • Docs state install-varying widget policy
testinge2edashboardwidgets
Was this page helpful?