Skip to content

Commit 6a0316c

Browse files
committed
Render list results in CQL list syntax in the results file
List-typed actual values were flattened with String(), so a list like ['@2012-01-01T', '@2012-01-01T12'] appeared in the results JSON as the comma-joined string '@2012-01-01T,@2012-01-01T12', obscuring the list structure (and hiding that only element order differed from expected). Format array actuals as '{ a, b, c }' (recursively, '{}' when empty) so the actual value reads like the expected value's CQL/CVL notation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M6fK2KhnNsn28SJfe83Htw
1 parent 39d289e commit 6a0316c

2 files changed

Lines changed: 83 additions & 3 deletions

File tree

src/test-results/cql-test-results.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ import { TestResult, InternalTestResult } from '../models/test-types.js';
55
import { TestResultsSummary, CQLTestResultsData } from '../models/results-types.js';
66
import { ResultsValidator } from '../conf/results-validator.js';
77

8+
/**
9+
* Formats an actual value for the results file. Lists are rendered in CQL list
10+
* syntax (`{ a, b, c }`) so they read like the expected value, which is kept in
11+
* its original CQL/CVL notation.
12+
*/
13+
function formatActualValue(value: any): string {
14+
if (Array.isArray(value)) {
15+
return value.length === 0 ? '{}' : `{ ${value.map(formatActualValue).join(', ')} }`;
16+
}
17+
if (value !== null && typeof value === 'object') {
18+
return JSON.stringify(value);
19+
}
20+
return String(value);
21+
}
22+
823
/**
924
* Represents the results of running CQL tests.
1025
*/
@@ -106,7 +121,7 @@ export class CQLTestResults {
106121
...(result.responseStatus !== undefined && {
107122
responseStatus: result.responseStatus,
108123
}),
109-
...(result.actual !== undefined && { actual: String(result.actual) }),
124+
...(result.actual !== undefined && { actual: formatActualValue(result.actual) }),
110125
...(result.expected && { expected: result.expected }),
111126
...(result.error && {
112127
error: {
@@ -188,8 +203,9 @@ export class CQLTestResults {
188203
} else if (typeof act === 'number' && typeof exp === 'string') {
189204
r.actual = String(act);
190205
} else if (act !== undefined && act !== null && typeof act !== 'string') {
191-
// Convert any non-string value to string for schema compliance
192-
r.actual = String(act);
206+
// Convert any non-string value to string for schema compliance;
207+
// lists are rendered in CQL list syntax to mirror the expected value
208+
r.actual = formatActualValue(act);
193209
}
194210
}
195211
}

test/cql-test-results-validator.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,67 @@ describe('CQLTestResults.validateSchema', () => {
129129
expect(ResultsValidator).toHaveBeenCalled();
130130
});
131131
});
132+
133+
describe('CQLTestResults.toJSON actual serialization', () => {
134+
it('renders list results in CQL list syntax and stringifies scalar results', () => {
135+
const results = new CQLTestResults(new CQLEngine('http://localhost:8080/fhir/$cql'));
136+
results.add({
137+
testsName: 'CqlListOperatorsTest',
138+
groupName: 'Sort',
139+
testName: 'SortDatesAsc',
140+
expression: 'sort asc',
141+
testStatus: 'fail',
142+
invalid: 'false',
143+
actual: ['@2012-01-01T', '@2012-01-01T12'],
144+
expected: '{ @2012-01-01T, @2012-01-01T12 }',
145+
} as any);
146+
results.add({
147+
testsName: 'CqlArithmeticFunctionsTest',
148+
groupName: 'Power',
149+
testName: 'Power2To4',
150+
expression: '2^4',
151+
testStatus: 'pass',
152+
invalid: 'false',
153+
actual: 16,
154+
expected: '16',
155+
} as any);
156+
157+
const json = results.toJSON();
158+
expect(json.results[0].actual).toBe('{ @2012-01-01T, @2012-01-01T12 }');
159+
expect(json.results[1].actual).toBe('16');
160+
});
161+
162+
it('equalizeValueTypes renders array actuals in CQL list syntax', () => {
163+
const results = new CQLTestResults(new CQLEngine('http://localhost:8080/fhir/$cql'));
164+
results.add({
165+
testsName: 'T',
166+
groupName: 'G',
167+
testName: 'N',
168+
expression: 'e',
169+
testStatus: 'pass',
170+
invalid: 'false',
171+
actual: [1, 2, 3],
172+
expected: '{ 1, 2, 3 }',
173+
} as any);
174+
175+
results.equalizeValueTypes();
176+
expect(results.results[0].actual).toBe('{ 1, 2, 3 }');
177+
});
178+
179+
it('renders nested lists and empty lists', () => {
180+
const results = new CQLTestResults(new CQLEngine('http://localhost:8080/fhir/$cql'));
181+
results.add({
182+
testsName: 'T',
183+
groupName: 'G',
184+
testName: 'Nested',
185+
expression: 'e',
186+
testStatus: 'pass',
187+
invalid: 'false',
188+
actual: [[1, 2], []],
189+
expected: '{ { 1, 2 }, {} }',
190+
} as any);
191+
192+
const json = results.toJSON();
193+
expect(json.results[0].actual).toBe('{ { 1, 2 }, {} }');
194+
});
195+
});

0 commit comments

Comments
 (0)