diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index bf76b826..00000000 --- a/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -### Dependencies ### -.yarn/ -node_modules/ - -### Auto-generated ### -build/ -dist/ diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index dea8d66b..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,252 +0,0 @@ -{ - "root": true, - "env": { - "browser": true, - "es6": true, - "node": true - }, - "settings": { - "import/resolver": { - "typescript": { - "alwaysTryTypes": true, - "project": "./tsconfig.json" - } - }, - "react": { - "version": "detect" - } - }, - "parserOptions": { - "ecmaVersion": "latest", - "project": "./tsconfig.json", - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint", - "eslint-plugin-import", - "eslint-plugin-jsdoc", - "eslint-plugin-react", - "etc", - "react-hooks", - "sonarjs" - ], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking", - "plugin:etc/recommended", - "plugin:import/typescript", - "plugin:react/recommended", - "plugin:react/jsx-runtime", - "plugin:sonarjs/recommended" - ], - "overrides": [{ - "files": "*.ts?(x)", - "parser": "@typescript-eslint/parser" - }, { - "files": "*.test.ts?(x)", - "rules": { - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/restrict-template-expressions": "off", - "etc/throw-error": "off" - } - }, { - "files": "*.typetest.ts?(x)", - "rules": { - "@typescript-eslint/ban-ts-comment": ["error", { "ts-expect-error": false }], - "etc/throw-error": "off" - } - }, { - "files": "*.js?(x)", - "rules": { - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-member-accessibility": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-unsafe-argument": "off", - "@typescript-eslint/no-unsafe-assignment": "off", - "@typescript-eslint/no-unsafe-member-access": "off", - "@typescript-eslint/no-unsafe-call": "off", - "@typescript-eslint/no-unsafe-return": "off", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/restrict-plus-operands": "off", - "@typescript-eslint/restrict-template-expressions": "off" - } - }], - "rules": { - "@typescript-eslint/ban-types": "error", - "@typescript-eslint/comma-dangle": ["error", "always-multiline"], - "@typescript-eslint/consistent-type-assertions": "error", - "@typescript-eslint/dot-notation": "error", - "@typescript-eslint/explicit-function-return-type": ["error", { "allowExpressions": true }], - "@typescript-eslint/explicit-member-accessibility": "error", - "@typescript-eslint/explicit-module-boundary-types": "error", - "@typescript-eslint/member-ordering": ["error", { - "classes": [ - "static-field", - "field", - "constructor", - "static-method", - "abstract-method", - "protected-method", - "public-method", - "private-method" - ], - "interfaces": { "order": "alphabetically" }, - "typeLiterals": { "order": "alphabetically" } - }], - "@typescript-eslint/member-delimiter-style": ["error", { - "multiline": { - "delimiter": "semi", - "requireLast": true - }, - "singleline": { - "delimiter": "semi", - "requireLast": true - } - }], - "@typescript-eslint/no-empty-function": "error", - "@typescript-eslint/no-empty-interface": "error", - "@typescript-eslint/no-explicit-any": ["error", { "ignoreRestArgs": true }], - "@typescript-eslint/no-floating-promises": "off", - "@typescript-eslint/no-inferrable-types": ["error", { - "ignoreParameters": true, - "ignoreProperties": true - }], - "@typescript-eslint/no-misused-new": "error", - "@typescript-eslint/no-namespace": "off", - "@typescript-eslint/no-non-null-assertion": "error", - "@typescript-eslint/no-redundant-type-constituents": "error", - "@typescript-eslint/no-shadow": ["error", { "hoist": "all" }], - "@typescript-eslint/no-unused-expressions": ["error", { "allowTernary": true }], - "@typescript-eslint/no-unused-vars": ["error", { - "destructuredArrayIgnorePattern": "^_", - "ignoreRestSiblings": true - }], - "@typescript-eslint/no-use-before-define": ["error", { - "functions": false, - "classes": false - }], - "@typescript-eslint/no-var-requires": "error", - "@typescript-eslint/parameter-properties": "error", - "@typescript-eslint/prefer-for-of": "error", - "@typescript-eslint/prefer-function-type": "error", - "@typescript-eslint/prefer-namespace-keyword": "error", - "@typescript-eslint/quotes": ["error", "double", { - "avoidEscape": true, - "allowTemplateLiterals": false - }], - "@typescript-eslint/restrict-template-expressions": ["error", { - "allowNumber": true, - "allowBoolean": true, - "allowNullish": true - }], - "@typescript-eslint/semi": "error", - "@typescript-eslint/space-infix-ops": "error", - "@typescript-eslint/triple-slash-reference": "error", - "@typescript-eslint/type-annotation-spacing": "error", - "@typescript-eslint/unbound-method": ["error", { "ignoreStatic": true }], - "@typescript-eslint/unified-signatures": "error", - "array-bracket-spacing": "error", - "arrow-parens": ["error", "as-needed"], - "arrow-spacing": "error", - "brace-style": "error", - "camelcase": "error", - "comma-spacing": "error", - "computed-property-spacing": "error", - "constructor-super": "error", - "curly": "error", - "etc/no-commented-out-code": "error", - "etc/throw-error": "error", - "eol-last": "error", - "eqeqeq": "error", - "func-style": ["error", "declaration", { "allowArrowFunctions": true }], - "import/newline-after-import": "error", - "import/no-absolute-path": "error", - "import/no-cycle": ["error", { - "allowUnsafeDynamicCyclicDependency": true, - "ignoreExternal": true - }], - "import/no-duplicates": "error", - "import/no-import-module-exports": "error", - "import/no-namespace": "error", - "import/no-relative-packages": "error", - "import/no-unresolved": "error", - "import/no-useless-path-segments": "error", - "import/order": ["error", { - "alphabetize": { - "caseInsensitive": false, - "order": "asc" - }, - "newlines-between": "always", - "groups": ["external", "parent", "sibling"] - }], - "jsdoc/check-alignment": "error", - "jsdoc/check-indentation": ["error", { "excludeTags": ["example", "param", "returns"] }], - "jsdoc/tag-lines": ["error", "any", { "startLines": 1 }], - "jsx-quotes": "error", - "keyword-spacing": "error", - "linebreak-style": "error", - "max-classes-per-file": ["error", 1], - "max-len": ["error", { - "code": 120, - "comments": 80, - "ignoreRegExpLiterals": true, - "ignorePattern": "^import (\\{ )?\\w+( \\})? from \".+\";$", - "ignoreUrls": true, - "tabWidth": 2 - }], - "new-parens": "error", - "no-caller": "error", - "no-cond-assign": "error", - "no-console": "error", - "no-duplicate-imports": "error", - "no-empty-function": "error", - "no-eval": "error", - "no-extra-boolean-cast": ["error", { "enforceForLogicalOperands": true }], - "no-invalid-this": "error", - "no-labels": "error", - "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0, "maxBOF": 0 }], - "no-multi-spaces": "error", - "no-new-wrappers": "error", - "no-param-reassign": "error", - "no-tabs": "error", - "no-throw-literal": "error", - "no-trailing-spaces": "error", - "no-underscore-dangle": "error", - "no-use-before-define": "off", - "no-useless-computed-key": ["error", { "enforceForClassMembers": true }], - "no-var": "error", - "object-curly-spacing": ["error", "always"], - "object-shorthand": "error", - "one-var": ["error", "never"], - "prefer-const": "error", - "quote-props": ["error", "as-needed"], - "radix": "error", - "rest-spread-spacing": "error", - "react/display-name": "off", - "react/hook-use-state": "error", - "react/jsx-boolean-value": ["error", "always"], - "react/jsx-closing-bracket-location": "error", - "react/jsx-curly-spacing": ["error", { "when": "never" }], - "react/jsx-equals-spacing": ["error", "never"], - "react/jsx-max-props-per-line": ["error", { - "maximum": 1, - "when": "multiline" - }], - "react/jsx-no-bind": "error", - "react/jsx-no-literals": "error", - "react/jsx-tag-spacing": "error", - "react/prop-types": "off", - "react/self-closing-comp": "error", - "react-hooks/rules-of-hooks": "error", - "semi-spacing": "error", - "sonarjs/cognitive-complexity": "off", - "sonarjs/no-duplicate-string": "off", - "sonarjs/no-inverted-boolean-check": "error", - "sort-keys": "error", - "space-before-blocks": "error", - "space-in-parens": "error", - "spaced-comment": "error", - "switch-colon-spacing": "error" - } -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..af51afcc --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,277 @@ +// @ts-check +import eslintJs from "@eslint/js"; +import stylistic from "@stylistic/eslint-plugin"; +import { defineConfig } from "eslint/config"; +import importPlugin from "eslint-plugin-import"; +import jsdoc from "eslint-plugin-jsdoc"; +import perfectionist from "eslint-plugin-perfectionist"; +import reactPlugin from "eslint-plugin-react"; +import sonarjs from "eslint-plugin-sonarjs"; +import globals from "globals"; +import eslintTs from "typescript-eslint"; + +const project = "./tsconfig.json"; + +export default defineConfig( + eslintJs.configs.recommended, + ...eslintTs.configs.recommendedTypeChecked, + importPlugin.flatConfigs?.typescript, + reactPlugin.configs.flat?.recommended, + reactPlugin.configs.flat?.["jsx-runtime"], + sonarjs.configs.recommended, + stylistic.configs.customize({ + braceStyle: "1tbs", + quotes: "double", + semi: true, + }), + { + ignores: [ + "**/.turbo/**", + "**/.yarn/**", + "**/build/**", + "**/dist/**", + "**/node_modules/**", + ], + }, + { + languageOptions: { + globals: { ...globals.browser, ...globals.node }, + parserOptions: { + ecmaFeatures: { jsx: true }, + ecmaVersion: 2024, + jsxPragma: null, + project, + tsconfigRootDir: import.meta.dirname, + }, + sourceType: "module", + }, + linterOptions: { + reportUnusedDisableDirectives: "error", + }, + plugins: { + jsdoc, + perfectionist, + }, + settings: { + "import/resolver": { + typescript: { + alwaysTryTypes: true, + project, + }, + }, + react: { + version: "detect", + }, + }, + }, + { + rules: { + "@stylistic/arrow-parens": ["error", "as-needed"], + "@stylistic/indent-binary-ops": "off", + "@stylistic/jsx-curly-brace-presence": ["error", { children: "always" }], + "@stylistic/jsx-curly-newline": "off", + "@stylistic/jsx-pascal-case": ["error", { allowNamespace: true }], + "@stylistic/jsx-self-closing-comp": "error", + "@stylistic/jsx-wrap-multilines": ["error", { prop: "ignore" }], + "@stylistic/linebreak-style": "error", + "@stylistic/max-len": ["error", { + code: 120, + comments: 80, + ignorePattern: /^import \{?.+\}? from ["'].+["'];$/iu.source, + ignoreRegExpLiterals: true, + ignoreUrls: true, + tabWidth: 2, + }], + "@stylistic/member-delimiter-style": ["error", { singleline: { requireLast: true } }], + "@stylistic/no-extra-semi": "error", + "@stylistic/no-mixed-spaces-and-tabs": "error", + "@stylistic/no-multi-spaces": "error", + "@stylistic/no-multiple-empty-lines": ["error", { max: 1, maxBOF: 0, maxEOF: 0 }], + "@stylistic/object-curly-spacing": ["error", "always"], + "@stylistic/object-property-newline": ["error", { allowAllPropertiesOnSameLine: true }], + "@stylistic/padded-blocks": ["error", "never", { allowSingleLineBlocks: false }], + "@stylistic/quote-props": ["error", "as-needed"], + "@stylistic/quotes": ["error", "double", { + allowTemplateLiterals: "always", + avoidEscape: true, + }], + "@stylistic/space-before-function-paren": ["error", { anonymous: "never", named: "never" }], + "@stylistic/switch-colon-spacing": "error", + "@typescript-eslint/consistent-type-assertions": "error", + "@typescript-eslint/consistent-type-exports": "error", + "@typescript-eslint/consistent-type-imports": ["error", { fixStyle: "inline-type-imports" }], + "@typescript-eslint/dot-notation": "error", + "@typescript-eslint/explicit-function-return-type": ["error", { allowExpressions: true }], + "@typescript-eslint/explicit-member-accessibility": "error", + "@typescript-eslint/explicit-module-boundary-types": "error", + "@typescript-eslint/member-ordering": ["error", { + classes: [ + "static-field", + "field", + "constructor", + "static-method", + "abstract-method", + "protected-method", + "public-method", + "private-method", + ], + interfaces: { memberTypes: "never" }, + typeLiterals: { memberTypes: "never" }, + }], + "@typescript-eslint/no-empty-function": "error", + "@typescript-eslint/no-empty-object-type": "error", + "@typescript-eslint/no-explicit-any": ["error", { ignoreRestArgs: true }], + "@typescript-eslint/no-floating-promises": "off", + "@typescript-eslint/no-import-type-side-effects": "error", + "@typescript-eslint/no-inferrable-types": ["error", { + ignoreParameters: true, + ignoreProperties: true, + }], + "@typescript-eslint/no-misused-new": "error", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-non-null-assertion": "error", + "@typescript-eslint/no-redundant-type-constituents": "error", + "@typescript-eslint/no-shadow": ["error", { hoist: "all" }], + "@typescript-eslint/no-unused-expressions": ["error", { allowTernary: true }], + "@typescript-eslint/no-unused-vars": ["error", { + destructuredArrayIgnorePattern: "^_", + ignoreRestSiblings: true, + }], + "@typescript-eslint/no-use-before-define": ["error", { + classes: false, + functions: false, + }], + "@typescript-eslint/no-useless-constructor": "error", + "@typescript-eslint/no-var-requires": "error", + "@typescript-eslint/only-throw-error": "error", + "@typescript-eslint/parameter-properties": "error", + "@typescript-eslint/prefer-for-of": "error", + "@typescript-eslint/prefer-function-type": "error", + "@typescript-eslint/prefer-namespace-keyword": "error", + "@typescript-eslint/require-array-sort-compare": "error", + "@typescript-eslint/restrict-template-expressions": ["error", { + allowBoolean: true, + allowNullish: true, + allowNumber: true, + }], + "@typescript-eslint/triple-slash-reference": "error", + "@typescript-eslint/unbound-method": ["error", { ignoreStatic: true }], + "@typescript-eslint/unified-signatures": "error", + camelcase: "error", + "constructor-super": "error", + curly: "error", + eqeqeq: "error", + "func-style": ["error", "declaration", { allowArrowFunctions: true }], + "import/newline-after-import": "error", + "import/no-absolute-path": "error", + "import/no-cycle": ["error", { + allowUnsafeDynamicCyclicDependency: true, + ignoreExternal: true, + maxDepth: 1, + }], + "import/no-duplicates": ["error", { "prefer-inline": true }], + "import/no-import-module-exports": "error", + "import/no-namespace": ["error", { ignore: ["eslint-plugin-import"] }], + "import/no-relative-packages": "error", + "import/no-unresolved": "error", + "import/no-useless-path-segments": "error", + "import/order": ["error", { + alphabetize: { + caseInsensitive: false, + order: "asc", + orderImportKind: "asc", + }, + groups: [ + "builtin", + ["external", "internal"], + "parent", + "sibling", + "index", + "type", + ], + "newlines-between": "always", + }], + "jsdoc/check-alignment": "error", + "jsdoc/check-indentation": ["error", { excludeTags: ["example", "param", "returns"] }], + "jsdoc/tag-lines": ["error", "any", { startLines: 1 }], + "max-classes-per-file": ["error", 1], + "no-caller": "error", + "no-cond-assign": "error", + "no-console": "error", + "no-duplicate-imports": "error", + "no-empty-function": "error", + "no-eval": "error", + "no-extra-boolean-cast": ["error", { enforceForLogicalOperands: true }], + "no-inner-declarations": ["error", "both"], + "no-invalid-this": "error", + "no-labels": "error", + "no-new-wrappers": "error", + "no-param-reassign": "error", + "no-throw-literal": "off", + "no-underscore-dangle": "error", + "no-use-before-define": "off", + "no-useless-computed-key": ["error", { enforceForClassMembers: true }], + "no-useless-constructor": "off", + "no-var": "error", + "object-shorthand": "error", + "one-var": ["error", "never"], + "perfectionist/sort-interfaces": ["error", { ignoreCase: false, type: "natural" }], + "perfectionist/sort-intersection-types": ["error", { ignoreCase: false, type: "natural" }], + "perfectionist/sort-object-types": ["error", { ignoreCase: false, type: "natural" }], + "perfectionist/sort-objects": ["error", { ignoreCase: false, type: "natural" }], + "perfectionist/sort-union-types": ["error", { ignoreCase: false, type: "natural" }], + "prefer-const": "error", + radix: "error", + "react/display-name": "off", + "react/hook-use-state": "error", + "react/jsx-boolean-value": ["error", "always"], + "react/jsx-no-bind": "error", + "react/jsx-no-literals": "error", + "react/prop-types": "off", + "sonarjs/cognitive-complexity": "off", + "sonarjs/different-types-comparison": "off", + "sonarjs/function-return-type": "off", + "sonarjs/no-duplicate-string": "off", + "sonarjs/no-extend-native": "off", + "sonarjs/no-nested-functions": "off", + "sonarjs/no-selector-parameter": "off", + "sonarjs/no-unused-expressions": "off", + "sonarjs/no-unused-vars": "off", + "sonarjs/prefer-read-only-props": "off", + "sort-imports": ["error", { ignoreDeclarationSort: true }], + }, + }, + { + files: ["**/*.?(c|m)js"], + languageOptions: { + parserOptions: { + programs: null, + project: false, + }, + }, + rules: { + ...eslintTs.configs.disableTypeChecked.rules, + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "sonarjs/no-unused-vars": "error", + }, + }, + { + files: ["**/*.cjs"], + rules: { + "@typescript-eslint/no-require-imports": "off", + "@typescript-eslint/no-var-requires": "off", + }, + }, + { + files: ["**/*.test.ts?(x)"], + rules: { + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/prefer-promise-reject-errors": "off", + "@typescript-eslint/restrict-template-expressions": "off", + "sonarjs/assertions-in-tests": "off", + "sonarjs/no-empty-test-file": "off", + "sonarjs/no-nested-functions": "off", + }, + }, +); diff --git a/examples/jest/jest.config.ts b/examples/jest/jest.config.ts index 1d6f9f13..37ced079 100644 --- a/examples/jest/jest.config.ts +++ b/examples/jest/jest.config.ts @@ -1,4 +1,4 @@ -import { type Config } from "jest"; +import type { Config } from "jest"; const jestConfig: Config = { preset: "ts-jest", diff --git a/examples/jest/package.json b/examples/jest/package.json index c7f98622..cd1cf904 100644 --- a/examples/jest/package.json +++ b/examples/jest/package.json @@ -14,6 +14,6 @@ "jest": "^29.7.0", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", - "typescript": "^5.4.2" + "typescript": "^5.9.3" } } diff --git a/examples/mocha/package.json b/examples/mocha/package.json index 778a6460..35cb0b69 100644 --- a/examples/mocha/package.json +++ b/examples/mocha/package.json @@ -13,6 +13,6 @@ "@types/node": "^24.10.1", "mocha": "^10.3.0", "ts-node": "^10.9.2", - "typescript": "^5.4.2" + "typescript": "^5.9.3" } } diff --git a/examples/mocha/test/hooks.ts b/examples/mocha/test/hooks.ts index 4ca6597a..f4398295 100644 --- a/examples/mocha/test/hooks.ts +++ b/examples/mocha/test/hooks.ts @@ -1,4 +1,3 @@ -/* eslint-disable react-hooks/rules-of-hooks */ import { usePlugin } from "@assertive-ts/core"; import { SymbolPlugin } from "@examples/symbol-plugin"; diff --git a/examples/symbolPlugin/package.json b/examples/symbolPlugin/package.json index 0a96b92c..6ad7da56 100644 --- a/examples/symbolPlugin/package.json +++ b/examples/symbolPlugin/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@assertive-ts/core": "workspace:^", - "typescript": "^5.4.2" + "typescript": "^5.9.3" }, "peerDependencies": { "@assertive-ts/core": "*" diff --git a/examples/symbolPlugin/src/lib/SymbolAssertion.ts b/examples/symbolPlugin/src/lib/SymbolAssertion.ts index d2044ea3..5f531c26 100644 --- a/examples/symbolPlugin/src/lib/SymbolAssertion.ts +++ b/examples/symbolPlugin/src/lib/SymbolAssertion.ts @@ -1,7 +1,6 @@ import { Assertion, AssertionError } from "@assertive-ts/core"; export class SymbolAssertion extends Assertion { - public constructor(actual: symbol) { super(actual); } diff --git a/examples/symbolPlugin/src/main.ts b/examples/symbolPlugin/src/main.ts index c59a9b31..a16fa4ce 100644 --- a/examples/symbolPlugin/src/main.ts +++ b/examples/symbolPlugin/src/main.ts @@ -1,7 +1,7 @@ -import { Plugin } from "@assertive-ts/core"; - import { SymbolAssertion } from "./lib/SymbolAssertion"; +import type { Plugin } from "@assertive-ts/core"; + declare module "@assertive-ts/core" { export interface Expect { diff --git a/package.json b/package.json index 062b1dcd..48942503 100644 --- a/package.json +++ b/package.json @@ -20,22 +20,24 @@ "compile": "turbo run compile", "docs": "turbo docs", "lint": "eslint .", + "lint:fix": "eslint . --fix", "release": "turbo release --concurrency=1", "test": "turbo run test" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^7.3.0", - "@typescript-eslint/parser": "^7.3.0", - "eslint": "^8.57.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-etc": "^2.0.3", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsdoc": "^48.2.1", - "eslint-plugin-react": "^7.34.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-sonarjs": "^0.24.0", + "@eslint/js": "^9.39.1", + "@stylistic/eslint-plugin": "^5.6.1", + "eslint": "^9.39.1", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsdoc": "^61.2.1", + "eslint-plugin-perfectionist": "^4.15.1", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-sonarjs": "^3.0.5", + "globals": "^16.5.0", "rimraf": "^6.1.2", "turbo": "^1.12.4", - "typescript": "^5.4.2" + "typescript": "^5.9.3", + "typescript-eslint": "^8.47.0" } } diff --git a/packages/core/package.json b/packages/core/package.json index 3339f602..223aff78 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -33,7 +33,7 @@ "dependencies": { "dedent": "^1.5.1", "fast-deep-equal": "^3.1.3", - "tslib": "^2.6.2" + "tslib": "^2.8.1" }, "devDependencies": { "@types/mocha": "^10.0.6", @@ -48,7 +48,7 @@ "typedoc": "^0.25.8", "typedoc-plugin-markdown": "^3.17.1", "typedoc-plugin-merge-modules": "^5.1.0", - "typescript": "^5.4.2" + "typescript": "^5.9.3" }, "publishConfig": { "access": "public", diff --git a/packages/core/src/lib/ArrayAssertion.ts b/packages/core/src/lib/ArrayAssertion.ts index 0f5773fe..b6cdd7cf 100644 --- a/packages/core/src/lib/ArrayAssertion.ts +++ b/packages/core/src/lib/ArrayAssertion.ts @@ -1,12 +1,13 @@ +import { AssertionError } from "assert"; + import isDeepEqual from "fast-deep-equal/es6"; import { Assertion } from "./Assertion"; import { UnsupportedOperationError } from "./errors/UnsupportedOperationError"; -import { type Expect } from "./expect"; -import { type TypeFactory } from "./helpers/TypeFactories"; import { prettify } from "./helpers/messages"; -import { AssertionError } from "assert"; +import type { Expect } from "./expect"; +import type { TypeFactory } from "./helpers/TypeFactories"; /** * Encapsulates assertion methods applicable to arrays. @@ -14,7 +15,6 @@ import { AssertionError } from "assert"; * @param T the type of the array */ export class ArrayAssertion extends Assertion { - public constructor(actual: T[]) { super(actual); } @@ -142,7 +142,7 @@ export class ArrayAssertion extends Assertion { try { consumer(value); return true; - } catch (err) { + } catch { return false; } }), @@ -354,7 +354,10 @@ export class ArrayAssertion extends Assertion { } // We use `require(..)` to avoid import cycles with the `./expect` module - // eslint-disable-next-line @typescript-eslint/no-var-requires + /* eslint-disable-next-line + @typescript-eslint/no-var-requires, + @typescript-eslint/no-require-imports + */ const { expect } = require("./expect") as { expect: Expect; }; return expect(this.actual[index]).asType(typeFactory); diff --git a/packages/core/src/lib/Assertion.ts b/packages/core/src/lib/Assertion.ts index 8405a89e..9b13d765 100644 --- a/packages/core/src/lib/Assertion.ts +++ b/packages/core/src/lib/Assertion.ts @@ -1,19 +1,20 @@ +import { AssertionError } from "assert"; + import isDeepEqual from "fast-deep-equal/es6"; import { UnsupportedOperationError } from "./errors/UnsupportedOperationError"; -import { type TypeFactory } from "./helpers/TypeFactories"; -import { isStruct, isKeyOf } from "./helpers/guards"; +import { isKeyOf, isStruct } from "./helpers/guards"; import { prettify } from "./helpers/messages"; -import { AssertionError } from "assert"; +import type { TypeFactory } from "./helpers/TypeFactories"; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type export interface Constructor extends Function { prototype: T; } -export type DataType = - | "array" +export type DataType + = "array" | "bigint" | "boolean" | "function" @@ -46,7 +47,6 @@ export interface ExecuteOptions { * @param T the type of the `actual` value */ export class Assertion { - protected readonly actual: T; protected readonly inverted: boolean; @@ -60,6 +60,18 @@ export class Assertion { this.not = new Proxy(this, { get: this.proxyInverter(true) }); } + protected proxyInverter(isInverted: boolean): ProxyHandler["get"] { + return (target, p) => { + const key = isKeyOf(target, p) ? p : undefined; + + if (key === "inverted") { + return isInverted; + } + + return key ? target[key] : undefined; + }; + } + /** * A convenience method to normalize the assertion instance. If it was * inverted with `.not`, it'll return it back to the previous non-inverted @@ -565,16 +577,4 @@ export class Assertion { message: `Expected <${prettify(this.actual)}> to be of type "${typeName}"`, }); } - - private proxyInverter(isInverted: boolean): ProxyHandler["get"] { - return (target, p) => { - const key = isKeyOf(target, p) ? p : undefined; - - if (key === "inverted") { - return isInverted; - } - - return key ? target[key] : undefined; - }; - } } diff --git a/packages/core/src/lib/BooleanAssertion.ts b/packages/core/src/lib/BooleanAssertion.ts index a088a296..2d2e8d13 100644 --- a/packages/core/src/lib/BooleanAssertion.ts +++ b/packages/core/src/lib/BooleanAssertion.ts @@ -1,12 +1,11 @@ -import { Assertion } from "./Assertion"; - import { AssertionError } from "assert"; +import { Assertion } from "./Assertion"; + /** * Encapsulates assertion methods applicable to values of type boolean */ export class BooleanAssertion extends Assertion { - public constructor(actual: boolean) { super(actual); } diff --git a/packages/core/src/lib/DateAssertion.ts b/packages/core/src/lib/DateAssertion.ts index 9ffd4e72..e29363e1 100644 --- a/packages/core/src/lib/DateAssertion.ts +++ b/packages/core/src/lib/DateAssertion.ts @@ -1,8 +1,9 @@ +import { AssertionError } from "assert"; + import { Assertion } from "./Assertion"; -import { DateMethod, DateOptions, DayOfWeek } from "./DateAssertion.types"; -import { optionsToDate, dayOfWeekAsNumber, dateToOptions } from "./helpers/dates"; +import { dateToOptions, dayOfWeekAsNumber, optionsToDate } from "./helpers/dates"; -import { AssertionError } from "assert"; +import type { DateMethod, DateOptions, DayOfWeek } from "./DateAssertion.types"; const DATE_METHOD_MAP: Record = { day: "getDay", @@ -18,7 +19,6 @@ const DATE_METHOD_MAP: Record = { * Encapsulates assertion methods applicable to values of type Date */ export class DateAssertion extends Assertion { - public constructor(actual: Date) { super(actual); } diff --git a/packages/core/src/lib/DateAssertion.types.ts b/packages/core/src/lib/DateAssertion.types.ts index f0612a3d..dca5762c 100644 --- a/packages/core/src/lib/DateAssertion.types.ts +++ b/packages/core/src/lib/DateAssertion.types.ts @@ -1,25 +1,25 @@ -export type Month = - | "january" +export type Month + = "april" + | "august" + | "december" | "february" + | "january" + | "july" + | "june" | "march" - | "april" | "may" - | "june" - | "july" - | "august" - | "september" - | "october" | "november" - | "december"; + | "october" + | "september"; -export type DayOfWeek = +export type DayOfWeek + = "friday" | "monday" - | "tuesday" - | "wednesday" - | "thursday" - | "friday" | "saturday" - | "sunday"; + | "sunday" + | "thursday" + | "tuesday" + | "wednesday"; export type DateMethod = { [K in keyof Date]: Date[K] extends () => number diff --git a/packages/core/src/lib/ErrorAssertion.ts b/packages/core/src/lib/ErrorAssertion.ts index 7ba7ea50..013e374f 100644 --- a/packages/core/src/lib/ErrorAssertion.ts +++ b/packages/core/src/lib/ErrorAssertion.ts @@ -1,14 +1,13 @@ -import { Assertion } from "./Assertion"; - import { AssertionError } from "assert"; +import { Assertion } from "./Assertion"; + /** * Encapsulates assertion methods applicable to Error instances. * * @param T the Error constructor type */ export class ErrorAssertion extends Assertion { - public constructor(actual: T) { super(actual); } @@ -84,7 +83,7 @@ export class ErrorAssertion extends Assertion { * @param fragment the fragment the message should contain * @returns the assertion instance */ - public toHaveMessageContaining(fragment: string): this { + public toHaveMessageContaining(fragment: string): this { const error = new AssertionError({ actual: this.actual.message, message: `Expected error to have a message containing: ${fragment}`, @@ -113,7 +112,7 @@ export class ErrorAssertion extends Assertion { * @param fragment the fragment the message should end with * @returns the assertion instance */ - public toHaveMessageEndingWith(fragment: string): this { + public toHaveMessageEndingWith(fragment: string): this { const error = new AssertionError({ actual: this.actual.message, message: `Expected error to have a message ending with: ${fragment}`, diff --git a/packages/core/src/lib/FunctionAssertion.ts b/packages/core/src/lib/FunctionAssertion.ts index a36a1a0c..7bac65cb 100644 --- a/packages/core/src/lib/FunctionAssertion.ts +++ b/packages/core/src/lib/FunctionAssertion.ts @@ -1,11 +1,12 @@ +import { AssertionError } from "assert"; + import isDeepEqual from "fast-deep-equal/es6"; -import { Assertion, Constructor } from "./Assertion"; +import { Assertion, type Constructor } from "./Assertion"; import { ErrorAssertion } from "./ErrorAssertion"; -import { type TypeFactory } from "./helpers/TypeFactories"; import { prettify } from "./helpers/messages"; -import { AssertionError } from "assert"; +import type { TypeFactory } from "./helpers/TypeFactories"; export type AnyFunction = (...args: unknown[]) => unknown; @@ -23,7 +24,6 @@ const NoThrow = Symbol("NoThrow"); * @param T the type of the function's signature */ export class FunctionAssertion extends Assertion { - public constructor(actual: T) { super(actual); } @@ -184,7 +184,7 @@ export class FunctionAssertion extends Assertion { : new Assertion(captured) as A; } - private captureError(): X | typeof NoThrow { + private captureError(): typeof NoThrow | X { try { this.actual(); return NoThrow; diff --git a/packages/core/src/lib/NumberAssertion.helpers.ts b/packages/core/src/lib/NumberAssertion.helpers.ts index b50bdd67..af0a3bde 100644 --- a/packages/core/src/lib/NumberAssertion.helpers.ts +++ b/packages/core/src/lib/NumberAssertion.helpers.ts @@ -19,11 +19,11 @@ export interface HighInclusiveBetweenOptions extends BaseBetweenOptions { highInclusive: boolean; } -export type BetweenOptions = - | BaseBetweenOptions +export type BetweenOptions + = BaseBetweenOptions + | HighInclusiveBetweenOptions | InclusiveBetweenOptions - | LowInclusiveBetweenOptions - | HighInclusiveBetweenOptions; + | LowInclusiveBetweenOptions; export function isInclusiveOptions(options: BetweenOptions): options is InclusiveBetweenOptions { return (options as InclusiveBetweenOptions).inclusive !== undefined; diff --git a/packages/core/src/lib/NumberAssertion.ts b/packages/core/src/lib/NumberAssertion.ts index 7f214aaf..ea2699cb 100644 --- a/packages/core/src/lib/NumberAssertion.ts +++ b/packages/core/src/lib/NumberAssertion.ts @@ -1,8 +1,8 @@ +import { AssertionError } from "assert"; + import { Assertion } from "./Assertion"; import { isHighInclusiveOptions, isInclusiveOptions, isLowInclusiveOptions } from "./helpers/guards"; -import { AssertionError } from "assert"; - export interface BaseBetweenOptions { range: [number, number]; } @@ -24,17 +24,16 @@ export interface HighInclusiveBetweenOptions extends BaseBetweenOptions { highInclusive: boolean; } -export type BetweenOptions = - | BaseBetweenOptions +export type BetweenOptions + = BaseBetweenOptions + | HighInclusiveBetweenOptions | InclusiveBetweenOptions - | LowInclusiveBetweenOptions - | HighInclusiveBetweenOptions; + | LowInclusiveBetweenOptions; /** * Encapsulates assertion methods applicable to values of type number */ export class NumberAssertion extends Assertion { - public constructor(actual: number) { super(actual); } @@ -275,8 +274,8 @@ export class NumberAssertion extends Assertion { * `lowInclusive: true` or `highInclusive: true`, respectively * @returns the assertion instance */ -public toBeBetween(options: BetweenOptions): this { - const [min, max] = options.range; + public toBeBetween(options: BetweenOptions): this { + const [min, max] = options.range; if (isInclusiveOptions(options)) { const rangeText = options.inclusive ? `[${min}, ${max}]` : `(${min}, ${max})`; diff --git a/packages/core/src/lib/ObjectAssertion.ts b/packages/core/src/lib/ObjectAssertion.ts index 0cb80701..b609ed7f 100644 --- a/packages/core/src/lib/ObjectAssertion.ts +++ b/packages/core/src/lib/ObjectAssertion.ts @@ -1,10 +1,11 @@ +import { AssertionError } from "assert"; + import isDeepEqual from "fast-deep-equal/es6"; import { Assertion } from "./Assertion"; import { prettify } from "./helpers/messages"; -import { Entry, Struct } from "./helpers/types"; -import { AssertionError } from "assert"; +import type { Entry, Struct } from "./helpers/types"; /** * Encapsulates assertion methods applicable to objects. @@ -12,7 +13,6 @@ import { AssertionError } from "assert"; * @param T the object's definition type */ export class ObjectAssertion extends Assertion { - public constructor(actual: T) { super(actual); } @@ -146,8 +146,8 @@ export class ObjectAssertion extends Assertion { * @returns the assertion instance */ public toHaveKeys(...keys: Array): this { - const sortedActual = Object.keys(this.actual).sort(); - const sortedKeys = [...keys].sort(); + const sortedActual = Object.keys(this.actual).toSorted(alphabetically); + const sortedKeys = keys.toSorted(alphabetically); const allKeys = sortedKeys.map(prettify).join(", "); const error = new AssertionError({ @@ -274,8 +274,8 @@ export class ObjectAssertion extends Assertion { * @returns the assertion instance */ public toHaveValues(...values: Array): this { - const sortedActual = Object.values(this.actual).sort(); - const sorterdValues = [...values].sort(); + const sortedActual = Object.values(this.actual).toSorted(alphabetically); + const sorterdValues = values.toSorted(alphabetically); const allValues = sorterdValues.map(prettify).join(", "); const error = new AssertionError({ @@ -318,8 +318,8 @@ export class ObjectAssertion extends Assertion { }); return this.execute({ assertWhen: - this.hasOwnProp(entry[0]) && - isDeepEqual(Object.getOwnPropertyDescriptor(this.actual, entry[0])?.value, entry[1]), + this.hasOwnProp(entry[0]) + && isDeepEqual(Object.getOwnPropertyDescriptor(this.actual, entry[0])?.value, entry[1]), error, invertedError, }); @@ -351,8 +351,8 @@ export class ObjectAssertion extends Assertion { return this.execute({ assertWhen: entries .every(entry => - this.hasOwnProp(entry[0]) && - isDeepEqual(Object.getOwnPropertyDescriptor(this.actual, entry[0])?.value, entry[1]), + this.hasOwnProp(entry[0]) + && isDeepEqual(Object.getOwnPropertyDescriptor(this.actual, entry[0])?.value, entry[1]), ), error, invertedError, @@ -386,8 +386,8 @@ export class ObjectAssertion extends Assertion { return this.execute({ assertWhen: entries .some(entry => - this.hasOwnProp(entry[0]) && - isDeepEqual(Object.getOwnPropertyDescriptor(this.actual, entry[0])?.value, entry[1]), + this.hasOwnProp(entry[0]) + && isDeepEqual(Object.getOwnPropertyDescriptor(this.actual, entry[0])?.value, entry[1]), ), error, invertedError, @@ -407,8 +407,8 @@ export class ObjectAssertion extends Assertion { * @returns the assertion instance */ public toHaveEntries(...entries: Entry[]): this { - const sortedActual = Object.entries(this.actual).sort(); - const sortedEntries = [...entries].sort(); + const sortedActual = Object.entries(this.actual).toSorted(alphabetically); + const sortedEntries = entries.toSorted(alphabetically); const allEntries = sortedEntries.map(prettify).join(", "); const error = new AssertionError({ actual: sortedActual, @@ -466,3 +466,7 @@ export class ObjectAssertion extends Assertion { : false; } } + +function alphabetically(a: T, b: T): number { + return String(a).localeCompare(String(b)); +} diff --git a/packages/core/src/lib/PromiseAssertion.ts b/packages/core/src/lib/PromiseAssertion.ts index 0d7e336d..0b2ed542 100644 --- a/packages/core/src/lib/PromiseAssertion.ts +++ b/packages/core/src/lib/PromiseAssertion.ts @@ -1,11 +1,11 @@ +import { AssertionError } from "assert/strict"; + import dedent from "dedent"; import isDeepEqual from "fast-deep-equal/es6"; import { Assertion } from "./Assertion"; import { prettify } from "./helpers/messages"; -import { AssertionError } from "assert/strict"; - /** * Encapsulates assertion methods applicable to Promises * @@ -13,7 +13,6 @@ import { AssertionError } from "assert/strict"; * @param I type to track the current inverted state */ export class PromiseAssertion extends Assertion> { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: // Needed because the `I` generic has to change in the inverted @@ -24,6 +23,7 @@ export class PromiseAssertion extends Assertion) { super(actual); + this.not = new Proxy(this, { get: super.proxyInverter(true) }) as PromiseAssertion; } /** @@ -42,30 +42,31 @@ export class PromiseAssertion extends Assertion { - return this.actual.then(value => { - if (this.inverted) { + return this.actual + .then(value => { + if (this.inverted) { + throw new AssertionError({ + actual: this.actual, + message: "Expected promise NOT to be resolved", + }); + } + + return value; + }) + .catch((error: unknown) => { + if (error instanceof AssertionError) { + throw error; + } + + if (this.inverted) { + return error as I extends false ? T : unknown; + } + throw new AssertionError({ actual: this.actual, - message: "Expected promise NOT to be resolved", + message: `Expected promise to be resolved, but it was rejected with <${prettify(error)}> instead`, }); - } - - return value; - }) - .catch((error: unknown) => { - if (error instanceof AssertionError) { - throw error; - } - - if (this.inverted) { - return error as I extends false ? T : unknown; - } - - throw new AssertionError({ - actual: this.actual, - message: `Expected promise to be resolved, but it was rejected with <${prettify(error)}> instead`, }); - }); } /** @@ -85,41 +86,45 @@ export class PromiseAssertion extends Assertion { - return this.actual.then(value => { - this.execute({ - assertWhen: isDeepEqual(value, expected), - error: new AssertionError({ - actual: value, - expected, - message: `Expected promise to be resolved with <${prettify(expected)}>, but got <${prettify(value)}> instead`, - }), - invertedError: new AssertionError({ - actual: value, - message: `Expected promise NOT to be resolved with <${prettify(value)}>`, - }), - }); + return this.actual + .then(value => { + this.execute({ + assertWhen: isDeepEqual(value, expected), + error: new AssertionError({ + actual: value, + expected, + message: dedent` + Expected promise to be resolved with <${prettify(expected)}>, \ + but got <${prettify(value)}> instead + `, + }), + invertedError: new AssertionError({ + actual: value, + message: `Expected promise NOT to be resolved with <${prettify(value)}>`, + }), + }); + + return value; + }) + .catch((error: unknown) => { + if (error instanceof AssertionError) { + throw error; + } - return value; - }) - .catch((error: unknown) => { - if (error instanceof AssertionError) { - throw error; - } - - throw new AssertionError({ - actual: error, - expected: !this.inverted ? expected : undefined, - message: this.inverted - ? dedent` + throw new AssertionError({ + actual: error, + expected: !this.inverted ? expected : undefined, + message: this.inverted + ? dedent` Expected promise to be resolved with anything but <${prettify(expected)}>, but was rejected with \ <${prettify(error)}> instead ` - : dedent` + : dedent` Expected promise to be resolved with <${prettify(expected)}>, but it was rejected with \ <${prettify(error)}> instead `, + }); }); - }); } /** @@ -138,30 +143,31 @@ export class PromiseAssertion extends Assertion { - return this.actual.then(value => { - if (this.inverted) { - return value; - } - - throw new AssertionError({ - actual: this.actual, - message: `Expected promise to be rejected, but it was resolved with <${prettify(value)}> instead`, - }); - }) - .catch((error: unknown) => { - if (error instanceof AssertionError) { - throw error; - } + return this.actual + .then(value => { + if (this.inverted) { + return value; + } - if (this.inverted) { throw new AssertionError({ - actual: error, - message: "Expected promise NOT to be rejected", + actual: this.actual, + message: `Expected promise to be rejected, but it was resolved with <${prettify(value)}> instead`, }); - } - - return error as I extends false ? unknown : T; - }); + }) + .catch((error: unknown) => { + if (error instanceof AssertionError) { + throw error; + } + + if (this.inverted) { + throw new AssertionError({ + actual: error, + message: "Expected promise NOT to be rejected", + }); + } + + return error as I extends false ? unknown : T; + }); } /** @@ -182,40 +188,44 @@ export class PromiseAssertion extends Assertion(expected: E): Promise { - return this.actual.then(value => { - throw new AssertionError({ - actual: this.actual, - expected: !this.inverted ? expected : undefined, - message: this.inverted - ? dedent` + return this.actual + .then(value => { + throw new AssertionError({ + actual: this.actual, + expected: !this.inverted ? expected : undefined, + message: this.inverted + ? dedent` Expected promise to be rejected with anything but <${prettify(expected)}>, but it was resolved with \ <${prettify(value)}> instead ` - : dedent` + : dedent` Expected promise to be rejected with <${prettify(expected)}>, but it was resolved with \ <${prettify(value)}> instead `, - }); - }) - .catch((error: unknown) => { - if (error instanceof AssertionError) { - throw error; - } - - this.execute({ - assertWhen: isDeepEqual(error, expected), - error: new AssertionError({ - actual: error, - expected, - message: `Expected promise to be rejected with <${prettify(expected)}>, but got <${prettify(error)}> instead`, - }), - invertedError: new AssertionError({ - actual: error, - message: `Expected promise NOT to be rejected with <${prettify(error)}>`, - }), - }); + }); + }) + .catch((error: unknown) => { + if (error instanceof AssertionError) { + throw error; + } + + this.execute({ + assertWhen: isDeepEqual(error, expected), + error: new AssertionError({ + actual: error, + expected, + message: dedent` + Expected promise to be rejected with <${prettify(expected)}>, \ + but got <${prettify(error)}> instead + `, + }), + invertedError: new AssertionError({ + actual: error, + message: `Expected promise NOT to be rejected with <${prettify(error)}>`, + }), + }); - return error as I extends false ? E : unknown; - }); + return error as I extends false ? E : unknown; + }); } } diff --git a/packages/core/src/lib/StringAssertion.ts b/packages/core/src/lib/StringAssertion.ts index 3d07c1a6..b1de6347 100644 --- a/packages/core/src/lib/StringAssertion.ts +++ b/packages/core/src/lib/StringAssertion.ts @@ -1,12 +1,11 @@ -import { Assertion } from "./Assertion"; - import { AssertionError } from "assert"; +import { Assertion } from "./Assertion"; + /** * Encapsulates assertion methods applicable to values of type string */ export class StringAssertion extends Assertion { - public constructor(actual: string) { super(actual); } diff --git a/packages/core/src/lib/config/Config.ts b/packages/core/src/lib/config/Config.ts index 578c2d5d..482ba2bb 100644 --- a/packages/core/src/lib/config/Config.ts +++ b/packages/core/src/lib/config/Config.ts @@ -1,4 +1,4 @@ -import { Assertion } from "../Assertion"; +import type { Assertion } from "../Assertion"; /** * A plugin object that can be used to extend the `expect(..)` function. @@ -16,7 +16,7 @@ export interface Plugin> { * - `top`: Test before all primitives and object-related types. * - `bottom`: Test after all primitives and object-related types. */ - insertAt: "top" | "bottom"; + insertAt: "bottom" | "top"; /** * The predicate that tests if the actual value should returnt and instance of * the plugin's `Assertion`. @@ -29,7 +29,6 @@ export interface Plugin> { * methods that can change global settings. */ export class Config { - // eslint-disable-next-line @typescript-eslint/no-explicit-any private pluginSet: Set>>; diff --git a/packages/core/src/lib/errors/UnsupportedOperationError.ts b/packages/core/src/lib/errors/UnsupportedOperationError.ts index cb5efae8..7d0c2d67 100644 --- a/packages/core/src/lib/errors/UnsupportedOperationError.ts +++ b/packages/core/src/lib/errors/UnsupportedOperationError.ts @@ -2,7 +2,6 @@ * Custom Error to describe an unsupported operation. */ export class UnsupportedOperationError extends Error { - public constructor(message?: string) { super(`Unsupported operation. ${message}`); diff --git a/packages/core/src/lib/expect.ts b/packages/core/src/lib/expect.ts index 073e7062..3898aaff 100644 --- a/packages/core/src/lib/expect.ts +++ b/packages/core/src/lib/expect.ts @@ -3,14 +3,15 @@ import { Assertion } from "./Assertion"; import { BooleanAssertion } from "./BooleanAssertion"; import { DateAssertion } from "./DateAssertion"; import { ErrorAssertion } from "./ErrorAssertion"; -import { AnyFunction, FunctionAssertion } from "./FunctionAssertion"; +import { type AnyFunction, FunctionAssertion } from "./FunctionAssertion"; import { NumberAssertion } from "./NumberAssertion"; import { ObjectAssertion } from "./ObjectAssertion"; import { PromiseAssertion } from "./PromiseAssertion"; import { StringAssertion } from "./StringAssertion"; import { config } from "./config/Config"; -import { isAnyFunction, isStruct, isPromise } from "./helpers/guards"; -import { Struct } from "./helpers/types"; +import { isAnyFunction, isPromise, isStruct } from "./helpers/guards"; + +import type { Struct } from "./helpers/types"; export interface Expect { (actual: boolean): BooleanAssertion; diff --git a/packages/core/src/lib/helpers/TypeFactories.ts b/packages/core/src/lib/helpers/TypeFactories.ts index d09d11fc..e5ce8f4b 100644 --- a/packages/core/src/lib/helpers/TypeFactories.ts +++ b/packages/core/src/lib/helpers/TypeFactories.ts @@ -1,5 +1,5 @@ import { ArrayAssertion } from "../ArrayAssertion"; -import { Assertion, Constructor } from "../Assertion"; +import { Assertion, type Constructor } from "../Assertion"; import { BooleanAssertion } from "../BooleanAssertion"; import { DateAssertion } from "../DateAssertion"; import { ErrorAssertion } from "../ErrorAssertion"; @@ -9,7 +9,8 @@ import { ObjectAssertion } from "../ObjectAssertion"; import { StringAssertion } from "../StringAssertion"; import { isStruct } from "./guards"; -import { Struct } from "./types"; + +import type { Struct } from "./types"; export type AssertionFactory> = new(actual: S) => A; @@ -40,30 +41,6 @@ export interface TypeFactory> { * Encapsulates a set of predefined {@link TypeFactory} instances. */ export interface StaticTypeFactories { - /** - * A `boolean` TypeFactory. - */ - Boolean: TypeFactory; - /** - * A `Date` TypeFactory. - */ - Date: TypeFactory; - /** - * An `Error` TypeFactory. - */ - Error: TypeFactory>; - /** - * A `function` TypeFactory. - */ - Function: TypeFactory>; - /** - * A `number` TypeFactory. - */ - Number: TypeFactory; - /** - * A `string` TypeFactory. - */ - String: TypeFactory; /** * Creates an array TypeFactory of the given TypeFactory. * @@ -76,6 +53,14 @@ export interface StaticTypeFactories { * @param innerType the TypeFactory for the array type */ array(innerType?: TypeFactory>): TypeFactory>; + /** + * A `boolean` TypeFactory. + */ + Boolean: TypeFactory; + /** + * A `Date` TypeFactory. + */ + Date: TypeFactory; /** * Creates an `Error` TypeFactory for a specific error constructor. * @@ -90,6 +75,14 @@ export interface StaticTypeFactories { * @param Type the error constructor */ error(Type: Constructor): TypeFactory>; + /** + * An `Error` TypeFactory. + */ + Error: TypeFactory>; + /** + * A `function` TypeFactory. + */ + Function: TypeFactory>; /** * Creates a TypeFactory for an instance of the given constructor. * @@ -105,6 +98,10 @@ export interface StaticTypeFactories { * @param Type the instance constructor */ instanceOf(Type: Constructor): TypeFactory>; + /** + * A `number` TypeFactory. + */ + Number: TypeFactory; /** * Creates a TypeFactory for a Javascript Object. * @@ -120,9 +117,23 @@ export interface StaticTypeFactories { * @typeParam T the type of the object */ object(): TypeFactory>; + /** + * A `string` TypeFactory. + */ + String: TypeFactory; } export const TypeFactories: Readonly = { + array(innerType?: TypeFactory>) { + return { + Factory: ArrayAssertion, + predicate: (value): value is T[] => + innerType !== undefined + ? Array.isArray(value) && value.every(innerType.predicate) + : Array.isArray(value), + typeName: "array", + }; + }, Boolean: { Factory: BooleanAssertion, predicate: (value): value is boolean => typeof value === "boolean", @@ -138,31 +149,6 @@ export const TypeFactories: Readonly = { predicate: (value): value is Error => value instanceof Error, typeName: Error.name, }, - Function: { - Factory: FunctionAssertion, - predicate: (value): value is AnyFunction => typeof value === "function", - typeName: "function", - }, - Number: { - Factory: NumberAssertion, - predicate: (value): value is number => typeof value === "number", - typeName: "number", - }, - String: { - Factory: StringAssertion, - predicate: (value): value is string => typeof value === "string", - typeName: "string", - }, - array(innerType?: TypeFactory>) { - return { - Factory: ArrayAssertion, - predicate: (value): value is T[] => - innerType !== undefined - ? Array.isArray(value) && value.every(innerType.predicate) - : Array.isArray(value), - typeName: "array", - }; - }, error(Type: Constructor) { return { Factory: ErrorAssertion, @@ -170,6 +156,11 @@ export const TypeFactories: Readonly = { typeName: Type.name, }; }, + Function: { + Factory: FunctionAssertion, + predicate: (value): value is AnyFunction => typeof value === "function", + typeName: "function", + }, instanceOf(type: Constructor) { return { Factory: Assertion, @@ -177,6 +168,11 @@ export const TypeFactories: Readonly = { typeName: type.name, }; }, + Number: { + Factory: NumberAssertion, + predicate: (value): value is number => typeof value === "number", + typeName: "number", + }, object() { return { Factory: ObjectAssertion, @@ -184,4 +180,9 @@ export const TypeFactories: Readonly = { typeName: "object", }; }, + String: { + Factory: StringAssertion, + predicate: (value): value is string => typeof value === "string", + typeName: "string", + }, }; diff --git a/packages/core/src/lib/helpers/dates.ts b/packages/core/src/lib/helpers/dates.ts index 9449c9d4..5324b504 100644 --- a/packages/core/src/lib/helpers/dates.ts +++ b/packages/core/src/lib/helpers/dates.ts @@ -1,4 +1,4 @@ -import { DateOptions, DayOfWeek, Month } from "../DateAssertion.types"; +import type { DateOptions, DayOfWeek, Month } from "../DateAssertion.types"; const DAYS_OF_WEEK: DayOfWeek[] = [ "sunday", @@ -33,7 +33,7 @@ const MONTHS: Month[] = [ * @param day a day of the week string * @returns a number representing the day of the week */ -export function dayOfWeekAsNumber (day: DayOfWeek): number { +export function dayOfWeekAsNumber(day: DayOfWeek): number { return DAYS_OF_WEEK.indexOf(day); } @@ -45,19 +45,19 @@ export function dayOfWeekAsNumber (day: DayOfWeek): number { * @param month a month string * @returns a number representing the month */ -export function monthOfYear (month: Month): number { +export function monthOfYear(month: Month): number { return MONTHS.indexOf(month); } export function optionsToDate(options: DateOptions): Date { const { - year = 0, - month = 0, day = 0, hours = 0, + milliseconds = 0, minutes = 0, + month = 0, seconds = 0, - milliseconds = 0, + year = 0, } = options; const monthAsNum = typeof month === "string" ? monthOfYear(month) + 1 diff --git a/packages/core/src/lib/helpers/guards.ts b/packages/core/src/lib/helpers/guards.ts index d0fa2ef5..bf97bd91 100644 --- a/packages/core/src/lib/helpers/guards.ts +++ b/packages/core/src/lib/helpers/guards.ts @@ -1,3 +1,4 @@ +import type { Struct } from "./types"; import type { AnyFunction } from "../FunctionAssertion"; import type { BetweenOptions, @@ -6,9 +7,7 @@ import type { LowInclusiveBetweenOptions, } from "../NumberAssertion"; -import { Struct } from "./types"; - -export function isStruct(value: T): value is { [K in keyof T]: T[K] } & Struct { +export function isStruct(value: T): value is Struct & { [K in keyof T]: T[K] } { return typeof value === "object" && value !== null && !Array.isArray(value); @@ -24,7 +23,7 @@ export function isKeyOf(target: T, key: unknown): key is keyof } export function isPromise(value: unknown): value is Promise { - const maybePromise = value as Promise | null; + const maybePromise = value as null | Promise; return ( typeof value === "object" diff --git a/packages/core/src/lib/helpers/types.ts b/packages/core/src/lib/helpers/types.ts index 295b14e6..0ee9db17 100644 --- a/packages/core/src/lib/helpers/types.ts +++ b/packages/core/src/lib/helpers/types.ts @@ -1,10 +1,3 @@ -/** - * Backwards compatibility alias of the `Struct` type. - * - * @deprecated in favor of {@link Struct} - */ -export type JSObject = Struct; - /** * Utility type that represents any kind of structured object. */ diff --git a/packages/core/src/main.ts b/packages/core/src/main.ts index d9443948..5f35e73c 100644 --- a/packages/core/src/main.ts +++ b/packages/core/src/main.ts @@ -1,23 +1,13 @@ import { Assertion } from "./lib/Assertion"; -import { config, Plugin } from "./lib/config/Config"; -import { expect, Expect } from "./lib/expect"; +import { type Plugin, config } from "./lib/config/Config"; +import { type Expect, expect } from "./lib/expect"; export { AssertionError } from "assert/strict"; -export { - AssertionFactory, - StaticTypeFactories, - TypeFactory, - TypeFactories, -} from "./lib/helpers/TypeFactories"; +export type { AssertionFactory, StaticTypeFactories, TypeFactory } from "./lib/helpers/TypeFactories"; +export { TypeFactories } from "./lib/helpers/TypeFactories"; -export { - Assertion, - Expect, - Plugin, - expect, - expect as assert, - expect as assertThat, -}; +export type { Expect, Plugin }; +export { Assertion, expect, expect as assert, expect as assertThat }; /** * Extends `@assertive-ts/core` with local or 3rd-party plugin(s). diff --git a/packages/core/test/lib/ArrayAssertion.test.ts b/packages/core/test/lib/ArrayAssertion.test.ts index 9be879b1..93ec7af1 100644 --- a/packages/core/test/lib/ArrayAssertion.test.ts +++ b/packages/core/test/lib/ArrayAssertion.test.ts @@ -1,11 +1,11 @@ +import assert, { AssertionError } from "assert"; + import { ArrayAssertion } from "../../src/lib/ArrayAssertion"; import { NumberAssertion } from "../../src/lib/NumberAssertion"; import { UnsupportedOperationError } from "../../src/lib/errors/UnsupportedOperationError"; import { TypeFactories } from "../../src/lib/helpers/TypeFactories"; import { expect } from "../../src/main"; -import assert, { AssertionError } from "assert"; - describe("[Unit] ArrayAssertion.test.ts", () => { describe(".toMatchAll", () => { const isPositive = (actual: number): boolean => actual > 0; diff --git a/packages/core/test/lib/Assertion.test.ts b/packages/core/test/lib/Assertion.test.ts index fb9247b0..8ec5650b 100644 --- a/packages/core/test/lib/Assertion.test.ts +++ b/packages/core/test/lib/Assertion.test.ts @@ -1,13 +1,15 @@ +// Remove when migrated to Vitest +/* eslint-disable @typescript-eslint/no-base-to-string */ +import assert, { AssertionError } from "assert"; + import Sinon from "sinon"; -import { Assertion, DataType } from "../../src/lib/Assertion"; +import { Assertion, type DataType } from "../../src/lib/Assertion"; import { StringAssertion } from "../../src/lib/StringAssertion"; import { UnsupportedOperationError } from "../../src/lib/errors/UnsupportedOperationError"; import { TypeFactories } from "../../src/lib/helpers/TypeFactories"; import { prettify } from "../../src/lib/helpers/messages"; -import assert, { AssertionError } from "assert"; - const HERO = { name: "Batman", realName: "Bruce Wayne", @@ -56,7 +58,6 @@ const BASE_DIFFS = [ ]; class Car { - private readonly model: string; public constructor(model: string) { @@ -328,17 +329,17 @@ describe("[Unit] Assertion.test.ts", () => { ["deep-array", [...THINGS, { x: HERO }], [...THINGS, { x: HERO }]], ["date", TODAY, new Date(TODAY.toISOString())], ] - .forEach(([type, actual, expected]) => { - it(`[${type}] returns the assertion instance`, () => { - const test = new Assertion(actual); - - assert.deepStrictEqual(test.toBeEqual(expected), test); - assert.throws(() => test.not.toBeEqual(expected), { - message: "Expected both values to NOT be deep equal", - name: AssertionError.name, + .forEach(([type, actual, expected]) => { + it(`[${type}] returns the assertion instance`, () => { + const test = new Assertion(actual); + + assert.deepStrictEqual(test.toBeEqual(expected), test); + assert.throws(() => test.not.toBeEqual(expected), { + message: "Expected both values to NOT be deep equal", + name: AssertionError.name, + }); }); }); - }); }); context("when the value is NOT referentially, NOR shallow, NOR deep equal", () => { @@ -352,17 +353,17 @@ describe("[Unit] Assertion.test.ts", () => { ["deep-object", { ...HERO, opts: { x: 1 } }, { ...HERO, opts: { x: 2 } }], ["deep-array", [...THINGS, { x: 1 }], [...THINGS, { x: 2 }]], ] - .forEach(([type, actual, expected]) => { - it(`[${type}] throws an assertion error`, () => { - const test = new Assertion(actual); - - assert.throws(() => test.toBeEqual(expected), { - message: "Expected both values to be deep equal", - name: AssertionError.name, + .forEach(([type, actual, expected]) => { + it(`[${type}] throws an assertion error`, () => { + const test = new Assertion(actual); + + assert.throws(() => test.toBeEqual(expected), { + message: "Expected both values to be deep equal", + name: AssertionError.name, + }); + assert.deepStrictEqual(test.not.toBeEqual(expected), test); }); - assert.deepStrictEqual(test.not.toBeEqual(expected), test); }); - }); }); }); @@ -374,17 +375,17 @@ describe("[Unit] Assertion.test.ts", () => { ["shallow-object", HERO, { name: "Batman", realName: "Bruce Wayne" }], ["shallow-array", THINGS, [1, "foo", false]], ] - .forEach(([valueType, expected, actual]) => { - it(`[${valueType}] returns the assertion instance`, () => { - const test = new Assertion(actual); - - assert.deepStrictEqual(test.toBeSimilar(expected), test); - assert.throws(() => test.not.toBeSimilar(expected), { - message: "Expected both values to NOT be similar", - name: AssertionError.name, + .forEach(([valueType, expected, actual]) => { + it(`[${valueType}] returns the assertion instance`, () => { + const test = new Assertion(actual); + + assert.deepStrictEqual(test.toBeSimilar(expected), test); + assert.throws(() => test.not.toBeSimilar(expected), { + message: "Expected both values to NOT be similar", + name: AssertionError.name, + }); }); }); - }); }); context("when the value is NOT referentially, NOR shallow equal", () => { @@ -398,17 +399,17 @@ describe("[Unit] Assertion.test.ts", () => { ["deep-object", { ...HERO, opts: { x: THINGS } }, { ...HERO, opts: { x: THINGS } }], ["deep-array", [...THINGS, { x: 1 }], [...THINGS, { x: 1 }]], ] - .forEach(([type, actual, expected]) => { - it(`[${type}] throws an assertion error`, () => { - const test = new Assertion(actual); - - assert.throws(() => test.toBeSimilar(expected), { - message: "Expected both values to be similar", - name: AssertionError.name, + .forEach(([type, actual, expected]) => { + it(`[${type}] throws an assertion error`, () => { + const test = new Assertion(actual); + + assert.throws(() => test.toBeSimilar(expected), { + message: "Expected both values to be similar", + name: AssertionError.name, + }); + assert.deepStrictEqual(test.not.toBeSimilar(expected), test); }); - assert.deepStrictEqual(test.not.toBeSimilar(expected), test); }); - }); }); }); @@ -438,17 +439,17 @@ describe("[Unit] Assertion.test.ts", () => { ["deep-object", { ...HERO, opts: { x: THINGS } }, { ...HERO, opts: { x: THINGS } }], ["deep-array", [...THINGS, { x: 1 }], [...THINGS, { x: 1 }]], ] - .forEach(([type, actual, expected]) => { - it(`[${type}] throws an assertion error`, () => { - const test = new Assertion(actual); - - assert.throws(() => test.toBeSame(expected), { - message: "Expected both values to be the same", - name: AssertionError.name, + .forEach(([type, actual, expected]) => { + it(`[${type}] throws an assertion error`, () => { + const test = new Assertion(actual); + + assert.throws(() => test.toBeSame(expected), { + message: "Expected both values to be the same", + name: AssertionError.name, + }); + assert.deepStrictEqual(test.not.toBeSame(expected), test); }); - assert.deepStrictEqual(test.not.toBeSame(expected), test); }); - }); }); }); diff --git a/packages/core/test/lib/BooleanAssertion.test.ts b/packages/core/test/lib/BooleanAssertion.test.ts index fef10df6..0b601283 100644 --- a/packages/core/test/lib/BooleanAssertion.test.ts +++ b/packages/core/test/lib/BooleanAssertion.test.ts @@ -1,7 +1,7 @@ -import { BooleanAssertion } from "../../src/lib/BooleanAssertion"; - import assert, { AssertionError } from "assert"; +import { BooleanAssertion } from "../../src/lib/BooleanAssertion"; + describe("[Unit] BooleanAssertion.test.ts", () => { describe(".toBeTrue", () => { context("when the value is true", () => { diff --git a/packages/core/test/lib/DateAssertion.test.ts b/packages/core/test/lib/DateAssertion.test.ts index 0edd2ed6..efd880d9 100644 --- a/packages/core/test/lib/DateAssertion.test.ts +++ b/packages/core/test/lib/DateAssertion.test.ts @@ -1,10 +1,11 @@ +import assert, { AssertionError } from "assert"; + import dedent from "dedent"; import { DateAssertion } from "../../src/lib/DateAssertion"; -import { DateOptions } from "../../src/lib/DateAssertion.types"; import { dayOfWeekAsNumber } from "../../src/lib/helpers/dates"; -import assert, { AssertionError } from "assert"; +import type { DateOptions } from "../../src/lib/DateAssertion.types"; describe("[Unit] DateAssertion.test.ts", () => { describe(".toBeDayOfWeek", () => { @@ -128,37 +129,37 @@ describe("[Unit] DateAssertion.test.ts", () => { describe(".toBeBeforeOrEqualTo", () => { context("when the actual date is before or equal to the passed date", () => { - it("returns the assertion instance", () => { - const actualDate = new Date(2021, 1, 1); - const passedDate = new Date(2021, 1, 1); - const test = new DateAssertion(actualDate); - assert.deepStrictEqual(test.toBeBeforeOrEqual(passedDate), test); - assert.throws(() => test.not.toBeBeforeOrEqual(passedDate), { - message: dedent` + it("returns the assertion instance", () => { + const actualDate = new Date(2021, 1, 1); + const passedDate = new Date(2021, 1, 1); + const test = new DateAssertion(actualDate); + assert.deepStrictEqual(test.toBeBeforeOrEqual(passedDate), test); + assert.throws(() => test.not.toBeBeforeOrEqual(passedDate), { + message: dedent` Expected <${actualDate.toISOString()}> \ NOT to be before or equal to <${passedDate.toISOString()}> `, - name: AssertionError.name, - }); + name: AssertionError.name, }); - }, + }); + }, ); context("when the actual date is NOT before or equal to the passed date", () => { - it("throws an assertion error", () => { - const actualDate = new Date(2021, 2, 1); - const passedDate = new Date(2021, 1, 1); - const test = new DateAssertion(actualDate); - assert.throws(() => test.toBeBeforeOrEqual(passedDate), { - message: `Expected <${actualDate.toISOString()}> to be before or equal to <${passedDate.toISOString()}>`, - name: AssertionError.name, - }); - assert.deepStrictEqual( - test.not.toBeBeforeOrEqual(passedDate), - test, - ); + it("throws an assertion error", () => { + const actualDate = new Date(2021, 2, 1); + const passedDate = new Date(2021, 1, 1); + const test = new DateAssertion(actualDate); + assert.throws(() => test.toBeBeforeOrEqual(passedDate), { + message: `Expected <${actualDate.toISOString()}> to be before or equal to <${passedDate.toISOString()}>`, + name: AssertionError.name, }); - }, + assert.deepStrictEqual( + test.not.toBeBeforeOrEqual(passedDate), + test, + ); + }); + }, ); }); @@ -205,17 +206,17 @@ describe("[Unit] DateAssertion.test.ts", () => { }); context("when the actual date is NOT after or equal to the passed date", () => { - it("throws an assertion error", () => { - const actualDate = new Date(2021, 1, 1); - const passedDate = new Date(2021, 2, 1); - const test = new DateAssertion(actualDate); - assert.throws(() => test.toBeAfterOrEqual(passedDate), { - message: `Expected <${actualDate.toISOString()}> to be after or equal to <${passedDate.toISOString()}>`, - name: AssertionError.name, - }); - assert.deepStrictEqual(test.not.toBeAfterOrEqual(passedDate), test); + it("throws an assertion error", () => { + const actualDate = new Date(2021, 1, 1); + const passedDate = new Date(2021, 2, 1); + const test = new DateAssertion(actualDate); + assert.throws(() => test.toBeAfterOrEqual(passedDate), { + message: `Expected <${actualDate.toISOString()}> to be after or equal to <${passedDate.toISOString()}>`, + name: AssertionError.name, }); - }, + assert.deepStrictEqual(test.not.toBeAfterOrEqual(passedDate), test); + }); + }, ); }); }); diff --git a/packages/core/test/lib/ErrorAssertion.test.ts b/packages/core/test/lib/ErrorAssertion.test.ts index cccdace4..0634c6ad 100644 --- a/packages/core/test/lib/ErrorAssertion.test.ts +++ b/packages/core/test/lib/ErrorAssertion.test.ts @@ -1,9 +1,8 @@ -import { ErrorAssertion } from "../../src/lib/ErrorAssertion"; - import assert, { AssertionError } from "assert"; -class CustomError extends Error { +import { ErrorAssertion } from "../../src/lib/ErrorAssertion"; +class CustomError extends Error { public constructor(message?: string) { super(message); diff --git a/packages/core/test/lib/FunctionAssertion.test.ts b/packages/core/test/lib/FunctionAssertion.test.ts index 2521d157..3a9d19c7 100644 --- a/packages/core/test/lib/FunctionAssertion.test.ts +++ b/packages/core/test/lib/FunctionAssertion.test.ts @@ -1,14 +1,13 @@ -/* eslint-disable no-throw-literal */ +/* eslint-disable @typescript-eslint/only-throw-error */ +import assert, { AssertionError } from "assert"; + import { Assertion } from "../../src/lib/Assertion"; import { ErrorAssertion } from "../../src/lib/ErrorAssertion"; import { FunctionAssertion } from "../../src/lib/FunctionAssertion"; import { NumberAssertion } from "../../src/lib/NumberAssertion"; import { TypeFactories } from "../../src/lib/helpers/TypeFactories"; -import assert, { AssertionError } from "assert"; - class CustomError extends Error { - public constructor(message?: string) { super(message); diff --git a/packages/core/test/lib/NumberAssertion.helpers.test.ts b/packages/core/test/lib/NumberAssertion.helpers.test.ts index 99297490..fa57e20f 100644 --- a/packages/core/test/lib/NumberAssertion.helpers.test.ts +++ b/packages/core/test/lib/NumberAssertion.helpers.test.ts @@ -1,11 +1,11 @@ +import assert from "assert"; + import { isHighInclusiveOptions, isInclusiveOptions, isLowInclusiveOptions, } from "../../src/lib/NumberAssertion.helpers"; -import assert from "assert"; - describe("[Unit] NumberAssertion.helpers.test.ts", () => { describe(".isInclusiveOptions", () => { context("when inclusive is included", () => { diff --git a/packages/core/test/lib/NumberAssertion.test.ts b/packages/core/test/lib/NumberAssertion.test.ts index 072d5574..ea0f87eb 100644 --- a/packages/core/test/lib/NumberAssertion.test.ts +++ b/packages/core/test/lib/NumberAssertion.test.ts @@ -1,7 +1,7 @@ -import { NumberAssertion } from "../../src/lib/NumberAssertion"; - import assert, { AssertionError } from "assert"; +import { NumberAssertion } from "../../src/lib/NumberAssertion"; + describe("[Unit] NumberAssertion.test.ts", () => { describe(".toBeZero", () => { context("when the value is zero", () => { @@ -296,25 +296,25 @@ describe("[Unit] NumberAssertion.test.ts", () => { test.toBeBetween({ inclusive: true, range: [10, 12] }); assert.deepStrictEqual(test.toBeBetween({ inclusive: false, range: [10, 12] }), test); assert.throws(() => test.not.toBeBetween({ inclusive: false, range: [10, 12] }), { - message: "Expected <11> NOT to be between (10, 12) range", - name: AssertionError.name, - }, + message: "Expected <11> NOT to be between (10, 12) range", + name: AssertionError.name, + }, ); }); }); context("when the actual value is NOT between passed range argument and is NOT inclusive", () => { - it("throws an assertion error", () => { - const test = new NumberAssertion(5); + it("throws an assertion error", () => { + const test = new NumberAssertion(5); - assert.throws(() => test.toBeBetween({ inclusive: false, range: [10, 12] }), { - message: "Expected <5> to be between (10, 12) range", - name: AssertionError.name, - }, - ); - assert.deepStrictEqual(test.not.toBeBetween({ inclusive: false, range: [10, 12] }), test); - }); - }, + assert.throws(() => test.toBeBetween({ inclusive: false, range: [10, 12] }), { + message: "Expected <5> to be between (10, 12) range", + name: AssertionError.name, + }, + ); + assert.deepStrictEqual(test.not.toBeBetween({ inclusive: false, range: [10, 12] }), test); + }); + }, ); context("when the actual value is between passed range argument and is inclusive", () => { @@ -323,9 +323,9 @@ describe("[Unit] NumberAssertion.test.ts", () => { assert.deepStrictEqual(test.toBeBetween({ inclusive: true, range: [10, 12] }), test); assert.throws(() => test.not.toBeBetween({ inclusive: true, range: [10, 12] }), { - message: "Expected <12> NOT to be between [10, 12] range", - name: AssertionError.name, - }, + message: "Expected <12> NOT to be between [10, 12] range", + name: AssertionError.name, + }, ); }); }); @@ -335,9 +335,9 @@ describe("[Unit] NumberAssertion.test.ts", () => { const test = new NumberAssertion(5); assert.throws(() => test.toBeBetween({ inclusive: true, range: [10, 12] }), { - message: "Expected <5> to be between [10, 12] range", - name: AssertionError.name, - }, + message: "Expected <5> to be between [10, 12] range", + name: AssertionError.name, + }, ); assert.deepStrictEqual(test.not.toBeBetween({ inclusive: true, range: [10, 12] }), test); }); @@ -350,9 +350,9 @@ describe("[Unit] NumberAssertion.test.ts", () => { assert.deepStrictEqual(test.toBeBetween({ inclusive: true, range: [10, 12] }), test); assert.throws(() => test.not.toBeBetween({ inclusive: true, range: [10, 12] }), { - message: "Expected <11> NOT to be between [10, 12] range", - name: AssertionError.name, - }, + message: "Expected <11> NOT to be between [10, 12] range", + name: AssertionError.name, + }, ); }); }); @@ -362,9 +362,9 @@ describe("[Unit] NumberAssertion.test.ts", () => { const test = new NumberAssertion(10); assert.throws(() => test.toBeBetween({ inclusive: false, range: [10, 12] }), { - message: "Expected <10> to be between (10, 12) range", - name: AssertionError.name, - }, + message: "Expected <10> to be between (10, 12) range", + name: AssertionError.name, + }, ); assert.deepStrictEqual(test.not.toBeBetween({ inclusive: false, range: [10, 12] }), test); }); @@ -373,90 +373,90 @@ describe("[Unit] NumberAssertion.test.ts", () => { context("when LowInclusiveBetweenOptions are used", () => { context("and actual value is between passed range argument and low limit is NOT inclusive", () => { - it("returns the assertion instance", () => { - const test = new NumberAssertion(11); - - assert.deepStrictEqual(test.toBeBetween({ lowInclusive: false, range: [10, 12] }), test); - assert.throws(() => test.not.toBeBetween({ lowInclusive: false, range: [10, 12] }), - { - message: "Expected <11> NOT to be between (10, 12) range", - name: AssertionError.name, - }, - ); - }); - }, + it("returns the assertion instance", () => { + const test = new NumberAssertion(11); + + assert.deepStrictEqual(test.toBeBetween({ lowInclusive: false, range: [10, 12] }), test); + assert.throws(() => test.not.toBeBetween({ lowInclusive: false, range: [10, 12] }), + { + message: "Expected <11> NOT to be between (10, 12) range", + name: AssertionError.name, + }, + ); + }); + }, ); context("and actual value is NOT between passed range argument and low limit is NOT inclusive", () => { - it("throws an assertion error", () => { - const test = new NumberAssertion(5); + it("throws an assertion error", () => { + const test = new NumberAssertion(5); - assert.throws(() => test.toBeBetween({ lowInclusive: false, range: [10, 12] }), { - message: "Expected <5> to be between (10, 12) range", - name: AssertionError.name, - }, - ); - assert.deepStrictEqual(test.not.toBeBetween({ lowInclusive: false, range: [10, 12] }), test); - }); - }, + assert.throws(() => test.toBeBetween({ lowInclusive: false, range: [10, 12] }), { + message: "Expected <5> to be between (10, 12) range", + name: AssertionError.name, + }, + ); + assert.deepStrictEqual(test.not.toBeBetween({ lowInclusive: false, range: [10, 12] }), test); + }); + }, ); context("and the actual value is between passed range argument and low limit is inclusive", () => { - it("returns the assertion instance", () => { - const test = new NumberAssertion(10); + it("returns the assertion instance", () => { + const test = new NumberAssertion(10); - assert.deepStrictEqual(test.toBeBetween({ lowInclusive: true, range: [10, 12] }), test); - assert.throws(() => test.not.toBeBetween({ lowInclusive: true, range: [10, 12] }), { - message: "Expected <10> NOT to be between [10, 12) range", - name: AssertionError.name, - }, - ); - }); - }, + assert.deepStrictEqual(test.toBeBetween({ lowInclusive: true, range: [10, 12] }), test); + assert.throws(() => test.not.toBeBetween({ lowInclusive: true, range: [10, 12] }), { + message: "Expected <10> NOT to be between [10, 12) range", + name: AssertionError.name, + }, + ); + }); + }, ); context("and the actual value is NOT between passed range argument and low limit is inclusive", () => { - it("throws an assertion error", () => { - const test = new NumberAssertion(5); + it("throws an assertion error", () => { + const test = new NumberAssertion(5); - assert.throws(() => test.toBeBetween({ lowInclusive: true, range: [10, 12] }), { - message: "Expected <5> to be between [10, 12) range", - name: AssertionError.name, - }, - ); - assert.deepStrictEqual(test.not.toBeBetween({ lowInclusive: true, range: [10, 12] }), test); - }); - }, + assert.throws(() => test.toBeBetween({ lowInclusive: true, range: [10, 12] }), { + message: "Expected <5> to be between [10, 12) range", + name: AssertionError.name, + }, + ); + assert.deepStrictEqual(test.not.toBeBetween({ lowInclusive: true, range: [10, 12] }), test); + }); + }, ); }); context("when HighInclusiveBetweenOptions are used", () => { context("and the actual value is between passed range argument and high limit is NOT inclusive", () => { - it("returns the assertion instance", () => { - const test = new NumberAssertion(11); + it("returns the assertion instance", () => { + const test = new NumberAssertion(11); - assert.deepStrictEqual(test.toBeBetween({ highInclusive: false, range: [10, 12] }), test); - assert.throws(() => test.not.toBeBetween({ highInclusive: false, range: [10, 12] }), { - message: "Expected <11> NOT to be between (10, 12) range", - name: AssertionError.name, - }, - ); - }); - }, + assert.deepStrictEqual(test.toBeBetween({ highInclusive: false, range: [10, 12] }), test); + assert.throws(() => test.not.toBeBetween({ highInclusive: false, range: [10, 12] }), { + message: "Expected <11> NOT to be between (10, 12) range", + name: AssertionError.name, + }, + ); + }); + }, ); context("and the actual value is NOT between passed range argument and high limit is NOT inclusive", () => { - it("throws an assertion error", () => { - const test = new NumberAssertion(5); + it("throws an assertion error", () => { + const test = new NumberAssertion(5); - assert.throws(() => test.toBeBetween({ highInclusive: false, range: [10, 12] }), { - message: "Expected <5> to be between (10, 12) range", - name: AssertionError.name, - }, - ); - assert.deepStrictEqual(test.not.toBeBetween({ highInclusive: false, range: [10, 12] }), test); - }); - }, + assert.throws(() => test.toBeBetween({ highInclusive: false, range: [10, 12] }), { + message: "Expected <5> to be between (10, 12) range", + name: AssertionError.name, + }, + ); + assert.deepStrictEqual(test.not.toBeBetween({ highInclusive: false, range: [10, 12] }), test); + }); + }, ); context("and the actual value is between passed range argument and high limit is inclusive", @@ -466,27 +466,27 @@ describe("[Unit] NumberAssertion.test.ts", () => { assert.deepStrictEqual(test.toBeBetween({ highInclusive: true, range: [10, 12] }), test); assert.throws(() => test.not.toBeBetween({ highInclusive: true, range: [10, 12] }), { - message: + message: "Expected <12> NOT to be between (10, 12] range", - name: AssertionError.name, - }, + name: AssertionError.name, + }, ); }); }, ); context("and the actual value is NOT between passed range argument and high limit is inclusive", () => { - it("throws an assertion error", () => { - const test = new NumberAssertion(5); + it("throws an assertion error", () => { + const test = new NumberAssertion(5); - assert.throws(() => test.toBeBetween({ highInclusive: true, range: [10, 12] }), { - message: "Expected <5> to be between (10, 12] range", - name: AssertionError.name, - }, - ); - assert.deepStrictEqual(test.not.toBeBetween({ highInclusive: true, range: [10, 12] }), test); - }); - }, + assert.throws(() => test.toBeBetween({ highInclusive: true, range: [10, 12] }), { + message: "Expected <5> to be between (10, 12] range", + name: AssertionError.name, + }, + ); + assert.deepStrictEqual(test.not.toBeBetween({ highInclusive: true, range: [10, 12] }), test); + }); + }, ); }); }); @@ -498,9 +498,9 @@ describe("[Unit] NumberAssertion.test.ts", () => { assert.deepStrictEqual(test.toBeCloseTo({ value: 10, withOffset: 2 }), test); assert.throws(() => test.not.toBeCloseTo({ value: 10, withOffset: 2 }), { - message: "Expected <9> NOT to be close to <10> with offset <2>", - name: AssertionError.name, - }, + message: "Expected <9> NOT to be close to <10> with offset <2>", + name: AssertionError.name, + }, ); }); }); @@ -510,9 +510,9 @@ describe("[Unit] NumberAssertion.test.ts", () => { const test = new NumberAssertion(4); assert.throws(() => test.toBeCloseTo({ value: 10, withOffset: 2 }), { - message: "Expected <4> to be close to <10> with offset <2>", - name: AssertionError.name, - }, + message: "Expected <4> to be close to <10> with offset <2>", + name: AssertionError.name, + }, ); assert.deepStrictEqual(test.not.toBeCloseTo({ value: 10, withOffset: 2 }), test); }); diff --git a/packages/core/test/lib/ObjectAssertion.test.ts b/packages/core/test/lib/ObjectAssertion.test.ts index a41d8f02..de15c41e 100644 --- a/packages/core/test/lib/ObjectAssertion.test.ts +++ b/packages/core/test/lib/ObjectAssertion.test.ts @@ -1,11 +1,11 @@ +import assert, { AssertionError } from "assert"; + import dedent from "dedent"; import { ObjectAssertion } from "../../src/lib/ObjectAssertion"; import { prettify } from "../../src/lib/helpers/messages"; -import assert, { AssertionError } from "assert"; - -type Entry = ["myKey", number] | [2, { innerObjKey: number; message: string; }] | [string, boolean]; +type Entry = [2, { innerObjKey: number; message: string; }] | ["myKey", number] | [string, boolean]; const RECORD: Record = { falsy: false, diff --git a/packages/core/test/lib/PromiseAssertion.test.ts b/packages/core/test/lib/PromiseAssertion.test.ts index 90b0a88b..0a7306a2 100644 --- a/packages/core/test/lib/PromiseAssertion.test.ts +++ b/packages/core/test/lib/PromiseAssertion.test.ts @@ -1,10 +1,10 @@ +import assert from "assert"; +import { AssertionError } from "assert/strict"; + import dedent from "dedent"; import { PromiseAssertion } from "../../src/lib/PromiseAssertion"; -import assert from "assert"; -import { AssertionError } from "assert/strict"; - describe("[Unit] PromiseAssertion.test.ts", () => { describe(".toBeResolved", () => { context("when the promise is resolved", () => { diff --git a/packages/core/test/lib/StringAssertion.test.ts b/packages/core/test/lib/StringAssertion.test.ts index 55071dc7..8c109950 100644 --- a/packages/core/test/lib/StringAssertion.test.ts +++ b/packages/core/test/lib/StringAssertion.test.ts @@ -1,9 +1,9 @@ +import assert, { AssertionError } from "assert"; + import Sinon from "sinon"; import { StringAssertion } from "../../src/lib/StringAssertion"; -import assert, { AssertionError } from "assert"; - describe("[Unit] StringAssertion.test.ts", () => { describe(".toBeEmpty", () => { context("when the value is an empty string", () => { @@ -213,9 +213,9 @@ describe("[Unit] StringAssertion.test.ts", () => { it("returns the assertion instance", () => { const test = new StringAssertion("1234567890"); - assert.deepStrictEqual(test.toMatchRegex(/^[0-9]+$/), test); - assert.throws(() => test.not.toMatchRegex(/^[0-9]+$/), { - message: "Expected <1234567890> NOT to match the regular expression <^[0-9]+$>", + assert.deepStrictEqual(test.toMatchRegex(/^\d+$/), test); + assert.throws(() => test.not.toMatchRegex(/^\d+$/), { + message: "Expected <1234567890> NOT to match the regular expression <^\\d+$>", name: AssertionError.name, }); }); @@ -225,11 +225,11 @@ describe("[Unit] StringAssertion.test.ts", () => { it("throws an assertion error", () => { const test = new StringAssertion("1234567890x"); - assert.throws(() => test.toMatchRegex(/^[0-9]+$/), { - message: "Expected <1234567890x> to match the regular expression <^[0-9]+$>", + assert.throws(() => test.toMatchRegex(/^\d+$/), { + message: "Expected <1234567890x> to match the regular expression <^\\d+$>", name: AssertionError.name, }); - assert.deepStrictEqual(test.not.toMatchRegex(/^[0-9]+$/), test); + assert.deepStrictEqual(test.not.toMatchRegex(/^\d+$/), test); }); }); }); diff --git a/packages/core/test/lib/expect.test.ts b/packages/core/test/lib/expect.test.ts index 0306fd6f..8edbdaeb 100644 --- a/packages/core/test/lib/expect.test.ts +++ b/packages/core/test/lib/expect.test.ts @@ -1,3 +1,5 @@ +import assert from "assert"; + import { ArrayAssertion } from "../../src/lib/ArrayAssertion"; import { Assertion } from "../../src/lib/Assertion"; import { BooleanAssertion } from "../../src/lib/BooleanAssertion"; @@ -10,10 +12,7 @@ import { PromiseAssertion } from "../../src/lib/PromiseAssertion"; import { StringAssertion } from "../../src/lib/StringAssertion"; import { expect } from "../../src/main"; -import assert from "assert"; - class CustomError extends Error { - public constructor(message?: string) { super(message); @@ -85,13 +84,13 @@ describe("[Unit] expect.test.ts", () => { new Error("classic"), new CustomError("custom"), ] - .forEach(error => { - it(`[${error.name}] returns an ErrorAssertion`, () => { - const test = expect(error); + .forEach(error => { + it(`[${error.name}] returns an ErrorAssertion`, () => { + const test = expect(error); - assert(test instanceof ErrorAssertion); + assert(test instanceof ErrorAssertion); + }); }); - }); }); context("when the actual value is an Object", () => { diff --git a/packages/core/test/lib/helpers/TypeFactories.test.ts b/packages/core/test/lib/helpers/TypeFactories.test.ts index 4f9bb99d..289342de 100644 --- a/packages/core/test/lib/helpers/TypeFactories.test.ts +++ b/packages/core/test/lib/helpers/TypeFactories.test.ts @@ -1,9 +1,8 @@ -import { TypeFactories } from "../../../src/lib/helpers/TypeFactories"; - import assert from "assert"; -class CustomError extends Error { +import { TypeFactories } from "../../../src/lib/helpers/TypeFactories"; +class CustomError extends Error { public constructor(message?: string) { super(message); diff --git a/packages/core/test/lib/helpers/dates.test.ts b/packages/core/test/lib/helpers/dates.test.ts index 7e05239d..c0ed8599 100644 --- a/packages/core/test/lib/helpers/dates.test.ts +++ b/packages/core/test/lib/helpers/dates.test.ts @@ -1,7 +1,8 @@ -import { DateOptions } from "../../../src/lib/DateAssertion.types"; +import assert from "assert"; + import { dateToOptions, dayOfWeekAsNumber, monthOfYear, optionsToDate } from "../../../src/lib/helpers/dates"; -import assert from "assert"; +import type { DateOptions } from "../../../src/lib/DateAssertion.types"; describe("[Unit] dates.test.ts", () => { describe(".dayOfWeekAsNumber", () => { diff --git a/packages/core/test/lib/helpers/guards.test.ts b/packages/core/test/lib/helpers/guards.test.ts index 0428a2f6..24665422 100644 --- a/packages/core/test/lib/helpers/guards.test.ts +++ b/packages/core/test/lib/helpers/guards.test.ts @@ -1,12 +1,12 @@ +import assert from "assert"; + import { isAnyFunction, - isStruct, isKeyOf, isPromise, + isStruct, } from "../../../src/lib/helpers/guards"; -import assert from "assert"; - describe("[Unit] guards.test.ts", () => { describe(".isStruct", () => { context("when the value is an object", () => { @@ -17,7 +17,7 @@ describe("[Unit] guards.test.ts", () => { ["foo", false], [null, false], [{ }, true], - ]; + ] as const; variants.forEach(([value, expected]) => { context(`and the value is ${JSON.stringify(value)}`, () => { @@ -48,13 +48,13 @@ describe("[Unit] guards.test.ts", () => { ...Object.keys(target), ...Object.getOwnPropertySymbols(target), ] - .forEach(key => { - it(`[${key.toString()}] return true`, () => { - const isKey = isKeyOf(target, key); + .forEach(key => { + it(`[${key.toString()}] return true`, () => { + const isKey = isKeyOf(target, key); - assert.equal(isKey, true); + assert.equal(isKey, true); + }); }); - }); }); context("when the value is not a key of the target", () => { diff --git a/packages/core/test/lib/helpers/messages.test.ts b/packages/core/test/lib/helpers/messages.test.ts index a393d4ef..648ab8e7 100644 --- a/packages/core/test/lib/helpers/messages.test.ts +++ b/packages/core/test/lib/helpers/messages.test.ts @@ -1,7 +1,7 @@ -import { prettify } from "../../../src/lib/helpers/messages"; - import assert from "assert"; +import { prettify } from "../../../src/lib/helpers/messages"; + describe("[Unit] messages.test.ts", () => { describe(".prettify", () => { context("when the value is an object", () => { diff --git a/packages/core/test/main.test.ts b/packages/core/test/main.test.ts index c8a97309..0370fc9a 100644 --- a/packages/core/test/main.test.ts +++ b/packages/core/test/main.test.ts @@ -1,7 +1,7 @@ -import { assert as libAssert, assertThat, expect, TypeFactories } from "../src/main"; - import assert from "assert"; +import { TypeFactories, assertThat, expect, assert as libAssert } from "../src/main"; + describe("[Unit] main.test.ts", () => { context("expect", () => { it("is exposed to the API", () => { diff --git a/packages/dom/package.json b/packages/dom/package.json index db420e84..aa4aa61a 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -36,7 +36,7 @@ }, "dependencies": { "fast-deep-equal": "^3.1.3", - "tslib": "^2.6.2" + "tslib": "^2.8.1" }, "devDependencies": { "@assertive-ts/core": "workspace:^", @@ -60,7 +60,7 @@ "typedoc": "^0.25.8", "typedoc-plugin-markdown": "^3.17.1", "typedoc-plugin-merge-modules": "^5.1.0", - "typescript": "^5.4.2" + "typescript": "^5.9.3" }, "peerDependencies": { "@assertive-ts/core": ">=2.0.0" diff --git a/packages/dom/src/lib/ElementAssertion.ts b/packages/dom/src/lib/ElementAssertion.ts index c8aae1b9..974d9840 100644 --- a/packages/dom/src/lib/ElementAssertion.ts +++ b/packages/dom/src/lib/ElementAssertion.ts @@ -6,7 +6,6 @@ import { isElementEmpty } from "./helpers/dom"; import { getExpectedAndReceivedStyles } from "./helpers/styles"; export class ElementAssertion extends Assertion { - public constructor(actual: T) { super(actual); } @@ -158,28 +157,27 @@ export class ElementAssertion extends Assertion { * * @returns The assertion instance. */ - public toHaveFocus(): this { - - const hasFocus = this.actual === document.activeElement; + public toHaveFocus(): this { + const hasFocus = this.actual === document.activeElement; - const error = new AssertionError({ - actual: this.actual, - expected: document.activeElement, - message: "Expected the element to be focused", - }); + const error = new AssertionError({ + actual: this.actual, + expected: document.activeElement, + message: "Expected the element to be focused", + }); - const invertedError = new AssertionError({ - actual: this.actual, - expected: document.activeElement, - message: "Expected the element NOT to be focused", - }); + const invertedError = new AssertionError({ + actual: this.actual, + expected: document.activeElement, + message: "Expected the element NOT to be focused", + }); - return this.execute({ - assertWhen: hasFocus, - error, - invertedError, - }); - } + return this.execute({ + assertWhen: hasFocus, + error, + invertedError, + }); + } /** * Asserts that the element has the specified CSS styles. @@ -194,7 +192,6 @@ export class ElementAssertion extends Assertion { */ public toHaveStyle(expected: Partial): this { - const [expectedStyle, receivedStyle] = getExpectedAndReceivedStyles(this.actual, expected); if (!expectedStyle || !receivedStyle) { @@ -216,7 +213,7 @@ export class ElementAssertion extends Assertion { error, invertedError, }); - } + } /** * Asserts that the element has one or more of the specified CSS styles. @@ -231,7 +228,6 @@ export class ElementAssertion extends Assertion { */ public toHaveSomeStyle(expected: Partial): this { - const [expectedStyle, elementProcessedStyle] = getExpectedAndReceivedStyles(this.actual, expected); if (!expectedStyle || !elementProcessedStyle) { @@ -251,8 +247,9 @@ export class ElementAssertion extends Assertion { const invertedError = new AssertionError({ actual: this.actual, - // eslint-disable-next-line max-len - message: `Expected the element NOT to match some of the following styles:\n${JSON.stringify(expectedStyle, null, 2)}`, + + message: `Expected the element NOT to match some of the following styles:\n` + + `${JSON.stringify(expectedStyle, null, 2)}`, }); return this.execute({ @@ -274,7 +271,6 @@ export class ElementAssertion extends Assertion { */ public toBeEmpty(): this { - const isEmpty = isElementEmpty(this.actual); const error = new AssertionError({ @@ -318,7 +314,7 @@ export class ElementAssertion extends Assertion { * @returns the assertion instance. */ - public toHaveDescription(expectedDescription?: string | RegExp): this { + public toHaveDescription(expectedDescription?: RegExp | string): this { const description = getAccessibleDescription(this.actual); const hasExpectedValue = expectedDescription !== undefined; @@ -338,8 +334,8 @@ export class ElementAssertion extends Assertion { actual: description, expected: expectedDescription, message: hasExpectedValue - ? `Expected the element to have description ${formatExpectation(expectedDescription instanceof RegExp)}, ` + - `but received "${description}"` + ? `Expected the element to have description ${formatExpectation(expectedDescription instanceof RegExp)}, ` + + `but received "${description}"` : "Expected the element to have a description", }); @@ -347,8 +343,8 @@ export class ElementAssertion extends Assertion { actual: description, expected: expectedDescription, message: hasExpectedValue - ? `Expected the element NOT to have description ${formatExpectation(expectedDescription instanceof RegExp)}, ` + - `but received "${description}"` + ? `Expected the element NOT to have description ${formatExpectation(expectedDescription instanceof RegExp)}, ` + + `but received "${description}"` : `Expected the element NOT to have a description, but received "${description}"`, }); diff --git a/packages/dom/src/lib/helpers/accessibility.ts b/packages/dom/src/lib/helpers/accessibility.ts index 355409ac..21dae511 100644 --- a/packages/dom/src/lib/helpers/accessibility.ts +++ b/packages/dom/src/lib/helpers/accessibility.ts @@ -11,7 +11,7 @@ export function getAccessibleDescription(actual: Element): string { const descriptionIds = ariaDescribedBy.split(/\s+/).filter(Boolean); - const getElementText = (id: string): string | null => { + const getElementText = (id: string): null | string => { const element = actual.ownerDocument.getElementById(id); if (!element || !element.textContent) { diff --git a/packages/dom/src/lib/helpers/dom.ts b/packages/dom/src/lib/helpers/dom.ts index 57f57e85..62d00128 100644 --- a/packages/dom/src/lib/helpers/dom.ts +++ b/packages/dom/src/lib/helpers/dom.ts @@ -1,6 +1,6 @@ const COMMENT_NODE_TYPE = 8; -export function isElementEmpty (element: Element): boolean { +export function isElementEmpty(element: Element): boolean { const nonCommentChildNodes = [...element.childNodes].filter(child => child.nodeType !== COMMENT_NODE_TYPE); return nonCommentChildNodes.length === 0; } diff --git a/packages/dom/src/lib/helpers/styles.ts b/packages/dom/src/lib/helpers/styles.ts index d178a8da..d218bbf9 100644 --- a/packages/dom/src/lib/helpers/styles.ts +++ b/packages/dom/src/lib/helpers/styles.ts @@ -9,7 +9,6 @@ function normalizeStyles(css: Partial): StyleDeclaration { const { expectedStyle } = Object.entries(css).reduce( (acc, [property, value]) => { - if (typeof value !== "string") { return acc; } @@ -36,40 +35,39 @@ function normalizeStyles(css: Partial): StyleDeclaration { return expectedStyle; } -function getReceivedStyle (props: string[], received: CSSStyleDeclaration): StyleDeclaration { - +function getReceivedStyle(props: string[], received: CSSStyleDeclaration): StyleDeclaration { return props.reduce((acc, prop) => { - const actualStyle = received.getPropertyValue(prop).trim(); return actualStyle - ? { ...acc, [prop]: actualStyle } - : acc; - + ? { ...acc, [prop]: actualStyle } + : acc; }, {} as StyleDeclaration); } -export function getExpectedAndReceivedStyles -(actual: Element, expected: Partial): StyleDeclaration[] { - if (!actual.ownerDocument.defaultView) { - throw new Error("The element is not attached to a document with a default view."); - } - if (!(actual instanceof HTMLElement)) { - throw new Error("The element is not an HTMLElement."); - } +export function getExpectedAndReceivedStyles( + actual: Element, + expected: Partial, +): StyleDeclaration[] { + if (!actual.ownerDocument.defaultView) { + throw new Error("The element is not attached to a document with a default view."); + } + if (!(actual instanceof HTMLElement)) { + throw new Error("The element is not an HTMLElement."); + } - const window = actual.ownerDocument.defaultView; + const window = actual.ownerDocument.defaultView; - const rawElementStyles = window.getComputedStyle(actual); + const rawElementStyles = window.getComputedStyle(actual); - const expectedStyle = normalizeStyles(expected); + const expectedStyle = normalizeStyles(expected); - const styleKeys = Object.keys(expectedStyle); + const styleKeys = Object.keys(expectedStyle); - const elementProcessedStyle = getReceivedStyle(styleKeys, rawElementStyles); + const elementProcessedStyle = getReceivedStyle(styleKeys, rawElementStyles); - return [ - expectedStyle, - elementProcessedStyle, - ]; + return [ + expectedStyle, + elementProcessedStyle, + ]; } diff --git a/packages/dom/test/unit/lib/ElementAssertion.test.tsx b/packages/dom/test/unit/lib/ElementAssertion.test.tsx index 22c2c796..f6f40930 100644 --- a/packages/dom/test/unit/lib/ElementAssertion.test.tsx +++ b/packages/dom/test/unit/lib/ElementAssertion.test.tsx @@ -3,18 +3,18 @@ import { render } from "@testing-library/react"; import { ElementAssertion } from "../../../src/lib/ElementAssertion"; +import { HaveClassTest } from "./fixtures/HaveClassTest"; +import { NestedElementsTest } from "./fixtures/NestedElementsTest"; +import { SimpleTest } from "./fixtures/SimpleTest"; +import { WithAttributesTest } from "./fixtures/WithAttributesTest"; import { DescriptionTestComponent } from "./fixtures/descriptionTestComponent"; import { FocusTestComponent } from "./fixtures/focusTestComponent"; -import { HaveClassTestComponent } from "./fixtures/haveClassTestComponent"; -import { NestedElementsTestComponent } from "./fixtures/nestedElementsTestComponent"; -import { SimpleTestComponent } from "./fixtures/simpleTestComponent"; -import { WithAttributesTestComponent } from "./fixtures/withAttributesTestComponent"; describe("[Unit] ElementAssertion.test.ts", () => { describe(".toBeInTheDocument", () => { context("when the element is in the document", () => { it("returns the assertion instance", async () => { - const { findByRole } = render(); + const { findByRole } = render(); const button = await findByRole("button", { name: "click me" }); const test = new ElementAssertion(button); @@ -44,7 +44,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { context("when the descendant element is contained in the ancestor element", () => { context("and it is a direct child", () => { it("returns the assertion instance", async () => { - const { findByTestId } = render(); + const { findByTestId } = render(); const grandparent = await findByTestId("grandparent"); const parent = await findByTestId("parent"); const child = await findByTestId("child"); @@ -52,9 +52,9 @@ describe("[Unit] ElementAssertion.test.ts", () => { const grandparentTest = new ElementAssertion(grandparent); const parentTest = new ElementAssertion(parent); - expect(grandparentTest.toContainElement(parent)); - expect(grandparentTest.toContainElement(svgElement)); - expect(parentTest.toContainElement(child)); + expect(grandparentTest.toContainElement(parent)).toBeEqual(grandparentTest); + expect(grandparentTest.toContainElement(svgElement)).toBeEqual(grandparentTest); + expect(parentTest.toContainElement(child)).toBeEqual(parentTest); expect(() => grandparentTest.not.toContainElement(parent)) .toThrowError(AssertionError) @@ -72,12 +72,12 @@ describe("[Unit] ElementAssertion.test.ts", () => { context("and it is an indirect child", () => { it("returns the assertion instance", async () => { - const { findByTestId } = render(); + const { findByTestId } = render(); const grandparent = await findByTestId("grandparent"); const child = await findByTestId("child"); const grandparentTest = new ElementAssertion(grandparent); - expect(grandparentTest.toContainElement(child)); + expect(grandparentTest.toContainElement(child)).toBeEqual(grandparentTest); expect(() => grandparentTest.not.toContainElement(child)) .toThrowError(AssertionError) @@ -87,12 +87,12 @@ describe("[Unit] ElementAssertion.test.ts", () => { context("and it is a deeply nested child", () => { it("returns the assertion instance", async () => { - const { findByTestId } = render(); + const { findByTestId } = render(); const grandparent = await findByTestId("grandparent"); const deepChild = await findByTestId("deep-child"); const grandparentTest = new ElementAssertion(grandparent); - expect(grandparentTest.toContainElement(deepChild)); + expect(grandparentTest.toContainElement(deepChild)).toBeEqual(grandparentTest); expect(() => grandparentTest.not.toContainElement(deepChild)) .toThrowError(AssertionError) @@ -104,7 +104,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { context("when element is NOT contained in ancestor element", () => { it("throws an assertion error", async () => { const notChildElement = document.createElement("span"); - const { findByTestId } = render(); + const { findByTestId } = render(); const grandparent = await findByTestId("grandparent"); const grandparentTest = new ElementAssertion(grandparent); @@ -120,7 +120,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { describe(".toHaveAttribute", () => { context("when the element has the attribute with the expected value", () => { it("returns the assertion instance", async () => { - const { findByRole } = render(); + const { findByRole } = render(); const button = await findByRole("button", { name: "click me" }); const test = new ElementAssertion(button); @@ -134,7 +134,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { context("when the element has the attribute with a not expected value", () => { it("throws an assertion error", async () => { - const { findByRole } = render(); + const { findByRole } = render(); const button = await findByRole("button", { name: "click me" }); const test = new ElementAssertion(button); @@ -149,7 +149,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { context("when the element has the attribute without checking value", () => { it("returns the assertion instance", async () => { - const { findByRole } = render(); + const { findByRole } = render(); const button = await findByRole("button", { name: "click me" }); const test = new ElementAssertion(button); @@ -163,7 +163,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { context("when the element does not have the attribute", () => { it("throws an assertion error", async () => { - const { findByRole } = render(); + const { findByRole } = render(); const button = await findByRole("button", { name: "click me" }); const test = new ElementAssertion(button); @@ -179,7 +179,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { describe(".toHaveClass", () => { context("when the element has the expected class", () => { it("returns the assertion instance", () => { - const { getByText } = render(); + const { getByText } = render(); const divTest = getByText("Test text inside a div"); const test = new ElementAssertion(divTest); @@ -193,7 +193,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { context("when the element does not have the expected class", () => { it("throws an assertion error", () => { - const { getByText } = render(); + const { getByText } = render(); const divTest = getByText("Test text inside a div"); const test = new ElementAssertion(divTest); @@ -209,7 +209,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { describe(".toHaveAnyClass", () => { context("when the element has at least one of the expected classes", () => { it("returns the assertion instance", () => { - const { getByText } = render(); + const { getByText } = render(); const divTest = getByText("Test text inside a div"); const test = new ElementAssertion(divTest); @@ -223,7 +223,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { context("when the element does not have any of the expected classes", () => { it("throws an assertion error", () => { - const { getByText } = render(); + const { getByText } = render(); const divTest = getByText("Test text inside a div"); const test = new ElementAssertion(divTest); @@ -239,7 +239,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { describe(".toHaveAllClasses", () => { context("when the element has all the expected classes", () => { it("returns the assertion instance", () => { - const { getByText } = render(); + const { getByText } = render(); const divTest = getByText("Test text inside a div"); const test = new ElementAssertion(divTest); @@ -253,14 +253,14 @@ describe("[Unit] ElementAssertion.test.ts", () => { context("when the element does not have all the expected classes", () => { it("throws an assertion error", () => { - const { getByText } = render(); + const { getByText } = render(); const divTest = getByText("Test text inside a div"); divTest.classList.add("foo", "bar"); const test = new ElementAssertion(divTest); expect(() => test.toHaveAllClasses("foo", "bar", "baz")) - .toThrowError(AssertionError) - .toHaveMessage('Expected the element to have all of these classes: "foo bar baz"'); + .toThrowError(AssertionError) + .toHaveMessage('Expected the element to have all of these classes: "foo bar baz"'); expect(test.not.toHaveAllClasses("foo", "bar", "baz")).toBeEqual(test); }); @@ -301,11 +301,12 @@ describe("[Unit] ElementAssertion.test.ts", () => { context("when the element has the expected style", () => { it("returns the assertion instance", () => { const { getByTestId } = render( -
); +
, + ); const divTest = getByTestId("test-div"); const test = new ElementAssertion(divTest); @@ -314,59 +315,60 @@ describe("[Unit] ElementAssertion.test.ts", () => { expect(() => test.not.toHaveStyle({ border: "1px solid black", color: "red", display: "flex" })) .toThrowError(AssertionError) .toHaveMessage( - // eslint-disable-next-line max-len - 'Expected the element to NOT match the following style:\n{\n "border": "1px solid black",\n "color": "rgb(255, 0, 0)",\n "display": "flex"\n}', + + "Expected the element to NOT match the following style:\n" + + '{\n "border": "1px solid black",\n "color": "rgb(255, 0, 0)",\n "display": "flex"\n}', ); }); }); context("when the element does not have the expected style", () => { - it("throws an assertion error", () => { - const { getByTestId } = render( -
, - ); + it("throws an assertion error", () => { + const { getByTestId } = render( +
, + ); - const divTest = getByTestId("test-div"); - const test = new ElementAssertion(divTest); + const divTest = getByTestId("test-div"); + const test = new ElementAssertion(divTest); - expect(() => test.toHaveStyle(({ border: "1px solid black", color: "red", display: "flex" }))) - .toThrowError(AssertionError) - .toHaveMessage( - // eslint-disable-next-line max-len - 'Expected the element to match the following style:\n{\n "border": "1px solid black",\n "color": "rgb(255, 0, 0)",\n "display": "flex"\n}', - ); + expect(() => test.toHaveStyle(({ border: "1px solid black", color: "red", display: "flex" }))) + .toThrowError(AssertionError) + .toHaveMessage( - expect(test.not.toHaveStyle({ border: "1px solid black", color: "red", display: "flex" })).toBeEqual(test); + "Expected the element to match the following style:\n" + + '{\n "border": "1px solid black",\n "color": "rgb(255, 0, 0)",\n "display": "flex"\n}', + ); - }); + expect(test.not.toHaveStyle({ border: "1px solid black", color: "red", display: "flex" })).toBeEqual(test); + }); }); context("when the element partially match the style", () => { - it("throws an assertion error", () => { - const { getByTestId } = render( -
, - ); + it("throws an assertion error", () => { + const { getByTestId } = render( +
, + ); + + const divTest = getByTestId("test-div"); + const test = new ElementAssertion(divTest); - const divTest = getByTestId("test-div"); - const test = new ElementAssertion(divTest); + expect(() => test.toHaveStyle(({ color: "red", display: "flex" }))) + .toThrowError(AssertionError) + .toHaveMessage( - expect(() => test.toHaveStyle(({ color: "red", display: "flex" }))) - .toThrowError(AssertionError) - .toHaveMessage( - // eslint-disable-next-line max-len - 'Expected the element to match the following style:\n{\n "color": "rgb(255, 0, 0)",\n "display": "flex"\n}', + "Expected the element to match the following style:\n" + + '{\n "color": "rgb(255, 0, 0)",\n "display": "flex"\n}', ); - expect(test.not.toHaveStyle({ border: "1px solid black", color: "red", display: "flex" })).toBeEqual(test); - - }); + expect(test.not.toHaveStyle({ border: "1px solid black", color: "red", display: "flex" })).toBeEqual(test); + }); }); }); @@ -374,39 +376,41 @@ describe("[Unit] ElementAssertion.test.ts", () => { context("when the element contains one or more expected styles", () => { it("returns the assertion instance", () => { const { getByTestId } = render( -
, - ); +
, + ); const divTest = getByTestId("test-div"); const test = new ElementAssertion(divTest); expect(test.toHaveSomeStyle({ color: "red", display: "flex", height: "3rem", width: "2rem" })).toBeEqual(test); expect(() => test.not.toHaveSomeStyle({ color: "blue" })) - .toThrowError(AssertionError) - // eslint-disable-next-line max-len - .toHaveMessage("Expected the element NOT to match some of the following styles:\n{\n \"color\": \"rgb(0, 0, 255)\"\n}"); + .toThrowError(AssertionError) + + .toHaveMessage("Expected the element NOT to match some of the following styles:\n" + + "{\n \"color\": \"rgb(0, 0, 255)\"\n}"); }); }); context("when the element does not contain any of the expected styles", () => { it("throws an assertion error", () => { const { getByTestId } = render( -
, - ); +
, + ); const divTest = getByTestId("test-div"); const test = new ElementAssertion(divTest); expect(() => test.toHaveSomeStyle({ color: "red", display: "flex" })) - .toThrowError(AssertionError) - // eslint-disable-next-line max-len - .toHaveMessage("Expected the element to match some of the following styles:\n{\n \"color\": \"rgb(255, 0, 0)\",\n \"display\": \"flex\"\n}"); + .toThrowError(AssertionError) + + .toHaveMessage("Expected the element to match some of the following styles:\n" + + "{\n \"color\": \"rgb(255, 0, 0)\",\n \"display\": \"flex\"\n}"); expect(test.not.toHaveSomeStyle({ border: "1px solid blue", color: "red", display: "flex" })).toBeEqual(test); }); @@ -423,9 +427,8 @@ describe("[Unit] ElementAssertion.test.ts", () => { expect(test.toBeEmpty()).toBeEqual(test); expect(() => test.not.toBeEmpty()) - .toThrowError(AssertionError) - .toHaveMessage("Expected the element NOT to be empty."); - + .toThrowError(AssertionError) + .toHaveMessage("Expected the element NOT to be empty."); }); }); @@ -440,9 +443,8 @@ describe("[Unit] ElementAssertion.test.ts", () => { expect(test.toBeEmpty()).toBeEqual(test); expect(() => test.not.toBeEmpty()) - .toThrowError(AssertionError) - .toHaveMessage("Expected the element NOT to be empty."); - + .toThrowError(AssertionError) + .toHaveMessage("Expected the element NOT to be empty."); }); }); @@ -457,11 +459,10 @@ describe("[Unit] ElementAssertion.test.ts", () => { const test = new ElementAssertion(divTest); expect(() => test.toBeEmpty()) - .toThrowError(AssertionError) - .toHaveMessage("Expected the element to be empty."); + .toThrowError(AssertionError) + .toHaveMessage("Expected the element to be empty."); expect(test.not.toBeEmpty()).toBeEqual(test); - }); }); }); @@ -509,8 +510,8 @@ describe("[Unit] ElementAssertion.test.ts", () => { expect(() => test.not.toHaveDescription("This is a description")) .toThrowError(AssertionError) .toHaveMessage( - 'Expected the element NOT to have description "This is a description", ' + - 'but received "This is a description"', + 'Expected the element NOT to have description "This is a description", ' + + 'but received "This is a description"', ); }); }); @@ -526,8 +527,8 @@ describe("[Unit] ElementAssertion.test.ts", () => { expect(() => test.not.toHaveDescription("This is a description Additional info")) .toThrowError(AssertionError) .toHaveMessage( - 'Expected the element NOT to have description "This is a description Additional info", ' + - 'but received "This is a description Additional info"', + 'Expected the element NOT to have description "This is a description Additional info", ' + + 'but received "This is a description Additional info"', ); }); }); @@ -561,8 +562,8 @@ describe("[Unit] ElementAssertion.test.ts", () => { expect(() => test.not.toHaveDescription(/description/i)) .toThrowError(AssertionError) .toHaveMessage( - "Expected the element NOT to have description matching /description/i, " + - 'but received "This is a description"', + "Expected the element NOT to have description matching /description/i, " + + 'but received "This is a description"', ); }); }); @@ -576,8 +577,8 @@ describe("[Unit] ElementAssertion.test.ts", () => { expect(() => test.toHaveDescription(/wrong pattern/)) .toThrowError(AssertionError) .toHaveMessage( - "Expected the element to have description matching /wrong pattern/, " + - 'but received "This is a description"', + "Expected the element to have description matching /wrong pattern/, " + + 'but received "This is a description"', ); expect(test.not.toHaveDescription(/wrong pattern/)).toBeEqual(test); diff --git a/packages/dom/test/unit/lib/fixtures/HaveClassTest.tsx b/packages/dom/test/unit/lib/fixtures/HaveClassTest.tsx new file mode 100644 index 00000000..077051de --- /dev/null +++ b/packages/dom/test/unit/lib/fixtures/HaveClassTest.tsx @@ -0,0 +1,13 @@ +import type { ReactElement } from "react"; + +interface HaveClassTestComponentProps { + className?: string; +} + +export function HaveClassTest({ className }: HaveClassTestComponentProps): ReactElement { + return ( +
+ {"Test text inside a div"} +
+ ); +} diff --git a/packages/dom/test/unit/lib/fixtures/nestedElementsTestComponent.tsx b/packages/dom/test/unit/lib/fixtures/NestedElementsTest.tsx similarity index 78% rename from packages/dom/test/unit/lib/fixtures/nestedElementsTestComponent.tsx rename to packages/dom/test/unit/lib/fixtures/NestedElementsTest.tsx index 58273e01..e2199674 100644 --- a/packages/dom/test/unit/lib/fixtures/nestedElementsTestComponent.tsx +++ b/packages/dom/test/unit/lib/fixtures/NestedElementsTest.tsx @@ -1,6 +1,6 @@ -import { ReactElement } from "react"; +import type { ReactElement } from "react"; -export function NestedElementsTestComponent(): ReactElement { +export function NestedElementsTest(): ReactElement { return ( diff --git a/packages/dom/test/unit/lib/fixtures/SimpleTest.tsx b/packages/dom/test/unit/lib/fixtures/SimpleTest.tsx new file mode 100644 index 00000000..ae623903 --- /dev/null +++ b/packages/dom/test/unit/lib/fixtures/SimpleTest.tsx @@ -0,0 +1,9 @@ +import type { ReactElement } from "react"; + +export function SimpleTest(): ReactElement { + return ( +
+ +
+ ); +} diff --git a/packages/dom/test/unit/lib/fixtures/WithAttributesTest.tsx b/packages/dom/test/unit/lib/fixtures/WithAttributesTest.tsx new file mode 100644 index 00000000..b95b74f0 --- /dev/null +++ b/packages/dom/test/unit/lib/fixtures/WithAttributesTest.tsx @@ -0,0 +1,9 @@ +import type { ReactElement } from "react"; + +export function WithAttributesTest(): ReactElement { + return ( + + ); +} diff --git a/packages/dom/test/unit/lib/fixtures/descriptionTestComponent.tsx b/packages/dom/test/unit/lib/fixtures/descriptionTestComponent.tsx index c37685d5..3a6c9e22 100644 --- a/packages/dom/test/unit/lib/fixtures/descriptionTestComponent.tsx +++ b/packages/dom/test/unit/lib/fixtures/descriptionTestComponent.tsx @@ -1,4 +1,4 @@ -import { ReactElement } from "react"; +import type { ReactElement } from "react"; export function DescriptionTestComponent(): ReactElement { return ( diff --git a/packages/dom/test/unit/lib/fixtures/focusTestComponent.tsx b/packages/dom/test/unit/lib/fixtures/focusTestComponent.tsx index 91dd421a..0181b98e 100644 --- a/packages/dom/test/unit/lib/fixtures/focusTestComponent.tsx +++ b/packages/dom/test/unit/lib/fixtures/focusTestComponent.tsx @@ -1,4 +1,4 @@ -import { ReactElement } from "react"; +import type { ReactElement } from "react"; export function FocusTestComponent(): ReactElement { return ( diff --git a/packages/dom/test/unit/lib/fixtures/haveClassTestComponent.tsx b/packages/dom/test/unit/lib/fixtures/haveClassTestComponent.tsx deleted file mode 100644 index a7b8fb37..00000000 --- a/packages/dom/test/unit/lib/fixtures/haveClassTestComponent.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { ReactElement } from "react"; - -export function HaveClassTestComponent({ className }: { className?: string; }): ReactElement { - return ( -
- {"Test text inside a div"} -
- ); -} diff --git a/packages/dom/test/unit/lib/fixtures/simpleTestComponent.tsx b/packages/dom/test/unit/lib/fixtures/simpleTestComponent.tsx deleted file mode 100644 index 150062cb..00000000 --- a/packages/dom/test/unit/lib/fixtures/simpleTestComponent.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { ReactElement } from "react"; - -export function SimpleTestComponent(): ReactElement { - return ( -
- -
- ); - } diff --git a/packages/dom/test/unit/lib/fixtures/withAttributesTestComponent.tsx b/packages/dom/test/unit/lib/fixtures/withAttributesTestComponent.tsx deleted file mode 100644 index 14e52ff6..00000000 --- a/packages/dom/test/unit/lib/fixtures/withAttributesTestComponent.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { ReactElement } from "react"; - -export function WithAttributesTestComponent(): ReactElement { - return ( - - ); -} diff --git a/packages/native/package.json b/packages/native/package.json index 9d68014d..a19a86c5 100644 --- a/packages/native/package.json +++ b/packages/native/package.json @@ -36,7 +36,7 @@ "dependencies": { "dot-prop-immutable": "^2.1.1", "fast-deep-equal": "^3.1.3", - "tslib": "^2.6.2" + "tslib": "^2.8.1" }, "devDependencies": { "@assertive-ts/core": "workspace:^", @@ -58,7 +58,7 @@ "typedoc": "^0.25.8", "typedoc-plugin-markdown": "^3.17.1", "typedoc-plugin-merge-modules": "^5.1.0", - "typescript": "^5.4.2" + "typescript": "^5.9.3" }, "peerDependencies": { "@assertive-ts/core": ">=2.0.0", diff --git a/packages/native/src/lib/ElementAssertion.ts b/packages/native/src/lib/ElementAssertion.ts index f322b50d..e38503ea 100644 --- a/packages/native/src/lib/ElementAssertion.ts +++ b/packages/native/src/lib/ElementAssertion.ts @@ -1,12 +1,13 @@ import { Assertion, AssertionError } from "@assertive-ts/core"; import { get } from "dot-prop-immutable"; -import { ReactTestInstance } from "react-test-renderer"; -import { isAncestorDisabled, isElementDisabled, isAncestorNotVisible, isElementVisible } from "./helpers/accesibility"; +import { isAncestorDisabled, isAncestorNotVisible, isElementDisabled, isElementVisible } from "./helpers/accesibility"; import { getFlattenedStyle, styleToString } from "./helpers/styles"; import { getTextContent, textMatches } from "./helpers/text"; -import { AssertiveStyle, TestableTextMatcher } from "./helpers/types"; -import { isEmpty, instanceToString, isElementContained } from "./helpers/utils"; +import { instanceToString, isElementContained, isEmpty } from "./helpers/utils"; + +import type { AssertiveStyle, TestableTextMatcher } from "./helpers/types"; +import type { ReactTestInstance } from "react-test-renderer"; export class ElementAssertion extends Assertion { public constructor(actual: ReactTestInstance) { @@ -172,10 +173,12 @@ export class ElementAssertion extends Assertion { const errorMessage = value === undefined ? `Expected element ${this.toString()} to have prop '${propName}'.` + // eslint-disable-next-line @typescript-eslint/no-base-to-string : `Expected element ${this.toString()} to have prop '${propName}' with value '${String(value)}'.`; const invertedErrorMessage = value === undefined ? `Expected element ${this.toString()} NOT to have prop '${propName}'.` + // eslint-disable-next-line @typescript-eslint/no-base-to-string : `Expected element ${this.toString()} NOT to have prop '${propName}' with value '${String(value)}'.`; const error = new AssertionError({ actual: this.actual, message: errorMessage }); @@ -249,15 +252,15 @@ export class ElementAssertion extends Assertion { const error = new AssertionError({ actual: this.actual, - message: `Expected element ${this.toString()} to have text content matching '` + - `${text.toString()}'.`, + message: `Expected element ${this.toString()} to have text content matching '` + + `${text.toString()}'.`, }); const invertedError = new AssertionError({ actual: this.actual, message: - `Expected element ${this.toString()} NOT to have text content matching '` + - `${text.toString()}'.`, + `Expected element ${this.toString()} NOT to have text content matching '` + + `${text.toString()}'.`, }); return this.execute({ diff --git a/packages/native/src/lib/helpers/accesibility.ts b/packages/native/src/lib/helpers/accesibility.ts index 8855c711..53c43074 100644 --- a/packages/native/src/lib/helpers/accesibility.ts +++ b/packages/native/src/lib/helpers/accesibility.ts @@ -1,5 +1,6 @@ import { get } from "dot-prop-immutable"; -import { ReactTestInstance } from "react-test-renderer"; + +import type { ReactTestInstance } from "react-test-renderer"; export function isElementDisabled(element: ReactTestInstance): boolean { const { type } = element; diff --git a/packages/native/src/lib/helpers/styles.ts b/packages/native/src/lib/helpers/styles.ts index 3b14ef7d..3af8b703 100644 --- a/packages/native/src/lib/helpers/styles.ts +++ b/packages/native/src/lib/helpers/styles.ts @@ -1,10 +1,10 @@ import { StyleSheet } from "react-native"; -import { AssertiveStyle, StyleObject } from "./types"; +import type { AssertiveStyle, StyleObject } from "./types"; export function getFlattenedStyle(style: AssertiveStyle): StyleObject { - const flattenedStyle = StyleSheet.flatten(style); - return flattenedStyle ? (flattenedStyle as StyleObject) : {}; + const flattenedStyle = StyleSheet.flatten(style); + return flattenedStyle ? (flattenedStyle as StyleObject) : {}; } export function styleToString(flattenedStyle: StyleObject): string { diff --git a/packages/native/src/lib/helpers/text.ts b/packages/native/src/lib/helpers/text.ts index 6bccbbff..c9a22547 100644 --- a/packages/native/src/lib/helpers/text.ts +++ b/packages/native/src/lib/helpers/text.ts @@ -1,47 +1,46 @@ -import { ReactTestInstance } from "react-test-renderer"; +import type { TestableTextMatcher, TextContent } from "./types"; +import type { ReactTestInstance } from "react-test-renderer"; -import { TestableTextMatcher, TextContent } from "./types"; +function collectText(element: TextContent): string[] { + if (typeof element === "string") { + return [element]; + } -function collectText (element: TextContent): string[] { - if (typeof element === "string") { - return [element]; - } + if (Array.isArray(element)) { + return element.flatMap(child => collectText(child)); + } - if (Array.isArray(element)) { - return element.flatMap(child => collectText(child)); + if (element && (typeof element === "object" && "props" in element)) { + const value = element.props?.value as TextContent; + if (typeof value === "string") { + return [value]; } - if (element && (typeof element === "object" && "props" in element)) { - const value = element.props?.value as TextContent; - if (typeof value === "string") { - return [value]; - } - - const children = (element.props?.children as ReactTestInstance[]) ?? element.children; - if (!children) { - return []; - } - - return Array.isArray(children) - ? children.flatMap(collectText) - : collectText(children); + const children = (element.props?.children as ReactTestInstance[]) ?? element.children; + if (!children) { + return []; } - return []; + return Array.isArray(children) + ? children.flatMap(collectText) + : collectText(children); + } + + return []; } export function getTextContent(element: ReactTestInstance): string { - if (!element) { + if (!element) { return ""; - } - if (typeof element === "string") { + } + if (typeof element === "string") { return element; - } - if (typeof element.props?.value === "string") { + } + if (typeof element.props?.value === "string") { return element.props.value; - } + } - return collectText(element).join(" "); + return collectText(element).join(" "); } export function textMatches( diff --git a/packages/native/src/lib/helpers/types.ts b/packages/native/src/lib/helpers/types.ts index 69fad07c..36aa342f 100644 --- a/packages/native/src/lib/helpers/types.ts +++ b/packages/native/src/lib/helpers/types.ts @@ -1,12 +1,12 @@ -import { ImageStyle, StyleProp, TextStyle, ViewStyle } from "react-native"; -import { ReactTestInstance } from "react-test-renderer"; +import type { ImageStyle, StyleProp, TextStyle, ViewStyle } from "react-native"; +import type { ReactTestInstance } from "react-test-renderer"; -type Style = TextStyle | ViewStyle | ImageStyle; +type Style = ImageStyle | TextStyle | ViewStyle; export type AssertiveStyle = StyleProp