Repository navigation
test(router): restore Explore navigation coverage - #8950
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
rabble
left a comment
There was a problem hiding this comment.
Reviewed at 8980088. No correctness findings in the test changes: the restored test taps the real Explore navigation control, the grid-mode assertion checks the rendered tab bar, and the removed fresh-first tests did not establish the behavior they claimed.
Local verification: all 9 focused router/provider tests passed; flutter analyze --no-pub lib test integration_test reported no issues.
Merge prerequisite: this head conflicts with current main in mobile/scripts/baseline/skip_test_ceilings.txt. The skip-ceiling check also fails against current origin/main because the branch still contains the two profile-redirect entries that main removed. Rebase and preserve both sets of reductions: this PR's fresh-first/Explore entries and main's profile-redirect entries. Rerun the guard and tests after resolution. Leaving this as a comment review pending that update; no branch changes were made.
NotThatKindOfDrLiz
left a comment
There was a problem hiding this comment.
Approved after takeover cleanup at 8980088. The restored navigation tests now have failure-safe cleanup, use the production semantic anchor, and rely on the shell-owned retap cubit. Misleading provider tests were removed and insertion-order emission is asserted directly. All applicable checks are green.
|
@realmeylisdev I took over the final review cleanup in Fixed:
Escalated: whether discovery should prioritize unseen videos remains a product decision; this PR accurately preserves and tests today's insertion-order behavior. Declined: none. Verification is green: 9 targeted tests, full Please review the takeover commit and merge when you're ready. |
Review: Four findings (non-blocking)All changes are technically sound. These are nits and one mandatory follow-up for the backlog. Finding 1: Container disposal not guarded in
|
8980088 to
2165c91
Compare
This comment has been minimized.
This comment has been minimized.
NotThatKindOfDrLiz
left a comment
There was a problem hiding this comment.
Approved on rebased head 2165c91. The takeover cleanup remains intact, the skip-baseline conflict was resolved by preserving both independent reductions, and all applicable checks are green, including all four test shards and Mobile CI.
rabble
left a comment
There was a problem hiding this comment.
Re-reviewed rebased head 2165c91. The prerequisite in my previous review is resolved: both sets of skip-baseline reductions are preserved, and GitHub reports the branch mergeable.
Verified again on this head: 9 focused router/provider tests passed, flutter analyze --no-pub lib test integration_test reported no issues, and check_skip_ceiling.sh passed with 6 tracked keys and no growth. GitHub checks are green. No additional findings.
|
@realmeylisdev takeover follow-up: I rebased the PR onto current Fixed in the takeover commit:
The separate product decision about whether discovery should order unseen videos first remains intentionally outside this test-only PR; there is no implementation of that behavior to preserve here. No findings were declined. Verification on current head
I approved the rebased head and re-requested @rabble. Please review the takeover changes and merge when you’re happy with them. |
|
Update: @rabble has now re-reviewed and approved rebased head |
|
Pushed two commits on top of @NotThatKindOfDrLiz's
So the file is restored here: 4 directives that were disabling 9 tests, since three of them sat on a One thing genuinely needs separate eyes: the branch now carries a production change, Also noticed you dropped the Full suite on the rebased branch: 20000 passed, 41 skipped, 0 failed. Watching CI now. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
CI went red on my previous push and is green again on
The cause was reach. My first commit stubbed the relay-status surface inside
Verified by reproducing CI's shard locally rather than guessing: One honest caveat: my local shard took 5m56s against CI's 24m54s, so a 10-minute timeout is something this machine may simply be too fast to reproduce. The local green is supporting evidence, not proof on its own — the green CI run on this head is the actual verdict. |
|
Both approvals here predate every commit currently on the branch, so please do not merge on them as they stand. @NotThatKindOfDrLiz approved at 00:37Z and 00:53Z, @rabble at 01:02Z. The three commits now on the head landed at 01:55Z ( That matters more than usual here, because what landed after the approvals includes:
I raised the same pattern on #8885 a few hours ago and it would be inconsistent not to raise it on my own PR. A fresh look at |
rabble
left a comment
There was a problem hiding this comment.
Reviewed commit 78ff70e.
No blocking findings. Re-reviewed the expanded real-shell navigation coverage, relay-status fixture isolation, and hashtag service disposal added since my previous approval. The provider now cancels the service timer and removes its listener on disposal. Validation: all 20 focused router, provider-listener, and hashtag tests passed; full app analysis and the skip-ceiling guard passed.
createMockNostrService left relayStatuses, relayStatusStream, getRelayPoolCounters and defaultRelayUrl unstubbed, so any test that pumps the app shell died in AppShellSideEffects with type 'Null' is not a subtype of type 'Map<String, RelayConnectionStatus>' before the shell rendered. Four of #4836's baselined router/provider files fail on exactly that, which is why they were annotated with downstream symptoms nobody could reach. Same shape as the queryEvents and subscribe stubs directly above: a non-null default, with the error it prevents named in the comment.
Un-skips the tab-tap test and rebuilds its harness. Three things were
wrong: a bare ProviderContainer with no overrides, a cast of
Scaffold.bottomNavigationBar that cannot work now the shell renders
VineBottomNav inside a Column, and no unmount before the test ended,
which tripped the pending-timer check on the shell's periodic
relay-status timer.
Taps through find.bySemanticsIdentifier('explore_tab') per
.claude/rules/accessibility.md rather than reaching for the widget.
Verified it can fail: changing the explore branch of
VineBottomNav._handleTabTap to ExploreScreen.pathForIndex(lastIndex ?? 0)
turns it red, which is the grid-vs-feed regression it exists to catch.
Un-skips the second test in the file. It failed on the missing appVersionProvider override, and behind that it pinned three tab names that no longer exist: Explore renders Classics / New / Popular, not "New Vines", "Trending" and "Editor's Pick". Rewrites the assertions against ExploreTabBar rather than individual labels. The tab list is server-driven, so pinning names re-breaks the test whenever Explore gains or renames a tab, and the contract the test was written for is that grid mode shows the bar at all instead of the feed's empty state. The empty-state string now resolves through AppLocalizations per .claude/rules/localization.md instead of being hardcoded English. Verified it can fail: hiding ExploreTabBar in ExploreView turns it red.
The baseline annotated this `# quarantine: real relay via realintegrationtesthelper, 40s timeout`. It never reached a relay: it died at 00:00 on `sharedPreferencesProvider must be overridden`, so the recorded reason described a blocker nobody had observed. Deleting rather than repairing, on two of #4836's delete criteria. No meaningful assertion: the ordering claim is wrapped in `if (unseenIndices.isNotEmpty && seenIndices.isNotEmpty)`, so an empty or all-seen relay response passes it without checking anything — the same vacuity shape as the m3u8 row. Removed behavior: I rebuilt it hermetically against the harness in video_events_provider_listener_test, feeding the service [seen1, unseen1, seen2, unseen2] with seen1/seen2 marked seen. The provider emits that order back unchanged. Nothing under lib/ orders the discovery feed by seen state — videoEventsProvider watches seenVideosState but only threads it into subscription setup, and VideoEventService's seenEventIds is pagination dedup. So the contract this test asserted is not implemented at the layer it asserted it. Whether the feed should order unseen-first is a product question, and #4836 says to separate a defect a restored test exposes rather than patch it here. Raised on the issue instead.
Regenerates scripts/baseline/skip_test_ceilings.txt after the three reductions in this branch: 11 files / 19 skips down to 8 files / 16. Also makes the restored Explore MaterialApp const, which the analyzer flagged once the widget stopped taking a runtime container.
…poses HashtagService's constructor starts a 1-minute periodic timer and registers a listener on VideoEventService. It has a dispose() that undoes both, and nothing called it. The provider watches videoEventServiceProvider and hashtagCacheServiceProvider, so it rebuilds whenever either changes, and every rebuild stranded a live timer and a listener on the discarded instance. `ref.onDispose` is the one-line fix. Found by restoring app_shell_integration_test: the suite kept failing on "A Timer is still pending even after the widget tree was disposed" with the timer traced to HashtagService's constructor, and disposing the ProviderContainer did not stop it. This is a product change in an otherwise test-only PR, so it is called out separately in the description rather than folded in with the test restorations.
Un-skips all four groups in app_shell_integration_test — 4 skip
directives that were disabling 9 tests, since each sat on a group()
rather than a test.
Harness: the shared container now uses getStandardTestOverrides with an
authenticated auth service (the router sends a signed-out session to
/welcome), resolves both async route gates before pumping, provides the
five blocs the shell and profile route read, and overrides the
side-effect bridges. Tab selection reads VineBottomNav.currentIndex — the
shell has no BottomNavigationBar to cast — and taps go through semantics
identifiers. Each test unmounts and disposes its container inline,
because the pending-timer check runs before addTearDown callbacks.
Three assertions were wrong about current behaviour rather than merely
unrunnable:
- "/hashtag/rust selects Tags tab" expected index 2. There is no Tags
tab; index 2 is the inbox, and a hashtag feed is registered on the root
navigator ("standalone screen (no bottom nav)" in search_routes.dart),
so it is presented above the shell instead of selecting a tab. Now
asserts HashtagFeedScreen renders.
- "/explore/0" after tapping Explore contradicted
explore_tab_navigation_test, which exists to prevent exactly that. Now
asserts /explore.
- "/profile/me/5" expected the placeholder to survive; the profile route
resolves it to the signed-in npub. Asserted against a literal rather
than through the encoder.
"can navigate back within tab stack" is deleted. go() replaces the
location inside a shell branch, so two go() calls leave nothing on the
stack and pop throws "There is nothing to pop" — it asserted a mechanism
the app does not use, and back behaviour has live coverage in
navigation/back_navigation_policy_test.
Verified it can fail: pinning ShellChrome's currentIndex to 0 turns 4 of
the 8 red.
Baseline drops to 7 files / 12 directives.
The first version stubbed relayStatuses, relayStatusStream, getRelayPoolCounters and defaultRelayUrl inside createMockNostrService, which every suite using getStandardTestOverrides inherits — around 150 files. That is more reach than the change needs. relayStatisticsBridge opens a subscription and starts a 3-second periodic timer as soon as those reads stop throwing, and no suite should inherit that just for asking for a standard mock. Moves them to createMockNostrServiceWithRelayStatus(), which the three shell-pumping tests ask for explicitly. createMockNostrService is byte-for-byte back to its origin/main behaviour, so this file is now purely additive.
78ff70e to
321a601
Compare
Mobile PR PreviewPreview refreshed for Last refresh:
|
|
Rebased onto current The approved content is unchanged. @rabble's approval is recorded against Eight of nine are identical. The one marked Re-verified against Flutter 3.47.2Worth calling out, because the 16 commits this rebase absorbed include #8915. The branch was written and verified entirely on 3.44.9, so the whole ladder was re-run rather than assumed:
Two things I had expected to break did not, and are worth recording so nobody re-checks them: the 3.47 formatter left all six files untouched, and the riverpod I have not merged — this repo merges through the queue and the decision is not mine. @rabble @NotThatKindOfDrLiz, over to you. |
Description
Four test files that navigate the app's bottom tabs were switched off with
skip: trueand stayed off. This turns them back on and fixes the two underlying causes that kept them off.The baseline annotations named the wrong blocker.
skip_test_ceilings.txtdescribed things like "explore tabs renamed" and "shell nav bar is a column, bottomnavigationbar cast dies" — real problems, but no test ever reached them. Every one of these files died first on:createMockNostrService()never stubbed the relay-status surface, so anything pumping the app shell threw insideAppShellSideEffectsbefore a widget rendered. That one helper is why four rows carried annotations describing blockers nobody had observed. The fix follows the shape already used directly above it — a non-null default, with the error it prevents named in the comment.These rows disabled far more than they appeared to. Three of the four
skip: truedirectives inapp_shell_integration_testsat on agroup(), so 4 directives were disabling 9 tests. Across this PR, 7 directives were holding 12 tests off.What each restored file now covers:
/explore(grid), not/explore/0(feed). Taps throughfind.bySemanticsIdentifierper.claude/rules/accessibility.md; the shell rendersVineBottomNavinside aColumn, so there is noBottomNavigationBar.onTapto invoke.New Vines,Trending,Editor's Pick; the tabs are Classics / New / Popular). The tab list is server-driven, so this asserts onExploreTabBarinstead, and the empty-state string resolves throughAppLocalizationsrather than being hardcoded.Three assertions were wrong about current behaviour
Un-skipping surfaced expectations that no longer describe the app:
"/hashtag/rust selects Tags tab"expected index 2. There is no Tags tab — index 2 is the inbox — and a hashtag feed is registered on the root navigator (search_routes.dart: "standalone screen (no bottom nav)"), so it is presented above the shell rather than selecting a tab./explore/0. That is the exact bugexplore_tab_navigation_testexists to prevent."/profile/me/5"was expected to survive; the profile route resolves it to the signed-in npub. Asserted against a literal rather than through the encoder, so it cannot pass for a broken encoder."can navigate back within tab stack"is deleted.go()replaces the location inside a shell branch, so twogo()calls leave nothing on the stack andpop()throws "There is nothing to pop" — it asserted a mechanism the app does not use, and back behaviour has live coverage innavigation/back_navigation_policy_test.Why one file was deleted instead of fixed
video_events_provider_fresh_test.dartwas annotated# quarantine: real relay ... 40s timeout. It never reached a relay — it died at 00:00 on the missing provider override.Its ordering claim sits inside
if (unseenIndices.isNotEmpty && seenIndices.isNotEmpty), so an empty or all-seen response passes it without checking anything. And the behaviour it asserted is not implemented: rebuilt hermetically against the harness invideo_events_provider_listener_test, fed[seen1, unseen1, seen2, unseen2]withseen1/seen2marked seen, the provider emits that order straight back. Nothing underlib/orders the discovery feed by seen state.Whether the feed should order unseen-first is a product question this PR does not answer. It is raised on #4836 instead.
Related Issue: Relates to #4836
Unrequested change — please look at this separately
fix(hashtags): cancel the hashtag service timer when its provider disposesis a production change, not a test fix, and it is the onlylib/change here.HashtagService's constructor starts a 1-minute periodic timer and registers a listener onVideoEventService. It has adispose()that undoes both, and nothing called it. The provider watches two other providers, so it rebuilds whenever either changes, and every rebuild stranded a live timer and a listener on the discarded instance.ref.onDispose(service.dispose)is the fix.It surfaced because the restored app-shell suite kept failing on "A Timer is still pending even after the widget tree was disposed", traced to that constructor, and disposing the
ProviderContainerdid not stop it. Flagging it on its own because it changes shipped behaviour and was not part of the requested work — the effect is that the timer and listener now stop when the provider goes away.Out of Scope
Verification
flutter analyze lib test integration_test— clean.flutter test test --exclude-tags "integration || golden"— 20000 passed, 41 skipped, 0 failed on the rebased branch. The shared helper is imported by 154 test files, so the whole suite was run rather than the touched ones.VineBottomNav._handleTabTap's explore branch atExploreScreen.pathForIndex(lastIndex ?? 0)turns it red.ExploreTabBarturns it red.ShellChrome'scurrentIndexto 0 turns 4 of the 8 red.bash scripts/check_skip_ceiling.shpasses. This PR removes 4 files / 7 directives / 12 disabled tests; combined with test(profile): restore current-user redirect coverage #8885 landing, the baseline is now 5 files / 10 directives, down from 11 / 19.Type of Change