Merge pull request #3245 from Safelite/feature/CASH-2952

Feature/cash 2952
This commit is contained in:
matthew-sykes 2026-06-29 12:43:20 -04:00 committed by GitHub
commit 4c504b54ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 66 additions and 23 deletions

View file

@ -87,9 +87,22 @@ window.onerror = (msg, url, line, col, error) => {
return suppressErrorAlert;
};
function isThirdPartyUnhandledRejection(reason) {
const stack = reason instanceof Error ? reason.stack : "";
if (typeof stack !== "string") {
return false;
}
return stack.includes("chrome-extension://") || stack.includes("quantummetric.com");
}
// Log Promise rejections that are never handled (.catch / await try/catch), e.g. fire-and-forget async.
window.addEventListener("unhandledrejection", (event) => {
const reason = event.reason;
if (isThirdPartyUnhandledRejection(reason)) {
return;
}
const message = reason instanceof Error ? reason.message : String(reason);
global.$logger.logError(`unhandledrejection: ${message}`, {
stack: reason instanceof Error ? reason.stack : undefined,

View file

@ -13,6 +13,22 @@ const mockMixin = {
const maska = jest.fn();
describe("textboxQuestion.vue", () => {
it("Should coerce non-string modelValue to empty string for v-model.trim", async () => {
const wrapper = shallowMount(textboxQuestion, {
global: {
directives: {
maska: maska,
},
},
propsData: {
modelValue: null,
},
mixins: [mockMixin],
});
expect(wrapper.vm.value).toBe("");
});
it("Should render a text input", async () => {
// Arrange
const wrapper = shallowMount(textboxQuestion, {

View file

@ -25,7 +25,7 @@
<input
class="form-control"
v-model.trim="value"
v-maska="mask"
v-maska="effectiveMask"
:type="type"
:ref="inputId"
:id="inputId"
@ -90,6 +90,19 @@ import loader from "@/ux-components/loader/loader.vue";
import { ref } from "vue";
import { v4 as uuidv4 } from "uuid";
function coerceTextboxValue(modelValue) {
if (modelValue == null) {
return "";
}
if (typeof modelValue === "number") {
return String(modelValue);
}
if (typeof modelValue === "string") {
return modelValue;
}
return "";
}
export default {
name: "textbox-question",
props: {
@ -145,18 +158,8 @@ export default {
const inputId = !props.customInputId ? `input-${uuid}` : props.customInputId;
const propsClone = Object.assign({}, props);
const modelValue = propsClone.modelValue;
let initialValue;
let isImageProcessing = ref(false);
switch (typeof modelValue) {
case "number":
initialValue = modelValue;
break;
default:
initialValue = modelValue && modelValue.length > 0 ? modelValue : "";
break;
}
const modelValue = coerceTextboxValue(propsClone.modelValue);
const initialValue = modelValue;
const fieldOptions = {
type: "text",
@ -164,6 +167,8 @@ export default {
initialValue: initialValue,
};
let isImageProcessing = ref(false);
const { errorMessage, handleBlur, handleChange, meta, validate, errors } = useField(
inputId,
props.validationRules,
@ -223,18 +228,25 @@ export default {
},
value: {
get: function () {
return this.modelValue;
return coerceTextboxValue(this.modelValue);
},
set: function (newValue) {
this.$emit("update:modelValue", newValue);
},
},
effectiveMask() {
return this.mask ?? "";
},
},
mounted() {
this.$emit("textboxQuestionEvent.inputIdAssigned", this.inputId);
},
watch: {
async value(newValue) {
if (typeof this.handleChange !== "function") {
return;
}
const result = await validate(newValue, this.validationRules); // do a test validation check, without triggering full validation
if (result.valid) {
this.handleChange(newValue); // trigger full validation on this field only

View file

@ -34,7 +34,7 @@
class="keys-message" />
<mobileAddressQuestions
ref="addressQuestions"
v-model="this.addressQuestions"
v-model="addressQuestions"
captureApartmentNumberOrBusinessName="true"
preserveCityAndStateOnReset="true"
labelBold="true"
@ -42,7 +42,7 @@
isZipCodeDisabled="true" />
<vehicleProtectedQuestion
ref="vehicleProtectedQuestion"
v-model="this.isVehicleProtected"
v-model="isVehicleProtected"
cmsWidgetName="VehicleProtectedQuestionWidget"
labelBold="true" />
<textBlock
@ -84,11 +84,11 @@ export default {
data() {
return {
addressQuestions: {
streetAddress: this.getServiceAddressFromStore(),
apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(),
city: this.getServiceCityFromStore(),
state: this.getServiceStateFromStore(),
zipCode: this.getServiceZipCodeFromStore(),
streetAddress: String(this.getServiceAddressFromStore() ?? ""),
apartmentNumberOrBusinessName: String(this.getServiceAddress2FromStore() ?? ""),
city: String(this.getServiceCityFromStore() ?? ""),
state: String(this.getServiceStateFromStore() ?? ""),
zipCode: String(this.getServiceZipCodeFromStore() ?? ""),
},
isVehicleProtected: this.getIsVehicleProtectedFromStore(),
};

View file

@ -25,7 +25,8 @@ export default {
name: "vehicle-protected-question",
props: {
modelValue: {
isVehicleProtected: Boolean,
type: Boolean,
default: null,
},
cmsWidgetName: String,
labelBold: {

View file

@ -29,7 +29,8 @@ export default {
name: "service-zip-question",
props: {
modelValue: {
serviceZipCode: String,
type: String,
default: "",
},
cmsWidgetName: String,
isRequired: {