This commit is contained in:
Michaela Brydon 2024-03-19 11:04:24 -04:00
commit d5a087694d
38 changed files with 1444 additions and 138 deletions

38
package-lock.json generated
View file

@ -12,9 +12,10 @@
"axios-retry": "^3.5.0",
"bootstrap": "^5.3",
"iframe-resizer": "^4.3.9",
"maska": "^1.5.0",
"maska": "^2.1.1",
"pinia": "^2.1.4",
"pinia-plugin-persistedstate": "^2.2.0",
"uuid": "^9.0.1",
"vee-validate": "^4.5.7",
"vue": "^3.3.4",
"vue-plugin-load-script": "^2.1.0",
@ -11599,6 +11600,15 @@
"node": ">=10"
}
},
"node_modules/jest-junit/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/jest-leak-detector": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz",
@ -13937,9 +13947,9 @@
}
},
"node_modules/maska": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/maska/-/maska-1.5.0.tgz",
"integrity": "sha512-BwZXzs5gHeu6wtn3iWFqrKRtcsM3sTpkHvfAngVNVNlN7tl9ZyQUeHTz11s9Sy7Bq1MoQ+xyR/+IzghY8nR84Q=="
"version": "2.1.11",
"resolved": "https://registry.npmjs.org/maska/-/maska-2.1.11.tgz",
"integrity": "sha512-IGqWjBnKxMYcVa06pb4mPfag9sJjnR2T15CdGfQ2llR3gajiSd4AxXCvNqHMEq9W3UBhjjTazgWumsP3sWrUSg=="
},
"node_modules/mdn-data": {
"version": "2.0.14",
@ -16662,6 +16672,15 @@
"websocket-driver": "^0.7.4"
}
},
"node_modules/sockjs/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -17730,10 +17749,13 @@
}
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/bin/uuid"
}

View file

@ -19,9 +19,10 @@
"axios-retry": "^3.5.0",
"bootstrap": "^5.3",
"iframe-resizer": "^4.3.9",
"maska": "^1.5.0",
"maska": "^2.1.1",
"pinia": "^2.1.4",
"pinia-plugin-persistedstate": "^2.2.0",
"uuid": "^9.0.1",
"vee-validate": "^4.5.7",
"vue": "^3.3.4",
"vue-plugin-load-script": "^2.1.0",
@ -60,4 +61,4 @@
"vitest": "^0.33.0",
"volar-service-vetur": "latest"
}
}
}

View file

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<g fill="none" fill-rule="evenodd">
<g fill-rule="nonzero">
<g>
<g>
<path fill="#FBBB00" d="M4.432 12.086l-.696 2.6-2.544.053C.432 13.329 0 11.715 0 10c0-1.658.403-3.222 1.118-4.599l2.266.415.992 2.252c-.207.606-.32 1.256-.32 1.932 0 .734.132 1.437.376 2.086z" transform="translate(-119 -800) translate(104 753) translate(15 47)"/>
<path fill="#518EF8" d="M19.825 8.132C19.94 8.737 20 9.362 20 10c0 .716-.075 1.414-.219 2.088-.486 2.292-1.758 4.294-3.52 5.71h-.001l-2.853-.146-.404-2.52c1.169-.686 2.083-1.76 2.564-3.044H10.22V8.132h9.605z" transform="translate(-119 -800) translate(104 753) translate(15 47)"/>
<path fill="#28B446" d="M16.26 17.798C14.547 19.176 12.37 20 10 20c-3.808 0-7.12-2.129-8.808-5.261l3.24-2.653c.845 2.254 3.02 3.859 5.568 3.859 1.096 0 2.122-.297 3.003-.814l3.257 2.667z" transform="translate(-119 -800) translate(104 753) translate(15 47)"/>
<path fill="#F14336" d="M16.383 2.302l-3.24 2.652c-.911-.57-1.989-.899-3.143-.899-2.607 0-4.821 1.678-5.624 4.013L1.12 5.401C2.781 2.192 6.134 0 10 0c2.426 0 4.651.864 6.383 2.302z" transform="translate(-119 -800) translate(104 753) translate(15 47)"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="20" viewBox="0 0 22 20">
<g fill="none" fill-rule="evenodd">
<g fill="#0072C6" fill-rule="nonzero">
<g>
<g>
<path d="M12.794 4.525v3.677l1.37.809c.036.01.114.01.15 0l5.899-3.728c0-.441-.44-.758-.687-.758h-6.732z" transform="translate(-119 -870) translate(104 753) translate(15 117)"/>
<path d="M12.794 9.574l1.25.805c.176.121.389 0 .389 0-.212.121 5.78-3.61 5.78-3.61v6.757c0 .736-.502 1.044-1.067 1.044h-6.353V9.574z" transform="translate(-119 -870) translate(104 753) translate(15 117)"/>
<g>
<path d="M6.266 7.736c-.427 0-.766.187-1.017.562-.25.375-.376.871-.376 1.489 0 .627.125 1.122.376 1.487.25.365.58.546.986.546.42 0 .753-.177.998-.532.246-.354.37-.846.37-1.475 0-.656-.12-1.166-.359-1.531-.238-.364-.564-.546-.978-.546z" transform="translate(-119 -870) translate(104 753) translate(15 117) translate(.052 .143)"/>
<path d="M.332 2.515v14.383l11.671 2.295V.397L.333 2.515zm7.81 9.623c-.493.609-1.136.914-1.93.914-.773 0-1.403-.295-1.89-.885-.486-.59-.73-1.358-.73-2.305 0-1 .247-1.81.741-2.427.495-.618 1.15-.927 1.966-.927.77 0 1.393.295 1.869.887.476.591.713 1.37.713 2.338.001.995-.246 1.797-.739 2.405z" transform="translate(-119 -870) translate(104 753) translate(15 117) translate(.052 .143)"/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20">
<g fill="none" fill-rule="evenodd">
<g fill="#DBA702" fill-rule="nonzero">
<g>
<g>
<path d="M12.394 4.525v3.677l1.327.809c.035.01.111.01.146 0l5.714-3.728c0-.441-.425-.758-.665-.758h-6.522z" transform="translate(-119 -835) translate(104 753) translate(15 82)"/>
<path d="M12.394 9.574l1.211.805c.171.121.377 0 .377 0-.205.121 5.6-3.61 5.6-3.61v6.757c0 .736-.487 1.044-1.034 1.044h-6.155V9.574z" transform="translate(-119 -835) translate(104 753) translate(15 82)"/>
<g>
<path d="M6.07 7.736c-.413 0-.742.187-.985.562-.243.375-.365.871-.365 1.489 0 .627.122 1.122.365 1.487s.562.546.956.546c.406 0 .728-.177.966-.532.238-.354.358-.846.358-1.475 0-.656-.116-1.166-.347-1.531-.231-.364-.547-.546-.948-.546z" transform="translate(-119 -835) translate(104 753) translate(15 82) translate(.05 .143)"/>
<path d="M.322 2.515v14.383l11.306 2.295V.397L.322 2.515zm7.566 9.623c-.478.609-1.101.914-1.87.914-.75 0-1.359-.295-1.83-.885-.472-.59-.709-1.358-.709-2.305 0-1 .24-1.81.719-2.427.48-.618 1.114-.927 1.904-.927.746 0 1.35.295 1.81.887.461.591.692 1.37.692 2.338 0 .995-.239 1.797-.716 2.405z" transform="translate(-119 -835) translate(104 753) translate(15 82) translate(.05 .143)"/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="20" viewBox="0 0 22 20">
<g fill="none" fill-rule="evenodd">
<g fill="#5F01D1" fill-rule="nonzero">
<g>
<g>
<g>
<path d="M0 4.868h4.29l2.498 6.39 2.53-6.39h4.177l-6.29 15.129H3.003l1.722-4.01L0 4.869zm18.358 5.106H13.68L17.832 0h4.661l-4.135 9.974zm-3.452.955c1.435 0 2.599 1.163 2.599 2.599 0 1.435-1.164 2.598-2.6 2.598-1.434 0-2.598-1.163-2.598-2.598 0-1.436 1.164-2.6 2.599-2.6z" transform="translate(-119 -905) translate(104 753) translate(15 152) translate(0 .11)"/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 708 B

View file

@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_14504_113611)">
<path 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="#327289"/>
<path 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="#327289"/>
</g>
<defs>
<clipPath id="clip0_14504_113611">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="20" viewBox="0 0 17 20">
<g fill="none" fill-rule="evenodd">
<g fill="#606060" fill-rule="nonzero">
<g>
<g>
<path d="M5.44 20C2.463 19.983 0 13.907 0 10.812c0-5.055 3.793-6.161 5.254-6.161.659 0 1.362.258 1.982.487.434.16.883.325 1.132.325.15 0 .501-.14.812-.264.663-.263 1.487-.591 2.447-.591h.006c.717 0 2.89.157 4.198 2.12l.306.46-.44.332c-.63.475-1.778 1.341-1.778 3.058 0 2.032 1.3 2.814 1.925 3.19.276.166.561.337.561.71 0 .245-1.95 5.492-4.782 5.492-.692 0-1.182-.209-1.614-.392-.437-.186-.815-.346-1.438-.346-.316 0-.715.149-1.138.307-.578.216-1.232.461-1.974.461h-.02zM11.927 0C12.001 2.66 10.1 4.504 8.2 4.388 7.887 2.266 10.1 0 11.927 0z" transform="translate(-119 -763) translate(104 753) translate(15 10)"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 907 B

View file

@ -19,7 +19,11 @@ const applicationConfig = Object.freeze({
ISS_DEV_CMS_DOMAIN: 'https://digitalisscms.dev.safelite.io',
CASH_PARENT_ACCOUNT_NUMBER: 167132,
ITAC_FAIR_AND_REASONABLE_BILLTO: '214616', // Lib Mutal FnR BillTo
ITAC_CASH_BILLTO: '283393' // Lib Mutal ITAC Cash BillTo
ITAC_CASH_BILLTO: '283393', // Lib Mutal ITAC Cash BillTo
GOOGLE_CALENDAR: 'https://www.google.com/calendar/render?action=TEMPLATE',
YAHOO_CALENDAR: 'https://calendar.yahoo.com/?v=60',
OUTLOOK_CALENDAR:
'https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent'
});
export default applicationConfig;

View file

@ -0,0 +1,8 @@
const calendarOptions = Object.freeze({
ICAL: 'iCal',
GOOGLE: 'Google',
OUTLOOK: 'Outlook',
OUTLOOKCOM: 'Outlook.com',
YAHOO: 'Yahoo'
});
export default calendarOptions;

View file

@ -0,0 +1,7 @@
const calendarStatus = Object.freeze({
FREE: 'Free',
BUSY: 'Busy',
TENTATIVE: 'Tentative',
OUT_OF_THE_OFFICE: 'OutOfTheOffice'
});
export default calendarStatus;

View file

@ -0,0 +1,15 @@
const MaskaFormattedMasks = {
PHONE_NUMBER: {
mask: 'x##-###-####',
tokens: {
token: 'x',
pattern: '[2-9]'
}
},
ZIPCODE: {
mask: '#####',
tokens: null
}
};
export default MaskaFormattedMasks;

View file

@ -9,6 +9,7 @@ const queryStrings = Object.freeze({
CARD_TYPE: 'sgcardtype',
DISPLAY_PAY_IN_ADVANCE_ALERT: 'displayPayInAdvanceAlert',
ERROR: 'error',
TOKEN: 'token',
LAST_FOUR: 'last_four',
REFERENCE_NUMBER: 'req_reference_number',
REFERRAL_SEQ_NUM: 'referralseqnum',

View file

@ -0,0 +1,6 @@
const serviceType = Object.freeze({
REPLACEMENT: 'replacement',
REPAIR: 'repair',
REPLACEMENT_AND_RECALIBRATION: 'replacement and recalibration'
});
export default serviceType;

View file

@ -19,7 +19,9 @@
:id="inputId"
:ref="inputId"
v-model.trim="value"
v-maska="mask"
v-maska
:data-maska="getMaskFromProp"
:data-maska-tokens="getTokensFromProp"
class="form-control"
:type="type"
:name="inputId"
@ -97,7 +99,7 @@ export default {
iconRight: Boolean, // Place icon on right side of text input, otherwise default is left if hasIcon prop is used
hasError: Boolean,
mask: {
type: String,
type: [String, Object],
default: ''
},
validationRules: String,
@ -183,6 +185,21 @@ export default {
return questionText;
}
},
getMaskFromProp() {
if (this.mask) {
if (this.mask.mask) {
return this.mask.mask;
}
return this.mask;
}
return null;
},
getTokensFromProp() {
if (this.mask?.tokens) {
return `{ '${this.mask.tokens.token}': { 'pattern': '${this.mask.tokens.pattern}' }}`;
}
return null;
}
},
watch: {

View file

@ -0,0 +1,75 @@
import calendarStatus from '@/constants/calendar-status';
import { getDateFormat } from '@/helpers/date-helper';
function ToCalendarFileString(str, replacementArg = '<p>') {
return str.replace('\r\n', replacementArg);
}
export function getCalendarFile(calFile) {
const dateFormat = 'yyyyMMddTHHmmss';
const calEvent = [];
calEvent.push('BEGIN:VCALENDAR');
calEvent.push('VERSION:2.0');
calEvent.push('BEGIN:VEVENT');
calEvent.push(`DTSTAMP:${calFile.TimeStamp}`);
calEvent.push(`UID:${calFile.UniqueId}@safelite.com`);
calEvent.push('PRODID:noreply@safelite.com');
switch (calFile.Status) {
case calendarStatus.BUSY:
calEvent.push('X-MICROSOFT-CDO-BUSYSTATUS:BUSY');
break;
case calendarStatus.FREE:
calEvent.push('TRANSP:TRANSPARENT');
break;
case calendarStatus.TENTATIVE:
calEvent.push('STATUS:TENTATIVE');
break;
case calendarStatus.OUT_OF_THE_OFFICE:
calEvent.push('X-MICROSOFT-CDO-BUSYSTATUS:OOF');
break;
default:
// throw new Exception("Invalid CalendarStatus");
}
if (calFile.AllDayEvent) {
calEvent.push(`DTSTART;VALUE=DATE:${getDateFormat(calFile.StartDate, dateFormat)}`);
calEvent.push(`DTEND;;VALUE=DATE:${getDateFormat(calFile.EndDate, dateFormat)}`);
} else {
calEvent.push(`DTSTART:${getDateFormat(calFile.StartDate, dateFormat)}`);
calEvent.push(`DTEND:${getDateFormat(calFile.EndDate, dateFormat)}`);
}
calEvent.push(`SUMMARY:${ToCalendarFileString(calFile.Subject, ' ')}`);
if (calFile.Location != null && calFile.Location !== '') {
calEvent.push(`LOCATION:${ToCalendarFileString(calFile.Location, ' ')}`);
}
if (calFile.Body != null && calFile.Body !== '') {
if (calFile.IsHTML) {
calEvent.push(`DESCRIPTION:${ToCalendarFileString(calFile.Body)}`);
calEvent.push('X-ALT-DESC;FMTTYPE=text/html:'
+ '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">'
+ '<HTML><HEAD><TITLE></TITLE></HEAD><BODY>'
+ '<P DIR=LTR><SPAN LANG="en-us">'
+ `</SPAN>${
ToCalendarFileString(calFile.Body)
}</BODY></HTML>`);
} else {
calEvent.push(`DESCRIPTION:${ToCalendarFileString(calFile.Body)}`);
}
}
calEvent.push('END:VEVENT');
calEvent.push('END:VCALENDAR');
return calEvent.join('\r\n');
}
export function download(filename, fileBody) {
const element = document.createElement('a');
element.setAttribute('href', `data:text/plain;charset=utf-8,${encodeURIComponent(fileBody)}`);
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}

View file

@ -104,3 +104,72 @@ export function get12HourTimeFormat(time) {
}
return timeString.join(''); // return adjusted time or original string
}
export function getDateFormat(date, format) {
if (date instanceof Date !== true) return;
const year = date.getFullYear();
const month = (`0${date.getMonth() + 1}`).slice(-2);
const day = (`0${date.getDate()}`).slice(-2);
const hour = (`0${date.getHours()}`).slice(-2);
const minute = (`0${date.getMinutes()}`).slice(-2);
const second = (`0${date.getSeconds()}`).slice(-2);
// eslint-disable-next-line consistent-return
return format
.replace('yyyy', year)
.replace('MM', month)
.replace('dd', day)
.replace('hh', hour)
.replace('HH', hour)
.replace('mm', minute)
.replace('ss', second);
}
export function padTo2Digits(time) {
// Use the built-in method toString() with a radix of 10 to convert the time value to a decimal string
// eslint-disable-next-line no-param-reassign
time = time.toString(10);
// Use the conditional operator to check if the length of the string is less than 2
return time.length < 2
// If yes, prepend a '0' to the string and return it
// If no, return the original string
? `0${time}` : time;
}
export function convertMsToTime(milliseconds) {
let seconds = Math.floor(milliseconds / 1000);
let minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
seconds %= 60;
minutes %= 60;
// commenting to get 24 time format
// hours = hours % 24;
return `${padTo2Digits(hours)}${padTo2Digits(minutes)}`;
}
export function calculateDuration(startDate, endDate) {
if (startDate instanceof Date !== true) return;
if (endDate instanceof Date !== true) return;
// eslint-disable-next-line consistent-return
return convertMsToTime(endDate - startDate);
}
export function combineDateAndTime(date, time) {
// Use the Date.parse() method to convert the date and time strings to a numeric value
const timestamp = Date.parse(`${date}T${time}`);
// Use the new Date() constructor to create a new date object from the numeric value
const newDate = new Date(timestamp);
// Return the new date object
return newDate;
}
export function addMinutes(date, minutes) {
return new Date(date.getTime() + minutes * 60000);
}
export function shortTimeString(date) {
// Use a ternary operator to check if the input is a valid date object
return date instanceof Date
// Use the built-in method toLocaleTimeString() to get the short time string in the current locale
// Return undefined if the input is not a valid date object
? date.toLocaleTimeString('en-us', { hour: 'numeric', minute: 'numeric', hour12: true }) : undefined;
}

View file

@ -4,8 +4,8 @@ import { updateOrCreateISSCookie } from '@/helpers/cookie-helper';
/*
Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing
*/
async function saveSessionHelper(store) {
const savedSessionInfo = await store.saveSession();
async function saveSessionHelper(store, { submitAfterSave }) {
const savedSessionInfo = await store.saveSession({ submitAfterSave });
if (savedSessionInfo) {
store.setSaveSessionInfo(savedSessionInfo.data);
}
@ -17,11 +17,11 @@ async function saveSessionHelper(store) {
This will also set Referral information in the store after saving, and then
update the cookie. To force synchronous behavior pass in 'true' for shouldAwaitSaveSessionQueue
*/
export async function saveSession({ shouldAwaitSaveSessionQueue = false }) {
export async function saveSession({ shouldAwaitSaveSessionQueue = false, submitAfterSave = false }) {
const store = useMainStore();
const saveSessionPromise = store.applicationUser.saveSessionPromise
? store.applicationUser.saveSessionPromise.then(() => saveSessionHelper(store))
: saveSessionHelper(store);
? store.applicationUser.saveSessionPromise.then(() => saveSessionHelper(store, { submitAfterSave }))
: saveSessionHelper(store, { submitAfterSave });
store.setSaveSessionPromise(saveSessionPromise);

View file

@ -9,6 +9,8 @@ Object {
"amountDue": "AmountDueTextWidget",
"basePrice": "BasePriceWidget",
"deductible": "DeductibleWidget",
"mobileFee": "MobileServiceWidget",
"recycleFee": "RecycleFeeWidget",
"salesTax": "SalesTaxWidget",
"subtotal": "SubtotalWidget",
},

View file

@ -7,6 +7,7 @@ import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore } from '@/store';
import coverageStatuses from '@/constants/coverage-statuses';
import { formatAmountInDollars } from '@/helpers/text-helper.js';
import partTypeStrings from '@/constants/part-type-strings';
const VERIFYING_COVERAGE = 'Verifying coverage';
@ -41,7 +42,13 @@ function getMountedComponent(mainInitialState = {}, initialData = {}, propsData
describe('cart-dropdown component', () => {
test('initial data rendered as expected', () => {
// Arrange
const { wrapper } = getMountedComponent();
const { wrapper } = getMountedComponent({
order: {
lineItems: {
supportingItems: []
}
}
});
// Assert
expect(wrapper.vm.$data).toMatchSnapshot();
@ -568,5 +575,85 @@ describe('cart-dropdown component', () => {
expect(result).toBe(dollarAmount);
});
});
describe('Recyle fee', () => {
test('Shows recycle fee when preset in supported items', () => {
// Arrange
const reference = '#recycle-fee-item';
const {wrapper} = getMountedComponent({
order: {
lineItems: {
supportingItems: [{
partType: partTypeStrings.REPLACE_FEE
}]
}
}
});
// Act
const cartDeductible = wrapper.find(reference);
// Assert
expect(wrapper.vm.recycleFeeCartItem).not.toBeNull();
expect(cartDeductible.exists()).toBeTruthy();
});
test('Hides recycle fee when not in supported items', () => {
// Arrange
const reference = '#recycle-fee-item';
const {wrapper} = getMountedComponent({
order: {
lineItems: {
supportingItems: []
}
}
});
// Act
const cartDeductible = wrapper.find(reference);
// Assert
expect(wrapper.vm.recycleFeeCartItem).toBeNull();
expect(cartDeductible.exists()).toBeFalsy();
});
});
describe('Mobile Fee', () => {
test('Shows mobile fee when preset', () => {
// Arrange
const reference = '#mobile-fee-item';
const { wrapper } = getMountedComponent({
order: {
lineItems: {
supportingItems: [],
mobileFee: {}
}
}
});
// Act
const cartDeductible = wrapper.find(reference);
// Assert
expect(wrapper.vm.mobileFeeCartItem).not.toBeNull();
expect(cartDeductible.exists()).toBeTruthy();
});
test('Hides recycle fee when not in supported items', () => {
// Arrange
const reference = '#mobile-fee-item';
const { wrapper } = getMountedComponent({
order: {
lineItems: {
supportingItems: [],
mobileFee: null
}
}
});
// Act
const cartDeductible = wrapper.find(reference);
// Assert
expect(wrapper.vm.mobileFeeCartItem).toBeNull();
expect(cartDeductible.exists()).toBeFalsy();
});
});
});
});

View file

@ -21,7 +21,7 @@
class="color-darker-gray">
<div
id="cart-deductible-or-base-price"
class="cart-deductible-or-base-price">
class="cart-item cart-gray">
<div
v-if="showDeductibleLineItem"
id="cart-deductible"
@ -43,45 +43,69 @@
<!-- Nonpackaged Cart Items -->
<!-- This will be a loop when this is implemented -->
<div class="non-packaged-cart-item">
<textLink
linkType="text"
text="Recycling"
href="javascript:void(0)"
@clickEvent="openModal(recyclingModalCmsWidgetName)" />
</div>
</div>
<div
id="cart-footer"
class="cart-footer">
<div
id="cart-subtotal-and-tax"
class="subtotal-and-tax">
<div
id="cart-subtotal"
class="col d-flex justify-content-between">
<span id="subtotal-label">{{ subtotalLabel }}</span>
<span id="subtotal-value">{{ getDisplayed(subTotal) }}</span>
id="mobile-fee"
class="cart-item">
<div
v-if="mobileFeeCartItem"
id="mobile-fee-item"
class="d-flex justify-content-between align-items-center">
<span id="mobile-fee-label">{{ mobileFeeCartItem.name }}</span>
<span id="mobile-fee-value">{{ getDisplayed(mobileFeeCartItem.subTotal) }}</span>
</div>
</div>
<div
id="cart-sales-tax"
class="pt-1 col d-flex justify-content-between">
<span id="sales-tax-label">{{ salesTaxLabel }}</span>
<span id="sales-tax-value">{{ formatAmountInDollars(salesTax) }}</span>
id="recycle-fee"
class="cart-item cart-gray">
<div
v-if="recycleFeeCartItem"
id="recycle-fee-item"
class="d-flex justify-content-between align-items-center">
<span id="recycle-fee-label">
<textLink
linkType="text"
:text="recycleFeeCartItem.name"
href="javascript:void(0)"
@clickEvent="openModal(recyclingModalCmsWidgetName)" />
</span>
<span id="recycle-fee-value">{{ getDisplayed(recycleFeeCartItem.subTotal) }}</span>
</div>
</div>
</div>
<div
id="bottom-amount-due"
class="bottom-amount-due col d-flex justify-content-between">
<span id="bottom-amount-due-label">{{ amountDueLabel }}</span>
<span id="bottom-amount-due-value">{{ getDisplayed(amountDue) }}</span>
id="cart-footer"
class="cart-footer">
<div
id="cart-subtotal-and-tax"
class="subtotal-and-tax">
<div
id="cart-subtotal"
class="col d-flex justify-content-between">
<span id="subtotal-label">{{ subtotalLabel }}</span>
<span id="subtotal-value">{{ formatAmountInDollars(subTotal) }}</span>
</div>
<div
id="cart-sales-tax"
class="pt-1 col d-flex justify-content-between">
<span id="sales-tax-label">{{ salesTaxLabel }}</span>
<span id="sales-tax-value">{{ getDisplayed(salesTax) }}</span>
</div>
</div>
<div
id="bottom-amount-due"
class="bottom-amount-due col d-flex justify-content-between">
<span id="bottom-amount-due-label">{{ amountDueLabel }}</span>
<span id="bottom-amount-due-value">{{ getDisplayed(amountDue) }}</span>
</div>
</div>
</div>
<contentGroupModal
ref="RecycleModal"
cmsWidgetName="RecycleModal">
<textBlock cmsWidgetName="RecycleTextBlock" />
</contentGroupModal>
</div>
<contentGroupModal
ref="RecycleModal"
cmsWidgetName="RecycleModal">
<textBlock cmsWidgetName="RecycleTextBlock" />
</contentGroupModal>
</div>
</template>
@ -92,6 +116,8 @@ import textBlock from '@/digital-components/text-block/text-block.vue';
import textLink from '@/ux-components/text-link/text-link.vue';
import getPriceOfLineItems from '@/helpers/price-calculator.js';
import { formatAmountInDollars } from '@/helpers/text-helper.js';
import baseMixin from '@/mixins/base-mixin';
import partTypeStrings from '@/constants/part-type-strings';
import widgetFields from '@/constants/cms-widget-fields.js';
const VERIFYING_COVERAGE = 'Verifying coverage';
@ -105,6 +131,10 @@ export default {
recyclingModalCmsWidgetName: String,
showAsPaid: Boolean
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
const { currentDeductible } = useMainStore().order;
const { supportingItems, glassParts, otherParts } = useMainStore().lineItems;
@ -122,7 +152,9 @@ export default {
deductible: 'DeductibleWidget',
basePrice: 'BasePriceWidget',
subtotal: 'SubtotalWidget',
salesTax: 'SalesTaxWidget'
salesTax: 'SalesTaxWidget',
recycleFee: 'RecycleFeeWidget',
mobileFee: 'MobileServiceWidget'
}
};
},
@ -163,6 +195,18 @@ export default {
},
salesTaxLabel() {
return this.getCmsContent(this.widget.salesTax, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
recycleFeeCartItem() {
return this.getCartItem(
this.getCmsContent(this.widget.recycleFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT),
this.mainStore.order.lineItems.supportingItems.find((lineItem) => lineItem.partType === partTypeStrings.REPLACE_FEE)
);
},
mobileFeeCartItem() {
return this.getCartItem(
this.getCmsContent(this.widget.mobileFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT),
this.mainStore.order.lineItems.mobileFee
);
}
},
methods: {
@ -176,6 +220,19 @@ export default {
return this.isUnverified && this.showDeductibleLineItem
? VERIFYING_COVERAGE
: formatAmountInDollars(amount);
},
getCartItem(label, lineItem) {
let cartItem = null;
if (lineItem) {
cartItem = {
name: label,
subTotal: baseMixin.methods.getTotalLineItemPrice(lineItem, false),
salesTax: lineItem.salesTax ?? 0
};
}
return cartItem;
}
}
};
@ -201,13 +258,16 @@ export default {
transition: all 350ms ease-in;
overflow: hidden;
visibility: hidden;
margin-top: 1rem;
.cart-deductible-or-base-price {
background-color: $gray-100;
margin-top: 1rem;
.cart-item {
padding: 0.25rem 1.5rem;
}
.cart-gray {
background-color: $gray-100;
}
.cart-footer {
border-top: 1px solid $green;
}
@ -269,6 +329,9 @@ export default {
a {
text-decoration: none;
}
}
:deep(#recycle-fee-label a){
line-height: initial;
}
</style>

View file

@ -39,7 +39,7 @@
inputId="phoneNumberField"
cmsWidgetName="PhoneNumberQuestion"
isRequired
:mask="mask"
:mask="phoneMask"
disableAutoFill
:validationRules="rules.phoneNumber" />
<textboxQuestion
@ -80,6 +80,7 @@ import routerParams from '@/router/router-constants/router-params';
import canNavigateBackFromBailout from '@/helpers/bailout-helper';
import BailoutCode from '@/constants/bailoutCode';
import issPageValues from '@/router/router-constants/issPage-values';
import MaskaFormattedMasks from '@/constants/maska-masks';
export default {
name: 'bailout-page',
@ -176,15 +177,8 @@ export default {
canNavigateBack() {
return canNavigateBackFromBailout();
},
mask() {
return {
mask: 'x##-###-####',
tokens: {
x: {
pattern: /[2-9]/
}
}
};
phoneMask() {
return MaskaFormattedMasks.PHONE_NUMBER;
}
},
methods: {

View file

@ -46,7 +46,7 @@
inputId="phoneNumber"
:cmsWidgetName="widget.phoneNumberQuestion"
isRequired
:mask="mask"
:mask="phoneMask"
:validationRules="rules.phoneNumber" />
<checkbox
ref="requestTextUpdatesCheckbox"
@ -112,6 +112,7 @@ import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { useMainStore } from '@/store/index.js';
import globalRules from '@/constants/global-rules.js';
import MaskaFormattedMasks from '@/constants/maska-masks';
export default {
name: 'contact-details',
@ -181,15 +182,8 @@ export default {
textUpdateDisclaimerText() {
return `*${this.getCmsContent(this.widget.disclaimer, 'Text')}`;
},
mask() {
return {
mask: 'x##-###-####',
tokens: {
x: {
pattern: /[2-9]/
}
}
};
phoneMask() {
return MaskaFormattedMasks.PHONE_NUMBER;
}
},
methods:

View file

@ -0,0 +1,187 @@
// Components
import calendarOptions from '@/constants/calendar-options';
// Supporting Files
import { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore } from '@/store';
import addToCalendar from '@/layouts/order-confirmation/add-to-calendar/add-to-calendar.vue';
const appointmentText = 'Appointment content';
function setupMocks({ customMountOptions }) {
const mountOptions = getMountOptions({
...customMountOptions
});
mountOptions.mixins = [
{
methods: {
getCmsContent: jest.fn().mockImplementation(() => appointmentText)
}
}
];
const wrapper = shallowMount(addToCalendar, mountOptions);
wrapper.vm.$refs.calendarModalQuestion.openModal = jest.fn();
return { wrapper };
}
beforeEach(() => {
jest.restoreAllMocks();
jest.clearAllMocks();
useMainStore().submittedOrder = {
schedule: {
date: '2019-01-01',
startTime: '09:00',
endTime: '10:00',
routeCode: '000'
},
lineItems: {
glassParts: [
{
partNumber: 'ABC123'
}
],
supportingItems: []
},
serviceLocation: {
address: '',
address2: null,
city: '',
state: 'AZ',
appointmentType: 'Inshop',
zipCode: '12345',
zipCodeCtu: '01234',
provider: {
providerNumber: '123',
address: {
streetAddress: 'test1',
city: 'test',
state: 'AZ',
zipCode: '12345',
zipCodeCtu: '01234'
}
}
},
damage: {
isRepair: false
},
referralNumber: '1234567',
payment: {
isInsurance: true
}
};
});
afterEach(() => {
useMainStore().submittedOrder = {};
jest.restoreAllMocks();
jest.clearAllMocks();
});
describe('Add-to-calendar methods...', () => {
test('Add-to-calendar should trigger openModal method', () => {
// Arrange
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
appointment: {
RefrrlSeqNum: '123456',
StartDate: new Date(2023, 9, 11, 12, 0, 0),
EndDate: new Date(2023, 9, 11, 13, 0, 0),
Subject: 'Meeting with John',
Location: 'Conference Room',
Body: 'Discuss the project progress',
IsHTML: true,
Duration: '1220'
}
}
}
});
// Act
wrapper.vm.openCalendarModal();
// Assert
expect(wrapper.vm.$refs.calendarModalQuestion.openModal).toBeCalled();
});
test('getCalendarData should return expected model value.', () => {
// Arrange
const type = calendarOptions.OUTLOOKCOM;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
appointment: {
RefrrlSeqNum: '123456',
StartDate: new Date(2023, 9, 11, 12, 0, 0),
EndDate: new Date(2023, 9, 11, 13, 0, 0),
Subject: 'Meeting with John',
Location: 'Conference Room',
Body: 'Discuss the project progress',
IsHTML: true,
Duration: '1220'
}
}
}
});
// Act
const testValue = wrapper.vm.getCalendarData(type);
// Assert
expect(testValue.name).toEqual(calendarOptions.OUTLOOKCOM);
});
test('setCalendarAppointment should call getAppointment method for iCAL and Outlook.', () => {
// Arrange
const newValue = { name: calendarOptions.OUTLOOK };
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
appointment: {
RefrrlSeqNum: '123456',
StartDate: new Date(2023, 9, 11, 12, 0, 0),
EndDate: new Date(2023, 9, 11, 13, 0, 0),
Subject: 'Meeting with John',
Location: 'Conference Room',
Body: 'Discuss the project progress',
IsHTML: true,
Duration: '1220'
}
}
}
});
wrapper.vm.getAppointment = jest.fn();
// Act
wrapper.vm.setCalendarAppointment(newValue);
// Assert
expect(wrapper.vm.getAppointment).toHaveBeenCalled();
});
test('setCalendarAppointment should call window.open for Google, yahoo and Outlook.com.', () => {
// Arrange
const newValue = { name: calendarOptions.GOOGLE };
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
appointment: {
RefrrlSeqNum: '123456',
StartDate: new Date(2023, 9, 11, 12, 0, 0),
EndDate: new Date(2023, 9, 11, 13, 0, 0),
Subject: 'Meeting with John',
Location: 'Conference Room',
Body: 'Discuss the project progress',
IsHTML: true,
Duration: '1220'
}
}
}
});
window.open = jest.fn();
// Act
wrapper.vm.setCalendarAppointment(newValue);
// Assert
expect(window.open).toHaveBeenCalledTimes(1);
});
});

View file

@ -0,0 +1,325 @@
<template>
<div class="calendar-section">
<div class="add-to-calendar">
<button @click="openCalendarModal">
<span class="add-to-calendar-span">
<img
src="@/assets/img/icons/add-to-calendar.svg"
class="add-to-calendar-img" />
<span class="add-to-calendar-text">Add to calendar</span>
</span>
</button>
</div>
<calendarModalQuestion
ref="calendarModalQuestion"
v-model="selectedCalendarOption"
customComponentId="calendarModalQuestion"
cmsWidgetName="CalendarModalQuestion"
:calendarOptionsData="calendarValues"
@calendarModalClosed="calendarModalClosed">
</calendarModalQuestion>
</div>
</template>
<script>
import calendarOptions from '@/constants/calendar-options';
import calendarStatus from '@/constants/calendar-status';
import { getDateFormat,
calculateDuration,
combineDateAndTime,
shortTimeString,
addMinutes } from '@/helpers/date-helper';
import { AppointmentTypeStrings, RouteCodeFlags } from '@/constants/schedule-constants';
import { getCalendarFile, download } from '@/helpers/add-to-calendar-helper';
import { useMainStore } from '@/store';
import serviceType from '@/constants/service-type';
import applicationConfig from '@/constants/application-config.js';
import calendarModalQuestion from '@/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.vue';
export default {
name: 'add-to-calendar',
components: {
calendarModalQuestion
},
props: {
mobileWidgetName: String,
inShopWidgetName: String,
dropOffWidgetName: String,
overnightDropOffWidgetName: String,
allDayDropOffWidgetName: String,
sameDayDropOffWidgetName: String,
serviceLocationFullAddress: String,
providerFullAddress: String,
appointmentType: String,
scheduleDate: String,
scheduleStartTime: String,
scheduleEndTime: String
},
setup() {
const mainStore = useMainStore();
const { submittedOrder } = mainStore;
return { mainStore, submittedOrder };
},
data() {
return {
selectedCalendarOption: null
};
},
computed: {
calendarValues: {
get() {
const obj = [];
const options = Object.values(calendarOptions);
for (let i = 0; i < options.length; i++) {
obj[i] = this.getCalendarData(options[i]);
}
return obj;
}
},
IsSameDayDropOff() {
const todaysDate = new Date().toISOString().split('T')[0];
return this.scheduleDate === todaysDate;
},
AddToCalendar_Mobile_Subject() {
return this.getCmsContent(this.mobileWidgetName, 'HeaderText');
},
AddToCalendar_Mobile_Body() {
return this.getCmsContent(this.mobileWidgetName, 'BodyText');
},
AddToCalendar_InShop_Subject() {
return this.getCmsContent('AddToCalendar_InShop', 'HeaderText');
},
AddToCalendar_InShop_Body() {
return this.getCmsContent(this.inShopWidgetName, 'BodyText');
},
AddToCalendar_DropOff_Subject() {
return this.getCmsContent(this.dropOffWidgetName, 'HeaderText');
},
AddToCalendar_DropOff_Body() {
return this.getCmsContent(this.dropOffWidgetName, 'BodyText');
},
AddToCalendar_OvernightDropOff_Subject() {
return this.getCmsContent(this.overnightDropOffWidgetName, 'HeaderText');
},
AddToCalendar_OvernightDropOff_Body() {
return this.getCmsContent(this.overnightDropOffWidgetName, 'BodyText');
},
AddToCalendar_AllDayDropOff_Subject() {
return this.getCmsContent(this.allDayDropOffWidgetName, 'HeaderText');
},
AddToCalendar_AllDayDropOff_Body() {
return this.getCmsContent(this.allDayDropOffWidgetName, 'BodyText');
},
AddToCalendar_SameDayDropOff_Subject() {
return this.getCmsContent(this.sameDayDropOffWidgetName, 'HeaderText');
},
AddToCalendar_SameDayDropOff_Body() {
return this.getCmsContent(this.sameDayDropOffWidgetName, 'BodyText');
},
UniqueId() {
return this.submittedOrder.referralNumber?.toString();
},
ServiceType() {
const { isRepair } = this.submittedOrder.damage;
const { hasRecalibrationPart } = this.mainStore;
if (!isRepair) {
if (hasRecalibrationPart) {
return serviceType.REPLACEMENT_AND_RECALIBRATION;
}
return serviceType.REPLACEMENT;
}
return serviceType.REPAIR;
},
RouteCode() {
return this.submittedOrder.schedule.routeCode;
},
Appointment() {
let subject = '';
let location = '';
const startDateTime = combineDateAndTime(this.scheduleDate, this.scheduleStartTime);
let endDateTime = combineDateAndTime(this.scheduleDate, this.scheduleEndTime);
let body = '';
const isHTML = false;
let duration = '';
if (this.appointmentType === AppointmentTypeStrings.MOBILE
|| this.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
location = this.serviceLocationFullAddress?.replace('<br/>', '');
subject = this.AddToCalendar_Mobile_Subject?.replace(
'{custom:serviceType}',
this.ServiceType
);
body = this.AddToCalendar_Mobile_Body;
} else {
location = this.providerFullAddress?.replace('<br/>', '');
if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
if (this.RouteCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
subject = this.AddToCalendar_OvernightDropOff_Subject;
body = this.AddToCalendar_OvernightDropOff_Body;
} else if (this.RouteCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
if (this.IsSameDayDropOff) {
subject = this.AddToCalendar_SameDayDropOff_Subject;
endDateTime = addMinutes(startDateTime, 120);
body = this.AddToCalendar_SameDayDropOff_Body;
} else {
endDateTime = addMinutes(startDateTime, 120);
subject = this.AddToCalendar_AllDayDropOff_Subject?.replace(
'{custom:startDateTime}',
shortTimeString(startDateTime)
).replace('{custom:endDateTime}', shortTimeString(endDateTime));
body = this.AddToCalendar_AllDayDropOff_Body?.replace(
'{custom:endDateTime}',
shortTimeString(endDateTime)
);
}
} else {
subject = this.AddToCalendar_DropOff_Subject?.replace(
'{custom:serviceType}',
this.ServiceType
);
body = this.AddToCalendar_DropOff_Body?.replace(
'{custom:address}',
location
);
}
} else {
subject = this.AddToCalendar_InShop_Subject?.replace(
'{custom:serviceType}',
this.ServiceType
);
body = this.AddToCalendar_InShop_Body?.replace('{custom:address}', location);
}
duration = calculateDuration(startDateTime, endDateTime);
}
return {
Subject: subject,
Location: location,
StartDate: startDateTime,
EndDate: endDateTime,
RefrrlSeqNum: this.UniqueId,
Body: body,
IsHTML: isHTML,
Duration: duration
};
}
},
watch: {
selectedCalendarOption(newValue) {
if (newValue) {
this.setCalendarAppointment(newValue);
}
}
},
methods: {
setCalendarAppointment(calendarOption) {
if (
calendarOption.name === calendarOptions.ICAL
|| calendarOption.name === calendarOptions.OUTLOOK
) {
this.getAppointment();
} else {
window.open(calendarOption.url);
}
},
calendarModalClosed() {
// Clear the selectedOption if close the modal
this.selectedCalendarOption = null;
},
openCalendarModal() {
this.$refs.calendarModalQuestion.openModal();
},
getCalendarData(type) {
let URL = '';
const dateFormat = 'yyyyMMddTHHmmss';
const outlookComTimeSpanFormat = 'yyyy-MM-ddTHH:mm:ss';
if (type === calendarOptions.OUTLOOKCOM) {
URL = `${applicationConfig.OUTLOOK_CALENDAR}&startdt=${encodeURIComponent(
getDateFormat(this.Appointment.StartDate, outlookComTimeSpanFormat)
)}&enddt=${encodeURIComponent(
getDateFormat(this.Appointment.EndDate, outlookComTimeSpanFormat)
)}&subject=${encodeURIComponent(
this.Appointment.Subject
)}&body=${encodeURIComponent(this.Appointment.Body)}&location=${encodeURIComponent(
this.Appointment.Location
)}`;
}
if (type === calendarOptions.GOOGLE) {
URL = `${applicationConfig.GOOGLE_CALENDAR}&text=${encodeURIComponent(
this.Appointment.Subject
)}&dates=${encodeURIComponent(
getDateFormat(this.Appointment.StartDate, dateFormat)
)}/${encodeURIComponent(
getDateFormat(this.Appointment.EndDate, dateFormat
))}&details=${encodeURIComponent(
this.Appointment.Body
)}&location=${encodeURIComponent(this.Appointment.Location)}&sf=true&output=xml`;
}
if (type === calendarOptions.YAHOO) {
URL = `${applicationConfig.YAHOO_CALENDAR}&TITLE=${encodeURIComponent(
this.Appointment.Subject
)}&DESC=${encodeURIComponent(this.Appointment.Body)}&ST=${encodeURIComponent(
getDateFormat(this.Appointment.StartDate, dateFormat)
)}&DUR=${this.Appointment.Duration}&in_loc=${encodeURIComponent(
this.Appointment.Location
)}`;
}
return {
url: URL,
name: type,
icon: require(`@/assets/img/icons/${type}.svg`)
};
},
getAppointment() {
let body = this.Appointment.Body;
body = body?.replace('|', '<').replace('~', '>');
const calFile = {
Location: this.Appointment.Location,
Body: body,
IsHTML: this.Appointment.IsHTML,
UniqueId: this.Appointment.RefrrlSeqNum,
Subject: this.Appointment.Subject,
StartDate: this.Appointment.StartDate,
EndDate: this.Appointment.EndDate,
Status: calendarStatus.BUSY
};
const calBody = getCalendarFile(calFile);
download('SafeliteAppointment.ics', calBody);
}
// getInShopAppointmentText() {
// const test = this.getCmsContent('AddToCalendar_InShop_TEST', 'Text');
// return test;
// }
}
};
</script>
<style scoped>
.add-to-calendar-text {
margin: 0 0 0 0.4375rem;
color: #1574a1;
vertical-align: middle;
font-weight: 500;
font-family: "Roboto";
}
.add-to-calendar button {
border: 0;
background-color: #fff;
}
.calendar-section {
display: table;
position: relative;
margin: 0 auto;
}
.calendar-section .add-to-calendar {
padding-top: 1rem;
padding-bottom: 1rem;
}
.calendar-section .add-to-calendar .add-to-calendar-span {
cursor: pointer;
}
.calendar-section .add-to-calendar .add-to-calendar-img {
display: inline;
width: 1.25rem;
}
</style>

View file

@ -0,0 +1,81 @@
<template>
<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">
<span
class="m-0 position-relative"
:class="textPosition">
<img :src="buttonLabel" />
<span class="calendar-text">{{ value }}</span>
</span>
</div>
</baseInputButton>
</template>
<script>
import baseInputButton from '@/digital-components/base-input-button/base-input-button.vue';
import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin.js';
export default {
name: 'calendar-modal-list-button',
components: {
baseInputButton
},
mixins: [inputButtonWrapperMixin]
};
</script>
<style lang="scss" scoped>
.list-button {
outline: none;
input[type="radio"],
input[type="checkbox"] {
position: static; //override bootstrap
&:focus-visible + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:checked + .list-button-content {
color: $black;
font-weight: 500;
background: $blue-100;
box-shadow: 0 0 0 1px $blue;
}
&:checked:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:checked + .list-button-content p,
&:checked + .list-button-content span {
font-weight: 500;
}
&:checked + .list-button-content span:nth-child(2) {
font-weight: 400;
color: $gray-600;
}
}
}
.list-button-content {
color: $gray-600;
position: relative;
background: $white;
transition: all 150ms linear;
border-radius: $border-radius-lg;
border: 0.063rem solid $gray-500;
width: 100%;
outline: none;
span.calendar-text {
margin-left: 0.313rem;
padding: 0.125rem 0.5rem;
}
}
.position-relative {
position: relative;
}
</style>

View file

@ -0,0 +1,41 @@
import { shallowMount } from '@vue/test-utils';
import calendarModalQuestion from '@/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.vue';
describe('calendar-modal-question.vue', () => {
test('When selected calendar option is changed, a correctly selectedCalendarOption should be emitted', async () => {
// Arrange
const url = 'iCal url';
const name = 'iCal';
const icon = 'iCal.svg';
const expectedEmit = [
[
{
url,
name,
icon
}
]
];
const wrapper = shallowMount(calendarModalQuestion, {
propsData: {
calendarOptionsData: [
{
url,
name,
icon
}
]
}
});
wrapper.vm.$refs.calendarOptions.closeModal = jest.fn();
wrapper.vm.selectedCalendarOption = name;
// Act
wrapper.vm.setSelectedCalendarOption();
// Assert
expect(wrapper.emitted()['update:modelValue']).toEqual(expectedEmit);
});
});

View file

@ -0,0 +1,180 @@
<template>
<modal
:ref="modalName"
:fontSize="true"
:headerText="modalHeaderText"
:footerButtonText="footerCloseButtonText"
:onModalClosedCallback="onModalClosed"
class="calendar-modal"
@isModalOpened="setModalStatus"
@footerButtonEvent="setSelectedCalendarOption">
<template v-if="isModalOpened">
<buttonQuestion
ref="buttonQuestion"
v-model="selectedCalendarOption"
buttonTypeString="calendarModalListButton"
:buttonTypeObject="calendarModalListButton"
:answers="getCalendarOptionData"
groupName="chooseCalendarType"
class="mt-1"
textPosition="text-center"
isRequired
validationRules="calendar-option-required" />
</template>
</modal>
</template>
<script>
import modal from '@/digital-components/modal/modal.vue';
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import { defineRule, useField } from 'vee-validate';
import errorMessages from '@/constants/error-messages.js';
import { required } from '@/helpers/validation-rules';
import { deepClone } from '@/helpers/object-helper';
// eslint-disable-next-line import/no-extraneous-dependencies
import { v4 as uuidv4 } from 'uuid';
import calendarModalListButton from './calendar-modal-list-button/calendar-modal-list-button.vue';
// Validation for the modal button
defineRule('calendar-option-required', required(errorMessages.OPTION_REQUIRED));
export default {
name: 'calendar-modal-question',
components: {
modal,
buttonQuestion
},
props: {
calendarOptionsData: Object,
modelValue: {
type: Object,
default: () => ({
url: null,
name: null,
icon: null
})
}
},
emits: ['calendar-modal-closed', 'update:modelValue'],
setup(props) {
const uuid = uuidv4();
const componentId = !props.customComponentId
? `component-${uuid}`
: props.customComponentId;
const { modelValue } = deepClone(props);
const initialValue = modelValue;
const fieldOptions = {
value: modelValue,
initialValue
};
const { errorMessage, handleChange, meta, validate, errors } = useField(
componentId,
props.validationRules,
fieldOptions
);
return {
componentId,
errorMessage,
handleChange,
validate,
meta,
errors
};
},
data() {
return {
isModalOpened: false,
selectedValue: null,
calendarModalListButton
};
},
computed: {
modalName() {
return 'calendarOptions';
},
modalHeaderText() {
return 'Add to Calendar';
},
footerCloseButtonText() {
return 'Add to Calendar';
},
modal() {
return this.$refs[this.modalName];
},
selectedCalendarOption: {
get() {
return this.modelValue?.name;
},
set(newValue) {
// Button Question only supports Number, or String data types so we must convert to the full object before emitting
this.selectedValue = this.getSelectedCalendarObject(newValue);
}
},
getCalendarOptionData() {
return this.calendarOptionsData.map((item) => ({
value: item.name,
buttonLabel: item.icon
}));
},
watch: {
modelValue: {
handler(newValue) {
this.handleChange(newValue);
},
deep: true
}
}
},
methods: {
openModal() {
this.modal.openModal();
},
setModalStatus(isOpened) {
this.isModalOpened = isOpened;
},
closeModal() {
this.modal.closeModal();
},
onModalClosed() {
this.$emit('calendar-modal-closed');
},
async setSelectedCalendarOption() {
this.$emit('update:modelValue', this.selectedValue);
this.closeModal();
},
getSelectedCalendarObject(calendarOptionId) {
// eslint-disable-next-line no-shadow
const calendarOption = this.calendarOptionsData?.find((calendarOption) => calendarOption.name === calendarOptionId);
if (calendarOption) {
return {
url: calendarOption.url,
name: calendarOption.name,
icon: calendarOption.icon
};
}
return {
url: null,
name: null,
icon: null
};
}
}
};
</script>
<style lang="scss" scoped>
.calendar-modal.modal.modal-component {
> .modal-dialog > .modal-content {
margin-top: 1.5rem;
}
.modal-header {
padding-bottom: 0;
margin-bottom: 0 !important;
}
overflow: hidden;
}
</style>

View file

@ -39,6 +39,10 @@ const vehicleBannerStub = {
render: () => {}
};
const addToCalendarStub = {
render: () => {}
};
const initialStore = {
order: {
schedule: {
@ -127,7 +131,8 @@ function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRu
mountOptions.global.stubs = {
siteFooter: footerStub,
siteHeader: headerStub,
vehicleBanner: vehicleBannerStub
vehicleBanner: vehicleBannerStub,
addToCalendar: addToCalendarStub
};
const testingPinia = createTestingPinia({

View file

@ -25,6 +25,19 @@
<p>{{ appointmentDateFormatted }}</p>
<p>{{ appointmentTimeFormatted }}</p>
</div>
<addToCalendar
mobileWidgetName="AddToCalendar_Mobile"
inShopWidgetName="AddToCalendar_InShop"
dropOffWidgetName="AddToCalendar_DropOff"
overnightDropOffWidgetName="AddToCalendar_OvernightDropOff"
allDayDropOffWidgetName="AddToCalendar_AllDayDropOff"
sameDayDropOffWidgetName="AddToCalendar_SameDayDropOff"
:serviceLocationFullAddress="serviceLocationFullAddress"
:providerFullAddress="providerFullAddress"
:appointmentType="appointmentType"
:scheduleDate="appointmentDate"
:scheduleStartTime="appointmentStartTime"
:scheduleEndTime="appointmentEndTime" />
<div
class="appointment-text text-center lh-base"
v-html="appointmentWordingText">
@ -52,6 +65,7 @@
import siteHeader from '@/iss-components/site-header/site-header.vue';
import vehicleBanner from '@/iss-components/vehicle-banner/vehicle-banner.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import addToCalendar from '@/layouts/order-confirmation/add-to-calendar/add-to-calendar.vue';
// Supporting files
import { fetchCmsContentForPage, processIfStatements } from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
@ -72,7 +86,8 @@ export default {
Form,
siteHeader,
vehicleBanner,
siteFooter
siteFooter,
addToCalendar
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -180,7 +195,7 @@ export default {
},
providerFullAddress() {
// eslint-disable-next-line max-len
return `${this.providerAddress},<br/> ${this.providerCity}, ${this.providerState} ${this.providerZipCode}`;
return this.submittedOrder.serviceLocation?.provider?.address ? `${this.providerAddress},<br/> ${this.providerCity}, ${this.providerState} ${this.providerZipCode}` : '';
},
appointmentWordingText() {
switch (this.appointmentType) {
@ -297,15 +312,15 @@ $page-side-padding: 1.5rem;
.appointment-details {
margin-bottom: 1.5rem;
padding: 1rem 1.5rem 1.5rem;
box-shadow: 0 3px 10px rgb(0 0 0 / 0.2);
border-radius: 5px;
box-shadow: 0 0.188rem 0.625rem rgb(0 0 0 / 0.2);
border-radius: 0.313rem;
}
.appointment-date-time P {
color: $black;
font-size: $h5-font-size;
line-height: map-get($spacers, 6);
margin-bottom: 0.5rem;
margin-bottom: 0;
+ p {
font-size: map-get($spacers, 4);

View file

@ -74,6 +74,7 @@ export default {
await this.processCreditCardResponse();
break;
case paymentMethods.PAYPAL:
await this.processPaypalResponse();
break;
default:
console.log(`Unknown pay in advance type: ${payInAdvanceType}`);

View file

@ -340,8 +340,11 @@ export default {
await this.$refs.shopQuestion.reloadShopData(zipCode);
},
async forwardButtonAction() {
const providerToUse = this.isMobileLocationDisplayed
? {
let provider = this.selectedProvider;
this.mainStore.updateMobileFee(null);
if (this.isMobileLocationDisplayed) {
this.mainStore.updateMobileFee(this.mobileFeePart);
provider = {
providerNumber: this.mobileProviderNumber,
address: {
streetAddress: null,
@ -350,10 +353,10 @@ export default {
zipCode: null,
zipCodeCtu: null
}
}
: this.selectedProvider;
};
}
useMainStore().saveServiceLocation({
this.mainStore.saveServiceLocation({
address: this.streetAddress,
address2: this.streetAddress2,
city: this.city,
@ -362,16 +365,7 @@ export default {
zipCodeCtu: this.zipCodeCtu,
appointmentType: this.selectedAppointmentType,
isVehicleProtected: this.isVehicleProtected,
provider: {
providerNumber: providerToUse?.providerNumber,
address: {
streetAddress: providerToUse?.address?.streetAddress,
city: providerToUse?.address?.city,
state: providerToUse?.address?.state,
zipCode: providerToUse?.address?.zipCode,
zipCodeCtu: providerToUse?.address?.zipCodeCtu
}
}
provider
});
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);

View file

@ -38,7 +38,7 @@
inputId="phoneNumber"
:cmsWidgetName="widget.phoneNumberQuestion"
isRequired
:mask="mask"
:mask="phoneMask"
disableAutoFill
:validationRules="rules.phoneNumber" />
</template>
@ -53,6 +53,7 @@ import textboxQuestion from '@/digital-components/textbox-question/textbox-quest
// Supporting Files
import { useMainStore } from '@/store/index.js';
import globalRules from '@/constants/global-rules.js';
import MaskaFormattedMasks from '@/constants/maska-masks';
import widgetFields from '@/constants/cms-widget-fields.js';
export default {
@ -102,15 +103,8 @@ export default {
footerButtonText() {
return this.getCmsContent(this.widget.drawerFooter, widgetFields.FOOTER_WIDGET.FORWARD_BUTTON_TEXT);
},
mask() {
return {
mask: 'x##-###-####',
tokens: {
x: {
pattern: /[2-9]/
}
}
};
phoneMask() {
return MaskaFormattedMasks.PHONE_NUMBER;
}
},
methods: {

View file

@ -86,7 +86,7 @@
cmsWidgetName="PhoneNumberQuestion"
:validationRules="rules.phoneNumber"
isRequired
:mask="mask"
:mask="phoneMask"
disableAutoFill />
</div>
</div>
@ -197,6 +197,7 @@ import { useMainStore } from '@/store';
import states from '@/constants/states';
import globalRules from '@/constants/global-rules';
import routerParams from '@/router/router-constants/router-params';
import MaskaFormattedMasks from '@/constants/maska-masks';
// define validation rules
defineRule('damage-option-required', required(errorMessages.DAMAGE_OPTION_REQUIRED));
@ -313,15 +314,8 @@ export default {
maxCoverageLookupAttemptsReached() {
return this.mainStore.applicationUser.coverageLookupAttempts >= 11;
},
mask() {
return {
mask: 'x##-###-####',
tokens: {
x: {
pattern: /[2-9]/
}
}
};
phoneMask() {
return MaskaFormattedMasks.PHONE_NUMBER;
}
},
methods: {

View file

@ -4,7 +4,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import { useMainStore } from '@/store';
import baseMixin from '@/mixins/base-mixin';
import { createPinia } from 'pinia';
import Maska from 'maska';
import { vMaska } from 'maska';
import LoadScript from 'vue-plugin-load-script';
import analyticsMixin from '@/mixins/analytics-mixin';
@ -29,8 +29,8 @@ useMainStore().populateInitialState();
// Additional Vue items to setup
vueApp.use(router);
vueApp.use(Maska);
vueApp.use(LoadScript);
vueApp.directive('maska', vMaska);
vueApp.mixin(baseMixin);
vueApp.mixin(analyticsMixin);
vueApp.mixin(experimentMixin);

View file

@ -67,6 +67,10 @@ export default {
});
},
getAmountDue(lineItems) {
if (!useMainStore().isNoComp && !useMainStore().policy.isITAC) {
return useMainStore().order.currentDeductible;
}
let amountDue = 0;
if (lineItems.glassParts) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(

View file

@ -147,7 +147,8 @@ const getDefaultState = () => ({
glassParts: null,
otherParts: null,
supportingItems: null,
vaps: null
vaps: null,
mobileFee: null
},
payment: {
insuranceCoverage: {
@ -1145,7 +1146,7 @@ export const useMainStore = defineStore({
this.applicationUser.crmCustomerId = response.crmCustomerId.toString();
},
saveSession() {
saveSession({ submitAfterSave }) {
const { vehicle, damage, policy, customer, contactInfo, payment,
lineItems, serviceLocation, schedule } = this.order;
@ -1233,7 +1234,12 @@ export const useMainStore = defineStore({
coverageStatus: payment.insuranceCoverage?.coverageStatus,
claimNumber: payment.insuranceCoverage?.claimNumber
},
parentAccountNumber: this.issConfig.parentAccountNumber
parentAccountNumber: this.issConfig.parentAccountNumber,
paypalToken: payment.paypalToken,
isPaypal: payment.payInAdvanceType === paymentMethods.PAYPAL,
isCreditCard: payment.payInAdvanceType === paymentMethods.CREDIT_CARD,
isAfterpay: payment.payInAdvanceType === paymentMethods.AFTERPAY,
CCToken: payment.creditCardToken
},
serviceLocation: {
address: {
@ -1274,7 +1280,8 @@ export const useMainStore = defineStore({
referralSequenceNumber: this.order.referralSequenceNumber,
eon: this.order.eon,
submitToMainframe: !!this.order.referralNumber,
loadedFromDupeCheck
loadedFromDupeCheck,
submitAfterSave: !!submitAfterSave
},
additionalSuccessEventDataHandler: () =>
`Email provided: ${customer.emailAddress ? 'true' : 'false'}`
@ -1531,6 +1538,10 @@ export const useMainStore = defineStore({
this.order.lineItems.vaps = partsData;
},
updateMobileFee(mobileFee) {
this.order.lineItems.mobileFee = mobileFee;
},
updateVehicle(vehicle) {
// Assuming that the method caller pass all the properties.
// otherwise need to check for undefined for every property.
@ -2243,6 +2254,7 @@ export const useMainStore = defineStore({
resetPartsAndDependencies() {
this.resetGlassPartsState();
this.updateVaps(null);
this.updateMobileFee(null);
this.resetServiceLocationAndDependencies();
},

View file

@ -503,7 +503,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -517,7 +517,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(response));
// Act
const result = store.saveSession();
const result = store.saveSession({});
// Asserts
await expect(result).resolves.toBe(response);
@ -537,7 +537,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -570,7 +570,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -612,7 +612,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -659,7 +659,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -708,7 +708,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -742,7 +742,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -769,7 +769,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -802,7 +802,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -838,7 +838,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -861,7 +861,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -875,7 +875,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Assert
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -888,7 +888,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -904,7 +904,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -927,7 +927,7 @@ describe('Store', () => {
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession();
await store.saveSession({});
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
@ -941,13 +941,27 @@ describe('Store', () => {
})
}));
});
it('calls api with expected submitAfterSave', async () => {
// Arrange
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act
await store.saveSession({ submitAfterSave: true });
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
payload: expect.objectContaining({
submitAfterSave: true
})
}));
});
it('api call throws exception', async () => {
expect.assertions(2);
const error = 'save session error';
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.reject(error));
// Act
await store.saveSession().catch((e) => {
await store.saveSession({}).catch((e) => {
expect(e).toEqual(error);
});
@ -1887,4 +1901,17 @@ describe('Store', () => {
expect(store.order.payment.payInAdvanceType).toEqual(paymentMethod);
});
});
describe('updatePaypalToken method', () => {
it('paypalToken is valid when set', () => {
// Arrange
const paypalToken = getRandomString(6, 6);
// Act
store.updatePaypalToken(paypalToken);
// Assert
expect(store.order.payment.paypalToken).toEqual(paypalToken);
});
});
});