kibana/x-pack/plugins/lens/public/datatable_visualization/sorting.test.tsx

333 lines
9.3 KiB
TypeScript

/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getSortingCriteria } from './sorting';
import { FieldFormat } from 'src/plugins/data/public';
import { DatatableColumnType } from 'src/plugins/expressions';
function getMockFormatter() {
return { convert: (v: unknown) => `${v as string}` } as FieldFormat;
}
function testSorting({
input,
output,
direction,
type,
keepLast,
reverseOutput = true,
}: {
input: unknown[];
output: unknown[];
direction: 'asc' | 'desc';
type: DatatableColumnType | 'range';
keepLast?: boolean; // special flag to handle values that should always be last no matter the direction
reverseOutput?: boolean;
}) {
const datatable = input.map((v) => ({
a: v,
}));
const sorted = output.map((v) => ({ a: v }));
if (direction === 'desc' && reverseOutput) {
sorted.reverse();
if (keepLast) {
// Cycle shift of the first element
const firstEl = sorted.shift()!;
sorted.push(firstEl);
}
}
const criteria = getSortingCriteria(type, 'a', getMockFormatter(), direction);
expect(datatable.sort(criteria)).toEqual(sorted);
}
describe('Data sorting criteria', () => {
describe('Numeric values', () => {
for (const direction of ['asc', 'desc'] as const) {
it(`should provide the number criteria of numeric values (${direction})`, () => {
testSorting({
input: [7, 6, 5, -Infinity, Infinity],
output: [-Infinity, 5, 6, 7, Infinity],
direction,
type: 'number',
});
});
it(`should provide the number criteria for date values (${direction})`, () => {
const now = Date.now();
testSorting({
input: [now, 0, now - 150000],
output: [0, now - 150000, now],
direction,
type: 'date',
});
});
}
it(`should sort undefined and null to the end`, () => {
const now = Date.now();
testSorting({
input: [null, now, 0, undefined, null, now - 150000],
output: [0, now - 150000, now, null, undefined, null],
direction: 'asc',
type: 'date',
reverseOutput: false,
});
testSorting({
input: [null, now, 0, undefined, null, now - 150000],
output: [now, now - 150000, 0, null, undefined, null],
direction: 'desc',
type: 'date',
reverseOutput: false,
});
});
it(`should sort NaN to the end`, () => {
const now = Date.now();
testSorting({
input: [null, now, 0, undefined, Number.NaN, now - 150000],
output: [0, now - 150000, now, null, undefined, Number.NaN],
direction: 'asc',
type: 'number',
reverseOutput: false,
});
testSorting({
input: [null, now, 0, undefined, Number.NaN, now - 150000],
output: [now, now - 150000, 0, null, undefined, Number.NaN],
direction: 'desc',
type: 'number',
reverseOutput: false,
});
});
});
describe('String or anything else as string', () => {
for (const direction of ['asc', 'desc'] as const) {
it(`should provide the string criteria for terms values (${direction})`, () => {
testSorting({
input: ['a', 'b', 'c', 'd', '12'],
output: ['12', 'a', 'b', 'c', 'd'],
direction,
type: 'string',
});
});
it(`should provide the string criteria for other types of values (${direction})`, () => {
testSorting({
input: [true, false, false],
output: [false, false, true],
direction,
type: 'boolean',
});
});
}
it('should sort undefined and null to the end', () => {
testSorting({
input: ['a', null, 'b', 'c', undefined, 'd', '12'],
output: ['12', 'a', 'b', 'c', 'd', null, undefined],
direction: 'asc',
type: 'string',
reverseOutput: false,
});
testSorting({
input: ['a', null, 'b', 'c', undefined, 'd', '12'],
output: ['d', 'c', 'b', 'a', '12', null, undefined],
direction: 'desc',
type: 'string',
reverseOutput: false,
});
testSorting({
input: [true, null, false, undefined, false],
output: [false, false, true, null, undefined],
direction: 'asc',
type: 'boolean',
reverseOutput: false,
});
testSorting({
input: [true, null, false, undefined, false],
output: [true, false, false, null, undefined],
direction: 'desc',
type: 'boolean',
reverseOutput: false,
});
});
});
describe('IP sorting', () => {
for (const direction of ['asc', 'desc'] as const) {
it(`should provide the IP criteria for IP values (IPv4 only values) - ${direction}`, () => {
testSorting({
input: ['127.0.0.1', '192.168.1.50', '200.100.100.10', '10.0.1.76', '8.8.8.8'],
output: ['8.8.8.8', '10.0.1.76', '127.0.0.1', '192.168.1.50', '200.100.100.10'],
direction,
type: 'ip',
});
});
it(`should provide the IP criteria for IP values (IPv6 only values) - ${direction}`, () => {
testSorting({
input: [
'fc00::123',
'::1',
'2001:0db8:85a3:0000:0000:8a2e:0370:7334',
'2001:db8:1234:0000:0000:0000:0000:0000',
'2001:db8:1234::', // equivalent to the above
],
output: [
'::1',
'2001:db8:1234::',
'2001:db8:1234:0000:0000:0000:0000:0000',
'2001:0db8:85a3:0000:0000:8a2e:0370:7334',
'fc00::123',
],
direction,
type: 'ip',
});
});
it(`should provide the IP criteria for IP values (mixed values) - ${direction}`, () => {
// A mix of IPv4, IPv6, IPv4 mapped to IPv6
testSorting({
input: [
'fc00::123',
'192.168.1.50',
'::FFFF:192.168.1.50', // equivalent to the above with the IPv6 mapping
'10.0.1.76',
'8.8.8.8',
'::1',
],
output: [
'::1',
'8.8.8.8',
'10.0.1.76',
'192.168.1.50',
'::FFFF:192.168.1.50',
'fc00::123',
],
direction,
type: 'ip',
});
});
it(`should provide the IP criteria for IP values (mixed values with invalid "Other" field) - ${direction}`, () => {
testSorting({
input: ['fc00::123', '192.168.1.50', 'Other', '10.0.1.76', '8.8.8.8', '::1'],
output: ['::1', '8.8.8.8', '10.0.1.76', '192.168.1.50', 'fc00::123', 'Other'],
direction,
type: 'ip',
keepLast: true,
});
});
}
it('should sort undefined and null to the end', () => {
testSorting({
input: [
'fc00::123',
'192.168.1.50',
null,
undefined,
'Other',
'10.0.1.76',
'8.8.8.8',
'::1',
],
output: [
'::1',
'8.8.8.8',
'10.0.1.76',
'192.168.1.50',
'fc00::123',
'Other',
null,
undefined,
],
direction: 'asc',
type: 'ip',
reverseOutput: false,
});
testSorting({
input: [
'fc00::123',
'192.168.1.50',
null,
undefined,
'Other',
'10.0.1.76',
'8.8.8.8',
'::1',
],
output: [
'fc00::123',
'192.168.1.50',
'10.0.1.76',
'8.8.8.8',
'::1',
'Other',
null,
undefined,
],
direction: 'desc',
type: 'ip',
reverseOutput: false,
});
});
});
describe('Range sorting', () => {
for (const direction of ['asc', 'desc'] as const) {
it(`should sort closed ranges - ${direction}`, () => {
testSorting({
input: [
{ gte: 1, lt: 5 },
{ gte: 0, lt: 5 },
{ gte: 0, lt: 1 },
],
output: [
{ gte: 0, lt: 1 },
{ gte: 0, lt: 5 },
{ gte: 1, lt: 5 },
],
direction,
type: 'range',
});
});
it(`should sort open ranges - ${direction}`, () => {
testSorting({
input: [{ gte: 1, lt: 5 }, { gte: 0, lt: 5 }, { gte: 0 }],
output: [{ gte: 0, lt: 5 }, { gte: 0 }, { gte: 1, lt: 5 }],
direction,
type: 'range',
});
});
}
it('should sort undefined and null to the end', () => {
testSorting({
input: [{ gte: 1, lt: 5 }, undefined, { gte: 0, lt: 5 }, null, { gte: 0 }],
output: [{ gte: 0, lt: 5 }, { gte: 0 }, { gte: 1, lt: 5 }, undefined, null],
direction: 'asc',
type: 'range',
reverseOutput: false,
});
testSorting({
input: [{ gte: 1, lt: 5 }, undefined, { gte: 0, lt: 5 }, null, { gte: 0 }],
output: [{ gte: 1, lt: 5 }, { gte: 0 }, { gte: 0, lt: 5 }, undefined, null],
direction: 'desc',
type: 'range',
reverseOutput: false,
});
});
});
});