medium priority low complexity testing pending testing specialist Tier 3

Acceptance Criteria

MapFilterPanel is collapsed by default and expands to full height on drag handle tap — verified by asserting widget height before and after tap
MapFilterPanel emits FilterChanged(availability: x) when the availability filter radio/toggle changes — verified via bloc_test expectLater
MapFilterPanel emits FilterChanged(specialisations: [...]) with the correct updated list when a specialisation chip is tapped — verified for add and remove cases
Multi-select specialisation chip updates its visual state (selected/unselected) and its Semantics checked property in sync — verified via semantic tree
Drag handle widget has a Semantics label 'Filter panel, drag to expand/collapse' or equivalent — verified via find.bySemanticsLabel
Filter changes do not trigger a Supabase data fetch — the MapBloc must not emit a MentorsLoading state after a filter change, only a MentorsFiltered state
All existing filters remain set after panel collapse and re-expansion — BLoC state is not reset on panel animation
Tests cover at least: zero chips selected, one chip selected, all chips selected, panel collapse mid-filter-edit

Technical Requirements

frameworks
Flutter
flutter_test
BLoC
bloc_test
data models
MapFilterState
SpecialisationTag
performance requirements
Panel expand/collapse animation must complete within 300ms — assert no pending timers after pumpAndSettle(duration: Duration(milliseconds: 350))
ui components
MapFilterPanel
SpecialisationChip
DragHandle
AvailabilityFilter

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Inject the MapBloc mock via BlocProvider in the widget test to intercept emitted events. Use a SpyBloc (subclass that records events) rather than a full mock if the MapBloc has complex internal state that affects child widget rendering. For the drag handle collapse/expand test, assert the Visibility or SliverToBoxAdapter height rather than pixel positions, which can vary by device. Do not test animation curves — test observable outcomes (height, semantic label) at animation end state only.

Testing Requirements

Widget tests using flutter_test and bloc_test. Use blocTest() to assert event sequences on the MapBloc mock. For animation timing, use tester.pump(Duration(milliseconds: 350)) rather than pumpAndSettle to avoid infinite animation loops if any animation does not terminate. Assert chip semantics with tester.getSemantics(find.byKey(Key('chip-{id}'))).hasFlag(SemanticsFlag.isChecked).

All tests must be deterministic — no timers or async gaps beyond controlled pump calls.

Component
Map Filter Panel
ui medium
Epic Risks (3)
high impact high prob technical

Flutter's map canvas (flutter_map) does not natively support semantic focus traversal for screen readers, meaning map markers may be entirely invisible to VoiceOver/TalkBack users. If the accessible list fallback is not treated as a first-class view, screen reader users will have no access to the feature.

Mitigation & Contingency

Mitigation: From sprint 1, treat mentor-list-fallback as a fully featured primary view, not an afterthought. Implement and test it in parallel with the map canvas. Make the view-toggle-button keyboard focusable and announced on every screen state. Conduct VoiceOver testing on device before submitting each PR touching UI components.

Contingency: If map canvas accessibility cannot be achieved for marker focus traversal, make the view-toggle-button the default focus target on screen load for VoiceOver users (detected via screen-reader-detection-service) so they are immediately directed to the list fallback without needing to discover the toggle.

medium impact medium prob technical

The mentor-info-popup must occupy no more than 40% of visible map area on small screens. On devices with screen heights under 667px (iPhone SE), overlapping with the filter panel or obscuring most of the map could severely degrade usability.

Mitigation & Contingency

Mitigation: Implement the popup as a bottom sheet capped at 40% of screen height with a ScrollView for overflow content. Test on iPhone SE (375x667pt) and the smallest commonly used Android form factor in the device lab. Define max-height as a percentage constant in location-privacy-config or design tokens.

Contingency: If the popup cannot fit all required fields within 40% height on smallest targets, truncate assigned contact count and certification badge to icons-only in the compact view, with a 'View Profile' button always visible at the bottom of the popup regardless of scroll position.

medium impact medium prob integration

Filter state must remain perfectly synchronised between the map view and the list fallback. If the filter panel emits state that is not consumed identically by both views, coordinators switching between views will see inconsistent mentor sets, eroding trust in the feature.

Mitigation & Contingency

Mitigation: Store active filter criteria in a single shared Riverpod provider owned by the map-view-screen and consumed by both map-marker-widget (via mentor-location-service) and mentor-list-fallback. Write integration tests that apply a filter, switch views, and assert identical mentor counts in both views.

Contingency: If filter sync proves brittle, simplify to a single filter state object passed explicitly as a constructor argument to both views on each rebuild, eliminating indirect state sharing.