├── .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 |
59 |
60 |
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 [](https://github.com/lukeed/clsx/actions?query=workflow%3ACI) [](https://codecov.io/gh/lukeed/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 |
--------------------------------------------------------------------------------