Merge branch 'develop' into feature/CSR-120_vehicle-banner-2

This commit is contained in:
Adam Caouette 2021-11-18 09:52:43 -05:00
commit fc8a9632cb
34 changed files with 905 additions and 368 deletions

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45.6 36.1" xml:space="preserve">
<path d="M43.6 0H18.1c-.4 0-.8.1-1.1.4l-.3.3-16 16c-.8.8-.8 2 0 2.8l16 16 .3.3c.3.2.7.4 1.1.4h25.5c1.1 0 2-.9 2-2V2c0-1.1-.9-2-2-2zm-3.4 28.1-1.4 1.4-10-10-10 10-1.4-1.4 10-10-10-10 1.4-1.4 10 10 10-10L40.2 8l-10 10 10 10.1z" fill="#8e9292"/>
</svg>

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1,3 @@
<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>

After

Width:  |  Height:  |  Size: 397 B

View file

@ -1,6 +0,0 @@
describe("baseTemplate.vue", () => {
it("Is a test", () => {
expect(true).toBe(true);
});
});

View file

@ -1,56 +0,0 @@
<template>
<div class="select-car sticky-lg-top py-4">
<div class="select-car-form overflow-hidden p-3 rounded">
<div class="car_info text-center">
<div class="car-image mb-3">
<img
class="vehicle-image img-fluid"
:class="{ blur: blurImg }"
:src="carImg"
/>
</div>
<div class="current_car_info">
<transition appear name="fade">
<div
class="current_car_info-text"
v-on:click="backFunction ? backFunction() : null"
>
<span class="text-center text-secondary fs-5 d-block">{{
this.currentCarInfo
}}</span>
<span v-if="style" class="text-center text-secondary fs-6 mt-2">{{
style
}}</span>
</div>
</transition>
</div>
<transition appear :name="slideTransition">
<div class="needed_car_info d-flex overflow-hidden mt-4 mb-3">
<span class="text-center fs-6 fw-bold needed_car_info-text">{{
this.neededCarInfo
}}</span>
</div>
</transition>
</div>
<transition appear :name="slideTransition">
<slot></slot>
</transition>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "BaseTemplate",
computed: {
...mapState(["slideTransition", "carImg", "blurImg", "style"]),
},
props: {
currentCarInfo: String,
neededCarInfo: String,
backFunction: Function,
},
};
</script>

View file

@ -1,24 +0,0 @@
<template>
<form class="list-view d-flex">
<div class="car_list">
<div v-for="attritbute in attritbutes" :key="attritbute" class="mb-2">
<buttonPrimary :buttonText="attritbute" v-on:click="selectAttribute(attritbute)" buttonType="btn-funnel" />
</div>
</div>
</form>
</template>
<script>
import buttonPrimary from "@/uxComponents/buttonPrimary/buttonPrimary";
export default {
name: "CarAttributes",
props: {
attritbutes: Array,
selectAttribute: Function,
},
components: {
buttonPrimary,
},
};
</script>

View file

@ -0,0 +1,22 @@
import { shallowMount } from '@vue/test-utils';
import radioQuestion from './radioQuestion';
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.", () => {
// Act
const wrapper = shallowMount(radioQuestion, {
propsData: {
questionText: 'Question Text',
answers: ['2023', '2022', '2021'],
chooseAnswer: function(test){ console.log(test) }
}
});
// Assert
expect(wrapper.find('.needed_car_info-text').text()).toEqual('Question Text');
const radioButtons = wrapper.findAllComponents('[data-test="radio"]');
expect(radioButtons[0].attributes('text')).toEqual('2023');
expect(radioButtons[2].attributes('text')).toEqual('2021');
expect(typeof wrapper.props().chooseAnswer).toBe('function');
})
})

View file

@ -0,0 +1,27 @@
<template>
<div class="radio_question">
<div class="needed_car_info d-flex mt-4 mb-3">
<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>
</div>
</div>
</template>
<script>
import radio from "@/uxComponents/radio/radio";
export default {
name: "radio-question",
props: {
questionText: String,
answers: Array,
chooseAnswer: Function
},
components: {
radio
}
};
</script>

View file

@ -2,6 +2,25 @@ const endpoints = {
GetRouteInfoEndpoint: {
url: '/content/api/v1/content/RouteInfo',
method: 'POST'
},
GetYears: {
url: '/vehicle/api/v1/vehicle/years',
method: 'GET'
},
GetMakes: {
url: '/vehicle/api/v1/vehicle/Makes',
method: 'POST'
},
GetModels: {
url: '/vehicle/api/v1/vehicle/Models',
method: 'POST'
},
GetStyles: {
url: '/vehicle/api/v1/vehicle/Styles',
method: 'POST'
},
GetPageData: {
method: 'GET'
}
}

View file

@ -1,5 +1,7 @@
const storeActions = {
GET_ROUTE_INFO_ACTION: "getRouteInfo",
GET_YEARS: 'getYears',
GET_PAGE_DATA: 'getMockPageData',
}
export { storeActions }

View file

@ -11,7 +11,7 @@ export default {
axios({
method: method,
url: `${apiGatewayUrl}${endpoint}`,
url: apiGatewayUrl + endpoint,
data: payloadAndAnalyticsData,
crossDomain: true,
responseType: {},
@ -29,4 +29,30 @@ export default {
);
});
},
/* istanbul ignore next */
callMockHttpClient({ method, endpoint}) {
// For Mock use only!
return new Promise((resolve, reject) => {
axios({
method: method,
url: endpoint,
crossDomain: true,
responseType: {},
}).then((response) => {
if (response.status == httpStatusCodes.OK) {
resolve(response);
} else {
reject(response);
}
},
(error) => {
return reject(error.response);
}
);
});
}
};

View file

@ -0,0 +1,73 @@
import globalMethods from "@/global-methods";
import axios from 'axios';
//Mock external dependencies
jest.mock('axios');
it("Global Methods - Call Http Client - Should Resolve Promise", () => {
//Arrange
const endpoint = 'https://mock.safelite.com';
const httpArgs = setupMocksForHttpClient({ endpoint: endpoint });
//Act
globalMethods.callHttpClient(httpArgs)
.then((response) => {
//Assert
expect(axios.mock.calls[0][0].url).toContain(endpoint);
expect(response.data.message).toContain('Success');
expect(response.status).toEqual(200);
});
});
it("Global Methods - Call Http Client - Should Reject Promise", () => {
//Arrange
const endpoint = 'https://mock.safelite.com';
const httpArgs = setupMocksForHttpClient({ endpoint: endpoint, isError: true });
//Act
globalMethods.callHttpClient(httpArgs)
.catch((err) => {
//Assert
expect(axios.mock.calls[0][0].url).toContain(endpoint);
expect(err.data.message).toContain('Error');
expect(err.status).toEqual(500);
});
});
function setupMocksForHttpClient({ endpoint = null, isError = false, additionalData = null }) {
//Clear node module
axios.mockClear();
// Success Response
const response = {
status: 200,
data: {
message: 'Success',
additionalData: additionalData
}
};
// Error Response
const error = {
response: {
status: 500,
data: {
message: 'Error',
additionalData: additionalData
}
}
}
// Error interceptor on Axios returns a different object, so we need to mimic that.
if (isError) {
axios.mockRejectedValue(error);
} else {
axios.mockResolvedValue(response);
}
return {
endpoint: endpoint
}
}

View file

@ -0,0 +1,26 @@
import { storeActions } from "@/constants/storeActions";
export function getMountOptions(mockData) {
// Define our mocks to attached to the 'global' object for Vue/Jest.
const mocks = {};
mocks.dispatchNonBlockingStoreAction = jest.fn();
mocks.dispatchNonBlockingStoreAction.mockImplementation((actionName) => {
let actionFilterResult = mockData.actionList.filter(x => x.actionName == actionName);
if (actionFilterResult.length > 0 && actionFilterResult.length === 1) {
return Promise.resolve({
data: actionFilterResult[0].data
});
}
});
// Mock store actions from js file
mocks.storeActions = storeActions;
const global = {
mocks: mocks
};
return { global }
}

View file

@ -1,55 +1,110 @@
<template>
<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">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>
</div>
</div>
<div class="row">
<div class="col my-3 d-flex align-items-center">
<buttonPrimary
buttonText="Primary"
buttonText="Primary"
/>
</div>
</div>
<div class="row">
<div class="col my-3 d-flex align-items-center">
<buttonSecondary
buttonText="Secondary"
buttonText="Secondary"
/>
</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"
buttonText="List Button"
errorText="Test error message"
/>
</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>
</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>
<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>
@ -57,6 +112,11 @@
<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>
@ -98,6 +158,71 @@
/>
</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">
<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">
<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">
<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">
<alert
alertClass="alert-success"
alertHeadline="No Body Copy Alert"
alertCopy=""
v-bind:isDismissible = "false"
/>
</div>
</div>
</div>
</template>
@ -107,6 +232,8 @@ import buttonSecondary from "@/uxComponents/buttonSecondary/buttonSecondary";
import radioCard from "@/uxComponents/radioCard/radioCard";
import listButton from "@/uxComponents/listButton/listButton";
import vehicleBanner from "@/commonComponents/vehicleBanner/vehicleBanner";
import radioList from "@/uxComponents/radioList/radioList";
import alert from "@/uxComponents/alert/alert";
export default {
name: "App",
@ -115,7 +242,14 @@ export default {
buttonSecondary,
radioCard,
listButton,
vehicleBanner
vehicleBanner,
radioList,
alert
},
data() {
return {
years: [2023, 2022, 2021, 2020],
};
}
};
</script>

View file

@ -1,77 +0,0 @@
<template>
<div>
<baseTemplate
:currentCarInfo="year + ' ' + make + ' ' + model"
neededCarInfo="What is your damage?"
>
<div class="damage-section text-center">
<div class="choose-damage row">
<div class="col">
<radioCard />
</div>
<div class="col">
<radioCard />
</div>
<div class="col">
<radioCard />
</div>
</div>
<transition name="fade">
<div v-if="damage" class="how-bad mt-3">
<span class="text-center fs-6 fw-bold">How bad is it?</span>
<div class="choose-how-bad row mt-2">
<div class="col">
<radioCard />
</div>
<div class="col">
<radioCard />
</div>
</div>
</div>
</transition>
<div class="next-action row">
<div class="col">
<div v-on:click="goBack" class="change">
<span>Change Vehicle</span>
</div>
</div>
</div>
</div>
</baseTemplate>
</div>
</template>
<script>
import baseTemplate from "@/commonComponents/baseTemplate/baseTemplate";
import radioCard from "@/uxComponents/radioCard/radioCard";
import { mapState } from "vuex";
export default {
name: "SelectCar",
computed: {
...mapState(["year", "make", "model"]),
},
data() {
return {
damage: "",
};
},
methods: {
chooseDamage(damage) {
this.damage = damage;
console.log(this.damage);
},
goBack() {
this.$store.state.slideTransition = "slide_reverse";
this.$store.commit("removeStyle");
this.$router.push(
`/select-style?year=${this.$store.state.year}&make=${this.$store.state.make}`
);
},
},
components: {
baseTemplate,
radioCard,
},
};
</script>

View file

@ -1,14 +1,23 @@
<template>
<div>
<baseTemplate :currentCarInfo="year" neededCarInfo="What make is your vehicle?" :backFunction="goBack">
<carAttributes :attritbutes="makes" :selectAttribute="selectMake" />
</baseTemplate>
</div>
<div class="select-car overflow-hidden">
<div class="select-car-form rounded text-center">
<Header
:text="year"
/>
<radioQuestion
questionText="What is the make of your vehicle?"
:answers="makes"
:chooseAnswer="selectMake"
/>
</div>
</div>
</template>
<script>
import baseTemplate from "@/commonComponents/baseTemplate/baseTemplate";
import carAttributes from "@/commonComponents/carAttributes/carAttributes";
import radioQuestion from "@/commonComponents/radioQuestion/radioQuestion";
import Header from "@/uxComponents/header/header";
import store from "@/store";
import router from "@/router";
import {
mapState
} from "vuex";
@ -20,7 +29,11 @@ export default {
},
methods: {
selectMake(make) {
this.$store.dispatch("selectMake", make);
this.dispatchNonBlockingStoreAction("selectMake", { make: make }).then( (response) => {
const models = response;
store.commit('updateMake', {make, models});
router.push(`/select-model?year=${store.year}&make=${make}`);
});
},
goBack() {
this.$store.state.slideTransition = "slide_reverse";
@ -28,8 +41,8 @@ export default {
},
},
components: {
baseTemplate,
carAttributes,
radioQuestion,
Header,
},
};
</script>

View file

@ -1,18 +1,23 @@
<template>
<div>
<baseTemplate
:currentCarInfo="year + ' ' + make"
:neededCarInfo="'What is the model of your ' + make + '?'"
:backFunction="goBack"
>
<carAttributes :attritbutes="models" :selectAttribute="selectModel" />
</baseTemplate>
<div class="select-car overflow-hidden">
<div class="select-car-form rounded text-center">
<Header
:text="year + ' ' + make"
/>
<radioQuestion
:questionText="'What is the model of your ' + make + '?'"
:answers="models"
:chooseAnswer="selectModel"
/>
</div>
</div>
</template>
<script>
import baseTemplate from "@/commonComponents/baseTemplate/baseTemplate";
import carAttributes from "@/commonComponents/carAttributes/carAttributes";
import radioQuestion from "@/commonComponents/radioQuestion/radioQuestion";
import Header from "@/uxComponents/header/header";
import store from "@/store";
import router from "@/router";
import { mapState } from "vuex";
export default {
@ -22,16 +27,20 @@ export default {
},
methods: {
selectModel(model) {
this.$store.dispatch("selectModel", model);
this.dispatchNonBlockingStoreAction("selectModel", { model: model }).then( (response) => {
const styles = response;
store.commit("updateModel", { model, styles });
router.push(`/select-style?year=${store.year}&make=${store.make}&model=${model}`);
});
},
goBack() {
this.$store.state.slideTransition = "slide_reverse";
this.$router.push(`/select-make?year=${this.$store.state.year}`);
this.$router.push(`/select-make?year=${store.year}`);
},
},
components: {
baseTemplate,
carAttributes,
radioQuestion,
Header,
},
};
</script>

View file

@ -1,18 +1,23 @@
<template>
<div>
<baseTemplate
:currentCarInfo="year + ' ' + make + ' ' + model"
:neededCarInfo="'What style is your ' + model + '?'"
:backFunction="goBack"
>
<carAttributes :attritbutes="styles" :selectAttribute="selectStyle" />
</baseTemplate>
<div class="select-car overflow-hidden">
<div class="select-car-form rounded text-center">
<Header
:text="year + ' ' + make + ' ' + model"
/>
<radioQuestion
:questionText="'What style is your ' + model + '?'"
:answers="styles"
:chooseAnswer="selectStyle"
/>
</div>
</div>
</template>
<script>
import baseTemplate from "@/commonComponents/baseTemplate/baseTemplate";
import carAttributes from "@/commonComponents/carAttributes/carAttributes";
import radioQuestion from "@/commonComponents/radioQuestion/radioQuestion";
import Header from "@/uxComponents/header/header";
import store from "@/store";
import router from "@/router";
import { mapState } from "vuex";
export default {
@ -22,18 +27,21 @@ export default {
},
methods: {
selectStyle(style) {
this.$store.dispatch("selectStyle", style);
store.commit("updateStyle", { style });
router.push(
`/select-damage?year=${store.year}&make=${store.make}&model=${store.model}&style=${style}`
);
},
goBack() {
this.$store.state.slideTransition = "slide_reverse";
this.$router.push(
`/select-model?year=${this.$store.state.year}&make=${this.$store.state.make}`
`/select-model?year=${store.year}&make=${store.make}`
);
},
},
components: {
baseTemplate,
carAttributes,
radioQuestion,
Header,
},
};
</script>

View file

@ -1 +1,55 @@
test.todo('some test to be written in the future');
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue'
import { getMountOptions } from '@/helpers/unitTestHelper.js';
import { storeActions } from '@/constants/storeActions.js'
import selectYear from './selectYear.vue';
describe('selectYear.vue', () => {
test("Should render the 'pageHeader.Text' data value 'text' prop value for the Header component, 'radioQuestion.Question' data value as 'questionText' prop value for the 'radioQuestion' component and 'years' values for 'answers' prop values for the 'radioQuestion' component.", async () => {
// Arrange
const mockDataAndAction = {
actionList: [
{
actionName: storeActions.GET_PAGE_DATA,
data: {
"Result": [
{
Type: "PageHeadingWidget",
Model: {
Text: "Mock Data"
}
},
{
Type: "RadioQuestionWidget",
Model: {
Question: "Mock Data"
}
}
]
}
},
{
actionName: storeActions.GET_YEARS,
data: ['2023', '2022', '2021']
}
]
};
const mountOptions = getMountOptions(mockDataAndAction);
// Act
const wrapper = shallowMount(selectYear, mountOptions);
await nextTick()
// Assert
const header = await wrapper.find('.Header');
expect(header.attributes('text')).toEqual("Mock Data");
const radioQuestion = await wrapper.findComponent('.radioQuestion');
expect(radioQuestion.attributes('answers')).toEqual('2023,2022,2021');
expect(radioQuestion.attributes('questiontext')).toEqual("Mock Data");
});
})

View file

@ -1,60 +1,78 @@
<template>
<div>
<baseTemplate :currentCarInfo="pageHeader.Text" :neededCarInfo="radioQuestion.Question">
<carAttributes :attritbutes="years" :selectAttribute="selectYear" />
</baseTemplate>
</div>
<div class="select-car">
<div class="select-car-form rounded text-center">
<Header
:text="pageHeader.Text" class="Header"
/>
<radioQuestion
class="radioQuestion"
:questionText="radioQuestion.Question"
:answers="this.years"
:chooseAnswer="selectYear"
/>
</div>
</div>
</template>
<script>
import baseTemplate from "@/commonComponents/baseTemplate/baseTemplate";
import carAttributes from "@/commonComponents/carAttributes/carAttributes";
import axios from "axios";
import radioQuestion from "@/commonComponents/radioQuestion/radioQuestion";
import Header from "@/uxComponents/header/header";
import store from "@/store";
import router from "@/router";
import { mapState } from "vuex";
export default {
name: "SelectCar",
data() {
return {
years: [],
pageHeader: {},
radioQuestion: {}
};
},
mounted() {
axios.get("https://mockey.qa.sagaws.net/service/years").then((response) => {
this.years = response.data.Result;
});
name: "SelectYear",
data() {
return {
years: [],
pageHeader: {},
radioQuestion: {}
};
},
computed: {
...mapState(["slideTransition", "carImg", "blurImg", "style"]),
},
mounted() {
// Mocking Sitefinity data to (For Page Header and Radio Question)
this.dispatchNonBlockingStoreAction(this.storeActions.GET_PAGE_DATA, {relativeUrl: 'https://mockey.qa.sagaws.net/service/content/selectYear'})
.then( (response) => {
response.data.Result.forEach((m) => {
switch (m.Type) {
case 'PageHeadingWidget': {
this.pageHeader = m.Model;
break;
}
case 'RadioQuestionWidget': {
this.radioQuestion = m.Model;
break;
}
default: {
break;
}
}
})
});
// Sample Call to Mockey to represent real data being sent back from the API, and how the modles are supposed to
// be prosessed and passed to child components.
axios.get("https://mockey.qa.sagaws.net/service/content/selectYear").then((response) => {
response.data.Result.forEach((m) => {
switch (m.Type) {
case this.widgetNames.PAGE_HEADING_WIDGET: {
this.pageHeader = m.Model;
break;
}
case this.widgetNames.RADIO_QUESTION_WIDGET: {
this.radioQuestion = m.Model;
break;
}
default: {
break;
}
}
})
});
},
methods: {
selectYear(year) {
this.$store.dispatch("selectYear", year);
},
},
components: {
baseTemplate,
carAttributes,
},
// Getting Years list
this.dispatchNonBlockingStoreAction(this.storeActions.GET_YEARS)
.then( (response) => {
this.years = response.data;
});
},
methods: {
selectYear(year) {
this.dispatchNonBlockingStoreAction("selectYear", { year: year }).then( (response) => {
const makes = response;
store.commit('updateYear', {year, makes});
router.push(`select-make?year=${year}`);
});
}
},
components: {
radioQuestion,
Header,
},
};
</script>

View file

@ -1,9 +1,7 @@
import { createStore } from "vuex";
import { endpoints } from "@/constants/endpoints.js";
import createPersistedState from "vuex-persistedstate";
import axios from "axios";
import globalMethods from "@/global-methods";
import router from "@/router";
export default createStore({
plugins: [
@ -27,17 +25,17 @@ export default createStore({
mutations: {
updateYear(state, data) {
state.year = data.year;
state.makes = data.makes;
state.makes = data.makes.data;
state.slideTransition = "slide";
},
updateMake(state, data) {
state.make = data.make;
state.models = data.models.data.Result;
state.models = data.models.data;
state.slideTransition = "slide";
},
updateModel(state, data) {
state.model = data.model;
state.styles = data.styles.data.Result;
state.styles = data.styles.data;
state.slideTransition = "slide";
},
updateStyle(state, data) {
@ -55,50 +53,60 @@ export default createStore({
},
},
actions: {
async selectYear(context, year) {
let makes = [];
await globalMethods
.callHttpClient({
method: "GET",
endpoint: `https://mockey.qa.sagaws.net/service/makes/year=${year}`,
payload: {},
})
.then(function (response) {
makes = response.data.Result;
getYears(){
return globalMethods.callHttpClient({
method: endpoints.GetYears.method,
endpoint: endpoints.GetYears.url,
payload:{}
});
context.commit("updateYear", { year, makes });
router.push(`/select-make?year=${year}`);
},
async selectMake({ commit, state }, make) {
const models = await axios.get(
`https://mockey.qa.sagaws.net/service/models/year=${state.year}/make=${make}`
);
commit("updateMake", { make, models });
router.push(`/select-model?year=${state.year}&make=${make}`);
},
async selectModel({ commit, state }, model) {
const styles = await axios.get(
`https://mockey.qa.sagaws.net/service/styles/year=${state.year}/make=${state.make}/model=${model}`
);
commit("updateModel", { model, styles });
router.push(
`/select-style?year=${state.year}&make=${state.make}&model=${model}`
);
},
selectStyle({ commit, state }, style) {
commit("updateStyle", { style });
router.push(
`/damage?year=${state.year}&make=${state.make}&model=${state.model}&style=${style}`
);
},
getRouteInfo(context, { pageName }) {
return globalMethods.callHttpClient({
method: endpoints.GetRouteInfoEndpoint.method,
endpoint: endpoints.GetRouteInfoEndpoint.url,
payload: {
pageName: pageName,
},
});
},
},
selectYear(context, { year }) {
return globalMethods.callHttpClient({
method: endpoints.GetMakes.method,
endpoint: endpoints.GetMakes.url,
payload:{
"Year": year
}
});
},
selectMake({ commit, state }, { make }) {
return globalMethods.callHttpClient({
method: endpoints.GetModels.method,
endpoint: endpoints.GetModels.url,
payload:{
"Year": state.year,
"Make": make
}
});
},
selectModel({ commit, state }, { model }) {
return globalMethods.callHttpClient({
method: endpoints.GetStyles.method,
endpoint: endpoints.GetStyles.url,
payload:{
"Year": state.year,
"Make": state.make,
"Model": model
}
});
},
getRouteInfo(context, { relativeUrl }) {
return globalMethods.callHttpClient({
method: endpoints.GetRouteInfoEndpoint.method,
endpoint: endpoints.GetRouteInfoEndpoint.url,
payload: {
relativeUrl: relativeUrl,
},
});
},
// For mock data purposes only
getMockPageData(context, { relativeUrl }) {
return globalMethods.callMockHttpClient({
method: endpoints.GetPageData.method,
endpoint: relativeUrl,
payload: {
},
});
},
},
});

View file

@ -1,61 +1,41 @@
.vehicle-select-attributes {
height: 100vh;
.select-car {
height: calc(100vh - 1rem);
.select-car-form {
background: $white;
height: 100%;
form {
height: calc(100% - 230px);
overflow-y: scroll;
overflow-x: hidden;
}
.car_list, .current_car_info-text, .needed_car_info-text {
flex-shrink: 0;
width: 100%;
position: relative;
transition: transform .3s;
}
}
.current_car_info {
position: relative;
min-height: 2.5rem;
.current_car_info-text {
position: absolute;
}
.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);
opacity: 0;
transform: translateX(30vw);
}
.slide-enter-active, .slide-leave-active {
transition: all .4s;
transition: all .4s;
}
.slide-leave-to {
opacity: 0;
transform: translateX(-30vw);
opacity: 0;
transform: translateX(-30vw);
}
.slide_reverse-enter-from {
opacity: 0;
transform: translateX(-30vw);
opacity: 0;
transform: translateX(-30vw);
}
.slide_reverse-enter-active, .slide_reverse-leave-active {
transition: all .4s;
transition: all .4s;
}
.slide_reverse-leave-to {
opacity: 0;
transform: translateX(30vw);
opacity: 0;
transform: translateX(30vw);
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
opacity: 0;
}
.fade-enter-active, .fade-leave-active {
transition: all .5s ease;
transition: all .5s ease;
}

View file

@ -0,0 +1,59 @@
.radiogroup {
&.radio-list-button {
input[type="radio"] {
opacity: 0;
position: fixed;
width: 0;
&:focus-visible + label {
box-shadow: 0 0 0 2px $blue;
}
&:focus + label {
box-shadow: 0 0 0 2px $blue;
}
&:checked + label {
background: $blue-100;
box-shadow: 0 0 0 1px $blue;
}
}
label {
position: relative;
background: $white;
height: 3rem;
transition: all 150ms linear;
border-radius: $border-radius-lg;
border: 1px solid $gray-500;
width: 100%;
&:hover {
box-shadow: 0 0 0 4px $blue-100;
cursor: pointer;
}
+ p {
display: none;
}
&.error {
border: 1px solid $danger;
+ p {
display: flex;
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

@ -163,3 +163,8 @@ $box-shadow: 0 .5rem 1rem rgba($black, .15);
$box-shadow-sm: 0 .125rem .25rem rgba($black, .075);
$box-shadow-lg: 0 1rem 3rem rgba($black, .25);//Safelite default
$box-shadow-inset: inset 0 1px 2px rgba($black, .075);
//Alerts
$alert-bg-scale: -90%;
$alert-border-scale: -100%;
$alert-color-scale: 40%;

View file

@ -0,0 +1,81 @@
<template>
<div class="alert fade show text-center mb-0" 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">
<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>
</template>
<script>
export default {
name: "alert",
props: {
alertHeadline: String,
alertCopy: String,
isDismissible: Boolean,
/*
alertClass class names:
alert-sucess (green)
alert-danger (red)
alert-warning (yellow)
alert-info (blue)
*/
alertClass: String
}
};
</script>
<style lang="scss">
.alert {
button {
display: none;
}
.btn-close {
background: none;
opacity: 1;
}
&.alert-dismissible {
button {
display: flex;
}
}
&.alert-info {
.alert-heading {
color: $blue-700;
}
svg {
fill: $blue-700;
}
}
&.alert-danger {
.alert-heading {
color: $red-700;
}
svg {
fill: $red-700;
}
}
&.alert-warning {
.alert-heading {
color: $yellow-700;
}
svg {
fill: $yellow-700;
}
}
&.alert-success {
.alert-heading {
color: $green-700;
}
svg {
fill: $green-700;
}
}
}
</style>

View file

@ -14,12 +14,12 @@
export default {
name: "buttonPrimary",
props: {
buttonText: String
buttonText: String,
isDisabled: Boolean
},
data() {
return {
isLoading: false,
isDisabled: false,
isLoading: false
};
},
methods: {

View file

@ -0,0 +1,16 @@
import { shallowMount } from '@vue/test-utils';
import Header from './header';
describe('Header.vue', () => {
it("Should render the 'text' prop value as a span value for the header span text value.", () => {
// Act
const wrapper = shallowMount(Header, {
propsData: {
text: 'Header Content'
}
});
// Assert
expect(wrapper.find('span').text()).toContain("Header Content");
})
});

View file

@ -0,0 +1,16 @@
<template>
<div class="current_car_info-text">
<div class="d-flex align-items-center justify-content-center">
<span class="text-center text-dark fs-5 d-block">{{ text }}</span>
</div>
</div>
</template>
<script>
export default {
name: "Header",
props: {
text: String
}
};
</script>

View file

@ -0,0 +1,23 @@
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.", () => {
// Act
const wrapper = shallowMount(radio, {
propsData: {
value: '2023',
text: '12'
}
});
// Assert
expect(wrapper.find('span').text()).toEqual('12');
expect(wrapper.find('input').attributes()).toEqual({
class: 'position-absolute opacity-0',
id: '2023',
type: 'radio',
value: '2023'
});
})
})

View file

@ -0,0 +1,39 @@
<template>
<div>
<input
type="radio"
:id="this.value"
:value="this.value"
class="position-absolute opacity-0"
v-on:click="showLoader()"
/>
<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>
</template>
<script>
export default {
name: "radio",
props: [
"value",
"text"
],
data() {
return {
isLoading: false,
};
},
methods: {
showLoader() {
this.isLoading = true;
},
},
};
</script>

View file

@ -0,0 +1,34 @@
<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>
</template>
<script>
export default {
name: "radioList",
props: [
"groupName",
"ariaLabelBy",
"radioID",
"errorMessage"
],
data() {
return {
isLoading: false,
isError: false
};
},
methods: {
showLoader() {
this.isLoading = true;
},
},
};
</script>

View file

@ -24,6 +24,7 @@ module.exports = {
@import "./node_modules/bootstrap/scss/bootstrap";
@import "@/styles/commonStyles.scss";
@import "@/styles/commonButtonStyles.scss";
@import "@/styles/commonRadioStyles.scss";
@import "@/styles/commonTypographyStyles.scss";
@import "@/styles/commonComponentStyles/ymmsCommonStyles.scss";
`

View file

@ -14,6 +14,7 @@ module.exports = {
@import "./node_modules/bootstrap/scss/bootstrap";
@import "@/styles/commonStyles.scss";
@import "@/styles/commonButtonStyles.scss";
@import "@/styles/commonRadioStyles.scss";
@import "@/styles/commonTypographyStyles.scss";
@import "@/styles/commonComponentStyles/ymmsCommonStyles.scss";
`