diff --git a/PlotLine.Tests/Program.cs b/PlotLine.Tests/Program.cs index d80f5c4..1cafdc3 100644 --- a/PlotLine.Tests/Program.cs +++ b/PlotLine.Tests/Program.cs @@ -73,7 +73,10 @@ var tests = new (string Name, Action Test)[] ("Illustration evidence trace does not recursively store previous evidence", IllustrationEvidenceTraceDoesNotRecursivelyStorePreviousEvidence), ("StoryPosition rejects incomplete or invalid runtime scene positions", StoryPositionRejectsInvalidRuntimeScenePositions), ("StoryPosition builds canonical runtime scene navigation", StoryPositionBuildsCanonicalRuntimeSceneNavigation), - ("StoryPosition derives canonical scene context identity", StoryPositionDerivesCanonicalSceneContextIdentity) + ("StoryPosition derives canonical scene context identity", StoryPositionDerivesCanonicalSceneContextIdentity), + ("Writing Context route uses canonical story context service", WritingContextRouteUsesCanonicalStoryContextService), + ("Writing Context client follows canonical story position events", WritingContextClientFollowsCanonicalStoryPositionEvents), + ("Writing Context preserves selected tab and avoids event loops", WritingContextPreservesSelectedTabAndAvoidsEventLoops) }; foreach (var test in tests) @@ -136,6 +139,47 @@ static void StoryPositionDerivesCanonicalSceneContextIdentity() Assert(position!.ProjectId == 4 && position.BookId == 5 && position.ChapterId == 6 && position.SceneId == 7, "Scene workspace position IDs were not preserved."); } +static void WritingContextRouteUsesCanonicalStoryContextService() +{ + var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine"); + var controller = File.ReadAllText(Path.Combine(root, "Controllers/WritingController.cs")); + var viewModel = File.ReadAllText(Path.Combine(root, "ViewModels/CoreViewModels.cs")); + + Assert(controller.Contains("[Route(\"Writing\")", StringComparison.Ordinal), "Writing Context controller should expose the /Writing route prefix."); + Assert(controller.Contains("[HttpGet(\"Context\")", StringComparison.Ordinal), "Writing Context shell route is missing."); + Assert(controller.Contains("[HttpGet(\"Context/Scene\")", StringComparison.Ordinal), "Writing Context partial route is missing."); + Assert(controller.Contains("IStoryContextService storyContext", StringComparison.Ordinal), "Writing Context must consume IStoryContextService."); + Assert(controller.Contains("storyContext.GetForSceneAsync", StringComparison.Ordinal), "Writing Context scene requests must load canonical story context."); + Assert(!controller.Contains("ISceneService", StringComparison.Ordinal), "Writing Context should not bypass canonical story context with ISceneService."); + Assert(viewModel.Contains("StoryContextViewModel? Context", StringComparison.Ordinal), "Writing Context display model should carry canonical story context."); +} + +static void WritingContextClientFollowsCanonicalStoryPositionEvents() +{ + var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine"); + var script = File.ReadAllText(Path.Combine(root, "wwwroot/js/writing-context.js")); + var view = File.ReadAllText(Path.Combine(root, "Views/Writing/Context.cshtml")); + + Assert(view.Contains("data-writing-context", StringComparison.Ordinal), "Writing Context shell should expose a stable script root."); + Assert(script.Contains("window.PlotDirectorStoryPosition?.eventName", StringComparison.Ordinal), "Writing Context must subscribe to canonical browser story-position events."); + Assert(script.Contains("connection.on(\"storyPositionChanged\"", StringComparison.Ordinal), "Writing Context must bridge SignalR story-position events."); + Assert(script.Contains("fetch(url", StringComparison.Ordinal), "Writing Context should refresh context content without full page navigation."); + Assert(script.Contains("data-writing-context-scene-select", StringComparison.Ordinal), "Writing Context manual fallback selector should be wired."); + Assert(script.Contains("publishChanged", StringComparison.Ordinal), "Manual scene selection should publish canonical story-position changes."); +} + +static void WritingContextPreservesSelectedTabAndAvoidsEventLoops() +{ + var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine"); + var script = File.ReadAllText(Path.Combine(root, "wwwroot/js/writing-context.js")); + + Assert(script.Contains("plotdirector.writingContext.activeTab", StringComparison.Ordinal), "Writing Context should remember the selected tab."); + Assert(script.Contains("activateTab(tab)", StringComparison.Ordinal), "Writing Context should restore the selected tab after content refresh."); + Assert(script.Contains("position.sceneId === currentSceneId", StringComparison.Ordinal), "Writing Context should ignore duplicate current-scene events."); + Assert(script.Contains("position.sceneId === loadingSceneId", StringComparison.Ordinal), "Writing Context should ignore duplicate in-flight scene events."); + Assert(!script.Contains("window.location.assign", StringComparison.Ordinal) && !script.Contains("window.location.href", StringComparison.Ordinal), "Writing Context must not navigate away when Word changes scene."); +} + static void StripsMarkdownFence() { var result = StoryIntelligenceJsonRepair.Repair("""