Merge branch 'develop' into feature/CSR-6

This commit is contained in:
Frank 2021-11-30 14:35:30 -05:00
commit 6bafa88c28
35 changed files with 1042 additions and 341 deletions

29
package-lock.json generated
View file

@ -14,7 +14,6 @@
"canvas-confetti": "^1.4.0",
"core-js": "^3.6.5",
"http-status-codes": "^2.1.4",
"jest-junit": "^13.0.0",
"vue": "^3.0.0",
"vue-router": "^4.0.11",
"vuex": "^4.0.2",
@ -32,6 +31,7 @@
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.0.0",
"jest-junit": "^13.0.0",
"prettier": "^2.2.1",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
@ -10962,6 +10962,7 @@
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-13.0.0.tgz",
"integrity": "sha512-JSHR+Dhb32FGJaiKkqsB7AR3OqWKtldLd6ZH2+FJ8D4tsweb8Id8zEVReU4+OlrRO1ZluqJLQEETm+Q6/KilBg==",
"dev": true,
"dependencies": {
"mkdirp": "^1.0.4",
"strip-ansi": "^6.0.1",
@ -10976,6 +10977,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -10984,6 +10986,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true,
"bin": {
"mkdirp": "bin/cmd.js"
},
@ -10995,6 +10998,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@ -11006,6 +11010,7 @@
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
@ -11743,9 +11748,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.6"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@ -18003,7 +18005,6 @@
"integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==",
"dev": true,
"dependencies": {
"chokidar": "^3.4.1",
"graceful-fs": "^4.1.2",
"neo-async": "^2.5.0"
},
@ -18067,7 +18068,6 @@
"anymatch": "^2.0.0",
"async-each": "^1.0.1",
"braces": "^2.3.2",
"fsevents": "^1.2.7",
"glob-parent": "^3.1.0",
"inherits": "^2.0.3",
"is-binary-path": "^1.0.0",
@ -18982,7 +18982,8 @@
"node_modules/xml": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
"integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU="
"integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=",
"dev": true
},
"node_modules/xml-name-validator": {
"version": "3.0.0",
@ -27872,6 +27873,7 @@
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-13.0.0.tgz",
"integrity": "sha512-JSHR+Dhb32FGJaiKkqsB7AR3OqWKtldLd6ZH2+FJ8D4tsweb8Id8zEVReU4+OlrRO1ZluqJLQEETm+Q6/KilBg==",
"dev": true,
"requires": {
"mkdirp": "^1.0.4",
"strip-ansi": "^6.0.1",
@ -27882,17 +27884,20 @@
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
@ -27900,7 +27905,8 @@
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
}
}
},
@ -34454,7 +34460,8 @@
"xml": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
"integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU="
"integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=",
"dev": true
},
"xml-name-validator": {
"version": "3.0.0",

View file

@ -22,7 +22,6 @@
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"jest-junit": "^13.0.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-unit-jest": "~4.5.0",
"@vue/cli-service": "~4.5.0",
@ -33,6 +32,7 @@
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.0.0",
"jest-junit": "^13.0.0",
"prettier": "^2.2.1",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" xml:space="preserve">
<path d="M24.01 48c-2.93 0-5.92-.52-8.85-1.58-6.25-2.25-11.33-7.33-13.58-13.57C-1.25 25-.25 16.76 4.34 10.22A24.05 24.05 0 0 1 24 0v2.26c-7.08 0-13.74 3.46-17.81 9.27-4.16 5.91-5.07 13.4-2.49 20.55 2.03 5.62 6.6 10.19 12.22 12.22 7.14 2.58 14.64 1.67 20.56-2.49A21.78 21.78 0 0 0 45.74 24H48c0 7.81-3.82 15.16-10.22 19.66A23.846 23.846 0 0 1 24.01 48z"/>
</svg>

After

Width:  |  Height:  |  Size: 446 B

View file

@ -19,8 +19,7 @@ describe("radioQuestion.vue", () => {
"Question Text"
);
const radioButtons = wrapper.findAllComponents('[data-test="radio"]');
expect(radioButtons[0].attributes("text")).toEqual("2023");
expect(radioButtons[2].attributes("text")).toEqual("2021");
expect(radioButtons.length).toBe(3);
expect(typeof wrapper.props().chooseAnswer).toBe("function");
});
});

View file

@ -1,18 +1,22 @@
<template>
<div class="radio_question">
<div class="needed_car_info d-flex mt-4 mb-3">
<div class="needed_car_info mt-4 mb-2 d-flex">
<span class="text-center fs-6 fw-bold w-100 needed_car_info-text">{{
questionText
}}</span>
</div>
<div class="car_list overflow-scroll position-absolute">
<div v-for="answer in answers" :key="answer" class="mb-2">
<radio
:text="answer"
:value="answer"
@click="chooseAnswer(answer)"
data-test="radio"
/>
<div class="w-100 d-flex justify-content-center">
<div class="car_list overflow-scroll position-absolute container-fluid 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" class="mb-2"
:radioID="answer"
@click="chooseAnswer(answer)"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1.5"
data-test="radio"
groupName="radio-list"
/>
</div>
</div>
</div>
@ -31,4 +35,4 @@ export default {
radio,
},
};
</script>
</script>

View file

@ -0,0 +1,144 @@
import { shallowMount } from '@vue/test-utils';
import vehicleBanner from './vehicle-banner';
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { storeActions } from "@/constants/store-actions";
import { nextTick } from 'vue';
describe('vehicleBanner', () => {
const $store = {
state: {
order: {
vehicle: {
vin: null,
year: null,
make: null,
model: null,
style: null
}
}
}
};
const actionList = {
actionList: [
{
actionName: storeActions.LOOKUP_VEHICLE_BY_YMMS,
data: [
{
CarId: "CR00068434"
}
]
},
{
actionName: storeActions.LOOKUP_VEHICLE_BY_VIN,
data: [
{
CarId: "CR00068434"
}
]
},
{
actionName: storeActions.GET_EVOX_IMAGE,
data: {
imgSrc: "https://s3.amazonaws.com/safelite-lab-vehicle-images/Evox/8963_cc0320_032_UG_white_2014_Ford_Focus_18029225538399034889.jpg"
}
}
]
};
test('renders the blurrycar image when vehicleImageSrc is not present', () => {
// Arrange
const mountOptions = getMountOptions(actionList);
mountOptions.global.mocks = {
...mountOptions.global.mocks,
$store
}
// Act
const wrapper = shallowMount(vehicleBanner, {
...mountOptions
});
// Assert
expect(wrapper.find('img').attributes('class')).toContain('blurrycar');
wrapper.unmount();
});
test('does not render the blurrycar image when vehicleImageSrc exists', () => {
// Arrange
const mountOptions = getMountOptions(actionList);
mountOptions.global.mocks = {
...mountOptions.global.mocks,
$store
}
// Act
const wrapper = shallowMount(vehicleBanner, {
...mountOptions,
data() {
return {
vehicleImageSrc: "https://s3.amazonaws.com/safelite-lab-vehicle-images/Evox/8963_cc0320_032_UG_white_2014_Ford_Focus_18029225538399034889.jpg"
}
}
});
// Assert
expect(wrapper.find('img').attributes('class')).not.toContain('blurrycar');
wrapper.unmount();
});
test('does not render the blurrycar image when passedCarId is set', async () => {
// Arrange
const mountOptions = getMountOptions(actionList);
mountOptions.global.mocks = {
...mountOptions.global.mocks,
$store,
}
// Act
const wrapper = shallowMount(vehicleBanner, {
...mountOptions,
props: {
passedCarId: "CR00068434"
}
});
// Assert
await nextTick();
expect(wrapper.find('img').attributes('class')).not.toContain('blurrycar');
wrapper.unmount();
});
test('receives a carId when a YMMS object is found', async () => {
// Arrange
$store.state.order.vehicle.year = "2019"
$store.state.order.vehicle.make = "acura"
$store.state.order.vehicle.model = "rdx"
$store.state.order.vehicle.style = "4 door utility"
const mountOptions = getMountOptions(actionList);
mountOptions.global.mocks = {
...mountOptions.global.mocks,
$store
}
// Act
const wrapper = shallowMount(vehicleBanner, {
...mountOptions
});
// Assert
await nextTick();
expect(wrapper.vm.$data.carId.length).toBeGreaterThan(0);
wrapper.unmount();
});
test('receives a carId when a VIN is found', async () => {
// Arrange
$store.state.order.vehicle.vin = "1J4GW58S4XC541166";
const mountOptions = getMountOptions(actionList);
mountOptions.global.mocks = {
...mountOptions.global.mocks,
$store
}
// Act
const wrapper = shallowMount(vehicleBanner, {
...mountOptions
});
// Assert
await nextTick();
expect(wrapper.vm.$data.carId.length).toBeGreaterThan(0);
wrapper.unmount();
});
});

View file

@ -0,0 +1,81 @@
<template>
<div class="vehicle_banner mb-3 text-center">
<img
class="vehicle-image img-fluid"
:src="vehicleImageSrc"
v-if="vehicleImageSrc"
alt=""
/>
<img
class="vehicle-image img-fluid blurrycar"
src='@/assets/img/blurrycar.png'
alt=""
v-else
/>
</div>
</template>
<script>
export default {
name: "vehicle-banner",
computed: {
vin () {
return this.$store.state.order.vehicle.vin;
},
ymms () {
return {
year: this.$store.state.order.vehicle.year,
make: this.$store.state.order.vehicle.make,
model: this.$store.state.order.vehicle.model,
style: this.$store.state.order.vehicle.style
}
}
},
props: {
passedCarId: String
},
data() {
return {
years: [],
carId: '',
vehicleImageSrc: '',
};
},
created() {
if (this.passedCarId && this.passedCarId.length > 0) {
this.getVehicleImage(this.passedCarId)
} else if (this.vin && this.vin.length > 0) {
this.getCarIdWithVin(this.vin)
} else if (!Object.values(this.ymms).includes(null) && !Object.values(this.ymms).includes('')) {
this.getCarIdWithYmms(this.ymms)
}
},
methods: {
getCarIdWithYmms(ymms) { // retrieve carId with YMMS object
this.dispatchNonBlockingStoreAction(this.storeActions.LOOKUP_VEHICLE_BY_YMMS, ymms).then((response) => {
this.carId = response.data[0].CarId;
this.getVehicleImage(this.carId)
});
},
getCarIdWithVin(vin) { // retrieve carId with VIN string
this.dispatchNonBlockingStoreAction(this.storeActions.LOOKUP_VEHICLE_BY_VIN, vin).then((response) => {
this.carId = response.data[0].CarId;
this.getVehicleImage(this.carId)
});
},
getVehicleImage(carId) { // look up evox with carId
this.dispatchNonBlockingStoreAction(this.storeActions.GET_EVOX_IMAGE, {relativeUrl: "https://mockey.qa.sagaws.net/service/evox-image?carId=" + carId}).then((response) => {
if (response.data && response.data.imgSrc) {
this.vehicleImageSrc = response.data.imgSrc;
}
});
}
}
}
</script>
<style lang="scss" scoped>
.vehicle-image {
max-width: 290px;
}
</style>

View file

@ -23,6 +23,14 @@ const endpoints = {
url: "/content/api/v1/content",
method: "GET",
},
LookupVehicleByYmms: {
url: "/vehicle/api/v1/vehicle/Lookup",
method: "GET",
},
LookupVehicleByVin: {
url: "/vehicle/api/v1/vehicle/Lookup",
method: "POST",
},
};
export { endpoints };

View file

@ -5,6 +5,9 @@ const storeActions = {
GET_VEHICLE_MAKES: "getVehicleMakes",
GET_VEHICLE_MODELS: "getVehicleModels",
GET_VEHICLE_STYLES: "getVehicleStyles",
GET_EVOX_IMAGE: "getEvoxImage",
LOOKUP_VEHICLE_BY_YMMS: "lookupVehicleByYmms",
LOOKUP_VEHICLE_BY_VIN: "lookupVehicleByVin"
};
export { storeActions };

View file

@ -1,4 +1,5 @@
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
export function getMountOptions(mockData) {
// Define our mocks to attached to the 'global' object for Vue/Jest.
@ -22,7 +23,8 @@ export function getMountOptions(mockData) {
const global = {
mocks: mocks,
plugins: [store]
};
return { global };
}
}

View file

@ -1,125 +1,273 @@
<template>
<div class="container-fluid container-shadow p-2 rounded-3">
<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>
</div>
</div>
<div class="row g-2">
<radioCard 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" />
<radioCard radioLabel="Back Glass" radioImage="back-glass-damage.svg" altText="Back Glass" groupName="damageKey" radioID="backglass" />
<radioCard
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"
/>
<radioCard
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>
</div>
</div>
<div class="row">
<div class="col my-3 d-flex align-items-center">
<buttonPrimary buttonText="Primary" />
</div>
<div class="col my-3 d-flex align-items-center">
<buttonPrimary
buttonText="Primary"
loaderColor="white"
loaderPosition="right"
sizeInRem="1"
/>
</div>
</div>
<div class="row">
<div class="col my-3 d-flex align-items-center">
<buttonSecondary buttonText="Secondary" />
</div>
<div class="col my-3 d-flex align-items-center">
<buttonSecondary
buttonText="Secondary"
loaderColor="white"
loaderPosition="right"
sizeInRem="1"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<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" />
</div>
<div class="col my-3 d-flex align-items-center">
<listButton
buttonText="List Button"
errorText="Test error message"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1.5"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Radio Button Group</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="select-year-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="select-year-radio-group">
Select Vehicle Year
</h3>
<radioList groupName="demo" ariaLabelBy="vehicle-year" radioID="2021" errorMessage="Test error message" />
<radioList groupName="demo" ariaLabelBy="vehicle-year" radioID="2020" errorMessage="Test error message" />
<radioList groupName="demo" ariaLabelBy="vehicle-year" radioID="2019" errorMessage="Test error message" />
</div>
<!-- The role="radiogroup" and aria-labelledby must be included in the parent component for the radio group -->
<div role="radiogroup" aria-labelledby="select-year-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="select-year-radio-group">Select Vehicle Year</h3>
<radioList
groupName="demo"
ariaLabelBy="vehicle-year"
radioID="2021"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1.5"
/>
<radioList
groupName="demo"
ariaLabelBy="vehicle-year"
radioID="2020"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1.5"
/>
<radioList
groupName="demo"
ariaLabelBy="vehicle-year"
radioID="2019"
loaderColor="blue"
loaderPosition="right"
sizeInRem="1.5"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Text Link</h4>
</div>
</div>
<div class="row">
<div class="col my-3 d-flex align-items-center">
<a href="#">Text Link</a>
</div>
<div class="col my-3 d-flex align-items-center">
<a href="#">Text Link</a>
</div>
</div>
<div class="row my-4">
<div class="col">
<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>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Headings</h4>
</div>
</div>
<div class="row my-2">
<div class="col">
<h1>h1 Heading</h1>
</div>
</div>
<div class="row my-2">
<div class="col">
<h2>h2 Heading</h2>
</div>
</div>
<div class="row my-2">
<div class="col">
<h3>h3 Heading</h3>
</div>
</div>
<div class="row my-2">
<div class="col">
<h4>h4 Heading</h4>
</div>
</div>
<div class="row my-2">
<div class="col">
<h5>h5 Heading</h5>
</div>
</div>
<div class="row my-2">
<div class="col">
<h6>h6 Heading</h6>
</div>
</div>
<div class="row my-4">
<div class="col">
<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"
/>
</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">
<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>
</div>
<alert
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">
<h1>h1 Heading</h1>
</div>
<div class="col">
<alert
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">
<h2>h2 Heading</h2>
</div>
<div class="col">
<alert
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">
<h3>h3 Heading</h3>
</div>
<div class="col">
<alert
alertClass="alert-success"
alertHeadline="No Body Copy Alert"
alertCopy=""
v-bind:isDismissible = "false"
/>
</div>
</div>
<div class="row my-2">
<div class="col">
<h4>h4 Heading</h4>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Vehicle Banner</h4>
</div>
</div>
<div class="row my-2">
<div class="col">
<h5>h5 Heading</h5>
</div>
<div class="row my-3">
<div class="col">
<vehicleBanner
passedCarId=""
/>
<vehicleBanner
passedCarId="CR00068434"
/>
</div>
</div>
<div class="row my-2">
<div class="col">
<h6>h6 Heading</h6>
</div>
</div>
<div class="row">
<div class="col my-3 d-flex align-items-center">
<buttonPrimary buttonText="Primary" @click="testMethod" />
</div>
</div>
<div class="my-3">
<div class="select-car">
<div class="select-car-form">
<radioQuestion questionText="What year is your vehicle?" :answers="this.years" />
</div>
</div>
</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 radioList from "@/ux-components/radio-list/radio-list";
export default {
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 radioList from "@/ux-components/radio-list/radio-list";
import alert from "@/ux-components/alert/alert";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
export default {
name: "App",
components: {
buttonPrimary,
buttonSecondary,
radioCard,
listButton,
radioList,
buttonPrimary,
buttonSecondary,
radioCard,
listButton,
radioList,
alert,
vehicleBanner
},
data() {
return {
years: [2023, 2022, 2021, 2020],
};
},
created() {
console.log("App created");
},
methods: {
testMethod() {
this.$router.push("/?fmgPage=vehicle-year");
},
},
};
return {
years: [2023, 2022, 2021, 2020],
};
}
};
</script>

View file

@ -0,0 +1,28 @@
<template>
<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">Loader</h4>
</div>
</div>
<div class="row">
<div class="col my-3 d-flex align-items-center">
<loader
sizeInRem="10"
loaderColor="blue"
loaderPosition="center"
/>
</div>
</div>
</div>
</template>
<script>
import loader from "@/ux-components/loader/loader";
export default {
name: "App",
components: {
loader
}
};
</script>

View file

@ -55,3 +55,15 @@ export default {
},
};
</script>
<style lang="scss">
.select-car {
height: calc(100vh - 1rem);
.car_list {
// Height will be determined by overall height of content above list
height: calc(100% - 255px);
width: calc(100% - 1rem);
}
}
</style>

View file

@ -8,13 +8,13 @@ export default {
}
},
methods: {
dispatchBlockingStoreAction(type, payload) {
// globalMethods.showWaitingModal(true);
// dispatchBlockingStoreAction(type, payload) {
// // globalMethods.showWaitingModal(true);
return this.dispatchNonBlockingStoreAction(type, payload).finally(() => {
// globalMethods.showWaitingModal(false);
});
},
// return this.dispatchNonBlockingStoreAction(type, payload).finally(() => {
// // globalMethods.showWaitingModal(false);
// });
// },
dispatchNonBlockingStoreAction(type, payload, encodePayload = false) {
// Encode the payload if required
if (encodePayload) {
@ -23,6 +23,30 @@ export default {
return this.$store.dispatch(type, payload);
},
GetContentFromCms() {
return this.dispatchNonBlockingStoreAction(this.storeActions.GET_PAGE_DATA,{ pageName: this.$route.query.fmgPage }).then((response) => {
const pageDataFromCms = {};
response.data.Result.forEach((widget) => {
if (Object.values(this.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];
}
});
// Our 'Page' is ready because we have data now
this.isCmsContentReady = true;
return pageDataFromCms;
});
},
},
computed: {
storeActions() {

View file

@ -2,6 +2,7 @@ import { createWebHistory, createRouter } from "vue-router";
import { storeActions } from "@/constants/store-actions.js";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import ComponentTest from "@/layouts/component-test/component-test.vue";
import LoaderDemo from "@/layouts/loader-demo/loader-demo.vue";
import NotFound from "@/layouts/not-found/not-found.vue";
import store from "@/store";
@ -16,6 +17,11 @@ const routes = [
name: "ComponentTest",
component: ComponentTest,
},
{
path: "/loader-demo", // This is a temporary route for testing.
name: "LoaderDemo",
component: LoaderDemo,
},
{
path: "/",
beforeEnter(to, from, next) {

View file

@ -22,6 +22,7 @@ export default createStore({
evoxImageId: null,
hasSplitWindshieldOption: null,
hasBackglassSliderOption: null,
imageSrc: '',
registration: {
rawAddress: null,
zipCode: null,
@ -62,7 +63,11 @@ export default createStore({
experiments: null,
}
},
mutations: {},
mutations: {
updateVehicleImage(state, data) {
state.order.vehicle.imageSrc = data.imgSrc;
},
},
actions: {
// Vehicle API Actions
getVehicleYears(context) {
@ -72,6 +77,22 @@ export default createStore({
payload: {},
});
},
lookupVehicleByYmms(context, { year, make, model, style }) {
return globalMethods.callHttpClient({
method: endpoints.LookupVehicleByYmms.method,
endpoint: `${endpoints.LookupVehicleByYmms.url}/${year}/${make}/${model}/${style}`,
payload: {},
});
},
lookupVehicleByVin(context, { vin }) {
return globalMethods.callHttpClient({
method: endpoints.LookupVehicleByVin.method,
endpoint: endpoints.LookupVehicleByVin.url,
payload: {
"vin": vin // EX "1J4GW58S4XC541166"
},
});
},
getVehicleMakes(context, { year }) {
return globalMethods.callHttpClient({
method: endpoints.GetVehicleMakes.method,
@ -111,5 +132,12 @@ export default createStore({
payload: {},
});
},
getEvoxImage(context, { relativeUrl }) {
return globalMethods.callMockHttpClient({
method: endpoints.GetPageData.method,
endpoint: relativeUrl,
payload: {},
});
},
},
});

112
src/store/store.spec.js Normal file
View file

@ -0,0 +1,112 @@
import store from './index'
import globalMethods from '@/global-methods'
describe("Actions", () => {
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;
console.log(response);
});
// Assert
expect(years[0]).toBe(2023);
});
it("Should return list of makes retrieved", async () => {
// Arrange
let makes = [];
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: ['Baic','Honda','Ford']});
});
await store.dispatch('getVehicleMakes', {year: 2023})
.then( (response) => {
makes = response.data;
});
// Assert
expect(makes[0]).toBe('Baic');
});
it("Should return list of models retrieved", async () => {
// Arrange
let models = [];
// 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;
});
// Assert
expect(models[0]).toBe('BJ40 (MEX)');
});
it("Should return list of styles retrieved", async () => {
// Arrange
let styles = [];
// 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;
});
// Assert
expect(styles[0]).toBe('4 DOOR UTILITY');
});
it("Should return data from url retrieved", async () => {
// Arrange
let routeInfo = [];
// 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;
});
// Assert
expect(routeInfo).toBe('Route Info Data');
});
it("Should return page data from url retrieved", async () => {
// Arrange
let pageData = [];
// 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');
});
})

View file

@ -16,23 +16,8 @@
&:focus-visible { // Keyboard focus for accessibility
outline: none;
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
}
&.button-loader {
padding: 0 40px 0 12px;
&:after {
content: url(../../assets/img/icons/button-spinner-white.svg);
position: absolute;
right: 14px;
width: 16px;
height: 16px;
margin-left: 12px;
animation: rotation 1s infinite linear;
@keyframes rotation {
100% {
transform:rotate(360deg);
}
}
}
color: $white;
@include blue-gradient;
}
&:disabled {
background: $gray-100 !important;
@ -59,39 +44,8 @@
&:focus-visible { // Keyboard focus for accessibility
outline: none;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $blue-700;
}
&.button-loader {
padding: 0 40px 0 12px;
&:after {
content: url(../../assets/img/icons/button-spinner-blue.svg);
position: absolute;
right: 14px;
width: 16px;
height: 16px;
margin-left: 12px;
animation: rotation 1s infinite linear;
@keyframes rotation {
100% {
transform:rotate(360deg);
}
}
}
&:focus:hover {
&:after {
content: url(../../assets/img/icons/button-spinner-white.svg);
position: absolute;
right: 14px;
width: 16px;
height: 16px;
margin-left: 12px;
animation: rotation 1s infinite linear;
@keyframes rotation {
100% {
transform:rotate(360deg);
}
}
}
}
color: $white;
@include blue-gradient;
}
&:disabled {
background: transparent;
@ -130,22 +84,5 @@
color: $danger;
}
}
&.button-loader {
padding: 0 40px 0 12px;
&:after {
content: url(../../assets/img/icons/button-spinner-blue.svg);
position: absolute;
right: 14px;
width: 16px;
height: 16px;
margin-left: 12px;
animation: rotation 1s infinite linear;
@keyframes rotation {
100% {
transform:rotate(360deg);
}
}
}
}
}
}

View file

@ -1,41 +0,0 @@
.select-car {
height: calc(100vh - 1rem);
.car_list {
// Height will be determined by overall height of content above list
height: calc(100% - 100px);
width: calc(100% - 1rem);
}
}
// Animations
.slide-enter-from {
opacity: 0;
transform: translateX(30vw);
}
.slide-enter-active, .slide-leave-active {
transition: all .4s;
}
.slide-leave-to {
opacity: 0;
transform: translateX(-30vw);
}
.slide_reverse-enter-from {
opacity: 0;
transform: translateX(-30vw);
}
.slide_reverse-enter-active, .slide_reverse-leave-active {
transition: all .4s;
}
.slide_reverse-leave-to {
opacity: 0;
transform: translateX(30vw);
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.fade-enter-active, .fade-leave-active {
transition: all .5s ease;
}

View file

@ -18,7 +18,7 @@
label {
position: relative;
background: $white;
height: 3rem;
min-height: 3rem;
transition: all 150ms linear;
border-radius: $border-radius-lg;
border: 1px solid $gray-500;
@ -37,23 +37,6 @@
color: $danger;
}
}
&.button-loader {
padding: 0 40px 0 12px;
&:after {
content: url(../../assets/img/icons/button-spinner-blue.svg);
position: absolute;
right: 14px;
width: 16px;
height: 16px;
margin-left: 12px;
animation: rotation 1s infinite linear;
@keyframes rotation {
100% {
transform:rotate(360deg);
}
}
}
}
}
}
}

View file

@ -22,10 +22,3 @@ body {
cursor: pointer;
}
}
//Custom Mixins
//Blue gradient background mixin
@mixin blue-gradient {
background: linear-gradient(270deg, $blue-500 0%, $blue-700 100%);
}

View file

@ -43,3 +43,15 @@ caption {
line-height: 1.7;
font-weight: 400;
}
// Font size
.fs-5 {
line-height: 2;
}
.fs-6 {
font-size: 1rem !important;
line-height: 1.4;
font-weight: 500;
}

View file

@ -0,0 +1,6 @@
//Custom Mixins
//Blue gradient background mixin
@mixin blue-gradient {
background: linear-gradient(270deg, $blue-500 0%, $blue-700 100%);
}

View file

@ -1,10 +1,10 @@
<template>
<div class="alert fade show text-center mb-0" role="alert"
<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 alert-heading">{{alertHeadline}}</p>
<p class="m-0 text-body">{{alertCopy}}</p>
<button type="button" class="btn-close" 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>
@ -39,42 +39,58 @@ export default {
.btn-close {
background: none;
opacity: 1;
width: .75rem;
height: .75rem;
}
&.alert-dismissible {
button {
display: flex;
top: 2px;
right: 2px;
}
}
&.alert-info {
background-color: $blue-100;
.alert-heading {
color: $blue-700;
}
svg {
fill: $blue-700;
width: 1rem;
height: 1rem;
}
}
&.alert-danger {
background-color: $red-100;
.alert-heading {
color: $red-700;
color: $red-600;
}
svg {
fill: $red-700;
fill: $red-600;
width: 1rem;
height: 1rem;
}
}
&.alert-warning {
background-color: $yellow-100;
.alert-heading {
color: $yellow-700;
color: $yellow-600;
}
svg {
fill: $yellow-700;
fill: $yellow-600;
width: 1rem;
height: 1rem;
}
}
&.alert-success {
background-color: $green-100;
.alert-heading {
color: $green-700;
}
svg {
fill: $green-700;
width: 1rem;
height: 1rem;
}
}
}

View file

@ -3,29 +3,41 @@
:disabled="isDisabled"
:aria-disabled="isDisabled"
class="btn btn-primary d-flex align-items-center"
v-on:click="showLoader()"
v-bind:class="[this.isLoading ? 'button-loader' : 'not-loading']"
>
@click='displayComponent'
>
{{ this.buttonText }}
<loader
class="ms-2"
v-if="display"
v-bind:style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}"
v-bind:class="[this.loaderColor, this.loaderPosition]"
/>
</button>
</template>
<script>
import loader from "@/ux-components/loader/loader";
export default {
name: "buttonPrimary",
props: {
buttonText: String,
isDisabled: Boolean,
loaderColor: String,
loaderPosition: String,
sizeInRem: Number
},
data() {
return {
isLoading: false,
display: false,
};
},
methods: {
showLoader() {
this.isLoading = true;
displayComponent() {
this.display = true;
},
},
components: {
loader,
},
};
</script>

View file

@ -3,29 +3,41 @@
:disabled="isDisabled"
:aria-disabled="isDisabled"
class="btn btn-secondary d-flex align-items-center"
v-on:click="showLoader()"
v-bind:class="[this.isLoading ? 'button-loader' : 'not-loading']"
>
@click='displayComponent'
>
{{ this.buttonText }}
<loader
class="ms-2"
v-if="display"
v-bind:style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}"
v-bind:class="[this.loaderColor, this.loaderPosition]"
/>
</button>
</template>
<script>
import loader from "@/ux-components/loader/loader";
export default {
name: "buttonSecondary",
name: "buttonPrimary",
props: {
buttonText: String,
isDisabled: Boolean,
loaderColor: String,
loaderPosition: String,
sizeInRem: Number
},
data() {
return {
isLoading: false,
isDisabled: false,
display: false,
};
},
methods: {
showLoader() {
this.isLoading = true;
displayComponent() {
this.display = true;
},
},
components: {
loader,
},
};
</script>

View file

@ -1,36 +1,48 @@
<template>
<div class="d-flex flex-column w-100">
<button
class="btn list-button d-flex align-items-center"
v-on:click="showLoader()"
v-bind:class="[
this.isLoading ? 'button-loader' : 'not-loading',
this.isError ? 'error' : '',
]"
class="btn list-button d-flex align-items-center justify-content-between"
@click='displayComponent'
v-bind:class="[
this.isLoading ? 'button-loader' : 'not-loading',
this.isError ? 'error' : '',
]"
>
{{ this.buttonText }}
</button>
<label class="small mt-1">{{ this.errorText }}</label>
</div>
{{ this.buttonText }}
<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>
import loader from "@/ux-components/loader/loader";
export default {
name: "listButton",
props: {
buttonText: String,
errorText: String,
loaderColor: String,
loaderPosition: String,
sizeInRem: Number
},
data() {
return {
isLoading: false,
isError: false,
display: false,
isError: false
};
},
methods: {
showLoader() {
this.isLoading = true;
displayComponent() {
this.display = true;
},
},
components: {
loader,
},
};
</script>

View file

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

View file

@ -0,0 +1,88 @@
<template>
<div class="loader"
v-bind:style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}"
v-bind:class="[this.loaderColor, this.loaderPosition]"
></div>
</template>
<script>
export default {
name: "loader",
/* Specify size in number value which translates to rem value. For example, 1.5 = 1.5rem */
props: {
sizeInRem: {
type: Number,
default: 1
},
/* Color options: red, green, blue, white, black */
loaderColor: {
type: String
},
/* Position options: center, right, left (OPTIONAL) */
loaderPosition: {
type: String
}
},
}
</script>
<style lang="scss">
.loader {
display: flex;
//Open an overlay to prevent page interaction
&:before {
content: '';
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: transparent;
z-index: 9999;
cursor: default;
}
//Spinner basics
&:after {
content: '';
mask: url(../../assets/img/icons/spinner.svg);
mask-size: cover;
position: relative;
width: 100%;
height: 100%;
animation: rotation 1s infinite linear;
@keyframes rotation {
100% {
transform:rotate(360deg);
}
}
}
//Spinner position
&.center {
left: 0;
right: 0;
margin: 0 auto;
}
&.right {
margin: 0 0 0 auto;
}
&.left {
margin: 0 auto 0 0;
}
//Spinner color
&:after { //Default spinner color (blue) if no other color is specified from the options below
background-color: $blue;
}
&.red:after {
background-color: $red;
}
&.green:after {
background-color: $green;
}
&.white:after {
background-color: $white;
}
&.black:after {
background-color: $black;
}
}
</style>

View file

@ -1,28 +1,51 @@
<template>
<div class="radiogroup radio-list-button d-flex flex-column w-100">
<input type="radio" v-bind:id="radioID" v-bind:name="groupName" v-bind:value="radioID" />
<label role="radio" tabindex="0" aria-checked="false" v-bind:for="radioID" class="mb-2 d-flex align-items-center p-2" v-on:click="showLoader()" v-bind:class="[
this.isLoading ? 'button-loader' : 'not-loading',
this.isError ? 'error' : '',
]">{{ radioID }}</label>
<p class="small">{{ errorMessage }}</p>
<div class="radiogroup radio-list-button d-flex flex-column w-100">
<input type="radio"
v-bind:id="radioID"
v-bind:name="groupName"
v-bind:value="radioID">
<label role="radio" tabindex="0" aria-checked="false"
v-bind:for="radioID"
class="mb-2 d-flex align-items-center justify-content-between p-2"
@click='displayComponent'
>{{radioID}}
<loader
v-if="display"
v-bind:style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}"
v-bind:class="[this.loaderColor, this.loaderPosition]"
/>
</label>
<p class="small">{{errorMessage}}</p>
</div>
</template>
<script>
import loader from "@/ux-components/loader/loader";
export default {
name: "radioList",
props: ["groupName", "ariaLabelBy", "radioID", "errorMessage"],
data() {
return {
isLoading: false,
isError: false,
};
},
methods: {
showLoader() {
this.isLoading = true;
},
name: "radioList",
props: [
"groupName",
"ariaLabelBy",
"radioID",
"errorMessage",
"loaderColor",
"loaderPosition",
"sizeInRem"
],
data() {
return {
isError: false,
display: false
};
},
methods: {
displayComponent() {
this.display = true;
},
},
components: {
loader,
},
};
</script>

View file

@ -2,22 +2,46 @@ import { shallowMount } from "@vue/test-utils";
import radio from "./radio";
describe("radio.vue", () => {
it("Should render the 'text' prop value as a span value for the radio button label and add the 'value' prop value as the radio button value and id.", () => {
it("Should render the 'radioID' prop value as the label and id value as well as the radio button value, groupName as the name value, and fire a click even that sets the display data attribute to true.", async () => {
// Act
const wrapper = shallowMount(radio, {
propsData: {
value: "2023",
text: "12",
radioID: "2023",
groupName: "TestGroup",
loaderColor: "blue",
loaderPosition: "right",
sizeInRem: "1.5",
errorMessage: 'null'
},
});
// Assert
expect(wrapper.find("span").text()).toEqual("12");
expect(wrapper.find("input").attributes()).toEqual({
class: "position-absolute opacity-0",
const input = wrapper.find("input");
const label = wrapper.find("label");
const paragraph = wrapper.find("p");
await label.trigger('click');
expect(input.attributes()).toEqual({
id: "2023",
type: "radio",
value: "2023",
name: "TestGroup",
});
expect(label.attributes()).toEqual({
role: "radio",
tabindex: "0",
for: "2023",
class: "d-flex align-items-center justify-content-between p-2",
"aria-checked": "false"
});
expect(label.text()).toEqual('2023');
expect(paragraph.text()).toEqual('null');
expect(wrapper.vm.display).toBe(true);
});
});

View file

@ -1,35 +1,49 @@
<template>
<div>
<input
type="radio"
:id="this.value"
:value="this.value"
class="position-absolute opacity-0"
v-on:click="showLoader()"
<div class="radiogroup radio-list-button d-flex flex-column w-100">
<input type="radio"
:id="radioID"
:name="groupName"
:value="radioID">
<label role="radio" tabindex="0" aria-checked="false"
:for="radioID"
class="d-flex align-items-center justify-content-between p-2"
@click='displayComponent'
>{{radioID}}
<loader
v-if="display"
:style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}"
:class="[this.loaderColor, this.loaderPosition]"
/>
<label
:for="this.value"
class="btn list-button d-flex align-items-center"
v-bind:class="[this.isLoading ? 'button-loader' : '']"
>
<span>{{ text }}</span>
</label>
</div>
</label>
<p class="small">{{errorMessage}}</p>
</div>
</template>
<script>
import loader from "@/ux-components/loader/loader";
export default {
name: "radio",
props: ["value", "text"],
name: "radioList",
props: [
"groupName",
"radioID",
"errorMessage",
"loaderColor",
"loaderPosition",
"sizeInRem"
],
data() {
return {
isLoading: false,
isError: false,
display: false
};
},
methods: {
showLoader() {
this.isLoading = true;
displayComponent() {
this.display = true;
},
},
components: {
loader,
},
};
</script>

View file

@ -13,12 +13,12 @@ module.exports = {
@import "@/styles/ux-variables.scss";
@import "./node_modules/bootstrap/scss/variables";
@import "./node_modules/bootstrap/scss/mixins";
@import "@/styles/mixins/customMixins";
@import "./node_modules/bootstrap/scss/bootstrap";
@import "@/styles/common-styles.scss";
@import "@/styles/common-button-styles.scss";
@import "@/styles/common-radio-styles.scss";
@import "@/styles/common-typography-styles.scss";
@import "@/styles/common-component-styles/ymms-common-styles.scss";
`,
},
},

View file

@ -12,12 +12,12 @@ module.exports = {
@import "@/styles/ux-variables.scss";
@import "./node_modules/bootstrap/scss/variables";
@import "./node_modules/bootstrap/scss/mixins";
@import "@/styles/mixins/customMixins";
@import "./node_modules/bootstrap/scss/bootstrap";
@import "@/styles/common-styles.scss";
@import "@/styles/common-button-styles.scss";
@import "@/styles/common-radio-styles.scss";
@import "@/styles/common-typography-styles.scss";
@import "@/styles/common-component-styles/ymms-common-styles.scss";
`,
},
},