openapi_generate_typescript_fetch 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1214 @@
1
+ <%=
2
+ Gen.output.config = Gen.x.cfg['ts_indentation']
3
+ Gen.x.generator_info
4
+ %>
5
+
6
+ import {expect} from 'chai';
7
+ import type {String2String} from '../src/schemas.d.ts';
8
+ import type {Bodyful,Name2Functions,LoaderFunction,LoaderMap,SchemaFunctions,PatternFunctions,SchemaCheckers} from '../src/helpers.js';
9
+ import {addJsonBody,str2integer,str2float,isSchema,baseSchema,setExtras,query,headers,getLoader} from '../src/helpers.js';
10
+
11
+ describe('addJsonBody', function () {
12
+ it('add body and headers', function () {
13
+ const target: Bodyful = {};
14
+ const body = {key: 'value'};
15
+ const hdrs: String2String = {};
16
+ addJsonBody(target, body, hdrs);
17
+ const td = new TextDecoder();
18
+ expect(JSON.parse(td.decode(target.body))).to.deep.equal(body);
19
+ expect(hdrs['Content-Type']).to.equal('application/json');
20
+ expect(hdrs['Content-Length']).to.equal(target.body.length.toString());
21
+ })
22
+ it('no body with null', function () {
23
+ const target: Bodyful = {};
24
+ const hdrs: String2String = {};
25
+ addJsonBody(target, null, hdrs);
26
+ expect(target).not.to.have.property('body');
27
+ expect(hdrs).not.to.have.property('Content-Type');
28
+ expect(hdrs).not.to.have.property('Content-Length');
29
+ })
30
+ it('no body with undefined', function () {
31
+ const target: Bodyful = {};
32
+ const hdrs: String2String = {};
33
+ addJsonBody(target, undefined, hdrs);
34
+ expect(target).not.to.have.property('body');
35
+ expect(hdrs).not.to.have.property('Content-Type');
36
+ expect(hdrs).not.to.have.property('Content-Length');
37
+ })
38
+ it('handle empty object body', function () {
39
+ const target: Bodyful = {};
40
+ const body = {};
41
+ const hdrs: String2String = {};
42
+ addJsonBody(target, body, hdrs);
43
+ const td = new TextDecoder();
44
+ expect(JSON.parse(td.decode(target.body))).to.deep.equal({});
45
+ expect(hdrs['Content-Type']).to.equal('application/json');
46
+ expect(hdrs['Content-Length']).to.equal(target.body.length.toString());
47
+ })
48
+ it('preserves existing headers but overwrites Content-Type and Content-Length', function () {
49
+ const target: Bodyful = {};
50
+ const body = {test: 'value'};
51
+ const hdrs: String2String = {
52
+ 'Authorization': 'Bearer token',
53
+ 'Content-Type': 'text/plain', // This should be overwritten
54
+ 'Content-Length': '999' // This should be overwritten
55
+ };
56
+ addJsonBody(target, body, hdrs);
57
+ expect(hdrs['Authorization']).to.equal('Bearer token');
58
+ expect(hdrs['Content-Type']).to.equal('application/json');
59
+ expect(hdrs['Content-Length']).to.equal(target.body.length.toString());
60
+ expect(hdrs['Content-Length']).not.to.equal('999');
61
+ })
62
+ it('handle primitive values as body', function () {
63
+ const target1: Bodyful = {};
64
+ const hdrs1: String2String = {};
65
+ addJsonBody(target1, 'string value', hdrs1);
66
+ const td = new TextDecoder();
67
+ expect(JSON.parse(td.decode(target1.body))).to.equal('string value');
68
+
69
+ const target2: Bodyful = {};
70
+ const hdrs2: String2String = {};
71
+ addJsonBody(target2, 42, hdrs2);
72
+ expect(JSON.parse(td.decode(target2.body))).to.equal(42);
73
+
74
+ const target3: Bodyful = {};
75
+ const hdrs3: String2String = {};
76
+ addJsonBody(target3, true, hdrs3);
77
+ expect(JSON.parse(td.decode(target3.body))).to.equal(true);
78
+ })
79
+ })
80
+
81
+ describe('str2integer', function () {
82
+ it('convert string to integer', function () {
83
+ expect(str2integer('10')).to.equal(10);
84
+ })
85
+ it('return null for null input', function () {
86
+ expect(str2integer(null)).to.be.null;
87
+ })
88
+ it('handle edge cases and invalid inputs', function () {
89
+ // Valid cases
90
+ expect(str2integer('0')).to.equal(0);
91
+ expect(str2integer('-5')).to.equal(-5);
92
+ expect(str2integer('123')).to.equal(123);
93
+
94
+ // Whitespace handling
95
+ expect(str2integer(' 42 ')).to.equal(42);
96
+
97
+ // Invalid inputs - parseInt behavior
98
+ expect(str2integer('abc')).to.be.NaN;
99
+ expect(str2integer('')).to.be.NaN;
100
+ expect(str2integer('12.5')).to.equal(12); // parseInt truncates
101
+ expect(str2integer('12abc')).to.equal(12); // parseInt parses partial
102
+ expect(str2integer('abc123')).to.be.NaN;
103
+
104
+ // Special cases
105
+ expect(str2integer('Infinity')).to.be.NaN;
106
+ expect(str2integer('-Infinity')).to.be.NaN;
107
+ expect(str2integer('NaN')).to.be.NaN;
108
+ })
109
+ })
110
+
111
+ describe('str2float', function () {
112
+ it('convert string to float', function () {
113
+ expect(str2float('10.5')).to.equal(10.5);
114
+ })
115
+ it('return null for null input', function () {
116
+ expect(str2float(null)).to.be.null;
117
+ })
118
+ it('handle edge cases and invalid inputs', function () {
119
+ // Valid cases
120
+ expect(str2float('0')).to.equal(0);
121
+ expect(str2float('-5.5')).to.equal(-5.5);
122
+ expect(str2float('123.456')).to.equal(123.456);
123
+ expect(str2float('123')).to.equal(123);
124
+
125
+ // Scientific notation
126
+ expect(str2float('1.23e5')).to.equal(123000);
127
+ expect(str2float('1.23e-5')).to.equal(0.0000123);
128
+
129
+ // Whitespace handling
130
+ expect(str2float(' 42.5 ')).to.equal(42.5);
131
+
132
+ // Invalid inputs
133
+ expect(str2float('abc')).to.be.NaN;
134
+ expect(str2float('')).to.be.NaN;
135
+ expect(str2float('12.5abc')).to.equal(12.5); // parseFloat parses partial
136
+ expect(str2float('abc12.5')).to.be.NaN;
137
+
138
+ // Special values
139
+ expect(str2float('Infinity')).to.equal(Infinity);
140
+ expect(str2float('-Infinity')).to.equal(-Infinity);
141
+ expect(str2float('NaN')).to.be.NaN;
142
+ })
143
+ })
144
+
145
+ describe('isSchema', function () {
146
+ // From claude-3-7-sonnet-20250219 with minor modifications.
147
+ it('validates required fields correctly', function () {
148
+ // Setup schema checkers with required fields
149
+ const checkers: SchemaCheckers = {
150
+ requiredKeys: ['name'],
151
+ required: {
152
+ 'name': {
153
+ base: (x) => x,
154
+ is: (x) => typeof x === 'string'
155
+ }
156
+ },
157
+ optional: {},
158
+ patterns: []
159
+ };
160
+
161
+ // Valid case - has required field with correct type
162
+ expect(isSchema({ name: 'test' }, checkers)).to.be.true;
163
+
164
+ // Invalid case - has required field with wrong type
165
+ expect(isSchema({ name: 123 }, checkers)).to.be.false;
166
+
167
+ // Invalid case - missing required field
168
+ expect(isSchema({}, checkers)).to.be.false;
169
+ });
170
+
171
+ it('validates optional fields correctly', function () {
172
+ // Setup schema checkers with optional fields
173
+ const checkers: SchemaCheckers = {
174
+ requiredKeys: [],
175
+ required: {},
176
+ optional: {
177
+ 'age': {
178
+ base: (x) => x,
179
+ is: (x) => typeof x === 'number'
180
+ }
181
+ },
182
+ patterns: []
183
+ };
184
+
185
+ // Valid case - has optional field with correct type
186
+ expect(isSchema({ age: 25 }, checkers)).to.be.true;
187
+
188
+ // Valid case - missing optional field
189
+ expect(isSchema({}, checkers)).to.be.true;
190
+
191
+ // Invalid case - has optional field with wrong type
192
+ expect(isSchema({ age: '25' }, checkers)).to.be.false;
193
+ });
194
+
195
+ it('validates pattern fields correctly', function () {
196
+ // Setup schema checkers with pattern fields
197
+ const checkers: SchemaCheckers = {
198
+ requiredKeys: [],
199
+ required: {},
200
+ optional: {},
201
+ patterns: [
202
+ {
203
+ re: new RegExp('^prefix_.*$'),
204
+ funcs: {
205
+ base: (x) => x,
206
+ is: (x) => typeof x === 'boolean'
207
+ }
208
+ }
209
+ ]
210
+ };
211
+
212
+ // Valid case - has pattern field with correct type
213
+ expect(isSchema({ prefix_field: true }, checkers)).to.be.true;
214
+
215
+ // Invalid case - has pattern field with wrong type
216
+ expect(isSchema({ prefix_field: 'true' }, checkers)).to.be.false;
217
+
218
+ // Valid case - no pattern fields
219
+ expect(isSchema({}, checkers)).to.be.true;
220
+ });
221
+
222
+ it('validates additional fields correctly', function () {
223
+ // Setup schema checkers with additional fields
224
+ const checkers: SchemaCheckers = {
225
+ requiredKeys: [],
226
+ required: {},
227
+ optional: {},
228
+ patterns: [],
229
+ additional: {
230
+ is: (x) => typeof x === 'number'
231
+ }
232
+ };
233
+
234
+ // Valid case - has additional field with correct type
235
+ expect(isSchema({ random_field: 42 }, checkers)).to.be.true;
236
+
237
+ // Invalid case - has additional field with wrong type
238
+ expect(isSchema({ random_field: 'string' }, checkers)).to.be.false;
239
+ });
240
+
241
+ it('validates complex schemas correctly', function () {
242
+ // Setup complex schema checkers
243
+ const checkers: SchemaCheckers = {
244
+ requiredKeys: ['id'],
245
+ required: {
246
+ 'id': {
247
+ base: (x) => x,
248
+ is: (x) => typeof x === 'number'
249
+ }
250
+ },
251
+ optional: {
252
+ 'name': {
253
+ base: (x) => x,
254
+ is: (x) => typeof x === 'string'
255
+ }
256
+ },
257
+ patterns: [
258
+ {
259
+ re: new RegExp('^meta_.*$'),
260
+ funcs: {
261
+ base: (x) => x,
262
+ is: (x) => typeof x === 'string'
263
+ }
264
+ }
265
+ ],
266
+ additional: {
267
+ is: (x) => typeof x === 'boolean'
268
+ }
269
+ };
270
+
271
+ // Valid case - complex object with all types of fields
272
+ expect(isSchema({
273
+ id: 1,
274
+ name: 'test',
275
+ meta_created: '2023-01-01',
276
+ random_field: true
277
+ }, checkers)).to.be.true;
278
+
279
+ // Invalid case - missing required field
280
+ expect(isSchema({
281
+ name: 'test',
282
+ meta_created: '2023-01-01',
283
+ random_field: true
284
+ }, checkers)).to.be.false;
285
+
286
+ // Invalid case - wrong type for pattern field
287
+ expect(isSchema({
288
+ id: 1,
289
+ name: 'test',
290
+ meta_created: 123,
291
+ random_field: true
292
+ }, checkers)).to.be.false;
293
+
294
+ // Invalid case - wrong type for additional field
295
+ expect(isSchema({
296
+ id: 1,
297
+ name: 'test',
298
+ meta_created: '2023-01-01',
299
+ random_field: 'not-boolean'
300
+ }, checkers)).to.be.false;
301
+ });
302
+
303
+ it('handles multiple pattern matches correctly', function () {
304
+ const checkers: SchemaCheckers = {
305
+ requiredKeys: [],
306
+ required: {},
307
+ optional: {},
308
+ patterns: [
309
+ {
310
+ re: new RegExp('^test_.*$'),
311
+ funcs: {
312
+ base: (x) => `pattern1:${x}`,
313
+ is: (x) => typeof x === 'string'
314
+ }
315
+ },
316
+ {
317
+ re: new RegExp('.*_suffix$'),
318
+ funcs: {
319
+ base: (x) => `pattern2:${x}`,
320
+ is: (x) => typeof x === 'string'
321
+ }
322
+ }
323
+ ]
324
+ };
325
+
326
+ // Field matches first pattern only
327
+ expect(isSchema({ test_field: 'value' }, checkers)).to.be.true;
328
+ // Field matches both patterns - should validate against the first match
329
+ expect(isSchema({ test_field_suffix: 'value' }, checkers)).to.be.true;
330
+ // Invalid type for pattern
331
+ expect(isSchema({ test_field: 123 }, checkers)).to.be.false;
332
+ });
333
+
334
+ it('handles empty and edge case schema configurations', function () {
335
+ // Empty checkers
336
+ const emptyCheckers: SchemaCheckers = {
337
+ requiredKeys: [],
338
+ required: {},
339
+ optional: {},
340
+ patterns: []
341
+ };
342
+ expect(isSchema({}, emptyCheckers)).to.be.true;
343
+ expect(isSchema({ any_field: 'any_value' }, emptyCheckers)).to.be.true;
344
+
345
+ // Only required keys, no validation functions
346
+ const noFuncCheckers: SchemaCheckers = {
347
+ requiredKeys: ['id'],
348
+ required: {},
349
+ optional: {},
350
+ patterns: []
351
+ };
352
+ expect(isSchema({ id: 'anything' }, noFuncCheckers)).to.be.true;
353
+ expect(isSchema({}, noFuncCheckers)).to.be.false;
354
+ });
355
+
356
+ it('handles objects with special properties', function () {
357
+ const checkers: SchemaCheckers = {
358
+ requiredKeys: [],
359
+ required: {},
360
+ optional: {},
361
+ patterns: [],
362
+ additional: {
363
+ is: (x) => true // Accept anything
364
+ }
365
+ };
366
+
367
+ // Objects with prototype properties should still work
368
+ const obj = Object.create({ inheritedProp: 'inherited' });
369
+ obj.ownProp = 'own';
370
+ expect(isSchema(obj, checkers)).to.be.true;
371
+
372
+ // Object with null prototype
373
+ const nullProtoObj = Object.create(null);
374
+ nullProtoObj.prop = 'value';
375
+ expect(isSchema(nullProtoObj, checkers)).to.be.true;
376
+ });
377
+
378
+ it('validates field precedence correctly', function () {
379
+ // Test precedence: required > optional > patterns > additional
380
+ const checkers: SchemaCheckers = {
381
+ requiredKeys: [],
382
+ required: {
383
+ 'field': {
384
+ base: (x) => 'required',
385
+ is: (x) => typeof x === 'string'
386
+ }
387
+ },
388
+ optional: {
389
+ 'field': { // Same field name - should be ignored in favor of required
390
+ base: (x) => 'optional',
391
+ is: (x) => typeof x === 'number'
392
+ }
393
+ },
394
+ patterns: [
395
+ {
396
+ re: new RegExp('^field$'),
397
+ funcs: {
398
+ base: (x) => 'pattern',
399
+ is: (x) => typeof x === 'boolean'
400
+ }
401
+ }
402
+ ],
403
+ additional: {
404
+ is: (x) => typeof x === 'object'
405
+ }
406
+ };
407
+
408
+ // Should validate as string (required), not number (optional) or boolean (pattern)
409
+ expect(isSchema({ field: 'test' }, checkers)).to.be.true;
410
+ expect(isSchema({ field: 123 }, checkers)).to.be.false;
411
+ expect(isSchema({ field: true }, checkers)).to.be.false;
412
+ });
413
+ });
414
+
415
+ describe('baseSchema', function () {
416
+ // From claude-3-7-sonnet-20250219 with minor modifications.
417
+ it('processes required fields correctly', function () {
418
+ // Setup schema checkers with required fields
419
+ const checkers: SchemaCheckers = {
420
+ requiredKeys: [],
421
+ required: {
422
+ 'name': {
423
+ is: (x) => typeof x === 'string',
424
+ base: (x) => x.toUpperCase()
425
+ }
426
+ },
427
+ optional: {},
428
+ patterns: []
429
+ };
430
+
431
+ // Test with required field
432
+ const result = baseSchema({ name: 'test' }, checkers);
433
+ expect(result).to.deep.equal({ name: 'TEST' });
434
+ });
435
+
436
+ it('processes optional fields correctly', function () {
437
+ // Setup schema checkers with optional fields
438
+ const checkers: SchemaCheckers = {
439
+ requiredKeys: [],
440
+ required: {},
441
+ optional: {
442
+ 'age': {
443
+ is: (x) => typeof x === 'number',
444
+ base: (x) => x * 2
445
+ }
446
+ },
447
+ patterns: []
448
+ };
449
+
450
+ // Test with optional field
451
+ const result = baseSchema({ age: 25 }, checkers);
452
+ expect(result).to.deep.equal({ age: 50 });
453
+
454
+ // Test without optional field
455
+ const emptyResult = baseSchema({}, checkers);
456
+ expect(emptyResult).to.deep.equal({});
457
+ });
458
+
459
+ it('processes pattern fields correctly', function () {
460
+ // Setup schema checkers with pattern fields
461
+ const checkers: SchemaCheckers = {
462
+ requiredKeys: [],
463
+ required: {},
464
+ optional: {},
465
+ patterns: [
466
+ {
467
+ re: new RegExp('^prefix_.*$'),
468
+ funcs: {
469
+ is: (x) => typeof x === 'boolean',
470
+ base: (x) => !x // Invert boolean
471
+ }
472
+ }
473
+ ]
474
+ };
475
+
476
+ // Test with pattern field
477
+ const result = baseSchema({ prefix_field: true }, checkers);
478
+ expect(result).to.deep.equal({ prefix_field: false });
479
+ });
480
+
481
+ it('processes additional fields correctly', function () {
482
+ // Setup schema checkers with additional fields
483
+ const checkers: SchemaCheckers = {
484
+ requiredKeys: [],
485
+ required: {},
486
+ optional: {},
487
+ patterns: [],
488
+ additional: {
489
+ is: (x) => typeof x === 'number',
490
+ base: (x) => x + 10
491
+ }
492
+ };
493
+
494
+ // Test with additional field
495
+ const result = baseSchema({ random_field: 42 }, checkers);
496
+ expect(result).to.deep.equal({ random_field: 52 });
497
+ });
498
+
499
+ it('handles additional fields without base function', function () {
500
+ // Setup schema checkers with additional fields but no base function
501
+ const checkers: SchemaCheckers = {
502
+ requiredKeys: [],
503
+ required: {},
504
+ optional: {},
505
+ patterns: [],
506
+ additional: {
507
+ is: (x) => typeof x === 'number'
508
+ // No base function
509
+ }
510
+ };
511
+
512
+ // Test with additional field
513
+ const result = baseSchema({ random_field: 42 }, checkers);
514
+ expect(result).to.deep.equal({}); // Should not include the field
515
+ });
516
+
517
+ it('processes complex schemas correctly', function () {
518
+ // Setup complex schema checkers
519
+ const checkers: SchemaCheckers = {
520
+ requiredKeys: [],
521
+ required: {
522
+ 'id': {
523
+ is: (x) => typeof x === 'number',
524
+ base: (x) => x.toString()
525
+ }
526
+ },
527
+ optional: {
528
+ 'name': {
529
+ is: (x) => typeof x === 'string',
530
+ base: (x) => x.toUpperCase()
531
+ }
532
+ },
533
+ patterns: [
534
+ {
535
+ re: new RegExp('^meta_.*$'),
536
+ funcs: {
537
+ is: (x) => typeof x === 'string',
538
+ base: (x) => x.substring(0, 10) // First 10 chars
539
+ }
540
+ }
541
+ ],
542
+ additional: {
543
+ is: (x) => typeof x === 'boolean',
544
+ base: (x) => !x // Invert boolean
545
+ }
546
+ };
547
+
548
+ // Test with complex object
549
+ const result = baseSchema({
550
+ id: 1,
551
+ name: 'test',
552
+ meta_created: '2023-01-01T12:34:56Z',
553
+ random_field: true
554
+ }, checkers);
555
+
556
+ expect(result).to.deep.equal({
557
+ id: '1',
558
+ name: 'TEST',
559
+ meta_created: '2023-01-01',
560
+ random_field: false
561
+ });
562
+ });
563
+
564
+ it('handles multiple pattern matches and precedence', function () {
565
+ const checkers: SchemaCheckers = {
566
+ requiredKeys: [],
567
+ required: {},
568
+ optional: {},
569
+ patterns: [
570
+ {
571
+ re: new RegExp('^test_.*$'),
572
+ funcs: {
573
+ is: (x) => typeof x === 'string',
574
+ base: (x) => `first:${x}`
575
+ }
576
+ },
577
+ {
578
+ re: new RegExp('.*_suffix$'),
579
+ funcs: {
580
+ is: (x) => typeof x === 'string',
581
+ base: (x) => `second:${x}`
582
+ }
583
+ }
584
+ ]
585
+ };
586
+
587
+ // Should use first matching pattern
588
+ const result = baseSchema({ test_field_suffix: 'value' }, checkers);
589
+ expect(result).to.deep.equal({ test_field_suffix: 'first:value' });
590
+ });
591
+
592
+ it('handles field precedence in processing', function () {
593
+ const checkers: SchemaCheckers = {
594
+ requiredKeys: [],
595
+ required: {
596
+ 'field': {
597
+ is: (x) => typeof x === 'string',
598
+ base: (x) => `required:${x}`
599
+ }
600
+ },
601
+ optional: {
602
+ 'field': { // Same field - should be ignored
603
+ is: (x) => typeof x === 'string',
604
+ base: (x) => `optional:${x}`
605
+ }
606
+ },
607
+ patterns: [
608
+ {
609
+ re: new RegExp('^field$'),
610
+ funcs: {
611
+ is: (x) => typeof x === 'string',
612
+ base: (x) => `pattern:${x}`
613
+ }
614
+ }
615
+ ],
616
+ additional: {
617
+ is: (x) => typeof x === 'string',
618
+ base: (x) => `additional:${x}`
619
+ }
620
+ };
621
+
622
+ const result = baseSchema({ field: 'test' }, checkers);
623
+ expect(result).to.deep.equal({ field: 'required:test' });
624
+ });
625
+
626
+ it('handles empty configurations and edge cases', function () {
627
+ // Empty checkers
628
+ const emptyCheckers: SchemaCheckers = {
629
+ requiredKeys: [],
630
+ required: {},
631
+ optional: {},
632
+ patterns: []
633
+ };
634
+ expect(baseSchema({ field: 'value' }, emptyCheckers)).to.deep.equal({});
635
+
636
+ // Checkers with empty patterns array
637
+ const noPatternCheckers: SchemaCheckers = {
638
+ requiredKeys: [],
639
+ required: {},
640
+ optional: {},
641
+ patterns: [],
642
+ additional: {
643
+ is: (x) => true,
644
+ base: (x) => `processed:${x}`
645
+ }
646
+ };
647
+ const result = baseSchema({ field: 'value' }, noPatternCheckers);
648
+ expect(result).to.deep.equal({ field: 'processed:value' });
649
+ });
650
+
651
+ it('handles objects with special properties correctly', function () {
652
+ const checkers: SchemaCheckers = {
653
+ requiredKeys: [],
654
+ required: {},
655
+ optional: {},
656
+ patterns: [],
657
+ additional: {
658
+ is: (x) => true,
659
+ base: (x) => `processed:${x}`
660
+ }
661
+ };
662
+
663
+ // Object with null prototype
664
+ const nullProtoObj = Object.create(null);
665
+ nullProtoObj.prop = 'value';
666
+ const result = baseSchema(nullProtoObj, checkers);
667
+ expect(result).to.deep.equal({ prop: 'processed:value' });
668
+ });
669
+ });
670
+
671
+ // Integration tests combining multiple functions
672
+ describe('Integration Tests', function () {
673
+ it('combines addJsonBody with realistic API request scenario', function () {
674
+ // Simulate preparing an API request with complex data
675
+ const target: Bodyful = {};
676
+ const requestData = {
677
+ user: {
678
+ id: str2integer('123'),
679
+ score: str2float('98.5')
680
+ },
681
+ metadata: {
682
+ timestamp: new Date().toISOString(),
683
+ version: '1.0'
684
+ }
685
+ };
686
+ const headers: String2String = {
687
+ 'Authorization': 'Bearer token123',
688
+ 'X-Client-Version': '2.1.0'
689
+ };
690
+
691
+ addJsonBody(target, requestData, headers);
692
+
693
+ const td = new TextDecoder();
694
+ const decodedBody = JSON.parse(td.decode(target.body));
695
+ expect(decodedBody.user.id).to.equal(123);
696
+ expect(decodedBody.user.score).to.equal(98.5);
697
+ expect(headers['Content-Type']).to.equal('application/json');
698
+ expect(headers['Authorization']).to.equal('Bearer token123');
699
+ });
700
+
701
+ it('validates and processes API response with schema functions', function () {
702
+ // Simulate processing an API response
703
+ const apiResponse = {
704
+ user_id: 42,
705
+ user_name: 'john_doe',
706
+ meta_created: '2023-01-01T10:30:00Z',
707
+ meta_updated: '2023-01-02T15:45:00Z',
708
+ is_active: true,
709
+ score: 98.5
710
+ };
711
+
712
+ const checkers: SchemaCheckers = {
713
+ requiredKeys: ['user_id', 'user_name'],
714
+ required: {
715
+ 'user_id': {
716
+ is: (x) => typeof x === 'number',
717
+ base: (x) => x.toString()
718
+ },
719
+ 'user_name': {
720
+ is: (x) => typeof x === 'string',
721
+ base: (x) => x.replace('_', ' ').toUpperCase()
722
+ }
723
+ },
724
+ optional: {
725
+ 'score': {
726
+ is: (x) => typeof x === 'number',
727
+ base: (x) => Math.round(x)
728
+ }
729
+ },
730
+ patterns: [
731
+ {
732
+ re: new RegExp('^meta_.*$'),
733
+ funcs: {
734
+ is: (x) => typeof x === 'string',
735
+ base: (x) => new Date(x).getTime() // Convert to timestamp
736
+ }
737
+ }
738
+ ],
739
+ additional: {
740
+ is: (x) => typeof x === 'boolean',
741
+ base: (x) => x ? 1 : 0 // Convert boolean to number
742
+ }
743
+ };
744
+
745
+ // First validate the response
746
+ expect(isSchema(apiResponse, checkers)).to.be.true;
747
+
748
+ // Then process it
749
+ const processed = baseSchema(apiResponse, checkers);
750
+ expect(processed).to.deep.equal({
751
+ user_id: '42',
752
+ user_name: 'JOHN DOE',
753
+ score: 99,
754
+ meta_created: new Date('2023-01-01T10:30:00Z').getTime(),
755
+ meta_updated: new Date('2023-01-02T15:45:00Z').getTime(),
756
+ is_active: 1
757
+ });
758
+ });
759
+
760
+ it('handles string conversion in realistic data transformation pipeline', function () {
761
+ // Simulate processing query parameters or form data
762
+ const queryParams = {
763
+ page: '1',
764
+ limit: '25',
765
+ score_min: '75.5',
766
+ score_max: '100.0',
767
+ active: 'true'
768
+ };
769
+
770
+ // Transform query parameters to proper types
771
+ const processedParams = {
772
+ page: str2integer(queryParams.page),
773
+ limit: str2integer(queryParams.limit),
774
+ score_min: str2float(queryParams.score_min),
775
+ score_max: str2float(queryParams.score_max),
776
+ active: queryParams.active === 'true'
777
+ };
778
+
779
+ expect(processedParams).to.deep.equal({
780
+ page: 1,
781
+ limit: 25,
782
+ score_min: 75.5,
783
+ score_max: 100.0,
784
+ active: true
785
+ });
786
+
787
+ // Use processed params in request body
788
+ const target: Bodyful = {};
789
+ const headers: String2String = {};
790
+ addJsonBody(target, processedParams, headers);
791
+
792
+ const td = new TextDecoder();
793
+ const decodedBody = JSON.parse(td.decode(target.body));
794
+ expect(decodedBody.page).to.equal(1);
795
+ expect(decodedBody.score_min).to.equal(75.5);
796
+ });
797
+
798
+ it('handles malformed data gracefully in integration scenario', function () {
799
+ // Test with invalid string inputs that would produce NaN
800
+ const malformedData = {
801
+ id: str2integer('not-a-number'),
802
+ score: str2float('invalid-float'),
803
+ valid_field: 'test'
804
+ };
805
+
806
+ expect(malformedData.id).to.be.NaN;
807
+ expect(malformedData.score).to.be.NaN;
808
+
809
+ // Schema validation should handle NaN values appropriately
810
+ const checkers: SchemaCheckers = {
811
+ requiredKeys: [],
812
+ required: {
813
+ 'id': {
814
+ is: (x) => typeof x === 'number' && !isNaN(x),
815
+ base: (x) => x
816
+ }
817
+ },
818
+ optional: {},
819
+ patterns: [],
820
+ additional: {
821
+ is: (x) => typeof x === 'string',
822
+ base: (x) => x
823
+ }
824
+ };
825
+
826
+ // Should be invalid due to NaN id
827
+ expect(isSchema(malformedData, checkers)).to.be.false;
828
+
829
+ // But if we remove the invalid fields, it should work
830
+ const cleanData = { valid_field: 'test' };
831
+ expect(isSchema(cleanData, checkers)).to.be.true;
832
+ expect(baseSchema(cleanData, checkers)).to.deep.equal({ valid_field: 'test' });
833
+ });
834
+ });
835
+
836
+ describe('setExtras', function () {
837
+ it('returns empty object when thisExtras is undefined and extras is empty', function () {
838
+ const result = setExtras(undefined, {});
839
+ expect(result).to.deep.equal({});
840
+ });
841
+
842
+ it('returns empty object when both thisExtras and extras have no query or headers', function () {
843
+ const result = setExtras({}, {});
844
+ expect(result).to.deep.equal({});
845
+ });
846
+
847
+ it('includes query when only extras has query', function () {
848
+ const result = setExtras(undefined, { query: { key: 'value' } });
849
+ expect(result.query).to.deep.equal({ key: 'value' });
850
+ expect(result.headers).to.be.undefined;
851
+ });
852
+
853
+ it('includes query when only thisExtras has query', function () {
854
+ const result = setExtras({ query: { key: 'value' } }, {});
855
+ expect(result.query).to.deep.equal({ key: 'value' });
856
+ });
857
+
858
+ it('merges query from both thisExtras and extras, extras overrides thisExtras', function () {
859
+ const result = setExtras(
860
+ { query: { a: 'from-this', b: 'from-this' } },
861
+ { query: { b: 'from-extras', c: 'from-extras' } }
862
+ );
863
+ expect(result.query).to.deep.equal({ a: 'from-this', b: 'from-extras', c: 'from-extras' });
864
+ });
865
+
866
+ it('includes headers when only extras has headers', function () {
867
+ const result = setExtras(undefined, { headers: { Authorization: 'Bearer token' } });
868
+ expect(result.headers).to.deep.equal({ Authorization: 'Bearer token' });
869
+ expect(result.query).to.be.undefined;
870
+ });
871
+
872
+ it('includes headers when only thisExtras has headers', function () {
873
+ const result = setExtras({ headers: { Authorization: 'Bearer token' } }, {});
874
+ expect(result.headers).to.deep.equal({ Authorization: 'Bearer token' });
875
+ });
876
+
877
+ it('merges headers from both thisExtras and extras, extras overrides thisExtras', function () {
878
+ const result = setExtras(
879
+ { headers: { Accept: 'text/html', Authorization: 'old-token' } },
880
+ { headers: { Authorization: 'new-token', 'X-Custom': 'value' } }
881
+ );
882
+ expect(result.headers).to.deep.equal({ Accept: 'text/html', Authorization: 'new-token', 'X-Custom': 'value' });
883
+ });
884
+
885
+ it('merges both query and headers together', function () {
886
+ const result = setExtras(
887
+ { query: { page: '1' }, headers: { Accept: 'application/json' } },
888
+ { query: { limit: '10' }, headers: { Authorization: 'Bearer token' } }
889
+ );
890
+ expect(result.query).to.deep.equal({ page: '1', limit: '10' });
891
+ expect(result.headers).to.deep.equal({ Accept: 'application/json', Authorization: 'Bearer token' });
892
+ });
893
+
894
+ it('does not include query key when neither side has it', function () {
895
+ const result = setExtras(
896
+ { headers: { Accept: 'application/json' } },
897
+ { headers: { Authorization: 'Bearer token' } }
898
+ );
899
+ expect(result).not.to.have.property('query');
900
+ });
901
+
902
+ it('does not include headers key when neither side has it', function () {
903
+ const result = setExtras(
904
+ { query: { page: '1' } },
905
+ { query: { limit: '10' } }
906
+ );
907
+ expect(result).not.to.have.property('headers');
908
+ });
909
+
910
+ it('does not mutate the inputs', function () {
911
+ const thisExtras = { query: { a: '1' } };
912
+ const extras = { query: { b: '2' } };
913
+ setExtras(thisExtras, extras);
914
+ expect(thisExtras).to.deep.equal({ query: { a: '1' } });
915
+ expect(extras).to.deep.equal({ query: { b: '2' } });
916
+ });
917
+ });
918
+
919
+ describe('query', function () {
920
+ it('appends nothing when both classExtras and thisExtras are undefined', function () {
921
+ const out: Array<string> = [];
922
+ query(out, undefined, undefined, []);
923
+ expect(out).to.deep.equal([]);
924
+ });
925
+
926
+ it('appends nothing when both classExtras and thisExtras have no query', function () {
927
+ const out: Array<string> = [];
928
+ query(out, {}, {}, []);
929
+ expect(out).to.deep.equal([]);
930
+ });
931
+
932
+ it('appends key=value from classExtras.query', function () {
933
+ const out: Array<string> = [];
934
+ query(out, { query: { key: 'value' } }, undefined, []);
935
+ expect(out).to.deep.equal(['key=value']);
936
+ });
937
+
938
+ it('appends key=value from thisExtras.query', function () {
939
+ const out: Array<string> = [];
940
+ query(out, undefined, { query: { key: 'value' } }, []);
941
+ expect(out).to.deep.equal(['key=value']);
942
+ });
943
+
944
+ it('thisExtras.query overrides classExtras.query for the same key', function () {
945
+ const out: Array<string> = [];
946
+ query(out, { query: { key: 'class-value' } }, { query: { key: 'this-value' } }, []);
947
+ expect(out).to.include('key=this-value');
948
+ expect(out).not.to.include('key=class-value');
949
+ });
950
+
951
+ it('merges keys from both classExtras and thisExtras', function () {
952
+ const out: Array<string> = [];
953
+ query(out, { query: { a: '1' } }, { query: { b: '2' } }, []);
954
+ expect(out).to.include('a=1');
955
+ expect(out).to.include('b=2');
956
+ });
957
+
958
+ it('skips forbidden keys', function () {
959
+ const out: Array<string> = [];
960
+ query(out, { query: { skip: 'me', keep: 'this' } }, undefined, ['skip']);
961
+ expect(out).to.deep.equal(['keep=this']);
962
+ });
963
+
964
+ it('skips all entries when all keys are forbidden', function () {
965
+ const out: Array<string> = [];
966
+ query(out, { query: { a: '1', b: '2' } }, undefined, ['a', 'b']);
967
+ expect(out).to.deep.equal([]);
968
+ });
969
+
970
+ it('URI-encodes special characters in keys and values', function () {
971
+ const out: Array<string> = [];
972
+ query(out, { query: { 'key with spaces': 'value&special=chars' } }, undefined, []);
973
+ expect(out).to.deep.equal(['key%20with%20spaces=value%26special%3Dchars']);
974
+ });
975
+
976
+ it('preserves existing entries in the out array', function () {
977
+ const out: Array<string> = ['existing=entry'];
978
+ query(out, { query: { new: 'entry' } }, undefined, []);
979
+ expect(out).to.deep.equal(['existing=entry', 'new=entry']);
980
+ });
981
+ });
982
+
983
+ describe('headers', function () {
984
+ it('returns the same Headers object passed in', function () {
985
+ const out = new Headers();
986
+ const result = headers(out, undefined, undefined, []);
987
+ expect(result).to.equal(out);
988
+ });
989
+
990
+ it('returns unchanged Headers when both classExtras and thisExtras are undefined', function () {
991
+ const out = new Headers();
992
+ headers(out, undefined, undefined, []);
993
+ expect(Array.from(out.entries())).to.deep.equal([]);
994
+ });
995
+
996
+ it('returns unchanged Headers when both classExtras and thisExtras have no headers', function () {
997
+ const out = new Headers();
998
+ headers(out, {}, {}, []);
999
+ expect(Array.from(out.entries())).to.deep.equal([]);
1000
+ });
1001
+
1002
+ it('sets headers from classExtras.headers', function () {
1003
+ const out = new Headers();
1004
+ headers(out, { headers: { 'X-Custom': 'value' } }, undefined, []);
1005
+ expect(out.get('x-custom')).to.equal('value');
1006
+ });
1007
+
1008
+ it('sets headers from thisExtras.headers', function () {
1009
+ const out = new Headers();
1010
+ headers(out, undefined, { headers: { 'X-Custom': 'value' } }, []);
1011
+ expect(out.get('x-custom')).to.equal('value');
1012
+ });
1013
+
1014
+ it('thisExtras.headers overrides classExtras.headers for the same key', function () {
1015
+ const out = new Headers();
1016
+ headers(out, { headers: { Authorization: 'class-token' } }, { headers: { Authorization: 'this-token' } }, []);
1017
+ expect(out.get('authorization')).to.equal('this-token');
1018
+ });
1019
+
1020
+ it('merges keys from both classExtras and thisExtras', function () {
1021
+ const out = new Headers();
1022
+ headers(out, { headers: { Accept: 'application/json' } }, { headers: { Authorization: 'Bearer token' } }, []);
1023
+ expect(out.get('accept')).to.equal('application/json');
1024
+ expect(out.get('authorization')).to.equal('Bearer token');
1025
+ });
1026
+
1027
+ it('skips forbidden keys', function () {
1028
+ const out = new Headers();
1029
+ headers(out, { headers: { 'Content-Type': 'text/plain', 'X-Custom': 'value' } }, undefined, ['Content-Type']);
1030
+ expect(out.get('content-type')).to.be.null;
1031
+ expect(out.get('x-custom')).to.equal('value');
1032
+ });
1033
+
1034
+ it('skips all entries when all keys are forbidden', function () {
1035
+ const out = new Headers();
1036
+ headers(out, { headers: { 'Content-Type': 'text/plain' } }, undefined, ['Content-Type']);
1037
+ expect(Array.from(out.entries())).to.deep.equal([]);
1038
+ });
1039
+
1040
+ it('preserves existing headers on the Headers object', function () {
1041
+ const out = new Headers({ 'X-Pre-Existing': 'pre-existing' });
1042
+ headers(out, { headers: { 'X-New': 'new' } }, undefined, []);
1043
+ expect(out.get('x-pre-existing')).to.equal('pre-existing');
1044
+ expect(out.get('x-new')).to.equal('new');
1045
+ });
1046
+ });
1047
+
1048
+ describe('getLoader', function () {
1049
+ it('returns blob loader when contentType is null', function () {
1050
+ const classLoaders: LoaderMap = new Map();
1051
+ const loader = getLoader(null, undefined, undefined, classLoaders);
1052
+ // Verify it's the blob loader by calling it with a mock Response
1053
+ const mockBlob = new Blob(['test']);
1054
+ const mockResponse = new Response(mockBlob);
1055
+ return loader(mockResponse).then(result => {
1056
+ expect(result).to.be.instanceOf(Blob);
1057
+ });
1058
+ });
1059
+
1060
+ // Cases where classArgsLoader is undefined.
1061
+
1062
+ it('returns loader from loaders map when content type matches', function () {
1063
+ const expectedResult = { parsed: true };
1064
+ const customLoader: LoaderFunction = (_response) => Promise.resolve(expectedResult);
1065
+ const loaders: LoaderMap = new Map([['application/json', customLoader]]);
1066
+ const classLoaders: LoaderMap = new Map();
1067
+ const loader = getLoader('application/json', loaders, undefined, classLoaders);
1068
+ expect(loader).to.equal(customLoader);
1069
+ });
1070
+
1071
+ it('returns loader from classLoaders when not in others', function () {
1072
+ const expectedResult = { parsed: true };
1073
+ const classLoader: LoaderFunction = (_response) => Promise.resolve(expectedResult);
1074
+ const loaders: LoaderMap = new Map();
1075
+ const classLoaders: LoaderMap = new Map([['application/json', classLoader]]);
1076
+ const loader = getLoader('application/json', loaders, undefined, classLoaders);
1077
+ expect(loader).to.equal(classLoader);
1078
+ });
1079
+
1080
+ it('loaders map takes precedence over classLoaders', function () {
1081
+ const loaderResult = { source: 'loaders' };
1082
+ const classLoaderResult = { source: 'classLoaders' };
1083
+ const customLoader: LoaderFunction = (_response) => Promise.resolve(loaderResult);
1084
+ const classLoader: LoaderFunction = (_response) => Promise.resolve(classLoaderResult);
1085
+ const loaders: LoaderMap = new Map([['application/json', customLoader]]);
1086
+ const classLoaders: LoaderMap = new Map([['application/json', classLoader]]);
1087
+ const loader = getLoader('application/json', loaders, undefined, classLoaders);
1088
+ expect(loader).to.equal(customLoader);
1089
+ });
1090
+
1091
+ it('returns blob loader when content type is unknown in both maps', function () {
1092
+ const loaders: LoaderMap = new Map([['application/json', (_r) => Promise.resolve({})]]);
1093
+ const classLoaders: LoaderMap = new Map([['text/plain', (_r) => Promise.resolve({})]]);
1094
+ const loader = getLoader('text/html', loaders, undefined, classLoaders);
1095
+ const mockResponse = new Response(new Blob(['test']));
1096
+ return loader(mockResponse).then(result => {
1097
+ expect(result).to.be.instanceOf(Blob);
1098
+ });
1099
+ });
1100
+
1101
+ it('returns blob loader when loaders is undefined and content type not in classLoaders', function () {
1102
+ const classLoaders: LoaderMap = new Map([['application/json', (_r) => Promise.resolve({})]]);
1103
+ const loader = getLoader('text/html', undefined, undefined, classLoaders);
1104
+ const mockResponse = new Response(new Blob(['test']));
1105
+ return loader(mockResponse).then(result => {
1106
+ expect(result).to.be.instanceOf(Blob);
1107
+ });
1108
+ });
1109
+
1110
+ it('falls through to classLoaders when loaders is undefined', function () {
1111
+ const classLoader: LoaderFunction = (_response) => Promise.resolve('class-result');
1112
+ const classLoaders: LoaderMap = new Map([['text/plain', classLoader]]);
1113
+ const loader = getLoader('text/plain', undefined, undefined, classLoaders);
1114
+ expect(loader).to.equal(classLoader);
1115
+ });
1116
+
1117
+ it('matches content type case-insensitively (lowercased)', function () {
1118
+ const customLoader: LoaderFunction = (_response) => Promise.resolve({});
1119
+ const loaders: LoaderMap = new Map([['application/json', customLoader]]);
1120
+ const classLoaders: LoaderMap = new Map();
1121
+ // Content type with mixed case should be lowercased before lookup
1122
+ const loader = getLoader('Application/JSON', loaders, undefined, classLoaders);
1123
+ expect(loader).to.equal(customLoader);
1124
+ });
1125
+
1126
+ it('returns the loader that calls response.blob() for the blob loader', function () {
1127
+ const classLoaders: LoaderMap = new Map();
1128
+ const loader = getLoader(null, undefined, undefined, classLoaders);
1129
+ const mockBlob = new Blob(['hello']);
1130
+ const mockResponse = new Response(mockBlob);
1131
+ return loader(mockResponse).then(result => {
1132
+ expect(result).to.be.instanceOf(Blob);
1133
+ });
1134
+ });
1135
+
1136
+ it('returns loader content type has matching charset', function () {
1137
+ const expectedResult = { parsed: true };
1138
+ const customLoader: LoaderFunction = (_response) => Promise.resolve(expectedResult);
1139
+ const loaders: LoaderMap = new Map([['application/json; charset=utf-8', customLoader]]);
1140
+ const classLoaders: LoaderMap = new Map();
1141
+ let loader = getLoader('application/json; charset=utf-8', loaders, undefined, classLoaders);
1142
+ expect(loader).to.equal(customLoader);
1143
+ loader = getLoader('application/JSON ; charset=UTF-8', loaders, undefined, classLoaders);
1144
+ expect(loader).to.equal(customLoader);
1145
+ loader = getLoader('application/JSON;charset=UTF-8', loaders, undefined, classLoaders);
1146
+ expect(loader).to.equal(customLoader);
1147
+ });
1148
+
1149
+ // Cases where classArgsLoader is defined.
1150
+
1151
+ it('returns loader from classArgsLoaders when not in loaders', function () {
1152
+ const unexpectedResult = { parsed: false };
1153
+ const expectedResult = { parsed: true };
1154
+ const classLoader: LoaderFunction = (_response) => Promise.resolve(unexpectedResult);
1155
+ const classArgsLoader: LoaderFunction = (_response) => Promise.resolve(expectedResult);
1156
+ const loaders: LoaderMap = new Map();
1157
+ const classArgsLoaders: LoaderMap = new Map([['application/json', classArgsLoader]]);
1158
+ const classLoaders: LoaderMap = new Map([['application/json', classLoader]]);
1159
+ const loader = getLoader('application/json', loaders, classArgsLoaders, classLoaders);
1160
+ expect(loader).to.equal(classArgsLoader);
1161
+ });
1162
+
1163
+ it('loaders map takes precedence over classLoaders', function () {
1164
+ const loaderResult = { source: 'loaders' };
1165
+ const classLoaderResult = { source: 'classLoaders' };
1166
+ const customLoader: LoaderFunction = (_response) => Promise.resolve(loaderResult);
1167
+ const classArgsLoader: LoaderFunction = (_response) => Promise.resolve(classLoaderResult);
1168
+ const classLoader: LoaderFunction = (_response) => Promise.resolve(classLoaderResult);
1169
+ const loaders: LoaderMap = new Map([['application/json', customLoader]]);
1170
+ const classLoaders: LoaderMap = new Map([['application/json', classLoader]]);
1171
+ const classArgsLoaders: LoaderMap = new Map([['application/json', classArgsLoader]]);
1172
+ const loader = getLoader('application/json', loaders, classArgsLoaders, classLoaders);
1173
+ expect(loader).to.equal(customLoader);
1174
+ });
1175
+
1176
+ it('returns blob loader when content type is unknown in all maps', function () {
1177
+ const loaders: LoaderMap = new Map([['application/json', (_r) => Promise.resolve({})]]);
1178
+ const classArgsLoaders: LoaderMap = new Map([['application/xml', (_r) => Promise.resolve({})]]);
1179
+ const classLoaders: LoaderMap = new Map([['text/plain', (_r) => Promise.resolve({})]]);
1180
+ const loader = getLoader('text/html', loaders, classArgsLoaders, classLoaders);
1181
+ const mockResponse = new Response(new Blob(['test']));
1182
+ return loader(mockResponse).then(result => {
1183
+ expect(result).to.be.instanceOf(Blob);
1184
+ });
1185
+ });
1186
+
1187
+ it('returns blob loader when loaders is undefined and content type not in class(Args)Loaders', function () {
1188
+ const classLoaders: LoaderMap = new Map([['application/json', (_r) => Promise.resolve({})]]);
1189
+ const classArgsLoaders: LoaderMap = new Map([['application/xml', (_r) => Promise.resolve({})]]);
1190
+ const loader = getLoader('text/html', undefined, classArgsLoaders, classLoaders);
1191
+ const mockResponse = new Response(new Blob(['test']));
1192
+ return loader(mockResponse).then(result => {
1193
+ expect(result).to.be.instanceOf(Blob);
1194
+ });
1195
+ });
1196
+
1197
+ it('falls through to classArgsLoaders when loaders is undefined', function () {
1198
+ const classArgsLoader: LoaderFunction = (_response) => Promise.resolve('args-result');
1199
+ const classLoader: LoaderFunction = (_response) => Promise.resolve('class-result');
1200
+ const classArglLoaders: LoaderMap = new Map([['text/plain', classArgsLoader]]);
1201
+ const classLoaders: LoaderMap = new Map([['text/plain', classLoader]]);
1202
+ const loader = getLoader('text/plain', undefined, classArglLoaders, classLoaders);
1203
+ expect(loader).to.equal(classArgsLoader);
1204
+ });
1205
+
1206
+ it('matches content type case-insensitively (lowercased)', function () {
1207
+ const customLoader: LoaderFunction = (_response) => Promise.resolve({});
1208
+ const loaders: LoaderMap = new Map([['application/json', customLoader]]);
1209
+ const classLoaders: LoaderMap = new Map();
1210
+ // Content type with mixed case should be lowercased before lookup
1211
+ const loader = getLoader('Application/JSON', loaders, classLoaders, classLoaders);
1212
+ expect(loader).to.equal(customLoader);
1213
+ });
1214
+ });