diff --git a/tools/docs/verify-content.mjs b/tools/docs/verify-content.mjs index bc3ca8b50..c6812eeaa 100644 --- a/tools/docs/verify-content.mjs +++ b/tools/docs/verify-content.mjs @@ -95,26 +95,22 @@ for (const area of areas) { if (!roles.has(metadata.diataxis)) { failures.push(`${relativePage}: diataxis must name one supported role`); } + if (!metadata.title || /^(['"])\s*\1$/.test(metadata.title)) { + failures.push(`${relativePage}: missing title`); + } if (!metadata.persona) { failures.push(`${relativePage}: missing primary persona`); } - for (const section of [ - 'Orientation', - 'Example', - 'Pitfall', - 'Version notes', - 'Related guides and API', - ]) { - if (!new RegExp(`^## ${section}$`, 'm').test(content)) { - failures.push(`${relativePage}: missing "${section}" section`); - } - } - + const hasExampleSection = /^##[ \t]+Example(?:[ \t]+\{#[^}\s]+\})?[ \t]*\r?$/m.test(content); const hasExample = /!!raw-loader!.*Program\.cs/.test(content); const hasLabeledIllustration = metadata.example === 'illustrative' && /```[a-z]+[\s\S]+?```/.test(content); - if (!hasExample && !hasLabeledIllustration) { + if ( + (metadata.diataxis === 'tutorial' || hasExampleSection) && + !hasExample && + !hasLabeledIllustration + ) { failures.push( `${relativePage}: example must import tested source or be labeled illustrative`, ); @@ -130,14 +126,20 @@ for (const area of areas) { : contentAfterRelated; const links = [...related.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)] .map((match) => match[1]); - if (links.length < 2) { + if (relatedHeading && links.length < 2) { failures.push(`${relativePage}: related section needs at least two links`); } - if (!links.some((link) => /(^|\/)api(\/|$|\/index\.md$)/.test(link))) { + if ( + relatedHeading && + !links.some((link) => /(^|\/)api(\/|$|\/index\.md$)/.test(link)) + ) { failures.push(`${relativePage}: related section needs a same-version API link`); } if (area === 'scenarios' && path.basename(page) !== 'index.mdx') { const contractPath = relativePage.replaceAll(path.sep, '/'); + if (!relatedHeading) { + failures.push(`${relativePage}: focused scenario needs related API links`); + } if (!scenarioApiTargets.has(contractPath)) { failures.push(`${relativePage}: missing scenario API contract`); } diff --git a/website/docs/index.md b/website/docs/index.md index d5b49d110..5ac9bb877 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -7,17 +7,16 @@ sidebar_position: 0 # Humanizer documentation -Turn program-shaped values into text people can read. These guides cover -Humanizer from a first installation through the most common string, date, -number, enum, collection, and localization tasks. +Humanizer 4 turns program-shaped values into text people can read. These docs +cover the current package, APIs, and behavior. ## Start here - [What Humanizer does](./start/overview.mdx) — decide where it belongs in an application. -- [Install Humanizer](./start/installation.mdx) — choose a version and add the package. -- [Five-minute quick start](./start/quick-start.mdx) — run a verified first example. -- [Choose the right package](./start/package-selection.md) — understand the release-specific package layout. -- [Troubleshoot Humanizer](./start/troubleshooting.mdx) — diagnose version, culture, parser, analyzer, and AOT problems. +- [Install Humanizer](./start/installation.mdx) — add the package to a project. +- [Developer quick start](./start/quick-start.mdx) — run a small, deterministic example. +- [Configuration basics](./start/configuration.mdx) — choose per-call, ambient, or global configuration. +- [Troubleshoot Humanizer](./start/troubleshooting.mdx) — diagnose package, culture, parser, analyzer, and AOT problems. ## Solve a task @@ -28,19 +27,18 @@ number, enum, collection, and localization tasks. - [Humanize enums, flags, and collections](./scenarios/enums-and-collections.mdx) - [Configure localization and extensibility](./scenarios/localization-and-extensibility.mdx) -For exact signatures, open the [API reference](./api/index.md). The version -selector keeps guides, examples, and API pages in the same release. +For exact signatures, open the [API reference](./api/index.md). It is generated +from Humanizer 4. ## Use and improve languages -- [Languages and cultures](./languages/index.mdx) — choose a culture and understand the package boundary. -- [Supported cultures](./languages/supported-cultures.mdx) — find the culture codes supported by this version. +- [Languages and cultures](./languages/index.mdx) — choose a culture and understand localization behavior. +- [Supported cultures](./languages/supported-cultures.mdx) — find the culture codes supported by Humanizer 4. - [Report or correct a language issue](./contributing/report-language-issue.mdx) — provide the linguistic and platform context needed for a reliable fix. - [Contribute to Humanizer](./contributing/index.mdx) — follow the locale, validation, and documentation workflows. ## Maintain a project -- [Plan an upgrade](./upgrading/index.mdx) — follow every compatibility boundary between two supported versions. -- [Upgrade to Humanizer 4](./upgrading/main-preview.mdx) — review verified differences from `3.0.10`. +- [Upgrade from an earlier release](./upgrading/index.mdx) — follow the version-specific migration guidance. - [Migrate namespaces with the analyzer](./analyzer/index.mdx) — configure and run the bundled analyzer locally and in CI. - [Publish trimmed or Native AOT](./concepts/trimming-and-native-aot.mdx) — choose linker-safe enum APIs and run the publish proof. diff --git a/website/docs/start/configuration.mdx b/website/docs/start/configuration.mdx index 21b525e16..cab584570 100644 --- a/website/docs/start/configuration.mdx +++ b/website/docs/start/configuration.mdx @@ -11,39 +11,36 @@ import configuration from '!!raw-loader!../_examples/configuration/Program.cs'; # Configuration basics -## Orientation - Prefer per-call culture arguments when an overload provides one. Use `CultureInfo.CurrentCulture` and `CurrentUICulture` when a Humanizer API follows ambient application culture. Reserve `Configurator` for application-wide strategies, registries, or enum metadata rules. Configure global behavior once during application startup, before any request or background worker can use Humanizer. -## Example +## Configure a strategy -This verified example installs the built-in precision date strategy at startup -and proves the resulting output with an injected comparison instant: +This verified program installs date and duration strategies at startup: {configuration} -For a global date strategy, assign `Configurator.DateTimeHumanizeStrategy` once at startup. For a custom localized component, call the relevant registry's `Register` method before the registry is resolved. +It prints: -## Pitfall +```text +an hour ago; 1 hour +``` -In Humanizer `3.x` and current builds, `LocaliserRegistry` freezes its -registrations on first use, so registering after resolution throws -`InvalidOperationException`. Likewise, call -`Configurator.UseEnumDescriptionPropertyLocator` before the first enum -humanization. +For a global date strategy, assign +`Configurator.DateTimeHumanizeStrategy` once at startup. For a custom localized +component, call the relevant registry's `Register` method before the registry is +resolved. -## Version notes +## Configure before first use -Humanizer `2.x` keeps registries mutable and exposes an assignable -`Configurator.EnumDescriptionPropertyLocator`; `3.x` introduces registry -freezing and `UseEnumDescriptionPropertyLocator`. Date strategies exist across -the supported corpus. Confirm the selected-version API before adopting another -global extension point. +`LocaliserRegistry` freezes registrations on first use, so registering after +resolution throws `InvalidOperationException`. Likewise, call +`Configurator.UseEnumDescriptionPropertyLocator` before the first enum +humanization. ## Related guides and API - [Culture and global configuration](../concepts/culture-and-configuration.mdx) - [Localization and extensibility](../scenarios/localization-and-extensibility.mdx) -- [Selected-version API reference](../api/index.md) +- [API reference](../api/index.md) diff --git a/website/docs/start/installation.mdx b/website/docs/start/installation.mdx index 7176abede..e217005f8 100644 --- a/website/docs/start/installation.mdx +++ b/website/docs/start/installation.mdx @@ -6,55 +6,33 @@ diataxis: how-to persona: new developer --- -import CodeBlock from '@theme/CodeBlock'; -import quickStart from '!!raw-loader!../_examples/quick-start/Program.cs'; - # Install Humanizer -## Orientation - -Install the `Humanizer` package in the project that produces user-facing text. Select the documentation version that matches the package version in your application before copying commands or API links. - -## Example - -Install Humanizer 4 from NuGet: +Humanizer 4 ships as one NuGet package. Install it in the project that produces +user-facing text: ```console dotnet add package Humanizer --version 4.0.0 ``` -Then add `using Humanizer;` and run this verified program: - -{quickStart} +Add `using Humanizer;` where you use its extension methods. The package includes +the library, locale data, and analyzers; there is no package choice to make. -The project must target a framework supported by the selected package. Humanizer 4 ships `net11.0`, `net10.0`, `net8.0`, `net48`, and `netstandard2.0` assets. -## Confirm the reference - -Run: +## Confirm the reference {#example} ```console dotnet list package ``` -The project should list `Humanizer` at the version you selected. In centrally managed repositories, put the version in `Directory.Packages.props` and omit `--version` from the project command. - -## Pitfall - -Do not assume that every framework capable of consuming `netstandard2.0` is supported. For the current release line, .NET Framework support is explicitly .NET Framework 4.8. - -## Version notes - -Humanizer 4 is consolidated into the `Humanizer` package. Releases through -`3.0.10` use a metapackage over `Humanizer.Core` and locale packages. See -[package selection](./package-selection.md) before replacing package references -during an upgrade. +The project should list `Humanizer` at version `4.0.0`. In a centrally managed +repository, put the version in `Directory.Packages.props` and omit `--version` +from the project command. ## Related guides and API -- [Package selection](./package-selection.md) - [Quick start](./quick-start.mdx) - [Troubleshooting](./troubleshooting.mdx) -- [Selected-version API reference](../api/index.md) +- [API reference](../api/index.md) diff --git a/website/docs/start/overview.mdx b/website/docs/start/overview.mdx index 5d875b048..f701090bf 100644 --- a/website/docs/start/overview.mdx +++ b/website/docs/start/overview.mdx @@ -11,49 +11,34 @@ import quickStart from '!!raw-loader!../_examples/quick-start/Program.cs'; # What Humanizer does -## Orientation - Humanizer is a .NET library for turning program-shaped values into text people can read. Its extension methods cover identifiers, enums, dates and times, durations, numbers, byte sizes, quantities, and collections. Localization is part of those operations rather than a separate formatting pass. Use Humanizer at display boundaries: view models, messages, reports, logs intended for people, and other presentation code. Keep stored values and protocol payloads in their typed forms. -## Example +## See the result -The same small console program is compiled and run during documentation -validation for every selected version: +This program is compiled and run during documentation validation: {quickStart} -It prints `2 minutes`. The culture is explicit, so the result does not change with the machine running the example. - -## Choose a path - -- New to the library: [install Humanizer](./installation.mdx), then complete the [quick start](./quick-start.mdx). -- Choosing dependencies: read [package selection](./package-selection.md). -- Returning user: use the [scenario finder](../scenarios/index.mdx). -- Something failed: start with [troubleshooting](./troubleshooting.mdx). -- Need exact signatures: open the [selected-version API reference](../api/index.md). +It prints: -## Documentation and support +```text +2 minutes +``` -The site covers Humanizer `2.10.1` and later. Published releases receive -versioned snapshots, and the next-version documentation describes the upcoming -Humanizer 4 NuGet package. Documentation availability is not a promise that an -older release remains under active maintenance. Use the -[issue tracker](https://github.com/Humanizr/Humanizer/issues) for confirmed bugs -and support questions. +The culture is explicit, so the result does not change with the machine running +the example. -## Pitfall - -Humanized text is presentation output, not a stable serialization format. Do not persist it and later depend on parsing it back: cultures, wording, and formatting options can differ. - -## Version notes +## Choose a path -This canonical page describes the shared surface from Humanizer `2.10.1` onward. Package layout and target frameworks differ by release, so use the version selector before installing or following an API link. +- New to the library: [install Humanizer](./installation.mdx), then run the [developer quick start](./quick-start.mdx). +- Looking for a feature: use the [scenario finder](../scenarios/index.mdx). +- Something failed: start with [troubleshooting](./troubleshooting.mdx). +- Need exact signatures: open the [API reference](../api/index.md). -## Related guides and API +Humanized text is presentation output, not a stable serialization format. +Persist the typed value, then humanize it for the current culture and context. -- [Quick start](./quick-start.mdx) -- [Scenario finder](../scenarios/index.mdx) -- [Troubleshooting](./troubleshooting.mdx) -- [Selected-version API reference](../api/index.md) +For confirmed bugs and support questions, use the +[issue tracker](https://github.com/Humanizr/Humanizer/issues). diff --git a/website/docs/start/package-selection.md b/website/docs/start/package-selection.md index 4ffcbb224..bbaae751d 100644 --- a/website/docs/start/package-selection.md +++ b/website/docs/start/package-selection.md @@ -1,54 +1,15 @@ --- id: package-selection -title: Choose the right package +title: Package selection sidebar_position: 3 -diataxis: explanation +diataxis: how-to persona: developer choosing dependencies --- -import CodeBlock from '@theme/CodeBlock'; -import quickStart from '!!raw-loader!../_examples/quick-start/Program.cs'; +# Package selection -# Choose the right package +Humanizer 4 has one package: `Humanizer`. Follow the +[installation guide](./installation.mdx) to add it. -## Orientation - -For ordinary applications, install `Humanizer`. That name is stable even though the package's internal layout changed across release lines. - -| Documentation version | Normal install | Package shape | -| --- | --- | --- | -| `2.10.1` through `3.0.10` | `Humanizer` | Metapackage that brings in `Humanizer.Core` and locale packages | -| `4.0` | `Humanizer` | Consolidated library with runtime, generated locale data, and analyzers | - -The API reference for releases through `3.0.10` is generated from `Humanizer.Core`, because that is where the public implementation assembly lives. That does not change the normal install command. - -## Example - -Install Humanizer from NuGet: - -```console -dotnet add package Humanizer --version 4.0.0 -``` - -Then use the same `Humanizer` namespace regardless of package layout. This -verified program is compiled against the selected package: - -{quickStart} - -Choose `Humanizer.Core` directly only when you deliberately want the older release line's English-only core or must work around tooling that cannot restore its locale metapackage. Add only locale packages that exist for that exact release. - -## Pitfall - -Do not copy `Humanizer.Core.` references from a historical release into -Humanizer 4. Locale packaging is version-specific, and package names are not -proof that an API is present in another release. - -## Version notes - -Package selection is a compatibility concern, not an API preference. Keep the documentation selector aligned with `project.assets.json` or your lock file when diagnosing restore or runtime behavior. - -## Related guides and API - -- [Installation](./installation.mdx) -- [Localization and extensibility](../scenarios/localization-and-extensibility.mdx) -- [Selected-version API reference](../api/index.md) +This page remains available for existing inbound links. If you are moving from +an earlier release, use the [upgrade guide](../upgrading/index.mdx). diff --git a/website/docs/start/quick-start.mdx b/website/docs/start/quick-start.mdx index 6b7c3e8a6..97e29061c 100644 --- a/website/docs/start/quick-start.mdx +++ b/website/docs/start/quick-start.mdx @@ -1,6 +1,6 @@ --- id: quick-start -title: Five-minute quick start +title: Developer quick start sidebar_position: 4 diataxis: tutorial persona: new developer @@ -9,15 +9,14 @@ persona: new developer import CodeBlock from '@theme/CodeBlock'; import quickStart from '!!raw-loader!../_examples/quick-start/Program.cs'; -# Five-minute quick start +# Developer quick start -## Orientation +Create a console app, install Humanizer, and produce one deterministic duration. +You need a .NET 8 or newer SDK. -You will create a console app, install Humanizer, and produce one deterministic duration. You need a .NET 8 or newer SDK. +## Create and run {#example} -## Example - -Create the project and install Humanizer 4 from NuGet: +Create the project and install Humanizer 4: ```console dotnet new console --name HumanizerQuickStart @@ -43,19 +42,12 @@ The program prints exactly: 2 minutes ``` -The assertion fails immediately if the selected package produces a different value. - -## Pitfall - -Many Humanizer operations use the current culture or UI culture when no culture is supplied. Set both explicitly in examples, tests, background work, and any operation whose output must be predictable. - -## Version notes - -This source is compiled and run during documentation validation. Historical -snapshots retain the same source and resolve their declared package version. +Many Humanizer operations use the current culture or UI culture when no culture +is supplied. Set both explicitly in tests, background work, and any operation +whose output must be predictable. ## Related guides and API - [Configuration basics](./configuration.mdx) - [Dates, times, durations, and age](../scenarios/dates-times-durations-and-age.mdx) -- [Selected-version API reference](../api/index.md) +- [API reference](../api/index.md) diff --git a/website/docs/start/troubleshooting.mdx b/website/docs/start/troubleshooting.mdx index f5b5f91b3..9d03d4d2c 100644 --- a/website/docs/start/troubleshooting.mdx +++ b/website/docs/start/troubleshooting.mdx @@ -9,11 +9,12 @@ example: illustrative # Troubleshoot Humanizer -## Orientation +Start with the Humanizer package version, target framework, culture, exact API +call, and platform. Most apparent failures come from resolving an unexpected +package version, leaving ambient culture implicit, or treating locale-dependent +presentation text as invariant data. -Start with the resolved package version, target framework, culture, exact API call, and platform. Most apparent Humanizer failures come from following documentation for another version, resolving a different package asset, leaving ambient culture implicit, or treating locale-dependent presentation text as invariant data. - -## Example +## Capture a minimal reproduction Capture a minimal reproduction before changing configuration: @@ -23,49 +24,52 @@ using Humanizer; var culture = CultureInfo.GetCultureInfo("en-US"); var value = 42.ToWords(culture); -Console.WriteLine($"{typeof(Configurator).Assembly.GetName().Version}: {value}"); +Console.WriteLine(value); +``` + +The output should be: + +```text +forty-two ``` -Run `dotnet list package --include-transitive`, inspect the target in `project.assets.json`, and select that Humanizer version in this site. +From the application project directory, run +`dotnet list package --include-transitive` to confirm that the project resolved +Humanizer 4. -### Symptoms and actions +## Symptoms and actions | Symptom | Evidence to collect | Action | | --- | --- | --- | -| API page or method is missing | Package version, selected docs version, TFM | Switch the site version; check the page’s version notes and selected-version API root. | -| `MissingMethodException` or `TypeLoadException` | All direct/transitive Humanizer versions | Align `Humanizer`, `Humanizer.Core`, and historical locale packages; clean `bin`/`obj`, restore, and rebuild. | -| Output is English or the wrong regional form | `CurrentCulture`, `CurrentUICulture`, explicit argument, package version | Pass culture directly where possible; otherwise set both ambient cultures at the request/job boundary. If the culture is listed as supported, the wrong language is a bug. | -| Output differs on Windows, Linux, or macOS | OS, TFM, culture, ICU/NLS mode | Compare .NET globalization data and Humanizer’s generated platform-formatting coverage; avoid asserting platform-owned text without an explicit override. | -| A selected-version parser rejects input | Exact text and culture/provider | Confirm the parser exists in the selected API, use its `Try` form when available, and inspect the first unrecognized token or unit. | -| Enum input throws `NoMatchFoundException` | Enum members/metadata and input | Use an unambiguous label; pass `OnNoMatch.ReturnsNull` when failure is expected. Matching is case-insensitive. | -| `HUMANIZER001` | Resolved Humanizer version and source location | Apply the namespace code fix or configure a narrow intentional exception. | -| `CS8032` or `AD0001` from Humanizer analyzer | SDK/MSBuild and package version | Follow the selected-version analyzer guide, clear stale build outputs, and restore. Do not add Roslyn dependencies to the application. | -| `IL2026` or `IL3050` around enum parsing on an annotated release | Exact overload | Replace runtime `DehumanizeTo(string, Type, ...)` with generic `DehumanizeTo`. | -| Search or a copied URL opens another API version | Current selector and URL | Re-select the version, then navigate from its API root. Narrative and API routes are versioned together. | - -### Reduce culture issues +| API page or method is missing | Package version and TFM | Confirm the project resolved Humanizer 4, then check the [API reference](../api/index.md). | +| `MissingMethodException` or `TypeLoadException` | Direct and transitive Humanizer versions | Align the `Humanizer` version, clean `bin` and `obj`, restore, and rebuild. | +| Output is English or the wrong regional form | `CurrentCulture`, `CurrentUICulture`, explicit argument, package version | Pass culture directly where possible; otherwise set both ambient cultures at the request or job boundary. If the culture is listed as supported, the wrong language is a bug. | +| Output differs on Windows, Linux, or macOS | OS, TFM, culture, ICU/NLS mode | Run the same input and culture on each affected OS and TFM, then compare the complete output and .NET globalization settings. Use the [platform formatting guide](../languages/grammar-and-platform-formatting.mdx) to decide whether an override is required; avoid asserting platform-owned text without one. | +| A parser rejects input | Exact text and culture/provider | Use its `Try` form when available, then inspect the first unrecognized token or unit. | +| Enum input throws `NoMatchFoundException` | Enum members, metadata, and input | Use an unambiguous label; pass `OnNoMatch.ReturnsNull` when failure is expected. Matching is case-insensitive. | +| `HUMANIZER001` | Package version and source location | Apply the namespace code fix or configure a narrow intentional exception. | +| `CS8032` or `AD0001` from the Humanizer analyzer | SDK, MSBuild, and package version | Follow the analyzer guide, clear stale build outputs, and restore. Do not add Roslyn dependencies to the application. | +| `IL2026` or `IL3050` around enum parsing | Exact overload | Replace runtime `DehumanizeTo(string, Type, ...)` with generic `DehumanizeTo`. | + +## Reduce culture issues Set culture explicitly in a one-file reproduction. If the wrong result -persists, confirm that the culture is listed for the selected release and -report the bug. Include platform and TFM. +persists, confirm that the culture is [supported](../languages/supported-cultures.mdx) +and report the bug with the platform and TFM. -### Reduce package and analyzer issues +## Reduce package and analyzer issues -Use `dotnet restore --force-evaluate` after aligning versions. A compiler diagnostic from the analyzer proves it loaded; a loader diagnostic means the host could not load or select the packaged analyzer. +Use `dotnet restore --force-evaluate` after aligning versions. A compiler +diagnostic from the analyzer proves it loaded; a loader diagnostic means the +host could not load the packaged analyzer. -## Pitfall +## Avoid global fixes Do not “fix” a localized output difference by persisting the current humanized string or by setting process-wide culture inside a request. That hides the input boundary and creates concurrent, version-dependent behavior. Keep typed values, scope culture, and assert only output the application intentionally owns. -## Version notes - -Package layout, target frameworks, analyzer assets, locale implementation, and -API routes all vary across the supported corpus. This page follows the selected -documentation version. - ## Related guides and API - [Install Humanizer](./installation.mdx) - [Choose explicit and ambient cultures](../languages/using-cultures.mdx) - [Analyzer migration and CI](../analyzer/index.mdx) -- [Selected-version API reference](../api/index.md) +- [API reference](../api/index.md) diff --git a/website/sidebars.json b/website/sidebars.json index c69196192..d49b01b9c 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -8,7 +8,6 @@ "items": [ "start/overview", "start/installation", - "start/package-selection", "start/quick-start", "start/configuration", "start/troubleshooting" diff --git a/website/tests/contentCheck.test.mjs b/website/tests/contentCheck.test.mjs index 375b7da76..f523a7e45 100644 --- a/website/tests/contentCheck.test.mjs +++ b/website/tests/contentCheck.test.mjs @@ -9,29 +9,29 @@ import {fileURLToPath} from 'node:url'; const checker = fileURLToPath( new URL('../../tools/docs/verify-content.mjs', import.meta.url), ); -const validPage = `--- +const concisePage = `--- +title: Configure a task diataxis: how-to persona: existing user --- -import example from '!!raw-loader!./Program.cs'; - -## Orientation - Choose a task. +`; +const validPage = `--- +title: Configure a task +diataxis: how-to +persona: existing user +--- ## Example -The imported program is tested. - -## Pitfall - -Avoid stale examples. - -## Version notes - -This behavior is shared by the supported versions. +This fragment is illustrative: +\`\`\`csharp +Console.WriteLine("illustrative"); +\`\`\` +`; +const relatedPage = `${concisePage} ## Related guides and API - [Guide](../start/quick-start.mdx) @@ -50,8 +50,8 @@ async function withFixture(page, run) { } } -test('content checker accepts a related section that ends at EOF', async () => { - await withFixture(validPage, (cwd) => { +test('content checker accepts a concise page without template sections', async () => { + await withFixture(concisePage, (cwd) => { const result = spawnSync(process.execPath, [checker, 'sample'], { cwd, encoding: 'utf8', @@ -61,24 +61,53 @@ test('content checker accepts a related section that ends at EOF', async () => { }); }); -test('content checker reports a missing required section', async () => { - await withFixture(validPage.replace('## Pitfall', '## Caution'), (cwd) => { +test('content checker requires a page title', async () => { + await withFixture(concisePage.replace('title: Configure a task\n', ''), (cwd) => { const result = spawnSync(process.execPath, [checker, 'sample'], { cwd, encoding: 'utf8', }); assert.equal(result.status, 1); - assert.match(result.stderr, /missing "Pitfall" section/); + assert.match(result.stderr, /missing title/); }); }); -test('content checker rejects an unlabeled illustrative fragment', async () => { - const fragment = validPage - .replace("import example from '!!raw-loader!./Program.cs';", '') - .replace('The imported program is tested.', `\`\`\`csharp -Console.WriteLine("illustrative"); -\`\`\``); +for (const emptyTitle of ['""', "''", '" "']) { + test(`content checker rejects empty title ${emptyTitle}`, async () => { + const page = concisePage.replace( + 'title: Configure a task', + `title: ${emptyTitle}`, + ); + + await withFixture(page, (cwd) => { + const result = spawnSync(process.execPath, [checker, 'sample'], { + cwd, + encoding: 'utf8', + }); + + assert.equal(result.status, 1); + assert.match(result.stderr, /missing title/); + }); + }); +} + +test('content checker accepts a related section that ends at EOF', async () => { + await withFixture(relatedPage, (cwd) => { + const result = spawnSync(process.execPath, [checker, 'sample'], { + cwd, + encoding: 'utf8', + }); + + assert.equal(result.status, 0, result.stderr); + }); +}); + +test('content checker rejects an unlabeled fragment under an anchored example heading', async () => { + const fragment = validPage.replace( + '## Example', + '## Example {#legacy-example} ', + ); await withFixture(fragment, (cwd) => { const result = spawnSync(process.execPath, [checker, 'sample'], { @@ -95,12 +124,10 @@ Console.WriteLine("illustrative"); }); test('content checker accepts a labeled illustrative fragment', async () => { - const fragment = validPage - .replace('persona: existing user', 'persona: existing user\nexample: illustrative') - .replace("import example from '!!raw-loader!./Program.cs';", '') - .replace('The imported program is tested.', `\`\`\`csharp -Console.WriteLine("illustrative"); -\`\`\``); + const fragment = validPage.replace( + 'persona: existing user', + 'persona: existing user\nexample: illustrative', + ); await withFixture(fragment, (cwd) => { const result = spawnSync(process.execPath, [checker, 'sample'], { @@ -111,3 +138,18 @@ Console.WriteLine("illustrative"); assert.equal(result.status, 0, result.stderr); }); }); + +test('content checker requires an example and result for tutorials', async () => { + const tutorial = concisePage.replace('how-to', 'tutorial'); + + await withFixture(tutorial, (cwd) => { + const result = spawnSync(process.execPath, [checker, 'sample'], { + cwd, + encoding: 'utf8', + }); + + assert.equal(result.status, 1); + assert.match(result.stderr, /example must import tested source/); + assert.match(result.stderr, /tutorial needs a deterministic result/); + }); +});