Skip to content

QA Findings — system-wide test sweep ​

Living defect log produced by the test-coverage expansion effort (plan: web sweep → widget · golden · e2e · edge-fn). Each row is a concrete error observed while driving the app, with a status:

  • fixed — corrected in this effort (commit noted)
  • test-added — regression test added so it can't recur
  • flagged-backend — needs a Supabase table / edge function / server change (out of scope here)
  • flagged-manual — needs a real device (Face ID, camera, push) — can't reproduce in the web harness
  • open — confirmed defect, not yet fixed

Severity: S1 crash/blocker · S2 broken feature/flow · S3 visual/UX · S4 polish.

Method ​

Drive the dev web build (:8090, flutter run -d web-server --web-port 8090 --dart-define-from-file=env/dev.json) via the browser. Per route: load → read console errors → interact → screenshot → record. Authenticated routes require the user to sign in (the agent must not enter OTP credentials).

Local tests: this repo pins Flutter via FVM (.fvmrc → 3.47.1). Run fvm flutter test … / fvm flutter analyze — not bare dart test, which can pick up the global Dart SDK and fail compiling package:flutter with exhaustive-switch errors on Windows.

Findings ​

IDSevSurfaceRoute / AreaSymptomRoot cause / fileStatus
F-001S2Web/public tools gridTool cards rendered huge & "empty" on desktop widthsNo max-width; 2-col grid ballooned. public_landing_screen.dart → wrapped in Center>ConstrainedBox(maxWidth:640)fixed (0a11560)
F-002S2Web/all/sign-in from public landing"Login" button bounced back to /public for guests_authRedirect treated anonymous session as signed-in. router.dart → only redirect non-anonymous away from /sign-infixed (29252ac)
F-003S3Web/allpublic landing sign-in cardImplied a staff-only login that doesn't existRelabeled to general "تسجيل الدخول / Sign in". public_landing_screen.dart, web FAQfixed (0a8768b, web 5a66ba5c7)
F-004S1Webapp mount (long-lived tab)Renderer froze / "slow mount"; CDP screenshot timed outgotrue auth-refresh "Failed to fetch" retry storm pinned the isolate (transient). Mitigated by AuthRefreshGuard (circuit-breaker + visibility pause). Recover = full reload. See flutter-web-stuck-mount skillmitigated

| F-005 | S3 | Web | All list/content screens | Content balloons to full window width on desktop — rows/cards/inputs stretch ~1512px with huge gaps | Phone layout, no global max-width on web. Seen on /catalog, /tools/mortgage, /tools/roi, /tools/rent. Same root as F-001 but app-wide | fixed — global _GlobalWidthCap in lib/app.dart caps content to contentMaxWidth (960) on viewports wider than the cap, centered with brand-colored gutters, and narrows the reported MediaQuery width so screens don't overflow. Verified /catalog + /tools/mortgage now render as a centered column; landing's tighter local cap still applies | | F-006 | S1 | Web/all | /tools/transactions and /tools/price-map | Route froze the renderer (screen never rendered; CDP screenshot timed out) | webToolRedirects mapped each new path to itself → redirect-only self-loop → GoRouter assertion "redirect-only route must redirect elsewhere". routes.dart (removed both entries) | fixed + test-added (test/core/navigation/web_tool_redirects_test.dart) |

| F-007 | S3 | Web | /tools/price-map | Map renders but the tile/base layer is blank (no tiles, no markers) | flutter_map base layer not painting on web — no console/network error thrown. PACI vector basemap (PaciBasemapLayer in paci_flutter_map_layers.dart) reads its style directly from kuwaitportal.paci.gov.kw — CORS-blocked in the browser → _loadStyle() fails → SizedBox.shrink() = blank. | fixed — paci_flutter_map_layers.dart: on web (or vector-style load failure) render an OSM raster TileLayer instead of a blank surface; public_price_map_screen.dart: render the basemap even when the PACI polygons can't load (only show the disclosure when there's no price data at all). Verified: Map view now shows the OSM map of Kuwait + polygons + legend | | F-008 | S4 | Web | /contact | Social handle shows as aldilaijanre@ / khobaraco@ (the @ floats to the wrong side) | RTL bidi rendering of @handle; contact_screen.dart office.handle → textDirection: TextDirection.ltr | fixed |

| F-009 | S4 | Web/all (auth) | master-detail empty pane (properties, deals, valuations…) | Detail pane reads "Select an item from the list" in English on an Arabic-primary app | Hardcoded string in shared lib/shared/widgets/master_detail_scaffold.dart:178 (_DefaultEmptyDetail). One-line fix: localize via the existing key selectFromList (ar "اختيار من القائمة" / en "Select from list") | fixed — localized _DefaultEmptyDetail via paciPicker.selectFromList (one shared fix, all master-detail screens) |

Calculators verified correct (no defect) ​

  • /tools/mortgage — 100k KWD · 15yr · 5% → KWD 790.794/mo, total 142,342.853, interest 42,342.853. ✓ math correct.
  • /tools/roi — 150k price · 800 rent · 100 expenses → 6.4% gross · 5.6% net · KWD 8,400/yr. ✓ math correct.

Verified rendering OK (public sweep) — F-005 full-width ballooning applies to all ​

/public landing · /catalog (data loads) · /tools/rent · /tools/compare · /tools/transactions (after F-006 fix) · /tools/price-map (route ok; map blank = F-007) · /request-valuation (full form) · /about · /services · /contact (modulo F-008). Not individually screenshotted (static legal text, low risk): /terms, /privacy. Secondary public routes not yet swept: /area-guides, /submit-property, /market-indicators, /hedonic-pricing, /combined-valuation.

Authenticated sweep (signed in as admin) — all render, no crashes ​

Verified rendering correctly (the global F-005 cap applies on every screen): dashboard (greeting, deals pipeline, task/notification tiles, quick actions, 5-branch shell) · brokerage/properties (master-detail, data loads) · brokerage/sales/deals (6 deals + status filter chips, real data) · valuation (workflow-stage filters, list) · finance (revenue chart, invoices/quotations/AR-aging rows) · ai (Hermes chat) · comms (WhatsApp inbox) · settings (security/notifications/appearance/app-mode/sign-out) · admin/users (team list + role badges) · reports/kpi (KPI + closed-deals chart).

Only authenticated defect found: F-009 (shared master-detail empty-state string). No F-006-class crashes on the authenticated side.

Not yet manually re-swept on Flutter Web (2026-06-09): per-record tap-through detail and kanban boards. Mitigated in CI by widget render-smoke tests for all report + WhatsApp sub-screens and by web_routes_parity_test.dart + notification_link_normalization_test.dart for deep links.

Phase 5 — Edge-function deployment audit + .single() review (2026-06-08, updated 2026-06-09) ​

Edge functions: all healthy. All 19 functions the app invokes are deployed in the Supabase project (jrsgosnnyjonxaesqtln — 114+ functions total), so there is no silent-404 risk from a missing function: register-trusted-device, webauthn-register, webauthn-authenticate, paci-proxy, ai-valuation-analysis, ai-regression, send-email, mailbox-api, ai-marketing-caption, compute-matches, smart-whatsapp-alerts, send-whatsapp-message, get-whatsapp-media-url, generate-property-pdf, generate-valuation-pdf, generate-docx-report, generate-excel-export, whatsapp-ai-replay, whatsapp-reliability-action. Inventory test: test/core/navigation/edge_function_inventory_test.dart. (They can still error at runtime for downstream reasons, but they exist + are reachable; repos wrap calls in try/catch with empty/error fallbacks and the UI shows error states.)

.single(): all safe. Every .single() call in lib/data/repositories/ is on an insert→select→single create path (e.g. .insert(payload).select('id').single()) where exactly one row is guaranteed — none are on fetch paths that could return 0 / many rows. No crash risk found; the earlier MEDIUM flag is cleared.

Phase 2 — Widget tests for untested screens (2026-06-08) ​

Added widget tests for 64 previously-untested feature screens (~188 new tests), each hermetic via Riverpod provider overrides and verifying the screen's async branches don't crash on render (the class of bug the manual sweep kept surfacing — e.g. F-006). Test shape adapted per screen: golden/empty/error for lists, data/error render-smoke for dashboards, initial/data/error for interactive (forms + streaming AI chats).

Features now screen-tested: tasks, reminders, call_logs, owner_management, smart_alerts, bundles, documents (list+manager+files-dashboard), showcases, agent_actions, contracts (e+smart), matching (matching+request-matches), transactions (explorer+3 analytics), search, appointments, requests (list+detail+form), reports (all 13 doable), whatsapp (all config/list/dashboards), ai (all 8 sub-screens incl. streaming chats), email, onboarding — plus the realtime/auth screens unblocked in the follow-up (notifications, whatsapp inbox + conversation, internal chat list + thread, commissions). Full suite 1045 green, flutter analyze clean.

Reusable patterns (for the remaining/future screens):

  • List: pumpLocalizedApp(tester, const Screen(), overrides:[listProvider(query).overrideWith((ref) async => …)]) → pumpAndSettle() → tester.takeException() (drains the AppBar-bottom overflow, F-010) → find.text / find.byType(EmptyState|ErrorState).
  • Identity-keyed family (no Equatable key): use the family-wide form provider.overrideWith((ref, key) async => …).
  • appPreferences-derived keys: pin a fixed-state Notifier so SharedPreferences isn't hit and the family key is deterministic.
  • Streaming AI chats: inject a fake repo via repoProvider.overrideWithValue(FakeRepo()). If the repo ctor eagerly builds a SupabaseClient, pass authOptions: const AuthClientOptions(autoRefreshToken: false) (or a noSuchMethod stub) to avoid a leaked GoTrue timer that fails test teardown.
  • Date rows: setUpAll(() async => initializeDateFormatting('en_GB')).

Unblocked (follow-up — the realtime/auth blocked set is now covered, +18 tests):

  • Added lib/core/auth/auth_providers.dart (currentUserProvider / currentUserIdProvider) and refactored commissions_screen off the direct Supabase.instance.client.auth.currentUser read → now testable via currentUserIdProvider.overrideWithValue(...).
  • Added test/helpers/supabase_test_harness.dart: initSupabaseForTest() (in setUpAll) stands up Supabase with a stub config so realtime-in-initState screens mount — with no session, guarded screens (notifications) skip wiring, and resetSupabaseRealtimeForTest() (in tearDown) cancels the reconnect timer for screens that wire a channel unconditionally (whatsapp/chat).
  • Now covered: notifications, commissions, whatsapp_inbox, whatsapp_conversation, chat/internal_chat_list, chat/internal_chat_thread.

Still deferred: app_shell (not a content screen — exercised indirectly); clients/crm forms (left untouched — concurrent area-picker session owns that lib code).

| F-010 | S4 | mobile/web | screens with a 2-row AldilaijanAppBar bottom (tasks, reminders) | shared app-bar bottom Column overflowed its preferred height by ~4px | lib/core/widgets/app_bar.dart — bottom content (dense search + chip rows) renders a few px taller than the declared preferredSize.height | fixed — both app-bar variants reserve a 6px _bottomSafetyPad in the bottom block; the tasks golden test now renders without draining any overflow |

Phase 3 — Golden snapshot scaffolding (2026-06-08) ​

Golden tests for the highest-traffic presentational widgets, on the existing RUN_GOLDENS gate + golden_toolkit + goldenWrapper (brand × locale matrix — Aldilaijan/Khobara × ar/en; the app is dark-only, so brand×locale is the meaningful axis, not light/dark):

  • pre-existing: metric_tile_golden_test.dart (3 scenarios)
  • new: empty_state, error_state, brand_chip, property_card, valuation_card golden tests — 5 files, 20 scenarios. All hermetic (pure presentational widgets, no providers/Supabase).

Without RUN_GOLDENS=1 these run as smoke tests (the widget pumps; screenMatchesGolden is a no-op) — already green in the normal suite. To generate + commit the baseline PNGs (a macOS renderer is required — Windows/Linux baselines diverge):

RUN_GOLDENS=1 flutter test --update-goldens --tags golden   # generate on a macOS runner
RUN_GOLDENS=1 flutter test --tags golden                    # verify

Baselines are intentionally not committed from this (Windows) machine; generate them on a macOS CI lane (e.g. the existing iOS lane) or a Mac, then commit test/**/goldens/*.png. The Linux/Windows flutter test lanes stay green meanwhile (gate off → smoke only).

Aldilaijan & Khobara Real Estate Platform