feat: add support for interpolation on mockDataFunctions (#6393)

feat: implement `prepareMockObj` function for enhanced mock data processing in interpolation
This commit is contained in:
Pragadesh-45
2026-01-14 21:58:03 +05:30
committed by GitHub
parent c51381888a
commit b1e6a707bf
2 changed files with 144 additions and 24 deletions

View File

@@ -375,6 +375,62 @@ describe('interpolate - recursive', () => {
"x": "baz bar"
}`);
});
it('should replace variables pointing to mock data functions', () => {
const inputString = 'Timestamp: {{folderVar}}';
const inputObject = {
folderVar: '{{$isoTimestamp}}'
};
const result = interpolate(inputString, inputObject);
// Validate that the result is a valid ISO timestamp
const timestampPattern = /^Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
expect(timestampPattern.test(result)).toBe(true);
});
it('should replace nested variables pointing to mock data functions', () => {
const inputString = 'Random values: {{var1}} and {{var2}}';
const inputObject = {
var1: '{{nestedVar}}',
nestedVar: '{{$randomInt}}',
var2: '{{$randomBoolean}}'
};
const result = interpolate(inputString, inputObject);
// Validate the result
const parts = result.split(' and ');
expect(parts.length).toBe(2);
const randomInt = parts[0].replace('Random values: ', '');
const randomBoolean = parts[1];
// Check if randomInt is a number
expect(!isNaN(Number(randomInt))).toBe(true);
expect(Number(randomInt)).toBeGreaterThanOrEqual(0);
expect(Number(randomInt)).toBeLessThanOrEqual(1000);
// Check if randomBoolean is a boolean
expect(['true', 'false'].includes(randomBoolean)).toBe(true);
});
it('should replace variables pointing to mock data functions with escapeJSONStrings option', () => {
const inputString = '{"timestamp": "{{folderVar}}"}';
const inputObject = {
folderVar: '{{$isoTimestamp}}'
};
const result = interpolate(inputString, inputObject, { escapeJSONStrings: true });
// Should produce valid JSON
expect(() => {
const parsed = JSON.parse(result);
// Validate that the timestamp is a valid ISO timestamp
const timestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
expect(timestampPattern.test(parsed.timestamp)).toBe(true);
}).not.toThrow();
});
});
describe('interpolate - object handling', () => {
@@ -534,6 +590,37 @@ describe('interpolate - mock variable interpolation', () => {
JSON.parse(result); // This should throw an error
}).toThrow();
});
it('should process mock variables in nested objects', () => {
const inputString = '{{user.data}}';
const inputObject = {
user: {
data: {
id: '{{$randomUUID}}',
timestamp: '{{$isoTimestamp}}',
nested: {
randomInt: '{{$randomInt}}'
}
}
}
};
const result = interpolate(inputString, inputObject);
const parsed = JSON.parse(result);
// Validate UUID format
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
expect(uuidPattern.test(parsed.id)).toBe(true);
// Validate ISO timestamp format
const isoTimestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
expect(isoTimestampPattern.test(parsed.timestamp)).toBe(true);
// Validate nested randomInt
expect(!isNaN(Number(parsed.nested.randomInt))).toBe(true);
expect(Number(parsed.nested.randomInt)).toBeGreaterThanOrEqual(0);
expect(Number(parsed.nested.randomInt)).toBeLessThanOrEqual(1000);
});
});
describe('interpolate - Date() handling', () => {

View File

@@ -12,7 +12,58 @@
*/
import { mockDataFunctions } from '../utils/faker-functions';
import { get } from 'lodash-es';
import { get, isPlainObject } from 'lodash-es';
// regex to match {{$keyword}}
const MOCK_PATTERN = /\{\{\$(\w+)\}\}/g;
const JSON_SPECIAL_CHARS = /[\\\n\r\t\"]/;
const escapeJSONString = (str: string): string => {
if (!JSON_SPECIAL_CHARS.test(str)) {
return str;
}
return str
.replace(/\\/g, '\\\\')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t')
.replace(/\"/g, '\\"');
};
const prepareMock = (str: string, escapeJSONStrings: boolean): string => {
return str.replace(MOCK_PATTERN, (match, keyword) => {
let generatedValue = mockDataFunctions[keyword as keyof typeof mockDataFunctions]?.();
if (generatedValue === undefined) {
return match;
}
generatedValue = String(generatedValue);
return escapeJSONStrings ? escapeJSONString(generatedValue) : generatedValue;
});
};
const prepareMockObj = (
obj: Record<string, any>,
escapeJSONStrings: boolean
): Record<string, any> => {
const processed: Record<string, any> = {};
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'string') {
processed[key] = prepareMock(value, escapeJSONStrings);
} else if (isPlainObject(value)) {
// plain object is used to skip special objects like Date, RegExp, etc.
processed[key] = prepareMockObj(value, escapeJSONStrings);
} else {
processed[key] = value;
}
}
return processed;
};
const interpolate = (
str: string,
@@ -25,32 +76,14 @@ const interpolate = (
const { escapeJSONStrings } = options;
const patternRegex = /\{\{\$(\w+)\}\}/g;
str = str.replace(patternRegex, (match, keyword) => {
let replacement = mockDataFunctions[keyword as keyof typeof mockDataFunctions]?.();
if (replacement === undefined) return match;
replacement = String(replacement);
if (!escapeJSONStrings) return replacement;
// All the below chars inside of a JSON String field
// will make it invalid JSON. So we will have to escape them with `\`.
// This is not exhaustive but selective to what faker-js can output.
if (!/[\\\n\r\t\"]/.test(replacement)) return replacement;
return replacement
.replace(/\\/g, '\\\\')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t')
.replace(/\"/g, '\\"');
});
const preparedStr = prepareMock(str, escapeJSONStrings ?? false);
if (!obj || typeof obj !== 'object') {
return str;
return preparedStr;
}
return replace(str, obj);
// process the object with the mock data functions
const preparedObj = prepareMockObj(obj, escapeJSONStrings ?? false);
return replace(preparedStr, preparedObj);
};
const replace = (