Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .config/tsconfig.eslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"../.wallaby.js",
"../eslint.config.js",
"../.commitlintrc.js",
"./typedoc.js"
"./typedoc.js",
"../assets/font-loader.js"
]
}
25 changes: 22 additions & 3 deletions .config/typedoc.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { readFileSync } from 'node:fs';
import { OptionDefaults } from 'typedoc';

/** @type {Partial<import('typedoc').TypeDocOptions>} */
/**
* @import {TypeDocOptions} from 'typedoc'
*/

const customFooterHtml = readFileSync(
new URL('../assets/footer.html', import.meta.url),
'utf8',
);

/** @type {Partial<TypeDocOptions>} */
export default {
blockTags: [...OptionDefaults.blockTags, '@knipignore', '@assertion'],
cleanOutputDir: true,
cname: 'bupkis.zip',
customCss: './bupkis-theme.css',
customCss: '../assets/bupkis-theme.css',
customFooterHtml,
customJs: '../assets/font-loader.js',
darkHighlightTheme: 'red',
entryPoints: ['../src/index.ts'],
excludeInternal: true,
Expand All @@ -27,6 +39,7 @@ export default {
ZodType: 'https://zod.dev/packages/core#schemas',
},
},
favicon: '../assets/favicon.svg',
hostedBaseUrl: 'https://boneskull.github.io/bupkis',
kindSortOrder: [
'Reference',
Expand All @@ -52,7 +65,13 @@ export default {
'typedoc-plugin-mdn-links',
'typedoc-plugin-zod',
],
projectDocuments: ['../site/*.md', '../ROADMAP.md', '../CHANGELOG.md'],
projectDocuments: [
'../site/*.md',
'../ROADMAP.md',
'../CHANGELOG.md',
'../assets/Twentieth-Century-Bold.woff2',
],
searchInDocuments: true,
sort: ['kind'],
tsconfig: './tsconfig.typedoc.json',
};
164 changes: 100 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
<p align="center">
<img src="https://bupkis.zip/assets/bupkis-logo-256.png" width="256px" align="center" alt="BUPKIS logo"/>
<h1 align="center"><em><span class="federo-caps">BUPKIS<span></em></h1>
<img src="./assets/bupkis-logo-512.png" width="512px" align="center" alt="BUPKIS logo"/>
<h1 align="center"><span class="twentieth-century-caps">BUPKIS<span></h1>
<p align="center">
A well-typed and easily exensible <em>BDD-style</em> assertion library.
A well-typed and easily exensible <em>BDD-style</em> assertion library
<br/>
by <a href="https://github.com/boneskull">@boneskull</a>
<small>by <a href="https://github.com/boneskull">@boneskull</a></small>
</p>
</p>

## Quick Links

- [BUPKIS' Homepage][docs] (<https://bupkis.zip>)
- [Assertion Reference][assertion-reference]
- [Guide: Basic Usage][basic-usage]
- [Guide: How to Create a Custom Assertion][create-a-custom-assertion]

## Motivation

> "_Another_ assertion library? Are you daft? My test framework has its own assertions!"
>
> ‒sickos, probably

Look, I'm ~~old~~ ~~wizened~~ ~~experienced~~ knowledegable and I've written a lot of tests. I've used a lot of assertion libraries. There are ones I prefer and ones I don't.

But none of them do quite what _this_ does. The main goals of this library are:
Expand All @@ -20,15 +31,15 @@ But none of them do quite what _this_ does. The main goals of this library are:

A chainable API may provide type safety. But it seems to _guarantee_ implementing a custom assertion will be complicated. The API surface is necessarily a combinatoric explosion of methods.

> [!WARNING]
> ⚠️ **Caution!**
>
> Because chainable APIs are familiar, you may hate _BUPKIS_ once you see some examples. You don't have to use it, but please: _don't confuse familiarity with usability_.
> Because chainable APIs are familiar, you may hate _BUPKIS_ once you see some examples. Nobody's making you use it. But please, keep an open mind & give me this grace: _don't confuse familiarity with usability_.

To achieve these goals, I've adopted the following design principles:
To achieve these goals, _BUPKIS_ makes the following design choices.

### Natural-Language Assertions
### Assertions are Natural Language

In `bupkis` (stylized as "_BUPKIS_"), you **don't** write this:
When you're using _BUPKIS_, you **don't** write this:

```js
expect(actual).toEqual(expected);
Expand Down Expand Up @@ -60,98 +71,115 @@ Then _BUPKIS_ wants you to write:

```js
expect(actual, 'to be a string');
// it is tolerant of poor/ironic grammar
// it is tolerant of poor/ironic grammar, sometimes
expect(actual, 'to be an string');
```

Can't remember the string? Did you forget a word or make a typo? Maybe you also forgot _BUPKIS_ is type-safe? You'll get a nice squiggly for your trouble.
Can't remember the string? Did you forget a word or make a typo? Maybe you also forgot **_BUPKIS_ is type-safe?** You'll get a nice squiggly for your trouble. This isn't black magic. It ain't a _cauldron_. We're not just _throwing rat tails and `string`s in there._

> "Preposterous! Codswallop!"
>
> ‒the reader and/or more sickos

Right—how could this be anything by loosey-goosey _senselessness_? I beg to differ; _BUPKIS_ is nothing if not _intentional_.

The first parameter to a _BUPKIS_ assertion is always the _subject_ ([def.](https://bupkis.zip/documents/Reference.Glossary_of_Terms#subject)).

The "string" part of an expectation is known as a _phrase_. Every expectation will contain, at minimum, one phrase. As you can see from the above example, phrases often have aliases.
The "string" part of a _BUPKIS_ assertion is known as a _phrase_. Every expectation will contain _at minimum_ one (1) phrase. As you can see from the above "to be a string" example, phrases often have aliases.

You can negate just about any phrase:
Assertions may have multiple phrases or parameters, but the simplest assertions always look like this:

```ts
expect(subject, 'phrase');
```

...and more complex assertions look like this:

```ts
expect(subject, 'phrase', [parameter?, phrase?, parameter?, ...]);
```

One more convention worth mentioning is _negation_.

You can _negate_ just about any phrase by prepending it with `not` and a space. For example:

```js
expect(actual, 'to be', expected);
// did they not teach grammar in nerd school??
expect(actual, 'not is', expected);
expect(actual, 'not to be', expected);

expect(
() => throw new TypeError('aww, shucks'),
'to throw a',
'not to throw a',
TypeError,
'not satisfying',
'satisfying',
/gol durn/,
);
```

### Custom Assertions
### Custom Assertions by Zod

In _BUPKIS_, custom assertions are _first-class citizens_. You can create your own assertions with minimal boilerplate. You don't have to learn a new API or a new DSL (maybe); you just use [Zod][]. _It's so easy, even a **archaic human** could do it!_
[Zod][] is a popular object validation library which does some heavy lifting for _BUPKIS_. In fact, its fundamentals get us _most_ of the way to a type-safe assertion library!

Read [Guide: How to Create a Custom Assertion](https://boneskull.github.io/bupkis/documents/Guides.How_to_Create_a_Custom_Assertion) to learn more.
So We recognized that many (most?) custom assertions can be _implemented as Zod schemas._

## Prerequisites
Here's a ~~stupid~~ ~~quick~~ _stupid_ example of a creating and "registering" a basic assertion _which can be invoked using two different phrases_:

```ts
import { z, use, createAssertion } from 'bupkis';

_BUPKIS_ requires **Node.js ^20.19.0 || ^22.12.0 || >=23** and ships as a dual CJS/ESM package.
const stringAssertion = createAssertion(
z.string(),
[['to be based', 'to be bussin']],
z.string(),
);

The library has been designed for Node.js environments and testing frameworks.
const { expect } = use([stringAssertion]);

## Installation
expect('chat', 'to be based');
expect('fam', 'to be bussin');

```bash
npm install bupkis -D
// did you know? includes all builtin assertions!
expect('skiball lavatory', 'to be a string');
```

## Usage
**If you can express it in Zod, you can make it an assertion.** There's also a [function-based API][custom-assertion-function] for use with [parametric][] and behavioral assertions.

Here:
👉 For a thorough guide on creating assertions, read [Guide: How to Create a Custom Assertion][create-a-custom-assertion].

```ts
import { expect } from 'bupkis';

// Basic type assertions
expect('hello', 'to be a string');
expect(42, 'to be a number');
expect(true, 'to be a boolean');

// Value comparisons
expect(10, 'to equal', 10);
expect('hello world', 'to contain', 'world');
expect([1, 2, 3], 'to have length', 3);

// Negation
expect(42, 'not to be a string');
expect('hello', 'not to equal', 'goodbye');

// Object assertions
const user = { name: 'Alice', age: 30 };
expect(user, 'to be an object');
expect(user, 'to have property', 'name');
expect(user, 'to satisfy', { name: 'Alice' });
```
## Prerequisites

For comprehensive documentation and guides, visit the [project documentation](https://boneskull.github.io/bupkis/).
**Node.js**: ^20.19.0, ^22.12.0, >=23

### Worth Mentioning Right Now
_BUPKIS_ has a peer dependency on [Zod][] v4+, but will install it as an optional dependency if you are not already using it.

_BUPKIS_ has two main exports:
_BUPKIS_ ships as a dual CJS/ESM package.

- `expect()`: the main entrypoint for synchronous assertions
- `expectAsync()`: the main entrypoint for asynchronous assertions
> Disclaimer: _BUPKIS_ has been designed to run on Node.js in a development environment. Anyone attempting to deploy _BUPKIS_ to some server somewhere will get what is coming to them.

> [!IMPORTANT]
>
> As of this writing, the assertions available in `expectAsync()` are all `Promise`-related (and custom assertions can even use an async schema for the subject); they are completely disjoint from the assertions available in `expect()`. **This will likely change in the future.**
## Installation

## Project Scope
```bash
npm install bupkis -D
```

1. It's an assertion library
## Usage

## Prior Art & Appreciation
👉 See the [Basic Usage Guide](https://bupkis.zip/documents/guides.basic_usage) for a quick introduction.

📖 Visit [https://bupkis.zip](https://bupkis.zip) for comprehensive guides and reference.

## Acknowledgements

- [Unexpected][] is the main inspiration for _BUPKIS_. However, creating types for this library is exceedingly difficult (and was in fact the first thing I tried). Despite that drawback, I find it more usable than any other assertion library I've tried.
- [Zod][] is a popular object validation library which does most of the heavy lifting for _BUPKIS_. It's not an assertion library, but there's enough overlap in its use case that it makes sense to leverage it.
- [fast-check][]: A big thanks to Nicholas Dubien for this library. There is **no better library** for an assertion library to use to test itself! Well, besides itself, I mean. How about _in addition to_ itself? Yes. Thank you!
- [Zod][] is a popular object validation library upon which _BUPKIS_ builds many of its own assertions.
- [fast-check][]: Thanks to Nicholas Dubien for this library. There is **no better library** for an assertion library to use to test itself! Well, besides itself, I mean. How about _in addition to_ itself? Yes. Thank you!
- [tshy][] from Isaac Schlueter. Thanks for making dual ESM/CJS packages easy and not too fancy.
- [TypeDoc][] it really documents the hell out of TypeScript projects.
- [@cjihrig](https://github.com/cjihrig) and other Node.js contributors for the thoughtfulness put into [`node:test`](https://nodejs.org/api/test.html) that make it my current test-runner-of-choice.

## Why is it called _BUPKIS_?

TODO: think of good reason and fill in later

## A Note From The Author

Expand All @@ -164,5 +192,13 @@ _BUPKIS_ has two main exports:
Copyright © 2025 Christopher Hiller. Licensed under [BlueOak-1.0.0](https://blueoakcouncil.org/license/1.0.0).

[zod]: https://zod.dev
[docs]: https://bupkis.zip
[basic-usage]: https://bupkis.zip/documents/guides.basic_usage
[unexpected]: https://unexpected.js.org
[fast-check]: https://fast-check.dev
[parametric]: https://bupkis.zip/documents/Reference.Glossary_of_Terms#parametric-assertion
[custom-assertion-function]: https://bupkis.zip/documents/guides.how_to_create_a_custom_assertion#using-a-function
[create-a-custom-assertion]: https://bupkis.zip/documents/Guides.How_to_Create_a_Custom_Assertion
[assertion-reference]: https://bupkis.zip/documents/reference.assertions
[tshy]: https://github.com/isaacs/tshy
[typedoc]: https://typedoc.org
1 change: 1 addition & 0 deletions assets/Twentieth-Century-Bold.txt

Large diffs are not rendered by default.

Binary file added assets/Twentieth-Century-Bold.woff2
Binary file not shown.
Binary file added assets/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/bupkis-logo-1024.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/bupkis-logo-128.png
Binary file not shown.
Binary file removed assets/bupkis-logo-2048.png
Binary file not shown.
Binary file modified assets/bupkis-logo-256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/bupkis-logo-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading