Fixed merge conflicts
This commit is contained in:
commit
9eb36449a7
24 changed files with 2543 additions and 497 deletions
64
.eslintrc.js
64
.eslintrc.js
|
|
@ -1,21 +1,45 @@
|
|||
module.exports = {
|
||||
extends: [
|
||||
'eslint-config-airbnb-base',
|
||||
'plugin:vue/vue3-strongly-recommended'
|
||||
],
|
||||
rules: {
|
||||
'linebreak-style': 'off',
|
||||
'vue/component-definition-name-casing': ['warn', 'kebab-case'],
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/attribute-hyphenation': ['warn', 'never'],
|
||||
'vue/v-on-event-hyphenation': ['warn', 'never']
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
alias: {
|
||||
map: [['@', './src/']],
|
||||
extensions: ['.js', '.vue']
|
||||
}
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
jest: true
|
||||
},
|
||||
extends: [
|
||||
'eslint-config-airbnb-base',
|
||||
'plugin:vue/vue3-recommended'
|
||||
],
|
||||
rules: {
|
||||
'linebreak-style': 'off',
|
||||
'vue/component-definition-name-casing': ['warn', 'kebab-case'],
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/attribute-hyphenation': ['warn', 'never'],
|
||||
'vue/v-on-event-hyphenation': ['warn', 'never'],
|
||||
'object-curly-newline': ['error', { consistent: true }],
|
||||
'function-paren-newline': ['error', 'never'],
|
||||
'operator-linebreak': ['error', 'before'],
|
||||
'implicit-arrow-linebreak': ['error', 'below'],
|
||||
'comma-dangle': ['error', 'never'],
|
||||
indent: ['error', 4],
|
||||
'vue/html-indent': 'off',
|
||||
'vue/html-closing-bracket-newline': ['error', {
|
||||
singleline: 'never',
|
||||
multiline: 'never'
|
||||
}],
|
||||
'vue/html-self-closing': ['error', {
|
||||
html: {
|
||||
void: 'any',
|
||||
normal: 'any',
|
||||
component: 'any'
|
||||
},
|
||||
svg: 'always',
|
||||
math: 'always'
|
||||
}]
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
alias: {
|
||||
map: [['@', './src/']],
|
||||
extensions: ['.js', '.vue']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
1036
package-lock.json
generated
1036
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -40,6 +40,7 @@
|
|||
"eslint-plugin-vue": "9.8.0",
|
||||
"jest": "^27.0.5",
|
||||
"sass": "^1.32.7",
|
||||
"sass-loader": "^12.0.0"
|
||||
"sass-loader": "^12.0.0",
|
||||
"vitest": "^0.32.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
src/constants/global-rules.js
Normal file
22
src/constants/global-rules.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* @file global-rules.js
|
||||
* @author MB
|
||||
* @copyright Safelite
|
||||
*/
|
||||
|
||||
/**
|
||||
* @summary Contains all the globally defined rules.
|
||||
*/
|
||||
const globalRules = {
|
||||
POLICYHOLDER_FIRST_NAME_REQUIRED: 'policyholder-first-name-required',
|
||||
POLICYHOLDER_LAST_NAME_REQUIRED: 'policyholder-last-name-required',
|
||||
FIRST_NAME_REQUIRED: 'first-name-required',
|
||||
LAST_NAME_REQUIRED: 'last-name-required',
|
||||
PHONE_NUMBER_REQUIRED: 'phone-number-required',
|
||||
EMAIL_ADDRESS_REQUIRED: 'email-required',
|
||||
EMAIL_ADDRESS_FORMAT: 'email-address-format',
|
||||
PHONE_NUMBER_FORMAT: 'phone-number-format',
|
||||
OPTION_REQUIRED: 'option-required'
|
||||
};
|
||||
|
||||
export default globalRules;
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
// Components
|
||||
import textareaQuestion from '@/digital-components/textarea-question/textarea-question.vue';
|
||||
|
||||
// Supporting Files
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { getRandomString, getRandomInt } from '@/helpers/data-generation';
|
||||
import { nextTick } from 'vue';
|
||||
import { defineRule } from 'vee-validate';
|
||||
import { required } from '@/helpers/validation-rules';
|
||||
|
||||
describe('textarea-question.vue', () => {
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn().mockImplementation(() =>
|
||||
getRandomString(10, 20)),
|
||||
setCmsContent: jest.fn()
|
||||
}
|
||||
};
|
||||
describe('Expected components rendered', () => {
|
||||
test('Label rendered', () => {
|
||||
// Arrange
|
||||
const questionText = getRandomString(10, 20);
|
||||
const mixinWithQuestion = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn().mockImplementation(() =>
|
||||
questionText),
|
||||
setCmsContent: jest.fn()
|
||||
}
|
||||
};
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20)
|
||||
},
|
||||
mixins: [mixinWithQuestion]
|
||||
});
|
||||
|
||||
// Act
|
||||
const label = wrapper.find('label');
|
||||
|
||||
// Assert
|
||||
expect(label.exists()).toBe(true);
|
||||
expect(wrapper.text()).toContain(questionText);
|
||||
});
|
||||
test('Textarea rendered', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20)
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
const textarea = wrapper.find('textarea');
|
||||
|
||||
// Assert
|
||||
expect(textarea.exists()).toBe(true);
|
||||
});
|
||||
test('Character count rendered', () => {
|
||||
// Arrange
|
||||
const maxLength = getRandomInt(100, 900);
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
maxLength
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
const expected = `0/${maxLength}`;
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
expect(wrapper.text()).toContain(expected);
|
||||
});
|
||||
test('Error rendered', async () => {
|
||||
// Arrange
|
||||
const maxLength = getRandomInt(100, 900);
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
maxLength
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
const errorComponent = wrapper.find('#error-message');
|
||||
expect(errorComponent.text()).toBe('');
|
||||
|
||||
const errorMessage = getRandomString(20, 30);
|
||||
wrapper.vm.resetField({
|
||||
errors: [errorMessage]
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
// Assert
|
||||
expect(errorComponent.text()).toBe(errorMessage);
|
||||
});
|
||||
test('Not required => optional text rendered', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
isRequired: false
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
expect(wrapper.text()).toContain('Optional');
|
||||
});
|
||||
test('Required => optional text not rendered', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
isRequired: true
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
expect(wrapper.text()).not.toContain('Optional');
|
||||
});
|
||||
});
|
||||
describe('Computed properties rendered', () => {
|
||||
describe('characterCount', () => {
|
||||
test('Model value null => character count is 0 ', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
modelValue: null
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
expect(wrapper.vm.characterCount).toBe(0);
|
||||
});
|
||||
test('Model value undefined => character count is 0', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
modelValue: undefined
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
expect(wrapper.vm.characterCount).toBe(0);
|
||||
});
|
||||
test('Model value empty string => character count is 0', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
modelValue: ''
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
expect(wrapper.vm.characterCount).toBe(0);
|
||||
});
|
||||
test('Model value non-empty string => character count is as expected', () => {
|
||||
// Arrange
|
||||
const length = getRandomInt(10, 30);
|
||||
const value = getRandomString(length, length);
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
modelValue: value
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
expect(wrapper.vm.characterCount).toBe(length);
|
||||
});
|
||||
});
|
||||
describe('value', () => {
|
||||
test('Get value corresponds to modelValue', () => {
|
||||
// Arrange
|
||||
const modelValue = getRandomString(10, 20);
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
modelValue
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
expect(wrapper.vm.value).toBe(modelValue);
|
||||
});
|
||||
test('Set value => update modelValue event emitted with new value', () => {
|
||||
// Arrange
|
||||
const modelValue = getRandomString(10, 20);
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
modelValue
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
const newValue = getRandomString(10, 20);
|
||||
|
||||
// Act
|
||||
wrapper.vm.value = newValue;
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
||||
expect(wrapper.emitted('update:modelValue')[0][0]).toEqual(newValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Properties properly affect initial state', () => {
|
||||
test('Placeholder text sets placeholder text of text area', () => {
|
||||
// Arrange
|
||||
const placeholderText = getRandomString(10, 20);
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
placeholderText
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
const textarea = wrapper.find('textarea');
|
||||
|
||||
// Assert
|
||||
expect(textarea.attributes().placeholder).toBe(placeholderText);
|
||||
});
|
||||
test('isDisabled true => textarea disabled', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
isDisabled: true
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
const textarea = wrapper.find('textarea');
|
||||
|
||||
// Assert
|
||||
expect(textarea.attributes().disabled).toBeDefined();
|
||||
});
|
||||
test('isRequired true => textarea required', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
isRequired: true
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
const textarea = wrapper.find('textarea');
|
||||
|
||||
// Assert
|
||||
expect(textarea.attributes().required).toBeDefined();
|
||||
});
|
||||
test('validationRules set rules of text area', () => {
|
||||
// Arrange
|
||||
const rule = getRandomString(10, 20);
|
||||
const errorMessage = getRandomString(20, 30);
|
||||
defineRule(rule, required(errorMessage));
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
validationRules: rule
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
const textarea = wrapper.find('textarea');
|
||||
|
||||
// Assert
|
||||
expect(textarea.attributes().validationrules).toBe(rule);
|
||||
});
|
||||
test('maxLength set max length of text area', () => {
|
||||
// Arrange
|
||||
const maxLength = toString(getRandomInt(100, 900));
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
maxLength
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
const textarea = wrapper.find('textarea');
|
||||
|
||||
// Assert
|
||||
expect(textarea.attributes().maxlength).toBe(maxLength);
|
||||
});
|
||||
test('inputRows set input rows of text area', () => {
|
||||
// Arrange
|
||||
const inputRows = getRandomInt(10, 20);
|
||||
const wrapper = shallowMount(textareaQuestion, {
|
||||
propsData: {
|
||||
inputId: getRandomString(10, 20),
|
||||
cmsWidgetName: getRandomString(10, 20),
|
||||
inputRows
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
// Act
|
||||
const textarea = wrapper.find('textarea');
|
||||
|
||||
// Assert
|
||||
expect(textarea.attributes().rows).toBe(`${inputRows}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
197
src/digital-components/textarea-question/textarea-question.vue
Normal file
197
src/digital-components/textarea-question/textarea-question.vue
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
<template>
|
||||
<div
|
||||
class="textarea-question"
|
||||
:class="(errors && errors.length) ? 'has-error' : ''">
|
||||
<label
|
||||
:for="inputId"
|
||||
class="form-label">
|
||||
{{ questionText }}
|
||||
<span
|
||||
v-if="!isRequired"
|
||||
class="optional">(Optional)</span>
|
||||
</label>
|
||||
<div class="input-wrapper">
|
||||
<textarea
|
||||
:id="inputId"
|
||||
:ref="inputId"
|
||||
v-model.trim="value"
|
||||
class="form-control"
|
||||
:name="inputId"
|
||||
:rows="[inputRows ?? 10]"
|
||||
:placeholder="placeholderText"
|
||||
:disabled="isDisabled"
|
||||
:required="isRequired"
|
||||
:maxlength="maxLength"
|
||||
:validationRules="validationRules"
|
||||
@change="validationRules ? handleChange : () => {}"
|
||||
@blur="validationRules ? handleBlur : () => {}"
|
||||
@paste="trimOnPaste"
|
||||
@drop="trimOnPaste">
|
||||
</textarea>
|
||||
</div>
|
||||
<p class="character-count margin-top-8">
|
||||
{{ characterCount }}/{{ maxLength }} characters remaining
|
||||
</p>
|
||||
<div
|
||||
v-show="errorMessage"
|
||||
id="error-message"
|
||||
class="row my-1 form-test-error">
|
||||
<span
|
||||
class="d-inline-flex mt-0"
|
||||
role="alert">{{ errorMessage }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useField } from 'vee-validate';
|
||||
|
||||
export default {
|
||||
name: 'textarea-question',
|
||||
props: {
|
||||
placeholderText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
inputId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
modelValue: String,
|
||||
isDisabled: Boolean,
|
||||
isRequired: Boolean,
|
||||
validationRules: String,
|
||||
cmsWidgetName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
maxLength: {
|
||||
type: String,
|
||||
default: '300'
|
||||
},
|
||||
inputRows: Number
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props) {
|
||||
const propsClone = { ...props };
|
||||
const { modelValue } = propsClone;
|
||||
const initialValue = modelValue ?? '';
|
||||
|
||||
const fieldOptions = {
|
||||
type: 'text',
|
||||
value: modelValue,
|
||||
initialValue
|
||||
};
|
||||
|
||||
const { errorMessage,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
validate,
|
||||
errors,
|
||||
resetField }
|
||||
= useField(props.inputId,
|
||||
props.validationRules,
|
||||
fieldOptions);
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
validate,
|
||||
errors,
|
||||
resetField
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* @summary Returns the number of characters in the textarea field.
|
||||
*/
|
||||
characterCount() {
|
||||
return this?.modelValue?.length ?? 0;
|
||||
},
|
||||
/**
|
||||
* @summary Returns the CMS text associated with the question.
|
||||
*/
|
||||
questionText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
|
||||
},
|
||||
value: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(newValue) {
|
||||
this.$emit('update:modelValue', newValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @summary Removes whitespace from the end of pasted content.
|
||||
*/
|
||||
trimOnPaste(evt) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
|
||||
const data = evt.type === 'paste' ? (evt.clipboardData || window.clipboardData) : evt.dataTransfer;
|
||||
const value = data.getData('Text')?.trim();
|
||||
|
||||
this.$emit('update:modelValue', value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.textarea-question {
|
||||
label {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
}
|
||||
.form-test-error span{
|
||||
color: $red;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
max-height: 30rem;
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: 0.5rem;
|
||||
padding: 12px 16px;
|
||||
&::placeholder {
|
||||
color: $gray-500;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid $gray-500;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.optional {
|
||||
color: $gray-550;
|
||||
}
|
||||
|
||||
.character-count {
|
||||
color: $gray-600;
|
||||
font-size: 12px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.margin-top-8 {
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,325 +1,340 @@
|
|||
<template>
|
||||
<div class="textbox-question" :class="(errors && errors.length) || hasError ? 'has-error' : ''">
|
||||
<label
|
||||
v-if="displayQuestionText"
|
||||
:for="inputId"
|
||||
:aria-label="questionText"
|
||||
class="form-label"
|
||||
:class="[questionAlignment === 'center' ? 'text-center w-100 mb-5' : '']"
|
||||
v-html="labelText"></label>
|
||||
<div
|
||||
class="input-wrapper"
|
||||
:class="[
|
||||
includeSearchIcon ? 'has-search-icon' : '',
|
||||
includeSelectIcon ? 'has-select-icon' : '',
|
||||
]">
|
||||
<input
|
||||
class="form-control"
|
||||
v-model.trim.lazy="value"
|
||||
v-maska="mask"
|
||||
:type="type"
|
||||
:ref="inputId"
|
||||
:id="inputId"
|
||||
:name="inputId"
|
||||
:placeholder="placeholderText"
|
||||
:aria-disabled="isDisabled"
|
||||
:disabled="isDisabled"
|
||||
:aria-required="isRequired"
|
||||
:aria-label="questionText"
|
||||
:min="min"
|
||||
:max="max"
|
||||
required
|
||||
:class="[
|
||||
hasIcon ? 'has-icon' : '',
|
||||
iconRight ? 'icon-right' : '',
|
||||
cornerStyle === 'rounded' ? 'rounded-pill' : '',
|
||||
]"
|
||||
:validationRules="validationRules"
|
||||
@change="validationRules ? handleChange : () => {}"
|
||||
@blur="validationRules ? handleChange : () => {}"
|
||||
:maxlength="maxLength ? maxLength : '999'"
|
||||
@focus="$emit('focus', $event.target.value)"
|
||||
:data-bs-toggle="includeSelectIcon ? 'modal' : ''"
|
||||
:data-bs-target="'#' + this.cmsWidgetName"
|
||||
@paste="trimOnPaste"
|
||||
@drop="trimOnPaste"/>
|
||||
<button v-if="includeSearchIcon" type="submit" aria-label="Search button" />
|
||||
<button v-if="includeSelectIcon" type="submit" data-bs-toggle="modal"
|
||||
:data-bs-target="'#' + this.cmsWidgetName" aria-label="Select button" />
|
||||
</div>
|
||||
<div v-show="errorMessage" class="row my-1 form-test-error">
|
||||
<span class="d-inline-flex mt-0" role="alert">{{ errorMessage }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="textbox-question"
|
||||
:class="(errors && errors.length) || hasError ? 'has-error' : ''"
|
||||
>
|
||||
<label
|
||||
v-if="displayQuestionText"
|
||||
:for="inputId"
|
||||
:aria-label="questionText"
|
||||
class="form-label"
|
||||
:class="[questionAlignment === 'center' ? 'text-center w-100 mb-5' : '']"
|
||||
v-html="labelText"
|
||||
></label>
|
||||
<div
|
||||
class="input-wrapper"
|
||||
:class="[
|
||||
includeSearchIcon ? 'has-search-icon' : '',
|
||||
includeSelectIcon ? 'has-select-icon' : '',
|
||||
]"
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
v-model.trim.lazy="value"
|
||||
v-maska="mask"
|
||||
:type="type"
|
||||
:ref="inputId"
|
||||
:id="inputId"
|
||||
:name="inputId"
|
||||
:placeholder="placeholderText"
|
||||
:aria-disabled="isDisabled"
|
||||
:disabled="isDisabled"
|
||||
:aria-required="isRequired"
|
||||
:aria-label="questionText"
|
||||
:min="min"
|
||||
:max="max"
|
||||
required
|
||||
:class="[
|
||||
hasIcon ? 'has-icon' : '',
|
||||
iconRight ? 'icon-right' : '',
|
||||
cornerStyle === 'rounded' ? 'rounded-pill' : '',
|
||||
]"
|
||||
:validationRules="validationRules"
|
||||
@change="validationRules ? handleChange : () => {}"
|
||||
@blur="validationRules ? handleChange : () => {}"
|
||||
:maxlength="maxLength ? maxLength : '999'"
|
||||
@focus="$emit('focus', $event.target.value)"
|
||||
:data-bs-toggle="includeSelectIcon ? 'modal' : ''"
|
||||
:data-bs-target="'#' + this.cmsWidgetName"
|
||||
@paste="trimOnPaste"
|
||||
@drop="trimOnPaste"
|
||||
/>
|
||||
<button
|
||||
v-if="includeSearchIcon"
|
||||
type="submit"
|
||||
aria-label="Search button"
|
||||
/>
|
||||
<button
|
||||
v-if="includeSelectIcon"
|
||||
type="submit"
|
||||
data-bs-toggle="modal"
|
||||
:data-bs-target="'#' + this.cmsWidgetName"
|
||||
aria-label="Select button"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="errorMessage" class="row my-1 form-test-error">
|
||||
<span class="d-inline-flex mt-0" role="alert">{{ errorMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useField, validate } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "textbox-question",
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: "text",
|
||||
},
|
||||
placeholderText: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
displayQuestionText: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
modelValue: String,
|
||||
inputId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isDisabled: Boolean,
|
||||
isRequired: Boolean,
|
||||
hasIcon: Boolean, // If input has an icon
|
||||
iconRight: Boolean, // Place icon on right side of text input, otherwise default is left if hasIcon prop is used
|
||||
hasError: Boolean,
|
||||
mask: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
validationRules: String,
|
||||
cmsWidgetName: String,
|
||||
maxLength: String,
|
||||
questionAlignment: String, // Left or center. Left is default.
|
||||
cornerStyle: String, // Rounded or square. Square is default.
|
||||
includeSearchIcon: Boolean,
|
||||
includeSelectIcon: Boolean,
|
||||
min: String,
|
||||
max: String,
|
||||
disableAutoFill: Boolean
|
||||
name: "textbox-question",
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: "text",
|
||||
},
|
||||
setup(props) {
|
||||
const propsClone = Object.assign({}, props);
|
||||
const modelValue = propsClone.modelValue;
|
||||
let initialValue;
|
||||
placeholderText: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
displayQuestionText: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
modelValue: String,
|
||||
inputId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isDisabled: Boolean,
|
||||
isRequired: Boolean,
|
||||
hasIcon: Boolean, // If input has an icon
|
||||
iconRight: Boolean, // Place icon on right side of text input, otherwise default is left if hasIcon prop is used
|
||||
hasError: Boolean,
|
||||
mask: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
validationRules: String,
|
||||
cmsWidgetName: String,
|
||||
maxLength: String,
|
||||
questionAlignment: String, // Left or center. Left is default.
|
||||
cornerStyle: String, // Rounded or square. Square is default.
|
||||
includeSearchIcon: Boolean,
|
||||
includeSelectIcon: Boolean,
|
||||
min: String,
|
||||
max: String,
|
||||
disableAutoFill: Boolean,
|
||||
},
|
||||
setup(props) {
|
||||
const propsClone = Object.assign({}, props);
|
||||
const modelValue = propsClone.modelValue;
|
||||
let initialValue;
|
||||
|
||||
switch (typeof modelValue) {
|
||||
case "number":
|
||||
initialValue = modelValue;
|
||||
break;
|
||||
default:
|
||||
initialValue = modelValue && modelValue.length > 0 ? modelValue : "";
|
||||
break;
|
||||
switch (typeof modelValue) {
|
||||
case "number":
|
||||
initialValue = modelValue;
|
||||
break;
|
||||
default:
|
||||
initialValue = modelValue && modelValue.length > 0 ? modelValue : "";
|
||||
break;
|
||||
}
|
||||
|
||||
const fieldOptions = {
|
||||
type: "text",
|
||||
value: modelValue,
|
||||
initialValue: initialValue,
|
||||
};
|
||||
|
||||
const { errorMessage, handleBlur, handleChange, meta, validate, errors } =
|
||||
useField(props.inputId, props.validationRules, fieldOptions);
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
validate,
|
||||
meta,
|
||||
errors,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
trimOnPaste(evt) {
|
||||
evt.stopPropagation();
|
||||
preventDefault();
|
||||
|
||||
var data = null;
|
||||
if (evt.type === "paste") {
|
||||
data = evt.clipboardData || window.clipboardData;
|
||||
} else if (evt.type === "drop") {
|
||||
data = evt.dataTransfer;
|
||||
}
|
||||
const value = data.getData("Text")?.trim();
|
||||
|
||||
this.$emit("update:modelValue", value);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
questionText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
|
||||
},
|
||||
value: {
|
||||
get: function () {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function (newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
},
|
||||
labelText: {
|
||||
get: function () {
|
||||
const noBreakChar = "⁠";
|
||||
var questionText = "";
|
||||
if (this.disableAutoFill) {
|
||||
var words = this.questionText.toString().split(/[ ]+/);
|
||||
words.forEach(function (word) {
|
||||
const position = 1;
|
||||
word = [
|
||||
word.toString().slice(0, position),
|
||||
noBreakChar,
|
||||
word.toString().slice(position),
|
||||
].join("");
|
||||
questionText += `${word} `;
|
||||
});
|
||||
|
||||
questionText = questionText.trimEnd();
|
||||
} else {
|
||||
questionText = this.questionText.toString();
|
||||
}
|
||||
|
||||
const fieldOptions = {
|
||||
type: "text",
|
||||
value: modelValue,
|
||||
initialValue: initialValue,
|
||||
};
|
||||
|
||||
const { errorMessage, handleBlur, handleChange, meta, validate, errors } = useField(
|
||||
props.inputId,
|
||||
props.validationRules,
|
||||
fieldOptions
|
||||
);
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
validate,
|
||||
meta,
|
||||
errors,
|
||||
};
|
||||
return questionText;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
trimOnPaste(evt) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
|
||||
const data = evt.type == "paste" ? (evt.clipboardData || window.clipboardData) : evt.dataTransfer;
|
||||
const value = data.getData('Text')?.trim();
|
||||
|
||||
this.$emit("update:modelValue", value);
|
||||
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
questionText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
|
||||
},
|
||||
value: {
|
||||
get: function () {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function (newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
},
|
||||
labelText: {
|
||||
get: function () {
|
||||
const noBreakChar = "⁠";
|
||||
var questionText = "";
|
||||
if (this.disableAutoFill) {
|
||||
var words = this.questionText.toString().split(/[ ]+/);
|
||||
words.forEach(function (word) {
|
||||
const position = 1;
|
||||
word = [
|
||||
word.toString().slice(0, position),
|
||||
noBreakChar,
|
||||
word.toString().slice(position),
|
||||
].join("");
|
||||
questionText += `${word} `;
|
||||
});
|
||||
|
||||
questionText = questionText.trimEnd();
|
||||
} else {
|
||||
questionText = this.questionText.toString();
|
||||
}
|
||||
|
||||
return questionText;
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$emit("textboxQuestionEvent.inputIdAssigned", this.inputId);
|
||||
},
|
||||
watch: {
|
||||
async value(newValue) {
|
||||
const result = await validate(newValue, this.validationRules); // do a test validation check, without triggering full validation
|
||||
if (result.valid) {
|
||||
this.handleChange(newValue); // trigger full validation on this field only
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$emit("textboxQuestionEvent.inputIdAssigned", this.inputId);
|
||||
},
|
||||
watch: {
|
||||
async value(newValue) {
|
||||
const result = await validate(newValue, this.validationRules); // do a test validation check, without triggering full validation
|
||||
if (result.valid) {
|
||||
this.handleChange(newValue); // trigger full validation on this field only
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
input[type="date"]::-webkit-inner-spin-button{
|
||||
display: none;
|
||||
input[type="date"]::-webkit-inner-spin-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="date"]::-webkit-calendar-picker-indicator {
|
||||
position: absolute;
|
||||
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-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 16px; // check this
|
||||
height: 16px;
|
||||
display: flex;
|
||||
border-radius: 0 7px 7px 0;
|
||||
background-color: #E4F1F7;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
padding: 15px 0;
|
||||
min-width: 48px;
|
||||
position: absolute;
|
||||
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-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 16px; // check this
|
||||
height: 16px;
|
||||
display: flex;
|
||||
border-radius: 0 7px 7px 0;
|
||||
background-color: #e4f1f7;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
padding: 15px 0;
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
.textbox-question {
|
||||
label {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
}
|
||||
span{
|
||||
font-weight:400;
|
||||
font-size:14px;
|
||||
color: #4D5151;
|
||||
}
|
||||
.form-test-error span{
|
||||
color: #d4281c;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
label {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
}
|
||||
span {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #4d5151;
|
||||
}
|
||||
.form-test-error span {
|
||||
color: #d4281c;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
&.has-search-icon {
|
||||
input[type="text"] {
|
||||
border-radius: 50rem;
|
||||
}
|
||||
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-repeat: no-repeat;
|
||||
background-position: center;
|
||||
border-radius: 0 50rem 50rem 0;
|
||||
background-color: $blue-100;
|
||||
width: 2.75rem;
|
||||
height: 100%;
|
||||
border: 1px solid $gray-500;
|
||||
border-left: none;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
&.has-select-icon {
|
||||
button[type="submit"] {
|
||||
position: absolute;
|
||||
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-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-color: transparent;
|
||||
width: 1rem;
|
||||
height: 100%;
|
||||
border: 0px;
|
||||
border-left: none;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
input {
|
||||
&.has-icon {
|
||||
background-image: url(~@/assets/img/icons/location-pin.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0.75rem 50%;
|
||||
background-size: 1rem auto;
|
||||
padding: 0 0.75rem 0 2.5rem;
|
||||
&.icon-right {
|
||||
background-position: calc(100% - 0.75rem) 50%;
|
||||
padding: 0 2.5rem 0 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.form-control {
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
&.has-search-icon {
|
||||
input[type="text"] {
|
||||
border-radius: 50rem;
|
||||
}
|
||||
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-repeat: no-repeat;
|
||||
background-position: center;
|
||||
border-radius: 0 50rem 50rem 0;
|
||||
background-color: $blue-100;
|
||||
width: 2.75rem;
|
||||
height: 100%;
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: 0.5rem;
|
||||
min-height: 3rem;
|
||||
max-height: 48px;
|
||||
padding: 12px 16px;
|
||||
&::placeholder {
|
||||
color: $gray-500;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid $gray-500;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
border-left: none;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
p {
|
||||
display: none;
|
||||
&.has-select-icon {
|
||||
button[type="submit"] {
|
||||
position: absolute;
|
||||
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-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-color: transparent;
|
||||
width: 1rem;
|
||||
height: 100%;
|
||||
border: 0px;
|
||||
border-left: none;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
input {
|
||||
&.has-icon {
|
||||
background-image: url(~@/assets/img/icons/location-pin.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0.75rem 50%;
|
||||
background-size: 1rem auto;
|
||||
padding: 0 0.75rem 0 2.5rem;
|
||||
&.icon-right {
|
||||
background-position: calc(100% - 0.75rem) 50%;
|
||||
padding: 0 2.5rem 0 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.form-control {
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: 0.5rem;
|
||||
min-height: 3rem;
|
||||
max-height: 48px;
|
||||
padding: 12px 16px;
|
||||
&::placeholder {
|
||||
color: $gray-500;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid $gray-500;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
p {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
47
src/helpers/global-rule-definer.js
Normal file
47
src/helpers/global-rule-definer.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { defineRule } from 'vee-validate';
|
||||
import { errorMessages } from '@/constants/error-messages';
|
||||
import globalRules from '@/constants/global-rules';
|
||||
import { required, regex } from '@/helpers/validation-rules';
|
||||
|
||||
/**
|
||||
* @summary Define global rules related to names
|
||||
*/
|
||||
function defineGlobalNameRules() {
|
||||
defineRule(globalRules.FIRST_NAME_REQUIRED, required(errorMessages.FIRST_NAME_REQUIRED));
|
||||
defineRule(globalRules.LAST_NAME_REQUIRED, required(errorMessages.LAST_NAME_REQUIRED));
|
||||
defineRule(globalRules.POLICYHOLDER_FIRST_NAME_REQUIRED,
|
||||
required(errorMessages.POLICYHOLDER_FIRST_NAME_REQUIRED));
|
||||
defineRule(globalRules.POLICYHOLDER_LAST_NAME_REQUIRED,
|
||||
required(errorMessages.POLICYHOLDER_LAST_NAME_REQUIRED));
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Define global rules related to email addresses
|
||||
*/
|
||||
function defineGlobalEmailRules() {
|
||||
defineRule(globalRules.EMAIL_ADDRESS_REQUIRED, required(errorMessages.EMAIL_ADDRESS_REQUIRED));
|
||||
defineRule(globalRules.EMAIL_ADDRESS_FORMAT,
|
||||
regex(/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9_\-.]+)\.([a-zA-Z]{2,})$/,
|
||||
errorMessages.EMAIL_ADDRESS_FORMAT));
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Define global rules related to phone numbers
|
||||
*/
|
||||
function defineGlobalPhoneNumberRules() {
|
||||
defineRule(globalRules.PHONE_NUMBER_REQUIRED, required(errorMessages.PHONE_NUMBER_REQUIRED));
|
||||
defineRule(globalRules.PHONE_NUMBER_FORMAT,
|
||||
regex(/^(\([0-9]{3}\)|[0-9]{3}) *[-.]? *[0-9]{3} *[-.]? *[0-9]{4}$/,
|
||||
errorMessages.PHONE_NUMBER_FORMAT));
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Define all global rules
|
||||
*/
|
||||
export default function defineGlobalRules() {
|
||||
defineGlobalNameRules();
|
||||
defineGlobalEmailRules();
|
||||
defineGlobalPhoneNumberRules();
|
||||
|
||||
defineRule(globalRules.OPTION_REQUIRED, required(errorMessages.OPTION_REQUIRED));
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
ref="firstName"
|
||||
inputId="08497a2efd9a4a73a70360ab47b4838d"
|
||||
disableAutoFill
|
||||
validationRules="first-name-required" />
|
||||
:validationRules="rules.firstName" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
|
|
@ -19,21 +19,15 @@
|
|||
ref="lastName"
|
||||
inputId="0030e56a57e74a4ab92de7fb8e97fec5"
|
||||
disableAutoFill
|
||||
validationRules="last-name-required" />
|
||||
:validationRules="rules.lastName" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addressQuestions from "@/iss-components/address-questions/address-questions";
|
||||
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
|
||||
import { defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("first-name-required", required(errorMessages.FIRST_NAME_REQUIRED));
|
||||
defineRule("last-name-required", required(errorMessages.LAST_NAME_REQUIRED));
|
||||
import addressQuestions from '@/iss-components/address-questions/address-questions.vue';
|
||||
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
|
||||
import globalRules from '@/constants/global-rules';
|
||||
|
||||
export default {
|
||||
name: "customer-questions",
|
||||
|
|
@ -56,6 +50,14 @@ export default {
|
|||
},
|
||||
validationRules: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rules: {
|
||||
firstName: globalRules.FIRST_NAME_REQUIRED,
|
||||
lastName: globalRules.LAST_NAME_REQUIRED
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
customerModel: {
|
||||
get: function () {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
|
||||
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
|
||||
:questionsData="questionsData"
|
||||
validationRules="questions-required"
|
||||
:validationRules="rules.optionRequired"
|
||||
v-model="selectedAnswers"
|
||||
@forwardButtonAction="forwardButtonAction"
|
||||
@back-click="navigateBack"
|
||||
|
|
@ -26,14 +26,11 @@ import { settleAllPromises } from '@/helpers/layout-helper';
|
|||
// Import Component
|
||||
import baseFormMixin from '@/mixins/base-form-mixin';
|
||||
import { issPageValues } from "@/router/router-constants/issPage-values";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { Form } from "vee-validate";
|
||||
import { useMainStore } from "@/store";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import globalRules from '@/constants/global-rules';
|
||||
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
||||
import questionsPageLayout from "@/iss-components/questions-page-layout/questions-page-layout.vue";
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("questions-required", required(errorMessages.OPTION_REQUIRED));
|
||||
|
||||
export default {
|
||||
name: 'capability-questions',
|
||||
|
|
@ -45,8 +42,11 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
questionsData: [],
|
||||
selectedAnswers: {},
|
||||
currentGlassIndex: 0,
|
||||
selectedAnswers: {},
|
||||
currentGlassIndex: 0,
|
||||
rules: {
|
||||
optionRequired: globalRules.OPTION_REQUIRED
|
||||
}
|
||||
};
|
||||
},
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
|
|||
226
src/layouts/contact-details/contact-details.spec.js
Normal file
226
src/layouts/contact-details/contact-details.spec.js
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
// Components
|
||||
import contactDetails from '@/layouts/contact-details/contact-details.vue';
|
||||
|
||||
// Supporting Files
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { getMountOptions } from '@/helpers/unit-test-helper';
|
||||
import { getRandomString, getRandomInt } from '@/helpers/data-generation';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { navigationScenarios } from '@/router/router-constants/navigation-scenarios';
|
||||
|
||||
describe('contactDetails.vue', () => {
|
||||
describe('Rendering', () => {
|
||||
test('Should render site header', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(contactDetails, getMountOptions());
|
||||
|
||||
// Act
|
||||
const siteHeader = wrapper.findComponent({ ref: 'siteHeader' });
|
||||
|
||||
// Assert
|
||||
expect(siteHeader.exists()).toBe(true);
|
||||
});
|
||||
test('Should render sub title', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(contactDetails, getMountOptions());
|
||||
|
||||
// Act
|
||||
const siteSubHeader = wrapper.findComponent({ ref: 'siteSubHeader' });
|
||||
|
||||
// Assert
|
||||
expect(siteSubHeader.exists()).toBe(true);
|
||||
});
|
||||
test('Should render first name question subcomponent', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(contactDetails, getMountOptions());
|
||||
|
||||
// Act
|
||||
const firstNameQuestion = wrapper.findComponent({ ref: 'firstNameQuestion' });
|
||||
|
||||
// Assert
|
||||
expect(firstNameQuestion.exists()).toBe(true);
|
||||
});
|
||||
test('Should render last name question subcomponent', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(contactDetails, getMountOptions());
|
||||
|
||||
// Act
|
||||
const lastNameQuestion = wrapper.findComponent({ ref: 'lastNameQuestion' });
|
||||
|
||||
// Assert
|
||||
expect(lastNameQuestion.exists()).toBe(true);
|
||||
});
|
||||
test('Should render email question subcomponent', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(contactDetails, getMountOptions());
|
||||
|
||||
// Act
|
||||
const emailQuestion = wrapper.findComponent({ ref: 'emailQuestion' });
|
||||
|
||||
// Assert
|
||||
expect(emailQuestion.exists()).toBe(true);
|
||||
});
|
||||
test('Should render phone number question subcomponent', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(contactDetails, getMountOptions());
|
||||
|
||||
// Act
|
||||
const phoneNumberQuestion = wrapper.findComponent({ ref: 'phoneNumberQuestion' });
|
||||
|
||||
// Assert
|
||||
expect(phoneNumberQuestion.exists()).toBe(true);
|
||||
});
|
||||
test('Should render text updates checkbox subcomponent', () => {
|
||||
// Arrange
|
||||
const checkboxLabel = getRandomString(50, 100);
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn().mockImplementation(() =>
|
||||
checkboxLabel),
|
||||
setCmsContent: jest.fn()
|
||||
}
|
||||
};
|
||||
const mountOptions = getMountOptions();
|
||||
mountOptions.mixins = [mockMixin];
|
||||
const wrapper = shallowMount(contactDetails, mountOptions);
|
||||
|
||||
// Act
|
||||
const textUpdatesCheckbox = wrapper.findComponent({ ref: 'requestTextUpdatesCheckbox' });
|
||||
|
||||
// Assert
|
||||
expect(textUpdatesCheckbox.exists()).toBe(true);
|
||||
expect(wrapper.vm.requestTextUpdatesCheckboxText).toBe(`${checkboxLabel}*`);
|
||||
});
|
||||
test('Should render technician notes textarea question subcomponent', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(contactDetails, getMountOptions());
|
||||
|
||||
// Act
|
||||
const notesQuestion = wrapper.findComponent({ ref: 'notesQuestion' });
|
||||
|
||||
// Assert
|
||||
expect(notesQuestion.exists()).toBe(true);
|
||||
});
|
||||
test('Should render disclaimer text', () => {
|
||||
// Arrange
|
||||
const disclaimerText = getRandomString(50, 100);
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn().mockImplementation(() =>
|
||||
disclaimerText),
|
||||
setCmsContent: jest.fn()
|
||||
}
|
||||
};
|
||||
const mountOptions = getMountOptions();
|
||||
mountOptions.mixins = [mockMixin];
|
||||
const wrapper = shallowMount(contactDetails, mountOptions);
|
||||
const expectedDisclaimerText = `*${disclaimerText} I also agree to Safelite's`;
|
||||
|
||||
// Act
|
||||
const componentText = wrapper.text();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.textUpdateDisclaimerText).toBe(`*${disclaimerText}`);
|
||||
expect(componentText).toContain(expectedDisclaimerText);
|
||||
});
|
||||
test('Should render privacy policy link', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(contactDetails, getMountOptions());
|
||||
|
||||
// Act
|
||||
const privacyPolicyLink = wrapper.findComponent({ ref: 'privacyPolicyLink' });
|
||||
|
||||
// Assert
|
||||
expect(privacyPolicyLink.exists()).toBe(true);
|
||||
});
|
||||
test('Should render terms of use link', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(contactDetails, getMountOptions());
|
||||
|
||||
// Act
|
||||
const termsOfUseLink = wrapper.findComponent({ ref: 'termsOfUseLink' });
|
||||
|
||||
// Assert
|
||||
expect(termsOfUseLink.exists()).toBe(true);
|
||||
});
|
||||
test('Should render site footer', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(contactDetails, getMountOptions());
|
||||
|
||||
// Act
|
||||
const footer = wrapper.findComponent({ ref: 'siteFooter' });
|
||||
|
||||
// Assert
|
||||
expect(footer.exists()).toBe(true);
|
||||
});
|
||||
test('Mocked store yields expected data', () => {
|
||||
// Arrange
|
||||
const mountOptions = getMountOptions();
|
||||
|
||||
const firstName = getRandomString(4, 15);
|
||||
const lastName = getRandomString(4, 15);
|
||||
const emailAddress = getRandomString(10, 20);
|
||||
const phoneNumber = getRandomInt(1000000000, 9999999999);
|
||||
const mainInitialState = {
|
||||
order: {
|
||||
customer: {
|
||||
firstName,
|
||||
lastName,
|
||||
emailAddress,
|
||||
phoneNumber
|
||||
}
|
||||
}
|
||||
};
|
||||
mountOptions.global = {
|
||||
plugins: [createTestingPinia({
|
||||
initialState: {
|
||||
main: mainInitialState
|
||||
}
|
||||
})]
|
||||
};
|
||||
|
||||
const wrapper = shallowMount(contactDetails, mountOptions);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.firstName).toBe(firstName);
|
||||
expect(wrapper.vm.lastName).toBe(lastName);
|
||||
expect(wrapper.vm.email).toBe(emailAddress);
|
||||
expect(wrapper.vm.phoneNumber).toBe(phoneNumber);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigation', () => {
|
||||
test('Back button clicked triggers navigation', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(contactDetails, getMountOptions({
|
||||
router: {
|
||||
navigate: jest.fn()
|
||||
}
|
||||
}));
|
||||
|
||||
// Act
|
||||
wrapper.vm.backButtonAction();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
|
||||
expect(wrapper.vm.$router.navigate)
|
||||
.toHaveBeenCalledWith(navigationScenarios.CLICKED_BACK, undefined);
|
||||
});
|
||||
test('Forward button clicked triggers navigation', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(contactDetails, getMountOptions({
|
||||
router: {
|
||||
navigate: jest.fn()
|
||||
}
|
||||
}));
|
||||
|
||||
// Act
|
||||
wrapper.vm.forwardButtonAction();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
|
||||
expect(wrapper.vm.$router.navigate)
|
||||
.toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,17 +1,96 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }" >
|
||||
<Form
|
||||
ref="contact-details-form"
|
||||
v-slot="{ meta }"
|
||||
@submit="onSubmit"
|
||||
@invalidSubmit="onInvalidSubmit">
|
||||
<div class="page-container-grouped-styles">
|
||||
<div class="fade-on-route-transition position-relative">
|
||||
<siteHeader cmsWidgetName="SiteHeaderWidget"/>
|
||||
<siteHeader
|
||||
ref="siteHeader"
|
||||
:cmsWidgetName="widget.siteHeader" />
|
||||
<div class="container-fluid pb-2">
|
||||
<p>Placeholder for contact details page</p>
|
||||
<siteSubHeader
|
||||
id="sub-header"
|
||||
ref="siteSubHeader"
|
||||
class="margin-top-16"
|
||||
:cmsWidgetName="widget.siteSubHeader" />
|
||||
<textboxQuestion
|
||||
ref="firstNameQuestion"
|
||||
v-model="firstName"
|
||||
class="margin-top-24"
|
||||
inputId="firstName"
|
||||
:cmsWidgetName="widget.firstNameQuestion"
|
||||
isRequired
|
||||
:validationRules="rules.firstName" />
|
||||
<textboxQuestion
|
||||
ref="lastNameQuestion"
|
||||
v-model="lastName"
|
||||
class="margin-top-16"
|
||||
inputId="lastName"
|
||||
:cmsWidgetName="widget.lastNameQuestion"
|
||||
isRequired
|
||||
:validationRules="rules.lastName" />
|
||||
<textboxQuestion
|
||||
ref="emailQuestion"
|
||||
v-model="email"
|
||||
class="margin-top-16"
|
||||
inputId="email"
|
||||
:cmsWidgetName="widget.emailQuestion"
|
||||
isRequired
|
||||
:validationRules="rules.email" />
|
||||
<textboxQuestion
|
||||
ref="phoneNumberQuestion"
|
||||
v-model="phoneNumber"
|
||||
class="margin-top-16"
|
||||
inputId="phoneNumber"
|
||||
:cmsWidgetName="widget.phoneNumberQuestion"
|
||||
isRequired
|
||||
:validationRules="rules.phoneNumber" />
|
||||
<checkbox
|
||||
ref="requestTextUpdatesCheckbox"
|
||||
v-model="requestTextUpdates"
|
||||
class="margin-top-8"
|
||||
checkboxName="requestTextUpdates"
|
||||
buttonID="requestTextUpdates"
|
||||
:checkboxLabel="requestTextUpdatesCheckboxText" />
|
||||
|
||||
<textareaQuestion
|
||||
ref="notesQuestion"
|
||||
v-model="notesForTechnician"
|
||||
class="margin-top-16"
|
||||
inputId="technicianNotes"
|
||||
:isDisabled="false"
|
||||
:isRequired="false"
|
||||
:cmsWidgetName="widget.notesQuestion"
|
||||
maxLength="500"
|
||||
:inputRows="3" />
|
||||
|
||||
<p
|
||||
ref="disclaimerText"
|
||||
class="disclaimer margin-top-24">
|
||||
{{ textUpdateDisclaimerText }} I also agree to Safelite's
|
||||
<textLink
|
||||
ref="privacyPolicyLink"
|
||||
class="disclaimer-link"
|
||||
linkType="text"
|
||||
text="Privacy Policy"
|
||||
href="//www.safelite.com/privacy-center" />
|
||||
and
|
||||
<textLink
|
||||
ref="termsOfUseLink"
|
||||
class="disclaimer-link"
|
||||
linkType="text"
|
||||
text="Terms of Use"
|
||||
href="//www.safelite.com/terms-of-use" />.
|
||||
</p>
|
||||
|
||||
<siteFooter
|
||||
cmsWidgetName="SiteFooterWidget"
|
||||
ref="siteFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@ForwardClicked="navigateForward"
|
||||
@back-clicked="backButtonAction"
|
||||
/>
|
||||
ref="siteFooter"
|
||||
:cmsWidgetName="widget.siteFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@forwardClicked="navigateForward"
|
||||
@backClicked="backButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -19,67 +98,131 @@
|
|||
</template>
|
||||
<script>
|
||||
// Components
|
||||
import siteHeader from '@/iss-components/site-header/site-header';
|
||||
import siteFooter from "@/iss-components/site-footer/site-footer";
|
||||
import siteHeader from '@/iss-components/site-header/site-header.vue';
|
||||
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
||||
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
|
||||
import checkbox from '@/ux-components/checkbox/checkbox.vue';
|
||||
import textareaQuestion from '@/digital-components/textarea-question/textarea-question.vue';
|
||||
import textLink from '@/ux-components/text-link/text-link.vue';
|
||||
|
||||
// Supporting files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { Form } from "vee-validate";
|
||||
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
||||
import { Form } from 'vee-validate';
|
||||
import BaseFormMixin from '@/mixins/base-form-mixin';
|
||||
import { useMainStore } from '@/store';
|
||||
import globalRules from '@/constants/global-rules';
|
||||
|
||||
export default {
|
||||
name: "contact-details",
|
||||
name: 'contact-details',
|
||||
components: {
|
||||
siteHeader,
|
||||
siteSubHeader,
|
||||
textboxQuestion,
|
||||
checkbox,
|
||||
textareaQuestion,
|
||||
siteFooter,
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
Form,
|
||||
textLink
|
||||
},
|
||||
mixins: [BaseFormMixin],
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
const cmsContent = await fetchCmsContentForPage(to.query.issPage);
|
||||
|
||||
next((vm) => {
|
||||
vm.setCmsContent(cmsContent);
|
||||
});
|
||||
},
|
||||
data() {
|
||||
|
||||
return {
|
||||
firstName: useMainStore().order.customer.firstName,
|
||||
lastName: useMainStore().order.customer.lastName,
|
||||
email: useMainStore().order.customer.emailAddress,
|
||||
phoneNumber: useMainStore().order.customer.phoneNumber,
|
||||
requestTextUpdates: false,
|
||||
notesForTechnician: '',
|
||||
widget: {
|
||||
siteHeader: 'SiteHeaderWidget',
|
||||
siteSubHeader: 'SiteSubHeaderWidget',
|
||||
firstNameQuestion: 'FirstNameQuestionWidget',
|
||||
lastNameQuestion: 'LastNameQuestionWidget',
|
||||
emailQuestion: 'EmailQuestionWidget',
|
||||
phoneNumberQuestion: 'PhoneNumberQuestionWidget',
|
||||
requestTextUpdates: 'TextContentWidget',
|
||||
notesQuestion: 'NotesQuestionWidget',
|
||||
disclaimer: 'TextUpdateDisclaimerWidget',
|
||||
siteFooter: 'SiteFooterWidget'
|
||||
},
|
||||
rules: {
|
||||
firstName: globalRules.FIRST_NAME_REQUIRED,
|
||||
lastName: globalRules.LAST_NAME_REQUIRED,
|
||||
email: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`,
|
||||
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`
|
||||
}
|
||||
};
|
||||
},
|
||||
setup() {
|
||||
const mainStore = useMainStore();
|
||||
return { mainStore };
|
||||
computed: {
|
||||
/**
|
||||
* @summary Returns the CMS text associated with the "get text updates" checkbox.
|
||||
*/
|
||||
requestTextUpdatesCheckboxText() {
|
||||
return `${this.getCmsContent(this.widget.requestTextUpdates, 'Text')}*`;
|
||||
},
|
||||
/**
|
||||
* @summary Returns the CMS text associated with the "get text updates" checkbox.
|
||||
*/
|
||||
textUpdateDisclaimerText() {
|
||||
return `*${this.getCmsContent(this.widget.disclaimer, 'Text')}`;
|
||||
}
|
||||
},
|
||||
async beforeRouteEnter(to, from, next)
|
||||
methods:
|
||||
{
|
||||
// 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.
|
||||
let resultMap = await settleAllPromises(promiseResultMap);
|
||||
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
});
|
||||
},
|
||||
methods:
|
||||
{
|
||||
backButtonAction() {
|
||||
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
|
||||
forwardButtonAction() {
|
||||
},
|
||||
|
||||
navigateForward() {
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.CLICKED_FORWARD,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
siteHeader,
|
||||
siteFooter,
|
||||
Form,
|
||||
/**
|
||||
* @summary Steps to perform when back button clicked.
|
||||
*/
|
||||
backButtonAction() {
|
||||
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
/**
|
||||
* @summary Steps to perform when forward button clicked.
|
||||
*/
|
||||
forwardButtonAction() {
|
||||
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD,
|
||||
this.$route);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#sub-header {
|
||||
span {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
.margin-top-24 {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.margin-top-16 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.margin-top-8 {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
font-size: 12px;
|
||||
color: $darker-gray;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.disclaimer-link {
|
||||
text-decoration: none;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
|
||||
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
|
||||
:questionsData="questionsData"
|
||||
validationRules="questions-required"
|
||||
:validationRules="rules.optionRequired"
|
||||
v-model="selectedAnswers"
|
||||
@forwardButtonAction="forwardButtonAction"
|
||||
@back-click="navigateBack"
|
||||
|
|
@ -25,9 +25,7 @@
|
|||
// Import Supporting Files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import globalRules from '@/constants/global-rules';
|
||||
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
||||
import { useMainStore } from "@/store";
|
||||
import { issPageValues } from "@/router/router-constants/issPage-values";
|
||||
|
|
@ -37,9 +35,6 @@ import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
|||
import { Form } from "vee-validate";
|
||||
import questionsPageLayout from "@/iss-components/questions-page-layout/questions-page-layout.vue";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("questions-required", required(errorMessages.OPTION_REQUIRED));
|
||||
|
||||
export default {
|
||||
name: "molding-questions",
|
||||
mixins: [BaseFormMixin, vehicleQuestionsMixin],
|
||||
|
|
@ -65,6 +60,9 @@ export default {
|
|||
questionsData: [],
|
||||
selectedAnswers: {},
|
||||
currentGlassIndex: 0,
|
||||
rules: {
|
||||
optionRequired: globalRules.OPTION_REQUIRED
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
|
||||
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
|
||||
:questionsData="questionsData"
|
||||
validationRules="questions-required"
|
||||
:validationRules="rules.optionRequired"
|
||||
v-model="selectedAnswers"
|
||||
@forwardButtonAction="forwardButtonAction"
|
||||
@back-click="navigateBack"
|
||||
|
|
@ -23,14 +23,10 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
|||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { useMainStore } from '@/store';
|
||||
import { issPageValues } from "@/router/router-constants/issPage-values";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import { Form } from "vee-validate";
|
||||
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
||||
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("questions-required", required(errorMessages.OPTION_REQUIRED));
|
||||
import globalRules from '@/constants/global-rules';
|
||||
|
||||
export default {
|
||||
name: "part-questions",
|
||||
|
|
@ -59,6 +55,9 @@ export default {
|
|||
questionsData: [],
|
||||
selectedAnswers: {},
|
||||
currentGlassIndex: 0,
|
||||
rules: {
|
||||
optionRequired: globalRules.OPTION_REQUIRED
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
isRequired
|
||||
ref="policyHolderFirstName"
|
||||
disableAutoFill
|
||||
validationRules="first-name-required" />
|
||||
:validationRules="rules.firstName" />
|
||||
<textboxQuestion
|
||||
class="mt-4"
|
||||
inputId="lastNameField"
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
isRequired
|
||||
ref="policyHolderLastName"
|
||||
disableAutoFill
|
||||
validationRules="last-name-required" />
|
||||
:validationRules="rules.lastName" />
|
||||
</div>
|
||||
</div>
|
||||
<siteFooter
|
||||
|
|
@ -51,16 +51,11 @@ import siteFooter from "@/iss-components/site-footer/site-footer";
|
|||
// Supporting files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import { Form } from "vee-validate";
|
||||
import globalRules from '@/constants/global-rules';
|
||||
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||
import { useMainStore } from '@/store';
|
||||
|
||||
//define validation rules
|
||||
defineRule("first-name-required", required(errorMessages.POLICYHOLDER_FIRST_NAME_REQUIRED));
|
||||
defineRule("last-name-required", required(errorMessages.POLICYHOLDER_LAST_NAME_REQUIRED));
|
||||
|
||||
export default {
|
||||
name: "policy-holder-details",
|
||||
mixins: [BaseFormMixin],
|
||||
|
|
@ -68,6 +63,10 @@ export default {
|
|||
return {
|
||||
customerQuestions: this.getPolicyHolderDetailsFromStore(),
|
||||
vehiclesFound: [],
|
||||
rules: {
|
||||
firstName: globalRules.POLICYHOLDER_FIRST_NAME_REQUIRED,
|
||||
lastName: globalRules.POLICYHOLDER_LAST_NAME_REQUIRED
|
||||
}
|
||||
};
|
||||
},
|
||||
setup() {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
class="px-4"
|
||||
cmsWidgetName="PolicyVehiclesQuestion"
|
||||
:vehicles="VehiclesForQuestions"
|
||||
validationRules="option-required" />
|
||||
:validationRules="rules.optionRequired" />
|
||||
<siteFooter
|
||||
ref="siteFooter"
|
||||
cmsWidgetName="SiteFooterWidget"
|
||||
|
|
@ -47,17 +47,14 @@ import vehicleBanner from '@/iss-components/vehicle-banner/vehicle-banner.vue';
|
|||
import policyVehiclesQuestion from '@/layouts/policy-vehicles/policy-vehicles-question/policy-vehicles-question.vue';
|
||||
// Supporting files
|
||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
||||
import { required } from '@/helpers/validation-rules';
|
||||
import { Form, defineRule } from 'vee-validate';
|
||||
import { Form } from 'vee-validate';
|
||||
import BaseFormMixin from '@/mixins/base-form-mixin';
|
||||
import { issPageValues } from '@/router/router-constants/issPage-values';
|
||||
import { useMainStore } from '@/store';
|
||||
import { errorMessages } from '@/constants/error-messages';
|
||||
import { vehicleSelectionOptions } from '@/constants/vehicle-selection-options';
|
||||
import { endorsementOptions } from '@/constants/endorsement-options';
|
||||
import globalRules from '@/constants/global-rules';
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule('option-required', required(errorMessages.OPTION_REQUIRED));
|
||||
export default {
|
||||
name: 'policy-vehicles',
|
||||
components: {
|
||||
|
|
@ -83,7 +80,10 @@ export default {
|
|||
? policyVehicles[0].vin
|
||||
: '',
|
||||
displayGeneric: (policyVehicles?.length ?? 0) !== 1,
|
||||
bailout: false
|
||||
bailout: false,
|
||||
rules: {
|
||||
optionRequired: globalRules.OPTION_REQUIRED
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
groupName="prefQuestions"
|
||||
buttonTypeString="providerPrefRadio"
|
||||
v-model="selectedProvider"
|
||||
validationRules="option-required"
|
||||
:validationRules="rules.optionRequired"
|
||||
isRequired
|
||||
class="mx-5" />
|
||||
<div class="px-5">
|
||||
|
|
@ -44,7 +44,6 @@
|
|||
// Import Supporting Files
|
||||
import { fetchCmsContentForPage, setupModalLinks, processIfStatements } from '@/helpers/cms-content-helper';
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||
import contentGroupModal from "@/iss-components/content-group-modal/content-group-modal";
|
||||
|
|
@ -52,7 +51,7 @@ import { issPageValues } from "@/router/router-constants/issPage-values";
|
|||
|
||||
// Import Component
|
||||
import baseFormMixin from "@/mixins/base-form-mixin";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { Form } from "vee-validate";
|
||||
import siteFooter from "@/iss-components/site-footer/site-footer";
|
||||
import siteHeader from "@/iss-components/site-header/site-header";
|
||||
import siteSubHeader from "@/iss-components/site-sub-header/site-sub-header";
|
||||
|
|
@ -61,9 +60,8 @@ import recalModal from "@/layouts/coverage-statement/recal-modal/recal-modal";
|
|||
import steeringModal from "@/layouts/provider-preference/steering-modal/steering-modal";
|
||||
import shopPreferenceModal from "@/layouts/provider-preference/shop-preference-modal/shop-preference-modal";
|
||||
import tpaRecalModal from "@/layouts/provider-preference/tpa-recal-modal/tpa-recal-modal.vue"
|
||||
import globalRules from '@/constants/global-rules';
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule('option-required', required(errorMessages.OPTION_REQUIRED));
|
||||
const options = { SAFELITE: 'SafeliteOption', TPA: 'TPAOption' };
|
||||
|
||||
export default {
|
||||
|
|
@ -88,6 +86,9 @@ export default {
|
|||
selectedProvider : null,
|
||||
showSteeringLink: false,
|
||||
tpaAcknowledgement: false,
|
||||
rules: {
|
||||
optionRequired: globalRules.OPTION_REQUIRED
|
||||
}
|
||||
};
|
||||
},
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
<checkBox
|
||||
class="mb-2"
|
||||
:class="showError && ' has-error'"
|
||||
validationRules="option-required"
|
||||
:validationRules="rules.optionRequired"
|
||||
checkboxName="tpaAcknowledgement"
|
||||
buttonID="tpaAcknowledgement"
|
||||
:tabIndex="0"
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
import modal from "@/digital-components/modal/modal";
|
||||
import tpaRecalToggle from "@/layouts/provider-preference/tpa-recal-modal/tpa-recal-toggle/tpa-recal-toggle.vue"
|
||||
import checkBox from "@/ux-components/checkbox/checkbox.vue"
|
||||
import globalRules from "@/constants/global-rules";
|
||||
|
||||
export default {
|
||||
name: "content-group-modal",
|
||||
|
|
@ -45,7 +46,10 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
acknowledged: false,
|
||||
showError: false
|
||||
showError: false,
|
||||
rules: {
|
||||
optionRequired: globalRules.OPTION_REQUIRED
|
||||
}
|
||||
};
|
||||
},
|
||||
emits: ["buttonClick"],
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
:availableLineItems="availableLineItems"
|
||||
@vapsItemsSelected="vapsItemsSelectedAction"
|
||||
v-on:link-event="openModalAction"
|
||||
validationRules="option-required"
|
||||
:validationRules="rules.optionRequired"
|
||||
isRequired
|
||||
/>
|
||||
<p class="caption disclaimer" v-html="PriceDisclaimerText"></p>
|
||||
|
|
@ -52,8 +52,7 @@ import { required } from '@/helpers/validation-rules';
|
|||
import { errorMessages } from '@/constants/error-messages';
|
||||
import { Form, defineRule } from 'vee-validate';
|
||||
import { allGlassPartsAndItemsHavePrices } from '@/layouts/service-packages/service-package-helper/service-package-helper'
|
||||
|
||||
defineRule('option-required', required(errorMessages.OPTION_REQUIRED));
|
||||
import globalRules from "@/constants/global-rules";
|
||||
|
||||
const store = useMainStore();
|
||||
|
||||
|
|
@ -112,7 +111,10 @@ export default {
|
|||
selectedVaps: [],
|
||||
availableLineItems: [],
|
||||
supportingItems: [],
|
||||
pricedGlassParts: []
|
||||
pricedGlassParts: [],
|
||||
rules: {
|
||||
optionRequired: globalRules.OPTION_REQUIRED
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ import {vinLookupMethodSelections} from '@/constants/vin-lookup-methods';
|
|||
import { useMainStore } from '@/store';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { mapStores } from "pinia";
|
||||
import { defineRule } from 'vee-validate';
|
||||
import { errorMessages } from '@/constants/error-messages';
|
||||
import globalRules from '@/constants/global-rules';
|
||||
import { required } from '@/helpers/validation-rules';
|
||||
|
||||
const pinia = createTestingPinia();
|
||||
useMainStore(pinia);
|
||||
|
|
@ -44,6 +48,7 @@ const siteFooterWidgetMockData = {
|
|||
};
|
||||
|
||||
function setupMocks() {
|
||||
defineRule(globalRules.OPTION_REQUIRED, required(errorMessages.OPTION_REQUIRED));
|
||||
const mockRoute = {
|
||||
query: {
|
||||
issPage: 'vehicle-lookup',
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
button-type-string="listButton"
|
||||
:question-text="questionTextFromCms"
|
||||
is-required
|
||||
validation-rules="option-required"
|
||||
:validation-rules="rules.optionRequired"
|
||||
data-test-id="vin-lookup-methods-button-question"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -13,32 +13,30 @@
|
|||
<script>
|
||||
|
||||
// Import Other Supporting Files
|
||||
import { defineRule } from 'vee-validate';
|
||||
import { required } from '@/helpers/validation-rules';
|
||||
import { errorMessages } from '@/constants/error-messages';
|
||||
import { useMainStore } from "@/store";
|
||||
import {vinLookupMethodSelections} from '@/constants/vin-lookup-methods';
|
||||
import { vinLookupMethodSelections } from '@/constants/vin-lookup-methods';
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import globalRules from '@/constants/global-rules';
|
||||
|
||||
// Import Component
|
||||
import ButtonQuestion from '@/digital-components/button-question/button-question.vue';
|
||||
|
||||
// Define Validation Rules
|
||||
defineRule('option-required', required(errorMessages.OPTION_REQUIRED));
|
||||
|
||||
export default {
|
||||
name: 'vin-lookup-methods',
|
||||
data() {
|
||||
return {
|
||||
questionTextFromCms: '',
|
||||
answersFromCms: [],
|
||||
rules: {
|
||||
optionRequired: globalRules.OPTION_REQUIRED
|
||||
}
|
||||
};
|
||||
},
|
||||
props: {
|
||||
modelValue: String,
|
||||
cmsWidgetName: {
|
||||
type: String,
|
||||
required,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
ref="policyNumber"
|
||||
disableAutoFill
|
||||
:isDisabled="isPolicyHolderEnabled"
|
||||
validationRules="policy-number-required" />
|
||||
:validationRules="rules.policyNumber" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4" v-if="this.isCoverageEnabled">
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
v-model="welcomePageModel.policyZipCode"
|
||||
isRequired
|
||||
ref="policyZip"
|
||||
validationRules="policy-zip-required|policy-zip-format" />
|
||||
:validationRules="rules.policyZip" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4">
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
disableAutoFill
|
||||
:max="new Date().toJSON().slice(0,10)"
|
||||
:min="'1972-12-01'"
|
||||
validationRules="loss-date-required"
|
||||
:validationRules="rules.lossDate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
inputId="damageCauseQuestionField"
|
||||
:options="DamageCauseOptions"
|
||||
disableAutoFill
|
||||
validationRules="damage-option-required"
|
||||
:validationRules="rules.damageOption"
|
||||
placeHolderText="Select an option"
|
||||
id="welcomeDropdown"
|
||||
/>
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
inputId="phoneNumberField"
|
||||
cmsWidgetName="PhoneNumberQuestion"
|
||||
v-model="welcomePageModel.phoneNumber"
|
||||
validationRules="phone-number-required|phone-number-format"
|
||||
:validationRules="rules.phoneNumber"
|
||||
isRequired
|
||||
ref="phoneNumber"
|
||||
mask="###-###-####"
|
||||
|
|
@ -85,7 +85,7 @@
|
|||
cmsWidgetName="EmailAddressQuestion"
|
||||
v-model="welcomePageModel.email"
|
||||
ref="email"
|
||||
validationRules="email-address-required|email-address-format"
|
||||
:validationRules="rules.email"
|
||||
isRequired
|
||||
disableAutoFill />
|
||||
</div>
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
ref="damageCity"
|
||||
disableAutoFill
|
||||
v-if="this.displayDamageCityQuestion"
|
||||
validationRules="loss-city-required" />
|
||||
:validationRules="rules.lossCity" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
|
@ -113,7 +113,7 @@
|
|||
ref="state"
|
||||
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
|
||||
:options="getStates"
|
||||
validationRules="loss-state-required"
|
||||
:validationRules="rules.lossState"
|
||||
isRequired
|
||||
disableAutoFill
|
||||
v-if="this.displayDamageStateQuestion"
|
||||
|
|
@ -127,11 +127,11 @@
|
|||
class="px-0 mt-4"
|
||||
cmsWidgetName="GlassOnlyQuestion"
|
||||
v-model="welcomePageModel.isDamageGlassOnly"
|
||||
inputId = "isDamageGlassOnly"
|
||||
inputId="isDamageGlassOnly"
|
||||
:answers="DamageGlassOnlyOptions"
|
||||
:questionText="DamageGlassOnlyQuestion"
|
||||
buttonTypeString="listButtonHorizontal"
|
||||
validationRules="damage-option-required"
|
||||
:validationRules="rules.damageOption"
|
||||
isRequired
|
||||
isSmallQuestionLabelText
|
||||
ref="glassOnlyDamage"
|
||||
|
|
@ -173,32 +173,17 @@
|
|||
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||
import { useMainStore } from '@/store';
|
||||
import { states } from "@/constants/states"
|
||||
import globalRules from '@/constants/global-rules';
|
||||
|
||||
//define validation rules
|
||||
defineRule("loss-date-required", required(errorMessages.LOSS_DATE_REQUIRED));
|
||||
defineRule("loss-cause-required", required(errorMessages.LOSS_CAUSE_REQUIRED));
|
||||
defineRule("policy-number-required", required(errorMessages.POLICY_NUMBER_REQUIRED));
|
||||
defineRule("loss-date-required", required(errorMessages.LOSS_DATE_REQUIRED));
|
||||
defineRule("loss-state-required", required(errorMessages.LOSS_STATE_REQUIRED));
|
||||
defineRule("loss-city-required", required(errorMessages.LOSS_CITY_REQUIRED));
|
||||
defineRule("phone-number-required", required(errorMessages.PHONE_NUMBER_FORMAT));
|
||||
defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIRED));
|
||||
defineRule("damage-option-required", required(errorMessages.DAMAGE_OPTION_REQUIRED));
|
||||
defineRule("policy-zip-required", required(errorMessages.POLICY_ZIP_REQUIRED));
|
||||
defineRule("policy-zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.POLICY_ZIP_FORMAT));
|
||||
|
||||
defineRule("email-address-format",
|
||||
regex(
|
||||
/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9_\-.]+)\.([a-zA-Z]{2,})$/,
|
||||
errorMessages.EMAIL_ADDRESS_FORMAT
|
||||
));
|
||||
|
||||
defineRule("phone-number-format",
|
||||
regex(
|
||||
/^(\([0-9]{3}\)|[0-9]{3}) *[-.]? *[0-9]{3} *[-.]? *[0-9]{4}$/,
|
||||
errorMessages.PHONE_NUMBER_FORMAT
|
||||
));
|
||||
|
||||
export default {
|
||||
name: "welcome-page",
|
||||
mixins: [BaseFormMixin],
|
||||
|
|
@ -206,6 +191,16 @@ export default {
|
|||
return {
|
||||
welcomePageModel: this.getWelcomePageModelFromStore(),
|
||||
vehiclesFound: [],
|
||||
rules: {
|
||||
policyNumber: 'policy-number-required',
|
||||
policyZip: 'policy-zip-required|policy-zip-format',
|
||||
lossDate: 'loss-date-required',
|
||||
damageOption: 'damage-option-required',
|
||||
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`,
|
||||
email: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`,
|
||||
lossCity: 'loss-city-required',
|
||||
lossState: 'loss-state-required'
|
||||
}
|
||||
};
|
||||
},
|
||||
setup() {
|
||||
|
|
|
|||
34
src/main.js
34
src/main.js
|
|
@ -1,32 +1,32 @@
|
|||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import "../node_modules/bootstrap/dist/js/bootstrap.js";
|
||||
import 'bootstrap/dist/js/bootstrap';
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
|
||||
import { useMainStore } from '@/store';
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import baseMixin from '@/mixins/base-mixin';
|
||||
import { createPinia } from 'pinia';
|
||||
import Maska from "maska";
|
||||
import LoadScript from "vue-plugin-load-script";
|
||||
import Maska from 'maska';
|
||||
import LoadScript from 'vue-plugin-load-script';
|
||||
|
||||
import analyticsMixin from "@/mixins/analytics-mixin.js";
|
||||
import experimentMixin from "@/mixins/experiment-mixin.js";
|
||||
import analyticsMixin from '@/mixins/analytics-mixin';
|
||||
import experimentMixin from '@/mixins/experiment-mixin';
|
||||
import defineGlobalRules from '@/helpers/global-rule-definer';
|
||||
import router from './router';
|
||||
import App from './App.vue';
|
||||
|
||||
// Vue App Setup
|
||||
const vueApp = createApp(App);
|
||||
|
||||
/**
|
||||
/**
|
||||
* Needed to make injections reactively linked to the provider.
|
||||
* This is not needed once Vue.js is in version 3.3
|
||||
* https://vuejs.org/guide/components/provide-inject.html#working-with-reactivity
|
||||
*/
|
||||
vueApp.config.unwrapInjectedRef = true;
|
||||
|
||||
vueApp.config.compilerOptions.isCustomElement = (tag) => {
|
||||
return (tag === 'siteSubHeader' ||
|
||||
tag === 'ServicePackages' ||
|
||||
tag === 'servicePackageQuestion');
|
||||
}
|
||||
vueApp.config.compilerOptions.isCustomElement = (tag) =>
|
||||
(tag === 'siteSubHeader'
|
||||
|| tag === 'ServicePackages'
|
||||
|| tag === 'servicePackageQuestion');
|
||||
|
||||
// Pinia
|
||||
const pinia = createPinia();
|
||||
|
|
@ -34,7 +34,6 @@ vueApp.use(pinia);
|
|||
pinia.use(piniaPluginPersistedstate);
|
||||
useMainStore().populateInitialState();
|
||||
|
||||
|
||||
// Additional Vue items to setup
|
||||
vueApp.use(router);
|
||||
vueApp.use(Maska);
|
||||
|
|
@ -43,4 +42,7 @@ vueApp.mixin(baseMixin);
|
|||
vueApp.mixin(analyticsMixin);
|
||||
vueApp.mixin(experimentMixin);
|
||||
|
||||
vueApp.mount("#app");
|
||||
vueApp.mount('#app');
|
||||
|
||||
// define global rules
|
||||
defineGlobalRules();
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
&.list-card {
|
||||
border: 1px solid $red;
|
||||
}
|
||||
|
||||
&.ui-radio {
|
||||
&:hover {
|
||||
input[type="radio"],
|
||||
|
|
|
|||
Loading…
Reference in a new issue