Merge branch 'develop' into feature/SSR-669

This commit is contained in:
Katie Kroell 2023-11-10 11:30:49 -05:00
commit 7fbf836316
27 changed files with 3793 additions and 240 deletions

16
package-lock.json generated
View file

@ -43,6 +43,7 @@
"eslint-plugin-vue": "^9.15.1",
"jest": "^27.0.5",
"jest-junit": "^13.0.0",
"jest-serializer-vue": "^3.1.0",
"jsdoc": "^4.0.2",
"jsdom": "^22.1.0",
"sass": "^1.32.7",
@ -4413,6 +4414,15 @@
}
}
},
"node_modules/@vue/cli-plugin-unit-jest/node_modules/jest-serializer-vue": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/jest-serializer-vue/-/jest-serializer-vue-2.0.2.tgz",
"integrity": "sha512-nK/YIFo6qe3i9Ge+hr3h4PpRehuPPGZFt8LDBdTHYldMb7ZWlkanZS8Ls7D8h6qmQP2lBQVDLP0DKn5bJ9QApQ==",
"dev": true,
"dependencies": {
"pretty": "2.0.0"
}
},
"node_modules/@vue/cli-plugin-vuex": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz",
@ -12245,9 +12255,9 @@
}
},
"node_modules/jest-serializer-vue": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/jest-serializer-vue/-/jest-serializer-vue-2.0.2.tgz",
"integrity": "sha512-nK/YIFo6qe3i9Ge+hr3h4PpRehuPPGZFt8LDBdTHYldMb7ZWlkanZS8Ls7D8h6qmQP2lBQVDLP0DKn5bJ9QApQ==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jest-serializer-vue/-/jest-serializer-vue-3.1.0.tgz",
"integrity": "sha512-vXz9/3IgBbLhsaVANYLG4ROCQd+Wg3qbB6ICofzFL+fbhSFPlqb0/MMGXcueVsjaovdWlYiRaLQLpdi1PTcoRQ==",
"dev": true,
"dependencies": {
"pretty": "2.0.0"

View file

@ -11,7 +11,7 @@
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit --coverage --ci",
"test:unit": "vue-cli-service test:unit --coverage --ci --colors",
"test:unit:lite": "vue-cli-service test:unit --ci"
},
"dependencies": {
@ -50,6 +50,7 @@
"eslint-plugin-vue": "^9.15.1",
"jest": "^27.0.5",
"jest-junit": "^13.0.0",
"jest-serializer-vue": "^3.1.0",
"jsdoc": "^4.0.2",
"jsdom": "^22.1.0",
"sass": "^1.32.7",

View file

@ -0,0 +1,45 @@
const widgetFields = Object.freeze({
HEADER_WIDGET: {
NAME: 'Name',
ANSWERS: 'Answers'
},
SUB_HEADER_WIDGET: {
NAME: 'Name',
SUB_HEADER_TEXT: 'SubHeaderText',
SECONDARY_TEXT: 'SecondaryText',
BACK_BUTTON_ACCESSIBLE_TEXT: 'BackButtonAccessibleText'
},
INPUT_QUESTION_WIDGET: {
NAME: 'Name',
QUESTION_TEXT: 'QuestionText',
BUTTON_TEXT: 'ButtonText',
ANSWERS: 'Answers'
},
TEXT_BLOCK_WIDGET: {
NAME: 'Name',
TEXT: 'Text'
},
CONTENT_GROUP_WIDGET: {
NAME: 'Name',
HEADER_TEXT: 'HeaderText',
SUBHEADER_TEXT: 'SubheaderText',
BODY_TEXT: 'BodyText',
BODY_TEXT_2: 'BodyText2',
FOOTER_TEXT: 'FooterText',
IMAGE: 'Image'
},
ALERT_WIDGET: {
NAME: 'Name',
HEADLINE_TEXT: 'HeadlineText',
BODY_TEXT: 'BodyText'
},
FOOTER_WIDGET: {
NAME: 'Name',
BACK_BUTTON_TEXT: 'BackButtonText',
FORWARD_BUTTON_TEXT: 'ForwardButtonText',
FOOTER_IMAGE: 'FooterImage',
ALT_TEXT: 'AltText'
}
});
export default widgetFields;

View file

@ -13,6 +13,8 @@ const globalRules = Object.freeze({
EMAIL_ADDRESS_FORMAT: 'email-address-format',
PHONE_NUMBER_REQUIRED: 'phone-number-required',
PHONE_NUMBER_FORMAT: 'phone-number-format',
ZIP_CODE_REQUIRED: 'zip-code-required',
ZIP_CODE_SEARCH_FORMAT: 'zip-code-search-format',
OPTION_REQUIRED: 'option-required'
});

View file

@ -30,7 +30,7 @@
:aria-label="questionText"
:min="min"
:max="max"
required
:required="isRequired"
:class="[
hasIcon ? 'has-icon' : '',
iconRight ? 'icon-right' : '',
@ -48,7 +48,8 @@
<button
v-if="includeSearchIcon"
type="submit"
aria-label="Search button" />
aria-label="Search button"
@click="clickedSearch" />
<button
v-if="includeSelectIcon"
type="submit"
@ -58,6 +59,7 @@
</div>
<div
v-show="errorMessage"
ref="errorMessageDiv"
class="row my-1 form-test-error">
<span
class="d-inline-flex mt-0"
@ -99,7 +101,10 @@ export default {
default: ''
},
validationRules: String,
cmsWidgetName: String,
cmsWidgetName: {
String,
default: ''
},
maxLength: String,
questionAlignment: String, // Left or center. Left is default.
cornerStyle: String, // Rounded or square. Square is default.
@ -109,7 +114,7 @@ export default {
max: String,
disableAutoFill: Boolean
},
emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned'],
emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned', 'click-event'],
setup(props) {
const propsClone = { ...props };
const { modelValue } = propsClone;
@ -217,12 +222,18 @@ export default {
const blendedString = arrOriginalString.join('');
this.$emit('update:modelValue', blendedString);
},
clickedSearch() {
if (this.meta.valid) {
this.$emit('click-event');
}
}
}
};
</script>
<style lang="scss" scoped>
@import "@/styles/ux-variables-svg-strings.scss";
input[type='date']::-webkit-inner-spin-button {
display: none;
}
@ -232,7 +243,7 @@ input[type='date']::-webkit-calendar-picker-indicator {
top: 50%;
transform: translateY(-50%);
right: 1px;
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.1762 1.52764H13.7656V1.50352C13.7656 1.10476 13.6076 0.722334 13.3263 0.44037C13.0451 0.158406 12.6636 0 12.2659 0C11.8681 0 11.4866 0.158406 11.2054 0.44037C10.9241 0.722334 10.7661 1.10476 10.7661 1.50352V1.52764H5.42476V1.50352C5.42476 1.10476 5.26675 0.722334 4.9855 0.44037C4.70424 0.158406 4.32277 0 3.92501 0C3.52725 0 3.14579 0.158406 2.86453 0.44037C2.58327 0.722334 2.42526 1.10476 2.42526 1.50352V1.52764H1.82376C1.34046 1.52891 0.877316 1.72195 0.53557 2.06455C0.193824 2.40716 0.00127018 2.87146 0 3.35598V14.1717C0.0016909 14.656 0.194379 15.1201 0.536035 15.4626C0.87769 15.8051 1.34059 15.9983 1.82376 16H14.1746C14.6581 15.9987 15.1214 15.8057 15.4634 15.4632C15.8054 15.1206 15.9983 14.6563 16 14.1717V3.35598C15.9987 2.87146 15.8062 2.40716 15.4644 2.06455C15.1227 1.72195 14.6595 1.52891 14.1762 1.52764ZM11.8889 1.50352C11.8889 1.4033 11.9286 1.30718 11.9993 1.23631C12.07 1.16544 12.1659 1.12563 12.2659 1.12563C12.3658 1.12563 12.4617 1.16544 12.5324 1.23631C12.6031 1.30718 12.6428 1.4033 12.6428 1.50352V2.99899C12.6428 3.09922 12.6031 3.19534 12.5324 3.2662C12.4617 3.33707 12.3658 3.37688 12.2659 3.37688C12.1659 3.37688 12.07 3.33707 11.9993 3.2662C11.9286 3.19534 11.8889 3.09922 11.8889 2.99899V1.50352ZM3.54807 1.50352C3.54807 1.4033 3.58778 1.30718 3.65847 1.23631C3.72916 1.16544 3.82504 1.12563 3.92501 1.12563C4.02498 1.12563 4.12086 1.16544 4.19155 1.23631C4.26224 1.30718 4.30195 1.4033 4.30195 1.50352V2.99899C4.30195 3.09922 4.26224 3.19534 4.19155 3.2662C4.12086 3.33707 4.02498 3.37688 3.92501 3.37688C3.82504 3.37688 3.72916 3.33707 3.65847 3.2662C3.58778 3.19534 3.54807 3.09922 3.54807 2.99899V1.50352ZM14.8772 14.1717C14.8747 14.3573 14.8001 14.5345 14.6691 14.6658C14.5382 14.797 14.3614 14.8719 14.1762 14.8744H1.82536C1.63995 14.8723 1.4627 14.7976 1.33144 14.6663C1.20018 14.5351 1.12531 14.3575 1.12281 14.1717V6.59296H14.8772V14.1717Z' fill='%23167CAC'/%3E%3Cpath d='M2.33063 11.282H3.93464V12.6006C3.93464 12.7499 3.99379 12.8931 4.09907 12.9986C4.20435 13.1041 4.34715 13.1634 4.49604 13.1634C4.64494 13.1634 4.78773 13.1041 4.89301 12.9986C4.9983 12.8931 5.05745 12.7499 5.05745 12.6006V11.282H7.46346V12.6006C7.46346 12.7499 7.52261 12.8931 7.62789 12.9986C7.73318 13.1041 7.87597 13.1634 8.02486 13.1634C8.17376 13.1634 8.31655 13.1041 8.42184 12.9986C8.52712 12.8931 8.58627 12.7499 8.58627 12.6006V11.282H10.9923V12.6006C10.9923 12.7499 11.0514 12.8931 11.1567 12.9986C11.262 13.1041 11.4048 13.1634 11.5537 13.1634C11.7026 13.1634 11.8454 13.1041 11.9507 12.9986C12.0559 12.8931 12.1151 12.7499 12.1151 12.6006V11.282H13.7191C13.868 11.282 14.0108 11.2227 14.1161 11.1172C14.2214 11.0116 14.2805 10.8685 14.2805 10.7192C14.2805 10.57 14.2214 10.4268 14.1161 10.3213C14.0108 10.2157 13.868 10.1564 13.7191 10.1564H12.1151V8.84425C12.1151 8.69498 12.0559 8.55183 11.9507 8.44628C11.8454 8.34073 11.7026 8.28143 11.5537 8.28143C11.4048 8.28143 11.262 8.34073 11.1567 8.44628C11.0514 8.55183 10.9923 8.69498 10.9923 8.84425V10.1628H8.58627V8.84425C8.58627 8.69498 8.52712 8.55183 8.42184 8.44628C8.31655 8.34073 8.17376 8.28143 8.02486 8.28143C7.87597 8.28143 7.73318 8.34073 7.62789 8.44628C7.52261 8.55183 7.46346 8.69498 7.46346 8.84425V10.1628H5.05745V8.84425C5.05745 8.69498 4.9983 8.55183 4.89301 8.44628C4.78773 8.34073 4.64494 8.28143 4.49604 8.28143C4.34715 8.28143 4.20435 8.34073 4.09907 8.44628C3.99379 8.55183 3.93464 8.69498 3.93464 8.84425V10.1628H2.33063C2.18174 10.1628 2.03894 10.2221 1.93366 10.3277C1.82837 10.4332 1.76923 10.5764 1.76923 10.7257C1.76923 10.8749 1.82837 11.0181 1.93366 11.1236C2.03894 11.2292 2.18174 11.2885 2.33063 11.2885V11.282Z' fill='%23167CAC'/%3E%3C/svg%3E");
background-image: url($svg-calendar-picker);
background-repeat: no-repeat;
background-position: center;
width: 16px; // check this
@ -267,17 +278,17 @@ input[type='date']::-webkit-calendar-picker-indicator {
position: relative;
&.has-search-icon {
input[type='text'] {
border-radius: 50rem;
border-radius: $border-radius-lg;
}
button[type='submit'] {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 0;
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.7817 14.7328L11.8252 10.7762C12.8833 9.45005 13.3936 7.76911 13.2513 6.07849C13.1091 4.38788 12.325 2.81587 11.0601 1.6852C9.79515 0.554524 8.14538 -0.0490261 6.44946 -0.00154744C4.75353 0.0459312 3.14012 0.740836 1.94045 1.94051C0.740775 3.14018 0.0458701 4.75359 -0.00160848 6.44952C-0.0490871 8.14545 0.554463 9.79521 1.68514 11.0601C2.81581 12.325 4.38782 13.1091 6.07843 13.2514C7.76905 13.3937 9.44999 12.8834 10.7762 11.8252L14.7349 15.7839C14.8044 15.8527 14.8869 15.907 14.9774 15.9439C15.068 15.9808 15.165 15.9995 15.2628 15.9989C15.3606 15.9983 15.4573 15.9784 15.5475 15.9405C15.6376 15.9025 15.7194 15.8471 15.7881 15.7776C15.8568 15.708 15.9112 15.6256 15.9481 15.535C15.985 15.4444 16.0036 15.3474 16.0031 15.2496C16.0025 15.1518 15.9826 15.0551 15.9446 14.965C15.9067 14.8748 15.8513 14.7931 15.7817 14.7243V14.7328ZM6.63737 11.7913C5.61803 11.7913 4.62157 11.4891 3.77402 10.9228C2.92646 10.3564 2.26587 9.5515 1.87578 8.60975C1.4857 7.668 1.38363 6.63172 1.5825 5.63196C1.78136 4.6322 2.27222 3.71386 2.99301 2.99307C3.7138 2.27229 4.63214 1.78142 5.6319 1.58256C6.63166 1.38369 7.66793 1.48576 8.60969 1.87585C9.55144 2.26593 10.3564 2.92652 10.9227 3.77408C11.489 4.62163 11.7913 5.61809 11.7913 6.63743C11.7896 8.00382 11.2461 9.31376 10.2799 10.2799C9.3137 11.2461 8.00376 11.7897 6.63737 11.7913Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
background-image: url($svg-search-icon);
background-repeat: no-repeat;
background-position: center;
border-radius: 0 50rem 50rem 0;
border-radius: 0 $border-radius-lg $border-radius-lg 0;
background-color: $blue-100;
width: 2.75rem;
height: 100%;
@ -292,7 +303,7 @@ input[type='date']::-webkit-calendar-picker-indicator {
top: 50%;
transform: translateY(-50%);
right: 1rem;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
background-image: url($svg-select-icon);
background-repeat: no-repeat;
background-position: center;
background-color: transparent;

View file

@ -47,6 +47,14 @@ function defineGlobalPhoneNumberRules() {
);
}
/**
* @summary Define global rules related to zip codes
*/
function defineGlobalZipCodeRules() {
defineRule(globalRules.ZIP_CODE_REQUIRED, required(errorMessages.ZIP_REQUIRED));
defineRule(globalRules.ZIP_CODE_SEARCH_FORMAT, regex(/^\d{5}$/, errorMessages.ZIP_FORMAT));
}
/**
* @function defineGlobalRules
* @summary Define all global rules
@ -55,6 +63,7 @@ export default function defineGlobalRules() {
defineGlobalNameRules();
defineGlobalEmailRules();
defineGlobalPhoneNumberRules();
defineGlobalZipCodeRules();
defineRule(globalRules.OPTION_REQUIRED, required(errorMessages.OPTION_REQUIRED));
}

View file

@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Shop list button should render correctly with all relevant props 1`] = `
<transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true" selectedvalue="selected value">
<base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" buttonlabel="Label of button" buttonlabelsubcopy="Sub copy of button" buttonbodycopy="button body copy" screenreaderonlytext="screen reader only text" additionalbuttondata="[object Object]" alttext="" iswide="false" value="1234"></base-input-button-stub>
</transition-stub>
`;
exports[`Shop list button should render correctly with required props 1`] = `
<transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true">
<base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" alttext="" iswide="false" value="1234"></base-input-button-stub>
</transition-stub>
`;
exports[`Shop list button should render expected data with all relevant props 1`] = `
Object {
"availabilityRating": null,
"displayAvailabilityIndicators": true,
}
`;
exports[`Shop list button should render expected data with only required props 1`] = `
Object {
"availabilityRating": null,
"displayAvailabilityIndicators": false,
}
`;

View file

@ -0,0 +1,893 @@
import { shallowMount, mount } from '@vue/test-utils';
import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin';
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';
const buttonLabelReference = 'span[id="buttonLabelSpan"]';
const buttonLabelCopyReference = 'span[id="buttonLabelSubCopySpan"]';
const availabilityIndicatorBlockReference = 'div[id="availabilityIndicatorBlock"]';
const loaderReference = { ref: 'availabilityIndicatorLoader' };
const availabilityBadgeReference = 'div[id="availabilityBadge"]';
const buttonBodyCopyReference = 'span[id="buttonBodyCopy"]';
const screenReaderOnlySpanReference = '#screenReaderOnlyTextSpan';
describe('Shop list button', () => {
describe('should render', () => {
test('correctly with required props', async () => {
// Arrange
const wrapper = shallowMount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group'
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.html()).toMatchSnapshot();
});
test('correctly with all relevant props', async () => {
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(null)
};
const wrapper = shallowMount(shopListButton, {
propsData: {
buttonLabel: 'Label of button',
buttonLabelSubCopy: 'Sub copy of button',
selectedValue: 'selected value',
buttonBodyCopy: 'button body copy',
screenReaderOnlyText: 'screen reader only text',
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.html()).toMatchSnapshot();
});
test('expected data with only required props', async () => {
// Arrange
const wrapper = shallowMount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group'
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.$data).toMatchSnapshot();
});
test('expected data with all relevant props', async () => {
// Arrange
const wrapper = shallowMount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
additionalButtonData: {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(null)
}
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.$data).toMatchSnapshot();
});
describe('button label with expected classes', () => {
test('when "textPosition" prop provided', async () => {
// Arrange
const textPosition = 'positionOfText';
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
textPosition
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const buttonLabel = wrapper.find(buttonLabelReference);
// Assert
expect(buttonLabel.exists()).toBeTruthy();
expect(buttonLabel.classes()).toContain(textPosition);
});
test('when "textPosition" prop not provided', async () => {
// Arrange
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button'
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const buttonLabel = wrapper.find(buttonLabelReference);
// Assert
expect(buttonLabel.exists()).toBeTruthy();
});
});
describe('button label sub copy with expected classes', () => {
test('when "textPosition" prop provided', async () => {
// Arrange
const textPosition = 'positionOfText';
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
textPosition
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const buttonLabelSubCopy = wrapper.find(buttonLabelCopyReference);
// Assert
expect(buttonLabelSubCopy.exists()).toBeTruthy();
expect(buttonLabelSubCopy.classes().length).toBe(4);
expect(buttonLabelSubCopy.classes()).toContain('m-0');
expect(buttonLabelSubCopy.classes()).toContain('caption');
expect(buttonLabelSubCopy.classes()).toContain('ms-1');
expect(buttonLabelSubCopy.classes()).toContain(textPosition);
});
test('when "textPosition" prop not provided', async () => {
// Arrange
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button'
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const buttonLabelSubCopy = wrapper.find(buttonLabelCopyReference);
// Assert
expect(buttonLabelSubCopy.exists()).toBeTruthy();
expect(buttonLabelSubCopy.classes().length).toBe(3);
expect(buttonLabelSubCopy.classes()).toContain('m-0');
expect(buttonLabelSubCopy.classes()).toContain('caption');
expect(buttonLabelSubCopy.classes()).toContain('ms-1');
});
});
describe('availability indicator block with expected when displayAvailabilityIndicators true', () => {
test('and availability rating is high', async () => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve('high')
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const availabilityIndicatorBlock = wrapper.find(availabilityIndicatorBlockReference);
// Assert
expect(availabilityIndicatorBlock.exists()).toBeTruthy();
expect(availabilityIndicatorBlock.classes()).toContain('availability-indicator');
expect(availabilityIndicatorBlock.classes()).toContain('rounded-pill');
expect(availabilityIndicatorBlock.classes()).toContain('green');
});
test.each(['low', ''])(
'and availability rating is not high or null',
async (availabilityBadge) => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityBadge)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const availabilityIndicatorBlock = wrapper.find(availabilityIndicatorBlockReference);
// Assert
expect(availabilityIndicatorBlock.exists()).toBeTruthy();
expect(availabilityIndicatorBlock.classes()).toContain('availability-indicator');
expect(availabilityIndicatorBlock.classes()).toContain('rounded-pill');
expect(availabilityIndicatorBlock.classes()).toContain('orange');
}
);
test.each([null, undefined])(
'and availability rating is null',
async (availabilityRating) => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityRating)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const availabilityIndicatorBlock = wrapper.find(availabilityIndicatorBlockReference);
// Assert
expect(availabilityIndicatorBlock.exists()).toBeTruthy();
expect(availabilityIndicatorBlock.classes()).toContain('availability-indicator');
expect(availabilityIndicatorBlock.classes()).toContain('rounded-pill');
expect(availabilityIndicatorBlock.classes()).toContain('gray');
}
);
});
test('availability indicator block with expected classes when displayAvailabilityIndicators true', async () => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve({})
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const availabilityIndicatorBlock = wrapper.find(availabilityIndicatorBlockReference);
// Assert
expect(availabilityIndicatorBlock.exists()).toBeTruthy();
expect(availabilityIndicatorBlock.classes()).toContain('availability-indicator');
expect(availabilityIndicatorBlock.classes()).toContain('rounded-pill');
});
test('availability indicator loader when displayAvailabilityIndicators', async () => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(null)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const availabilityIndicatorLoader = wrapper.findComponent(loaderReference);
// Assert
expect(availabilityIndicatorLoader.exists()).toBeTruthy();
expect(availabilityIndicatorLoader.props().loaderPosition).toBe('left');
expect(availabilityIndicatorLoader.attributes().allowpageinteraction).toBe('true');
});
describe('availability badge when displayAvailabilityIndicators true', () => {
test('and "availabilityRating" is "high"', async () => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve('high')
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const availabilityBadge = wrapper.find(availabilityBadgeReference);
// Assert
expect(availabilityBadge.exists()).toBeTruthy();
expect(availabilityBadge.classes().length).toBe(2);
expect(availabilityBadge.classes()).toContain('availability-badge');
expect(availabilityBadge.classes()).toContain('green');
});
test.each(['low', ''])(
'and "availabilityRating" is not "high" or null',
async (availabilityRating) => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityRating)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const availabilityBadge = wrapper.find(availabilityBadgeReference);
// Assert
expect(availabilityBadge.exists()).toBeTruthy();
expect(availabilityBadge.classes().length).toBe(2);
expect(availabilityBadge.classes()).toContain('availability-badge');
expect(availabilityBadge.classes()).toContain('orange');
}
);
});
test('button body copy when buttonBodyCopy provided', async () => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve({})
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData,
buttonBodyCopy: 'body copy'
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const buttonBodyCopy = wrapper.find(buttonBodyCopyReference);
// Assert
expect(buttonBodyCopy.exists()).toBeTruthy();
expect(buttonBodyCopy.classes()).toContain('m-0');
expect(buttonBodyCopy.classes()).toContain('button-label-sub-copy');
expect(buttonBodyCopy.classes()).toContain('small');
});
test('screen reader only text when screenReaderOnlyText provided', async () => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve({})
};
const screenReaderOnlyText = 'text just for screen reader';
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData,
screenReaderOnlyText
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const screenReaderOnlySpan = wrapper.find(screenReaderOnlySpanReference);
// Assert
expect(screenReaderOnlySpan.exists()).toBeTruthy();
expect(screenReaderOnlySpan.classes()).toContain('sr-only');
expect(screenReaderOnlySpan.text()).toContain(screenReaderOnlyText);
});
});
describe('should not render', () => {
test.each([false, null, undefined])(
'availability indicator block when displayAvailabilityIndicators falsy',
async (displayAvailabilityIndicators) => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators,
availabilityRatingCallback: () => Promise.resolve({})
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const availabilityIndicatorBlock = wrapper.find(availabilityIndicatorBlockReference);
// Assert
expect(availabilityIndicatorBlock.exists()).toBeFalsy();
}
);
test.each([null, undefined])(
'availability badge when displayAvailabilityIndicators true and "availabilityRating" is null',
async (availabilityRating) => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityRating)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const availabilityBadge = wrapper.find(availabilityBadgeReference);
// Assert
expect(availabilityBadge.exists()).toBeFalsy();
}
);
test('button body copy when buttonBodyCopy not provided', async () => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve({})
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const buttonBodyCopy = wrapper.find(buttonBodyCopyReference);
// Assert
expect(buttonBodyCopy.exists()).toBeFalsy();
});
test('screen reader only text when screenReaderOnlyText not provided', async () => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve({})
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const screenReaderOnlySpan = wrapper.find(screenReaderOnlySpanReference);
// Assert
expect(screenReaderOnlySpan.exists()).toBeFalsy();
});
});
describe('computed', () => {
describe('isLoaderDisplayed', () => {
test.each([null, undefined])(
'should return true when availabilityRating is null or undefined',
async (availabilityRating) => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityRating)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const result = wrapper.vm.isLoaderDisplayed;
// Assert
expect(result).toBeTruthy();
}
);
test('should return false when availabilityRating is empty string', async () => {
// Arrange
const availabilityRating = '';
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityRating)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const result = wrapper.vm.isLoaderDisplayed;
// Assert
expect(result).toBeFalsy();
});
test('should return false when availabilityRating is non-empty string', async () => {
// Arrange
const availabilityRating = 'not empty string';
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityRating)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const result = wrapper.vm.isLoaderDisplayed;
// Assert
expect(result).toBeFalsy();
});
});
describe('availabilityRatingClass', () => {
test.each([null, undefined])(
'returns "gray" if availability rating null or undefined',
async (availabilityRating) => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityRating)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
const expected = 'gray';
// Act
const result = wrapper.vm.availabilityRatingClass;
// Assert
expect(result).toBe(expected);
}
);
test('returns green if availability rating is "high"', async () => {
// Arrange
const availabilityRating = 'high';
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityRating)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
const expected = 'green';
// Act
const result = wrapper.vm.availabilityRatingClass;
// Assert
expect(result).toBe(expected);
});
test.each(['', 'not high'])(
'returns orange if availability not "high"',
async (availabilityRating) => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityRating)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
const expected = 'orange';
// Act
const result = wrapper.vm.availabilityRatingClass;
// Assert
expect(result).toBe(expected);
}
);
});
describe('badgeText', () => {
test.each([null, undefined])(
'returns empty string if availability rating null or undefined',
async (availabilityRating) => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityRating)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
const expected = '';
// Act
const result = wrapper.vm.badgeText;
// Assert
expect(result).toBe(expected);
}
);
test('returns "Appts available" if availability rating "high"', async () => {
// Arrange
const availabilityRating = 'high';
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityRating)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
const expected = 'Appts available';
// Act
const result = wrapper.vm.badgeText;
// Assert
expect(result).toBe(expected);
});
test.each(['', 'not high'])(
'returns "Appts low" if availability rating not null or "high"',
async (availabilityRating) => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve(availabilityRating)
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
const expected = 'Appts low';
// Act
const result = wrapper.vm.badgeText;
// Assert
expect(result).toBe(expected);
}
);
});
describe('selectedValue', () => {
test('"get" returns modalValue prop', async () => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve({})
};
const modelValue = 'value of modal';
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue,
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Act
const result = wrapper.vm.selectedValue;
// Assert
expect(result).toBe(modelValue);
});
test('"set" emits "update:modalValue" event', async () => {
// Arrange
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback: () => Promise.resolve({})
};
const modelValue = 'value of modal';
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue,
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
const newValue = 'new value';
// Act
wrapper.vm.selectedValue = newValue;
// Assert
expect(wrapper.emitted()['update:modelValue'][0][0]).toEqual(newValue);
});
});
});
describe('before mount', () => {
test('should call availabilityRatingCallback with expected data if displayAvailabilityIndicators true', async () => {
// Arrange
const availabilityRating = 'rating';
const availabilityRatingCallback = jest.fn().mockImplementation(() => Promise.resolve(availabilityRating));
const additionalButtonData = {
displayAvailabilityIndicators: true,
availabilityRatingCallback
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Assert
expect(availabilityRatingCallback).toBeCalledTimes(1);
});
test('should not call availabilityRatingCallback if displayAvailabilityIndicators false', async () => {
// Arrange
const availabilityRating = 'rating';
const availabilityRatingCallback = jest.fn().mockImplementation(() => Promise.resolve(availabilityRating));
const additionalButtonData = {
displayAvailabilityIndicators: false,
availabilityRatingCallback
};
const wrapper = mount(shopListButton, {
propsData: {
value: 1234,
modelValue: 'value of modal',
groupName: 'name of group',
buttonLabel: 'label of button',
additionalButtonData
},
mixins: [inputButtonWrapperMixin]
});
await wrapper.vm.$nextTick();
// Assert
expect(availabilityRatingCallback).toBeCalledTimes(0);
});
});
});

View file

@ -1,55 +1,62 @@
<template>
<transition
name="fade"
mode="out-in">
<baseInputButton
v-bind="$props"
v-model="selectedValue"
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2">
<div
:aria-label="buttonLabel"
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
<div class="row-one">
<span
class="m-0 button-label-copy"
:class="textPosition">{{
buttonLabel
}}</span>
<span
class="m-0 caption ms-1"
:class="textPosition">{{
buttonLabelSubCopy
}}</span>
<div
<transition
name="fade"
mode="out-in">
<baseInputButton
v-bind="$props"
v-model="selectedValue"
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2">
<div
:aria-label="buttonLabel"
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
<div class="row-one">
<span
id="buttonLabelSpan"
class="m-0 button-label-copy"
:class="textPosition">{{ buttonLabel }}
</span>
<span
id="buttonLabelSubCopySpan"
class="m-0 caption ms-1"
:class="textPosition">{{ buttonLabelSubCopy }}
</span>
<div
v-if="displayAvailabilityIndicators"
id="availabilityIndicatorBlock"
class="availability-indicator rounded-pill"
:class="availabilityRatingClass">
<div
v-if="!isLoaderDisplayed"
class="availability-badge"
:class="availabilityRating == 'high' ? 'green' : 'orange'"></div>
<span
v-if="!isLoaderDisplayed"
class="m-0 button-auxillary-copy">{{ badgeText }}</span>
<loader
v-if="isLoaderDisplayed"
ref="availabilityIndicatorLoader"
loaderPosition="left"
:allowPageInteraction="true" />
<div
v-else
id="availabilityIndicator">
<div
id="availabilityBadge"
class="availability-badge"
:class="availabilityRating == 'high' ? 'green' : 'orange'">
</div>
<span class="m-0 button-auxillary-copy">{{ badgeText }}</span>
</div>
</div>
</div>
<div class="row-two">
<span
v-if="buttonBodyCopy"
class="m-0 button-label-sub-copy small"
v-html="buttonBodyCopy"></span>
</div>
<span
v-if="screenReaderOnlyText"
class="sr-only">
{{ screenReaderOnlyText }}
</span>
</div>
</baseInputButton>
</div>
<div class="row-two">
<span
v-if="buttonBodyCopy"
id="buttonBodyCopy"
class="m-0 button-label-sub-copy small"
v-html="buttonBodyCopy"></span>
</div>
<span
v-if="screenReaderOnlyText"
id="screenReaderOnlyTextSpan"
class="sr-only">
{{ screenReaderOnlyText }}
</span>
</div>
</baseInputButton>
</transition>
</template>
@ -67,13 +74,11 @@ export default {
mixins: [inputButtonWrapperMixin],
data() {
return {
availabilityRating: null
availabilityRating: null,
displayAvailabilityIndicators: this.additionalButtonData?.displayAvailabilityIndicators ?? false
};
},
computed: {
displayAvailabilityIndicators() {
return true;
},
isLoaderDisplayed() {
return this.availabilityRating == null;
},
@ -102,8 +107,7 @@ export default {
this.availabilityRating = data;
});
}
},
methods: { }
}
};
</script>
@ -182,7 +186,7 @@ export default {
margin: 0 0.25rem 0 0;
&.green {
background-image: url($svg-shop-list-button-green-availability);
background-image: url($svg-shop-list-button-green-availability);
}
&.orange {

View file

@ -0,0 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Bailout page returns the initial data 1`] = `
Object {
"bailoutPageModel": Object {
"email": "alexander.hamilton45@gmail.com",
"firstName": "Alexander",
"lastName": "Hamilton",
"phoneNumber": "6145550909",
},
"notSeeingPreferredShop": false,
"rules": Object {
"email": "email-required|email-address-format",
"firstName": "first-name-required",
"lastName": "last-name-required",
"phoneNumber": "phone-number-required|phone-number-format",
},
"widget": Object {
"defaultSiteHeader": "SiteSubHeaderWidget",
"noTpa": "ContentGroupNoTPAWidget",
"notSeeingPreferredShop": "ContentGroupNotSeeingPreferredShop",
},
}
`;

View file

@ -0,0 +1,487 @@
// Components
import { shallowMount } from '@vue/test-utils';
import { createTestingPinia } from '@pinia/testing';
import bailoutPage from '@/layouts/bailout-page/bailout-page.vue';
// Supporting Files
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import routerParams from '@/router/router-constants/router-params';
import { useMainStore } from '@/store';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper.js';
// Mock fetchCmsContentForPage
jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: jest.fn(),
doesCopyContainRouterLink: jest.fn()
}));
// Mock our module for promises.
jest.mock('@/helpers/layout-helper.js', () => jest.fn());
function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRunAfterInitializingStore = () => {}) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
}
});
const testingPinia = createTestingPinia({
initialState: {
main: mainInitialState
}
});
useMainStore(testingPinia);
methodToRunAfterInitializingStore();
mountOptions.global.plugins = [testingPinia];
mountOptions.data = () => (initialData);
const apiResponses = {
cmsContent: {}
};
settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
const wrapper = shallowMount(bailoutPage, mountOptions);
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => '');
wrapper.vm.setCmsContent = jest.fn();
return { wrapper };
}
describe('Bailout page', () => {
test('returns the initial data', () => {
// Arrange
const firstName = 'Alexander';
const lastName = 'Hamilton';
const phoneNumber = '6145550909';
const emailAddress = 'alexander.hamilton45@gmail.com';
const mainInitialState = {
order: {
customer: {
firstName,
lastName,
phoneNumber,
emailAddress
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Assert
expect(wrapper.vm.$data).toMatchSnapshot();
});
describe('renders', () => {
test('site header', () => {
// Arrange
const wrapper = shallowMount(bailoutPage, getMountOptions());
// Act
const siteHeader = wrapper.findComponent({ ref: 'siteHeader' });
// Assert
expect(siteHeader.exists()).toBeTruthy();
});
test('sub header', () => {
// Arrange
const wrapper = shallowMount(bailoutPage, getMountOptions());
// Act
const siteSubHeader = wrapper.findComponent({ ref: 'siteSubHeader' });
// Assert
expect(siteSubHeader.exists()).toBeTruthy();
});
test('first name question', () => {
// Arrange
const wrapper = shallowMount(bailoutPage, getMountOptions());
// Act
const firstName = wrapper.findComponent({ ref: 'firstName' });
// Assert
expect(firstName.exists()).toBeTruthy();
});
test('last name question', () => {
// Arrange
const wrapper = shallowMount(bailoutPage, getMountOptions());
// Act
const lastName = wrapper.findComponent({ ref: 'lastName' });
// Assert
expect(lastName.exists()).toBeTruthy();
});
test('phone number name question', () => {
// Arrange
const wrapper = shallowMount(bailoutPage, getMountOptions());
// Act
const phoneNumber = wrapper.findComponent({ ref: 'phoneNumber' });
// Assert
expect(phoneNumber.exists()).toBeTruthy();
});
test('email question', () => {
// Arrange
const wrapper = shallowMount(bailoutPage, getMountOptions());
// Act
const email = wrapper.findComponent({ ref: 'emailAddress' });
// Assert
expect(email.exists()).toBeTruthy();
});
test('footer', () => {
// Arrange
const wrapper = shallowMount(bailoutPage, getMountOptions());
// Act
const siteFooter = wrapper.findComponent({ ref: 'siteFooter' });
// Assert
expect(siteFooter.exists()).toBeTruthy();
});
});
describe('computed', () => {
describe('subHeaderCmsWidgetName', () => {
test.each([true, false])(
'returns noTpa widget name when tpa flow not enabled',
(notSeeingPreferredShop) => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow: false }
};
const initialData = { notSeeingPreferredShop };
const { wrapper } = getMountedComponent(mainInitialState, initialData);
const expected = 'ContentGroupNoTPAWidget';
// Act
const name = wrapper.vm.subHeaderCmsWidgetName;
// Assert
expect(name).toBe(expected);
}
);
test('returns notSeeingPreferredShop widget name when tpa enabled and notSeeingPreferredShop true', () => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow: true }
};
const initialData = { notSeeingPreferredShop: true };
const { wrapper } = getMountedComponent(mainInitialState, initialData);
const expected = 'ContentGroupNotSeeingPreferredShop';
// Act
const name = wrapper.vm.subHeaderCmsWidgetName;
// Assert
expect(name).toBe(expected);
});
test('returns default widget name when tpa enabled and notSeeingPreferredShop false', () => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow: true }
};
const initialData = { notSeeingPreferredShop: false };
const { wrapper } = getMountedComponent(mainInitialState, initialData);
const expected = 'SiteSubHeaderWidget';
// Act
const name = wrapper.vm.subHeaderCmsWidgetName;
// Assert
expect(name).toBe(expected);
});
});
describe('subHeaderContentProperty', () => {
test('returns "SubHeaderText" when tpa flow enabled and not seeing preferred shop flag false', () => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow: true }
};
const initialData = { notSeeingPreferredShop: false };
const { wrapper } = getMountedComponent(mainInitialState, initialData);
const expected = 'SubHeaderText';
// Act
const name = wrapper.vm.subHeaderContentProperty;
// Assert
expect(name).toBe(expected);
});
test.each([true, false])(
'returns "HeaderText" when tpa flow not enabled',
(notSeeingPreferredShop) => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow: false }
};
const initialData = { notSeeingPreferredShop };
const { wrapper } = getMountedComponent(mainInitialState, initialData);
const expected = 'HeaderText';
// Act
const name = wrapper.vm.subHeaderContentProperty;
// Assert
expect(name).toBe(expected);
}
);
test('returns "HeaderText" when tpa flow enabled and not seeing preferred shop flag true', () => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow: false }
};
const initialData = { notSeeingPreferredShop: true };
const { wrapper } = getMountedComponent(mainInitialState, initialData);
const expected = 'HeaderText';
// Act
const name = wrapper.vm.subHeaderContentProperty;
// Assert
expect(name).toBe(expected);
});
});
describe('subContentProperty', () => {
test('returns "SecondaryText" when tpa flow enabled and not seeing preferred shop flag false', () => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow: true }
};
const initialData = { notSeeingPreferredShop: false };
const { wrapper } = getMountedComponent(mainInitialState, initialData);
const expected = 'SecondaryText';
// Act
const name = wrapper.vm.subContentProperty;
// Assert
expect(name).toBe(expected);
});
test.each([true, false])(
'returns "BodyText" when tpa flow not enabled',
(notSeeingPreferredShop) => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow: false }
};
const initialData = { notSeeingPreferredShop };
const { wrapper } = getMountedComponent(mainInitialState, initialData);
const expected = 'BodyText';
// Act
const name = wrapper.vm.subContentProperty;
// Assert
expect(name).toBe(expected);
}
);
test('returns "BodyText" when tpa flow enabled and not seeing preferred shop flag true', () => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow: true }
};
const initialData = { notSeeingPreferredShop: true };
const { wrapper } = getMountedComponent(mainInitialState, initialData);
const expected = 'BodyText';
// Act
const name = wrapper.vm.subContentProperty;
// Assert
expect(name).toBe(expected);
});
});
describe('stripRteStyle', () => {
test('returns false when tpa flow enabled and not seeing preferred shop flag false', () => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow: true }
};
const initialData = { notSeeingPreferredShop: false };
const { wrapper } = getMountedComponent(mainInitialState, initialData);
const expected = false;
// Act
const flag = wrapper.vm.stripRteStyle;
// Assert
expect(flag).toBe(expected);
});
test('returns true when tpa flow not enabled and not seeing preferred shop flag false', () => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow: false }
};
const initialData = { notSeeingPreferredShop: false };
const { wrapper } = getMountedComponent(mainInitialState, initialData);
const expected = true;
// Act
const flag = wrapper.vm.stripRteStyle;
// Assert
expect(flag).toBe(expected);
});
test('returns true when tpa flow enabled and not seeing preferred shop flag true', () => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow: true }
};
const initialData = { notSeeingPreferredShop: true };
const { wrapper } = getMountedComponent(mainInitialState, initialData);
const expected = true;
// Act
const flag = wrapper.vm.stripRteStyle;
// Assert
expect(flag).toBe(expected);
});
});
describe('isTpaEnabled', () => {
test.each([true, false])(
'matches enableTPAFlow in store',
(enableTPAFlow) => {
// Arrange
const mainInitialState = {
issConfig: { enableTPAFlow }
};
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const flag = wrapper.vm.isTpaEnabled;
// Assert
expect(flag).toBe(enableTPAFlow);
}
);
});
});
describe('method', () => {
describe('backButtonAction', () => {
test('should navigate with CLICKED_BACK_PREVIOUS scenario', () => {
// Arrange
const { wrapper } = getMountedComponent();
// Act
wrapper.vm.backButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
wrapper.vm.navigationScenarios.CLICKED_BACK_PREVIOUS,
wrapper.vm.$route
);
});
});
describe('forwardButtonAction', () => {
test('should navigate to the next route', () => {
// Arrange
const { wrapper } = getMountedComponent();
// Act
wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
wrapper.vm.navigationScenarios.CLICKED_FORWARD,
wrapper.vm.$route,
{},
{},
wrapper.vm.bailoutPageModel
);
});
});
describe('getBailoutPageModelFromStore', () => {
test('should return expected bailout page model', () => {
// Arrange
const firstName = 'Jake';
const lastName = 'From State Farm';
const phoneNumber = '4215558234';
const emailAddress = 'jake.from.state.farm993@yahoo.com';
const mainInitialState = {
order: {
customer: {
firstName,
lastName,
phoneNumber,
emailAddress
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const pageModel = wrapper.vm.getBailoutPageModelFromStore();
// Assert
expect(pageModel).toEqual({
firstName,
lastName,
phoneNumber,
email: emailAddress
});
});
});
describe('setNotSeeingPreferShop', () => {
test.each([true, false])(
'updates notSeeingPreferredShop value',
(flag) => {
// Arrange
const { wrapper } = getMountedComponent();
// Act
wrapper.vm.setNotSeeingPreferShop(flag);
// Assert
expect(wrapper.vm.notSeeingPreferredShop).toBe(flag);
}
);
test('sets notSeeingPreferredShop to true when null is passed', () => {
// Arrange
const { wrapper } = getMountedComponent();
// Act
wrapper.vm.setNotSeeingPreferShop(null);
// Assert
expect(wrapper.vm.notSeeingPreferredShop).toBe(false);
});
});
});
describe('before entering the route', () => {
test.each([true, false])(
'sets notSeeingPreferredShop flag based value returned by store method pageData',
async (notSeeingPreferredShop) => {
// Arrange
const page = 'bailout-page';
const initialStoreState = {
applicationUser: {
pageData: {
[page]: {
[routerParams.NOT_SEEING_PREFERRED_SHOP]: notSeeingPreferredShop,
turtle: 5
}
}
}
};
const { wrapper } = getMountedComponent(initialStoreState);
// Act
await bailoutPage.beforeRouteEnter.call(
wrapper.vm,
{ query: { issPage: page } },
undefined,
(c) => c(wrapper.vm)
);
// Assert
expect(wrapper.vm.notSeeingPreferredShop).toBe(notSeeingPreferredShop);
}
);
});
});

View file

@ -5,21 +5,18 @@
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteHeader
ref="siteHeader"
cmsWidgetName="SiteHeaderWidget" />
<div class="main-content-container">
<siteSubHeader
v-if="!isNoTpa"
cmsWidgetName="SiteSubHeaderWidget"
ref="siteSubHeader"
:cmsWidgetName="subHeaderCmsWidgetName"
:contentProperty="subHeaderContentProperty"
:stripRteStyle="stripRteStyle"
:subContentProperty="subContentProperty"
class="sub-header-content"
justification="left" />
<siteSubHeader
v-else
cmsWidgetName="ContentGroupNoTPAWidget"
class="sub-header-content"
contentProperty="HeaderText"
justification="left"
:stripRteStyle="true"
subContentProperty="BodyText" />
<textboxQuestion
ref="firstName"
v-model="bailoutPageModel.firstName"
@ -54,6 +51,7 @@
disableAutoFill
:validationRules="rules.email" />
<siteFooter
ref="siteFooter"
class="footer-content-container"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@ -65,6 +63,7 @@
</template>
<script>
// Components
import { Form } from 'vee-validate';
import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
@ -72,10 +71,11 @@ import textboxQuestion from '@/digital-components/textbox-question/textbox-quest
// Supporting files
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { Form } from 'vee-validate';
import globalRules from '@/constants/global-rules';
import settleAllPromises from '@/helpers/layout-helper';
import { useMainStore } from '@/store';
import widgetFields from '@/constants/cms-widget-fields.js';
import routerParams from '@/router/router-constants/router-params';
export default {
name: 'bailout-page',
@ -101,17 +101,21 @@ export default {
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
const pageData = useMainStore().pageData(to.query.issPage);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.setNotSeeingPreferShop(pageData[routerParams.NOT_SEEING_PREFERRED_SHOP]);
});
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
return {
notSeeingPreferredShop: false,
bailoutPageModel: this.getBailoutPageModelFromStore(),
widget: {
defaultSiteHeader: 'SiteSubHeaderWidget',
noTpa: 'ContentGroupNoTPAWidget',
notSeeingPreferredShop: 'ContentGroupNotSeeingPreferredShop'
},
rules: {
firstName: globalRules.FIRST_NAME_REQUIRED,
lastName: globalRules.LAST_NAME_REQUIRED,
@ -121,8 +125,30 @@ export default {
};
},
computed: {
isNoTpa() {
return !this.mainStore.issConfig.enableTPAFlow;
subHeaderCmsWidgetName() {
if (!this.isTpaEnabled) {
return this.widget.noTpa;
}
if (this.notSeeingPreferredShop) {
return this.widget.notSeeingPreferredShop;
}
return this.widget.defaultSiteHeader;
},
subHeaderContentProperty() {
return !this.isTpaEnabled || this.notSeeingPreferredShop
? widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT
: widgetFields.SUB_HEADER_WIDGET.SUB_HEADER_TEXT;
},
subContentProperty() {
return !this.isTpaEnabled || this.notSeeingPreferredShop
? widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT
: widgetFields.SUB_HEADER_WIDGET.SECONDARY_TEXT;
},
stripRteStyle() {
return !this.isTpaEnabled || this.notSeeingPreferredShop;
},
isTpaEnabled() {
return useMainStore().issConfig.enableTPAFlow;
}
},
methods: {
@ -131,9 +157,6 @@ export default {
this.$router.navigate(this.navigationScenarios.CLICKED_BACK_PREVIOUS, this.$route);
},
forwardButtonAction() {
return this.navigateForward();
},
navigateForward() {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route,
@ -144,11 +167,14 @@ export default {
},
getBailoutPageModelFromStore() {
return {
firstName: this.mainStore.order.customer.firstName,
lastName: this.mainStore.order.customer.lastName,
phoneNumber: this.mainStore.order.customer.phoneNumber,
email: this.mainStore.order.customer.emailAddress
firstName: useMainStore().order.customer.firstName,
lastName: useMainStore().order.customer.lastName,
phoneNumber: useMainStore().order.customer.phoneNumber,
email: useMainStore().order.customer.emailAddress
};
},
setNotSeeingPreferShop(value) {
this.notSeeingPreferredShop = value ?? false;
}
}
};

View file

@ -338,12 +338,12 @@ export default {
},
methods: {
arePagePrerequisitesValid() {
return !!useMainStore().vehicle.vin;
return !!useMainStore().vehicle.carId;
},
async initializeComponent() {
if (this.policyLookupSuccessful
&& useMainStore().isClaimRegistrationRequired
&& this.coveredAndServicePriceAboveOrEqualDeductible) {
&& (this.coveredAndServicePriceAboveOrEqualDeductible || this.verifiedITAC)) {
await useMainStore().registerClaim()?.catch(() => {});
}
this.$refs.loadingModal.hideModal();
@ -362,6 +362,7 @@ export default {
{ [routerParams.SAVE_SESSION_SYNCHRONOUS]: true }
);
} else if (this.verifiedITAC || this.verifiedNoComp) {
useMainStore().updateIsSafeliteProvider(this.selectedProvider === 'Safelite');
if (this.selectedProvider === 'Safelite') {
this.mainStore.saveSupportingItems(this.supportingItems);
this.$router.navigate(

View file

@ -184,9 +184,11 @@ export default {
let scenario = null;
switch (this.selectedProvider) {
case options.SAFELITE:
this.mainStore.updateIsSafeliteProvider(true);
scenario = this.navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE;
break;
case options.TPA:
this.mainStore.updateIsSafeliteProvider(false);
if (this.mainStore.issConfig.enableTPAFlow) {
if (this.mainStore.hasRecalibrationPart) {
this.$refs.TPARecalModal.openModal();

View file

@ -56,7 +56,7 @@ import errorMessages from '@/constants/error-messages';
import { useMainStore } from '@/store/index.js';
import { markRaw, nextTick } from 'vue';
import { getAvailabilityRating } from '@/helpers/service-location-helper';
import shopListButton from './shop-list-button/shop-list-button.vue';
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';
defineRule('option-required', required(errorMessages.OPTION_REQUIRED));
@ -124,6 +124,7 @@ export default {
const formattedEndDate = endDate.toISOString().split('T')[0];
return {
displayAvailabilityIndicators: true,
availabilityRatingCallback: getAvailabilityRating,
startDate: formattedStartDate,
endDate: formattedEndDate,

View file

@ -0,0 +1,193 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TPA search page returns the initial data 1`] = `
Object {
"additionalButtonData": Object {
"displayAvailabilityIndicators": false,
},
"filter": "",
"providers": Array [],
"rules": Object {
"filter": "option-required",
"provider": "option-required",
"zipCode": "zip-code-required|zip-code-search-format",
},
"selectedProviderNumber": "",
"shopListButton": Object {
"beforeMount": [Function],
"components": Object {
"baseInputButton": Object {
"computed": Object {
"buttonId": [Function],
"eventTypes": [Function],
"inputType": [Function],
"isChecked": [Function],
"isValueSelectedOnClick": [Function],
},
"data": [Function],
"emits": Array [
"update:modelValue",
],
"methods": Object {
"handleBlur": [Function],
"handleClick": [Function],
"handleEventAction": [Function],
"handleFocus": [Function],
"handleSelectionChange": [Function],
},
"mounted": [Function],
"name": "base-input-button",
"props": Object {
"buttonWrapperClasses": Array [
[Function],
[Function],
[Function],
],
"groupName": Object {
"required": true,
"type": [Function],
},
"inputClasses": Array [
[Function],
[Function],
[Function],
],
"isMultiSelect": [Function],
"isRequired": Object {
"default": true,
"type": [Function],
},
"lastValuePushedToGa": Array [
[Function],
[Function],
],
"modelValue": Object {
"required": true,
"validator": [Function],
},
"selectingInitiatesLoad": Object {
"default": false,
"type": [Function],
},
"setLastValuePushedToGa": [Function],
"suppressError": [Function],
"validationRules": Object {
"default": "",
"type": [Function],
},
"value": Object {
"required": true,
"type": Array [
[Function],
[Function],
],
},
"valueToLogType": [Function],
},
"render": [Function],
"setup": [Function],
},
"loader": Object {
"name": "loader",
"props": Object {
"loaderColor": Object {
"type": [Function],
},
"loaderPosition": Object {
"type": [Function],
},
},
"render": [Function],
},
},
"computed": Object {
"availabilityRatingClass": [Function],
"badgeText": [Function],
"isLoaderDisplayed": [Function],
},
"data": [Function],
"mixins": Array [
Object {
"computed": Object {
"selectedValue": Object {
"get": [Function],
"set": [Function],
},
},
"model": Object {
"event": "change",
"prop": "modelValue",
},
"props": Object {
"additionalButtonData": [Function],
"additionalButtonStyling": [Function],
"altText": Object {
"default": "",
"type": [Function],
},
"buttonAuxiliaryCopy": [Function],
"buttonBodyCopy": [Function],
"buttonFooterCopy": [Function],
"buttonImage": [Function],
"buttonImageId": [Function],
"buttonLabel": Array [
[Function],
[Function],
],
"buttonLabelSubCopy": [Function],
"groupName": Object {
"required": true,
"type": [Function],
},
"isMultiSelect": [Function],
"isRequired": Object {
"default": true,
"type": [Function],
},
"isWide": [Function],
"lastValuePushedToGa": Array [
[Function],
[Function],
],
"modelValue": Object {
"required": true,
"validator": [Function],
},
"screenReaderOnlyText": [Function],
"selectingInitiatesLoad": Object {
"default": false,
"type": [Function],
},
"setLastValuePushedToGa": [Function],
"suppressError": [Function],
"textPosition": [Function],
"validationRules": Object {
"default": "",
"type": [Function],
},
"value": Object {
"required": true,
"type": Array [
[Function],
[Function],
],
},
"valueToLogType": [Function],
},
},
],
"name": "shop-list-button",
"render": [Function],
},
"widget": Object {
"filterByQuestion": "FilterByQuestion",
"noNetworkShopsAlert": "NoNetworkShopsAlertWidget",
"searchInstructions": "SearchInstructions",
"shopNotListedLink": "ShopNotListedLink",
"siteFooter": "SiteFooterWidget",
"siteHeader": "SiteHeaderWidget",
"tpaSearchQuestion": "TPASearchQuestion",
},
"zipCode": "12663",
}
`;

View file

@ -0,0 +1,734 @@
// Components
import { shallowMount } from '@vue/test-utils';
import { createTestingPinia } from '@pinia/testing';
import tpaSearch from '@/layouts/tpa-search/tpa-search.vue';
// Supporting Files
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore } from '@/store';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper.js';
import globalRules from '@/constants/global-rules.js';
import widgetFields from '@/constants/cms-widget-fields.js';
// Mock fetchCmsContentForPage
jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: jest.fn(),
doesCopyContainRouterLink: jest.fn()
}));
// Mock our module for promises.
jest.mock('@/helpers/layout-helper.js', () => jest.fn());
function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRunAfterInitializingStore = () => {}) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
}
});
const testingPinia = createTestingPinia({
initialState: {
main: mainInitialState
}
});
useMainStore(testingPinia);
methodToRunAfterInitializingStore();
mountOptions.global.plugins = [testingPinia];
mountOptions.data = () => (initialData);
const apiResponses = { cmsContent: {} };
settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
const wrapper = shallowMount(tpaSearch, mountOptions);
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => cmsContent);
wrapper.vm.setCmsContent = jest.fn();
return { wrapper };
}
describe('TPA search page', () => {
test('returns the initial data', () => {
// Arrange
const zipCode = '12663';
const mainInitialState = {
order: {
customer: {
address: { zipCode }
}
}
};
const { wrapper } = getMountedComponent(mainInitialState);
// Assert
expect(wrapper.vm.$data).toMatchSnapshot();
});
describe('should render', () => {
test('site header', () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
const expectedWidgetName = 'SiteHeaderWidget';
// Act
const siteHeader = wrapper.findComponent({ ref: 'siteHeader' });
// Assert
expect(siteHeader.exists()).toBeTruthy();
expect(siteHeader.props().cmsWidgetName).toBe(expectedWidgetName);
});
test('search providers form', () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
// Act
const searchProvidersForm = wrapper.findComponent('#searchProvidersForm');
// Assert
expect(searchProvidersForm.exists()).toBeTruthy();
});
test('search question label', () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
// Act
const searchQuestionLabel = wrapper.find('#tpaSearchQuestionLabel');
// Assert
expect(searchQuestionLabel.exists()).toBeTruthy();
expect(searchQuestionLabel.attributes().for).toBe('tpaSearchQuestionField');
expect(searchQuestionLabel.classes()).toContain('text-center');
expect(searchQuestionLabel.classes()).toContain('mt-5');
expect(searchQuestionLabel.classes()).toContain('mb-0');
expect(searchQuestionLabel.classes()).toContain('text-black');
expect(searchQuestionLabel.classes()).toContain('w-100');
expect(searchQuestionLabel.classes()).toContain('search-question');
});
test('search instructions', () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
// Act
const searchInstructions = wrapper.find('#searchInstructions');
// Assert
expect(searchInstructions.exists()).toBeTruthy();
expect(searchInstructions.attributes().for).toBe('tpaSearchQuestionField');
expect(searchInstructions.classes()).toContain('text-center');
expect(searchInstructions.classes()).toContain('small');
expect(searchInstructions.classes()).toContain('darker-gray');
expect(searchInstructions.classes()).toContain('w-100');
expect(searchInstructions.classes()).toContain('mb-4');
});
test('search question field', () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
const expectedWidgetName = 'TPASearchQuestion';
// Act
const searchQuestionField = wrapper.findComponent('#tpaSearchQuestionField');
// Assert
expect(searchQuestionField.exists()).toBeTruthy();
expect(searchQuestionField.props().inputId).toBe('tpaSearchQuestionField');
expect(searchQuestionField.props().isRequired).toBeTruthy();
expect(searchQuestionField.props().cmsWidgetName).toBe(expectedWidgetName);
expect(searchQuestionField.props().includeSearchIcon).toBeTruthy();
expect(searchQuestionField.props().displayQuestionText).toBeFalsy();
expect(searchQuestionField.props().validationRules)
.toBe(`${globalRules.ZIP_CODE_REQUIRED}|${globalRules.ZIP_CODE_SEARCH_FORMAT}`);
});
test('provider selection form', () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
// Act
const providerSelectionForm = wrapper.findComponent('#providerSelectionForm');
// Assert
expect(providerSelectionForm.exists()).toBeTruthy();
});
test('map', () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
// Act
const map = wrapper.find('#map');
// Assert
expect(map.exists()).toBeTruthy();
expect(map.classes()).toContain('mb-4');
});
test('search radius filter', () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
const expectedWidgetName = 'FilterByQuestion';
// Act
const searchRadiusFilter = wrapper.findComponent('#searchRadiusFilter');
// Assert
expect(searchRadiusFilter.exists()).toBeTruthy();
expect(searchRadiusFilter.props().cmsWidgetName).toBe(expectedWidgetName);
expect(searchRadiusFilter.props().validationRules).toBe(globalRules.OPTION_REQUIRED);
});
test('select provider question', () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
// Act
const selectProviderQuestion = wrapper.findComponent('#selectProviderQuestion');
// Assert
expect(selectProviderQuestion.exists()).toBeTruthy();
expect(selectProviderQuestion.props().buttonTypeString).toBe('shopListButton');
expect(selectProviderQuestion.classes()).toContain('radioQuestion');
expect(selectProviderQuestion.props().groupName).toBe('chooseShop');
expect(selectProviderQuestion.props().textPosition).toBe('text-start');
expect(selectProviderQuestion.props().isRequired).toBeTruthy();
expect(selectProviderQuestion.props().validationRules).toBe(globalRules.OPTION_REQUIRED);
});
test.each([[], null, undefined])(
'no network providers alert when providers length is 0, undefined, or null',
(providers) => {
// Arrange
const initialData = {
providers
};
const wrapper = shallowMount(tpaSearch, getMountOptions({}, initialData));
// Act
const noNetworkProvidersAlert = wrapper.findComponent('#alertNoNetworkProviders');
// Assert
expect(noNetworkProvidersAlert.exists()).toBeTruthy();
expect(noNetworkProvidersAlert.props().cmsWidgetName).toBeTruthy();
expect(noNetworkProvidersAlert.props().alertClass).toBe('alert-warning');
expect(noNetworkProvidersAlert.props().isDismissible).toBeFalsy();
}
);
test('preferred shop not listed link', () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
// Act
const preferredShopNotListedLink = wrapper.findComponent('#preferredShopNotListedLink');
// Assert
expect(preferredShopNotListedLink.exists()).toBeTruthy();
expect(preferredShopNotListedLink.props().linkType).toBe('navigation');
expect(preferredShopNotListedLink.props().href).toBe('#!');
});
test('site footer', () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
// Act
const footer = wrapper.findComponent({ ref: 'siteFooter' });
// Assert
expect(footer.exists()).toBeTruthy();
expect(footer.props().cmsWidgetName).toBe('SiteFooterWidget');
});
});
test('should not render no network providers alert when providers length is not 0', async () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ providers: ['p1', 'p2'] });
// Act
const noNetworkProvidersAlert = wrapper.findComponent('#alertNoNetworkProviders');
// Assert
expect(noNetworkProvidersAlert.exists()).toBeFalsy();
});
describe('computed', () => {
describe('filterOptions', () => {
test.each([[], null, undefined])(
'returns empty object when cms content is empty, null or undefined',
(cmsContent) => {
// Arrange
const mountOptions = getMountOptions();
mountOptions.mixins = [{
methods: {
getCmsContent: jest.fn().mockImplementation((widget, field) =>
(widget === 'FilterByQuestion' && field === widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
? cmsContent
: ''))
}
}];
const wrapper = shallowMount(tpaSearch, mountOptions);
// Act
const result = wrapper.vm.filterOptions;
// Assert
expect(result).toEqual({});
}
);
test('returns expected when cms content is some non empty iterable', () => {
// Arrange
const mountOptions = getMountOptions();
const cmsContent = [{ Name: 'foo' }, { Name: 'bar' }];
mountOptions.mixins = [{
methods: {
getCmsContent: jest.fn().mockImplementation((widget, field) =>
(widget === 'FilterByQuestion' && field === widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
? cmsContent
: ''))
}
}];
const wrapper = shallowMount(tpaSearch, mountOptions);
const expected = { foo: 'foo', bar: 'bar' };
// Act
const result = wrapper.vm.filterOptions;
// Assert
expect(result).toEqual(expected);
});
});
test.each([null, undefined, '', 'non empty string'])(
'tpaSearchQuestionLabel returns value from getCmsContent',
(cmsContent) => {
// Arrange
const mountOptions = getMountOptions();
mountOptions.mixins = [{
methods: {
getCmsContent: jest.fn().mockImplementation((widget, field) =>
(widget === 'TPASearchQuestion' && field === widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT
? cmsContent
: ''))
}
}];
const wrapper = shallowMount(tpaSearch, mountOptions);
// Act
const result = wrapper.vm.tpaSearchQuestionLabel;
// Assert
expect(result).toEqual(cmsContent);
}
);
test.each([null, undefined, '', 'non empty string'])(
'searchInstructionsText returns value from getCmsContent',
(cmsContent) => {
// Arrange
const mountOptions = getMountOptions();
mountOptions.mixins = [{
methods: {
getCmsContent: jest.fn().mockImplementation((widget, field) =>
(widget === 'SearchInstructions' && field === widgetFields.TEXT_BLOCK_WIDGET.TEXT
? cmsContent
: ''))
}
}];
const wrapper = shallowMount(tpaSearch, mountOptions);
// Act
const result = wrapper.vm.searchInstructionsText;
// Assert
expect(result).toEqual(cmsContent);
}
);
test.each([null, undefined, '', 'non empty string'])(
'shopNotListedModalLink returns value from getCmsContent',
(cmsContent) => {
// Arrange
const mountOptions = getMountOptions();
mountOptions.mixins = [{
methods: {
getCmsContent: jest.fn().mockImplementation((widget, field) =>
(widget === 'ShopNotListedLink' && field === widgetFields.TEXT_BLOCK_WIDGET.TEXT
? cmsContent
: ''))
}
}];
const wrapper = shallowMount(tpaSearch, mountOptions);
// Act
const result = wrapper.vm.shopNotListedModalLink;
// Assert
expect(result).toEqual(cmsContent);
}
);
describe('radiusInMiles', () => {
test('returns 25 when filter is "25 miles"', async () => {
// Arrange
const { wrapper } = getMountedComponent();
await wrapper.setData({ filter: '25 miles' });
// Act
const result = wrapper.vm.radiusInMiles;
// Assert
expect(result).toEqual(25);
});
test('returns 50 when filter is "50 miles"', async () => {
// Arrange
const { wrapper } = getMountedComponent();
await wrapper.setData({ filter: '50 miles' });
// Act
const result = wrapper.vm.radiusInMiles;
// Assert
expect(result).toEqual(50);
});
test('returns 100 when filter is "100 miles"', async () => {
// Arrange
const { wrapper } = getMountedComponent();
await wrapper.setData({ filter: '100 miles' });
// Act
const result = wrapper.vm.radiusInMiles;
// Assert
expect(result).toEqual(100);
});
test.each([null, undefined, 'some random string'])(
'returns 0 when filter is not "25 miles", "50 miles", or "100 miles"',
async (filter) => {
// Arrange
const { wrapper } = getMountedComponent();
await wrapper.setData({ filter });
// Act
const result = wrapper.vm.radiusInMiles;
// Assert
expect(result).toEqual(0);
}
);
});
describe('noNetworkShopsAlertHeaderText', () => {
test('returns exact cmsContent value when "{custom:radiusInMiles}" is not a substring', async () => {
// Arrange
const cmsContent = 'content returned from cms';
const mountOptions = getMountOptions();
mountOptions.mixins = [{
methods: {
getCmsContent: jest.fn().mockImplementation((widget, field) =>
(widget === 'NoNetworkShopsAlertWidget' && field === widgetFields.ALERT_WIDGET.HEADLINE_TEXT
? cmsContent
: ''))
}
}];
const wrapper = shallowMount(tpaSearch, mountOptions);
// Act
const result = wrapper.vm.noNetworkShopsAlertHeaderText;
// Assert
expect(result).toEqual(cmsContent);
});
test('returns cmsContent value with all instances of "{custom:radiusInMiles}" replaced with radiusInMiles', () => {
// Arrange
const cmsContent = '{custom:radiusInMiles} ret{custom:radiusInMiles}urned {custom:radiusInMiles}cms{custom:radiusInMiles}';
const mountOptions = getMountOptions();
mountOptions.mixins = [{
methods: {
getCmsContent: jest.fn().mockImplementation((widget, field) =>
(widget === 'NoNetworkShopsAlertWidget' && field === widgetFields.ALERT_WIDGET.HEADLINE_TEXT
? cmsContent
: ''))
}
}];
const wrapper = shallowMount(tpaSearch, mountOptions);
const expected = '0 ret0urned 0cms0';
// Act
const result = wrapper.vm.noNetworkShopsAlertHeaderText;
// Assert
expect(result).toEqual(expected);
});
});
});
describe('watch', () => {
test('on filter calls getTpaProviders and sets providers', async () => {
// Arrange
const { wrapper } = getMountedComponent();
const providers = { data: ['some data', 'some more data'] };
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers));
const newFilter = 'new filter';
// Act
await wrapper.vm.$options.watch.filter.call(wrapper.vm, newFilter);
// Assert
expect(useMainStore().getTpaProviders).toBeCalled();
expect(wrapper.vm.providers).toEqual(providers.data);
});
describe('on providers', () => {
test.each([null, undefined, []])(
'sets selected provider number to "" when there are no providers',
(newProviders) => {
// Arrange
const { wrapper } = getMountedComponent();
// Act
wrapper.vm.$options.watch.providers.call(wrapper.vm, newProviders);
// Assert
expect(wrapper.vm.selectedProviderNumber).toBe('');
}
);
test('sets selected provider number to value of provider when there is one provider', () => {
// Arrange
const { wrapper } = getMountedComponent();
const value = 'some value';
const newProviders = [{ value }];
// Act
wrapper.vm.$options.watch.providers.call(wrapper.vm, newProviders);
// Assert
expect(wrapper.vm.selectedProviderNumber).toBe(value);
});
test('sets selected provider number to "" when there are more than one provider', () => {
// Arrange
const { wrapper } = getMountedComponent();
const newProviders = [{ value: 'val1' }, { value: 'val2' }];
// Act
wrapper.vm.$options.watch.providers.call(wrapper.vm, newProviders);
// Assert
expect(wrapper.vm.selectedProviderNumber).toBe('');
});
});
});
describe('method', () => {
describe('getProviders', () => {
test.each([null, undefined, {}])(
'returns empty list when getTpaProviders returns no data',
async (newProviders) => {
// Arrange
const { wrapper } = getMountedComponent();
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders));
// Act
const result = await wrapper.vm.getProviders();
// Assert
expect(result).toEqual([]);
}
);
test('returns expected when getTpaProviders returns data', async () => {
// Arrange
const { wrapper } = getMountedComponent();
const providers = { data: [{ name: 'foo' }] };
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers));
// Act
const result = await wrapper.vm.getProviders();
// Assert
expect(result).toEqual(providers.data);
});
});
test('doNotSeeMyShopLinkClick invokes navigate method', () => {
// Arrange
const { wrapper } = getMountedComponent();
// Act
wrapper.vm.doNotSeeMyShopLinkClick();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
});
describe('searchClick', () => {
test.each([null, undefined, {}])(
'sets providers to empty list when no data returned from getProviders',
async (newProviders) => {
// Arrange
const { wrapper } = getMountedComponent();
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders));
// Act
await wrapper.vm.searchClick();
// Assert
expect(wrapper.vm.providers).toEqual([]);
}
);
test('returns value from getProviders', async () => {
// Arrange
const { wrapper } = getMountedComponent();
const newProviders = { data: [{ name: 'foo' }] };
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders));
// Act
await wrapper.vm.searchClick();
// Assert
expect(wrapper.vm.providers).toEqual(newProviders.data);
});
});
test('backButtonAction invokes navigate method', () => {
// Arrange
const { wrapper } = getMountedComponent();
// Act
wrapper.vm.backButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
});
test('forwardButtonAction invokes navigate method', () => {
// Arrange
const { wrapper } = getMountedComponent();
// Act
wrapper.vm.backButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
});
describe('getCustomValueFromString', () => {
test('returns radius in miles value if "radiusInMiles"', () => {
// Arrange
const { wrapper } = getMountedComponent();
const str = 'radiusInMiles';
// Act
const result = wrapper.vm.getCustomValueFromString(str);
// Assert
expect(result).toBe(0);
});
test('returns radius in miles value if not "radiusInMiles"', () => {
// Arrange
const { wrapper } = getMountedComponent();
const str = 'some other string';
// Act
const result = wrapper.vm.getCustomValueFromString(str);
// Assert
expect(result).toBeNull();
});
});
test.each([undefined, null, 'some value', ''])(
'setFilter sets value of filter',
(filter) => {
// Arrange
const { wrapper } = getMountedComponent();
// Act
wrapper.vm.setFilter(filter);
// Assert
expect(wrapper.vm.filter).toEqual(filter);
}
);
test.each([undefined, null, ['some value'], []])(
'setProviders sets value of providers',
(providers) => {
// Arrange
const { wrapper } = getMountedComponent();
// Act
wrapper.vm.setProviders(providers);
// Assert
expect(wrapper.vm.providers).toEqual(providers);
}
);
});
describe('before route enter', () => {
test('when providers exist at 25 mile radius, filter is set to "25 miles" and providers set to expected', async () => {
// Arrange
const { wrapper } = getMountedComponent();
const zipCode = '18394';
useMainStore().order.customer.address.zipCode = zipCode;
const providers = { data: [{ name: 'provider' }] };
useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => (radius === 25 ? providers : []));
const expectedFilter = '25 miles';
// Act
await tpaSearch.beforeRouteEnter.call(
wrapper.vm,
{ query: { issPage: 'tpa-search' } },
undefined,
(c) => c(wrapper.vm)
);
// Assert
expect(wrapper.vm.filter).toBe(expectedFilter);
expect(wrapper.vm.providers).toEqual(providers);
});
test('when providers exist at 50 mile radius but not 25, filter is set to "50 miles" and providers set to expected', async () => {
// Arrange
const { wrapper } = getMountedComponent();
const zipCode = '18394';
useMainStore().order.customer.address.zipCode = zipCode;
const providers = { data: [{ name: 'provider' }] };
useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => (radius === 50 ? providers : []));
const expectedFilter = '50 miles';
// Act
await tpaSearch.beforeRouteEnter.call(
wrapper.vm,
{ query: { issPage: 'tpa-search' } },
undefined,
(c) => c(wrapper.vm)
);
// Assert
expect(wrapper.vm.filter).toBe(expectedFilter);
expect(wrapper.vm.providers).toEqual(providers);
});
test(
'when providers exist at 100 mile radius but not 50 or 25, filter is set to "100 miles" and providers set to expected',
async () => {
// Arrange
const { wrapper } = getMountedComponent();
const zipCode = '18394';
useMainStore().order.customer.address.zipCode = zipCode;
const providers = { data: [{ name: 'provider' }] };
useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => (radius === 100 ? providers : []));
const expectedFilter = '100 miles';
// Act
await tpaSearch.beforeRouteEnter.call(
wrapper.vm,
{ query: { issPage: 'tpa-search' } },
undefined,
(c) => c(wrapper.vm)
);
// Assert
expect(wrapper.vm.filter).toBe(expectedFilter);
expect(wrapper.vm.providers).toEqual(providers);
}
);
test(
'when no providers exist at 100, 50, or 25 mile radius, filter is set to "100 miles" and providers set to empty list',
async () => {
// Arrange
const { wrapper } = getMountedComponent();
const zipCode = '18394';
useMainStore().order.customer.address.zipCode = zipCode;
useMainStore().getTpaProviders = jest.fn().mockImplementation(() => ([]));
const expectedFilter = '100 miles';
// Act
await tpaSearch.beforeRouteEnter.call(
wrapper.vm,
{ query: { issPage: 'tpa-search' } },
undefined,
(c) => c(wrapper.vm)
);
// Assert
expect(wrapper.vm.filter).toBe(expectedFilter);
expect(wrapper.vm.providers).toEqual([]);
}
);
});
});

View file

@ -1,81 +1,309 @@
<template>
<Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="container-fluid pb-2 search">
<p>Placeholder for TPA-Search</p>
<siteFooter
<div>
<siteHeader
ref="siteHeader"
:cmsWidgetName="widget.siteHeader" />
<Form
id="searchProvidersForm"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="container-fluid pb-2">
<div class="mx-5">
<label
id="tpaSearchQuestionLabel"
for="tpaSearchQuestionField"
class="text-center mt-5 mb-0 text-black w-100 search-question">
{{ tpaSearchQuestionLabel }}
</label>
<label
id="searchInstructions"
for="tpaSearchQuestionField"
class="text-center small darker-gray w-100 mb-4">
{{ searchInstructionsText }}
</label>
<div class="mb-5">
<textboxQuestion
id="tpaSearchQuestionField"
v-model="zipCode"
inputId="tpaSearchQuestionField"
:cmsWidgetName="widget.tpaSearchQuestion"
:includeSearchIcon="true"
:displayQuestionText="false"
isRequired
:validationRules="rules.zipCode"
@clickEvent="searchClick" />
</div>
</div>
</div>
</Form>
<Form
id="providerSelectionForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="container-fluid pb-2">
<p
id="map"
class="mb-4">
Placeholder for Map
</p>
<div class="mx-5">
<dropdownQuestion
id="searchRadiusFilter"
v-model="filter"
:cmsWidgetName="widget.filterByQuestion"
inputId="filterByQuestionField"
:options="filterOptions"
disableAutoFill
:validationRules="rules.filter" />
<div class="my-4">
<buttonQuestion
id="selectProviderQuestion"
v-model="selectedProviderNumber"
buttonTypeString="shopListButton"
:buttonTypeObject="shopListButton"
class="radioQuestion"
:answers="providers"
groupName="chooseShop"
textPosition="text-start"
isRequired
:validationRules="rules.provider"
:additionalButtonData="additionalButtonData" />
<alert
v-if="providers?.length === 0 ?? true"
id="alertNoNetworkProviders"
:cmsWidgetName="widget.noNetworkShopsAlert"
alertClass="alert-warning"
:isDismissible="false"
:manualHeadline="noNetworkShopsAlertHeaderText" />
</div>
<div
class="text-center">
<textLink
id="preferredShopNotListedLink"
linkType="navigation"
href="#!"
:text="shopNotListedModalLink"
@clickEvent="doNotSeeMyShopLinkClick" />
</div>
</div>
<siteFooter
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:cmsWidgetName="widget.siteFooter"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="backButtonAction" />
</div>
</div>
</div>
</Form>
</Form>
</div>
</template>
<script>
// Components
import { Form } from 'vee-validate';
import siteHeader from '@/iss-components/site-header/site-header.vue';
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question.vue';
import buttonQuestion from '@/digital-components/button-question/button-question.vue'
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue'
import textLink from '@/ux-components/text-link/text-link.vue';
import alert from '@/ux-components/alert/alert.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
// Supporting files
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
import { Form } from 'vee-validate';
import { useMainStore } from '@/store';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import globalRules from '@/constants/global-rules.js';
import widgetFields from '@/constants/cms-widget-fields.js';
import routerParams from '@/router/router-constants/router-params';
export default {
name: 'tpa-search',
components: {
siteHeader,
textboxQuestion,
dropdownQuestion,
buttonQuestion,
textLink,
alert,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}];
// use resultMap to populate layout content.
}
];
const resultMap = await settleAllPromises(promiseResultMap);
const radiusOptions = [25, 50, 100];
const { zipCode } = useMainStore().order.customer.address;
const tpaProvidersRadius25 = await useMainStore().getTpaProviders(zipCode, radiusOptions[0]);
const tpaProvidersRadius50 = await useMainStore().getTpaProviders(zipCode, radiusOptions[1]);
const tpaProvidersRadius100 = await useMainStore().getTpaProviders(zipCode, radiusOptions[2]);
let radius = '25 miles';
let providers = tpaProvidersRadius25;
if ((tpaProvidersRadius25?.data ?? []).length === 0) {
radius = '50 miles';
providers = tpaProvidersRadius50;
if ((tpaProvidersRadius50?.data ?? []).length === 0) {
radius = '100 miles';
providers = tpaProvidersRadius100;
}
}
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.setFilter(radius);
vm.setProviders(providers);
});
},
data() {
const { zipCode } = useMainStore().order.customer.address;
return {
zipCode,
filter: '',
providers: [],
selectedProviderNumber: '',
additionalButtonData: {
displayAvailabilityIndicators: false
},
widget: {
siteHeader: 'SiteHeaderWidget',
tpaSearchQuestion: 'TPASearchQuestion',
searchInstructions: 'SearchInstructions',
filterByQuestion: 'FilterByQuestion',
noNetworkShopsAlert: 'NoNetworkShopsAlertWidget',
shopNotListedLink: 'ShopNotListedLink',
siteFooter: 'SiteFooterWidget'
},
rules: {
zipCode: `${globalRules.ZIP_CODE_REQUIRED}|${globalRules.ZIP_CODE_SEARCH_FORMAT}`,
filter: globalRules.OPTION_REQUIRED, // TODO do we even need this?
provider: globalRules.OPTION_REQUIRED
},
shopListButton
};
},
computed: {
filterOptions() {
const filterByAnswers = this.getCmsContent(
this.widget.filterByQuestion,
widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
) ?? [];
const filterByAnswersObj = {};
[...filterByAnswers].forEach((answer) => {
filterByAnswersObj[answer.Name] = answer.Name;
});
return filterByAnswersObj;
},
tpaSearchQuestionLabel() {
return this.getCmsContent(
this.widget.tpaSearchQuestion,
widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT
);
},
searchInstructionsText() {
return this.getCmsContent(
this.widget.searchInstructions,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
},
shopNotListedModalLink() {
return this.getCmsContent(
this.widget.shopNotListedLink,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
},
radiusInMiles() {
switch (this.filter) {
case '25 miles':
return 25;
case '50 miles':
return 50;
case '100 miles':
return 100;
default:
return 0;
}
},
noNetworkShopsAlertHeaderText() {
return this.getCmsContent(
this.widget.noNetworkShopsAlert,
widgetFields.ALERT_WIDGET.HEADLINE_TEXT
)?.replaceAll('{custom:radiusInMiles}', this.radiusInMiles);
}
},
watch: {
async filter() {
// TODO right now this results in getProviders being called once more than it needs to be
this.providers = await this.getProviders();
},
providers(newProviders) {
this.selectedProviderNumber = newProviders?.length === 1 ?? false
? newProviders[0].value
: '';
}
},
methods:
{
async getProviders() {
const getTpaProvidersResult = await useMainStore().getTpaProviders(this.zipCode, this.radiusInMiles);
return getTpaProvidersResult?.data ?? [];
},
doNotSeeMyShopLinkClick() {
this.$router.navigate(
this.navigationScenarios.CLICKED_DO_NOT_SEE_MY_SHOP_LINK,
this.$route
);
},
async searchClick() {
this.providers = await this.getProviders();
},
backButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
},
forwardButtonAction() {
return this.navigateForward();
},
navigateForward() {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
},
getCustomValueFromString(str) {
switch (str) {
case 'radiusInMiles':
return this.radiusInMiles;
default:
return null;
}
},
setFilter(filter) {
this.filter = filter;
},
setProviders(providers) {
this.providers = providers;
}
}
};
</script>
<style lang="scss" scoped>
.search p{
margin-bottom:0.75rem;
.search-question {
font-size: $h5-font-size;
}
.darker-gray {
color: map-get($colors, "darker-gray");
}
</style>

View file

@ -154,7 +154,8 @@ router.navigateWithoutSaving = (
optionalParams = {},
optionalPageData = {}
) => {
navigate(scenario, currentRoute, false, optionalQuery, optionalParams, optionalPageData);
// TODO does not save session
navigate(scenario, currentRoute, optionalQuery, optionalParams, optionalPageData);
};
// Get route information by page name.
@ -252,7 +253,7 @@ function navigate(
query: Object.assign(optionalQuery, {
issPage: matchingScenarioMap.destinationIssPageValue
}),
params: optionalParams
params: optionalParams // TODO I don't think this actually works
});
} else if (matchingScenarioMap.destinationUrl) {
navigateToUrl(matchingScenarioMap.destinationUrl, optionalQuery);

View file

@ -63,6 +63,9 @@ const navigationScenarios = Object.freeze({
CLICKED_BACK_WITH_REPAIR: 'CLICKED_BACK_WITH_REPAIR',
CLICKED_FORWARD_WITH_INVALID_STATE: 'CLICKED_FORWARD_WITH_INVALID_STATE',
// TPA Search
CLICKED_DO_NOT_SEE_MY_SHOP_LINK: 'CLICKED_DO_NOT_SEE_MY_SHOP_LINK',
// Provider Preference
CLICKED_FORWARD_WITH_SAFELITE: 'CLICKED_FORWARD_WITH_SAFELITE',
CLICKED_FORWARD_WITH_TPA_ENABLED: 'CLICKED_FORWARD_WITH_TPA_ENABLED',

View file

@ -1,6 +1,7 @@
const routerParams = Object.freeze({
DISPLAY_VEHICLE_CHANGE_ALERT: 'displayVehicleChangeAlert',
SAVE_SESSION_SYNCHRONOUS: 'saveSessionSynchronous'
SAVE_SESSION_SYNCHRONOUS: 'saveSessionSynchronous',
NOT_SEEING_PREFERRED_SHOP: 'notSeeingPreferredShop'
});
export default routerParams;

View file

@ -115,7 +115,7 @@ const routingTable = () => [
destinationIssPageValue: issPageValues.VEHICLE_LOOKUP
},
{
scenario: issPageValues.BAILOUT_PAGE,
scenario: issPageValues.BAILOUT_PAGE, // TODO is this a bug
destinationIssPageValue: issPageValues.BAILOUT_PAGE
},
{
@ -666,6 +666,10 @@ const routingTable = () => [
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.TPA_SUBMIT
},
{
scenario: navigationScenarios.CLICKED_DO_NOT_SEE_MY_SHOP_LINK,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},

View file

@ -106,6 +106,7 @@ const getDefaultState = () => ({
zipCodeCtu: null,
appointmentType: null,
isVehicleProtected: null,
IsSafeliteProvider: null,
provider: {
providerNumber: null,
address: {
@ -417,12 +418,12 @@ export const useMainStore = defineStore({
order.customer.address.streetAddress = insured?.address;
order.customer.address.city = insured?.city;
order.customer.address.state = insured?.state;
order.customer.address.zipCode = insured?.zipCode;
order.customer.address.zipCode = insured?.zipCode?.toString();
order.customer.firstName = insured?.firstName;
order.customer.lastName = insured?.lastName;
// populate additional fields
order.serviceLocation.zipCode = insured?.zipCode;
order.serviceLocation.zipCode = insured?.zipCode?.toString();
order.policy.policyData = responsePolicy.policyData;
// populate vehicles
@ -881,6 +882,43 @@ export const useMainStore = defineStore({
});
},
// TODO what info is returned here
getTpaProviders(zipCode, radius) {
return new Promise((resolve, reject) => {
if (radius === 25) {
resolve({});
} else if (radius === 50) {
resolve({
data: [
{
buttonLabel: 'USA Auto Glass',
buttonLabelSubCopy: '1.5 mi',
buttonBodyCopy: '760 Dearborn Park Ln, Worthington, OH 43085<br>614-123-5555',
value: '000123'
}
]
});
} else {
resolve({
data: [
{
buttonLabel: 'USA Auto Glass',
buttonLabelSubCopy: '1.5 mi',
buttonBodyCopy: '760 Dearborn Park Ln, Worthington, OH 43085<br>614-123-5555',
value: '000123'
},
{
buttonLabel: 'USA Auto Glass',
buttonLabelSubCopy: '1.5 mi',
buttonBodyCopy: '760 Dearborn Park Ln, Worthington, OH 43085<br>614-123-5555',
value: '000123'
}
]
});
}
});
},
async getSupportingItems() {
const glassPartsArray = this.order.lineItems.glassParts ?? [];
const { carId } = this.order.vehicle;
@ -1054,7 +1092,9 @@ export const useMainStore = defineStore({
noCoverage: policy.noCoverage,
policyLookupSuccessful: policy.policyLookupSuccessful,
originalDeductible: this.order.originalDeductible,
currentDeductible: this.order.currentDeductible
currentDeductible: this.order.currentDeductible,
IsItac: this.order.policy.isITAC,
OemEndorsement: policy.endorsements?.indexOf('OEM Approved') !== -1 ?? false
},
customer: {
address: {
@ -1062,7 +1102,7 @@ export const useMainStore = defineStore({
streetAddress2: customer.address?.streetAddress2,
city: customer.address?.city,
state: customer.address?.state,
zipCode: customer.address?.zipCode
zipCode: customer.address?.zipCode?.toString()
},
emailAddress: contactInfo.emailAddress,
firstName: contactInfo.firstName || customer.firstName,
@ -1096,6 +1136,7 @@ export const useMainStore = defineStore({
? AppointmentTypeStrings.MOBILE : serviceLocation.appointmentType,
isVehicleProtected: serviceLocation.isVehicleProtected,
provider: {
IsSafeliteProvider: serviceLocation?.IsSafeliteProvider,
providerNumber: serviceLocation.provider?.providerNumber,
address: {
streetAddress: serviceLocation.provider?.address?.streetAddress,
@ -1592,6 +1633,9 @@ export const useMainStore = defineStore({
updatePolicyITACFlag(isITAC) {
this.order.policy.isITAC = isITAC;
},
updateIsSafeliteProvider(isSafelite) {
this.order.serviceLocation.IsSafeliteProvider = isSafelite;
},
updateDeductible(finalDeductible) {
this.order.currentDeductible = finalDeductible;

View file

@ -1,4 +1,5 @@
$page-side-padding: 1.5rem;
$font-size: 0.875rem;
.page-container-grouped-styles {
> div.main-content-container {
@ -19,13 +20,14 @@ $page-side-padding: 1.5rem;
p {
line-height: 1.5rem;
font-size: $font-size;
&:last-child {
margin-bottom: 0;
}
}
span {
font-size: 0.875rem;
font-size: $font-size;
}
}
}

View file

@ -3,4 +3,7 @@ $svg-date-picker-forward-button: "data:image/svg+xml,%3Csvg width='7' height='12
$svg-drop-off-alert: "data:image/svg+xml,%3Csvg viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_13957_112512)'%3E%3Cpath d='M5.99865 0C4.81147 4.82643e-07 3.65095 0.352111 2.66392 1.01179C1.67688 1.67146 0.907678 2.60907 0.45361 3.70599C-0.000459241 4.80291 -0.11899 6.00986 0.113013 7.17415C0.345015 8.33845 0.917126 9.40778 1.75697 10.2469C2.59682 11.086 3.66666 11.6571 4.83117 11.8881C5.99567 12.119 7.20251 11.9994 8.29902 11.5443C9.39553 11.0893 10.3324 10.3192 10.9912 9.33159C11.65 8.34396 12.0011 7.18313 12 5.99594C11.9971 4.40566 11.3638 2.88142 10.2388 1.75742C9.11375 0.633431 7.58894 0.00143011 5.99865 0V0ZM5.99865 11.2478C4.96135 11.2473 3.94748 10.9392 3.08518 10.3627C2.22288 9.7861 1.55085 8.96685 1.15401 8.00846C0.75718 7.05006 0.653353 5.99554 0.855656 4.97815C1.05796 3.96077 1.55731 3.02618 2.29061 2.29251C3.0239 1.55884 3.95823 1.059 4.97551 0.856176C5.99279 0.653349 7.04737 0.756633 8.00597 1.15297C8.96457 1.54931 9.78416 2.22092 10.3612 3.08293C10.9382 3.94493 11.2467 4.95864 11.2478 5.99594C11.2478 7.38835 10.6949 8.72377 9.71053 9.70861C8.7262 10.6934 7.39106 11.2471 5.99865 11.2478V11.2478Z' fill='%2306577C'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.22736 8.84695C6.30613 8.76818 6.35038 8.66135 6.35038 8.54996V5.30996C6.35038 5.19857 6.30613 5.09174 6.22736 5.01298C6.1486 4.93421 6.04177 4.88996 5.93038 4.88996C5.81899 4.88996 5.71216 4.93421 5.63339 5.01298C5.55463 5.09174 5.51038 5.19857 5.51038 5.30996V8.54996C5.51038 8.66135 5.55463 8.76818 5.63339 8.84695C5.71216 8.92571 5.81899 8.96996 5.93038 8.96996C6.04177 8.96996 6.1486 8.92571 6.22736 8.84695ZM5.69704 3.97918C5.76611 4.02533 5.84731 4.04996 5.93038 4.04996C5.98558 4.05012 6.04026 4.03936 6.09129 4.01831C6.14232 3.99726 6.18868 3.96633 6.22771 3.9273C6.26675 3.88827 6.29768 3.8419 6.31873 3.79088C6.33978 3.73985 6.35053 3.68516 6.35038 3.62996C6.35038 3.54689 6.32574 3.46569 6.27959 3.39662C6.23344 3.32755 6.16785 3.27372 6.0911 3.24193C6.01436 3.21014 5.92991 3.20183 5.84844 3.21803C5.76697 3.23424 5.69213 3.27424 5.63339 3.33298C5.57465 3.39171 5.53465 3.46655 5.51845 3.54802C5.50224 3.6295 5.51056 3.71394 5.54235 3.79069C5.57414 3.86743 5.62797 3.93303 5.69704 3.97918Z' fill='%2306577C'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_13957_112512'%3E%3Crect width='12' height='12' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A";
$svg-shop-list-button-green-availability: "data:image/svg+xml,%3Csvg viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 12C3.19159 12 0.5 9.30841 0.5 6C0.5 2.69159 3.19159 0 6.5 0C9.80841 0 12.5 2.69159 12.5 6C12.5 9.30841 9.80841 12 6.5 12ZM6.5 0.785047C3.62449 0.785047 1.28505 3.12449 1.28505 6C1.28505 8.87551 3.62449 11.215 6.5 11.215C9.37551 11.215 11.715 8.87551 11.715 6C11.715 3.12449 9.37551 0.785047 6.5 0.785047Z' fill='%23006A36'/%3E%3Cpath d='M5.697 7.95252C5.5927 7.95252 5.49289 7.91102 5.41999 7.837L3.90597 6.32299C3.75233 6.16934 3.75233 5.92149 3.90597 5.76785C4.05962 5.6142 4.30747 5.6142 4.46111 5.76785L5.69812 7.00373L8.53999 4.16186C8.69364 4.00822 8.94149 4.00822 9.09513 4.16186C9.24878 4.31551 9.24878 4.56336 9.09513 4.717L5.97626 7.83588C5.90224 7.9099 5.80242 7.9514 5.69925 7.9514L5.697 7.95252Z' fill='%23006A36'/%3E%3C/svg%3E%0A";
$svg-shop-list-button-orange-availability: "data:image/svg+xml,%3Csvg viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 0C9.81368 0 12.5 2.68632 12.5 6C12.5 9.31368 9.81368 12 6.5 12C3.18632 12 0.5 9.31368 0.5 6C0.5 2.68632 3.18632 0 6.5 0ZM6.5 0.84C3.6548 0.84 1.34 3.1548 1.34 6C1.34 8.8452 3.6548 11.16 6.5 11.16C9.3452 11.16 11.66 8.8452 11.66 6C11.66 3.1548 9.3452 0.84 6.5 0.84ZM7.90018 4.00596C8.06422 3.84204 8.33002 3.84192 8.49406 4.00596C8.6581 4.17 8.6581 4.43592 8.49406 4.59996L7.0939 6L8.49406 7.40004C8.6581 7.56408 8.6581 7.83 8.49406 7.99404C8.4121 8.076 8.30458 8.11704 8.19706 8.11704C8.08966 8.11704 7.98214 8.076 7.90018 7.99404L6.50002 6.594L5.09986 7.99404C5.01778 8.076 4.91038 8.11704 4.80286 8.11704C4.69546 8.11704 4.58794 8.076 4.50598 7.99404C4.34182 7.83 4.34182 7.56408 4.50598 7.40004L5.90614 6L4.50598 4.59996C4.34182 4.43592 4.34182 4.17 4.50598 4.00596C4.66978 3.84192 4.93582 3.84204 5.09986 4.00596L6.50002 5.406L7.90018 4.00596Z' fill='%23E86421'/%3E%3C/svg%3E%0A";
$svg-update-zip-text-link: "data:image/svg+xml,%3Csvg viewBox='0 0 13 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.49635 1.00142e-07C5.64734 -0.000153295 4.80722 0.175918 4.0274 0.517444C3.24757 0.858969 2.54443 1.35877 1.96099 1.98626C0.765713 3.27588 0.0999756 4.98141 0.0999756 6.75394C0.0999756 8.52646 0.765713 10.232 1.96099 11.5216L5.98324 15.777C6.04954 15.8475 6.12918 15.9036 6.21736 15.9419C6.30555 15.9802 6.40045 16 6.49635 16C6.59225 16 6.68716 15.9802 6.77534 15.9419C6.86353 15.9036 6.94317 15.8475 7.00946 15.777L11.0317 11.52C12.2391 10.2383 12.909 8.52914 12.8999 6.75394C12.9094 4.97818 12.2394 3.26832 11.0317 1.98626C10.4481 1.35899 9.74493 0.859347 8.96514 0.517839C8.18535 0.176331 7.34532 0.000130509 6.49635 1.00142e-07V1.00142e-07ZM6.49635 9.13131C6.02507 9.13131 5.56437 8.98913 5.17251 8.72275C4.78065 8.45637 4.47524 8.07776 4.29488 7.63479C4.11453 7.19181 4.06734 6.70438 4.15928 6.23412C4.25123 5.76387 4.47817 5.33191 4.81142 4.99287C5.14467 4.65384 5.56925 4.42295 6.03148 4.32941C6.49371 4.23587 6.97282 4.28388 7.40823 4.46736C7.84364 4.65085 8.21579 4.96157 8.47762 5.36023C8.73945 5.7589 8.87921 6.2276 8.87921 6.70707C8.87921 7.34974 8.62837 7.96611 8.18185 8.4207C7.73532 8.87528 7.12964 9.13088 6.49794 9.13131H6.49635Z' fill='%231574A1'/%3E%3C/svg%3E%0A";
$svg-update-zip-text-link: "data:image/svg+xml,%3Csvg viewBox='0 0 13 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.49635 1.00142e-07C5.64734 -0.000153295 4.80722 0.175918 4.0274 0.517444C3.24757 0.858969 2.54443 1.35877 1.96099 1.98626C0.765713 3.27588 0.0999756 4.98141 0.0999756 6.75394C0.0999756 8.52646 0.765713 10.232 1.96099 11.5216L5.98324 15.777C6.04954 15.8475 6.12918 15.9036 6.21736 15.9419C6.30555 15.9802 6.40045 16 6.49635 16C6.59225 16 6.68716 15.9802 6.77534 15.9419C6.86353 15.9036 6.94317 15.8475 7.00946 15.777L11.0317 11.52C12.2391 10.2383 12.909 8.52914 12.8999 6.75394C12.9094 4.97818 12.2394 3.26832 11.0317 1.98626C10.4481 1.35899 9.74493 0.859347 8.96514 0.517839C8.18535 0.176331 7.34532 0.000130509 6.49635 1.00142e-07V1.00142e-07ZM6.49635 9.13131C6.02507 9.13131 5.56437 8.98913 5.17251 8.72275C4.78065 8.45637 4.47524 8.07776 4.29488 7.63479C4.11453 7.19181 4.06734 6.70438 4.15928 6.23412C4.25123 5.76387 4.47817 5.33191 4.81142 4.99287C5.14467 4.65384 5.56925 4.42295 6.03148 4.32941C6.49371 4.23587 6.97282 4.28388 7.40823 4.46736C7.84364 4.65085 8.21579 4.96157 8.47762 5.36023C8.73945 5.7589 8.87921 6.2276 8.87921 6.70707C8.87921 7.34974 8.62837 7.96611 8.18185 8.4207C7.73532 8.87528 7.12964 9.13088 6.49794 9.13131H6.49635Z' fill='%231574A1'/%3E%3C/svg%3E%0A";
$svg-search-icon: "data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.7817 14.7328L11.8252 10.7762C12.8833 9.45005 13.3936 7.76911 13.2513 6.07849C13.1091 4.38788 12.325 2.81587 11.0601 1.6852C9.79515 0.554524 8.14538 -0.0490261 6.44946 -0.00154744C4.75353 0.0459312 3.14012 0.740836 1.94045 1.94051C0.740775 3.14018 0.0458701 4.75359 -0.00160848 6.44952C-0.0490871 8.14545 0.554463 9.79521 1.68514 11.0601C2.81581 12.325 4.38782 13.1091 6.07843 13.2514C7.76905 13.3937 9.44999 12.8834 10.7762 11.8252L14.7349 15.7839C14.8044 15.8527 14.8869 15.907 14.9774 15.9439C15.068 15.9808 15.165 15.9995 15.2628 15.9989C15.3606 15.9983 15.4573 15.9784 15.5475 15.9405C15.6376 15.9025 15.7194 15.8471 15.7881 15.7776C15.8568 15.708 15.9112 15.6256 15.9481 15.535C15.985 15.4444 16.0036 15.3474 16.0031 15.2496C16.0025 15.1518 15.9826 15.0551 15.9446 14.965C15.9067 14.8748 15.8513 14.7931 15.7817 14.7243V14.7328ZM6.63737 11.7913C5.61803 11.7913 4.62157 11.4891 3.77402 10.9228C2.92646 10.3564 2.26587 9.5515 1.87578 8.60975C1.4857 7.668 1.38363 6.63172 1.5825 5.63196C1.78136 4.6322 2.27222 3.71386 2.99301 2.99307C3.7138 2.27229 4.63214 1.78142 5.6319 1.58256C6.63166 1.38369 7.66793 1.48576 8.60969 1.87585C9.55144 2.26593 10.3564 2.92652 10.9227 3.77408C11.489 4.62163 11.7913 5.61809 11.7913 6.63743C11.7896 8.00382 11.2461 9.31376 10.2799 10.2799C9.3137 11.2461 8.00376 11.7897 6.63737 11.7913Z' fill='%231574A1'/%3E%3C/svg%3E%0A";
$svg-select-icon: "data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e";
$svg-calendar-picker: "data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.1762 1.52764H13.7656V1.50352C13.7656 1.10476 13.6076 0.722334 13.3263 0.44037C13.0451 0.158406 12.6636 0 12.2659 0C11.8681 0 11.4866 0.158406 11.2054 0.44037C10.9241 0.722334 10.7661 1.10476 10.7661 1.50352V1.52764H5.42476V1.50352C5.42476 1.10476 5.26675 0.722334 4.9855 0.44037C4.70424 0.158406 4.32277 0 3.92501 0C3.52725 0 3.14579 0.158406 2.86453 0.44037C2.58327 0.722334 2.42526 1.10476 2.42526 1.50352V1.52764H1.82376C1.34046 1.52891 0.877316 1.72195 0.53557 2.06455C0.193824 2.40716 0.00127018 2.87146 0 3.35598V14.1717C0.0016909 14.656 0.194379 15.1201 0.536035 15.4626C0.87769 15.8051 1.34059 15.9983 1.82376 16H14.1746C14.6581 15.9987 15.1214 15.8057 15.4634 15.4632C15.8054 15.1206 15.9983 14.6563 16 14.1717V3.35598C15.9987 2.87146 15.8062 2.40716 15.4644 2.06455C15.1227 1.72195 14.6595 1.52891 14.1762 1.52764ZM11.8889 1.50352C11.8889 1.4033 11.9286 1.30718 11.9993 1.23631C12.07 1.16544 12.1659 1.12563 12.2659 1.12563C12.3658 1.12563 12.4617 1.16544 12.5324 1.23631C12.6031 1.30718 12.6428 1.4033 12.6428 1.50352V2.99899C12.6428 3.09922 12.6031 3.19534 12.5324 3.2662C12.4617 3.33707 12.3658 3.37688 12.2659 3.37688C12.1659 3.37688 12.07 3.33707 11.9993 3.2662C11.9286 3.19534 11.8889 3.09922 11.8889 2.99899V1.50352ZM3.54807 1.50352C3.54807 1.4033 3.58778 1.30718 3.65847 1.23631C3.72916 1.16544 3.82504 1.12563 3.92501 1.12563C4.02498 1.12563 4.12086 1.16544 4.19155 1.23631C4.26224 1.30718 4.30195 1.4033 4.30195 1.50352V2.99899C4.30195 3.09922 4.26224 3.19534 4.19155 3.2662C4.12086 3.33707 4.02498 3.37688 3.92501 3.37688C3.82504 3.37688 3.72916 3.33707 3.65847 3.2662C3.58778 3.19534 3.54807 3.09922 3.54807 2.99899V1.50352ZM14.8772 14.1717C14.8747 14.3573 14.8001 14.5345 14.6691 14.6658C14.5382 14.797 14.3614 14.8719 14.1762 14.8744H1.82536C1.63995 14.8723 1.4627 14.7976 1.33144 14.6663C1.20018 14.5351 1.12531 14.3575 1.12281 14.1717V6.59296H14.8772V14.1717Z' fill='%23167CAC'/%3E%3Cpath d='M2.33063 11.282H3.93464V12.6006C3.93464 12.7499 3.99379 12.8931 4.09907 12.9986C4.20435 13.1041 4.34715 13.1634 4.49604 13.1634C4.64494 13.1634 4.78773 13.1041 4.89301 12.9986C4.9983 12.8931 5.05745 12.7499 5.05745 12.6006V11.282H7.46346V12.6006C7.46346 12.7499 7.52261 12.8931 7.62789 12.9986C7.73318 13.1041 7.87597 13.1634 8.02486 13.1634C8.17376 13.1634 8.31655 13.1041 8.42184 12.9986C8.52712 12.8931 8.58627 12.7499 8.58627 12.6006V11.282H10.9923V12.6006C10.9923 12.7499 11.0514 12.8931 11.1567 12.9986C11.262 13.1041 11.4048 13.1634 11.5537 13.1634C11.7026 13.1634 11.8454 13.1041 11.9507 12.9986C12.0559 12.8931 12.1151 12.7499 12.1151 12.6006V11.282H13.7191C13.868 11.282 14.0108 11.2227 14.1161 11.1172C14.2214 11.0116 14.2805 10.8685 14.2805 10.7192C14.2805 10.57 14.2214 10.4268 14.1161 10.3213C14.0108 10.2157 13.868 10.1564 13.7191 10.1564H12.1151V8.84425C12.1151 8.69498 12.0559 8.55183 11.9507 8.44628C11.8454 8.34073 11.7026 8.28143 11.5537 8.28143C11.4048 8.28143 11.262 8.34073 11.1567 8.44628C11.0514 8.55183 10.9923 8.69498 10.9923 8.84425V10.1628H8.58627V8.84425C8.58627 8.69498 8.52712 8.55183 8.42184 8.44628C8.31655 8.34073 8.17376 8.28143 8.02486 8.28143C7.87597 8.28143 7.73318 8.34073 7.62789 8.44628C7.52261 8.55183 7.46346 8.69498 7.46346 8.84425V10.1628H5.05745V8.84425C5.05745 8.69498 4.9983 8.55183 4.89301 8.44628C4.78773 8.34073 4.64494 8.28143 4.49604 8.28143C4.34715 8.28143 4.20435 8.34073 4.09907 8.44628C3.99379 8.55183 3.93464 8.69498 3.93464 8.84425V10.1628H2.33063C2.18174 10.1628 2.03894 10.2221 1.93366 10.3277C1.82837 10.4332 1.76923 10.5764 1.76923 10.7257C1.76923 10.8749 1.82837 11.0181 1.93366 11.1236C2.03894 11.2292 2.18174 11.2885 2.33063 11.2885V11.282Z' fill='%23167CAC'/%3E%3C/svg%3E";

View file

@ -2,7 +2,7 @@
<button
type="button"
:aria-disabled="isDisabled"
class="btn d-flex align-items-center justify-content-center py-3 px-4 delay"
class="btn d-flex btn-override align-items-center justify-content-center py-3 px-4 delay"
:class="[
isPrimary ? 'btn-primary' : 'btn-secondary',
isFloat ? 'float-end' : '',