Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

prevOpen Source→test-mixernext

test-mixer4.4.2

Test helper to generate function opts object variations

Downloads per monthChangelogMIT Licenselibera manifesto
  • the top
  • Installation
  • Quick Take
  • Examples
  • PURPOSE
  • API — MIXER()
  • API — MIXERLAZY()
  • API — VERSION
  • API — TYPES
  • IN PRACTICE
  • Changelog

No 3rd party dependencies. All dependencies and devDependencies, checked recursively, are Codsen packages.

Permalink to InstallationInstallation

Permalink to Quick TakeQuick Take

Permalink to ExamplesExamples

  • Preserve callback values without invoking them
  • Clone nested values for every generated variation
  • Process combinations lazily and stop when enough rows have been handled
  • No defaults means no variations
  • Carry non-boolean values into every variation
  • Pin one option while varying the others

Purpose

test-mixer generates option objects for exhaustive tests. Each boolean-valued default that is not pinned by ref doubles the number of generated rows. Non-boolean values are carried into every row.

For example, detergent has 12 boolean toggles, so testing every combination requires 2^12 = 4096 option objects. string-collapse-white-space has six boolean toggles, which produce 2^6 = 64 objects.

Use mixer() when the result fits the eager safety limit. Use mixerLazy() to process larger sets without retaining every row at once.

API — mixer()

The main function mixer() is imported like this:

declare function mixer<
  Defaults extends PlainObject = Record<never, never>,
>(
  ref?: undefined,
  defaultsObj?: Defaults,
  opts?: Partial<MixerOptions>,
): BooleanValuesWidened<Defaults>[];

declare function mixer<
  Ref extends PlainObject,
  Defaults extends PlainObject = Record<never, never>,
>(
  ref: Ref,
  defaultsObj?: Defaults,
  opts?: Partial<MixerOptions>,
): MixerResult<Ref, Defaults>[];

declare function mixer<
  Ref extends PlainObject,
  Defaults extends PlainObject = Record<never, never>,
>(
  ref: Ref | undefined,
  defaultsObj?: Defaults,
  opts?: Partial<MixerOptions>,
): Array<MixerResult<Ref, Defaults> | BooleanValuesWidened<Defaults>>;
Input argumentTypeDescription
ref
Type: PlainObject | undefined
refPlainObject | undefinedPins or supplies values. Omit it or pass undefined to vary every boolean default. Each own key whose default value is boolean halves the row count. Other non-boolean keys are carried without changing the count.
defaultsObj
Type: PlainObject | undefined
defaultsObjPlainObject | undefinedThe complete defaults object. Every unpinned boolean key varies between false and true; other values are cloned into each row.
opts
Type: Partial<MixerOptions> | undefined
optsPartial<MixerOptions> | undefinedConfigures the eager safety limit through maxCombinations.

If n boolean defaults remain unpinned, mixer() returns 2^n rows in a stable order: the first boolean key toggles fastest. An empty or omitted defaultsObj returns []; a non-empty object with no free booleans returns one row.

Only plain objects, omitted arguments, and explicit undefined are accepted. Values such as null, false, 0, strings, arrays, dates, maps, sets, regular expressions, and functions are rejected. A boolean key in ref must also exist in defaultsObj.

Each returned row owns its nested object, array, map, set, and cyclic data graph, so mutating one test case does not affect its siblings or the inputs. Repeated references within one source graph remain aliases within that row. Functions retain their identity and are not called.

Eager safety limit

mixer() defaults to at most 16,384 rows, equivalent to 14 unpinned booleans. Set a smaller positive integer when a test needs a tighter budget. The limit cannot be raised above 16,384; pin more booleans or switch to mixerLazy() instead.

You can import defaults:

It's a plain object:

The main function calculates the options to be used by merging the options you passed with these defaults.

API — mixerLazy()

The main function mixerLazy() is imported like this:

declare function mixerLazy<
  Defaults extends PlainObject = Record<never, never>,
>(
  ref?: undefined,
  defaultsObj?: Defaults,
): Generator<BooleanValuesWidened<Defaults>, void, unknown>;

declare function mixerLazy<
  Ref extends PlainObject,
  Defaults extends PlainObject = Record<never, never>,
>(
  ref: Ref,
  defaultsObj?: Defaults,
): Generator<MixerResult<Ref, Defaults>, void, unknown>;

declare function mixerLazy<
  Ref extends PlainObject,
  Defaults extends PlainObject = Record<never, never>,
>(
  ref: Ref | undefined,
  defaultsObj?: Defaults,
): Generator<
  MixerResult<Ref, Defaults> | BooleanValuesWidened<Defaults>,
  void,
  unknown
>;

mixerLazy() accepts the same ref and defaultsObj arguments as mixer(). It snapshots them when called, then yields isolated rows in the same order without building the complete result array. Stop iteration with break to cancel the remaining work, or consume the iterator in caller-sized batches.

The iterator removes the eager memory limit, not the exponential amount of possible work. Process only as many rows as the test genuinely needs.

API — version

You can import version:

API — types

This package is written in TypeScript and exports the following types:

TypeDescription
PlainObject
Type: PlainObject
PlainObjectA string-keyed plain object accepted by both functions.
BooleanValuesWidened<T>
Type: BooleanValuesWidened<T>
BooleanValuesWidened<T>Keeps non-boolean property types from T and widens its boolean properties to boolean.
MixerResult<Ref, Defaults>
Type: MixerResult<Ref, Defaults>
MixerResult<Ref, Defaults>Describes one generated row: widened defaults merged with the exact property types supplied by Ref.
MixerOptions
Type: MixerOptions
MixerOptionsConfigures mixer() through maxCombinations.
PlainObjectOfBool
Type: PlainObjectOfBool
PlainObjectOfBoolDeprecated compatibility alias. Generated rows can contain non-boolean values; use MixerResult instead.
import type {
  BooleanValuesWidened,
  MixerOptions,
  MixerResult,
  PlainObject,
} from "test-mixer";

In practice

Package tests commonly wrap mixer() with their own defaults. The wrapper makes ref the only argument each test needs to supply:

import { opts } from "detergent";
import { mixer as testMixer } from "test-mixer";

export const mixer = (ref = {}) => testMixer(ref, opts);

Here is a complete detergent example. Pinning stripHtml leaves 11 free booleans, so each call generates 2^11 = 2048 cases:

import { strict as assert } from "node:assert";

import { det, opts } from "detergent";
import { mixer as testMixer } from "test-mixer";

const input = "text <a>link</a> text";
const mixDetergentOptions = (ref = {}) => testMixer(ref, opts);

const keepHtmlCases = mixDetergentOptions({ stripHtml: false });
assert.equal(keepHtmlCases.length, 2048);
for (const options of keepHtmlCases) {
  assert.match(det(input, options).res, /<a>/u, JSON.stringify(options));
}

const stripHtmlCases = mixDetergentOptions({ stripHtml: true });
assert.equal(stripHtmlCases.length, 2048);
for (const options of stripHtmlCases) {
  assert.doesNotMatch(
    det(input, options).res,
    /<\/?a>/u,
    JSON.stringify(options),
  );
}

Permalink to changelogChangelog

Open Changelog
↑ back to top
prev next

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab