teamBackgroundManagement.spec.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import fs from 'fs'
  2. import path from 'path'
  3. import * as babel from '@babel/core'
  4. import { parseComponent } from 'vue-template-compiler'
  5. import { ACCEPT, formatFileSize, validateBackgroundFile } from '@/utils/teamBackgroundFile'
  6. process.env.BROWSERSLIST_IGNORE_OLD_DATA = 'true'
  7. const queuePath = path.resolve(__dirname, '../../src/components/team-background/TeamBackgroundUploadQueue.vue')
  8. const dialogPath = path.resolve(__dirname, '../../src/components/team-background/TeamBackgroundFileDialog.vue')
  9. const managerPath = path.resolve(__dirname, '../../src/views/modules/wechatUser/teamManager.vue')
  10. const readIfPresent = file => fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : ''
  11. function loadVueOptions(file, mocks = {}) {
  12. const source = readIfPresent(file)
  13. const descriptor = parseComponent(source)
  14. const code = babel.transformSync(descriptor.script.content, {
  15. babelrc: false,
  16. configFile: false,
  17. plugins: [require.resolve('@babel/plugin-transform-modules-commonjs')]
  18. }).code
  19. const module = { exports: {} }
  20. const localRequire = id => {
  21. if (Object.prototype.hasOwnProperty.call(mocks, id)) return mocks[id]
  22. throw new Error(`Missing test mock for ${id}`)
  23. }
  24. const evaluate = new Function('require', 'module', 'exports', code)
  25. evaluate(localRequire, module, module.exports)
  26. return module.exports.default
  27. }
  28. function expectComponent(source) {
  29. expect(source).not.toBe('')
  30. return source !== ''
  31. }
  32. describe('team background upload queue contract', () => {
  33. const source = readIfPresent(queuePath)
  34. test('accepts queue items and renders every required status without mutating the list', () => {
  35. if (!expectComponent(source)) return
  36. const options = loadVueOptions(queuePath)
  37. expect(options.props.files).toBeDefined()
  38. for (const state of ['pending', 'uploading', 'success', 'failed']) {
  39. expect(source).toContain(state)
  40. }
  41. expect(source).not.toMatch(/files\.(?:splice|pop|shift)|files\s*=|filter\([^)]*status/)
  42. })
  43. test('exposes explicit retry and remove actions', () => {
  44. if (!expectComponent(source)) return
  45. expect(source).toMatch(/\$emit\(['"]retry['"],\s*item\)/)
  46. expect(source).toMatch(/\$emit\(['"]remove['"],\s*item\)/)
  47. })
  48. })
  49. describe('team background management dialog behavior', () => {
  50. const source = readIfPresent(dialogPath)
  51. const api = {
  52. createTeamBackgroundSession: jest.fn(),
  53. disableTeamBackgroundFile: jest.fn(),
  54. downloadTeamBackgroundFile: jest.fn(),
  55. listTeamBackgroundFiles: jest.fn(),
  56. uploadTeamBackgroundFile: jest.fn()
  57. }
  58. const saveAs = jest.fn()
  59. function createVm(overrides = {}) {
  60. const options = loadVueOptions(dialogPath, {
  61. '@/api/teamBackgroundFile': api,
  62. '@/utils/teamBackgroundFile': { ACCEPT, formatFileSize, validateBackgroundFile },
  63. 'file-saver': { saveAs },
  64. './TeamBackgroundUploadQueue.vue': {}
  65. })
  66. const vm = {
  67. ...options.data(),
  68. visible: true,
  69. teamId: 7,
  70. teamName: 'Alpha',
  71. $emit: jest.fn(),
  72. $message: { success: jest.fn(), error: jest.fn() },
  73. $confirm: jest.fn().mockResolvedValue(),
  74. $refs: { fileInput: { value: 'selected' } },
  75. ...options.methods,
  76. ...overrides
  77. }
  78. return vm
  79. }
  80. beforeEach(() => {
  81. Object.values(api).forEach(mock => mock.mockReset())
  82. saveAs.mockReset()
  83. })
  84. test('creates the exact queue item shape and rejects more than ten files before enqueueing', async () => {
  85. if (!expectComponent(source)) return
  86. const vm = createVm()
  87. const files = [{ name: 'one.pdf', size: 1 }, { name: 'two.docx', size: 2 }]
  88. const now = jest.spyOn(Date, 'now').mockReturnValue(123456)
  89. expect(vm.createQueueItems(files)).toEqual([
  90. { id: '123456-0', file: files[0], name: 'one.pdf', progress: 0, status: 'pending', error: '' },
  91. { id: '123456-1', file: files[1], name: 'two.docx', progress: 0, status: 'pending', error: '' }
  92. ])
  93. now.mockRestore()
  94. vm.uploadOne = jest.fn()
  95. await vm.handleFileChange({ target: { files: Array.from({ length: 11 }, (_, index) => ({ name: `${index}.pdf`, size: 1 })) } })
  96. expect(vm.queue).toEqual([])
  97. expect(vm.uploadOne).not.toHaveBeenCalled()
  98. expect(vm.$message.error).toHaveBeenCalledWith(expect.stringContaining('10'))
  99. })
  100. test('validates before upload and retains a successful item when another upload fails', async () => {
  101. if (!expectComponent(source)) return
  102. const vm = createVm()
  103. const success = { id: '1-0', file: { name: 'ok.pdf', size: 1 }, name: 'ok.pdf', progress: 0, status: 'pending', error: '' }
  104. const failure = { id: '1-1', file: { name: 'bad.docx', size: 1 }, name: 'bad.docx', progress: 0, status: 'pending', error: '' }
  105. const invalid = { id: '1-2', file: { name: 'bad.zip', size: 1 }, name: 'bad.zip', progress: 0, status: 'pending', error: '' }
  106. vm.queue = [success, failure, invalid]
  107. vm.refreshFiles = jest.fn().mockResolvedValue()
  108. api.uploadTeamBackgroundFile
  109. .mockResolvedValueOnce({ code: 0, data: {} })
  110. .mockRejectedValueOnce({ msg: '网络失败' })
  111. await vm.uploadOne(success)
  112. await vm.uploadOne(failure)
  113. await vm.uploadOne(invalid)
  114. expect(success).toMatchObject({ status: 'success', progress: 100, error: '' })
  115. expect(failure).toMatchObject({ status: 'failed', error: '网络失败' })
  116. expect(invalid).toMatchObject({ status: 'failed', error: expect.stringContaining('文件类型') })
  117. expect(vm.queue).toEqual([success, failure, invalid])
  118. expect(api.uploadTeamBackgroundFile).toHaveBeenCalledTimes(2)
  119. })
  120. test('uses a multiple accept-filtered input and keeps the ten-file batch cap in source', () => {
  121. if (!expectComponent(source)) return
  122. expect(source).toMatch(/<input[^>]+type="file"[^>]+multiple[^>]+:accept="ACCEPT"/s)
  123. expect(source).toMatch(/selectedFiles\.length\s*>\s*10/)
  124. })
  125. test('refreshes active files and emits their count', async () => {
  126. if (!expectComponent(source)) return
  127. const rows = [{ id: 1 }, { id: 2 }]
  128. api.listTeamBackgroundFiles.mockResolvedValue({ code: 0, data: rows })
  129. const vm = createVm()
  130. await vm.refreshFiles()
  131. expect(vm.fileList).toBe(rows)
  132. expect(vm.$emit).toHaveBeenCalledWith('changed', 2)
  133. })
  134. test('downloads only the authorized blob and disables through the team API', async () => {
  135. if (!expectComponent(source)) return
  136. const blob = { type: 'application/pdf' }
  137. api.downloadTeamBackgroundFile.mockResolvedValue(blob)
  138. api.disableTeamBackgroundFile.mockResolvedValue({ code: 0 })
  139. const vm = createVm()
  140. vm.refreshFiles = jest.fn().mockResolvedValue()
  141. const row = { id: 12, fileName: 'brief.pdf', url: 'https://untrusted.example/file' }
  142. await vm.downloadFile(row)
  143. await vm.disableFile(row)
  144. expect(api.downloadTeamBackgroundFile).toHaveBeenCalledWith(7, 12)
  145. expect(saveAs).toHaveBeenCalledWith(blob, 'brief.pdf')
  146. expect(api.disableTeamBackgroundFile).toHaveBeenCalledWith(7, 12)
  147. expect(vm.refreshFiles).toHaveBeenCalledTimes(1)
  148. })
  149. test('generates and displays a copyable, refreshable PC upload session', async () => {
  150. if (!expectComponent(source)) return
  151. const session = { code: '123456', uploadUrl: 'https://upload.example/token', expiresAt: '2026-07-21 18:00:00' }
  152. api.createTeamBackgroundSession.mockResolvedValue({ code: 0, data: session })
  153. const vm = createVm()
  154. await vm.createUploadSession()
  155. expect(vm.session).toBe(session)
  156. for (const field of ['session.code', 'session.expiresAt', 'session.uploadUrl', 'copyUploadUrl', 'refreshFiles']) {
  157. expect(source).toContain(field)
  158. }
  159. })
  160. test('shows complete file metadata and user-facing WECHAT/PC labels', () => {
  161. if (!expectComponent(source)) return
  162. for (const field of ['fileName', 'fileExt', 'fileSize', 'source', 'createDate']) {
  163. expect(source).toContain(field)
  164. }
  165. expect(source).toContain("WECHAT: '微信端'")
  166. expect(source).toContain("PC: '电脑端'")
  167. expect(source).toContain('formatFileSize')
  168. })
  169. })
  170. describe('team table integration contract', () => {
  171. const source = readIfPresent(managerPath)
  172. test('places enterprise website immediately after company and renders the document count link', () => {
  173. expect(source).toContain('label="企业官网" prop="enterpriseWebsite"')
  174. const company = source.indexOf('label="所在公司"')
  175. const website = source.indexOf('label="企业官网"')
  176. const region = source.indexOf('label="所属地区"')
  177. const nextLabel = source.slice(company + 1).match(/label="([^"]+)"/)
  178. expect(company).toBeGreaterThan(-1)
  179. expect(website).toBeGreaterThan(company)
  180. expect(region).toBeGreaterThan(website)
  181. expect(nextLabel[1]).toBe('企业官网')
  182. expect(source).toContain("row.enterpriseWebsite || '无'")
  183. expect(source).toContain('row.backgroundFileCount || 0')
  184. expect(source).toContain('@click="openBackgroundFiles(row)"')
  185. })
  186. test('mounts one dialog and updates only the matching row on changed(count)', () => {
  187. const mounts = source.match(/<team-background-file-dialog\b/g) || []
  188. expect(mounts).toHaveLength(1)
  189. expect(source).toContain('@changed="handleBackgroundFilesChanged"')
  190. expect(source).toMatch(/dataList\.value\.find\([^)]*activeTeam\.value\.id/)
  191. expect(source).toMatch(/row\.backgroundFileCount\s*=\s*count/)
  192. expect(source).not.toMatch(/handleBackgroundFilesChanged[\s\S]{0,180}getList\(/)
  193. })
  194. })