From ac405eae1b80f719a80dc4fec663b763e73cdf5d Mon Sep 17 00:00:00 2001 From: Gabriel Schrag Date: Mon, 27 Jul 2020 09:00:52 -0600 Subject: [PATCH] feat(segmented-button): Implement components (#6223) --- packages/mdc-segmented-button/package.json | 3 +- .../mdc-segmented-button/segment/adapter.ts | 34 ++ .../mdc-segmented-button/segment/component.ts | 98 ++++- .../mdc-segmented-button/segment/constants.ts | 24 +- .../segment/foundation.ts | 44 ++- .../mdc-segmented-button/segment/index.ts | 1 + .../segment/test/component.test.ts | 240 ++++++++++++ .../segment/test/constants.ts | 31 ++ .../segment/test/foundation.test.ts | 36 +- .../segmented-button/adapter.ts | 27 ++ .../segmented-button/component.ts | 131 ++++++- .../segmented-button/constants.ts | 15 + .../segmented-button/foundation.ts | 40 ++ .../segmented-button/index.ts | 1 + .../segmented-button/test/component.test.ts | 341 ++++++++++++++++++ .../segmented-button/test/constants.ts | 19 +- .../segmented-button/test/foundation.test.ts | 48 +-- 17 files changed, 1055 insertions(+), 78 deletions(-) create mode 100644 packages/mdc-segmented-button/segment/test/component.test.ts create mode 100644 packages/mdc-segmented-button/segment/test/constants.ts create mode 100644 packages/mdc-segmented-button/segmented-button/test/component.test.ts diff --git a/packages/mdc-segmented-button/package.json b/packages/mdc-segmented-button/package.json index 5f527891b7a..45d8bba747b 100644 --- a/packages/mdc-segmented-button/package.json +++ b/packages/mdc-segmented-button/package.json @@ -17,6 +17,7 @@ "access": "public" }, "dependencies": { - "@material/base": "^7.0.0" + "@material/base": "^7.0.0", + "@material/ripple": "^7.0.0" } } diff --git a/packages/mdc-segmented-button/segment/adapter.ts b/packages/mdc-segmented-button/segment/adapter.ts index 41efc1e37ea..05d104ddbec 100644 --- a/packages/mdc-segmented-button/segment/adapter.ts +++ b/packages/mdc-segmented-button/segment/adapter.ts @@ -22,17 +22,51 @@ */ export interface MDCSegmentedButtonSegmentAdapter { + /** + * @return Returns true if wrapping segmented button is single select + */ isSingleSelect(): boolean; + /** + * @param attrName Attribute of interest + * @return Returns segment's attribute value if it is set, otherwise returns + * null + */ getAttr(attrName: string): string | null; + /** + * Sets segment's attribute value to new value + * + * @param attrName Attribute of interest + * @param value New value of attribute + */ setAttr(attrName: string, value: string): void; + /** + * Adds css class to segment + * + * @param className Class to add + */ addClass(className: string): void; + /** + * Removes css class from segment + * + * @param className Class to remove + */ removeClass(className: string): void; + /** + * @param className Class of interest + * @return Returns true if segment has css class, otherwise returns false + */ hasClass(className: string): boolean; + /** + * Emits event about segment to wrapping segmented button + * + * @param selected Represents whether segment is currently selected + * @event selected With detail - SegmentDetail + */ notifySelectedChange(selected: boolean): void; } diff --git a/packages/mdc-segmented-button/segment/component.ts b/packages/mdc-segmented-button/segment/component.ts index 8010b14911c..452cc0e6dd6 100644 --- a/packages/mdc-segmented-button/segment/component.ts +++ b/packages/mdc-segmented-button/segment/component.ts @@ -22,42 +22,124 @@ */ import {MDCComponent} from '@material/base/component'; +import {SpecificEventListener} from '@material/base/types'; +import {MDCRippleCapableSurface} from '@material/ripple/types'; +import {MDCSegmentedButtonSegmentAdapter} from './adapter'; import {MDCSegmentedButtonSegmentFoundation} from './foundation'; +import {SegmentDetail} from '../types'; +import {events} from './constants'; export type MDCSegmentedButtonSegmentFactory = (el: Element, foundation?: MDCSegmentedButtonSegmentFoundation) => MDCSegmentedButtonSegment; -export class MDCSegmentedButtonSegment extends MDCComponent { +/** + * Implementation of MDCSegmentedButtonSegmentFoundation + */ +export class MDCSegmentedButtonSegment extends MDCComponent implements MDCRippleCapableSurface { static attachTo(root: Element) { return new MDCSegmentedButtonSegment(root); } + private index!: number; // assigned in setIndex by parent + private isSingleSelect!: boolean; // assigned in setIsSingleSelect by parent + private handleClick!: + SpecificEventListener<'click'>; // assigned in initialSyncWithDOM + initialSyncWithDOM() { - return; + this.handleClick = () => this.foundation.handleClick(); + + this.listen(events.CLICK, this.handleClick); } destroy() { + this.unlisten(events.CLICK, this.handleClick); + super.destroy(); } getDefaultFoundation(): MDCSegmentedButtonSegmentFoundation { - return new MDCSegmentedButtonSegmentFoundation(); + // DO NOT INLINE this variable. For backward compatibility, foundations take a Partial. + // To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable. + // tslint:disable:object-literal-sort-keys Methods should be in the same order as the adapter interface. + const adapter: MDCSegmentedButtonSegmentAdapter = { + isSingleSelect: () => { + return this.isSingleSelect; + }, + getAttr: (attrName) => { + return this.root.getAttribute(attrName); + }, + setAttr: (attrName, value) => { + this.root.setAttribute(attrName, value); + }, + addClass: (className) => { + this.root.classList.add(className); + }, + removeClass: (className) => { + this.root.classList.remove(className); + }, + hasClass: (className) => { + return this.root.classList.contains(className); + }, + notifySelectedChange: (selected) => { + this.emit( + events.SELECTED, + { + index: this.index, + selected: selected, + segmentId: this.getSegmentId() + }, + true /* shouldBubble */ + ); + } + }; + return new MDCSegmentedButtonSegmentFoundation(adapter); + } + + /** + * Sets segment's index value + * + * @param index Segment's index within wrapping segmented button + */ + setIndex(index: number) { + this.index = index; + } + + /** + * Sets segment's isSingleSelect value + * + * @param isSingleSelect True if wrapping segmented button is single select + */ + setIsSingleSelect(isSingleSelect: boolean) { + this.isSingleSelect = isSingleSelect; } + /** + * @return Returns true if segment is currently selected, otherwise returns + * false + */ isSelected(): boolean { - return false; + return this.foundation.isSelected(); } + /** + * Sets segment to be selected + */ setSelected() { - return; + this.foundation.setSelected(); } + /** + * Sets segment to be not selected + */ setUnselected() { - return; + this.foundation.setUnselected(); } - getSegmentId(): string { - return ''; + /** + * @return Returns segment's segmentId if it was set by client + */ + getSegmentId(): string | undefined { + return this.foundation.getSegmentId(); } } diff --git a/packages/mdc-segmented-button/segment/constants.ts b/packages/mdc-segmented-button/segment/constants.ts index 9a1cbe5a5dc..b03226f64f0 100644 --- a/packages/mdc-segmented-button/segment/constants.ts +++ b/packages/mdc-segmented-button/segment/constants.ts @@ -22,16 +22,30 @@ */ /** - * Strings constants used by segment + * Boolean strings for segment */ -export const strings = { - ARIA_CHECKED: 'aria-checked', - ARIA_PRESSED: 'aria-pressed', - DATA_SEGMENT_ID: 'data-segment-id', +export const booleans = { TRUE: 'true', FALSE: 'false' }; +/** + * Attributes referenced by segment + */ +export const attributes = { + ARIA_CHECKED: 'aria-checked', + ARIA_PRESSED: 'aria-pressed', + DATA_SEGMENT_ID: 'data-segment-id' +} + +/** + * Events received or emitted by segment + */ +export const events = { + CLICK: 'click', + SELECTED: 'selected' +} + /** * Style classes for segment */ diff --git a/packages/mdc-segmented-button/segment/foundation.ts b/packages/mdc-segmented-button/segment/foundation.ts index 1e40e10f295..723a30414f5 100644 --- a/packages/mdc-segmented-button/segment/foundation.ts +++ b/packages/mdc-segmented-button/segment/foundation.ts @@ -23,7 +23,7 @@ import {MDCFoundation} from '@material/base/foundation'; import {MDCSegmentedButtonSegmentAdapter} from './adapter'; -import {cssClasses, strings} from './constants'; +import {cssClasses, booleans, attributes} from './constants'; export class MDCSegmentedButtonSegmentFoundation extends MDCFoundation { static get defaultAdapter(): MDCSegmentedButtonSegmentAdapter { @@ -42,24 +42,45 @@ export class MDCSegmentedButtonSegmentFoundation extends MDCFoundation { + const wrapper = document.createElement('div'); + wrapper.innerHTML = ` + + `; + + const el = wrapper.firstElementChild as HTMLElement; + wrapper.removeChild(el); + return el; +}; + +const setupTest = () => { + const root = getFixtureMultiSelectWithLabel(); + const component = new MDCSegmentedButtonSegment(root); + const adapter = (component.getDefaultFoundation() as any).adapter; + return {root, component, adapter}; +}; + +/** + * TODO: add tests for ripple, touch, and having icon + */ +describe('MDCSegmentedButtonSegment', () => { + it('attachTo return an MDCSegmentedButtonSegment instance', () => { + expect(MDCSegmentedButtonSegment.attachTo(getFixtureMultiSelectWithLabel()) instanceof MDCSegmentedButtonSegment).toBeTruthy(); + }); + + it('#initialSyncWithDOM sets up event handlers', () => { + const {root, component} = setupTest(); + const handler = jasmine.createSpy('handle selected'); + component.listen(events.SELECTED, handler); + + emitEvent(root, events.CLICK); + expect(handler).toHaveBeenCalledTimes(1); + + component.unlisten(events.SELECTED, handler); + component.destroy(); + }); + + it('#destroy removes event handlers', () => { + const {root, component} = setupTest(); + const handler = jasmine.createSpy('handle selected'); + component.listen(events.SELECTED, handler); + component.destroy(); + + emitEvent(root, events.CLICK); + expect(handler).not.toHaveBeenCalled(); + + component.unlisten(events.SELECTED, handler); + }); + + describe('Adapter', () => { + it('#isSingleSelect returns value of isSingleSelect', () => { + const {component, adapter} = setupTest(); + + component.setIsSingleSelect(true); + expect(adapter.isSingleSelect()).toBeTrue(); + + component.setIsSingleSelect(false); + expect(adapter.isSingleSelect()).toBeFalse(); + + component.destroy(); + }); + + it('#getAttr returns value of attribute of root element', () => { + const {root, component, adapter} = setupTest(); + + root.setAttribute(testStrings.ATTRIBUTE, booleans.FALSE); + expect(adapter.getAttr(testStrings.ATTRIBUTE)).toEqual(booleans.FALSE); + root.setAttribute(testStrings.ATTRIBUTE, booleans.TRUE); + expect(adapter.getAttr(testStrings.ATTRIBUTE)).toEqual(booleans.TRUE); + root.removeAttribute(testStrings.ATTRIBUTE); + expect(adapter.getAttr(testStrings.ATTRIBUTE)).toEqual(null); + + component.destroy(); + }); + + it('#setAttr sets the value of attribute of root element', () => { + const {root, component, adapter} = setupTest(); + + adapter.setAttr(testStrings.ATTRIBUTE, booleans.TRUE); + expect(root.getAttribute(testStrings.ATTRIBUTE)).toEqual(booleans.TRUE); + adapter.setAttr(testStrings.ATTRIBUTE, booleans.FALSE); + expect(root.getAttribute(testStrings.ATTRIBUTE)).toEqual(booleans.FALSE); + + component.destroy(); + }); + + it('#addClass adds class to root element', () => { + const {root, component, adapter} = setupTest(); + + root.classList.remove(testStrings.CLASS); + adapter.addClass(testStrings.CLASS); + expect(root.classList.contains(testStrings.CLASS)).toBeTrue(); + + component.destroy(); + }); + + it('#removeClass removes class from root element', () => { + const {root, component, adapter} = setupTest(); + + root.classList.add(testStrings.CLASS); + adapter.removeClass(testStrings.CLASS); + expect(root.classList.contains(testStrings.CLASS)).toBeFalse(); + + component.destroy(); + }); + + it('#hasClass returns whether root element has class', () => { + const {root, component, adapter} = setupTest(); + + root.classList.add(testStrings.CLASS); + expect(adapter.hasClass(testStrings.CLASS)).toBeTrue(); + root.classList.remove(testStrings.CLASS); + expect(adapter.hasClass(testStrings.CLASS)).toBeFalse(); + + component.destroy(); + }); + + it(`#notifySelectedChange emits ${events.SELECTED} event with SegmentDetail`, () => { + const {root, component, adapter} = setupTest(); + const handler = jasmine.createSpy('selected handler'); + component.listen(events.SELECTED, handler); + + const index = 0; + component.setIndex(index); + root.setAttribute(attributes.DATA_SEGMENT_ID, testStrings.SEGMENT_ID); + + adapter.notifySelectedChange(true); + expect(handler).toHaveBeenCalledWith(jasmine.anything()); + expect(handler.calls.mostRecent().args[0].detail.index).toEqual(index); + expect(handler.calls.mostRecent().args[0].detail.selected).toBeTrue(); + expect(handler.calls.mostRecent().args[0].detail.segmentId).toEqual(testStrings.SEGMENT_ID); + + adapter.notifySelectedChange(false); + expect(handler.calls.mostRecent().args[0].detail.index).toEqual(index); + expect(handler.calls.mostRecent().args[0].detail.selected).toBeFalse(); + expect(handler.calls.mostRecent().args[0].detail.segmentId).toEqual(testStrings.SEGMENT_ID); + + component.unlisten(events.SELECTED, handler); + component.destroy(); + }); + }); + + it('#isSelected returns whether segment is selected', () => { + const {root, component} = setupTest(); + + root.classList.add(cssClasses.SELECTED); + expect(component.isSelected()).toBeTrue(); + + root.classList.remove(cssClasses.SELECTED); + expect(component.isSelected()).toBeFalse(); + + component.destroy(); + }); + + it('#setSelected sets segment to be selected', () => { + const {root, component} = setupTest(); + + root.classList.remove(cssClasses.SELECTED); + root.setAttribute(attributes.ARIA_PRESSED, booleans.FALSE); + component.setIsSingleSelect(false); + component.setSelected(); + expect(root.classList.contains(cssClasses.SELECTED)).toBeTrue(); + expect(root.getAttribute(attributes.ARIA_PRESSED)).toEqual(booleans.TRUE); + + root.classList.remove(cssClasses.SELECTED); + root.setAttribute(attributes.ARIA_CHECKED, booleans.FALSE); + component.setIsSingleSelect(true); + component.setSelected(); + expect(root.classList.contains(cssClasses.SELECTED)).toBeTrue(); + expect(root.getAttribute(attributes.ARIA_CHECKED)).toEqual(booleans.TRUE); + + component.destroy(); + }); + + it('#setUnselected sets segment to be not selected', () => { + const {root, component} = setupTest(); + + component.setUnselected(); + expect(root.classList.contains(cssClasses.SELECTED)).toBeFalse(); + + root.classList.add(cssClasses.SELECTED); + root.setAttribute(attributes.ARIA_PRESSED, booleans.TRUE); + component.setIsSingleSelect(false); + component.setUnselected(); + expect(root.classList.contains(cssClasses.SELECTED)).toBeFalse(); + expect(root.getAttribute(attributes.ARIA_PRESSED)).toEqual(booleans.FALSE); + + root.classList.add(cssClasses.SELECTED); + root.setAttribute(attributes.ARIA_CHECKED, booleans.TRUE); + component.setIsSingleSelect(true); + component.setUnselected(); + expect(root.classList.contains(cssClasses.SELECTED)).toBeFalse(); + expect(root.getAttribute(attributes.ARIA_CHECKED)).toEqual(booleans.FALSE); + + component.destroy(); + }); + + it('#getSegmentId returns segment\'s segmentId if it has one', () => { + const {root, component} = setupTest(); + + root.setAttribute(attributes.DATA_SEGMENT_ID, testStrings.SEGMENT_ID); + expect(component.getSegmentId()).toEqual(testStrings.SEGMENT_ID); + + root.removeAttribute(attributes.DATA_SEGMENT_ID); + expect(component.getSegmentId()).toEqual(undefined); + + component.destroy(); + }); +}); diff --git a/packages/mdc-segmented-button/segment/test/constants.ts b/packages/mdc-segmented-button/segment/test/constants.ts new file mode 100644 index 00000000000..74dda9761d1 --- /dev/null +++ b/packages/mdc-segmented-button/segment/test/constants.ts @@ -0,0 +1,31 @@ +/** + * @license + * Copyright 2020 Google Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Strings constants to be used for tests + */ +export const testStrings = { + SEGMENT_ID: 'test-segment-id', + CLASS: 'test-class', + ATTRIBUTE: 'test-attribute' +}; diff --git a/packages/mdc-segmented-button/segment/test/foundation.test.ts b/packages/mdc-segmented-button/segment/test/foundation.test.ts index 561c9ec8d9a..a1b3f4186e5 100644 --- a/packages/mdc-segmented-button/segment/test/foundation.test.ts +++ b/packages/mdc-segmented-button/segment/test/foundation.test.ts @@ -24,7 +24,7 @@ import {MDCSegmentedButtonSegmentFoundation} from '../foundation'; import {verifyDefaultAdapter} from '../../../../testing/helpers/foundation'; import {setUpFoundationTest} from '../../../../testing/helpers/setup'; -import {cssClasses, strings} from '../constants'; +import {cssClasses, attributes, booleans} from '../constants'; describe('MDCSegmentedButtonSegmentFoundation', () => { it('defaultAdapter returns a complete adapter implementation', () => { @@ -44,16 +44,16 @@ describe('MDCSegmentedButtonSegmentFoundation', () => { mockAdapter.hasClass.withArgs(cssClasses.SELECTED).and.returnValue(selected); mockAdapter.isSingleSelect.and.returnValue(singleSelect); mockAdapter.getAttr.and.callFake((name: string) => { - let is_selected = false; - if ((singleSelect && name === strings.ARIA_PRESSED) || - (!singleSelect && name === strings.ARIA_CHECKED)) { + let isSelected = false; + if ((singleSelect && name === attributes.ARIA_PRESSED) || + (!singleSelect && name === attributes.ARIA_CHECKED)) { return null; - } else if (name === strings.ARIA_CHECKED) { - is_selected = singleSelect; - } else if (name === strings.ARIA_PRESSED) { - is_selected = !singleSelect; + } else if (name === attributes.ARIA_CHECKED) { + isSelected = singleSelect; + } else if (name === attributes.ARIA_PRESSED) { + isSelected = !singleSelect; } - return (selected && is_selected) ? strings.TRUE : strings.FALSE; + return (selected && isSelected) ? booleans.TRUE : booleans.FALSE; }); // Need calls to mocked methods to change mocked state @@ -90,7 +90,7 @@ describe('MDCSegmentedButtonSegmentFoundation', () => { it('#getSegmentId returns segment id', () => { const {foundation, mockAdapter} = setupSelectedTest(); - mockAdapter.getAttr.withArgs(strings.DATA_SEGMENT_ID).and.returnValue('segment0'); + mockAdapter.getAttr.withArgs(attributes.DATA_SEGMENT_ID).and.returnValue('segment0'); expect(foundation.getSegmentId()).toEqual('segment0'); }); @@ -100,7 +100,7 @@ describe('MDCSegmentedButtonSegmentFoundation', () => { foundation.setSelected(); expect(mockAdapter.addClass).toHaveBeenCalledWith(cssClasses.SELECTED); - expect(mockAdapter.setAttr).toHaveBeenCalledWith(strings.ARIA_CHECKED, strings.TRUE); + expect(mockAdapter.setAttr).toHaveBeenCalledWith(attributes.ARIA_CHECKED, booleans.TRUE); }); it('#setSelected adds the `selected` css class and if not singleSelect then sets aria-pressed to true', () => { @@ -108,7 +108,7 @@ describe('MDCSegmentedButtonSegmentFoundation', () => { foundation.setSelected(); expect(mockAdapter.addClass).toHaveBeenCalledWith(cssClasses.SELECTED); - expect(mockAdapter.setAttr).toHaveBeenCalledWith(strings.ARIA_PRESSED, strings.TRUE); + expect(mockAdapter.setAttr).toHaveBeenCalledWith(attributes.ARIA_PRESSED, booleans.TRUE); }); it('#setUnselected removes the `selected` css class and if singleSelect then sets aria-checked to false', () => { @@ -116,7 +116,7 @@ describe('MDCSegmentedButtonSegmentFoundation', () => { foundation.setUnselected(); expect(mockAdapter.removeClass).toHaveBeenCalledWith(cssClasses.SELECTED); - expect(mockAdapter.setAttr).toHaveBeenCalledWith(strings.ARIA_CHECKED, strings.FALSE); + expect(mockAdapter.setAttr).toHaveBeenCalledWith(attributes.ARIA_CHECKED, booleans.FALSE); }); it('#setUnselected removes the `selected` css class and if not singleSelect then sets aria-pressed to false', () => { @@ -124,7 +124,7 @@ describe('MDCSegmentedButtonSegmentFoundation', () => { foundation.setUnselected(); expect(mockAdapter.removeClass).toHaveBeenCalledWith(cssClasses.SELECTED); - expect(mockAdapter.setAttr).toHaveBeenCalledWith(strings.ARIA_PRESSED, strings.FALSE); + expect(mockAdapter.setAttr).toHaveBeenCalledWith(attributes.ARIA_PRESSED, booleans.FALSE); }); }); @@ -134,7 +134,7 @@ describe('MDCSegmentedButtonSegmentFoundation', () => { foundation.handleClick(); expect(mockAdapter.hasClass(cssClasses.SELECTED)).toBeTruthy(); - expect(mockAdapter.getAttr(strings.ARIA_CHECKED)).toBeTruthy(); + expect(mockAdapter.getAttr(attributes.ARIA_CHECKED)).toBeTruthy(); expect(mockAdapter.notifySelectedChange).toHaveBeenCalledWith(true); }); @@ -143,7 +143,7 @@ describe('MDCSegmentedButtonSegmentFoundation', () => { foundation.handleClick(); expect(mockAdapter.hasClass(cssClasses.SELECTED)).toBeTruthy(); - expect(mockAdapter.getAttr(strings.ARIA_CHECKED)).toBeTruthy(); + expect(mockAdapter.getAttr(attributes.ARIA_CHECKED)).toBeTruthy(); expect(mockAdapter.notifySelectedChange).toHaveBeenCalledWith(true); }); @@ -152,7 +152,7 @@ describe('MDCSegmentedButtonSegmentFoundation', () => { foundation.handleClick(); expect(mockAdapter.addClass).toHaveBeenCalledWith(cssClasses.SELECTED); - expect(mockAdapter.setAttr).toHaveBeenCalledWith(strings.ARIA_PRESSED, strings.TRUE); + expect(mockAdapter.setAttr).toHaveBeenCalledWith(attributes.ARIA_PRESSED, booleans.TRUE); expect(mockAdapter.notifySelectedChange).toHaveBeenCalledWith(true); }); @@ -161,7 +161,7 @@ describe('MDCSegmentedButtonSegmentFoundation', () => { foundation.handleClick(); expect(mockAdapter.removeClass).toHaveBeenCalledWith(cssClasses.SELECTED); - expect(mockAdapter.setAttr).toHaveBeenCalledWith(strings.ARIA_PRESSED, strings.FALSE); + expect(mockAdapter.setAttr).toHaveBeenCalledWith(attributes.ARIA_PRESSED, booleans.FALSE); expect(mockAdapter.notifySelectedChange).toHaveBeenCalledWith(false); }); }); diff --git a/packages/mdc-segmented-button/segmented-button/adapter.ts b/packages/mdc-segmented-button/segmented-button/adapter.ts index e24e7237c26..8abbcd2f58f 100644 --- a/packages/mdc-segmented-button/segmented-button/adapter.ts +++ b/packages/mdc-segmented-button/segmented-button/adapter.ts @@ -24,13 +24,40 @@ import {SegmentDetail} from '../types'; export interface MDCSegmentedButtonAdapter { + /** + * @param className Class of interest + * @return Returns true if segmented button has css class, otherwise returns + * false + */ hasClass(className: string): boolean; + /** + * @return Returns child segments represented as readonly list of + * SegmentDetails + */ getSegments(): readonly SegmentDetail[]; + /** + * Sets identified child segment to be selected + * + * @param indexOrSegmentId Number index or string segmentId that identifies + * child segment + */ selectSegment(indexOrSegmentId: number | string): void; + /** + * Sets identified child segment to be not selected + * + * @param indexOrSegmentId Number index or string segmentId that identifies + * child segment + */ unselectSegment(indexOrSegmentId: number | string): void; + /** + * Emits event about changed child segment to client + * + * @param detail Changed child segment represented as a SegmentDetail + * @event change With detail - SegmentDetail + */ notifySelectedChange(detail: SegmentDetail): void; } diff --git a/packages/mdc-segmented-button/segmented-button/component.ts b/packages/mdc-segmented-button/segmented-button/component.ts index 47324637a85..8da0e99d90c 100644 --- a/packages/mdc-segmented-button/segmented-button/component.ts +++ b/packages/mdc-segmented-button/segmented-button/component.ts @@ -22,44 +22,149 @@ */ import {MDCComponent} from '@material/base/component'; -import {MDCSegmentedButtonSegmentFactory} from '../segment/component'; -import {SegmentDetail} from '../types'; +import {CustomEventListener} from '@material/base/types'; +import {MDCSegmentedButtonSegment, MDCSegmentedButtonSegmentFactory} from '../segment/component'; +import {SegmentDetail, MDCSegmentedButtonEvent} from '../types'; +import {MDCSegmentedButtonAdapter} from './adapter'; import {MDCSegmentedButtonFoundation} from './foundation'; +import {selectors, events} from './constants'; export class MDCSegmentedButton extends MDCComponent { static attachTo(root: Element): MDCSegmentedButton { return new MDCSegmentedButton(root); } - initialize(_segmentFactory: MDCSegmentedButtonSegmentFactory) { - return; + get segments(): ReadonlyArray { + return this.segments_.slice(); + } + + private segments_!: MDCSegmentedButtonSegment[]; // assigned in initialize + private segmentFactory!: + (el: Element) => MDCSegmentedButtonSegment; // assigned in initialize + private handleSelected!: + CustomEventListener; // assigned in initialSyncWithDOM + + initialize(segmentFactory: MDCSegmentedButtonSegmentFactory = (el) => new MDCSegmentedButtonSegment(el)) { + this.segmentFactory = segmentFactory; + this.segments_ = this.instantiateSegments(this.segmentFactory); + } + + /** + * @param segmentFactory Factory to create new child segments + * @return Returns list of child segments found in DOM + */ + private instantiateSegments(segmentFactory: MDCSegmentedButtonSegmentFactory): MDCSegmentedButtonSegment[] { + const segmentElements: Element[] = + [].slice.call(this.root.querySelectorAll(selectors.SEGMENT)); + return segmentElements.map((el: Element) => segmentFactory(el)); } initialSyncWithDOM() { - return; + this.handleSelected = (event) => + this.foundation.handleSelected(event.detail); + this.listen(events.SELECTED, this.handleSelected); + + const isSingleSelect = this.foundation.isSingleSelect(); + this.segments_.forEach((segment, index: number) => { + segment.setIndex(index); + segment.setIsSingleSelect(isSingleSelect); + }); } destroy() { + this.segments_.forEach((segment) => { + segment.destroy(); + }); + + this.unlisten(events.SELECTED, this.handleSelected); + super.destroy(); } getDefaultFoundation(): MDCSegmentedButtonFoundation { - return new MDCSegmentedButtonFoundation(); + const adapter: MDCSegmentedButtonAdapter = { + hasClass: (className) => { + return this.root.classList.contains(className); + }, + getSegments: () => { + return this.mappedSegments(); + }, + selectSegment: (indexOrSegmentId) => { + const segmentDetail = this.mappedSegments().find((_segmentDetail) => + _segmentDetail.index === indexOrSegmentId + || _segmentDetail.segmentId === indexOrSegmentId + ); + if (segmentDetail) { + this.segments_[segmentDetail.index].setSelected(); + } + }, + unselectSegment: (indexOrSegmentId) => { + const segmentDetail = this.mappedSegments().find((_segmentDetail) => + _segmentDetail.index === indexOrSegmentId + || _segmentDetail.segmentId === indexOrSegmentId + ); + if (segmentDetail) { + this.segments_[segmentDetail.index].setUnselected(); + } + }, + notifySelectedChange: (detail) => { + this.emit( + events.CHANGE, + detail, + true /* shouldBubble */ + ); + } + }; + return new MDCSegmentedButtonFoundation(adapter); } + /** + * @return Returns readonly list of selected child segments as SegmentDetails + */ getSelectedSegments(): readonly SegmentDetail[] { - return []; + return this.foundation.getSelectedSegments(); + } + + /** + * Sets identified segment to be selected + * + * @param indexOrSegmentId Number index or string segmentId that identifies + * child segment + */ + selectSegment(indexOrSegmentId: number | string) { + this.foundation.selectSegment(indexOrSegmentId); } - selectSegment(_indexOrSegmentId: number | string) { - return; + /** + * Sets identified segment to be not selected + * + * @param indexOrSegmentId Number index or string segmentId that identifies + * child segment + */ + unselectSegment(indexOrSegmentId: number | string) { + this.foundation.unselectSegment(indexOrSegmentId); } - unselectSegment(_indexOrSegmentId: number | string) { - return; + /** + * @param indexOrSegmentId Number index or string segmentId that identifies + * child segment + * @return Returns true if identified child segment is currently selected, + * otherwise returns false + */ + isSegmentSelected(indexOrSegmentId: number | string): boolean { + return this.foundation.isSegmentSelected(indexOrSegmentId); } - isSegmentSelected(_indexOrSegmentId: number | string): boolean { - return false; + /** + * @return Returns child segments mapped to readonly SegmentDetail list + */ + private mappedSegments(): readonly SegmentDetail[] { + return this.segments_.map((segment: MDCSegmentedButtonSegment, index: number) => { + return { + index, + selected: segment.isSelected(), + segmentId: segment.getSegmentId() + }; + }); } } diff --git a/packages/mdc-segmented-button/segmented-button/constants.ts b/packages/mdc-segmented-button/segmented-button/constants.ts index a740e083b63..bd65954b017 100644 --- a/packages/mdc-segmented-button/segmented-button/constants.ts +++ b/packages/mdc-segmented-button/segmented-button/constants.ts @@ -21,6 +21,21 @@ * THE SOFTWARE. */ +/** + * Selectors used by segmented-button + */ +export const selectors = { + SEGMENT: '.mdc-segmented-button__segment' +}; + +/** + * Events received or emitted by segmented-button + */ +export const events = { + SELECTED: 'selected', + CHANGE: 'change' +} + /** * Style classes for segmented-button */ diff --git a/packages/mdc-segmented-button/segmented-button/foundation.ts b/packages/mdc-segmented-button/segmented-button/foundation.ts index c133ff001e7..84fa2cd3004 100644 --- a/packages/mdc-segmented-button/segmented-button/foundation.ts +++ b/packages/mdc-segmented-button/segmented-button/foundation.ts @@ -41,26 +41,60 @@ export class MDCSegmentedButtonFoundation extends MDCFoundation segmentDetail.selected); } + /** + * @param indexOrSegmentId Number index or string segmentId that identifies + * child segment + * @return Returns true if identified child segment is currently selected, + * otherwise returns false + */ isSegmentSelected(indexOrSegmentId: number | string): boolean { return this.adapter.getSegments().some(segmentDetail => (segmentDetail.index === indexOrSegmentId || segmentDetail.segmentId === indexOrSegmentId) && segmentDetail.selected); } + /** + * @return Returns true if segmented button is single select, otherwise + * returns false + */ isSingleSelect(): boolean { return this.adapter.hasClass(cssClasses.SINGLE_SELECT); } + /** + * Called when child segment's selected status may have changed. If segmented + * button is single select, unselects all child segments other than identified + * child segment. Finally, emits event to client. + * + * @param detail Child segment affected represented as SegmentDetail + * @event change With detail - SegmentDetail + */ handleSelected(detail: SegmentDetail) { if (this.isSingleSelect()) { this.unselectPrevSelected(detail.index); @@ -68,6 +102,12 @@ export class MDCSegmentedButtonFoundation extends MDCFoundation { + const wrapper = document.createElement('div'); + wrapper.innerHTML = ` +
+ + + +
+ `; + + const el = wrapper.firstElementChild as HTMLElement; + wrapper.removeChild(el); + return el; +}; + +const setupTest = () => { + const root = getFixtureMultiWithLabel(); + const component = new MDCSegmentedButton(root); + const adapter = (component.getDefaultFoundation() as any).adapter; + return {root, component, adapter}; +}; + +const setAllSelected = (els: NodeListOf) => { + els.forEach((el) => { + el.classList.add(testCssClasses.SELECTED); + }); +} + +const setAllUnselected = (els: NodeListOf) => { + els.forEach((el) => { + el.classList.remove(testCssClasses.SELECTED); + }); +} + +describe('MDCSegmentedButton', () => { + it('#attachTo returns an MDCSegmentedButton instance', () => { + expect(MDCSegmentedButton.attachTo(getFixtureMultiWithLabel()) instanceof MDCSegmentedButton).toBeTruthy(); + }); + + it('#constructor instantiates child segment components', () => { + const {component} = setupTest(); + expect(component.segments.length).toEqual(3); + expect(component.segments[0]).toEqual(jasmine.any(MDCSegmentedButtonSegment)); + expect(component.segments[1]).toEqual(jasmine.any(MDCSegmentedButtonSegment)); + expect(component.segments[2]).toEqual(jasmine.any(MDCSegmentedButtonSegment)); + component.destroy(); + }); + + it('#destroy cleans up child segment components', () => { + const {root, component} = setupTest(); + const handler = jasmine.createSpy('handle click') + for (let i = 0; i < component.segments.length; i++) { + component.segments[i].listen('click', handler); + } + component.destroy(); + emitEvent(root, 'click'); + expect(handler).not.toHaveBeenCalled(); + }); + + it('#initialSyncWithDOM sets up event handlers', () => { + const {root, component} = setupTest(); + const handler = jasmine.createSpy('handle change'); + component.listen(events.CHANGE, handler); + + emitEvent(root, events.SELECTED); + expect(handler).toHaveBeenCalled(); + + component.unlisten(events.CHANGE, handler); + component.destroy(); + }); + + it('#destroy removes event handlers', () => { + const {root, component} = setupTest(); + const handler = jasmine.createSpy('handle change'); + component.listen(events.CHANGE, handler); + component.destroy(); + + emitEvent(root, events.SELECTED); + expect(handler).not.toHaveBeenCalled(); + + component.unlisten(events.CHANGE, handler); + }); + + it('#initialSyncWithDOM sets children\'s \'index\' and \'isSingleSelect\' values', () => { + const {root, component} = setupTest(); + const handler = jasmine.createSpy('handle selected'); + component.listen(events.SELECTED, handler); + const isSingleSelect = root.classList.contains(cssClasses.SINGLE_SELECT); + + for (let i = 0; i < component.segments.length; i++) { + const segmentAdapter = (component.segments[i].getDefaultFoundation() as any).adapter + segmentAdapter.notifySelectedChange(true); + + expect(handler.calls.mostRecent().args[0].detail.index).toEqual(i); + expect(segmentAdapter.isSingleSelect()).toEqual(isSingleSelect); + } + + component.destroy(); + }); + + describe('Adapter', () => { + it('#hasClass returns whether root element has test class', () => { + const {root, component, adapter} = setupTest(); + + root.classList.add(testCssClasses.TEST_CLASS); + expect(adapter.hasClass(testCssClasses.TEST_CLASS)).toBeTrue(); + root.classList.remove(testCssClasses.TEST_CLASS); + expect(adapter.hasClass(testCssClasses.TEST_CLASS)).toBeFalse(); + + component.destroy(); + }); + + it('#getSegments returns child segments as readonly SegmentDetails array', () => { + const {component, adapter} = setupTest(); + + const segments = adapter.getSegments(); + expect(segments.length).toEqual(3); + for (let i = 0; i < segments.length; i++) { + expect(segments[i].index).toEqual(i); + expect(segments[i].hasOwnProperty('selected')).toBeTrue(); + expect(segments[i].hasOwnProperty('segmentId')).toBeTrue(); + } + + component.destroy(); + }); + + it('#selectSegment selects identified child segment if found', () => { + const {root, component, adapter} = setupTest(); + + const segments = root.querySelectorAll(testSelectors.SEGMENT); + segments[testIndices.SELECTED].setAttribute(attributes.DATA_SEGMENT_ID, testSegmentIds.SELECTED_SEGMENT_ID); + + setAllUnselected(segments); + adapter.selectSegment(testIndices.SELECTED); + for (let i = 0; i < segments.length; i++) { + expect(segments[i].classList.contains(testCssClasses.SELECTED)).toEqual(i === testIndices.SELECTED); + } + + setAllUnselected(segments); + adapter.selectSegment(testSegmentIds.SELECTED_SEGMENT_ID); + for (let i = 0; i < segments.length; i++) { + expect(segments[i].classList.contains(testCssClasses.SELECTED)).toEqual(i === testIndices.SELECTED); + } + + component.destroy(); + }); + + it('#selectSegment selects no child segment if none is identified', () => { + const {root, component, adapter} = setupTest(); + + const segments = root.querySelectorAll(testSelectors.SEGMENT); + setAllUnselected(segments); + + adapter.selectSegment(testIndices.NOT_PRESENT); + for (let i = 0; i < segments.length; i++) { + expect(segments[i].classList.contains(testCssClasses.SELECTED)).toBeFalse(); + } + + adapter.selectSegment(testSegmentIds.NOT_PRESENT_SEGMENT_ID); + for (let i = 0; i < segments.length; i++) { + expect(segments[i].classList.contains(testCssClasses.SELECTED)).toBeFalse(); + } + + component.destroy(); + }); + + it('#unselectSegment unselectes identified child segment if found', () => { + const {root, component, adapter} = setupTest(); + + const segments = root.querySelectorAll(testSelectors.SEGMENT); + segments[testIndices.UNSELECTED].setAttribute(attributes.DATA_SEGMENT_ID, testSegmentIds.UNSELECTED_SEGMENT_ID); + + setAllSelected(segments); + adapter.unselectSegment(testIndices.UNSELECTED); + for (let i = 0; i < segments.length; i++) { + expect(segments[i].classList.contains(testCssClasses.SELECTED)).toEqual(i !== testIndices.UNSELECTED); + } + + setAllSelected(segments); + adapter.unselectSegment(testSegmentIds.UNSELECTED_SEGMENT_ID); + for (let i = 0; i < segments.length; i++) { + expect(segments[i].classList.contains(testCssClasses.SELECTED)).toEqual(i !== testIndices.UNSELECTED); + } + + component.destroy(); + }); + + it('#unselectSegment unselects no child segment if none is identified', () => { + const {root, component, adapter} = setupTest(); + + const segments = root.querySelectorAll(testSelectors.SEGMENT); + setAllSelected(segments); + + adapter.unselectSegment(testIndices.NOT_PRESENT); + for (let i = 0; i < segments.length; i++) { + expect(segments[i].classList.contains(testCssClasses.SELECTED)).toBeTrue(); + } + + adapter.unselectSegment(testSegmentIds.NOT_PRESENT_SEGMENT_ID); + for (let i = 0; i < segments.length; i++) { + expect(segments[i].classList.contains(testCssClasses.SELECTED)).toBeTrue(); + } + + component.destroy(); + }); + + it(`#notifySelectedChange emits ${events.CHANGE} with SegmentDetail`, () => { + const {component, adapter} = setupTest(); + const handler = jasmine.createSpy('change handler'); + + component.listen(events.CHANGE, handler); + + adapter.notifySelectedChange({ + index: testIndices.SELECTED, + selected: true, + segmentId: testSegmentIds.SELECTED_SEGMENT_ID + }); + expect(handler).toHaveBeenCalledWith(jasmine.anything()); + expect(handler.calls.mostRecent().args[0].detail.index).toEqual(testIndices.SELECTED); + expect(handler.calls.mostRecent().args[0].detail.selected).toBeTrue(); + expect(handler.calls.mostRecent().args[0].detail.segmentId).toEqual(testSegmentIds.SELECTED_SEGMENT_ID); + + component.unlisten(events.CHANGE, handler); + component.destroy(); + }); + }); + + it('#getSelectedSegments returns selected child segments as a SegmentDetail list', () => { + const {root, component} = setupTest(); + + const segments = root.querySelectorAll(testSelectors.SEGMENT); + setAllSelected(segments); + + const selectedSegments = component.getSelectedSegments(); + expect(selectedSegments.length).toEqual(segments.length); + for (let i = 0; i < selectedSegments.length; i++) { + expect(selectedSegments[i].index).toEqual(i); + expect(selectedSegments[i].selected).toBeTrue(); + expect(selectedSegments[i].hasOwnProperty('segmentId')).toBeTrue(); + } + + setAllUnselected(segments); + expect(component.getSelectedSegments().length).toEqual(0); + + component.destroy(); + }); + + it('#selectSegment selects identified child segment', () => { + const {root, component} = setupTest(); + + const segments = root.querySelectorAll(testSelectors.SEGMENT); + const selectedSegment = segments[testIndices.SELECTED]; + selectedSegment.setAttribute(attributes.DATA_SEGMENT_ID, testSegmentIds.SELECTED_SEGMENT_ID); + + setAllUnselected(segments); + component.selectSegment(testIndices.SELECTED); + expect(selectedSegment.classList.contains(testCssClasses.SELECTED)).toBeTrue(); + + setAllUnselected(segments); + component.selectSegment(testSegmentIds.SELECTED_SEGMENT_ID); + expect(selectedSegment.classList.contains(testCssClasses.SELECTED)).toBeTrue(); + + component.destroy(); + }); + + it('#unselectSegment unselects identified child segment', () => { + const {root, component} = setupTest(); + + const segments = root.querySelectorAll(testSelectors.SEGMENT); + const unselectedSegment = segments[testIndices.UNSELECTED]; + unselectedSegment.setAttribute(attributes.DATA_SEGMENT_ID, testSegmentIds.UNSELECTED_SEGMENT_ID); + + setAllSelected(segments); + component.unselectSegment(testIndices.UNSELECTED); + expect(unselectedSegment.classList.contains(testCssClasses.SELECTED)).toBeFalse(); + + setAllSelected(segments); + component.unselectSegment(testSegmentIds.UNSELECTED_SEGMENT_ID); + expect(unselectedSegment.classList.contains(testCssClasses.SELECTED)).toBeFalse(); + + component.destroy(); + }); + + it('#isSegmentSelected returns whether identified child segment is selected', () => { + const {root, component} = setupTest(); + + const segments = root.querySelectorAll(testSelectors.SEGMENT); + const selectedSegment = segments[testIndices.SELECTED]; + const unselectedSegment = segments[testIndices.UNSELECTED]; + selectedSegment.classList.add(testCssClasses.SELECTED); + selectedSegment.setAttribute(attributes.DATA_SEGMENT_ID, testSegmentIds.SELECTED_SEGMENT_ID); + unselectedSegment.classList.remove(testCssClasses.SELECTED); + unselectedSegment.setAttribute(attributes.DATA_SEGMENT_ID, testSegmentIds.UNSELECTED_SEGMENT_ID); + + expect(component.isSegmentSelected(testIndices.SELECTED)).toBeTrue(); + expect(component.isSegmentSelected(testSegmentIds.SELECTED_SEGMENT_ID)).toBeTrue(); + expect(component.isSegmentSelected(testIndices.UNSELECTED)).toBeFalse(); + expect(component.isSegmentSelected(testSegmentIds.UNSELECTED_SEGMENT_ID)).toBeFalse(); + expect(component.isSegmentSelected(testIndices.NOT_PRESENT)).toBeFalse(); + expect(component.isSegmentSelected(testSegmentIds.NOT_PRESENT_SEGMENT_ID)).toBeFalse(); + + component.destroy(); + }); +}); diff --git a/packages/mdc-segmented-button/segmented-button/test/constants.ts b/packages/mdc-segmented-button/segmented-button/test/constants.ts index 6309b1256dd..28e63467ad8 100644 --- a/packages/mdc-segmented-button/segmented-button/test/constants.ts +++ b/packages/mdc-segmented-button/segmented-button/test/constants.ts @@ -21,10 +21,25 @@ * THE SOFTWARE. */ +/** + * String constants for segmented button tests + */ +export const testCssClasses = { + TEST_CLASS: 'test-class', + SELECTED: 'mdc-segmented-button__segment--selected' +}; + +/** + * CSS class selectors for segmented button tests + */ +export const testSelectors = { + SEGMENT: '.mdc-segmented-button__segment' +} + /** * Indices for segments used in tests */ -export enum test_indices { +export enum testIndices { NOT_PRESENT = -1, UNSELECTED = 0, SELECTED = 1 @@ -33,7 +48,7 @@ export enum test_indices { /** * SegmentIds for segments used in tests */ -export const test_segment_ids = { +export const testSegmentIds = { NOT_PRESENT_SEGMENT_ID: 'segment-1', UNSELECTED_SEGMENT_ID: 'segment0', SELECTED_SEGMENT_ID: 'segment1' diff --git a/packages/mdc-segmented-button/segmented-button/test/foundation.test.ts b/packages/mdc-segmented-button/segmented-button/test/foundation.test.ts index 0e1764f4d38..afd8c9759f6 100644 --- a/packages/mdc-segmented-button/segmented-button/test/foundation.test.ts +++ b/packages/mdc-segmented-button/segmented-button/test/foundation.test.ts @@ -25,7 +25,7 @@ import {MDCSegmentedButtonFoundation} from '../foundation'; import {verifyDefaultAdapter} from '../../../../testing/helpers/foundation'; import {setUpFoundationTest} from '../../../../testing/helpers/setup'; import {cssClasses} from '../constants'; -import {test_indices, test_segment_ids} from './constants'; +import {testIndices, testSegmentIds} from './constants'; describe('MDCSegmentedButtonFoundation', () => { it('defaultAdapter returns a complete adapter implementation', () => { @@ -54,15 +54,15 @@ describe('MDCSegmentedButtonFoundation', () => { const {foundation, mockAdapter} = singleSelect ? setupSingleSelectTest() : setupMultiSelectTest(); mockAdapter.getSegments.and.callFake(() => { let l = [] - l[test_indices.UNSELECTED] = { - 'index': test_indices.UNSELECTED, + l[testIndices.UNSELECTED] = { + 'index': testIndices.UNSELECTED, 'selected': false, - 'segmentId': test_segment_ids.UNSELECTED_SEGMENT_ID + 'segmentId': testSegmentIds.UNSELECTED_SEGMENT_ID }; - l[test_indices.SELECTED] = { - 'index': test_indices.SELECTED, + l[testIndices.SELECTED] = { + 'index': testIndices.SELECTED, 'selected': true, - 'segmentId': test_segment_ids.SELECTED_SEGMENT_ID + 'segmentId': testSegmentIds.SELECTED_SEGMENT_ID }; return l; }); @@ -71,7 +71,7 @@ describe('MDCSegmentedButtonFoundation', () => { it('#unselectSegment does not emit an event', () => { const {foundation, mockAdapter} = setupSegmentTest(); - let selectedSegment = mockAdapter.getSegments()[test_indices.SELECTED]; + let selectedSegment = mockAdapter.getSegments()[testIndices.SELECTED]; foundation.unselectSegment(selectedSegment.segmentId); expect(mockAdapter.notifySelectedChange).toHaveBeenCalledTimes(0); @@ -79,7 +79,7 @@ describe('MDCSegmentedButtonFoundation', () => { it('#getSelectedSegments returns selected segments', () => { const {foundation, mockAdapter} = setupSegmentTest(); - let selectedSegment = mockAdapter.getSegments()[test_indices.SELECTED]; + let selectedSegment = mockAdapter.getSegments()[testIndices.SELECTED]; let selectedSegments = foundation.getSelectedSegments(); expect(selectedSegments.length).toEqual(1); @@ -90,7 +90,7 @@ describe('MDCSegmentedButtonFoundation', () => { it('#selectSegment does not emit an event', () => { const {foundation, mockAdapter} = setupSegmentTest(); - let unselectedSegment = mockAdapter.getSegments()[test_indices.UNSELECTED]; + let unselectedSegment = mockAdapter.getSegments()[testIndices.UNSELECTED]; foundation.selectSegment(unselectedSegment.index); expect(mockAdapter.notifySelectedChange).toHaveBeenCalledTimes(0); @@ -104,8 +104,8 @@ describe('MDCSegmentedButtonFoundation', () => { it('#handleSelected unselects segment if single select and another segment was selected', () => { const {foundation, mockAdapter} = setupSegmentTest(true); - let newSelectedSegment = mockAdapter.getSegments()[test_indices.UNSELECTED]; - let newUnselectedSegment = mockAdapter.getSegments()[test_indices.SELECTED]; + let newSelectedSegment = mockAdapter.getSegments()[testIndices.UNSELECTED]; + let newUnselectedSegment = mockAdapter.getSegments()[testIndices.SELECTED]; newSelectedSegment.selected = true; foundation.handleSelected(newSelectedSegment); @@ -127,8 +127,8 @@ describe('MDCSegmentedButtonFoundation', () => { it('#handleSelected changes nothing if multi select and segment is selected or unselected', () => { const {foundation, mockAdapter} = setupSegmentTest(); - let newUnselectedSegment = mockAdapter.getSegments()[test_indices.SELECTED]; - let newSelectedSegment = mockAdapter.getSegments()[test_indices.UNSELECTED]; + let newUnselectedSegment = mockAdapter.getSegments()[testIndices.SELECTED]; + let newSelectedSegment = mockAdapter.getSegments()[testIndices.UNSELECTED]; newUnselectedSegment.selected = false; newSelectedSegment.selected = true; @@ -147,24 +147,24 @@ describe('MDCSegmentedButtonFoundation', () => { describe('Segment selections by index', () => { it('#isSegmentSelected returns true if segment at index is selected', () => { const {foundation, mockAdapter} = setupSegmentTest(); - let selectedSegment = mockAdapter.getSegments()[test_indices.SELECTED]; + let selectedSegment = mockAdapter.getSegments()[testIndices.SELECTED]; expect(foundation.isSegmentSelected(selectedSegment.index)).toBeTruthy(); }); it('#isSegmentSelected returns false if segment at index is not selected', () => { const {foundation, mockAdapter} = setupSegmentTest(); - let unselectedSegment = mockAdapter.getSegments()[test_indices.UNSELECTED]; + let unselectedSegment = mockAdapter.getSegments()[testIndices.UNSELECTED]; expect(foundation.isSegmentSelected(unselectedSegment.index)).toBeFalsy(); }); it('#isSegmentSelected returns false if no segment is at index', () => { const {foundation} = setupSegmentTest(); - expect(foundation.isSegmentSelected(test_indices.NOT_PRESENT)).toBeFalsy(); + expect(foundation.isSegmentSelected(testIndices.NOT_PRESENT)).toBeFalsy(); }); it('#selectSegment selects segment at index if it is unselected', () => { const {foundation, mockAdapter} = setupSegmentTest(); - let unselectedSegment = mockAdapter.getSegments()[test_indices.UNSELECTED]; + let unselectedSegment = mockAdapter.getSegments()[testIndices.UNSELECTED]; foundation.selectSegment(unselectedSegment.index); expect(mockAdapter.selectSegment).toHaveBeenCalledWith(unselectedSegment.index); @@ -172,7 +172,7 @@ describe('MDCSegmentedButtonFoundation', () => { it('#unselectSegment unselects segment at index if it is selected', () => { const {foundation, mockAdapter} = setupSegmentTest(); - let selectedSegment = mockAdapter.getSegments()[test_indices.SELECTED]; + let selectedSegment = mockAdapter.getSegments()[testIndices.SELECTED]; foundation.unselectSegment(selectedSegment.index); expect(mockAdapter.unselectSegment).toHaveBeenCalledWith(selectedSegment.index); @@ -182,24 +182,24 @@ describe('MDCSegmentedButtonFoundation', () => { describe('Segment selections by segmentId', () => { it('#isSegmentSelected returns true if segment with segmentId is selected', () => { const {foundation, mockAdapter} = setupSegmentTest(); - let selectedSegment = mockAdapter.getSegments()[test_indices.SELECTED]; + let selectedSegment = mockAdapter.getSegments()[testIndices.SELECTED]; expect(foundation.isSegmentSelected(selectedSegment.segmentId)).toBeTruthy(); }); it('#isSegmentSelected returns false if segment with segmentId is not selected', () => { const {foundation, mockAdapter} = setupSegmentTest(); - let unselectedSegment = mockAdapter.getSegments()[test_indices.UNSELECTED]; + let unselectedSegment = mockAdapter.getSegments()[testIndices.UNSELECTED]; expect(foundation.isSegmentSelected(unselectedSegment.segmentId)).toBeFalsy(); }); it('#isSegmentSelected returns false if no segment has segmentId', () => { const {foundation} = setupSegmentTest(); - expect(foundation.isSegmentSelected(test_segment_ids.NOT_PRESENT_SEGMENT_ID)).toBeFalsy(); + expect(foundation.isSegmentSelected(testSegmentIds.NOT_PRESENT_SEGMENT_ID)).toBeFalsy(); }); it('#selectSegment selects segment with segmentId if it is unselected', () => { const {foundation, mockAdapter} = setupSegmentTest(); - let unselectedSegment = mockAdapter.getSegments()[test_indices.UNSELECTED]; + let unselectedSegment = mockAdapter.getSegments()[testIndices.UNSELECTED]; foundation.selectSegment(unselectedSegment.segmentId); expect(mockAdapter.selectSegment).toHaveBeenCalledWith(unselectedSegment.segmentId); @@ -207,7 +207,7 @@ describe('MDCSegmentedButtonFoundation', () => { it('#unselectSegment unselects segment with segmentId if it is selected', () => { const {foundation, mockAdapter} = setupSegmentTest(); - let selectedSegment = mockAdapter.getSegments()[test_indices.SELECTED]; + let selectedSegment = mockAdapter.getSegments()[testIndices.SELECTED]; foundation.unselectSegment(selectedSegment.segmentId); expect(mockAdapter.unselectSegment).toHaveBeenCalledWith(selectedSegment.segmentId);