Ran linter

This commit is contained in:
Frank 2021-12-08 21:00:37 -05:00
parent 43c6aed0a2
commit daab1b0f1a
37 changed files with 927 additions and 753 deletions

View file

@ -11,7 +11,7 @@ module.exports = {
"!src/constants/*.js",
"!src/router/**/*.js",
"!src/helpers/unit-test-helper.js",
"!src/layouts/component-test/component-test.vue"
"!src/layouts/component-test/component-test.vue",
], //! means exclude from coverage.
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: {

View file

@ -4,11 +4,11 @@ import radioQuestion from "@/common-components/radio-question/radio-question";
describe("radioQuestion.vue", () => {
it("Should render the 'questionText' prop value as a span value for the radio question and the 'answer' values should render as text values for radio components.", async () => {
// Act
const wrapper = shallowMount(radioQuestion)
const wrapper = shallowMount(radioQuestion);
await wrapper.setProps({
questionText: "Question Text",
answers: ["2023", "2022", "2021"],
modelValue: "2020"
modelValue: "2020",
});
wrapper.vm.chooseAnswer("2021");

View file

@ -6,19 +6,32 @@
}}</span>
</div>
<div class="w-100 d-flex justify-content-center">
<div class="car_list overflow-scroll position-absolute container-fluid w-100 pt-1" role="radiogroup" aria-labelledby="select-year-radio-group">
<div
class="
car_list
overflow-scroll
position-absolute
container-fluid
w-100
pt-1
"
role="radiogroup"
aria-labelledby="select-year-radio-group"
>
<h3 class="visually-hidden" id="select-year-radio-group">*</h3>
<radio v-for="answer in answers" :key="answer"
:radioID="answer"
@click="chooseAnswer(answer)"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1.5"
data-test="radio"
groupName="radio-list"
textPosition="text-start"
:value="modelValue"
/>
<radio
v-for="answer in answers"
:key="answer"
:radioID="answer"
@click="chooseAnswer(answer)"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1.5"
data-test="radio"
groupName="radio-list"
textPosition="text-start"
:value="modelValue"
/>
</div>
</div>
</div>
@ -31,12 +44,12 @@ export default {
props: {
questionText: String,
answers: Array,
modelValue: String
modelValue: String,
},
methods: {
chooseAnswer(answer) {
this.$emit('update:modelValue', answer);
}
this.$emit("update:modelValue", answer);
},
},
components: {
radio,

View file

@ -1,21 +1,19 @@
import { shallowMount } from '@vue/test-utils';
import siteHeader from './site-header';
import { shallowMount } from "@vue/test-utils";
import siteHeader from "./site-header";
describe('siteHeader', () => {
describe("siteHeader", () => {
test("renders the logo image", () => {
// Arrange
test('renders the logo image', () => {
// Arrange
// Act
const wrapper = shallowMount(siteHeader, {
propsData: {
imageSrc: "image_url",
},
});
// Assert
expect(wrapper.find('img')).toBeTruthy();
wrapper.unmount();
// Act
const wrapper = shallowMount(siteHeader, {
propsData: {
imageSrc: "image_url",
},
});
// Assert
expect(wrapper.find("img")).toBeTruthy();
wrapper.unmount();
});
});

View file

@ -1,10 +1,9 @@
<template>
<div class="funnel-header d-flex justify-content-center align-items-center" v-if="imageSrc">
<img
class="logo-image img-fluid"
:src="imageSrc"
alt="Safelite logo"
/>
<div
class="funnel-header d-flex justify-content-center align-items-center"
v-if="imageSrc"
>
<img class="logo-image img-fluid" :src="imageSrc" alt="Safelite logo" />
</div>
</template>
@ -14,17 +13,17 @@ export default {
props: {
imageSrc: {
type: String,
required: true
}
required: true,
},
},
}
};
</script>
<style lang="scss" scoped>
.funnel-header {
height: 56px;
}
.logo-image {
max-width: 78px;
}
</style>
.funnel-header {
height: 56px;
}
.logo-image {
max-width: 78px;
}
</style>

View file

@ -1,21 +1,19 @@
import { shallowMount } from '@vue/test-utils';
import vehicleBanner from './vehicle-banner';
import { shallowMount } from "@vue/test-utils";
import vehicleBanner from "./vehicle-banner";
describe('vehicleBanner', () => {
describe("vehicleBanner", () => {
test("renders the blurrycar image", () => {
// Arrange
test('renders the blurrycar image', () => {
// Arrange
// Act
const wrapper = shallowMount(vehicleBanner, {
propsData: {
vehicleImageSrc: "image_url",
},
});
// Assert
expect(wrapper.find('img').attributes('class')).toContain('blurrycar');
wrapper.unmount();
// Act
const wrapper = shallowMount(vehicleBanner, {
propsData: {
vehicleImageSrc: "image_url",
},
});
// Assert
expect(wrapper.find("img").attributes("class")).toContain("blurrycar");
wrapper.unmount();
});
});

View file

@ -14,14 +14,14 @@ export default {
props: {
vehicleImageSrc: {
type: String,
required: true
}
required: true,
},
},
}
};
</script>
<style lang="scss" scoped>
.vehicle-image {
max-width: 290px;
}
</style>
.vehicle-image {
max-width: 290px;
}
</style>

View file

@ -1,5 +1,5 @@
const storeMutations = {
UPDATE_YEAR: "updateYear",
}
UPDATE_YEAR: "updateYear",
};
export { storeMutations };
export { storeMutations };

View file

@ -3,22 +3,23 @@ import { widgetNames } from "@/constants/widget-names.js";
import store from "@/store";
export function fetchCmsContentForPage(fmgPage) {
return store.dispatch(storeActions.GET_PAGE_DATA, { pageName: fmgPage }).then((response) => {
const pageDataFromCms = {};
return store
.dispatch(storeActions.GET_PAGE_DATA, { pageName: fmgPage })
.then((response) => {
const pageDataFromCms = {};
response.data.Result.forEach((widget) => {
if (Object.values(widgetNames).includes(widget.Type)) {
// If we already have this widget, push it on the collection
if (widget.Type in pageDataFromCms) {
pageDataFromCms[widget.Type].push(widget.Model);
return;
}
response.data.Result.forEach((widget) => {
if (Object.values(widgetNames).includes(widget.Type)) {
// If we already have this widget, push it on the collection
if (widget.Type in pageDataFromCms) {
pageDataFromCms[widget.Type].push(widget.Model);
return;
}
pageDataFromCms[widget.Type] = [widget.Model];
}
});
pageDataFromCms[widget.Type] = [widget.Model];
}
});
return pageDataFromCms;
return pageDataFromCms;
});
}

View file

@ -2,52 +2,52 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { dispatch } from "@/store";
jest.mock("@/store", () => ({
dispatch: jest.fn()
dispatch: jest.fn(),
}));
it("cms-content-helper: Should return data from CMS", () => {
// Arrange
const cmsMockData = {
Result: [
{
Type: "VehicleBannerWidget",
Model: {
ImageId: "28452dcb-7762-4cc9-ab09-7643d0b89203",
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
GenericVehicleImageFilePath:
"images/default-source/default-album/blurred-image.jpg",
},
},
{
Type: "PageHeaderWidget",
Model: {
HeaderText: "Select a year to get started",
},
},
{
Type: "PageHeaderWidget",
Model: {
HeaderText: "Select a model",
},
},
{
Type: "RadioQuestionWidget",
Model: {
QuestionText: "What year is your vehicle?",
},
},
],
};
// Arrange
const cmsMockData = {
Result: [
{
Type: "VehicleBannerWidget",
Model: {
ImageId: "28452dcb-7762-4cc9-ab09-7643d0b89203",
GenericVehicleImage: "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
GenericVehicleImageFilePath: "images/default-source/default-album/blurred-image.jpg"
}
},
{
Type: "PageHeaderWidget",
Model: {
"HeaderText": "Select a year to get started"
}
},
{
Type: "PageHeaderWidget",
Model: {
"HeaderText": "Select a model"
}
},
{
Type: "RadioQuestionWidget",
Model: {
"QuestionText": "What year is your vehicle?"
}
}
]
}
dispatch.mockImplementation(() => Promise.resolve({ data: cmsMockData }));
// Act
fetchCmsContentForPage('testPage').then((response) => {
// Assert
expect(response.isCmsContentReady).toBe(true);
expect(response.PageHeaderWidget[0].HeaderText).toEqual('Select a year to get started');
});
dispatch.mockImplementation(() => Promise.resolve({ data: cmsMockData }));
// Act
fetchCmsContentForPage("testPage").then((response) => {
// Assert
expect(response.isCmsContentReady).toBe(true);
expect(response.PageHeaderWidget[0].HeaderText).toEqual(
"Select a year to get started"
);
});
});

View file

@ -1,29 +1,27 @@
export function settleAllPromises(promiseResultMap) {
// Pull our keys out of the promise 'table'
const promiseNames = Object.entries(promiseResultMap);
// Pull our keys out of the promise 'table'
const promiseNames = Object.entries(promiseResultMap);
return Promise.allSettled(
promiseNames.map((e) => e[1]).map((n) => n.promise)
).then((results) => {
const resultMap = {};
return Promise.allSettled(promiseNames.map(e => e[1]).map(n => n.promise))
.then(results => {
// Build a map of the results
for (let i = 0; i < results.length; ++i) {
const promiseName = promiseNames[i][1].resultKey;
const resultMap = {};
// Some Promises like the cms content call don't have a 'data' field
// when returned, so other promises do. Map the results to the object
// so that the object is the return data.
// Build a map of the results
for (let i = 0; i < results.length; ++i) {
const promiseName = promiseNames[i][1].resultKey;
// Some Promises like the cms content call don't have a 'data' field
// when returned, so other promises do. Map the results to the object
// so that the object is the return data.
if (results[i].value.data === undefined) {
resultMap[promiseName] = results[i].value;
} else {
resultMap[promiseName] = results[i].value.data;
}
}
if (results[i].value.data === undefined) {
resultMap[promiseName] = results[i].value
} else {
resultMap[promiseName] = results[i].value.data;
}
}
return resultMap;
});
}
return resultMap;
});
}

View file

@ -1,28 +1,25 @@
import { settleAllPromises } from "@/helpers/layout-helper";
it("layout-helper: Should settle all promises and return mapped promise results", () => {
// Arrange
const mockPromiseOne = Promise.resolve({ data: "test-data" });
const mockPromiseTwo = Promise.resolve({ data: "test-data-two" });
// Arrange
const mockPromiseOne = Promise.resolve({ data: "test-data" });
const mockPromiseTwo = Promise.resolve({ data: "test-data-two" });
const promiseResultMap = [
{
resultKey: "MockResultOne",
promise: mockPromiseOne,
},
{
resultKey: "MockResultTwo",
promise: mockPromiseTwo,
},
];
const promiseResultMap = [
{
resultKey: "MockResultOne",
promise: mockPromiseOne,
},
{
resultKey: "MockResultTwo",
promise: mockPromiseTwo,
},
];
// Act
settleAllPromises(promiseResultMap).then(results => {
// Assert
expect(results.MockResultOne).toEqual('test-data');
expect(results.MockResultTwo).toEqual('test-data-two');
});
})
// Act
settleAllPromises(promiseResultMap).then((results) => {
// Assert
expect(results.MockResultOne).toEqual("test-data");
expect(results.MockResultTwo).toEqual("test-data-two");
});
});

View file

@ -2,245 +2,275 @@
<div class="container-fluid container-shadow p-2 rounded-3">
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Radio Card</h4>
<h4 class="m-0 p-2 bg-light rounded">Radio Card</h4>
</div>
</div>
<div class="row g-2">
<radioCard
radioLabel="Windshield"
radioImage="windshield-damage.svg"
altText="Windshield"
groupName="damageKey"
radioID="windshield"
radioLabel="Windshield"
radioImage="windshield-damage.svg"
altText="Windshield"
groupName="damageKey"
radioID="windshield"
/>
<radioCard
radioLabel="Side Window"
radioImage="side-window-damage.svg"
altText="Side Window"
groupName="damageKey"
radioID="sidewindow"
radioLabel="Side Window"
radioImage="side-window-damage.svg"
altText="Side Window"
groupName="damageKey"
radioID="sidewindow"
/>
<radioCard
radioLabel="Back Glass"
radioImage="back-glass-damage.svg"
altText="Back Glass"
groupName="damageKey"
radioID="backglass"
radioLabel="Back Glass"
radioImage="back-glass-damage.svg"
altText="Back Glass"
groupName="damageKey"
radioID="backglass"
/>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Buttons</h4>
<h4 class="m-0 p-2 bg-light rounded">Buttons</h4>
</div>
</div>
<div class="row">
<div class="col my-3 d-flex align-items-center">
<buttonPrimary buttonText="Primary" loaderColor="white" sizeInRem="1" />
</div>
</div>
<div class="row">
<div class="col my-3 d-flex align-items-center">
<buttonPrimary
buttonText="Primary"
loaderColor="white"
sizeInRem="1"
buttonText="Primary"
loaderColor="white"
sizeInRem="1"
@click="onClick"
/>
</div>
</div>
<div class="row">
<div class="col my-3 d-flex align-items-center">
<buttonSecondary
buttonText="Secondary"
loaderColor="white"
sizeInRem="1"
buttonText="Secondary"
loaderColor="white"
sizeInRem="1"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">List Button</h4>
<h4 class="m-0 p-2 bg-light rounded">List Button</h4>
</div>
</div>
<div class="row">
<div class="col my-3 d-flex align-items-center">
<listButton
buttonText="List Button"
errorText="Test error message"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
buttonText="List Button"
errorText="Test error message"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Radio - Single-Line</h4>
<h4 class="m-0 p-2 bg-light rounded">Radio - Single-Line</h4>
</div>
</div>
<div class="row">
<!-- The role="radiogroup" and aria-labelledby must be included in the parent component for the radio group -->
<div role="radiogroup" aria-labelledby="demo-1-radio-group" class="col my-3 d-flex align-items-center flex-column">
<div
role="radiogroup"
aria-labelledby="demo-1-radio-group"
class="col my-3 d-flex align-items-center flex-column"
>
<!-- The h3 and id must be included. The id must match the aria-labelledby of the parent div. -->
<h3 class="visually-hidden" id="demo-1-radio-group">Select Vehicle Year</h3>
<h3 class="visually-hidden" id="demo-1-radio-group">
Select Vehicle Year
</h3>
<radio
groupName="demo-1"
ariaLabelBy="vehicle-year"
radioID="2021"
textPosition="text-start"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
groupName="demo-1"
ariaLabelBy="vehicle-year"
radioID="2021"
textPosition="text-start"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
/>
<radio
groupName="demo-1"
ariaLabelBy="vehicle-year"
radioID="2020"
textPosition="text-start"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
groupName="demo-1"
ariaLabelBy="vehicle-year"
radioID="2020"
textPosition="text-start"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
/>
<radio
groupName="demo-1"
ariaLabelBy="vehicle-year"
radioID="2019"
textPosition="text-start"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
groupName="demo-1"
ariaLabelBy="vehicle-year"
radioID="2019"
textPosition="text-start"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Radio - Multi-Line</h4>
<h4 class="m-0 p-2 bg-light rounded">Radio - Multi-Line</h4>
</div>
</div>
<div class="row">
<!-- The role="radiogroup" and aria-labelledby must be included in the parent component for the radio group -->
<div role="radiogroup" aria-labelledby="demo-2-radio-group" class="col my-3 d-flex align-items-center flex-column">
<div
role="radiogroup"
aria-labelledby="demo-2-radio-group"
class="col my-3 d-flex align-items-center flex-column"
>
<!-- The h3 and id must be included. The id must match the aria-labelledby of the parent div. -->
<h3 class="visually-hidden" id="demo-2-radio-group">Select Vehicle Year</h3>
<h3 class="visually-hidden" id="demo-2-radio-group">
Select Vehicle Year
</h3>
<radio
groupName="demo-2"
ariaLabelBy="vehicle-make"
radioID="Chevrolet"
radioLabelSubCopy="Test sub-headline"
textPosition="text-start"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
groupName="demo-2"
ariaLabelBy="vehicle-make"
radioID="Chevrolet"
radioLabelSubCopy="Test sub-headline"
textPosition="text-start"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
/>
<radio
groupName="demo-2"
ariaLabelBy="vehicle-make"
radioID="Dodge"
radioLabelSubCopy="Test sub-headline"
textPosition="text-start"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
groupName="demo-2"
ariaLabelBy="vehicle-make"
radioID="Dodge"
radioLabelSubCopy="Test sub-headline"
textPosition="text-start"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
/>
<radio
groupName="demo-2"
ariaLabelBy="vehicle-make"
radioID="Ford"
radioLabelSubCopy="Test sub-headline"
textPosition="text-start"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
groupName="demo-2"
ariaLabelBy="vehicle-make"
radioID="Ford"
radioLabelSubCopy="Test sub-headline"
textPosition="text-start"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Radio - Multi-Line Centered</h4>
<h4 class="m-0 p-2 bg-light rounded">Radio - Multi-Line Centered</h4>
</div>
</div>
<div class="row">
<!-- The role="radiogroup" and aria-labelledby must be included in the parent component for the radio group -->
<div role="radiogroup" aria-labelledby="demo-3-radio-group" class="col my-3 d-flex align-items-center flex-column">
<div
role="radiogroup"
aria-labelledby="demo-3-radio-group"
class="col my-3 d-flex align-items-center flex-column"
>
<!-- The h3 and id must be included. The id must match the aria-labelledby of the parent div. -->
<h3 class="visually-hidden" id="demo-3-radio-group">Multi-Line Centered</h3>
<h3 class="visually-hidden" id="demo-3-radio-group">
Multi-Line Centered
</h3>
<radio
groupName="demo-3"
ariaLabelBy="vehicle-model"
radioID="Corvette"
radioLabelSubCopy="Test sub-headline"
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
groupName="demo-3"
ariaLabelBy="vehicle-model"
radioID="Corvette"
radioLabelSubCopy="Test sub-headline"
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
/>
<radio
groupName="demo-3"
ariaLabelBy="vehicle-model"
radioID="Testarosa"
radioLabelSubCopy="Test sub-headline"
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
groupName="demo-3"
ariaLabelBy="vehicle-model"
radioID="Testarosa"
radioLabelSubCopy="Test sub-headline"
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
/>
<radio
groupName="demo-3"
ariaLabelBy="vehicle-model"
radioID="S600"
radioLabelSubCopy="Test sub-headline"
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
groupName="demo-3"
ariaLabelBy="vehicle-model"
radioID="S600"
radioLabelSubCopy="Test sub-headline"
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Radio Horizontal</h4>
<h4 class="m-0 p-2 bg-light rounded">Radio Horizontal</h4>
</div>
</div>
<div class="row px-3">
<!-- The role="radiogroup" and aria-labelledby must be included in the parent component for the radio group -->
<div role="radiogroup" aria-labelledby="demo-4-radio-group" class="d-flex flex-row p-0">
<div
role="radiogroup"
aria-labelledby="demo-4-radio-group"
class="d-flex flex-row p-0"
>
<!-- The h3 and id must be included. The id must match the aria-labelledby of the parent div. -->
<h3 class="visually-hidden" id="demo-4-radio-group">Select Vehicle Year</h3>
<h3 class="visually-hidden" id="demo-4-radio-group">
Select Vehicle Year
</h3>
<radioHorizontal
groupName="demo-4"
ariaLabelBy="vehicle-model"
radioID="1"
radioLabelSubCopy=""
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
v-bind:totalInGroup="3"
v-bind:positionInGroup="1"
groupName="demo-4"
ariaLabelBy="vehicle-model"
radioID="1"
radioLabelSubCopy=""
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
v-bind:totalInGroup="3"
v-bind:positionInGroup="1"
/>
<radioHorizontal
groupName="demo-4"
ariaLabelBy="vehicle-model"
radioID="2"
radioLabelSubCopy=""
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
v-bind:totalInGroup="3"
v-bind:positionInGroup="2"
groupName="demo-4"
ariaLabelBy="vehicle-model"
radioID="2"
radioLabelSubCopy=""
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
v-bind:totalInGroup="3"
v-bind:positionInGroup="2"
/>
<radioHorizontal
groupName="demo-4"
ariaLabelBy="vehicle-model"
radioID="3"
radioLabelSubCopy=""
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
v-bind:totalInGroup="3"
v-bind:positionInGroup="3"
groupName="demo-4"
ariaLabelBy="vehicle-model"
radioID="3"
radioLabelSubCopy=""
textPosition="text-center"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1"
v-bind:totalInGroup="3"
v-bind:positionInGroup="3"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Text Link</h4>
<h4 class="m-0 p-2 bg-light rounded">Text Link</h4>
</div>
</div>
<div class="row">
@ -250,19 +280,23 @@
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Typogrophy</h4>
<h4 class="m-0 p-2 bg-light rounded">Typogrophy</h4>
</div>
</div>
<div class="row my-2">
<div class="col">
<p>This is default body copy font size/weight</p>
<p class="small">This is small body copy using <code>.small</code> class</p>
<p><small>This is also small using <code>&lt;small&gt;</code> tag</small></p>
<p class="small">
This is small body copy using <code>.small</code> class
</p>
<p>
<small>This is also small using <code>&lt;small&gt;</code> tag</small>
</p>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Headings</h4>
<h4 class="m-0 p-2 bg-light rounded">Headings</h4>
</div>
</div>
<div class="row my-2">
@ -297,119 +331,128 @@
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Alerts</h4>
<h4 class="m-0 p-2 bg-light rounded">Alerts</h4>
</div>
</div>
<div class="row my-2">
<div class="col">
<alert
alertClass="alert-success"
alertHeadline="Dismissible Alert"
alertCopy="This is an example of a DISMISSIBLE alert. It will fade away and content around it will shift when dismissed."
v-bind:isDismissible = "true"
alertClass="alert-success"
alertHeadline="Dismissible Alert"
alertCopy="This is an example of a DISMISSIBLE alert. It will fade away and content around it will shift when dismissed."
v-bind:isDismissible="true"
/>
</div>
</div>
<div class="row my-2">
<div class="col">
<alert
alertClass="alert-danger"
alertHeadline="NON-Dismissible Alert"
alertCopy="This is an example of a NON-DISMISSIBLE alert."
v-bind:isDismissible = "false"
/>
</div>
</div>
<div class="row my-2">
<div class="col">
<alert
alertClass="alert-warning"
alertHeadline="Warning Alert"
alertCopy="This is an example of a WARNING alert."
v-bind:isDismissible = "false"
alertClass="alert-danger"
alertHeadline="NON-Dismissible Alert"
alertCopy="This is an example of a NON-DISMISSIBLE alert."
v-bind:isDismissible="false"
/>
</div>
</div>
<div class="row my-2">
<div class="col">
<alert
alertClass="alert-info"
alertHeadline="Info Alert"
alertCopy="This is an example of a INFO alert."
v-bind:isDismissible = "false"
alertClass="alert-warning"
alertHeadline="Warning Alert"
alertCopy="This is an example of a WARNING alert."
v-bind:isDismissible="false"
/>
</div>
</div>
<div class="row my-2">
<div class="col">
<alert
alertClass="alert-danger"
alertHeadline="Danger Alert"
alertCopy="This is an example of a DANGER alert."
v-bind:isDismissible = "false"
alertClass="alert-info"
alertHeadline="Info Alert"
alertCopy="This is an example of a INFO alert."
v-bind:isDismissible="false"
/>
</div>
</div>
<div class="row my-2">
<div class="col">
<alert
alertClass="alert-success"
alertHeadline="No Body Copy Alert"
alertCopy=""
v-bind:isDismissible = "false"
alertClass="alert-danger"
alertHeadline="Danger Alert"
alertCopy="This is an example of a DANGER alert."
v-bind:isDismissible="false"
/>
</div>
</div>
<div class="row my-2">
<div class="col">
<alert
alertClass="alert-success"
alertHeadline="No Body Copy Alert"
alertCopy=""
v-bind:isDismissible="false"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Site Header</h4>
<h4 class="m-0 p-2 bg-light rounded">Site Header</h4>
</div>
</div>
<div class="row my-3">
<div class="col">
<siteHeader imageSrc="https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3" />
<siteHeader
imageSrc="https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Vehicle Banner</h4>
<h4 class="m-0 p-2 bg-light rounded">Vehicle Banner</h4>
</div>
</div>
<div class="row my-3">
<div class="col">
<vehicleBanner vehicleImageSrc="https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3" />
<vehicleBanner
vehicleImageSrc="https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3"
/>
</div>
</div>
</div>
</template>
<script>
import buttonPrimary from "@/ux-components/button-primary/button-primary";
import buttonSecondary from "@/ux-components/button-secondary/button-secondary";
import radioCard from "@/ux-components/radio-card/radio-card";
import listButton from "@/ux-components/list-button/list-button";
import radio from "@/ux-components/radio/radio";
import alert from "@/ux-components/alert/alert";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import siteHeader from "@/common-components/site-header/site-header";
import radioHorizontal from "@/ux-components/radio-horizontal/radio-horizontal";
export default {
name: "App",
components: {
buttonPrimary,
buttonSecondary,
radioCard,
listButton,
radio,
alert,
vehicleBanner,
radioHorizontal,
siteHeader,
import buttonPrimary from "@/ux-components/button-primary/button-primary";
import buttonSecondary from "@/ux-components/button-secondary/button-secondary";
import radioCard from "@/ux-components/radio-card/radio-card";
import listButton from "@/ux-components/list-button/list-button";
import radio from "@/ux-components/radio/radio";
import alert from "@/ux-components/alert/alert";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import siteHeader from "@/common-components/site-header/site-header";
import radioHorizontal from "@/ux-components/radio-horizontal/radio-horizontal";
export default {
name: "App",
components: {
buttonPrimary,
buttonSecondary,
radioCard,
listButton,
radio,
alert,
vehicleBanner,
radioHorizontal,
siteHeader,
},
data() {
return {
years: [2023, 2022, 2021, 2020],
};
},
methods: {
onClick() {
this.$router.navigate("SELECTED_YEAR", this.$route);
},
data() {
return {
years: [2023, 2022, 2021, 2020],
};
}
};
},
};
</script>

View file

@ -7,11 +7,7 @@
</div>
<div class="row">
<div class="col my-3 d-flex align-items-center">
<loader
sizeInRem="10"
loaderColor="blue"
loaderPosition="center"
/>
<loader sizeInRem="10" loaderColor="blue" loaderPosition="center" />
</div>
</div>
</div>
@ -22,7 +18,7 @@ import loader from "@/ux-components/loader/loader";
export default {
name: "App",
components: {
loader
}
loader,
},
};
</script>

View file

@ -2,16 +2,15 @@ import { shallowMount, flushPromises } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import vehicleYear from "@/layouts/vehicle-year/vehicle-year.vue";
import { settleAllPromises } from "@/helpers/layout-helper.js";
import { nextTick } from 'vue'
import { nextTick } from "vue";
// Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({
settleAllPromises: jest.fn()
jest.mock("@/helpers/layout-helper.js", () => ({
settleAllPromises: jest.fn(),
}));
describe("vehicle-year.vue", () => {
test("vehicle-year.vue should render data from CMS", async () => {
// Arrange
// Our mock data for our call to settleAllPromises
@ -19,31 +18,43 @@ describe("vehicle-year.vue", () => {
getPageContent: {
PageHeaderWidget: [{ HeaderText: "Select a year to get started" }],
RadioQuestionWidget: [{ QuestionText: "What year is your vehicle?" }],
VehicleBannerWidget: [{ GenericVehicleImage: "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3" }],
SiteHeaderWidget: [{ "LogoImage": "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3" }],
VehicleBannerWidget: [
{
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
},
],
SiteHeaderWidget: [
{
LogoImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
},
],
isCmsContentReady: true,
},
getVehicleYear: [2023, 2022, 2021],
store: {
commit: jest.fn(),
year: null
year: null,
},
router: {
push: jest.fn()
}
}
push: jest.fn(),
},
};
// our router information needed.
const to = {
query: {
fmgPage: 'vehicle-year'
}
fmgPage: "vehicle-year",
},
};
const mountOptions = getMountOptions(mockData);
// our mock implementation of settleAllPromises
settleAllPromises.mockImplementation(() => { return Promise.resolve(mockData);});
settleAllPromises.mockImplementation(() => {
return Promise.resolve(mockData);
});
// Act
const wrapper = shallowMount(vehicleYear, mountOptions);
@ -52,7 +63,9 @@ describe("vehicle-year.vue", () => {
// Call our beforeRouteEnter on the component.
// This passes (c) => c(wrapper.vm) so that next can be called and our
// data can be set.
vehicleYear.beforeRouteEnter.call(wrapper.vm, to, undefined, (c) => c(wrapper.vm));
vehicleYear.beforeRouteEnter.call(wrapper.vm, to, undefined, (c) =>
c(wrapper.vm)
);
await nextTick(); // Wait for the DOM to update.
@ -60,7 +73,9 @@ describe("vehicle-year.vue", () => {
const header = await wrapper.find(".Header");
expect(header.attributes("text")).toEqual("Select a year to get started");
const yearQuestion = wrapper.findComponent({ name: 'year-question' });
expect(yearQuestion.attributes("questiontext")).toEqual("What year is your vehicle?");
const yearQuestion = wrapper.findComponent({ name: "year-question" });
expect(yearQuestion.attributes("questiontext")).toEqual(
"What year is your vehicle?"
);
});
});

View file

@ -2,9 +2,15 @@
<siteHeader :imageSrc="siteHeaderWidget.LogoImage" />
<div class="select-car">
<div class="select-car-form rounded text-center">
<vehicleBanner :vehicleImageSrc="vehicleBannerWidget.GenericVehicleImage" />
<vehicleBanner
:vehicleImageSrc="vehicleBannerWidget.GenericVehicleImage"
/>
<pageHeader :text="pageHeaderWidgets.HeaderText" class="Header" />
<yearQuestion :questionText="radioQuestionWidgets.QuestionText" :years="vehicleYears" v-model="selectedYear" />
<yearQuestion
:questionText="radioQuestionWidgets.QuestionText"
:years="vehicleYears"
v-model="selectedYear"
/>
</div>
</div>
</template>
@ -29,16 +35,18 @@ export default {
vehicleYears: [],
siteHeaderWidget: {},
vehicleBannerWidget: {},
selectedYear: null
selectedYear: null,
};
},
computed: {},
beforeRouteEnter(to, from, next) {
// Call APIs
const contentPromise = fetchCmsContentForPage(to.query.fmgPage);
const getVehicleYearPromise = store.dispatch(storeActions.GET_VEHICLE_YEARS, {});
const getVehicleYearPromise = store.dispatch(
storeActions.GET_VEHICLE_YEARS,
{}
);
// Settle promises and get results
const promiseResultMap = [
@ -52,13 +60,14 @@ export default {
},
];
settleAllPromises(promiseResultMap).then((resultMap) => {
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.pageHeaderWidgets = resultMap.getPageContent.PageHeaderWidget[0];
vm.siteHeaderWidget = resultMap.getPageContent.SiteHeaderWidget[0];
vm.radioQuestionWidgets = resultMap.getPageContent.RadioQuestionWidget[0];
vm.vehicleBannerWidget = resultMap.getPageContent.VehicleBannerWidget[0];
vm.radioQuestionWidgets =
resultMap.getPageContent.RadioQuestionWidget[0];
vm.vehicleBannerWidget =
resultMap.getPageContent.VehicleBannerWidget[0];
vm.vehicleYears = resultMap.getVehicleYear;
});
});
@ -66,9 +75,9 @@ export default {
watch: {
selectedYear(year) {
this.$store.commit(this.storeMutations.UPDATE_YEAR, year);
this.$router.push('?fmgPage=vehicle-make');
}
this.$store.commit(this.storeMutations.UPDATE_YEAR, year);
this.$router.push("?fmgPage=vehicle-make");
},
},
components: {
@ -81,15 +90,15 @@ export default {
</script>
<style lang="scss">
.select-car {
height: calc(100vh - 56px);
padding: 0 1.5rem;
.select-car {
height: calc(100vh - 56px);
padding: 0 1.5rem;
.car_list {
// Height will be determined by overall height of content above list
height: calc(100% - 300px);
padding: 0 1.5rem;
-webkit-overflow-scrolling: touch;
}
}
.car_list {
// Height will be determined by overall height of content above list
height: calc(100% - 300px);
padding: 0 1.5rem;
-webkit-overflow-scrolling: touch;
}
}
</style>

View file

@ -1,21 +1,25 @@
import yearQuestion from "@/layouts/vehicle-year/year-question/year-question";
import { shallowMount } from "@vue/test-utils";
describe('year-question.vue', () => {
test('year-question should take a prop for years and questionText, and trigger a selectYear function when an option is clicked.', async () => {
// Act
const wrapper = shallowMount(yearQuestion);
await wrapper.setProps({
questionText: 'What year is your vehicle?',
years: ['2023', '2022', '2021'],
modelValue: '2020'
});
wrapper.vm.$options.watch.selectedYear.call(wrapper.vm);
// Assert
const radioQuestion = await wrapper.findComponent({name: 'radioQuestion'});
expect(radioQuestion.attributes('questiontext')).toBe("What year is your vehicle?");
expect(radioQuestion.attributes('answers')).toBe("2023,2022,2021");
expect(wrapper.componentVM.modelValue).toBe("2020");
describe("year-question.vue", () => {
test("year-question should take a prop for years and questionText, and trigger a selectYear function when an option is clicked.", async () => {
// Act
const wrapper = shallowMount(yearQuestion);
await wrapper.setProps({
questionText: "What year is your vehicle?",
years: ["2023", "2022", "2021"],
modelValue: "2020",
});
});
wrapper.vm.$options.watch.selectedYear.call(wrapper.vm);
// Assert
const radioQuestion = await wrapper.findComponent({
name: "radioQuestion",
});
expect(radioQuestion.attributes("questiontext")).toBe(
"What year is your vehicle?"
);
expect(radioQuestion.attributes("answers")).toBe("2023,2022,2021");
expect(wrapper.componentVM.modelValue).toBe("2020");
});
});

View file

@ -1,33 +1,34 @@
<template>
<radioQuestion class="radioQuestion"
:questionText="questionText"
<radioQuestion
class="radioQuestion"
:questionText="questionText"
:answers="years"
v-model="selectedYear"
/>
/>
</template>
<script>
import radioQuestion from "@/common-components/radio-question/radio-question";
export default {
name: "year-question",
data(){
return {
selectedYear: null
}
name: "year-question",
data() {
return {
selectedYear: null,
};
},
props: {
questionText: String,
years: Array,
modelValue: String,
},
components: {
radioQuestion,
},
watch: {
selectedYear(val) {
this.$emit("update:modelValue", val);
},
props: {
questionText: String,
years: Array,
modelValue: String
},
components: {
radioQuestion,
},
watch: {
selectedYear(val) {
this.$emit('update:modelValue', val)
}
}
},
};
</script>

View file

@ -3,10 +3,9 @@ import { storeMutations } from "@/constants/store-mutations.js";
import { widgetNames } from "@/constants/widget-names.js";
export default {
data(){
data() {
return {
isCmsContentReady: false
}
};
},
methods: {
// dispatchBlockingStoreAction(type, payload) {

View file

@ -1,54 +1,53 @@
import baseMixin from "@/mixins/base-mixin"
import baseMixin from "@/mixins/base-mixin";
import { storeActions } from "@/constants/store-actions.js";
import { widgetNames } from "@/constants/widget-names.js";
describe("baseMixin.js", () => {
test('dispatchNonblockingStoreAction: calls dispatch with type and payload', () => {
const mixIn = getMixInInstance({});
const type = {};
const payload = {};
test("dispatchNonblockingStoreAction: calls dispatch with type and payload", () => {
const mixIn = getMixInInstance({});
const type = {};
const payload = {};
mixIn.methods.dispatchNonBlockingStoreAction(type, payload);
mixIn.methods.dispatchNonBlockingStoreAction(type, payload);
expect(mixIn.methods.$store.dispatch).toBeCalledWith(type, payload);
});
expect(mixIn.methods.$store.dispatch).toBeCalledWith(type, payload);
});
test('dispatchNonblockingStoreAction: calls dispatch with type and payload, handles Uri encode', () => {
const mixIn = getMixInInstance({});
const type = {};
const payload = { make: 'Alfa Romeo/Chrysler' };
test("dispatchNonblockingStoreAction: calls dispatch with type and payload, handles Uri encode", () => {
const mixIn = getMixInInstance({});
const type = {};
const payload = { make: "Alfa Romeo/Chrysler" };
mixIn.methods.dispatchNonBlockingStoreAction(type, payload, true);
mixIn.methods.dispatchNonBlockingStoreAction(type, payload, true);
expect(mixIn.methods.$store.dispatch).toBeCalledWith(type, payload);
});
})
expect(mixIn.methods.$store.dispatch).toBeCalledWith(type, payload);
});
});
function getMixInInstance({ isDispatchSuccess = true }) {
// Mock Store
const store = {
dispatch: jest.fn(),
};
// Mock Store
const store = {
dispatch: jest.fn()
}
if (isDispatchSuccess) {
store.dispatch.mockReturnValue(Promise.resolve());
} else {
store.dispatch.mockReturnValue(Promise.reject());
}
if (isDispatchSuccess) {
store.dispatch.mockReturnValue(Promise.resolve())
} else {
store.dispatch.mockReturnValue(Promise.reject())
}
// Mock Route
const route = {
query: {
fmgPage: "test-page",
},
};
// Mock Route
const route = {
query: {
fmgPage: 'test-page'
}
};
// Attach mocks to mixin
const baseMixIn = baseMixin;
baseMixIn.methods.$route = route;
baseMixIn.methods.$store = store;
baseMixIn.methods.storeActions = storeActions;
baseMixIn.methods.widgetNames = widgetNames;
// Attach mocks to mixin
const baseMixIn = baseMixin;
baseMixIn.methods.$route = route;
baseMixIn.methods.$store = store;
baseMixIn.methods.storeActions = storeActions;
baseMixIn.methods.widgetNames = widgetNames;
return baseMixIn;
}
return baseMixIn;
}

View file

@ -66,18 +66,20 @@ const routes = [
},
];
const router = createRouter({
history: createWebHistory("/fmg/"),
routes,
});
//---------------------------------------------------------- Router Functions ----------------------------------------------------------
// Navigate to the next route, depending on the scenario.
router.navigate = (scenario, currentRoute, optionalQuery = {}, optionalParams = {}) => {
router.navigate = (
scenario,
currentRoute,
optionalQuery = {},
optionalParams = {}
) => {
if (!scenario) {
console.error("No scenario provided. Please review the routing table.");
return;
@ -88,21 +90,31 @@ router.navigate = (scenario, currentRoute, optionalQuery = {}, optionalParams =
if (matchingScenarioMap.destinationFmgPageValue !== undefined) {
// We're always pushing the same path, just changing query strings. Make sure our optional query strings get combined with our fmgPage one.
router.push({ path: '/', query: Object.assign(optionalQuery, { fmgPage: matchingScenarioMap.destinationFmgPageValue }), params: optionalParams });
router.push({
path: "/",
query: Object.assign(optionalQuery, {
fmgPage: matchingScenarioMap.destinationFmgPageValue,
}),
params: optionalParams,
});
} else if (matchingScenarioMap.destinationUrl !== undefined) {
navigateToUrl(matchingScenarioMap.destinationUrl);
}
}
};
// Get navigation map depeding on the scenario and the current 'page' you're on.
router.getNavigationMap = (scenario, currentRoute) => {
const fmgPageValue = currentRoute.query.fmgPage;
const matchedQueryValue = routingTable.filter(item => (item.fmgPageValue === fmgPageValue) && item.maps.filter(map => map.scenario === scenario).length > 0).map(m => m.maps.filter(map => map.scenario === scenario));
const matchedQueryValue = routingTable
.filter(
(item) =>
item.fmgPageValue === fmgPageValue &&
item.maps.filter((map) => map.scenario === scenario).length > 0
)
.map((m) => m.maps.filter((map) => map.scenario === scenario));
return matchedQueryValue[0][0];
}
};
//---------------------------------------------------------- Private Functions ----------------------------------------------------------

View file

@ -1,6 +1,6 @@
const fmgPageValues = {
VEHICLE_YEAR: 'vehicle-year',
VEHICLE_MAKE: 'vehicle-make',
VEHICLE_YEAR: "vehicle-year",
VEHICLE_MAKE: "vehicle-make",
};
export { fmgPageValues };
export { fmgPageValues };

View file

@ -1,5 +1,5 @@
const navigationScenarios = {
SELECTED_YEAR: 'SELECTED_YEAR',
const navigationScenarios = {
SELECTED_YEAR: "SELECTED_YEAR",
};
export { navigationScenarios } ;
export { navigationScenarios };

View file

@ -1,14 +1,16 @@
import { fmgPageValues } from '@/router/router-constants/fmgPage-values';
import { navigationScenarios } from '@/router/router-constants/navigation-scenarios';
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
const routingTable = [
{
fmgPageValue: fmgPageValues.VEHICLE_YEAR,
maps: [
{ scenario: navigationScenarios.SELECTED_YEAR, destinationFmgPageValue: fmgPageValues.VEHICLE_MAKE }
]
},
]
{
fmgPageValue: fmgPageValues.VEHICLE_YEAR,
maps: [
{
scenario: navigationScenarios.SELECTED_YEAR,
destinationFmgPageValue: fmgPageValues.VEHICLE_MAKE,
},
],
},
];
export { routingTable };
export { routingTable };

View file

@ -32,7 +32,7 @@ export default createStore({
zipCode: null,
firstName: null,
lastName: null,
licensePlate: null
licensePlate: null,
},
damage: {
isRepair: null,
@ -52,16 +52,16 @@ export default createStore({
isCash: null,
},
referralSeqNum: null,
}
},
},
applicationUser: {
experiments: null,
}
},
},
// See IMPORTANT note at top of "state" declaration.
mutations: {
updateYear(state, year){
updateYear(state, year) {
state.order.vehicle.year = year;
},
},
@ -86,7 +86,7 @@ export default createStore({
method: endpoints.LookupVehicleByVin.method,
endpoint: endpoints.LookupVehicleByVin.url,
payload: {
"vin": vin // EX "1J4GW58S4XC541166"
vin: vin, // EX "1J4GW58S4XC541166"
},
});
},
@ -135,6 +135,6 @@ export default createStore({
endpoint: relativeUrl,
payload: {},
});
}
},
},
});

View file

@ -1,121 +1,131 @@
import store from './index'
import globalMethods from '@/global-methods'
import store from "./index";
import globalMethods from "@/global-methods";
describe("Actions", () => {
it("Should return list of years retrieved", async () => {
// Arrange
let years = [];
it("Should return list of years retrieved", async () => {
// Arrange
let years = [];
// Act
globalMethods.callHttpClient = jest.fn();
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: [2023,2022,2021]});
});
await store.dispatch('getVehicleYears')
.then( (response) => {
years = response.data;
});
// Assert
expect(years[0]).toBe(2023);
// Act
globalMethods.callHttpClient = jest.fn();
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: [2023, 2022, 2021] });
});
await store.dispatch("getVehicleYears").then((response) => {
years = response.data;
});
it("Should return list of makes retrieved", async () => {
// Arrange
let makes = [];
// Assert
expect(years[0]).toBe(2023);
});
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: ['Baic','Honda','Ford']});
});
await store.dispatch('getVehicleMakes', {year: 2023})
.then( (response) => {
makes = response.data;
});
it("Should return list of makes retrieved", async () => {
// Arrange
let makes = [];
// Assert
expect(makes[0]).toBe('Baic');
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: ["Baic", "Honda", "Ford"] });
});
await store.dispatch("getVehicleMakes", { year: 2023 }).then((response) => {
makes = response.data;
});
it("Should return list of models retrieved", async () => {
// Arrange
let models = [];
// Assert
expect(makes[0]).toBe("Baic");
});
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: ['BJ40 (MEX)','Civic','Accord']});
});
await store.dispatch('getVehicleModels', {year: 2023, make: 'Baic'})
.then( (response) => {
models = response.data;
});
it("Should return list of models retrieved", async () => {
// Arrange
let models = [];
// Assert
expect(models[0]).toBe('BJ40 (MEX)');
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: ["BJ40 (MEX)", "Civic", "Accord"] });
});
await store
.dispatch("getVehicleModels", { year: 2023, make: "Baic" })
.then((response) => {
models = response.data;
});
it("Should return list of styles retrieved", async () => {
// Arrange
let styles = [];
// Assert
expect(models[0]).toBe("BJ40 (MEX)");
});
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: ['4 DOOR UTILITY','2 DOOR']});
});
await store.dispatch('getVehicleStyles', {year: 2023, make: 'Baic', model: 'BJ40 (MEX)'})
.then( (response) => {
styles = response.data;
});
it("Should return list of styles retrieved", async () => {
// Arrange
let styles = [];
// Assert
expect(styles[0]).toBe('4 DOOR UTILITY');
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: ["4 DOOR UTILITY", "2 DOOR"] });
});
await store
.dispatch("getVehicleStyles", {
year: 2023,
make: "Baic",
model: "BJ40 (MEX)",
})
.then((response) => {
styles = response.data;
});
it("Should return data from url retrieved", async () => {
// Arrange
let routeInfo = [];
// Assert
expect(styles[0]).toBe("4 DOOR UTILITY");
});
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: {
Result: 'Route Info Data'
}});
});
await store.dispatch('getRouteInfo', { pageName: 'vehicle-year' })
.then( (response) => {
routeInfo = response.data.Result;
});
it("Should return data from url retrieved", async () => {
// Arrange
let routeInfo = [];
// Assert
expect(routeInfo).toBe('Route Info Data');
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({
data: {
Result: "Route Info Data",
},
});
});
await store
.dispatch("getRouteInfo", { pageName: "vehicle-year" })
.then((response) => {
routeInfo = response.data.Result;
});
it("Should return page data from url retrieved", async () => {
// Arrange
let pageData = [];
// Assert
expect(routeInfo).toBe("Route Info Data");
});
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: {
Result: 'Page Info Data'
}});
});
await store.dispatch('getPageData', { pageName: 'vehicle-year' })
.then( (response) => {
pageData = response.data.Result;
});
it("Should return page data from url retrieved", async () => {
// Arrange
let pageData = [];
// Assert
expect(pageData).toBe('Page Info Data');
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({
data: {
Result: "Page Info Data",
},
});
});
})
await store
.dispatch("getPageData", { pageName: "vehicle-year" })
.then((response) => {
pageData = response.data.Result;
});
// Assert
expect(pageData).toBe("Page Info Data");
});
});
describe("Mutations", () => {
it("Should update the year property in the store", () => {
// Act
store.commit('updateYear', 2020);
it("Should update the year property in the store", () => {
// Act
store.commit("updateYear", 2020);
// Assert
expect(store.state.order.vehicle.year).toBe(2020);
});
});
// Assert
expect(store.state.order.vehicle.year).toBe(2020);
});
});

View file

@ -1 +1 @@
test.todo('some test to be written in the future');
test.todo("some test to be written in the future");

View file

@ -1,12 +1,25 @@
<template>
<div class="alert fade show text-center mb-0 py-2 px-3" role="alert"
:class="[ isDismissible ? 'alert-dismissible' : '', this.alertClass ]"
<div
class="alert fade show text-center mb-0 py-2 px-3"
role="alert"
:class="[isDismissible ? 'alert-dismissible' : '', this.alertClass]"
>
<p class="m-0 fw-bold small alert-heading">{{ alertHeadline }}</p>
<p class="m-0 text-body small">{{ alertCopy }}</p>
<button
type="button"
class="btn-close p-2"
data-bs-dismiss="alert"
aria-label="Close"
>
<p class="m-0 fw-bold small alert-heading">{{alertHeadline}}</p>
<p class="m-0 text-body small">{{alertCopy}}</p>
<button type="button" class="btn-close p-2" data-bs-dismiss="alert" aria-label="Close">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23.7 23.7" xml:space="preserve">
<path d="m23.24 2.7-9.15 9.15L23.24 21a1.581 1.581 0 0 1-1.12 2.7c-.42 0-.82-.16-1.12-.46l-9.15-9.15-9.15 9.15c-.3.3-.7.46-1.12.46A1.581 1.581 0 0 1 .46 21l8.47-8.47.68-.68L.46 2.7c-.62-.62-.62-1.62 0-2.24.62-.62 1.62-.62 2.24 0l8.47 8.47.68.68L21 .46a1.57 1.57 0 0 1 2.23 0c.63.62.63 1.62.01 2.24z"/>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 23.7 23.7"
xml:space="preserve"
>
<path
d="m23.24 2.7-9.15 9.15L23.24 21a1.581 1.581 0 0 1-1.12 2.7c-.42 0-.82-.16-1.12-.46l-9.15-9.15-9.15 9.15c-.3.3-.7.46-1.12.46A1.581 1.581 0 0 1 .46 21l8.47-8.47.68-.68L.46 2.7c-.62-.62-.62-1.62 0-2.24.62-.62 1.62-.62 2.24 0l8.47 8.47.68.68L21 .46a1.57 1.57 0 0 1 2.23 0c.63.62.63 1.62.01 2.24z"
/>
</svg>
</button>
</div>
@ -26,8 +39,8 @@ export default {
alert-warning (yellow)
alert-info (blue)
*/
alertClass: String
}
alertClass: String,
},
};
</script>
@ -39,8 +52,8 @@ export default {
.btn-close {
background: none;
opacity: 1;
width: .75rem;
height: .75rem;
width: 0.75rem;
height: 0.75rem;
}
&.alert-dismissible {
button {

View file

@ -3,14 +3,14 @@
:disabled="isDisabled"
:aria-disabled="isDisabled"
class="btn btn-primary d-flex align-items-center py-3 px-4"
@click='displayComponent'
>
@click="displayComponent"
>
<span class="m-0">{{ this.buttonText }}</span>
<loader
class="ms-2"
v-if="display"
v-bind:style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}"
v-bind:class="[this.loaderColor, this.loaderPosition]"
class="ms-2"
v-if="display"
v-bind:style="{ width: `${sizeInRem}rem`, height: `${sizeInRem}rem` }"
v-bind:class="[this.loaderColor, this.loaderPosition]"
/>
</button>
</template>
@ -24,7 +24,7 @@ export default {
isDisabled: Boolean,
loaderColor: String,
loaderPosition: String,
sizeInRem: [Number,String]
sizeInRem: [Number, String],
},
data() {
return {

View file

@ -3,14 +3,14 @@
:disabled="isDisabled"
:aria-disabled="isDisabled"
class="btn btn-secondary d-flex align-items-center py-3 px-4"
@click='displayComponent'
>
@click="displayComponent"
>
<span class="m-0">{{ this.buttonText }}</span>
<loader
class="ms-2"
v-if="display"
v-bind:style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}"
v-bind:class="[this.loaderColor, this.loaderPosition]"
class="ms-2"
v-if="display"
v-bind:style="{ width: `${sizeInRem}rem`, height: `${sizeInRem}rem` }"
v-bind:class="[this.loaderColor, this.loaderPosition]"
/>
</button>
</template>
@ -24,7 +24,7 @@ export default {
isDisabled: Boolean,
loaderColor: String,
loaderPosition: String,
sizeInRem: [Number,String]
sizeInRem: [Number, String],
},
data() {
return {

View file

@ -1,7 +1,9 @@
<template>
<div class="current_car_info-text">
<div class="d-flex align-items-center justify-content-center">
<h2 class="text-center text-dark fs-5 d-block fw-normal mb-0">{{ text }}</h2>
<h2 class="text-center text-dark fs-5 d-block fw-normal mb-0">
{{ text }}
</h2>
</div>
</div>
</template>

View file

@ -1,21 +1,27 @@
<template>
<div class="d-flex flex-column w-100">
<button
class="btn list-button d-flex align-items-center justify-content-between py-3 px-4"
@click='displayComponent'
v-bind:class="[
this.isError ? 'error' : '',
]"
class="
btn
list-button
d-flex
align-items-center
justify-content-between
py-3
px-4
"
@click="displayComponent"
v-bind:class="[this.isError ? 'error' : '']"
>
<span class="m-0">{{ this.buttonText }}</span>
<loader
v-if="display"
v-bind:style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}"
v-bind:class="[this.loaderColor, this.loaderPosition]"
/>
</button>
<label class="small mt-1">{{ this.errorText }}</label>
</div>
<span class="m-0">{{ this.buttonText }}</span>
<loader
v-if="display"
v-bind:style="{ width: `${sizeInRem}rem`, height: `${sizeInRem}rem` }"
v-bind:class="[this.loaderColor, this.loaderPosition]"
/>
</button>
<label class="small mt-1">{{ this.errorText }}</label>
</div>
</template>
<script>
@ -27,12 +33,12 @@ export default {
errorText: String,
loaderColor: String,
loaderPosition: String,
sizeInRem: [Number,String]
sizeInRem: [Number, String],
},
data() {
return {
display: false,
isError: false
isError: false,
};
},
methods: {

View file

@ -1,7 +1,8 @@
<template>
<div class="loader"
v-bind:style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}"
v-bind:class="[this.loaderColor, this.loaderPosition]"
<div
class="loader"
v-bind:style="{ width: `${sizeInRem}rem`, height: `${sizeInRem}rem` }"
v-bind:class="[this.loaderColor, this.loaderPosition]"
></div>
</template>
@ -12,18 +13,18 @@ export default {
props: {
sizeInRem: {
type: Number,
default: 1
default: 1,
},
/* Color options: red, green, blue, white, black */
loaderColor: {
type: String
type: String,
},
/* Position options: center, right, left (OPTIONAL, do NOT use on btn-* classes) */
loaderPosition: {
type: String
}
type: String,
},
},
}
};
</script>
<style lang="scss">
@ -31,7 +32,7 @@ export default {
display: flex;
//Open an overlay to prevent page interaction
&:before {
content: '';
content: "";
position: fixed;
top: 0;
bottom: 0;
@ -43,7 +44,7 @@ export default {
}
//Spinner basics
&:after {
content: '';
content: "";
mask: url(../../assets/img/icons/spinner.svg);
mask-size: cover;
position: relative;
@ -52,7 +53,7 @@ export default {
animation: rotation 1s infinite linear;
@keyframes rotation {
100% {
transform:rotate(360deg);
transform: rotate(360deg);
}
}
}
@ -70,7 +71,8 @@ export default {
left: 1rem;
}
//Spinner color
&:after { //Default spinner color (blue) if no other color is specified from the options below
&:after {
//Default spinner color (blue) if no other color is specified from the options below
background-color: $blue;
}
&.red:after {

View file

@ -11,7 +11,7 @@ describe("radio.vue", () => {
loaderColor: "blue",
loaderPosition: "right",
sizeInRem: "1.5",
errorMessage: 'null'
errorMessage: "null",
},
});
@ -20,14 +20,14 @@ describe("radio.vue", () => {
const label = wrapper.find("label");
const paragraph = wrapper.find("span");
await label.trigger('click');
await label.trigger("click");
expect(input.attributes()).toEqual({
id: "2023",
type: "radio",
value: "2023",
name: "TestGroup",
"aria-required": "true"
"aria-required": "true",
});
expect(label.attributes()).toEqual({
@ -35,14 +35,13 @@ describe("radio.vue", () => {
tabindex: "-1",
for: "2023",
class: "d-flex flex-column justify-content-center py-3 px-4 last-item",
"aria-checked": "false"
"aria-checked": "false",
});
expect(label.text()).toEqual('2023');
expect(label.text()).toEqual("2023");
expect(paragraph.text()).toEqual('2023');
expect(paragraph.text()).toEqual("2023");
expect(wrapper.vm.display).toBe(true);
});
});

View file

@ -8,13 +8,34 @@
<template>
<!-- IMPORTANT: Refrain from using more than 4 horizontal radio buttons on desktop, 3 on mobile. -->
<div class="col radiogroup radio-horizontal d-flex flex-column mb-2">
<input type="radio" :id="radioID" :name="groupName" :value="radioID" aria-required="true" @keyup.space="displayComponent"/>
<label role="radio" tabindex="-1" aria-checked="false" :for="radioID" class="d-flex flex-column justify-content-center py-3 px-4" :class="isFirstOrLastButton" @click='displayComponent'>
<span class="m-0" :class="[this.textPosition]">{{radioID}}</span>
<span class="m-0 small" :class="[this.textPosition]">{{radioLabelSubCopy}}</span>
<loader v-if="display" :style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}" :class="[this.loaderColor, this.loaderPosition]" />
<input
type="radio"
:id="radioID"
:name="groupName"
:value="radioID"
aria-required="true"
@keyup.space="displayComponent"
/>
<label
role="radio"
tabindex="-1"
aria-checked="false"
:for="radioID"
class="d-flex flex-column justify-content-center py-3 px-4"
:class="isFirstOrLastButton"
@click="displayComponent"
>
<span class="m-0" :class="[this.textPosition]">{{ radioID }}</span>
<span class="m-0 small" :class="[this.textPosition]">{{
radioLabelSubCopy
}}</span>
<loader
v-if="display"
:style="{ width: `${sizeInRem}rem`, height: `${sizeInRem}rem` }"
:class="[this.loaderColor, this.loaderPosition]"
/>
</label>
<p class="small">{{errorMessage}}</p>
<p class="small">{{ errorMessage }}</p>
</div>
</template>
@ -23,42 +44,52 @@ import loader from "@/ux-components/loader/loader";
export default {
name: "radioHorizontal",
props: {
groupName: String, /* Required, unique for each radio button GROUP */
radioID: String, /* Required, unique for each radio button. Used for button id, label and <label for> */
radioLabelSubCopy: String, /* Optional, used for multi-line radio 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)) */
totalInGroup: Number, /* Required, total number of radio buttons in group. Used to tell first and last in group to apply border radius. */
positionInGroup: Number /* Rquired, position of radio button in group. Example, 1,2,3 */
groupName: String /* Required, unique for each radio button GROUP */,
radioID:
String /* Required, unique for each radio button. Used for button id, label and <label for> */,
radioLabelSubCopy: String /* Optional, used for multi-line radio 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)) */,
totalInGroup:
Number /* Required, total number of radio buttons in group. Used to tell first and last in group to apply border radius. */,
positionInGroup:
Number /* Rquired, position of radio button in group. Example, 1,2,3 */,
},
data() {
return {
isError: false,
display: false
display: false,
};
},
methods: {
displayComponent() {
this.display = true;
}
},
},
components: {
loader,
},
computed: {
isFirstOrLastButton() {
let className = '';
if(this.positionInGroup == this.totalInGroup) {
className = 'last-item'
} else if(this.positionInGroup == 1) {
className = 'first-item'
let className = "";
if (this.positionInGroup == this.totalInGroup) {
className = "last-item";
} else if (this.positionInGroup == 1) {
className = "first-item";
}
return className;
}
}
},
},
};
</script>
<style lang="scss">
@ -103,12 +134,12 @@ export default {
}
}
&.first-item {
border-bottom-left-radius: .5rem;
border-top-left-radius: .5rem;
border-bottom-left-radius: 0.5rem;
border-top-left-radius: 0.5rem;
}
&.last-item {
border-bottom-right-radius: .5rem;
border-top-right-radius: .5rem;
border-bottom-right-radius: 0.5rem;
border-top-right-radius: 0.5rem;
}
}
}

View file

@ -11,7 +11,7 @@ describe("radio.vue", () => {
loaderColor: "blue",
loaderPosition: "right",
sizeInRem: "1.5",
errorMessage: 'null'
errorMessage: "null",
},
});
@ -20,14 +20,14 @@ describe("radio.vue", () => {
const label = wrapper.find("label");
const paragraph = wrapper.find("span");
await label.trigger('click');
await label.trigger("click");
expect(input.attributes()).toEqual({
id: "2023",
type: "radio",
value: "2023",
name: "TestGroup",
"aria-required": "true"
"aria-required": "true",
});
expect(label.attributes()).toEqual({
@ -35,14 +35,13 @@ describe("radio.vue", () => {
tabindex: "-1",
for: "2023",
class: "d-flex flex-column justify-content-center py-3 px-4",
"aria-checked": "false"
"aria-checked": "false",
});
expect(label.text()).toEqual('2023');
expect(label.text()).toEqual("2023");
expect(paragraph.text()).toEqual('2023');
expect(paragraph.text()).toEqual("2023");
expect(wrapper.vm.display).toBe(true);
});
});

View file

@ -7,14 +7,34 @@
<!-- Example -->
<!-- <h3 class="visually-hidden" id="demo-radio-group">Select Vehicle Year</h3> -->
<div class="radiogroup radio-list-button d-flex flex-column w-100 mb-2">
<input type="radio" :id="radioID" :name="groupName" :value="radioID" aria-required="true" @keyup.space="displayComponent">
<label role="radio" tabindex="-1" aria-checked="false" :for="radioID" class="d-flex flex-column justify-content-center py-3 px-4" @click='displayComponent'>
<span class="m-0" :class="[this.textPosition]">{{radioID}}</span>
<span class="m-0 small" :class="[this.textPosition]">{{radioLabelSubCopy}}</span>
<loader v-if="display" :style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}" :class="[this.loaderColor, this.loaderPosition]" />
</label>
<p class="small">{{errorMessage}}</p>
</div>
<input
type="radio"
:id="radioID"
:name="groupName"
:value="radioID"
aria-required="true"
@keyup.space="displayComponent"
/>
<label
role="radio"
tabindex="-1"
aria-checked="false"
:for="radioID"
class="d-flex flex-column justify-content-center py-3 px-4"
@click="displayComponent"
>
<span class="m-0" :class="[this.textPosition]">{{ radioID }}</span>
<span class="m-0 small" :class="[this.textPosition]">{{
radioLabelSubCopy
}}</span>
<loader
v-if="display"
:style="{ width: `${sizeInRem}rem`, height: `${sizeInRem}rem` }"
:class="[this.loaderColor, this.loaderPosition]"
/>
</label>
<p class="small">{{ errorMessage }}</p>
</div>
</template>
<script>
@ -22,19 +42,27 @@ import loader from "@/ux-components/loader/loader";
export default {
name: "radioList",
props: {
groupName: String, /* Required, unique for each radio button GROUP */
radioID: String, /* Required, unique for each radio button. Used for button id, label and <label for> */
radioLabelSubCopy: String, /* Optional, used for multi-line radio 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)) */
groupName: String /* Required, unique for each radio button GROUP */,
radioID:
String /* Required, unique for each radio button. Used for button id, label and <label for> */,
radioLabelSubCopy: String /* Optional, used for multi-line radio 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)) */,
},
data() {
return {
isError: false,
display: false
display: false,
};
},
methods: {