diff --git a/src/test-results/cql-test-results.ts b/src/test-results/cql-test-results.ts index eb3b26d..489cad9 100644 --- a/src/test-results/cql-test-results.ts +++ b/src/test-results/cql-test-results.ts @@ -5,6 +5,21 @@ import { TestResult, InternalTestResult } from '../models/test-types.js'; import { TestResultsSummary, CQLTestResultsData } from '../models/results-types.js'; import { ResultsValidator } from '../conf/results-validator.js'; +/** + * Formats an actual value for the results file. Lists are rendered in CQL list + * syntax (`{ a, b, c }`) so they read like the expected value, which is kept in + * its original CQL/CVL notation. + */ +function formatActualValue(value: any): string { + if (Array.isArray(value)) { + return value.length === 0 ? '{}' : `{ ${value.map(formatActualValue).join(', ')} }`; + } + if (value !== null && typeof value === 'object') { + return JSON.stringify(value); + } + return String(value); +} + /** * Represents the results of running CQL tests. */ @@ -106,7 +121,7 @@ export class CQLTestResults { ...(result.responseStatus !== undefined && { responseStatus: result.responseStatus, }), - ...(result.actual !== undefined && { actual: String(result.actual) }), + ...(result.actual !== undefined && { actual: formatActualValue(result.actual) }), ...(result.expected && { expected: result.expected }), ...(result.error && { error: { @@ -188,8 +203,9 @@ export class CQLTestResults { } else if (typeof act === 'number' && typeof exp === 'string') { r.actual = String(act); } else if (act !== undefined && act !== null && typeof act !== 'string') { - // Convert any non-string value to string for schema compliance - r.actual = String(act); + // Convert any non-string value to string for schema compliance; + // lists are rendered in CQL list syntax to mirror the expected value + r.actual = formatActualValue(act); } } } diff --git a/test/cql-test-results-validator.test.ts b/test/cql-test-results-validator.test.ts index efb9f33..fcff34f 100644 --- a/test/cql-test-results-validator.test.ts +++ b/test/cql-test-results-validator.test.ts @@ -129,3 +129,67 @@ describe('CQLTestResults.validateSchema', () => { expect(ResultsValidator).toHaveBeenCalled(); }); }); + +describe('CQLTestResults.toJSON actual serialization', () => { + it('renders list results in CQL list syntax and stringifies scalar results', () => { + const results = new CQLTestResults(new CQLEngine('http://localhost:8080/fhir/$cql')); + results.add({ + testsName: 'CqlListOperatorsTest', + groupName: 'Sort', + testName: 'SortDatesAsc', + expression: 'sort asc', + testStatus: 'fail', + invalid: 'false', + actual: ['@2012-01-01T', '@2012-01-01T12'], + expected: '{ @2012-01-01T, @2012-01-01T12 }', + } as any); + results.add({ + testsName: 'CqlArithmeticFunctionsTest', + groupName: 'Power', + testName: 'Power2To4', + expression: '2^4', + testStatus: 'pass', + invalid: 'false', + actual: 16, + expected: '16', + } as any); + + const json = results.toJSON(); + expect(json.results[0].actual).toBe('{ @2012-01-01T, @2012-01-01T12 }'); + expect(json.results[1].actual).toBe('16'); + }); + + it('equalizeValueTypes renders array actuals in CQL list syntax', () => { + const results = new CQLTestResults(new CQLEngine('http://localhost:8080/fhir/$cql')); + results.add({ + testsName: 'T', + groupName: 'G', + testName: 'N', + expression: 'e', + testStatus: 'pass', + invalid: 'false', + actual: [1, 2, 3], + expected: '{ 1, 2, 3 }', + } as any); + + results.equalizeValueTypes(); + expect(results.results[0].actual).toBe('{ 1, 2, 3 }'); + }); + + it('renders nested lists and empty lists', () => { + const results = new CQLTestResults(new CQLEngine('http://localhost:8080/fhir/$cql')); + results.add({ + testsName: 'T', + groupName: 'G', + testName: 'Nested', + expression: 'e', + testStatus: 'pass', + invalid: 'false', + actual: [[1, 2], []], + expected: '{ { 1, 2 }, {} }', + } as any); + + const json = results.toJSON(); + expect(json.results[0].actual).toBe('{ { 1, 2 }, {} }'); + }); +});