From 7ce0fba6fd782ca25cc389ca07ec34bb0f09a54c Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Thu, 2 Aug 2018 11:22:29 -0700 Subject: [PATCH] feat(chips): Pass chip ids instead of foundations in events (#3265) BREAKING CHANGE: `MDCChip` takes an `id`, no longer exposes its `foundation`, and has `selected` as a property. Custom event details require a `chipId` instead of `chipFoundation`. New methods added to `MDCChipSetAdapter` and `MDCChipSetFoundation`. --- packages/mdc-chips/README.md | 82 +++++---- packages/mdc-chips/chip-set/adapter.js | 16 +- packages/mdc-chips/chip-set/foundation.js | 84 ++++++---- packages/mdc-chips/chip-set/index.js | 52 +++++- packages/mdc-chips/chip/foundation.js | 15 +- packages/mdc-chips/chip/index.js | 37 +++-- .../mdc-chips/mdc-chip-set.foundation.test.js | 157 ++++++++++-------- test/unit/mdc-chips/mdc-chip-set.test.js | 39 ++--- test/unit/mdc-chips/mdc-chip.test.js | 21 ++- 9 files changed, 309 insertions(+), 194 deletions(-) diff --git a/packages/mdc-chips/README.md b/packages/mdc-chips/README.md index 082aa387e63..1cc669bef72 100644 --- a/packages/mdc-chips/README.md +++ b/packages/mdc-chips/README.md @@ -94,40 +94,60 @@ A trailing icon comes with the functionality to remove the chip from the set. If Choice chips are a variant of chips which allow single selection from a set of options. To define a set of chips as choice chips, add the class `mdc-chip-set--choice` to the chip set element. +```html +
+ ... +
+``` + ### Filter Chips Filter chips are a variant of chips which allow multiple selection from a set of options. To define a set of chips as filter chips, add the class `mdc-chip-set--filter` to the chip set element. When a filter chip is selected, a checkmark appears as the leading icon. If the chip already has a leading icon, the checkmark replaces it. This requires the HTML structure of a filter chip to differ from other chips: ```html -
-
- - - +
+
+
+ + + +
+
Filterable content
-
Filterable content
+ ...
``` To use a leading icon in a filter chip, put the `mdc-chip__icon--leading` element _before_ the `mdc-chip__checkmark` element: ```html -
- face -
- - - +
+
+ face +
+ + + +
+
Filterable content
-
Filterable content
+ ...
``` ### Input Chips -Input chips are a variant of chips which enable user input by converting text into chips. To define a set of chips as input chips, add the class `mdc-chip-set--input` to the chip set element. You'd also want to add an event listener that calls `addChip` on the `MDCChipSet` to convert text to a chip. More information can be found in the "`MDCChip` Properties and Methods" section below. +Input chips are a variant of chips which enable user input by converting text into chips. To define a set of chips as input chips, add the class `mdc-chip-set--input` to the chip set element. + +```html +
+ ... +
+``` + + You'd also want to add an event listener that calls `addChip` on the `MDCChipSet` to convert text to a chip. More information can be found in the "`MDCChip` Properties and Methods" section below. ### Pre-selected @@ -211,22 +231,25 @@ To use the `MDCChip` and `MDCChipSet` classes, [import](../../docs/importing-js. Method Signature | Description --- | --- -`isSelected() => boolean` | Proxies to the foundation's `isSelected` method `beginExit() => void` | Proxies to the foundation's `beginExit` method Property | Value Type | Description --- | --- | --- -`foundation` | MDCChipFoundation | The foundation -`shouldRemoveOnTrailingIconClick` | Boolean | Proxies to the foundation's `getShouldRemoveOnTrailingIconClick`/`setShouldRemoveOnTrailingIconClick` methods +`id` | string | Unique identifier on the chip\* +`selected` | Boolean | Proxies to the foundation's `isSelected`/`setSelected` methods +`shouldRemoveOnTrailingIconClick` | Boolean | Proxies to the foundation's `getShouldRemoveOnTrailingIconClick`/`setShouldRemoveOnTrailingIconClick` methods\*\* `ripple` | `MDCRipple` | The `MDCRipple` instance for the root element that `MDCChip` initializes ->_NOTE_: If `shouldRemoveOnTrailingIconClick` is set to false, you must manually call `beginExit()` on the chip to remove it. +> \*_NOTE_: This will be the same as the `id` attribute on the root element. If an `id` is not provided, a unique one will be generated. + +> \*\*_NOTE_: If `shouldRemoveOnTrailingIconClick` is set to false, you must manually call `beginExit()` on the chip to remove it. #### `MDCChipSet` Method Signature | Description --- | --- `addChip(chipEl: Element) => void` | Adds a new `MDCChip` instance to the chip set based on the given `mdc-chip` element +`getSelectedChipIds() => boolean` | Returns an array of the IDs of all selected chips Property | Value Type | Description --- | --- | --- @@ -248,22 +271,23 @@ Method Signature | Description `addClassToLeadingIcon(className: string) => void` | Adds a class to the leading icon element `removeClassFromLeadingIcon(className: string) => void` | Removes a class from the leading icon element `eventTargetHasClass(target: EventTarget, className: string) => boolean` | Returns true if target has className, false otherwise -`notifyInteraction() => void` | Emits a custom event `MDCChip:interaction` denoting the chip has been interacted with -`notifyTrailingIconInteraction() => void` | Emits a custom event `MDCChip:trailingIconInteraction` denoting the chip's trailing icon has been interacted with -`notifyRemoval() => void` | Emits a custom event `MDCChip:removal` denoting the chip will be removed +`notifyInteraction() => void` | Emits a custom event `MDCChip:interaction` denoting the chip has been interacted with\* +`notifyTrailingIconInteraction() => void` | Emits a custom event `MDCChip:trailingIconInteraction` denoting the chip's trailing icon has been interacted with\* +`notifyRemoval() => void` | Emits a custom event `MDCChip:removal` denoting the chip will be removed\*\* `getComputedStyleValue(propertyName: string) => string` | Returns the computed property value of the given style property on the root element `setStyleProperty(propertyName: string, value: string) => void` | Sets the property value of the given style property on the root element -> _NOTE_: The custom events emitted by `notifyInteraction` and `notifyTrailingIconInteraction` must pass along the target chip in its event `detail`, as well as bubble to the parent `mdc-chip-set` element. +> \*_NOTE_: The custom events emitted by `notifyInteraction` and `notifyTrailingIconInteraction` must pass along the target chip's ID via `event.detail.chipId`, as well as bubble to the parent `mdc-chip-set` element. -> _NOTE_: The custom event emitted by `notifyRemoval` must pass along the target chip and its root element in the event `detail`, as well as bubble to the parent `mdc-chip-set` element. +> \*\*_NOTE_: The custom event emitted by `notifyRemoval` must pass along the target chip's ID via `event.detail.chipId` and its root element via `event.detail.root`, as well as bubble to the parent `mdc-chip-set` element. #### `MDCChipSetAdapter` Method Signature | Description --- | --- `hasClass(className: string) => boolean` | Returns whether the chip set element has the given class -`removeChip(chip: MDCChip) => void` | Removes the chip object from the chip set +`removeChip(chipId: string) => void` | Removes the chip with the given id from the chip set +`setSelected(chipId: string, selected: boolean) => void` | Sets the selected state of the chip with the given id ### Foundations: `MDCChipFoundation` and `MDCChipSetFoundation` @@ -284,7 +308,9 @@ Method Signature | Description Method Signature | Description --- | --- -`select(chipFoundation: MDCChipFoundation) => void` | Selects the given chip -`deselect(chipFoundation: MDCChipFoundation) => void` | Deselects the given chip +`getSelectedChipIds() => boolean` | Returns an array of the IDs of all selected chips +`select(chipId: string) => void` | Selects the chip with the given id +`deselect(chipId: string) => void` | Deselects the chip with the given id +`toggleSelect(chipId: string) => void` | Toggles selection of the chip with the given id `handleChipInteraction(evt: Event) => void` | Handles a custom `MDCChip:interaction` event on the root element `handleChipRemoval(evt: Event) => void` | Handles a custom `MDCChip:removal` event on the root element diff --git a/packages/mdc-chips/chip-set/adapter.js b/packages/mdc-chips/chip-set/adapter.js index 6c4f2e11f02..678ca46cec2 100644 --- a/packages/mdc-chips/chip-set/adapter.js +++ b/packages/mdc-chips/chip-set/adapter.js @@ -15,9 +15,6 @@ * limitations under the License. */ -// eslint-disable-next-line no-unused-vars -import {MDCChipInteractionEventType} from '../chip/foundation'; - /* eslint no-unused-vars: [2, {"args": "none"}] */ /** @@ -39,10 +36,17 @@ class MDCChipSetAdapter { hasClass(className) {} /** - * Removes the chip object from the chip set. - * @param {!Object} chip + * Removes the chip with the given id from the chip set. + * @param {string} chipId + */ + removeChip(chipId) {} + + /** + * Sets the selected state of the chip with the given id. + * @param {string} chipId + * @param {boolean} selected */ - removeChip(chip) {} + setSelected(chipId, selected) {} } export default MDCChipSetAdapter; diff --git a/packages/mdc-chips/chip-set/foundation.js b/packages/mdc-chips/chip-set/foundation.js index c0955bf8ae1..5235c088500 100644 --- a/packages/mdc-chips/chip-set/foundation.js +++ b/packages/mdc-chips/chip-set/foundation.js @@ -18,7 +18,7 @@ import MDCFoundation from '@material/base/foundation'; import MDCChipSetAdapter from './adapter'; // eslint-disable-next-line no-unused-vars -import {MDCChipFoundation, MDCChipInteractionEventType} from '../chip/foundation'; +import {MDCChipInteractionEventType, MDCChipRemovalEventType} from '../chip/foundation'; import {strings, cssClasses} from './constants'; /** @@ -45,6 +45,7 @@ class MDCChipSetFoundation extends MDCFoundation { return /** @type {!MDCChipSetAdapter} */ ({ hasClass: () => {}, removeChip: () => {}, + setSelected: () => {}, }); } @@ -55,69 +56,80 @@ class MDCChipSetFoundation extends MDCFoundation { super(Object.assign(MDCChipSetFoundation.defaultAdapter, adapter)); /** - * The selected chips in the set. Only used for choice chip set or filter chip set. - * @private {!Array} + * The ids of the selected chips in the set. Only used for choice chip set or filter chip set. + * @private {!Array} */ - this.selectedChips_ = []; + this.selectedChipIds_ = []; } /** - * Selects the given chip. Deselects all other chips if the chip set is of the choice variant. - * @param {!MDCChipFoundation} chipFoundation + * Returns an array of the IDs of all selected chips. + * @return {!Array} */ - select(chipFoundation) { - if (this.adapter_.hasClass(cssClasses.CHOICE)) { - this.deselectAll_(); + getSelectedChipIds() { + return this.selectedChipIds_; + } + + /** + * Toggles selection of the chip with the given id. + * @param {string} chipId + */ + toggleSelect(chipId) { + if (this.selectedChipIds_.indexOf(chipId) >= 0) { + this.deselect(chipId); + } else { + this.select(chipId); } - chipFoundation.setSelected(true); - this.selectedChips_.push(chipFoundation); } /** - * Deselects the given chip. - * @param {!MDCChipFoundation} chipFoundation + * Selects the chip with the given id. Deselects all other chips if the chip set is of the choice variant. + * @param {string} chipId */ - deselect(chipFoundation) { - const index = this.selectedChips_.indexOf(chipFoundation); - if (index >= 0) { - this.selectedChips_.splice(index, 1); + select(chipId) { + if (this.selectedChipIds_.indexOf(chipId) >= 0) { + return; + } + + if (this.adapter_.hasClass(cssClasses.CHOICE) && this.selectedChipIds_.length > 0) { + this.adapter_.setSelected(this.selectedChipIds_[0], false); + this.selectedChipIds_.length = 0; } - chipFoundation.setSelected(false); + this.adapter_.setSelected(chipId, true); + this.selectedChipIds_.push(chipId); } - /** Deselects all selected chips. */ - deselectAll_() { - this.selectedChips_.forEach((chipFoundation) => { - chipFoundation.setSelected(false); - }); - this.selectedChips_.length = 0; + /** + * Deselects the chip with the given id. + * @param {string} chipId + */ + deselect(chipId) { + const index = this.selectedChipIds_.indexOf(chipId); + if (index >= 0) { + this.selectedChipIds_.splice(index, 1); + this.adapter_.setSelected(chipId, false); + } } /** * Handles a chip interaction event * @param {!MDCChipInteractionEventType} evt - * @private */ handleChipInteraction(evt) { - const chipFoundation = evt.detail.chip.foundation; + const {chipId} = evt.detail; if (this.adapter_.hasClass(cssClasses.CHOICE) || this.adapter_.hasClass(cssClasses.FILTER)) { - if (chipFoundation.isSelected()) { - this.deselect(chipFoundation); - } else { - this.select(chipFoundation); - } + this.toggleSelect(chipId); } } /** * Handles the event when a chip is removed. - * @param {!MDCChipInteractionEventType} evt - * @private + * @param {!MDCChipRemovalEventType} evt */ handleChipRemoval(evt) { - const {chip} = evt.detail; - this.deselect(chip.foundation); - this.adapter_.removeChip(chip); + const {chipId} = evt.detail; + this.deselect(chipId); + this.adapter_.removeChip(chipId); } } diff --git a/packages/mdc-chips/chip-set/index.js b/packages/mdc-chips/chip-set/index.js index 78f9eca4e4d..73acadc03fe 100644 --- a/packages/mdc-chips/chip-set/index.js +++ b/packages/mdc-chips/chip-set/index.js @@ -21,6 +21,8 @@ import MDCChipSetAdapter from './adapter'; import MDCChipSetFoundation from './foundation'; import {MDCChip, MDCChipFoundation} from '../chip/index'; +let idCounter = 0; + /** * @extends {MDCComponent} * @final @@ -34,7 +36,7 @@ class MDCChipSet extends MDCComponent { /** @type {!Array} */ this.chips; - /** @type {(function(!Element): !MDCChip)} */ + /** @private {(function(!Element): !MDCChip)} */ this.chipFactory_; /** @private {?function(?Event): undefined} */ @@ -62,8 +64,8 @@ class MDCChipSet extends MDCComponent { initialSyncWithDOM() { this.chips.forEach((chip) => { - if (chip.isSelected()) { - this.foundation_.select(chip.foundation); + if (chip.selected) { + this.foundation_.select(chip.id); } }); @@ -93,19 +95,36 @@ class MDCChipSet extends MDCComponent { * @param {!Element} chipEl */ addChip(chipEl) { + chipEl.id = chipEl.id || `mdc-chip-${++idCounter}`; this.chips.push(this.chipFactory_(chipEl)); } + /** + * Returns an array of the IDs of all selected chips. + * @return {!Array} + */ + get selectedChipIds() { + return this.foundation_.getSelectedChipIds(); + } + /** * @return {!MDCChipSetFoundation} */ getDefaultFoundation() { return new MDCChipSetFoundation(/** @type {!MDCChipSetAdapter} */ (Object.assign({ hasClass: (className) => this.root_.classList.contains(className), - removeChip: (chip) => { - const index = this.chips.indexOf(chip); - this.chips.splice(index, 1); - chip.destroy(); + removeChip: (chipId) => { + const index = this.findChipIndex_(chipId); + if (index >= 0) { + this.chips[index].destroy(); + this.chips.splice(index, 1); + } + }, + setSelected: (chipId, selected) => { + const index = this.findChipIndex_(chipId); + if (index >= 0) { + this.chips[index].selected = selected; + } }, }))); } @@ -117,7 +136,24 @@ class MDCChipSet extends MDCComponent { */ instantiateChips_(chipFactory) { const chipElements = [].slice.call(this.root_.querySelectorAll(MDCChipSetFoundation.strings.CHIP_SELECTOR)); - return chipElements.map((el) => chipFactory(el)); + return chipElements.map((el) => { + el.id = el.id || `mdc-chip-${++idCounter}`; + return chipFactory(el); + }); + } + + /** + * Returns the index of the chip with the given id, or -1 if the chip does not exist. + * @param {string} chipId + * @return {number} + */ + findChipIndex_(chipId) { + for (let i = 0; i < this.chips.length; i++) { + if (this.chips[i].id === chipId) { + return i; + } + } + return -1; } } diff --git a/packages/mdc-chips/chip/foundation.js b/packages/mdc-chips/chip/foundation.js index 4535db25509..ecf66d7cc70 100644 --- a/packages/mdc-chips/chip/foundation.js +++ b/packages/mdc-chips/chip/foundation.js @@ -181,11 +181,22 @@ class MDCChipFoundation extends MDCFoundation { /** * @typedef {{ * detail: { - * chip: {foundation: !MDCChipFoundation}, + * chipId: string, * }, * bubbles: boolean, * }} */ let MDCChipInteractionEventType; -export {MDCChipFoundation, MDCChipInteractionEventType}; +/** + * @typedef {{ + * detail: { + * chipId: string, + * root: Element, + * }, + * bubbles: boolean, + * }} + */ +let MDCChipRemovalEventType; + +export {MDCChipFoundation, MDCChipInteractionEventType, MDCChipRemovalEventType}; diff --git a/packages/mdc-chips/chip/index.js b/packages/mdc-chips/chip/index.js index 78de50ab80f..b78460443a8 100644 --- a/packages/mdc-chips/chip/index.js +++ b/packages/mdc-chips/chip/index.js @@ -35,6 +35,8 @@ class MDCChip extends MDCComponent { constructor(...args) { super(...args); + /** @type {string} */ + this.id; /** @private {?Element} */ this.leadingIcon_; /** @private {?Element} */ @@ -59,6 +61,7 @@ class MDCChip extends MDCComponent { } initialize() { + this.id = this.root_.id; this.leadingIcon_ = this.root_.querySelector(strings.LEADING_ICON_SELECTOR); this.trailingIcon_ = this.root_.querySelector(strings.TRAILING_ICON_SELECTOR); @@ -116,25 +119,19 @@ class MDCChip extends MDCComponent { } /** - * Returns true if the chip is selected. + * Returns whether the chip is selected. * @return {boolean} */ - isSelected() { + get selected() { return this.foundation_.isSelected(); } /** - * Begins the exit animation which leads to removal of the chip. - */ - beginExit() { - this.foundation_.beginExit(); - } - - /** - * @return {!MDCChipFoundation} + * Sets selected state on the chip. + * @param {boolean} selected */ - get foundation() { - return this.foundation_; + set selected(selected) { + this.foundation_.setSelected(selected); } /** @@ -150,7 +147,14 @@ class MDCChip extends MDCComponent { * @param {boolean} shouldRemove */ set shouldRemoveOnTrailingIconClick(shouldRemove) { - return this.foundation_.setShouldRemoveOnTrailingIconClick(shouldRemove); + this.foundation_.setShouldRemoveOnTrailingIconClick(shouldRemove); + } + + /** + * Begins the exit animation which leads to removal of the chip. + */ + beginExit() { + this.foundation_.beginExit(); } /** @@ -172,10 +176,11 @@ class MDCChip extends MDCComponent { } }, eventTargetHasClass: (target, className) => target.classList.contains(className), - notifyInteraction: () => this.emit(strings.INTERACTION_EVENT, {chip: this}, true /* shouldBubble */), + notifyInteraction: () => this.emit(strings.INTERACTION_EVENT, {chipId: this.id}, true /* shouldBubble */), notifyTrailingIconInteraction: () => this.emit( - strings.TRAILING_ICON_INTERACTION_EVENT, {chip: this}, true /* shouldBubble */), - notifyRemoval: () => this.emit(strings.REMOVAL_EVENT, {chip: this, root: this.root_}, true /* shouldBubble */), + strings.TRAILING_ICON_INTERACTION_EVENT, {chipId: this.id}, true /* shouldBubble */), + notifyRemoval: () => + this.emit(strings.REMOVAL_EVENT, {chipId: this.id, root: this.root_}, true /* shouldBubble */), getComputedStyleValue: (propertyName) => window.getComputedStyle(this.root_).getPropertyValue(propertyName), setStyleProperty: (propertyName, value) => this.root_.style.setProperty(propertyName, value), }))); diff --git a/test/unit/mdc-chips/mdc-chip-set.foundation.test.js b/test/unit/mdc-chips/mdc-chip-set.foundation.test.js index 5e9ec9d3361..b6b1f22149b 100644 --- a/test/unit/mdc-chips/mdc-chip-set.foundation.test.js +++ b/test/unit/mdc-chips/mdc-chip-set.foundation.test.js @@ -34,118 +34,133 @@ test('exports cssClasses', () => { test('defaultAdapter returns a complete adapter implementation', () => { verifyDefaultAdapter(MDCChipSetFoundation, [ - 'hasClass', 'removeChip', + 'hasClass', 'removeChip', 'setSelected', ]); }); const setupTest = () => { const mockAdapter = td.object(MDCChipSetFoundation.defaultAdapter); const foundation = new MDCChipSetFoundation(mockAdapter); - const chipA = td.object({ - foundation: { - isSelected: () => {}, - setSelected: () => {}, - }, - }); - const chipB = td.object({ - foundation: { - isSelected: () => {}, - setSelected: () => {}, - }, - }); - return {foundation, mockAdapter, chipA, chipB}; + return {foundation, mockAdapter}; }; -test('in choice chips, #handleChipInteraction selects chip if no chips are selected', () => { - const {foundation, mockAdapter, chipA} = setupTest(); +test('in choice chips, #select does nothing if chip is already selected', () => { + const {foundation, mockAdapter} = setupTest(); td.when(mockAdapter.hasClass(cssClasses.CHOICE)).thenReturn(true); - td.when(chipA.foundation.isSelected()).thenReturn(false); - assert.equal(foundation.selectedChips_.length, 0); + foundation.select('chipA'); + foundation.select('chipA'); + td.verify(mockAdapter.setSelected('chipA', true), {times: 1}); + assert.equal(foundation.getSelectedChipIds().length, 1); +}); - foundation.handleChipInteraction({ - detail: { - chip: chipA, - }, - }); - td.verify(chipA.foundation.setSelected(true)); - assert.equal(foundation.selectedChips_.length, 1); +test('in choice chips, #select selects chip if no chips are selected', () => { + const {foundation, mockAdapter} = setupTest(); + td.when(mockAdapter.hasClass(cssClasses.CHOICE)).thenReturn(true); + assert.equal(foundation.getSelectedChipIds().length, 0); + + foundation.select('chipA'); + td.verify(mockAdapter.setSelected('chipA', true)); + assert.equal(foundation.getSelectedChipIds().length, 1); }); -test('in choice chips, #handleChipInteraction deselects chip if another chip is selected', () => { - const {foundation, mockAdapter, chipA, chipB} = setupTest(); +test('in choice chips, #select deselects chip if another chip is selected', () => { + const {foundation, mockAdapter} = setupTest(); td.when(mockAdapter.hasClass(cssClasses.CHOICE)).thenReturn(true); - foundation.select(chipB.foundation); - td.when(chipA.foundation.isSelected()).thenReturn(false); - td.when(chipB.foundation.isSelected()).thenReturn(true); - assert.equal(foundation.selectedChips_.length, 1); + foundation.select('chipB'); + assert.equal(foundation.getSelectedChipIds().length, 1); - foundation.handleChipInteraction({ - detail: { - chip: chipA, - }, - }); - td.verify(chipA.foundation.setSelected(true)); - td.verify(chipB.foundation.setSelected(false)); - assert.equal(foundation.selectedChips_.length, 1); + foundation.select('chipA'); + td.verify(mockAdapter.setSelected('chipB', false)); + td.verify(mockAdapter.setSelected('chipA', true)); + assert.equal(foundation.getSelectedChipIds().length, 1); }); -test('in filter chips, #handleChipInteraction selects multiple chips', () => { - const {foundation, mockAdapter, chipA, chipB} = setupTest(); +test('in filter chips, #select selects multiple chips', () => { + const {foundation, mockAdapter} = setupTest(); td.when(mockAdapter.hasClass(cssClasses.FILTER)).thenReturn(true); - td.when(chipA.foundation.isSelected()).thenReturn(false); - td.when(chipB.foundation.isSelected()).thenReturn(false); - assert.equal(foundation.selectedChips_.length, 0); + assert.equal(foundation.getSelectedChipIds().length, 0); - foundation.handleChipInteraction({ - detail: { - chip: chipA, - }, - }); - td.verify(chipA.foundation.setSelected(true)); - assert.equal(foundation.selectedChips_.length, 1); + foundation.select('chipA'); + td.verify(mockAdapter.setSelected('chipA', true)); + assert.equal(foundation.getSelectedChipIds().length, 1); + + foundation.select('chipB'); + td.verify(mockAdapter.setSelected('chipB', true)); + assert.equal(foundation.getSelectedChipIds().length, 2); +}); + +test('in filter chips, #select does nothing if chip is already selected', () => { + const {foundation, mockAdapter} = setupTest(); + td.when(mockAdapter.hasClass(cssClasses.CHOICE)).thenReturn(false); + td.when(mockAdapter.hasClass(cssClasses.FILTER)).thenReturn(true); + foundation.select('chipA'); + foundation.select('chipA'); + td.verify(mockAdapter.setSelected('chipA', true), {times: 1}); + assert.equal(foundation.getSelectedChipIds().length, 1); +}); + +test('in filter chips, #deselect deselects selected chips', () => { + const {foundation, mockAdapter} = setupTest(); + td.when(mockAdapter.hasClass(cssClasses.FILTER)).thenReturn(true); + foundation.select('chipA'); + foundation.select('chipB'); + assert.equal(foundation.getSelectedChipIds().length, 2); + + foundation.deselect('chipB'); + td.verify(mockAdapter.setSelected('chipB', false)); + assert.equal(foundation.getSelectedChipIds().length, 1); + + foundation.deselect('chipA'); + td.verify(mockAdapter.setSelected('chipA', false)); + assert.equal(foundation.getSelectedChipIds().length, 0); +}); + +test('#handleChipInteraction selects chip if the chip set is a filter chip set', () => { + const {foundation, mockAdapter} = setupTest(); + td.when(mockAdapter.hasClass(cssClasses.CHOICE)).thenReturn(false); + td.when(mockAdapter.hasClass(cssClasses.FILTER)).thenReturn(true); foundation.handleChipInteraction({ detail: { - chip: chipB, + chipId: 'chipA', }, }); - td.verify(chipB.foundation.setSelected(true)); - assert.equal(foundation.selectedChips_.length, 2); + td.verify(mockAdapter.setSelected('chipA', true)); }); -test('in filter chips, #handleChipInteraction event deselects selected chips', () => { - const {foundation, mockAdapter, chipA, chipB} = setupTest(); - td.when(mockAdapter.hasClass(cssClasses.FILTER)).thenReturn(true); - foundation.select(chipA.foundation); - foundation.select(chipB.foundation); - td.when(chipA.foundation.isSelected()).thenReturn(true); - td.when(chipB.foundation.isSelected()).thenReturn(true); - assert.equal(foundation.selectedChips_.length, 2); +test('#handleChipInteraction selects chip if the chip set is a choice chip set', () => { + const {foundation, mockAdapter} = setupTest(); + td.when(mockAdapter.hasClass(cssClasses.CHOICE)).thenReturn(true); + td.when(mockAdapter.hasClass(cssClasses.FILTER)).thenReturn(false); foundation.handleChipInteraction({ detail: { - chip: chipB, + chipId: 'chipA', }, }); - td.verify(chipB.foundation.setSelected(false)); - assert.equal(foundation.selectedChips_.length, 1); + td.verify(mockAdapter.setSelected('chipA', true)); +}); + +test('#handleChipInteraction does nothing if the chip set is neither choice nor filter', () => { + const {foundation, mockAdapter} = setupTest(); + td.when(mockAdapter.hasClass(cssClasses.CHOICE)).thenReturn(false); + td.when(mockAdapter.hasClass(cssClasses.FILTER)).thenReturn(false); foundation.handleChipInteraction({ detail: { - chip: chipA, + chipId: 'chipA', }, }); - td.verify(chipA.foundation.setSelected(false)); - assert.equal(foundation.selectedChips_.length, 0); + td.verify(mockAdapter.setSelected('chipA', true), {times: 0}); }); test('#handleChipRemoval removes chip', () => { - const {foundation, mockAdapter, chipA} = setupTest(); + const {foundation, mockAdapter} = setupTest(); foundation.handleChipRemoval({ detail: { - chip: chipA, + chipId: 'chipA', }, }); - td.verify(mockAdapter.removeChip(chipA)); + td.verify(mockAdapter.removeChip('chipA')); }); diff --git a/test/unit/mdc-chips/mdc-chip-set.test.js b/test/unit/mdc-chips/mdc-chip-set.test.js index bb402f2607c..c273f3796fd 100644 --- a/test/unit/mdc-chips/mdc-chip-set.test.js +++ b/test/unit/mdc-chips/mdc-chip-set.test.js @@ -22,13 +22,13 @@ import {MDCChipSet} from '../../../packages/mdc-chips/chip-set'; const getFixture = () => bel`
-
+
Chip content
-
+
Chip content
-
+
Chip content
@@ -41,9 +41,10 @@ test('attachTo returns an MDCChipSet instance', () => { }); class FakeChip { - constructor() { + constructor(el) { + this.id = el.id; this.destroy = td.func('.destroy'); - this.isSelected = td.func('.isSelected'); + this.selected = false; } } @@ -81,24 +82,6 @@ test('#addChip adds a new chip to the chip set', () => { assert.instanceOf(component.chips[3], FakeChip); }); -class FakeSelectedChip { - constructor() { - this.foundation = td.object({ - setSelected: () => {}, - }); - this.destroy = td.func('.destroy'); - this.isSelected = () => true; - } -} - -test('#initialSyncWithDOM sets selects chips with mdc-chip--selected class', () => { - const root = getFixture(); - const component = new MDCChipSet(root, undefined, (el) => new FakeSelectedChip(el)); - td.verify(component.foundation_.select(component.chips[0].foundation)); - td.verify(component.foundation_.select(component.chips[1].foundation)); - td.verify(component.foundation_.select(component.chips[2].foundation)); -}); - function setupTest() { const root = getFixture(); const component = new MDCChipSet(root); @@ -115,7 +98,15 @@ test('#adapter.removeChip removes the chip object from the chip set', () => { const root = getFixture(); const component = new MDCChipSet(root, undefined, (el) => new FakeChip(el)); const chip = component.chips[0]; - component.getDefaultFoundation().adapter_.removeChip(chip); + component.getDefaultFoundation().adapter_.removeChip(chip.id); assert.equal(component.chips.length, 2); td.verify(chip.destroy()); }); + +test('#adapter.setSelected sets selected on chip object', () => { + const root = getFixture(); + const component = new MDCChipSet(root, undefined, (el) => new FakeChip(el)); + const chip = component.chips[0]; + component.getDefaultFoundation().adapter_.setSelected(chip.id, true); + assert.equal(chip.selected, true); +}); diff --git a/test/unit/mdc-chips/mdc-chip.test.js b/test/unit/mdc-chips/mdc-chip.test.js index 9d3b0bce73d..25d40a7a5e4 100644 --- a/test/unit/mdc-chips/mdc-chip.test.js +++ b/test/unit/mdc-chips/mdc-chip.test.js @@ -48,6 +48,16 @@ test('get ripple returns MDCRipple instance', () => { assert.isTrue(component.ripple instanceof MDCRipple); }); +test('sets id on chip if attribute exists', () => { + const root = bel` +
+
Hello
+
+ `; + const component = new MDCChip(root); + assert.equal(component.id, 'hello-chip'); +}); + test('#adapter.hasClass returns true if class is set on chip set element', () => { const {root, component} = setupTest(); root.classList.add('foo'); @@ -146,10 +156,15 @@ function setupMockFoundationTest(root = getFixture()) { return {root, component, mockFoundation}; } -test('#isSelected proxies to foundation', () => { +test('#get selected proxies to foundation', () => { + const {component, mockFoundation} = setupMockFoundationTest(); + assert.equal(component.selected, mockFoundation.isSelected()); +}); + +test('#set selected proxies to foundation', () => { const {component, mockFoundation} = setupMockFoundationTest(); - component.isSelected(); - td.verify(mockFoundation.isSelected()); + component.selected = true; + td.verify(mockFoundation.setSelected(true)); }); test('#get shouldRemoveOnTrailingIconClick proxies to foundation', () => {