@@ -18,8 +18,21 @@ private BunitJSModuleInterop SetupModule(bool beginReturns)
1818 return module ;
1919 }
2020
21+ // Waits for a JS invocation that no render follows (e.g. the transition completion fired from
22+ // OnAfterRenderAsync after the navigation's final render): render-driven WaitForAssertion never
23+ // re-checks in that window, so poll the interop log instead.
24+ private static async Task WaitForInvocationAsync ( BunitJSModuleInterop module , string identifier )
25+ {
26+ var deadline = DateTime . UtcNow . AddSeconds ( 5 ) ;
27+ while ( module . Invocations . All ( i => i . Identifier != identifier ) )
28+ {
29+ if ( DateTime . UtcNow > deadline ) Assert . Fail ( $ "JS invocation '{ identifier } ' was never made.") ;
30+ await Task . Delay ( 10 ) ;
31+ }
32+ }
33+
2134 [ TestMethod ]
22- public void Navigation_with_ViewTransitions_enabled_runs_the_begin_complete_handshake ( )
35+ public async Task Navigation_with_ViewTransitions_enabled_runs_the_begin_complete_handshake ( )
2336 {
2437 Services . Configure < BrouterOptions > ( o => o . ViewTransitions = true ) ;
2538 var module = SetupModule ( beginReturns : true ) ;
@@ -29,45 +42,71 @@ public void Navigation_with_ViewTransitions_enabled_runs_the_begin_complete_hand
2942 var cut = RenderComponent < NavigationTypeHost > ( ) ;
3043 cut . WaitForAssertion ( ( ) => cut . Find ( "[data-testid=a]" ) ) ;
3144
45+ var brouter = Services . GetRequiredService < IBrouter > ( ) ;
46+ await cut . InvokeAsync ( ( ) => brouter . Navigate ( "/b" ) ) ;
47+
48+ cut . WaitForAssertion ( ( ) => cut . Find ( "[data-testid=b]" ) ) ;
49+
50+ // Begin fired before the render, complete after it landed.
51+ Assert . IsTrue ( module . Invocations . Count ( i => i . Identifier == "beginViewTransition" ) >= 1 ) ;
52+ await WaitForInvocationAsync ( module , "completeViewTransition" ) ;
53+
54+ // Begin carries the direction token (a programmatic Navigate is a push), the
55+ // default-animations flag and the reduced-motion flag (both on by default), which
56+ // drive the built-in direction-aware animations.
57+ var begin = module . Invocations . First ( i => i . Identifier == "beginViewTransition" ) ;
58+ Assert . AreEqual ( "push" , begin . Arguments [ 0 ] ) ;
59+ Assert . AreEqual ( true , begin . Arguments [ 1 ] ) ;
60+ Assert . AreEqual ( true , begin . Arguments [ 2 ] ) ;
61+ }
62+
63+ [ TestMethod ]
64+ public void Unsupported_browser_skips_the_completion_round_trip ( )
65+ {
66+ Services . Configure < BrouterOptions > ( o => o . ViewTransitions = true ) ;
67+ var module = SetupModule ( beginReturns : false ) ;
68+
69+ var nav = Services . GetRequiredService < BunitNavigationManager > ( ) ;
70+ nav . NavigateTo ( "http://localhost/a" ) ;
71+ var cut = RenderComponent < NavigationTypeHost > ( ) ;
72+ cut . WaitForAssertion ( ( ) => cut . Find ( "[data-testid=a]" ) ) ;
73+
3274 var brouter = Services . GetRequiredService < IBrouter > ( ) ;
3375 cut . InvokeAsync ( ( ) => brouter . Navigate ( "/b" ) ) ;
3476
3577 cut . WaitForAssertion ( ( ) =>
3678 {
3779 Assert . IsNotNull ( cut . Find ( "[data-testid=b]" ) ) ;
38- // Begin fired before the render, complete after it landed.
3980 Assert . IsTrue ( module . Invocations . Count ( i => i . Identifier == "beginViewTransition" ) >= 1 ) ;
40- Assert . IsTrue ( module . Invocations . Count ( i => i . Identifier == "completeViewTransition" ) >= 1 ) ;
41-
42- // Begin carries the direction token (a programmatic Navigate is a push), the
43- // default-animations flag and the reduced-motion flag (both on by default), which
44- // drive the built-in direction-aware animations.
45- var begin = module . Invocations . First ( i => i . Identifier == "beginViewTransition" ) ;
46- Assert . AreEqual ( "push" , begin . Arguments [ 0 ] ) ;
47- Assert . AreEqual ( true , begin . Arguments [ 1 ] ) ;
48- Assert . AreEqual ( true , begin . Arguments [ 2 ] ) ;
81+ Assert . AreEqual ( 0 , module . Invocations . Count ( i => i . Identifier == "completeViewTransition" ) ) ;
4982 } ) ;
5083 }
5184
5285 [ TestMethod ]
53- public void Unsupported_browser_skips_the_completion_round_trip ( )
86+ public void Initial_load_does_not_start_a_transition ( )
5487 {
5588 Services . Configure < BrouterOptions > ( o => o . ViewTransitions = true ) ;
56- var module = SetupModule ( beginReturns : false ) ;
89+ var module = SetupModule ( beginReturns : true ) ;
5790
5891 var nav = Services . GetRequiredService < BunitNavigationManager > ( ) ;
5992 nav . NavigateTo ( "http://localhost/a" ) ;
6093 var cut = RenderComponent < NavigationTypeHost > ( ) ;
6194 cut . WaitForAssertion ( ( ) => cut . Find ( "[data-testid=a]" ) ) ;
6295
96+ // The first mount renders the initial route without any transition interop: there is no
97+ // outgoing page to animate from, and after prerendering a transition here would replay the
98+ // animation over the already-visible static HTML (a "double render" of the first page).
99+ Assert . AreEqual ( 0 , module . Invocations . Count ( i => i . Identifier == "beginViewTransition" ) ) ;
100+ Assert . AreEqual ( 0 , module . Invocations . Count ( i => i . Identifier == "completeViewTransition" ) ) ;
101+
102+ // A real navigation afterwards still animates.
63103 var brouter = Services . GetRequiredService < IBrouter > ( ) ;
64104 cut . InvokeAsync ( ( ) => brouter . Navigate ( "/b" ) ) ;
65105
66106 cut . WaitForAssertion ( ( ) =>
67107 {
68108 Assert . IsNotNull ( cut . Find ( "[data-testid=b]" ) ) ;
69109 Assert . IsTrue ( module . Invocations . Count ( i => i . Identifier == "beginViewTransition" ) >= 1 ) ;
70- Assert . AreEqual ( 0 , module . Invocations . Count ( i => i . Identifier == "completeViewTransition" ) ) ;
71110 } ) ;
72111 }
73112
0 commit comments