Merge remote-tracking branch 'origin/develop' into feature/CSR-77

This commit is contained in:
Max 2022-01-10 15:39:14 -05:00
commit 21c5316950
23 changed files with 532 additions and 233 deletions

View file

@ -1,8 +1,7 @@
<template>
<button
v-if="backButtonAction"
@click="backButtonAction"
class="back-button-wrapper p-2"
@click="handleClick"
class="back-button-wrapper p-3"
:aria-label="backButtonAccessibleText"
>
<div class="button-back">
@ -20,12 +19,13 @@ export default {
type: String,
required: true,
default: "",
},
backButtonAction: {
type: Function,
default: null,
}
},
methods: {
handleClick() {
this.$emit('click-event');
}
}
};
</script>

View file

@ -13,7 +13,7 @@ describe("buttonQuestion.vue", () => {
wrapper.vm.chooseAnswer("2021");
// Assert
expect(wrapper.find(".needed_car_info-text").text()).toEqual(
expect(wrapper.find(".text-center").text()).toEqual(
"Question Text"
);
const buttonButtons = wrapper.findAllComponents('[data-test="button"]');

View file

@ -1,7 +1,7 @@
<template>
<div class="button_question">
<div class="needed_car_info mt-6 mb-4 d-flex">
<span class="text-center fs-6 fw-bold w-100 needed_car_info-text">{{
<div class="mt-6 mb-4 d-flex">
<span class="text-center fs-6 fw-bold w-100">{{
questionText
}}</span>
</div>
@ -32,6 +32,7 @@ import listButton from "@/ux-components/list-button/list-button";
export default {
name: "buttonQuestion",
props: {
isMultiSelect: Boolean,
questionText: String,
answers: Array,
modelValue: String,
@ -58,4 +59,4 @@ export default {
-webkit-overflow-scrolling: touch;
}
}
</style>
</style>

View file

@ -6,16 +6,17 @@
{{ text }}
</span>
<buttonBack
v-if="hasBackButton"
:backButtonAction="backButtonAction"
/>
</h5>
v-if="hasBackButton"
:backButtonAccessibleText="backButtonAccessibleText"
@click-event="clickEvent"
/>
</h5>
</div>
</div>
</template>
<script>
import buttonBack from "@/ux-components/button-back/button-back";
import buttonBack from "@/common-components/button-back/button-back";
export default {
name: "FunnelSubHeader",
@ -26,14 +27,15 @@ export default {
},
props: {
hasBackButton: Boolean,
backButtonUrl: String,
backButtonAccessibleText: String,
backButtonAction: Function,
},
components: {
buttonBack,
},
methods: {
clickEvent() {
this.$emit('click-event');
},
initializeComponent(cmsContent) {
this.text = cmsContent.HeaderText;
}

View file

@ -10,16 +10,20 @@ export function fetchCmsContentForPage(fmgPage) {
response.data.Result.forEach((widget) => {
let widgetWithReplacements = findAndReplaceGlobalStateValues(widget.Model, widget.Type);
let widgetWithReplacements = findAndReplaceGlobalStateValues(widget.Model, widget.Name);
if (Object.values(widgetNames).includes(widgetWithReplacements.Type)) {
// If we already have this widget, push it on the collection
if (widgetWithReplacements.Type in pageDataFromCms) {
pageDataFromCms[widgetWithReplacements.Type].push(widgetWithReplacements.Model);
return;
}
// If we already have this widget, push it on the collection
if (widgetWithReplacements.Name in pageDataFromCms) {
pageDataFromCms[widgetWithReplacements.Name].push(widgetWithReplacements.Model);
return;
}
pageDataFromCms[widgetWithReplacements.Type] = [widgetWithReplacements.Model];
pageDataFromCms[widgetWithReplacements.Name] = [widgetWithReplacements.Model];
});
Object.keys(pageDataFromCms).forEach((key) => {
if (pageDataFromCms[key].length === 1){
pageDataFromCms[key] = pageDataFromCms[key][0];
}
});
@ -66,10 +70,10 @@ function mapStringToState(str) {
// Parent function for processWidgetItemForReplacement. This will loop through the parent
// object and pass any objects that need additional processing to the processWidgetItemForReplacement function.
function findAndReplaceGlobalStateValues(widgetModel, widgetType) {
function findAndReplaceGlobalStateValues(widgetModel, widgetName) {
const objWithReplacements = {
Type: widgetType,
Name: widgetName,
Model: {}
};

View file

@ -15,19 +15,19 @@ describe("cms-content-helper.js", () => {
const cmsMockData = {
Result: [
{
Type: "FunnelSubHeaderWidget",
Name: "FunnelHeaderWidget",
Model: {
HeaderText: "Select a year to get started",
},
},
{
Type: "FunnelSubHeaderWidget",
Name: "FunnelSubHeaderWidget",
Model: {
HeaderText: "Select a model",
},
},
{
Type: "RadioQuestionWidget",
Name: "VehicleYearQuestion",
Model: {
QuestionText: "What year is your vehicle?",
},
@ -40,7 +40,7 @@ describe("cms-content-helper.js", () => {
// Act
fetchCmsContentForPage("testPage").then((response) => {
// Assert
expect(response.FunnelSubHeaderWidget[0].HeaderText).toEqual(
expect(response.FunnelHeaderWidget.HeaderText).toEqual(
"Select a year to get started"
);
});
@ -53,7 +53,7 @@ describe("cms-content-helper.js", () => {
const cmsMockData = {
Result: [
{
Type: "FunnelSubHeaderWidget",
Name: "FunnelSubHeaderWidget",
Model: {
HeaderText: "{globalState:order.vehicle.year}",
},
@ -65,7 +65,7 @@ describe("cms-content-helper.js", () => {
fetchCmsContentForPage("testPage").then((response) => {
// Assert
expect(response.FunnelSubHeaderWidget[0].HeaderText).toEqual(
expect(response.FunnelSubHeaderWidget.HeaderText).toEqual(
"2019"
);
});
@ -78,13 +78,13 @@ describe("cms-content-helper.js", () => {
const cmsMockData = {
Result: [
{
Type: "FunnelSubHeaderWidget",
Name: "FunnelSubHeaderWidget",
Model: {
HeaderText: "{globalState:order.vehicle.year}",
},
},
{
Type: "RadioQuestionWidget",
Name: "VehicleYearQuestion",
Model: {
ExampleText: "My widget value!",
},
@ -96,8 +96,8 @@ describe("cms-content-helper.js", () => {
fetchCmsContentForPage("testPage").then((response) => {
// Assert
expect(response.FunnelSubHeaderWidget[0].HeaderText).toEqual("2019");
expect(response.RadioQuestionWidget[0].ExampleText).toEqual("My widget value!");
expect(response.FunnelSubHeaderWidget.HeaderText).toEqual("2019");
expect(response.VehicleYearQuestion.ExampleText).toEqual("My widget value!");
});
});
@ -109,13 +109,13 @@ describe("cms-content-helper.js", () => {
const cmsMockData = {
Result: [
{
Type: "FunnelSubHeaderWidget",
Name: "FunnelSubHeaderWidget",
Model: {
HeaderText: "{globalState:order.vehicle.year}",
},
},
{
Type: "RadioQuestionWidget",
Name: "VehicleMakeQuestion",
Model: {
OtherObjectInside: {
ExampleText: "{globalState:order.vehicle.make}",
@ -130,8 +130,8 @@ describe("cms-content-helper.js", () => {
fetchCmsContentForPage("testPage").then((response) => {
// Assert
expect(response.FunnelSubHeaderWidget[0].HeaderText).toEqual("2019");
expect(response.RadioQuestionWidget[0].OtherObjectInside.ExampleText).toEqual("Acura");
expect(response.FunnelSubHeaderWidget.HeaderText).toEqual("2019");
expect(response.VehicleMakeQuestion.OtherObjectInside.ExampleText).toEqual("Acura");
});
});

View file

@ -185,24 +185,6 @@
</fieldset>
</div>
</div>
<div class="row g-2">
<div class="col">
<h6 class="mx-2 my-4">With error</h6>
<fieldset>
<legend class="sr-only">Radio button with error state</legend>
<listCard
isRadioHorizontal
buttonImage="windshield-damage.svg"
buttonLabel="Windshield"
altText=""
buttonID="Horizontal Radio Button Error State"
groupID="radio-demo-5"
buttonLabelSubCopy="Description"
hasError
/>
</fieldset>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">List Button - Single-Line</h4>
@ -227,8 +209,6 @@
loaderPosition="right"
sizeInRem="1"
screenReaderOnlyText=" opens new window"
hasError
errorMessage="Error"
/>
<listButton
isMultiSelect
@ -495,7 +475,6 @@
groupName="demo-4"
ariaLabelBy="vehicle-model"
buttonID="1"
buttonLabelSubCopy=""
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
@ -509,7 +488,6 @@
groupName="demo-4"
ariaLabelBy="vehicle-model"
buttonID="2"
buttonLabelSubCopy=""
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
@ -523,7 +501,6 @@
groupName="demo-4"
ariaLabelBy="vehicle-model"
buttonID="3"
buttonLabelSubCopy=""
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
@ -546,8 +523,8 @@
groupName="demo-4-checkbox"
ariaLabelBy="vehicle-model"
buttonID="4"
buttonLabelSubCopy=""
textPosition="text-center"
loaderEnabled=true
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
@ -559,8 +536,8 @@
groupName="demo-4-checkbox"
ariaLabelBy="vehicle-model"
buttonID="5"
buttonLabelSubCopy=""
textPosition="text-center"
loaderEnabled=true
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
@ -572,8 +549,8 @@
groupName="demo-4-checkbox"
ariaLabelBy="vehicle-model"
buttonID="6"
buttonLabelSubCopy=""
textPosition="text-center"
loaderEnabled=true
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
@ -739,7 +716,7 @@
<script>
import buttonPrimary from "@/ux-components/button-primary/button-primary";
import buttonSecondary from "@/ux-components/button-secondary/button-secondary";
import buttonBack from "@/ux-components/button-back/button-back";
import buttonBack from "@/common-components/button-back/button-back";
import listCard from "@/ux-components/list-card/list-card";
import listButton from "@/ux-components/list-button/list-button";
import alert from "@/ux-components/alert/alert";

View file

@ -17,10 +17,10 @@ describe("vehicle-make.vue", () => {
test("Make question component is initized with api data", async (done) => {
//Arrange
const radioQuestionCmsContent = { QuestionText: "What make is your vehicle?" };
const vehicleMakeQuestionCmsContent = { QuestionText: "What make is your vehicle?" };
const makeQuestionInitialData = ["honda", "ford", "dodge"];
const { wrapper, apiPromise } = setupMocks( {
radioQuestionCmsContent: radioQuestionCmsContent,
vehicleMakeQuestionCmsContent: vehicleMakeQuestionCmsContent,
makeQuestionInitialData: makeQuestionInitialData,
} );
@ -29,7 +29,7 @@ describe("vehicle-make.vue", () => {
//Assert
apiPromise.finally(() => {
expect(makeQuestion.methods.initializeComponent).toHaveBeenCalledWith(radioQuestionCmsContent, makeQuestionInitialData);
expect(makeQuestion.methods.initializeComponent).toHaveBeenCalledWith(vehicleMakeQuestionCmsContent, makeQuestionInitialData);
done();
});
});
@ -122,7 +122,7 @@ describe("vehicle-make.vue", () => {
});
function setupMocks({
radioQuestionCmsContent = {},
vehicleMakeQuestionCmsContent = {},
makeQuestionInitialData = {},
pageHeaderWidgetHeaderText = {},
mountOptionsMockData = {},
@ -131,20 +131,18 @@ function setupMocks({
//Mock api responses
const apiResponses = {
cmsContent: {
FunnelSubHeaderWidget: [pageHeaderWidgetHeaderText],
RadioQuestionWidget: [radioQuestionCmsContent],
VehicleBannerWidget: [
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
VehicleMakeQuestion: vehicleMakeQuestionCmsContent,
VehicleBannerWidget:
{
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
},
],
FunnelHeaderWidget: [
FunnelHeaderWidget:
{
LogoImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
},
],
},
makeQuestionInitialData: makeQuestionInitialData,
};

View file

@ -8,8 +8,8 @@
ref="funnelSubHeader"
:hasBackButton="true"
backButtonAccessibleText="Change Vehicle Year"
:backButtonAction="backButtonAction"
class="Header"
@click-event="backButtonAction"
class="Header"
/>
<makeQuestion v-model="selectedMake" ref="makeQuestion" />
</div>
@ -55,16 +55,16 @@ export default {
settleAllPromises(promiseResultMap).then((resultMap) => {
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.$refs.funnelSubHeader.initializeComponent(resultMap.cmsContent.FunnelSubHeaderWidget[0]);
vm.$refs.funnelHeader.initializeComponent(resultMap.cmsContent.FunnelHeaderWidget[0]);
vm.$refs.vehicleBanner.initializeComponent(resultMap.cmsContent.VehicleBannerWidget[0]);
vm.$refs.makeQuestion.initializeComponent(resultMap.cmsContent.RadioQuestionWidget[0], resultMap.makeQuestionInitialData);
vm.$refs.funnelSubHeader.initializeComponent(resultMap.cmsContent.FunnelSubHeaderWidget);
vm.$refs.funnelHeader.initializeComponent(resultMap.cmsContent.FunnelHeaderWidget);
vm.$refs.vehicleBanner.initializeComponent(resultMap.cmsContent.VehicleBannerWidget);
vm.$refs.makeQuestion.initializeComponent(resultMap.cmsContent.VehicleMakeQuestion, resultMap.makeQuestionInitialData);
});
});
},
methods: {
backButtonAction: function () {
backButtonAction() {
// route to move backwards
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
}

View file

@ -16,7 +16,6 @@ import baseMixin from "@/mixins/base-mixin.js";
export default {
name: "model-question",
mixins: [baseMixin],
data() {
return {
questionText: null,

View file

@ -7,44 +7,35 @@ import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
import { settleAllPromises } from "@/helpers/layout-helper.js";
import { nextTick } from "vue";
// Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({
settleAllPromises: jest.fn(),
}));
describe("vehicle-model.vue", () => {
test("Model question component is initized with api data", async (done) => {
//Arange
const radioQuestionCmsContent = { QuestionText: "What model is your vehicle?" };
const buttonQuestionContent = { QuestionText: "What model is your vehicle?" };
const modelQuestionInitialData = ["accord", "civic", "insight"];
const { wrapper, apiPromise } = setupMocks( {
radioQuestionCmsContent: radioQuestionCmsContent,
buttonQuestionContent: buttonQuestionContent,
modelQuestionInitialData: modelQuestionInitialData,
} );
//Act
vehicleModel.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-model" } }, undefined, (c) => c(wrapper.vm));
//Assert
apiPromise.finally(() => {
expect(modelQuestion.methods.initializeComponent).toHaveBeenCalledWith(radioQuestionCmsContent, modelQuestionInitialData);
expect(modelQuestion.methods.initializeComponent).toHaveBeenCalledWith(buttonQuestionContent, modelQuestionInitialData);
done();
});
});
});
describe("vehicle-model.vue", () => {
test("Page header is initailized with api data", async (done) => {
//Arrange
const pageHeaderWidgetHeaderText = "Select a model to get started";
const { wrapper, apiPromise } = setupMocks( { pageHeaderWidgetHeaderText: pageHeaderWidgetHeaderText });
//Act
vehicleModel.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-model" } }, undefined, (c) => c(wrapper.vm));
//Assert
apiPromise.finally(() => {
expect(funnelSubHeader.methods.initializeComponent).toHaveBeenCalledWith(pageHeaderWidgetHeaderText);
@ -52,20 +43,16 @@ describe("vehicle-model.vue", () => {
});
});
});
describe("vehicle-model.vue", () => {
test("Page logo image is initailized with api data", async (done) => {
//Arrange
const SiteHeaderWidget = {
LogoImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
}
const { wrapper, apiPromise } = setupMocks( { SiteHeaderWidget: SiteHeaderWidget});
//Act
vehicleModel.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-model" } }, undefined, (c) => c(wrapper.vm));
//Assert
apiPromise.finally(() => {
expect(funnelHeader.methods.initializeComponent).toHaveBeenCalledWith(SiteHeaderWidget);
@ -73,20 +60,16 @@ describe("vehicle-model.vue", () => {
});
});
});
describe("vehicle-model.vue", () => {
test("Vehicle image is initailized with api data", async (done) => {
//Arrange
const VehicleBannerWidget = {
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
}
const { wrapper, apiPromise } = setupMocks( { VehicleBannerWidget: VehicleBannerWidget});
//Act
vehicleModel.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-model" } }, undefined, (c) => c(wrapper.vm));
//Assert
apiPromise.finally(() => {
expect(vehicleBanner.methods.initializeComponent).toHaveBeenCalledWith(VehicleBannerWidget);
@ -94,10 +77,8 @@ describe("vehicle-model.vue", () => {
});
});
});
describe("vehicle-model.vue", () => {
test("BackButtonAction triggers a router.navigate change", async (done) => {
//Arrange
const { wrapper, apiPromise } = setupMocks( {
pageHeaderWidgetHeaderText: "Select a model to get started",
@ -107,12 +88,10 @@ describe("vehicle-model.vue", () => {
},
},
});
//Act
vehicleModel.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-model" } }, undefined, (c) => c(wrapper.vm));
wrapper.vm.backButtonAction();
await nextTick();
//Assert
apiPromise.finally(() => {
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
@ -120,37 +99,32 @@ describe("vehicle-model.vue", () => {
});
});
});
function setupMocks({
radioQuestionCmsContent = {},
buttonQuestionContent = {},
modelQuestionInitialData = {},
pageHeaderWidgetHeaderText = {},
mountOptionsMockData = {},
}) {
//Mock api responses
const apiResponses = {
cmsContent: {
FunnelSubHeaderWidget: [pageHeaderWidgetHeaderText],
RadioQuestionWidget: [radioQuestionCmsContent],
VehicleBannerWidget: [
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
VehicleModelQuestion: buttonQuestionContent,
VehicleBannerWidget:
{
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
},
],
FunnelHeaderWidget: [
FunnelHeaderWidget:
{
LogoImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
},
],
},
modelQuestionInitialData: modelQuestionInitialData,
};
const apiPromise = Promise.resolve(apiResponses);
settleAllPromises.mockImplementation(() => apiPromise);
//Mock model question methods
modelQuestion.methods = {
loadInitialData: jest.fn(),
@ -175,6 +149,5 @@ function setupMocks({
vehicleBannerWrapper.vm.initializeComponent = vehicleBanner.methods.initializeComponent;
const funnelSubHeaderWrapper = wrapper.findComponent({ name: "funnelSubHeader" });
funnelSubHeaderWrapper.vm.initializeComponent = funnelSubHeader.methods.initializeComponent;
return { wrapper, apiPromise };
}

View file

@ -8,7 +8,7 @@
ref="funnelSubHeader"
:hasBackButton="true"
backButtonAccessibleText="Change Vehicle Make"
:backButtonAction="backButtonAction"
@click-event="backButtonAction"
class="Header"
/>
<modelQuestion v-model="selectedModel" ref="modelQuestion" />
@ -55,16 +55,16 @@ export default {
settleAllPromises(promiseResultMap).then((resultMap) => {
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.$refs.funnelSubHeader.initializeComponent(resultMap.cmsContent.FunnelSubHeaderWidget[0]);
vm.$refs.funnelHeader.initializeComponent(resultMap.cmsContent.FunnelHeaderWidget[0]);
vm.$refs.vehicleBanner.initializeComponent(resultMap.cmsContent.VehicleBannerWidget[0]);
vm.$refs.modelQuestion.initializeComponent(resultMap.cmsContent.RadioQuestionWidget[0], resultMap.modelQuestionInitialData);
vm.$refs.funnelSubHeader.initializeComponent(resultMap.cmsContent.FunnelSubHeaderWidget);
vm.$refs.funnelHeader.initializeComponent(resultMap.cmsContent.FunnelHeaderWidget);
vm.$refs.vehicleBanner.initializeComponent(resultMap.cmsContent.VehicleBannerWidget);
vm.$refs.modelQuestion.initializeComponent(resultMap.cmsContent.VehicleModelQuestion, resultMap.modelQuestionInitialData);
});
});
},
methods: {
backButtonAction: function () {
backButtonAction() {
// route to move backwards
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
}

View file

@ -17,10 +17,10 @@ describe("vehicle-style.vue", () => {
test("Style question component is initized with api data", async (done) => {
//Arrange
const radioQuestionCmsContent = { QuestionText: "What style is your vehicle?" };
const vehicleStyleQuestionCmsContent = { QuestionText: "What style is your vehicle?" };
const styleQuestionInitialData = ["2 Door", "4 Door"];
const { wrapper, apiPromise } = setupMocks( {
radioQuestionCmsContent: radioQuestionCmsContent,
vehicleStyleQuestionCmsContent: vehicleStyleQuestionCmsContent,
styleQuestionInitialData: styleQuestionInitialData,
} );
@ -29,7 +29,7 @@ describe("vehicle-style.vue", () => {
//Assert
apiPromise.finally(() => {
expect(styleQuestion.methods.initializeComponent).toHaveBeenCalledWith(radioQuestionCmsContent, styleQuestionInitialData);
expect(styleQuestion.methods.initializeComponent).toHaveBeenCalledWith(vehicleStyleQuestionCmsContent, styleQuestionInitialData);
done();
});
});
@ -156,7 +156,7 @@ describe("vehicle-style.vue", () => {
});
function setupMocks({
radioQuestionCmsContent = {},
vehicleStyleQuestionCmsContent = {},
styleQuestionInitialData = {},
pageHeaderWidgetHeaderText = {},
mountOptionsMockData = {},
@ -165,20 +165,18 @@ function setupMocks({
//Mock api responses
const apiResponses = {
cmsContent: {
FunnelSubHeaderWidget: [pageHeaderWidgetHeaderText],
RadioQuestionWidget: [radioQuestionCmsContent],
VehicleBannerWidget: [
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
VehicleStyleQuestion: vehicleStyleQuestionCmsContent,
VehicleBannerWidget:
{
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
},
],
FunnelHeaderWidget: [
FunnelHeaderWidget:
{
LogoImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
},
],
},
styleQuestionInitialData: styleQuestionInitialData,
};

View file

@ -8,7 +8,7 @@
ref="funnelSubHeader"
:hasBackButton="true"
backButtonAccessibleText="Change Vehicle Model"
:backButtonAction="backButtonAction"
@click-event="backButtonAction"
class="Header"
/>
<styleQuestion v-model="selectedStyle" ref="styleQuestion" />
@ -55,16 +55,16 @@ export default {
settleAllPromises(promiseResultMap).then((resultMap) => {
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.$refs.funnelSubHeader.initializeComponent(resultMap.cmsContent.FunnelSubHeaderWidget[0]);
vm.$refs.funnelHeader.initializeComponent(resultMap.cmsContent.FunnelHeaderWidget[0]);
vm.$refs.vehicleBanner.initializeComponent(resultMap.cmsContent.VehicleBannerWidget[0]);
vm.$refs.styleQuestion.initializeComponent(resultMap.cmsContent.RadioQuestionWidget[0], resultMap.styleQuestionInitialData);
vm.$refs.funnelSubHeader.initializeComponent(resultMap.cmsContent.FunnelSubHeaderWidget);
vm.$refs.funnelHeader.initializeComponent(resultMap.cmsContent.FunnelHeaderWidget);
vm.$refs.vehicleBanner.initializeComponent(resultMap.cmsContent.VehicleBannerWidget);
vm.$refs.styleQuestion.initializeComponent(resultMap.cmsContent.VehicleStyleQuestion, resultMap.styleQuestionInitialData);
});
});
},
methods: {
backButtonAction: function () {
backButtonAction() {
// route to move backwards
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
},

View file

@ -17,10 +17,10 @@ describe("vehicle-year.vue", () => {
test("Year question component is initized with api data", async (done) => {
//Arrange
const radioQuestionCmsContent = { QuestionText: "What year is your vehicle?" };
const vehicleYearQuestionCmsContent = { QuestionText: "What year is your vehicle?" };
const yearQuestionInitialData = ["2023", "2022", "2021"];
const { wrapper, apiPromise } = setupMocks( {
radioQuestionCmsContent: radioQuestionCmsContent,
vehicleYearQuestionCmsContent: vehicleYearQuestionCmsContent,
yearQuestionInitialData: yearQuestionInitialData,
} );
@ -29,7 +29,7 @@ describe("vehicle-year.vue", () => {
//Assert
apiPromise.finally(() => {
expect(yearQuestion.methods.initializeComponent).toHaveBeenCalledWith(radioQuestionCmsContent, yearQuestionInitialData);
expect(yearQuestion.methods.initializeComponent).toHaveBeenCalledWith(vehicleYearQuestionCmsContent, yearQuestionInitialData);
done();
});
});
@ -78,7 +78,7 @@ describe("vehicle-year.vue", () => {
});
function setupMocks({
radioQuestionCmsContent = {},
vehicleYearQuestionCmsContent = {},
yearQuestionInitialData = {},
pageHeaderWidgetHeaderText = {},
mountOptionsMockData = {},
@ -87,20 +87,18 @@ function setupMocks({
//Mock api responses
const apiResponses = {
cmsContent: {
FunnelSubHeaderWidget: [{ HeaderText: pageHeaderWidgetHeaderText }],
RadioQuestionWidget: [radioQuestionCmsContent],
VehicleBannerWidget: [
FunnelSubHeaderWidget: { HeaderText: pageHeaderWidgetHeaderText },
VehicleYearQuestion: vehicleYearQuestionCmsContent,
VehicleBannerWidget:
{
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
},
],
FunnelHeaderWidget: [
FunnelHeaderWidget:
{
LogoImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
},
],
},
yearQuestionInitialData: yearQuestionInitialData,
};

View file

@ -53,10 +53,10 @@ export default {
settleAllPromises(promiseResultMap).then((resultMap) => {
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.$refs.funnelSubHeader.initializeComponent(resultMap.cmsContent.FunnelSubHeaderWidget[0]);
vm.$refs.funnelHeader.initializeComponent(resultMap.cmsContent.FunnelHeaderWidget[0]);
vm.$refs.vehicleBanner.initializeComponent(resultMap.cmsContent.VehicleBannerWidget[0]);
vm.$refs.yearQuestion.initializeComponent(resultMap.cmsContent.RadioQuestionWidget[0], resultMap.yearQuestionInitialData);
vm.$refs.funnelSubHeader.initializeComponent(resultMap.cmsContent.FunnelSubHeaderWidget);
vm.$refs.funnelHeader.initializeComponent(resultMap.cmsContent.FunnelHeaderWidget);
vm.$refs.vehicleBanner.initializeComponent(resultMap.cmsContent.VehicleBannerWidget);
vm.$refs.yearQuestion.initializeComponent(resultMap.cmsContent.VehicleYearQuestion, resultMap.yearQuestionInitialData);
});
});
},

View file

@ -1,46 +1,219 @@
import { shallowMount } from "@vue/test-utils";
import listButtonHorizontal from "./list-button-horizontal";
import { nextTick } from "vue";
describe("list-button.vue", () => {
it("Should render the 'buttonID' prop value as the label and id value as well as the button value, groupName as the name value, and fire a click even that sets the display data attribute to true.", async () => {
it("Should return input type checkbox if isMultiSelect is true", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
buttonID: "2023",
groupName: "TestGroup",
loaderColor: "blue",
loaderPosition: "right",
sizeInRem: "1.5",
errorMessage: "null",
isMultiSelect: true,
},
});
// Assert
const input = wrapper.find("input");
const label = wrapper.find("label");
const paragraph = wrapper.find("span");
await label.trigger("click");
expect(input.attributes()).toEqual({
id: "2023",
type: "radio",
value: "2023",
name: "TestGroup",
"aria-required": "true",
});
expect(label.attributes()).toEqual({
tabindex: "-1",
for: "2023",
class: "d-flex flex-column justify-content-center py-3 px-4 last-item",
"aria-checked": "false",
});
expect(label.text()).toEqual("2023");
expect(paragraph.text()).toEqual("2023");
expect(wrapper.vm.isLoaderDisplayed).toBe(true);
expect(input.attributes().type).toEqual("checkbox");
});
it("Should return input type radio if isMultiSelect is false or not specified", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
isMultiSelect: false,
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes().type).toEqual("radio");
});
it("Should return primary label text (buttonID)", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
buttonID: "List Card Checkbox"
},
});
// Assert
const label = wrapper.find("label");
expect(label.attributes().for).toEqual("List Card Checkbox");
});
it("Should return screen reader text", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
screenReaderOnlyText: "Screen Reader Only Text"
},
});
// Assert
const paragraph = wrapper.find("span.sr-only");
expect(paragraph.text()).toEqual("Screen Reader Only Text");
});
it("Should return text alignment class", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
textPosition: "text-center"
},
});
// Assert
const paragraph = wrapper.find("span.m-0");
expect(paragraph.attributes('class')).toContain("text-center");
});
it("Should return aria-required state", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
isRequired: true
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes()["aria-required"]).toEqual("true");
});
it("Should return loader enabled true", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
loaderEnabled: true
},
});
// Assert
const label = wrapper.find("label");
wrapper.vm.handleClick();
await nextTick();
const loader = wrapper.find("loader-stub");
expect(loader.exists()).toBe(true);
});
it("Should return loader color", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
loaderColor: "blue",
loaderEnabled: true
},
});
// Assert
const label = wrapper.find("label");
wrapper.vm.handleClick();
await nextTick();
const loader = wrapper.find("loader-stub");
expect(loader.attributes('class')).toContain("blue");
});
it("Should return loader position", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
loaderPosition: "right",
loaderEnabled: true
},
});
// Assert
const label = wrapper.find("label");
wrapper.vm.handleClick();
await nextTick();
const loader = wrapper.find("loader-stub");
expect(loader.attributes('class')).toContain("right");
});
it("Should return loader size in rem", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
sizeInRem: 1,
loaderEnabled: true
},
});
// Assert
const label = wrapper.find("label");
wrapper.vm.handleClick();
await nextTick();
const loader = wrapper.find("loader-stub");
expect(loader.attributes('style')).toContain("1rem");
});
it("Should return first item if first item in group", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
positionInGroup: 1
},
});
// Assert
const label = wrapper.find("label");
expect(label.attributes('class')).toContain("first-item");
});
it("Should return last item if last item in group", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
positionInGroup: 3,
totalInGroup: 3
},
});
// Assert
const label = wrapper.find("label");
expect(label.attributes('class')).toContain("last-item");
});
});

View file

@ -3,42 +3,39 @@
<!-- IMPORTANT: Refrain from using more than 4 horizontal buttons on desktop, 3 on mobile. -->
<div v-if="isMultiSelect" class="list-group list-button-horizontal d-flex flex-column w-100 mb-2">
<input type="checkbox" :id="buttonID" :name="groupName" :value="buttonID" :aria-required="isRequired">
<label tabindex="-1" aria-checked="false" :for="buttonID" class="d-flex flex-column justify-content-center py-3 px-4" :class="isFirstOrLastButton">
<label tabindex="-1" aria-checked="false" :for="buttonID" :aria-labelledby="buttonID" class="d-flex flex-column justify-content-center py-3 px-4" :class="isFirstOrLastButton">
<span class="m-0" :class="[this.textPosition]">{{buttonID}}</span>
<span v-if="buttonLabelSubCopy" class="m-0 small" :class="[this.textPosition]">{{buttonLabelSubCopy}}</span>
<span v-if="screenReaderOnlyText" class="sr-only">{{screenReaderOnlyText}}</span>
</label>
<p class="small">{{errorMessage}}</p>
</div>
<div v-else class="col list-group list-button-horizontal d-flex flex-column mb-2">
<input type="radio" :id="buttonID" :name="groupName" :value="buttonID" aria-required="true" @keyup.space="displayLoader()"/>
<label tabindex="-1" aria-checked="false" :for="buttonID" class="d-flex flex-column justify-content-center py-3 px-4" :class="isFirstOrLastButton" @click="displayLoader()">
<span class="m-0" :class="[this.textPosition]">{{buttonID}}</span>
<span v-if="buttonLabelSubCopy" class="m-0 small" :class="[this.textPosition]">{{buttonLabelSubCopy}}</span>
<input type="radio" :id="buttonID" :name="groupName" :value="buttonID" aria-required="true" @keyup.space="handleClick()" @click="handleClick()" />
<label tabindex="-1" aria-checked="false" :for="buttonID" :aria-labelledby="buttonID" class="d-flex flex-column justify-content-center py-3 px-4" :class="isFirstOrLastButton">
<span class="m-0" :class="textPosition">{{buttonID}}</span>
<span v-if="screenReaderOnlyText" class="sr-only">{{screenReaderOnlyText}}</span>
<loader v-if="isLoaderDisplayed" :style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}" :class="[this.loaderColor, this.loaderPosition]" />
<loader v-if="isLoaderDisplayed" :style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}" :class="[loaderColor, loaderPosition]" />
</label>
<p class="small">{{ errorMessage }}</p>
</div>
</template>
<script>
import loader from "@/ux-components/loader/loader";
export default {
name: "listButtonHorizontal",
props: {
isMultiSelect: Boolean,
isMultiSelect: Boolean, /* Defines use as checkbox */
groupName: String, /* Required, unique for each button GROUP */
buttonID: String, /* Required, unique for each button. Used for button id, label and <label for> */
buttonLabelSubCopy: String, /* Optional, used for multi-line buttons */
screenReaderOnlyText: String, /* Optional, copy to be read by screenreader */
textPosition: String, /* Optional, use Bootstrap classes: text-start, text-center, text-end. Default (empty) is text-start */
errorMessage: String, /* Optional, if there is an error message to be displayed */
loaderEnabled: Boolean, /* Optional, need to include this for loader to be used at all */
loaderColor: String, /* Specify color of loader/spinner. Options are blue, red, green, white, black. Default is blue */
loaderPosition: String, /* Specify horizontal position of loader/spinner. Options are center, right, left */
sizeInRem: [Number,String], /* Specify size of loader/spinner in rem. Example: 1.5 (equals 24px (16x1.5)) */
sizeInRem: Number, /* Specify size of loader/spinner in rem. Example: 1.5 (equals 24px (16x1.5)) */
totalInGroup: Number, /* Required, total number of buttons in group. Used to tell first and last in group to apply border radius. */
positionInGroup: Number /* Rquired, position of button in group. Example, 1,2,3 */
positionInGroup: Number, /* Required, position of button in group. Example, 1,2,3 */
isRequired: Boolean,
},
data() {
return {
@ -49,6 +46,9 @@ export default {
displayLoader() {
this.isLoaderDisplayed = true;
},
handleClick() {
this.loaderEnabled && this.displayLoader();
}
},
components: {
loader,
@ -66,6 +66,7 @@ export default {
},
};
</script>
<style lang="scss">
.list-button-horizontal {
input[type="radio"],
@ -101,13 +102,6 @@ export default {
+ p {
display: none;
}
&.error {
border: 1px solid $danger;
+ p {
display: flex;
color: $danger;
}
}
&.first-item {
border-bottom-left-radius: 0.5rem;
border-top-left-radius: 0.5rem;

View file

@ -11,7 +11,6 @@ describe("list-button.vue", () => {
loaderColor: "blue",
loaderPosition: "right",
sizeInRem: "1.5",
errorMessage: "null",
},
});

View file

@ -24,14 +24,13 @@ import loader from "@/ux-components/loader/loader";
export default {
name: "listButton",
props: {
isMultiSelect: Boolean,
isMultiSelect: Boolean, /* Defines use as checkbox */
groupName: String, /* Required, unique for each button GROUP */
buttonID: [Number,String], /* Required, unique for each button. Used for button id, label and <label for> */
isRequired: Boolean, /* Optional, default is false */
screenReaderOnlyText: String, /* Optional, copy to be read by screenreader */
buttonLabelSubCopy: String, /* Optional, used for multi-line buttons */
textPosition: String, /* Optional, use Bootstrap classes: text-start, text-center, text-end. Default (empty) is text-start */
errorMessage: String, /* Optional, if there is an error message to be displayed */
loaderColor: String, /* Specify color of loader/spinner. Options are blue, red, green, white, black. Default is blue */
loaderPosition: String, /* Specify horizontal position of loader/spinner. Options are center, right, left */
sizeInRem: [Number,String], /* Specify size of loader/spinner in rem. Example: 1.5 (equals 24px (16x1.5)) */

View file

@ -1 +1,188 @@
test.todo("some test to be written in the future");
import { shallowMount } from "@vue/test-utils";
import listCard from "./list-card";
describe("list-card.vue", () => {
it("Should return input type checkbox if isMultiSelect is true", async () => {
// Act
const wrapper = shallowMount(listCard, {
propsData: {
isMultiSelect: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "checkbox-demo-1",
groupName: "Checkbox 1",
buttonImage: "windshield-damage.svg",
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes().type).toEqual("checkbox");
});
it("Should return input type radio if isRadio is true", async () => {
// Act
const wrapper = shallowMount(listCard, {
propsData: {
isRadio: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes().type).toEqual("radio");
});
it("Should return input type checkbox if isMultiSelectHorizontal is true", async () => {
// Act
const wrapper = shallowMount(listCard, {
propsData: {
isMultiSelectHorizontal: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg"
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes().type).toEqual("checkbox");
});
it("Should return input type radio if isRadioHorizontal is true", async () => {
// Act
const wrapper = shallowMount(listCard, {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg"
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes().type).toEqual("radio");
});
it("Should return primary label text", async () => {
// Act
const wrapper = shallowMount(listCard, {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg"
},
});
// Assert
const paragraph = wrapper.find("p");
expect(paragraph.text()).toEqual("Windshield");
});
it("Should return secondary (sub) label text", async () => {
// Act
const wrapper = shallowMount(listCard, {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
buttonLabelSubCopy: "Test"
},
});
// Assert
const paragraph = wrapper.find("p:nth-of-type(2)");
expect(paragraph.text()).toEqual("Test");
});
it("Should return value used for various text settings including the label 'for' and input id", async () => {
// Act
const wrapper = shallowMount(listCard, {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
buttonLabelSubCopy: "Test"
},
});
// Assert
const label = wrapper.find("label");
expect(label.attributes().for).toEqual("List Card Checkbox");
});
it("Should return input group name used for radio or checkbox", async () => {
// Act
const wrapper = shallowMount(listCard, {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
buttonLabelSubCopy: "Test"
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes().name).toEqual("radio 1");
});
it("Should return aria-required state", async () => {
// Act
const wrapper = shallowMount(listCard, {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
isRequired: true
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes()["aria-required"]).toEqual("true");
});
});

View file

@ -1,38 +1,38 @@
<template>
<!-- Heavily documented below -->
<div v-if="isMultiSelect" class="list-card w-100 rounded-3 d-flex align-items-center" :class="{'error': hasError}">
<div v-if="isMultiSelect" class="list-card w-100 rounded-3 d-flex align-items-center">
<input type="checkbox" :id="buttonID" :name="groupName" :value="buttonLabel" :aria-required="isRequired" />
<label :for="buttonID" class="d-flex flex-column w-100 align-items-center pt-4 pb-2" tabindex="1">
<img class="order-1" v-bind:src="require(`@/assets/img/icons/${buttonImage}`)" v-bind:alt="altText" />
<p class="small m-0 order-3">{{buttonLabel}}</p>
<p class="fs-7 m-0 order-4">{{buttonLabelSubCopy}}</p>
<p v-if="buttonLabelSubCopy" class="fs-7 m-0 order-4">{{buttonLabelSubCopy}}</p>
</label>
</div>
<div v-else-if="isRadio" class="list-card w-100 rounded-3 d-flex align-items-center" :class="{'error': hasError}">
<div v-else-if="isRadio" class="list-card w-100 rounded-3 d-flex align-items-center">
<input type="radio" :id="buttonID" :name="groupName" :value="buttonID" :aria-required="isRequired" />
<label :for="buttonID" class="d-flex flex-column w-100 align-items-center pt-4 pb-2" tabindex="1">
<img class="order-1" v-bind:src="require(`@/assets/img/icons/${buttonImage}`)" v-bind:alt="altText" />
<p class="small mt-2 mb-0 order-2">{{buttonLabel}}</p>
<p class="fs-7 m-0 order-3">{{buttonLabelSubCopy}}</p>
<p v-if="buttonLabelSubCopy" class="fs-7 m-0 order-3">{{buttonLabelSubCopy}}</p>
</label>
</div>
<div v-else-if="isMultiSelectHorizontal" class="list-card horizontal w-100 rounded-3 d-flex align-items-center" :class="{'error': hasError}">
<div v-else-if="isMultiSelectHorizontal" class="list-card horizontal w-100 rounded-3 d-flex align-items-center">
<input type="checkbox" :id="buttonID" :name="groupName" :value="buttonLabel" :aria-required="isRequired" />
<label :for="buttonID" class="d-flex flex-row w-100 align-items-center py-3 ps-3 pe-8" tabindex="1" :class="{'checkboxTop': buttonLabelSubCopy}">
<img class="ms-auto order-3" v-bind:src="require(`@/assets/img/icons/${buttonImage}`)" v-bind:alt="altText" />
<div class="order-2">
<p class="m-0 small">{{buttonLabel}}</p>
<p class="m-0 fs-7">{{buttonLabelSubCopy}}</p>
<p v-if="buttonLabelSubCopy" class="m-0 fs-7">{{buttonLabelSubCopy}}</p>
</div>
</label>
</div>
<div v-else-if="isRadioHorizontal" class="list-card horizontal w-100 rounded-3 d-flex align-items-center" :class="{'error': hasError}">
<div v-else-if="isRadioHorizontal" class="list-card horizontal w-100 rounded-3 d-flex align-items-center">
<input type="radio" :id="buttonID" :name="groupName" :value="buttonLabel" :aria-required="isRequired" />
<label :for="buttonID" class="d-flex flex-row w-100 align-items-center py-3 ps-3 pe-8" tabindex="1" :class="{'checkboxTop': buttonLabelSubCopy}">
<img class="ms-auto order-3" v-bind:src="require(`@/assets/img/icons/${buttonImage}`)" v-bind:alt="altText" />
<div class="order-2">
<p class="m-0 small">{{buttonLabel}}</p>
<p class="m-0 fs-7">{{buttonLabelSubCopy}}</p>
<p v-if="buttonLabelSubCopy" class="m-0 fs-7">{{buttonLabelSubCopy}}</p>
</div>
</label>
</div>
@ -43,27 +43,24 @@ export default {
name: "listCard",
props: {
//Must choose one of the following four options
isMultiSelect: Boolean,//Defines use as checkbox
isRadio: Boolean,//Defines use as radio button
isMultiSelectHorizontal: Boolean,//Defines use as horizontal checkbox
isRadioHorizontal: Boolean,//Defines use as horizontal radio button
isMultiSelect: Boolean, //Defines use as checkbox
isRadio: Boolean, //Defines use as radio button
isMultiSelectHorizontal: Boolean, //Defines use as horizontal checkbox
isRadioHorizontal: Boolean, //Defines use as horizontal radio button
//end must choose
buttonImage: String,//Optional: File name of image
buttonImage: String,//Required: File name of image
buttonLabel: String,//Required: Label text
isRequired: Boolean, //Required: is aria-required required or not?
altText: String,//Leave empty. Screen readers read the buttonLabel text. If alt has content, it will repeat unnecessarily.
buttonID: String,//Required: Unique
groupName: String,//Rquired: Unique
buttonLabelSubCopy: String,//Optional: sub text
hasError: Boolean//Used to show error state
groupName: String,//Required: Unique
buttonLabelSubCopy: String,//Optional: sub tex
}
};
</script>
<style lang="scss">
.list-card {
&.error {
border: 1px solid $red !important;
}
border: 1px solid $gray-500;
&.invalid { //Red border if invalid
border: 1px solid $red;