-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { Component, ComponentOptions, IQueryResult, IResultsComponentBindings, $$ } from 'coveo-search-ui'; | ||
|
||
/** | ||
* The possible options for _ResultAction_. | ||
*/ | ||
export interface IResultActionOptions { | ||
/** | ||
* The icon that the ResultAction will display. | ||
* If text is provided, the button will contain that text. | ||
* If the HTML of an SVG image is provided, that image will be displayed in the button. | ||
*/ | ||
icon?: string; | ||
|
||
/** | ||
* The tooltip that displays on hovering the ResultAction. | ||
*/ | ||
tooltip?: string; | ||
} | ||
|
||
/** | ||
* The base class for all ResultAction components. | ||
* Its main responsibility is handling the visual elements of the Result Action. | ||
*/ | ||
export abstract class ResultAction extends Component { | ||
static ID = 'ResultAction'; | ||
|
||
private isInitialized = false; | ||
|
||
/** | ||
* The possible options for _ResultAction_. | ||
* @componentOptions | ||
*/ | ||
static options: IResultActionOptions = { | ||
/** | ||
* See {@link IResultActionOptions.icon} | ||
* Optional. You may instead provide the icon by appending it as a child element. | ||
*/ | ||
icon: ComponentOptions.buildStringOption(), | ||
|
||
/** | ||
* See {@link IResultActionOptions.tooltip} | ||
* Optional. If no tooltip is provided, the tooltip popup will not appear. | ||
*/ | ||
tooltip: ComponentOptions.buildStringOption() | ||
}; | ||
|
||
/** | ||
* Construct a ResultAction component. | ||
* @param element The HTML element bound to this component. | ||
* @param options The options that can be provided to this component. | ||
* @param bindings The bindings, or environment within which this component exists. | ||
* @param queryResult The result of the query in which this resultAction exists. | ||
*/ | ||
constructor( | ||
public element: HTMLElement, | ||
public options: IResultActionOptions, | ||
public bindings?: IResultsComponentBindings, | ||
public queryResult?: IQueryResult | ||
) { | ||
super(element, ResultAction.ID, bindings); | ||
|
||
this.options = ComponentOptions.initComponentOptions(element, ResultAction, options); | ||
this.queryResult = this.queryResult || this.resolveResult(); | ||
|
||
// Hide until initialized. | ||
$$(this.element).addClass('coveo-hidden'); | ||
|
||
this.bind.on(this.element, 'click', () => this.doAction()); | ||
} | ||
|
||
/** | ||
* The action that will be performed when the ResultAction is clicked. | ||
* @abstract | ||
*/ | ||
protected abstract doAction(): void; | ||
|
||
/** | ||
* Initializes the component if it is not already initialized. | ||
*/ | ||
protected init() { | ||
if (!this.isInitialized) { | ||
this.show(); | ||
this.isInitialized = true; | ||
} else { | ||
this.logger.debug('Attempted to initialize ResultAction that was already initialized.'); | ||
} | ||
} | ||
|
||
/** | ||
* Deactivate the component if it is initialized. | ||
* @param e The reason for the deactivation. | ||
*/ | ||
protected deactivate(e: string) { | ||
$$(this.element).remove(); | ||
this.logger.warn(e); | ||
this.isInitialized = false; | ||
} | ||
|
||
/** | ||
* Make the result action button visible. | ||
*/ | ||
private show() { | ||
$$(this.element).removeClass('coveo-hidden'); | ||
|
||
if (this.options.icon) { | ||
const icon = document.createElement('span'); | ||
icon.innerHTML = this.options.icon; | ||
icon.className = 'coveo-icon'; | ||
this.element.appendChild(icon); | ||
} | ||
|
||
if (this.options.tooltip) { | ||
const tooltip = document.createElement('span'); | ||
tooltip.innerText = this.options.tooltip; | ||
tooltip.className = 'coveo-caption-for-icon'; | ||
this.element.appendChild(tooltip); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { IQueryResult, $$ } from 'coveo-search-ui'; | ||
import { createSandbox, SinonStub, SinonSandbox } from 'sinon'; | ||
import { Mock, Fake } from 'coveo-search-ui-tests'; | ||
import { ResultAction } from '../../../src/components/ResultAction/ResultAction'; | ||
|
||
describe('ResultAction', () => { | ||
let sandbox: SinonSandbox; | ||
|
||
let componentSetup: Mock.IBasicComponentSetup<ResultActionMockImpl>; | ||
let result: IQueryResult; | ||
let element: HTMLElement; | ||
let testComponent: ResultActionMockImpl; | ||
let testOptions: Mock.AdvancedComponentSetupOptions; | ||
|
||
// Since ResultAction is abstract, create a mock implementation. | ||
class ResultActionMockImpl extends ResultAction { | ||
// Make functions public so they can be tested. | ||
public doAction: SinonStub; | ||
public init: () => void; | ||
public deactivate: (e?: string) => void; | ||
} | ||
|
||
beforeAll(() => { | ||
sandbox = createSandbox(); | ||
}); | ||
|
||
beforeEach(() => { | ||
result = Fake.createFakeResult(); | ||
element = document.createElement('div'); | ||
document.body.append(element); | ||
testOptions = new Mock.AdvancedComponentSetupOptions(element, { icon: 'someIcon', tooltip: 'someTooltip' }); | ||
|
||
componentSetup = Mock.advancedResultComponentSetup(ResultActionMockImpl, result, testOptions); | ||
testComponent = componentSetup.cmp; | ||
testComponent.doAction = sandbox.stub(); | ||
}); | ||
|
||
afterEach(() => { | ||
sandbox.reset(); | ||
sandbox.restore(); | ||
$$(document.body) | ||
.children() | ||
.forEach(el => el.remove()); | ||
}); | ||
|
||
describe('after construction', () => { | ||
it('should call resolveResult if no results are given', () => { | ||
const resolveResultStub = sandbox.stub(ResultActionMockImpl.prototype, 'resolveResult'); | ||
|
||
componentSetup = Mock.advancedResultComponentSetup(ResultActionMockImpl, null, testOptions); | ||
testComponent = componentSetup.cmp; | ||
testComponent.doAction = sandbox.stub(); | ||
|
||
expect(resolveResultStub.called).toBeTrue(); | ||
}); | ||
|
||
it('should be hidden by default', () => { | ||
expect(element.classList.contains('coveo-hidden')).toBeTrue(); | ||
expect(element.hasChildNodes()).toBeFalse(); | ||
}); | ||
}); | ||
|
||
describe('after initialization', () => { | ||
beforeEach(() => { | ||
testComponent.init(); | ||
}); | ||
|
||
it('should log a debug message if initialized twice', () => { | ||
const debugStub = sandbox.stub(testComponent.logger, 'debug'); | ||
expect(debugStub.called).toBeFalse(); | ||
testComponent.init(); | ||
expect(debugStub.called).toBeTrue(); | ||
}); | ||
|
||
it('should be visible', () => { | ||
expect(element.classList.contains('coveo-hidden')).toBeFalse(); | ||
expect(element.hasChildNodes()).toBeTrue(); | ||
}); | ||
|
||
it('should become invisible after deactivation', () => { | ||
testComponent.deactivate(); | ||
expect(element.parentElement).toBeNull(); | ||
}); | ||
|
||
it('should be able to reinitialize after being deactivated', () => { | ||
testComponent.deactivate(); | ||
testComponent.init(); | ||
expect(element.classList.contains('coveo-hidden')).toBeFalse(); | ||
expect(element.hasChildNodes()).toBeTrue(); | ||
}); | ||
|
||
it('should invoke the action when clicked', () => { | ||
element.click(); | ||
expect(testComponent.doAction.called).toBeTrue(); | ||
}); | ||
}); | ||
|
||
describe('options', () => { | ||
it('should not display an icon after initialization if *icon* is not set', () => { | ||
testComponent.options.icon = null; | ||
|
||
testComponent.init(); | ||
|
||
expect(element.classList.contains('coveo-hidden')).toBeFalse(); | ||
expect(element.querySelector('.coveo-icon')).toBeNull(); | ||
}); | ||
|
||
it('should not display a tooltip after initialization if *tooltip* is not set', () => { | ||
testComponent.options.tooltip = null; | ||
|
||
testComponent.init(); | ||
|
||
expect(element.classList.contains('coveo-hidden')).toBeFalse(); | ||
expect(element.querySelector('.coveo-tooltip')).toBeNull(); | ||
}); | ||
}); | ||
}); |