diff --git a/src/assets/img/icons/back-forward.svg b/src/assets/img/icons/back-forward.svg new file mode 100644 index 000000000..3835f94cc --- /dev/null +++ b/src/assets/img/icons/back-forward.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/commonComponents/baseTemplate/baseTemplate.spec.js b/src/commonComponents/baseTemplate/baseTemplate.spec.js deleted file mode 100644 index c308fd2f7..000000000 --- a/src/commonComponents/baseTemplate/baseTemplate.spec.js +++ /dev/null @@ -1,6 +0,0 @@ - -describe("baseTemplate.vue", () => { - it("Is a test", () => { - expect(true).toBe(true); - }); -}); diff --git a/src/commonComponents/baseTemplate/baseTemplate.vue b/src/commonComponents/baseTemplate/baseTemplate.vue deleted file mode 100644 index 0263616b0..000000000 --- a/src/commonComponents/baseTemplate/baseTemplate.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - diff --git a/src/commonComponents/carAttributes/carAttributes.spec.js b/src/commonComponents/carAttributes/carAttributes.spec.js deleted file mode 100644 index 882a68129..000000000 --- a/src/commonComponents/carAttributes/carAttributes.spec.js +++ /dev/null @@ -1 +0,0 @@ -test.todo('some test to be written in the future'); \ No newline at end of file diff --git a/src/commonComponents/carAttributes/carAttributes.vue b/src/commonComponents/carAttributes/carAttributes.vue deleted file mode 100644 index 0297654ee..000000000 --- a/src/commonComponents/carAttributes/carAttributes.vue +++ /dev/null @@ -1,24 +0,0 @@ - - - diff --git a/src/commonComponents/radioQuestion/radioQuestion.spec.js b/src/commonComponents/radioQuestion/radioQuestion.spec.js new file mode 100644 index 000000000..a9ba7476e --- /dev/null +++ b/src/commonComponents/radioQuestion/radioQuestion.spec.js @@ -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'); + }) +}) \ No newline at end of file diff --git a/src/commonComponents/radioQuestion/radioQuestion.vue b/src/commonComponents/radioQuestion/radioQuestion.vue new file mode 100644 index 000000000..7e8b16c55 --- /dev/null +++ b/src/commonComponents/radioQuestion/radioQuestion.vue @@ -0,0 +1,27 @@ + + + diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 504a55c46..a390db3ec 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -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' } } diff --git a/src/constants/storeActions.js b/src/constants/storeActions.js index e358e4dab..5ef5a3157 100644 --- a/src/constants/storeActions.js +++ b/src/constants/storeActions.js @@ -1,5 +1,7 @@ const storeActions = { GET_ROUTE_INFO_ACTION: "getRouteInfo", + GET_YEARS: 'getYears', + GET_PAGE_DATA: 'getMockPageData', } export { storeActions } \ No newline at end of file diff --git a/src/global-methods.js b/src/global-methods.js index 824a87266..0b33cf613 100644 --- a/src/global-methods.js +++ b/src/global-methods.js @@ -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); + } + ); + }); + } }; diff --git a/src/global-methods.spec.js b/src/global-methods.spec.js new file mode 100644 index 000000000..6885a23e0 --- /dev/null +++ b/src/global-methods.spec.js @@ -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 + } +} \ No newline at end of file diff --git a/src/helpers/unitTestHelper.js b/src/helpers/unitTestHelper.js new file mode 100644 index 000000000..417b47e43 --- /dev/null +++ b/src/helpers/unitTestHelper.js @@ -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 } +} \ No newline at end of file diff --git a/src/layouts/componentTest/componentTest.vue b/src/layouts/componentTest/componentTest.vue index 376ccc851..01f15ff55 100644 --- a/src/layouts/componentTest/componentTest.vue +++ b/src/layouts/componentTest/componentTest.vue @@ -158,5 +158,10 @@ export default { radioList, alert }, + data() { + return { + years: [2023, 2022, 2021, 2020], + }; + } }; diff --git a/src/layouts/selectDamage/selectDamage.spec.js b/src/layouts/selectDamage/selectDamage.spec.js deleted file mode 100644 index 882a68129..000000000 --- a/src/layouts/selectDamage/selectDamage.spec.js +++ /dev/null @@ -1 +0,0 @@ -test.todo('some test to be written in the future'); \ No newline at end of file diff --git a/src/layouts/selectDamage/selectDamage.vue b/src/layouts/selectDamage/selectDamage.vue deleted file mode 100644 index 0d78a2ead..000000000 --- a/src/layouts/selectDamage/selectDamage.vue +++ /dev/null @@ -1,77 +0,0 @@ - - - diff --git a/src/layouts/selectMake/selectMake.vue b/src/layouts/selectMake/selectMake.vue index 412e235c1..8821286a3 100644 --- a/src/layouts/selectMake/selectMake.vue +++ b/src/layouts/selectMake/selectMake.vue @@ -1,14 +1,23 @@ diff --git a/src/layouts/selectModel/selectModel.vue b/src/layouts/selectModel/selectModel.vue index f2becd40a..4c0bd9b0d 100644 --- a/src/layouts/selectModel/selectModel.vue +++ b/src/layouts/selectModel/selectModel.vue @@ -1,18 +1,23 @@ diff --git a/src/layouts/selectStyle/selectStyle.vue b/src/layouts/selectStyle/selectStyle.vue index e9dd0d143..731b0d8d9 100644 --- a/src/layouts/selectStyle/selectStyle.vue +++ b/src/layouts/selectStyle/selectStyle.vue @@ -1,18 +1,23 @@ diff --git a/src/layouts/selectYear/selectYear.spec.js b/src/layouts/selectYear/selectYear.spec.js index 882a68129..27b84ed5f 100644 --- a/src/layouts/selectYear/selectYear.spec.js +++ b/src/layouts/selectYear/selectYear.spec.js @@ -1 +1,55 @@ -test.todo('some test to be written in the future'); \ No newline at end of file +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"); + }); + + +}) \ No newline at end of file diff --git a/src/layouts/selectYear/selectYear.vue b/src/layouts/selectYear/selectYear.vue index 63a08e02e..3b633b005 100644 --- a/src/layouts/selectYear/selectYear.vue +++ b/src/layouts/selectYear/selectYear.vue @@ -1,60 +1,78 @@ diff --git a/src/store/index.js b/src/store/index.js index 3c78d7f1c..e861a5ff0 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -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: { + }, + }); + }, }, }); diff --git a/src/styles/commonComponentStyles/ymmsCommonStyles.scss b/src/styles/commonComponentStyles/ymmsCommonStyles.scss index a550552b9..7fd2a4f18 100644 --- a/src/styles/commonComponentStyles/ymmsCommonStyles.scss +++ b/src/styles/commonComponentStyles/ymmsCommonStyles.scss @@ -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; +} \ No newline at end of file diff --git a/src/uxComponents/buttonPrimary/buttonPrimary.vue b/src/uxComponents/buttonPrimary/buttonPrimary.vue index 5e0dd1dcf..9741d9c49 100644 --- a/src/uxComponents/buttonPrimary/buttonPrimary.vue +++ b/src/uxComponents/buttonPrimary/buttonPrimary.vue @@ -14,12 +14,12 @@ export default { name: "buttonPrimary", props: { - buttonText: String + buttonText: String, + isDisabled: Boolean }, data() { return { - isLoading: false, - isDisabled: false, + isLoading: false }; }, methods: { diff --git a/src/uxComponents/header/header.spec.js b/src/uxComponents/header/header.spec.js new file mode 100644 index 000000000..d02c78d9f --- /dev/null +++ b/src/uxComponents/header/header.spec.js @@ -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"); + }) +}); \ No newline at end of file diff --git a/src/uxComponents/header/header.vue b/src/uxComponents/header/header.vue new file mode 100644 index 000000000..a2a68eeb7 --- /dev/null +++ b/src/uxComponents/header/header.vue @@ -0,0 +1,16 @@ + + + \ No newline at end of file diff --git a/src/uxComponents/radio/radio.spec.js b/src/uxComponents/radio/radio.spec.js new file mode 100644 index 000000000..ba59e2650 --- /dev/null +++ b/src/uxComponents/radio/radio.spec.js @@ -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' + }); + }) +}) \ No newline at end of file diff --git a/src/uxComponents/radio/radio.vue b/src/uxComponents/radio/radio.vue new file mode 100644 index 000000000..1f4e63f39 --- /dev/null +++ b/src/uxComponents/radio/radio.vue @@ -0,0 +1,39 @@ + + +