Created
November 24, 2020 11:49
-
-
Save mikeapr4/e331ff94a59ba325e705b0ae8616cbd2 to your computer and use it in GitHub Desktop.
Sample Code for Review
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Vue from 'vue' | |
// Convert the API Postage Option format into the NEXT APP Postage option format | |
const convertPostage = (locationGroups) => (postage) => { | |
const [add, addQty] = postage.price_addl.split('/') | |
const option = { | |
flat: true, | |
cost: Number(postage.price), | |
add: Number(add), | |
addQty: addQty ? Number(addQty) : 1, | |
label: postage.method, | |
} | |
// There may be multiple location groups in the option, but NEXT only supports | |
// one group per option, so we create multiple options. | |
return postage.locations.map((index) => ({ | |
dest: locationGroups[Number(index)], | |
...option, | |
})) | |
} | |
// By default any field with a truthy value will be mapped as is, | |
// here we add custom validation/mapping functions, if the output of | |
// this is truthy it will be applied to local data. | |
const validate = { | |
description: (v) => { | |
if (v) { | |
// Strip HTML | |
const el = document.createElement('div') | |
el.innerHTML = v | |
return el.textContent | |
} | |
return v | |
}, | |
enable_make_offer: (v) => v === '1', | |
duration: Number, | |
nb_relists: Number, | |
country: Number, | |
state: (v) => Number(v) || v, | |
accept_returns: (v) => v === '1', | |
} | |
const inputSet = (field) => (input, val) => (input[field] = val) | |
const prefSet = (field) => (input, val) => (input.preferences[field] = val) | |
const pricingSet = (field) => (input, val) => (input.pricing[field] = val) | |
// Configure the placement of each field in the frontend data | |
const placement = { | |
description: inputSet('desc'), | |
currency: prefSet('currency'), | |
enable_make_offer: pricingSet('allowOffers'), | |
duration: pricingSet('duration'), | |
nb_relists: (input, val) => { | |
input.pricing.autoRelist = true | |
input.pricing.relistQty = val | |
}, | |
country: prefSet('country'), | |
state: prefSet('state'), | |
postal_code: prefSet('zip'), | |
accept_returns: prefSet('acceptReturns'), | |
returns_policy: prefSet('returnPolicy'), | |
} | |
// uses: $axios, $store, $auth, $t, input | |
export default { | |
data() { | |
return { | |
defaultsLoading: true, | |
} | |
}, | |
async mounted() { | |
try { | |
await this.loadDefaults() | |
} finally { | |
this.defaultsLoading = false | |
} | |
}, | |
methods: { | |
async loadDefaults() { | |
try { | |
const { | |
results: [rs], | |
} = await this.$axios.$get('/papi/listings-default') | |
const input = JSON.parse(JSON.stringify(this.input)) | |
Object.keys(placement).forEach((field) => { | |
const mapped = validate[field] | |
? validate[field](rs[field]) | |
: rs[field] | |
if (mapped) { | |
placement[field](input, mapped) | |
} | |
}) | |
// Wait for location groups to be fully loaded | |
await this.$store.dispatch('postage/locationGroups') | |
if (rs.postage && rs.postage.length) { | |
const mapper = convertPostage( | |
this.$store.state.postage.locationGroups, | |
) | |
input.shipping.services = rs.postage.reduce( | |
(srvs, srv) => [...srvs, ...mapper(srv)], | |
[], | |
) | |
} | |
Vue.set(this, 'input', input) | |
} catch (err) { | |
if (err.response && err.response.status) { | |
if (err.response.status === 401) { | |
setTimeout(() => this.$auth.redirect('login'), 3000) | |
throw this.$t('401') | |
} | |
} | |
} | |
}, | |
}, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable prefer-promise-reject-errors */ | |
import { mount, createLocalVue } from '@vue/test-utils' | |
import DefaultListing from '@/mixins/default-listing.js' | |
import Vuex from 'vuex' | |
import { passive } from '@/utils/promise' | |
const localVue = createLocalVue() | |
localVue.use(Vuex) | |
let locationGroupPromise = passive() | |
const store = new Vuex.Store({ | |
modules: { | |
postage: { | |
namespaced: true, | |
state: { | |
locationGroups: [ | |
{ id: 1, name: 'United States', countries: [2084] }, | |
{ id: 2, name: 'Everywhere Else', countries: [2316] }, | |
{ | |
id: 3, | |
name: 'South America', | |
countries: [1874, 1890, 1894, 1908], | |
}, | |
{ id: 4, name: 'Europe', countries: [2083, 1939, 2058] }, | |
], | |
}, | |
actions: { locationGroups: () => locationGroupPromise }, | |
}, | |
}, | |
}) | |
let getResp = { | |
description: 'ACTION COMICS #340 FIRST ...', | |
currency: 'USD', | |
enable_make_offer: '1', | |
duration: '3', | |
nb_relists: '0', | |
country: '2084', | |
state: '2135', | |
postal_code: '03464', | |
shipping_details: '', | |
accept_returns: '', | |
returns_policy: '', | |
postage: [ | |
{ | |
price: '6.75', | |
price_addl: '0', | |
method: 'PRIORITY FLAT RATE ENVELOPE', | |
locations: ['0', '2'], | |
}, | |
], | |
} | |
const $axios = { | |
$get: () => Promise.resolve({ results: [getResp] }), | |
} | |
const data = () => ({ | |
input: { | |
keep: 'this', | |
pricing: { starting: 'data' }, | |
shipping: { a: 1 }, | |
preferences: { check: 'this' }, | |
}, | |
}) | |
describe('Default Listing Mixin', () => { | |
beforeEach(() => { | |
jest.useFakeTimers() | |
locationGroupPromise = passive() | |
}) | |
afterEach(() => jest.useRealTimers()) | |
test('happy path', async () => { | |
const wrapper = mount( | |
{ template: '<div/>', mixins: [DefaultListing], data }, | |
{ localVue, store, mocks: { $axios } }, | |
) | |
expect(wrapper.vm.defaultsLoading).toBe(true) | |
expect(wrapper.vm.input).toEqual(data().input) | |
locationGroupPromise.resolve() | |
await wrapper.vm.$nextTick() // store action completes | |
await wrapper.vm.$nextTick() // loadDefaults completes | |
await wrapper.vm.$nextTick() // mounted finally completes | |
expect(wrapper.vm.defaultsLoading).toBe(false) | |
expect(wrapper.vm.input).toEqual({ | |
keep: 'this', // preexisting | |
desc: 'ACTION COMICS #340 FIRST ...', | |
preferences: { | |
check: 'this', // preexisting | |
country: 2084, | |
currency: 'USD', | |
state: 2135, | |
zip: '03464', | |
}, | |
pricing: { | |
starting: 'data', // preexisting | |
allowOffers: true, | |
duration: 3, | |
}, | |
shipping: { | |
a: 1, // preexisting | |
services: [ | |
{ | |
add: 0, | |
addQty: 1, | |
cost: 6.75, | |
dest: { | |
countries: [2084], | |
id: 1, | |
name: 'United States', | |
}, | |
flat: true, | |
label: 'PRIORITY FLAT RATE ENVELOPE', | |
}, | |
{ | |
add: 0, | |
addQty: 1, | |
cost: 6.75, | |
dest: { | |
id: 3, | |
name: 'South America', | |
countries: [1874, 1890, 1894, 1908], | |
}, | |
flat: true, | |
label: 'PRIORITY FLAT RATE ENVELOPE', | |
}, | |
], | |
}, | |
}) | |
}) | |
test('description with html', async () => { | |
getResp = { | |
description: | |
'<p>something here in <b>bold</b> & a < less than</p>', | |
} | |
const wrapper = mount( | |
{ template: '<div/>', mixins: [DefaultListing], data }, | |
{ localVue, store, mocks: { $axios } }, | |
) | |
locationGroupPromise.resolve() | |
await wrapper.vm.$nextTick() // store action completes | |
await wrapper.vm.$nextTick() // loadDefaults completes | |
await wrapper.vm.$nextTick() // mounted finally completes | |
expect(wrapper.vm.defaultsLoading).toBe(false) | |
expect(wrapper.vm.input.desc).toEqual( | |
'something here in bold & a < less than', | |
) | |
}) | |
test('relists', async () => { | |
getResp = { | |
nb_relists: '3', | |
} | |
const wrapper = mount( | |
{ template: '<div/>', mixins: [DefaultListing], data }, | |
{ localVue, store, mocks: { $axios } }, | |
) | |
locationGroupPromise.resolve() | |
await wrapper.vm.$nextTick() // store action completes | |
await wrapper.vm.$nextTick() // loadDefaults completes | |
await wrapper.vm.$nextTick() // mounted finally completes | |
expect(wrapper.vm.defaultsLoading).toBe(false) | |
expect(wrapper.vm.input.pricing).toEqual({ | |
starting: 'data', // preexisting | |
autoRelist: true, | |
relistQty: 3, | |
}) | |
}) | |
test('freetext state', async () => { | |
getResp = { | |
country: '123', | |
state: 'Leinster', | |
} | |
const wrapper = mount( | |
{ template: '<div/>', mixins: [DefaultListing], data }, | |
{ localVue, store, mocks: { $axios } }, | |
) | |
locationGroupPromise.resolve() | |
await wrapper.vm.$nextTick() // store action completes | |
await wrapper.vm.$nextTick() // loadDefaults completes | |
await wrapper.vm.$nextTick() // mounted finally completes | |
expect(wrapper.vm.defaultsLoading).toBe(false) | |
expect(wrapper.vm.input.preferences).toEqual({ | |
check: 'this', // preexisting | |
country: 123, | |
state: 'Leinster', | |
}) | |
}) | |
test('accept returns', async () => { | |
getResp = { | |
accept_returns: '1', | |
returns_policy: 'policy', | |
} | |
const wrapper = mount( | |
{ template: '<div/>', mixins: [DefaultListing], data }, | |
{ localVue, store, mocks: { $axios } }, | |
) | |
locationGroupPromise.resolve() | |
await wrapper.vm.$nextTick() // store action completes | |
await wrapper.vm.$nextTick() // loadDefaults completes | |
await wrapper.vm.$nextTick() // mounted finally completes | |
expect(wrapper.vm.defaultsLoading).toBe(false) | |
expect(wrapper.vm.input.preferences).toEqual({ | |
check: 'this', // preexisting | |
acceptReturns: true, | |
returnPolicy: 'policy', | |
}) | |
}) | |
test('postage variant', async () => { | |
getResp = { | |
postage: [ | |
{ | |
price: '5.5', | |
price_addl: '4/5', | |
method: 'Another', | |
locations: ['1'], | |
}, | |
], | |
} | |
const wrapper = mount( | |
{ template: '<div/>', mixins: [DefaultListing], data }, | |
{ localVue, store, mocks: { $axios } }, | |
) | |
locationGroupPromise.resolve() | |
await wrapper.vm.$nextTick() // store action completes | |
await wrapper.vm.$nextTick() // loadDefaults completes | |
await wrapper.vm.$nextTick() // mounted finally completes | |
expect(wrapper.vm.defaultsLoading).toBe(false) | |
expect(wrapper.vm.input.shipping.services).toEqual([ | |
{ | |
add: 4, | |
addQty: 5, | |
cost: 5.5, | |
dest: { | |
countries: [2316], | |
id: 2, | |
name: 'Everywhere Else', | |
}, | |
flat: true, | |
label: 'Another', | |
}, | |
]) | |
}) | |
// If the API fails, the user has the option to continue and manually set their fields, | |
// or exit and re-enter the page. This provides flexibility both ways. | |
test('500 error', async () => { | |
$axios.$get = () => Promise.reject({ response: { status: 500 } }) | |
const wrapper = mount( | |
{ template: '<div/>', mixins: [DefaultListing], data }, | |
{ localVue, store, mocks: { $axios } }, | |
) | |
await wrapper.vm.$nextTick() // loadDefaults completes | |
await wrapper.vm.$nextTick() // mounted finally completes | |
expect(wrapper.vm.defaultsLoading).toBe(false) | |
expect(wrapper.vm.input).toEqual(data().input) | |
}) | |
// Even though we have the XHR encapsulated refresh token retry, maybe | |
// that doesn't work, so if not, we need to redirect the user to login. | |
test('401 error', async () => { | |
// this test logs a lot | |
const spy = jest.spyOn(global.console, 'error').mockImplementation(() => {}) | |
$axios.$get = () => Promise.reject({ response: { status: 401 } }) | |
const $auth = { redirect: jest.fn() } | |
try { | |
mount( | |
{ template: '<div/>', mixins: [DefaultListing], data }, | |
{ localVue, store, mocks: { $axios, $auth } }, | |
) | |
} catch (err) { | |
expect(err).toBe('i18n:401') | |
await jest.runAllTimers() | |
expect($auth.redirect).toHaveBeenCalledWith('login') | |
spy.mockRestore() | |
} | |
}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment