diff --git a/src/schema/expressionLanguage.test.ts b/src/schema/expressionLanguage.test.ts index 742300bd..bec4ff7e 100644 --- a/src/schema/expressionLanguage.test.ts +++ b/src/schema/expressionLanguage.test.ts @@ -16,16 +16,27 @@ Deno.test('test expression functions', async (t) => { }) await t.step('intersects function', () => { const intersects = expressionFunctions.intersects - assert(intersects([1, 2, 3], [2, 3, 4]) === true) + const equal = expressionFunctions.allequal + + const truthy = (a: any): boolean => !!a + + assert(truthy(intersects([1, 2, 3], [2, 3, 4]))) assert(intersects([1, 2, 3], [4, 5, 6]) === false) - assert(intersects(['abc', 'def'], ['def']) === true) + assert(truthy(intersects(['abc', 'def'], ['def']))) assert(intersects(['abc', 'def'], ['ghi']) === false) + // Just checking values, I'm not concerned about types here + // @ts-expect-error + assert(equal(intersects([1, 2, 3], [2, 3, 4]), [2, 3])) + // @ts-expect-error + assert(equal(intersects(['abc', 'def'], ['def']), ['def'])) // Promote scalars to arrays // @ts-ignore - assert(intersects('abc', ['abc', 'def']) === true) + assert(truthy(intersects('abc', ['abc', 'def']))) // @ts-ignore assert(intersects('abc', ['a', 'b', 'c']) === false) + // @ts-expect-error + assert(equal(intersects('abc', ['abc', 'def']), ['abc'])) }) await t.step('match function', () => { const match = expressionFunctions.match diff --git a/src/schema/expressionLanguage.ts b/src/schema/expressionLanguage.ts index ee399b35..2dcf029f 100644 --- a/src/schema/expressionLanguage.ts +++ b/src/schema/expressionLanguage.ts @@ -40,14 +40,30 @@ export const expressionFunctions = { const index = list.indexOf(item) return index != -1 ? index : null }, - intersects: (a: T[], b: T[]): boolean => { + intersects: (a: T[], b: T[]): T[] | boolean => { + // Tolerate single values if (!Array.isArray(a)) { a = [a] } if (!Array.isArray(b)) { b = [b] } - return a.some((x) => b.includes(x)) + // Construct a set from the smaller list + if (a.length < b.length) { + const tmp = a + a = b + b = tmp + } + if (b.length === 0) { + return false + } + + const bSet = new Set(b) + const intersection = a.filter((x) => bSet.has(x)) + if (intersection.length === 0) { + return false + } + return intersection }, match: (target: string, regex: string): boolean => { const re = RegExp(regex) diff --git a/src/tests/schema-expression-language.test.ts b/src/tests/schema-expression-language.test.ts index b994fcb2..c6ee58a8 100644 --- a/src/tests/schema-expression-language.test.ts +++ b/src/tests/schema-expression-language.test.ts @@ -19,6 +19,7 @@ const equal = (a: T, b: T): boolean => { Deno.test('validate schema expression tests', async (t) => { const results: string[][] = [] const header = ['expression', 'desired', 'actual', 'result'].map((x) => colors.magenta(x)) + const xfails = ['intersects([1], [1, 2])'] for (const test of schema.meta.expression_tests) { await t.step(`${test.expression} evals to ${test.result}`, () => { const context = { file: { parent: null }, dataset: { tree: null } } as unknown as BIDSContext @@ -33,6 +34,13 @@ Deno.test('validate schema expression tests', async (t) => { pretty_null(actual_result), colors.green('pass'), ]) + } else if (xfails.includes(test.expression)) { + results.push([ + colors.cyan(test.expression), + pretty_null(test.result), + pretty_null(actual_result), + colors.yellow('xfail'), + ]) } else { results.push([ colors.cyan(test.expression), @@ -41,7 +49,10 @@ Deno.test('validate schema expression tests', async (t) => { colors.red('fail'), ]) } - assertEquals(actual_result, test.result) + // Don't fail on xfail + if (!xfails.includes(test.expression)) { + assertEquals(actual_result, test.result) + } }) } results.sort((a, b) => {