/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import * as dataValueHelper from '@/src/data/helper/dataValueHelper'; const NO_SUCH_CASE = 'NO_SUCH_CASE'; // Tags for relational comparison cases. // LT: less than, GT: greater than, INCMPR: incomparable const TAG = { BothNumeric_AtLeastOneNumber_L_LT_R: 'BothNumeric_AtLeastOneNumber_L_LT_R', BothNumeric_AtLeastOneNumber_L_GT_R: 'BothNumeric_AtLeastOneNumber_L_GT_R', BothString_L_LT_R: 'BothString_L_LT_R', BothString_L_GT_R: 'BothString_L_GT_R', BothNumericString_NotStrictEQ_BeNumericEQ: 'BothNumericString_NotStrictEQ_BeNumericEQ', Strict_EQ: 'Strict_EQ', BothNumeric_OneNumber_NumericEQ: 'BothNumeric_OneNumber_NumericEQ', BothIncmpr_NotEQ: 'BothIncmpr_NotEQ', L_Incmpr_R_NumberOrString: 'L_Incmpr_R_NumberOrString', R_Incmpr_L_NumberOrString: 'R_Incmpr_L_NumberOrString' } as const; type CaseTag = typeof TAG[keyof typeof TAG]; type Operation = 'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'ne'; type Order = 'asc' | 'desc'; type Incomparable = 'min' | 'max'; const tagRevertPairs = [ ['BothNumeric_AtLeastOneNumber_L_LT_R', 'BothNumeric_AtLeastOneNumber_L_GT_R'], ['BothString_L_LT_R', 'BothString_L_GT_R'], ['BothNumericString_NotStrictEQ_BeNumericEQ', 'BothNumericString_NotStrictEQ_BeNumericEQ'], ['Strict_EQ', 'Strict_EQ'], ['BothNumeric_OneNumber_NumericEQ', 'BothNumeric_OneNumber_NumericEQ'], ['BothIncmpr_NotEQ', 'BothIncmpr_NotEQ'], ['L_Incmpr_R_NumberOrString', 'R_Incmpr_L_NumberOrString'] ] as const; const filterResultMap = { BothNumeric_AtLeastOneNumber_L_LT_R: { lt: true, lte: true, gt: false, gte: false, eq: false, ne: true }, BothNumeric_AtLeastOneNumber_L_GT_R: { lt: false, lte: false, gt: true, gte: true, eq: false, ne: true }, BothString_L_LT_R: { lt: NO_SUCH_CASE, lte: NO_SUCH_CASE, gt: NO_SUCH_CASE, gte: NO_SUCH_CASE, eq: false, ne: true }, BothString_L_GT_R: { lt: NO_SUCH_CASE, lte: NO_SUCH_CASE, gt: NO_SUCH_CASE, gte: NO_SUCH_CASE, eq: false, ne: true }, BothNumericString_NotStrictEQ_BeNumericEQ: { lt: NO_SUCH_CASE, lte: NO_SUCH_CASE, gt: NO_SUCH_CASE, gte: NO_SUCH_CASE, eq: false, ne: true }, Strict_EQ: { lt: false, lte: true, gt: false, gte: true, eq: true, ne: false }, BothNumeric_OneNumber_NumericEQ: { lt: false, lte: true, gt: false, gte: true, eq: true, ne: false }, BothIncmpr_NotEQ: { lt: false, lte: false, gt: false, gte: false, eq: false, ne: true }, L_Incmpr_R_NumberOrString: { lt: false, lte: false, gt: false, gte: false, eq: false, ne: true }, R_Incmpr_L_NumberOrString: { lt: false, lte: false, gt: false, gte: false, eq: false, ne: true } } as const; const sortResultMap = { BothNumeric_AtLeastOneNumber_L_LT_R: { asc_incmprmin: -1, asc_incmprmax: -1, desc_incmprmin: 1, desc_incmprmax: 1 }, BothNumeric_AtLeastOneNumber_L_GT_R: { asc_incmprmin: 1, asc_incmprmax: 1, desc_incmprmin: -1, desc_incmprmax: -1 }, BothString_L_LT_R: { asc_incmprmin: -1, asc_incmprmax: -1, desc_incmprmin: 1, desc_incmprmax: 1 }, BothString_L_GT_R: { asc_incmprmin: 1, asc_incmprmax: 1, desc_incmprmin: -1, desc_incmprmax: -1 }, BothNumericString_NotStrictEQ_BeNumericEQ: { asc_incmprmin: 0, asc_incmprmax: 0, desc_incmprmin: 0, desc_incmprmax: 0 }, Strict_EQ: { asc_incmprmin: 0, asc_incmprmax: 0, desc_incmprmin: 0, desc_incmprmax: 0 }, BothNumeric_OneNumber_NumericEQ: { asc_incmprmin: 0, asc_incmprmax: 0, desc_incmprmin: 0, desc_incmprmax: 0 }, BothIncmpr_NotEQ: { asc_incmprmin: 0, asc_incmprmax: 0, desc_incmprmin: 0, desc_incmprmax: 0 }, L_Incmpr_R_NumberOrString: { asc_incmprmin: -1, asc_incmprmax: 1, desc_incmprmin: 1, desc_incmprmax: -1 }, R_Incmpr_L_NumberOrString: { asc_incmprmin: 1, asc_incmprmax: -1, desc_incmprmin: -1, desc_incmprmax: 1 } } as const; type EvaluateFunction = (lval: unknown, rval: unknown, caseTag: CaseTag) => void; function eachRelationalComparisonCase(evalFn: EvaluateFunction) { const FULL_WIDTH_SPACE = String.fromCharCode(12288); const testerMap = { notEqualAndHasOrder: function () { expectDual(123, 555, TAG.BothNumeric_AtLeastOneNumber_L_LT_R); expectDual(-123, -555, TAG.BothNumeric_AtLeastOneNumber_L_GT_R); expectDual(-123, 123, TAG.BothNumeric_AtLeastOneNumber_L_LT_R); expectDual(Infinity, 123, TAG.BothNumeric_AtLeastOneNumber_L_GT_R); expectDual(-Infinity, -123, TAG.BothNumeric_AtLeastOneNumber_L_LT_R); expectDual('Infinity', 123, TAG.BothNumeric_AtLeastOneNumber_L_GT_R); expectDual('-Infinity', 123, TAG.BothNumeric_AtLeastOneNumber_L_LT_R); expectDual(123, '555', TAG.BothNumeric_AtLeastOneNumber_L_LT_R); expectDual(555, '555.6', TAG.BothNumeric_AtLeastOneNumber_L_LT_R); expectDual('-555', -555.6, TAG.BothNumeric_AtLeastOneNumber_L_GT_R); expectDual(123, ' 555 ', TAG.BothNumeric_AtLeastOneNumber_L_LT_R); expectDual(' -555 ', 123, TAG.BothNumeric_AtLeastOneNumber_L_LT_R); expectDual(123, ' \r \n 555 \t ' + FULL_WIDTH_SPACE, TAG.BothNumeric_AtLeastOneNumber_L_LT_R); }, notEqualAndNoOrder: function () { const makeDate = () => new Date(2012, 5, 12); const makeFn = () => function () {}; expectDual(NaN, NaN, TAG.BothIncmpr_NotEQ); expectDual(NaN, -NaN, TAG.BothIncmpr_NotEQ); expectDual(NaN, 0, TAG.L_Incmpr_R_NumberOrString); expectDual(NaN, 2, TAG.L_Incmpr_R_NumberOrString); expectDual('NaN', NaN, TAG.R_Incmpr_L_NumberOrString); expectDual('NaN', 0, TAG.L_Incmpr_R_NumberOrString); expectDual('NaN', 2, TAG.L_Incmpr_R_NumberOrString); expectDual('-NaN', -NaN, TAG.R_Incmpr_L_NumberOrString); expectDual('-NaN', 0, TAG.L_Incmpr_R_NumberOrString); expectDual('-NaN', 2, TAG.L_Incmpr_R_NumberOrString); expectDual(true, 0, TAG.L_Incmpr_R_NumberOrString); expectDual(false, 1, TAG.L_Incmpr_R_NumberOrString); expectDual('true', 0, TAG.L_Incmpr_R_NumberOrString); expectDual('false', 1, TAG.L_Incmpr_R_NumberOrString); expectDual(undefined, 2, TAG.L_Incmpr_R_NumberOrString); expectDual(undefined, 0, TAG.L_Incmpr_R_NumberOrString); expectDual(null, 2, TAG.L_Incmpr_R_NumberOrString); expectDual(null, 0, TAG.L_Incmpr_R_NumberOrString); expectDual(makeDate(), 0, TAG.L_Incmpr_R_NumberOrString); expectDual(makeDate(), makeDate(), TAG.BothIncmpr_NotEQ); expectDual(makeDate(), +makeDate(), TAG.L_Incmpr_R_NumberOrString); expectDual([], 1, TAG.L_Incmpr_R_NumberOrString); expectDual([], 0, TAG.L_Incmpr_R_NumberOrString); expectDual({}, 1, TAG.L_Incmpr_R_NumberOrString); expectDual([], '0', TAG.L_Incmpr_R_NumberOrString); expectDual({}, '1', TAG.L_Incmpr_R_NumberOrString); expectDual({}, 0, TAG.L_Incmpr_R_NumberOrString); expectDual({}, '1', TAG.L_Incmpr_R_NumberOrString); expectDual({}, '0', TAG.L_Incmpr_R_NumberOrString); expectDual(/1/, 0, TAG.L_Incmpr_R_NumberOrString); expectDual(/0/, 0, TAG.L_Incmpr_R_NumberOrString); expectDual('555a', 123, TAG.L_Incmpr_R_NumberOrString); expectDual('abc', 123, TAG.L_Incmpr_R_NumberOrString); expectDual('abc', null, TAG.R_Incmpr_L_NumberOrString); // See [SORT_COMPARISON_RULE] expectDual('abc', '123', TAG.L_Incmpr_R_NumberOrString); expectDual('abc', 'abcde', TAG.BothString_L_LT_R); expectDual('abc', 'abc', TAG.Strict_EQ); expectDual('2', '12', TAG.BothString_L_LT_R); // '2' > '12' in JS but should not happen here. expectDual(' ', '', TAG.BothString_L_GT_R); expectDual(0.5, '0. 5', TAG.R_Incmpr_L_NumberOrString); expectDual('0.5', '0. 5', TAG.R_Incmpr_L_NumberOrString); expectDual('- 5', -5, TAG.L_Incmpr_R_NumberOrString); expectDual('-123.5', ' -123.5 ', TAG.BothNumericString_NotStrictEQ_BeNumericEQ); expectDual('0x11', 17, TAG.L_Incmpr_R_NumberOrString); // not 17 in int16. expectDual('0x11', 0, TAG.L_Incmpr_R_NumberOrString); expectDual('0x0', 0, TAG.L_Incmpr_R_NumberOrString); expectDual('0. 5', 0.5, TAG.L_Incmpr_R_NumberOrString); expectDual('0 .5', 0.5, TAG.L_Incmpr_R_NumberOrString); expectDual('', 2, TAG.L_Incmpr_R_NumberOrString); expectDual('', 0, TAG.L_Incmpr_R_NumberOrString); expectDual(' ', 2, TAG.L_Incmpr_R_NumberOrString); expectDual(' ', 0, TAG.L_Incmpr_R_NumberOrString); expectDual(' \n', '\n', TAG.BothString_L_GT_R); expectDual('\n', 0, TAG.L_Incmpr_R_NumberOrString); expectDual('\n', 2, TAG.L_Incmpr_R_NumberOrString); expectDual({}, {}, TAG.BothIncmpr_NotEQ); expectDual({}, [], TAG.BothIncmpr_NotEQ); expectDual(makeFn(), makeFn(), TAG.BothIncmpr_NotEQ); expectDual(makeFn(), 0, TAG.L_Incmpr_R_NumberOrString); expectDual(makeFn(), 1, TAG.L_Incmpr_R_NumberOrString); expectDual(makeFn(), makeFn().toString(), TAG.L_Incmpr_R_NumberOrString); }, equalNumeric: function () { expectDual(123, 123, TAG.Strict_EQ); expectDual(1e3, 1000, TAG.Strict_EQ); expectDual(-1e3, -1000, TAG.Strict_EQ); expectDual('1e3', 1000, TAG.BothNumeric_OneNumber_NumericEQ); expectDual('-1e3', -1000, TAG.BothNumeric_OneNumber_NumericEQ); expectDual(123, '123', TAG.BothNumeric_OneNumber_NumericEQ); expectDual(123, ' 123 ', TAG.BothNumeric_OneNumber_NumericEQ); expectDual(123.5, ' \n \r 123.5 \t ', TAG.BothNumeric_OneNumber_NumericEQ); expectDual(123.5, 123.5 + FULL_WIDTH_SPACE, TAG.BothNumeric_OneNumber_NumericEQ); expectDual(' -123.5 ', -123.5, TAG.BothNumeric_OneNumber_NumericEQ); expectDual('011', 11, TAG.BothNumeric_OneNumber_NumericEQ); // not 9 in int8. }, equalOtherTypes: function () { const emptyObj = {}; const emptyArr = [] as unknown[]; const date = new Date(2012, 5, 12); const fn = function () {}; expectDual(emptyObj, emptyObj, TAG.Strict_EQ); expectDual(emptyArr, emptyArr, TAG.Strict_EQ); expectDual(date, date, TAG.Strict_EQ); expectDual(fn, fn, TAG.Strict_EQ); } }; function expectDual(lval: unknown, rval: unknown, caseTag: CaseTag) { validateCaseTag(caseTag); evalFn(lval, rval, caseTag); const revertedCaseTag = findRevertTag(caseTag); validateCaseTag(revertedCaseTag); evalFn(rval, lval, revertedCaseTag); } function validateCaseTag(caseTag: CaseTag) { expect(TAG.hasOwnProperty(caseTag)).toEqual(true); } function findRevertTag(caseTag: CaseTag) { for (let i = 0; i < tagRevertPairs.length; i++) { const item = tagRevertPairs[i]; if (item[0] === caseTag) { return item[1]; } else if (item[1] === caseTag) { return item[0]; } } } Object.keys(testerMap).forEach((name: keyof typeof testerMap) => testerMap[name]()); } describe('data/helper/dataValueHelper', function () { describe('filter_relational_comparison', function () { function testFilterComparator(op: Operation) { it(op + '_filter_comparator', () => { eachRelationalComparisonCase((lval, rval, caseTag) => { expect(filterResultMap.hasOwnProperty(caseTag)); expect(filterResultMap[caseTag].hasOwnProperty(op)); const expectedResult = filterResultMap[caseTag][op]; if ((op === 'lt' || op === 'lte' || op === 'gt' || op === 'gte') && typeof rval !== 'number' ) { expect(() => { dataValueHelper.createFilterComparator(op, rval); }).toThrow(); } else { const comparator = dataValueHelper.createFilterComparator(op, rval); expect(comparator.evaluate(lval)).toEqual(expectedResult); } }); }); } testFilterComparator('lt'); testFilterComparator('lte'); testFilterComparator('gt'); testFilterComparator('gte'); testFilterComparator('eq'); testFilterComparator('ne'); }); describe('sort_relational_comparison', function () { function testSortComparator(order: Order, incomparable: Incomparable) { const key = order + '_incmpr' + incomparable; const SortOrderComparator = dataValueHelper.SortOrderComparator; const sortOrderComparator = new SortOrderComparator(order, incomparable); it(key + '_sort_comparator', () => { eachRelationalComparisonCase((lval, rval, caseTag) => { expect(sortResultMap.hasOwnProperty(caseTag)); expect(sortResultMap[caseTag].hasOwnProperty(key)); const expectedResult = (sortResultMap[caseTag] as any)[key]; expect(sortOrderComparator.evaluate(lval, rval)).toEqual(expectedResult); }); }); } testSortComparator('asc', 'min'); testSortComparator('asc', 'max'); testSortComparator('desc', 'min'); testSortComparator('desc', 'max'); }); });