Skip to content

Commit b45354f

Browse files
committed
Harden shell go-live routing and library root breadcrumb
1 parent dde5699 commit b45354f

File tree

9 files changed

+118
-11
lines changed

9 files changed

+118
-11
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ Ask first:
406406
- thin WASM host boundaries
407407
- browser-realistic UI verification
408408
- domain logic that stays reusable and serializable
409+
- one extra proactive hardening pass after a broad audit or release-critical task, even when the first CI run is already green, so lingering UX or lifecycle issues are caught before the user has to ask again
409410

410411
### Dislikes
411412

src/PrompterOne.Shared/AppShell/Layout/MainLayout.razor

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,18 @@
2626
@if (IsLibraryScreen)
2727
{
2828
<div class="lib-breadcrumb">
29-
<span class="bc-item">@Text(UiTextKey.LibraryAllScripts)</span>
30-
<span class="bc-sep">/</span>
31-
<span class="bc-item bc-current"
32-
data-testid="@UiTestIds.Header.LibraryBreadcrumbCurrent">@ShellState.BreadcrumbLabel</span>
29+
@if (IsLibraryRootBreadcrumb)
30+
{
31+
<span class="bc-item bc-current"
32+
data-testid="@UiTestIds.Header.LibraryBreadcrumbCurrent">@LibraryBreadcrumbCurrentLabel</span>
33+
}
34+
else
35+
{
36+
<span class="bc-item">@Text(UiTextKey.LibraryAllScripts)</span>
37+
<span class="bc-sep">/</span>
38+
<span class="bc-item bc-current"
39+
data-testid="@UiTestIds.Header.LibraryBreadcrumbCurrent">@LibraryBreadcrumbCurrentLabel</span>
40+
}
3341
</div>
3442
}
3543
else

src/PrompterOne.Shared/AppShell/Layout/MainLayout.razor.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
4141

4242
private bool ShowLibraryActions => ShellState.Screen == AppShellScreen.Library;
4343

44+
private bool IsLibraryRootBreadcrumb =>
45+
string.IsNullOrWhiteSpace(ShellState.BreadcrumbLabel)
46+
|| string.Equals(ShellState.BreadcrumbLabel, Text(UiTextKey.LibraryAllScripts), StringComparison.Ordinal);
47+
48+
private string LibraryBreadcrumbCurrentLabel => IsLibraryRootBreadcrumb
49+
? Text(UiTextKey.LibraryAllScripts)
50+
: ShellState.BreadcrumbLabel;
51+
4452
private bool ShowLearnAction => ShellState.Screen == AppShellScreen.Editor;
4553

4654
private bool ShowLearnWpmBadge => ShellState.Screen == AppShellScreen.Learn;
@@ -102,8 +110,10 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
102110

103111
private bool ShowGoLiveWidget => GoLiveSessionState.HasActiveSession && ShellState.Screen != AppShellScreen.GoLive;
104112

105-
private string GoLiveRoute => !string.IsNullOrWhiteSpace(GoLiveSessionState.ScriptId)
106-
? AppRoutes.GoLiveWithId(GoLiveSessionState.ScriptId)
113+
private string GoLiveRoute => GoLiveSessionState.HasActiveSession
114+
? string.IsNullOrWhiteSpace(GoLiveSessionState.ScriptId)
115+
? AppRoutes.GoLive
116+
: AppRoutes.GoLiveWithId(GoLiveSessionState.ScriptId)
107117
: Shell.GetGoLiveRoute();
108118

109119
private const string IdleStateValue = "idle";

src/PrompterOne.Shared/Library/Pages/LibraryPage.ViewState.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,7 @@ private string ResolveSelectedFolderLabel()
8181
{
8282
if (IsAllSelected)
8383
{
84-
return _folderNodes.Count > 0
85-
? _folderNodes[RootFolderNodeIndex].Name
86-
: Text(UiTextKey.LibraryAllScripts);
84+
return Text(UiTextKey.LibraryAllScripts);
8785
}
8886

8987
return _folders

src/PrompterOne.Shared/Library/Pages/LibraryPage.razor.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ namespace PrompterOne.Shared.Pages;
1212

1313
public partial class LibraryPage : ComponentBase, IDisposable
1414
{
15-
private const int RootFolderNodeIndex = 0;
1615
private const string LibrarySettingsKey = "prompterone.library";
1716
private const string LoadLibraryOperation = "Library load";
1817
private const string LoadLibraryMessage = "Unable to load the library right now.";

tests/PrompterOne.App.Tests/AppShell/MainLayoutActionTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using Bunit;
22
using Microsoft.AspNetCore.Components;
33
using Microsoft.Extensions.DependencyInjection;
4+
using PrompterOne.Core.Models.Workspace;
45
using PrompterOne.Shared.Contracts;
56
using PrompterOne.Shared.Layout;
7+
using PrompterOne.Shared.Services;
68
using PrompterOne.Shared.Tests;
79

810
namespace PrompterOne.App.Tests;
@@ -82,4 +84,36 @@ public void MainLayout_LibraryHeaderMatchesReferenceActionOrder()
8284
Assert.True(goLiveIndex >= 0 && newScriptIndex >= 0 && goLiveIndex < newScriptIndex);
8385
});
8486
}
87+
88+
[Fact]
89+
public void MainLayout_ActiveGenericGoLiveSession_UsesPlainGoLiveRoute_InsteadOfCurrentEditorScriptScope()
90+
{
91+
_ = TestHarnessFactory.Create(this);
92+
var navigation = Services.GetRequiredService<NavigationManager>();
93+
navigation.NavigateTo(AppRoutes.EditorWithId(AppTestData.Scripts.DemoId));
94+
Services.GetRequiredService<GoLiveSessionService>().SetState(new GoLiveSessionState(
95+
ScriptId: string.Empty,
96+
ScriptTitle: string.Empty,
97+
ScriptSubtitle: string.Empty,
98+
SelectedSourceId: AppTestData.Camera.FirstSourceId,
99+
SelectedSourceLabel: AppTestData.Camera.FrontCamera,
100+
ActiveSourceId: AppTestData.Camera.FirstSourceId,
101+
ActiveSourceLabel: AppTestData.Camera.FrontCamera,
102+
PrimaryMicrophoneLabel: AppTestData.Scripts.BroadcastMic,
103+
OutputResolution: StreamingResolutionPreset.FullHd1080p30,
104+
BitrateKbps: AppTestData.Streaming.BitrateKbps,
105+
IsStreamActive: true,
106+
IsRecordingActive: false,
107+
StreamStartedAt: DateTimeOffset.UtcNow.AddMinutes(-1),
108+
RecordingStartedAt: null));
109+
110+
var cut = Render<MainLayout>(parameters => parameters
111+
.Add(layout => layout.Body, (RenderFragment)(builder => builder.AddMarkupContent(0, "<div>Body</div>"))));
112+
113+
cut.WaitForAssertion(() => Assert.NotNull(cut.FindByTestId(UiTestIds.Header.LiveWidget)));
114+
cut.FindByTestId(UiTestIds.Header.LiveWidget).Click();
115+
116+
Assert.EndsWith(AppRoutes.GoLive, navigation.Uri, StringComparison.Ordinal);
117+
Assert.DoesNotContain(AppRoutes.ScriptIdQueryKey, navigation.Uri, StringComparison.Ordinal);
118+
}
85119
}

tests/PrompterOne.App.UITests/GoLive/GoLiveShellSessionFlowTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,41 @@ await Expect(page.GetByTestId(UiTestIds.Header.GoLive))
9898
}
9999
}
100100

101+
[Fact]
102+
public async Task GoLivePage_GenericActiveSession_WidgetReturnsToPlainGoLiveRoute_WithoutInjectingEditorScriptId()
103+
{
104+
var pages = await _fixture.NewSharedPagesAsync(BrowserTestConstants.GoLive.SharedContextPageCount);
105+
var primaryPage = pages[0];
106+
var secondaryPage = pages[1];
107+
108+
try
109+
{
110+
await GoLiveFlowTests.SeedGoLiveSceneForReuseAsync(primaryPage);
111+
await primaryPage.GotoAsync(BrowserTestConstants.Routes.GoLive);
112+
await Expect(primaryPage.GetByTestId(UiTestIds.GoLive.Page)).ToBeVisibleAsync();
113+
114+
await primaryPage.GetByTestId(UiTestIds.GoLive.StartRecording).ClickAsync();
115+
await primaryPage.WaitForFunctionAsync(
116+
BrowserTestConstants.GoLive.RecordingRuntimeActiveScript,
117+
BrowserTestConstants.GoLive.RuntimeSessionId,
118+
new() { Timeout = BrowserTestConstants.Timing.ExtendedVisibleTimeoutMs });
119+
120+
await secondaryPage.GotoAsync(BrowserTestConstants.Routes.EditorDemo);
121+
await Expect(secondaryPage.GetByTestId(UiTestIds.Editor.Page)).ToBeVisibleAsync();
122+
await Expect(secondaryPage.GetByTestId(UiTestIds.Header.LiveWidget)).ToBeVisibleAsync();
123+
124+
await secondaryPage.GetByTestId(UiTestIds.Header.LiveWidget).ClickAsync();
125+
126+
await secondaryPage.WaitForURLAsync(BrowserTestConstants.Routes.Pattern(BrowserTestConstants.Routes.GoLive));
127+
await Expect(secondaryPage.GetByTestId(UiTestIds.GoLive.Page)).ToBeVisibleAsync();
128+
Assert.Equal(BrowserTestConstants.Routes.GoLive, new Uri(secondaryPage.Url).PathAndQuery);
129+
}
130+
finally
131+
{
132+
await primaryPage.Context.CloseAsync();
133+
}
134+
}
135+
101136
[Fact]
102137
public async Task GoLivePage_RecordingState_PropagatesAcrossSharedTabsAndReturnsToIdleAfterStop()
103138
{

tests/PrompterOne.App.UITests/Library/LibraryScreenFlowTests.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public Task LibraryScreen_NavigatesIntoEditorAndSettings() =>
1515
await page.GotoAsync(BrowserTestConstants.Routes.Library);
1616
await Expect(page.GetByTestId(UiTestIds.Library.Page)).ToBeVisibleAsync();
1717
await Expect(page.GetByTestId(UiTestIds.Header.LibraryBreadcrumbCurrent))
18-
.ToHaveTextAsync(BrowserTestConstants.Folders.PresentationsName);
18+
.ToHaveTextAsync(BrowserTestConstants.Folders.AllScriptsName);
1919
await Expect(page.GetByTestId(UiTestIds.Header.GoLive))
2020
.ToHaveClassAsync(BrowserTestConstants.Regexes.GoLiveHeaderClass);
2121
await Expect(page.GetByTestId(UiTestIds.Library.FolderChips)).ToHaveCountAsync(0);
@@ -133,6 +133,26 @@ public Task LibraryScreen_SidebarFoldersFilterCards() =>
133133
await Expect(page.GetByTestId(BrowserTestConstants.Elements.LeadershipCard)).ToContainTextAsync(BrowserTestConstants.Scripts.LeadershipTitle);
134134
});
135135

136+
[Fact]
137+
public Task LibraryScreen_RootBreadcrumb_RendersSingleAllScriptsLabel() =>
138+
RunPageAsync(async page =>
139+
{
140+
await page.GotoAsync(BrowserTestConstants.Routes.Library);
141+
await Expect(page.GetByTestId(UiTestIds.Library.Page)).ToBeVisibleAsync();
142+
143+
await page.GetByTestId(UiTestIds.Library.FolderAll).ClickAsync();
144+
145+
await Expect(page.GetByTestId(UiTestIds.Header.LibraryBreadcrumbCurrent))
146+
.ToHaveTextAsync(BrowserTestConstants.Folders.AllScriptsName);
147+
148+
var headerCenterText = (await page.GetByTestId(UiTestIds.Header.Center).TextContentAsync()) ?? string.Empty;
149+
var allScriptsOccurrences = headerCenterText.Split(
150+
BrowserTestConstants.Folders.AllScriptsName,
151+
StringSplitOptions.None).Length - 1;
152+
153+
Assert.Equal(1, allScriptsOccurrences);
154+
});
155+
136156
[Fact]
137157
public Task LibraryScreen_CreatesFolderAndMovesScript() =>
138158
RunPageAsync(async page =>

tests/PrompterOne.App.UITests/Support/BrowserTestConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ public static class Teleprompter
136136

137137
public static class Folders
138138
{
139+
public const string AllScriptsName = "All Scripts";
139140
public const string PresentationsId = "test-presentations";
140141
public const string PresentationsName = "Presentations";
141142
public const string TedTalksId = "test-ted-talks";
@@ -810,6 +811,7 @@ public static class Keyboard
810811
public static class Routes
811812
{
812813
public static string Editor => AppRoutes.Editor;
814+
public static string GoLive => AppRoutes.GoLive;
813815
public static string Library => AppRoutes.Library;
814816
public static string Settings => AppRoutes.Settings;
815817
public static string EditorDemo => AppRoutes.EditorWithId(Scripts.DemoId);

0 commit comments

Comments
 (0)