修复采购审核竞态并整理管理页面展示

This commit is contained in:
2026-10-30 15:42:37 +08:00
parent 8cc8b4fb94
commit 4abd6ffd5d
23 changed files with 791 additions and 75 deletions
@@ -0,0 +1,101 @@
import { nextTick } from 'vue'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { printLoadingList } from './printLoadingList'
afterEach(() => { vi.restoreAllMocks(); document.body.replaceChildren() })
function manifest() {
document.body.innerHTML = `<aside>平台导航</aside><nav>后台页签</nav><section id="manifest" aria-label="装柜清单 · LIST-TEST">
<header><button>返回货柜概览</button><button>打印装柜清单</button></header>
<section class="detail-summary" data-loading-list-print-summary><div><span>柜次号</span><strong>CB-TEST</strong></div><div><span>集装箱号</span><strong>CONTAINER-TEST</strong></div></section>
<div data-loading-list-print-items><button>字段设置</button><div style="width:3000px;overflow:hidden"><table>
<thead><tr><th>入仓码</th><th>SKU</th><th>备注</th></tr></thead>
<tbody><tr><td style="width:200px;white-space:nowrap">IN-FIRST</td><td>SKU &amp; &lt;盒&gt;</td><td>第一行备注</td></tr>
<tr><td>IN-LAST</td><td>SKU-LAST</td><td>最后一列完整备注</td></tr></tbody>
</table></div></div><section>操作历史:创建计划、调整货物</section></section>`
return document.getElementById('manifest')!
}
function interceptPrint(onPrint: (frame: HTMLIFrameElement) => void) {
let frame: HTMLIFrameElement | undefined
const append = document.body.append.bind(document.body)
vi.spyOn(document.body, 'append').mockImplementation((...nodes) => {
append(...nodes)
for (const node of nodes) {
if (!(node instanceof HTMLIFrameElement)) continue
frame = node
vi.spyOn(node.contentWindow!, 'focus').mockImplementation(() => {})
vi.spyOn(node.contentWindow!, 'print').mockImplementation(() => onPrint(node))
}
})
return async () => {
await nextTick()
if (!frame) throw new Error('Print frame was not created')
frame.dispatchEvent(new Event('load'))
return frame
}
}
describe('loading list printing', () => {
it('prints the manifest summary and every cargo row and column in an isolated document', async () => {
const root = manifest(), original = root.outerHTML
const hostPrint = vi.spyOn(window, 'print').mockImplementation(() => {})
let snapshot: Document | undefined
const load = interceptPrint(frame => {
snapshot = frame.contentDocument!
expect(snapshot.title).toBe('装柜清单 · LIST-TEST')
expect(snapshot.querySelector('h1')?.textContent).toBe('装柜清单 · LIST-TEST')
expect(snapshot.body.textContent).toContain('CB-TEST')
expect(snapshot.body.textContent).toContain('CONTAINER-TEST')
expect(snapshot.querySelectorAll('tbody tr')).toHaveLength(2)
expect(snapshot.querySelectorAll('thead th')).toHaveLength(3)
expect(snapshot.body.textContent).toContain('最后一列完整备注')
expect(snapshot.body.textContent).toContain('SKU & <盒>')
expect(snapshot.querySelector('td')?.getAttribute('style')).toBeNull()
expect(snapshot.body.textContent).not.toMatch(/平台导航|后台页签|返回货柜概览|字段设置|操作历史/)
expect(snapshot.querySelectorAll('button')).toHaveLength(0)
frame.contentWindow!.dispatchEvent(new Event('afterprint'))
})
const pending = printLoadingList(root)
const frame = await load()
await expect(pending).resolves.toBe(true)
expect(snapshot).toBeDefined()
expect(hostPrint).not.toHaveBeenCalled()
expect(frame.isConnected).toBe(false)
expect(root.outerHTML).toBe(original)
})
it('keeps the print document until the dialog closes and prevents duplicate print jobs', async () => {
const root = manifest(), print = vi.fn()
const load = interceptPrint(print)
const pending = printLoadingList(root)
const frame = await load()
expect(frame.isConnected).toBe(true)
expect(print).toHaveBeenCalledOnce()
await expect(printLoadingList(root)).resolves.toBe(false)
expect(document.querySelectorAll('iframe')).toHaveLength(1)
frame.contentWindow!.dispatchEvent(new Event('afterprint'))
await expect(pending).resolves.toBe(true)
expect(frame.isConnected).toBe(false)
expect(document.querySelector('aside')?.textContent).toBe('平台导航')
})
it('never falls back to printing the entire page when the manifest is missing or incomplete', async () => {
const hostPrint = vi.spyOn(window, 'print').mockImplementation(() => {})
await expect(printLoadingList(null)).rejects.toThrow('清单尚未加载完成')
await expect(printLoadingList(document.createElement('section'))).rejects.toThrow('清单尚未加载完成')
expect(hostPrint).not.toHaveBeenCalled()
expect(document.querySelector('iframe')).toBeNull()
})
it('removes the isolated document after a print failure', async () => {
const root = manifest(), original = root.outerHTML
const load = interceptPrint(() => { throw new Error('printer unavailable') })
const pending = printLoadingList(root)
const rejected = expect(pending).rejects.toThrow('printer unavailable')
const frame = await load()
await rejected
expect(frame.isConnected).toBe(false)
expect(root.outerHTML).toBe(original)
})
})