Merge pull request #508 from Safelite/feature/digital/SSR-755

Feature/digital/ssr 755
This commit is contained in:
michaela-brydon-safelite 2023-11-30 12:55:28 -05:00 committed by GitHub
commit e7a8f55f38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 839 additions and 306 deletions

View file

@ -3,6 +3,22 @@
exports[`Google Map should render expected initial data 1`] = ` exports[`Google Map should render expected initial data 1`] = `
Object { Object {
"country": "USA", "country": "USA",
"geocoder": null, "geocoder": Object {
"geocode": [MockFunction] {
"calls": Array [
Array [
Object {
"address": " USA",
},
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
},
},
} }
`; `;

View file

@ -1,6 +1,11 @@
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { createTestingPinia } from '@pinia/testing';
import googleMap from '@/iss-components/google-map/google-map.vue'; import googleMap from '@/iss-components/google-map/google-map.vue';
// Supporting Files
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore } from '@/store';
const mockFitBounds = jest.fn(); const mockFitBounds = jest.fn();
const mockMapInstance = { const mockMapInstance = {
fitBounds: mockFitBounds fitBounds: mockFitBounds
@ -8,10 +13,13 @@ const mockMapInstance = {
const mockMap = jest.fn(() => mockMapInstance); const mockMap = jest.fn(() => mockMapInstance);
const mockGeocode = jest.fn(); const mockGeocode = jest.fn();
const mockGeocoderInstance = { const mockGeocoderInstance1 = {
geocode: mockGeocode geocode: mockGeocode
}; };
const mockGeocoder = jest.fn(() => (mockGeocoderInstance)); const mockGeocoderInstance2 = {
geocode: mockGeocode
};
const mockGeocoder = jest.fn(() => (mockGeocoderInstance1));
const mockAdvancedMarkerElement = jest.fn(); const mockAdvancedMarkerElement = jest.fn();
@ -47,20 +55,52 @@ beforeEach(() => {
setupGoogleMock(); setupGoogleMock();
}); });
function getMountedComponent(mainInitialState = {}, initialData = {}, propsData = {}) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
}
});
const testingPinia = createTestingPinia({
initialState: {
main: mainInitialState
}
});
useMainStore(testingPinia);
mountOptions.global.plugins = [testingPinia];
mountOptions.data = () => (initialData);
mountOptions.propsData = propsData;
const wrapper = shallowMount(googleMap, mountOptions);
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {});
wrapper.vm.setCmsContent = jest.fn();
return { wrapper };
}
async function awaitingSetupTicks(wrapper) {
for (let i = 0; i < 11; i++) {
// eslint-disable-next-line no-await-in-loop
await wrapper.vm.$nextTick();
}
}
describe('Google Map', () => { describe('Google Map', () => {
describe('should render', () => { describe('should render', () => {
test('expected initial data', async () => { test('expected initial data', async () => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {}); const wrapper = shallowMount(googleMap, {});
await wrapper.vm.$nextTick(); await awaitingSetupTicks(wrapper);
// Assert // Assert
expect(wrapper.vm.$data).toMatchSnapshot(); expect(wrapper.vm.$data).toMatchSnapshot();
}); });
test('map', () => { test('map', async () => {
// Arrange // Arrange
const mapReference = '#map'; const mapReference = '#map';
const wrapper = shallowMount(googleMap, {}); const wrapper = shallowMount(googleMap, {});
await awaitingSetupTicks(wrapper);
// Act // Act
const map = wrapper.find(mapReference); const map = wrapper.find(mapReference);
@ -78,8 +118,8 @@ describe('Google Map', () => {
'given that zipcode set to "%p", computed zipCodeAddress should return "%p"', 'given that zipcode set to "%p", computed zipCodeAddress should return "%p"',
async (zipCode, expected) => { async (zipCode, expected) => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {}); const { wrapper } = getMountedComponent({}, {}, { zipCode });
await wrapper.setData({ zipCode }); await awaitingSetupTicks(wrapper);
// Act // Act
const result = wrapper.vm.zipCodeAddress; const result = wrapper.vm.zipCodeAddress;
@ -88,11 +128,78 @@ describe('Google Map', () => {
expect(result).toBe(expected); expect(result).toBe(expected);
} }
); );
test('before mount addresses creates map with expected markers', async () => {
// Arrange
const mockGetCenter = jest.fn();
const mockNorthEast = jest.fn();
const mockSouthWest = jest.fn();
const zipCodeGeocodeResult = {
results: [
{
geometry: {
bounds:
{
getCenter: mockGetCenter,
getNorthEast: mockNorthEast,
getSouthWest: mockSouthWest
}
}
}
]
};
const zipCode = '00882';
const [address1, address2, position1, position2] = ['a1', 'a2', 'p1', 'p2'];
const addresses = [address1, address2];
mockGeocode.mockImplementation((obj) => {
let result = {};
if (obj.address.includes(zipCode)) {
result = zipCodeGeocodeResult;
} else if (obj.address === address1) {
result = {
results: [
{ geometry: { location: position1 } }
]
};
} else if (obj.address === address2) {
result = {
results: [
{ geometry: { location: position2 } }
]
};
}
return Promise.resolve(result);
});
const propsData = { addresses, zipCode };
const { wrapper } = getMountedComponent({}, {}, propsData);
await awaitingSetupTicks(wrapper);
// Assert
expect(mockMap).toBeCalledTimes(1);
expect(mockAdvancedMarkerElement).toBeCalledTimes(2);
expect(mockAdvancedMarkerElement).toBeCalledWith(expect.objectContaining({
map: expect.anything(),
position: position1
}));
expect(mockAdvancedMarkerElement).toBeCalledWith(expect.objectContaining({
map: expect.anything(),
position: position2
}));
expect(mockGeocoder).toBeCalledTimes(1);
expect(mockExtend).toBeCalledTimes(4);
expect(mockFitBounds).toBeCalledTimes(1);
expect(mockGetCenter).toBeCalledTimes(1);
expect(mockNorthEast).toBeCalledTimes(1);
expect(mockSouthWest).toBeCalledTimes(1);
});
test('watch addresses creates map with expected markers', async () => { test('watch addresses creates map with expected markers', async () => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {});
const zipCode = '00882'; const zipCode = '00882';
await wrapper.setData({ zipCode }); const { wrapper } = getMountedComponent({}, { zipCode });
await awaitingSetupTicks(wrapper);
const mockGetCenter = jest.fn(); const mockGetCenter = jest.fn();
const mockNorthEast = jest.fn(); const mockNorthEast = jest.fn();
@ -135,14 +242,10 @@ describe('Google Map', () => {
// Act // Act
await wrapper.vm.$options.watch.addresses.call(wrapper.vm, newAddresses); await wrapper.vm.$options.watch.addresses.call(wrapper.vm, newAddresses);
await awaitingSetupTicks(wrapper);
for (let i = 0; i < 11; i++) {
// eslint-disable-next-line no-await-in-loop
await wrapper.vm.$nextTick();
}
// Assert // Assert
expect(mockMap).toBeCalledTimes(1); expect(mockMap).toBeCalledTimes(2);
expect(mockAdvancedMarkerElement).toBeCalledTimes(2); expect(mockAdvancedMarkerElement).toBeCalledTimes(2);
expect(mockAdvancedMarkerElement).toBeCalledWith(expect.objectContaining({ expect(mockAdvancedMarkerElement).toBeCalledWith(expect.objectContaining({
map: expect.anything(), map: expect.anything(),
@ -154,8 +257,8 @@ describe('Google Map', () => {
})); }));
expect(mockGeocoder).toBeCalledTimes(1); expect(mockGeocoder).toBeCalledTimes(1);
expect(mockExtend).toBeCalledTimes(4); expect(mockExtend).toBeCalledTimes(6);
expect(mockFitBounds).toBeCalledTimes(1); expect(mockFitBounds).toBeCalledTimes(2);
expect(mockGetCenter).toBeCalledTimes(1); expect(mockGetCenter).toBeCalledTimes(1);
expect(mockNorthEast).toBeCalledTimes(1); expect(mockNorthEast).toBeCalledTimes(1);
@ -165,6 +268,7 @@ describe('Google Map', () => {
test('getMap calls map constructor with expected parameters', async () => { test('getMap calls map constructor with expected parameters', async () => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {}); const wrapper = shallowMount(googleMap, {});
await awaitingSetupTicks(wrapper);
const center = { lat: 90, lng: -5 }; const center = { lat: 90, lng: -5 };
// Act // Act
@ -185,32 +289,35 @@ describe('Google Map', () => {
[['loc1', 'loc1', 'loc1', 'loc1'], 4] [['loc1', 'loc1', 'loc1', 'loc1'], 4]
])( ])(
'given parameter %p, getBounds calls extend %p time(s)', 'given parameter %p, getBounds calls extend %p time(s)',
(locations, numberOfExtendCalls) => { async (locations, numberOfExtendCallsPostMount) => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {}); const wrapper = shallowMount(googleMap, {});
await awaitingSetupTicks(wrapper);
const numberOfExtendCallsDuringMount = 2;
// Act // Act
const result = wrapper.vm.getBounds(locations); const result = wrapper.vm.getBounds(locations);
// Assert // Assert
expect(mockExtend).toHaveBeenCalledTimes(numberOfExtendCalls); expect(mockExtend).toHaveBeenCalledTimes(
numberOfExtendCallsDuringMount + numberOfExtendCallsPostMount);
expect(result).toBe(mockLatLngBound); expect(result).toBe(mockLatLngBound);
} }
); );
test.each([ test.each([
[1, null, mockGeocoderInstance], [1, null, mockGeocoderInstance1],
[1, undefined, mockGeocoderInstance], [1, undefined, mockGeocoderInstance1],
[0, {}, {}], [0, mockGeocoderInstance2, mockGeocoderInstance2]
[0, { value: 'any value' }, { value: 'any value' }]
])( ])(
'setGeocoder calls Geocoder constructor %p time(s) when original geocoder %p and geocoder set to %p', 'setGeocoder calls Geocoder constructor %p time(s) when original geocoder %p and geocoder set to %p',
async (numberOfCalls, originalGeocoder, newGeocoder) => { async (numberOfCalls, originalGeocoder, newGeocoder) => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {}); const { wrapper } = getMountedComponent({}, { geocoder: originalGeocoder }, {});
await wrapper.setData({ geocoder: originalGeocoder }); await awaitingSetupTicks(wrapper);
// Act // Act
await wrapper.vm.setGeocoder(); await wrapper.vm.setGeocoder();
await wrapper.vm.$nextTick();
// Assert // Assert
expect(mockGeocoder).toHaveBeenCalledTimes(numberOfCalls); expect(mockGeocoder).toHaveBeenCalledTimes(numberOfCalls);
@ -228,6 +335,7 @@ describe('Google Map', () => {
async (positions, numberOfCalls) => { async (positions, numberOfCalls) => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {}); const wrapper = shallowMount(googleMap, {});
await awaitingSetupTicks(wrapper);
const map = jest.fn(); const map = jest.fn();
// Act // Act
@ -246,21 +354,26 @@ describe('Google Map', () => {
[['a1', 'a2', 'a3'], 3] [['a1', 'a2', 'a3'], 3]
])( ])(
'given address(es) %p calls geocode %p times', 'given address(es) %p calls geocode %p times',
async (addresses, numberOfCalls) => { async (addresses, numberOfCallsPostMount) => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {}); const wrapper = shallowMount(googleMap, {});
await awaitingSetupTicks(wrapper);
const numberOfCallsFromMount = 1;
expect(mockGeocode).toHaveBeenCalledTimes(numberOfCallsFromMount);
// Act // Act
await wrapper.vm.getLocationsFromAddresses(addresses); await wrapper.vm.getLocationsFromAddresses(addresses);
await wrapper.vm.$nextTick();
// Assert // Assert
expect(mockGeocode).toHaveBeenCalledTimes(numberOfCalls); expect(mockGeocode).toHaveBeenCalledTimes(numberOfCallsFromMount + numberOfCallsPostMount);
expect(wrapper.vm.geocoder).not.toBeNull(); expect(wrapper.vm.geocoder).not.toBeNull();
} }
); );
test('only returns geocode results that are not null', async () => { test('only returns geocode results that are not null', async () => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {}); const wrapper = shallowMount(googleMap, {});
await awaitingSetupTicks(wrapper);
const addresses = ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8']; const addresses = ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8'];
mockGeocode.mockImplementationOnce(() => null) mockGeocode.mockImplementationOnce(() => null)
.mockImplementationOnce(() => ({ results: null })) .mockImplementationOnce(() => ({ results: null }))
@ -300,6 +413,7 @@ describe('Google Map', () => {
test('returns expected when geocode returns valid result', async () => { test('returns expected when geocode returns valid result', async () => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {}); const wrapper = shallowMount(googleMap, {});
await awaitingSetupTicks(wrapper);
const address = 'random address'; const address = 'random address';
const expectedResult = 'bounds returned'; const expectedResult = 'bounds returned';
const geocodeResult = { const geocodeResult = {
@ -321,6 +435,7 @@ describe('Google Map', () => {
test('returns null when geocode returns invalid result', async () => { test('returns null when geocode returns invalid result', async () => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {}); const wrapper = shallowMount(googleMap, {});
await awaitingSetupTicks(wrapper);
const address = 'random address'; const address = 'random address';
mockGeocode.mockImplementationOnce(() => Promise.resolve({})); mockGeocode.mockImplementationOnce(() => Promise.resolve({}));
@ -335,9 +450,6 @@ describe('Google Map', () => {
describe('createMapWithMarkersForAddresses', () => { describe('createMapWithMarkersForAddresses', () => {
test('calls expected when addresses provided', async () => { test('calls expected when addresses provided', async () => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {});
const zipCode = '00882';
await wrapper.setData({ zipCode });
const mockGetCenter = jest.fn(); const mockGetCenter = jest.fn();
const mockNorthEast = jest.fn(); const mockNorthEast = jest.fn();
const mockSouthWest = jest.fn(); const mockSouthWest = jest.fn();
@ -360,34 +472,35 @@ describe('Google Map', () => {
{ geometry: { location: 'valid location' } } { geometry: { location: 'valid location' } }
] ]
}; };
const zipCode = '00882';
mockGeocode.mockImplementation((obj) => mockGeocode.mockImplementation((obj) =>
Promise.resolve(obj.address.includes(zipCode) Promise.resolve(obj.address.includes(zipCode)
? zipCodeGeocodeResult ? zipCodeGeocodeResult
: shopGeocodeResult)); : shopGeocodeResult));
const addresses = ['a1', 'a2']; const addresses = ['a1', 'a2'];
const { wrapper } = getMountedComponent({}, { zipCode });
await awaitingSetupTicks(wrapper);
// Act // Act
await wrapper.vm.createMapWithMarkersForAddresses(addresses); await wrapper.vm.createMapWithMarkersForAddresses(addresses);
await awaitingSetupTicks(wrapper);
// Assert // Assert
expect(mockMap).toBeCalledTimes(1); expect(mockMap).toBeCalledTimes(2);
expect(mockAdvancedMarkerElement).toBeCalledTimes(2); expect(mockAdvancedMarkerElement).toBeCalledTimes(2);
expect(mockGeocoder).toBeCalledTimes(1); expect(mockGeocoder).toBeCalledTimes(1);
expect(mockExtend).toBeCalledTimes(4); expect(mockExtend).toBeCalledTimes(6);
expect(mockFitBounds).toBeCalledTimes(1); expect(mockFitBounds).toBeCalledTimes(2);
expect(mockGetCenter).toBeCalledTimes(1); expect(mockGetCenter).toBeCalledTimes(2);
expect(mockNorthEast).toBeCalledTimes(1); expect(mockNorthEast).toBeCalledTimes(2);
expect(mockSouthWest).toBeCalledTimes(1); expect(mockSouthWest).toBeCalledTimes(2);
}); });
test.each([null, undefined, []])( test.each([null, undefined, []])(
'calls expected when addresses argument is "%p"', 'calls expected when addresses argument is "%p"',
async (addresses) => { async (addresses) => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {});
const zipCode = '00882';
await wrapper.setData({ zipCode });
const mockGetCenter = jest.fn(); const mockGetCenter = jest.fn();
const mockNorthEast = jest.fn(); const mockNorthEast = jest.fn();
const mockSouthWest = jest.fn(); const mockSouthWest = jest.fn();
@ -406,21 +519,25 @@ describe('Google Map', () => {
] ]
}; };
mockGeocode.mockImplementation(() => Promise.resolve(zipCodeGeocodeResult)); mockGeocode.mockImplementation(() => Promise.resolve(zipCodeGeocodeResult));
const zipCode = '00882';
const { wrapper } = getMountedComponent({}, { zipCode });
await awaitingSetupTicks(wrapper);
// Act // Act
await wrapper.vm.createMapWithMarkersForAddresses(addresses); await wrapper.vm.createMapWithMarkersForAddresses(addresses);
await awaitingSetupTicks(wrapper);
// Assert // Assert
expect(mockMap).toBeCalledTimes(1); expect(mockMap).toBeCalledTimes(2);
expect(mockAdvancedMarkerElement).toBeCalledTimes(0); expect(mockAdvancedMarkerElement).toBeCalledTimes(0);
expect(mockGeocoder).toBeCalledTimes(1); expect(mockGeocoder).toBeCalledTimes(1);
expect(mockExtend).toBeCalledTimes(2); expect(mockExtend).toBeCalledTimes(4);
expect(mockFitBounds).toBeCalledTimes(1); expect(mockFitBounds).toBeCalledTimes(2);
expect(mockGetCenter).toBeCalledTimes(1); expect(mockGetCenter).toBeCalledTimes(2);
expect(mockNorthEast).toBeCalledTimes(1); expect(mockNorthEast).toBeCalledTimes(2);
expect(mockSouthWest).toBeCalledTimes(1); expect(mockSouthWest).toBeCalledTimes(2);
} }
); );
}); });

View file

@ -27,6 +27,9 @@ export default {
this.createMapWithMarkersForAddresses(newAddresses); this.createMapWithMarkersForAddresses(newAddresses);
} }
}, },
beforeMount() {
this.createMapWithMarkersForAddresses(this.addresses);
},
methods: { methods: {
async getMap(center) { async getMap(center) {
const { Map } = await window.google.maps.importLibrary('maps'); const { Map } = await window.google.maps.importLibrary('maps');
@ -72,13 +75,13 @@ export default {
async createMapWithMarkersForAddresses(addresses) { async createMapWithMarkersForAddresses(addresses) {
const shopPositions = await this.getLocationsFromAddresses(addresses); const shopPositions = await this.getLocationsFromAddresses(addresses);
const zipBounds = await this.getBoundsFromAddress(this.zipCodeAddress); const zipBounds = await this.getBoundsFromAddress(this.zipCodeAddress);
const map = await this.getMap(zipBounds.getCenter()); const map = await this.getMap(zipBounds?.getCenter());
await this.addMarkersToMap(map, shopPositions); await this.addMarkersToMap(map, shopPositions);
const positionsToDisplay = shopPositions.concat(zipBounds.getNorthEast(), zipBounds.getSouthWest()); const positionsToDisplay = shopPositions.concat(zipBounds?.getNorthEast(), zipBounds?.getSouthWest());
const bounds = this.getBounds(positionsToDisplay); const bounds = this.getBounds(positionsToDisplay);
map.fitBounds(bounds); map?.fitBounds(bounds);
} }
} }
}; };

View file

@ -6,8 +6,24 @@ Object {
"displayAvailabilityIndicators": false, "displayAvailabilityIndicators": false,
}, },
"filter": "", "filter": "",
"initialLoading": false,
"mapZipCode": "12663", "mapZipCode": "12663",
"providers": Array [], "providers": Array [],
"radiusFilterPairs": Array [
Object {
"filter": "25 miles",
"radius": 25,
},
Object {
"filter": "50 miles",
"radius": 50,
},
Object {
"filter": "100 miles",
"radius": 100,
},
],
"reloadProviders": false,
"rules": Object { "rules": Object {
"filter": "option-required", "filter": "option-required",
"provider": "option-required", "provider": "option-required",
@ -89,14 +105,25 @@ Object {
"setup": [Function], "setup": [Function],
}, },
"loader": Object { "loader": Object {
"computed": Object {
"cssProps": [Function],
},
"name": "loader", "name": "loader",
"props": Object { "props": Object {
"height": Object {
"default": 1,
"type": [Function],
},
"loaderColor": Object { "loaderColor": Object {
"type": [Function], "type": [Function],
}, },
"loaderPosition": Object { "loaderPosition": Object {
"type": [Function], "type": [Function],
}, },
"width": Object {
"default": 1,
"type": [Function],
},
}, },
"render": [Function], "render": [Function],
}, },

View file

@ -10,6 +10,7 @@ import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper.js'; import settleAllPromises from '@/helpers/layout-helper.js';
import globalRules from '@/constants/global-rules.js'; import globalRules from '@/constants/global-rules.js';
import widgetFields from '@/constants/cms-widget-fields.js'; import widgetFields from '@/constants/cms-widget-fields.js';
import navigationScenarios from '@/router/router-constants/navigation-scenarios';
// Mock fetchCmsContentForPage // Mock fetchCmsContentForPage
jest.mock('@/helpers/cms-content-helper', () => ({ jest.mock('@/helpers/cms-content-helper', () => ({
@ -149,7 +150,18 @@ describe('TPA search page', () => {
// Assert // Assert
expect(providerSelectionForm.exists()).toBeTruthy(); expect(providerSelectionForm.exists()).toBeTruthy();
}); });
test('map', async () => { test('map loader when initialLoading is true', async () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ initialLoading: true });
// Act
const mapLoader = wrapper.findComponent('#mapLoader');
// Assert
expect(mapLoader.exists()).toBeTruthy();
});
test('map when initialLoading is false', async () => {
// Arrange // Arrange
const zipCode = '12345'; const zipCode = '12345';
const { wrapper } = getMountedComponent(); const { wrapper } = getMountedComponent();
@ -162,7 +174,8 @@ describe('TPA search page', () => {
zipCode: '99230' zipCode: '99230'
} }
}], }],
mapZipCode: zipCode mapZipCode: zipCode,
initialLoading: false
}); });
const expectedAddresses = ['123 Lane Ave, New York, NY 99230']; const expectedAddresses = ['123 Lane Ave, New York, NY 99230'];
@ -175,9 +188,10 @@ describe('TPA search page', () => {
expect(map.props().addresses).toEqual(expectedAddresses); expect(map.props().addresses).toEqual(expectedAddresses);
expect(map.props().zipCode).toBe(zipCode); expect(map.props().zipCode).toBe(zipCode);
}); });
test('search radius filter', () => { test('search radius filter when initialLoading is false', async () => {
// Arrange // Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions()); const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ initialLoading: false });
const expectedWidgetName = 'FilterByQuestion'; const expectedWidgetName = 'FilterByQuestion';
// Act // Act
@ -188,12 +202,23 @@ describe('TPA search page', () => {
expect(searchRadiusFilter.props().cmsWidgetName).toBe(expectedWidgetName); expect(searchRadiusFilter.props().cmsWidgetName).toBe(expectedWidgetName);
expect(searchRadiusFilter.props().validationRules).toBe(globalRules.OPTION_REQUIRED); expect(searchRadiusFilter.props().validationRules).toBe(globalRules.OPTION_REQUIRED);
}); });
test('select provider question', async () => { test('providers loader when initialLoading is false and reloadProviders is true', async () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ initialLoading: false, reloadProviders: true });
// Act
const providersLoader = wrapper.findComponent('#providersLoader');
// Assert
expect(providersLoader.exists()).toBeTruthy();
});
test('select provider question when initialLoading and reloadProviders are false', async () => {
// Arrange // Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions()); const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ await wrapper.setData({
providers: [{ providers: [{
name: 'Shop Auto Glass', companyName: 'Shop Auto Glass',
phoneNumber: '740-555-1234', phoneNumber: '740-555-1234',
distanceInMiles: 1.2345, distanceInMiles: 1.2345,
providerNumber: '232209', providerNumber: '232209',
@ -203,7 +228,9 @@ describe('TPA search page', () => {
state: 'NY', state: 'NY',
zipCode: '99230' zipCode: '99230'
} }
}] }],
initialLoading: false,
reloadProviders: false
}); });
const expectedAnswers = [{ const expectedAnswers = [{
buttonLabel: 'Shop Auto Glass', buttonLabel: 'Shop Auto Glass',
@ -230,13 +257,17 @@ describe('TPA search page', () => {
expect(selectProviderQuestion.props().additionalButtonData).toEqual(expectedAdditionalButtonData); expect(selectProviderQuestion.props().additionalButtonData).toEqual(expectedAdditionalButtonData);
}); });
test.each([[], null, undefined])( test.each([[], null, undefined])(
'no network providers alert when providers length is 0, undefined, or null', 'no network providers alert when providers length is 0, undefined, or null and initialLoading and reloadProviders are false',
(providers) => { async (providers) => {
// Arrange // Arrange
const initialData = { const initialData = {
providers providers
}; };
const wrapper = shallowMount(tpaSearch, getMountOptions({}, initialData)); const wrapper = shallowMount(tpaSearch, getMountOptions({}, initialData));
await wrapper.setData({
initialLoading: false,
reloadProviders: false
});
// Act // Act
const noNetworkProvidersAlert = wrapper.findComponent('#alertNoNetworkProviders'); const noNetworkProvidersAlert = wrapper.findComponent('#alertNoNetworkProviders');
@ -248,9 +279,13 @@ describe('TPA search page', () => {
expect(noNetworkProvidersAlert.props().isDismissible).toBeFalsy(); expect(noNetworkProvidersAlert.props().isDismissible).toBeFalsy();
} }
); );
test('preferred shop not listed link', () => { test('preferred shop not listed link when initialLoading and reloadProviders are false', async () => {
// Arrange // Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions()); const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({
initialLoading: false,
reloadProviders: false
});
// Act // Act
const preferredShopNotListedLink = wrapper.findComponent('#preferredShopNotListedLink'); const preferredShopNotListedLink = wrapper.findComponent('#preferredShopNotListedLink');
@ -272,7 +307,66 @@ describe('TPA search page', () => {
expect(footer.props().cmsWidgetName).toBe('SiteFooterWidget'); expect(footer.props().cmsWidgetName).toBe('SiteFooterWidget');
}); });
}); });
test('should not render no network providers alert when providers length is not 0', async () => { describe('should not render', () => {
test('map loader when initialLoading is false', async () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ initialLoading: false });
// Act
const mapLoader = wrapper.findComponent('#mapLoader');
// Assert
expect(mapLoader.exists()).toBeFalsy();
});
test('map when initialLoading is true', async () => {
// Arrange
const { wrapper } = getMountedComponent();
await wrapper.setData({ initialLoading: true });
// Act
const map = wrapper.findComponent('#map');
// Assert
expect(map.exists()).toBeFalsy();
});
test('search radius filter when initialLoading is true', async () => {
// Arrange
const { wrapper } = getMountedComponent();
await wrapper.setData({ initialLoading: true });
// Act
const searchRadiusFilter = wrapper.findComponent('#searchRadiusFilter');
// Assert
expect(searchRadiusFilter.exists()).toBeFalsy();
});
describe('select provider question', () => {
test('when initialLoading is true', async () => {
// Arrange
const { wrapper } = getMountedComponent();
await wrapper.setData({ initialLoading: true });
// Act
const selectProviderQuestion = wrapper.findComponent('#selectProviderQuestion');
// Assert
expect(selectProviderQuestion.exists()).toBeFalsy();
});
test('when reloadProviders is true', async () => {
// Arrange
const { wrapper } = getMountedComponent();
await wrapper.setData({ reloadProviders: true });
// Act
const selectProviderQuestion = wrapper.findComponent('#selectProviderQuestion');
// Assert
expect(selectProviderQuestion.exists()).toBeFalsy();
});
});
describe('no network providers alert', () => {
test('when providers length is not 0', async () => {
// Arrange // Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions()); const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ providers: ['p1', 'p2'] }); await wrapper.setData({ providers: ['p1', 'p2'] });
@ -283,6 +377,54 @@ describe('TPA search page', () => {
// Assert // Assert
expect(noNetworkProvidersAlert.exists()).toBeFalsy(); expect(noNetworkProvidersAlert.exists()).toBeFalsy();
}); });
test('when initialLoading is true', async () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ providers: [], initialLoading: true });
// Act
const noNetworkProvidersAlert = wrapper.findComponent('#alertNoNetworkProviders');
// Assert
expect(noNetworkProvidersAlert.exists()).toBeFalsy();
});
test('when reloadProviders is true', async () => {
// Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ providers: [], reloadProviders: true });
// Act
const noNetworkProvidersAlert = wrapper.findComponent('#alertNoNetworkProviders');
// Assert
expect(noNetworkProvidersAlert.exists()).toBeFalsy();
});
});
describe('preferred shop not listed link', () => {
test('when initialLoading is true', async () => {
// Arrange
const { wrapper } = getMountedComponent();
await wrapper.setData({ initialLoading: true });
// Act
const preferredShopNotListedLink = wrapper.findComponent('#preferredShopNotListedLink');
// Assert
expect(preferredShopNotListedLink.exists()).toBeFalsy();
});
test('when reloadProviders is true', async () => {
// Arrange
const { wrapper } = getMountedComponent();
await wrapper.setData({ reloadProviders: true });
// Act
const preferredShopNotListedLink = wrapper.findComponent('#preferredShopNotListedLink');
// Assert
expect(preferredShopNotListedLink.exists()).toBeFalsy();
});
});
});
describe('computed', () => { describe('computed', () => {
describe('filterOptions', () => { describe('filterOptions', () => {
test.each([[], null, undefined])( test.each([[], null, undefined])(
@ -330,7 +472,7 @@ describe('TPA search page', () => {
}); });
}); });
test.each([null, undefined, '', 'non empty string'])( test.each([null, undefined, '', 'non empty string'])(
'tpaSearchQuestionLabel returns value from getCmsContent', 'tpaSearchQuestionLabel returns "%p" from getCmsContent',
(cmsContent) => { (cmsContent) => {
// Arrange // Arrange
const mountOptions = getMountOptions(); const mountOptions = getMountOptions();
@ -352,7 +494,7 @@ describe('TPA search page', () => {
} }
); );
test.each([null, undefined, '', 'non empty string'])( test.each([null, undefined, '', 'non empty string'])(
'searchInstructionsText returns value from getCmsContent', 'searchInstructionsText returns "%p" from getCmsContent',
(cmsContent) => { (cmsContent) => {
// Arrange // Arrange
const mountOptions = getMountOptions(); const mountOptions = getMountOptions();
@ -374,7 +516,7 @@ describe('TPA search page', () => {
} }
); );
test.each([null, undefined, '', 'non empty string'])( test.each([null, undefined, '', 'non empty string'])(
'shopNotListedModalLink returns value from getCmsContent', 'shopNotListedModalLink returns "%p" from getCmsContent',
(cmsContent) => { (cmsContent) => {
// Arrange // Arrange
const mountOptions = getMountOptions(); const mountOptions = getMountOptions();
@ -508,7 +650,7 @@ describe('TPA search page', () => {
await wrapper.setData({ await wrapper.setData({
providers: [ providers: [
{ {
name: 'Shop Auto Glass', companyName: 'Shop Auto Glass',
phoneNumber: '740-555-1234', phoneNumber: '740-555-1234',
distanceInMiles: 1.2345, distanceInMiles: 1.2345,
providerNumber: '232209', providerNumber: '232209',
@ -520,7 +662,7 @@ describe('TPA search page', () => {
} }
}, },
{ {
name: 'Safelite AutoGlass', companyName: 'Safelite AutoGlass',
phoneNumber: '676-555-0099', phoneNumber: '676-555-0099',
distanceInMiles: 123.9452, distanceInMiles: 123.9452,
providerNumber: '123456', providerNumber: '123456',
@ -547,7 +689,7 @@ describe('TPA search page', () => {
}); });
describe('providerButtonData', () => { describe('providerButtonData', () => {
test.each([null, undefined, []])( test.each([null, undefined, []])(
'returns empty list when providers empty', 'returns empty list when providers equals "%p"',
async (providers) => { async (providers) => {
// Arrange // Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions()); const wrapper = shallowMount(tpaSearch, getMountOptions());
@ -566,7 +708,7 @@ describe('TPA search page', () => {
await wrapper.setData({ await wrapper.setData({
providers: [ providers: [
{ {
name: 'Shop Auto Glass', companyName: 'Shop Auto Glass',
phoneNumber: '740-555-1234', phoneNumber: '740-555-1234',
distanceInMiles: 1.2345, distanceInMiles: 1.2345,
providerNumber: '232209', providerNumber: '232209',
@ -578,7 +720,7 @@ describe('TPA search page', () => {
} }
}, },
{ {
name: 'Safelite AutoGlass', companyName: 'Safelite AutoGlass',
phoneNumber: '676-555-0099', phoneNumber: '676-555-0099',
distanceInMiles: 123.9452, distanceInMiles: 123.9452,
providerNumber: '123456', providerNumber: '123456',
@ -613,8 +755,86 @@ describe('TPA search page', () => {
expect(result).toEqual(expectedResult); expect(result).toEqual(expectedResult);
}); });
}); });
describe('selectedProviderIsSafeliteShop', () => {
test('no issue when provider in providers with no provider number', async () => {
// Arrange
const selectedProviderNumber = '10099';
const providers = [
{ isSafeliteShop: false },
{ isSafeliteShop: true }
];
const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ selectedProviderNumber, providers });
// Act
const result = wrapper.vm.selectedProviderIsSafeliteShop;
// Assert
expect(result).toEqual(false);
});
test.each([null, undefined, []])(
'returns false when providers %s',
async (providers) => {
// Arrange
const selectedProviderNumber = '10099';
const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ selectedProviderNumber, providers });
// Act
const result = wrapper.vm.selectedProviderIsSafeliteShop;
// Assert
expect(result).toEqual(false);
}
);
test.each([[true, true], [false, false]])(
'returns %p when first instance of multiple matching providerNumbers has isSafeliteShop value %p',
async (expectedOutput, isSafeliteShop) => {
// Arrange
const selectedProviderNumber = '29987';
const providers = [
{
providerNumber: selectedProviderNumber,
isSafeliteShop
},
{
providerNumber: selectedProviderNumber,
isSafeliteShop: !isSafeliteShop
}
];
const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ selectedProviderNumber, providers });
// Act
const result = wrapper.vm.selectedProviderIsSafeliteShop;
// Assert
expect(result).toEqual(expectedOutput);
}
);
test.each([[false, false], [true, true]])(
'returns %p when providerNumber match has isSafeliteShop equal to %p',
async (expectedOutput, isSafeliteShop) => {
// Arrange
const selectedProviderNumber = '19274';
const providers = [{
providerNumber: selectedProviderNumber,
isSafeliteShop
}];
const wrapper = shallowMount(tpaSearch, getMountOptions());
await wrapper.setData({ selectedProviderNumber, providers });
// Act
const result = wrapper.vm.selectedProviderIsSafeliteShop;
// Assert
expect(result).toEqual(expectedOutput);
}
);
});
}); });
describe('watch', () => { describe('watch', () => {
// TODO update/add to filter watch tests
test('on filter calls getTpaProviders and sets providers', async () => { test('on filter calls getTpaProviders and sets providers', async () => {
// Arrange // Arrange
const zipCode = '78220'; const zipCode = '78220';
@ -635,6 +855,7 @@ describe('TPA search page', () => {
expect(wrapper.vm.providers).toEqual(providers.data.shopProviders); expect(wrapper.vm.providers).toEqual(providers.data.shopProviders);
expect(wrapper.vm.mapZipCode).toBe(zipCode); expect(wrapper.vm.mapZipCode).toBe(zipCode);
}); });
// TODO confirm tests appropriate. Also should this method be async?
describe('on providers', () => { describe('on providers', () => {
test.each([null, undefined, []])( test.each([null, undefined, []])(
'sets selected provider number to "" when there are no providers', 'sets selected provider number to "" when there are no providers',
@ -686,7 +907,6 @@ describe('TPA search page', () => {
(streetAddress, city, state, zipCode, expected) => { (streetAddress, city, state, zipCode, expected) => {
// Arrange // Arrange
const { wrapper } = getMountedComponent(); const { wrapper } = getMountedComponent();
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders));
const provider = { const provider = {
address: { address: {
streetAddress, streetAddress,
@ -723,7 +943,7 @@ describe('TPA search page', () => {
const { wrapper } = getMountedComponent(); const { wrapper } = getMountedComponent();
const providers = { const providers = {
data: { data: {
shopProviders: [{ name: 'foo' }] shopProviders: [{ companyName: 'foo' }]
} }
}; };
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers)); useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers));
@ -774,7 +994,7 @@ describe('TPA search page', () => {
zipCode, zipCode,
mapZipCode: initialMapZipCode mapZipCode: initialMapZipCode
}); });
const shopProviders = [{ name: 'foo' }]; const shopProviders = [{ companyName: 'foo' }];
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => ({ useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => ({
data: { shopProviders } data: { shopProviders }
})); }));
@ -797,15 +1017,46 @@ describe('TPA search page', () => {
// Assert // Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
}); });
test('forwardButtonAction invokes navigate method', () => { describe('forwardButtonAction', () => {
test('invokes navigate method with safelite shop scenario when selectedProviderIsSafeliteShop true', async () => {
// Arrange // Arrange
const { wrapper } = getMountedComponent(); const { wrapper } = getMountedComponent();
const providerNumber = '1109';
await wrapper.setData({
selectedProviderNumber: providerNumber,
providers: [{
providerNumber,
isSafeliteShop: true
}]
});
// Act // Act
wrapper.vm.backButtonAction(); wrapper.vm.forwardButtonAction();
// Assert // Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP, undefined);
});
test('invokes navigate method with non safelite shop scenario when selectedProviderIsSafeliteShop false', async () => {
// Arrange
const { wrapper } = getMountedComponent();
const providerNumber = '928';
await wrapper.setData({
selectedProviderNumber: providerNumber,
providers: [{
providerNumber,
isSafeliteShop: false
}]
});
// Act
wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP,
undefined
);
});
}); });
describe('getCustomValueFromString', () => { describe('getCustomValueFromString', () => {
test('returns radius in miles value if "radiusInMiles"', () => { test('returns radius in miles value if "radiusInMiles"', () => {
@ -855,7 +1106,7 @@ describe('TPA search page', () => {
// Arrange // Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions()); const wrapper = shallowMount(tpaSearch, getMountOptions());
const provider = { const provider = {
name: 'Shop Auto Glass', companyName: 'Shop Auto Glass',
phoneNumber: '740-555-1234', phoneNumber: '740-555-1234',
distanceInMiles: 1.2345, distanceInMiles: 1.2345,
providerNumber: '232209', providerNumber: '232209',
@ -882,10 +1133,10 @@ describe('TPA search page', () => {
describe('returns result with button label', () => { describe('returns result with button label', () => {
test.each(['', undefined, null])( test.each(['', undefined, null])(
'equal to empty string when name is empty', 'equal to empty string when name is empty',
(name) => { (companyName) => {
// Arrange // Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions()); const wrapper = shallowMount(tpaSearch, getMountOptions());
const provider = { name }; const provider = { companyName };
// Act // Act
const result = wrapper.vm.getShopButtonDataFromProvider(provider); const result = wrapper.vm.getShopButtonDataFromProvider(provider);
@ -897,14 +1148,14 @@ describe('TPA search page', () => {
test('returns provider name when exists', () => { test('returns provider name when exists', () => {
// Arrange // Arrange
const wrapper = shallowMount(tpaSearch, getMountOptions()); const wrapper = shallowMount(tpaSearch, getMountOptions());
const name = 'Some Shop Name'; const companyName = 'Some Shop Name';
const provider = { name }; const provider = { companyName };
// Act // Act
const result = wrapper.vm.getShopButtonDataFromProvider(provider); const result = wrapper.vm.getShopButtonDataFromProvider(provider);
// Assert // Assert
expect(result.buttonLabel).toEqual(name); expect(result.buttonLabel).toEqual(companyName);
}); });
}); });
describe('returns result with button label sub copy', () => { describe('returns result with button label sub copy', () => {
@ -994,7 +1245,7 @@ describe('TPA search page', () => {
const { wrapper } = getMountedComponent(); const { wrapper } = getMountedComponent();
const zipCode = '18394'; const zipCode = '18394';
useMainStore().order.customer.address.zipCode = zipCode; useMainStore().order.customer.address.zipCode = zipCode;
const providers = [{ name: 'provider' }]; const providers = [{ companyName: 'provider' }];
useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) =>
({ data: { shopProviders: radius === 25 ? providers : [] } })); ({ data: { shopProviders: radius === 25 ? providers : [] } }));
const expectedFilter = '25 miles'; const expectedFilter = '25 miles';
@ -1013,13 +1264,14 @@ describe('TPA search page', () => {
// Assert // Assert
expect(wrapper.vm.filter).toBe(expectedFilter); expect(wrapper.vm.filter).toBe(expectedFilter);
expect(wrapper.vm.providers).toEqual(providers); expect(wrapper.vm.providers).toEqual(providers);
expect(useMainStore().getTpaProviders).toBeCalledTimes(1);
}); });
test('when providers exist at 50 mile radius but not 25, filter is set to "50 miles" and providers set to expected', async () => { test('when providers exist at 50 mile radius but not 25, filter is set to "50 miles" and providers set to expected', async () => {
// Arrange // Arrange
const { wrapper } = getMountedComponent(); const { wrapper } = getMountedComponent();
const zipCode = '18394'; const zipCode = '18394';
useMainStore().order.customer.address.zipCode = zipCode; useMainStore().order.customer.address.zipCode = zipCode;
const providers = [{ name: 'provider' }]; const providers = [{ companyName: 'provider' }];
useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) =>
({ data: { shopProviders: radius === 50 ? providers : [] } })); ({ data: { shopProviders: radius === 50 ? providers : [] } }));
const expectedFilter = '50 miles'; const expectedFilter = '50 miles';
@ -1038,6 +1290,7 @@ describe('TPA search page', () => {
// Assert // Assert
expect(wrapper.vm.filter).toBe(expectedFilter); expect(wrapper.vm.filter).toBe(expectedFilter);
expect(wrapper.vm.providers).toEqual(providers); expect(wrapper.vm.providers).toEqual(providers);
expect(useMainStore().getTpaProviders).toBeCalledTimes(2);
}); });
test( test(
'when providers exist at 100 mile radius but not 50 or 25, filter is set to "100 miles" and providers set to expected', 'when providers exist at 100 mile radius but not 50 or 25, filter is set to "100 miles" and providers set to expected',
@ -1046,7 +1299,7 @@ describe('TPA search page', () => {
const { wrapper } = getMountedComponent(); const { wrapper } = getMountedComponent();
const zipCode = '18394'; const zipCode = '18394';
useMainStore().order.customer.address.zipCode = zipCode; useMainStore().order.customer.address.zipCode = zipCode;
const providers = [{ name: 'provider' }]; const providers = [{ companyName: 'provider' }];
useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) =>
({ data: { shopProviders: radius === 100 ? providers : [] } })); ({ data: { shopProviders: radius === 100 ? providers : [] } }));
const expectedFilter = '100 miles'; const expectedFilter = '100 miles';
@ -1065,6 +1318,7 @@ describe('TPA search page', () => {
// Assert // Assert
expect(wrapper.vm.filter).toBe(expectedFilter); expect(wrapper.vm.filter).toBe(expectedFilter);
expect(wrapper.vm.providers).toEqual(providers); expect(wrapper.vm.providers).toEqual(providers);
expect(useMainStore().getTpaProviders).toBeCalledTimes(3);
} }
); );
test( test(
@ -1091,6 +1345,7 @@ describe('TPA search page', () => {
// Assert // Assert
expect(wrapper.vm.filter).toBe(expectedFilter); expect(wrapper.vm.filter).toBe(expectedFilter);
expect(wrapper.vm.providers).toEqual([]); expect(wrapper.vm.providers).toEqual([]);
expect(useMainStore().getTpaProviders).toBeCalledTimes(3);
} }
); );
}); });

View file

@ -37,6 +37,14 @@
</div> </div>
</div> </div>
</Form> </Form>
<loader
v-if="initialLoading"
id="mapLoader"
loaderPosition="center"
loaderColor="blue"
:width="4"
:height="4" />
<div v-else>
<googleMap <googleMap
id="map" id="map"
class="mb-4" class="mb-4"
@ -57,7 +65,15 @@
:options="filterOptions" :options="filterOptions"
disableAutoFill disableAutoFill
:validationRules="rules.filter" /> :validationRules="rules.filter" />
<loader
v-if="reloadProviders"
id="providersLoader"
loaderPosition="center"
loaderColor="blue"
:width="4"
:height="4"
class="my-4" />
<div v-else>
<div class="my-4"> <div class="my-4">
<buttonQuestion <buttonQuestion
id="selectProviderQuestion" id="selectProviderQuestion"
@ -89,6 +105,7 @@
@clickEvent="doNotSeeMyShopLinkClick" /> @clickEvent="doNotSeeMyShopLinkClick" />
</div> </div>
</div> </div>
</div>
<siteFooter <siteFooter
ref="siteFooter" ref="siteFooter"
:cmsWidgetName="widget.siteFooter" :cmsWidgetName="widget.siteFooter"
@ -99,6 +116,7 @@
</Form> </Form>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
// Components // Components
@ -112,6 +130,7 @@ import alert from '@/ux-components/alert/alert.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue'; import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import googleMap from '@/iss-components/google-map/google-map.vue'; import googleMap from '@/iss-components/google-map/google-map.vue';
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue'; import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';
import loader from '@/ux-components/loader/loader.vue';
// Supporting files // Supporting files
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper'; import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
@ -120,6 +139,7 @@ import { useMainStore } from '@/store';
import BaseFormMixin from '@/mixins/base-form-mixin.js'; import BaseFormMixin from '@/mixins/base-form-mixin.js';
import globalRules from '@/constants/global-rules.js'; import globalRules from '@/constants/global-rules.js';
import widgetFields from '@/constants/cms-widget-fields.js'; import widgetFields from '@/constants/cms-widget-fields.js';
import { shallowRef } from 'vue';
export default { export default {
name: 'tpa-search', name: 'tpa-search',
@ -130,6 +150,7 @@ export default {
buttonQuestion, buttonQuestion,
textLink, textLink,
alert, alert,
loader,
googleMap, googleMap,
siteFooter, siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names // eslint-disable-next-line vue/no-reserved-component-names
@ -159,6 +180,8 @@ export default {
filter: '', filter: '',
providers: [], providers: [],
selectedProviderNumber: '', selectedProviderNumber: '',
initialLoading: false,
reloadProviders: false,
additionalButtonData: { additionalButtonData: {
displayAvailabilityIndicators: false displayAvailabilityIndicators: false
}, },
@ -176,7 +199,21 @@ export default {
filter: globalRules.OPTION_REQUIRED, // TODO do we even need this? filter: globalRules.OPTION_REQUIRED, // TODO do we even need this?
provider: globalRules.OPTION_REQUIRED provider: globalRules.OPTION_REQUIRED
}, },
shopListButton shopListButton: shallowRef(shopListButton),
radiusFilterPairs: [
{
radius: 25,
filter: '25 miles'
},
{
radius: 50,
filter: '50 miles'
},
{
radius: 100,
filter: '100 miles'
}
]
}; };
}, },
computed: { computed: {
@ -210,16 +247,8 @@ export default {
); );
}, },
radiusInMiles() { radiusInMiles() {
switch (this.filter) { const pair = this.radiusFilterPairs.find((p) => p.filter === this.filter);
case '25 miles': return pair?.radius ?? 0;
return 25;
case '50 miles':
return 50;
case '100 miles':
return 100;
default:
return 0;
}
}, },
noNetworkShopsAlertHeaderText() { noNetworkShopsAlertHeaderText() {
return this.getCmsContent( return this.getCmsContent(
@ -232,18 +261,28 @@ export default {
}, },
providerButtonData() { providerButtonData() {
return this.providers?.map((provider) => this.getShopButtonDataFromProvider(provider)) ?? []; return this.providers?.map((provider) => this.getShopButtonDataFromProvider(provider)) ?? [];
},
selectedProviderIsSafeliteShop() {
return this.providers?.find((p) => p.providerNumber === this.selectedProviderNumber)?.isSafeliteShop ?? false;
} }
}, },
watch: { watch: {
async filter() { async filter() {
// TODO right now this results in getProviders being called once more than it needs to be if (!this.initialLoading) {
this.providers = await this.getProviderButtonData(); this.reloadProviders = true;
await this.getProviderButtonData().then((providers) => {
this.providers = providers;
});
this.mapZipCode = this.zipCode; this.mapZipCode = this.zipCode;
this.reloadProviders = false;
} else {
this.initialLoading = false;
}
}, },
providers(newProviders) { async providers(newProviders) {
this.selectedProviderNumber = newProviders?.length === 1 ?? false if (newProviders?.length === 1 ?? false) {
? newProviders[0].providerNumber this.selectedProviderNumber = newProviders[0]?.providerNumber ?? '';
: ''; }
} }
}, },
methods: methods:
@ -276,25 +315,25 @@ export default {
: addressLine2; : addressLine2;
}, },
async setInitialFilterAndProviders() { async setInitialFilterAndProviders() {
const radiusOptions = [25, 50, 100]; this.initialLoading = true;
const { zipCode } = useMainStore().order.customer.address; const { zipCode } = useMainStore().order.customer.address;
const tpaProvidersRadius25 = await useMainStore().getTpaProviders(zipCode, radiusOptions[0]); let pairIndex = 0;
const tpaProvidersRadius50 = await useMainStore().getTpaProviders(zipCode, radiusOptions[1]); let providers = [];
const tpaProvidersRadius100 = await useMainStore().getTpaProviders(zipCode, radiusOptions[2]); let filter = '';
let radius = '25 miles'; while (providers.length === 0 && pairIndex < this.radiusFilterPairs.length) {
const pair = this.radiusFilterPairs[pairIndex];
const { radius } = pair;
filter = pair.filter;
// eslint-disable-next-line no-await-in-loop
const result = await useMainStore().getTpaProviders(zipCode, radius);
providers = result?.data?.shopProviders ?? [];
let providers = tpaProvidersRadius25; pairIndex += 1;
if ((tpaProvidersRadius25?.data?.shopProviders ?? []).length === 0) {
radius = '50 miles';
providers = tpaProvidersRadius50;
if ((tpaProvidersRadius50?.data?.shopProviders ?? []).length === 0) {
radius = '100 miles';
providers = tpaProvidersRadius100;
} }
}
this.filter = radius; this.filter = filter;
this.providers = providers?.data?.shopProviders ?? []; this.providers = providers;
}, },
async getProviderButtonData() { async getProviderButtonData() {
const getTpaProvidersResult = await useMainStore().getTpaProviders(this.zipCode, this.radiusInMiles); const getTpaProvidersResult = await useMainStore().getTpaProviders(this.zipCode, this.radiusInMiles);
@ -307,17 +346,19 @@ export default {
); );
}, },
async searchClick() { async searchClick() {
this.reloadProviders = true;
this.providers = await this.getProviderButtonData(); this.providers = await this.getProviderButtonData();
this.mapZipCode = this.zipCode; this.mapZipCode = this.zipCode;
this.reloadProviders = false;
}, },
backButtonAction() { backButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route); this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
}, },
forwardButtonAction() { forwardButtonAction() {
this.$router.navigate( const scenario = this.selectedProviderIsSafeliteShop
this.navigationScenarios.CLICKED_FORWARD, ? this.navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP
this.$route : this.navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP;
); this.$router.navigate(scenario, this.$route);
}, },
getCustomValueFromString(str) { getCustomValueFromString(str) {
switch (str) { switch (str) {
@ -334,7 +375,7 @@ export default {
: null; : null;
return { return {
buttonLabel: provider?.name ?? '', buttonLabel: provider?.companyName ?? '',
buttonLabelSubCopy: distance === null buttonLabelSubCopy: distance === null
? '' ? ''
: `${distance} mi`, : `${distance} mi`,
@ -347,7 +388,6 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.search-question { .search-question {
font-size: $h5-font-size; font-size: $h5-font-size;
} }

View file

@ -65,6 +65,8 @@ const navigationScenarios = Object.freeze({
// TPA Search // TPA Search
CLICKED_DO_NOT_SEE_MY_SHOP_LINK: 'CLICKED_DO_NOT_SEE_MY_SHOP_LINK', CLICKED_DO_NOT_SEE_MY_SHOP_LINK: 'CLICKED_DO_NOT_SEE_MY_SHOP_LINK',
CLICKED_FORWARD_WITH_SAFELITE_SHOP: 'CLICKED_FORWARD_WITH_SAFELITE_SHOP',
CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP: 'CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP',
// Provider Preference // Provider Preference
CLICKED_FORWARD_WITH_SAFELITE: 'CLICKED_FORWARD_WITH_SAFELITE', CLICKED_FORWARD_WITH_SAFELITE: 'CLICKED_FORWARD_WITH_SAFELITE',

View file

@ -664,7 +664,11 @@ const routingTable = () => [
destinationIssPageValue: issPageValues.PROVIDER_PREFERENCE destinationIssPageValue: issPageValues.PROVIDER_PREFERENCE
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD, scenario: navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP,
destinationIssPageValue: issPageValues.SERVICE_LOCATION
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP,
destinationIssPageValue: issPageValues.TPA_SUBMIT destinationIssPageValue: issPageValues.TPA_SUBMIT
}, },
{ {

View file

@ -857,78 +857,15 @@ export const useMainStore = defineStore({
}); });
}, },
// TODO update with real endpoint
getTpaProviders(zipCode, radius) { getTpaProviders(zipCode, radius) {
const damageType = this.damage.isRepair ? 'Repair' : 'Replace';
const { parentAccountNumber } = this.issConfig;
const safeliteOnly = false;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (radius === 25) { globalMethods.callHttpClient({
resolve({ method: endpoints.GetProviders.method,
data: { endpoint: `${endpoints.GetProviders.url}/${zipCode}/${damageType}/${radius}/${parentAccountNumber}/${safeliteOnly}`
mobileProviderNumber: null, }).then((response) => resolve(response), (error) => reject(error));
shopProviders: null
}
});
} else if (radius === 50) {
resolve({
data: {
mobileProviderNumber: null,
shopProviders: [
{
name: 'USA Auto Glass',
address: {
city: 'WESTERVILLE',
country: 'US',
state: 'OH',
streetAddress: '4403 EXECUTIVE PKWY',
streetAddress2: '',
zipCode: '43081',
zipCodeCtu: '01820'
},
distanceInMiles: 8.393453111956896,
providerNumber: '003335',
phoneNumber: '614-123-5555'
}
]
}
});
} else {
resolve({
data: {
mobileProviderNumber: null,
shopProviders: [
{
name: 'USA Auto Glass',
address: {
city: 'WESTERVILLE',
country: 'US',
state: 'OH',
streetAddress: '4403 EXECUTIVE PKWY',
streetAddress2: '',
zipCode: '43081',
zipCodeCtu: '01820'
},
distanceInMiles: 8.393453111956896,
providerNumber: '003335',
phoneNumber: '614-123-5555'
},
{
name: 'Safelite AutoGlass',
address: {
city: 'WORTHINGTON',
country: 'US',
state: 'OH',
streetAddress: '760 DEARBORN PARK LN',
streetAddress2: '',
zipCode: '43085',
zipCodeCtu: '01820'
},
distanceInMiles: 8.704336770196678,
providerNumber: '001820',
phoneNumber: '740-555-1234'
}
]
}
});
}
}); });
}, },

View file

@ -1961,4 +1961,120 @@ describe('Store', () => {
expect(store.order.currentDeductible).toEqual(finalDeductible); expect(store.order.currentDeductible).toEqual(finalDeductible);
}); });
}); });
describe('getTpaProviders method', () => {
describe('successful method call', () => {
it.each([[true, 'Repair'], [false, 'Replace']])(
'calls getProviders api endpoint',
async (isRepair, damageType) => {
// Arrange
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
const parentAccountNumber = getRandomString(6, 6);
store.issConfig.parentAccountNumber = parentAccountNumber;
store.order.damage.isRepair = isRepair;
const zipCode = getRandomString(6, 6);
const radius = getRandomInt(5, 100);
const expectedEndpoint = [
endpoints.GetProviders.url,
zipCode,
damageType,
radius,
parentAccountNumber,
'false'
].join('/');
// Act
await store.getTpaProviders(zipCode, radius);
// Asserts
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(expect.objectContaining({
method: endpoints.GetProviders.method,
endpoint: expectedEndpoint
}));
}
);
it('Returns expected response object', async () => {
// Arrange
const response = { ReferralNumber: getRandomString(6, 6) };
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(response));
const zipCode = getRandomString(6, 6);
const radius = getRandomInt(5, 100);
// Act
const result = store.getTpaProviders(zipCode, radius);
// Asserts
await expect(result).resolves.toBe(response);
});
it('null response by api => no exceptions thrown', async () => {
// Arrange
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(null));
const zipCode = getRandomString(6, 6);
const radius = getRandomInt(5, 100);
// Act
await store.getTpaProviders(zipCode, radius);
});
it('no providers returned by api => promise resolves to empty list', async () => {
// Arrange
const responseNoProviders = {
data: {
shopProviders: []
}
};
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(responseNoProviders));
const zipCode = getRandomString(6, 6);
const radius = getRandomInt(5, 100);
// Act
const result = await store.getTpaProviders(zipCode, radius);
// Asserts
expect(result.data.shopProviders).toHaveLength(0);
});
it('multiple providers returned by api => promise resolves to expected', async () => {
// Arrange
const provider1 = {
companyName: getRandomString(6, 6)
};
const provider2 = {
companyName: getRandomString(6, 6)
};
const responseProviders = {
data: {
shopProviders: [provider1, provider2]
}
};
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(responseProviders));
// Act
const result = await store.getTpaProviders();
// Asserts
expect(result.data.shopProviders).toHaveLength(2);
expect(result.data.shopProviders[0]).toBe(provider1);
expect(result.data.shopProviders[1]).toBe(provider2);
});
});
it('api call throws exception => policyLookupSuccessful false', async () => {
expect.assertions(3);
const error = 'get coverage policy info error';
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.reject(error));
// Act
await store.getCoveragePolicyInfo().catch((e) => {
expect(e).toEqual(error);
});
// Asserts
expect(globalMethods.callHttpClient)
.toHaveBeenCalledWith(expect.objectContaining({
method: endpoints.CoveragePolicyInfo.method,
endpoint: endpoints.CoveragePolicyInfo.url
}));
expect(store.order.policy.policyLookupSuccessful).toBe(false);
});
});
}); });

View file

@ -1,5 +1,6 @@
<template> <template>
<div <div
:style="cssProps"
class="loader" class="loader"
role="alert" role="alert"
aria-label="Loading new page" aria-label="Loading new page"
@ -18,6 +19,22 @@ export default {
/* Position options: center, right, left (OPTIONAL, do NOT use on btn-* classes) */ /* Position options: center, right, left (OPTIONAL, do NOT use on btn-* classes) */
loaderPosition: { loaderPosition: {
type: String type: String
},
width: {
type: Number,
default: 1
},
height: {
type: Number,
default: 1
}
},
computed: {
cssProps() {
return {
'--loader-width': `${this.width}rem`,
'--loader-height': `${this.height}rem`
};
} }
} }
}; };
@ -45,8 +62,8 @@ export default {
mask: url(../../assets/img/icons/spinner.svg); mask: url(../../assets/img/icons/spinner.svg);
mask-size: cover; mask-size: cover;
position: relative; position: relative;
width: 1rem; width: var(--loader-width);
height: 1rem; height: var(--loader-height);
border-radius:50%; border-radius:50%;
animation: rotation 1s infinite linear; animation: rotation 1s infinite linear;
@keyframes rotation { @keyframes rotation {
@ -57,8 +74,7 @@ export default {
} }
//Spinner position //Spinner position
&.center { &.center {
right: 50%; justify-content: center;
transform: translateX(50%);
} }
&.right { &.right {
right: 1rem; right: 1rem;