Files
flipper/desktop/flipper-plugin/src/ui/data-table/__tests__/DataTable.node.tsx
Michel Weststrate 2127dea447 Fix initialisation error
Summary: There was an initialisation error possible if the crash reporter tried to start with an initial selection (after coming from a deeplink or the notification pane), which would cause an unending update loop.

Reviewed By: passy

Differential Revision: D29331127

fbshipit-source-id: 14e75e483c232039e6a80aa131fa5ce7c105b670
2021-06-24 01:37:09 -07:00

625 lines
13 KiB
TypeScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import React, {createRef} from 'react';
import {DataTable, DataTableColumn} from '../DataTable';
import {render, act} from '@testing-library/react';
import {createDataSource} from '../../../state/createDataSource';
import {computeDataTableFilter, DataTableManager} from '../DataTableManager';
import {Button} from 'antd';
type Todo = {
title: string;
done: boolean;
};
function createTestDataSource() {
return createDataSource<Todo>([
{
title: 'test DataTable',
done: true,
},
]);
}
const columns: DataTableColumn[] = [
{
key: 'title',
wrap: false,
},
{
key: 'done',
wrap: false,
},
];
test('update and append', async () => {
const ds = createTestDataSource();
const ref = createRef<DataTableManager<Todo>>();
const rendering = render(
<DataTable dataSource={ds} columns={columns} tableManagerRef={ref} />,
);
{
const elem = await rendering.findAllByText('test DataTable');
expect(elem.length).toBe(1);
expect(elem[0].parentElement).toMatchInlineSnapshot(`
<div
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
>
<div
class="css-9bipfg-TableBodyColumnContainer e1luu51r0"
width="50%"
>
test DataTable
</div>
<div
class="css-9bipfg-TableBodyColumnContainer e1luu51r0"
width="50%"
>
true
</div>
</div>
`);
}
act(() => {
ds.append({
title: 'Drink coffee',
done: false,
});
});
{
const elem = await rendering.findAllByText('Drink coffee');
expect(elem.length).toBe(1);
}
// update
act(() => {
ds.update(0, {
title: 'DataTable tested',
done: false,
});
});
{
const elem = await rendering.findAllByText('DataTable tested');
expect(elem.length).toBe(1);
expect(rendering.queryByText('test DataTable')).toBeNull();
}
});
test('column visibility', async () => {
const ds = createTestDataSource();
const ref = createRef<DataTableManager<Todo>>();
const rendering = render(
<DataTable dataSource={ds} columns={columns} tableManagerRef={ref} />,
);
{
const elem = await rendering.findAllByText('test DataTable');
expect(elem.length).toBe(1);
expect(elem[0].parentElement).toMatchInlineSnapshot(`
<div
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
>
<div
class="css-9bipfg-TableBodyColumnContainer e1luu51r0"
width="50%"
>
test DataTable
</div>
<div
class="css-9bipfg-TableBodyColumnContainer e1luu51r0"
width="50%"
>
true
</div>
</div>
`);
}
// hide done
act(() => {
ref.current?.toggleColumnVisibility('done');
});
{
const elem = await rendering.findAllByText('test DataTable');
expect(elem.length).toBe(1);
expect(elem[0].parentElement).toMatchInlineSnapshot(`
<div
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
>
<div
class="css-9bipfg-TableBodyColumnContainer e1luu51r0"
width="50%"
>
test DataTable
</div>
</div>
`);
}
// reset
act(() => {
ref.current?.reset();
});
{
const elem = await rendering.findAllByText('test DataTable');
expect(elem.length).toBe(1);
expect(elem[0].parentElement?.children.length).toBe(2);
}
});
test('sorting', async () => {
const ds = createTestDataSource();
ds.clear();
ds.append({
title: 'item a',
done: false,
});
ds.append({
title: 'item x',
done: false,
});
ds.append({
title: 'item b',
done: false,
});
const ref = createRef<DataTableManager<Todo>>();
const rendering = render(
<DataTable dataSource={ds} columns={columns} tableManagerRef={ref} />,
);
// insertion order
{
const elem = await rendering.findAllByText(/item/);
expect(elem.length).toBe(3);
expect(elem.map((e) => e.textContent)).toEqual([
'item a',
'item x',
'item b',
]);
}
// sort asc
act(() => {
ref.current?.sortColumn('title', 'asc');
});
{
const elem = await rendering.findAllByText(/item/);
expect(elem.length).toBe(3);
expect(elem.map((e) => e.textContent)).toEqual([
'item a',
'item b',
'item x',
]);
}
// sort desc
act(() => {
ref.current?.sortColumn('title', 'desc');
});
{
const elem = await rendering.findAllByText(/item/);
expect(elem.length).toBe(3);
expect(elem.map((e) => e.textContent)).toEqual([
'item x',
'item b',
'item a',
]);
}
// reset sort
act(() => {
ref.current?.sortColumn('title', undefined);
});
{
const elem = await rendering.findAllByText(/item/);
expect(elem.length).toBe(3);
expect(elem.map((e) => e.textContent)).toEqual([
'item a',
'item x',
'item b',
]);
}
});
test('search', async () => {
const ds = createTestDataSource();
ds.clear();
ds.append({
title: 'item abc',
done: false,
});
ds.append({
title: 'item x',
done: false,
});
ds.append({
title: 'item b',
done: false,
});
const ref = createRef<DataTableManager<Todo>>();
const rendering = render(
<DataTable
dataSource={ds}
columns={columns}
tableManagerRef={ref}
extraActions={<Button>Test Button</Button>}
/>,
);
{
// button is visible
rendering.getByText('Test Button');
const elem = await rendering.findAllByText(/item/);
expect(elem.length).toBe(3);
expect(elem.map((e) => e.textContent)).toEqual([
'item abc',
'item x',
'item b',
]);
}
{
// filter
act(() => {
ref.current?.setSearchValue('b');
});
const elem = await rendering.findAllByText(/item/);
expect(elem.map((e) => e.textContent)).toEqual(['item abc', 'item b']);
}
{
// reset
act(() => {
ref.current?.reset();
});
const elem = await rendering.findAllByText(/item/);
expect(elem.length).toBe(3);
}
});
test('compute filters', () => {
const coffee = {
level: 'info',
title: 'Drink coffee',
done: true,
};
const espresso = {
level: 'info',
title: 'Make espresso',
done: false,
};
const meet = {
level: 'error',
title: 'Meet me',
done: false,
};
const data = [coffee, espresso, meet];
// results in empty filter
expect(computeDataTableFilter('', false, [])).toBeUndefined();
expect(
computeDataTableFilter('', false, [
{
key: 'title',
filters: [
{
enabled: false,
value: 'coffee',
label: 'coffee',
},
],
},
]),
).toBeUndefined();
{
const filter = computeDataTableFilter('tEsT', false, [])!;
expect(data.filter(filter)).toEqual([]);
}
{
const filter = computeDataTableFilter('EE', false, [])!;
expect(data.filter(filter)).toEqual([coffee, meet]);
}
{
const filter = computeDataTableFilter('D', false, [])!;
expect(data.filter(filter)).toEqual([coffee]);
}
{
// regex, positive (mind the double escaping of \\b)
const filter = computeDataTableFilter('..t', true, [])!;
expect(data.filter(filter)).toEqual([meet]);
}
{
// regex, words with 6 chars
const filter = computeDataTableFilter('\\w{6}', true, [])!;
expect(data.filter(filter)).toEqual([coffee, espresso]);
}
{
// no match
const filter = computeDataTableFilter('\\w{18}', true, [])!;
expect(data.filter(filter)).toEqual([]);
}
{
// invalid regex
const filter = computeDataTableFilter('bla/[', true, [])!;
expect(data.filter(filter)).toEqual([]);
}
{
const filter = computeDataTableFilter('true', false, [])!;
expect(data.filter(filter)).toEqual([coffee]);
}
{
const filter = computeDataTableFilter('false', false, [])!;
expect(data.filter(filter)).toEqual([espresso, meet]);
}
{
const filter = computeDataTableFilter('EE', false, [
{
key: 'level',
filters: [
{
enabled: true,
value: 'error',
label: 'error',
},
],
},
])!;
expect(data.filter(filter)).toEqual([meet]);
}
{
const filter = computeDataTableFilter('EE', false, [
{
key: 'level',
filters: [
{
enabled: true,
value: 'info',
label: 'info',
},
{
enabled: true,
value: 'error',
label: 'error',
},
],
},
])!;
expect(data.filter(filter)).toEqual([coffee, meet]);
}
{
const filter = computeDataTableFilter('', false, [
{
key: 'level',
filters: [
{
enabled: true,
value: 'info',
label: 'info',
},
{
enabled: false,
value: 'error',
label: 'error',
},
],
},
])!;
expect(data.filter(filter)).toEqual([coffee, espresso]);
}
{
const filter = computeDataTableFilter('', false, [
{
key: 'done',
filters: [
{
enabled: true,
value: 'false',
label: 'Not done',
},
],
},
])!;
expect(data.filter(filter)).toEqual([espresso, meet]);
}
{
// nothing selected anything will not filter anything out for that column
const filter = computeDataTableFilter('', false, [
{
key: 'level',
filters: [
{
enabled: false,
value: 'info',
label: 'info',
},
{
enabled: false,
value: 'error',
label: 'error',
},
],
},
])!;
expect(filter).toBeUndefined();
}
{
const filter = computeDataTableFilter('', false, [
{
key: 'level',
filters: [
{
enabled: true,
value: 'info',
label: 'info',
},
{
enabled: true,
value: 'error',
label: 'error',
},
],
},
])!;
expect(data.filter(filter)).toEqual([coffee, espresso, meet]);
}
{
const filter = computeDataTableFilter('', false, [
{
key: 'level',
filters: [
{
enabled: true,
value: 'info',
label: 'info',
},
],
},
{
key: 'done',
filters: [
{
enabled: true,
value: 'false',
label: 'false,',
},
],
},
])!;
expect(data.filter(filter)).toEqual([espresso]);
}
{
const filter = computeDataTableFilter('nonsense', false, [
{
key: 'level',
filters: [
{
enabled: true,
value: 'info',
label: 'info',
},
],
},
{
key: 'done',
filters: [
{
enabled: true,
value: 'false',
label: 'false,',
},
],
},
])!;
expect(data.filter(filter)).toEqual([]);
}
});
test('onSelect callback fires, and in order', () => {
const events: any[] = [];
const ds = createTestDataSource();
const ref = createRef<DataTableManager<Todo>>();
const rendering = render(
<DataTable
dataSource={ds}
columns={columns}
tableManagerRef={ref}
onSelect={(item, items) => {
events.push([item, items]);
}}
/>,
);
const item1 = {
title: 'item 1',
done: false,
};
const item2 = {
title: 'item 2',
done: false,
};
const item3 = {
title: 'item 3',
done: false,
};
act(() => {
ds.clear();
ds.append(item1);
ds.append(item2);
ds.append(item3);
ref.current!.selectItem(2);
});
expect(events.splice(0)).toEqual([[item3, [item3]]]);
act(() => {
ref.current!.addRangeToSelection(0, 0);
});
expect(events.splice(0)).toEqual([
[item1, [item1, item3]], // order preserved!
]);
rendering.unmount();
});
test('selection always has the latest state', () => {
const events: any[] = [];
const ds = createTestDataSource();
const ref = createRef<DataTableManager<Todo>>();
const rendering = render(
<DataTable
dataSource={ds}
columns={columns}
tableManagerRef={ref}
onSelect={(item, items) => {
events.push([item, items]);
}}
/>,
);
const item1 = {
title: 'item 1',
done: false,
};
const item2 = {
title: 'item 2',
done: false,
};
const item3 = {
title: 'item 3',
done: false,
};
act(() => {
ds.clear();
ds.append(item1);
ds.append(item2);
ds.append(item3);
ref.current!.selectItem(2);
});
expect(events.splice(0)).toEqual([[item3, [item3]]]);
const item3updated = {
title: 'item 3 updated',
done: false,
};
act(() => {
ds.update(2, item3updated);
});
act(() => {
ref.current!.addRangeToSelection(0, 0);
});
expect(events.splice(0)).toEqual([
[item1, [item1, item3updated]], // update reflected in callback!
]);
rendering.unmount();
});