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
625 lines
13 KiB
TypeScript
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();
|
|
});
|