diff --git a/pages/toggle_action_button.html b/pages/toggle_action_button.html
index 3d236641..f12c0408 100644
--- a/pages/toggle_action_button.html
+++ b/pages/toggle_action_button.html
@@ -26,6 +26,27 @@
Toggle Button
diff --git a/src/Index.ts b/src/Index.ts
index 1b6a0df0..0a5e1d55 100644
--- a/src/Index.ts
+++ b/src/Index.ts
@@ -2,6 +2,7 @@
export { ActionButton } from './components/ActionButton/ActionButton';
export { ToggleActionButton } from './components/ActionButton/ToggleActionButton';
+export { DisableableToggleActionButton } from './components/ActionButton/DisableableToggleActionButton';
export { AttachResult } from './components/AttachResult/AttachResult';
export { UserActivity } from './components/UserActions/UserActivity';
export { UserActions } from './components/UserActions/UserActions';
diff --git a/src/components/ActionButton/DisableableActionButton.scss b/src/components/ActionButton/DisableableActionButton.scss
new file mode 100644
index 00000000..28daf4cb
--- /dev/null
+++ b/src/components/ActionButton/DisableableActionButton.scss
@@ -0,0 +1,15 @@
+@import './ActionButton.scss';
+@import '../../sass/Variables.scss';
+
+$disable-color: $primary-color-light;
+
+button.CoveoActionButton.coveo-actionbutton.coveo-actionbutton-disabled {
+ background-color: $primary-color-lightest;
+ border-color: $disable-color;
+
+ .coveo-actionbutton_icon {
+ svg {
+ fill: $disable-color;
+ }
+ }
+}
diff --git a/src/components/ActionButton/DisableableButton.ts b/src/components/ActionButton/DisableableButton.ts
new file mode 100644
index 00000000..556e1716
--- /dev/null
+++ b/src/components/ActionButton/DisableableButton.ts
@@ -0,0 +1,34 @@
+import { StatefulActionButton, IStatefulActionButtonOptionsWithIcon } from './StatefulActionButton';
+
+export interface IDisableableButtonOptions {
+ disabledIcon: string;
+ disabledTooltip: string;
+}
+
+export interface IDisableableButton {
+ options: IDisableableButtonOptions;
+}
+
+export class DisabledState implements IStatefulActionButtonOptionsWithIcon {
+ static DISABLED_CLASS_NAME = 'coveo-actionbutton-disabled';
+ public readonly onStateEntry: (this: StatefulActionButton) => void;
+ public readonly onStateExit: (this: StatefulActionButton) => void;
+ public readonly click: () => void;
+ public readonly icon: string;
+ public readonly tooltip: string;
+ public readonly name = 'DisabledState';
+
+ constructor(disabledButton: IDisableableButton) {
+ this.onStateEntry = function () {
+ this.element.classList.add(DisabledState.DISABLED_CLASS_NAME);
+ this.element.setAttribute('disabled', '');
+ };
+ this.onStateExit = function () {
+ this.element.classList.remove(DisabledState.DISABLED_CLASS_NAME);
+ this.element.removeAttribute('disabled');
+ };
+ this.click = () => {};
+ this.icon = disabledButton.options.disabledIcon;
+ this.tooltip = disabledButton.options.disabledTooltip;
+ }
+}
diff --git a/src/components/ActionButton/DisableableToggleActionButton.ts b/src/components/ActionButton/DisableableToggleActionButton.ts
new file mode 100644
index 00000000..5f8a6d5f
--- /dev/null
+++ b/src/components/ActionButton/DisableableToggleActionButton.ts
@@ -0,0 +1,120 @@
+import { ComponentOptions, IResultsComponentBindings, Component, Initialization } from 'coveo-search-ui';
+import { StatefulActionButton } from './StatefulActionButton';
+import {
+ ToggleActivatedState as ActivatedState,
+ ToggleDeactivatedState as DeactivatedState,
+ IToggleableButton,
+ IToggleableButtonOptions,
+} from './ToggleableButton';
+import { IDisableableButton, IDisableableButtonOptions, DisabledState } from './DisableableButton';
+import { ToggleActionButton } from './ToggleActionButton';
+
+export interface IDisableableToggleActionButtonOptions extends IToggleableButtonOptions, IDisableableButtonOptions {}
+
+export class DisableableToggleActionButton extends Component implements IToggleableButton, IDisableableButton {
+ static ID = 'DisableableToggleActionButton';
+ static ACTIVATED_CLASS_NAME = 'coveo-toggleactionbutton-activated';
+
+ private innerStatefulActionButton: StatefulActionButton;
+ private activatedState: ActivatedState;
+ private deactivatedState: DeactivatedState;
+ private disabledState: DisabledState;
+
+ static options: IDisableableToggleActionButtonOptions = {
+ ...ToggleActionButton.options,
+ disabledTooltip: ComponentOptions.buildStringOption(),
+ disabledIcon: ComponentOptions.buildStringOption(),
+ };
+
+ constructor(public element: HTMLElement, public options: IDisableableToggleActionButtonOptions, public bindings?: IResultsComponentBindings) {
+ super(element, DisableableToggleActionButton.ID, bindings);
+ this.options = ComponentOptions.initComponentOptions(element, DisableableToggleActionButton, options);
+
+ this.createInnerButton(bindings);
+ }
+
+ /**
+ * Indicates whether the toggle button is in the activated state.
+ */
+ public isActivated(): boolean {
+ return this.innerStatefulActionButton.getCurrentState() === this.activatedState;
+ }
+
+ /**
+ * Indicates whether the disableable toggle button is in the disable state.
+ */
+ public isDisabled(): boolean {
+ return this.innerStatefulActionButton.getCurrentState() === this.disabledState;
+ }
+
+ /**
+ * Sets the toggle button to the specified state.
+ * @param activated Whether the button is activated.
+ */
+ public setActivated(activated: boolean): void {
+ if (this.isDisabled() && !activated) {
+ this.innerStatefulActionButton.switchTo(this.deactivatedState);
+ }
+ if (!this.isDisabled() && activated !== this.isActivated()) {
+ this.innerStatefulActionButton.switchTo(activated ? this.activatedState : this.deactivatedState);
+ }
+ }
+
+ public setEnabled(enabled: boolean): void {
+ if (enabled) {
+ this.enable();
+ } else {
+ this.disable();
+ }
+ }
+
+ public disable(): void {
+ if (this.isDisabled()) {
+ return;
+ }
+ if (this.isActivated()) {
+ this.innerStatefulActionButton.switchTo(this.deactivatedState);
+ }
+ this.innerStatefulActionButton.switchTo(this.disabledState);
+ }
+
+ public enable(): void {
+ if (this.isDisabled()) {
+ this.innerStatefulActionButton.switchTo(this.deactivatedState);
+ }
+ }
+
+ public onClick(): void {
+ if (this.isDisabled()) {
+ return;
+ }
+ this.setActivated(!this.isActivated());
+
+ if (this.options.click) {
+ this.options.click();
+ }
+ }
+
+ private createInnerButton(bindings?: IResultsComponentBindings): void {
+ this.deactivatedState = new DeactivatedState(this);
+ this.disabledState = new DisabledState(this);
+ this.activatedState = new ActivatedState(this);
+
+ this.innerStatefulActionButton = new StatefulActionButton(
+ this.element,
+ {
+ initialState: this.deactivatedState,
+ states: [this.deactivatedState, this.activatedState, this.disabledState],
+ allowedTransitions: [
+ { from: this.deactivatedState, to: this.disabledState },
+ { from: this.disabledState, to: this.deactivatedState },
+ { from: this.deactivatedState, to: this.activatedState },
+ { from: this.activatedState, to: this.deactivatedState },
+ ],
+ },
+ bindings
+ );
+ }
+}
+
+Initialization.registerAutoCreateComponent(DisableableToggleActionButton);
diff --git a/src/components/ActionButton/StatefulActionButton.ts b/src/components/ActionButton/StatefulActionButton.ts
index c2fda486..27cd141a 100644
--- a/src/components/ActionButton/StatefulActionButton.ts
+++ b/src/components/ActionButton/StatefulActionButton.ts
@@ -1,10 +1,12 @@
import { IResultsComponentBindings } from 'coveo-search-ui';
-import { ActionButton, ActionButtonOptions } from './ActionButton';
+import { ActionButton, ActionButtonOptions, IActionButtonOptionsWithTitle, IActionButtonOptionsWithIcon } from './ActionButton';
/**
* Represent a state that can be used by a StatefulActionButton.
*/
export type StatefulActionButtonState = ActionButtonOptions & IStateOptions;
+export interface IStatefulActionButtonOptionsWithTitle extends IActionButtonOptionsWithTitle, IStateOptions {}
+export interface IStatefulActionButtonOptionsWithIcon extends IActionButtonOptionsWithIcon, IStateOptions {}
export interface IStateOptions {
/**
diff --git a/src/components/ActionButton/ToggleActionButton.ts b/src/components/ActionButton/ToggleActionButton.ts
index 90c3e297..64176506 100644
--- a/src/components/ActionButton/ToggleActionButton.ts
+++ b/src/components/ActionButton/ToggleActionButton.ts
@@ -1,57 +1,12 @@
import { ComponentOptions, IResultsComponentBindings, Component, Initialization } from 'coveo-search-ui';
+import { ToggleActivatedState, ToggleDeactivatedState, IToggleableButton, IToggleableButtonOptions } from './ToggleableButton';
import { StatefulActionButtonState, StatefulActionButton } from './StatefulActionButton';
-export interface IToggleActionButtonOptions {
- activateIcon: string;
- activateTooltip: string;
- deactivateIcon: string;
- deactivateTooltip: string;
- click?: () => void;
- activate?: () => void;
- deactivate?: () => void;
-}
-
-export class ToggleActionButton extends Component {
- /**
- * Create the deactivated state for a given ToggleActionButton
- * @param button {ToggleActionButton}
- */
- static generateDeactivatedStateInstance(button: ToggleActionButton): StatefulActionButtonState {
- return {
- name: 'DeactivatedState',
- icon: button.options.activateIcon,
- tooltip: button.options.activateTooltip,
- click: () => button.onClick(),
- };
- }
-
- /**
- * Create the activated state for a given ToggleActionButton
- * @param button {ToggleActionButton}
- */
- static generateActivatedStateInstance(button: ToggleActionButton): StatefulActionButtonState {
- return {
- onStateEntry: function () {
- this.element.classList.add(ToggleActionButton.ACTIVATED_CLASS_NAME);
- this.element.setAttribute('aria-pressed', 'true');
- button.options.activate?.apply(button);
- },
- onStateExit: function () {
- this.element.classList.remove(ToggleActionButton.ACTIVATED_CLASS_NAME);
- this.element.setAttribute('aria-pressed', 'false');
- button.options.deactivate?.apply(button);
- },
- name: 'ActivatedState',
- click: () => button.onClick(),
- icon: button.options.deactivateIcon,
- tooltip: button.options.deactivateTooltip,
- };
- }
-
+export class ToggleActionButton extends Component implements IToggleableButton {
static ID = 'ToggleActionButton';
static ACTIVATED_CLASS_NAME = 'coveo-toggleactionbutton-activated';
- static options: IToggleActionButtonOptions = {
+ static options: IToggleableButtonOptions = {
/**
* Specifies the button SVG icon displayed to activate the button.
* Note: The SVG markup has to be HTML encoded when set using the HTML attributes.
@@ -145,7 +100,7 @@ export class ToggleActionButton extends Component {
private activatedState: StatefulActionButtonState;
private deactivatedState: StatefulActionButtonState;
- constructor(public element: HTMLElement, public options: IToggleActionButtonOptions, public bindings?: IResultsComponentBindings) {
+ constructor(public element: HTMLElement, public options: IToggleableButtonOptions, public bindings?: IResultsComponentBindings) {
super(element, ToggleActionButton.ID, bindings);
this.options = ComponentOptions.initComponentOptions(element, ToggleActionButton, options);
@@ -169,7 +124,7 @@ export class ToggleActionButton extends Component {
}
}
- protected onClick(): void {
+ public onClick(): void {
this.setActivated(!this.isActivated());
if (this.options.click) {
@@ -178,8 +133,8 @@ export class ToggleActionButton extends Component {
}
private createInnerButton(bindings?: IResultsComponentBindings): void {
- this.activatedState = ToggleActionButton.generateActivatedStateInstance(this);
- this.deactivatedState = ToggleActionButton.generateDeactivatedStateInstance(this);
+ this.deactivatedState = new ToggleDeactivatedState(this);
+ this.activatedState = new ToggleActivatedState(this);
this.innerStatefulActionButton = new StatefulActionButton(
this.element,
diff --git a/src/components/ActionButton/ToggleableButton.ts b/src/components/ActionButton/ToggleableButton.ts
new file mode 100644
index 00000000..cb52c5d7
--- /dev/null
+++ b/src/components/ActionButton/ToggleableButton.ts
@@ -0,0 +1,53 @@
+import { StatefulActionButton, IStatefulActionButtonOptionsWithIcon } from './StatefulActionButton';
+
+export interface IToggleableButtonOptions {
+ activateIcon: string;
+ activateTooltip: string;
+ deactivateIcon: string;
+ deactivateTooltip: string;
+ click?: () => void;
+ activate?: () => void;
+ deactivate?: () => void;
+}
+
+export interface IToggleableButton {
+ options: IToggleableButtonOptions;
+ onClick: () => void;
+}
+
+export class ToggleDeactivatedState implements IStatefulActionButtonOptionsWithIcon {
+ public readonly name = 'ToggleDeactivatedState';
+ public readonly icon: string;
+ public readonly tooltip: string;
+ public readonly click: { (): void; (): void; (): void };
+ constructor(toggleableButton: IToggleableButton) {
+ this.icon = toggleableButton.options.activateIcon;
+ this.tooltip = toggleableButton.options.activateTooltip;
+ this.click = () => toggleableButton.onClick();
+ }
+}
+
+export class ToggleActivatedState implements IStatefulActionButtonOptionsWithIcon {
+ static ACTIVATED_CLASS_NAME = 'coveo-toggleactionbutton-activated';
+ public readonly name = 'ToggleActivatedState';
+ public readonly onStateEntry: (this: StatefulActionButton) => void;
+ public readonly onStateExit: (this: StatefulActionButton) => void;
+ public readonly click: () => void;
+ public readonly icon: string;
+ public readonly tooltip: string;
+ constructor(toggleableButton: IToggleableButton) {
+ this.onStateEntry = function () {
+ this.element.classList.add(ToggleActivatedState.ACTIVATED_CLASS_NAME);
+ this.element.setAttribute('aria-pressed', 'true');
+ toggleableButton.options.activate?.apply(toggleableButton);
+ };
+ this.onStateExit = function () {
+ this.element.classList.remove(ToggleActivatedState.ACTIVATED_CLASS_NAME);
+ this.element.setAttribute('aria-pressed', 'false');
+ toggleableButton.options.deactivate?.apply(toggleableButton);
+ };
+ this.click = () => toggleableButton.onClick();
+ this.icon = toggleableButton.options.deactivateIcon;
+ this.tooltip = toggleableButton.options.deactivateTooltip;
+ }
+}
diff --git a/src/sass/Index.scss b/src/sass/Index.scss
index 999bc5d5..43b27f0b 100644
--- a/src/sass/Index.scss
+++ b/src/sass/Index.scss
@@ -1,5 +1,6 @@
@import '../components/ActionButton/ActionButton.scss';
@import '../components/ActionButton/ToggleActionButton.scss';
+@import '../components/ActionButton/DisableableActionButton.scss';
@import '../components/AttachResult/AttachResult.scss';
@import '../components/UserActions/UserActions.scss';
@import '../components/ViewedByCustomer/ViewedByCustomer.scss';
diff --git a/tests/components/ActionButton/DisableableButton.spec.ts b/tests/components/ActionButton/DisableableButton.spec.ts
new file mode 100644
index 00000000..3e13fe89
--- /dev/null
+++ b/tests/components/ActionButton/DisableableButton.spec.ts
@@ -0,0 +1,50 @@
+import { DisabledState } from '../../../src/components/ActionButton/DisableableButton';
+
+describe('DisabledState', () => {
+ let testElement: HTMLElement;
+ let testSubject: DisabledState;
+ let fakeStatefulActionButton: { element: HTMLElement };
+
+ beforeEach(() => {
+ testElement = document.createElement('div');
+ fakeStatefulActionButton = { element: testElement };
+ testSubject = new DisabledState({ options: { disabledIcon: 'someSvgIcon', disabledTooltip: 'someTooltip' } });
+ });
+
+ describe('constructor', () => {
+ it('should use the icon and tooltip from the option of the disabledButton', () => {
+ expect(testSubject.icon).toBe('someSvgIcon');
+ expect(testSubject.tooltip).toBe('someTooltip');
+ });
+ });
+
+ describe('onStateEntry', () => {
+ beforeEach(() => {
+ testSubject.onStateEntry.apply(fakeStatefulActionButton);
+ });
+
+ it('should add coveo-actionbutton-disabled to the classlist on this.element of the caller', () => {
+ expect(testElement.classList.value).toBe('coveo-actionbutton-disabled');
+ });
+
+ it('should add the attribute disabled to this.element of the caller', () => {
+ expect(testElement.hasAttribute('disabled')).toBeTrue();
+ });
+ });
+
+ describe('onStateExit', () => {
+ beforeEach(() => {
+ testElement.classList.value = 'coveo-actionbutton-disabled';
+ testElement.setAttribute('disabled', '');
+ testSubject.onStateExit.apply(fakeStatefulActionButton);
+ });
+
+ it('should remove coveo-actionbutton-disabled to the classlist on this.element of the caller', () => {
+ expect(testElement.classList.value).toBe('');
+ });
+
+ it('should remove the attribute disabled to this.element of the caller', () => {
+ expect(testElement.hasAttribute('disabled')).toBeFalse();
+ });
+ });
+});
diff --git a/tests/components/ActionButton/DisableableToggleActionButton.spec.ts b/tests/components/ActionButton/DisableableToggleActionButton.spec.ts
new file mode 100644
index 00000000..ff3a9ed1
--- /dev/null
+++ b/tests/components/ActionButton/DisableableToggleActionButton.spec.ts
@@ -0,0 +1,311 @@
+import { SinonSandbox, SinonSpy, createSandbox } from 'sinon';
+import {
+ IDisableableToggleActionButtonOptions,
+ DisableableToggleActionButton,
+} from '../../../src/components/ActionButton/DisableableToggleActionButton';
+import * as icons from '../../../src/utils/icons';
+
+import { Mock } from 'coveo-search-ui-tests';
+import { StatefulActionButton } from '../../../src/components/ActionButton/StatefulActionButton';
+import { ToggleDeactivatedState, ToggleActivatedState } from '../../../src/components/ActionButton/ToggleableButton';
+import { DisabledState } from '../../../src/components/ActionButton/DisableableButton';
+
+describe('DisableableToggleActionButton', () => {
+ let sandbox: SinonSandbox;
+ let options: IDisableableToggleActionButtonOptions;
+ let testSubject: DisableableToggleActionButton;
+
+ let clickSpy: SinonSpy;
+ let activateSpy: SinonSpy;
+ let deactivateSpy: SinonSpy;
+ let switchToSpy: SinonSpy;
+
+ beforeAll(() => {
+ sandbox = createSandbox();
+
+ clickSpy = sandbox.spy();
+ activateSpy = sandbox.spy();
+ deactivateSpy = sandbox.spy();
+ switchToSpy = sandbox.spy(
StatefulActionButton.prototype, 'switchTo');
+ });
+
+ beforeEach(() => {
+ options = {
+ activateIcon: icons.copy,
+ activateTooltip: 'Activate feature',
+ deactivateIcon: icons.duplicate,
+ deactivateTooltip: 'Deactivate feature',
+ click: clickSpy,
+ activate: activateSpy,
+ deactivate: deactivateSpy,
+ disabledIcon: icons.dot,
+ disabledTooltip: 'Feature disabled',
+ };
+
+ testSubject = createToggleButton(options);
+ });
+
+ afterEach(() => {
+ sandbox.reset();
+ });
+
+ function createToggleButton(options: IDisableableToggleActionButtonOptions) {
+ const element = document.createElement('button');
+ const componentSetup = Mock.advancedComponentSetup(
+ DisableableToggleActionButton,
+ new Mock.AdvancedComponentSetupOptions(element, options)
+ );
+ return componentSetup.cmp;
+ }
+
+ describe('when disabled', () => {
+ beforeEach(() => {
+ testSubject.disable();
+ switchToSpy.resetHistory();
+ });
+
+ it('clicking it should do nothing', () => {
+ Coveo.$$(testSubject.element).trigger('click');
+ expect(clickSpy.called).toBeFalse();
+ });
+
+ it('isActivated should return false', () => {
+ expect(testSubject.isActivated()).toBeFalse();
+ });
+
+ it('isDisabled should return true', () => {
+ expect(testSubject.isDisabled()).toBeTrue();
+ });
+
+ describe('setEnabled', () => {
+ let spyEnable: SinonSpy;
+ let spyDisable: SinonSpy;
+
+ beforeEach(() => {
+ spyEnable = sandbox.spy(testSubject, 'enable');
+ spyDisable = sandbox.spy(testSubject, 'disable');
+ });
+
+ it('should do call disabled if called with false', () => {
+ testSubject.setEnabled(false);
+
+ expect(spyEnable.called).toBeFalse();
+ expect(spyDisable.calledOnce).toBeTrue();
+ });
+
+ it('should call enable if called with true', () => {
+ testSubject.setEnabled(true);
+
+ expect(spyEnable.calledOnce).toBeTrue();
+ expect(spyDisable.called).toBeFalse();
+ });
+ });
+
+ describe('enable', () => {
+ beforeEach(() => {
+ testSubject.enable();
+ });
+
+ it('should call switchTo with the deactivatedState', () => {
+ expect(switchToSpy.calledOnce).toBeTrue();
+ expect(switchToSpy.firstCall.args[0] instanceof ToggleDeactivatedState).toBeTrue();
+ });
+ });
+
+ describe('disable', () => {
+ beforeEach(() => {
+ testSubject.disable();
+ });
+
+ it('should not call switchTo at all', () => {
+ expect(switchToSpy.called).toBeFalse();
+ });
+ });
+
+ describe('setActivated', () => {
+ it('should switch to deactivated if called with false', () => {
+ testSubject.setActivated(false);
+
+ expect(switchToSpy.calledOnce).toBeTrue();
+ expect(switchToSpy.firstCall.args[0] instanceof ToggleDeactivatedState).toBeTrue();
+ });
+
+ it('should do nothing if called with true', () => {
+ testSubject.setActivated(true);
+
+ expect(switchToSpy.called).toBeFalse();
+ });
+ });
+ });
+
+ describe('when Deactivated', () => {
+ it('isActivated should return false', () => {
+ expect(testSubject.isActivated()).toBeFalse();
+ });
+ it('isDisabled should return false', () => {
+ expect(testSubject.isDisabled()).toBeFalse();
+ });
+
+ describe('when clicked on', () => {
+ let setActivatedSpy: SinonSpy;
+ beforeEach(() => {
+ setActivatedSpy = sandbox.spy(testSubject, 'setActivated');
+ Coveo.$$(testSubject.element).trigger('click');
+ });
+
+ it('should call the click handler and setActivated with true', () => {
+ expect(clickSpy.called).toBeTrue();
+ expect(setActivatedSpy.calledOnceWithExactly(true)).toBeTrue();
+ });
+ });
+
+ describe('setEnabled', () => {
+ let spyEnable: SinonSpy;
+ let spyDisable: SinonSpy;
+
+ beforeEach(() => {
+ spyEnable = sandbox.spy(testSubject, 'enable');
+ spyDisable = sandbox.spy(testSubject, 'disable');
+ });
+
+ it('should do call disabled if called with false', () => {
+ testSubject.setEnabled(false);
+
+ expect(spyEnable.called).toBeFalse();
+ expect(spyDisable.calledOnce).toBeTrue();
+ });
+
+ it('should call enable if called with true', () => {
+ testSubject.setEnabled(true);
+
+ expect(spyEnable.calledOnce).toBeTrue();
+ expect(spyDisable.called).toBeFalse();
+ });
+ });
+
+ describe('enable', () => {
+ beforeEach(() => {
+ testSubject.enable();
+ });
+
+ it('should not call switchTo at all', () => {
+ expect(switchToSpy.called).toBeFalse();
+ });
+ });
+
+ describe('disable', () => {
+ beforeEach(() => {
+ testSubject.disable();
+ });
+
+ it('should call switchTo with the disabledState', () => {
+ expect(switchToSpy.calledOnce).toBeTrue();
+ expect(switchToSpy.firstCall.args[0] instanceof DisabledState).toBeTrue();
+ });
+ });
+
+ describe('setActivated', () => {
+ it('should do nothing if called with false', () => {
+ testSubject.setActivated(false);
+
+ expect(switchToSpy.called).toBeFalse();
+ });
+
+ it('should switch to activated if called with true', () => {
+ testSubject.setActivated(true);
+
+ expect(switchToSpy.calledOnce).toBeTrue();
+ expect(switchToSpy.firstCall.args[0] instanceof ToggleActivatedState).toBeTrue();
+ });
+ });
+ });
+
+ describe('when activated', () => {
+ beforeEach(() => {
+ testSubject.setActivated(true);
+ switchToSpy.resetHistory();
+ });
+
+ it('isActivated should return false', () => {
+ expect(testSubject.isActivated()).toBeTrue();
+ });
+
+ it('isDisabled should return false', () => {
+ expect(testSubject.isDisabled()).toBeFalse();
+ });
+
+ describe('when clicked on', () => {
+ let setActivatedSpy: SinonSpy;
+ beforeEach(() => {
+ setActivatedSpy = sandbox.spy(testSubject, 'setActivated');
+ Coveo.$$(testSubject.element).trigger('click');
+ });
+
+ it('should call the click handler and setActivated with false', () => {
+ expect(clickSpy.called).toBeTrue();
+ expect(setActivatedSpy.calledOnceWithExactly(false)).toBeTrue();
+ });
+ });
+
+ describe('setEnabled', () => {
+ let spyEnable: SinonSpy;
+ let spyDisable: SinonSpy;
+
+ beforeEach(() => {
+ spyEnable = sandbox.spy(testSubject, 'enable');
+ spyDisable = sandbox.spy(testSubject, 'disable');
+ });
+
+ it('should do call disabled if called with false', () => {
+ testSubject.setEnabled(false);
+
+ expect(spyEnable.called).toBeFalse();
+ expect(spyDisable.calledOnce).toBeTrue();
+ });
+
+ it('should call enable if called with true', () => {
+ testSubject.setEnabled(true);
+
+ expect(spyEnable.calledOnce).toBeTrue();
+ expect(spyDisable.called).toBeFalse();
+ });
+ });
+
+ describe('enable', () => {
+ beforeEach(() => {
+ testSubject.enable();
+ });
+
+ it('should not call switchTo at all', () => {
+ expect(switchToSpy.called).toBeFalse();
+ });
+ });
+
+ describe('disable', () => {
+ beforeEach(() => {
+ testSubject.disable();
+ });
+
+ it('should call switchTo with the DeactivatedState and then with the disabledState', () => {
+ expect(switchToSpy.calledTwice).toBeTrue();
+ expect(switchToSpy.firstCall.args[0] instanceof ToggleDeactivatedState).toBeTrue();
+ expect(switchToSpy.secondCall.args[0] instanceof DisabledState).toBeTrue();
+ });
+ });
+
+ describe('setActivated', () => {
+ it('should do nothing if called with true', () => {
+ testSubject.setActivated(true);
+
+ expect(switchToSpy.called).toBeFalse();
+ });
+
+ it('should switch to Deactivated if called with false', () => {
+ testSubject.setActivated(false);
+
+ expect(switchToSpy.calledOnce).toBeTrue();
+ expect(switchToSpy.firstCall.args[0] instanceof ToggleDeactivatedState).toBeTrue();
+ });
+ });
+ });
+});
diff --git a/tests/components/ActionButton/ToggleActionButton.spec.ts b/tests/components/ActionButton/ToggleActionButton.spec.ts
index 4c6ddef6..ad01b74b 100644
--- a/tests/components/ActionButton/ToggleActionButton.spec.ts
+++ b/tests/components/ActionButton/ToggleActionButton.spec.ts
@@ -1,13 +1,14 @@
import { SinonSandbox, createSandbox, SinonSpy } from 'sinon';
import { Mock } from 'coveo-search-ui-tests';
-import { IToggleActionButtonOptions, ToggleActionButton } from '../../../src/components/ActionButton/ToggleActionButton';
+import { ToggleActionButton } from '../../../src/components/ActionButton/ToggleActionButton';
import * as icons from '../../../src/utils/icons';
import { ActionButton } from '../../../src/components/ActionButton/ActionButton';
import { IComponentOptions } from 'coveo-search-ui';
+import { IToggleableButtonOptions } from '../../../src/components/ActionButton/ToggleableButton';
describe('ToggleActionButton', () => {
let sandbox: SinonSandbox;
- let options: IToggleActionButtonOptions;
+ let options: IToggleableButtonOptions;
let testSubject: ToggleActionButton;
let clickSpy: SinonSpy;
@@ -44,7 +45,7 @@ describe('ToggleActionButton', () => {
sandbox.reset();
});
- function createToggleButton(options: IToggleActionButtonOptions) {
+ function createToggleButton(options: IToggleableButtonOptions) {
const element = document.createElement('button');
const componentSetup = Mock.advancedComponentSetup(
ToggleActionButton,
diff --git a/tests/components/ActionButton/ToggleableButton.spec.ts b/tests/components/ActionButton/ToggleableButton.spec.ts
new file mode 100644
index 00000000..d1f8a5c8
--- /dev/null
+++ b/tests/components/ActionButton/ToggleableButton.spec.ts
@@ -0,0 +1,143 @@
+import {
+ ToggleDeactivatedState,
+ IToggleableButtonOptions,
+ ToggleActivatedState,
+ IToggleableButton,
+} from '../../../src/components/ActionButton/ToggleableButton';
+import { createSandbox, SinonSandbox, SinonSpy } from 'sinon';
+
+describe('ToggleStates', () => {
+ let sandbox: SinonSandbox;
+
+ let testElement: HTMLElement;
+ let fakeStatefulActionButton: { element: HTMLElement };
+ let onClickSpy: SinonSpy;
+ const toggleStateOptions: IToggleableButtonOptions = {
+ activateIcon: 'someActivateIcon',
+ activateTooltip: 'someActivateTooltip',
+ deactivateIcon: 'someDeactivateIcon',
+ deactivateTooltip: 'someDeactivatedTooltip',
+ };
+
+ beforeAll(() => {
+ sandbox = createSandbox();
+ });
+
+ beforeEach(() => {
+ testElement = document.createElement('div');
+ fakeStatefulActionButton = { element: testElement };
+ onClickSpy = sandbox.spy();
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ describe('ToggleDeactivatedState', () => {
+ let testSubject: ToggleDeactivatedState;
+ beforeEach(() => {
+ testSubject = new ToggleDeactivatedState({
+ options: toggleStateOptions,
+ onClick: onClickSpy,
+ });
+ });
+
+ describe('constructor', () => {
+ it('should use the deactivateIcon, deactivateTooltip from the option of the ToggleableButton', () => {
+ expect(testSubject.icon).toBe('someActivateIcon');
+ expect(testSubject.tooltip).toBe('someActivateTooltip');
+ });
+
+ it('should use the onclick of the ToggleableButton', () => {
+ testSubject.click();
+ expect(onClickSpy.calledOnce).toBeTrue();
+ });
+ });
+ });
+
+ describe('ToggleActivatedState', () => {
+ let testSubject: ToggleActivatedState;
+ beforeEach(() => {
+ testSubject = new ToggleActivatedState({
+ options: toggleStateOptions,
+ onClick: onClickSpy,
+ });
+ });
+
+ describe('constructor', () => {
+ it('should use the deactivateIcon, deactivateTooltip from the option of the ToggleableButton', () => {
+ expect(testSubject.icon).toBe('someDeactivateIcon');
+ expect(testSubject.tooltip).toBe('someDeactivatedTooltip');
+ });
+
+ it('should use the onclick of the ToggleableButton', () => {
+ testSubject.click();
+ expect(onClickSpy.calledOnce).toBeTrue();
+ });
+ });
+
+ describe('onStateEntry', () => {
+ beforeEach(() => {
+ testSubject.onStateEntry.apply(fakeStatefulActionButton);
+ });
+ it('should add coveo-toggleactionbutton-activated to the classlist on this.element of the caller', () => {
+ expect(testElement.classList.value).toBe('coveo-toggleactionbutton-activated');
+ });
+ it('should set the attribute aria-pressed to true on this.element of the caller', () => {
+ expect(testElement.getAttribute('aria-pressed')).toBe('true');
+ });
+ describe('if the toggleableButton options include activate', () => {
+ let activateSpy: SinonSpy;
+ let toggleableButton: IToggleableButton;
+
+ beforeEach(() => {
+ activateSpy = sandbox.spy();
+ toggleableButton = {
+ options: { ...toggleStateOptions, activate: activateSpy },
+ onClick: onClickSpy,
+ };
+ testSubject = new ToggleActivatedState(toggleableButton);
+ testSubject.onStateEntry.apply(fakeStatefulActionButton);
+ });
+
+ it('should call call it with the toggleableButton for context', () => {
+ expect(activateSpy.calledOnce).toBeTrue();
+ expect(activateSpy.firstCall.thisValue).toBe(toggleableButton);
+ });
+ });
+ });
+
+ describe('onStateExit', () => {
+ beforeEach(() => {
+ testElement.classList.value = 'coveo-toggleactionbutton-activated';
+ testElement.setAttribute('aria-pressed', 'true');
+ testSubject.onStateExit.apply(fakeStatefulActionButton);
+ });
+
+ it('should remove coveo-actionbutton-disabled to the classlist on this.element of the caller', () => {
+ expect(testElement.classList.value).toBe('');
+ });
+ it('should set the attribute aria-pressed to false on to this.element of the caller', () => {
+ expect(testElement.getAttribute('aria-pressed')).toBe('false');
+ });
+ describe('if the toggleableButton options include deactivate', () => {
+ let deactivateSpy: SinonSpy;
+ let toggleableButton: IToggleableButton;
+ beforeEach(() => {
+ deactivateSpy = sandbox.spy();
+ toggleableButton = {
+ options: { ...toggleStateOptions, deactivate: deactivateSpy },
+ onClick: onClickSpy,
+ };
+ testSubject = new ToggleActivatedState(toggleableButton);
+ testSubject.onStateExit.apply(fakeStatefulActionButton);
+ });
+
+ it('should call call it with the toggleableButton for context', () => {
+ expect(deactivateSpy.calledOnce).toBeTrue();
+ expect(deactivateSpy.firstCall.thisValue).toBe(toggleableButton);
+ });
+ });
+ });
+ });
+});