├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── bench ├── chrome.png ├── firefox.png ├── index.js ├── package.json ├── readme.md └── safari.png ├── bin └── index.js ├── clsx.d.mts ├── clsx.d.ts ├── license ├── package.json ├── readme.md ├── src ├── index.js └── lite.js └── test ├── classnames.js ├── index.js └── lite.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = tab 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.{json,yml,md}] 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: lukeed 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Node.js v${{ matrix.nodejs }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | nodejs: [8, 10, 12, 14, 16, 18] 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: ${{ matrix.nodejs }} 17 | 18 | - name: (cache) restore 19 | uses: actions/cache@master 20 | with: 21 | path: node_modules 22 | key: ${{ runner.os }}-${{ hashFiles('**/package.json') }} 23 | 24 | - name: Install 25 | run: npm install 26 | 27 | - name: (coverage) Install 28 | if: matrix.nodejs >= 18 29 | run: npm install -g c8 30 | 31 | - name: Build 32 | run: npm run build 33 | 34 | - name: Test 35 | run: npm test 36 | if: matrix.nodejs < 18 37 | 38 | - name: (coverage) Test 39 | run: c8 --include=src npm test 40 | if: matrix.nodejs >= 18 41 | 42 | - name: (coverage) Report 43 | if: matrix.nodejs >= 18 44 | run: | 45 | c8 report --reporter=text-lcov > coverage.lcov 46 | bash <(curl -s https://codecov.io/bash) 47 | env: 48 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *-lock.* 4 | *.lock 5 | *.log 6 | dist 7 | -------------------------------------------------------------------------------- /bench/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/clsx/925494cf31bcd97d3337aacd34e659e80cae7fe2/bench/chrome.png -------------------------------------------------------------------------------- /bench/firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/clsx/925494cf31bcd97d3337aacd34e659e80cae7fe2/bench/firefox.png -------------------------------------------------------------------------------- /bench/index.js: -------------------------------------------------------------------------------- 1 | const { Suite } = require('benchmark'); 2 | const classnames = require('classnames'); 3 | const classcat = require('classcat'); 4 | const clsx = require('../dist/clsx'); 5 | const old = require('clsx'); 6 | const lite = require('../dist/lite'); 7 | 8 | function bench(name, ...args) { 9 | console.log(`\n# ${name}`); 10 | new Suite() 11 | .add('classcat ≠ ', () => classcat.apply(classcat, [args])) 12 | .add('classnames ', () => classnames.apply(classnames, args)) 13 | .add('clsx (prev) ', () => old.apply(old, args)) 14 | .add('clsx ', () => clsx.apply(clsx, args)) 15 | .add('clsx (lite) ', () => lite.apply(lite, args)) 16 | .on('cycle', e => console.log(' ' + e.target)) 17 | .run(); 18 | } 19 | 20 | bench( 21 | 'Strings', 22 | 'foo', '', 'bar', 'baz', 'bax', 'bux' 23 | ); 24 | 25 | bench( 26 | 'Objects', 27 | { foo:true, bar:true, bax:true, bux:false }, 28 | { baz:true, bax:false, bux:true } 29 | ); 30 | 31 | bench( 32 | 'Arrays', 33 | ['foo', 'bar'], 34 | ['baz', 'bax', 'bux'] 35 | ); 36 | 37 | bench( 38 | 'Nested Arrays', 39 | ['foo', ['bar']], 40 | ['baz', ['bax', ['bux']]] 41 | ); 42 | 43 | bench( 44 | 'Nested Arrays w/ Objects', 45 | ['foo', { bar:true, bax:true, bux:false }], 46 | ['bax', { bax:false, bux:true }] 47 | ); 48 | 49 | bench( 50 | 'Mixed', 51 | 'foo', 'bar', 52 | { bax:true, bux:false }, 53 | ['baz', { bax:false, bux:true }] 54 | ); 55 | 56 | bench( 57 | 'Mixed (Bad Data)', 58 | 'foo', 'bar', 59 | undefined, null, NaN, 60 | () => {}, 61 | { bax:true, bux:false, 123:true }, 62 | ['baz', { bax:false, bux:true, abc:null }, {}] 63 | ); 64 | -------------------------------------------------------------------------------- /bench/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "benchmark": "^2.1.4", 5 | "classcat": "^3.2.5", 6 | "classnames": "^2.2.6", 7 | "clsx": "1.1.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /bench/readme.md: -------------------------------------------------------------------------------- 1 | ## Benchmarks 2 | 3 | > Below you will find benchmark results for [Node.js](#Node) and [multiple browser](#Browsers) engines. 4 | 5 | ## Node 6 | 7 | These are the results while running this directory's benchmark suite in Node v20.10.0. 8 | 9 | > **Note:** The `≠` denotes that the candidate has a different API and is not compatible with `classnames` usage. 10 | 11 | ``` 12 | # Strings 13 | classcat ≠ x 9,613,381 ops/sec ±0.16% (94 runs sampled) 14 | classnames x 6,540,072 ops/sec ±0.11% (101 runs sampled) 15 | clsx x 12,924,662 ops/sec ±0.15% (102 runs sampled) 16 | clsx/lite x 13,122,004 ops/sec ±0.40% (99 runs sampled) 17 | 18 | # Objects 19 | classcat ≠ x 8,936,903 ops/sec ±0.12% (100 runs sampled) 20 | classnames x 6,143,319 ops/sec ±0.14% (100 runs sampled) 21 | clsx x 9,444,110 ops/sec ±0.11% (102 runs sampled) 22 | 23 | # Arrays 24 | classcat ≠ x 8,247,121 ops/sec ±0.12% (98 runs sampled) 25 | classnames x 3,451,489 ops/sec ±0.18% (99 runs sampled) 26 | clsx x 9,401,030 ops/sec ±0.18% (101 runs sampled) 27 | 28 | # Nested Arrays 29 | classcat ≠ x 6,759,204 ops/sec ±0.31% (97 runs sampled) 30 | classnames x 2,015,566 ops/sec ±0.18% (100 runs sampled) 31 | clsx x 7,315,032 ops/sec ±0.43% (99 runs sampled) 32 | 33 | # Nested Arrays w/ Objects 34 | classcat ≠ x 6,726,315 ops/sec ±0.16% (98 runs sampled) 35 | classnames x 3,059,235 ops/sec ±0.45% (99 runs sampled) 36 | clsx x 7,352,761 ops/sec ±0.44% (98 runs sampled) 37 | 38 | # Mixed 39 | classcat ≠ x 6,956,920 ops/sec ±0.21% (97 runs sampled) 40 | classnames x 4,171,381 ops/sec ±0.15% (98 runs sampled) 41 | clsx x 8,468,116 ops/sec ±0.11% (96 runs sampled) 42 | 43 | # Mixed (Bad Data) 44 | classcat ≠ x 2,128,702 ops/sec ±0.13% (101 runs sampled) 45 | classnames x 1,925,670 ops/sec ±0.19% (100 runs sampled) 46 | clsx x 2,996,516 ops/sec ±0.07% (100 runs sampled) 47 | ``` 48 | 49 | ## Browsers 50 | 51 | Results are taken from the [benchmark suite](https://github.com/JedWatson/classnames/tree/master/benchmarks) within the `classnames` repository. 52 | 53 | These were run in a clean environment and on 4GHz i7 with 32GB of RAM. As you can see, results _will_ vary from browser to browser – multiple results were included for quick viewing. 54 | 55 | --- 56 | 57 |

58 | chrome 59 | firefox 60 | safari 61 |

62 | -------------------------------------------------------------------------------- /bench/safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukeed/clsx/925494cf31bcd97d3337aacd34e659e80cae7fe2/bench/safari.png -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const fs = require('fs'); 3 | const zlib = require('zlib'); 4 | const { minify } = require('terser'); 5 | const pkg = require('../package.json'); 6 | 7 | /** 8 | * @param {string} file 9 | * @param {string} source 10 | */ 11 | function write(file, source) { 12 | let isModule = !source.startsWith('!function'); 13 | let result = minify(source, { 14 | module: isModule, 15 | compress: true, 16 | }); 17 | 18 | if (result.code) { 19 | fs.writeFileSync(file, result.code); 20 | let size = zlib.gzipSync(result.code).byteLength; 21 | console.log('~> "%s" (%d b)', file, size); 22 | } else { 23 | console.error('!! "%s" ::', file, result.error); 24 | } 25 | } 26 | 27 | /** 28 | * @typedef Export 29 | * @property {Condition} import 30 | * @property {Condition} default 31 | */ 32 | 33 | /** 34 | * @typedef Condition 35 | * @property {string} types 36 | * @property {string} default 37 | */ 38 | 39 | /** 40 | * @param {string} file 41 | * @param {"." | "./lite"} entry 42 | */ 43 | function bundle(file, entry) { 44 | fs.existsSync('dist') || fs.mkdirSync('dist'); 45 | 46 | /** 47 | * @type {Export} 48 | */ 49 | let output = pkg.exports[entry]; 50 | let input = fs.readFileSync(file, 'utf8'); 51 | 52 | // copy for ESM file 53 | write(output.import.default, input); 54 | 55 | // transform ESM -> CJS exports 56 | write(output.default.default, input.replace('export function', 'function').replace( 57 | 'export default clsx;', 58 | 'module.exports = clsx;\n' 59 | + 'module.exports.clsx = clsx;' 60 | )); 61 | 62 | if (entry === '.') { 63 | // transform ESM -> UMD exports 64 | input = input.replace('export function', 'function').replace('export default clsx;', 'return clsx.clsx=clsx, clsx;'); 65 | write(pkg.unpkg, '!function(global,factory){"object"==typeof exports&&"undefined"!=typeof module?module.exports=factory():"function"==typeof define&&define.amd?define(factory):global.clsx=factory()}(this,function(){' + input + '});'); 66 | } 67 | } 68 | 69 | bundle('src/index.js', '.'); 70 | console.log('---'); 71 | bundle('src/lite.js', './lite'); 72 | -------------------------------------------------------------------------------- /clsx.d.mts: -------------------------------------------------------------------------------- 1 | export type ClassValue = ClassArray | ClassDictionary | string | number | bigint | null | boolean | undefined; 2 | export type ClassDictionary = Record; 3 | export type ClassArray = ClassValue[]; 4 | 5 | export function clsx(...inputs: ClassValue[]): string; 6 | export default clsx; 7 | -------------------------------------------------------------------------------- /clsx.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace clsx { 2 | type ClassValue = ClassArray | ClassDictionary | string | number | bigint | null | boolean | undefined; 3 | type ClassDictionary = Record; 4 | type ClassArray = ClassValue[]; 5 | function clsx(...inputs: ClassValue[]): string; 6 | } 7 | 8 | declare function clsx(...inputs: clsx.ClassValue[]): string; 9 | 10 | export = clsx; 11 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Luke Edwards (lukeed.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clsx", 3 | "version": "2.1.1", 4 | "repository": "lukeed/clsx", 5 | "description": "A tiny (239B) utility for constructing className strings conditionally.", 6 | "module": "dist/clsx.mjs", 7 | "unpkg": "dist/clsx.min.js", 8 | "main": "dist/clsx.js", 9 | "types": "clsx.d.ts", 10 | "license": "MIT", 11 | "exports": { 12 | ".": { 13 | "import": { 14 | "types": "./clsx.d.mts", 15 | "default": "./dist/clsx.mjs" 16 | }, 17 | "default": { 18 | "types": "./clsx.d.ts", 19 | "default": "./dist/clsx.js" 20 | } 21 | }, 22 | "./lite": { 23 | "import": { 24 | "types": "./clsx.d.mts", 25 | "default": "./dist/lite.mjs" 26 | }, 27 | "default": { 28 | "types": "./clsx.d.ts", 29 | "default": "./dist/lite.js" 30 | } 31 | } 32 | }, 33 | "author": { 34 | "name": "Luke Edwards", 35 | "email": "luke.edwards05@gmail.com", 36 | "url": "https://lukeed.com" 37 | }, 38 | "engines": { 39 | "node": ">=6" 40 | }, 41 | "scripts": { 42 | "build": "node bin", 43 | "test": "uvu -r esm test" 44 | }, 45 | "files": [ 46 | "*.d.mts", 47 | "*.d.ts", 48 | "dist" 49 | ], 50 | "keywords": [ 51 | "classes", 52 | "classname", 53 | "classnames" 54 | ], 55 | "devDependencies": { 56 | "esm": "3.2.25", 57 | "terser": "4.8.0", 58 | "uvu": "0.5.4" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # clsx [![CI](https://github.com/lukeed/clsx/workflows/CI/badge.svg)](https://github.com/lukeed/clsx/actions?query=workflow%3ACI) [![codecov](https://badgen.net/codecov/c/github/lukeed/clsx)](https://codecov.io/gh/lukeed/clsx) [![licenses](https://licenses.dev/b/npm/clsx)](https://licenses.dev/npm/clsx) 2 | 3 | > A tiny (239B) utility for constructing `className` strings conditionally.
Also serves as a [faster](bench) & smaller drop-in replacement for the `classnames` module. 4 | 5 | This module is available in three formats: 6 | 7 | * **ES Module**: `dist/clsx.mjs` 8 | * **CommonJS**: `dist/clsx.js` 9 | * **UMD**: `dist/clsx.min.js` 10 | 11 | 12 | ## Install 13 | 14 | ``` 15 | $ npm install --save clsx 16 | ``` 17 | 18 | 19 | ## Usage 20 | 21 | ```js 22 | import clsx from 'clsx'; 23 | // or 24 | import { clsx } from 'clsx'; 25 | 26 | // Strings (variadic) 27 | clsx('foo', true && 'bar', 'baz'); 28 | //=> 'foo bar baz' 29 | 30 | // Objects 31 | clsx({ foo:true, bar:false, baz:isTrue() }); 32 | //=> 'foo baz' 33 | 34 | // Objects (variadic) 35 | clsx({ foo:true }, { bar:false }, null, { '--foobar':'hello' }); 36 | //=> 'foo --foobar' 37 | 38 | // Arrays 39 | clsx(['foo', 0, false, 'bar']); 40 | //=> 'foo bar' 41 | 42 | // Arrays (variadic) 43 | clsx(['foo'], ['', 0, false, 'bar'], [['baz', [['hello'], 'there']]]); 44 | //=> 'foo bar baz hello there' 45 | 46 | // Kitchen sink (with nesting) 47 | clsx('foo', [1 && 'bar', { baz:false, bat:null }, ['hello', ['world']]], 'cya'); 48 | //=> 'foo bar hello world cya' 49 | ``` 50 | 51 | 52 | ## API 53 | 54 | ### clsx(...input) 55 | Returns: `String` 56 | 57 | #### input 58 | Type: `Mixed` 59 | 60 | The `clsx` function can take ***any*** number of arguments, each of which can be an Object, Array, Boolean, or String. 61 | 62 | > **Important:** _Any_ falsey values are discarded!
Standalone Boolean values are discarded as well. 63 | 64 | ```js 65 | clsx(true, false, '', null, undefined, 0, NaN); 66 | //=> '' 67 | ``` 68 | 69 | ## Modes 70 | 71 | There are multiple "versions" of `clsx` available, which allows you to bring only the functionality you need! 72 | 73 | #### `clsx` 74 | > **Size (gzip):** 239 bytes
75 | > **Availability:** CommonJS, ES Module, UMD 76 | 77 | The default `clsx` module; see [API](#API) for info. 78 | 79 | ```js 80 | import { clsx } from 'clsx'; 81 | // or 82 | import clsx from 'clsx'; 83 | ``` 84 | 85 | #### `clsx/lite` 86 | > **Size (gzip):** 140 bytes
87 | > **Availability:** CommonJS, ES Module
88 | > **CAUTION:** Accepts **ONLY** string arguments! 89 | 90 | Ideal for applications that ***only*** use the string-builder pattern. 91 | 92 | Any non-string arguments are ignored! 93 | 94 | ```js 95 | import { clsx } from 'clsx/lite'; 96 | // or 97 | import clsx from 'clsx/lite'; 98 | 99 | // string 100 | clsx('hello', true && 'foo', false && 'bar'); 101 | // => "hello foo" 102 | 103 | // NOTE: Any non-string input(s) ignored 104 | clsx({ foo: true }); 105 | //=> "" 106 | ``` 107 | 108 | ## Benchmarks 109 | 110 | For snapshots of cross-browser results, check out the [`bench`](bench) directory~! 111 | 112 | ## Support 113 | 114 | All versions of Node.js are supported. 115 | 116 | All browsers that support [`Array.isArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray#Browser_compatibility) are supported (IE9+). 117 | 118 | >**Note:** For IE8 support and older, please install `clsx@1.0.x` and beware of [#17](https://github.com/lukeed/clsx/issues/17). 119 | 120 | ## Tailwind Support 121 | 122 | Here some additional (optional) steps to enable classes autocompletion using `clsx` with Tailwind CSS. 123 | 124 |
125 | 126 | Visual Studio Code 127 | 128 | 129 | 1. [Install the "Tailwind CSS IntelliSense" Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) 130 | 131 | 2. Add the following to your [`settings.json`](https://code.visualstudio.com/docs/getstarted/settings): 132 | 133 | ```json 134 | { 135 | "tailwindCSS.experimental.classRegex": [ 136 | ["clsx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] 137 | ] 138 | } 139 | ``` 140 |
141 | 142 | You may find the [`clsx/lite`](#clsxlite) module useful within Tailwind contexts. This is especially true if/when your application **only** composes classes in this pattern: 143 | 144 | ```js 145 | clsx('text-base', props.active && 'text-primary', props.className); 146 | ``` 147 | 148 | ## Related 149 | 150 | - [obj-str](https://github.com/lukeed/obj-str) - A smaller (96B) and similiar utility that only works with Objects. 151 | 152 | ## License 153 | 154 | MIT © [Luke Edwards](https://lukeed.com) 155 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | function toVal(mix) { 2 | var k, y, str=''; 3 | 4 | if (typeof mix === 'string' || typeof mix === 'number') { 5 | str += mix; 6 | } else if (typeof mix === 'object') { 7 | if (Array.isArray(mix)) { 8 | var len=mix.length; 9 | for (k=0; k < len; k++) { 10 | if (mix[k]) { 11 | if (y = toVal(mix[k])) { 12 | str && (str += ' '); 13 | str += y; 14 | } 15 | } 16 | } 17 | } else { 18 | for (y in mix) { 19 | if (mix[y]) { 20 | str && (str += ' '); 21 | str += y; 22 | } 23 | } 24 | } 25 | } 26 | 27 | return str; 28 | } 29 | 30 | export function clsx() { 31 | var i=0, tmp, x, str='', len=arguments.length; 32 | for (; i < len; i++) { 33 | if (tmp = arguments[i]) { 34 | if (x = toVal(tmp)) { 35 | str && (str += ' '); 36 | str += x 37 | } 38 | } 39 | } 40 | return str; 41 | } 42 | 43 | export default clsx; 44 | -------------------------------------------------------------------------------- /src/lite.js: -------------------------------------------------------------------------------- 1 | export function clsx() { 2 | var i=0, tmp, str='', len=arguments.length; 3 | for (; i < len; i++) { 4 | if (tmp = arguments[i]) { 5 | if (typeof tmp === 'string') { 6 | str += (str && ' ') + tmp; 7 | } 8 | } 9 | } 10 | return str; 11 | } 12 | 13 | export default clsx; 14 | -------------------------------------------------------------------------------- /test/classnames.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ported from `classnames` for compatibility checks. 3 | */ 4 | 5 | import { test } from 'uvu'; 6 | import * as assert from 'uvu/assert'; 7 | import clsx from '../src'; 8 | 9 | test('(compat) keeps object keys with truthy values', () => { 10 | const out = clsx({ a:true, b:false, c:0, d:null, e:undefined, f:1 }); 11 | assert.is(out, 'a f'); 12 | }); 13 | 14 | test('(compat) joins arrays of class names and ignore falsy values', () => { 15 | const out = clsx('a', 0, null, undefined, true, 1, 'b'); 16 | assert.is(out, 'a 1 b'); 17 | }); 18 | 19 | test('(compat) supports heterogenous arguments', () => { 20 | assert.is(clsx({ a:true }, 'b', 0), 'a b'); 21 | }); 22 | 23 | test('(compat) should be trimmed', () => { 24 | assert.is(clsx('', 'b', {}, ''), 'b'); 25 | }); 26 | 27 | test('(compat) returns an empty string for an empty configuration', () => { 28 | assert.is(clsx({}), ''); 29 | }); 30 | 31 | test('(compat) supports an array of class names', () => { 32 | assert.is(clsx(['a', 'b']), 'a b'); 33 | }); 34 | 35 | test('(compat) joins array arguments with string arguments', () => { 36 | assert.is(clsx(['a', 'b'], 'c'), 'a b c'); 37 | assert.is(clsx('c', ['a', 'b']), 'c a b'); 38 | }); 39 | 40 | test('(compat) handles multiple array arguments', () => { 41 | assert.is(clsx(['a', 'b'], ['c', 'd']), 'a b c d'); 42 | }); 43 | 44 | test('(compat) handles arrays that include falsy and true values', () => { 45 | assert.is(clsx(['a', 0, null, undefined, false, true, 'b']), 'a b'); 46 | }); 47 | 48 | test('(compat) handles arrays that include arrays', () => { 49 | assert.is(clsx(['a', ['b', 'c']]), 'a b c'); 50 | }); 51 | 52 | test('(compat) handles arrays that include objects', () => { 53 | assert.is(clsx(['a', { b:true, c:false }]), 'a b'); 54 | }); 55 | 56 | test('(compat) handles deep array recursion', () => { 57 | assert.is(clsx(['a', ['b', ['c', { d:true }]]]), 'a b c d'); 58 | }); 59 | 60 | test('(compat) handles arrays that are empty', () => { 61 | assert.is(clsx('a', []), 'a'); 62 | }); 63 | 64 | test('(compat) handles nested arrays that have empty nested arrays', () => { 65 | assert.is(clsx('a', [[]]), 'a'); 66 | }); 67 | 68 | test('(compat) handles all types of truthy and falsy property values as expected', () => { 69 | const out = clsx({ 70 | // falsy: 71 | null: null, 72 | emptyString: '', 73 | noNumber: NaN, 74 | zero: 0, 75 | negativeZero: -0, 76 | false: false, 77 | undefined: undefined, 78 | 79 | // truthy (literally anything else): 80 | nonEmptyString: 'foobar', 81 | whitespace: ' ', 82 | function: Object.prototype.toString, 83 | emptyObject: {}, 84 | nonEmptyObject: {a: 1, b: 2}, 85 | emptyList: [], 86 | nonEmptyList: [1, 2, 3], 87 | greaterZero: 1 88 | }); 89 | 90 | assert.is(out, 'nonEmptyString whitespace function emptyObject nonEmptyObject emptyList nonEmptyList greaterZero'); 91 | }); 92 | 93 | test.run(); 94 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { test } from 'uvu'; 3 | import * as assert from 'uvu/assert'; 4 | import * as mod from '../src'; 5 | 6 | const fn = mod.default; 7 | 8 | test('exports', () => { 9 | assert.type(mod.default, 'function', 'exports default function'); 10 | assert.type(mod.clsx, 'function', 'exports named function'); 11 | assert.is(mod.default, mod.clsx, 'exports are equal'); 12 | 13 | assert.type(mod.default(), 'string', '~> returns string output'); 14 | assert.type(mod.clsx(), 'string', '~> returns string output'); 15 | }); 16 | 17 | test('strings', () => { 18 | assert.is(fn(''), ''); 19 | assert.is(fn('foo'), 'foo'); 20 | assert.is(fn(true && 'foo'), 'foo'); 21 | assert.is(fn(false && 'foo'), ''); 22 | }); 23 | 24 | test('strings (variadic)', () => { 25 | assert.is(fn(''), ''); 26 | assert.is(fn('foo', 'bar'), 'foo bar'); 27 | assert.is(fn(true && 'foo', false && 'bar', 'baz'), 'foo baz'); 28 | assert.is(fn(false && 'foo', 'bar', 'baz', ''), 'bar baz'); 29 | }); 30 | 31 | test('numbers', () => { 32 | assert.is(fn(1), '1'); 33 | assert.is(fn(12), '12'); 34 | assert.is(fn(0.1), '0.1'); 35 | assert.is(fn(0), ''); 36 | 37 | assert.is(fn(Infinity), 'Infinity'); 38 | assert.is(fn(NaN), ''); 39 | }); 40 | 41 | test('numbers (variadic)', () => { 42 | assert.is(fn(0, 1), '1'); 43 | assert.is(fn(1, 2), '1 2'); 44 | }); 45 | 46 | test('objects', () => { 47 | assert.is(fn({}), ''); 48 | assert.is(fn({ foo:true }), 'foo'); 49 | assert.is(fn({ foo:true, bar:false }), 'foo'); 50 | assert.is(fn({ foo:'hiya', bar:1 }), 'foo bar'); 51 | assert.is(fn({ foo:1, bar:0, baz:1 }), 'foo baz'); 52 | assert.is(fn({ '-foo':1, '--bar':1 }), '-foo --bar'); 53 | }); 54 | 55 | test('objects (variadic)', () => { 56 | assert.is(fn({}, {}), ''); 57 | assert.is(fn({ foo:1 }, { bar:2 }), 'foo bar'); 58 | assert.is(fn({ foo:1 }, null, { baz:1, bat:0 }), 'foo baz'); 59 | assert.is(fn({ foo:1 }, {}, {}, { bar:'a' }, { baz:null, bat:Infinity }), 'foo bar bat'); 60 | }); 61 | 62 | test('arrays', () => { 63 | assert.is(fn([]), ''); 64 | assert.is(fn(['foo']), 'foo'); 65 | assert.is(fn(['foo', 'bar']), 'foo bar'); 66 | assert.is(fn(['foo', 0 && 'bar', 1 && 'baz']), 'foo baz'); 67 | }); 68 | 69 | test('arrays (nested)', () => { 70 | assert.is(fn([[[]]]), ''); 71 | assert.is(fn([[['foo']]]), 'foo'); 72 | assert.is(fn([true, [['foo']]]), 'foo');; 73 | assert.is(fn(['foo', ['bar', ['', [['baz']]]]]), 'foo bar baz'); 74 | }); 75 | 76 | test('arrays (variadic)', () => { 77 | assert.is(fn([], []), ''); 78 | assert.is(fn(['foo'], ['bar']), 'foo bar'); 79 | assert.is(fn(['foo'], null, ['baz', ''], true, '', []), 'foo baz'); 80 | }); 81 | 82 | test('arrays (no `push` escape)', () => { 83 | assert.is(fn({ push:1 }), 'push'); 84 | assert.is(fn({ pop:true }), 'pop'); 85 | assert.is(fn({ push:true }), 'push'); 86 | assert.is(fn('hello', { world:1, push:true }), 'hello world push'); 87 | }); 88 | 89 | test('functions', () => { 90 | const foo = () => {}; 91 | assert.is(fn(foo, 'hello'), 'hello'); 92 | assert.is(fn(foo, 'hello', fn), 'hello'); 93 | assert.is(fn(foo, 'hello', [[fn], 'world']), 'hello world'); 94 | }); 95 | 96 | test.run(); 97 | -------------------------------------------------------------------------------- /test/lite.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { test } from 'uvu'; 3 | import * as assert from 'uvu/assert'; 4 | import * as mod from '../src/lite'; 5 | 6 | const fn = mod.default; 7 | 8 | test('exports', () => { 9 | assert.type(mod.default, 'function', 'exports default function'); 10 | assert.type(mod.clsx, 'function', 'exports named function'); 11 | assert.is(mod.default, mod.clsx, 'exports are equal'); 12 | 13 | assert.type(mod.default(), 'string', '~> returns string output'); 14 | assert.type(mod.clsx(), 'string', '~> returns string output'); 15 | }); 16 | 17 | test('strings', () => { 18 | assert.is(fn(''), ''); 19 | assert.is(fn('foo'), 'foo'); 20 | assert.is(fn(true && 'foo'), 'foo'); 21 | assert.is(fn(false && 'foo'), ''); 22 | }); 23 | 24 | test('strings (variadic)', () => { 25 | assert.is(fn(''), ''); 26 | assert.is(fn('foo', 'bar'), 'foo bar'); 27 | assert.is(fn(true && 'foo', false && 'bar', 'baz'), 'foo baz'); 28 | assert.is(fn(false && 'foo', 'bar', 'baz', ''), 'bar baz'); 29 | }); 30 | 31 | test('emptys', () => { 32 | assert.is(fn(''), ''); 33 | assert.is(fn(undefined), ''); 34 | assert.is(fn(null), ''); 35 | assert.is(fn(0), ''); 36 | }); 37 | 38 | // lite ignores all non-strings 39 | test('non-strings', () => { 40 | // number 41 | assert.is(fn(1), ''); 42 | assert.is(fn(1, 2), ''); 43 | assert.is(fn(Infinity), ''); 44 | assert.is(fn(NaN), ''); 45 | assert.is(fn(0), ''); 46 | 47 | // objects 48 | assert.is(fn({}), ''); 49 | assert.is(fn(null), ''); 50 | assert.is(fn({ a:1 }), ''); 51 | assert.is(fn({ a:1 }, { b:2 }), ''); 52 | 53 | // arrays 54 | assert.is(fn([]), ''); 55 | assert.is(fn(['foo']), ''); 56 | assert.is(fn(['foo', 'bar']), ''); 57 | 58 | // functions 59 | assert.is(fn(fn), ''); 60 | assert.is(fn(fn, fn), ''); 61 | }); 62 | 63 | test.run(); 64 | --------------------------------------------------------------------------------