From 70fcc1a49a7aa7cd3fb787e656c0c26afa427adc Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Wed, 25 Sep 2024 22:31:08 -0700 Subject: [PATCH] feat: add testing for the global configs We want to make sure we aren't missing anything important. --- eslint.config.js | 5 +- src/index.ts | 109 +++++++++++++++--- test/config.spec.ts | 268 ++++++++++++++++++++++++++++++++++++++++++++ test/vitest.d.ts | 19 ++++ 4 files changed, 383 insertions(+), 18 deletions(-) create mode 100644 test/config.spec.ts create mode 100644 test/vitest.d.ts diff --git a/eslint.config.js b/eslint.config.js index a41037a..4d45c4f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -8,7 +8,10 @@ export default [ "import/no-default-export": "off", "import/namespace": "off", "import/no-deprecated": "off", - "@typescript-eslint/consistent-type-assertions": "off" + "@typescript-eslint/consistent-type-assertions": "off", + "max-lines-per-function": "off", + "complexity": "off", + "max-nested-callbacks": "off" }, }, ]; diff --git a/src/index.ts b/src/index.ts index 570cb44..45e7868 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,18 +35,15 @@ import { vitestRules } from "./rules/vitest.js"; import type { ESLint, Linter } from "eslint"; const config: Array = [ + // #region Typescript Config { files: [ "src/**/*.ts" ], languageOptions: { globals: { ...globals.node, - ...globals.browser, }, parser: parser, parserOptions: { - ecmaFeatures: { - jsx: true, - }, ecmaVersion: 11, project: true, sourceType: "module", @@ -60,15 +57,10 @@ const config: Array = [ "deprecation": fixupPluginRules(deprecation), "import": fixupPluginRules(importPlugin as ESLint.Plugin), "jsdoc": jsdoc, - // @ts-expect-error I'm not sure what's going on here, to be honest. - "playwright": fixupPluginRules(playwright), - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- No typedef means it's unsafe... - "react": react, "sort-keys-fix": sortKeysFix as ESLint.Plugin, // @ts-expect-error They haven't typedef this yet because it technically doesn't support eslint9 "stylistic": fixupPluginRules(stylistic), "unicorn": unicorn, - "vitest": vitest, }, rules: { ...eslintRules, @@ -81,11 +73,11 @@ const config: Array = [ ...stylisticRules, ...unicornRules, ...sortKeysFixRules, - ...vitestRules, - ...playwrightRules, - ...reactRules, }, }, + // #endregion + + // #region Vitest Config { files: [ "test/**/*.spec.ts" ], languageOptions: { @@ -119,11 +111,94 @@ const config: Array = [ ...stylisticRules, ...unicornRules, ...sortKeysFixRules, - // Overrides - "complexity": "off", - "import/no-extraneous-dependencies": "error", - "max-lines-per-function": "off", - "max-nested-callbacks": "off", + }, + }, + // #endregion + + // #region Playwright Config + { + files: [ "e2e/**/*.spec.ts" ], + languageOptions: { + globals: { + ...globals.node, + }, + parser: parser, + parserOptions: { + ecmaVersion: 11, + sourceType: "module", + }, + }, + plugins: { + // @ts-expect-error It's a config. It's just not the narrow config. SMH. + "@typescript-eslint": tslint, + "import": fixupPluginRules(importPlugin as ESLint.Plugin), + "jsdoc": jsdoc, + // @ts-expect-error I'm not actually sure what's going on here... + "playwright": playwright, + + "sort-keys-fix": sortKeysFix as ESLint.Plugin, + // @ts-expect-error They haven't typedef this yet because it technically doesn't support eslint9 + "stylistic": fixupPluginRules(stylistic), + "unicorn": unicorn, + }, + rules: { + ...eslintRules, + ...disabledEslintRules, + ...typescriptEslintRules, + ...playwrightRules, + ...importRules, + ...jsdocRules, + ...stylisticRules, + ...unicornRules, + ...sortKeysFixRules, + }, + }, + // #endregion + + // #region React Config + { + files: [ "src/**/*.tsx" ], + languageOptions: { + globals: { + ...globals.browser, + }, + parser: parser, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 11, + project: true, + sourceType: "module", + tsconfigRootDir: process.cwd(), + }, + }, + plugins: { + // @ts-expect-error It's a config. It's just not the narrow config. SMH. + "@typescript-eslint": tslint, + // @ts-expect-error They haven't typedef this yet because it technically doesn't support eslint9 + "deprecation": fixupPluginRules(deprecation), + "import": fixupPluginRules(importPlugin as ESLint.Plugin), + "jsdoc": jsdoc, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- No typedef means it's unsafe... + "react": react, + "sort-keys-fix": sortKeysFix as ESLint.Plugin, + // @ts-expect-error They haven't typedef this yet because it technically doesn't support eslint9 + "stylistic": fixupPluginRules(stylistic), + "unicorn": unicorn, + }, + rules: { + ...eslintRules, + ...disabledEslintRules, + ...typescriptEslintRules, + ...typescriptEslintRulesWithTypes, + ...importRules, + ...jsdocRules, + ...deprecationRules, + ...stylisticRules, + ...unicornRules, + ...sortKeysFixRules, + ...reactRules, }, }, ]; diff --git a/test/config.spec.ts b/test/config.spec.ts new file mode 100644 index 0000000..8c07503 --- /dev/null +++ b/test/config.spec.ts @@ -0,0 +1,268 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { describe, expect, it } from "vitest"; +import config from "../src/index.ts"; +import { deprecationRules } from "../src/rules/deprecation.ts"; +import { eslintRules } from "../src/rules/eslint.ts"; +import { importRules } from "../src/rules/import.ts"; +import { jsdocRules } from "../src/rules/jsdoc.ts"; +import { playwrightRules } from "../src/rules/playwright.ts"; +import { reactRules } from "../src/rules/react.ts"; +import { sortKeysFixRules } from "../src/rules/sortKeysFix.ts"; +import { stylisticRules } from "../src/rules/stylistic.ts"; +import { + typescriptEslintRules, + typescriptEslintRulesWithTypes, +} from "../src/rules/typescriptEslint.ts"; +import { unicornRules } from "../src/rules/unicorn.ts"; +import { vitestRules } from "../src/rules/vitest.ts"; +import type { Linter } from "eslint"; + +// #region Custom matcher +expect.extend({ + toContainRules(received, expected) { + const missingRules: Array = []; + const mismatchedRules: Array = []; + + for (const [ key, value ] of Object.entries(expected)) { + if (!(key in received)) { + missingRules.push(key); + } else if (!this.equals(received[key], value)) { + mismatchedRules.push(key); + } + } + + const pass = missingRules.length === 0 && mismatchedRules.length === 0; + + let message = ""; + // eslint-disable-next-line no-negated-condition + if (!pass) { + message = `${message}Expected rules to contain all specified rules.\n`; + if (missingRules.length > 0) { + message = `${message}Missing rules: ${missingRules.join(", ")}\n`; + } + if (mismatchedRules.length > 0) { + message = `${message}Mismatched rules: ${mismatchedRules.join(", ")}\n`; + } + } else { + message = "Expected rules not to contain all specified rules"; + } + + return { + message: (): string => { + return message; + }, + pass: pass, + }; + }, +}); +// #endregion + +// #region Helper function + +const baseProperties = ( + ruleset: Linter.Config, + isNoTypes: boolean, +): void => { + expect(ruleset?.plugins, "missing @typescript-eslint plugin").toHaveProperty( + "@typescript-eslint", + ); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- This is a test. + isNoTypes + ? expect( + ruleset?.plugins, + "should not have deprecation plugin", + ).not.toHaveProperty("deprecation") + : expect(ruleset?.plugins, "missing deprecation plugin").toHaveProperty( + "deprecation", + ); + expect(ruleset?.plugins, "missing import plugin").toHaveProperty("import"); + expect(ruleset?.plugins, "missing jsdoc plugin").toHaveProperty("jsdoc"); + expect(ruleset?.plugins, "missing sort-keys-fix plugin").toHaveProperty( + "sort-keys-fix", + ); + expect(ruleset?.plugins, "missing stylistic plugin").toHaveProperty( + "stylistic", + ); + expect(ruleset?.plugins, "missing unicorn plugin").toHaveProperty("unicorn"); + expect(ruleset?.rules, "missing eslint rules").toContainRules(eslintRules); + expect(ruleset?.rules, "missing typescript-eslint rules").toContainRules( + typescriptEslintRules, + ); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- This is a test. + isNoTypes + ? expect( + ruleset?.rules, + "should not have deprecation rules", + ).not.toContainRules(deprecationRules) + : expect(ruleset?.rules, "missing deprecation rules").toContainRules( + deprecationRules, + ); + expect(ruleset?.rules, "missing import rules").toContainRules(importRules); + expect(ruleset?.rules, "missing jsdoc rules").toContainRules(jsdocRules); + expect(ruleset?.rules, "missing sort-keys-fix rules").toContainRules( + sortKeysFixRules, + ); + expect(ruleset?.rules, "missing stylistic rules").toContainRules( + stylisticRules, + ); + expect(ruleset?.rules, "missing unicorn rules").toContainRules(unicornRules); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- This is a test. + isNoTypes + ? expect( + ruleset?.rules, + "should not have typescript-eslint rules with types", + ).not.toContainRules(typescriptEslintRulesWithTypes) + : expect( + ruleset?.rules, + "missing typescript-eslint rules with types", + ).toContainRules(typescriptEslintRulesWithTypes); +}; +// #endregion + +describe("global config", () => { + // #region Typescript + it("should apply the expected plugins for typescript", () => { + expect.assertions(26); + const ruleset = config.find((rule) => { + return rule?.files?.includes("src/**/*.ts"); + }); + expect(ruleset, "ruleset is not defined").toBeDefined(); + expect(ruleset?.plugins, "ruleset does not have plugins").toBeDefined(); + expect( + ruleset?.plugins, + "should not have vitest plugin", + ).not.toHaveProperty("vitest"); + expect( + ruleset?.plugins, + "should not have playwright plugin", + ).not.toHaveProperty("playwright"); + expect(ruleset?.plugins, "should not have react plugin").not.toHaveProperty( + "react", + ); + expect(ruleset?.rules, "ruleset does not have rules").toBeDefined(); + expect(ruleset?.rules, "should not have vitest rules").not.toContainRules( + vitestRules, + ); + expect( + ruleset?.rules, + "should not have playwright rules", + ).not.toContainRules(playwrightRules); + expect(ruleset?.rules, "should not have react rules").not.toContainRules( + reactRules, + ); + baseProperties(ruleset as Linter.Config, false); + expect( + Object.keys(ruleset?.plugins ?? {}), + "should not have extraneous plugins", + ).toHaveLength(7); + }); + // #endregion + + // #region Vitest + it("should apply the expected plugins for vitest files", () => { + expect.assertions(26); + const ruleset = config.find((rule) => { + return rule?.files?.includes("test/**/*.spec.ts"); + }); + expect(ruleset, "ruleset is not defined").toBeDefined(); + expect(ruleset?.plugins, "ruleset does not have plugins").toBeDefined(); + expect(ruleset?.plugins, "missing vitest plugin").toHaveProperty("vitest"); + expect( + ruleset?.plugins, + "should not have playwright plugin", + ).not.toHaveProperty("playwright"); + expect(ruleset?.plugins, "should not have react plugin").not.toHaveProperty( + "react", + ); + expect(ruleset?.rules, "ruleset does not have rules").toBeDefined(); + expect(ruleset?.rules, "missing vitest rules").toContainRules(vitestRules); + expect( + ruleset?.rules, + "should not have playwright rules", + ).not.toContainRules(playwrightRules); + expect(ruleset?.rules, "should not have react rules").not.toContainRules( + reactRules, + ); + baseProperties(ruleset as Linter.Config, true); + expect( + Object.keys(ruleset?.plugins ?? {}), + "should not have extraneous plugins", + ).toHaveLength(7); + }); + // #endregion + + // #region Playwright + it("should apply the expected plugins for playwright", () => { + expect.assertions(26); + const ruleset = config.find((rule) => { + return rule?.files?.includes("e2e/**/*.spec.ts"); + }); + expect(ruleset, "ruleset is not defined").toBeDefined(); + expect(ruleset?.plugins, "ruleset does not have plugins").toBeDefined(); + expect( + ruleset?.plugins, + "should not have vitest plugin", + ).not.toHaveProperty("vitest"); + expect(ruleset?.plugins, "missing playwright plugin").toHaveProperty( + "playwright", + ); + expect(ruleset?.plugins, "should not have react plugin").not.toHaveProperty( + "react", + ); + expect(ruleset?.rules, "ruleset does not have rules").toBeDefined(); + expect(ruleset?.rules, "should not have vitest rules").not.toContainRules( + vitestRules, + ); + expect(ruleset?.rules, "missing playwright rules").toContainRules( + playwrightRules, + ); + expect(ruleset?.rules, "should not have react rules").not.toContainRules( + reactRules, + ); + baseProperties(ruleset as Linter.Config, true); + expect( + Object.keys(ruleset?.plugins ?? {}), + "should not have extraneous plugins", + ).toHaveLength(7); + }); + // #endregion + + // #region React + it("should apply the expected plugins for react", () => { + expect.assertions(26); + const ruleset = config.find((rule) => { + return rule?.files?.includes("src/**/*.tsx"); + }); + expect(ruleset, "ruleset is not defined").toBeDefined(); + expect(ruleset?.plugins, "ruleset does not have plugins").toBeDefined(); + expect( + ruleset?.plugins, + "should not have vitest plugin", + ).not.toHaveProperty("vitest"); + expect( + ruleset?.plugins, + "should not have playwright plugin", + ).not.toHaveProperty("playwright"); + expect(ruleset?.plugins, "missing react plugin").toHaveProperty("react"); + expect(ruleset?.rules, "ruleset does not have rules").toBeDefined(); + expect(ruleset?.rules, "should not have vitest rules").not.toContainRules( + vitestRules, + ); + expect( + ruleset?.rules, + "should not have playwright rules", + ).not.toContainRules(playwrightRules); + expect(ruleset?.rules, "missing react rules").toContainRules(reactRules); + baseProperties(ruleset as Linter.Config, false); + expect( + Object.keys(ruleset?.plugins ?? {}), + "should not have extraneous plugins", + ).toHaveLength(8); + }); + // #endregion +}); diff --git a/test/vitest.d.ts b/test/vitest.d.ts new file mode 100644 index 0000000..38e1c54 --- /dev/null +++ b/test/vitest.d.ts @@ -0,0 +1,19 @@ +/// + +import type { Assertion, AsymmetricMatchersContaining } from 'vitest'; + +interface CustomMatchers { + toContainRules(expected: Record): R; +} + +declare module 'vitest' { + interface Assertion extends CustomMatchers {} + interface AsymmetricMatchersContaining extends CustomMatchers {} +} + +declare module 'vitest' { + // @ts-expect-error We need to extend the TestAPI interface + export interface TestAPI { + extend: (matchers: Record any>) => void; + } +} \ No newline at end of file