From d05d99bfa48b4d46b79b8a2b4b1641622466dbb7 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 17 Dec 2024 14:36:36 -0500 Subject: [PATCH] test: fill in unit tests for most create Blocks --- package.json | 8 +- pnpm-lock.yaml | 65 ++- src/next/blocks/blockAllContributors.test.ts | 128 +++++ src/next/blocks/blockAreTheTypesWrong.test.ts | 35 ++ src/next/blocks/blockCSpell.test.ts | 143 +++++ src/next/blocks/blockDevelopmentDocs.test.ts | 145 ++++++ src/next/blocks/blockDevelopmentDocs.ts | 12 +- src/next/blocks/blockESLint.test.ts | 297 +++++++++++ src/next/blocks/blockExampleFiles.test.ts | 50 ++ src/next/blocks/blockGitHubActionsCI.test.ts | 216 ++++++++ src/next/blocks/blockGitignore.test.ts | 41 ++ src/next/blocks/blockMarkdownlint.test.ts | 169 ++++++ src/next/blocks/blockPackageJson.test.ts | 61 +++ src/next/blocks/blockPrettier.test.ts | 236 +++++++++ src/next/blocks/blockREADME.test.ts | 179 +++++++ .../blockRepositoryBranchProtection.test.ts | 47 ++ src/next/blocks/blockTSup.test.ts | 184 +++++++ src/next/blocks/blockTemplatedBy.ts | 5 - src/next/blocks/blockTypeScript.test.ts | 313 +++++++++++ src/next/blocks/blockVSCode.test.ts | 118 +++++ src/next/blocks/blockVSCode.ts | 4 + src/next/blocks/blockVitest.test.ts | 492 ++++++++++++++++++ src/next/blocks/packageData.test.ts | 36 ++ src/next/blocks/packageData.ts | 1 - src/next/utils/removeTrailingSlash.ts | 3 - 25 files changed, 2939 insertions(+), 49 deletions(-) create mode 100644 src/next/blocks/blockAllContributors.test.ts create mode 100644 src/next/blocks/blockAreTheTypesWrong.test.ts create mode 100644 src/next/blocks/blockCSpell.test.ts create mode 100644 src/next/blocks/blockDevelopmentDocs.test.ts create mode 100644 src/next/blocks/blockESLint.test.ts create mode 100644 src/next/blocks/blockExampleFiles.test.ts create mode 100644 src/next/blocks/blockGitHubActionsCI.test.ts create mode 100644 src/next/blocks/blockGitignore.test.ts create mode 100644 src/next/blocks/blockMarkdownlint.test.ts create mode 100644 src/next/blocks/blockPackageJson.test.ts create mode 100644 src/next/blocks/blockPrettier.test.ts create mode 100644 src/next/blocks/blockREADME.test.ts create mode 100644 src/next/blocks/blockRepositoryBranchProtection.test.ts create mode 100644 src/next/blocks/blockTSup.test.ts create mode 100644 src/next/blocks/blockTypeScript.test.ts create mode 100644 src/next/blocks/blockVSCode.test.ts create mode 100644 src/next/blocks/blockVitest.test.ts create mode 100644 src/next/blocks/packageData.test.ts delete mode 100644 src/next/utils/removeTrailingSlash.ts diff --git a/package.json b/package.json index 09d562a8..3e704deb 100644 --- a/package.json +++ b/package.json @@ -44,12 +44,12 @@ "@clack/prompts": "^0.8.2", "@prettier/sync": "^0.5.2", "chalk": "^5.3.0", - "create": "0.1.0-alpha.5", + "create": "0.1.0-alpha.6", "execa": "^9.5.1", "git-remote-origin-url": "^4.0.0", "git-url-parse": "^16.0.0", - "input-from-file": "0.1.0-alpha.0", - "input-from-file-json": "0.1.0-alpha.1", + "input-from-file": "0.1.0-alpha.2", + "input-from-file-json": "0.1.0-alpha.2", "js-yaml": "^4.1.0", "lazy-value": "^3.0.0", "npm-user": "^6.1.1", @@ -82,7 +82,7 @@ "all-contributors-cli": "6.26.1", "c8": "10.1.2", "console-fail-test": "0.5.0", - "create-testers": "0.1.0-alpha.5", + "create-testers": "0.1.0-alpha.6", "cspell": "8.16.1", "eslint": "9.16.0", "eslint-plugin-jsdoc": "50.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c1343176..19ad37d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^5.3.0 version: 5.3.0 create: - specifier: 0.1.0-alpha.5 - version: 0.1.0-alpha.5 + specifier: 0.1.0-alpha.6 + version: 0.1.0-alpha.6 execa: specifier: ^9.5.1 version: 9.5.2 @@ -30,11 +30,11 @@ importers: specifier: ^16.0.0 version: 16.0.0 input-from-file: - specifier: 0.1.0-alpha.0 - version: 0.1.0-alpha.0(create@0.1.0-alpha.5) + specifier: 0.1.0-alpha.2 + version: 0.1.0-alpha.2(create@0.1.0-alpha.6) input-from-file-json: - specifier: 0.1.0-alpha.1 - version: 0.1.0-alpha.1(create@0.1.0-alpha.5) + specifier: 0.1.0-alpha.2 + version: 0.1.0-alpha.2(create@0.1.0-alpha.6) js-yaml: specifier: ^4.1.0 version: 4.1.0 @@ -127,8 +127,8 @@ importers: specifier: 0.5.0 version: 0.5.0 create-testers: - specifier: 0.1.0-alpha.5 - version: 0.1.0-alpha.5(create@0.1.0-alpha.5) + specifier: 0.1.0-alpha.6 + version: 0.1.0-alpha.6(create@0.1.0-alpha.6) cspell: specifier: 8.16.1 version: 8.16.1 @@ -1897,14 +1897,14 @@ packages: typescript: optional: true - create-testers@0.1.0-alpha.5: - resolution: {integrity: sha512-0Wgn9P6zi0Gn89yI3NqB7Qxx+jn2/v5P+VvQTTCWgnh/qkc/DqlKs7t2FZNJdMiSDfokWFpbBCiJmkDzcK8HHQ==} + create-testers@0.1.0-alpha.6: + resolution: {integrity: sha512-WF5D+6fhZmtR8+wTDXml//HdO4tzwrmo8sjpKBwYGtZXLl8VCkoqYAtKqe2TjUBNXrRTfISAKWbEYQykcD1ukw==} engines: {node: '>=18'} peerDependencies: - create: 0.1.0-alpha.5 + create: 0.1.0-alpha.6 - create@0.1.0-alpha.5: - resolution: {integrity: sha512-pHbkdn8a6FTI9Mo09mETR9WyX9I9tka4lQDHNUHEUZuatKP+j4qyStFrktOGXV0hoSg2mTqGV+s4cktbOegdYg==} + create@0.1.0-alpha.6: + resolution: {integrity: sha512-XWaBK+NMK+LTeT7cod96e4sXQ9K8FGRK05tF9oQIhD5p/zWEfdoGUKQZoEW0WsBHZBiiuP1Y0W0uTPC0LoTBVQ==} engines: {node: '>=18'} hasBin: true @@ -2622,17 +2622,23 @@ packages: resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - input-from-file-json@0.1.0-alpha.1: - resolution: {integrity: sha512-fkB4Y8dwplNkaiRe2O9IX1hYYz1efUxFX5bCKiiqUmrB67yTnUoDeDg+LlKJr/py6L7aPkCObZ6MrF5WH+PDiQ==} + input-from-file-json@0.1.0-alpha.2: + resolution: {integrity: sha512-YjH+2/nc2SNM/iHmc8EOTyYSwJJeffvoxCdNxVqIwlNmENB/jPh1wu5ffd1VFZKsg878jFG7OEFEmm4YHY9PUg==} engines: {node: '>=18'} peerDependencies: - create: 0.1.0-alpha.0 + create: 0.1.0-alpha.6 - input-from-file@0.1.0-alpha.0: - resolution: {integrity: sha512-XcGuSxpNEMN2tlMV+TfMNK21KrUN8T6RNWeIUIyIukJ7TzNbf+lgSTl9rxjAXWmNfFA59puMQk6qsa+HJ76X+g==} + input-from-file@0.1.0-alpha.1: + resolution: {integrity: sha512-O4BrmU5E8HYNuS8Yqc68N60zSf3rUGCtBcEcYf9DBjMhuLDhNF2XHNjuuOuZTPtNkSZuYk0N+ix2Op+FjWnEHw==} engines: {node: '>=18'} peerDependencies: - create: 0.1.0-alpha.0 + create: 0.1.0-alpha.6 + + input-from-file@0.1.0-alpha.2: + resolution: {integrity: sha512-R2YwGgdVIGJJP6/XkFYR3KQaFTGTz5RsEdoGDGWc9D+5yeVVWLdBtcfqIY4IGknXlquVOSORxmVfAiwnZ80sgg==} + engines: {node: '>=18'} + peerDependencies: + create: 0.1.0-alpha.6 inquirer@7.3.3: resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} @@ -5769,12 +5775,12 @@ snapshots: optionalDependencies: typescript: 5.7.2 - create-testers@0.1.0-alpha.5(create@0.1.0-alpha.5): + create-testers@0.1.0-alpha.6(create@0.1.0-alpha.6): dependencies: - create: 0.1.0-alpha.5 + create: 0.1.0-alpha.6 octokit: 4.0.2 - create@0.1.0-alpha.5: + create@0.1.0-alpha.6: dependencies: execa: 9.5.2 hash-object: 5.0.1 @@ -6673,15 +6679,20 @@ snapshots: ini@4.1.1: {} - input-from-file-json@0.1.0-alpha.1(create@0.1.0-alpha.5): + input-from-file-json@0.1.0-alpha.2(create@0.1.0-alpha.6): + dependencies: + create: 0.1.0-alpha.6 + input-from-file: 0.1.0-alpha.1(create@0.1.0-alpha.6) + zod: 3.24.1 + + input-from-file@0.1.0-alpha.1(create@0.1.0-alpha.6): dependencies: - create: 0.1.0-alpha.5 - input-from-file: 0.1.0-alpha.0(create@0.1.0-alpha.5) + create: 0.1.0-alpha.6 zod: 3.24.1 - input-from-file@0.1.0-alpha.0(create@0.1.0-alpha.5): + input-from-file@0.1.0-alpha.2(create@0.1.0-alpha.6): dependencies: - create: 0.1.0-alpha.5 + create: 0.1.0-alpha.6 zod: 3.24.1 inquirer@7.3.3: diff --git a/src/next/blocks/blockAllContributors.test.ts b/src/next/blocks/blockAllContributors.test.ts new file mode 100644 index 00000000..2226113b --- /dev/null +++ b/src/next/blocks/blockAllContributors.test.ts @@ -0,0 +1,128 @@ +import { testBlock } from "create-testers"; +import { describe, expect, it } from "vitest"; + +import { blockAllContributors } from "./blockAllContributors.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockAllContributors", () => { + it("defaults contributors to [] when not provided", () => { + const creation = testBlock(blockAllContributors, { options: optionsBase }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "ignores": [ + "/.all-contributorsrc", + ], + }, + "block": [Function], + }, + ], + "files": { + ".all-contributorsrc": "{"badgeTemplate":"\\t\\"๐Ÿ‘ช\\" src=\\"https://img.shields.io/badge/%F0%9F%91%AA_all_contributors-<%= contributors.length %>-21bb42.svg\\" />","commit":false,"commitConvention":"angular","commitType":"docs","contributors":[],"contributorsPerLine":7,"contributorsSortAlphabetically":true,"files":["README.md"],"imageSize":100,"projectName":"test-repository","projectOwner":"test-owner","repoHost":"https://github.com","repoType":"github"}", + ".github": { + "workflows": { + "contributors.yml": "jobs: + contributors: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: ./.github/actions/prepare + - env: + GITHUB_TOKEN: \${{ secrets.ACCESS_TOKEN }} + uses: JoshuaKGoldberg/all-contributors-auto-action@v0.5.0 + + name: Contributors + + on: + push: + branches: + - main + ", + }, + }, + }, + "scripts": [ + { + "commands": [ + "npx -y all-contributors-cli generate", + "npx -y all-contributors-cli add test-owner code,content,docs,ideas,infra,maintenance,projectManagement,tool", + ], + "phase": 2, + }, + ], + } + `); + }); + + it("includes contributors when not provided", () => { + const creation = testBlock(blockAllContributors, { + options: { + ...optionsBase, + contributors: [ + { + avatar_url: "https://avatars.githubusercontent.com/u/3335181?v=4", + contributions: ["bug", "ideas"], + login: "JoshuaKGoldberg", + name: "Josh Goldberg", + profile: "http://www.joshuakgoldberg.com", + }, + ], + }, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "ignores": [ + "/.all-contributorsrc", + ], + }, + "block": [Function], + }, + ], + "files": { + ".all-contributorsrc": "{"badgeTemplate":"\\t\\"๐Ÿ‘ช\\" src=\\"https://img.shields.io/badge/%F0%9F%91%AA_all_contributors-<%= contributors.length %>-21bb42.svg\\" />","commit":false,"commitConvention":"angular","commitType":"docs","contributors":[{"avatar_url":"https://avatars.githubusercontent.com/u/3335181?v=4","contributions":["bug","ideas"],"login":"JoshuaKGoldberg","name":"Josh Goldberg","profile":"http://www.joshuakgoldberg.com"}],"contributorsPerLine":7,"contributorsSortAlphabetically":true,"files":["README.md"],"imageSize":100,"projectName":"test-repository","projectOwner":"test-owner","repoHost":"https://github.com","repoType":"github"}", + ".github": { + "workflows": { + "contributors.yml": "jobs: + contributors: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: ./.github/actions/prepare + - env: + GITHUB_TOKEN: \${{ secrets.ACCESS_TOKEN }} + uses: JoshuaKGoldberg/all-contributors-auto-action@v0.5.0 + + name: Contributors + + on: + push: + branches: + - main + ", + }, + }, + }, + "scripts": [ + { + "commands": [ + "npx -y all-contributors-cli generate", + "npx -y all-contributors-cli add test-owner code,content,docs,ideas,infra,maintenance,projectManagement,tool", + ], + "phase": 2, + }, + ], + } + `); + }); +}); diff --git a/src/next/blocks/blockAreTheTypesWrong.test.ts b/src/next/blocks/blockAreTheTypesWrong.test.ts new file mode 100644 index 00000000..04524ed0 --- /dev/null +++ b/src/next/blocks/blockAreTheTypesWrong.test.ts @@ -0,0 +1,35 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockAreTheTypesWrong } from "./blockAreTheTypesWrong.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockAreTheTypesWrong", () => { + test("production", () => { + const creation = testBlock(blockAreTheTypesWrong, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "jobs": [ + { + "name": "Are The Types Wrong?", + "steps": [ + { + "run": "npx --yes @arethetypeswrong/cli --pack . --ignore-rules cjs-resolves-to-esm", + }, + ], + }, + ], + }, + "block": [Function], + }, + ], + } + `); + }); +}); diff --git a/src/next/blocks/blockCSpell.test.ts b/src/next/blocks/blockCSpell.test.ts new file mode 100644 index 00000000..a7ffb5c4 --- /dev/null +++ b/src/next/blocks/blockCSpell.test.ts @@ -0,0 +1,143 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockCSpell } from "./blockCSpell.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockCSpell", () => { + test("without addons", () => { + const creation = testBlock(blockCSpell, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "sections": { + "Linting": { + "contents": { + "items": [ + "- \`pnpm lint:spelling\` ([cspell](https://cspell.org)): Spell checks across all source files", + ], + }, + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "extensions": [ + "streetsidesoftware.code-spell-checker", + ], + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Lint Spelling", + "steps": [ + { + "run": "pnpm lint:spelling", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "cspell": "8.16.1", + }, + "scripts": { + "lint:spelling": "cspell "**" ".github/**/*"", + }, + }, + }, + "block": [Function], + }, + ], + "files": { + "cspell.json": "{"dictionaries":["npm","node","typescript"],"ignorePaths":[".github","CHANGELOG.md","lib","node_modules","pnpm-lock.yaml"]}", + }, + } + `); + }); + + test("with addons", () => { + const creation = testBlock(blockCSpell, { + addons: { + ignores: ["lib/"], + words: ["joshuakgoldberg"], + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "sections": { + "Linting": { + "contents": { + "items": [ + "- \`pnpm lint:spelling\` ([cspell](https://cspell.org)): Spell checks across all source files", + ], + }, + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "extensions": [ + "streetsidesoftware.code-spell-checker", + ], + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Lint Spelling", + "steps": [ + { + "run": "pnpm lint:spelling", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "cspell": "8.16.1", + }, + "scripts": { + "lint:spelling": "cspell "**" ".github/**/*"", + }, + }, + }, + "block": [Function], + }, + ], + "files": { + "cspell.json": "{"dictionaries":["npm","node","typescript"],"ignorePaths":[".github","CHANGELOG.md","lib","lib/","node_modules","pnpm-lock.yaml"],"words":["joshuakgoldberg"]}", + }, + } + `); + }); +}); diff --git a/src/next/blocks/blockDevelopmentDocs.test.ts b/src/next/blocks/blockDevelopmentDocs.test.ts new file mode 100644 index 00000000..c057e47a --- /dev/null +++ b/src/next/blocks/blockDevelopmentDocs.test.ts @@ -0,0 +1,145 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockDevelopmentDocs } from "./blockDevelopmentDocs.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockDevelopmentDocs", () => { + test("without addons", () => { + const creation = testBlock(blockDevelopmentDocs, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + ".github": { + "DEVELOPMENT.md": "# Development + + After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation): + + \`\`\`shell + git clone https://github.com//test-repository + cd test-repository + pnpm install + \`\`\` + ", + }, + }, + } + `); + }); + + test("with addons", () => { + const creation = testBlock(blockDevelopmentDocs, { + addons: { + hints: ["> Be excellent to each other!"], + sections: { + First: { + contents: "abc def", + }, + Fourth: {}, + Second: { + contents: { + after: ["second-after"], + before: "second-before", + items: ["- a", "- b", "- c"], + plural: "seconds", + }, + innerSections: [ + { + contents: "second-inner", + heading: "Seconds Inside", + }, + ], + }, + Third: { + contents: {}, + innerSections: [ + { + contents: "second-inner", + heading: "Seconds Inside", + }, + ], + }, + }, + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + ".github": { + "DEVELOPMENT.md": "# Development + + After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation): + + \`\`\`shell + git clone https://github.com//test-repository + cd test-repository + pnpm install + \`\`\` + + > Be excellent to each other! + + ## First + + abc def + ## Second + + second-before + - a + - b + - c + + seconds + second-after + ### Seconds Inside + + second-inner + ## Third + + ### Seconds Inside + + second-inner", + }, + }, + } + `); + }); + + test("with options.guide", () => { + const creation = testBlock(blockDevelopmentDocs, { + options: { + ...optionsBase, + guide: { + href: "https://example.com", + title: "My Guide", + }, + }, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + ".github": { + "DEVELOPMENT.md": "# Development + + > If you'd like a more guided walkthrough, see [My Guide](https://example.com). + > It'll walk you through the common activities you'll need to contribute. + + After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation): + + \`\`\`shell + git clone https://github.com//test-repository + cd test-repository + pnpm install + \`\`\` + ", + }, + }, + } + `); + }); +}); diff --git a/src/next/blocks/blockDevelopmentDocs.ts b/src/next/blocks/blockDevelopmentDocs.ts index 597676d9..27501763 100644 --- a/src/next/blocks/blockDevelopmentDocs.ts +++ b/src/next/blocks/blockDevelopmentDocs.ts @@ -25,16 +25,12 @@ const zSection = z.object({ }), ]) .optional(), - innerSections: z.array(zInnerSection).default([]).optional(), + innerSections: z.array(zInnerSection).optional(), }); type Section = z.infer; function printSection(heading: string, section: Section) { - if (typeof section === "string") { - return section; - } - const innerSections = section.innerSections?.flatMap(printInnerSection) ?? []; if (section.contents === undefined) { @@ -75,9 +71,9 @@ export const blockDevelopmentDocs = base.createBlock({ ? [ `> If you'd like a more guided walkthrough, see [${options.guide.title}](${options.guide.href}).`, `> It'll walk you through the common activities you'll need to contribute.`, + ``, ] : []), - ``, `After [forking the repo from GitHub](https://help.github.com/articles/fork-a-repo) and [installing pnpm](https://pnpm.io/installation):`, ``, `\`\`\`shell`, @@ -86,9 +82,7 @@ export const blockDevelopmentDocs = base.createBlock({ `pnpm install`, `\`\`\``, ``, - `> This repository includes a list of suggested VS Code extensions.`, - `> It's a good idea to use [VS Code](https://code.visualstudio.com) and accept its suggestion to install them, as they'll help with development.`, - ``, + ...(addons.hints.length ? [...addons.hints, ``] : []), ...Object.entries(addons.sections) .sort(([a], [b]) => a.localeCompare(b)) .flatMap(([heading, section]) => printSection(heading, section)), diff --git a/src/next/blocks/blockESLint.test.ts b/src/next/blocks/blockESLint.test.ts new file mode 100644 index 00000000..87ad728a --- /dev/null +++ b/src/next/blocks/blockESLint.test.ts @@ -0,0 +1,297 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockESLint } from "./blockESLint.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockESLint", () => { + test("without addons", () => { + const creation = testBlock(blockESLint, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "words": [ + "tseslint", + ], + }, + "block": [Function], + }, + { + "addons": { + "sections": { + "Linting": { + "contents": { + "after": [ + " + For example, ESLint can be run with \`--fix\` to auto-fix some lint rule complaints: + + \`\`\`shell + pnpm run lint --fix + \`\`\` + ", + ], + "before": " + This package includes several forms of linting to enforce consistent code quality and styling. + Each should be shown in VS Code, and can be run manually on the command-line: + ", + "items": [ + "- \`pnpm lint\` ([ESLint](https://eslint.org) with [typescript-eslint](https://typescript-eslint.io)): Lints JavaScript and TypeScript source files", + ], + "plural": "Read the individual documentation for each linter to understand how it can be configured and used best.", + }, + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Lint", + "steps": [ + { + "run": "pnpm lint", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "@eslint/js": "9.16.0", + "@types/node": "22.10.1", + "eslint": "9.16.0", + "typescript-eslint": "8.18.0", + }, + "scripts": { + "lint": "eslint . --max-warnings 0", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "extensions": [ + "dbaeumer.vscode-eslint", + ], + "settings": { + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + }, + "eslint.probe": [ + "javascript", + "javascriptreact", + "json", + "jsonc", + "markdown", + "typescript", + "typescriptreact", + "yaml", + ], + "eslint.rules.customizations": [ + { + "rule": "*", + "severity": "warn", + }, + ], + }, + }, + "block": [Function], + }, + ], + "files": { + "eslint.config.js": "import eslint from "@eslint/js"; + import tseslint from "typescript-eslint"; + + export default tseslint.config( + { ignores: ["lib", "node_modules", "pnpm-lock.yaml"] }, + { linterOptions: {"reportUnusedDisableDirectives":"error"} }, + eslint.configs.recommended, + { extends: [tseslint.configs.strictTypeChecked, tseslint.configs.stylisticTypeChecked], files: ["**/*.js", "**/*.ts"], languageOptions: {"parserOptions":{"projectService":{"allowDefaultProject":["*.config.*s"]},"tsconfigRootDir":import.meta.dirname}}, } + );", + }, + "scripts": [ + { + "commands": [ + "pnpm lint --fix", + ], + "phase": 2, + }, + ], + } + `); + }); + + test("with addons", () => { + const creation = testBlock(blockESLint, { + addons: { + beforeLint: "Before lint.", + extensions: [ + "a.configs.recommended", + { + extends: ["b.configs.recommended"], + files: ["**/*.b"], + rules: { + "b/c": "error", + "b/d": ["error", { e: "f" }], + }, + }, + ], + ignores: ["generated"], + imports: [ + { source: "eslint-plugin-markdown", specifier: "a", types: true }, + { source: "eslint-plugin-regexp", specifier: "b" }, + ], + rules: { + "a/b": "error", + "a/c": ["error", { d: "e" }], + }, + settings: { + react: { + version: "detect", + }, + }, + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "words": [ + "tseslint", + ], + }, + "block": [Function], + }, + { + "addons": { + "sections": { + "Linting": { + "contents": { + "after": [ + " + For example, ESLint can be run with \`--fix\` to auto-fix some lint rule complaints: + + \`\`\`shell + pnpm run lint --fix + \`\`\` + ", + "Before lint.", + ], + "before": " + This package includes several forms of linting to enforce consistent code quality and styling. + Each should be shown in VS Code, and can be run manually on the command-line: + ", + "items": [ + "- \`pnpm lint\` ([ESLint](https://eslint.org) with [typescript-eslint](https://typescript-eslint.io)): Lints JavaScript and TypeScript source files", + ], + "plural": "Read the individual documentation for each linter to understand how it can be configured and used best.", + }, + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Lint", + "steps": [ + { + "run": "pnpm lint", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "@eslint/js": "9.16.0", + "@types/eslint-plugin-markdown": "2.0.2", + "@types/node": "22.10.1", + "eslint": "9.16.0", + "eslint-plugin-markdown": "5.1.0", + "eslint-plugin-regexp": "2.7.0", + "typescript-eslint": "8.18.0", + }, + "scripts": { + "lint": "eslint . --max-warnings 0", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "extensions": [ + "dbaeumer.vscode-eslint", + ], + "settings": { + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + }, + "eslint.probe": [ + "javascript", + "javascriptreact", + "json", + "jsonc", + "markdown", + "typescript", + "typescriptreact", + "yaml", + ], + "eslint.rules.customizations": [ + { + "rule": "*", + "severity": "warn", + }, + ], + }, + }, + "block": [Function], + }, + ], + "files": { + "eslint.config.js": "import eslint from "@eslint/js"; + import a from "eslint-plugin-markdown" + import b from "eslint-plugin-regexp" + import tseslint from "typescript-eslint"; + + export default tseslint.config( + { ignores: ["generated", "lib", "node_modules", "pnpm-lock.yaml"] }, + { linterOptions: {"reportUnusedDisableDirectives":"error"} }, + eslint.configs.recommended, + a.configs.recommended,{ extends: [b.configs.recommended], files: ["**/*.b"], rules: {"b/c":"error","b/d":["error",{"e":"f"}]}, },{ extends: [tseslint.configs.strictTypeChecked, tseslint.configs.stylisticTypeChecked], files: ["**/*.js", "**/*.ts"], languageOptions: {"parserOptions":{"projectService":{"allowDefaultProject":["*.config.*s"]},"tsconfigRootDir":import.meta.dirname}}, rules: {"a/b":"error","a/c":["error",{"d":"e"}]}, settings: {"react":{"version":"detect"}}, } + );", + }, + "scripts": [ + { + "commands": [ + "pnpm lint --fix", + ], + "phase": 2, + }, + ], + } + `); + }); +}); diff --git a/src/next/blocks/blockExampleFiles.test.ts b/src/next/blocks/blockExampleFiles.test.ts new file mode 100644 index 00000000..b6392c37 --- /dev/null +++ b/src/next/blocks/blockExampleFiles.test.ts @@ -0,0 +1,50 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockExampleFiles } from "./blockExampleFiles.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockExampleFiles", () => { + test("without addons", () => { + const creation = testBlock(blockExampleFiles, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(`{}`); + }); + + test("with addons and without mode", () => { + const creation = testBlock(blockExampleFiles, { + addons: { + files: { + "index.ts": "console.log('Hello, world!');", + }, + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(`{}`); + }); + + test("with addons and mode", () => { + const creation = testBlock(blockExampleFiles, { + addons: { + files: { + "index.ts": "console.log('Hello, world!');", + }, + }, + mode: "new", + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + "src": { + "index.ts": "console.log('Hello, world!');", + }, + }, + } + `); + }); +}); diff --git a/src/next/blocks/blockGitHubActionsCI.test.ts b/src/next/blocks/blockGitHubActionsCI.test.ts new file mode 100644 index 00000000..bbe02a89 --- /dev/null +++ b/src/next/blocks/blockGitHubActionsCI.test.ts @@ -0,0 +1,216 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockGitHubActionsCI } from "./blockGitHubActionsCI.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockGitHubActionsCI", () => { + test("without addons", () => { + const creation = testBlock(blockGitHubActionsCI, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + ".github": { + "actions": { + "prepare": { + "action.yml": "description: Prepares the repo for a typical CI job + + name: Prepare + + runs: + steps: + - uses: pnpm/action-setup@v4 + with: + version: 9 + - uses: actions/setup-node@v4 + with: + cache: pnpm + node-version: '20' + - run: pnpm install --frozen-lockfile + shell: bash + using: composite + ", + }, + }, + "workflows": { + "accessibility-alt-text-bot.yml": "jobs: + accessibility_alt_text_bot: + if: \${{ !endsWith(github.actor, '[bot]') }} + runs-on: ubuntu-latest + steps: + - uses: github/accessibility-alt-text-bot@v1.4.0 + + name: Accessibility Alt Text Bot + + on: + issue_comment: + types: + - created + - edited + issues: + types: + - edited + - opened + pull_request: + types: + - edited + - opened + + permissions: + issues: write + pull-requests: write + ", + "ci.yml": undefined, + "pr-review-requested.yml": "jobs: + pr_review_requested: + runs-on: ubuntu-latest + steps: + - uses: actions-ecosystem/action-remove-labels@v1 + with: + labels: "status: waiting for author" + - if: failure() + run: | + echo "Don't worry if the previous step failed." + echo "See https://github.com/actions-ecosystem/action-remove-labels/issues/221." + + name: PR Review Requested + + on: + pull_request_target: + types: + - review_requested + + permissions: + pull-requests: write + ", + }, + }, + }, + } + `); + }); + + test("with addons", () => { + const creation = testBlock(blockGitHubActionsCI, { + addons: { + jobs: [ + { + name: "Validate", + steps: [ + { + if: "always()", + run: "pnpm validate", + with: { ENV_VAR: "true" }, + }, + ], + }, + ], + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + ".github": { + "actions": { + "prepare": { + "action.yml": "description: Prepares the repo for a typical CI job + + name: Prepare + + runs: + steps: + - uses: pnpm/action-setup@v4 + with: + version: 9 + - uses: actions/setup-node@v4 + with: + cache: pnpm + node-version: '20' + - run: pnpm install --frozen-lockfile + shell: bash + using: composite + ", + }, + }, + "workflows": { + "accessibility-alt-text-bot.yml": "jobs: + accessibility_alt_text_bot: + if: \${{ !endsWith(github.actor, '[bot]') }} + runs-on: ubuntu-latest + steps: + - uses: github/accessibility-alt-text-bot@v1.4.0 + + name: Accessibility Alt Text Bot + + on: + issue_comment: + types: + - created + - edited + issues: + types: + - edited + - opened + pull_request: + types: + - edited + - opened + + permissions: + issues: write + pull-requests: write + ", + "ci.yml": "jobs: + validate: + name: Validate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/prepare + - if: always() + run: pnpm validate + with: + ENV_VAR: "true" + + name: CI + + on: + pull_request: ~ + push: + branches: + - main + ", + "pr-review-requested.yml": "jobs: + pr_review_requested: + runs-on: ubuntu-latest + steps: + - uses: actions-ecosystem/action-remove-labels@v1 + with: + labels: "status: waiting for author" + - if: failure() + run: | + echo "Don't worry if the previous step failed." + echo "See https://github.com/actions-ecosystem/action-remove-labels/issues/221." + + name: PR Review Requested + + on: + pull_request_target: + types: + - review_requested + + permissions: + pull-requests: write + ", + }, + }, + }, + } + `); + }); +}); diff --git a/src/next/blocks/blockGitignore.test.ts b/src/next/blocks/blockGitignore.test.ts new file mode 100644 index 00000000..67c2b35b --- /dev/null +++ b/src/next/blocks/blockGitignore.test.ts @@ -0,0 +1,41 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockGitignore } from "./blockGitignore.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockGitignore", () => { + test("without addons", () => { + const creation = testBlock(blockGitignore, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + ".gitignore": "/node_modules + ", + }, + } + `); + }); + + test("with addons", () => { + const creation = testBlock(blockGitignore, { + addons: { + ignores: ["/lib"], + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + ".gitignore": "/lib + /node_modules + ", + }, + } + `); + }); +}); diff --git a/src/next/blocks/blockMarkdownlint.test.ts b/src/next/blocks/blockMarkdownlint.test.ts new file mode 100644 index 00000000..e955adbb --- /dev/null +++ b/src/next/blocks/blockMarkdownlint.test.ts @@ -0,0 +1,169 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockMarkdownlint } from "./blockMarkdownlint.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockMarkdownlint", () => { + test("without addons", () => { + const creation = testBlock(blockMarkdownlint, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "words": [ + "markdownlintignore", + ], + }, + "block": [Function], + }, + { + "addons": { + "sections": { + "Linting": { + "contents": { + "items": [ + "- \`pnpm lint:md\` ([Markdownlint](https://github.com/DavidAnson/markdownlint)): Checks Markdown source files", + ], + }, + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Lint Markdown", + "steps": [ + { + "run": "pnpm lint:md", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "markdownlint": "0.36.1", + "markdownlint-cli": "0.43.0", + "sentences-per-line": "0.2.1", + }, + "scripts": { + "lint:md": "markdownlint "**/*.md" ".github/**/*.md" --rules sentences-per-line", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "extensions": [ + "DavidAnson.vscode-markdownlint", + ], + }, + "block": [Function], + }, + ], + "files": { + ".markdownlint.json": "{"extends":"markdownlint/style/prettier","first-line-h1":false,"no-inline-html":false}", + ".markdownlintignore": ".github/CODE_OF_CONDUCT.md + CHANGELOG.md + node_modules/", + }, + } + `); + }); + + test("with addons", () => { + const creation = testBlock(blockMarkdownlint, { + addons: { + ignores: ["lib/"], + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "words": [ + "markdownlintignore", + ], + }, + "block": [Function], + }, + { + "addons": { + "sections": { + "Linting": { + "contents": { + "items": [ + "- \`pnpm lint:md\` ([Markdownlint](https://github.com/DavidAnson/markdownlint)): Checks Markdown source files", + ], + }, + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Lint Markdown", + "steps": [ + { + "run": "pnpm lint:md", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "markdownlint": "0.36.1", + "markdownlint-cli": "0.43.0", + "sentences-per-line": "0.2.1", + }, + "scripts": { + "lint:md": "markdownlint "**/*.md" ".github/**/*.md" --rules sentences-per-line", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "extensions": [ + "DavidAnson.vscode-markdownlint", + ], + }, + "block": [Function], + }, + ], + "files": { + ".markdownlint.json": "{"extends":"markdownlint/style/prettier","first-line-h1":false,"no-inline-html":false}", + ".markdownlintignore": ".github/CODE_OF_CONDUCT.md + CHANGELOG.md + lib/ + node_modules/", + }, + } + `); + }); +}); diff --git a/src/next/blocks/blockPackageJson.test.ts b/src/next/blocks/blockPackageJson.test.ts new file mode 100644 index 00000000..fe3ae479 --- /dev/null +++ b/src/next/blocks/blockPackageJson.test.ts @@ -0,0 +1,61 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockPackageJson } from "./blockPackageJson.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockPackageJson", () => { + test("without addons", () => { + const creation = testBlock(blockPackageJson, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + "package.json": "{"name":"test-repository","version":"0.0.0","description":"Test description","repository":{"type":"git","url":"https://github.com/test-owner/test-repository"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","main":"./lib/index.js","files":["README.md","package.json"],"scripts":{},"dependencies":{},"devDependencies":{}}", + }, + "scripts": [ + { + "commands": [ + "pnpm install", + ], + "phase": 0, + }, + ], + } + `); + }); + + test("with addons", () => { + const creation = testBlock(blockPackageJson, { + addons: { + cleanupCommands: ["pnpm dedupe"], + properties: { + dependencies: { + "is-odd": "3.0.1", + }, + other: true, + }, + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + "package.json": "{"name":"test-repository","version":"0.0.0","description":"Test description","repository":{"type":"git","url":"https://github.com/test-owner/test-repository"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","main":"./lib/index.js","files":["README.md","package.json"],"scripts":{},"dependencies":{"is-odd":"3.0.1"},"devDependencies":{},"other":true}", + }, + "scripts": [ + { + "commands": [ + "pnpm install", + "pnpm dedupe", + ], + "phase": 0, + }, + ], + } + `); + }); +}); diff --git a/src/next/blocks/blockPrettier.test.ts b/src/next/blocks/blockPrettier.test.ts new file mode 100644 index 00000000..a87b04d7 --- /dev/null +++ b/src/next/blocks/blockPrettier.test.ts @@ -0,0 +1,236 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockPrettier } from "./blockPrettier.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockPrettier", () => { + test("without addons", () => { + const creation = testBlock(blockPrettier, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "ignores": [ + ".all-contributorsrc", + ], + }, + "block": [Function], + }, + { + "addons": { + "sections": { + "Formatting": { + "contents": " + [Prettier](https://prettier.io) is used to format code. + It should be applied automatically when you save files in VS Code or make a Git commit. + + To manually reformat all files, you can run: + + \`\`\`shell + pnpm format --write + \`\`\` + ", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Prettier", + "steps": [ + { + "run": "pnpm format --list-different", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "husky": "9.1.7", + "lint-staged": "15.2.10", + "prettier": "^3.4.1", + }, + "lint-staged": { + "*": "prettier --ignore-unknown --write", + }, + "scripts": { + "format": "prettier .", + "prepare": "husky", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "extensions": [ + "esbenp.prettier-vscode", + ], + "settings": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + }, + }, + "block": [Function], + }, + ], + "files": { + ".husky": { + ".gitignore": "_", + "pre-commit": [ + "npx lint-staged", + { + "mode": 1911, + }, + ], + }, + ".prettierignore": "/.husky + /lib + /pnpm-lock.yaml", + ".prettierrc.json": "{"$schema":"http://json.schemastore.org/prettierrc","useTabs":true}", + }, + "scripts": [ + { + "commands": [ + "pnpm format --write", + ], + "phase": 3, + }, + ], + } + `); + }); + + test("with addons", () => { + const creation = testBlock(blockPrettier, { + addons: { + ignores: ["generated"], + overrides: [{ files: ".nvmrc", options: { parser: "yaml" } }], + plugins: [ + "prettier-plugin-curly", + "prettier-plugin-packagejson", + "prettier-plugin-sh", + ], + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "ignores": [ + ".all-contributorsrc", + ], + }, + "block": [Function], + }, + { + "addons": { + "sections": { + "Formatting": { + "contents": " + [Prettier](https://prettier.io) is used to format code. + It should be applied automatically when you save files in VS Code or make a Git commit. + + To manually reformat all files, you can run: + + \`\`\`shell + pnpm format --write + \`\`\` + ", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Prettier", + "steps": [ + { + "run": "pnpm format --list-different", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "husky": "9.1.7", + "lint-staged": "15.2.10", + "prettier": "^3.4.1", + "prettier-plugin-curly": "0.3.1", + "prettier-plugin-packagejson": "2.5.6", + "prettier-plugin-sh": "0.14.0", + }, + "lint-staged": { + "*": "prettier --ignore-unknown --write", + }, + "scripts": { + "format": "prettier .", + "prepare": "husky", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "extensions": [ + "esbenp.prettier-vscode", + ], + "settings": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + }, + }, + "block": [Function], + }, + ], + "files": { + ".husky": { + ".gitignore": "_", + "pre-commit": [ + "npx lint-staged", + { + "mode": 1911, + }, + ], + }, + ".prettierignore": "/.husky + /lib + /pnpm-lock.yaml + generated", + ".prettierrc.json": "{"$schema":"http://json.schemastore.org/prettierrc","overrides":[{"files":".nvmrc","options":{"parser":"yaml"}}],"plugins":["prettier-plugin-curly","prettier-plugin-packagejson","prettier-plugin-sh"],"useTabs":true}", + }, + "scripts": [ + { + "commands": [ + "pnpm format --write", + ], + "phase": 3, + }, + ], + } + `); + }); +}); diff --git a/src/next/blocks/blockREADME.test.ts b/src/next/blocks/blockREADME.test.ts new file mode 100644 index 00000000..2b4e5353 --- /dev/null +++ b/src/next/blocks/blockREADME.test.ts @@ -0,0 +1,179 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockREADME } from "./blockREADME.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockREADME", () => { + test("options.logo", () => { + const creation = testBlock(blockREADME, { + options: { + ...optionsBase, + logo: { + alt: "My logo", + src: "img.jpg", + }, + }, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + "README.md": "

Test Title

+ +

Test description

+ +

+ + + + + ๐Ÿค Code of Conduct: Kept + ๐Ÿงช Coverage + ๐Ÿ“ License: MIT + ๐Ÿ“ฆ npm version + ๐Ÿ’ช TypeScript: Strict +

+ + My logo + + ## Usage + + \`\`\`shell + npm i test-repository + \`\`\` + \`\`\`ts + import { greet } from "test-repository"; + + greet("Hello, world! ๐Ÿ’–"); + \`\`\` + + ## Development + + See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md). + Thanks! ๐Ÿ’– + + ## Contributors + + + + + + ", + }, + } + `); + }); + + test("without addons", () => { + const creation = testBlock(blockREADME, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + "README.md": "

Test Title

+ +

Test description

+ +

+ + + + + ๐Ÿค Code of Conduct: Kept + ๐Ÿงช Coverage + ๐Ÿ“ License: MIT + ๐Ÿ“ฆ npm version + ๐Ÿ’ช TypeScript: Strict +

+ + + + ## Usage + + \`\`\`shell + npm i test-repository + \`\`\` + \`\`\`ts + import { greet } from "test-repository"; + + greet("Hello, world! ๐Ÿ’–"); + \`\`\` + + ## Development + + See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md). + Thanks! ๐Ÿ’– + + ## Contributors + + + + + + ", + }, + } + `); + }); + + test("with addons", () => { + const creation = testBlock(blockREADME, { + addons: { + notices: ["> Hello, world! ๐Ÿ’–"], + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + "README.md": "

Test Title

+ +

Test description

+ +

+ + + + + ๐Ÿค Code of Conduct: Kept + ๐Ÿงช Coverage + ๐Ÿ“ License: MIT + ๐Ÿ“ฆ npm version + ๐Ÿ’ช TypeScript: Strict +

+ + + + ## Usage + + \`\`\`shell + npm i test-repository + \`\`\` + \`\`\`ts + import { greet } from "test-repository"; + + greet("Hello, world! ๐Ÿ’–"); + \`\`\` + + ## Development + + See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md). + Thanks! ๐Ÿ’– + + ## Contributors + + + + + + + > Hello, world! ๐Ÿ’–", + }, + } + `); + }); +}); diff --git a/src/next/blocks/blockRepositoryBranchProtection.test.ts b/src/next/blocks/blockRepositoryBranchProtection.test.ts new file mode 100644 index 00000000..bef44aa9 --- /dev/null +++ b/src/next/blocks/blockRepositoryBranchProtection.test.ts @@ -0,0 +1,47 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockRepositoryBranchProtection } from "./blockRepositoryBranchProtection.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockRepositoryBranchProtection", () => { + test("without addons", () => { + const creation = testBlock(blockRepositoryBranchProtection, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "requests": [ + { + "id": "branch-protection", + "send": [Function], + }, + ], + } + `); + }); + + // TODO for improving the "requests" snapshots: + // https://github.com/JoshuaKGoldberg/create/issues/65 + + test("with addons", () => { + const creation = testBlock(blockRepositoryBranchProtection, { + addons: { + requiredStatusChecks: ["build", "test"], + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "requests": [ + { + "id": "branch-protection", + "send": [Function], + }, + ], + } + `); + }); +}); diff --git a/src/next/blocks/blockTSup.test.ts b/src/next/blocks/blockTSup.test.ts new file mode 100644 index 00000000..ccde157f --- /dev/null +++ b/src/next/blocks/blockTSup.test.ts @@ -0,0 +1,184 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockTSup } from "./blockTSup.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockTSup", () => { + test("without addons", () => { + const creation = testBlock(blockTSup, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "sections": { + "Building": { + "contents": " + Run [**tsup**](https://tsup.egoist.dev) locally to build source files from \`src/\` into output files in \`lib/\`: + + \`\`\`shell + pnpm build + \`\`\` + + Add \`--watch\` to run the builder in a watch mode that continuously cleans and recreates \`lib/\` as you save files: + + \`\`\`shell + pnpm build --watch + \`\`\` + ", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "beforeLint": "Note that you'll need to run \`pnpm build\` before \`pnpm lint\` so that lint rules which check the file system can pick up on any built files.", + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Build", + "steps": [ + { + "run": "pnpm build", + }, + { + "run": "node ./lib/index.js", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "tsup": "8.3.5", + }, + "scripts": { + "build": "tsup", + }, + }, + }, + "block": [Function], + }, + ], + "files": { + "tsup.config.ts": "import { defineConfig } from "tsup"; + + export default defineConfig({ + bundle: false, + clean: true, + dts: true, + entry: ["src/**/*.ts"], + format: "esm", + outDir: "lib", + sourcemap: true, + }); + ", + }, + "scripts": undefined, + } + `); + }); + + test("with addons", () => { + const creation = testBlock(blockTSup, { + addons: { + entry: ["!src/**/*.test.ts"], + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "sections": { + "Building": { + "contents": " + Run [**tsup**](https://tsup.egoist.dev) locally to build source files from \`src/\` into output files in \`lib/\`: + + \`\`\`shell + pnpm build + \`\`\` + + Add \`--watch\` to run the builder in a watch mode that continuously cleans and recreates \`lib/\` as you save files: + + \`\`\`shell + pnpm build --watch + \`\`\` + ", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "beforeLint": "Note that you'll need to run \`pnpm build\` before \`pnpm lint\` so that lint rules which check the file system can pick up on any built files.", + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Build", + "steps": [ + { + "run": "pnpm build", + }, + { + "run": "node ./lib/index.js", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "tsup": "8.3.5", + }, + "scripts": { + "build": "tsup", + }, + }, + }, + "block": [Function], + }, + ], + "files": { + "tsup.config.ts": "import { defineConfig } from "tsup"; + + export default defineConfig({ + bundle: false, + clean: true, + dts: true, + entry: ["src/**/*.ts","!src/**/*.test.ts"], + format: "esm", + outDir: "lib", + sourcemap: true, + }); + ", + }, + "scripts": undefined, + } + `); + }); +}); diff --git a/src/next/blocks/blockTemplatedBy.ts b/src/next/blocks/blockTemplatedBy.ts index bcf3aab7..72403124 100644 --- a/src/next/blocks/blockTemplatedBy.ts +++ b/src/next/blocks/blockTemplatedBy.ts @@ -1,5 +1,3 @@ -import { z } from "zod"; - import { base } from "../base.js"; import { blockCSpell } from "./blockCSpell.js"; import { blockREADME } from "./blockREADME.js"; @@ -8,9 +6,6 @@ export const blockTemplatedBy = base.createBlock({ about: { name: "Templated By Notice", }, - addons: { - notices: z.array(z.string()).default([]), - }, produce() { return { addons: [ diff --git a/src/next/blocks/blockTypeScript.test.ts b/src/next/blocks/blockTypeScript.test.ts new file mode 100644 index 00000000..47f57b24 --- /dev/null +++ b/src/next/blocks/blockTypeScript.test.ts @@ -0,0 +1,313 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockTypeScript } from "./blockTypeScript.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockTypeScript", () => { + test("without options.bin", () => { + const creation = testBlock(blockTypeScript, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "sections": { + "Type Checking": { + "contents": " + You should be able to see suggestions from [TypeScript](https://typescriptlang.org) in your editor for all open files. + + However, it can be useful to run the TypeScript command-line (\`tsc\`) to type check all files in \`src/\`: + + \`\`\`shell + pnpm tsc + \`\`\` + + Add \`--watch\` to keep the type checker running in a watch mode that updates the display as you save files: + + \`\`\`shell + pnpm tsc --watch + \`\`\` + ", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "files": { + "greet.ts": "import { GreetOptions } from "./types.js"; + + export function greet(options: GreetOptions | string) { + const { + logger = console.log.bind(console), + message, + times = 1, + } = typeof options === "string" ? { message: options } : options; + + for (let i = 0; i < times; i += 1) { + logger(message); + } + } + ", + "index.ts": "export * from "./greet.js"; + export * from "./types.js"; + ", + "types.ts": "export interface GreetOptions { + logger?: (message: string) => void; + message: string; + times?: number; + } + ", + }, + }, + "block": [Function], + }, + { + "addons": { + "ignores": [ + "/lib", + ], + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Type Check", + "steps": [ + { + "run": "pnpm tsc", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "ignores": [ + "lib/", + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "typescript": "5.7.2", + }, + "files": [ + "lib/", + ], + "main": "./lib/index.js", + "scripts": { + "tsc": "tsc", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "exclude": [ + "lib", + ], + "include": [ + "src", + ], + }, + "block": [Function], + }, + { + "addons": { + "debuggers": [], + "settings": { + "typescript.tsdk": "node_modules/typescript/lib", + }, + "tasks": [ + { + "detail": "Build the project", + "label": "build", + "script": "build", + "type": "npm", + }, + ], + }, + "block": [Function], + }, + ], + "files": { + "tsconfig.json": "{"compilerOptions":{"declaration":true,"declarationMap":true,"esModuleInterop":true,"module":"NodeNext","moduleResolution":"NodeNext","noEmit":true,"resolveJsonModule":true,"skipLibCheck":true,"sourceMap":true,"strict":true,"target":"ES2022"},"include":["src"]}", + }, + } + `); + }); + + test("with options.bin", () => { + const creation = testBlock(blockTypeScript, { + options: { + ...optionsBase, + bin: "bin/index.mjs", + }, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "sections": { + "Type Checking": { + "contents": " + You should be able to see suggestions from [TypeScript](https://typescriptlang.org) in your editor for all open files. + + However, it can be useful to run the TypeScript command-line (\`tsc\`) to type check all files in \`src/\`: + + \`\`\`shell + pnpm tsc + \`\`\` + + Add \`--watch\` to keep the type checker running in a watch mode that updates the display as you save files: + + \`\`\`shell + pnpm tsc --watch + \`\`\` + ", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "files": { + "greet.ts": "import { GreetOptions } from "./types.js"; + + export function greet(options: GreetOptions | string) { + const { + logger = console.log.bind(console), + message, + times = 1, + } = typeof options === "string" ? { message: options } : options; + + for (let i = 0; i < times; i += 1) { + logger(message); + } + } + ", + "index.ts": "export * from "./greet.js"; + export * from "./types.js"; + ", + "types.ts": "export interface GreetOptions { + logger?: (message: string) => void; + message: string; + times?: number; + } + ", + }, + }, + "block": [Function], + }, + { + "addons": { + "ignores": [ + "/lib", + ], + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Type Check", + "steps": [ + { + "run": "pnpm tsc", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "ignores": [ + "lib/", + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "typescript": "5.7.2", + }, + "files": [ + "lib/", + ], + "main": "./lib/index.js", + "scripts": { + "tsc": "tsc", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "exclude": [ + "lib", + ], + "include": [ + "src", + ], + }, + "block": [Function], + }, + { + "addons": { + "debuggers": [ + { + "name": "Debug Program", + "preLaunchTask": "build", + "program": "bin/index.mjs", + "request": "launch", + "skipFiles": [ + "/**", + ], + "type": "node", + }, + ], + "settings": { + "typescript.tsdk": "node_modules/typescript/lib", + }, + "tasks": [ + { + "detail": "Build the project", + "label": "build", + "script": "build", + "type": "npm", + }, + ], + }, + "block": [Function], + }, + ], + "files": { + "tsconfig.json": "{"compilerOptions":{"declaration":true,"declarationMap":true,"esModuleInterop":true,"module":"NodeNext","moduleResolution":"NodeNext","noEmit":true,"resolveJsonModule":true,"skipLibCheck":true,"sourceMap":true,"strict":true,"target":"ES2022"},"include":["src"]}", + }, + } + `); + }); +}); diff --git a/src/next/blocks/blockVSCode.test.ts b/src/next/blocks/blockVSCode.test.ts new file mode 100644 index 00000000..91c0130a --- /dev/null +++ b/src/next/blocks/blockVSCode.test.ts @@ -0,0 +1,118 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockVSCode } from "./blockVSCode.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockVSCode", () => { + test("without addons", () => { + const creation = testBlock(blockVSCode, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "hints": [ + "> This repository includes a list of suggested VS Code extensions.", + "> It's a good idea to use [VS Code](https://code.visualstudio.com) and accept its suggestion to install them, as they'll help with development.", + ], + "sections": { + "Building": { + "innerSections": [], + }, + "Testing": { + "innerSections": [ + { + "contents": " + This repository includes a [VS Code launch configuration](https://code.visualstudio.com/docs/editor/debugging) for debugging unit tests. + To launch it, open a test file, then run _Debug Current Test File_ from the VS Code Debug panel (or press F5). + ", + "heading": "Debugging Tests", + }, + ], + }, + }, + }, + "block": [Function], + }, + ], + "files": { + ".vscode": { + "extensions.json": undefined, + "launch.json": undefined, + "settings.json": "{"editor.formatOnSave":true,"editor.rulers":[80]}", + "tasks.json": undefined, + }, + }, + } + `); + }); + + test("with addons", () => { + const creation = testBlock(blockVSCode, { + addons: { + debuggers: [ + { + name: "fake-debugger", + other: true, + }, + ], + settings: { + "editor.formatOnSave": true, + }, + tasks: [ + { + detail: "Build the project", + label: "build", + script: "build", + type: "npm", + }, + ], + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "hints": [ + "> This repository includes a list of suggested VS Code extensions.", + "> It's a good idea to use [VS Code](https://code.visualstudio.com) and accept its suggestion to install them, as they'll help with development.", + ], + "sections": { + "Building": { + "innerSections": [], + }, + "Testing": { + "innerSections": [ + { + "contents": " + This repository includes a [VS Code launch configuration](https://code.visualstudio.com/docs/editor/debugging) for debugging unit tests. + To launch it, open a test file, then run _Debug Current Test File_ from the VS Code Debug panel (or press F5). + ", + "heading": "Debugging Tests", + }, + ], + }, + }, + }, + "block": [Function], + }, + ], + "files": { + ".vscode": { + "extensions.json": undefined, + "launch.json": "{"configurations":[{"name":"fake-debugger","other":true}],"version":"0.2.0"}", + "settings.json": "{"editor.formatOnSave":true,"editor.rulers":[80]}", + "tasks.json": "{"tasks":[{"detail":"Build the project","label":"build","script":"build","type":"npm"}],"version":"2.0.0"}", + }, + }, + } + `); + }); +}); diff --git a/src/next/blocks/blockVSCode.ts b/src/next/blocks/blockVSCode.ts index d1779288..81efe730 100644 --- a/src/next/blocks/blockVSCode.ts +++ b/src/next/blocks/blockVSCode.ts @@ -34,6 +34,10 @@ export const blockVSCode = base.createBlock({ return { addons: [ blockDevelopmentDocs({ + hints: [ + `> This repository includes a list of suggested VS Code extensions.`, + `> It's a good idea to use [VS Code](https://code.visualstudio.com) and accept its suggestion to install them, as they'll help with development.`, + ], sections: { Building: { innerSections: options.bin diff --git a/src/next/blocks/blockVitest.test.ts b/src/next/blocks/blockVitest.test.ts new file mode 100644 index 00000000..83476358 --- /dev/null +++ b/src/next/blocks/blockVitest.test.ts @@ -0,0 +1,492 @@ +import { testBlock } from "create-testers"; +import { describe, expect, test } from "vitest"; + +import { blockVitest } from "./blockVitest.js"; +import { optionsBase } from "./options.fakes.js"; + +describe("blockVitest", () => { + test("without addons", () => { + const creation = testBlock(blockVitest, { + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "ignores": [ + "coverage", + ], + }, + "block": [Function], + }, + { + "addons": { + "extensions": [ + { + "extends": [ + "vitest.configs.recommended", + ], + "files": [ + "**/*.test.*", + ], + "rules": [ + { + "entries": { + "@typescript-eslint/no-unsafe-assignment": "off", + }, + }, + ], + }, + ], + "ignores": [ + "coverage", + "**/*.snap", + ], + "imports": [ + { + "source": "@vitest/eslint-plugin", + "specifier": "vitest", + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "sections": { + "Testing": { + "contents": " + [Vitest](https://vitest.dev) is used for tests. + You can run it locally on the command-line: + + \`\`\`shell + pnpm run test + \`\`\` + + Add the \`--coverage\` flag to compute test coverage and place reports in the \`coverage/\` directory: + + \`\`\`shell + pnpm run test --coverage + \`\`\` + + Note that [console-fail-test](https://github.com/JoshuaKGoldberg/console-fail-test) is enabled for all test runs. + Calls to \`console.log\`, \`console.warn\`, and other console methods will cause a test to fail. + + + ", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "files": { + "greet.test.ts": "import { describe, expect, it, vi } from "vitest"; + + import { greet } from "./greet.js"; + + const message = "Yay, testing!"; + + describe("greet", () => { + it("logs to the console once when message is provided as a string", () => { + const logger = vi.spyOn(console, "log").mockImplementation(() => undefined); + + greet(message); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(1); + }); + + it("logs to the console once when message is provided as an object", () => { + const logger = vi.spyOn(console, "log").mockImplementation(() => undefined); + + greet({ message }); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(1); + }); + + it("logs once when times is not provided in an object", () => { + const logger = vi.fn(); + + greet({ logger, message }); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(1); + }); + + it("logs a specified number of times when times is provided", () => { + const logger = vi.fn(); + const times = 7; + + greet({ logger, message, times }); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(7); + }); + }); + ", + }, + }, + "block": [Function], + }, + { + "addons": { + "ignores": [ + "/coverage", + ], + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Test", + "steps": [ + { + "run": "pnpm run test --coverage", + }, + { + "if": "always()", + "uses": "codecov/codecov-action@v3", + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "@vitest/coverage-v8": "2.1.8", + "@vitest/eslint-plugin": "1.1.14", + "console-fail-test": "0.5.0", + "vitest": "2.1.8", + }, + "scripts": { + "test": "vitest", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "ignores": [ + "/coverage", + ], + }, + "block": [Function], + }, + { + "addons": { + "entry": [ + "!src/**/*.test.*", + ], + }, + "block": [Function], + }, + { + "addons": { + "debuggers": [ + { + "args": [ + "run", + "\${relativeFile}", + ], + "autoAttachChildProcesses": true, + "console": "integratedTerminal", + "name": "Debug Current Test File", + "program": "\${workspaceRoot}/node_modules/vitest/vitest.mjs", + "request": "launch", + "skipFiles": [ + "/**", + "**/node_modules/**", + ], + "smartStep": true, + "type": "node", + }, + ], + "extensions": [ + "vitest.explorer", + ], + }, + "block": [Function], + }, + ], + "files": { + "vitest.config.ts": "import { defineConfig } from "vitest/config"; + + export default defineConfig({ + test: { + clearMocks: true, + coverage: { + all: true, + exclude: [], + include: [], + reporter: ["html", "lcov"], + }, + exclude: [, "node_modules"], + setupFiles: ["console-fail-test/setup"], + }, + }); + ", + }, + } + `); + }); + + test("with addons", () => { + const creation = testBlock(blockVitest, { + addons: { + coverage: { + directory: "coverage*", + flags: "unit", + }, + exclude: ["lib/"], + include: ["src/"], + }, + options: optionsBase, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "addons": [ + { + "addons": { + "ignores": [ + "coverage*", + ], + }, + "block": [Function], + }, + { + "addons": { + "extensions": [ + { + "extends": [ + "vitest.configs.recommended", + ], + "files": [ + "**/*.test.*", + ], + "rules": [ + { + "entries": { + "@typescript-eslint/no-unsafe-assignment": "off", + }, + }, + ], + }, + ], + "ignores": [ + "coverage*", + "**/*.snap", + ], + "imports": [ + { + "source": "@vitest/eslint-plugin", + "specifier": "vitest", + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "sections": { + "Testing": { + "contents": " + [Vitest](https://vitest.dev) is used for tests. + You can run it locally on the command-line: + + \`\`\`shell + pnpm run test + \`\`\` + + Add the \`--coverage\` flag to compute test coverage and place reports in the \`coverage/\` directory: + + \`\`\`shell + pnpm run test --coverage + \`\`\` + + Note that [console-fail-test](https://github.com/JoshuaKGoldberg/console-fail-test) is enabled for all test runs. + Calls to \`console.log\`, \`console.warn\`, and other console methods will cause a test to fail. + + + ", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "files": { + "greet.test.ts": "import { describe, expect, it, vi } from "vitest"; + + import { greet } from "./greet.js"; + + const message = "Yay, testing!"; + + describe("greet", () => { + it("logs to the console once when message is provided as a string", () => { + const logger = vi.spyOn(console, "log").mockImplementation(() => undefined); + + greet(message); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(1); + }); + + it("logs to the console once when message is provided as an object", () => { + const logger = vi.spyOn(console, "log").mockImplementation(() => undefined); + + greet({ message }); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(1); + }); + + it("logs once when times is not provided in an object", () => { + const logger = vi.fn(); + + greet({ logger, message }); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(1); + }); + + it("logs a specified number of times when times is provided", () => { + const logger = vi.fn(); + const times = 7; + + greet({ logger, message, times }); + + expect(logger).toHaveBeenCalledWith(message); + expect(logger).toHaveBeenCalledTimes(7); + }); + }); + ", + }, + }, + "block": [Function], + }, + { + "addons": { + "ignores": [ + "/coverage*", + ], + }, + "block": [Function], + }, + { + "addons": { + "jobs": [ + { + "name": "Test", + "steps": [ + { + "run": "pnpm run test --coverage", + }, + { + "if": "always()", + "uses": "codecov/codecov-action@v3", + "with": { + "flags": "unit", + }, + }, + ], + }, + ], + }, + "block": [Function], + }, + { + "addons": { + "properties": { + "devDependencies": { + "@vitest/coverage-v8": "2.1.8", + "@vitest/eslint-plugin": "1.1.14", + "console-fail-test": "0.5.0", + "vitest": "2.1.8", + }, + "scripts": { + "test": "vitest", + }, + }, + }, + "block": [Function], + }, + { + "addons": { + "ignores": [ + "/coverage*", + ], + }, + "block": [Function], + }, + { + "addons": { + "entry": [ + "!src/**/*.test.*", + ], + }, + "block": [Function], + }, + { + "addons": { + "debuggers": [ + { + "args": [ + "run", + "\${relativeFile}", + ], + "autoAttachChildProcesses": true, + "console": "integratedTerminal", + "name": "Debug Current Test File", + "program": "\${workspaceRoot}/node_modules/vitest/vitest.mjs", + "request": "launch", + "skipFiles": [ + "/**", + "**/node_modules/**", + ], + "smartStep": true, + "type": "node", + }, + ], + "extensions": [ + "vitest.explorer", + ], + }, + "block": [Function], + }, + ], + "files": { + "vitest.config.ts": "import { defineConfig } from "vitest/config"; + + export default defineConfig({ + test: { + clearMocks: true, + coverage: { + all: true, + exclude: ["lib/"], + include: ["src/"], + reporter: ["html", "lcov"], + }, + exclude: ["lib/", "node_modules"], + setupFiles: ["console-fail-test/setup"], + }, + }); + ", + }, + } + `); + }); +}); diff --git a/src/next/blocks/packageData.test.ts b/src/next/blocks/packageData.test.ts new file mode 100644 index 00000000..14eafebd --- /dev/null +++ b/src/next/blocks/packageData.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it, vi } from "vitest"; + +import { getPackageDependencies } from "./packageData.js"; + +vi.mock("node:module", () => ({ + createRequire: () => () => ({ + dependencies: { + "package-dep": "0.0.2", + }, + devDependencies: { + "package-dev-dep": "0.0.1", + }, + }), +})); + +describe("packageData", () => { + it("returns a devDependency when it exists", () => { + const actual = getPackageDependencies("package-dev-dep"); + + expect(actual).toEqual({ "package-dev-dep": "0.0.1" }); + }); + + it("returns a dependency when it exists", () => { + const actual = getPackageDependencies("package-dep"); + + expect(actual).toEqual({ "package-dep": "0.0.2" }); + }); + + it("throws an error when neither exist", () => { + const act = () => getPackageDependencies("package-unknown"); + + expect(act).toThrowError( + "package-unknown is neither in package.json's dependencies nor its devDependencies.", + ); + }); +}); diff --git a/src/next/blocks/packageData.ts b/src/next/blocks/packageData.ts index bbec5e03..f3def9b8 100644 --- a/src/next/blocks/packageData.ts +++ b/src/next/blocks/packageData.ts @@ -4,7 +4,6 @@ const require = createRequire(import.meta.url); const packageData = // Importing from above src/ would expand the TS build rootDir - require("../../../package.json") as typeof import("../../../package.json"); const getPackageInner = ( diff --git a/src/next/utils/removeTrailingSlash.ts b/src/next/utils/removeTrailingSlash.ts deleted file mode 100644 index 12dd8500..00000000 --- a/src/next/utils/removeTrailingSlash.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function removeTrailingSlash(text: string) { - return text.replace(/\/$/, ""); -}