Skip to content

Commit 3b11f73

Browse files
committed
feat: implement MapTemplate random generator
- Add MapTemplate type to TemplateNode union and TemplateShorthand - Implement generateMap method in TemplateJson class - Handle 'map' shorthand in generate method - Support custom key tokens, value templates, and min/max constraints - Respect maxNodes limit for map generation - Add comprehensive test suite covering all map functionality - Add demo showcasing various map use cases - Fix import type declarations for better tree-shaking
1 parent f47796d commit 3b11f73

File tree

9 files changed

+387
-115
lines changed

9 files changed

+387
-115
lines changed

src/RandomJson.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {randomString, Token} from './string';
1+
import {randomString, type Token} from './string';
22

33
type JsonValue = unknown;
44

@@ -170,7 +170,7 @@ export class RandomJson {
170170
: Math.random() < 0.2
171171
? Math.round(0xffff * (2 * Math.random() - 1))
172172
: Math.round(Number.MAX_SAFE_INTEGER * (2 * Math.random() - 1));
173-
if (num === -0) return 0;
173+
if (num === 0) return 0;
174174
return num;
175175
}
176176

src/__demos__/map-demo.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Run with:
3+
*
4+
* npx ts-node src/__demos__/map-demo.ts
5+
*/
6+
7+
import {TemplateJson} from '../structured/TemplateJson';
8+
9+
console.log('=== Map Template Demo ===\n');
10+
11+
// Basic map usage
12+
console.log('1. Basic map with shorthand:');
13+
const basicMap = TemplateJson.gen('map');
14+
console.log(JSON.stringify(basicMap, null, 2));
15+
16+
// Map with custom key tokens and values
17+
console.log('\n2. Map with custom user IDs and profile data:');
18+
const userMap = TemplateJson.gen([
19+
'map',
20+
['list', 'user_', ['pick', '001', '002', '003', '004', '005']],
21+
[
22+
'obj',
23+
[
24+
['name', ['str', ['list', ['pick', 'John', 'Jane', 'Bob', 'Alice'], ' ', ['pick', 'Doe', 'Smith', 'Johnson']]]],
25+
['age', ['int', 18, 65]],
26+
['active', 'bool'],
27+
],
28+
],
29+
2,
30+
4,
31+
]);
32+
console.log(JSON.stringify(userMap, null, 2));
33+
34+
// Map with complex nested structures
35+
console.log('\n3. Map with API endpoints and their configurations:');
36+
const apiMap = TemplateJson.gen([
37+
'map',
38+
['list', 'api/', ['pick', 'users', 'posts', 'comments', 'auth']],
39+
[
40+
'obj',
41+
[
42+
['method', ['str', ['pick', 'GET', 'POST', 'PUT', 'DELETE']]],
43+
['timeout', ['int', 1000, 5000]],
44+
['retries', ['int', 0, 3]],
45+
['auth_required', 'bool'],
46+
],
47+
],
48+
3,
49+
3,
50+
]);
51+
console.log(JSON.stringify(apiMap, null, 2));
52+
53+
// Map with guaranteed size
54+
console.log('\n4. Map with exactly 2 entries:');
55+
const fixedMap = TemplateJson.gen([
56+
'map',
57+
['pick', 'key1', 'key2', 'key3'],
58+
['or', 'str', 'int', 'bool'],
59+
2,
60+
2,
61+
]);
62+
console.log(JSON.stringify(fixedMap, null, 2));

src/__tests__/RandomJson.spec.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,7 @@ test('random strings can be converted to UTF-8', () => {
124124
test('can specify string generation schema', () => {
125125
const str = RandomJson.generate({
126126
rootNode: 'string',
127-
strings: [
128-
'list',
129-
['repeat', 2, 2, 'xx'],
130-
['pick', ['y']],
131-
],
127+
strings: ['list', ['repeat', 2, 2, 'xx'], ['pick', ['y']]],
132128
});
133129
expect(str).toBe('xxxxy');
134130
});

src/__tests__/string.spec.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {randomString, Token} from '../string';
1+
import {randomString, type Token} from '../string';
22
import {deterministic} from '../util';
33

44
describe('randomString', () => {
@@ -43,11 +43,7 @@ describe('randomString', () => {
4343
});
4444

4545
it('can nest picks', () => {
46-
const token: Token = [
47-
'pick',
48-
['pick', 'monkey', 'dog', 'cat'],
49-
['pick', 'banana', 'apple'],
50-
];
46+
const token: Token = ['pick', ['pick', 'monkey', 'dog', 'cat'], ['pick', 'banana', 'apple']];
5147
const str = deterministic(123, () => randomString(token));
5248
expect(str).toBe('dog');
5349
});

src/structured/TemplateJson.ts

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1-
import {int} from "../number";
2-
import {randomString} from "../string";
3-
import {clone} from "../util";
4-
import * as templates from "./templates";
5-
import type {ArrayTemplate, BooleanTemplate, FloatTemplate, IntegerTemplate, LiteralTemplate, NumberTemplate, ObjectTemplate, OrTemplate, StringTemplate, Template, TemplateNode, TemplateShorthand} from "./types";
1+
import {int} from '../number';
2+
import {randomString} from '../string';
3+
import {clone} from '../util';
4+
import * as templates from './templates';
5+
import type {
6+
ArrayTemplate,
7+
BooleanTemplate,
8+
FloatTemplate,
9+
IntegerTemplate,
10+
LiteralTemplate,
11+
MapTemplate,
12+
NumberTemplate,
13+
ObjectTemplate,
14+
OrTemplate,
15+
StringTemplate,
16+
Template,
17+
TemplateNode,
18+
TemplateShorthand,
19+
} from './types';
620

721
export interface TemplateJsonOpts {
822
/**
@@ -23,7 +37,10 @@ export class TemplateJson {
2337
protected nodes: number = 0;
2438
protected maxNodes: number;
2539

26-
constructor(public readonly template: Template = templates.nil, public readonly opts: TemplateJsonOpts = {}) {
40+
constructor(
41+
public readonly template: Template = templates.nil,
42+
public readonly opts: TemplateJsonOpts = {},
43+
) {
2744
this.maxNodes = opts.maxNodes ?? 100;
2845
}
2946

@@ -34,13 +51,39 @@ export class TemplateJson {
3451
protected generate(tpl: Template): unknown {
3552
this.nodes++;
3653
while (typeof tpl === 'function') tpl = tpl();
37-
const template: TemplateNode = typeof tpl === 'string' ? [tpl] : tpl;
54+
if (typeof tpl === 'string') {
55+
switch (tpl) {
56+
case 'arr':
57+
return this.generateArray(['arr']);
58+
case 'obj':
59+
return this.generateObject(['obj']);
60+
case 'map':
61+
return this.generateMap(['map', null]);
62+
case 'str':
63+
return this.generateString(['str']);
64+
case 'num':
65+
return this.generateNumber(['num']);
66+
case 'int':
67+
return this.generateInteger(['int']);
68+
case 'float':
69+
return this.generateFloat(['float']);
70+
case 'bool':
71+
return this.generateBoolean(['bool']);
72+
case 'nil':
73+
return null;
74+
default:
75+
throw new Error(`Unknown template shorthand: ${tpl}`);
76+
}
77+
}
78+
const template: TemplateNode = tpl;
3879
const type = template[0];
3980
switch (type) {
4081
case 'arr':
4182
return this.generateArray(template as ArrayTemplate);
4283
case 'obj':
4384
return this.generateObject(template as ObjectTemplate);
85+
case 'map':
86+
return this.generateMap(template as MapTemplate);
4487
case 'str':
4588
return this.generateString(template as StringTemplate);
4689
case 'num':
@@ -95,12 +138,24 @@ export class TemplateJson {
95138
return result;
96139
}
97140

141+
protected generateMap(template: MapTemplate): Record<string, unknown> {
142+
const [, keyToken, valueTemplate = 'nil', min = 0, max = 5] = template;
143+
const length = this.minmax(min, max);
144+
const result: Record<string, unknown> = {};
145+
for (let i = 0; i < length; i++) {
146+
const key = randomString(keyToken ?? templates.tokensObjectKey);
147+
const value = this.generate(valueTemplate);
148+
result[key] = value;
149+
}
150+
return result;
151+
}
152+
98153
protected generateString(template: StringTemplate): string {
99154
return randomString(template[1] ?? templates.tokensHelloWorld);
100155
}
101156

102157
protected generateNumber([, min, max]: NumberTemplate): number {
103-
if (Math.random() > .5) return this.generateInteger(['int', min, max]);
158+
if (Math.random() > 0.5) return this.generateInteger(['int', min, max]);
104159
else return this.generateFloat(['float', min, max]);
105160
}
106161

0 commit comments

Comments
 (0)