You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
580 lines
20 KiB
580 lines
20 KiB
import type { Page } from '@playwright/test';
|
|
|
|
import type { LayoutMetrics, RegionMetrics } from './common/layout';
|
|
|
|
import { writeFile } from 'node:fs/promises';
|
|
|
|
import {
|
|
getLayoutMetrics,
|
|
LAYOUT_TYPES,
|
|
updateLayoutPreferences,
|
|
waitForLayoutSettled,
|
|
} from './common/layout';
|
|
import { measureSidebarPerformance } from './common/layout-performance';
|
|
import { expect, test } from './fixtures/layout';
|
|
|
|
const HEADER_MODES = ['fixed', 'static', 'auto', 'auto-scroll'] as const;
|
|
|
|
test.describe.configure({ mode: 'serial' });
|
|
|
|
function requireRegion(
|
|
metrics: LayoutMetrics,
|
|
name: keyof LayoutMetrics['regions'],
|
|
): RegionMetrics {
|
|
const region = metrics.regions[name];
|
|
if (!region) {
|
|
throw new Error(`Missing layout region: ${String(name)}`);
|
|
}
|
|
return region;
|
|
}
|
|
|
|
function expectNoViewportOverflow(metrics: LayoutMetrics) {
|
|
expect(metrics.body.scrollWidth).toBeLessThanOrEqual(
|
|
metrics.body.clientWidth + 1,
|
|
);
|
|
expect(metrics.document.scrollWidth).toBeLessThanOrEqual(
|
|
metrics.document.clientWidth + 1,
|
|
);
|
|
expect(metrics.body.scrollHeight).toBeLessThanOrEqual(
|
|
metrics.body.clientHeight + 1,
|
|
);
|
|
expect(metrics.document.scrollHeight).toBeLessThanOrEqual(
|
|
metrics.document.clientHeight + 1,
|
|
);
|
|
}
|
|
|
|
async function configureDesktopRolePage(page: Page) {
|
|
await page.setViewportSize({ height: 900, width: 1440 });
|
|
await updateLayoutPreferences(page, {
|
|
app: { layout: 'sidebar-nav' },
|
|
footer: { enable: false, fixed: false },
|
|
header: { enable: true, hidden: false, mode: 'fixed' },
|
|
sidebar: {
|
|
collapsed: false,
|
|
enable: true,
|
|
expandOnHover: true,
|
|
hidden: false,
|
|
},
|
|
tabbar: { enable: true },
|
|
widget: { sidebarToggle: true },
|
|
});
|
|
}
|
|
|
|
async function navigateToLayoutPage(page: Page, path: string) {
|
|
await page.goto(path);
|
|
await page
|
|
.locator('[data-layout-region="layout"]')
|
|
.waitFor({ state: 'visible' });
|
|
await page.waitForFunction(() => Boolean(window.__VBEN_LAYOUT_TEST__));
|
|
}
|
|
|
|
async function getSidebarTransitionDuration(
|
|
page: Page,
|
|
property: 'clip-path' | 'transform',
|
|
) {
|
|
return page
|
|
.locator('[data-layout-region="sidebar"]')
|
|
.evaluate((element, animatedProperty) => {
|
|
const style = getComputedStyle(element);
|
|
const properties = style.transitionProperty
|
|
.split(',')
|
|
.map((item) => item.trim());
|
|
const durations = style.transitionDuration
|
|
.split(',')
|
|
.map(
|
|
(item) =>
|
|
Number.parseFloat(item) * (item.trim().endsWith('ms') ? 1 : 1000),
|
|
);
|
|
const index = properties.indexOf(animatedProperty);
|
|
return index === -1 ? 0 : (durations[index] ?? durations[0] ?? 0);
|
|
}, property);
|
|
}
|
|
|
|
test('keeps page content interactive while loading is hidden', async ({
|
|
layoutPage,
|
|
}) => {
|
|
await configureDesktopRolePage(layoutPage);
|
|
const createButton = layoutPage.getByRole('button', {
|
|
name: '新增角色',
|
|
});
|
|
await expect(createButton).toBeVisible();
|
|
|
|
const hitRegion = await createButton.evaluate((element) => {
|
|
const rect = element.getBoundingClientRect();
|
|
return document
|
|
.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2)
|
|
?.closest('[data-layout-region]')?.dataset.layoutRegion;
|
|
});
|
|
expect(hitRegion).not.toBe('content-overlay');
|
|
|
|
await createButton.click();
|
|
const dialog = layoutPage.getByRole('dialog');
|
|
await expect(dialog).toBeVisible();
|
|
await dialog.locator('button:has(.lucide-x)').click();
|
|
await expect(dialog).toBeHidden();
|
|
});
|
|
|
|
test('keeps the layout background continuous while content scrolls', async ({
|
|
layoutPage,
|
|
}) => {
|
|
await configureDesktopRolePage(layoutPage);
|
|
const result = await layoutPage.evaluate(() => {
|
|
const scroll = document.querySelector('#__vben_layout_scroll');
|
|
const main = document.querySelector('#__vben_main_content');
|
|
if (!(scroll instanceof HTMLElement) || !(main instanceof HTMLElement)) {
|
|
throw new Error('Layout scroll regions are missing');
|
|
}
|
|
|
|
const spacer = document.createElement('div');
|
|
spacer.dataset.layoutTestSpacer = 'background';
|
|
spacer.style.flex = '0 0 1600px';
|
|
scroll.append(spacer);
|
|
scroll.scrollTo({ top: scroll.scrollHeight });
|
|
|
|
const mainRect = main.getBoundingClientRect();
|
|
const scrollStyle = getComputedStyle(scroll);
|
|
const mainStyle = getComputedStyle(main);
|
|
const scrollBottom = scroll.scrollHeight - scroll.clientHeight;
|
|
const mainBottomAtScrollEnd = mainRect.bottom - scrollBottom;
|
|
spacer.remove();
|
|
scroll.scrollTo({ top: 0 });
|
|
|
|
return {
|
|
mainBackground: mainStyle.backgroundColor,
|
|
mainBottom: mainRect.bottom,
|
|
mainBottomAtScrollEnd,
|
|
scrollBackground: scrollStyle.backgroundColor,
|
|
scrollBottom,
|
|
};
|
|
});
|
|
|
|
expect(result.scrollBackground).not.toBe('rgba(0, 0, 0, 0)');
|
|
expect(result.mainBackground).toBe('rgba(0, 0, 0, 0)');
|
|
expect(result.mainBottomAtScrollEnd).toBeLessThanOrEqual(1);
|
|
expect(result.scrollBottom).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('preserves content sizing across footer modes in short viewports', async ({
|
|
layoutPage,
|
|
}) => {
|
|
await configureDesktopRolePage(layoutPage);
|
|
await layoutPage.setViewportSize({ height: 600, width: 1365 });
|
|
await waitForLayoutSettled(layoutPage);
|
|
|
|
try {
|
|
for (const mode of [
|
|
{ enable: false, fixed: false, name: 'disabled' },
|
|
{ enable: true, fixed: true, name: 'fixed' },
|
|
{ enable: true, fixed: false, name: 'non-fixed' },
|
|
] as const) {
|
|
await test.step(mode.name, async () => {
|
|
if (mode.name === 'non-fixed') {
|
|
await navigateToLayoutPage(layoutPage, '/dashboard/analytics');
|
|
await layoutPage
|
|
.locator('#__vben_main_content .page-route-container > *')
|
|
.waitFor({ state: 'visible' });
|
|
}
|
|
await updateLayoutPreferences(layoutPage, {
|
|
footer: { enable: mode.enable, fixed: mode.fixed },
|
|
});
|
|
|
|
const metrics = await getLayoutMetrics(layoutPage);
|
|
const sizing = await layoutPage.evaluate(() => {
|
|
const footer = document.querySelector(
|
|
'[data-layout-region="footer"]',
|
|
);
|
|
const main = document.querySelector('#__vben_main_content');
|
|
const pageContent = document.querySelector(
|
|
'[data-layout-region="page-content"]',
|
|
);
|
|
const scroll = document.querySelector('#__vben_layout_scroll');
|
|
const routeContainer = document.querySelector(
|
|
'#__vben_main_content .page-route-container',
|
|
);
|
|
const routeContent = routeContainer?.firstElementChild;
|
|
if (
|
|
!(main instanceof HTMLElement) ||
|
|
!(scroll instanceof HTMLElement)
|
|
) {
|
|
throw new Error('Layout content regions are missing');
|
|
}
|
|
|
|
const footerRect = footer?.getBoundingClientRect();
|
|
return {
|
|
footerPosition:
|
|
footer instanceof HTMLElement
|
|
? getComputedStyle(footer).position
|
|
: undefined,
|
|
footerTop: footerRect?.top,
|
|
layoutClientHeight: scroll.clientHeight,
|
|
layoutScrollHeight: scroll.scrollHeight,
|
|
mainMinHeight: getComputedStyle(main).minHeight,
|
|
mainPaddingBottom: getComputedStyle(main).paddingBottom,
|
|
pageContentBottom:
|
|
pageContent instanceof HTMLElement
|
|
? pageContent.getBoundingClientRect().bottom
|
|
: 0,
|
|
routeContentBottom: routeContent?.getBoundingClientRect().bottom,
|
|
};
|
|
});
|
|
|
|
expectNoViewportOverflow(metrics);
|
|
expect(sizing.mainMinHeight).toBe(
|
|
mode.name === 'non-fixed' ? 'auto' : '0px',
|
|
);
|
|
expect(Number.parseFloat(sizing.mainPaddingBottom)).toBe(
|
|
mode.name === 'fixed' ? requireRegion(metrics, 'footer').height : 0,
|
|
);
|
|
|
|
if (mode.name === 'disabled') {
|
|
expect(metrics.regions.footer).toBeNull();
|
|
expect(sizing.layoutScrollHeight).toBeLessThanOrEqual(
|
|
sizing.layoutClientHeight + 1,
|
|
);
|
|
} else if (mode.name === 'fixed') {
|
|
const footer = requireRegion(metrics, 'footer');
|
|
expect(sizing.footerPosition).toBe('fixed');
|
|
expect(Math.abs(footer.bottom - 600)).toBeLessThanOrEqual(1);
|
|
expect(sizing.pageContentBottom).toBeLessThanOrEqual(footer.top + 1);
|
|
expect(sizing.layoutScrollHeight).toBeLessThanOrEqual(
|
|
sizing.layoutClientHeight + 1,
|
|
);
|
|
} else {
|
|
expect(sizing.footerPosition).toBe('static');
|
|
expect(sizing.layoutScrollHeight).toBeGreaterThan(
|
|
sizing.layoutClientHeight,
|
|
);
|
|
expect(sizing.footerTop).toBeGreaterThanOrEqual(
|
|
(sizing.routeContentBottom ?? 0) - 1,
|
|
);
|
|
}
|
|
|
|
await layoutPage.evaluate(() => {
|
|
document.querySelector('#__vben_layout_scroll')?.scrollTo({ top: 0 });
|
|
});
|
|
});
|
|
}
|
|
} finally {
|
|
await updateLayoutPreferences(layoutPage, {
|
|
footer: { enable: false, fixed: false },
|
|
});
|
|
await layoutPage.setViewportSize({ height: 900, width: 1440 });
|
|
await navigateToLayoutPage(layoutPage, '/system/role');
|
|
}
|
|
});
|
|
|
|
test('keeps appendToMain drawers within the available content height', async ({
|
|
layoutPage,
|
|
}) => {
|
|
await configureDesktopRolePage(layoutPage);
|
|
await layoutPage.setViewportSize({ height: 600, width: 1365 });
|
|
await navigateToLayoutPage(layoutPage, '/examples/drawer');
|
|
|
|
try {
|
|
const card = layoutPage
|
|
.getByText('在内容区域打开', { exact: true })
|
|
.locator('xpath=ancestor::*[.//button][1]');
|
|
await card
|
|
.getByRole('button', { exact: true, name: '右侧打开' })
|
|
.dispatchEvent('click');
|
|
|
|
const main = layoutPage.locator('#__vben_main_content');
|
|
const drawer = main.getByRole('dialog');
|
|
await expect(drawer).toBeVisible();
|
|
await drawer.evaluate((element) => {
|
|
for (const animation of element.getAnimations({ subtree: true })) {
|
|
if (Number.isFinite(animation.effect?.getComputedTiming().endTime)) {
|
|
animation.finish();
|
|
}
|
|
}
|
|
});
|
|
await waitForLayoutSettled(layoutPage);
|
|
|
|
const metrics = await getLayoutMetrics(layoutPage);
|
|
const geometry = await drawer.evaluate((element) => {
|
|
const main = document.querySelector('#__vben_main_content');
|
|
const scroll = document.querySelector('#__vben_layout_scroll');
|
|
const drawerBody = [...element.querySelectorAll('div')].find(
|
|
(child) => getComputedStyle(child).overflowY === 'auto',
|
|
);
|
|
if (
|
|
!(drawerBody instanceof HTMLElement) ||
|
|
!(main instanceof HTMLElement) ||
|
|
!(scroll instanceof HTMLElement)
|
|
) {
|
|
throw new Error('Drawer layout regions are missing');
|
|
}
|
|
|
|
const spacer = document.createElement('div');
|
|
spacer.dataset.layoutTestSpacer = 'drawer';
|
|
spacer.style.height = '1200px';
|
|
drawerBody.append(spacer);
|
|
|
|
const drawerRect = element.getBoundingClientRect();
|
|
const mainRect = main.getBoundingClientRect();
|
|
return {
|
|
drawerBodyClientHeight: drawerBody.clientHeight,
|
|
drawerBodyScrollHeight: drawerBody.scrollHeight,
|
|
drawerBottom: drawerRect.bottom,
|
|
drawerHeight: drawerRect.height,
|
|
drawerTop: drawerRect.top,
|
|
layoutClientHeight: scroll.clientHeight,
|
|
layoutScrollHeight: scroll.scrollHeight,
|
|
mainBottom: mainRect.bottom,
|
|
mainHeight: mainRect.height,
|
|
mainTop: mainRect.top,
|
|
parentId: element.parentElement?.id,
|
|
};
|
|
});
|
|
|
|
expectNoViewportOverflow(metrics);
|
|
expect(geometry.parentId).toBe('__vben_main_content');
|
|
expect(Math.abs(geometry.mainTop - 88)).toBeLessThanOrEqual(1);
|
|
expect(Math.abs(geometry.drawerTop - geometry.mainTop)).toBeLessThanOrEqual(
|
|
1,
|
|
);
|
|
expect(
|
|
Math.abs(geometry.drawerBottom - geometry.mainBottom),
|
|
).toBeLessThanOrEqual(1);
|
|
expect(
|
|
Math.abs(geometry.drawerHeight - geometry.mainHeight),
|
|
).toBeLessThanOrEqual(1);
|
|
expect(geometry.drawerBodyScrollHeight).toBeGreaterThan(
|
|
geometry.drawerBodyClientHeight,
|
|
);
|
|
expect(geometry.layoutScrollHeight).toBeLessThanOrEqual(
|
|
geometry.layoutClientHeight + 1,
|
|
);
|
|
} finally {
|
|
await layoutPage.setViewportSize({ height: 900, width: 1440 });
|
|
await navigateToLayoutPage(layoutPage, '/system/role');
|
|
}
|
|
});
|
|
|
|
test('uses the same deliberate duration for both sidebar controls', async ({
|
|
layoutPage,
|
|
}) => {
|
|
await configureDesktopRolePage(layoutPage);
|
|
const bottomToggle = layoutPage.locator(
|
|
'[data-layout-action="toggle-sidebar-collapse"]',
|
|
);
|
|
const headerToggle = layoutPage.locator(
|
|
'[data-layout-action="toggle-sidebar"]',
|
|
);
|
|
expect(await getSidebarTransitionDuration(layoutPage, 'clip-path')).toBe(300);
|
|
expect(await getSidebarTransitionDuration(layoutPage, 'transform')).toBe(300);
|
|
|
|
await bottomToggle.click();
|
|
await expect(
|
|
layoutPage.locator('[data-layout-region="layout"]'),
|
|
).toHaveAttribute('data-sidebar-collapsed', 'true');
|
|
await waitForLayoutSettled(layoutPage);
|
|
await bottomToggle.click();
|
|
await waitForLayoutSettled(layoutPage);
|
|
|
|
await headerToggle.click();
|
|
await expect
|
|
.poll(() =>
|
|
layoutPage
|
|
.locator('[data-layout-region="sidebar"]')
|
|
.evaluate((element) => getComputedStyle(element).transform),
|
|
)
|
|
.not.toBe('none');
|
|
await waitForLayoutSettled(layoutPage);
|
|
await headerToggle.click();
|
|
await waitForLayoutSettled(layoutPage);
|
|
});
|
|
|
|
test('reuses one page across the desktop layout matrix', async ({
|
|
layoutPage,
|
|
}) => {
|
|
await configureDesktopRolePage(layoutPage);
|
|
expect(layoutPage.context().pages()).toHaveLength(1);
|
|
|
|
for (const layout of LAYOUT_TYPES) {
|
|
await test.step(layout, async () => {
|
|
await updateLayoutPreferences(layoutPage, { app: { layout } });
|
|
const metrics = await getLayoutMetrics(layoutPage);
|
|
const main = requireRegion(metrics, 'main');
|
|
const pageContent = requireRegion(metrics, 'pageContent');
|
|
const headerVisible = Boolean(metrics.regions.header?.visible);
|
|
const sidebarVisible = Boolean(metrics.regions.sidebar?.visible);
|
|
const expectsSidebar = !['full-content', 'header-nav'].includes(layout);
|
|
const expectsHeader = layout !== 'full-content';
|
|
|
|
expectNoViewportOverflow(metrics);
|
|
expect(metrics.scroll).not.toBeNull();
|
|
if (!metrics.scroll) {
|
|
throw new Error('Missing layout scroll metrics');
|
|
}
|
|
expect(metrics.scroll.scrollHeight).toBeLessThanOrEqual(
|
|
metrics.scroll.clientHeight + 1,
|
|
);
|
|
expect(metrics.scroll.scrollWidth).toBeLessThanOrEqual(
|
|
metrics.scroll.clientWidth + 1,
|
|
);
|
|
expect(main.width).toBeGreaterThan(0);
|
|
expect(pageContent.height).toBeGreaterThan(0);
|
|
expect(sidebarVisible).toBe(expectsSidebar);
|
|
expect(headerVisible).toBe(expectsHeader);
|
|
|
|
const sidebarGap =
|
|
expectsSidebar && metrics.regions.sidebar
|
|
? Math.abs(metrics.regions.sidebar.right - main.left)
|
|
: 0;
|
|
expect(sidebarGap).toBeLessThanOrEqual(1);
|
|
});
|
|
}
|
|
|
|
await updateLayoutPreferences(layoutPage, {
|
|
app: { layout: 'sidebar-nav' },
|
|
sidebar: { collapsed: false },
|
|
});
|
|
const trigger = layoutPage.locator(
|
|
'[data-layout-action="toggle-sidebar-collapse"]',
|
|
);
|
|
const expanded = await getLayoutMetrics(layoutPage);
|
|
|
|
await trigger.click();
|
|
await expect(
|
|
layoutPage.locator('[data-layout-region="layout"]'),
|
|
).toHaveAttribute('data-sidebar-collapsed', 'true');
|
|
await waitForLayoutSettled(layoutPage);
|
|
const collapsed = await getLayoutMetrics(layoutPage);
|
|
|
|
expect(requireRegion(collapsed, 'sidebar').width).toBeLessThan(
|
|
requireRegion(expanded, 'sidebar').width,
|
|
);
|
|
expect(requireRegion(collapsed, 'main').width).toBeGreaterThan(
|
|
requireRegion(expanded, 'main').width,
|
|
);
|
|
|
|
await trigger.click();
|
|
await waitForLayoutSettled(layoutPage);
|
|
});
|
|
|
|
test('keeps all header modes bound to the layout scroll container', async ({
|
|
layoutPage,
|
|
}) => {
|
|
await configureDesktopRolePage(layoutPage);
|
|
|
|
for (const mode of HEADER_MODES) {
|
|
await test.step(mode, async () => {
|
|
await updateLayoutPreferences(layoutPage, { header: { mode } });
|
|
await layoutPage.evaluate(() => {
|
|
const scroll = document.querySelector('#__vben_layout_scroll');
|
|
if (scroll instanceof HTMLElement) {
|
|
const spacer = document.createElement('div');
|
|
spacer.dataset.layoutTestSpacer = 'true';
|
|
spacer.style.flex = '0 0 1800px';
|
|
scroll.append(spacer);
|
|
}
|
|
});
|
|
await layoutPage.mouse.move(1000, 500);
|
|
await layoutPage.locator('#__vben_layout_scroll').evaluate((element) => {
|
|
element.scrollTo({ top: 500 });
|
|
});
|
|
await expect
|
|
.poll(() =>
|
|
layoutPage
|
|
.locator('#__vben_layout_scroll')
|
|
.evaluate((element) => element.scrollTop),
|
|
)
|
|
.toBeGreaterThan(0);
|
|
|
|
await expect
|
|
.poll(() =>
|
|
layoutPage
|
|
.locator('[data-layout-region="header"]')
|
|
.evaluate((element) => element.getBoundingClientRect().bottom <= 1),
|
|
)
|
|
.toBe(mode !== 'fixed');
|
|
|
|
await layoutPage.evaluate(() => {
|
|
document.querySelector('[data-layout-test-spacer="true"]')?.remove();
|
|
document.querySelector('#__vben_layout_scroll')?.scrollTo({ top: 0 });
|
|
});
|
|
await waitForLayoutSettled(layoutPage);
|
|
});
|
|
}
|
|
});
|
|
|
|
test('animates the mobile sidebar without changing the desktop collapse state', async ({
|
|
layoutPage,
|
|
}) => {
|
|
await configureDesktopRolePage(layoutPage);
|
|
await layoutPage.emulateMedia({ reducedMotion: 'no-preference' });
|
|
await layoutPage.setViewportSize({ height: 844, width: 390 });
|
|
const layout = layoutPage.locator('[data-layout-region="layout"]');
|
|
await expect(layout).toHaveAttribute('data-mobile', 'true');
|
|
await expect(layout).toHaveAttribute('data-sidebar-collapsed', 'true');
|
|
await expect(
|
|
layoutPage.locator('[data-layout-region="sidebar"]'),
|
|
).toHaveCount(0);
|
|
|
|
await layoutPage.locator('[data-layout-action="toggle-sidebar"]').click();
|
|
const sidebar = layoutPage.locator('[data-layout-region="sidebar"]');
|
|
const getAnimatedProperties = () =>
|
|
sidebar.evaluate((element) =>
|
|
element.getAnimations().flatMap((animation) => {
|
|
const effect = animation.effect as KeyframeEffect | null;
|
|
return effect
|
|
? effect.getKeyframes().flatMap((frame) => Object.keys(frame))
|
|
: [];
|
|
}),
|
|
);
|
|
await expect.poll(getAnimatedProperties).toContain('transform');
|
|
await expect(sidebar).toBeVisible();
|
|
await expect(
|
|
layoutPage.locator('[data-layout-region="sidebar-mask"]'),
|
|
).toBeVisible();
|
|
const animatedProperties = await getAnimatedProperties();
|
|
expect(animatedProperties).not.toContain('width');
|
|
|
|
await layoutPage
|
|
.locator('[data-layout-region="sidebar-mask"]')
|
|
.click({ position: { x: 380, y: 400 } });
|
|
await expect(sidebar).toHaveCount(0);
|
|
|
|
await layoutPage.setViewportSize({ height: 900, width: 1440 });
|
|
await expect(layout).toHaveAttribute('data-mobile', 'false');
|
|
await expect(layout).toHaveAttribute('data-sidebar-collapsed', 'false');
|
|
await expect(sidebar).toBeVisible();
|
|
expect(layoutPage.context().pages()).toHaveLength(1);
|
|
});
|
|
|
|
test('records desktop sidebar performance metrics', async ({
|
|
layoutPage,
|
|
}, testInfo) => {
|
|
await configureDesktopRolePage(layoutPage);
|
|
const summary = await measureSidebarPerformance(layoutPage);
|
|
const performanceArtifactPath = testInfo.outputPath(
|
|
'layout-performance.json',
|
|
);
|
|
await writeFile(
|
|
performanceArtifactPath,
|
|
`${JSON.stringify(summary, null, 2)}\n`,
|
|
'utf8',
|
|
);
|
|
await testInfo.attach('layout-performance.json', {
|
|
path: performanceArtifactPath,
|
|
contentType: 'application/json',
|
|
});
|
|
|
|
expect(summary.runs).toHaveLength(6);
|
|
expect(summary.runs.map((run) => run.direction)).toEqual([
|
|
'collapse',
|
|
'expand',
|
|
'collapse',
|
|
'expand',
|
|
'collapse',
|
|
'expand',
|
|
]);
|
|
for (const run of summary.runs) {
|
|
expect(run.frameCount).toBeGreaterThanOrEqual(0);
|
|
expect(run.layoutDurationMs).toBeGreaterThanOrEqual(0);
|
|
expect(run.recalcStyleDurationMs).toBeGreaterThanOrEqual(0);
|
|
expect(run.scriptDurationMs).toBeGreaterThanOrEqual(0);
|
|
expect(run.taskDurationMs).toBeGreaterThanOrEqual(0);
|
|
}
|
|
});
|
|
|