signup.spec.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. /* eslint-env jest */
  2. import Vue from 'vue'
  3. import registration from '@/ui-refactor/views/signup/components/welcomePackRegistration'
  4. import { getLanguages } from '@/api/setting'
  5. import request from '@/utils/request'
  6. jest.mock('@/api/setting', () => ({ getLanguages: jest.fn() }))
  7. jest.mock('@/utils/usersInfo', () => ({ userName: () => null, userEmail: () => null }))
  8. jest.mock('@/utils/request', () => jest.fn())
  9. function instance() {
  10. const vm = registration.data()
  11. vm.$message = jest.fn()
  12. vm.$message.error = jest.fn()
  13. vm.$t = key => key
  14. vm.$router = { push: jest.fn() }
  15. Object.keys(registration.methods).forEach(key => {
  16. vm[key] = registration.methods[key].bind(vm)
  17. })
  18. return vm
  19. }
  20. async function settle() {
  21. await Promise.resolve()
  22. await Promise.resolve()
  23. }
  24. describe('Signup welcome-pack compatibility', () => {
  25. beforeAll(() => jest.spyOn(window.location, 'assign').mockImplementation(() => {}))
  26. afterAll(() => window.location.assign.mockRestore())
  27. beforeEach(() => {
  28. jest.resetAllMocks()
  29. jest.useFakeTimers()
  30. })
  31. afterEach(() => {
  32. jest.clearAllTimers()
  33. jest.useRealTimers()
  34. })
  35. it.each([
  36. [{ ref: 'test-inviter' }, 'test-inviter'],
  37. [{ ref: ' test-inviter ' }, 'test-inviter'],
  38. [{}, ''],
  39. [{ ref: '' }, ''],
  40. [{ ref: ' ' }, ''],
  41. [{ ref: ['first', 'second'] }, '']
  42. ])('defaults both network IDs from the invitation query %j without making requests', (query, expected) => {
  43. const vm = new Vue({
  44. mixins: [registration],
  45. beforeCreate() {
  46. this.$route = { query }
  47. }
  48. })
  49. expect(vm.form.recUserName).toBe(expected)
  50. expect(vm.form.conUserName).toBe(expected)
  51. vm.form.recUserName = 'edited-referrer'
  52. vm.form.conUserName = 'edited-actual-referrer'
  53. expect(vm.form.recUserName).toBe('edited-referrer')
  54. expect(vm.form.conUserName).toBe('edited-actual-referrer')
  55. expect(request).not.toHaveBeenCalled()
  56. vm.$destroy()
  57. })
  58. it.each([
  59. ['', 'actual-referrer', 'Please enter the referrer ID.'],
  60. [' ', 'actual-referrer', 'Please enter the referrer ID.'],
  61. ['referrer', '', 'Please enter the actual referrer ID.'],
  62. ['referrer', ' ', 'Please enter the actual referrer ID.']
  63. ])('requires both network IDs before creating an order: %j / %j', (referrer, actualReferrer, message) => {
  64. const vm = instance()
  65. Object.assign(vm.form, { decLv: 'level', decUserName: 'center', recUserName: referrer, conUserName: actualReferrer })
  66. vm.isDec = '0'
  67. vm.draft = { draftId: 'test-draft', accessToken: 'test-only-guest-token' }
  68. vm.multipleSelection = [{ ID: 'goods', goodsNum: 1, MARKET_PRICE: 120 }]
  69. request.mockResolvedValue({ data: { accessCode: 'test-access-code', reference: 'test-reference' }})
  70. vm.onSubmit()
  71. expect(request).not.toHaveBeenCalled()
  72. expect(vm.submitButtonStat).toBe(false)
  73. expect(vm.$message.error).toHaveBeenCalledWith(message)
  74. })
  75. it('queries the guest referrer and resets the previous actual referrer', async() => {
  76. const vm = instance()
  77. vm.form.recUserName = 'test-referrer'
  78. vm.form.conUserName = 'previous-referrer'
  79. vm.form.autoPlace = 'previous-placement'
  80. vm.conRealName = 'Previous name'
  81. request.mockResolvedValue({ data: { REAL_NAME: 'Test referrer' }})
  82. vm.handleChkRecUser()
  83. expect(request).toHaveBeenCalledWith({
  84. url: '/v1/user/guest-full-info', method: 'get', params: { userName: 'test-referrer' }
  85. })
  86. expect(vm.form.conUserName).toBe('')
  87. expect(vm.form.autoPlace).toBe('')
  88. expect(vm.conRealName).toBe('-')
  89. expect(vm.loading).toBe(true)
  90. await settle()
  91. expect(vm.recRealName).toBe('Test referrer')
  92. expect(vm.loading).toBe(false)
  93. })
  94. it('queries the guest actual referrer with the existing network parameters', async() => {
  95. const vm = instance()
  96. vm.form.recUserName = 'test-referrer'
  97. vm.form.conUserName = 'test-actual-referrer'
  98. request.mockResolvedValue({ data: { REAL_NAME: 'Test actual referrer' }})
  99. vm.handleChkRecommenderActualName()
  100. expect(request).toHaveBeenCalledWith({
  101. url: '/v1/user/guest-inspect-network', method: 'get',
  102. params: { recommenderName: 'test-referrer', recommenderActualName: 'test-actual-referrer' }
  103. })
  104. expect(vm.loading).toBe(true)
  105. await settle()
  106. expect(vm.conRealName).toBe('Test actual referrer')
  107. expect(vm.loading).toBe(false)
  108. })
  109. it.each([
  110. ['handleChkRecUser', 'recRealName'],
  111. ['handleChkRecommenderActualName', 'conRealName']
  112. ])('clears the name and loading state when %s fails', async(method, nameField) => {
  113. const vm = instance()
  114. vm.form.recUserName = 'test-referrer'
  115. vm.form.conUserName = 'test-actual-referrer'
  116. vm[nameField] = 'Previous name'
  117. request.mockRejectedValue(new Error('Test query failure'))
  118. vm[method]()
  119. await settle()
  120. expect(vm[nameField]).toBe('-')
  121. expect(vm.loading).toBe(false)
  122. })
  123. it('does not query guest network information for empty referrer IDs', () => {
  124. const vm = instance()
  125. vm.handleChkRecUser()
  126. vm.handleChkRecommenderActualName()
  127. expect(request).not.toHaveBeenCalled()
  128. expect(vm.loading).toBe(false)
  129. })
  130. it('initializes with fixed country and language options without requesting lists', () => {
  131. const dispatch = jest.fn(() => Promise.resolve())
  132. const vm = new Vue({
  133. mixins: [registration],
  134. beforeCreate() {
  135. this.$store = { dispatch, getters: {}}
  136. }
  137. })
  138. expect(vm.countriesList).toEqual([
  139. { ID: '620699181539139625', NAME: 'Nigeria', DEFAULT_LANGUAGE_ID: '620713695651307520' }
  140. ])
  141. expect(vm.languageList).toEqual([
  142. { ID: '620713695718416384', NAME: '中文' },
  143. { ID: '620713695651307520', NAME: 'English' }
  144. ])
  145. expect(vm.form.countryId).toBe('')
  146. expect(dispatch).not.toHaveBeenCalled()
  147. expect(getLanguages).not.toHaveBeenCalled()
  148. expect(request).not.toHaveBeenCalled()
  149. vm.$destroy()
  150. })
  151. it('selects the country default language and automatically loads the guest order', async() => {
  152. const vm = instance()
  153. vm.form.countryId = '620699181539139625'
  154. vm.form.languageId = '620713695718416384'
  155. request.mockResolvedValue({ data: {
  156. draft: { draftId: 'test-draft', accessToken: 'test-only-token' },
  157. allGoods: [], region: [], decLevel: [], payList: {}
  158. }})
  159. vm.countriesChange()
  160. expect(vm.form.languageId).toBe('620713695651307520')
  161. expect(request).toHaveBeenCalledTimes(1)
  162. expect(request).toHaveBeenCalledWith({
  163. url: '/v1/user/guest-dec', method: 'get', params: { countryId: '620699181539139625' }
  164. })
  165. await settle()
  166. expect(vm.draft.draftId).toBe('test-draft')
  167. })
  168. it('loads guest identity, member ID, currency and payment methods from the selected country', async() => {
  169. const vm = instance()
  170. vm.form.countryId = 'country'
  171. const draft = {
  172. draftId: '90071992547409931234', accessToken: 'test-only-guest-token',
  173. insertUserName: 'test-guest', currency: 'NGN', expiresAt: 2000000000
  174. }
  175. const payList = { pay_stack: { label: 'pay_stack', name: 'Paystack' }}
  176. request.mockResolvedValue({ data: { draft, allGoods: [], region: [], decLevel: [], payList }})
  177. vm.fetchWelcomePack()
  178. expect(request).toHaveBeenCalledWith({
  179. url: '/v1/user/guest-dec', method: 'get', params: { countryId: 'country' }
  180. })
  181. await settle()
  182. jest.runOnlyPendingTimers()
  183. expect(vm.draft).toEqual(draft)
  184. expect(vm.form.insertUserName).toBe('test-guest')
  185. expect(vm.currencyUnitCode).toBe('NGN')
  186. expect(vm.payList).toEqual(payList)
  187. expect(vm.form.payType).toBe('pay_stack')
  188. expect(vm.userBalance).toEqual({ cash: 0 })
  189. expect(vm.loading).toBe(false)
  190. })
  191. it('clears the previous identity when reloading and keeps it cleared if the GET fails', async() => {
  192. const vm = instance()
  193. vm.draft = { draftId: 'old-draft', accessToken: 'test-only-old-token' }
  194. vm.form.insertUserName = 'old-guest'
  195. request.mockRejectedValue(new Error('Test load failure'))
  196. vm.countriesChange()
  197. expect(vm.draft).toEqual({})
  198. expect(vm.form.insertUserName).toBe('')
  199. await settle()
  200. expect(vm.loading).toBe(false)
  201. expect(vm.$message.error).toHaveBeenCalledWith('Test load failure')
  202. })
  203. it('ignores a delayed guest response for a previously selected country', async() => {
  204. const vm = instance()
  205. let resolvePrevious
  206. request.mockReturnValueOnce(new Promise(resolve => { resolvePrevious = resolve }))
  207. const draft = { draftId: 'current-draft', accessToken: 'test-only-current-token', insertUserName: 'current-guest' }
  208. request.mockResolvedValueOnce({ data: {
  209. draft, allGoods: [], region: [], decLevel: [],
  210. sellType: [{ sell_type: [{ label: 'pay_stack', name: 'Paystack' }] }]
  211. }})
  212. vm.form.countryId = 'previous'
  213. vm.countriesChange()
  214. vm.form.countryId = 'current'
  215. vm.countriesChange()
  216. await settle()
  217. resolvePrevious({ data: { draft: { draftId: 'previous-draft' }}})
  218. await settle()
  219. expect(vm.draft).toEqual(draft)
  220. expect(vm.form.insertUserName).toBe('current-guest')
  221. expect(vm.form.payType).toBe('pay_stack')
  222. })
  223. it('uses market price, quantity, BV and inclusive tax instead of the sales price', () => {
  224. const vm = instance()
  225. vm.tableData = [{ ID: 'goods', SELL_PRICE: 100, MARKET_PRICE: 120, PRICE_PV: 10, TAX_RATE: 10 }]
  226. vm.storeNums = [2]
  227. vm.handleSelectionChange(vm.tableData.slice())
  228. expect([vm.sellPriceSum, vm.pricePvSum, vm.taxSum]).toEqual(['240.00', '20.00', '21.82'])
  229. vm.handleSelectionChange([])
  230. expect([vm.sellPriceSum, vm.pricePvSum, vm.taxSum]).toEqual([0, 0, 0])
  231. })
  232. it('keeps the level, reporting center, product and cash balance guards', () => {
  233. const vm = instance()
  234. vm.onSubmit()
  235. vm.form.decLv = 'level'
  236. vm.isDec = '0'
  237. // 页面已有默认中心编号,此处显式清空,独立验证必填拦截。
  238. vm.form.decUserName = ''
  239. vm.onSubmit()
  240. vm.form.decUserName = 'center'
  241. vm.form.recUserName = 'test-referrer'
  242. vm.form.conUserName = 'test-actual-referrer'
  243. vm.onSubmit()
  244. vm.multipleSelection = [{ ID: 'goods', goodsNum: 2, MARKET_PRICE: 120 }]
  245. vm.form.payType = 'cash'
  246. vm.userBalance.cash = 1
  247. vm.onSubmit()
  248. expect(request).not.toHaveBeenCalled()
  249. expect(vm.$message.mock.calls.map(call => call[0].message)).toEqual([
  250. 'profile.selectLevel', 'shop.declarationCenterNumberTips', 'shop.balanceNotAllow'
  251. ])
  252. expect(vm.$message.error).toHaveBeenCalledWith('shop.chooseTips')
  253. })
  254. it.each([
  255. ['pay_stack', true],
  256. ['paystack', false]
  257. ])('preserves order fields and opens the existing payment dialog for %s', async(payType, wrapped) => {
  258. const vm = instance()
  259. vm.draft = { draftId: '90071992547409931234', accessToken: 'test-only-guest-token' }
  260. Object.assign(vm.form, {
  261. decLv: 'level', decUserName: 'center', countryId: 'country', languageId: 'language',
  262. recUserName: 'edited-referrer', conUserName: 'edited-actual-referrer',
  263. areaSelected: ['province', 'city', 'county'], openBank: 'bank', bankNo: 'account',
  264. bankAddress: 'account name', payType, password: 'test-only', payPassword: 'test-only'
  265. })
  266. vm.multipleSelection = [{ ID: 'goods', goodsNum: 2, MARKET_PRICE: 120 }]
  267. const order = {
  268. orderSn: 'test-order', amount: 873400, currency: 'NGN', reference: 'test-reference',
  269. authorizationUrl: 'https://checkout.paystack.com/test-checkout', accessCode: 'test-checkout'
  270. }
  271. request.mockResolvedValue(wrapped ? { data: order } : order)
  272. vm.onSubmit()
  273. expect(request).toHaveBeenCalledWith({
  274. url: '/v1/user/guest-dec', method: 'post', data: expect.objectContaining({
  275. draftId: '90071992547409931234', accessToken: 'test-only-guest-token',
  276. countryId: 'country', languageId: 'language', province: 'province', city: 'city', county: 'county',
  277. openBank: 'bank', bankNo: 'account', bankAddress: 'account name',
  278. recUserName: 'edited-referrer', conUserName: 'edited-actual-referrer',
  279. goodsId: ['goods'], goodsNum: [2], decType: 'normal', decWay: '2', location: 1, payType
  280. })
  281. })
  282. expect(request.mock.calls[0][0].data).not.toHaveProperty('way')
  283. expect(request.mock.calls[0][0].data).not.toHaveProperty('draft')
  284. await settle()
  285. expect(vm.submitButtonStat).toBe(false)
  286. expect(vm.visible).toBe(true)
  287. expect(vm.paymentOrder).toEqual(order)
  288. expect(vm.paymentOrder.amount).toBe(873400)
  289. expect(vm.$message).not.toHaveBeenCalled()
  290. jest.advanceTimersByTime(6000)
  291. expect(vm.$router.push).not.toHaveBeenCalled()
  292. expect(window.location.assign).not.toHaveBeenCalled()
  293. })
  294. it('does not submit a guest order without both temporary identifiers', () => {
  295. const vm = instance()
  296. vm.form.decLv = 'level'
  297. vm.form.recUserName = 'test-referrer'
  298. vm.form.conUserName = 'test-actual-referrer'
  299. vm.form.payType = 'pay_stack'
  300. vm.multipleSelection = [{ ID: 'goods', goodsNum: 1, MARKET_PRICE: 120 }]
  301. vm.onSubmit()
  302. vm.draft = { draftId: 'test-draft' }
  303. vm.onSubmit()
  304. vm.draft = { accessToken: 'test-only-guest-token' }
  305. vm.onSubmit()
  306. expect(request).not.toHaveBeenCalled()
  307. expect(vm.$message.error).toHaveBeenCalledTimes(3)
  308. expect(vm.submitButtonStat).toBe(false)
  309. })
  310. it('clears the submit loading state after a guest order failure', async() => {
  311. const vm = instance()
  312. vm.draft = { draftId: 'test-draft', accessToken: 'test-only-guest-token' }
  313. vm.form.decLv = 'level'
  314. vm.form.recUserName = 'test-referrer'
  315. vm.form.conUserName = 'test-actual-referrer'
  316. vm.form.payType = 'pay_stack'
  317. vm.multipleSelection = [{ ID: 'goods', goodsNum: 1, MARKET_PRICE: 120 }]
  318. request.mockRejectedValue('Test submit failure')
  319. vm.onSubmit()
  320. expect(vm.submitButtonStat).toBe(true)
  321. await settle()
  322. expect(vm.submitButtonStat).toBe(false)
  323. expect(vm.$message).toHaveBeenCalledWith({ message: 'Test submit failure', type: 'error' })
  324. })
  325. it.each([
  326. {},
  327. { reference: 'test-reference' },
  328. { accessCode: 'test-access-code' }
  329. ])('does not open checkout when the transaction identifiers are incomplete: %s', async(order) => {
  330. const vm = instance()
  331. vm.draft = { draftId: 'test-draft', accessToken: 'test-only-guest-token' }
  332. vm.form.decLv = 'level'
  333. vm.form.recUserName = 'test-referrer'
  334. vm.form.conUserName = 'test-actual-referrer'
  335. vm.form.payType = 'pay_stack'
  336. vm.multipleSelection = [{ ID: 'goods', goodsNum: 1, MARKET_PRICE: 120 }]
  337. request.mockResolvedValue({ data: order })
  338. vm.onSubmit()
  339. await settle()
  340. expect(vm.submitButtonStat).toBe(false)
  341. expect(window.location.assign).not.toHaveBeenCalled()
  342. expect(vm.$router.push).not.toHaveBeenCalled()
  343. expect(vm.$message).toHaveBeenCalledWith({ message: expect.any(String), type: 'error' })
  344. })
  345. it('refreshes payment details on every continuation using the current form and prevents concurrent requests', async() => {
  346. const vm = instance()
  347. vm.draft = { draftId: 'test-draft', accessToken: 'test-only-guest-token' }
  348. Object.assign(vm.form, {
  349. decLv: 'level', recUserName: 'test-referrer', conUserName: 'test-actual-referrer',
  350. payType: 'pay_stack', email: 'first@example.invalid'
  351. })
  352. vm.multipleSelection = [{ ID: 'goods', goodsNum: 1, MARKET_PRICE: 120 }]
  353. vm.paymentOrder = { orderSn: 'test-order', accessCode: 'test-access-code', reference: 'test-reference' }
  354. const firstOrder = { accessCode: 'first-access-code', reference: 'first-reference', amount: 120, currency: 'NGN' }
  355. const secondOrder = { accessCode: 'second-access-code', reference: 'second-reference', amount: 240, currency: 'NGN' }
  356. request.mockResolvedValueOnce({ data: firstOrder }).mockResolvedValueOnce({ data: secondOrder })
  357. vm.visible = true
  358. vm.handleClose()
  359. expect(vm.visible).toBe(false)
  360. vm.onSubmit()
  361. expect(vm.visible).toBe(false)
  362. expect(vm.submitButtonStat).toBe(true)
  363. vm.onSubmit()
  364. expect(request).toHaveBeenCalledTimes(1)
  365. await settle()
  366. expect(vm.visible).toBe(true)
  367. expect(vm.paymentOrder).toEqual(firstOrder)
  368. expect(vm.submitButtonStat).toBe(false)
  369. vm.handleClose()
  370. vm.form.email = 'updated@example.invalid'
  371. vm.multipleSelection[0].goodsNum = 2
  372. vm.onSubmit()
  373. expect(request).toHaveBeenCalledTimes(2)
  374. expect(request).toHaveBeenLastCalledWith({
  375. url: '/v1/user/guest-dec', method: 'post', data: expect.objectContaining({
  376. draftId: 'test-draft', accessToken: 'test-only-guest-token',
  377. email: 'updated@example.invalid', goodsId: ['goods'], goodsNum: [2]
  378. })
  379. })
  380. expect(vm.visible).toBe(false)
  381. await settle()
  382. expect(vm.visible).toBe(true)
  383. expect(vm.paymentOrder).toEqual(secondOrder)
  384. expect(vm.submitButtonStat).toBe(false)
  385. expect(window.location.assign).not.toHaveBeenCalled()
  386. expect(vm.$router.push).not.toHaveBeenCalled()
  387. })
  388. it.each([false, true])('does not reopen cached payment after a failed or incomplete refresh: %s', async(incomplete) => {
  389. const vm = instance()
  390. vm.draft = { draftId: 'test-draft', accessToken: 'test-only-guest-token' }
  391. Object.assign(vm.form, { decLv: 'level', recUserName: 'test-referrer', conUserName: 'test-actual-referrer', payType: 'pay_stack' })
  392. vm.multipleSelection = [{ ID: 'goods', goodsNum: 1, MARKET_PRICE: 120 }]
  393. vm.paymentOrder = { accessCode: 'old-access-code', reference: 'old-reference' }
  394. if (incomplete) request.mockResolvedValue({ data: { reference: 'new-reference' }})
  395. else request.mockRejectedValue(new Error('Unable to refresh payment.'))
  396. vm.onSubmit()
  397. expect(request).toHaveBeenCalledTimes(1)
  398. await settle()
  399. expect(vm.visible).toBe(false)
  400. expect(vm.submitButtonStat).toBe(false)
  401. expect(vm.$message).toHaveBeenCalledWith({
  402. message: incomplete ? 'Payment details are missing. Please contact customer service.' : 'Unable to refresh payment.',
  403. type: 'error'
  404. })
  405. })
  406. it('validates the edited form before requesting continued payment', () => {
  407. const vm = instance()
  408. vm.paymentOrder = { accessCode: 'old-access-code', reference: 'old-reference' }
  409. vm.onSubmit()
  410. expect(request).not.toHaveBeenCalled()
  411. expect(vm.visible).toBe(false)
  412. expect(vm.$message).toHaveBeenCalledWith({ message: 'profile.selectLevel', type: 'error' })
  413. })
  414. it('stays on signup after the payment callback and prevents another submission', () => {
  415. const vm = instance()
  416. vm.paymentOrder = { accessCode: 'test-access-code', reference: 'test-reference' }
  417. vm.visible = true
  418. vm.processPayment({ reference: 'test-reference' })
  419. expect(vm.visible).toBe(false)
  420. expect(vm.paymentComplete).toBe(true)
  421. expect(vm.$message).toHaveBeenCalledWith({
  422. message: 'Payment submitted. Please wait for order confirmation.', type: 'success'
  423. })
  424. vm.onSubmit()
  425. jest.advanceTimersByTime(6000)
  426. expect(request).not.toHaveBeenCalled()
  427. expect(window.location.assign).not.toHaveBeenCalled()
  428. expect(vm.$router.push).not.toHaveBeenCalled()
  429. })
  430. it.each([undefined, { reference: 'different-reference' }])('rejects a callback for a different payment: %s', response => {
  431. const vm = instance()
  432. vm.paymentOrder = { reference: 'test-reference' }
  433. vm.processPayment(response)
  434. expect(vm.paymentComplete).toBe(false)
  435. expect(vm.$message.error).toHaveBeenCalledWith('Payment reference does not match the current order.')
  436. expect(vm.$router.push).not.toHaveBeenCalled()
  437. })
  438. })