Skip to content

Commit e9fedb9

Browse files
committed
feat: add a common class to be used when dealing with selection logic
Adds the `MdSelectionModel` class that can be used when dealing with single and multiple selection within a component. Relates to angular#2412.
1 parent 0be5acd commit e9fedb9

File tree

3 files changed

+427
-0
lines changed

3 files changed

+427
-0
lines changed

src/lib/core/core.ts

+3
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export {
6969
LIVE_ANNOUNCER_ELEMENT_TOKEN,
7070
} from './a11y/live-announcer';
7171

72+
// Selection
73+
export * from './selection/selection';
74+
7275
/** @deprecated */
7376
export {LiveAnnouncer as MdLiveAnnouncer} from './a11y/live-announcer';
7477

+256
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import {MdSelectionModel} from './selection';
2+
3+
4+
describe('MdSelectionModel', () => {
5+
describe('single selection', () => {
6+
let model: MdSelectionModel;
7+
8+
beforeEach(() => model = new MdSelectionModel([1, 2, 3]));
9+
10+
it('should be able to select a single value', () => {
11+
model.select(1);
12+
13+
expect(model.selected.length).toBe(1);
14+
expect(model.isSelected(1)).toBe(true);
15+
});
16+
17+
it('should deselect the previously selected value', () => {
18+
model.select(1);
19+
model.select(2);
20+
21+
expect(model.isSelected(1)).toBe(false);
22+
expect(model.isSelected(2)).toBe(true);
23+
});
24+
25+
it('should throw an error when trying to select all of the values', () => {
26+
expect(() => model.selectAll()).toThrow();
27+
});
28+
29+
it('should only preselect one value', () => {
30+
model = new MdSelectionModel([1, 2, 3], false, [1, 2]);
31+
32+
expect(model.selected.length).toBe(1);
33+
expect(model.isSelected(1)).toBe(true);
34+
expect(model.isSelected(2)).toBe(false);
35+
});
36+
});
37+
38+
describe('multiple selection', () => {
39+
let model: MdSelectionModel;
40+
41+
beforeEach(() => model = new MdSelectionModel([1, 2, 3], true));
42+
43+
it('should be able to select multiple options at the same time', () => {
44+
model.select(1);
45+
model.select(2);
46+
47+
expect(model.selected.length).toBe(2);
48+
expect(model.isSelected(1)).toBe(true);
49+
expect(model.isSelected(2)).toBe(true);
50+
});
51+
52+
it('should be able to preselect multiple options', () => {
53+
model = new MdSelectionModel([1, 2, 3], true, [1, 2]);
54+
55+
expect(model.selected.length).toBe(2);
56+
expect(model.isSelected(1)).toBe(true);
57+
expect(model.isSelected(2)).toBe(true);
58+
});
59+
60+
it('should be able to select all of the options', () => {
61+
model.selectAll();
62+
expect(model.options.every(value => model.isSelected(value))).toBe(true);
63+
});
64+
});
65+
66+
describe('updating the options', () => {
67+
let model: MdSelectionModel;
68+
69+
beforeEach(() => model = new MdSelectionModel([1, 2, 3], true));
70+
71+
it('should be able to update the list of options', () => {
72+
let newOptions = [1, 2, 3, 4, 5];
73+
74+
model.options = newOptions;
75+
76+
expect(model.options).not.toBe(newOptions, 'Expected the array to have been cloned.');
77+
expect(model.options).toEqual(newOptions);
78+
});
79+
80+
it('should keep the selected value', () => {
81+
model.select(2);
82+
83+
model.options = [1, 2, 3, 4, 5];
84+
85+
expect(model.isSelected(2)).toBe(true);
86+
});
87+
88+
it('should deselect values that are not longer in the list', () => {
89+
model.select(1);
90+
91+
model.options = [2, 3, 4];
92+
93+
expect(model.isSelected(1)).toBe(false);
94+
});
95+
});
96+
97+
describe('onChange event', () => {
98+
it('should return both the added and removed values', () => {
99+
let model = new MdSelectionModel([1, 2, 3]);
100+
let spy = jasmine.createSpy('MdSelectionModel change event');
101+
102+
model.select(1);
103+
104+
model.onChange.subscribe(spy);
105+
106+
model.select(2);
107+
108+
let event = spy.calls.mostRecent().args[0];
109+
110+
expect(spy).toHaveBeenCalled();
111+
expect(event.removed).toEqual([1]);
112+
expect(event.added).toEqual([2]);
113+
});
114+
115+
describe('selection', () => {
116+
let model: MdSelectionModel;
117+
let spy: jasmine.Spy;
118+
119+
beforeEach(() => {
120+
model = new MdSelectionModel([1, 2, 3], true);
121+
spy = jasmine.createSpy('MdSelectionModel change event');
122+
123+
model.onChange.subscribe(spy);
124+
});
125+
126+
it('should emit an event when a value is selected', () => {
127+
model.select(1);
128+
129+
let event = spy.calls.mostRecent().args[0];
130+
131+
expect(spy).toHaveBeenCalled();
132+
expect(event.added).toEqual([1]);
133+
expect(event.removed).toEqual([]);
134+
});
135+
136+
it('should not emit multiple events for the same value', () => {
137+
model.select(1);
138+
model.select(1);
139+
140+
expect(spy).toHaveBeenCalledTimes(1);
141+
});
142+
143+
it('should emit a single event when selecting all of the values', () => {
144+
model.selectAll();
145+
146+
let event = spy.calls.mostRecent().args[0];
147+
148+
expect(spy).toHaveBeenCalledTimes(1);
149+
expect(event.added).toEqual([1, 2, 3]);
150+
});
151+
152+
it('should not emit an event when preselecting values', () => {
153+
model = new MdSelectionModel([1, 2, 3], false, [1]);
154+
spy = jasmine.createSpy('MdSelectionModel initial change event');
155+
model.onChange.subscribe(spy);
156+
157+
expect(spy).not.toHaveBeenCalled();
158+
});
159+
});
160+
161+
describe('deselection', () => {
162+
let model: MdSelectionModel;
163+
let spy: jasmine.Spy;
164+
165+
beforeEach(() => {
166+
model = new MdSelectionModel([1, 2, 3], true, [1, 2]);
167+
spy = jasmine.createSpy('MdSelectionModel change event');
168+
169+
model.onChange.subscribe(spy);
170+
});
171+
172+
it('should emit an event when a value is deselected', () => {
173+
model.deselect(1);
174+
175+
let event = spy.calls.mostRecent().args[0];
176+
177+
expect(spy).toHaveBeenCalled();
178+
expect(event.removed).toEqual([1]);
179+
});
180+
181+
it('should not emit an event when a non-selected value is deselected', () => {
182+
model.deselect(3);
183+
expect(spy).not.toHaveBeenCalled();
184+
});
185+
186+
it('should emit a single event when clearing all of the selected options', () => {
187+
model.clear();
188+
189+
let event = spy.calls.mostRecent().args[0];
190+
191+
expect(spy).toHaveBeenCalledTimes(1);
192+
expect(event.removed).toEqual([2, 1]);
193+
});
194+
195+
it('should emit an event when a value is deselected due to it being removed from the options',
196+
() => {
197+
model.options = [4, 5, 6];
198+
199+
let event = spy.calls.mostRecent().args[0];
200+
201+
expect(spy).toHaveBeenCalledTimes(1);
202+
expect(event.removed).toEqual([2, 1]);
203+
});
204+
});
205+
});
206+
207+
it('should be able to determine whether it is empty', () => {
208+
let model = new MdSelectionModel([1, 2, 3]);
209+
210+
expect(model.isEmpty()).toBe(true);
211+
212+
model.select(1);
213+
214+
expect(model.isEmpty()).toBe(false);
215+
});
216+
217+
it('should throw when trying to select a value that is not in the list of options', () => {
218+
let model = new MdSelectionModel([]);
219+
expect(() => model.select(1)).toThrow();
220+
});
221+
222+
it('should throw when trying to deselect a value that is not in the list of options', () => {
223+
let model = new MdSelectionModel([]);
224+
expect(() => model.deselect(1)).toThrow();
225+
});
226+
227+
it('should be able to clear the selected options', () => {
228+
let model = new MdSelectionModel([1, 2, 3], true);
229+
230+
model.select(1);
231+
model.select(2);
232+
233+
expect(model.selected.length).toBe(2);
234+
235+
model.clear();
236+
237+
expect(model.selected.length).toBe(0);
238+
expect(model.isEmpty()).toBe(true);
239+
});
240+
241+
it('should not expose the internal array of options directly', () => {
242+
let options = [1, 2, 3];
243+
let model = new MdSelectionModel(options);
244+
245+
expect(model.options).not.toBe(options, 'Expect the array to be different');
246+
expect(model.options).toEqual(options);
247+
});
248+
249+
it('should not expose the internal array of selected values directly', () => {
250+
let model = new MdSelectionModel([1, 2, 3], true, [1, 2]);
251+
let selected = model.selected;
252+
253+
selected.length = 0;
254+
expect(model.selected).toEqual([1, 2]);
255+
});
256+
});

0 commit comments

Comments
 (0)