- Pre-compile all regex patterns as module-level constants in parser.js and jsonRepair.js - Add format detection caching (lastDetectedFormat, formatCacheHits) to skip redundant checks - Add clearFormatCache() export for cache invalidation - Add comprehensive test suite (33 tests) for parser and jsonRepair modules - Fix regex escaping in dynamic patterns (statRegex, fieldRegex) - Improve toFieldKey() regex specificity
137 lines
5.1 KiB
JavaScript
137 lines
5.1 KiB
JavaScript
import { repairJSON, extractJSONFromText, validateJSONSchema, safeParseJSON } from '../src/utils/jsonRepair.js';
|
|
|
|
describe('repairJSON', () => {
|
|
test('parses valid JSON', () => {
|
|
const input = '{"name": "test", "value": 42}';
|
|
const result = repairJSON(input);
|
|
expect(result).toEqual({ name: 'test', value: 42 });
|
|
});
|
|
|
|
test('handles trailing commas', () => {
|
|
const input = '{"name": "test", "value": 42,}';
|
|
const result = repairJSON(input);
|
|
expect(result).toEqual({ name: 'test', value: 42 });
|
|
});
|
|
|
|
test('handles markdown code fences', () => {
|
|
const input = '```json\n{"name": "test"}\n```';
|
|
const result = repairJSON(input);
|
|
expect(result).toEqual({ name: 'test' });
|
|
});
|
|
|
|
test('removes thinking tags', () => {
|
|
const input = '```json\n{"name": "test"}\n```';
|
|
const result = repairJSON(input);
|
|
expect(result).toEqual({ name: 'test' });
|
|
});
|
|
|
|
test('returns null for invalid input', () => {
|
|
expect(repairJSON(null)).toBeNull();
|
|
expect(repairJSON(123)).toBeNull();
|
|
expect(repairJSON('')).toBeNull();
|
|
expect(repairJSON('not json at all')).toBeNull();
|
|
});
|
|
|
|
test('handles JSON arrays', () => {
|
|
const input = '[1, 2, 3]';
|
|
const result = repairJSON(input);
|
|
expect(result).toEqual([1, 2, 3]);
|
|
});
|
|
|
|
test('handles nested objects', () => {
|
|
const input = '{"outer": {"inner": {"deep": true}}}';
|
|
const result = repairJSON(input);
|
|
expect(result).toEqual({ outer: { inner: { deep: true } } });
|
|
});
|
|
|
|
test('removes JavaScript comments', () => {
|
|
const input = '{"name": "test"} // comment';
|
|
const result = repairJSON(input);
|
|
expect(result).toEqual({ name: 'test' });
|
|
});
|
|
});
|
|
|
|
describe('extractJSONFromText', () => {
|
|
test('extracts from json code fence', () => {
|
|
const input = 'Here is some text\n```json\n{"key": "value"}\n```\nMore text';
|
|
const result = extractJSONFromText(input);
|
|
expect(result).toBe('{"key": "value"}');
|
|
});
|
|
|
|
test('extracts from generic code fence', () => {
|
|
const input = '```{"key": "value"}```';
|
|
const result = extractJSONFromText(input);
|
|
expect(result).toBe('{"key": "value"}');
|
|
});
|
|
|
|
test('extracts standalone JSON object', () => {
|
|
const input = 'Some text {"key": "value"} more text';
|
|
const result = extractJSONFromText(input);
|
|
expect(result).toBe('{"key": "value"}');
|
|
});
|
|
|
|
test('extracts standalone JSON array', () => {
|
|
const input = 'Some text [1, 2, 3] more text';
|
|
const result = extractJSONFromText(input);
|
|
expect(result).toBe('[1, 2, 3]');
|
|
});
|
|
|
|
test('returns null for no JSON', () => {
|
|
expect(extractJSONFromText('no json here')).toBeNull();
|
|
expect(extractJSONFromText(null)).toBeNull();
|
|
expect(extractJSONFromText(123)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('validateJSONSchema', () => {
|
|
test('validates userStats schema', () => {
|
|
const valid = { stats: [{ id: 'health', name: 'Health', value: 100 }] };
|
|
const invalid = { stats: [{ id: 'health', name: 'Health' }] }; // missing value
|
|
expect(validateJSONSchema(valid, 'userStats')).toBe(true);
|
|
expect(validateJSONSchema(invalid, 'userStats')).toBe(false);
|
|
});
|
|
|
|
test('validates infoBox schema', () => {
|
|
const valid = { date: '2024-01-01' };
|
|
const invalid = { unrelated: 'data' };
|
|
// Note: validateJSONSchema returns the truthy field value, not strictly true
|
|
expect(validateJSONSchema(valid, 'infoBox')).toBeTruthy();
|
|
expect(validateJSONSchema(invalid, 'infoBox')).toBeFalsy();
|
|
});
|
|
|
|
test('validates characters schema', () => {
|
|
const valid = { characters: [{ name: 'Alice' }] };
|
|
const invalid = { characters: [{ noName: true }] };
|
|
expect(validateJSONSchema(valid, 'characters')).toBe(true);
|
|
expect(validateJSONSchema(invalid, 'characters')).toBe(false);
|
|
});
|
|
|
|
test('returns false for invalid types', () => {
|
|
expect(validateJSONSchema(null, 'userStats')).toBe(false);
|
|
expect(validateJSONSchema('string', 'userStats')).toBe(false);
|
|
expect(validateJSONSchema({}, 'unknown')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('safeParseJSON', () => {
|
|
test('successfully parses and validates', () => {
|
|
const result = safeParseJSON('{"stats": [{"id": "health", "name": "Health", "value": 100}]}', 'userStats');
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toEqual({ stats: [{ id: 'health', name: 'Health', value: 100 }] });
|
|
expect(result.error).toBeNull();
|
|
});
|
|
|
|
test('returns error when no JSON found', () => {
|
|
const result = safeParseJSON('no json here');
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toBe('No JSON found in text');
|
|
});
|
|
|
|
test('returns error when schema validation fails', () => {
|
|
const result = safeParseJSON('{"unrelated": "data"}', 'userStats');
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain('does not match expected schema');
|
|
expect(result.data).not.toBeNull(); // Still returns data
|
|
});
|
|
});
|