Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useSelections): support object array #2485

Merged
merged 2 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
271 changes: 171 additions & 100 deletions packages/hooks/src/useSelections/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,125 +1,196 @@
import { act, renderHook } from '@testing-library/react';
import useSelections from '../index';
import type { Options } from '../index';

const data = [1, 2, 3];
const _data = [1, 2, 3];
const _selected = [1];
const _selectedItem = 1;

const setup = <T>(items: T[], defaultSelected?: T[]) => {
return renderHook(() => useSelections(items, defaultSelected));
const _dataObj = [{ id: 1 }, { id: 2 }, { id: 3 }];
const _selectedObj = [{ id: 1 }];
const _selectedItemObj = { id: 1 };

const setup = <T>(items: T[], options?: T[] | Options<T>) => {
return renderHook(() => useSelections(items, options));
};

interface CaseCallback<T = number | object> {
(data: T[], selected: T[], selectedItem: T): void;
}

const runCaseCallback = (
dataCallback: CaseCallback<number>,
objDataCallback: CaseCallback<object>,
) => {
dataCallback(_data, _selected, _selectedItem);
objDataCallback(_dataObj, _selectedObj, _selectedItemObj);
};

describe('useSelections', () => {
it('defaultSelected should work correct', () => {
const { result } = setup(data, [1]);
expect(result.current.selected).toEqual([1]);
expect(result.current.isSelected(1)).toBe(true);
const caseCallback: CaseCallback = (data, selected, selectedItem) => {
const { result } = setup(data, {
defaultSelected: selected,
itemKey: 'id',
});

expect(result.current.selected).toEqual(selected);
expect(result.current.isSelected(selectedItem)).toBe(true);
};

runCaseCallback(caseCallback, caseCallback);
});

it('select and unSelect should work correct', () => {
const { result } = setup(data, [1]);
const { unSelect, select } = result.current;
act(() => {
unSelect(1);
});
expect(result.current.selected).toEqual([]);
expect(result.current.isSelected(1)).toBe(false);
expect(result.current.allSelected).toBe(false);
act(() => {
select(1);
});
expect(result.current.selected).toEqual([1]);
expect(result.current.isSelected(1)).toBe(true);
expect(result.current.allSelected).toBe(false);
const caseCallback: CaseCallback = (data, selected, selectedItem) => {
const { result } = setup(data, {
defaultSelected: selected,
itemKey: 'id',
});
const { unSelect, select } = result.current;

act(() => {
unSelect(selectedItem);
});
expect(result.current.selected).toEqual([]);
expect(result.current.isSelected(selectedItem)).toBe(false);
expect(result.current.allSelected).toBe(false);

act(() => {
select(selectedItem);
});
expect(result.current.selected).toEqual(selected);
expect(result.current.isSelected(selectedItem)).toBe(true);
expect(result.current.allSelected).toBe(false);
};

runCaseCallback(caseCallback, caseCallback);
});

it('toggle should work correct', () => {
const { result } = setup(data);
const { toggle } = result.current;
act(() => {
toggle(1);
});
expect(result.current.selected).toEqual([1]);
expect(result.current.isSelected(1)).toBe(true);
expect(result.current.allSelected).toBe(false);
act(() => {
toggle(1);
});
expect(result.current.selected).toEqual([]);
expect(result.current.isSelected(1)).toBe(false);
expect(result.current.allSelected).toBe(false);
const caseCallback: CaseCallback = (data, selected, selectedItem) => {
const { result } = setup(data, {
itemKey: 'id',
});
const { toggle } = result.current;

act(() => {
toggle(selectedItem);
});
expect(result.current.selected).toEqual(selected);
expect(result.current.isSelected(selectedItem)).toBe(true);
expect(result.current.allSelected).toBe(false);

act(() => {
toggle(selectedItem);
});
expect(result.current.selected).toEqual([]);
expect(result.current.isSelected(selectedItem)).toBe(false);
expect(result.current.allSelected).toBe(false);
};

runCaseCallback(caseCallback, caseCallback);
});

it('selectAll and unSelectAll should work correct', async () => {
const { result } = setup(data);
const { selectAll, unSelectAll } = result.current;

expect(result.current.noneSelected).toBe(true);
act(() => {
selectAll();
});
expect(result.current.selected).toEqual([1, 2, 3]);
expect(result.current.allSelected).toBe(true);
expect(result.current.noneSelected).toBe(false);
expect(result.current.partiallySelected).toBe(false);

act(() => {
unSelectAll();
});
expect(result.current.selected).toEqual([]);
expect(result.current.allSelected).toBe(false);
expect(result.current.noneSelected).toBe(true);
expect(result.current.partiallySelected).toBe(false);
const caseCallback: CaseCallback = (data) => {
const { result } = setup(data, {
itemKey: 'id',
});
const { selectAll, unSelectAll } = result.current;

expect(result.current.noneSelected).toBe(true);
act(() => {
selectAll();
});
expect(result.current.selected).toEqual(data);
expect(result.current.allSelected).toBe(true);
expect(result.current.noneSelected).toBe(false);
expect(result.current.partiallySelected).toBe(false);

act(() => {
unSelectAll();
});
expect(result.current.selected).toEqual([]);
expect(result.current.allSelected).toBe(false);
expect(result.current.noneSelected).toBe(true);
expect(result.current.partiallySelected).toBe(false);
};

runCaseCallback(caseCallback, caseCallback);
});

it('toggleAll should work correct', async () => {
const { result } = setup(data);
const { toggleAll } = result.current;
expect(result.current.noneSelected).toBe(true);
act(() => {
toggleAll();
});
expect(result.current.selected).toEqual([1, 2, 3]);
expect(result.current.allSelected).toBe(true);
expect(result.current.noneSelected).toBe(false);
expect(result.current.partiallySelected).toBe(false);

act(() => {
toggleAll();
});
expect(result.current.selected).toEqual([]);
expect(result.current.allSelected).toBe(false);
expect(result.current.noneSelected).toBe(true);
expect(result.current.partiallySelected).toBe(false);
const caseCallback: CaseCallback = (data) => {
const { result } = setup(data, {
itemKey: 'id',
});
const { toggleAll } = result.current;

expect(result.current.noneSelected).toBe(true);
act(() => {
toggleAll();
});
expect(result.current.selected).toEqual(data);
expect(result.current.allSelected).toBe(true);
expect(result.current.noneSelected).toBe(false);
expect(result.current.partiallySelected).toBe(false);

act(() => {
toggleAll();
});
expect(result.current.selected).toEqual([]);
expect(result.current.allSelected).toBe(false);
expect(result.current.noneSelected).toBe(true);
expect(result.current.partiallySelected).toBe(false);
};

runCaseCallback(caseCallback, caseCallback);
});

it('setSelected should work correct', async () => {
const { result } = setup(data);
const { setSelected } = result.current;
expect(result.current.noneSelected).toBe(true);
act(() => {
setSelected([1]);
});
expect(result.current.selected).toEqual([1]);
expect(result.current.isSelected(1)).toBe(true);
expect(result.current.noneSelected).toBe(false);
expect(result.current.allSelected).toBe(false);
expect(result.current.partiallySelected).toBe(true);

act(() => {
setSelected([]);
});
expect(result.current.selected).toEqual([]);
expect(result.current.isSelected(1)).toBe(false);
expect(result.current.noneSelected).toBe(true);
expect(result.current.allSelected).toBe(false);
expect(result.current.partiallySelected).toBe(false);

act(() => {
setSelected([1, 2, 3]);
});
expect(result.current.selected).toEqual([1, 2, 3]);
expect(result.current.isSelected(1)).toBe(true);
expect(result.current.noneSelected).toBe(false);
expect(result.current.allSelected).toBe(true);
expect(result.current.partiallySelected).toBe(false);
const caseCallback: CaseCallback = (data, selected, selectedItem) => {
const { result } = setup(data, {
itemKey: 'id',
});
const { setSelected } = result.current;

expect(result.current.noneSelected).toBe(true);
act(() => {
setSelected(selected);
});
expect(result.current.selected).toEqual(selected);
expect(result.current.isSelected(selectedItem)).toBe(true);
expect(result.current.noneSelected).toBe(false);
expect(result.current.allSelected).toBe(false);
expect(result.current.partiallySelected).toBe(true);

act(() => {
setSelected([]);
});
expect(result.current.selected).toEqual([]);
expect(result.current.isSelected(selectedItem)).toBe(false);
expect(result.current.noneSelected).toBe(true);
expect(result.current.allSelected).toBe(false);
expect(result.current.partiallySelected).toBe(false);

act(() => {
setSelected(data);
});
expect(result.current.selected).toEqual(data);
expect(result.current.isSelected(selectedItem)).toBe(true);
expect(result.current.noneSelected).toBe(false);
expect(result.current.allSelected).toBe(true);
expect(result.current.partiallySelected).toBe(false);
};

runCaseCallback(caseCallback, caseCallback);
});

it('legacy parameter should work in <4.0', async () => {
const { result } = setup(_data, _selected);

expect(result.current.selected).toEqual(_selected);
expect(result.current.isSelected(_selectedItem)).toBe(true);
});
});
6 changes: 4 additions & 2 deletions packages/hooks/src/useSelections/demo/demo1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ export default () => {

const { selected, allSelected, isSelected, toggle, toggleAll, partiallySelected } = useSelections(
list,
[1],
{
defaultSelected: [1],
},
);

return (
<div>
<div>Selected : {selected.join(',')}</div>
<div>Selected: {selected.join(',')}</div>
<div style={{ borderBottom: '1px solid #E9E9E9', padding: '10px 0' }}>
<Checkbox checked={allSelected} onClick={toggleAll} indeterminate={partiallySelected}>
Check all
Expand Down
52 changes: 52 additions & 0 deletions packages/hooks/src/useSelections/demo/demo2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* title: Object array
* desc: When array items are object, you need to specify the field name for the unique key.
*
* title.zh-CN: 对象数组
* desc.zh-CN: 数组项是对象时,需要指定唯一 key 的字段名称。
*/

import { Checkbox, Col, Row } from 'antd';
import React, { useMemo, useState } from 'react';
import { useSelections } from 'ahooks';

export default () => {
const [hideOdd, setHideOdd] = useState(false);
const list = useMemo(() => {
if (hideOdd) {
return [2, 4, 6, 8].map((id) => ({ id }));
}
return [1, 2, 3, 4, 5, 6, 7, 8].map((id) => ({ id }));
}, [hideOdd]);

const { selected, allSelected, isSelected, toggle, toggleAll, partiallySelected } = useSelections(
list,
{
defaultSelected: [{ id: 1 }],
itemKey: 'id',
},
);

return (
<div>
<div>Selected: {JSON.stringify(selected)}</div>
<div style={{ borderBottom: '1px solid #E9E9E9', padding: '10px 0' }}>
<Checkbox checked={allSelected} onClick={toggleAll} indeterminate={partiallySelected}>
Check all
</Checkbox>
<Checkbox checked={hideOdd} onClick={() => setHideOdd((v) => !v)}>
Hide Odd
</Checkbox>
</div>
<Row style={{ padding: '10px 0' }}>
{list.map((item) => (
<Col span={12} key={item.id}>
<Checkbox checked={isSelected(item)} onClick={() => toggle(item)}>
{item.id}
</Checkbox>
</Col>
))}
</Row>
</div>
);
};
Loading
Loading