0
# Configuration and Extension
1
2
Library configuration options, prototype extension management, and custom assertion plugin system.
3
4
## Library Configuration
5
6
### should.config
7
8
Global configuration object that controls should.js behavior.
9
10
```javascript { .api }
11
/**
12
* Global configuration object for should.js behavior
13
*/
14
interface Config {
15
/** Whether .eql checks object prototypes for equality */
16
checkProtoEql: boolean;
17
/** Whether .eql treats +0 and -0 as equal */
18
plusZeroAndMinusZeroEqual: boolean;
19
/** Additional formatting options for should-format */
20
[key: string]: any;
21
}
22
23
const config: Config;
24
```
25
26
**Usage:**
27
```javascript
28
import should from 'should';
29
30
// Default configuration
31
console.log(should.config.checkProtoEql); // false
32
console.log(should.config.plusZeroAndMinusZeroEqual); // true
33
34
// Modify configuration
35
should.config.checkProtoEql = true;
36
should.config.plusZeroAndMinusZeroEqual = false;
37
38
// Test behavior with different settings
39
const objA = { value: 10 };
40
const objB = Object.create(null);
41
objB.value = 10;
42
43
// With checkProtoEql = false (default)
44
should.config.checkProtoEql = false;
45
objA.should.eql(objB); // Passes - prototypes ignored
46
47
// With checkProtoEql = true
48
should.config.checkProtoEql = true;
49
objA.should.not.eql(objB); // Different prototypes detected
50
51
// Zero equality behavior
52
should.config.plusZeroAndMinusZeroEqual = true;
53
(+0).should.eql(-0); // Passes
54
55
should.config.plusZeroAndMinusZeroEqual = false;
56
(+0).should.not.eql(-0); // Fails - treats +0 and -0 as different
57
```
58
59
## Prototype Extension Management
60
61
### should.extend()
62
63
Extend a prototype with the should property using a custom name.
64
65
```javascript { .api }
66
/**
67
* Extend given prototype with should property using specified name
68
* @param propertyName - Name of property to add (default: 'should')
69
* @param proto - Prototype to extend (default: Object.prototype)
70
* @returns Descriptor object to restore later with noConflict()
71
*/
72
extend(propertyName?: string, proto?: object): {
73
name: string;
74
descriptor: PropertyDescriptor | undefined;
75
proto: object;
76
};
77
```
78
79
**Usage:**
80
```javascript
81
import should from 'should';
82
83
// Default extension (already done automatically)
84
// Object.prototype.should = getter
85
86
// Custom property name
87
const mustDescriptor = should.extend('must', Object.prototype);
88
'test'.must.be.a.String(); // Now using 'must' instead of 'should'
89
90
// Custom prototype
91
class CustomClass {
92
constructor(value) {
93
this.value = value;
94
}
95
}
96
97
const customDescriptor = should.extend('expect', CustomClass.prototype);
98
const instance = new CustomClass(42);
99
instance.expect.have.property('value', 42);
100
101
// Multiple extensions
102
const checkDescriptor = should.extend('check', Array.prototype);
103
[1, 2, 3].check.have.length(3);
104
105
// Store descriptors for later cleanup
106
const extensions = {
107
must: should.extend('must'),
108
expect: should.extend('expect', CustomClass.prototype),
109
check: should.extend('check', Array.prototype)
110
};
111
```
112
113
### should.noConflict()
114
115
Remove should extension and restore previous property if it existed.
116
117
```javascript { .api }
118
/**
119
* Remove should extension and restore previous property descriptor
120
* @param descriptor - Descriptor returned from extend() (optional)
121
* @returns The should function
122
*/
123
noConflict(descriptor?: {
124
name: string;
125
descriptor: PropertyDescriptor | undefined;
126
proto: object;
127
}): typeof should;
128
```
129
130
**Usage:**
131
```javascript
132
// Remove default should extension
133
const cleanShould = should.noConflict();
134
135
// Now Object.prototype.should is removed
136
try {
137
'test'.should.be.a.String(); // TypeError: Cannot read property 'should'
138
} catch (error) {
139
console.log('should property removed');
140
}
141
142
// Use functional syntax instead
143
cleanShould('test').should.be.a.String(); // Works
144
145
// Remove specific extension
146
const mustDescriptor = should.extend('must');
147
'test'.must.be.a.String(); // Works
148
149
should.noConflict(mustDescriptor);
150
// Now 'must' property is removed
151
152
// Restore previous property if it existed
153
Object.prototype.previousProperty = 'original';
154
const prevDescriptor = should.extend('previousProperty');
155
'test'.previousProperty.be.a.String(); // should assertion
156
157
should.noConflict(prevDescriptor);
158
console.log('test'.previousProperty); // 'original' - restored
159
```
160
161
## Functional API Usage
162
163
### As-Function Import
164
165
Use should.js without prototype extension.
166
167
```javascript { .api }
168
/**
169
* Should.js as a function without prototype extension
170
* Import from 'should/as-function' to avoid Object.prototype modification
171
*/
172
declare function should(obj: any): should.Assertion;
173
```
174
175
**Usage:**
176
```javascript
177
// Import as function only
178
const should = require('should/as-function');
179
// or
180
import should from 'should/as-function';
181
182
// No prototype extension - Object.prototype.should doesn't exist
183
console.log(typeof ''.should); // 'undefined'
184
185
// Use functional syntax
186
should('test').be.a.String();
187
should(42).be.a.Number().and.be.above(0);
188
should({ name: 'john' }).have.property('name', 'john');
189
190
// Useful for testing null/undefined values
191
should(null).be.null();
192
should(undefined).be.undefined();
193
194
// Can still use all assertion methods
195
should([1, 2, 3])
196
.be.an.Array()
197
.and.have.length(3)
198
.and.containEql(2);
199
```
200
201
## Plugin System
202
203
### should.use()
204
205
Add custom assertion plugins to extend functionality.
206
207
```javascript { .api }
208
/**
209
* Use a plugin to extend should.js functionality
210
* @param plugin - Plugin function that receives (should, Assertion) parameters
211
* @returns The should function for chaining
212
*/
213
use(plugin: (should: any, Assertion: any) => void): typeof should;
214
```
215
216
**Usage:**
217
```javascript
218
// Basic plugin example
219
should.use(function(should, Assertion) {
220
// Add custom assertion method
221
Assertion.add('between', function(min, max) {
222
this.params = {
223
operator: `to be between ${min} and ${max}`
224
};
225
226
this.obj.should.be.a.Number();
227
this.obj.should.be.above(min - 1);
228
this.obj.should.be.below(max + 1);
229
});
230
});
231
232
// Use the custom assertion
233
(5).should.be.between(1, 10);
234
(15).should.not.be.between(1, 10);
235
236
// Advanced plugin with multiple methods
237
should.use(function(should, Assertion) {
238
// Email validation assertion
239
Assertion.add('email', function() {
240
this.params = { operator: 'to be a valid email' };
241
242
this.obj.should.be.a.String();
243
this.obj.should.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
244
});
245
246
// Phone number validation
247
Assertion.add('phoneNumber', function() {
248
this.params = { operator: 'to be a valid phone number' };
249
250
this.obj.should.be.a.String();
251
this.obj.should.match(/^\(?[\d\s\-\+\(\)]{10,}$/);
252
});
253
254
// Credit card validation (simplified)
255
Assertion.add('creditCard', function() {
256
this.params = { operator: 'to be a valid credit card number' };
257
258
this.obj.should.be.a.String();
259
this.obj.should.match(/^\d{4}[\s\-]?\d{4}[\s\-]?\d{4}[\s\-]?\d{4}$/);
260
});
261
});
262
263
// Use custom validation assertions
264
'user@example.com'.should.be.email();
265
'123-456-7890'.should.be.phoneNumber();
266
'1234 5678 9012 3456'.should.be.creditCard();
267
```
268
269
### Complex Plugin Examples
270
271
#### HTTP Status Code Plugin
272
```javascript
273
should.use(function(should, Assertion) {
274
// HTTP status code categories
275
const statusCategories = {
276
informational: (code) => code >= 100 && code < 200,
277
success: (code) => code >= 200 && code < 300,
278
redirection: (code) => code >= 300 && code < 400,
279
clientError: (code) => code >= 400 && code < 500,
280
serverError: (code) => code >= 500 && code < 600
281
};
282
283
Object.keys(statusCategories).forEach(category => {
284
Assertion.add(category, function() {
285
this.params = { operator: `to be a ${category} status code` };
286
287
this.obj.should.be.a.Number();
288
statusCategories[category](this.obj).should.be.true();
289
});
290
});
291
292
// Specific status codes
293
const specificCodes = {
294
ok: 200,
295
created: 201,
296
badRequest: 400,
297
unauthorized: 401,
298
forbidden: 403,
299
notFound: 404,
300
internalServerError: 500
301
};
302
303
Object.keys(specificCodes).forEach(name => {
304
Assertion.add(name, function() {
305
this.params = { operator: `to be ${name} (${specificCodes[name]})` };
306
this.obj.should.equal(specificCodes[name]);
307
});
308
});
309
});
310
311
// Usage
312
(200).should.be.success();
313
(404).should.be.clientError();
314
(500).should.be.serverError();
315
316
(200).should.be.ok();
317
(404).should.be.notFound();
318
(401).should.be.unauthorized();
319
```
320
321
#### Date Range Plugin
322
```javascript
323
should.use(function(should, Assertion) {
324
Assertion.add('dateAfter', function(date) {
325
this.params = { operator: `to be after ${date}` };
326
327
this.obj.should.be.a.Date();
328
this.obj.getTime().should.be.above(new Date(date).getTime());
329
});
330
331
Assertion.add('dateBefore', function(date) {
332
this.params = { operator: `to be before ${date}` };
333
334
this.obj.should.be.a.Date();
335
this.obj.getTime().should.be.below(new Date(date).getTime());
336
});
337
338
Assertion.add('dateWithin', function(startDate, endDate) {
339
this.params = {
340
operator: `to be between ${startDate} and ${endDate}`
341
};
342
343
this.obj.should.be.a.Date();
344
const time = this.obj.getTime();
345
const start = new Date(startDate).getTime();
346
const end = new Date(endDate).getTime();
347
348
time.should.be.above(start - 1);
349
time.should.be.below(end + 1);
350
});
351
352
Assertion.add('today', function() {
353
this.params = { operator: 'to be today' };
354
355
this.obj.should.be.a.Date();
356
const today = new Date();
357
const objDate = new Date(this.obj);
358
359
objDate.getFullYear().should.equal(today.getFullYear());
360
objDate.getMonth().should.equal(today.getMonth());
361
objDate.getDate().should.equal(today.getDate());
362
});
363
});
364
365
// Usage
366
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
367
const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000);
368
const now = new Date();
369
370
now.should.be.dateAfter(yesterday);
371
now.should.be.dateBefore(tomorrow);
372
now.should.be.dateWithin(yesterday, tomorrow);
373
now.should.be.today();
374
```
375
376
#### API Response Plugin
377
```javascript
378
should.use(function(should, Assertion) {
379
Assertion.add('apiResponse', function() {
380
this.params = { operator: 'to be a valid API response' };
381
382
this.obj.should.be.an.Object();
383
this.obj.should.have.properties('status', 'data');
384
this.obj.status.should.be.a.Number();
385
});
386
387
Assertion.add('successResponse', function() {
388
this.params = { operator: 'to be a successful API response' };
389
390
this.obj.should.be.apiResponse();
391
this.obj.status.should.be.within(200, 299);
392
});
393
394
Assertion.add('errorResponse', function() {
395
this.params = { operator: 'to be an error API response' };
396
397
this.obj.should.be.apiResponse();
398
this.obj.status.should.be.above(399);
399
this.obj.should.have.property('error');
400
});
401
402
Assertion.add('paginatedResponse', function() {
403
this.params = { operator: 'to be a paginated API response' };
404
405
this.obj.should.be.successResponse();
406
this.obj.should.have.property('pagination');
407
this.obj.pagination.should.have.properties('page', 'limit', 'total');
408
});
409
});
410
411
// Usage
412
const response = {
413
status: 200,
414
data: [{ id: 1, name: 'user1' }],
415
pagination: { page: 1, limit: 10, total: 50 }
416
};
417
418
response.should.be.apiResponse();
419
response.should.be.successResponse();
420
response.should.be.paginatedResponse();
421
422
const errorResponse = {
423
status: 404,
424
data: null,
425
error: { message: 'Not found' }
426
};
427
428
errorResponse.should.be.errorResponse();
429
```
430
431
## Advanced Configuration
432
433
### Environment-Specific Setup
434
```javascript
435
// Development environment
436
if (process.env.NODE_ENV === 'development') {
437
should.config.checkProtoEql = true; // Strict prototype checking
438
439
// Add development-only assertions
440
should.use(function(should, Assertion) {
441
Assertion.add('debug', function() {
442
console.log('Debug assertion:', this.obj);
443
return this;
444
});
445
});
446
}
447
448
// Test environment
449
if (process.env.NODE_ENV === 'test') {
450
// More lenient configuration for tests
451
should.config.plusZeroAndMinusZeroEqual = true;
452
453
// Test-specific utilities
454
should.use(function(should, Assertion) {
455
Assertion.add('testFixture', function(type) {
456
this.params = { operator: `to be a ${type} test fixture` };
457
458
this.obj.should.be.an.Object();
459
this.obj.should.have.property('_fixture', type);
460
});
461
});
462
}
463
```
464
465
### Framework Integration
466
```javascript
467
// Express.js response testing
468
should.use(function(should, Assertion) {
469
Assertion.add('expressResponse', function() {
470
this.params = { operator: 'to be an Express response object' };
471
472
this.obj.should.be.an.Object();
473
this.obj.should.have.properties('status', 'json', 'send');
474
this.obj.status.should.be.a.Function();
475
});
476
});
477
478
// React component testing
479
should.use(function(should, Assertion) {
480
Assertion.add('reactComponent', function() {
481
this.params = { operator: 'to be a React component' };
482
483
this.obj.should.be.a.Function();
484
// Additional React-specific checks
485
});
486
});
487
```
488
489
## Chaining and Integration
490
491
All configuration and extension features integrate with standard should.js chaining:
492
493
```javascript
494
// Chain configuration changes
495
should.config.checkProtoEql = true;
496
497
const obj1 = { value: 1 };
498
const obj2 = Object.create(null);
499
obj2.value = 1;
500
501
obj1.should.not.eql(obj2); // Prototype difference detected
502
503
// Chain custom assertions
504
'user@test.com'.should.be.email().and.be.a.String();
505
(404).should.be.clientError().and.be.notFound();
506
507
// Use extensions in complex chains
508
const apiResponse = { status: 200, data: [] };
509
apiResponse.should.be.successResponse()
510
.and.have.property('data')
511
.which.is.an.Array();
512
```