@coveo/quantic 3.38.2 → 3.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/force-app/main/default/labels/CustomLabels.labels-meta.xml +14 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/__tests__/quanticGeneratedAnswerFollowUpInput.test.js +180 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.css +17 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.html +29 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.js +79 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.js-meta.xml +5 -0
- package/force-app/main/default/lwc/quanticUtils/__tests__/accessibilityUtils.test.js +214 -0
- package/force-app/main/default/lwc/quanticUtils/__tests__/facetStoreUtils.test.js +86 -0
- package/force-app/main/default/lwc/quanticUtils/accessibilityUtils.js +225 -0
- package/force-app/main/default/lwc/quanticUtils/facetStoreUtils.js +65 -0
- package/force-app/main/default/lwc/quanticUtils/quanticUtils.js +2 -291
- package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +4 -4
- package/force-app/main/default/staticresources/coveoheadless/headless.js +5 -5
- package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +4 -4
- package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +14 -14
- package/package.json +4 -4
|
@@ -1624,4 +1624,18 @@
|
|
|
1624
1624
|
<protected>false</protected>
|
|
1625
1625
|
<shortDescription>No generated answer available.</shortDescription>
|
|
1626
1626
|
</labels>
|
|
1627
|
+
<labels>
|
|
1628
|
+
<fullName>quantic_AskFollowUp</fullName>
|
|
1629
|
+
<value>Ask follow-up</value>
|
|
1630
|
+
<language>en_US</language>
|
|
1631
|
+
<protected>false</protected>
|
|
1632
|
+
<shortDescription>Ask follow-up</shortDescription>
|
|
1633
|
+
</labels>
|
|
1634
|
+
<labels>
|
|
1635
|
+
<fullName>quantic_SubmitFollowUp</fullName>
|
|
1636
|
+
<value>Submit follow-up</value>
|
|
1637
|
+
<language>en_US</language>
|
|
1638
|
+
<protected>false</protected>
|
|
1639
|
+
<shortDescription>Submit follow-up</shortDescription>
|
|
1640
|
+
</labels>
|
|
1627
1641
|
</CustomLabels>
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import {buildCreateTestComponent, cleanup, flushPromises} from 'c/testUtils';
|
|
2
|
+
import QuanticGeneratedAnswerFollowUpInput from '../quanticGeneratedAnswerFollowUpInput';
|
|
3
|
+
|
|
4
|
+
jest.mock(
|
|
5
|
+
'@salesforce/label/c.quantic_SubmitFollowUp',
|
|
6
|
+
() => ({default: 'Submit follow-up'}),
|
|
7
|
+
{virtual: true}
|
|
8
|
+
);
|
|
9
|
+
jest.mock(
|
|
10
|
+
'@salesforce/label/c.quantic_AskFollowUp',
|
|
11
|
+
() => ({default: 'Ask follow-up'}),
|
|
12
|
+
{virtual: true}
|
|
13
|
+
);
|
|
14
|
+
jest.mock('c/quanticUtils', () => ({
|
|
15
|
+
keys: {ENTER: 'Enter'},
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const selectors = {
|
|
19
|
+
input: 'lightning-input',
|
|
20
|
+
submitButton: 'lightning-button-icon',
|
|
21
|
+
inputContainer: '.follow-up-input__input-container',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const createTestComponent = buildCreateTestComponent(
|
|
25
|
+
QuanticGeneratedAnswerFollowUpInput,
|
|
26
|
+
'c-quantic-generated-answer-follow-up-input'
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
describe('c-quantic-generated-answer-follow-up-input', () => {
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
cleanup();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should render the input and submit button', async () => {
|
|
35
|
+
const element = createTestComponent();
|
|
36
|
+
await flushPromises();
|
|
37
|
+
|
|
38
|
+
const input = element.shadowRoot.querySelector(selectors.input);
|
|
39
|
+
const submitButton = element.shadowRoot.querySelector(
|
|
40
|
+
selectors.submitButton
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
expect(input).not.toBeNull();
|
|
44
|
+
expect(submitButton).not.toBeNull();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('submitting a follow up', () => {
|
|
48
|
+
it('should dispatch #quantic__submitfollowup when pressing Enter', async () => {
|
|
49
|
+
const element = createTestComponent();
|
|
50
|
+
await flushPromises();
|
|
51
|
+
|
|
52
|
+
const handler = jest.fn();
|
|
53
|
+
element.addEventListener('quantic__submitfollowup', handler);
|
|
54
|
+
|
|
55
|
+
const input = element.shadowRoot.querySelector(selectors.input);
|
|
56
|
+
input.value = 'follow up question';
|
|
57
|
+
input.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
|
|
58
|
+
|
|
59
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
60
|
+
expect(handler.mock.calls[0][0].detail.value).toBe('follow up question');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should dispatch #quantic__submitfollowup when clicking the submit button', async () => {
|
|
64
|
+
const element = createTestComponent();
|
|
65
|
+
await flushPromises();
|
|
66
|
+
|
|
67
|
+
const handler = jest.fn();
|
|
68
|
+
element.addEventListener('quantic__submitfollowup', handler);
|
|
69
|
+
|
|
70
|
+
const input = element.shadowRoot.querySelector(selectors.input);
|
|
71
|
+
input.value = 'another question';
|
|
72
|
+
|
|
73
|
+
const submitButton = element.shadowRoot.querySelector(
|
|
74
|
+
selectors.submitButton
|
|
75
|
+
);
|
|
76
|
+
submitButton.click();
|
|
77
|
+
|
|
78
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
79
|
+
expect(handler.mock.calls[0][0].detail.value).toBe('another question');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should clear the input and blur it after a successful submit', async () => {
|
|
83
|
+
const element = createTestComponent();
|
|
84
|
+
await flushPromises();
|
|
85
|
+
|
|
86
|
+
const input = element.shadowRoot.querySelector(selectors.input);
|
|
87
|
+
input.value = 'a question';
|
|
88
|
+
input.blur = jest.fn();
|
|
89
|
+
|
|
90
|
+
input.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
|
|
91
|
+
|
|
92
|
+
expect(input.value).toBe('');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('when submission should be blocked', () => {
|
|
97
|
+
it('should not dispatch the event when submitButtonDisabled is true', async () => {
|
|
98
|
+
const element = createTestComponent({submitButtonDisabled: true});
|
|
99
|
+
await flushPromises();
|
|
100
|
+
|
|
101
|
+
const handler = jest.fn();
|
|
102
|
+
element.addEventListener('quantic__submitfollowup', handler);
|
|
103
|
+
|
|
104
|
+
const input = element.shadowRoot.querySelector(selectors.input);
|
|
105
|
+
input.value = 'a question';
|
|
106
|
+
input.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
|
|
107
|
+
|
|
108
|
+
expect(handler).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should not dispatch the event when input is whitespace only', async () => {
|
|
112
|
+
const element = createTestComponent();
|
|
113
|
+
await flushPromises();
|
|
114
|
+
|
|
115
|
+
const handler = jest.fn();
|
|
116
|
+
element.addEventListener('quantic__submitfollowup', handler);
|
|
117
|
+
|
|
118
|
+
const input = element.shadowRoot.querySelector(selectors.input);
|
|
119
|
+
input.value = ' ';
|
|
120
|
+
input.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
|
|
121
|
+
|
|
122
|
+
expect(handler).not.toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should not dispatch the event when input is empty', async () => {
|
|
126
|
+
const element = createTestComponent();
|
|
127
|
+
await flushPromises();
|
|
128
|
+
|
|
129
|
+
const handler = jest.fn();
|
|
130
|
+
element.addEventListener('quantic__submitfollowup', handler);
|
|
131
|
+
|
|
132
|
+
const input = element.shadowRoot.querySelector(selectors.input);
|
|
133
|
+
input.value = '';
|
|
134
|
+
|
|
135
|
+
const submitButton = element.shadowRoot.querySelector(
|
|
136
|
+
selectors.submitButton
|
|
137
|
+
);
|
|
138
|
+
submitButton.click();
|
|
139
|
+
|
|
140
|
+
expect(handler).not.toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('focus and blur CSS class toggling', () => {
|
|
145
|
+
it('should add the focused class when the input is focused', async () => {
|
|
146
|
+
const element = createTestComponent();
|
|
147
|
+
await flushPromises();
|
|
148
|
+
|
|
149
|
+
const input = element.shadowRoot.querySelector(selectors.input);
|
|
150
|
+
input.dispatchEvent(new CustomEvent('focus'));
|
|
151
|
+
await flushPromises();
|
|
152
|
+
|
|
153
|
+
const container = element.shadowRoot.querySelector(
|
|
154
|
+
selectors.inputContainer
|
|
155
|
+
);
|
|
156
|
+
expect(container.classList).toContain(
|
|
157
|
+
'follow-up-input__input-container--focused'
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should remove the focused class when the input is blurred', async () => {
|
|
162
|
+
const element = createTestComponent();
|
|
163
|
+
await flushPromises();
|
|
164
|
+
|
|
165
|
+
const input = element.shadowRoot.querySelector(selectors.input);
|
|
166
|
+
input.dispatchEvent(new CustomEvent('focus'));
|
|
167
|
+
await flushPromises();
|
|
168
|
+
|
|
169
|
+
input.dispatchEvent(new CustomEvent('blur'));
|
|
170
|
+
await flushPromises();
|
|
171
|
+
|
|
172
|
+
const container = element.shadowRoot.querySelector(
|
|
173
|
+
selectors.inputContainer
|
|
174
|
+
);
|
|
175
|
+
expect(container.classList).not.toContain(
|
|
176
|
+
'follow-up-input__input-container--focused'
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
--slds-c-input-shadow: none;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.follow-up-input__input {
|
|
6
|
+
border: none;
|
|
7
|
+
box-shadow: none;
|
|
8
|
+
outline: none;
|
|
9
|
+
width: 100%;
|
|
10
|
+
--slds-c-input-color-border: transparent;
|
|
11
|
+
--slds-c-input-shadow-focus: transparent;
|
|
12
|
+
--slds-c-input-spacing-border: 0px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.follow-up-input__input-container.follow-up-input__input-container--focused {
|
|
16
|
+
border-color: var(--lwc-brandAccessible, #0176d3);
|
|
17
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class={inputContainerClasses}>
|
|
3
|
+
<lightning-input
|
|
4
|
+
class="slds-col slds-grow follow-up-input__input"
|
|
5
|
+
type="text"
|
|
6
|
+
variant="label-hidden"
|
|
7
|
+
label={labels.askFollowUp}
|
|
8
|
+
placeholder={labels.askFollowUp}
|
|
9
|
+
name="ask-followup"
|
|
10
|
+
lwc:ref="askFollowUpInput"
|
|
11
|
+
onfocus={handleFocus}
|
|
12
|
+
onblur={handleBlur}
|
|
13
|
+
onkeydown={handleKeyDown}
|
|
14
|
+
></lightning-input>
|
|
15
|
+
<lightning-button-icon
|
|
16
|
+
disabled={submitButtonDisabled}
|
|
17
|
+
onclick={handleSubmitFollowUp}
|
|
18
|
+
icon-name="utility:arrowup"
|
|
19
|
+
variant="brand"
|
|
20
|
+
class="slds-col slds-grow-none slds-align_absolute-center"
|
|
21
|
+
name="submit-follow-up"
|
|
22
|
+
title={labels.submitFollowUp}
|
|
23
|
+
aria-label={labels.submitFollowUp}
|
|
24
|
+
alternative-text={labels.submitFollowUp}
|
|
25
|
+
>
|
|
26
|
+
<label>{labels.submitFollowUp}</label>
|
|
27
|
+
</lightning-button-icon>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import submitFollowUp from '@salesforce/label/c.quantic_SubmitFollowUp';
|
|
2
|
+
import askFollowUp from '@salesforce/label/c.quantic_AskFollowUp';
|
|
3
|
+
import {keys} from 'c/quanticUtils';
|
|
4
|
+
import {api, LightningElement} from 'lwc';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The `QuanticGeneratedAnswerFollowUpInput` component allows users to submit follow-up questions related to a generated answer.
|
|
8
|
+
* @fires CustomEvent#quantic__submitfollowup
|
|
9
|
+
* @category Internal
|
|
10
|
+
* @example
|
|
11
|
+
* <c-quantic-generated-answer-follow-up-input></c-quantic-generated-answer-follow-up-input>
|
|
12
|
+
*/
|
|
13
|
+
export default class QuanticGeneratedAnswerFollowUpInput extends LightningElement {
|
|
14
|
+
labels = {
|
|
15
|
+
submitFollowUp,
|
|
16
|
+
askFollowUp,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Whether the submit button is disabled.
|
|
21
|
+
* @type {boolean}
|
|
22
|
+
* @defaultValue false
|
|
23
|
+
*/
|
|
24
|
+
@api
|
|
25
|
+
submitButtonDisabled = false;
|
|
26
|
+
|
|
27
|
+
/** @type {boolean} */
|
|
28
|
+
_focused = false;
|
|
29
|
+
|
|
30
|
+
handleKeyDown(event) {
|
|
31
|
+
// Let the browser commit IME text before handling shortcuts like Enter during composition.
|
|
32
|
+
if (event.isComposing || event.keyCode === 229) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (event.key === keys.ENTER) {
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
this.handleSubmitFollowUp();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
handleSubmitFollowUp() {
|
|
43
|
+
if (
|
|
44
|
+
this.submitButtonDisabled ||
|
|
45
|
+
this.refs.askFollowUpInput.value.trim() === ''
|
|
46
|
+
) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.sendSubmitFollowUpEvent();
|
|
50
|
+
this.refs.askFollowUpInput.value = '';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
handleFocus() {
|
|
54
|
+
this._focused = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
handleBlur() {
|
|
58
|
+
this._focused = false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sends the "quantic__submitfollowup" event.
|
|
63
|
+
*/
|
|
64
|
+
sendSubmitFollowUpEvent() {
|
|
65
|
+
this.dispatchEvent(
|
|
66
|
+
new CustomEvent('quantic__submitfollowup', {
|
|
67
|
+
bubbles: true,
|
|
68
|
+
composed: true,
|
|
69
|
+
detail: {
|
|
70
|
+
value: this.refs.askFollowUpInput.value,
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get inputContainerClasses() {
|
|
77
|
+
return `follow-up-input__input-container slds-box slds-grid slds-box_xx-small ${this._focused ? 'follow-up-input__input-container--focused' : ''}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AriaLiveRegion,
|
|
3
|
+
isFocusable,
|
|
4
|
+
getFirstFocusableElement,
|
|
5
|
+
getLastFocusableElement,
|
|
6
|
+
isCustomElement,
|
|
7
|
+
isParentOf,
|
|
8
|
+
} from 'c/quanticUtils';
|
|
9
|
+
|
|
10
|
+
describe('accessibilityUtils', () => {
|
|
11
|
+
describe('AriaLiveRegion', () => {
|
|
12
|
+
let elem;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
elem = {dispatchEvent: jest.fn()};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should dispatch a register region event on creation', () => {
|
|
19
|
+
AriaLiveRegion('test-region', elem);
|
|
20
|
+
expect(elem.dispatchEvent).toHaveBeenCalledTimes(1);
|
|
21
|
+
const event = elem.dispatchEvent.mock.calls[0][0];
|
|
22
|
+
expect(event.type).toBe('quantic__registerregion');
|
|
23
|
+
expect(event.detail).toEqual({
|
|
24
|
+
regionName: 'test-region',
|
|
25
|
+
assertive: false,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should dispatch a register region event with assertive flag', () => {
|
|
30
|
+
AriaLiveRegion('test-region', elem, true);
|
|
31
|
+
const event = elem.dispatchEvent.mock.calls[0][0];
|
|
32
|
+
expect(event.detail.assertive).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should dispatch an aria live message event when dispatchMessage is called', () => {
|
|
36
|
+
const region = AriaLiveRegion('test-region', elem);
|
|
37
|
+
region.dispatchMessage('hello');
|
|
38
|
+
expect(elem.dispatchEvent).toHaveBeenCalledTimes(2);
|
|
39
|
+
const event = elem.dispatchEvent.mock.calls[1][0];
|
|
40
|
+
expect(event.type).toBe('quantic__arialivemessage');
|
|
41
|
+
expect(event.detail).toEqual({
|
|
42
|
+
regionName: 'test-region',
|
|
43
|
+
assertive: false,
|
|
44
|
+
message: 'hello',
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('isFocusable', () => {
|
|
50
|
+
function createElement(tag, attrs = {}) {
|
|
51
|
+
const el = document.createElement(tag);
|
|
52
|
+
Object.entries(attrs).forEach(([k, v]) => el.setAttribute(k, v));
|
|
53
|
+
return el;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
it('should return true for a button', () => {
|
|
57
|
+
expect(isFocusable(createElement('button'))).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should return false for a disabled button', () => {
|
|
61
|
+
expect(isFocusable(createElement('button', {disabled: ''}))).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should return true for an anchor with href', () => {
|
|
65
|
+
expect(isFocusable(createElement('a', {href: '#'}))).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return false for an anchor without href', () => {
|
|
69
|
+
expect(isFocusable(createElement('a'))).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should return true for an input', () => {
|
|
73
|
+
expect(isFocusable(createElement('input'))).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should return false for a disabled input', () => {
|
|
77
|
+
expect(isFocusable(createElement('input', {disabled: ''}))).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should return true for an element with tabindex >= 0', () => {
|
|
81
|
+
expect(isFocusable(createElement('div', {tabindex: '0'}))).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return false for an element with tabindex -1', () => {
|
|
85
|
+
expect(isFocusable(createElement('div', {tabindex: '-1'}))).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should return true for a contentEditable element', () => {
|
|
89
|
+
expect(isFocusable(createElement('div', {contentEditable: 'true'}))).toBe(
|
|
90
|
+
true
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should return false for a plain div', () => {
|
|
95
|
+
expect(isFocusable(createElement('div'))).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should return true for an iframe', () => {
|
|
99
|
+
expect(isFocusable(createElement('iframe'))).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should return true for a select', () => {
|
|
103
|
+
expect(isFocusable(createElement('select'))).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should return true for a textarea', () => {
|
|
107
|
+
expect(isFocusable(createElement('textarea'))).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('isCustomElement', () => {
|
|
112
|
+
it('should return true for elements with a hyphen in the tag name', () => {
|
|
113
|
+
const el = document.createElement('my-component');
|
|
114
|
+
expect(isCustomElement(el)).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should return false for standard elements', () => {
|
|
118
|
+
const el = document.createElement('div');
|
|
119
|
+
expect(isCustomElement(el)).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should return false for null', () => {
|
|
123
|
+
expect(isCustomElement(null)).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('getFirstFocusableElement', () => {
|
|
128
|
+
it('should return null for null input', () => {
|
|
129
|
+
expect(getFirstFocusableElement(null)).toBeNull();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should return the element itself if it is focusable and has no focusable children', () => {
|
|
133
|
+
const btn = document.createElement('button');
|
|
134
|
+
expect(getFirstFocusableElement(btn)).toBe(btn);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should return the first focusable child', () => {
|
|
138
|
+
const div = document.createElement('div');
|
|
139
|
+
const span = document.createElement('span');
|
|
140
|
+
const btn1 = document.createElement('button');
|
|
141
|
+
const btn2 = document.createElement('button');
|
|
142
|
+
div.appendChild(span);
|
|
143
|
+
div.appendChild(btn1);
|
|
144
|
+
div.appendChild(btn2);
|
|
145
|
+
expect(getFirstFocusableElement(div)).toBe(btn1);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should return null for a custom element without data-focusable', () => {
|
|
149
|
+
const el = document.createElement('my-component');
|
|
150
|
+
expect(getFirstFocusableElement(el)).toBeNull();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should return the custom element if data-focusable is true', () => {
|
|
154
|
+
const el = document.createElement('my-component');
|
|
155
|
+
el.dataset.focusable = 'true';
|
|
156
|
+
expect(getFirstFocusableElement(el)).toBe(el);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should return null for a text node', () => {
|
|
160
|
+
const text = document.createTextNode('hello');
|
|
161
|
+
expect(getFirstFocusableElement(text)).toBeNull();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('getLastFocusableElement', () => {
|
|
166
|
+
it('should return null for null input', () => {
|
|
167
|
+
expect(getLastFocusableElement(null)).toBeNull();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should return the last focusable child', () => {
|
|
171
|
+
const div = document.createElement('div');
|
|
172
|
+
const btn1 = document.createElement('button');
|
|
173
|
+
const btn2 = document.createElement('button');
|
|
174
|
+
div.appendChild(btn1);
|
|
175
|
+
div.appendChild(btn2);
|
|
176
|
+
expect(getLastFocusableElement(div)).toBe(btn2);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should return the element itself if it is focusable and has no focusable children', () => {
|
|
180
|
+
const input = document.createElement('input');
|
|
181
|
+
expect(getLastFocusableElement(input)).toBe(input);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('isParentOf', () => {
|
|
186
|
+
it('should return false for null', () => {
|
|
187
|
+
expect(isParentOf(null, 'MY-COMPONENT')).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should return true if the element itself matches the target tag', () => {
|
|
191
|
+
const el = document.createElement('my-component');
|
|
192
|
+
expect(isParentOf(el, 'MY-COMPONENT')).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should return true if a descendant matches the target tag', () => {
|
|
196
|
+
const wrapper = document.createElement('div');
|
|
197
|
+
const child = document.createElement('my-component');
|
|
198
|
+
wrapper.appendChild(child);
|
|
199
|
+
expect(isParentOf(wrapper, 'MY-COMPONENT')).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should return false if no descendant matches', () => {
|
|
203
|
+
const wrapper = document.createElement('div');
|
|
204
|
+
const child = document.createElement('span');
|
|
205
|
+
wrapper.appendChild(child);
|
|
206
|
+
expect(isParentOf(wrapper, 'MY-COMPONENT')).toBe(false);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should return false for a text node', () => {
|
|
210
|
+
const text = document.createTextNode('hello');
|
|
211
|
+
expect(isParentOf(text, 'MY-COMPONENT')).toBe(false);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {Store} from 'c/quanticUtils';
|
|
2
|
+
|
|
3
|
+
describe('storeUtils', () => {
|
|
4
|
+
describe('Store.facetTypes', () => {
|
|
5
|
+
it('should expose the expected facet type keys', () => {
|
|
6
|
+
expect(Store.facetTypes).toEqual({
|
|
7
|
+
FACETS: 'facets',
|
|
8
|
+
NUMERICFACETS: 'numericFacets',
|
|
9
|
+
DATEFACETS: 'dateFacets',
|
|
10
|
+
CATEGORYFACETS: 'categoryFacets',
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('Store.initialize', () => {
|
|
16
|
+
it('should return a store with empty state', () => {
|
|
17
|
+
const store = Store.initialize();
|
|
18
|
+
expect(store).toEqual({
|
|
19
|
+
state: {
|
|
20
|
+
facets: {},
|
|
21
|
+
numericFacets: {},
|
|
22
|
+
dateFacets: {},
|
|
23
|
+
categoryFacets: {},
|
|
24
|
+
sort: {},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('Store.registerFacetToStore', () => {
|
|
31
|
+
it('should register facet data under the given facet type and id', () => {
|
|
32
|
+
const store = Store.initialize();
|
|
33
|
+
const data = {facetId: 'author', label: 'Author'};
|
|
34
|
+
Store.registerFacetToStore(store, 'facets', data);
|
|
35
|
+
expect(store.state.facets.author).toEqual(data);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should not overwrite an existing facet entry', () => {
|
|
39
|
+
const store = Store.initialize();
|
|
40
|
+
const data = {facetId: 'author', label: 'Author'};
|
|
41
|
+
Store.registerFacetToStore(store, 'facets', data);
|
|
42
|
+
Store.registerFacetToStore(store, 'facets', {
|
|
43
|
+
facetId: 'author',
|
|
44
|
+
label: 'Updated',
|
|
45
|
+
});
|
|
46
|
+
expect(store.state.facets.author.label).toBe('Author');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('Store.registerSortOptionDataToStore', () => {
|
|
51
|
+
it('should set sort data on the store', () => {
|
|
52
|
+
const store = Store.initialize();
|
|
53
|
+
const sortData = [{label: 'Relevance', value: 'relevance'}];
|
|
54
|
+
Store.registerSortOptionDataToStore(store, sortData);
|
|
55
|
+
expect(store.state.sort).toBe(sortData);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('Store.getFromStore', () => {
|
|
60
|
+
it('should return facet data for the given type', () => {
|
|
61
|
+
const store = Store.initialize();
|
|
62
|
+
const data = {facetId: 'source', label: 'Source'};
|
|
63
|
+
Store.registerFacetToStore(store, 'facets', data);
|
|
64
|
+
expect(Store.getFromStore(store, 'facets')).toEqual({source: data});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should return an empty object when no facets are registered', () => {
|
|
68
|
+
const store = Store.initialize();
|
|
69
|
+
expect(Store.getFromStore(store, 'facets')).toEqual({});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('Store.getSortOptionsFromStore', () => {
|
|
74
|
+
it('should return the sort options', () => {
|
|
75
|
+
const store = Store.initialize();
|
|
76
|
+
const sortData = [{label: 'Date', value: 'date'}];
|
|
77
|
+
Store.registerSortOptionDataToStore(store, sortData);
|
|
78
|
+
expect(Store.getSortOptionsFromStore(store)).toBe(sortData);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should return an empty object when no sort options are registered', () => {
|
|
82
|
+
const store = Store.initialize();
|
|
83
|
+
expect(Store.getSortOptionsFromStore(store)).toEqual({});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|