diff --git a/.eslintrc.json b/.eslintrc.json index 9e2680e..de6d15d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -35,7 +35,7 @@ "import/no-extraneous-dependencies": [ "error", { - "devDependencies": ["./bin/*", "./vitest.config.mts", "./lint-staged.config.mjs", "**/*.{bench,test}.ts"] + "devDependencies": ["./bin/*", "./vitest.config.mts", "./lint-staged.config.mjs", "**/*.{bench,test,test-d}.ts"] } ], "import/no-relative-packages": "error", diff --git a/package.json b/package.json index cc0c2a0..dd284cc 100644 --- a/package.json +++ b/package.json @@ -75,8 +75,8 @@ "typecheck": "tsc --build --verbose --noEmit", "lint": "eslint ./*.{js,cjs,mjs,ts,cts,mts} ./src/ ./bench/ ./test/ --ext .ts,.mjs,.cjs", "bench": "vitest bench", - "test": "vitest", - "coverage": "vitest run --coverage", + "test": "vitest --typecheck", + "coverage": "vitest run --coverage --typecheck", "validate": "validate-package-exports --check --verify", "spellcheck": "cspell './{.github,src,bench,test}/**/*.{ts,json}' './*.{md,mjs,mts}' './package.json' --no-progress", "prepublishOnly": "pnpm typecheck && pnpm spellcheck && pnpm lint && pnpm coverage && pnpm build", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a59e6f..4ae5611 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -742,8 +742,8 @@ packages: '@octokit/openapi-types@23.0.1': resolution: {integrity: sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==} - '@octokit/plugin-paginate-rest@11.4.2': - resolution: {integrity: sha512-BXJ7XPCTDXFF+wxcg/zscfgw2O/iDPtNSkwwR1W1W5c4Mb3zav/M2XvxQ23nVmKj7jpweB4g8viMeCQdm7LMVA==} + '@octokit/plugin-paginate-rest@11.4.3': + resolution: {integrity: sha512-tBXaAbXkqVJlRoA/zQVe9mUdb8rScmivqtpv3ovsC5xhje/a+NOCivs7eUhWBwCApJVsR4G5HMeaLbq7PxqZGA==} engines: {node: '>= 18'} peerDependencies: '@octokit/core': '>=6' @@ -4604,7 +4604,7 @@ snapshots: '@octokit/openapi-types@23.0.1': {} - '@octokit/plugin-paginate-rest@11.4.2(@octokit/core@6.1.4)': + '@octokit/plugin-paginate-rest@11.4.3(@octokit/core@6.1.4)': dependencies: '@octokit/core': 6.1.4 '@octokit/types': 13.8.0 @@ -4733,7 +4733,7 @@ snapshots: '@semantic-release/github@11.0.1(semantic-release@24.2.3(typescript@5.7.3))': dependencies: '@octokit/core': 6.1.4 - '@octokit/plugin-paginate-rest': 11.4.2(@octokit/core@6.1.4) + '@octokit/plugin-paginate-rest': 11.4.3(@octokit/core@6.1.4) '@octokit/plugin-retry': 7.1.4(@octokit/core@6.1.4) '@octokit/plugin-throttling': 9.4.0(@octokit/core@6.1.4) '@semantic-release/error': 4.0.0 diff --git a/test/predicate-factory/createObjectShapePredicate.test-d.ts b/test/predicate-factory/createObjectShapePredicate.test-d.ts new file mode 100644 index 0000000..24776ef --- /dev/null +++ b/test/predicate-factory/createObjectShapePredicate.test-d.ts @@ -0,0 +1,93 @@ +import { describe, expectTypeOf, it } from 'vitest'; + +import { createNumberRangePredicate, isNumber } from '../../src/index.js'; +import { isOptionalString } from '../../src/predicate/isOptionalString.js'; +import { isString } from '../../src/predicate/isString.js'; +import { + createObjectShapePredicate, + type InferTypeFromShape, + type ObjectShapeRecord, +} from '../../src/predicate-factory/createObjectShapePredicate.js'; + +describe('createObjectShapePredicate', () => { + enum Role { + Admin = 'admin', + User = 'user', + } + + type User = { + name: string; + role: Role; + value: number; + age?: number; + contact: { + email: string; + phone?: string; + }; + tuple: [key: string, value: number]; + }; + + const user: unknown = { + name: 'Test', + role: Role.User, + value: 50, + age: 100, + contact: { + email: 'test@example.com', + phone: undefined, + }, + tuple: ['PI', Math.PI], + } satisfies User; + + type UserShape = ObjectShapeRecord; + + const userShape = { + name: /^Test$/, + role: Role.User, + value: createNumberRangePredicate(0, 100), + age: isNumber, + contact: { + email: isString, + phone: isOptionalString, + }, + tuple: ['PI', Math.PI], + } satisfies UserShape; + + type InferredUserType = InferTypeFromShape; + type InferredUserTypeConst = InferTypeFromShape; + + it('Returns a type predicate function', () => { + const fn = createObjectShapePredicate(userShape); + + expectTypeOf(fn).toBeFunction(); + expectTypeOf(fn).parameter(0).toMatchTypeOf(); + }); + + describe('Type parameters', () => { + it('Infers type parameter values', () => { + const fn = createObjectShapePredicate(userShape); + + if (fn(user)) { + expectTypeOf(user).toMatchTypeOf(); + } + }); + + it('Can provide the Type to constrain the Shape', () => { + const fn = createObjectShapePredicate(userShape); + + if (fn(user)) { + expectTypeOf(user).toMatchTypeOf(); + } + }); + + it('Can provide the Type and Shape parameters', () => { + const fn = createObjectShapePredicate<{ name: string }, { name: string | RegExp }>({ + name: 'test', + }); + + if (fn(user)) { + expectTypeOf(user).toMatchTypeOf<{ name: string }>(); + } + }); + }); +}); diff --git a/test/types/arrays.test-d.ts b/test/types/arrays.test-d.ts new file mode 100644 index 0000000..ccf2249 --- /dev/null +++ b/test/types/arrays.test-d.ts @@ -0,0 +1,63 @@ +import { describe, expectTypeOf, it } from 'vitest'; + +import type { First, Last, Head, Tail, GetLength, IfLength, IfEmpty, IfArray } from '../../src/types/arrays.js'; + +describe('First', () => { + it('returns the first element of an array', () => { + expectTypeOf>().toEqualTypeOf<1>(); + }); +}); + +describe('Last', () => { + it('returns the last element of an array', () => { + expectTypeOf>().toEqualTypeOf<3>(); + }); +}); + +describe('Head', () => { + it('returns all but the last element of an array', () => { + expectTypeOf>().toEqualTypeOf<[1, 2]>(); + }); +}); + +describe('Tail', () => { + it('returns all but the first element of an array', () => { + expectTypeOf>().toEqualTypeOf<[2, 3]>(); + }); +}); + +describe('GetLength', () => { + it('returns the length of an array', () => { + expectTypeOf>().toEqualTypeOf<3>(); + }); +}); + +describe('IfLength', () => { + it('returns the true type if the array length matches the specified length', () => { + expectTypeOf>().toEqualTypeOf<'yes'>(); + }); + + it('returns the false type if the array length does not match the specified length', () => { + expectTypeOf>().toEqualTypeOf<'no'>(); + }); +}); + +describe('IfEmpty', () => { + it('returns the true type if the array is empty', () => { + expectTypeOf>().toEqualTypeOf<'yes'>(); + }); + + it('returns the false type if the array is not empty', () => { + expectTypeOf>().toEqualTypeOf<'no'>(); + }); +}); + +describe('IfArray', () => { + it('returns the true type if the type is an array', () => { + expectTypeOf>().toEqualTypeOf<'yes'>(); + }); + + it('returns the false type if the type is not an array', () => { + expectTypeOf>().toEqualTypeOf<'no'>(); + }); +}); diff --git a/vitest.config.mts b/vitest.config.mts index bcd4e23..ef6628e 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -2,11 +2,15 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + environment: 'jsdom', + include: ['./test/**/*.test.ts'], benchmark: { include: ['./bench/**/*.bench.ts'], }, - environment: 'jsdom', - include: ['./test/**/*.test.ts'], + typecheck: { + enabled: true, + include: ['./test/**/*.test-d.ts'], + }, coverage: { all: false, provider: 'v8',