Initial commit: iOS Build Server
- FastAPI backend with build queue, WebSocket logs, task management - Vue 3 frontend with build/config/history views - Xcode project build automation with IPA export - Fix: initialize build_dir before try block to ensure cleanup on early failure
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import App from '../App.vue'
|
||||
|
||||
global.fetch = vi.fn()
|
||||
|
||||
// Mock localStorage
|
||||
const localStorageMock = (() => {
|
||||
let store = {}
|
||||
return {
|
||||
getItem: vi.fn(k => store[k] || null),
|
||||
setItem: vi.fn((k, v) => { store[k] = v }),
|
||||
removeItem: vi.fn(k => { delete store[k] }),
|
||||
clear: vi.fn(() => { store = {} }),
|
||||
}
|
||||
})()
|
||||
Object.defineProperty(global, 'localStorage', { value: localStorageMock })
|
||||
|
||||
function createMockRouter() {
|
||||
return createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [{ path: '/', component: { template: '<div />' } }],
|
||||
})
|
||||
}
|
||||
|
||||
describe('App.vue', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
localStorageMock.getItem.mockImplementation(k => null)
|
||||
})
|
||||
|
||||
it('显示标题', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(App, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
expect(wrapper.text()).toContain('iOS 自动打包服务')
|
||||
})
|
||||
|
||||
it('未登录时显示登录按钮', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(App, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
expect(wrapper.find('.btn-login').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('已登录时显示退出按钮', async () => {
|
||||
localStorageMock.getItem.mockReturnValue('true')
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(App, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
expect(wrapper.find('.btn-logout').exists()).toBe(true)
|
||||
expect(wrapper.find('.btn-login').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('点击登录按钮弹出登录框', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(App, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await wrapper.find('.btn-login').trigger('click')
|
||||
expect(wrapper.find('.login-modal').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('登录成功后更新状态', async () => {
|
||||
fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ token: 'admin-token', is_admin: true }),
|
||||
})
|
||||
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(App, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
|
||||
await wrapper.find('.btn-login').trigger('click')
|
||||
await wrapper.find('.login-modal input[type="text"]').setValue('admin')
|
||||
await wrapper.find('.login-modal input[type="password"]').setValue('admin123')
|
||||
await wrapper.find('.login-btn').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.find('.btn-logout').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('登录失败显示错误', async () => {
|
||||
fetch.mockResolvedValueOnce({ ok: false })
|
||||
window.alert = vi.fn()
|
||||
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(App, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
|
||||
await wrapper.find('.btn-login').trigger('click')
|
||||
await wrapper.find('.login-modal input[type="text"]').setValue('admin')
|
||||
await wrapper.find('.login-modal input[type="password"]').setValue('wrong')
|
||||
await wrapper.find('.login-btn').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(window.alert).toHaveBeenCalledWith('用户名或密码错误')
|
||||
})
|
||||
|
||||
it('点击退出清除登录状态', async () => {
|
||||
localStorageMock.getItem.mockReturnValue('true')
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(App, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
|
||||
await wrapper.find('.btn-logout').trigger('click')
|
||||
expect(wrapper.find('.btn-login').exists()).toBe(true)
|
||||
expect(localStorageMock.removeItem).toHaveBeenCalledWith('isAdmin')
|
||||
})
|
||||
|
||||
it('已登录时显示管理导航', async () => {
|
||||
localStorageMock.getItem.mockReturnValue('true')
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(App, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
const links = wrapper.findAll('.nav a')
|
||||
const texts = links.map(l => l.text())
|
||||
expect(texts).toContain('管理')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,142 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import BuildView from '../views/BuildView.vue'
|
||||
|
||||
global.fetch = vi.fn()
|
||||
|
||||
// Mock WebSocket as a class
|
||||
class MockWebSocket {
|
||||
constructor(url) { this.url = url; this.onmessage = null; }
|
||||
close() {}
|
||||
}
|
||||
global.WebSocket = MockWebSocket
|
||||
|
||||
describe('BuildView.vue', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
fetch.mockImplementation((url) => {
|
||||
const responses = {
|
||||
'/api/apps': { '1': { name: '测试App', server: '测试环境' } },
|
||||
'/api/schemes': { '1': { name: 'testScheme' } },
|
||||
'/api/branches': ['main', 'dev'],
|
||||
'/api/tasks': [],
|
||||
}
|
||||
return Promise.resolve({
|
||||
json: () => Promise.resolve(responses[url] || {}),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('显示打包配置面板', async () => {
|
||||
const wrapper = mount(BuildView)
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('打包配置')
|
||||
expect(wrapper.text()).toContain('选择 App')
|
||||
expect(wrapper.text()).toContain('打包类型')
|
||||
expect(wrapper.text()).toContain('代码分支')
|
||||
expect(wrapper.text()).toContain('代码混淆')
|
||||
})
|
||||
|
||||
it('加载 apps 和 schemes', async () => {
|
||||
const wrapper = mount(BuildView)
|
||||
await flushPromises()
|
||||
|
||||
const appOptions = wrapper.findAll('select')[0].findAll('option')
|
||||
expect(appOptions.length).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
it('加载分支列表并显示下拉', async () => {
|
||||
const wrapper = mount(BuildView)
|
||||
await flushPromises()
|
||||
|
||||
const branchSelect = wrapper.findAll('select').find(s => {
|
||||
const options = s.findAll('option')
|
||||
return options.some(o => o.text() === 'main')
|
||||
})
|
||||
expect(branchSelect).toBeTruthy()
|
||||
})
|
||||
|
||||
it('默认值正确', async () => {
|
||||
const wrapper = mount(BuildView)
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.vm.form.build_type).toBe('Ad_Hoc')
|
||||
expect(wrapper.vm.form.obfuscation).toBe(false)
|
||||
expect(wrapper.vm.form.branch).toBe('main')
|
||||
})
|
||||
|
||||
it('提交打包任务', async () => {
|
||||
fetch.mockImplementation((url, opts) => {
|
||||
if (url === '/api/tasks' && opts?.method === 'POST') {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ id: 'test-id', status: 'pending' }),
|
||||
})
|
||||
}
|
||||
const responses = {
|
||||
'/api/apps': { '1': { name: '测试App', server: '测试环境' } },
|
||||
'/api/schemes': { '1': { name: 'testScheme' } },
|
||||
'/api/branches': ['main', 'dev'],
|
||||
'/api/tasks': [],
|
||||
}
|
||||
return Promise.resolve({
|
||||
json: () => Promise.resolve(responses[url] || {}),
|
||||
})
|
||||
})
|
||||
|
||||
const wrapper = mount(BuildView)
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.setData({ form: { ...wrapper.vm.form, app_id: '1' } })
|
||||
await wrapper.find('.btn-primary').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith('/api/tasks', expect.objectContaining({
|
||||
method: 'POST',
|
||||
}))
|
||||
})
|
||||
|
||||
it('未选择 App 时提示', async () => {
|
||||
window.alert = vi.fn()
|
||||
const wrapper = mount(BuildView)
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.find('.btn-primary').trigger('click')
|
||||
expect(window.alert).toHaveBeenCalledWith('请选择 App')
|
||||
})
|
||||
|
||||
it('显示任务队列', async () => {
|
||||
fetch.mockImplementation((url) => {
|
||||
const responses = {
|
||||
'/api/apps': { '1': { name: '测试App', server: '测试环境' } },
|
||||
'/api/schemes': { '1': { name: 'testScheme' } },
|
||||
'/api/branches': ['main', 'dev'],
|
||||
'/api/tasks': [
|
||||
{ id: '1', app_name: 'App1', build_type: 'Ad_Hoc', scheme_name: 'sch', status: 'pending', created_at: '2024-01-01T00:00:00' },
|
||||
{ id: '2', app_name: 'App2', build_type: 'App_Store', scheme_name: 'sch', status: 'completed', created_at: '2024-01-01T00:00:00' },
|
||||
],
|
||||
}
|
||||
return Promise.resolve({
|
||||
json: () => Promise.resolve(responses[url] || {}),
|
||||
})
|
||||
})
|
||||
|
||||
const wrapper = mount(BuildView)
|
||||
await flushPromises()
|
||||
|
||||
const taskItems = wrapper.findAll('.task-item')
|
||||
expect(taskItems.length).toBe(1) // 只显示未完成的任务(pending),completed 已过滤
|
||||
})
|
||||
|
||||
it('状态文本正确', async () => {
|
||||
const wrapper = mount(BuildView)
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.vm.statusText('pending')).toBe('等待中')
|
||||
expect(wrapper.vm.statusText('running')).toBe('打包中')
|
||||
expect(wrapper.vm.statusText('completed')).toBe('已完成')
|
||||
expect(wrapper.vm.statusText('failed')).toBe('失败')
|
||||
expect(wrapper.vm.statusText('cancelled')).toBe('已取消')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,164 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import ConfigView from '../views/ConfigView.vue'
|
||||
|
||||
global.fetch = vi.fn()
|
||||
|
||||
// Mock localStorage
|
||||
const localStorageMock = (() => {
|
||||
let store = {}
|
||||
return {
|
||||
getItem: vi.fn(k => store[k] || null),
|
||||
setItem: vi.fn((k, v) => { store[k] = v }),
|
||||
removeItem: vi.fn(k => { delete store[k] }),
|
||||
clear: vi.fn(() => { store = {} }),
|
||||
}
|
||||
})()
|
||||
Object.defineProperty(global, 'localStorage', { value: localStorageMock })
|
||||
|
||||
function mockFetch(url) {
|
||||
const responses = {
|
||||
'/api/config/apps': { '1': { name: '测试App', server: '测试环境', AppGuid: 'guid' } },
|
||||
'/api/config/schemes': { '1': { name: 'testScheme', ossFloder: 'test' } },
|
||||
'/api/config/servers': { '测试环境': { api: 'https://test.com', assDom: '', universalLink: '' } },
|
||||
'/api/config/branches': ['main', 'dev'],
|
||||
'/api/config/build': { max_concurrent_builds: 2, build_dir_retention_hours: 24, build_base_dir: '/tmp' },
|
||||
'/api/config': { apps: {}, schemes: {}, branches: ['main'] },
|
||||
}
|
||||
return Promise.resolve({
|
||||
json: () => Promise.resolve(responses[url] || {}),
|
||||
})
|
||||
}
|
||||
|
||||
describe('ConfigView.vue', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
localStorageMock.clear()
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
fetch.mockImplementation(mockFetch)
|
||||
})
|
||||
|
||||
it('未登录时显示权限提示', async () => {
|
||||
const wrapper = mount(ConfigView)
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('需要管理员权限')
|
||||
})
|
||||
|
||||
it('已登录时显示管理页面', async () => {
|
||||
localStorageMock.getItem.mockReturnValue('true')
|
||||
const wrapper = mount(ConfigView)
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('配置管理')
|
||||
})
|
||||
|
||||
it('显示侧边栏菜单', async () => {
|
||||
localStorageMock.getItem.mockReturnValue('true')
|
||||
const wrapper = mount(ConfigView)
|
||||
await flushPromises()
|
||||
|
||||
const menuItems = wrapper.findAll('.sidebar-menu li')
|
||||
const texts = menuItems.map(li => li.text())
|
||||
expect(texts).toContain('服务器环境')
|
||||
expect(texts).toContain('Apps 配置')
|
||||
expect(texts).toContain('Schemes 配置')
|
||||
expect(texts).toContain('分支管理')
|
||||
expect(texts).toContain('打包设置')
|
||||
})
|
||||
|
||||
it('默认显示服务器环境 tab', async () => {
|
||||
localStorageMock.getItem.mockReturnValue('true')
|
||||
const wrapper = mount(ConfigView)
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('服务器环境配置')
|
||||
})
|
||||
|
||||
it('切换到分支管理 tab', async () => {
|
||||
localStorageMock.getItem.mockReturnValue('true')
|
||||
const wrapper = mount(ConfigView)
|
||||
await flushPromises()
|
||||
|
||||
const branchesMenu = wrapper.findAll('.sidebar-menu li').find(li => li.text() === '分支管理')
|
||||
await branchesMenu.trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.text()).toContain('main')
|
||||
expect(wrapper.text()).toContain('dev')
|
||||
})
|
||||
|
||||
it('添加分支', async () => {
|
||||
fetch.mockImplementation((url, opts) => {
|
||||
if (url === '/api/config/branches' && opts?.method === 'POST') {
|
||||
return Promise.resolve({ ok: true, json: () => Promise.resolve({ message: 'ok' }) })
|
||||
}
|
||||
return mockFetch(url)
|
||||
})
|
||||
|
||||
localStorageMock.getItem.mockReturnValue('true')
|
||||
const wrapper = mount(ConfigView)
|
||||
await flushPromises()
|
||||
|
||||
const branchesMenu = wrapper.findAll('.sidebar-menu li').find(li => li.text() === '分支管理')
|
||||
await branchesMenu.trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const input = wrapper.find('input[placeholder="输入分支名称"]')
|
||||
await input.setValue('new-branch')
|
||||
|
||||
const addBtn = wrapper.findAll('.btn-primary').find(b => b.text() === '添加分支')
|
||||
await addBtn.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith('/api/config/branches', expect.objectContaining({
|
||||
method: 'POST',
|
||||
}))
|
||||
})
|
||||
|
||||
it('删除分支', async () => {
|
||||
window.confirm = vi.fn(() => true)
|
||||
fetch.mockImplementation((url, opts) => {
|
||||
if (url.includes('/api/config/branches/') && opts?.method === 'DELETE') {
|
||||
return Promise.resolve({ ok: true, json: () => Promise.resolve({ message: 'ok' }) })
|
||||
}
|
||||
return mockFetch(url)
|
||||
})
|
||||
|
||||
localStorageMock.getItem.mockReturnValue('true')
|
||||
const wrapper = mount(ConfigView)
|
||||
await flushPromises()
|
||||
|
||||
const branchesMenu = wrapper.findAll('.sidebar-menu li').find(li => li.text() === '分支管理')
|
||||
await branchesMenu.trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const deleteBtns = wrapper.findAll('.action-btn.delete')
|
||||
if (deleteBtns.length > 0) {
|
||||
await deleteBtns[0].trigger('click')
|
||||
await flushPromises()
|
||||
expect(window.confirm).toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
|
||||
it('保存打包设置', async () => {
|
||||
fetch.mockImplementation((url, opts) => {
|
||||
if (url === '/api/config/build' && opts?.method === 'PUT') {
|
||||
return Promise.resolve({ ok: true, json: () => Promise.resolve({ message: 'ok' }) })
|
||||
}
|
||||
return mockFetch(url)
|
||||
})
|
||||
window.alert = vi.fn()
|
||||
|
||||
localStorageMock.getItem.mockReturnValue('true')
|
||||
const wrapper = mount(ConfigView)
|
||||
await flushPromises()
|
||||
|
||||
const buildMenu = wrapper.findAll('.sidebar-menu li').find(li => li.text() === '打包设置')
|
||||
await buildMenu.trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const saveBtn = wrapper.findAll('.btn-primary').find(b => b.text() === '保存设置')
|
||||
await saveBtn.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(window.alert).toHaveBeenCalledWith('设置已保存')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,179 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import HistoryView from '../views/HistoryView.vue'
|
||||
|
||||
global.fetch = vi.fn()
|
||||
window.open = vi.fn()
|
||||
|
||||
const mockTasks = [
|
||||
{ id: '1', app_name: 'App1', build_type: 'Ad_Hoc', scheme_name: 'sch1', status: 'completed', created_at: '2024-01-01T10:00:00', dsym_path: '/path/dsym', oss_url: 'https://oss.com/app1.ipa', qr_code_path: '/path/qr.png' },
|
||||
{ id: '2', app_name: 'App2', build_type: 'App_Store', scheme_name: 'sch2', status: 'failed', created_at: '2024-01-02T10:00:00', error_message: '构建失败' },
|
||||
{ id: '3', app_name: 'App3', build_type: 'Ad_Hoc', scheme_name: 'sch1', status: 'pending', created_at: '2024-01-03T10:00:00' },
|
||||
]
|
||||
|
||||
function createMockRouter() {
|
||||
return createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: { template: '<div />' } },
|
||||
{ path: '/build', component: { template: '<div />' } },
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
describe('HistoryView.vue', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
fetch.mockResolvedValue({
|
||||
json: () => Promise.resolve(mockTasks),
|
||||
})
|
||||
})
|
||||
|
||||
it('显示打包历史标题', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('打包历史')
|
||||
})
|
||||
|
||||
it('加载并显示任务列表', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
const rows = wrapper.findAll('tbody tr')
|
||||
expect(rows.length).toBe(2) // App2(failed) 默认隐藏
|
||||
})
|
||||
|
||||
it('显示任务信息', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('App1')
|
||||
// App2 是 failed 状态,默认隐藏
|
||||
expect(wrapper.text()).not.toContain('App2')
|
||||
expect(wrapper.text()).toContain('Ad_Hoc')
|
||||
})
|
||||
|
||||
it('默认隐藏失败和已取消的任务', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
// 只显示 App1(completed)和 App3(pending),App2(failed)被隐藏
|
||||
const rows = wrapper.findAll('tbody tr')
|
||||
expect(rows.length).toBe(2)
|
||||
expect(wrapper.text()).toContain('App1')
|
||||
expect(wrapper.text()).toContain('App3')
|
||||
expect(wrapper.text()).toContain('等待中')
|
||||
})
|
||||
|
||||
it('按打包类型过滤', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
const selects = wrapper.findAll('.filter-select')
|
||||
await selects[0].setValue('Ad_Hoc')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const rows = wrapper.findAll('tbody tr')
|
||||
expect(rows.length).toBe(2) // App1 和 App3
|
||||
})
|
||||
|
||||
it('按状态过滤', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
const selects = wrapper.findAll('.filter-select')
|
||||
await selects[1].setValue('failed')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const rows = wrapper.findAll('tbody tr')
|
||||
expect(rows.length).toBe(1) // App2
|
||||
})
|
||||
|
||||
it('已完成任务显示下载按钮', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
// 第一行(App1, completed)应该有 dSYM 和下载按钮
|
||||
const firstRow = wrapper.findAll('tbody tr')[0]
|
||||
const buttons = firstRow.findAll('.action-btn')
|
||||
const buttonTexts = buttons.map(b => b.text())
|
||||
expect(buttonTexts).toContain('dSYM')
|
||||
expect(buttonTexts).toContain('下载')
|
||||
expect(buttonTexts).toContain('二维码')
|
||||
})
|
||||
|
||||
it('空列表显示提示', async () => {
|
||||
fetch.mockResolvedValue({ json: () => Promise.resolve([]) })
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('暂无打包记录')
|
||||
})
|
||||
|
||||
it('状态文本正确', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.vm.statusText('pending')).toBe('等待中')
|
||||
expect(wrapper.vm.statusText('running')).toBe('打包中')
|
||||
expect(wrapper.vm.statusText('completed')).toBe('已完成')
|
||||
expect(wrapper.vm.statusText('failed')).toBe('失败')
|
||||
})
|
||||
|
||||
it('点击查看日志跳转', async () => {
|
||||
const router = createMockRouter()
|
||||
router.push = vi.fn()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
const logBtn = wrapper.findAll('.action-btn').find(b => b.text() === '日志')
|
||||
await logBtn.trigger('click')
|
||||
|
||||
expect(router.push).toHaveBeenCalledWith({ path: '/build', query: { taskId: '1' } })
|
||||
})
|
||||
|
||||
it('点击二维码弹出弹窗', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
const qrBtn = wrapper.findAll('.action-btn').find(b => b.text() === '二维码')
|
||||
await qrBtn.trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.find('.qr-modal').exists()).toBe(true)
|
||||
expect(wrapper.text()).toContain('下载二维码')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user