1- using Microsoft . AspNetCore . Hosting ;
1+ using Microsoft . AspNetCore . Hosting ;
22using Microsoft . Extensions . DependencyInjection ;
33using Microsoft . Extensions . Logging ;
44using Sudoku . Blazor . Models ;
@@ -9,117 +9,120 @@ namespace UnitTests.Blazor.Pages;
99
1010public class IndexPageTests : BunitContext
1111{
12- private const string Alias = "test-alias" ;
13- private readonly Mock < IGameManager > _mockGameManager = new ( ) ;
14- private readonly Mock < IPlayerManager > _mockPlayerManager = new ( ) ;
12+ private readonly Mock < ILocalStorageService > _mockLocalStorage = new ( ) ;
1513
1614 public IndexPageTests ( )
1715 {
18- var savedGames = new List < GameModel >
19- {
20- new ( ) { Id = Guid . NewGuid ( ) . ToString ( ) , PlayerAlias = Alias } ,
21- new ( ) { Id = Guid . NewGuid ( ) . ToString ( ) , PlayerAlias = Alias } ,
22- } ;
23- _mockGameManager . SetupLoadGamesAsync ( savedGames ) ;
24- _mockPlayerManager . SetupGetCurrentPlayerAsync ( Alias ) ;
25- Services . AddSingleton ( _mockGameManager . Object ) ;
26- Services . AddSingleton ( _mockPlayerManager . Object ) ;
27-
28- // Add IWebHostEnvironment mock for error boundary
16+ Services . AddSingleton ( _mockLocalStorage . Object ) ;
17+
2918 var mockWebHostEnvironment = new Mock < IWebHostEnvironment > ( ) ;
3019 mockWebHostEnvironment . Setup ( x => x . EnvironmentName ) . Returns ( "Test" ) ;
3120 Services . AddSingleton ( mockWebHostEnvironment . Object ) ;
32-
33- // Add logger mocks
21+
3422 Services . AddSingleton ( new Mock < ILogger < IndexPage > > ( ) . Object ) ;
3523 Services . AddSingleton ( new Mock < ILogger < Sudoku . Blazor . Components . IndexErrorBoundary > > ( ) . Object ) ;
3624 }
3725
38- [ Fact ]
39- public void DeleteGame_RemovesGameFromList ( )
26+ private void SetupReturningPlayer ( string alias = "test-alias" )
4027 {
41- // Arrange
42- var component = Render < IndexPage > ( ) ;
43- var delGameElement = component . Find ( ".del-game-icon" ) ;
28+ var profile = new ProfileInfo { ProfileId = Guid . NewGuid ( ) . ToString ( ) , Alias = alias } ;
29+ _mockLocalStorage . Setup ( x => x . GetProfileAsync ( ) ) . ReturnsAsync ( profile ) ;
30+ _mockLocalStorage . Setup ( x => x . GetAliasAsync ( ) ) . ReturnsAsync ( ( string ? ) null ) ;
31+ }
4432
45- // Act
46- delGameElement . Click ( ) ;
33+ private void SetupNewPlayer ( )
34+ {
35+ _mockLocalStorage . Setup ( x => x . GetProfileAsync ( ) ) . ReturnsAsync ( ( ProfileInfo ? ) null ) ;
36+ _mockLocalStorage . Setup ( x => x . GetAliasAsync ( ) ) . ReturnsAsync ( ( string ? ) null ) ;
37+ }
4738
48- // Assert
49- component . FindAll ( ".del-game-icon" ) . Count . Should ( ) . Be ( 1 ) ;
39+ private void SetupLegacyPlayer ( string alias )
40+ {
41+ _mockLocalStorage . Setup ( x => x . GetProfileAsync ( ) ) . ReturnsAsync ( ( ProfileInfo ? ) null ) ;
42+ _mockLocalStorage . Setup ( x => x . GetAliasAsync ( ) ) . ReturnsAsync ( alias ) ;
43+ _mockLocalStorage . Setup ( x => x . SetProfileAsync ( It . IsAny < ProfileInfo > ( ) ) ) . Returns ( Task . CompletedTask ) ;
44+ _mockLocalStorage . Setup ( x => x . RemoveAliasAsync ( ) ) . Returns ( Task . CompletedTask ) ;
5045 }
5146
5247 [ Fact ]
53- public void DeleteGame_WhenClicked_RemovesGameFromGameStateManager ( )
48+ public void Render_NewPlayer_ShowsCreateProfileCard ( )
5449 {
55- // Arrange
50+ SetupNewPlayer ( ) ;
5651 var component = Render < IndexPage > ( ) ;
57- var delGameElement = component . Find ( ".del-game-icon" ) ;
58-
59- // Act
60- delGameElement . Click ( ) ;
61-
62- // Assert
63- _mockGameManager . VerifyDeleteGameAsyncCalled ( Times . Once ) ;
52+ var profileButton = component . Find ( "button:contains('Create Profile')" ) ;
53+ Assert . NotNull ( profileButton ) ;
6454 }
6555
6656 [ Fact ]
67- public void Render_WhenSavedGamesPresent_DisplaysEachSavedGame ( )
57+ public void Render_ReturningPlayer_ShowsManageProfileCard ( )
6858 {
69- // Arrange
59+ SetupReturningPlayer ( ) ;
7060 var component = Render < IndexPage > ( ) ;
71-
72- // Act
73- var delGameElements = component . FindAll ( ".del-game-icon" ) ;
74-
75- // Assert
76- delGameElements . Count . Should ( ) . Be ( 2 ) ;
61+ var profileButton = component . Find ( "button:contains('Manage Profile')" ) ;
62+ Assert . NotNull ( profileButton ) ;
7763 }
7864
7965 [ Fact ]
80- public void RendersCorrectly ( )
66+ public void Render_NewPlayer_StartNewGameIsDisabled ( )
8167 {
82- // Arrange
68+ SetupNewPlayer ( ) ;
8369 var component = Render < IndexPage > ( ) ;
84-
85- // Act
86- var startNewGameButton = component . Find ( "button:contains('Start New Game')" ) ;
87- var loadGameButton = component . Find ( "button:contains('Load Game')" ) ;
88-
89- // Assert
90- Assert . NotNull ( startNewGameButton ) ;
91- Assert . NotNull ( loadGameButton ) ;
70+ var startBtn = component . Find ( "button:contains('Start New Game')" ) ;
71+ Assert . True ( startBtn . HasAttribute ( "disabled" ) ) ;
9272 }
9373
9474 [ Fact ]
95- public void ShowsDifficultyOptions_WhenStartNewGameClicked ( )
75+ public void Render_NewPlayer_BrowseGameListIsDisabled ( )
9676 {
97- // Arrange
77+ SetupNewPlayer ( ) ;
9878 var component = Render < IndexPage > ( ) ;
79+ var browseBtn = component . Find ( "button:contains('Browse Game List')" ) ;
80+ Assert . True ( browseBtn . HasAttribute ( "disabled" ) ) ;
81+ }
9982
100- // Act
101- component . Find ( "button:contains('Start New Game')" ) . Click ( ) ;
102- var difficultyButtons = component . FindAll ( "button:contains('Easy'), button:contains('Medium'), button:contains('Hard')" ) ;
83+ [ Fact ]
84+ public void Render_ReturningPlayer_StartNewGameIsEnabled ( )
85+ {
86+ SetupReturningPlayer ( ) ;
87+ var component = Render < IndexPage > ( ) ;
88+ var startBtn = component . Find ( "button:contains('Start New Game')" ) ;
89+ Assert . False ( startBtn . HasAttribute ( "disabled" ) ) ;
90+ }
10391
104- // Assert
105- Assert . Equal ( 3 , difficultyButtons . Count ) ;
92+ [ Fact ]
93+ public void Render_ReturningPlayer_BrowseGameListIsEnabled ( )
94+ {
95+ SetupReturningPlayer ( ) ;
96+ var component = Render < IndexPage > ( ) ;
97+ var browseBtn = component . Find ( "button:contains('Browse Game List')" ) ;
98+ Assert . False ( browseBtn . HasAttribute ( "disabled" ) ) ;
10699 }
107100
108101 [ Fact ]
109- public void ShowsSavedGames_WhenLoadGameClicked ( )
102+ public void Render_NewPlayer_ShowsHelperTextOnDisabledCards ( )
110103 {
111- // Arrange
112- var savedGames = new List < GameModel >
113- {
114- } ;
115- _mockGameManager . SetupLoadGamesAsync ( savedGames ) ;
104+ SetupNewPlayer ( ) ;
116105 var component = Render < IndexPage > ( ) ;
106+ var helperTexts = component . FindAll ( ".helper-text" ) ;
107+ helperTexts . Count . Should ( ) . Be ( 2 ) ;
108+ }
117109
118- // Act
119- component . Find ( "button:contains('Load Game')" ) . Click ( ) ;
110+ [ Fact ]
111+ public void Render_DoesNotCallBackendApi ( )
112+ {
113+ SetupNewPlayer ( ) ;
114+ Render < IndexPage > ( ) ;
115+ // LocalStorageService is the only service injected — no IPlayerApiClient or IGameManager
116+ // This test verifies no backend dependency is needed
117+ _mockLocalStorage . Verify ( x => x . GetProfileAsync ( ) , Times . AtLeastOnce ) ;
118+ }
120119
121- // Assert
122- var savedGameButtons = component . FindAll ( ".saved-game-card" ) ;
123- savedGameButtons . Count . Should ( ) . Be ( savedGames . Count ) ;
120+ [ Fact ]
121+ public void LegacyMigration_WritesProfileAndRemovesAlias ( )
122+ {
123+ SetupLegacyPlayer ( "old-alias" ) ;
124+ Render < IndexPage > ( ) ;
125+ _mockLocalStorage . Verify ( x => x . SetProfileAsync ( It . Is < ProfileInfo > ( p => p . Alias == "old-alias" ) ) , Times . Once ) ;
126+ _mockLocalStorage . Verify ( x => x . RemoveAliasAsync ( ) , Times . Once ) ;
124127 }
125- }
128+ }
0 commit comments