UNPKG

@hyperlane-xyz/utils

Version:

General utilities and types for the Hyperlane network

519 lines 16.9 kB
//@ts-ignore import { expect } from 'chai'; import { sortNestedArrays, transformYaml, tryParseJsonOrYaml, } from './yaml.js'; describe('tryParseJsonOrYaml', () => { it('should parse valid JSON string', () => { const jsonString = '{"key": "value"}'; const result = tryParseJsonOrYaml(jsonString); expect(result.success).to.be.true; expect(result.data).to.deep.equal({ key: 'value' }); }); it('should parse valid YAML string', () => { const yamlString = 'key: value'; const result = tryParseJsonOrYaml(yamlString); expect(result.success).to.be.true; expect(result.data).to.deep.equal({ key: 'value' }); }); it('should fail for invalid JSON string', () => { const invalidJsonString = '{"key": "value"'; const result = tryParseJsonOrYaml(invalidJsonString); expect(result.success).to.be.false; expect(result.error).to.equal('Input is not valid JSON or YAML'); }); it('should fail for invalid YAML string', () => { const invalidYamlString = 'key: value:'; const result = tryParseJsonOrYaml(invalidYamlString); expect(result.success).to.be.false; expect(result.error).to.equal('Input is not valid JSON or YAML'); }); }); describe('sortNestedArrays', () => { it('should sort a simple array by key', () => { const data = { items: [ { name: 'c', value: 3 }, { name: 'a', value: 1 }, { name: 'b', value: 2 }, ], }; const config = { arrays: [{ path: 'items', sortKey: 'name' }], }; const result = sortNestedArrays(data, config); expect(result.items).to.deep.equal([ { name: 'a', value: 1 }, { name: 'b', value: 2 }, { name: 'c', value: 3 }, ]); }); it('should sort nested arrays by different keys', () => { const data = { level1: { items: [ { id: 3, name: 'item3' }, { id: 1, name: 'item1' }, { id: 2, name: 'item2' }, ], categories: [ { code: 'cat-b', items: [ { name: 'z', priority: 3 }, { name: 'x', priority: 1 }, { name: 'y', priority: 2 }, ], }, { code: 'cat-a', items: [] }, ], }, }; const config = { arrays: [ { path: 'level1.items', sortKey: 'id' }, { path: 'level1.categories', sortKey: 'code' }, { path: 'level1.categories[].items', sortKey: 'priority' }, ], }; const result = sortNestedArrays(data, config); expect(result.level1.items).to.deep.equal([ { id: 1, name: 'item1' }, { id: 2, name: 'item2' }, { id: 3, name: 'item3' }, ]); expect(result.level1.categories).to.deep.equal([ { code: 'cat-a', items: [] }, { code: 'cat-b', items: [ { name: 'x', priority: 1 }, { name: 'y', priority: 2 }, { name: 'z', priority: 3 }, ], }, ]); }); it('should handle array paths with wildcard patterns', () => { const data = { users: [ { name: 'user2', tasks: [ { id: 'c', completed: true }, { id: 'a', completed: false }, { id: 'b', completed: true }, ], }, { name: 'user1', tasks: [ { id: 'z', completed: false }, { id: 'x', completed: true }, { id: 'y', completed: false }, ], }, ], }; const config = { arrays: [ { path: 'users', sortKey: 'name' }, { path: 'users[].tasks', sortKey: 'id' }, ], }; const result = sortNestedArrays(data, config); expect(result.users[0].name).to.equal('user1'); expect(result.users[1].name).to.equal('user2'); expect(result.users[0].tasks.map((t) => t.id)).to.deep.equal([ 'x', 'y', 'z', ]); expect(result.users[1].tasks.map((t) => t.id)).to.deep.equal([ 'a', 'b', 'c', ]); }); it('should not modify data when no sort configuration matches', () => { const data = { items: [ { name: 'c', value: 3 }, { name: 'a', value: 1 }, { name: 'b', value: 2 }, ], otherItems: [ { id: 3, label: 'Three' }, { id: 1, label: 'One' }, { id: 2, label: 'Two' }, ], }; const config = { arrays: [ { path: 'nonExistent', sortKey: 'name' }, { path: 'items.wrong.path', sortKey: 'value' }, ], }; const result = sortNestedArrays(data, config); // Data should remain unchanged expect(result).to.deep.equal(data); // Verify the original order is preserved expect(result.items[0].name).to.equal('c'); expect(result.items[1].name).to.equal('a'); expect(result.items[2].name).to.equal('b'); expect(result.otherItems[0].id).to.equal(3); expect(result.otherItems[1].id).to.equal(1); expect(result.otherItems[2].id).to.equal(2); }); it('should not match when path length is shorter than pattern length', () => { const data = { items: [ { name: 'c', value: 3 }, { name: 'a', value: 1 }, { name: 'b', value: 2 }, ], }; // Pattern with longer path than exists in data const config = { arrays: [{ path: 'items.subItems[].name', sortKey: 'value' }], }; const result = sortNestedArrays(data, config); expect(result).to.deep.equal(data); expect(result.items[0].name).to.equal('c'); expect(result.items[1].name).to.equal('a'); expect(result.items[2].name).to.equal('b'); }); it('should demonstrate difference between [] and * notations', () => { const dataWithArray = { root: { categories: [ { name: 'c', items: [ { age: 20, name: 'c2', }, { age: 10, name: 'c1', }, ], }, { name: 'a', items: [ { age: 5, name: 'a2', }, { age: 15, name: 'a1', }, ], }, { name: 'b', items: [ { age: 30, name: 'b2', }, { age: 25, name: 'b1', }, ], }, ], }, }; const dataWithObject = { root: { categories: { first: { name: 'c', items: [ { age: 20, name: 'c2', }, { age: 10, name: 'c1', }, ], }, second: { name: 'a', items: [ { age: 5, name: 'a2', }, { age: 15, name: 'a1', }, ], }, third: { name: 'b', items: [ { age: 30, name: 'b2', }, { age: 25, name: 'b1', }, ], }, }, }, }; // Using [] notation only matches array structures const arrayOnlyConfig = { arrays: [{ path: 'root.categories[].items', sortKey: 'age' }], }; // With * notation, both array and object children are matched const wildcardConfig = { arrays: [{ path: 'root.categories.*.items', sortKey: 'name' }], }; const resultArrayNotationWithArray = sortNestedArrays(dataWithArray, arrayOnlyConfig); expect(resultArrayNotationWithArray.root.categories[0].items).to.deep.equal([ { age: 10, name: 'c1' }, { age: 20, name: 'c2' }, ]); expect(resultArrayNotationWithArray.root.categories[1].items).to.deep.equal([ { age: 5, name: 'a2' }, { age: 15, name: 'a1' }, ]); const resultArrayNotationWithObject = sortNestedArrays(dataWithObject, arrayOnlyConfig); expect(resultArrayNotationWithObject.root.categories.first.items).to.deep.equal([ { age: 20, name: 'c2' }, { age: 10, name: 'c1' }, ]); const resultWildcardWithArray = sortNestedArrays(dataWithArray, wildcardConfig); expect(resultWildcardWithArray.root.categories[0].items).to.deep.equal([ { age: 10, name: 'c1' }, { age: 20, name: 'c2' }, ]); const resultWildcardWithObject = sortNestedArrays(dataWithObject, wildcardConfig); expect(resultWildcardWithObject.root.categories.first.items).to.deep.equal([ { age: 10, name: 'c1' }, { age: 20, name: 'c2' }, ]); expect(resultWildcardWithObject.root.categories.second.items).to.deep.equal([ { age: 15, name: 'a1' }, { age: 5, name: 'a2' }, ]); const hybridConfig = { arrays: [ { path: 'root.categories[].items', sortKey: 'age' }, { path: 'root.categories.*.items', sortKey: 'name' }, ], }; const resultHybrid = sortNestedArrays(dataWithObject, hybridConfig); expect(resultHybrid.root.categories.first.items).to.deep.equal([ { age: 10, name: 'c1' }, { age: 20, name: 'c2' }, ]); }); }); describe('transformYaml', () => { const testCases = [ { name: 'should transform YAML content using the provided transformer', original: ` name: test items: - id: 2 name: item2 - id: 1 name: item1 `, expected: `name: test items: - id: 1 name: item1 - id: 2 name: item2`, transformer: (data) => { // Sort items by id if (data?.items) { data.items.sort((a, b) => a.id - b.id); } return data; }, }, { name: 'should preserve comments when transforming YAML', original: ` # Root comment name: test # Comment for items items: # First item comment - id: 2 name: item2 # Second item comment - id: 1 name: item1 `, expected: `# Root comment name: test # Comment for items items: # Second item comment - id: 1 name: item1 # First item comment - id: 2 name: item2`, transformer: (data) => { // Sort items by id if (data?.items) { data.items.sort((a, b) => a.id - b.id); } return data; }, }, { name: 'should handle nested objects and arrays', original: ` config: settings: - name: setting3 value: 30 - name: setting1 value: 10 - name: setting2 value: 20 nested: arrays: - items: - key: c val: 3 - key: a val: 1 - key: b val: 2 `, expected: `config: settings: - name: setting1 value: 10 - name: setting2 value: 20 - name: setting3 value: 30 nested: arrays: - items: - key: a val: 1 - key: b val: 2 - key: c val: 3`, transformer: (data) => { // Sort settings by name if (data?.config?.settings) { data.config.settings.sort((a, b) => a.name.localeCompare(b.name)); } // Sort nested items by key if (data?.config?.nested?.arrays?.[0]?.items) { data.config.nested.arrays[0].items.sort((a, b) => a.key.localeCompare(b.key)); } return data; }, }, { name: 'should handle empty arrays and add new items', original: ` project: tasks: [] status: pending `, expected: `project: tasks: - id: 1 name: Task 1 - id: 2 name: Task 2 status: pending`, transformer: (data) => { // Add new items to empty array if (data?.project?.tasks) { data.project.tasks = [ { id: 1, name: 'Task 1' }, { id: 2, name: 'Task 2' }, ]; } return data; }, }, { name: 'should handle inline comments', original: ` services: # Main services - name: api # REST API port: 3000 # Default port - name: db # Database port: 5432 # Postgres port `, expected: `services: # Main services - name: db # Database port: 5432 # Postgres port - name: api # REST API port: 3000 # Default port`, transformer: (data) => { // Sort services by name in reverse if (data?.services) { data.services.sort((a, b) => b.name.localeCompare(a.name)); } return data; }, }, { name: 'should handle property deletion and modification', original: ` config: debug: true environment: development features: legacy: true experimental: false beta: true `, expected: `config: environment: production features: experimental: true beta: true`, transformer: (data) => { if (data?.config) { // Delete debug property delete data.config.debug; // Change environment value data.config.environment = 'production'; // Remove legacy feature if (data.config.features) { delete data.config.features.legacy; // Enable experimental feature data.config.features.experimental = true; } } return data; }, }, ]; testCases.forEach(({ name, original, expected, transformer }) => { it(name, () => { const result = transformYaml(original, transformer); expect(result.trim()).to.equal(expected); }); }); }); //# sourceMappingURL=yaml.test.js.map