|
@@ -12,6 +12,16 @@ const managerPath = path.resolve(__dirname, '../../src/views/modules/wechatUser/
|
|
|
|
|
|
|
|
const readIfPresent = file => fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : ''
|
|
const readIfPresent = file => fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : ''
|
|
|
|
|
|
|
|
|
|
+function deferred() {
|
|
|
|
|
+ let resolve
|
|
|
|
|
+ let reject
|
|
|
|
|
+ const promise = new Promise((resolvePromise, rejectPromise) => {
|
|
|
|
|
+ resolve = resolvePromise
|
|
|
|
|
+ reject = rejectPromise
|
|
|
|
|
+ })
|
|
|
|
|
+ return { promise, resolve, reject }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function loadVueOptions(file, mocks = {}) {
|
|
function loadVueOptions(file, mocks = {}) {
|
|
|
const source = readIfPresent(file)
|
|
const source = readIfPresent(file)
|
|
|
const descriptor = parseComponent(source)
|
|
const descriptor = parseComponent(source)
|
|
@@ -87,9 +97,17 @@ describe('team background management dialog behavior', () => {
|
|
|
...options.methods,
|
|
...options.methods,
|
|
|
...overrides
|
|
...overrides
|
|
|
}
|
|
}
|
|
|
|
|
+ vm.$componentWatch = options.watch || {}
|
|
|
return vm
|
|
return vm
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function invokeWatcher(vm, name, value, oldValue) {
|
|
|
|
|
+ const watcher = vm.$componentWatch[name]
|
|
|
|
|
+ if (!watcher) return
|
|
|
|
|
+ const handler = typeof watcher === 'function' ? watcher : watcher.handler
|
|
|
|
|
+ return handler.call(vm, value, oldValue)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
beforeEach(() => {
|
|
beforeEach(() => {
|
|
|
Object.values(api).forEach(mock => mock.mockReset())
|
|
Object.values(api).forEach(mock => mock.mockReset())
|
|
|
saveAs.mockReset()
|
|
saveAs.mockReset()
|
|
@@ -188,6 +206,139 @@ describe('team background management dialog behavior', () => {
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ test('visible false and closed invalidate pending work and reset both loading flags immediately', () => {
|
|
|
|
|
+ if (!expectComponent(source)) return
|
|
|
|
|
+ api.listTeamBackgroundFiles.mockReturnValue(new Promise(() => {}))
|
|
|
|
|
+ api.createTeamBackgroundSession.mockReturnValue(new Promise(() => {}))
|
|
|
|
|
+ const vm = createVm()
|
|
|
|
|
+
|
|
|
|
|
+ vm.refreshFiles()
|
|
|
|
|
+ vm.createUploadSession()
|
|
|
|
|
+ expect(vm.loading).toBe(true)
|
|
|
|
|
+ expect(vm.sessionLoading).toBe(true)
|
|
|
|
|
+
|
|
|
|
|
+ vm.visible = false
|
|
|
|
|
+ invokeWatcher(vm, 'visible', false, true)
|
|
|
|
|
+ expect(vm.loading).toBe(false)
|
|
|
|
|
+ expect(vm.sessionLoading).toBe(false)
|
|
|
|
|
+
|
|
|
|
|
+ vm.loading = true
|
|
|
|
|
+ vm.sessionLoading = true
|
|
|
|
|
+ vm.handleClosed()
|
|
|
|
|
+ expect(vm.loading).toBe(false)
|
|
|
|
|
+ expect(vm.sessionLoading).toBe(false)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ test('ignores late team-A list and session responses after close and reopen for team B', async () => {
|
|
|
|
|
+ if (!expectComponent(source)) return
|
|
|
|
|
+ const teamAList = deferred()
|
|
|
|
|
+ const teamBList = deferred()
|
|
|
|
|
+ const teamASession = deferred()
|
|
|
|
|
+ const teamBSession = deferred()
|
|
|
|
|
+ api.listTeamBackgroundFiles
|
|
|
|
|
+ .mockReturnValueOnce(teamAList.promise)
|
|
|
|
|
+ .mockReturnValueOnce(teamBList.promise)
|
|
|
|
|
+ api.createTeamBackgroundSession
|
|
|
|
|
+ .mockReturnValueOnce(teamASession.promise)
|
|
|
|
|
+ .mockReturnValueOnce(teamBSession.promise)
|
|
|
|
|
+ const vm = createVm({ teamId: 7 })
|
|
|
|
|
+
|
|
|
|
|
+ const listA = vm.handleOpen()
|
|
|
|
|
+ const sessionA = vm.createUploadSession()
|
|
|
|
|
+ vm.visible = false
|
|
|
|
|
+ invokeWatcher(vm, 'visible', false, true)
|
|
|
|
|
+ vm.teamId = 8
|
|
|
|
|
+ invokeWatcher(vm, 'teamId', 8, 7)
|
|
|
|
|
+ vm.visible = true
|
|
|
|
|
+ const listB = vm.handleOpen()
|
|
|
|
|
+ const sessionB = vm.createUploadSession()
|
|
|
|
|
+
|
|
|
|
|
+ const filesB = [{ id: 81 }, { id: 82 }]
|
|
|
|
|
+ const currentSession = { code: '888888', uploadUrl: 'https://upload.example/b', expiresAt: 'later' }
|
|
|
|
|
+ teamBList.resolve({ code: 0, data: filesB })
|
|
|
|
|
+ teamBSession.resolve({ code: 0, data: currentSession })
|
|
|
|
|
+ await Promise.all([listB, sessionB])
|
|
|
|
|
+
|
|
|
|
|
+ teamAList.resolve({ code: 0, data: [{ id: 71 }] })
|
|
|
|
|
+ teamASession.resolve({ code: 0, data: { code: '777777', uploadUrl: 'https://upload.example/a', expiresAt: 'old' } })
|
|
|
|
|
+ await Promise.all([listA, sessionA])
|
|
|
|
|
+
|
|
|
|
|
+ expect(api.listTeamBackgroundFiles.mock.calls).toEqual([[7], [8]])
|
|
|
|
|
+ expect(api.createTeamBackgroundSession.mock.calls).toEqual([[7], [8]])
|
|
|
|
|
+ expect(vm.fileList).toBe(filesB)
|
|
|
|
|
+ expect(vm.session).toBe(currentSession)
|
|
|
|
|
+ expect(vm.$emit.mock.calls.filter(call => call[0] === 'changed')).toEqual([['changed', 2]])
|
|
|
|
|
+ expect(vm.loading).toBe(false)
|
|
|
|
|
+ expect(vm.sessionLoading).toBe(false)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ test('only the newest same-team list and session requests may update state', async () => {
|
|
|
|
|
+ if (!expectComponent(source)) return
|
|
|
|
|
+ const firstList = deferred()
|
|
|
|
|
+ const secondList = deferred()
|
|
|
|
|
+ const firstSession = deferred()
|
|
|
|
|
+ const secondSession = deferred()
|
|
|
|
|
+ api.listTeamBackgroundFiles
|
|
|
|
|
+ .mockReturnValueOnce(firstList.promise)
|
|
|
|
|
+ .mockReturnValueOnce(secondList.promise)
|
|
|
|
|
+ api.createTeamBackgroundSession
|
|
|
|
|
+ .mockReturnValueOnce(firstSession.promise)
|
|
|
|
|
+ .mockReturnValueOnce(secondSession.promise)
|
|
|
|
|
+ const vm = createVm()
|
|
|
|
|
+
|
|
|
|
|
+ const listOne = vm.refreshFiles()
|
|
|
|
|
+ const listTwo = vm.refreshFiles()
|
|
|
|
|
+ const sessionOne = vm.createUploadSession()
|
|
|
|
|
+ const sessionTwo = vm.createUploadSession()
|
|
|
|
|
+ const newestFiles = [{ id: 2 }, { id: 3 }]
|
|
|
|
|
+ const newestSession = { code: '222222', uploadUrl: 'https://upload.example/new', expiresAt: 'new' }
|
|
|
|
|
+ secondList.resolve({ code: 0, data: newestFiles })
|
|
|
|
|
+ secondSession.resolve({ code: 0, data: newestSession })
|
|
|
|
|
+ await Promise.all([listTwo, sessionTwo])
|
|
|
|
|
+
|
|
|
|
|
+ firstList.resolve({ code: 0, data: [{ id: 1 }] })
|
|
|
|
|
+ firstSession.resolve({ code: 0, data: { code: '111111', uploadUrl: 'https://upload.example/old', expiresAt: 'old' } })
|
|
|
|
|
+ await Promise.all([listOne, sessionOne])
|
|
|
|
|
+
|
|
|
|
|
+ expect(vm.fileList).toBe(newestFiles)
|
|
|
|
|
+ expect(vm.session).toBe(newestSession)
|
|
|
|
|
+ expect(vm.$emit.mock.calls.filter(call => call[0] === 'changed')).toEqual([['changed', 2]])
|
|
|
|
|
+ expect(vm.loading).toBe(false)
|
|
|
|
|
+ expect(vm.sessionLoading).toBe(false)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ test('an old disable confirmation cannot act on a new team context', async () => {
|
|
|
|
|
+ if (!expectComponent(source)) return
|
|
|
|
|
+ const confirmation = deferred()
|
|
|
|
|
+ const vm = createVm({ teamId: 7, $confirm: jest.fn(() => confirmation.promise) })
|
|
|
|
|
+ const disabling = vm.disableFile({ id: 12, fileName: 'old.pdf' })
|
|
|
|
|
+
|
|
|
|
|
+ vm.visible = false
|
|
|
|
|
+ invokeWatcher(vm, 'visible', false, true)
|
|
|
|
|
+ vm.teamId = 8
|
|
|
|
|
+ invokeWatcher(vm, 'teamId', 8, 7)
|
|
|
|
|
+ confirmation.resolve()
|
|
|
|
|
+ await disabling
|
|
|
|
|
+
|
|
|
|
|
+ expect(api.disableTeamBackgroundFile).not.toHaveBeenCalled()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ test('reports a JSON blob download error and never saves it as a file', async () => {
|
|
|
|
|
+ if (!expectComponent(source)) return
|
|
|
|
|
+ const blob = {
|
|
|
|
|
+ type: 'application/json;charset=UTF-8',
|
|
|
|
|
+ text: jest.fn().mockResolvedValue(JSON.stringify({ code: 500, msg: '文件已停用' }))
|
|
|
|
|
+ }
|
|
|
|
|
+ api.downloadTeamBackgroundFile.mockResolvedValue(blob)
|
|
|
|
|
+ const vm = createVm()
|
|
|
|
|
+
|
|
|
|
|
+ await vm.downloadFile({ id: 12, fileName: 'brief.pdf' })
|
|
|
|
|
+
|
|
|
|
|
+ expect(blob.text).toHaveBeenCalled()
|
|
|
|
|
+ expect(saveAs).not.toHaveBeenCalled()
|
|
|
|
|
+ expect(vm.$message.error).toHaveBeenCalledWith('文件已停用')
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
test('shows complete file metadata and user-facing WECHAT/PC labels', () => {
|
|
test('shows complete file metadata and user-facing WECHAT/PC labels', () => {
|
|
|
if (!expectComponent(source)) return
|
|
if (!expectComponent(source)) return
|
|
|
|
|
|
|
@@ -216,6 +367,8 @@ describe('team table integration contract', () => {
|
|
|
expect(source).toContain("row.enterpriseWebsite || '无'")
|
|
expect(source).toContain("row.enterpriseWebsite || '无'")
|
|
|
expect(source).toContain('row.backgroundFileCount || 0')
|
|
expect(source).toContain('row.backgroundFileCount || 0')
|
|
|
expect(source).toContain('@click="openBackgroundFiles(row)"')
|
|
expect(source).toContain('@click="openBackgroundFiles(row)"')
|
|
|
|
|
+ expect(source).toMatch(/<el-button[^>]+type="text"[^>]+@click="openBackgroundFiles\(row\)"/s)
|
|
|
|
|
+ expect(source).not.toMatch(/<el-link[^>]+@click="openBackgroundFiles\(row\)"/s)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
test('mounts one dialog and updates only the matching row on changed(count)', () => {
|
|
test('mounts one dialog and updates only the matching row on changed(count)', () => {
|