guestShopUi.spec.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. /* eslint-env jest */
  2. import Vue from 'vue'
  3. import ElementUI from 'element-ui'
  4. import path from 'path'
  5. import { load } from './loadGuestComponent'
  6. import * as shop from '@/api/shop'
  7. import { getGuestRegionList } from '@/api/config'
  8. import { toThousandFilter } from '@/filters'
  9. jest.mock('@/api/shop', () => ({ fetchGuestProducts: jest.fn(), fetchGuestCart: jest.fn(), createGuestOrder: jest.fn() }))
  10. jest.mock('@/api/config', () => ({ getGuestRegionList: jest.fn() }))
  11. jest.mock('@/router', () => ({ push: jest.fn(), replace: jest.fn() }))
  12. jest.mock('vue-paystack', () => ({
  13. name: 'Paystack', props: ['amount', 'currency', 'reference', 'email', 'metadata', 'callback', 'close', 'paystackkey', 'channels'],
  14. created() { this.scriptLoaded = Promise.resolve() },
  15. render: h => h('button')
  16. }))
  17. Vue.use(ElementUI)
  18. Vue.filter('toThousandFilter', toThousandFilter)
  19. const push = jest.fn()
  20. Vue.prototype.$router = { push, replace: jest.fn() }
  21. Vue.prototype.$route = { query: {}}
  22. Vue.prototype.$message = { error: jest.fn(), info: jest.fn() }
  23. const root = path.resolve('src/ui-refactor/views/guest-shop')
  24. const Flow = load(path.join(root, 'GuestPurchaseFlow.vue'))
  25. const MarketDialog = load(path.join(root, 'MarketDialog.vue'))
  26. const Order = load(path.join(root, 'GuestOrderPage.vue'))
  27. const errors = []
  28. const instances = []
  29. Vue.config.errorHandler = error => errors.push(error)
  30. async function settle() { for (let i = 0; i < 16; i++) { await Promise.resolve(); await Vue.nextTick() } }
  31. const descendants = vm => vm.$children.flatMap(child => [child, ...descendants(child)])
  32. function mount(component, propsData = {}) {
  33. const vm = new (Vue.extend(component))({ propsData }).$mount()
  34. document.body.appendChild(vm.$el)
  35. instances.push(vm)
  36. return vm
  37. }
  38. beforeEach(() => {
  39. jest.clearAllMocks()
  40. errors.length = 0
  41. shop.fetchGuestProducts.mockResolvedValue({ data: { list: [{ ID: 'P1', GOODS_NAME: 'Guest product', GOODS_NO: 'SKU', SELL_PRICE: 100, MARKET_PRICE: 150, PRICE_PV: 20, DISCOUNT: 80, TAX_RATE: 7.5, STORE_NUMS: 5, COIN: 'AED' }], totalCount: 1 }})
  42. shop.fetchGuestCart.mockResolvedValue({ data: { sellType: [{ id: 1, sell_type: [{ label: 'pay_stack', name: 'Paystack' }, { label: 'cash', name: 'Cash' }] }], freight: 10, freeShipping: 500, coin: 'AED' }})
  43. getGuestRegionList.mockResolvedValue({ data: [{ value: 'R1', label: 'Dubai' }] })
  44. })
  45. afterEach(() => {
  46. instances.splice(0).forEach(vm => { vm.$destroy(); vm.$el.remove() })
  47. delete window.PaystackPop
  48. expect(errors).toEqual([])
  49. })
  50. it('offers the two markets and navigates without asking for credentials', async() => {
  51. const vm = mount(MarketDialog, { visible: true })
  52. await settle()
  53. const options = vm.$el.querySelectorAll('.ng-market-option')
  54. expect(options).toHaveLength(2)
  55. expect(options[1].textContent).toContain('UAE')
  56. options[1].click()
  57. expect(push).toHaveBeenCalledWith({ path: '/guest-shop/ae' })
  58. })
  59. it('connects product, cart and checkout controls without guest BV or member fields', async() => {
  60. const vm = mount(Flow, { market: 'ae', stage: 'catalog' })
  61. await settle()
  62. expect(vm.$el.querySelector('.ng-signup-product-price').textContent).toContain('AED 80')
  63. expect(vm.$el.textContent).not.toContain('BV')
  64. vm.$el.querySelector('.ng-signup-product-add').click()
  65. await settle()
  66. vm.stage = 'cart'
  67. await settle()
  68. expect(vm.$el.querySelector('.ng-purchase-cart-item').textContent).toContain('AED 80 each')
  69. const cart = descendants(vm).find(child => child.$options.name === 'GuestCart')
  70. expect(cart.$children[0].$options.name).toBe('PurchaseCartItems')
  71. expect(vm.$el.querySelector('.ng-purchase-summary').textContent).not.toContain('BV')
  72. vm.stage = 'checkout'
  73. await settle()
  74. expect(getGuestRegionList).toHaveBeenCalledWith({ countryId: '620699181539139643' })
  75. const shippingForm = vm.$refs.shipping.$children[0]
  76. expect(shippingForm.$children.some(child => child.$options.name === 'PurchaseCheckoutFields')).toBe(true)
  77. expect(vm.$el.textContent).not.toContain('Member information')
  78. expect(vm.$el.textContent).not.toContain('Reporting Center')
  79. const recipient = vm.$el.querySelector('#ng-guest-consignee')
  80. recipient.value = 'Recipient'
  81. recipient.dispatchEvent(new Event('input', { bubbles: true }))
  82. await settle()
  83. expect(vm.form.consignee).toBe('Recipient')
  84. Object.assign(vm.form, { acceptMobile: '123', province: ['R1'], lgaName: 'District', cityName: 'Dubai', detailaddress: 'Street' })
  85. vm.$el.querySelector('.ng-purchase-place-order').click()
  86. await settle()
  87. const dialog = vm.$children.find(child => child.$options.name === 'PaystackPaymentDialog')
  88. expect(dialog.visible).toBe(true)
  89. expect(dialog.readonlyEmail).toBe(false)
  90. const input = dialog.$el.querySelector('#ng-paystack-payment-email')
  91. input.value = 'visitor@example.test'
  92. input.dispatchEvent(new Event('input', { bubbles: true }))
  93. await settle()
  94. expect(vm.form.email).toBe('visitor@example.test')
  95. expect(dialog.$el.querySelector('[type=password]')).toBeNull()
  96. })
  97. it('keeps Nigeria merchandise, cart, checkout and email amounts in the product currency', async() => {
  98. shop.fetchGuestProducts.mockResolvedValue({ data: { list: [{ ID: 'P1', GOODS_NAME: 'Nigeria product', SELL_PRICE: 100, DISCOUNT: 80, TAX_RATE: 7.5, STORE_NUMS: 5, COIN: 'NGN' }], totalCount: 1 }})
  99. const vm = mount(Flow, { market: 'ng', stage: 'catalog' })
  100. await settle()
  101. expect(vm.$el.querySelector('.ng-signup-product-price').textContent).toContain('NGN 80')
  102. vm.$el.querySelector('.ng-signup-product-add').click()
  103. vm.stage = 'cart'
  104. await settle()
  105. expect(vm.$el.querySelector('.ng-purchase-cart-item').textContent).toContain('NGN 80 each')
  106. expect(vm.$el.querySelector('.ng-purchase-summary').textContent).toContain('NGN 90')
  107. vm.stage = 'checkout'
  108. await settle()
  109. expect(vm.$el.querySelector('.ng-purchase-summary').textContent).toContain('NGN 90')
  110. Object.assign(vm.form, { consignee: 'Recipient', acceptMobile: '123', province: ['R1'], lgaName: 'District', cityName: 'Lagos', detailaddress: 'Street' })
  111. vm.openEmail()
  112. await settle()
  113. const dialog = descendants(vm).find(child => child.$options.name === 'PaystackPaymentDialog')
  114. expect(dialog.visible).toBe(true)
  115. expect(dialog.$el.querySelector('.ng-paystack-payment-amount').textContent).toContain('NGN 90')
  116. })
  117. it('opens Paystack with the order record amount, product currency and entered email', async() => {
  118. const openIframe = jest.fn()
  119. window.PaystackPop = { setup: jest.fn(() => ({ openIframe })) }
  120. shop.fetchGuestProducts.mockResolvedValue({ data: { list: [{ ID: 'P1', GOODS_NAME: 'Nigeria product', SELL_PRICE: 62500, DISCOUNT: 80, TAX_RATE: 7.5, STORE_NUMS: 5, COIN: 'NGN', LOCAL_CURRENCY_ID: '104' }], totalCount: 1 }})
  121. shop.fetchGuestCart.mockResolvedValue({ data: { sellType: [{ id: 1, sell_type: [{ label: 'pay_stack', name: 'Paystack' }] }], freight: 0, freeShipping: 0, coin: 'NGN' }})
  122. shop.createGuestOrder.mockResolvedValue({ data: { SN: 'SAMPLE-ORDER', DEC_SN: 'SAMPLE-DECLARATION', ORDER_AMOUNT: 50000, PAY_AMOUNT: 50000, PAY_FREIGHT: 0, COUNTRY_ID: '620699181539139625', CURRENCY_ID: '104', EMAIL: '', STATUS: 1 }})
  123. const vm = mount(Flow, { market: 'ng', stage: 'catalog' })
  124. vm.publicKey = 'sample-key'
  125. await settle()
  126. vm.toggleProduct(vm.products[0])
  127. vm.stage = 'checkout'
  128. await settle()
  129. Object.assign(vm.form, { consignee: 'Recipient', acceptMobile: '123', province: ['R1'], lgaName: 'District', cityName: 'Lagos', detailaddress: 'Street', email: 'visitor@example.test' })
  130. vm.$el.querySelector('.ng-purchase-place-order').click()
  131. await settle()
  132. const dialog = descendants(vm).find(child => child.$options.name === 'PaystackPaymentDialog')
  133. dialog.$el.querySelector('.ng-paystack-payment-continue').click()
  134. await settle()
  135. expect(openIframe).toHaveBeenCalledTimes(1)
  136. expect(window.PaystackPop.setup.mock.calls[0][0]).toMatchObject({ amount: 5000000, currency: 'NGN', email: 'visitor@example.test', metadata: { cart_id: 'SAMPLE-ORDER' }})
  137. expect(push).not.toHaveBeenCalled()
  138. window.PaystackPop.setup.mock.calls[0][0].onClose()
  139. await settle()
  140. vm.openEmail()
  141. await settle()
  142. dialog.$el.querySelector('.ng-paystack-payment-continue').click()
  143. await settle()
  144. expect(shop.createGuestOrder).toHaveBeenCalledTimes(1)
  145. expect(openIframe).toHaveBeenCalledTimes(2)
  146. window.PaystackPop.setup.mock.calls[1][0].callback({ reference: 'sample-reference' })
  147. await settle()
  148. expect(push).toHaveBeenCalledWith({ path: '/guest-order', query: { order: 'SAMPLE-ORDER' }})
  149. })
  150. it('does not turn a backend guest Cash option into a member balance', async() => {
  151. const vm = mount(Flow, { market: 'ae', stage: 'catalog' })
  152. await settle()
  153. vm.toggleProduct(vm.products[0])
  154. vm.stage = 'checkout'
  155. vm.payType = 'cash'
  156. await settle()
  157. expect(vm.$el.querySelector('.ng-payment-methods').textContent).not.toContain('Account Balance')
  158. expect(vm.$el.textContent).not.toContain('Available balance')
  159. })
  160. it('shows only the supplied order reference, without fabricating receipt or paid data', async() => {
  161. Vue.prototype.$route = { query: { order: 'REAL-ORDER' }}
  162. const vm = mount(Order)
  163. await settle()
  164. expect(vm.$el.textContent).toContain('Order REAL-ORDER')
  165. expect(vm.$el.textContent).toContain('Order details are not available yet')
  166. expect(vm.$el.textContent).not.toContain('PAID')
  167. expect(shop.fetchGuestProducts).not.toHaveBeenCalled()
  168. })