mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-10 16:13:28 +08:00
fix(dashboard): remeasure async card content (#625)
This commit is contained in:
@@ -1057,7 +1057,8 @@ watch(
|
||||
|
||||
<template>
|
||||
<div ref="containerRef" class="progressive-card-grid">
|
||||
<div ref="trackRef" class="progressive-card-grid__track">
|
||||
<!-- 完整高度由虚拟占位与可见网格共同承载,供上层自适应布局观察稳定的尺寸语义。 -->
|
||||
<div ref="trackRef" class="progressive-card-grid__track" data-layout-size-source>
|
||||
<div
|
||||
v-if="topSpacerHeight > 0"
|
||||
class="progressive-card-grid__spacer"
|
||||
|
||||
@@ -35,6 +35,20 @@ describe('ProgressiveCardGrid scroll target lifecycle', () => {
|
||||
expect(addScrollListener).toHaveBeenCalledWith('scroll', expect.any(Function), { passive: true })
|
||||
})
|
||||
})
|
||||
|
||||
it('exposes the complete virtual track as a layout size source', () => {
|
||||
const { container } = render(ProgressiveCardGrid, {
|
||||
props: {
|
||||
items: [{ id: 1 }],
|
||||
getItemKey: (item: { id: number }) => item.id,
|
||||
},
|
||||
slots: {
|
||||
default: '<div>item</div>',
|
||||
},
|
||||
})
|
||||
|
||||
expect(container.querySelector('.progressive-card-grid__track')).toHaveAttribute('data-layout-size-source')
|
||||
})
|
||||
})
|
||||
|
||||
describe('ProgressiveCardGrid mount scheduling', () => {
|
||||
|
||||
@@ -67,6 +67,44 @@ class ResizeObserverMock implements ResizeObserver {
|
||||
unobserve() {}
|
||||
}
|
||||
|
||||
class LayoutSourceResizeObserverMock implements ResizeObserver {
|
||||
static readonly instances: LayoutSourceResizeObserverMock[] = []
|
||||
|
||||
readonly observed = new Set<Element>()
|
||||
readonly unobserved = new Set<Element>()
|
||||
private readonly callback: ResizeObserverCallback
|
||||
|
||||
constructor(callback: ResizeObserverCallback) {
|
||||
this.callback = callback
|
||||
LayoutSourceResizeObserverMock.instances.push(this)
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.observed.clear()
|
||||
}
|
||||
|
||||
observe(target: Element) {
|
||||
this.observed.add(target)
|
||||
}
|
||||
|
||||
unobserve(target: Element) {
|
||||
this.observed.delete(target)
|
||||
this.unobserved.add(target)
|
||||
}
|
||||
|
||||
resize(target: Element, height: number) {
|
||||
this.callback([{ contentRect: { height }, target } as ResizeObserverEntry], this)
|
||||
}
|
||||
}
|
||||
|
||||
async function findLayoutSourceObserver(source: Element) {
|
||||
await waitFor(() => {
|
||||
expect(LayoutSourceResizeObserverMock.instances.some(observer => observer.observed.has(source))).toBe(true)
|
||||
})
|
||||
|
||||
return LayoutSourceResizeObserverMock.instances.find(observer => observer.observed.has(source))!
|
||||
}
|
||||
|
||||
vi.mock('@/api', () => ({
|
||||
default: {
|
||||
get: (...args: unknown[]) => mocks.apiGet(...args),
|
||||
@@ -108,7 +146,7 @@ vi.mock('@/composables/useSharedDialog', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/components/misc/DashboardElement.vue', async () => {
|
||||
const { defineComponent, h, onMounted } = await import('vue')
|
||||
const { defineComponent, h, onMounted, ref } = await import('vue')
|
||||
|
||||
return {
|
||||
default: defineComponent({
|
||||
@@ -118,6 +156,7 @@ vi.mock('@/components/misc/DashboardElement.vue', async () => {
|
||||
},
|
||||
emits: ['loaded'],
|
||||
setup(props, { emit }) {
|
||||
const showLayoutSizeSource = ref(false)
|
||||
onMounted(() => emit('loaded'))
|
||||
|
||||
return () =>
|
||||
@@ -126,8 +165,16 @@ vi.mock('@/components/misc/DashboardElement.vue', async () => {
|
||||
{
|
||||
'data-dashboard-id': (props.config as { id: string }).id,
|
||||
'data-testid': 'dashboard-item',
|
||||
onClick: () => {
|
||||
showLayoutSizeSource.value = !showLayoutSizeSource.value
|
||||
},
|
||||
},
|
||||
(props.config as { name: string }).name,
|
||||
[
|
||||
(props.config as { name: string }).name,
|
||||
showLayoutSizeSource.value
|
||||
? h('div', { 'data-layout-size-source': '', 'data-testid': 'layout-size-source' }, 'size source')
|
||||
: null,
|
||||
],
|
||||
)
|
||||
},
|
||||
}),
|
||||
@@ -158,6 +205,11 @@ const enabledOnlyLibrary = {
|
||||
systemInfo: false,
|
||||
}
|
||||
|
||||
const enabledLibraryAndSystemInfo = {
|
||||
...enabledOnlySystemInfo,
|
||||
library: true,
|
||||
}
|
||||
|
||||
function deferred<T>() {
|
||||
let resolve!: (value: T) => void
|
||||
const promise = new Promise<T>(promiseResolve => {
|
||||
@@ -181,8 +233,10 @@ async function renderDashboard() {
|
||||
|
||||
describe('dashboard page initial layout', () => {
|
||||
beforeEach(() => {
|
||||
LayoutSourceResizeObserverMock.instances.length = 0
|
||||
mocks.grid.engine.nodes.length = 0
|
||||
mocks.gridInit.mockClear()
|
||||
mocks.grid.resizeToContent.mockClear()
|
||||
mocks.grid.setAnimation.mockClear()
|
||||
mocks.apiGet.mockReset()
|
||||
mocks.apiPost.mockReset()
|
||||
@@ -376,6 +430,135 @@ describe('dashboard page initial layout', () => {
|
||||
await waitFor(() => expect(mocks.grid.load).toHaveBeenCalled())
|
||||
})
|
||||
|
||||
it('observes an async size source and remeasures only its automatic card', async () => {
|
||||
vi.stubGlobal('ResizeObserver', LayoutSourceResizeObserverMock)
|
||||
localStorage.setItem(
|
||||
'MP_DASHBOARD_GRID_LAYOUT',
|
||||
JSON.stringify({
|
||||
enabled: enabledLibraryAndSystemInfo,
|
||||
items: { library: { x: 0, y: 0, w: 6 }, systemInfo: { x: 6, y: 0, w: 6 } },
|
||||
updatedAt: 10,
|
||||
}),
|
||||
)
|
||||
mocks.apiGet.mockImplementation((url: string) => {
|
||||
if (url === '/user/config/DashboardOrder') {
|
||||
return {
|
||||
data: {
|
||||
value: [
|
||||
{ id: 'library', key: '' },
|
||||
{ id: 'systemInfo', key: '' },
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
if (url === '/user/config/DashboardGridLayout') {
|
||||
return {
|
||||
data: {
|
||||
value: {
|
||||
enabled: enabledLibraryAndSystemInfo,
|
||||
items: { library: { x: 0, y: 0, w: 6 }, systemInfo: { x: 6, y: 0, w: 6 } },
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (url === '/plugin/dashboard/meta') return []
|
||||
throw new Error('Unexpected GET ' + url)
|
||||
})
|
||||
|
||||
const { container } = await renderDashboard()
|
||||
await waitFor(() => expect(mocks.grid.makeWidget).toHaveBeenCalledTimes(2))
|
||||
await waitFor(() => expect(mocks.grid.load).toHaveBeenCalled())
|
||||
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve(undefined))))
|
||||
mocks.grid.resizeToContent.mockClear()
|
||||
|
||||
const libraryDashboard = screen
|
||||
.getAllByTestId('dashboard-item')
|
||||
.find(element => element.getAttribute('data-dashboard-id') === 'library')!
|
||||
await fireEvent.click(libraryDashboard)
|
||||
const sizeSource = libraryDashboard.querySelector('[data-layout-size-source]')!
|
||||
const observer = await findLayoutSourceObserver(sizeSource)
|
||||
const libraryItem = container.querySelector('.dashboard-grid-item[gs-id="library"]')
|
||||
const systemInfoItem = container.querySelector('.dashboard-grid-item[gs-id="systemInfo"]')
|
||||
observer.resize(sizeSource, 480)
|
||||
|
||||
await waitFor(() => expect(mocks.grid.resizeToContent).toHaveBeenCalledWith(libraryItem))
|
||||
expect(mocks.grid.resizeToContent).toHaveBeenCalledTimes(1)
|
||||
expect(mocks.grid.resizeToContent).not.toHaveBeenCalledWith(systemInfoItem)
|
||||
|
||||
mocks.grid.resizeToContent.mockClear()
|
||||
sizeSource.append(document.createElement('span'))
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(mocks.grid.resizeToContent).not.toHaveBeenCalled()
|
||||
|
||||
await fireEvent.click(libraryDashboard)
|
||||
await waitFor(() => expect(observer.unobserved.has(sizeSource)).toBe(true))
|
||||
})
|
||||
|
||||
it('ignores size source changes for manually sized cards', async () => {
|
||||
vi.stubGlobal('ResizeObserver', LayoutSourceResizeObserverMock)
|
||||
localStorage.setItem(
|
||||
'MP_DASHBOARD_GRID_LAYOUT',
|
||||
JSON.stringify({
|
||||
enabled: enabledOnlyLibrary,
|
||||
items: { library: { x: 0, y: 0, w: 12, h: 20 } },
|
||||
updatedAt: 10,
|
||||
}),
|
||||
)
|
||||
mocks.apiGet.mockImplementation((url: string) => {
|
||||
if (url === '/user/config/DashboardOrder') return { data: { value: [{ id: 'library', key: '' }] } }
|
||||
if (url === '/user/config/DashboardGridLayout') {
|
||||
return {
|
||||
data: { value: { enabled: enabledOnlyLibrary, items: { library: { x: 0, y: 0, w: 12, h: 20 } } } },
|
||||
}
|
||||
}
|
||||
if (url === '/plugin/dashboard/meta') return []
|
||||
throw new Error('Unexpected GET ' + url)
|
||||
})
|
||||
|
||||
const { container } = await renderDashboard()
|
||||
await waitFor(() => expect(mocks.grid.makeWidget).toHaveBeenCalledTimes(1))
|
||||
await new Promise(resolve => requestAnimationFrame(() => resolve(undefined)))
|
||||
mocks.grid.resizeToContent.mockClear()
|
||||
|
||||
await fireEvent.click(screen.getByTestId('dashboard-item'))
|
||||
const sizeSource = container.querySelector('[data-layout-size-source]')!
|
||||
const observer = await findLayoutSourceObserver(sizeSource)
|
||||
observer.resize(sizeSource, 480)
|
||||
await new Promise(resolve => requestAnimationFrame(() => resolve(undefined)))
|
||||
expect(mocks.grid.resizeToContent).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('ignores size source changes while editing an automatic card', async () => {
|
||||
vi.stubGlobal('ResizeObserver', LayoutSourceResizeObserverMock)
|
||||
localStorage.setItem(
|
||||
'MP_DASHBOARD_GRID_LAYOUT',
|
||||
JSON.stringify({ enabled: enabledOnlyLibrary, items: { library: { x: 0, y: 0, w: 12 } }, updatedAt: 10 }),
|
||||
)
|
||||
mocks.apiGet.mockImplementation((url: string) => {
|
||||
if (url === '/user/config/DashboardOrder') return { data: { value: [{ id: 'library', key: '' }] } }
|
||||
if (url === '/user/config/DashboardGridLayout') {
|
||||
return { data: { value: { enabled: enabledOnlyLibrary, items: { library: { x: 0, y: 0, w: 12 } } } } }
|
||||
}
|
||||
if (url === '/plugin/dashboard/meta') return []
|
||||
throw new Error('Unexpected GET ' + url)
|
||||
})
|
||||
|
||||
const { container } = await renderDashboard()
|
||||
await waitFor(() => expect(mocks.grid.makeWidget).toHaveBeenCalledTimes(1))
|
||||
await waitFor(() => expect(mocks.grid.resizeToContent).toHaveBeenCalled())
|
||||
mocks.grid.resizeToContent.mockClear()
|
||||
|
||||
await fireEvent.click(screen.getByTestId('dashboard-item'))
|
||||
const sizeSource = container.querySelector('[data-layout-size-source]')!
|
||||
const observer = await findLayoutSourceObserver(sizeSource)
|
||||
await fireEvent.click(document.querySelector('.compact-fab--primary') as HTMLElement)
|
||||
await new Promise(resolve => requestAnimationFrame(() => resolve(undefined)))
|
||||
mocks.grid.resizeToContent.mockClear()
|
||||
observer.resize(sizeSource, 480)
|
||||
await new Promise(resolve => requestAnimationFrame(() => resolve(undefined)))
|
||||
expect(mocks.grid.resizeToContent).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('applies a newer remote profile and refreshes the local first-frame cache', async () => {
|
||||
const remoteProfile = deferred<unknown>()
|
||||
localStorage.setItem(
|
||||
|
||||
@@ -41,6 +41,7 @@ const DASHBOARD_GRID_CELL_HEIGHT = 16
|
||||
const DASHBOARD_GRID_FALLBACK_ROWS = 4
|
||||
const DASHBOARD_GRID_MARGIN = 8
|
||||
const DASHBOARD_GRID_CONTENT_RESIZE_THRESHOLD = 4
|
||||
const DASHBOARD_GRID_SIZE_SOURCE_SELECTOR = '[data-layout-size-source]'
|
||||
const DASHBOARD_ENABLE_STORAGE_KEY = 'MP_DASHBOARD'
|
||||
const DASHBOARD_ORDER_STORAGE_KEY = 'MP_DASHBOARD_ORDER'
|
||||
const DASHBOARD_GRID_LAYOUT_STORAGE_KEY_PREFIX = 'MP_DASHBOARD_GRID_LAYOUT'
|
||||
@@ -141,8 +142,10 @@ let isLegacyDashboardEnableConfigLoaded = false
|
||||
const dashboardGridResizeStartHeights = new Map<string, number | undefined>()
|
||||
const dashboardGridPendingContentResize = new Set<GridItemHTMLElement>()
|
||||
const dashboardGridObservedContentHeights = new Map<string, number>()
|
||||
const dashboardGridObservedSizeSources = new Set<Element>()
|
||||
|
||||
let dashboardGridContentObserver: ResizeObserver | null = null
|
||||
let dashboardGridContentMutationObserver: MutationObserver | null = null
|
||||
let dashboardGridContentResizeFrame: number | null = null
|
||||
let dashboardGridResizeRefreshFrame: number | null = null
|
||||
let dashboardGridAnimationFrame: number | null = null
|
||||
@@ -1326,16 +1329,24 @@ function syncDashboardFillContentState(element?: GridItemHTMLElement) {
|
||||
// 监听仪表板组件内容尺寸变化,让未手动调高的组件按内容高度自适应。
|
||||
function observeDashboardGridContent() {
|
||||
const gridElement = dashboardGridRef.value
|
||||
dashboardGridContentMutationObserver?.disconnect()
|
||||
dashboardGridContentMutationObserver = null
|
||||
dashboardGridContentObserver?.disconnect()
|
||||
dashboardGridContentObserver = null
|
||||
dashboardGridPendingContentResize.clear()
|
||||
dashboardGridObservedContentHeights.clear()
|
||||
dashboardGridObservedSizeSources.clear()
|
||||
if (!gridElement || typeof ResizeObserver === 'undefined') return
|
||||
|
||||
syncDashboardFillContentState()
|
||||
dashboardGridContentObserver?.disconnect()
|
||||
dashboardGridPendingContentResize.clear()
|
||||
dashboardGridObservedContentHeights.clear()
|
||||
dashboardGridContentObserver = new ResizeObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
const itemElement = entry.target.closest('.dashboard-grid-item') as GridItemHTMLElement | null
|
||||
if (itemElement && shouldScheduleDashboardContentResize(itemElement, entry.contentRect.height)) {
|
||||
const isSizeSource = entry.target.matches(DASHBOARD_GRID_SIZE_SOURCE_SELECTOR)
|
||||
if (
|
||||
itemElement &&
|
||||
(isSizeSource || shouldScheduleDashboardContentResize(itemElement, entry.contentRect.height))
|
||||
) {
|
||||
scheduleDashboardItemContentResize(itemElement)
|
||||
}
|
||||
})
|
||||
@@ -1344,6 +1355,46 @@ function observeDashboardGridContent() {
|
||||
gridElement.querySelectorAll<HTMLElement>('.dashboard-grid-auto-size').forEach(element => {
|
||||
dashboardGridContentObserver?.observe(element)
|
||||
})
|
||||
gridElement.querySelectorAll<HTMLElement>(DASHBOARD_GRID_SIZE_SOURCE_SELECTOR).forEach(observeDashboardGridSizeSource)
|
||||
|
||||
if (typeof MutationObserver === 'undefined') return
|
||||
|
||||
dashboardGridContentMutationObserver = new MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
mutation.removedNodes.forEach(node => {
|
||||
findDashboardGridSizeSources(node).forEach(unobserveDashboardGridSizeSource)
|
||||
})
|
||||
mutation.addedNodes.forEach(node => {
|
||||
findDashboardGridSizeSources(node).forEach(observeDashboardGridSizeSource)
|
||||
})
|
||||
})
|
||||
})
|
||||
dashboardGridContentMutationObserver.observe(gridElement, { childList: true, subtree: true })
|
||||
}
|
||||
|
||||
// 返回节点自身及后代声明的真实尺寸源,普通子节点变化不进入测高路径。
|
||||
function findDashboardGridSizeSources(node: Node) {
|
||||
if (!(node instanceof Element)) return []
|
||||
|
||||
const sources = Array.from(node.querySelectorAll<Element>(DASHBOARD_GRID_SIZE_SOURCE_SELECTOR))
|
||||
if (node.matches(DASHBOARD_GRID_SIZE_SOURCE_SELECTOR)) sources.unshift(node)
|
||||
|
||||
return sources
|
||||
}
|
||||
|
||||
// 注册异步挂载的尺寸源;ResizeObserver 的首次回调负责触发实际测高。
|
||||
function observeDashboardGridSizeSource(element: Element) {
|
||||
if (!dashboardGridContentObserver || dashboardGridObservedSizeSources.has(element)) return
|
||||
|
||||
dashboardGridObservedSizeSources.add(element)
|
||||
dashboardGridContentObserver.observe(element)
|
||||
}
|
||||
|
||||
// 节点卸载后同步解除观察,避免 KeepAlive 与布局重建保留失效引用。
|
||||
function unobserveDashboardGridSizeSource(element: Element) {
|
||||
if (!dashboardGridContentObserver || !dashboardGridObservedSizeSources.delete(element)) return
|
||||
|
||||
dashboardGridContentObserver.unobserve(element)
|
||||
}
|
||||
|
||||
// 判断内容高度变化是否足够触发 GridStack 行高重算,避免 hover 级微小波动造成布局抖动。
|
||||
@@ -1648,6 +1699,8 @@ onDeactivated(() => {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
Object.keys(refreshTimers.value).forEach(clearPluginDashboardTimer)
|
||||
dashboardGridContentMutationObserver?.disconnect()
|
||||
dashboardGridContentMutationObserver = null
|
||||
dashboardGridContentObserver?.disconnect()
|
||||
dashboardGridContentObserver = null
|
||||
if (dashboardGridContentResizeFrame !== null) {
|
||||
@@ -1668,6 +1721,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
dashboardGridPendingContentResize.clear()
|
||||
dashboardGridObservedContentHeights.clear()
|
||||
dashboardGridObservedSizeSources.clear()
|
||||
dashboardGridResizeStartHeights.clear()
|
||||
dashboardGrid.value?.destroy(false)
|
||||
dashboardGrid.value = null
|
||||
@@ -1676,11 +1730,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
<template>
|
||||
<!-- 仪表板 -->
|
||||
<div
|
||||
ref="dashboardGridRef"
|
||||
class="grid-stack dashboard-grid"
|
||||
:class="{ 'is-editing': isLayoutEditing }"
|
||||
>
|
||||
<div ref="dashboardGridRef" class="grid-stack dashboard-grid" :class="{ 'is-editing': isLayoutEditing }">
|
||||
<div
|
||||
v-for="gridItem in dashboardGridItems"
|
||||
:key="gridItem.id"
|
||||
@@ -1876,5 +1926,4 @@ onBeforeUnmount(() => {
|
||||
inset-block-end: -4px;
|
||||
inset-inline-end: -4px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -52,15 +52,20 @@ onActivated(loadRecentImports)
|
||||
</VCardItem>
|
||||
|
||||
<VCardText class="recent-import-list">
|
||||
<div v-for="item in recentImports" :key="item.id" class="recent-import-item">
|
||||
<VImg :src="getPosterUrl(item)" :alt="item.title" class="recent-import-poster" cover />
|
||||
<div class="recent-import-copy">
|
||||
<div class="recent-import-title">{{ item.title }}<span v-if="item.year"> ({{ item.year }})</span></div>
|
||||
<div class="recent-import-meta">{{ getImportMeta(item) }}</div>
|
||||
</div>
|
||||
<div class="recent-import-time">
|
||||
{{ item.date ? formatDateDifference(item.date) : '' }}
|
||||
<VIcon icon="mdi-check-circle" color="success" size="16" />
|
||||
<!-- 非空列表使用自然内容高度,避免外层填充布局隐藏异步增长。 -->
|
||||
<div v-if="recentImports.length > 0" data-layout-size-source>
|
||||
<div v-for="item in recentImports" :key="item.id" class="recent-import-item">
|
||||
<VImg :src="getPosterUrl(item)" :alt="item.title" class="recent-import-poster" cover />
|
||||
<div class="recent-import-copy">
|
||||
<div class="recent-import-title">
|
||||
{{ item.title }}<span v-if="item.year"> ({{ item.year }})</span>
|
||||
</div>
|
||||
<div class="recent-import-meta">{{ getImportMeta(item) }}</div>
|
||||
</div>
|
||||
<div class="recent-import-time">
|
||||
{{ item.date ? formatDateDifference(item.date) : '' }}
|
||||
<VIcon icon="mdi-check-circle" color="success" size="16" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
33
src/views/dashboard/__tests__/DashboardRecentImports.spec.ts
Normal file
33
src/views/dashboard/__tests__/DashboardRecentImports.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import DashboardRecentImports from '@/views/dashboard/DashboardRecentImports.vue'
|
||||
import { renderWithProviders } from '@tests/support/render'
|
||||
import { screen } from '@testing-library/vue'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
apiGet: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/api', () => ({
|
||||
default: {
|
||||
get: (...args: unknown[]) => mocks.apiGet(...args),
|
||||
},
|
||||
}))
|
||||
|
||||
describe('dashboard recent imports', () => {
|
||||
beforeEach(() => {
|
||||
mocks.apiGet.mockReset()
|
||||
})
|
||||
|
||||
it('exposes the rendered list as a layout size source', async () => {
|
||||
mocks.apiGet.mockResolvedValue({
|
||||
data: {
|
||||
list: [{ id: 1, title: '异步入库记录' }],
|
||||
},
|
||||
})
|
||||
|
||||
const { container } = await renderWithProviders(DashboardRecentImports)
|
||||
|
||||
const renderedItem = await screen.findByText('异步入库记录')
|
||||
expect(container.querySelector('[data-layout-size-source]')).toContainElement(renderedItem)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user