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

[IndexTable]: optimizes and add more stuff to use-index-resource-state #11657

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions .changeset/good-lizards-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@shopify/polaris': minor
---

- adds dirty and unselected states to `use-index-resource-state`
- adds stronger types to `use-index-resource-state`
- Keeps the same array interface of the API but uses `Set` under the hood for performance optimization.
- Set was used since it offers O(1) direct access to its values. In some cases we removed O(N) traversal times since we cared about access lookup.
150 changes: 85 additions & 65 deletions polaris-react/src/utilities/tests/use-index-resource-state.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,7 @@ import {
SelectionType,
} from '../use-index-resource-state';

interface TypedChildProps {
onClick: ReturnType<typeof useIndexResourceState>['handleSelectionChange'];
allResourcesSelected: ReturnType<
typeof useIndexResourceState
>['allResourcesSelected'];
selectedResources: ReturnType<
typeof useIndexResourceState
>['selectedResources'];
removeSelectedResources: ReturnType<
typeof useIndexResourceState
>['removeSelectedResources'];
}
interface TypedChildProps extends ReturnType<typeof useIndexResourceState> {}

describe('useIndexResourceState', () => {
function TypedChild(_: TypedChildProps) {
Expand All @@ -28,46 +17,28 @@ describe('useIndexResourceState', () => {
resources = [],
options,
}: {
resources?: T[];
resources?: readonly T[];
options?: Parameters<typeof useIndexResourceState>[1];
}) {
const {
selectedResources,
allResourcesSelected,
handleSelectionChange,
removeSelectedResources,
} = useIndexResourceState(resources, options);

return (
<TypedChild
onClick={handleSelectionChange}
selectedResources={selectedResources}
allResourcesSelected={allResourcesSelected}
removeSelectedResources={removeSelectedResources}
/>
);
}

function MockClearComponent<T extends {[key: string]: unknown}>({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was unnecessary since we can do everything from the "main" component

resources = [],
options,
}: {
resources?: T[];
options?: Parameters<typeof useIndexResourceState>[1];
}) {
const {
selectedResources,
allResourcesSelected,
dirty,
unselectedResources,
clearSelection,
removeSelectedResources,
} = useIndexResourceState(resources, options);

return (
<TypedChild
onClick={clearSelection}
handleSelectionChange={handleSelectionChange}
selectedResources={selectedResources}
allResourcesSelected={allResourcesSelected}
removeSelectedResources={removeSelectedResources}
dirty={dirty}
unselectedResources={unselectedResources}
clearSelection={clearSelection}
/>
);
}
Expand Down Expand Up @@ -122,7 +93,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Page, true);
.trigger('handleSelectionChange', SelectionType.Page, true);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [selectedID],
Expand All @@ -139,7 +110,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Page, true);
.trigger('handleSelectionChange', SelectionType.Page, true);
}

expect(throwResourceSelectionError).toThrow(
Expand All @@ -161,7 +132,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Page, true);
.trigger('handleSelectionChange', SelectionType.Page, true);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [selectedID],
Expand All @@ -177,7 +148,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Page, true);
.trigger('handleSelectionChange', SelectionType.Page, true);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [selectedID],
Expand All @@ -199,7 +170,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Page, true);
.trigger('handleSelectionChange', SelectionType.Page, true);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [selectedID],
Expand All @@ -214,7 +185,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.All, true);
.trigger('handleSelectionChange', SelectionType.All, true);

expect(mockComponent).toContainReactComponent(TypedChild, {
allResourcesSelected: true,
Expand All @@ -228,7 +199,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.All, false);
.trigger('handleSelectionChange', SelectionType.All, false);

expect(mockComponent).toContainReactComponent(TypedChild, {
allResourcesSelected: false,
Expand All @@ -242,7 +213,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Single, false, '1');
.trigger('handleSelectionChange', SelectionType.Single, false, '1');

expect(mockComponent).toContainReactComponent(TypedChild, {
allResourcesSelected: false,
Expand All @@ -260,7 +231,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Single, true, id);
.trigger('handleSelectionChange', SelectionType.Single, true, id);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [id],
Expand All @@ -279,7 +250,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Single, false, id);
.trigger('handleSelectionChange', SelectionType.Single, false, id);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [],
Expand All @@ -305,7 +276,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.All, true);
.trigger('handleSelectionChange', SelectionType.All, true);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [idOne],
Expand All @@ -323,7 +294,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.All, true);
.trigger('handleSelectionChange', SelectionType.All, true);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [idOne, idTwo],
Expand All @@ -343,7 +314,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.All, false);
.trigger('handleSelectionChange', SelectionType.All, false);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [],
Expand All @@ -369,7 +340,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.All, true);
.trigger('handleSelectionChange', SelectionType.All, true);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [idOne],
Expand All @@ -387,7 +358,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Page, true);
.trigger('handleSelectionChange', SelectionType.Page, true);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [idOne, idTwo],
Expand All @@ -407,7 +378,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Page, false);
.trigger('handleSelectionChange', SelectionType.Page, false);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [],
Expand All @@ -434,7 +405,12 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Multi, true, [0, 1]);
.trigger(
'handleSelectionChange',
SelectionType.Multi,
true,
[0, 1],
);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [idOne],
Expand All @@ -450,7 +426,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Multi, true);
.trigger('handleSelectionChange', SelectionType.Multi, true);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources,
Expand All @@ -468,7 +444,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Multi, true, [0, 1]);
.trigger('handleSelectionChange', SelectionType.Multi, true, [0, 1]);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [idOne, idTwo],
Expand All @@ -489,7 +465,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Multi, false, [0, 1]);
.trigger('handleSelectionChange', SelectionType.Multi, false, [0, 1]);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [idThree],
Expand All @@ -516,7 +492,12 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Range, true, [0, 2]);
.trigger(
'handleSelectionChange',
SelectionType.Range,
true,
[0, 2],
);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [idTwo, idThree],
Expand All @@ -535,7 +516,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Range, true, [0, 2]);
.trigger('handleSelectionChange', SelectionType.Range, true, [0, 2]);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [idOne, idTwo, idThree],
Expand All @@ -554,7 +535,7 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Range, true, [0, 2]);
.trigger('handleSelectionChange', SelectionType.Range, true, [0, 2]);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [idOne, idTwo, idThree],
Expand All @@ -575,13 +556,27 @@ describe('useIndexResourceState', () => {

mockComponent
.find(TypedChild)!
.trigger('onClick', SelectionType.Range, false, [0, 2]);
.trigger('handleSelectionChange', SelectionType.Range, false, [0, 2]);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [],
});
});
});

describe('dirty', () => {
it('sets dirty to true when a selection is made', () => {
const mockComponent = mountWithApp(<MockComponent />);

mockComponent
.find(TypedChild)!
.trigger('handleSelectionChange', SelectionType.Single, true, '1');

expect(mockComponent).toContainReactComponent(TypedChild, {
dirty: true,
});
});
});
});

describe('clearSelection', () => {
Expand All @@ -591,16 +586,41 @@ describe('useIndexResourceState', () => {
const idThree = '3';
const resources = [{id: idOne}, {id: idTwo}, {id: idThree}];
const mockComponent = mountWithApp(
<MockClearComponent
<MockComponent
resources={resources}
options={{selectedResources: [idOne, idTwo, idThree]}}
options={{selectedResources: [idOne, idThree]}}
/>,
);

mockComponent.find(TypedChild)!.trigger('onClick');
mockComponent
.find(TypedChild)!
.trigger('handleSelectionChange', SelectionType.Single, false, '1');

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [idThree],
allResourcesSelected: false,
dirty: true,
unselectedResources: [idOne],
});

mockComponent
.find(TypedChild)!
.trigger('handleSelectionChange', SelectionType.All, true);

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [idOne, idTwo, idThree],
allResourcesSelected: true,
dirty: true,
unselectedResources: [],
});

mockComponent.find(TypedChild)!.trigger('clearSelection');

expect(mockComponent).toContainReactComponent(TypedChild, {
selectedResources: [],
allResourcesSelected: false,
dirty: true,
unselectedResources: [],
});
});
});
Expand All @@ -612,7 +632,7 @@ describe('useIndexResourceState', () => {
const idThree = '3';
const resources = [{id: idOne}, {id: idTwo}, {id: idThree}];
const mockComponent = mountWithApp(
<MockClearComponent
<MockComponent
resources={resources}
options={{selectedResources: [idOne, idTwo, idThree]}}
/>,
Expand Down
Loading
Loading