diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts new file mode 100644 index 0000000..9c2c072 --- /dev/null +++ b/__tests__/utils.test.ts @@ -0,0 +1,52 @@ +import { test, describe } from 'node:test'; +import assert from 'node:assert/strict'; +import { cn } from '../lib/utils'; + +describe('cn utility function', () => { + test('combines basic classes correctly', () => { + assert.equal(cn('a', 'b', 'c'), 'a b c'); + }); + + test('handles conditional classes', () => { + const condition = true; + const falseCondition = false; + assert.equal(cn('a', condition && 'b', falseCondition && 'c'), 'a b'); + }); + + test('handles array inputs', () => { + assert.equal(cn(['a', 'b'], ['c', 'd']), 'a b c d'); + }); + + test('handles object inputs', () => { + assert.equal(cn({ a: true, b: false, c: true }), 'a c'); + }); + + test('handles mixed inputs (string, array, object)', () => { + assert.equal( + cn('a', ['b', 'c'], { d: true, e: false }, 'f'), + 'a b c d f' + ); + }); + + test('merges tailwind classes correctly (twMerge)', () => { + // p-4 and p-8 are conflicting padding classes, twMerge should keep the last one + assert.equal(cn('p-4', 'p-8'), 'p-8'); + + // text-red-500 and text-blue-500 are conflicting text color classes + assert.equal(cn('text-red-500', 'text-blue-500'), 'text-blue-500'); + + // bg-red-500 and hover:bg-blue-500 do not conflict directly but are related + assert.equal( + cn('bg-red-500 hover:bg-blue-500', 'bg-blue-500'), + 'hover:bg-blue-500 bg-blue-500' + ); + }); + + test('handles falsy values', () => { + assert.equal(cn('a', null, undefined, false, 0, '', 'b'), 'a b'); + }); + + test('handles empty input', () => { + assert.equal(cn(), ''); + }); +}); diff --git a/package-lock.json b/package-lock.json index 433dffd..81602c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,6 +76,7 @@ "serwist": "^9.5.7", "shadcn": "^3.8.4", "tailwindcss": "^4", + "tsx": "^4.21.0", "tw-animate-css": "^1.4.0", "typescript": "5.7.3" } @@ -10012,6 +10013,21 @@ "node": ">=14.14" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -15988,6 +16004,26 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tunnel-rat": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz", diff --git a/package.json b/package.json index 2ae4b5a..53930a4 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "test": "tsx --test __tests__/**/*.test.ts" }, "dependencies": { "@ai-sdk/google": "^3.0.53", @@ -77,6 +78,7 @@ "serwist": "^9.5.7", "shadcn": "^3.8.4", "tailwindcss": "^4", + "tsx": "^4.21.0", "tw-animate-css": "^1.4.0", "typescript": "5.7.3" } diff --git a/tsconfig.json b/tsconfig.json index f17f736..91136a0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,7 @@ "isolatedModules": true, "jsx": "react-jsx", "incremental": true, + "allowImportingTsExtensions": true, "plugins": [ { "name": "next"