Behavior modification system using flags and grammar words for readable, flexible assertions.
Grammar words have no functional impact but improve assertion readability by making them more natural language-like.
// Grammar properties (all return 'this' for chaining)
a // Article for readability
an // Article for readability
and // Conjunction for chaining assertions
at // Preposition for readability
be // Verb for readability
have // Verb for readability
in // Preposition for readability
to // Infinitive marker for readabilityUsage Examples:
const Code = require('@hapi/code');
const expect = Code.expect;
// Natural language-like assertions
expect(10).to.be.above(5);
expect('hello').to.be.a.string();
expect([1, 2]).to.be.an.array();
expect(20).to.be.at.least(20);
expect('abc').to.have.length(3);
expect(6).to.be.in.range(5, 10);
// Chaining with 'and'
expect('hello world')
.to.be.a.string()
.and.contain('world')
.and.have.length(11);Flags modify the behavior of assertions. Each flag toggles its state when accessed, allowing complex assertion logic.
Inverses the expected result of any assertion.
/**
* Inverses the expected result of the assertion
* Special behavior: For functions and promises, restricts available methods
*/
notUsage Examples:
// Basic negation
expect(10).to.not.be.above(20);
expect('hello').to.not.be.a.number();
expect([]).to.not.have.length(5);
// Chaining with negation
expect(user)
.to.be.an.object()
.and.not.be.empty()
.and.not.be.null();
// Special behavior with functions (cannot specify error type/message)
expect(nonThrowingFunction).to.not.throw();
// expect(nonThrowingFunction).to.not.throw(Error); // This would error!
// Special behavior with promises
await expect(resolvingPromise).to.not.reject();
// await expect(resolvingPromise).to.not.reject(Error); // This would error!Requires that inclusion matches appear only once in the provided value. Used with
include()/**
* Requires inclusion matches appear only once
* Used with: include(), includes(), contain(), contains()
*/
onceUsage Examples:
// String occurrence checking
expect('hello world').to.once.include('o'); // Fails - 'o' appears twice
expect('hello world').to.once.include('h'); // Passes - 'h' appears once
// Array element checking
expect([1, 2, 3]).to.once.include(2); // Passes - 2 appears once
expect([1, 2, 2, 3]).to.once.include(2); // Fails - 2 appears twice
// Multiple elements
expect('abcde').to.once.include(['a', 'e']); // Passes - each appears once
expect('abccde').to.once.include(['a', 'c']); // Fails - 'c' appears twiceRequires that only the provided elements appear in the provided value (exact match with no extra elements). Used with
include()/**
* Requires exact match with no extra elements
* Used with: include(), includes(), contain(), contains()
*/
onlyUsage Examples:
// Array exact matching
expect([1, 2, 3]).to.only.include([1, 2, 3]); // Passes - exact match
expect([1, 2, 3]).to.only.include([1, 2]); // Fails - array has extra element (3)
expect([1, 2]).to.only.include([1, 2, 3]); // Fails - expected element (3) not present
// Order doesn't matter
expect([3, 1, 2]).to.only.include([1, 2, 3]); // Passes - same elements
// Object key matching
expect({a: 1, b: 2}).to.only.include(['a', 'b']); // Passes - exact keys
expect({a: 1, b: 2, c: 3}).to.only.include(['a', 'b']); // Fails - extra key 'c'
// String character matching
expect('abc').to.only.include(['a', 'b', 'c']); // Passes - exact characters
expect('abcd').to.only.include(['a', 'b', 'c']); // Fails - extra character 'd'Allows partial matching when asserting inclusion instead of full comparison. Used with
include()/**
* Allows partial matching instead of full comparison
* Used with: include(), includes(), contain(), contains()
*/
partUsage Examples:
// Object partial matching
expect({a: 1, b: 2, c: 3}).to.part.include({a: 1}); // Passes - partial match
expect({a: 1, b: 2}).to.part.include({a: 1, c: 3}); // Fails - 'c' not present
// Array of objects partial matching
expect([{a: 1, b: 2}, {c: 3, d: 4}]).to.part.include([{a: 1}]); // Passes
expect([{a: 1, b: 2}]).to.part.include([{a: 1, c: 3}]); // Fails - 'c' not in object
// Nested object partial matching
const users = [
{id: 1, name: 'Alice', profile: {age: 25, city: 'NYC'}},
{id: 2, name: 'Bob', profile: {age: 30, city: 'LA'}}
];
expect(users).to.part.include([{id: 1, name: 'Alice'}]); // Passes - partial match
expect(users).to.part.include([{profile: {age: 25}}]); // Passes - nested partial matchPerforms comparison using strict equality (
===equal()include()/**
* Use strict equality (===) instead of deep comparison
* Used with: equal(), equals(), include(), includes(), contain(), contains()
*/
shallowUsage Examples:
// Object reference comparison
const obj1 = {a: 1, b: 2};
const obj2 = {a: 1, b: 2}; // Different object, same content
const obj3 = obj1; // Same reference
// Deep comparison (default)
expect(obj1).to.equal(obj2); // Passes - deep equality
expect(obj1).to.equal(obj3); // Passes - same reference
// Shallow comparison
expect(obj1).to.shallow.equal(obj3); // Passes - same reference
expect(obj1).to.not.shallow.equal(obj2); // Passes - different references
// Array reference comparison
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
const arr3 = arr1;
expect(arr1).to.equal(arr2); // Passes - deep equality
expect(arr1).to.shallow.equal(arr3); // Passes - same reference
expect(arr1).to.not.shallow.equal(arr2); // Passes - different references
// Shallow inclusion
expect([obj1, obj2]).to.shallow.include(obj1); // Passes - same reference
expect([obj1, obj2]).to.not.shallow.include(obj3); // Passes if obj3 !== obj1Flags toggle their state each time they're accessed:
// Flag toggling
expect(10).to.not.not.be.above(5); // Double negation - 'not' cancels out
expect('hello').to.shallow.shallow.equal('hello'); // Double shallow - cancels out
// This can be confusing, so avoid multiple flag usage
expect(value).to.not.be.above(10); // Clear and readable
// expect(value).to.not.not.not.be.above(10); // Confusing - avoid thisFlags apply to the next assertion method and then reset:
// Flags reset after each assertion
expect(obj)
.to.be.an.object() // No flags active
.and.not.be.empty() // 'not' flag active for empty() only
.and.have.length.above(0); // No flags active for above()
// Each assertion gets its own flag state
expect(arr)
.to.shallow.include(item1) // 'shallow' applies to this include()
.and.include(item2); // No 'shallow' flag for this include()Multiple flags can be combined in a single assertion:
// Multiple flags on one assertion
expect([{a: 1, b: 2}, {c: 3, d: 4}])
.to.once.part.include([{a: 1}]); // Both 'once' and 'part' flags active
expect([obj1, obj1, obj2])
.to.not.once.shallow.include(obj1); // 'not', 'once', and 'shallow' flags
// String with multiple flags
expect('hello world hello')
.to.not.once.include('hello'); // Fails because 'hello' appears twiceconst Code = require('@hapi/code');
const expect = Code.expect;
// Complex object validation
const user = {
id: 123,
name: 'John Doe',
email: 'john@example.com',
roles: ['user', 'admin'],
settings: {
theme: 'dark',
notifications: true
}
};
expect(user)
.to.be.an.object()
.and.not.be.empty()
.and.include(['id', 'name', 'email'])
.and.not.include(['password', 'secret']);
expect(user.roles)
.to.be.an.array()
.and.only.include(['user', 'admin'])
.and.once.include('admin');
expect(user.settings)
.to.part.include({theme: 'dark'})
.and.not.be.empty();Understanding how flags affect error messages:
// Different error messages based on flags
try {
expect([1, 2, 3]).to.only.include([1, 2]); // Fails
} catch (err) {
console.log(err.message); // Includes 'only' context
}
try {
expect('hello hello').to.once.include('hello'); // Fails
} catch (err) {
console.log(err.message); // Includes 'once' context
}
try {
expect(obj1).to.shallow.equal(obj2); // Fails if different references
} catch (err) {
console.log(err.message); // Shows reference comparison failure
}// Shallow comparisons are faster for large objects
const largeObj1 = generateLargeObject();
const largeObj2 = generateLargeObject();
const largeObj3 = largeObj1;
// Fast reference check
expect(largeObj1).to.shallow.equal(largeObj3); // Quick reference comparison
// Slower deep comparison
expect(largeObj1).to.equal(largeObj2); // Deep traversal of object properties