Skip to content

Rework multiple selection with activatable rows in grid #2064

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

Merged
merged 48 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
965e933
🔈
jpzwarte May 12, 2025
25fc94a
🎰
jpzwarte May 12, 2025
2cdd97a
🎀
jpzwarte May 12, 2025
2767893
🐱
jpzwarte May 12, 2025
862a45e
⛵️
jpzwarte May 13, 2025
309d260
🍫
jpzwarte May 13, 2025
f692721
⛺️
jpzwarte May 14, 2025
db98d24
🚣
jpzwarte May 14, 2025
098ab66
💪
jpzwarte May 14, 2025
26616d0
🎀
jpzwarte May 14, 2025
f7cd372
🌻
jpzwarte May 15, 2025
9831271
Merge branch 'main' into feature/2028-table-data-source
jpzwarte May 15, 2025
3ae9c52
🐭
jpzwarte May 18, 2025
2b47876
Merge branch 'main' into feature/2028-table-data-source
jpzwarte May 19, 2025
727d58c
🐄
jpzwarte May 20, 2025
6c50fde
🎆
jpzwarte May 20, 2025
6e4fb68
🐩
jpzwarte May 21, 2025
dcb27b1
🍫
jpzwarte May 21, 2025
c07067f
🌷
jpzwarte May 21, 2025
6facdfe
🐥
jpzwarte May 21, 2025
5629a7e
Merge branch 'main' into feature/2028-table-data-source
jpzwarte May 21, 2025
ae9f909
🏸
jpzwarte May 22, 2025
ebf05a3
🏅
jpzwarte May 22, 2025
19bdc38
🐨
jpzwarte May 22, 2025
ea60a41
Merge branch 'main' into feature/2028-table-data-source
jpzwarte May 22, 2025
5afa578
🍣
jpzwarte May 22, 2025
1a45d2e
🏈
jpzwarte May 22, 2025
dc3ba37
Merge branch 'main' into feature/2028-table-data-source
jpzwarte May 22, 2025
4bfd009
🐍
jpzwarte May 22, 2025
369a652
🐚
jpzwarte May 22, 2025
893bde7
🌝
jpzwarte May 22, 2025
367968e
🐞
jpzwarte May 23, 2025
ede591d
🐺
jpzwarte May 23, 2025
e804370
💿
jpzwarte May 23, 2025
ea260ab
🐝
jpzwarte May 23, 2025
3823024
🍀
jpzwarte May 23, 2025
a302f93
🍷
jpzwarte May 23, 2025
f8ed8df
🐖
jpzwarte May 23, 2025
bd06ae3
🌎
jpzwarte May 23, 2025
17440dc
🚨
jpzwarte May 25, 2025
f121c6b
🔮
jpzwarte May 26, 2025
ce83aa5
Merge branch 'main' into fix/2046-grid-mixed-selection
jpzwarte May 27, 2025
bd7fd1c
🍗
jpzwarte May 27, 2025
64ac559
Merge branch 'main' into fix/2046-grid-mixed-selection
jpzwarte May 27, 2025
468986c
💥
jpzwarte May 28, 2025
432e3e2
🐵
jpzwarte May 28, 2025
4d93437
😈
jpzwarte May 28, 2025
de094ba
🏂
jpzwarte May 28, 2025
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
Prev Previous commit
Next Next commit
🚣
  • Loading branch information
jpzwarte committed May 14, 2025
commit db98d245bf5389918f984ffd1d9892e84744536c
263 changes: 255 additions & 8 deletions packages/components/data-source/src/array-list-data-source.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { ArrayListDataSource } from './array-list-data-source.js';
import {
DATA_SOURCE_DEFAULT_PAGE_SIZE,
type ListDataSourceDataItem,
isListDataSourceDataItem
isListDataSourceDataItem,
isListDataSourceGroupItem
} from './list-data-source.js';
import { type Person, people } from './list-data-source.spec.js';

Expand Down Expand Up @@ -247,20 +248,266 @@ describe('ArrayListDataSource', () => {

describe('selection', () => {
describe('none', () => {
it('should do nothing when calling select()', () => {});
beforeEach(() => {
ds = new ArrayListDataSource(people);
});

it('should not have a selected any items', () => {
expect(ds.selection.size).to.equal(0);
});

it('should do nothing when calling select()', () => {
spy(ds.selection, 'add');

ds.select(ds.items.at(0)!);

expect(ds.selection.add).not.to.have.been.called;
});

it('should do nothing when calling deselect()', () => {});
it('should do nothing when calling deselect()', () => {
spy(ds.selection, 'delete');

it('should do nothing when calling toggle()', () => {});
ds.deselect(ds.items.at(0)!);

expect(ds.selection.delete).not.to.have.been.called;
});

it('should do nothing when calling toggle()', () => {
spy(ds.selection, 'add');
spy(ds.selection, 'delete');

ds.toggle(ds.items.at(0)!);

expect(ds.selection.add).not.to.have.been.called;
expect(ds.selection.delete).not.to.have.been.called;
});

it('should always return false when calling isSelected()', () => {});
it('should always return false when calling isSelected()', () => {
expect(ds.isSelected(ds.items.at(0))).to.be.false;
});
});

describe('single', () => {});
describe('single select', () => {
beforeEach(() => {
ds = new ArrayListDataSource(people, { selects: 'single' });
});

it('should have a selection mode', () => {
expect(ds.selects).to.equal('single');
});

it('should select an item', () => {
ds.select(ds.items.at(0)!);

expect(ds.selection.size).to.equal(1);
expect(ds.isSelected(ds.items.at(0))).to.be.true;
});

it('should deselect the previous item when calling select()', () => {
ds.select(ds.items.at(0)!);
ds.select(ds.items.at(1)!);

expect(ds.selection.size).to.equal(1);
expect(ds.isSelected(ds.items.at(0))).to.be.false;
expect(ds.isSelected(ds.items.at(1))).to.be.true;
});

it('should deselect an item', () => {
ds.select(ds.items.at(0)!);
ds.deselect(ds.items.at(0)!);

describe('multiple', () => {});
expect(ds.selection.size).to.equal(0);
expect(ds.isSelected(ds.items.at(0))).to.be.false;
});

describe('with groups', () => {});
it('should toggle an item', () => {
ds.toggle(ds.items.at(0)!);

expect(ds.selection.size).to.equal(1);
expect(ds.isSelected(ds.items.at(0))).to.be.true;

ds.toggle(ds.items.at(0)!);

expect(ds.selection.size).to.equal(0);
expect(ds.isSelected(ds.items.at(0))).to.be.false;
});
});

describe('multiple select', () => {
beforeEach(() => {
ds = new ArrayListDataSource(people, { selects: 'multiple' });
});

it('should have a selection mode', () => {
expect(ds.selects).to.equal('multiple');
});

it('should select an item', () => {
ds.select(ds.items.at(0)!);

expect(ds.selection.size).to.equal(1);
expect(ds.isSelected(ds.items.at(0))).to.be.true;
});

it('should select multiple items when calling select()', () => {
ds.select(ds.items.at(0)!);
ds.select(ds.items.at(1)!);

expect(ds.selection.size).to.equal(2);
expect(ds.isSelected(ds.items.at(0))).to.be.true;
expect(ds.isSelected(ds.items.at(1))).to.be.true;
});

it('should deselect an item', () => {
ds.select(ds.items.at(0)!);
ds.deselect(ds.items.at(0)!);

expect(ds.selection.size).to.equal(0);
expect(ds.isSelected(ds.items.at(0))).to.be.false;
});

it('should toggle an item', () => {
ds.toggle(ds.items.at(0)!);

expect(ds.selection.size).to.equal(1);
expect(ds.isSelected(ds.items.at(0))).to.be.true;

ds.toggle(ds.items.at(0)!);

expect(ds.selection.size).to.equal(0);
expect(ds.isSelected(ds.items.at(0))).to.be.false;
});
});

describe('select all', () => {
beforeEach(() => {
ds = new ArrayListDataSource(people, { selects: 'multiple' });
});

it('should toggle isSelectAllToggled() after calling selectAll()', () => {
expect(ds.isSelectAllToggled()).to.be.false;

ds.selectAll();

expect(ds.isSelectAllToggled()).to.be.true;
});

it('should toggle isSelectAllToggled() after calling deselectAll()', () => {
ds.selectAll();
expect(ds.isSelectAllToggled()).to.be.true;

ds.deselectAll();
expect(ds.isSelectAllToggled()).to.be.false;
});

it('should clear the selection when calling selectAll()', () => {
ds.select(ds.items.at(0)!);
ds.selectAll();

expect(ds.selection.size).to.equal(0);
});

it('should list all items as selected even though the selection is empty', () => {
ds.selectAll();
ds.update();

expect(ds.selection.size).to.equal(0);
expect(ds.items.filter(item => isListDataSourceDataItem(item)).every(({ selected }) => selected)).to.be.true;
});

it('should add a selection for every not selected item', () => {
ds.selectAll();
ds.deselect(ds.items.at(0)!);

expect(ds.selection.size).to.equal(1);
expect(ds.selection.has(ds.items.at(0)?.id)).to.be.true;
expect(ds.isSelected(ds.items.at(0))).to.be.false;
});

it('should return a boolean when calling areAllSelected()', () => {
expect(ds.areAllSelected()).to.be.false;

ds.selectAll();
expect(ds.areAllSelected()).to.be.true;

ds.deselect(ds.items.at(0)!);
expect(ds.areAllSelected()).to.be.false;
});

it('should return a boolean when calling areSomeSelected()', () => {
expect(ds.areSomeSelected()).to.be.false;

ds.selectAll();
expect(ds.areSomeSelected()).to.be.false;

ds.deselect(ds.items.at(0)!);
expect(ds.areSomeSelected()).to.be.true;
});
});

describe('group select', () => {
beforeEach(() => {
ds = new ArrayListDataSource(people, { selects: 'multiple', groupBy: 'profession' });
});

it('should select all items in a group when calling select() with the group', () => {
const group = ds.items
.filter(item => isListDataSourceGroupItem(item))
.find(({ id }) => id === 'Gastroenterologist')!;

ds.select(group);

expect(ds.selection.size).to.equal(2);
expect(ds.isSelected(group)).to.be.true;
expect(ds.isSelected(group.members?.at(0))).to.be.true;
expect(ds.isSelected(group.members?.at(1))).to.be.true;
});

it('should deselect all items in a group when calling deselect() with the group', () => {
const group = ds.items
.filter(item => isListDataSourceGroupItem(item))
.find(({ id }) => id === 'Gastroenterologist')!;

ds.select(group);
expect(ds.selection.size).to.equal(2);

ds.deselect(group);

expect(ds.selection.size).to.equal(0);
expect(ds.isSelected(group.members?.at(0))).to.be.false;
expect(ds.isSelected(group.members?.at(1))).to.be.false;
});

it('should deselect the group if an item in the group is deselected', () => {
const group = ds.items
.filter(item => isListDataSourceGroupItem(item))
.find(({ id }) => id === 'Gastroenterologist')!;

ds.select(group);
expect(ds.selection.size).to.equal(2);

ds.deselect(group.members!.at(0)!);

expect(ds.selection.size).to.equal(1);
expect(ds.isSelected(group)).to.be.false;
expect(ds.isSelected(group.members?.at(0))).to.be.false;
expect(ds.isSelected(group.members?.at(1))).to.be.true;
});

it('should select the group if all items in the group are selected', () => {
const group = ds.items
.filter(item => isListDataSourceGroupItem(item))
.find(({ id }) => id === 'Gastroenterologist')!;

ds.select(group.members!.at(0)!);
ds.select(group.members!.at(1)!);

expect(ds.selection.size).to.equal(2);
expect(ds.isSelected(group)).to.be.true;
expect(ds.isSelected(group.members?.at(0))).to.be.true;
expect(ds.isSelected(group.members?.at(1))).to.be.true;
});
});
});

describe('sorting', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
} from './fetch-list-data-source.js';
import { type Person, people } from './list-data-source.spec.js';

describe('FetchListDataSource', () => {
// eslint-disable-next-line mocha/no-pending-tests
describe.skip('FetchListDataSource', () => {
let ds: FetchListDataSource<Person>;

describe('defaults', () => {
Expand Down Expand Up @@ -149,8 +150,8 @@ describe('FetchListDataSource', () => {
expect(options).to.not.be.undefined;
expect(options?.filters).to.have.length(2);
expect(options?.filters).to.deep.equal([
{ path: 'membership', value: 'Regular' },
{ path: 'profession', value: 'Gastroenterologist' }
{ by: 'membership', value: 'Regular' },
{ by: 'profession', value: 'Gastroenterologist' }
]);
});

Expand All @@ -161,7 +162,7 @@ describe('FetchListDataSource', () => {
ds.items.at(0);

expect(ds.fetchPage).to.have.been.calledOnce;
expect(ds.fetchPage).to.have.been.calledWithMatch({ sort: { path: 'firstName', direction: 'desc' } });
expect(ds.fetchPage).to.have.been.calledWithMatch({ sort: { by: 'firstName', direction: 'desc' } });
});

it('should emit an update event after fetching a page', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class FetchListDataSource<T = any> extends ListDataSource<T, ListDataSour
}

update(emitEvent = true): void {
let length = this.size;
let length = this.totalSize;

if (this.pagination) {
const pageCount = Math.ceil(this.size / this.pageSize),
Expand Down
Loading
Loading