high priority low complexity testing pending testing specialist Tier 4

Acceptance Criteria

A test file exists at test/widgets/no_access_screen_widget_test.dart with at least 8 distinct test cases
Test 1 — Logo renders: pumping the widget produces exactly one Image widget matching the organisation logo asset path; test passes for all organisation variants if the widget is organisation-aware
Test 2 — Denial message appears: a Text widget containing the expected denial message string is found in the widget tree after pump
Test 3 — Portal link triggers url_launcher: tapping the portal link widget causes the mocked url_launcher to receive the expected URL string; mock is set up with mockito or mocktail
Test 4 — Logout button triggers sign-out: tapping the logout button dispatches the expected sign-out event to the mocked BLoC or Riverpod notifier; no real Supabase call is made
Test 5 — Semantics heading node: find.bySemanticsLabel combined with SemanticsFlag.isHeader returns at least one node matching the page heading text
Test 6 — Live region fires on first build: after pumpWidget and pumpAndSettle, the SemanticsData for the live region node has SemanticsFlag.isLiveRegion set and the label contains the denial message
Test 7 — Android tap target guideline: expectLater(tester, meetsGuideline(androidTapTargetGuideline)) passes without assertion errors
Test 8 — iOS tap target guideline: expectLater(tester, meetsGuideline(iOSTapTargetGuideline)) passes without assertion errors
All tests are hermetic — no network calls, no real Supabase client, no real url_launcher; all external dependencies are mocked or stubbed
The test file runs successfully with flutter test with zero failures and zero skipped tests
Test descriptions are written in plain English and describe user-observable behaviour, not implementation details

Technical Requirements

frameworks
Flutter
flutter_test
BLoC
Riverpod
apis
url_launcher (mocked)
performance requirements
Each test case completes within 5 seconds — use pumpAndSettle with a timeout override if animations are present
security requirements
No real credentials or Supabase URLs may appear in test files — use placeholder strings
ui components
WidgetTester from flutter_test
SemanticsController for Semantics tree traversal
MockUrlLauncher (mocktail or mockito)
MockAuthBloc / MockAuthNotifier for sign-out interaction

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Mock url_launcher by overriding the platform channel: use the url_launcher_platform_interface package and register a MockUrlLauncherPlatform. This is the standard approach for testing URL launches without real device capabilities. For the live region test, pump the widget once and then query the SemanticsNode tree directly via tester.getSemantics(find.byKey(Key('no-access-live-region'))). For the heading test, use tester.getSemantics(find.text(expectedHeadingText)) and assert node.hasFlag(SemanticsFlag.isHeader).

Avoid snapshot/golden tests in this task (those are covered in task-012) — focus on behavioural assertions. If the widget uses BLoC, use the bloc_test package's MockBloc and whenListen helper to simulate state emission. If using Riverpod, override the relevant provider with a StateProvider or NotifierProvider that emits the no-access state. Keep the test widget wrapper minimal: MaterialApp > ProviderScope/BlocProvider > NoAccessScreenWidget.

Testing Requirements

This task IS the testing task. The test suite must use flutter_test exclusively (no integration_test package). Structure tests in group() blocks: 'rendering', 'interactions', 'accessibility'. Use setUp() to create the widget under test with all dependencies mocked.

For BLoC: provide a MockBloc via BlocProvider in the test widget tree. For Riverpod: use ProviderScope with overrides. For url_launcher: register a mock with the plugin channel using TestDefaultBinaryMessengerBinding. Ensure test isolation — each test calls setUp independently.

Code coverage target: 100% branch coverage of the no-access-screen-widget file.

Component
No-Access Screen
ui low
Epic Risks (2)
medium impact medium prob technical

Flutter's live region (SemanticsProperties.liveRegion) announcement may be delayed or swallowed by the OS accessibility engine if the Semantics tree is not fully built when the screen mounts, causing screen-reader users to miss the denial announcement.

Mitigation & Contingency

Mitigation: Trigger the live region announcement from a post-frame callback (WidgetsBinding.addPostFrameCallback) to ensure the Semantics tree is committed before the announcement fires. Test on both VoiceOver (iOS) and TalkBack (Android) physical devices.

Contingency: If live region timing is unreliable, fall back to using SemanticsService.announce() directly in the initState post-frame callback, which provides more deterministic announcement timing.

low impact low prob scope

The organisation logo may fail to load (network error, missing asset) leaving a broken image in an otherwise functional screen, degrading the professional appearance and potentially confusing users.

Mitigation & Contingency

Mitigation: Wrap the logo widget in an error builder that renders a styled fallback (organisation name text or a generic icon) when the logo asset or network image fails to load.

Contingency: If logo loading is persistently unreliable across organisations, remove the logo from the no-access screen entirely in favour of a text-only header using the organisation's display name from the design token system.