Merge pull request #2482 from Safelite/rlsmerge/2025.05.08-to-develop

Rlsmerge/2025.05.08 to develop
This commit is contained in:
Matt Caimi 2025-05-05 11:07:37 -04:00 committed by GitHub
commit 5bdf91117c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 2980 additions and 3522 deletions

View file

@ -18,9 +18,11 @@ pr:
resources: resources:
containers: containers:
- container: node - container: node
image: packagerepository.sagaws.net:8082/safelite/node-build:18 image: ghcr.io/safelite/node-build:18
endpoint: GitHub Docker
- container: awscli - container: awscli
image: packagerepository.sagaws.net:8082/safelite/awscli2-build:3.9 image: ghcr.io/safelite/awscli2-build:3.9
endpoint: GitHub Docker
- container: prettier-node - container: prettier-node
image: ghcr.io/safelite/prettier-node-build:23 image: ghcr.io/safelite/prettier-node-build:23
endpoint: GitHub Docker endpoint: GitHub Docker

View file

@ -3,10 +3,10 @@ module.exports = {
coverageReporters: ["html", "text", "jest-junit", "cobertura"], coverageReporters: ["html", "text", "jest-junit", "cobertura"],
testResultsProcessor: "jest-junit", testResultsProcessor: "jest-junit",
preset: "@vue/cli-plugin-unit-jest", preset: "@vue/cli-plugin-unit-jest",
transform: { "^.+\\.vue$": "@vue/vue3-jest" }, transform: { "^.+\\.vue$": "@vue/vue3-jest",
transformIgnorePatterns: [ "^.+\\.mjs$": "babel-jest",
'/node_modules/(?!.*\\.mjs$)', },
], transformIgnorePatterns: ["'/node_modules/(?!vee-validate)"],
moduleFileExtensions: ["js", "vue"], moduleFileExtensions: ["js", "vue"],
modulePathIgnorePatterns: ["vin-lookup"], modulePathIgnorePatterns: ["vin-lookup"],
collectCoverageFrom: [ collectCoverageFrom: [

5773
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -853,7 +853,7 @@ export default {
} }
} }
.calendar-grid-container { .calendar-grid-container {
margin: 0 auto 2rem auto; margin: 0 auto 1rem auto;
max-width: 400px; max-width: 400px;
position: relative; position: relative;
transition: transition:
@ -1075,12 +1075,16 @@ export default {
border-radius: 50%; border-radius: 50%;
background-color: $black; background-color: $black;
position: absolute; position: absolute;
top: 28px; top: 1.875rem;
} }
.first-day { .first-day {
color: $blue; color: $blue;
} }
} }
input[type="radio"]:checked + label:after {
background-color: $black;
top: 2rem;
}
} }
} }
@ -1206,6 +1210,7 @@ export default {
} }
} }
label { label {
color: $gray-500;
flex-direction: column; flex-direction: column;
height: 2.5rem; height: 2.5rem;
width: 100%; width: 100%;
@ -1228,10 +1233,11 @@ export default {
} }
} }
&.current-day { &.current-day {
label:after { label {
margin-top: -0.25rem; font-weight: 600;
position: relative; font-family: "AvertaSemibold";
top: 0.5rem; color: $black;
font-size-adjust: 0.5; // needed for Averta fonts baseline alignment
} }
} }
} }
@ -1242,6 +1248,7 @@ export default {
color: $black; color: $black;
font-weight: 400; font-weight: 400;
font-family: "AvertaSemibold"; font-family: "AvertaSemibold";
font-size-adjust: 0.5; // needed for Averta fonts baseline alignment
&.price { &.price {
font-family: "AvertaRegular"; font-family: "AvertaRegular";
@ -1274,7 +1281,6 @@ export default {
max-width: 100%; max-width: 100%;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0 0.5rem; gap: 0 0.5rem;
justify-content: center;
& > div { & > div {
flex: 0 0 calc(50% - 0.25rem); flex: 0 0 calc(50% - 0.25rem);

View file

@ -0,0 +1,66 @@
/*
The code below is generated from salesforce but modified in the following ways:
* Only the javascript inside the second <script> tag in the provided salesforce code is copy pasted
* CSS is not copied in
* First <script> tag loading in the file from the CDN is not copied in
* The only custom code added here is `let embedded_svc = window.embedded_svc` in the first line of the `initESW` function
and the wrapper exported function `initializeSalesforceWebChatForDev`
*/
export function initializeSalesforceWebchatForDev() {
var initESW = function (gslbBaseURL) {
let embedded_svc = window.embedded_svc;
embedded_svc.settings.displayHelpButton = true; //Or false
embedded_svc.settings.language = ""; //For example, enter 'en' or 'en-US'
//embedded_svc.settings.defaultMinimizedText = '...'; //(Defaults to Chat with an Expert)
//embedded_svc.settings.disabledMinimizedText = '...'; //(Defaults to Agent Offline)
//embedded_svc.settings.loadingText = ''; //(Defaults to Loading)
//embedded_svc.settings.storageDomain = 'yourdomain.com'; //(Sets the domain for your deployment so that visitors can navigate subdomains during a chat session)
// Settings for Chat
//embedded_svc.settings.directToButtonRouting = function(prechatFormData) {
// Dynamically changes the button ID based on what the visitor enters in the pre-chat form.
// Returns a valid button ID.
//};
//embedded_svc.settings.prepopulatedPrechatFields = {}; //Sets the auto-population of pre-chat form fields
//embedded_svc.settings.fallbackRouting = []; //An array of button IDs, user IDs, or userId_buttonId
//embedded_svc.settings.offlineSupportMinimizedText = '...'; //(Defaults to Contact Us)
embedded_svc.settings.enabledFeatures = ["LiveAgent"];
embedded_svc.settings.entryFeature = "LiveAgent";
embedded_svc.init(
"https://safelite2--dev.sandbox.my.salesforce.com",
"https://safelite2--dev.sandbox.my.salesforce-sites.com/chat",
gslbBaseURL,
"00D6s0000008r6A",
"Sales_Central_Snap_in",
{
baseLiveAgentContentURL:
"https://c.la12s-core1.sfdc-8tgtt5.salesforceliveagent.com/content",
deploymentId: "572Wr0000017xKX",
buttonId: "573Wr000000CpgH",
baseLiveAgentURL: "https://d.la12s-core1.sfdc-8tgtt5.salesforceliveagent.com/chat",
eswLiveAgentDevName:
"EmbeddedServiceLiveAgent_Parent04IWr000003yKq9MAE_1961681659a",
isOfflineSupportEnabled: false,
}
);
};
if (!window.embedded_svc) {
var s = document.createElement("script");
s.setAttribute(
"src",
"https://safelite2--dev.sandbox.my.salesforce.com/embeddedservice/5.0/esw.min.js"
);
s.onload = function () {
initESW(null);
};
document.body.appendChild(s);
} else {
initESW("https://service.force.com");
}
}

View file

@ -0,0 +1,63 @@
/*
The code below is generated from salesforce but modified in the following ways:
* Only the javascript inside the second <script> tag in the provided salesforce code is copy pasted
* CSS is not copied in
* First <script> tag loading in the file from the CDN is not copied in
* The only custom code added here is `let embedded_svc = window.embedded_svc` in the first line of the `initESW` function
and the wrapper exported function `initializeSalesforceWebChatForProd`
*/
export function initializeSalesforceWebchatForProd(embedded_svc) {
var initESW = function (gslbBaseURL) {
let embedded_svc = window.embedded_svc;
embedded_svc.settings.displayHelpButton = true; //Or false
embedded_svc.settings.language = ""; //For example, enter 'en' or 'en-US'
//embedded_svc.settings.defaultMinimizedText = '...'; //(Defaults to Chat with an Expert)
//embedded_svc.settings.disabledMinimizedText = '...'; //(Defaults to Agent Offline)
//embedded_svc.settings.loadingText = ''; //(Defaults to Loading)
//embedded_svc.settings.storageDomain = 'yourdomain.com'; //(Sets the domain for your deployment so that visitors can navigate subdomains during a chat session)
// Settings for Chat
//embedded_svc.settings.directToButtonRouting = function(prechatFormData) {
// Dynamically changes the button ID based on what the visitor enters in the pre-chat form.
// Returns a valid button ID.
//};
//embedded_svc.settings.prepopulatedPrechatFields = {}; //Sets the auto-population of pre-chat form fields
//embedded_svc.settings.fallbackRouting = []; //An array of button IDs, user IDs, or userId_buttonId
//embedded_svc.settings.offlineSupportMinimizedText = '...'; //(Defaults to Contact Us)
embedded_svc.settings.enabledFeatures = ["LiveAgent"];
embedded_svc.settings.entryFeature = "LiveAgent";
embedded_svc.init(
"https://safelite2.my.salesforce.com",
"https://safelite2.my.salesforce-sites.com/chat",
gslbBaseURL,
"00D3t000004nvsT",
"Sales_Central_Snap_in",
{
baseLiveAgentContentURL:
"https://c.la13-core2.sfdc-lywfpd.salesforceliveagent.com/content",
deploymentId: "5723t0000008wWB",
buttonId: "5733t000000D90z",
baseLiveAgentURL: "https://d.la13-core2.sfdc-lywfpd.salesforceliveagent.com/chat",
eswLiveAgentDevName:
"EmbeddedServiceLiveAgent_Parent04I3t000000Cq0SEAS_188de1871ea",
isOfflineSupportEnabled: false,
}
);
};
if (!window.embedded_svc) {
var s = document.createElement("script");
s.setAttribute("src", "https://safelite2.my.salesforce.com/embeddedservice/5.0/esw.min.js");
s.onload = function () {
initESW(null);
};
document.body.appendChild(s);
} else {
initESW("https://service.force.com");
}
}

View file

@ -0,0 +1,67 @@
/*
The code below is generated from salesforce but modified in the following ways:
* Only the javascript inside the second <script> tag in the provided salesforce code is copy pasted
* CSS is not copied in
* First <script> tag loading in the file from the CDN is not copied in
* The only custom code added here is `let embedded_svc = window.embedded_svc` in the first line of the `initESW` function
and the wrapper exported function `initializeSalesforceWebChatForQa`
*/
export function initializeSalesforceWebchatForQa() {
var initESW = function (gslbBaseURL) {
let embedded_svc = window.embedded_svc;
embedded_svc.settings.displayHelpButton = true; //Or false
embedded_svc.settings.language = ""; //For example, enter 'en' or 'en-US'
//embedded_svc.settings.defaultMinimizedText = '...'; //(Defaults to Chat with an Expert)
//embedded_svc.settings.disabledMinimizedText = '...'; //(Defaults to Agent Offline)
//embedded_svc.settings.loadingText = ''; //(Defaults to Loading)
//embedded_svc.settings.storageDomain = 'yourdomain.com'; //(Sets the domain for your deployment so that visitors can navigate subdomains during a chat session)
// Settings for Chat
//embedded_svc.settings.directToButtonRouting = function(prechatFormData) {
// Dynamically changes the button ID based on what the visitor enters in the pre-chat form.
// Returns a valid button ID.
//};
//embedded_svc.settings.prepopulatedPrechatFields = {}; //Sets the auto-population of pre-chat form fields
//embedded_svc.settings.fallbackRouting = []; //An array of button IDs, user IDs, or userId_buttonId
//embedded_svc.settings.offlineSupportMinimizedText = '...'; //(Defaults to Contact Us)
embedded_svc.settings.enabledFeatures = ["LiveAgent"];
embedded_svc.settings.entryFeature = "LiveAgent";
embedded_svc.init(
"https://safelite2--safeliteua.sandbox.my.salesforce.com",
"https://safelite2--safeliteua.sandbox.my.salesforce-sites.com/chat",
gslbBaseURL,
"00DDJ000000HRqO",
"Sales_Central_Snap_in",
{
baseLiveAgentContentURL:
"https://c.la12s-core1.sfdc-8tgtt5.salesforceliveagent.com/content",
deploymentId: "572DJ00000001j3",
buttonId: "573DJ00000001ur",
baseLiveAgentURL: "https://d.la12s-core1.sfdc-8tgtt5.salesforceliveagent.com/chat",
eswLiveAgentDevName:
"EmbeddedServiceLiveAgent_Parent04IDJ0000008OZk2AM_1883057c9a6",
isOfflineSupportEnabled: false,
}
);
};
if (!window.embedded_svc) {
var s = document.createElement("script");
s.setAttribute(
"src",
"https://safelite2--safeliteua.sandbox.my.salesforce.com/embeddedservice/5.0/esw.min.js"
);
s.onload = function () {
initESW(null);
};
document.body.appendChild(s);
} else {
initESW("https://service.force.com");
}
}

View file

@ -0,0 +1,43 @@
import * as salesforceWebchatHelperdev from "./salesforce-helper-dev";
import * as salesforceWebchatHelperqa from "./salesforce-helper-qa";
import * as salesforceWebchatHelperprod from "./salesforce-helper-prod";
const mockEmbeddedSvc = {
settings: {},
init: jest.fn(),
testFlag: true,
};
describe("Salesforce Webchat Helper Tests", () => {
describe("Files and exposed methods exist", () => {
it("exposes initialization methods", () => {
expect(typeof salesforceWebchatHelperdev.initializeSalesforceWebchatForDev).toBe(
"function"
);
expect(typeof salesforceWebchatHelperqa.initializeSalesforceWebchatForQa).toBe(
"function"
);
expect(typeof salesforceWebchatHelperprod.initializeSalesforceWebchatForProd).toBe(
"function"
);
});
}),
describe("Custom code added correctly", () => {
it("defines a local variable 'embedded_svc' in the initESW method", () => {
mockEmbeddedSvc.testFlag = "dev";
window.embedded_svc = mockEmbeddedSvc;
salesforceWebchatHelperdev.initializeSalesforceWebchatForDev();
expect(embedded_svc.testFlag).toBe("dev");
mockEmbeddedSvc.testFlag = "qa";
window.embedded_svc = mockEmbeddedSvc;
salesforceWebchatHelperqa.initializeSalesforceWebchatForQa();
expect(embedded_svc.testFlag).toBe("qa");
mockEmbeddedSvc.testFlag = "prod";
window.embedded_svc = mockEmbeddedSvc;
salesforceWebchatHelperprod.initializeSalesforceWebchatForProd();
expect(embedded_svc.testFlag).toBe("prod");
});
});
});

View file

@ -0,0 +1,43 @@
import { shallowMount } from "@vue/test-utils";
import salesforceWebchat from "./salesforce-webchat.vue";
import * as devHelper from "./salesforce-helper-dev";
import * as qaHelper from "./salesforce-helper-qa";
import * as prodHelper from "./salesforce-helper-prod";
import { applicationConfig } from "../../constants/application-config";
describe("salesforceWebchat.vue", () => {
it("renders correctly", () => {
const wrapper = shallowMount(salesforceWebchat);
expect(wrapper.exists()).toBe(true);
});
it("has the correct default data", () => {
const wrapper = shallowMount(salesforceWebchat);
expect(wrapper.vm.$data).toEqual({});
});
it("has the correct default props", () => {
const wrapper = shallowMount(salesforceWebchat);
expect(wrapper.props().hideSalesforceWebchatLaunchButton).toBe(false);
});
it("calls the correct initialization function based on environment", () => {
applicationConfig.CURRENT_ENVIRONMENT = "Prod";
let wrapper = shallowMount(salesforceWebchat);
const mockProd = jest.spyOn(prodHelper, "initializeSalesforceWebchatForProd");
wrapper.vm.initializeSalesforceWebchat();
expect(mockProd).toHaveBeenCalled();
applicationConfig.CURRENT_ENVIRONMENT = "QA";
wrapper = shallowMount(salesforceWebchat);
const mockQa = jest.spyOn(qaHelper, "initializeSalesforceWebchatForQa");
wrapper.vm.initializeSalesforceWebchat();
expect(mockQa).toHaveBeenCalled();
applicationConfig.CURRENT_ENVIRONMENT = "Dev";
wrapper = shallowMount(salesforceWebchat);
const mockDev = jest.spyOn(devHelper, "initializeSalesforceWebchatForDev");
wrapper.vm.initializeSalesforceWebchat();
expect(mockDev).toHaveBeenCalled();
});
});

View file

@ -0,0 +1,60 @@
<template>
<span class="salesforce-webchat"></span>
</template>
<script>
// Supporting files
import { initializeSalesforceWebchatForDev } from "./salesforce-helper-dev";
import { initializeSalesforceWebchatForQa } from "./salesforce-helper-qa";
import { initializeSalesforceWebchatForProd } from "./salesforce-helper-prod";
import { applicationConfig } from "@/constants/application-config";
export default {
name: "salesforceWebchat",
data() {
return {};
},
props: {
hideSalesforceWebchatLaunchButton: {
type: Boolean,
default: false,
},
},
mounted() {
// Load in script from salesforce CDN
const script = document.createElement("script");
script.src = "https://service.force.com/embeddedservice/5.0/esw.min.js";
script.onload = this.initializeSalesforceWebchat;
document.body.appendChild(script);
},
methods: {
initializeSalesforceWebchat() {
switch (applicationConfig.CURRENT_ENVIRONMENT) {
case "Prod":
initializeSalesforceWebchatForProd();
break;
case "QA":
case "SysTest":
initializeSalesforceWebchatForQa();
break;
case "Dev":
case "Localhost":
initializeSalesforceWebchatForDev();
break;
}
},
},
computed: {},
components: {},
};
</script>
<style type="text/css">
.embeddedServiceHelpButton .helpButton .uiButton {
background-color: #167cac;
font-family: "Arial", sans-serif;
}
.embeddedServiceHelpButton .helpButton .uiButton:focus {
outline: 1px solid #167cac;
}
</style>

View file

@ -2,6 +2,7 @@
<div <div
class="funnel-header d-flex justify-content-center align-items-center flex-column" class="funnel-header d-flex justify-content-center align-items-center flex-column"
v-if="imageSrc"> v-if="imageSrc">
<!-- HIDDEN FOR 05.08 <salesforceWebchat hideSalesforceWebchatLaunchButton />-->
<img class="logo-image img-fluid" :src="imageSrc" alt="Safelite logo" /> <img class="logo-image img-fluid" :src="imageSrc" alt="Safelite logo" />
<!-- // menu-modal temporarily not in use until the hamburger menu is re-implemented for use with the progress bar. See display: none below. --> <!-- // menu-modal temporarily not in use until the hamburger menu is re-implemented for use with the progress bar. See display: none below. -->
<menuModal /> <menuModal />
@ -27,6 +28,7 @@ import alert from "@/ux-components/alert/alert";
import eventBus from "@/helpers/event-bus/event-bus"; import eventBus from "@/helpers/event-bus/event-bus";
import { globalEvents } from "@/constants/events"; import { globalEvents } from "@/constants/events";
import menuModal from "@/fmg-components/funnel-header/menu-modal/menu-modal"; import menuModal from "@/fmg-components/funnel-header/menu-modal/menu-modal";
import salesforceWebchat from "../../digital-components/salesforce-webchat/salesforce-webchat.vue";
import progressBar from "@/fmg-components/funnel-header/progress-bar/progress-bar"; import progressBar from "@/fmg-components/funnel-header/progress-bar/progress-bar";
// Constants // Constants
@ -41,6 +43,10 @@ export default {
}, },
props: { props: {
cmsWidgetName: String, cmsWidgetName: String,
hideSalesforceWebchatLaunchButton: {
type: Boolean,
default: false,
},
}, },
computed: { computed: {
imageSrc() { imageSrc() {
@ -64,6 +70,7 @@ export default {
components: { components: {
alert, alert,
menuModal, menuModal,
salesforceWebchat,
progressBar, progressBar,
}, },
mounted() { mounted() {
@ -98,7 +105,7 @@ export default {
} }
.funnel-header { .funnel-header {
position: relative; position: relative;
padding: 0.97rem 0; padding-top: 0.97rem;
} }
.logo-image { .logo-image {

View file

@ -25,7 +25,7 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
#progress-bar-container { #progress-bar-container {
padding: 0.75rem 0; padding-top: 0.75rem;
width: 100%; width: 100%;
progress { progress {

View file

@ -182,6 +182,23 @@ describe("address-lookup.vue", () => {
// Assert // Assert
expect(wrapper.findComponent({ ref: "alertVinNotFound" }).isVisible()).toBe(true); expect(wrapper.findComponent({ ref: "alertVinNotFound" }).isVisible()).toBe(true);
}); });
test("should hide the AlertNoService when displayNoServiceAlert is false", async () => {
const { wrapper } = setupMocks({
isZipServiceable: true,
displayNoServiceAlert: false,
lookupVinbyAddressResponse: {
isStatePermissible: true,
vinVehicles: [], // Return no vehicles
},
});
// Ensure displayNoServiceAlert is false
await wrapper.setData({ displayNoServiceAlert: false });
await wrapper.vm.forwardButtonAction();
// Check if the AlertNoService component is not rendered
const alertNoService = wrapper.findComponent({ ref: "AlertNoService" });
expect(alertNoService.exists()).toBe(false);
});
}); });
describe("navigation", () => { describe("navigation", () => {
@ -741,6 +758,17 @@ function setupMocks({
wrapper.vm.setCmsContent = jest.fn(); wrapper.vm.setCmsContent = jest.fn();
wrapper.vm.$refs.navbar.updateButtonText = jest.fn(); wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
wrapper.vm.$refs.navbar.removeLoader = jest.fn(); wrapper.vm.$refs.navbar.removeLoader = jest.fn();
const closestShops = {
data: {
providers: [
{ id: 1, name: "Shop 1" },
{ id: 2, name: "Shop 2" },
],
},
};
wrapper.vm.findClosestApplicableShops = jest.fn().mockImplementation(() => {
return new Promise((resolve) => resolve(closestShops));
});
return { wrapper }; return { wrapper };
} }

View file

@ -81,13 +81,20 @@
</div> </div>
</div> </div>
</transition> </transition>
<alert
ref="AlertNoService"
v-if="displayNoServiceAlert"
class="my-4"
cmsWidgetName="AlertNoServiceWidget"
alertClass="alert-danger"
v-bind:isDismissible="false" />
<navbar <navbar
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
ref="navbar" ref="navbar"
:isDisabled="!meta.valid" :isDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction" @ForwardClicked="forwardButtonAction"
@back-clicked="backButtonAction" @back-clicked="backButtonAction"
:isForwardActionDisabled="!meta.valid" /> :isForwardActionDisabled="!meta.valid || displayNoServiceAlert" />
</div> </div>
</div> </div>
</div> </div>
@ -177,6 +184,7 @@ export default {
displayInvalidZipAlert: false, displayInvalidZipAlert: false,
showServiceZipField: this.getServiceZipFromStore(), showServiceZipField: this.getServiceZipFromStore(),
isZipServiceable: false, isZipServiceable: false,
displayNoServiceAlert: false,
}; };
}, },
methods: { methods: {
@ -293,6 +301,17 @@ export default {
return this.$refs.navbar.removeLoader(); return this.$refs.navbar.removeLoader();
} }
this.displayInvalidZipAlert = false; this.displayInvalidZipAlert = false;
if (this.serviceZipCode != null && this.isHeavyTruck) {
const closestShops = await this.findClosestApplicableShops(
this.serviceZipCode,
this.carId
);
this.displayNoServiceAlert = !closestShops?.data?.providers?.length;
if (this.displayNoServiceAlert) {
return this.$refs.navbar.removeLoader();
}
}
const carsFound = resultMap.vinLookupResponse.vinVehicles; const carsFound = resultMap.vinLookupResponse.vinVehicles;
// Handle cases for different amounts of VINS found for the address. // Handle cases for different amounts of VINS found for the address.
@ -444,6 +463,7 @@ export default {
this.displayNonServiceableZipAlert = false; this.displayNonServiceableZipAlert = false;
this.displayMatchedDifferentVehicleAlert = false; this.displayMatchedDifferentVehicleAlert = false;
this.displayVinLookupByHomeAddressNotAllowedAlert = false; this.displayVinLookupByHomeAddressNotAllowedAlert = false;
this.displayNoServiceAlert = false;
}, },
}, },
mounted() { mounted() {
@ -495,12 +515,13 @@ export default {
handler(newValue) { handler(newValue) {
// If they modify the service zip code, then hide the error message. // If they modify the service zip code, then hide the error message.
this.displayNonServiceableZipAlert = false; this.displayNonServiceableZipAlert = false;
this.displayNoServiceAlert = false;
}, },
}, },
showServiceZipField: { showServiceZipField: {
handler(newValue) { handler(newValue) {
// If the Service Zip Code field is ever hidden, clear out it's value // If the Service Zip Code field is ever hidden and not a heavy truck, clear out it's value
if (!newValue) { if (!newValue && !this.isHeavyTruck) {
this.serviceZipCode = null; this.serviceZipCode = null;
} }
}, },

View file

@ -5,7 +5,10 @@
<div class="container-fluid pb-2"> <div class="container-fluid pb-2">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" /> <funnelHeader
cmsWidgetName="FunnelHeaderWidget"
ref="funnelHeader"
hideSalesforceWebchatLaunchButton />
</div> </div>
</div> </div>
<div class="row justify-content-center"> <div class="row justify-content-center">

View file

@ -620,6 +620,8 @@ function setupMocks({
validateZipResponse = { zipCodeCtu: null, isServiceable: false, isValid: true, state: null }, validateZipResponse = { zipCodeCtu: null, isServiceable: false, isValid: true, state: null },
//Values to set in the state store //Values to set in the state store
carId = null, carId = null,
isHeavyTruck = true,
displayNoServiceAlert = true,
serviceLocationZipCode = null, serviceLocationZipCode = null,
registrationZipCode = null, registrationZipCode = null,
licensePlate = null, //"TESTPLATE", licensePlate = null, //"TESTPLATE",
@ -672,6 +674,8 @@ function setupMocks({
zipCode: registrationZipCode, zipCode: registrationZipCode,
}, },
carId: carId, carId: carId,
isBigTruck: isHeavyTruck,
canSafeliteService: true,
}, },
order: { order: {
customer: { customer: {
@ -716,5 +720,17 @@ function setupMocks({
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => ""); wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
wrapper.vm.$refs.navbar.updateButtonText = jest.fn(); wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
wrapper.vm.$refs.navbar.removeLoader = jest.fn(); wrapper.vm.$refs.navbar.removeLoader = jest.fn();
const closestShops = {
data: {
providers: [
{ id: 1, name: "Shop 1" },
{ id: 2, name: "Shop 2" },
],
},
};
wrapper.vm.findClosestApplicableShops = jest.fn().mockImplementation(() => {
return new Promise((resolve) => resolve(closestShops));
});
return { wrapper, apiPromise }; return { wrapper, apiPromise };
} }

View file

@ -76,6 +76,7 @@
:manualCopy="AlertMatchedDifferentVehicleBody" :manualCopy="AlertMatchedDifferentVehicleBody"
alertClass="alert-warning" alertClass="alert-warning"
v-bind:isDismissible="false" /> v-bind:isDismissible="false" />
<alert <alert
ref="AlertNoService" ref="AlertNoService"
v-if="displayNoServiceAlert" v-if="displayNoServiceAlert"
@ -87,9 +88,9 @@
<navbar <navbar
ref="navbar" ref="navbar"
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid || displayNoServiceAlert"
@back-clicked="backButtonAction" @back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction || displayNoServiceAlert" /> @ForwardClicked="forwardButtonAction" />
</div> </div>
</div> </div>
</div> </div>
@ -177,6 +178,7 @@ export default {
displayInvalidZipAlert: false, displayInvalidZipAlert: false,
showServiceZipField: this.getServiceZipFromStore(), showServiceZipField: this.getServiceZipFromStore(),
isZipServiceable: false, isZipServiceable: false,
displayNoServiceAlert: false,
}; };
}, },
methods: { methods: {
@ -279,6 +281,17 @@ export default {
this.displayInvalidZipAlert = true; this.displayInvalidZipAlert = true;
return this.$refs.navbar.removeLoader(); return this.$refs.navbar.removeLoader();
} }
if (this.isHeavyTruck) {
const closestShops = await this.findClosestApplicableShops(
this.serviceZipCode,
this.carId
);
this.displayNoServiceAlert = !closestShops?.data?.providers?.length;
if (this.displayNoServiceAlert) {
return this.$refs.navbar.removeLoader();
}
}
// Check if the CarId has changed. // Check if the CarId has changed.
this.isCarIdDifferent = this.isCarIdDifferent =
vinLookup.data.vehicle.carId !== this.$store.getters.vehicle.carId; vinLookup.data.vehicle.carId !== this.$store.getters.vehicle.carId;
@ -393,6 +406,7 @@ export default {
this.displayMatchedDifferentVehicleAlert = false; this.displayMatchedDifferentVehicleAlert = false;
this.displayVinLookupByHomeAddressNotAllowedAlert = false; this.displayVinLookupByHomeAddressNotAllowedAlert = false;
this.displayInvalidZipAlert = false; this.displayInvalidZipAlert = false;
this.displayNoServiceAlert = false;
}, },
}, },
mounted() { mounted() {
@ -437,6 +451,7 @@ export default {
serviceZipCode() { serviceZipCode() {
// If they modify the service zip code, then hide the error message. // If they modify the service zip code, then hide the error message.
this.displayNonServiceableZipAlert = false; this.displayNonServiceableZipAlert = false;
this.displayNoServiceAlert = false;
this.$refs.navbar.updateButtonText( this.$refs.navbar.updateButtonText(
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText") this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
); );

View file

@ -3,18 +3,16 @@
<component :is="'script'" src="https://js.afterpay.com/afterpay-1.x.js" async></component> <component :is="'script'" src="https://js.afterpay.com/afterpay-1.x.js" async></component>
<div class="my-4 alert-heading text-center"> <div class="my-4 alert-heading text-center">
<span v-html="afterpayHeaderCopy" /> <span v-html="afterpayHeaderCopy" />
&nbsp;
<span class="afterpay-amount">{{ this.afterpayPrice }}</span> <span class="afterpay-amount">{{ this.afterpayPrice }}</span>
<br /> <br />
<span class="alert-sub-heading" v-html="afterpayHeaderSubHeaderCopy" /> <span class="alert-sub-heading" v-html="afterpayHeaderSubHeaderCopy" />
&nbsp; <img :src="afterPayImageUrl" class="afterpay-image" />
<img :src="afterPayImageUrl" />
&nbsp;
<a <a
id="afterpay-learnmore" id="afterpay-learnmore"
href="#" href="#"
data-afterpay-modal="en_US-safelite" data-afterpay-modal="en_US-safelite"
data-bind="click:afterpayLearnMore"> data-bind="click:afterpayLearnMore"
class="afterpay-learn-more">
<img :src="infoIcon" alt="Info Icon" class="info-icon" /> <img :src="infoIcon" alt="Info Icon" class="info-icon" />
</a> </a>
<div class="after-pay"> <div class="after-pay">
@ -148,11 +146,12 @@ export default {
.alert { .alert {
border-radius: 5px; border-radius: 5px;
margin-top: 1rem; margin-top: 1rem;
margin-bottom: 1.5rem;
&.alert-info { &.alert-info {
.alert-heading { .alert-heading {
color: #000000; color: $black;
font-weight: 600; font-weight: $font-weight-600;
margin-left: 0.5rem; margin-left: 0.5rem;
margin-right: 0.5rem; margin-right: 0.5rem;
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
@ -162,13 +161,15 @@ export default {
} }
} }
.afterpay-amount { .afterpay-amount {
color: #0c7e47; color: $green;
margin-left: 0.25rem;
} }
.alert-sub-heading { .alert-sub-heading {
color: #4b4c4e; color: $gray-1100;
font-weight: 400; font-weight: $font-weight-normal;
} }
.after-pay { .after-pay {
margin-top: 1rem;
.after-pay-toggle { .after-pay-toggle {
cursor: pointer; cursor: pointer;
@ -180,7 +181,7 @@ export default {
background-position: right center; background-position: right center;
margin-left: 0.5rem; margin-left: 0.5rem;
width: 1rem; width: 1rem;
height: 9px; height: 0.5625rem;
display: inline-flex; display: inline-flex;
} }
&.expanded:after { &.expanded:after {
@ -194,8 +195,8 @@ export default {
} }
} }
.after-pay-toggle a { .after-pay-toggle a {
font-weight: 600; font-weight: $font-weight-600;
color: #0070d1; color: $blue;
text-decoration: none; text-decoration: none;
} }
.after-pay-details { .after-pay-details {
@ -211,6 +212,7 @@ export default {
justify-content: space-around; justify-content: space-around;
gap: 0.5rem; gap: 0.5rem;
margin-top: 1rem; margin-top: 1rem;
font-size: 0.875rem;
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
gap: 0rem; gap: 0rem;
} }
@ -218,42 +220,52 @@ export default {
.payment-card { .payment-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-radius: 6px; border-radius: 0.375rem;
border: 0.25px #b3b4b5; border: 0.25px $gray-1000;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); background: linear-gradient(to bottom, $white 50%, $green-600 50%);
box-shadow: 0px 4px 8px -4px rgba(0, 0, 0, 0.15);
width: 4.375rem;
@include media-breakpoint-up(md) {
width: 5rem;
}
font-weight: $font-weight-700;
text-align: center;
} }
.calendar-section { .calendar-section {
background-color: #fff; /* White background for calendar area */
padding: 0.5rem; padding: 0.5rem;
text-align: center; gap: 0.125rem;
gap: 2px;
font-weight: 700;
white-space: nowrap; white-space: nowrap;
color: $black-100;
} }
.payment-amount-section { .payment-amount-section {
background-color: #e0f7e9; /* Light green background for payment area */
padding: 0.5rem; padding: 0.5rem;
text-align: center; }
font-weight: 700; .afterpay-learn-more {
margin-left: 0.25rem;
}
.afterpay-image {
margin-left: 0.25rem;
} }
.amount-due { .amount-due {
color: #00723e; /* Green text */ color: $green-1000; /* Green text */
} }
.legend { .legend {
display: flex; display: flex;
justify-content: center; justify-content: center;
margin-top: 1rem; margin-top: 1rem;
gap: 1rem; gap: 1rem;
color: $gray-1100;
font-size: 0.875rem;
} }
.legend-item { .legend-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 2px; gap: 0.125rem;
white-space: nowrap; white-space: nowrap;
} }
.legend-item span { .legend-item span {
font-weight: 400; font-weight: $font-weight-normal;
} }
} }
</style> </style>

View file

@ -22,6 +22,10 @@
v-model="selectedRouteCode" v-model="selectedRouteCode"
isRequired isRequired
validationRules="time-slot-required" /> validationRules="time-slot-required" />
<div
class="supplemental-information"
v-if="supplementalInformationBlock"
v-html="supplementalInformationBlock" />
<div <div
v-if="hasWaitListExperiment && displayWaitListFeature" v-if="hasWaitListExperiment && displayWaitListFeature"
class="mt-5 bg-light rounded waitlist"> class="mt-5 bg-light rounded waitlist">
@ -36,23 +40,16 @@
v-model="waitListRequested" v-model="waitListRequested"
@click="waitListChecked" /> @click="waitListChecked" />
</div> </div>
<div <div v-if="waitListRequested" class="rounded waitlist-success" ref="waitlistSuccessMessage">
v-if="waitListRequested"
class="mt-5 rounded waitlist-success"
ref="waitlistSuccessMessage">
<img :src="waitListSuccessImage" class="success-image" /> <img :src="waitListSuccessImage" class="success-image" />
<span v-html="waitListSuccessText" class="success-text"></span> <span v-html="waitListSuccessText" class="success-text"></span>
</div> </div>
<div
class="mt-5 mb-2 supplemental-information"
v-if="supplementalInformationBlock"
v-html="supplementalInformationBlock" />
<textBlock <textBlock
v-show="disclaimerTextBlockCopy" v-show="disclaimerTextBlockCopy"
:customText="disclaimerTextBlockCopy" :customText="disclaimerTextBlockCopy"
justifyText="left" justifyText="left"
typeStyle="caption" typeStyle="caption"
class="mb-2" /> class="disclaimer" />
</div> </div>
</template> </template>
@ -574,6 +571,7 @@ export default {
line-height: 1.5rem; line-height: 1.5rem;
font-size: 0.875rem; font-size: 0.875rem;
text-align: left; text-align: left;
margin: 1rem 0 1.5rem;
li strong { li strong {
font-weight: bold; font-weight: bold;
} }
@ -586,6 +584,7 @@ export default {
border-radius: 8px; border-radius: 8px;
font-size: 1rem; font-size: 1rem;
padding: 16px; padding: 16px;
text-align: left;
.ui-checkbox { .ui-checkbox {
padding-left: 0px; padding-left: 0px;
@ -599,6 +598,7 @@ export default {
} }
} }
.waitlist-success { .waitlist-success {
margin: 1rem 0 0;
background-color: #ecf5e9; background-color: #ecf5e9;
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
@ -606,9 +606,11 @@ export default {
border-radius: 5px; border-radius: 5px;
font-size: 1.125rem; font-size: 1.125rem;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
margin-bottom: -0.5rem;
.success-text { .success-text {
margin-left: 0.5rem; margin-left: 0.5rem;
p {
margin-bottom: 0;
}
} }
.success-image { .success-image {
margin-top: 4.5px; margin-top: 4.5px;
@ -617,5 +619,9 @@ export default {
color: #0c7e47; color: #0c7e47;
} }
} }
.disclaimer {
text-align: left;
margin: 1.5rem 0 0;
}
} }
</style> </style>

View file

@ -100,7 +100,7 @@ describe("appointment-type-question.vue", () => {
]); ]);
}); });
it("Should display only the In-Shop and Drop-Off answers when only in-shop service is available", async () => { it("Should display only the In-Shop answer when only in-shop service is available", async () => {
// Arrange/Act // Arrange/Act
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
mixins: [mockMixin], mixins: [mockMixin],
@ -108,6 +108,34 @@ describe("appointment-type-question.vue", () => {
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
isServiceableInshop: true, isServiceableInshop: true,
isServiceableMobile: false, isServiceableMobile: false,
isServiceableDropoff: false,
},
mountOptions: {
attachTo: document.body,
},
});
// Assert
expect(wrapper.vm.answersToDisplay).toEqual([
{
AnswerImageUrl: "",
Name: "Inshop",
SubText: "",
SubWidgetName: "",
Text: "In-shop",
},
]);
});
it("Should display only the In-Shop and Drop-Off answers when mobile service is not available", async () => {
// Arrange/Act
const { wrapper } = setupMocks({
mixins: [mockMixin],
props: {
cmsWidgetName: cmsWidgetName,
isServiceableInshop: true,
isServiceableMobile: false,
isServiceableDropoff: true,
}, },
mountOptions: { mountOptions: {
attachTo: document.body, attachTo: document.body,

View file

@ -32,6 +32,7 @@ export default {
cmsWidgetName: String, cmsWidgetName: String,
isServiceableMobile: Boolean, isServiceableMobile: Boolean,
isServiceableInshop: Boolean, isServiceableInshop: Boolean,
isServiceableDropoff: Boolean,
mobileFeeApplies: Boolean, mobileFeeApplies: Boolean,
}, },
computed: { computed: {
@ -45,7 +46,7 @@ export default {
const shouldShowMobile = this.isServiceableMobile; const shouldShowMobile = this.isServiceableMobile;
const shouldShowInshop = this.isServiceableInshop; const shouldShowInshop = this.isServiceableInshop;
const shouldShowDropoff = const shouldShowDropoff =
this.isServiceableInshop && !this.$store.getters.damage.isRepair; this.isServiceableDropoff && !this.$store.getters.damage.isRepair;
var answers = this.answersFromCms var answers = this.answersFromCms
? this.answersFromCms.filter((answer) => { ? this.answersFromCms.filter((answer) => {
@ -77,6 +78,11 @@ export default {
isMobileOnly() { isMobileOnly() {
return this.isServiceableMobile && !this.isServiceableInshop; return this.isServiceableMobile && !this.isServiceableInshop;
}, },
isInshopOnly() {
return (
this.isServiceableInshop && !this.isServiceableMobile && !this.isServiceableDropoff
);
},
}, },
watch: { watch: {
answersToDisplay: { answersToDisplay: {
@ -86,7 +92,7 @@ export default {
newValue.length == 1 && newValue.length == 1 &&
newValue.findIndex((answer) => answer.Name == "Mobile") != -1 newValue.findIndex((answer) => answer.Name == "Mobile") != -1
) { ) {
this.selectedValues = "Mobile"; this.selectedValue = "Mobile";
} }
}, },
immediate: true, immediate: true,
@ -94,7 +100,14 @@ export default {
isMobileOnly: { isMobileOnly: {
handler(newValue) { handler(newValue) {
if (newValue) { if (newValue) {
this.selectedValues = "Mobile"; this.selectedValue = "Mobile";
}
},
},
isInshopOnly: {
handler(newValue) {
if (newValue) {
this.selectedValue = "Inshop";
} }
}, },
}, },

View file

@ -76,6 +76,7 @@
v-show="isAppointmentTypeDisplayed" v-show="isAppointmentTypeDisplayed"
:isServiceableMobile="isServiceableMobile" :isServiceableMobile="isServiceableMobile"
:isServiceableInshop="isServiceableInshop" :isServiceableInshop="isServiceableInshop"
:isServiceableDropoff="isServiceableDropoff"
:isDisplayed="isAppointmentTypeDisplayed" :isDisplayed="isAppointmentTypeDisplayed"
:mobileFeeApplies="mobileFeeApplies" :mobileFeeApplies="mobileFeeApplies"
ref="appointmentTypeQuestion" ref="appointmentTypeQuestion"
@ -197,6 +198,8 @@ export default {
isVehicleProtected: this.getIsVehicleProtectedFromStore(), isVehicleProtected: this.getIsVehicleProtectedFromStore(),
isGlassServiceableInshop: null, isGlassServiceableInshop: null,
isRecalibrationServiceableInshop: null, isRecalibrationServiceableInshop: null,
isGlassServiceableDropoff: null,
isRecalibrationServiceableDropoff: null,
isGlassServiceableMobile: null, isGlassServiceableMobile: null,
isRecalibrationServiceableMobile: null, isRecalibrationServiceableMobile: null,
selectedAppointmentType: this.getSelectedAppointmentType(), selectedAppointmentType: this.getSelectedAppointmentType(),
@ -356,6 +359,13 @@ export default {
return this.isGlassServiceableInshop; return this.isGlassServiceableInshop;
} }
}, },
isServiceableDropoff() {
if (this.isRecalibrationServiceableDropoff !== null) {
return this.isGlassServiceableDropoff && this.isRecalibrationServiceableDropoff;
} else {
return this.isGlassServiceableDropoff;
}
},
isShopQuestionDisplayed() { isShopQuestionDisplayed() {
return ( return (
this.selectedAppointmentType === "Inshop" || this.selectedAppointmentType === "Inshop" ||
@ -561,6 +571,9 @@ export default {
this.isGlassServiceableInshop = serviceabilityDetails.isGlassServiceableInshop; this.isGlassServiceableInshop = serviceabilityDetails.isGlassServiceableInshop;
this.isRecalibrationServiceableInshop = this.isRecalibrationServiceableInshop =
serviceabilityDetails.isRecalibrationServiceableInshop; serviceabilityDetails.isRecalibrationServiceableInshop;
this.isGlassServiceableDropoff = serviceabilityDetails.isGlassServiceableDropoff;
this.isRecalibrationServiceableDropoff =
serviceabilityDetails.isRecalibrationServiceableDropoff;
this.isGlassServiceableMobile = serviceabilityDetails.isGlassServiceableMobile; this.isGlassServiceableMobile = serviceabilityDetails.isGlassServiceableMobile;
this.isRecalibrationServiceableMobile = this.isRecalibrationServiceableMobile =
serviceabilityDetails.isRecalibrationServiceableMobile; serviceabilityDetails.isRecalibrationServiceableMobile;

View file

@ -415,6 +415,21 @@ describe("service-zip.vue", () => {
expect(wrapper.vm.displayInvalidZipAlert).toBe(false); expect(wrapper.vm.displayInvalidZipAlert).toBe(false);
expect(wrapper.vm.displayNonServiceableZipAlert).toBe(false); expect(wrapper.vm.displayNonServiceableZipAlert).toBe(false);
}); });
test("should hide the AlertNoService when displayNoServiceAlert is false", async () => {
// Arrange
// no changes to store
applyMockStoreDataToGetters();
const wrapper = setupMocks({});
wrapper.vm.serviceZipCode = "12345";
wrapper.vm.displayNoServiceAlert = false;
await wrapper.vm.forwardButtonAction();
// Check if the AlertNoService component is rendered
expect(wrapper.vm.displayNoServiceAlert).toBe(false);
});
}); });
}); });
@ -511,5 +526,15 @@ function setupMocks({ customMountOptions, customZipQuery, customZipDataResponse
]; ];
const wrapper = shallowMount(serviceZip, mountOptions); const wrapper = shallowMount(serviceZip, mountOptions);
wrapper.vm.findClosestApplicableShops = jest.fn().mockImplementation(() => {
return Promise.resolve({
data: {
providers: [
{ id: 1, name: "Shop 1" },
{ id: 2, name: "Shop 2" },
],
},
});
});
return wrapper; return wrapper;
} }

View file

@ -46,11 +46,18 @@
v-model="customAlertData" v-model="customAlertData"
v-if="displayNonServiceableZipAlert" v-if="displayNonServiceableZipAlert"
alertClass="alert-danger" /> alertClass="alert-danger" />
<alert
ref="AlertNoService"
v-if="displayNoServiceAlert"
class="my-4"
cmsWidgetName="AlertNoServiceWidget"
alertClass="alert-danger"
v-bind:isDismissible="false" />
<navbar <navbar
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
ref="navbar" ref="navbar"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid || displayNoServiceAlert"
@back-clicked="backButtonAction" @back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" /> @ForwardClicked="forwardButtonAction" />
</div> </div>
@ -114,6 +121,7 @@ export default {
isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(), isHeavyTruck: this.getIsVehicleHeavyTruckServiceableFromStore(),
displayInvalidZipAlert: false, displayInvalidZipAlert: false,
displayNonServiceableZipAlert: false, displayNonServiceableZipAlert: false,
displayNoServiceAlert: false, // Initialize the property
}; };
}, },
@ -278,17 +286,6 @@ export default {
return this.$refs.navbar.removeLoader(); return this.$refs.navbar.removeLoader();
} }
} }
if (this.isHeavyTruck) {
const closestShops = await this.findClosestApplicableShops(
this.serviceZipCode,
this.carId
);
this.displayNoServiceAlert = !closestShops?.data?.providers?.length;
if (this.displayNoServiceAlert) {
return this.$refs.navbar.removeLoader();
}
}
const payment = this.$store.getters.payment; const payment = this.$store.getters.payment;
const policy = this.$store.getters.policy; const policy = this.$store.getters.policy;
@ -346,7 +343,6 @@ export default {
serviceZipCode() { serviceZipCode() {
this.displayNonServiceableZipAlert = false; this.displayNonServiceableZipAlert = false;
this.displayNoServiceAlert = false; this.displayNoServiceAlert = false;
this.displayNoServiceAlert = false;
}, },
}, },
components: { components: {

View file

@ -95,12 +95,11 @@ describe("glass-part-question.vue", () => {
}); });
//Act //Act
await wrapper.vm.$nextTick();
const listCard = await wrapper.findComponent({ const listCard = await wrapper.findComponent({
name: "buttonQuestion", name: "buttonQuestion",
}); });
await wrapper.setData({ selectedTint: "Green Tint" }); wrapper.setData({ selectedTint: "Green Tint" });
// to trigger the computed setter // to trigger the computed setter
wrapper.vm.selectedPartNumber = "DB12209GTYN"; wrapper.vm.selectedPartNumber = "DB12209GTYN";
@ -253,8 +252,7 @@ describe("glass-part-question.vue", () => {
}); });
// Act // Act
await wrapper.setData({ selectedTint: selectedTint }); wrapper.setData({ selectedTint: selectedTint });
// Assert // Assert
expect(expectedResults).toEqual(wrapper.vm.partsForSelectedTint); expect(expectedResults).toEqual(wrapper.vm.partsForSelectedTint);
} }

View file

@ -258,6 +258,23 @@ describe("vin-lookup.vue", () => {
// Assert // Assert
expect(wrapper.findAllComponents({ name: "alert" }).length).toBe(1); expect(wrapper.findAllComponents({ name: "alert" }).length).toBe(1);
}); });
test("should hide the AlertNoService when displayNoServiceAlert is false", async () => {
const { wrapper } = setupMocks({
isZipServiceable: true,
displayNoServiceAlert: false,
lookupVinbyAddressResponse: {
isStatePermissible: true,
vinVehicles: [], // Return no vehicles
},
});
// Ensure displayNoServiceAlert is false
await wrapper.setData({ displayNoServiceAlert: false });
await wrapper.vm.forwardButtonAction();
// Check if the AlertNoService component is not rendered
const alertNoService = wrapper.findComponent({ ref: "AlertNoService" });
expect(alertNoService.exists()).toBe(false);
});
}); });
describe("getVinFromImage", () => { describe("getVinFromImage", () => {

View file

@ -111,10 +111,18 @@
" "
alertClass="alert-success" /> alertClass="alert-success" />
<alert
ref="AlertNoService"
v-if="displayNoServiceAlert"
class="my-4"
cmsWidgetName="AlertNoServiceWidget"
alertClass="alert-danger"
v-bind:isDismissible="false" />
<navbar <navbar
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
ref="navbar" ref="navbar"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid || displayNoServiceAlert"
@back-clicked="backButtonAction" @back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" /> @ForwardClicked="forwardButtonAction" />
</div> </div>
@ -209,6 +217,7 @@ export default {
displayVinNotFoundAlert: false, displayVinNotFoundAlert: false,
displayMatchedDifferentVehicleAlert: false, displayMatchedDifferentVehicleAlert: false,
displayVinScanFailedAlert: false, displayVinScanFailedAlert: false,
displayNoServiceAlert: false,
}; };
}, },
methods: { methods: {
@ -247,6 +256,9 @@ export default {
); );
} }
}, },
getCarIdfromStore() {
return this.$store.getters.vehicle.carId;
},
getIsVehicleHeavyTruckServiceableFromStore() { getIsVehicleHeavyTruckServiceableFromStore() {
return store.getters.vehicle.isBigTruck && store.getters.vehicle.canSafeliteService; return store.getters.vehicle.isBigTruck && store.getters.vehicle.canSafeliteService;
}, },
@ -341,7 +353,7 @@ export default {
false false
); );
if (this.isHeavyTruck) { if (this.isHeavyTruck) {
closestShops = await this.findClosestApplicableShops( const closestShops = await this.findClosestApplicableShops(
this.serviceZipCode, this.serviceZipCode,
this.carId this.carId
); );
@ -499,7 +511,6 @@ export default {
this.displayVinNotFoundAlert = false; this.displayVinNotFoundAlert = false;
this.displayVinScanFailedAlert = false; this.displayVinScanFailedAlert = false;
this.displayNoServiceAlert = false; this.displayNoServiceAlert = false;
this.displayNoServiceAlert = false;
}, },
}, },
mounted() { mounted() {
@ -576,6 +587,7 @@ export default {
}, },
serviceZipCode() { serviceZipCode() {
this.displayNonServiceableZipAlert = false; this.displayNonServiceableZipAlert = false;
this.displayNoServiceAlert = false;
}, },
}, },
components: { components: {

View file

@ -3,6 +3,7 @@
// White/black // White/black
$white: #ffffff; $white: #ffffff;
$black: #000000; $black: #000000;
$black-100: #0a0a0a;
// Blues // Blues
$blue-100: #e4f1f7; // Used in theme $blue-100: #e4f1f7; // Used in theme
@ -33,9 +34,11 @@ $green-300: #94d7b6;
$green-400: #5abc8c; // Used in theme $green-400: #5abc8c; // Used in theme
$green-500: #2ca168; $green-500: #2ca168;
$green: #0c7e47; // Default Green $green: #0c7e47; // Default Green
$green-600: #e7f2ed;
$green-700: #006a36; $green-700: #006a36;
$green-800: #004f28; $green-800: #004f28;
$green-900: #00331a; $green-900: #00331a;
$green-1000: #00723e;
// Yellows // Yellows
$yellow-100: #fff5eb; $yellow-100: #fff5eb;
@ -59,6 +62,8 @@ $gray-600: #4d5151; // Used in theme
$gray-700: #303333; // Used in theme $gray-700: #303333; // Used in theme
$gray-800: #222424; // Used in theme $gray-800: #222424; // Used in theme
$gray-900: #181a1a; // Used in theme $gray-900: #181a1a; // Used in theme
$gray-1000: #b3b4b5;
$gray-1100: #4b4c4e;
//orange //orange
$orange-600: #e86421; $orange-600: #e86421;
@ -134,6 +139,8 @@ $font-weight-lighter: lighter;
$font-weight-light: 300; $font-weight-light: 300;
$font-weight-normal: 400; $font-weight-normal: 400;
$font-weight-bold: 500; $font-weight-bold: 500;
$font-weight-600: 600;
$font-weight-700: 700;
$font-weight-bolder: bolder; $font-weight-bolder: bolder;
//Headings //Headings