Merge pull request #499 from Safelite/feature/digital/SSR-836
Feature/digital/ssr 836
This commit is contained in:
commit
a6d55d1712
15 changed files with 1100 additions and 112 deletions
|
|
@ -88,6 +88,7 @@ stages:
|
|||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
__VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
|
|
@ -137,6 +138,7 @@ stages:
|
|||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
__VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
|
|
@ -186,6 +188,7 @@ stages:
|
|||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
__VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
|
|
@ -235,6 +238,7 @@ stages:
|
|||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
__VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
window.dataLayer = [{}];
|
||||
</script>
|
||||
<script><%= process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY %></script>
|
||||
<script><%= process.env.VUE_APP_GOOGLE_MAPS_API_SCRIPT %></script>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ const endpoints = Object.freeze({
|
|||
method: 'GET'
|
||||
},
|
||||
GooglePlaces: {
|
||||
url: 'https://maps.googleapis.com/maps/api/js?key={apiKey}&libraries=places'
|
||||
url: (apiKey) => `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`
|
||||
},
|
||||
ValidateClientTag: {
|
||||
url: '/clientauth/api/v1/clientauth/validate-client-tag',
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ export default {
|
|||
const addressField1 = document.getElementById('autocomplete');
|
||||
const self = this;
|
||||
|
||||
const url = endpoints.GooglePlaces.url.replace('{apiKey}', applicationConfig.GOOGLE_PLACES_API_KEY);
|
||||
const url = endpoints.GooglePlaces.url(applicationConfig.GOOGLE_PLACES_API_KEY);
|
||||
|
||||
this.$loadScript(url)
|
||||
.then(() => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Google Map should render expected initial data 1`] = `
|
||||
Object {
|
||||
"country": "USA",
|
||||
"geocoder": null,
|
||||
}
|
||||
`;
|
||||
428
src/iss-components/google-map/google-map.spec.js
Normal file
428
src/iss-components/google-map/google-map.spec.js
Normal file
|
|
@ -0,0 +1,428 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import googleMap from '@/iss-components/google-map/google-map.vue';
|
||||
|
||||
const mockFitBounds = jest.fn();
|
||||
const mockMapInstance = {
|
||||
fitBounds: mockFitBounds
|
||||
};
|
||||
const mockMap = jest.fn(() => mockMapInstance);
|
||||
|
||||
const mockGeocode = jest.fn();
|
||||
const mockGeocoderInstance = {
|
||||
geocode: mockGeocode
|
||||
};
|
||||
const mockGeocoder = jest.fn(() => (mockGeocoderInstance));
|
||||
|
||||
const mockAdvancedMarkerElement = jest.fn();
|
||||
|
||||
const mockImportLibrary = jest.fn().mockImplementation(() => (Promise.resolve({
|
||||
Map: mockMap,
|
||||
Geocoder: mockGeocoder,
|
||||
AdvancedMarkerElement: mockAdvancedMarkerElement
|
||||
})));
|
||||
|
||||
const mockExtend = jest.fn();
|
||||
const mockLatLngBound = { extend: mockExtend };
|
||||
|
||||
const setupGoogleMock = () => {
|
||||
global.window.google = {
|
||||
maps: {
|
||||
importLibrary: mockImportLibrary,
|
||||
LatLngBounds: jest.fn(() => (mockLatLngBound))
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// clear constructors
|
||||
mockMap.mockClear();
|
||||
mockGeocoder.mockClear();
|
||||
mockAdvancedMarkerElement.mockClear();
|
||||
|
||||
// clear methods
|
||||
mockExtend.mockClear();
|
||||
mockGeocode.mockClear();
|
||||
mockFitBounds.mockClear();
|
||||
|
||||
setupGoogleMock();
|
||||
});
|
||||
|
||||
describe('Google Map', () => {
|
||||
describe('should render', () => {
|
||||
test('expected initial data', async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.$data).toMatchSnapshot();
|
||||
});
|
||||
test('map', () => {
|
||||
// Arrange
|
||||
const mapReference = '#map';
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
|
||||
// Act
|
||||
const map = wrapper.find(mapReference);
|
||||
|
||||
// Assert
|
||||
expect(map.exists()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
test.each([
|
||||
['13001', '13001 USA'],
|
||||
['9', '9 USA'],
|
||||
['', ' USA'],
|
||||
[null, ' USA']
|
||||
])(
|
||||
'given that zipcode set to "%p", computed zipCodeAddress should return "%p"',
|
||||
async (zipCode, expected) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
await wrapper.setData({ zipCode });
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.zipCodeAddress;
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(expected);
|
||||
}
|
||||
);
|
||||
test('watch addresses creates map with expected markers', async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
const zipCode = '00882';
|
||||
await wrapper.setData({ zipCode });
|
||||
|
||||
const mockGetCenter = jest.fn();
|
||||
const mockNorthEast = jest.fn();
|
||||
const mockSouthWest = jest.fn();
|
||||
const zipCodeGeocodeResult = {
|
||||
results: [
|
||||
{
|
||||
geometry: {
|
||||
bounds:
|
||||
{
|
||||
getCenter: mockGetCenter,
|
||||
getNorthEast: mockNorthEast,
|
||||
getSouthWest: mockSouthWest
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const [address1, address2, position1, position2] = ['a1', 'a2', 'p1', 'p2'];
|
||||
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 newAddresses = [address1, address2];
|
||||
|
||||
// Act
|
||||
await wrapper.vm.$options.watch.addresses.call(wrapper.vm, newAddresses);
|
||||
|
||||
for (let i = 0; i < 11; i++) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await wrapper.vm.$nextTick();
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
describe('method', () => {
|
||||
test('getMap calls map constructor with expected parameters', async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
const center = { lat: 90, lng: -5 };
|
||||
|
||||
// Act
|
||||
const result = await wrapper.vm.getMap(center);
|
||||
|
||||
// Assert
|
||||
expect(mockMap).toHaveBeenCalledWith(
|
||||
null,
|
||||
expect.objectContaining({ center, mapId: 'map_id', mapTypeControl: false })
|
||||
);
|
||||
expect(result).toBe(mockMapInstance);
|
||||
});
|
||||
test.each([
|
||||
[undefined, 0],
|
||||
[null, 0],
|
||||
[[], 0],
|
||||
[['loc1'], 1],
|
||||
[['loc1', 'loc1', 'loc1', 'loc1'], 4]
|
||||
])(
|
||||
'given parameter %p, getBounds calls extend %p time(s)',
|
||||
(locations, numberOfExtendCalls) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.getBounds(locations);
|
||||
|
||||
// Assert
|
||||
expect(mockExtend).toHaveBeenCalledTimes(numberOfExtendCalls);
|
||||
expect(result).toBe(mockLatLngBound);
|
||||
}
|
||||
);
|
||||
test.each([
|
||||
[1, null, mockGeocoderInstance],
|
||||
[1, undefined, mockGeocoderInstance],
|
||||
[0, {}, {}],
|
||||
[0, { value: 'any value' }, { value: 'any value' }]
|
||||
])(
|
||||
'setGeocoder calls Geocoder constructor %p time(s) when original geocoder %p and geocoder set to %p',
|
||||
async (numberOfCalls, originalGeocoder, newGeocoder) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
await wrapper.setData({ geocoder: originalGeocoder });
|
||||
|
||||
// Act
|
||||
await wrapper.vm.setGeocoder();
|
||||
|
||||
// Assert
|
||||
expect(mockGeocoder).toHaveBeenCalledTimes(numberOfCalls);
|
||||
expect(wrapper.vm.geocoder).toEqual(newGeocoder);
|
||||
}
|
||||
);
|
||||
test.each([
|
||||
[null, 0],
|
||||
[undefined, 0],
|
||||
[[], 0],
|
||||
[['p1'], 1],
|
||||
[['p1', 'p2', 'p3'], 3]
|
||||
])(
|
||||
'addMarkersToMap given positions argument %p, creates AdvancedMarkerElement %p time(s)',
|
||||
async (positions, numberOfCalls) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
const map = jest.fn();
|
||||
|
||||
// Act
|
||||
await wrapper.vm.addMarkersToMap(map, positions);
|
||||
|
||||
// Assert
|
||||
expect(mockAdvancedMarkerElement).toHaveBeenCalledTimes(numberOfCalls);
|
||||
}
|
||||
);
|
||||
describe('getLocationsFromAddresses', () => {
|
||||
test.each([
|
||||
[null, 0],
|
||||
[undefined, 0],
|
||||
[[], 0],
|
||||
[['a1'], 1],
|
||||
[['a1', 'a2', 'a3'], 3]
|
||||
])(
|
||||
'given address(es) %p calls geocode %p times',
|
||||
async (addresses, numberOfCalls) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
|
||||
// Act
|
||||
await wrapper.vm.getLocationsFromAddresses(addresses);
|
||||
|
||||
// Assert
|
||||
expect(mockGeocode).toHaveBeenCalledTimes(numberOfCalls);
|
||||
expect(wrapper.vm.geocoder).not.toBeNull();
|
||||
}
|
||||
);
|
||||
test('only returns geocode results that are not null', async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
const addresses = ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8'];
|
||||
mockGeocode.mockImplementationOnce(() => null)
|
||||
.mockImplementationOnce(() => ({ results: null }))
|
||||
.mockImplementationOnce(() => ({ results: [] }))
|
||||
.mockImplementationOnce(() => ({ results: [{}] }))
|
||||
.mockImplementationOnce(() => (
|
||||
{ results: [{ geometry: undefined }] }
|
||||
))
|
||||
.mockImplementationOnce(() => (
|
||||
{ results: [{ geometry: {} }] }
|
||||
))
|
||||
.mockImplementationOnce(() => (
|
||||
{
|
||||
results: [
|
||||
{ geometry: { location: 'some value' } }
|
||||
]
|
||||
}
|
||||
))
|
||||
.mockImplementationOnce(() => (
|
||||
{
|
||||
results: [
|
||||
{ geometry: { location: 'some other value' } }
|
||||
]
|
||||
}
|
||||
));
|
||||
const expectedResult = ['some value', 'some other value'];
|
||||
|
||||
// Act
|
||||
const result = await wrapper.vm.getLocationsFromAddresses(addresses);
|
||||
|
||||
// Assert
|
||||
expect(result.length).toBe(2);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
describe('getBoundsFromAddress', () => {
|
||||
test('returns expected when geocode returns valid result', async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
const address = 'random address';
|
||||
const expectedResult = 'bounds returned';
|
||||
const geocodeResult = {
|
||||
results: [
|
||||
{
|
||||
geometry: { bounds: expectedResult }
|
||||
}
|
||||
]
|
||||
};
|
||||
mockGeocode.mockImplementationOnce(() => Promise.resolve(geocodeResult));
|
||||
|
||||
// Act
|
||||
const result = await wrapper.vm.getBoundsFromAddress(address);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(expectedResult);
|
||||
expect(wrapper.vm.geocoder).not.toBeNull();
|
||||
});
|
||||
test('returns null when geocode returns invalid result', async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
const address = 'random address';
|
||||
mockGeocode.mockImplementationOnce(() => Promise.resolve({}));
|
||||
|
||||
// Act
|
||||
const result = await wrapper.vm.getBoundsFromAddress(address);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
expect(wrapper.vm.geocoder).not.toBeNull();
|
||||
});
|
||||
});
|
||||
describe('createMapWithMarkersForAddresses', () => {
|
||||
test('calls expected when addresses provided', async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
const zipCode = '00882';
|
||||
await wrapper.setData({ zipCode });
|
||||
const mockGetCenter = jest.fn();
|
||||
const mockNorthEast = jest.fn();
|
||||
const mockSouthWest = jest.fn();
|
||||
const zipCodeGeocodeResult = {
|
||||
results: [
|
||||
{
|
||||
geometry: {
|
||||
bounds:
|
||||
{
|
||||
getCenter: mockGetCenter,
|
||||
getNorthEast: mockNorthEast,
|
||||
getSouthWest: mockSouthWest
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const shopGeocodeResult = {
|
||||
results: [
|
||||
{ geometry: { location: 'valid location' } }
|
||||
]
|
||||
};
|
||||
mockGeocode.mockImplementation((obj) =>
|
||||
Promise.resolve(obj.address.includes(zipCode)
|
||||
? zipCodeGeocodeResult
|
||||
: shopGeocodeResult));
|
||||
const addresses = ['a1', 'a2'];
|
||||
|
||||
// Act
|
||||
await wrapper.vm.createMapWithMarkersForAddresses(addresses);
|
||||
|
||||
// Assert
|
||||
expect(mockMap).toBeCalledTimes(1);
|
||||
expect(mockAdvancedMarkerElement).toBeCalledTimes(2);
|
||||
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.each([null, undefined, []])(
|
||||
'calls expected when addresses argument is "%p"',
|
||||
async (addresses) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
const zipCode = '00882';
|
||||
await wrapper.setData({ zipCode });
|
||||
const mockGetCenter = jest.fn();
|
||||
const mockNorthEast = jest.fn();
|
||||
const mockSouthWest = jest.fn();
|
||||
const zipCodeGeocodeResult = {
|
||||
results: [
|
||||
{
|
||||
geometry: {
|
||||
bounds:
|
||||
{
|
||||
getCenter: mockGetCenter,
|
||||
getNorthEast: mockNorthEast,
|
||||
getSouthWest: mockSouthWest
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
mockGeocode.mockImplementation(() => Promise.resolve(zipCodeGeocodeResult));
|
||||
|
||||
// Act
|
||||
await wrapper.vm.createMapWithMarkersForAddresses(addresses);
|
||||
|
||||
// Assert
|
||||
expect(mockMap).toBeCalledTimes(1);
|
||||
expect(mockAdvancedMarkerElement).toBeCalledTimes(0);
|
||||
expect(mockGeocoder).toBeCalledTimes(1);
|
||||
|
||||
expect(mockExtend).toBeCalledTimes(2);
|
||||
expect(mockFitBounds).toBeCalledTimes(1);
|
||||
|
||||
expect(mockGetCenter).toBeCalledTimes(1);
|
||||
expect(mockNorthEast).toBeCalledTimes(1);
|
||||
expect(mockSouthWest).toBeCalledTimes(1);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
93
src/iss-components/google-map/google-map.vue
Normal file
93
src/iss-components/google-map/google-map.vue
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<div>
|
||||
<div id="map" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'google-map',
|
||||
props: {
|
||||
addresses: Array,
|
||||
zipCode: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
country: 'USA',
|
||||
geocoder: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
zipCodeAddress() {
|
||||
return `${this.zipCode ?? ''} ${this.country}`;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async addresses(newAddresses) {
|
||||
this.createMapWithMarkersForAddresses(newAddresses);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getMap(center) {
|
||||
const { Map } = await window.google.maps.importLibrary('maps');
|
||||
return new Map(document.getElementById('map'), {
|
||||
center,
|
||||
mapId: 'map_id',
|
||||
mapTypeControl: false
|
||||
});
|
||||
},
|
||||
getBounds(locations) {
|
||||
const bounds = new window.google.maps.LatLngBounds();
|
||||
locations?.forEach((location) => bounds.extend(location));
|
||||
return bounds;
|
||||
},
|
||||
async setGeocoder() {
|
||||
if (this.geocoder == null) {
|
||||
const { Geocoder } = await window.google.maps.importLibrary('geocoding');
|
||||
this.geocoder = new Geocoder();
|
||||
}
|
||||
},
|
||||
async addMarkersToMap(map, positions) {
|
||||
const { AdvancedMarkerElement } = await window.google.maps.importLibrary('marker');
|
||||
positions?.forEach((position) => new AdvancedMarkerElement({ map, position }));
|
||||
},
|
||||
async getLocationsFromAddresses(addresses) {
|
||||
await this.setGeocoder();
|
||||
|
||||
const geocodeShopAddressPromises = addresses?.map((address) => this.geocoder.geocode({ address })) ?? [];
|
||||
const geocodeShopAddressResults = await Promise.all(geocodeShopAddressPromises);
|
||||
return geocodeShopAddressResults.map((result) =>
|
||||
(result?.results?.length > 0
|
||||
? result.results[0].geometry?.location
|
||||
: null))
|
||||
.filter((loc) => loc != null);
|
||||
},
|
||||
async getBoundsFromAddress(address) {
|
||||
await this.setGeocoder();
|
||||
const result = await this.geocoder.geocode({ address });
|
||||
return result?.results?.length > 0
|
||||
? result.results[0].geometry?.bounds
|
||||
: null;
|
||||
},
|
||||
async createMapWithMarkersForAddresses(addresses) {
|
||||
const shopPositions = await this.getLocationsFromAddresses(addresses);
|
||||
const zipBounds = await this.getBoundsFromAddress(this.zipCodeAddress);
|
||||
const map = await this.getMap(zipBounds.getCenter());
|
||||
|
||||
await this.addMarkersToMap(map, shopPositions);
|
||||
|
||||
const positionsToDisplay = shopPositions.concat(zipBounds.getNorthEast(), zipBounds.getSouthWest());
|
||||
const bounds = this.getBounds(positionsToDisplay);
|
||||
map.fitBounds(bounds);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
#map {
|
||||
height: 250px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -152,7 +152,7 @@ describe('Shop list button', () => {
|
|||
expect(buttonLabelSubCopy.classes().length).toBe(4);
|
||||
expect(buttonLabelSubCopy.classes()).toContain('m-0');
|
||||
expect(buttonLabelSubCopy.classes()).toContain('caption');
|
||||
expect(buttonLabelSubCopy.classes()).toContain('ms-1');
|
||||
expect(buttonLabelSubCopy.classes()).toContain('ms-2');
|
||||
expect(buttonLabelSubCopy.classes()).toContain(textPosition);
|
||||
});
|
||||
test('when "textPosition" prop not provided', async () => {
|
||||
|
|
@ -176,7 +176,7 @@ describe('Shop list button', () => {
|
|||
expect(buttonLabelSubCopy.classes().length).toBe(3);
|
||||
expect(buttonLabelSubCopy.classes()).toContain('m-0');
|
||||
expect(buttonLabelSubCopy.classes()).toContain('caption');
|
||||
expect(buttonLabelSubCopy.classes()).toContain('ms-1');
|
||||
expect(buttonLabelSubCopy.classes()).toContain('ms-2');
|
||||
});
|
||||
});
|
||||
describe('availability indicator block with expected when displayAvailabilityIndicators true', () => {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
</span>
|
||||
<span
|
||||
id="buttonLabelSubCopySpan"
|
||||
class="m-0 caption ms-1"
|
||||
class="m-0 caption ms-2"
|
||||
:class="textPosition">{{ buttonLabelSubCopy }}
|
||||
</span>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ Object {
|
|||
"displayAvailabilityIndicators": false,
|
||||
},
|
||||
"filter": "",
|
||||
"mapZipCode": "12663",
|
||||
"providers": Array [],
|
||||
"rules": Object {
|
||||
"filter": "option-required",
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRu
|
|||
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
||||
|
||||
const wrapper = shallowMount(tpaSearch, mountOptions);
|
||||
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => cmsContent);
|
||||
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {});
|
||||
wrapper.vm.setCmsContent = jest.fn();
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
@ -149,16 +149,31 @@ describe('TPA search page', () => {
|
|||
// Assert
|
||||
expect(providerSelectionForm.exists()).toBeTruthy();
|
||||
});
|
||||
test('map', () => {
|
||||
test('map', async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
const zipCode = '12345';
|
||||
const { wrapper } = getMountedComponent();
|
||||
await wrapper.setData({
|
||||
providers: [{
|
||||
address: {
|
||||
streetAddress: '123 Lane Ave',
|
||||
city: 'New York',
|
||||
state: 'NY',
|
||||
zipCode: '99230'
|
||||
}
|
||||
}],
|
||||
mapZipCode: zipCode
|
||||
});
|
||||
const expectedAddresses = ['123 Lane Ave, New York, NY 99230'];
|
||||
|
||||
// Act
|
||||
const map = wrapper.find('#map');
|
||||
const map = wrapper.findComponent('#map');
|
||||
|
||||
// Assert
|
||||
expect(map.exists()).toBeTruthy();
|
||||
expect(map.classes()).toContain('mb-4');
|
||||
expect(map.props().addresses).toEqual(expectedAddresses);
|
||||
expect(map.props().zipCode).toBe(zipCode);
|
||||
});
|
||||
test('search radius filter', () => {
|
||||
// Arrange
|
||||
|
|
@ -173,9 +188,32 @@ describe('TPA search page', () => {
|
|||
expect(searchRadiusFilter.props().cmsWidgetName).toBe(expectedWidgetName);
|
||||
expect(searchRadiusFilter.props().validationRules).toBe(globalRules.OPTION_REQUIRED);
|
||||
});
|
||||
test('select provider question', () => {
|
||||
test('select provider question', async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
await wrapper.setData({
|
||||
providers: [{
|
||||
name: 'Shop Auto Glass',
|
||||
phoneNumber: '740-555-1234',
|
||||
distanceInMiles: 1.2345,
|
||||
providerNumber: '232209',
|
||||
address: {
|
||||
streetAddress: '123 Lane Ave',
|
||||
city: 'New York',
|
||||
state: 'NY',
|
||||
zipCode: '99230'
|
||||
}
|
||||
}]
|
||||
});
|
||||
const expectedAnswers = [{
|
||||
buttonLabel: 'Shop Auto Glass',
|
||||
buttonLabelSubCopy: '1.2 mi',
|
||||
buttonBodyCopy: '123 Lane Ave, New York, NY 99230<br>740-555-1234',
|
||||
value: '232209'
|
||||
}];
|
||||
const expectedAdditionalButtonData = {
|
||||
displayAvailabilityIndicators: false
|
||||
};
|
||||
|
||||
// Act
|
||||
const selectProviderQuestion = wrapper.findComponent('#selectProviderQuestion');
|
||||
|
|
@ -188,6 +226,8 @@ describe('TPA search page', () => {
|
|||
expect(selectProviderQuestion.props().textPosition).toBe('text-start');
|
||||
expect(selectProviderQuestion.props().isRequired).toBeTruthy();
|
||||
expect(selectProviderQuestion.props().validationRules).toBe(globalRules.OPTION_REQUIRED);
|
||||
expect(selectProviderQuestion.props().answers).toEqual(expectedAnswers);
|
||||
expect(selectProviderQuestion.props().additionalButtonData).toEqual(expectedAdditionalButtonData);
|
||||
});
|
||||
test.each([[], null, undefined])(
|
||||
'no network providers alert when providers length is 0, undefined, or null',
|
||||
|
|
@ -447,12 +487,143 @@ describe('TPA search page', () => {
|
|||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
describe('providerAddresses', () => {
|
||||
test.each([null, undefined, []])(
|
||||
'returns empty list when providers empty',
|
||||
async (providers) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
await wrapper.setData({ providers });
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.providerAddresses;
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
}
|
||||
);
|
||||
test('returns expected when providers not empty', async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
await wrapper.setData({
|
||||
providers: [
|
||||
{
|
||||
name: 'Shop Auto Glass',
|
||||
phoneNumber: '740-555-1234',
|
||||
distanceInMiles: 1.2345,
|
||||
providerNumber: '232209',
|
||||
address: {
|
||||
streetAddress: '123 Lane Ave',
|
||||
city: 'New York',
|
||||
state: 'NY',
|
||||
zipCode: '99230'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Safelite AutoGlass',
|
||||
phoneNumber: '676-555-0099',
|
||||
distanceInMiles: 123.9452,
|
||||
providerNumber: '123456',
|
||||
address: {
|
||||
streetAddress: '2003 Coral Ln',
|
||||
city: 'Birmingham',
|
||||
state: 'AL',
|
||||
zipCode: '43001'
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
const expectedResult = [
|
||||
'123 Lane Ave, New York, NY 99230',
|
||||
'2003 Coral Ln, Birmingham, AL 43001'
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.providerAddresses;
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
describe('providerButtonData', () => {
|
||||
test.each([null, undefined, []])(
|
||||
'returns empty list when providers empty',
|
||||
async (providers) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
await wrapper.setData({ providers });
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.providerButtonData;
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
}
|
||||
);
|
||||
test('returns expected when providers not empty', async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
await wrapper.setData({
|
||||
providers: [
|
||||
{
|
||||
name: 'Shop Auto Glass',
|
||||
phoneNumber: '740-555-1234',
|
||||
distanceInMiles: 1.2345,
|
||||
providerNumber: '232209',
|
||||
address: {
|
||||
streetAddress: '123 Lane Ave',
|
||||
city: 'New York',
|
||||
state: 'NY',
|
||||
zipCode: '99230'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Safelite AutoGlass',
|
||||
phoneNumber: '676-555-0099',
|
||||
distanceInMiles: 123.9452,
|
||||
providerNumber: '123456',
|
||||
address: {
|
||||
streetAddress: '2003 Coral Ln',
|
||||
city: 'Birmingham',
|
||||
state: 'AL',
|
||||
zipCode: '43001'
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
const expectedResult = [
|
||||
{
|
||||
buttonLabel: 'Shop Auto Glass',
|
||||
buttonLabelSubCopy: '1.2 mi',
|
||||
buttonBodyCopy: '123 Lane Ave, New York, NY 99230<br>740-555-1234',
|
||||
value: '232209'
|
||||
},
|
||||
{
|
||||
buttonLabel: 'Safelite AutoGlass',
|
||||
buttonLabelSubCopy: '123.9 mi',
|
||||
buttonBodyCopy: '2003 Coral Ln, Birmingham, AL 43001<br>676-555-0099',
|
||||
value: '123456'
|
||||
}
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.providerButtonData;
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('watch', () => {
|
||||
test('on filter calls getTpaProviders and sets providers', async () => {
|
||||
// Arrange
|
||||
const { wrapper } = getMountedComponent();
|
||||
const providers = { data: ['some data', 'some more data'] };
|
||||
const zipCode = '78220';
|
||||
const initialMapZipCode = '88800';
|
||||
const { wrapper } = getMountedComponent({}, {
|
||||
zipCode,
|
||||
mapZipCode: initialMapZipCode
|
||||
});
|
||||
const providers = { data: { shopProviders: ['some data', 'some more data']} };
|
||||
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers));
|
||||
const newFilter = 'new filter';
|
||||
|
||||
|
|
@ -461,7 +632,8 @@ describe('TPA search page', () => {
|
|||
|
||||
// Assert
|
||||
expect(useMainStore().getTpaProviders).toBeCalled();
|
||||
expect(wrapper.vm.providers).toEqual(providers.data);
|
||||
expect(wrapper.vm.providers).toEqual(providers.data.shopProviders);
|
||||
expect(wrapper.vm.mapZipCode).toBe(zipCode);
|
||||
});
|
||||
describe('on providers', () => {
|
||||
test.each([null, undefined, []])(
|
||||
|
|
@ -481,7 +653,7 @@ describe('TPA search page', () => {
|
|||
// Arrange
|
||||
const { wrapper } = getMountedComponent();
|
||||
const value = 'some value';
|
||||
const newProviders = [{ value }];
|
||||
const newProviders = [{ providerNumber: value }];
|
||||
|
||||
// Act
|
||||
wrapper.vm.$options.watch.providers.call(wrapper.vm, newProviders);
|
||||
|
|
@ -503,7 +675,35 @@ describe('TPA search page', () => {
|
|||
});
|
||||
});
|
||||
describe('method', () => {
|
||||
describe('getProviders', () => {
|
||||
test.each([
|
||||
['address', 'city', 'ST', 'zip', 'address, city, ST zip'],
|
||||
['address', null, 'ST', 'zip', 'address, ST zip'],
|
||||
['address', 'city', null, 'zip', 'address, city zip'],
|
||||
['address', 'city', 'ST', null, 'address, city, ST'],
|
||||
[null, null, null, null, '']
|
||||
])(
|
||||
'getProviderAddress returns expected',
|
||||
(streetAddress, city, state, zipCode, expected) => {
|
||||
// Arrange
|
||||
const { wrapper } = getMountedComponent();
|
||||
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders));
|
||||
const provider = {
|
||||
address: {
|
||||
streetAddress,
|
||||
city,
|
||||
state,
|
||||
zipCode
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.getProviderAddress(provider);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expected);
|
||||
}
|
||||
);
|
||||
describe('getProviderButtonData', () => {
|
||||
test.each([null, undefined, {}])(
|
||||
'returns empty list when getTpaProviders returns no data',
|
||||
async (newProviders) => {
|
||||
|
|
@ -512,7 +712,7 @@ describe('TPA search page', () => {
|
|||
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders));
|
||||
|
||||
// Act
|
||||
const result = await wrapper.vm.getProviders();
|
||||
const result = await wrapper.vm.getProviderButtonData();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
|
|
@ -521,14 +721,18 @@ describe('TPA search page', () => {
|
|||
test('returns expected when getTpaProviders returns data', async () => {
|
||||
// Arrange
|
||||
const { wrapper } = getMountedComponent();
|
||||
const providers = { data: [{ name: 'foo' }] };
|
||||
const providers = {
|
||||
data: {
|
||||
shopProviders: [{ name: 'foo' }]
|
||||
}
|
||||
};
|
||||
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers));
|
||||
|
||||
// Act
|
||||
const result = await wrapper.vm.getProviders();
|
||||
const result = await wrapper.vm.getProviderButtonData();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(providers.data);
|
||||
expect(result).toEqual(providers.data.shopProviders);
|
||||
});
|
||||
});
|
||||
test('doNotSeeMyShopLinkClick invokes navigate method', () => {
|
||||
|
|
@ -546,7 +750,12 @@ describe('TPA search page', () => {
|
|||
'sets providers to empty list when no data returned from getProviders',
|
||||
async (newProviders) => {
|
||||
// Arrange
|
||||
const { wrapper } = getMountedComponent();
|
||||
const zipCode = '28331';
|
||||
const initialMapZipCode = '10022';
|
||||
const { wrapper } = getMountedComponent({}, {
|
||||
zipCode,
|
||||
mapZipCode: initialMapZipCode
|
||||
});
|
||||
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders));
|
||||
|
||||
// Act
|
||||
|
|
@ -554,19 +763,28 @@ describe('TPA search page', () => {
|
|||
|
||||
// Assert
|
||||
expect(wrapper.vm.providers).toEqual([]);
|
||||
expect(wrapper.vm.mapZipCode).toEqual(zipCode);
|
||||
}
|
||||
);
|
||||
test('returns value from getProviders', async () => {
|
||||
// Arrange
|
||||
const { wrapper } = getMountedComponent();
|
||||
const newProviders = { data: [{ name: 'foo' }] };
|
||||
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders));
|
||||
const zipCode = '12990';
|
||||
const initialMapZipCode = '00981';
|
||||
const { wrapper } = getMountedComponent({}, {
|
||||
zipCode,
|
||||
mapZipCode: initialMapZipCode
|
||||
});
|
||||
const shopProviders = [{ name: 'foo' }];
|
||||
useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => ({
|
||||
data: { shopProviders }
|
||||
}));
|
||||
|
||||
// Act
|
||||
await wrapper.vm.searchClick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.providers).toEqual(newProviders.data);
|
||||
expect(wrapper.vm.providers).toEqual(shopProviders);
|
||||
expect(wrapper.vm.mapZipCode).toEqual(zipCode);
|
||||
});
|
||||
});
|
||||
test('backButtonAction invokes navigate method', () => {
|
||||
|
|
@ -613,32 +831,162 @@ describe('TPA search page', () => {
|
|||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
test.each([undefined, null, 'some value', ''])(
|
||||
'setFilter sets value of filter',
|
||||
(filter) => {
|
||||
describe('getShopButtonDataFromProvider', () => {
|
||||
test.each([null, undefined, {}])(
|
||||
'returns empty list given parameter with no provider',
|
||||
(provider) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
const expectedResult = {
|
||||
buttonLabel: '',
|
||||
buttonLabelSubCopy: '',
|
||||
buttonBodyCopy: '<br>',
|
||||
value: ''
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.getShopButtonDataFromProvider(provider);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
}
|
||||
);
|
||||
test('returns expected given provider', () => {
|
||||
// Arrange
|
||||
const { wrapper } = getMountedComponent();
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
const provider = {
|
||||
name: 'Shop Auto Glass',
|
||||
phoneNumber: '740-555-1234',
|
||||
distanceInMiles: 1.2345,
|
||||
providerNumber: '232209',
|
||||
address: {
|
||||
streetAddress: '123 Lane Ave',
|
||||
city: 'New York',
|
||||
state: 'NY',
|
||||
zipCode: '99230'
|
||||
}
|
||||
};
|
||||
const expectedResult = {
|
||||
buttonLabel: 'Shop Auto Glass',
|
||||
buttonLabelSubCopy: '1.2 mi',
|
||||
buttonBodyCopy: '123 Lane Ave, New York, NY 99230<br>740-555-1234',
|
||||
value: '232209'
|
||||
};
|
||||
|
||||
// Act
|
||||
wrapper.vm.setFilter(filter);
|
||||
const result = wrapper.vm.getShopButtonDataFromProvider(provider);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.filter).toEqual(filter);
|
||||
}
|
||||
);
|
||||
test.each([undefined, null, ['some value'], []])(
|
||||
'setProviders sets value of providers',
|
||||
(providers) => {
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
describe('returns result with button label', () => {
|
||||
test.each(['', undefined, null])(
|
||||
'equal to empty string when name is empty',
|
||||
(name) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
const provider = { name };
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.getShopButtonDataFromProvider(provider);
|
||||
|
||||
// Assert
|
||||
expect(result.buttonLabel).toEqual('');
|
||||
}
|
||||
);
|
||||
test('returns provider name when exists', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
const name = 'Some Shop Name';
|
||||
const provider = { name };
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.getShopButtonDataFromProvider(provider);
|
||||
|
||||
// Assert
|
||||
expect(result.buttonLabel).toEqual(name);
|
||||
});
|
||||
});
|
||||
describe('returns result with button label sub copy', () => {
|
||||
test.each([null, undefined, 'random string'])(
|
||||
'as empty string when distanceInMiles is not a number',
|
||||
(distanceInMiles) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
const provider = { distanceInMiles };
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.getShopButtonDataFromProvider(provider);
|
||||
|
||||
// Assert
|
||||
expect(result.buttonLabelSubCopy).toEqual('');
|
||||
}
|
||||
);
|
||||
test.each([[0, 0], [0, 0.000], [-7.89, -7.9], [109, 109], [1.234, 1.2], [2.99, 3]])(
|
||||
'displays expected distance value',
|
||||
(distanceInMiles, displayedDistance) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
const provider = { distanceInMiles };
|
||||
const expectedSubCopy = `${displayedDistance} mi`;
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.getShopButtonDataFromProvider(provider);
|
||||
|
||||
// Assert
|
||||
expect(result.buttonLabelSubCopy).toEqual(expectedSubCopy);
|
||||
}
|
||||
);
|
||||
});
|
||||
test('returns result with button body copy showing expected address and phone number', () => {
|
||||
// Arrange
|
||||
const { wrapper } = getMountedComponent();
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
const provider = {
|
||||
phoneNumber: '123-555-0099',
|
||||
address: {
|
||||
streetAddress: '434 Up Down Blvd',
|
||||
city: 'San Francisco',
|
||||
state: 'CA',
|
||||
zipCode: '90887'
|
||||
}
|
||||
};
|
||||
const expected = '434 Up Down Blvd, San Francisco, CA 90887<br>123-555-0099';
|
||||
|
||||
// Act
|
||||
wrapper.vm.setProviders(providers);
|
||||
const result = wrapper.vm.getShopButtonDataFromProvider(provider);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.providers).toEqual(providers);
|
||||
}
|
||||
);
|
||||
expect(result.buttonBodyCopy).toEqual(expected);
|
||||
});
|
||||
describe('returns result with value', () => {
|
||||
test.each(['', undefined, null])(
|
||||
'equal to empty string when providerNumber is empty',
|
||||
(providerNumber) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
const provider = { providerNumber };
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.getShopButtonDataFromProvider(provider);
|
||||
|
||||
// Assert
|
||||
expect(result.value).toEqual('');
|
||||
}
|
||||
);
|
||||
test('returns provider number when exists', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(tpaSearch, getMountOptions());
|
||||
const providerNumber = '123391';
|
||||
const provider = { providerNumber };
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.getShopButtonDataFromProvider(provider);
|
||||
|
||||
// Assert
|
||||
expect(result.value).toEqual(providerNumber);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('before route enter', () => {
|
||||
test('when providers exist at 25 mile radius, filter is set to "25 miles" and providers set to expected', async () => {
|
||||
|
|
@ -646,8 +994,9 @@ describe('TPA search page', () => {
|
|||
const { wrapper } = getMountedComponent();
|
||||
const zipCode = '18394';
|
||||
useMainStore().order.customer.address.zipCode = zipCode;
|
||||
const providers = { data: [{ name: 'provider' }] };
|
||||
useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => (radius === 25 ? providers : []));
|
||||
const providers = [{ name: 'provider' }];
|
||||
useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) =>
|
||||
({ data: { shopProviders: radius === 25 ? providers : [] } }));
|
||||
const expectedFilter = '25 miles';
|
||||
|
||||
// Act
|
||||
|
|
@ -658,6 +1007,9 @@ describe('TPA search page', () => {
|
|||
(c) => c(wrapper.vm)
|
||||
);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.filter).toBe(expectedFilter);
|
||||
expect(wrapper.vm.providers).toEqual(providers);
|
||||
|
|
@ -667,8 +1019,9 @@ describe('TPA search page', () => {
|
|||
const { wrapper } = getMountedComponent();
|
||||
const zipCode = '18394';
|
||||
useMainStore().order.customer.address.zipCode = zipCode;
|
||||
const providers = { data: [{ name: 'provider' }] };
|
||||
useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => (radius === 50 ? providers : []));
|
||||
const providers = [{ name: 'provider' }];
|
||||
useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) =>
|
||||
({ data: { shopProviders: radius === 50 ? providers : [] } }));
|
||||
const expectedFilter = '50 miles';
|
||||
|
||||
// Act
|
||||
|
|
@ -679,6 +1032,9 @@ describe('TPA search page', () => {
|
|||
(c) => c(wrapper.vm)
|
||||
);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.filter).toBe(expectedFilter);
|
||||
expect(wrapper.vm.providers).toEqual(providers);
|
||||
|
|
@ -690,8 +1046,9 @@ describe('TPA search page', () => {
|
|||
const { wrapper } = getMountedComponent();
|
||||
const zipCode = '18394';
|
||||
useMainStore().order.customer.address.zipCode = zipCode;
|
||||
const providers = { data: [{ name: 'provider' }] };
|
||||
useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => (radius === 100 ? providers : []));
|
||||
const providers = [{ name: 'provider' }];
|
||||
useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) =>
|
||||
({ data: { shopProviders: radius === 100 ? providers : [] } }));
|
||||
const expectedFilter = '100 miles';
|
||||
|
||||
// Act
|
||||
|
|
@ -702,6 +1059,9 @@ describe('TPA search page', () => {
|
|||
(c) => c(wrapper.vm)
|
||||
);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.filter).toBe(expectedFilter);
|
||||
expect(wrapper.vm.providers).toEqual(providers);
|
||||
|
|
@ -725,6 +1085,9 @@ describe('TPA search page', () => {
|
|||
(c) => c(wrapper.vm)
|
||||
);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.filter).toBe(expectedFilter);
|
||||
expect(wrapper.vm.providers).toEqual([]);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="page-container-grouped-styles">
|
||||
<div class="fade-on-route-transition">
|
||||
<siteHeader
|
||||
ref="siteHeader"
|
||||
:cmsWidgetName="widget.siteHeader" />
|
||||
|
|
@ -36,18 +37,17 @@
|
|||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
<googleMap
|
||||
id="map"
|
||||
class="mb-4"
|
||||
:addresses="providerAddresses"
|
||||
:zipCode="mapZipCode"></googleMap>
|
||||
<Form
|
||||
id="providerSelectionForm"
|
||||
v-slot="{ meta }"
|
||||
@submit="onSubmit"
|
||||
@invalidSubmit="onInvalidSubmit">
|
||||
<div class="container-fluid pb-2">
|
||||
<p
|
||||
id="map"
|
||||
class="mb-4">
|
||||
Placeholder for Map
|
||||
</p>
|
||||
|
||||
<div class="mx-5">
|
||||
<dropdownQuestion
|
||||
id="searchRadiusFilter"
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
buttonTypeString="shopListButton"
|
||||
:buttonTypeObject="shopListButton"
|
||||
class="radioQuestion"
|
||||
:answers="providers"
|
||||
:answers="providerButtonData"
|
||||
groupName="chooseShop"
|
||||
textPosition="text-start"
|
||||
isRequired
|
||||
|
|
@ -98,6 +98,7 @@
|
|||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// Components
|
||||
|
|
@ -105,11 +106,12 @@ import { Form } from 'vee-validate';
|
|||
import siteHeader from '@/iss-components/site-header/site-header.vue';
|
||||
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
|
||||
import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question.vue';
|
||||
import buttonQuestion from '@/digital-components/button-question/button-question.vue'
|
||||
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue'
|
||||
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
||||
import textLink from '@/ux-components/text-link/text-link.vue';
|
||||
import alert from '@/ux-components/alert/alert.vue';
|
||||
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||
import googleMap from '@/iss-components/google-map/google-map.vue';
|
||||
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';
|
||||
|
||||
// Supporting files
|
||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
||||
|
|
@ -118,7 +120,6 @@ import { useMainStore } from '@/store';
|
|||
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||
import globalRules from '@/constants/global-rules.js';
|
||||
import widgetFields from '@/constants/cms-widget-fields.js';
|
||||
import routerParams from '@/router/router-constants/router-params';
|
||||
|
||||
export default {
|
||||
name: 'tpa-search',
|
||||
|
|
@ -129,6 +130,7 @@ export default {
|
|||
buttonQuestion,
|
||||
textLink,
|
||||
alert,
|
||||
googleMap,
|
||||
siteFooter,
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
Form
|
||||
|
|
@ -144,34 +146,16 @@ export default {
|
|||
];
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
|
||||
const radiusOptions = [25, 50, 100];
|
||||
const { zipCode } = useMainStore().order.customer.address;
|
||||
|
||||
const tpaProvidersRadius25 = await useMainStore().getTpaProviders(zipCode, radiusOptions[0]);
|
||||
const tpaProvidersRadius50 = await useMainStore().getTpaProviders(zipCode, radiusOptions[1]);
|
||||
const tpaProvidersRadius100 = await useMainStore().getTpaProviders(zipCode, radiusOptions[2]);
|
||||
let radius = '25 miles';
|
||||
|
||||
let providers = tpaProvidersRadius25;
|
||||
if ((tpaProvidersRadius25?.data ?? []).length === 0) {
|
||||
radius = '50 miles';
|
||||
providers = tpaProvidersRadius50;
|
||||
if ((tpaProvidersRadius50?.data ?? []).length === 0) {
|
||||
radius = '100 miles';
|
||||
providers = tpaProvidersRadius100;
|
||||
}
|
||||
}
|
||||
|
||||
next((vm) => {
|
||||
next(async (vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.setFilter(radius);
|
||||
vm.setProviders(providers);
|
||||
await vm.setInitialFilterAndProviders();
|
||||
});
|
||||
},
|
||||
data() {
|
||||
const { zipCode } = useMainStore().order.customer.address;
|
||||
return {
|
||||
zipCode,
|
||||
mapZipCode: zipCode,
|
||||
filter: '',
|
||||
providers: [],
|
||||
selectedProviderNumber: '',
|
||||
|
|
@ -242,24 +226,79 @@ export default {
|
|||
this.widget.noNetworkShopsAlert,
|
||||
widgetFields.ALERT_WIDGET.HEADLINE_TEXT
|
||||
)?.replaceAll('{custom:radiusInMiles}', this.radiusInMiles);
|
||||
},
|
||||
providerAddresses() {
|
||||
return this.providers?.map((provider) => this.getProviderAddress(provider)) ?? [];
|
||||
},
|
||||
providerButtonData() {
|
||||
return this.providers?.map((provider) => this.getShopButtonDataFromProvider(provider)) ?? [];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async filter() {
|
||||
// TODO right now this results in getProviders being called once more than it needs to be
|
||||
this.providers = await this.getProviders();
|
||||
this.providers = await this.getProviderButtonData();
|
||||
this.mapZipCode = this.zipCode;
|
||||
},
|
||||
providers(newProviders) {
|
||||
this.selectedProviderNumber = newProviders?.length === 1 ?? false
|
||||
? newProviders[0].value
|
||||
? newProviders[0].providerNumber
|
||||
: '';
|
||||
}
|
||||
},
|
||||
methods:
|
||||
{
|
||||
async getProviders() {
|
||||
getProviderAddress(provider) {
|
||||
const city = provider?.address?.city ?? '';
|
||||
const state = provider?.address?.state ?? '';
|
||||
const zipCode = provider?.address?.zipCode ?? '';
|
||||
let addressLine2 = '';
|
||||
if (city) {
|
||||
addressLine2 = city;
|
||||
if (state || zipCode) {
|
||||
if (state) {
|
||||
addressLine2 += ',';
|
||||
}
|
||||
addressLine2 += ' ';
|
||||
}
|
||||
}
|
||||
if (state) {
|
||||
addressLine2 += `${state}`;
|
||||
if (zipCode) {
|
||||
addressLine2 += ' ';
|
||||
}
|
||||
}
|
||||
if (zipCode) {
|
||||
addressLine2 += `${zipCode}`;
|
||||
}
|
||||
return provider?.address?.streetAddress
|
||||
? `${provider.address.streetAddress}, ${addressLine2}`
|
||||
: addressLine2;
|
||||
},
|
||||
async setInitialFilterAndProviders() {
|
||||
const radiusOptions = [25, 50, 100];
|
||||
const { zipCode } = useMainStore().order.customer.address;
|
||||
|
||||
const tpaProvidersRadius25 = await useMainStore().getTpaProviders(zipCode, radiusOptions[0]);
|
||||
const tpaProvidersRadius50 = await useMainStore().getTpaProviders(zipCode, radiusOptions[1]);
|
||||
const tpaProvidersRadius100 = await useMainStore().getTpaProviders(zipCode, radiusOptions[2]);
|
||||
let radius = '25 miles';
|
||||
|
||||
let providers = tpaProvidersRadius25;
|
||||
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.providers = providers?.data?.shopProviders ?? [];
|
||||
},
|
||||
async getProviderButtonData() {
|
||||
const getTpaProvidersResult = await useMainStore().getTpaProviders(this.zipCode, this.radiusInMiles);
|
||||
return getTpaProvidersResult?.data ?? [];
|
||||
return getTpaProvidersResult?.data?.shopProviders ?? [];
|
||||
},
|
||||
doNotSeeMyShopLinkClick() {
|
||||
this.$router.navigate(
|
||||
|
|
@ -268,7 +307,8 @@ export default {
|
|||
);
|
||||
},
|
||||
async searchClick() {
|
||||
this.providers = await this.getProviders();
|
||||
this.providers = await this.getProviderButtonData();
|
||||
this.mapZipCode = this.zipCode;
|
||||
},
|
||||
backButtonAction() {
|
||||
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
|
|
@ -287,11 +327,20 @@ export default {
|
|||
return null;
|
||||
}
|
||||
},
|
||||
setFilter(filter) {
|
||||
this.filter = filter;
|
||||
},
|
||||
setProviders(providers) {
|
||||
this.providers = providers;
|
||||
getShopButtonDataFromProvider(provider) {
|
||||
const cellNumber = provider?.phoneNumber ?? '';
|
||||
const distance = provider?.distanceInMiles !== null && !Number.isNaN(parseFloat(provider?.distanceInMiles))
|
||||
? +provider.distanceInMiles.toFixed(1)
|
||||
: null;
|
||||
|
||||
return {
|
||||
buttonLabel: provider?.name ?? '',
|
||||
buttonLabelSubCopy: distance === null
|
||||
? ''
|
||||
: `${distance} mi`,
|
||||
buttonBodyCopy: `${this.getProviderAddress(provider)}<br>${cellNumber ?? ''}`,
|
||||
value: provider?.providerNumber ?? ''
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -848,38 +848,76 @@ export const useMainStore = defineStore({
|
|||
});
|
||||
},
|
||||
|
||||
// TODO what info is returned here
|
||||
// TODO update with real endpoint
|
||||
getTpaProviders(zipCode, radius) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (radius === 25) {
|
||||
resolve({});
|
||||
resolve({
|
||||
data: {
|
||||
mobileProviderNumber: null,
|
||||
shopProviders: null
|
||||
}
|
||||
});
|
||||
} else if (radius === 50) {
|
||||
resolve({
|
||||
data: [
|
||||
{
|
||||
buttonLabel: 'USA Auto Glass',
|
||||
buttonLabelSubCopy: '1.5 mi',
|
||||
buttonBodyCopy: '760 Dearborn Park Ln, Worthington, OH 43085<br>614-123-5555',
|
||||
value: '000123'
|
||||
}
|
||||
]
|
||||
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: [
|
||||
{
|
||||
buttonLabel: 'USA Auto Glass',
|
||||
buttonLabelSubCopy: '1.5 mi',
|
||||
buttonBodyCopy: '760 Dearborn Park Ln, Worthington, OH 43085<br>614-123-5555',
|
||||
value: '000123'
|
||||
},
|
||||
{
|
||||
buttonLabel: 'USA Auto Glass',
|
||||
buttonLabelSubCopy: '1.5 mi',
|
||||
buttonBodyCopy: '760 Dearborn Park Ln, Worthington, OH 43085<br>614-123-5555',
|
||||
value: '000123'
|
||||
}
|
||||
]
|
||||
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'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY =
|
|||
"(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-KKNWZ3');";
|
||||
process.env.VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC =
|
||||
'https://www.googletagmanager.com/ns.html?id=GTM-KKNWZ3>m_auth=amlAYNhxUxuskQo7jmjadg>m_preview=env-38>m_cookies_win=x';
|
||||
process.env.VUE_APP_GOOGLE_MAPS_API_SCRIPT =
|
||||
"(g=>{var h,a,k,p='The Google Maps JavaScript API',c='google',l='importLibrary',q='__ib__',m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement('script'));e.set('libraries',[...r]+'');for(k in g)e.set(k.replace(/[A-Z]/g,t=>'_'+t[0].toLowerCase()),g[k]);e.set('callback',c+'.maps.'+q);a.src=`https://maps.googleapis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+' could not load.'));a.nonce=m.querySelector('script[nonce]')?.nonce||'';m.head.append(a)}));d[l]?console.warn(p+' only loads once. Ignoring:',g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({key: 'AIzaSyCuLhQcDdZTTb4JzpUFms1OCch2dk5lHF0', v: 'weekly'});";
|
||||
|
||||
module.exports = {
|
||||
publicPath: '/',
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "__VUE_APP_GOOGLE_PLACES_API_KEY__";
|
|||
// GA & GTM
|
||||
process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY = "__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__";
|
||||
process.env.VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC = "__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__"
|
||||
process.env.VUE_APP_GOOGLE_MAPS_API_SCRIPT = "__VUE_APP_GOOGLE_MAPS_API_SCRIPT__";
|
||||
|
||||
module.exports = {
|
||||
publicPath: "/",
|
||||
|
|
|
|||
Loading…
Reference in a new issue