55
66using  System . Reflection ; 
77using  Microsoft . AspNetCore . Components . RenderTree ; 
8+ using  Microsoft . AspNetCore . Components . Rendering ; 
89using  Microsoft . AspNetCore . Components . Test . Helpers ; 
910using  Microsoft . Extensions . DependencyInjection ; 
1011using  Microsoft . Extensions . Logging ; 
@@ -301,6 +302,192 @@ await renderer.Dispatcher.InvokeAsync(() =>
301302        Assert . Contains ( "Use either NotFound or NotFoundPage" ,  exception . Message ) ; 
302303    } 
303304
305+     [ Fact ] 
306+     public  async  Task  OnNotFound_WithNotFoundPageSet_UsesNotFoundPage ( ) 
307+     { 
308+         // Create a new router instance for this test to control Attach() timing 
309+         var  services  =  new  ServiceCollection ( ) ; 
310+         var  testNavManager  =  new  TestNavigationManager ( ) ; 
311+         services . AddSingleton < ILoggerFactory > ( NullLoggerFactory . Instance ) ; 
312+         services . AddSingleton < NavigationManager > ( testNavManager ) ; 
313+         services . AddSingleton < INavigationInterception ,  TestNavigationInterception > ( ) ; 
314+         services . AddSingleton < IScrollToLocationHash ,  TestScrollToLocationHash > ( ) ; 
315+         var  serviceProvider  =  services . BuildServiceProvider ( ) ; 
316+ 
317+         var  testRenderer  =  new  TestRenderer ( serviceProvider ) ; 
318+         testRenderer . ShouldHandleExceptions  =  true ; 
319+         var  testRouter  =  ( Router ) testRenderer . InstantiateComponent < Router > ( ) ; 
320+         testRouter . AppAssembly  =  Assembly . GetExecutingAssembly ( ) ; 
321+         testRouter . Found  =  routeData =>  ( builder )  =>  builder . AddContent ( 0 ,  $ "Rendering route matching { routeData . PageType } ") ; 
322+ 
323+         var  parameters  =  new  Dictionary < string ,  object > 
324+         { 
325+             {  nameof ( Router . AppAssembly ) ,  typeof ( RouterTest ) . Assembly  } , 
326+             {  nameof ( Router . NotFoundPage ) ,  typeof ( NotFoundTestComponent )  } 
327+         } ; 
328+ 
329+         // Assign the root component ID which will call Attach() 
330+         testRenderer . AssignRootComponentId ( testRouter ) ; 
331+ 
332+         // Act 
333+         await  testRenderer . Dispatcher . InvokeAsync ( ( )  => 
334+             testRouter . SetParametersAsync ( ParameterView . FromDictionary ( parameters ) ) ) ; 
335+ 
336+         // Trigger the NavigationManager's OnNotFound event 
337+         await  testRenderer . Dispatcher . InvokeAsync ( ( )  =>  testNavManager . TriggerNotFound ( ) ) ; 
338+ 
339+         // Assert 
340+         var  lastBatch  =  testRenderer . Batches . Last ( ) ; 
341+         var  renderedFrame  =  lastBatch . ReferenceFrames . First ( ) ; 
342+         Assert . Equal ( RenderTreeFrameType . Component ,  renderedFrame . FrameType ) ; 
343+         Assert . Equal ( typeof ( RouteView ) ,  renderedFrame . ComponentType ) ; 
344+ 
345+         // Verify that the RouteData contains the NotFoundTestComponent 
346+         var  routeViewFrame  =  lastBatch . ReferenceFrames . Skip ( 1 ) . First ( ) ; 
347+         Assert . Equal ( RenderTreeFrameType . Attribute ,  routeViewFrame . FrameType ) ; 
348+         var  routeData  =  ( RouteData ) routeViewFrame . AttributeValue ; 
349+         Assert . Equal ( typeof ( NotFoundTestComponent ) ,  routeData . PageType ) ; 
350+     } 
351+ 
352+     [ Fact ] 
353+     public  async  Task  OnNotFound_WithArgsPathSet_RendersComponentByRoute ( ) 
354+     { 
355+         // Create a new router instance for this test to control Attach() timing 
356+         var  services  =  new  ServiceCollection ( ) ; 
357+         var  testNavManager  =  new  TestNavigationManager ( ) ; 
358+         services . AddSingleton < ILoggerFactory > ( NullLoggerFactory . Instance ) ; 
359+         services . AddSingleton < NavigationManager > ( testNavManager ) ; 
360+         services . AddSingleton < INavigationInterception ,  TestNavigationInterception > ( ) ; 
361+         services . AddSingleton < IScrollToLocationHash ,  TestScrollToLocationHash > ( ) ; 
362+         var  serviceProvider  =  services . BuildServiceProvider ( ) ; 
363+ 
364+         var  testRenderer  =  new  TestRenderer ( serviceProvider ) ; 
365+         testRenderer . ShouldHandleExceptions  =  true ; 
366+         var  testRouter  =  ( Router ) testRenderer . InstantiateComponent < Router > ( ) ; 
367+         testRouter . AppAssembly  =  Assembly . GetExecutingAssembly ( ) ; 
368+         testRouter . Found  =  routeData =>  ( builder )  =>  builder . AddContent ( 0 ,  $ "Rendering route matching { routeData . PageType } ") ; 
369+ 
370+         var  parameters  =  new  Dictionary < string ,  object > 
371+         { 
372+             {  nameof ( Router . AppAssembly ) ,  typeof ( RouterTest ) . Assembly  } 
373+         } ; 
374+ 
375+         // Subscribe to OnNotFound event BEFORE router attaches and set args.Path 
376+         testNavManager . OnNotFound  +=  ( sender ,  args )  => 
377+         { 
378+             args . Path  =  "/jan" ;  // Point to an existing route 
379+         } ; 
380+ 
381+         // Assign the root component ID which will call Attach() 
382+         testRenderer . AssignRootComponentId ( testRouter ) ; 
383+ 
384+         // Act 
385+         await  testRenderer . Dispatcher . InvokeAsync ( ( )  => 
386+             testRouter . SetParametersAsync ( ParameterView . FromDictionary ( parameters ) ) ) ; 
387+ 
388+         // Trigger the NavigationManager's OnNotFound event 
389+         await  testRenderer . Dispatcher . InvokeAsync ( ( )  =>  testNavManager . TriggerNotFound ( ) ) ; 
390+ 
391+         // Assert 
392+         var  lastBatch  =  testRenderer . Batches . Last ( ) ; 
393+         var  renderedFrame  =  lastBatch . ReferenceFrames . First ( ) ; 
394+         Assert . Equal ( RenderTreeFrameType . Component ,  renderedFrame . FrameType ) ; 
395+         Assert . Equal ( typeof ( RouteView ) ,  renderedFrame . ComponentType ) ; 
396+ 
397+         // Verify that the RouteData contains the correct component type 
398+         var  routeViewFrame  =  lastBatch . ReferenceFrames . Skip ( 1 ) . First ( ) ; 
399+         Assert . Equal ( RenderTreeFrameType . Attribute ,  routeViewFrame . FrameType ) ; 
400+         var  routeData  =  ( RouteData ) routeViewFrame . AttributeValue ; 
401+         Assert . Equal ( typeof ( JanComponent ) ,  routeData . PageType ) ; 
402+     } 
403+ 
404+     [ Fact ] 
405+     public  async  Task  OnNotFound_WithBothNotFoundPageAndArgsPath_PreferArgs ( ) 
406+     { 
407+         // Create a new router instance for this test to control Attach() timing 
408+         var  services  =  new  ServiceCollection ( ) ; 
409+         var  testNavManager  =  new  TestNavigationManager ( ) ; 
410+         services . AddSingleton < ILoggerFactory > ( NullLoggerFactory . Instance ) ; 
411+         services . AddSingleton < NavigationManager > ( testNavManager ) ; 
412+         services . AddSingleton < INavigationInterception ,  TestNavigationInterception > ( ) ; 
413+         services . AddSingleton < IScrollToLocationHash ,  TestScrollToLocationHash > ( ) ; 
414+         var  serviceProvider  =  services . BuildServiceProvider ( ) ; 
415+ 
416+         var  testRenderer  =  new  TestRenderer ( serviceProvider ) ; 
417+         testRenderer . ShouldHandleExceptions  =  true ; 
418+         var  testRouter  =  ( Router ) testRenderer . InstantiateComponent < Router > ( ) ; 
419+         testRouter . AppAssembly  =  Assembly . GetExecutingAssembly ( ) ; 
420+         testRouter . Found  =  routeData =>  ( builder )  =>  builder . AddContent ( 0 ,  $ "Rendering route matching { routeData . PageType } ") ; 
421+ 
422+         var  parameters  =  new  Dictionary < string ,  object > 
423+         { 
424+             {  nameof ( Router . AppAssembly ) ,  typeof ( RouterTest ) . Assembly  } , 
425+             {  nameof ( Router . NotFoundPage ) ,  typeof ( NotFoundTestComponent )  } 
426+         } ; 
427+ 
428+         // Subscribe to OnNotFound event BEFORE router attaches and sets up its own subscription 
429+         testNavManager . OnNotFound  +=  ( sender ,  args )  => 
430+         { 
431+             args . Path  =  "/jan" ;  // This should take precedence over NotFoundPage 
432+         } ; 
433+ 
434+         // Now assign the root component ID which will call Attach() 
435+         testRenderer . AssignRootComponentId ( testRouter ) ; 
436+ 
437+         await  testRenderer . Dispatcher . InvokeAsync ( ( )  => 
438+             testRouter . SetParametersAsync ( ParameterView . FromDictionary ( parameters ) ) ) ; 
439+ 
440+         // trigger the NavigationManager's OnNotFound event 
441+         await  testRenderer . Dispatcher . InvokeAsync ( ( )  =>  testNavManager . TriggerNotFound ( ) ) ; 
442+ 
443+         // The Router should have rendered using RenderComponentByRoute (args.Path) instead of NotFoundPage 
444+         var  lastBatch  =  testRenderer . Batches . Last ( ) ; 
445+         var  renderedFrame  =  lastBatch . ReferenceFrames . First ( ) ; 
446+         Assert . Equal ( RenderTreeFrameType . Component ,  renderedFrame . FrameType ) ; 
447+         Assert . Equal ( typeof ( RouteView ) ,  renderedFrame . ComponentType ) ; 
448+ 
449+         // Verify that the RouteData contains the JanComponent (from args.Path), not NotFoundTestComponent 
450+         var  routeViewFrame  =  lastBatch . ReferenceFrames . Skip ( 1 ) . First ( ) ; 
451+         Assert . Equal ( RenderTreeFrameType . Attribute ,  routeViewFrame . FrameType ) ; 
452+         var  routeData  =  ( RouteData ) routeViewFrame . AttributeValue ; 
453+         Assert . Equal ( typeof ( JanComponent ) ,  routeData . PageType ) ; 
454+     } 
455+ 
456+     [ Fact ] 
457+     public  async  Task  FindComponentTypeByRoute_WithValidRoute_ReturnsComponentType ( ) 
458+     { 
459+         var  parameters  =  new  Dictionary < string ,  object > 
460+         { 
461+             {  nameof ( Router . AppAssembly ) ,  typeof ( RouterTest ) . Assembly  } 
462+         } ; 
463+ 
464+         await  _renderer . Dispatcher . InvokeAsync ( ( )  => 
465+             _router . SetParametersAsync ( ParameterView . FromDictionary ( parameters ) ) ) ; 
466+ 
467+         var  result  =  _router . FindComponentTypeByRoute ( "/jan" ) ; 
468+         Assert . Equal ( typeof ( JanComponent ) ,  result ) ; 
469+     } 
470+ 
471+     [ Fact ] 
472+     public  async  Task  RenderComponentByRoute_WithInvalidRoute_ThrowsException ( ) 
473+     { 
474+         var  parameters  =  new  Dictionary < string ,  object > 
475+         { 
476+             {  nameof ( Router . AppAssembly ) ,  typeof ( RouterTest ) . Assembly  } 
477+         } ; 
478+ 
479+         await  _renderer . Dispatcher . InvokeAsync ( ( )  => 
480+             _router . SetParametersAsync ( ParameterView . FromDictionary ( parameters ) ) ) ; 
481+ 
482+         var  builder  =  new  RenderTreeBuilder ( ) ; 
483+ 
484+         var  exception  =  Assert . Throws < InvalidOperationException > ( ( )  => 
485+         { 
486+             _router . RenderComponentByRoute ( builder ,  "/nonexistent-route" ) ; 
487+         } ) ; 
488+         Assert . Contains ( "No component found for route '/nonexistent-route'" ,  exception . Message ) ; 
489+     } 
490+ 
304491    internal  class  TestNavigationManager  :  NavigationManager 
305492    { 
306493        public  TestNavigationManager ( )  => 
@@ -311,6 +498,11 @@ public void NotifyLocationChanged(string uri, bool intercepted, string state = n
311498            Uri  =  uri ; 
312499            NotifyLocationChanged ( intercepted ) ; 
313500        } 
501+ 
502+         public  void  TriggerNotFound ( ) 
503+         { 
504+             base . NotFound ( ) ; 
505+         } 
314506    } 
315507
316508    internal  sealed  class  TestNavigationInterception  :  INavigationInterception 
0 commit comments