446 lines
22 KiB
TypeScript
446 lines
22 KiB
TypeScript
import { computed, ref } from 'vue'
|
|
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { NSelect } from 'naive-ui'
|
|
import { readFileSync } from 'node:fs'
|
|
import BreakdownPage from '@/features/breakdown/BreakdownPage.vue'
|
|
import { projectContextKey, type useProjectData } from '@/features/projects/context'
|
|
import type { ProjectDetail } from '@/features/projects/types'
|
|
import type { Checkpoint } from '@/features/workflows/types'
|
|
import type { BreakdownModule, EpisodePlan } from '@/features/breakdown/types'
|
|
import { drawerPanel, selectMenu } from '@/testing/naive'
|
|
import { readAllStyles } from '@/testing/styles'
|
|
|
|
let wrapper: VueWrapper | undefined
|
|
afterEach(() => {
|
|
wrapper?.unmount()
|
|
wrapper = undefined
|
|
document.body.innerHTML = ''
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
/** 长主体、长分镜与长任务列表,用真实 Naive 滚动组件验证内容边界。 */
|
|
function checkpoint(): Checkpoint {
|
|
const plan: EpisodePlan = {
|
|
episodeNo: 1,
|
|
episodeTitle: '长剧集',
|
|
storyGoal: '',
|
|
centralConflict: '',
|
|
emotionalArc: '',
|
|
pacing: '',
|
|
endingHook: '',
|
|
beats: Array.from({ length: 25 }, (_, i) => ({
|
|
beatNo: i + 1,
|
|
title: `节拍 ${i + 1}`,
|
|
purpose: 'action',
|
|
description: '节拍内容'.repeat(100),
|
|
visualFocus: '',
|
|
narrativeGoal: '',
|
|
emotionalTone: '',
|
|
estimatedDurationSeconds: 5,
|
|
subjectRefs: [],
|
|
isKeyBeat: false
|
|
}))
|
|
}
|
|
return {
|
|
checkpointId: 'long-breakdown',
|
|
workflowName: 'breakdown',
|
|
createdAt: '2026-08-28T00:00:00Z',
|
|
state: {
|
|
workflowExecution: { executionId: 'run', status: 'completed', startedAt: '2026-08-28T00:00:00Z' },
|
|
breakdownResult: {
|
|
subjectCandidates: (['character', 'scene', 'prop'] as BreakdownModule[]).flatMap(module =>
|
|
Array.from({ length: 30 }, (_, i) => ({
|
|
profileId: `${module}-${i}`,
|
|
name: `${module} 主体 ${i + 1}`,
|
|
ref: `@${module}${i}`,
|
|
description: '很长的主体描述。'.repeat(100),
|
|
module,
|
|
appearance_prompt: '外观描述'
|
|
}))
|
|
),
|
|
subjectForms: [],
|
|
storyboardPlans: [plan],
|
|
storyboardEpisodeShots: [
|
|
{
|
|
episodeNo: 1,
|
|
episodePlan: plan,
|
|
beatShots: plan.beats.map(beat => ({
|
|
beatNo: beat.beatNo,
|
|
shots: [
|
|
{
|
|
shotNo: 1,
|
|
title: `镜头 ${beat.beatNo}`,
|
|
description: '镜头内容',
|
|
visualFocus: '',
|
|
subjectRefs: [],
|
|
durationSeconds: 5
|
|
}
|
|
]
|
|
}))
|
|
}
|
|
]
|
|
},
|
|
tasks: Array.from({ length: 50 }, (_, i) => ({
|
|
taskId: `task-${i}`,
|
|
module: 'character',
|
|
group: {
|
|
groupId: `group-${i}`,
|
|
groupNo: i + 1,
|
|
startEpisodeNo: i + 1,
|
|
endEpisodeNo: i + 1,
|
|
episodes: []
|
|
},
|
|
status: 'failed',
|
|
attempt: 1,
|
|
errorMessage: `任务错误 ${i + 1}`
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 提供响应式 checkpoint,模拟轮询更新但不连接后端或启动模型任务。 */
|
|
function mountPage(records = [checkpoint()], episodes = 1): ReturnType<typeof useProjectData> {
|
|
const project: ProjectDetail = {
|
|
id: 'breakdown-scroll-test',
|
|
title: '滚动回归',
|
|
topic: '',
|
|
style: '',
|
|
status: 'completed',
|
|
createdAt: '',
|
|
updatedAt: '',
|
|
characters: [],
|
|
world: null,
|
|
|
|
episodes: episodes ? [{ episode: 1, title: '第一集', content: '正文' }] : []
|
|
}
|
|
const data = ref({ project, checkpoints: records })
|
|
const provided: ReturnType<typeof useProjectData> = {
|
|
data,
|
|
project: computed(() => data.value.project),
|
|
checkpoints: computed(() => data.value.checkpoints),
|
|
loading: ref(false),
|
|
error: ref(''),
|
|
updatedAt: ref(''),
|
|
refresh: vi.fn<() => Promise<void>>().mockResolvedValue()
|
|
}
|
|
wrapper = mount(BreakdownPage, {
|
|
attachTo: document.body,
|
|
global: { provide: { [projectContextKey as symbol]: provided }, stubs: { RouterLink: true } }
|
|
})
|
|
return provided
|
|
}
|
|
|
|
/** 切换真实标签,不直接修改组件内部状态。 */
|
|
async function selectTab(label: string) {
|
|
await wrapper!
|
|
.findAll('.result-toolbar .n-tabs-tab')
|
|
.find(tab => tab.text().startsWith(label))!
|
|
.trigger('click')
|
|
await flushPromises()
|
|
}
|
|
|
|
describe('拆解页内容滚动', () => {
|
|
it('拆解设置保留输入标签、单行单位与模块说明,改版后仍校验模块和剧本状态', async () => {
|
|
const fetcher = vi.fn<typeof fetch>()
|
|
vi.stubGlobal('fetch', fetcher)
|
|
const provided = mountPage()
|
|
await wrapper!.get('[data-workspace-tools]').trigger('click')
|
|
await flushPromises()
|
|
const config = drawerPanel().get('.breakdown-config')
|
|
expect(config.get('label[for="group-size"]').text()).toContain('每组集数')
|
|
expect(config.get<HTMLInputElement>('#group-size').element.value).toBe('3')
|
|
expect(config.get('.breakdown-group-unit').text()).toBe('集 / 组')
|
|
expect(config.get('.breakdown-modules-field .n-form-item-label').text()).toContain('抽取模块')
|
|
const options = config.findAll('.breakdown-module-option')
|
|
expect(options).toHaveLength(3)
|
|
const preview = config.get<HTMLButtonElement>('.breakdown-preview-button')
|
|
expect(preview.element.disabled).toBe(false)
|
|
for (const option of options) {
|
|
const checkbox = option.get('[role="checkbox"]')
|
|
const description = option.get('.breakdown-module-description')
|
|
expect(checkbox.attributes('aria-describedby')).toBe(description.attributes('id'))
|
|
expect(checkbox.find('.breakdown-module-description').exists()).toBe(false)
|
|
expect(description.text()).not.toBe('')
|
|
await checkbox.trigger('click')
|
|
}
|
|
await preview.trigger('click')
|
|
await flushPromises()
|
|
expect(config.get('.n-form-item-feedback--error').text()).toContain('至少选择一个抽取模块')
|
|
expect(fetcher).not.toHaveBeenCalled()
|
|
await options[1]!.get('[role="checkbox"]').trigger('click')
|
|
expect(preview.element.disabled).toBe(false)
|
|
provided.data.value!.project.status = 'generating'
|
|
await flushPromises()
|
|
expect(preview.element.disabled).toBe(true)
|
|
expect(fetcher).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('拆解配置按抽屉宽度换行,单位不收缩,三类控件对齐且说明位于控件下方', () => {
|
|
// DOM 环境不计算坐标,检查统一标题偏移、34px 控件行和容器断点契约。
|
|
const css = readAllStyles()
|
|
expect(css).toMatch(/\.breakdown-settings-panel\s*\{\s*container:\s*breakdown-settings \/ inline-size;/)
|
|
expect(css).toMatch(/\.breakdown-config\s*\{[^}]*--breakdown-label-offset:\s*29px;/)
|
|
expect(css).toMatch(/\.breakdown-group-unit\s*\{[^}]*flex-shrink:\s*0;[^}]*white-space:\s*nowrap;/)
|
|
expect(css).toMatch(
|
|
/\.n-checkbox\.control-row-checkbox\s*\{[^}]*align-items:\s*center;[^}]*min-height:\s*34px;/
|
|
)
|
|
expect(css).toMatch(
|
|
/\.breakdown-preview-button\.n-button\s*\{[^}]*align-self:\s*start;[^}]*margin-top:\s*var\(--breakdown-label-offset\);/
|
|
)
|
|
expect(css).toContain('@container breakdown-settings (max-width: 780px)')
|
|
expect(css).toContain('@container breakdown-settings (max-width: 560px)')
|
|
expect(css).toContain('repeat(auto-fit, minmax(min(100%, 160px), 1fr))')
|
|
expect(readFileSync('src/styles/styles.css', 'utf8')).not.toContain('.breakdown-config')
|
|
})
|
|
|
|
it.each(['人物', '场景', '道具'])('%s 使用共用固定斑马纹样式,搜索后保持列表条目结构', async label => {
|
|
mountPage()
|
|
await selectTab(label)
|
|
expect(wrapper!.find('.history-panel').exists()).toBe(false)
|
|
await selectMenu('拆解更多操作', '执行记录')
|
|
const list = wrapper!.get('.subject-record-list')
|
|
expect(list.classes()).toContain('record-list')
|
|
expect(list.findAll(':scope > article')).toHaveLength(30)
|
|
const toolbar = wrapper!.get('.subject-list-toolbar')
|
|
expect(toolbar.element.firstElementChild).toBe(toolbar.get('.subject-list-search').element)
|
|
expect(toolbar.element.lastElementChild).toBe(toolbar.get('.subject-list-count').element)
|
|
expect(toolbar.get('.n-input__prefix svg').attributes('aria-hidden')).toBe('true')
|
|
expect(toolbar.find(':scope > svg').exists()).toBe(false)
|
|
expect(toolbar.get('[role="status"]').text()).toBe('共 30 个主体')
|
|
const input = toolbar.get('input[aria-label="搜索主体"]')
|
|
await input.setValue('主体 30')
|
|
expect(list.findAll(':scope > article')).toHaveLength(1)
|
|
expect(list.get(':scope > article h3').text()).toContain('主体 30')
|
|
expect(toolbar.get('[role="status"]').text()).toBe('匹配 1 / 30 个主体')
|
|
await input.setValue('不存在的主体')
|
|
expect(toolbar.get('[role="status"]').text()).toBe('匹配 0 / 30 个主体')
|
|
expect(wrapper!.text()).toContain('没有匹配的主体')
|
|
await input.setValue('')
|
|
expect(toolbar.get('[role="status"]').text()).toBe('共 30 个主体')
|
|
expect(wrapper!.findAll('.subject-record-list > article')).toHaveLength(30)
|
|
})
|
|
|
|
it('主体搜索与统计靠左居中,窄屏搜索拉满同一行,输入框不再使用外置图标容器', () => {
|
|
// DOM 环境不计算布局,保护输入框宽度、主题统计色与窄屏单行搜索的样式契约。
|
|
const css = readAllStyles()
|
|
expect(css).toMatch(
|
|
/\.subject-list-toolbar\s*\{[^}]*display:\s*flex;[^}]*flex-wrap:\s*wrap;[^}]*align-items:\s*center;[^}]*justify-content:\s*flex-start;/
|
|
)
|
|
expect(css).toMatch(
|
|
/\.subject-list-search\.n-input\s*\{[^}]*flex:\s*0 1 260px;[^}]*min-width:\s*0;[^}]*max-width:\s*100%;/
|
|
)
|
|
expect(css).toMatch(/\.subject-list-count\s*\{[^}]*color:\s*var\(--app-muted\);/)
|
|
expect(css).toMatch(/@media \(max-width: 800px\)[\s\S]*?\.subject-list-search\.n-input\s*\{[^}]*flex:\s*1/)
|
|
expect(css + readFileSync('src/styles/styles.css', 'utf8')).not.toContain('.search-field')
|
|
})
|
|
|
|
it('窄屏收口拆解顶栏并隐藏重复标题,设置按钮保留图标', () => {
|
|
const css = readAllStyles()
|
|
expect(css).toMatch(/@media \(max-width: 800px\)[\s\S]*?\.workspace-toolbar h2\s*\{[^}]*display:\s*none/)
|
|
expect(css).toMatch(/@media \(max-width: 800px\)[\s\S]*?\.workspace-tools-label\s*\{[^}]*display:\s*none/)
|
|
expect(css).toMatch(
|
|
/@media \(max-width: 800px\)[\s\S]*?\.workspace-tools-trigger\s*\{[^}]*height:\s*32px;[^}]*width:\s*32px/
|
|
)
|
|
})
|
|
|
|
it.each([
|
|
['人物', 'character'],
|
|
['场景', 'scene'],
|
|
['道具', 'prop']
|
|
] as const)('%s 的奇偶主体默认展示独立形态块,搜索重排不影响形态归属', async (label, module) => {
|
|
const record = checkpoint()
|
|
// 前两个主体各准备两种形态,覆盖奇偶条纹和默认标签,不调用模型接口。
|
|
record.state.breakdownResult!.subjectForms = [0, 1].flatMap(subjectIndex =>
|
|
[0, 1].map(formIndex => ({
|
|
formId: `${module}-${subjectIndex}-form-${formIndex}`,
|
|
profileId: `${module}-${subjectIndex}`,
|
|
type: module,
|
|
name: `主体 ${subjectIndex + 1} 形态 ${formIndex + 1}`,
|
|
isDefault: formIndex === 0,
|
|
description: '形态描述',
|
|
appearancePrompt: '形态提示词'
|
|
}))
|
|
)
|
|
mountPage([record])
|
|
await selectTab(label)
|
|
const articles = wrapper!.findAll('.subject-record-list > article')
|
|
for (const index of [0, 1]) {
|
|
const article = articles[index]!
|
|
const cards = article.findAll('.subject-form-card')
|
|
expect(cards).toHaveLength(2)
|
|
expect(cards[0]!.get('.n-tag').text()).toBe('默认')
|
|
for (const card of cards) {
|
|
expect(card.classes()).not.toContain('surface-inset')
|
|
expect(card.text()).toContain(`主体 ${index + 1} 形态`)
|
|
expect(card.text()).toContain('形态描述')
|
|
expect(card.text()).toContain('形态提示词')
|
|
}
|
|
}
|
|
// 原偶数主体过滤后成为首项,仍由当前 DOM 的奇偶选择器决定内外层底色。
|
|
await wrapper!.get('input[aria-label="搜索主体"]').setValue(`@${module}1`)
|
|
const first = wrapper!.get('.subject-record-list > article')
|
|
expect(first.findAll('.subject-form-card')).toHaveLength(2)
|
|
expect(first.get('.subject-form-card').text()).toContain('主体 2 形态 1')
|
|
})
|
|
|
|
it('形态块在两种主体条纹上采用相反灰阶,以间距分组且没有整块悬停态', () => {
|
|
// DOM 环境不提供真实主题绘制,检查奇偶行底色、内边距和状态选择器契约。
|
|
const css = readAllStyles()
|
|
expect(css).toMatch(
|
|
/\.subject-form-card\s*\{[^}]*margin-top:\s*12px;[^}]*padding:\s*16px;[^}]*background:\s*var\(--app-subtle\);/
|
|
)
|
|
expect(css).toMatch(
|
|
/\.subject-record-list > article:nth-child\(even\) \.subject-form-card\s*\{\s*background:\s*var\(--app-control\);/
|
|
)
|
|
expect(css).not.toMatch(/\.subject-form-card[^{}]*(?::hover|:focus-within)/)
|
|
})
|
|
|
|
it('主体奇数行常驻原悬停底色,偶数行不变,整行不再随鼠标或焦点变色', () => {
|
|
// DOM 环境无法模拟浏览器 :hover 命中;检查静态底色与无整行交互选择器的契约。
|
|
const css = readAllStyles()
|
|
expect(css).toMatch(/\.record-list > article:nth-child\(even\)\s*\{\s*background:\s*var\(--app-subtle\);/)
|
|
expect(css).toMatch(
|
|
/\.subject-record-list > article:nth-child\(odd\)\s*\{\s*background:\s*var\(--app-control\);/
|
|
)
|
|
expect(css + readFileSync('src/styles/admin.css', 'utf8')).not.toMatch(
|
|
/\.subject-record-list[^{}]*(?::hover|:focus-within)/
|
|
)
|
|
// 仅移除整行变色,不影响内部链接、折叠等控件的键盘焦点提示。
|
|
expect(css).toMatch(/:focus-visible\s*\{[^}]*outline:\s*2px solid var\(--color-accent\);/)
|
|
})
|
|
|
|
it('分镜选择与计数、入口同排,默认显示实际剧集,切换后内容和统计同步', async () => {
|
|
const record = checkpoint()
|
|
const result = record.state.breakdownResult!
|
|
const first = result.storyboardPlans![0]!
|
|
const second = { ...first, episodeNo: 2, episodeTitle: '第二集', beats: first.beats.slice(0, 2) }
|
|
result.storyboardPlans!.push(second)
|
|
result.storyboardEpisodeShots!.push({
|
|
episodeNo: 2,
|
|
episodePlan: second,
|
|
beatShots: result.storyboardEpisodeShots![0]!.beatShots.slice(0, 2)
|
|
})
|
|
const provided = mountPage([record])
|
|
await selectTab('分镜')
|
|
const toolbar = wrapper!.get('.breakdown-storyboard-toolbar')
|
|
expect(toolbar.get('.breakdown-episode-picker > span').text()).toBe('选择剧集')
|
|
const select = toolbar.getComponent(NSelect)
|
|
expect(select.props('value')).toBe(1)
|
|
expect(toolbar.text()).toContain('长剧集')
|
|
expect(toolbar.get('.breakdown-storyboard-summary').text()).toContain('25 个节拍 · 25 个镜头')
|
|
expect(toolbar.get('.breakdown-storyboard-summary router-link-stub').attributes('to')).toBe(
|
|
'/projects/breakdown-scroll-test/storyboard'
|
|
)
|
|
expect(wrapper!.find('.breakdown-storyboard > .surface-inset').exists()).toBe(false)
|
|
select.vm.$emit('update:value', 2)
|
|
await flushPromises()
|
|
expect(select.props('value')).toBe(2)
|
|
expect(toolbar.text()).toContain('第二集')
|
|
expect(toolbar.get('.breakdown-storyboard-summary').text()).toContain('2 个节拍 · 2 个镜头')
|
|
expect(wrapper!.findAll('.beat-section')).toHaveLength(2)
|
|
// 轮询删除当前选项时,输入框与正文一起回到仍存在的第一集。
|
|
provided.data.value!.checkpoints = [checkpoint()]
|
|
await flushPromises()
|
|
expect(select.props('value')).toBe(1)
|
|
expect(wrapper!.findAll('.beat-section')).toHaveLength(25)
|
|
})
|
|
|
|
it('分镜为空时保留空提示与进入设计入口,不显示空选择器', async () => {
|
|
const record = checkpoint()
|
|
record.state.breakdownResult!.storyboardPlans = []
|
|
record.state.breakdownResult!.storyboardEpisodeShots = []
|
|
mountPage([record])
|
|
await selectTab('分镜')
|
|
expect(wrapper!.get('.breakdown-storyboard').text()).toContain('分镜规划尚未生成')
|
|
expect(wrapper!.find('.breakdown-episode-picker').exists()).toBe(false)
|
|
expect(wrapper!.get('.breakdown-storyboard-summary router-link-stub').attributes('to')).toBe(
|
|
'/projects/breakdown-scroll-test/storyboard'
|
|
)
|
|
})
|
|
|
|
it('分镜顶部无叠加操作行,选择标签保持单行且统计操作垂直居中', () => {
|
|
// 仅保护样式契约,DOM 环境不提供真实布局坐标。
|
|
const css = readAllStyles()
|
|
expect(css).toMatch(/\.breakdown-result-pad\s*\{[^}]*padding:\s*16px/)
|
|
expect(css).toMatch(/\.breakdown-episode-picker > span\s*\{[^}]*flex-shrink:\s*0;[^}]*white-space:\s*nowrap;/)
|
|
expect(css).toMatch(/\.breakdown-storyboard-summary\s*\{[^}]*align-items:\s*center;/)
|
|
})
|
|
|
|
it.each([
|
|
['人物', 'character 主体 30'],
|
|
['场景', 'scene 主体 30'],
|
|
['道具', 'prop 主体 30'],
|
|
['分镜', '镜头 25'],
|
|
['任务明细', '任务错误 50']
|
|
])('%s 的末项始终放在独立结果滚动容器内', async (label, lastItem) => {
|
|
mountPage()
|
|
await selectTab(label)
|
|
await selectMenu('拆解更多操作', '执行记录')
|
|
expect(wrapper!.find('.workspace-scroll').exists()).toBe(false)
|
|
const results = wrapper!.get('.breakdown-results-scroll .n-scrollbar-container')
|
|
expect(results.text()).toContain(lastItem)
|
|
expect(results.find('.result-toolbar').exists()).toBe(false)
|
|
expect(results.find('.history-panel').exists()).toBe(false)
|
|
expect(wrapper!.get('.history-panel .n-scrollbar-container').text()).toContain('执行记录')
|
|
expect(wrapper!.get('[data-workspace-tools]').attributes('aria-expanded')).toBe('false')
|
|
expect(results.find('.table-scroll').exists()).toBe(label === '任务明细')
|
|
})
|
|
|
|
it('刷新和开关配置保留阅读位置,切换标签只重置结果区,不改变执行记录位置', async () => {
|
|
const fetcher = vi.fn<typeof fetch>()
|
|
vi.stubGlobal('fetch', fetcher)
|
|
const provided = mountPage()
|
|
await selectMenu('拆解更多操作', '执行记录')
|
|
const results = wrapper!.get<HTMLElement>('.breakdown-results-scroll .n-scrollbar-container').element
|
|
const history = wrapper!.get<HTMLElement>('.history-panel .n-scrollbar-container').element
|
|
results.scrollTop = 900
|
|
history.scrollTop = 120
|
|
await wrapper!.get('[data-workspace-tools]').trigger('click')
|
|
await flushPromises()
|
|
expect(drawerPanel().element.closest('.workspace-split')).toBeNull()
|
|
await drawerPanel().get('#group-size').setValue('2')
|
|
await drawerPanel().get('[aria-label="关闭操作面板"]').trigger('click')
|
|
provided.data.value!.checkpoints = [checkpoint()]
|
|
await flushPromises()
|
|
expect(wrapper!.get('.breakdown-results-scroll .n-scrollbar-container').element).toBe(results)
|
|
expect(results.scrollTop).toBe(900)
|
|
await selectTab('场景')
|
|
const next = wrapper!.get<HTMLElement>('.breakdown-results-scroll .n-scrollbar-container').element
|
|
expect(next).not.toBe(results)
|
|
expect(next.scrollTop).toBe(0)
|
|
expect(wrapper!.get('.history-panel .n-scrollbar-container').element).toBe(history)
|
|
expect(history.scrollTop).toBe(120)
|
|
await wrapper!.get('[data-workspace-tools]').trigger('click')
|
|
await flushPromises()
|
|
expect(drawerPanel().get<HTMLInputElement>('#group-size').element.value).toBe('2')
|
|
expect(fetcher).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('关闭设置时错误仍可见,详细校验与恢复在面板内,不挤占结果高度', async () => {
|
|
const record = checkpoint()
|
|
record.state.workflowExecution!.status = 'failed'
|
|
record.state.workflowExecution!.errorMessage = '工作流中断'
|
|
record.state.breakdownResult!.storyboardShotValidation = {
|
|
valid: false,
|
|
issues: [{ episodeNo: 1, message: '缺少形态绑定' }]
|
|
}
|
|
mountPage([record])
|
|
const feedback = wrapper!.get('.workspace-feedback')
|
|
expect(feedback.text()).toContain('工作流中断')
|
|
expect(feedback.text()).toContain('分镜校验未通过,共 1 项问题')
|
|
expect(feedback.find('.breakdown-results-section').exists()).toBe(false)
|
|
await feedback.get('button').trigger('click')
|
|
await flushPromises()
|
|
expect(drawerPanel().text()).toContain('缺少形态绑定')
|
|
expect(drawerPanel().text()).toContain('重试失败抽取')
|
|
})
|
|
|
|
it('没有正式剧集时保留可滚动的空状态,不显示拆解设置与结果', () => {
|
|
mountPage([], 0)
|
|
expect(wrapper!.get('.panel-scroll .n-scrollbar-container').text()).toContain('还没有可拆解的剧集')
|
|
expect(wrapper!.find('.breakdown-results-section').exists()).toBe(false)
|
|
expect(wrapper!.find('[data-workspace-tools]').exists()).toBe(false)
|
|
})
|
|
})
|