diff --git a/FirstClassErrors.GenDoc.UnitTests/HtmlErrorDocumentationRendererTests.cs b/FirstClassErrors.GenDoc.UnitTests/HtmlErrorDocumentationRendererTests.cs index 5e1b356..d9ef026 100644 --- a/FirstClassErrors.GenDoc.UnitTests/HtmlErrorDocumentationRendererTests.cs +++ b/FirstClassErrors.GenDoc.UnitTests/HtmlErrorDocumentationRendererTests.cs @@ -154,6 +154,34 @@ public void TheSplitLayoutProducesAHomePageAndOnePagePerError() { Check.That(page).Contains("../index.html"); } + [Fact(DisplayName = "The split search index links each error to its own page.")] + public void TheSplitSearchIndexLinksEachErrorToItsPage() { + // Exercise + string json = ContentOf(RenderSplit(TemperatureError(), EmailError()), "assets/search-index.json"); + + // Verify: in split, the errors/… pages exist, so the index points at them. + Check.That(json).Contains("\"href\": \"errors/TEMPERATURE_BELOW_ABSOLUTE_ZERO.html\""); + Check.That(json).Contains("\"href\": \"errors/INVALID_EMAIL.html\""); + } + + [Fact(DisplayName = "The single search index links each error to its in-page anchor, not a missing page.")] + public void TheSingleSearchIndexLinksEachErrorToItsInPageAnchor() { + // Exercise + IReadOnlyList documents = RenderSingle(TemperatureError(), EmailError()); + string json = ContentOf(documents, "assets/search-index.json"); + + // Verify: single emits only index.html (no errors/… files), so the index must point at #err-… anchors there. + Check.That(documents.Select(d => d.RelativePath)).Not.Contains("errors/TEMPERATURE_BELOW_ABSOLUTE_ZERO.html"); + Check.That(json).Contains("\"href\": \"index.html#err-TEMPERATURE_BELOW_ABSOLUTE_ZERO\""); + Check.That(json).Contains("\"href\": \"index.html#err-INVALID_EMAIL\""); + Check.That(json).Not.Contains("\"href\": \"errors/"); + + // The referenced anchors actually exist in the single page. + string html = ContentOf(documents, "index.html"); + Check.That(html).Contains("id=\"err-TEMPERATURE_BELOW_ABSOLUTE_ZERO\""); + Check.That(html).Contains("id=\"err-INVALID_EMAIL\""); + } + [Fact(DisplayName = "The HTML renderer escapes catalog content.")] public void TheHtmlRendererEscapesCatalogContent() { // Setup: a title and a message carrying HTML metacharacters. diff --git a/FirstClassErrors.GenDoc/Rendering/HtmlErrorDocumentationRenderer.cs b/FirstClassErrors.GenDoc/Rendering/HtmlErrorDocumentationRenderer.cs index 3bb0a93..d29b3f5 100644 --- a/FirstClassErrors.GenDoc/Rendering/HtmlErrorDocumentationRenderer.cs +++ b/FirstClassErrors.GenDoc/Rendering/HtmlErrorDocumentationRenderer.cs @@ -51,7 +51,7 @@ public IReadOnlyList Render(IEnumerable ca // The CSS and JS are inlined into every page (see BuildPage) so each file is self-contained and stays styled // even when opened on its own. Only the search index remains an external asset, for external tooling. - documents.Add(new RenderedDocument("assets/search-index.json", BuildSearchIndex(entries))); + documents.Add(new RenderedDocument("assets/search-index.json", BuildSearchIndex(entries, split))); return documents; } @@ -381,19 +381,22 @@ private static string UniqueAnchor(string source, HashSet used) { return anchor; } - private static string BuildSearchIndex(IReadOnlyList entries) { + private static string BuildSearchIndex(IReadOnlyList entries, bool split) { // A curated, deterministic index for external tooling. In-page search uses the embedded row data instead, // so it keeps working when the site is opened from file:// (where fetch of a local JSON is often blocked). + // The href must match the actual layout: one page per error in split, an in-page anchor in single (where + // there is no errors/… file — the errors are #err-… anchors inside index.html). StringBuilder json = new(); json.Append("[\n"); for (int index = 0; index < entries.Count; index++) { - Entry entry = entries[index]; + Entry entry = entries[index]; + string href = split ? $"errors/{entry.FileName}" : $"index.html#err-{entry.Anchor}"; json.Append(" {\n"); json.Append($" \"code\": \"{JsonString(entry.Code)}\",\n"); json.Append($" \"title\": \"{JsonString(entry.Title)}\",\n"); json.Append($" \"summary\": \"{JsonString(entry.Summary)}\",\n"); json.Append($" \"source\": \"{JsonString(entry.Source ?? string.Empty)}\",\n"); - json.Append($" \"href\": \"errors/{JsonString(entry.FileName)}\",\n"); + json.Append($" \"href\": \"{JsonString(href)}\",\n"); json.Append($" \"text\": \"{JsonString(entry.SearchText)}\"\n"); json.Append(index == entries.Count - 1 ? " }\n" : " },\n"); }