diff --git a/.prettierignore b/.prettierignore index 745b019..ed95976 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,4 @@ /.makefiles/ /artifacts/ /CHANGELOG.md +/test/fixture/specification/no-prettier/ diff --git a/src/specification.ts b/src/specification.ts index 4da7d6f..cd1db27 100644 --- a/src/specification.ts +++ b/src/specification.ts @@ -37,9 +37,13 @@ export async function render(variables: Variable[]): Promise { async function prettyPrint(markdown: string): Promise { try { const prettier = await import("prettier"); - const options = - (await prettier.resolveConfig(join(process.cwd(), "ENVIRONMENT.md"))) ?? - {}; + + const options = (await prettier.resolveConfig( + join(process.cwd(), "ENVIRONMENT.md"), + )) ?? { + printWidth: 80, + proseWrap: "always", + }; return ( await prettier.format(markdown, { ...options, parser: "markdown" }) diff --git a/test/fixture/specification/no-prettier/prettier-not-configured.md b/test/fixture/specification/no-prettier/prettier-not-configured.md new file mode 100644 index 0000000..eb78f5d --- /dev/null +++ b/test/fixture/specification/no-prettier/prettier-not-configured.md @@ -0,0 +1,35 @@ +# Environment variables + +The `` app uses **declarative environment variables** powered by +**[Austenite]**. + +[austenite]: https://github.com/ezzatron/austenite + +| Name | Usage | Description | +| :-------------- | :------- | :-------------- | +| [`LOGO`](#logo) | Required | Main logo image | + + + +> [!TIP] +> If you set an empty value for an environment variable, the app behaves as if that variable isn't set. + + + +## `LOGO` + +_Main logo image_ + +The `LOGO` variable is a **required** variable that takes **absolute URL** +values, or **relative URL** values relative to +`https://base.example.org/path/to/resource`. + +### Example values + +```sh +export LOGO=https://host.example.org/path/to/resource # URL (absolute) +``` + +```sh +export LOGO=path/to/resource # URL (relative) +``` diff --git a/test/fixture/specification/no-prettier/prettier-unavailable.md b/test/fixture/specification/no-prettier/prettier-unavailable.md new file mode 100644 index 0000000..77c4a1c --- /dev/null +++ b/test/fixture/specification/no-prettier/prettier-unavailable.md @@ -0,0 +1,32 @@ +# Environment variables + +The `` app uses **declarative environment variables** powered by **[Austenite]**. + +[austenite]: https://github.com/ezzatron/austenite + +| Name | Usage | Description | +| :-------------- | :------- | :-------------- | +| [`LOGO`](#logo) | Required | Main logo image | + + + +> [!TIP] +> If you set an empty value for an environment variable, the app behaves as if that variable isn't set. + + + +## `LOGO` + +_Main logo image_ + +The `LOGO` variable is a **required** variable that takes **absolute URL** values, or **relative URL** values relative to `https://base.example.org/path/to/resource`. + +### Example values + +```sh +export LOGO=https://host.example.org/path/to/resource # URL (absolute) +``` + +```sh +export LOGO=path/to/resource # URL (relative) +``` diff --git a/test/suite/specification-prettier.spec.ts b/test/suite/specification-prettier.spec.ts new file mode 100644 index 0000000..bda6fd4 --- /dev/null +++ b/test/suite/specification-prettier.spec.ts @@ -0,0 +1,100 @@ +import { join } from "path"; +import { fileURLToPath } from "url"; +import { + afterAll, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; +import { initialize, url } from "../../src/index.js"; +import { MockConsole, createMockConsole } from "../helpers.js"; + +const fixturesPath = fileURLToPath( + new URL("../fixture/specification", import.meta.url), +); + +describe("Specification documents (Prettier unavailable)", () => { + let exitCode: number | undefined; + let mockConsole: MockConsole; + + beforeEach(() => { + exitCode = undefined; + vi.spyOn(process, "exit").mockImplementation((code) => { + exitCode = code ?? 0; + + return undefined as never; + }); + + process.env = { + AUSTENITE_SPEC: "true", + }; + + mockConsole = createMockConsole(); + }); + + describe("when Prettier is not installed", () => { + beforeAll(() => { + // eslint-disable-next-line @typescript-eslint/require-await + vi.doMock("prettier", async () => { + throw new Error('Cannot find module "prettier"'); + }); + }); + + afterAll(() => { + vi.doUnmock("prettier"); + }); + + it("doesn't pretty print the specification output", async () => { + url("LOGO", "Main logo image", { + base: new URL("https://base.example.org/path/to/resource"), + }); + await initialize(); + + await expect(mockConsole.readStdout()).toMatchFileSnapshot( + fixturePath("no-prettier/prettier-unavailable"), + ); + expect(exitCode).toBe(0); + }); + }); + + describe("when Prettier is installed but not configured", () => { + beforeAll(() => { + // eslint-disable-next-line @typescript-eslint/require-await + vi.doMock("prettier", async () => { + const prettier = await vi.importActual("prettier"); + + return { + ...prettier, + + // eslint-disable-next-line @typescript-eslint/require-await + async resolveConfig() { + return null; + }, + }; + }); + }); + + afterAll(() => { + vi.doUnmock("prettier"); + }); + + it("uses prose wrap at 80 columns", async () => { + url("LOGO", "Main logo image", { + base: new URL("https://base.example.org/path/to/resource"), + }); + await initialize(); + + await expect(mockConsole.readStdout()).toMatchFileSnapshot( + fixturePath("no-prettier/prettier-not-configured"), + ); + expect(exitCode).toBe(0); + }); + }); +}); + +function fixturePath(name: string): string { + return join(fixturesPath, `${name}.md`); +}