diff --git a/src/common-components/site-footer/site-footer.spec.js b/src/common-components/site-footer/site-footer.spec.js index 8a4d8a69..6734ec66 100644 --- a/src/common-components/site-footer/site-footer.spec.js +++ b/src/common-components/site-footer/site-footer.spec.js @@ -9,7 +9,7 @@ describe("site-footer.vue", () => { }); wrapper.vm.buttonClick(); // Assert - expect(wrapper.emitted()["ForwardClicked"][0]).toHaveBeenCalled; + expect(wrapper.emitted()["forwardClicked"][0]).toHaveBeenCalled; }); it("Should emit BackClicked on link click", async () => { @@ -19,7 +19,7 @@ describe("site-footer.vue", () => { }); wrapper.vm.linkClick(); // Assert - expect(wrapper.emitted()["BackClicked"][0]).toHaveBeenCalled; + expect(wrapper.emitted()["backClicked"][0]).toHaveBeenCalled; }); it("Should change button text when update button text is called", async () => { diff --git a/src/common-components/site-footer/site-footer.vue b/src/common-components/site-footer/site-footer.vue index fc21f9ce..49f84229 100644 --- a/src/common-components/site-footer/site-footer.vue +++ b/src/common-components/site-footer/site-footer.vue @@ -13,7 +13,8 @@ :isDisabled="isForwardActionDisabled" @click-event="buttonClick" data-bs-target="#footerModal" - data-bs-dismiss="modal" /> + data-bs-dismiss="modal" + data-test-id="site-footer-main-button" /> @@ -34,6 +36,7 @@ import buttonMain from "@/ux-components/button-main/button-main"; export default { name: "siteFooter", + emits: ['backClicked', 'forwardClicked'], props: { isForwardActionDisabled: Boolean, isBackButtonHidden: { type: Boolean, default: false }, @@ -89,10 +92,10 @@ export default { document.onkeydown = function (e) { return false; }; - this.$emit("ForwardClicked"); + this.$emit("forwardClicked"); }, linkClick() { - this.$emit("BackClicked"); + this.$emit("backClicked"); }, }, }; diff --git a/src/constants/vin-lookup-methods.js b/src/constants/vin-lookup-methods.js new file mode 100644 index 00000000..a12f29eb --- /dev/null +++ b/src/constants/vin-lookup-methods.js @@ -0,0 +1,7 @@ +const VIN_LOOKUP_METHODS = Object.freeze({ + manualVin: 'ManualVin', + licensePlate: 'LicensePlate', + homeAddress: 'HomeAddress', +}); + +export default VIN_LOOKUP_METHODS; diff --git a/src/layouts/address-lookup/address-lookup.vue b/src/layouts/address-lookup/address-lookup.vue new file mode 100644 index 00000000..996479e0 --- /dev/null +++ b/src/layouts/address-lookup/address-lookup.vue @@ -0,0 +1,90 @@ + + diff --git a/src/layouts/license-plate-lookup/license-plate-lookup.vue b/src/layouts/license-plate-lookup/license-plate-lookup.vue new file mode 100644 index 00000000..e6eb61f9 --- /dev/null +++ b/src/layouts/license-plate-lookup/license-plate-lookup.vue @@ -0,0 +1,90 @@ + + diff --git a/src/layouts/vehicle-lookup/vehicle-lookup.spec.js b/src/layouts/vehicle-lookup/vehicle-lookup.spec.js new file mode 100644 index 00000000..c66d164f --- /dev/null +++ b/src/layouts/vehicle-lookup/vehicle-lookup.spec.js @@ -0,0 +1,249 @@ +/* eslint-env jest */ +import { mount } from '@vue/test-utils'; +import { navigationScenarios } from '@/router/router-constants/navigation-scenarios'; +import VehicleLookup from './vehicle-lookup.vue'; + +const vinLookupMethodsMockData = { + QuestionText: 'To find the right part, let’s get your VIN. Or we can look it up for you!', + Answers: [ + { + Name: 'ManualVin', + Text: 'Provide my VIN manually', + SubText: 'Most specific to your vehicle', + ImageId: '', + SubWidgetName: '', + }, + { + Name: 'LicensePlate', + Text: 'Provide my license plate #', + SubText: 'Most accurate VIN match', + ImageId: '', + SubWidgetName: '', + }, + { + Name: 'HomeAddress', + Text: 'Provide my home address', + SubText: 'Most convenient VIN match', + ImageId: '', + SubWidgetName: '', + }, + ], +}; + +const siteFooterWidgetMockData = { + ForwardButtonText: 'Continue', +}; + +function setupMocks() { + const mockRoute = { + query: { + issPage: 'vehicle-lookup', + }, + }; + + const mockRouter = { + navigate: jest.fn(), + }; + + const wrapper = mount(VehicleLookup, { + data() { + return { + selectedVinLookupMethod: null, + }; + }, + global: { + mixins: [ + { + computed: { + navigationScenarios() { + return navigationScenarios; + }, + }, + methods: { + getCmsContent: jest.fn((cmsWidgetName, fieldName) => { + if (cmsWidgetName === 'SiteFooterWidget') { + if (fieldName === 'ForwardButtonText') { + return siteFooterWidgetMockData.ForwardButtonText; + } + } + + if (cmsWidgetName === 'VINLookupMethod') { + if (fieldName === 'QuestionText') { + return vinLookupMethodsMockData.QuestionText; + } + if (fieldName === 'Answers') { + return vinLookupMethodsMockData.Answers; + } + } + + return ''; + }), + getFooterInfoBoxHeight: jest.fn(() => 80), + }, + }, + ], + mocks: { + $route: mockRoute, + $router: mockRouter, + }, + stubs: { + SiteHeader: true, + SiteSubHeader: true, + VehicleBanner: true, + }, + }, + }); + + return { mockRoute, mockRouter, wrapper }; +} + +describe('vehicle-lookup.vue', () => { + test('"Continue" button is disabled when no VIN Lookup method is selected.', () => { + const { wrapper } = setupMocks(); + + const continueButton = wrapper.get('[data-test-id="site-footer-main-button"]'); + expect(continueButton.attributes()['aria-disabled']).toBe('true'); + /** + * To Do: Review if need to use VeeValidate meta.valid to check whether an element is disabled. + * The issue if relying on VeeValidate meta is not available inside the script + * (can only be used inside a template) + */ + }); + + test('"Continue" button is enabled after a VIN Lookup method is selected.', async () => { + const { wrapper } = setupMocks(); + + const vinLookupMethodsWrapper = wrapper.findComponent({ name: 'vin-lookup-methods' }); + // Not using initializeComponent() because it doesn't trigger reactivity in test. + await vinLookupMethodsWrapper.setData({ + questionTextFromCms: vinLookupMethodsMockData.QuestionText, + answersFromCms: vinLookupMethodsMockData.Answers, + }); + + const lookupByManualVinOptionLabel = wrapper.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-ManualVin"]'); + await lookupByManualVinOptionLabel.trigger('mousedown'); + + const continueButton = wrapper.get('[data-test-id="site-footer-main-button"]'); + expect(continueButton.attributes()['aria-disabled']).toBe('false'); + }); + + test('Click the back button, trigger navigate function from Vue Router with CLICKED_BACK parameter.', async () => { + const { mockRoute, mockRouter, wrapper } = setupMocks(); + + await wrapper.get('[data-test-id="site-footer-back-button"]').trigger('click'); + expect(mockRouter.navigate).toHaveBeenCalledTimes(1); + expect(mockRouter.navigate) + .toHaveBeenCalledWith(navigationScenarios.CLICKED_BACK, mockRoute); + }); + + test('Select "Provide VIN Manually" then click "Continue". Navigates to "vin-lookup" page.', async () => { + const { mockRoute, mockRouter, wrapper } = setupMocks(); + + const vinLookupMethodsWrapper = wrapper.findComponent({ name: 'vin-lookup-methods' }); + // Not using initializeComponent() because it doesn't trigger reactivity in test. + await vinLookupMethodsWrapper.setData({ + questionTextFromCms: vinLookupMethodsMockData.QuestionText, + answersFromCms: vinLookupMethodsMockData.Answers, + }); + + const lookupByManualVinOptionLabel = wrapper.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-ManualVin"]'); + await lookupByManualVinOptionLabel.trigger('mousedown'); + + const continueButton = wrapper.get('[data-test-id="site-footer-main-button"]'); + await continueButton.trigger('click'); + + expect(mockRouter.navigate).toHaveBeenCalledTimes(1); + expect(mockRouter.navigate) + .toHaveBeenCalledWith(navigationScenarios.SELECTED_MANUAL_VIN, mockRoute); + /** + * To Do: find a way to confirm that Vue Router really routes to the expected page. + * Currently just trust that our custom navigate() method does its job properly. + */ + }); + + test('Select "Provide My License Plate Number" then click "Continue". Navigates to "license-plate-lookup" page.', async () => { + const { mockRoute, mockRouter, wrapper } = setupMocks(); + + const vinLookupMethodsWrapper = wrapper.findComponent({ name: 'vin-lookup-methods' }); + // Not using initializeComponent() because it doesn't trigger reactivity in test. + await vinLookupMethodsWrapper.setData({ + questionTextFromCms: vinLookupMethodsMockData.QuestionText, + answersFromCms: vinLookupMethodsMockData.Answers, + }); + + const lookupByLicensePlateOptionLabel = wrapper.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-LicensePlate"]'); + await lookupByLicensePlateOptionLabel.trigger('mousedown'); + + const continueButton = wrapper.get('[data-test-id="site-footer-main-button"]'); + await continueButton.trigger('click'); + + expect(mockRouter.navigate).toHaveBeenCalledTimes(1); + expect(mockRouter.navigate) + .toHaveBeenCalledWith(navigationScenarios.SELECTED_LICENSE_PLATE, mockRoute); + /** + * To Do: find a way to confirm that Vue Router really routes to the expected page. + * Currently just trust that our custom navigate() method does its job properly. + */ + }); + + test('Select "Provide My Home Address" then click "Continue". Navigates to "address-lookup" page.', async () => { + const { mockRoute, mockRouter, wrapper } = setupMocks(); + + const vinLookupMethodsWrapper = wrapper.findComponent({ name: 'vin-lookup-methods' }); + // Not using initializeComponent() because it doesn't trigger reactivity in test. + await vinLookupMethodsWrapper.setData({ + questionTextFromCms: vinLookupMethodsMockData.QuestionText, + answersFromCms: vinLookupMethodsMockData.Answers, + }); + + const lookupByAddressOptionLabel = wrapper.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-HomeAddress"]'); + await lookupByAddressOptionLabel.trigger('mousedown'); + + const continueButton = wrapper.get('[data-test-id="site-footer-main-button"]'); + await continueButton.trigger('click'); + + expect(mockRouter.navigate).toHaveBeenCalledTimes(1); + expect(mockRouter.navigate) + .toHaveBeenCalledWith(navigationScenarios.SELECTED_HOME_ADDRESS, mockRoute); + /** + * To Do: find a way to confirm that Vue Router really routes to the expected page. + * Currently just trust that our custom navigate() method does its job properly. + */ + }); + + /** + * To Do: Error message is not displayed when running test + * but in reality it shows up. Need to find out + * how to write a test that verifies the error message is displayed. + */ + // test.only('Error message is diplayed when no selection is made + // and "Continue" button clicked.', async () => { + // const { wrapper } = setupMocks(); + + // const vinLookupMethodsWrapper = wrapper.findComponent({ name: 'vin-lookup-methods' }); + // // Not using initializeComponent() because it doesn't trigger reactivity in test. + // await vinLookupMethodsWrapper.setData({ + // questionTextFromCms: vinLookupMethodsMockData.QuestionText, + // answersFromCms: vinLookupMethodsMockData.Answers, + // }); + + // // const lookupByManualVinOptionLabel = + // // wrapper.find('[data-test-id="vin-lookup-methods-button-question"] + // //label[for="VinLookupMethods-ManualVin"]'); + // // await lookupByManualVinOptionLabel.trigger('mousedown'); + + // const continueButton = wrapper.get('[data-test-id="site-footer-main-button"]'); + // await continueButton.trigger('click'); + + // await flushPromises(); + // await nextTick(); + + // console.log(vinLookupMethodsWrapper.get('.form-test-error').html()); + // //const veeValidateForm = wrapper.findComponent() + // //console.log(wrapper.emitted()); + // // console.log(vinLookupMethodsWrapper.html()); + // // console.log(vinLookupMethodsWrapper.get('.form-test-error').html()); + // // console.log(vinLookupMethodsWrapper.get('.form-test-error').text()); + // // expect(vinLookupMethodsWrapper.get('.form-test-error').); + // }); +}); diff --git a/src/layouts/vehicle-lookup/vehicle-lookup.vue b/src/layouts/vehicle-lookup/vehicle-lookup.vue index 059a3c2d..8ebbd2c3 100644 --- a/src/layouts/vehicle-lookup/vehicle-lookup.vue +++ b/src/layouts/vehicle-lookup/vehicle-lookup.vue @@ -1,5 +1,8 @@ diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue new file mode 100644 index 00000000..c3b67d93 --- /dev/null +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -0,0 +1,90 @@ + + diff --git a/src/router/router-constants/issPage-values.js b/src/router/router-constants/issPage-values.js index 9e991c9f..af17bd8f 100644 --- a/src/router/router-constants/issPage-values.js +++ b/src/router/router-constants/issPage-values.js @@ -5,13 +5,14 @@ export const issPageValues = { VEHICLE_MODEL: "vehicle-model", VEHICLE_STYLE: "vehicle-style", VEHICLE_DAMAGE: "vehicle-damage", + VEHICLE_LOOKUP: "vehicle-lookup", ADDRESS_LOOKUP: "address-lookup", + LICENSE_PLATE_LOOKUP: "license-plate-lookup", VIN_LOOKUP: "vin-lookup", VEHICLE_PARTS: "vehicle-parts", PART_QUESTIONS: "part-questions", MOLDING_QUESTIONS: "molding-questions", CAPABILITY_QUESTIONS: "capability-questions", - LICENSE_PLATE_LOOKUP: "license-plate-lookup", REVEAL: "reveal", ESTIMATE: "estimate", ADDRESS_VEHICLES: "address-vehicles", diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js index cfa431f0..0bbc01c0 100644 --- a/src/router/router-constants/routing-table.js +++ b/src/router/router-constants/routing-table.js @@ -65,11 +65,32 @@ const routingTable = function(store) { }, { scenario: navigationScenarios.CLICKED_FORWARD_WITH_VIN, - destinationIssPageValue: issPageValues.WELCOME_PAGE, + destinationIssPageValue: issPageValues.VEHICLE_LOOKUP, }, { scenario: navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN, - destinationIssPageValue: issPageValues.WELCOME_PAGE, + destinationIssPageValue: issPageValues.VEHICLE_LOOKUP, + }, + ], + }, + { + issPageValue: issPageValues.VEHICLE_LOOKUP, + maps: [ + { + scenario: navigationScenarios.CLICKED_BACK, + destinationIssPageValue: issPageValues.VEHICLE_DAMAGE, + }, + { + scenario: navigationScenarios.SELECTED_MANUAL_VIN, + destinationIssPageValue: issPageValues.VIN_LOOKUP, + }, + { + scenario: navigationScenarios.SELECTED_LICENSE_PLATE, + destinationIssPageValue: issPageValues.LICENSE_PLATE_LOOKUP, + }, + { + scenario: navigationScenarios.SELECTED_HOME_ADDRESS, + destinationIssPageValue: issPageValues.ADDRESS_LOOKUP, }, ], },