Write widget and accessibility unit tests
epic-no-access-screen-ui-task-011 — Write flutter_test widget tests for the no-access-screen-widget covering: (1) logo renders, (2) denial message appears, (3) portal link triggers url_launcher, (4) logout button triggers sign-out, (5) Semantics tree contains a heading node, (6) live region announcement fires on first build. Use AccessibilityGuideline.androidTapTargetGuideline and iOSTapTargetGuideline checks.
Acceptance Criteria
Technical Requirements
Execution Context
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.
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.
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.