fix(next): live preview device position when using zoom (#6665)
This commit is contained in:
@@ -10,16 +10,20 @@ export const DeviceContainer: React.FC<{
|
|||||||
const { children } = props
|
const { children } = props
|
||||||
|
|
||||||
const deviceFrameRef = React.useRef<HTMLDivElement>(null)
|
const deviceFrameRef = React.useRef<HTMLDivElement>(null)
|
||||||
|
const outerFrameRef = React.useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const { breakpoint, setMeasuredDeviceSize, size, zoom } = useLivePreviewContext()
|
const { breakpoint, setMeasuredDeviceSize, size: desiredSize, zoom } = useLivePreviewContext()
|
||||||
|
|
||||||
// Keep an accurate measurement of the actual device size as it is truly rendered
|
// Keep an accurate measurement of the actual device size as it is truly rendered
|
||||||
// This is helpful when `sizes` are non-number units like percentages, etc.
|
// This is helpful when `sizes` are non-number units like percentages, etc.
|
||||||
const { size: measuredDeviceSize } = useResize(deviceFrameRef)
|
const { size: measuredDeviceSize } = useResize(deviceFrameRef.current)
|
||||||
|
const { size: outerFrameSize } = useResize(outerFrameRef.current)
|
||||||
|
|
||||||
|
let deviceIsLargerThanFrame: boolean = false
|
||||||
|
|
||||||
// Sync the measured device size with the context so that other components can use it
|
// Sync the measured device size with the context so that other components can use it
|
||||||
// This happens from the bottom up so that as this component mounts and unmounts,
|
// This happens from the bottom up so that as this component mounts and unmounts,
|
||||||
// Its size is freshly populated again upon re-mounting, i.e. going from iframe->popup->iframe
|
// its size is freshly populated again upon re-mounting, i.e. going from iframe->popup->iframe
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (measuredDeviceSize) {
|
if (measuredDeviceSize) {
|
||||||
setMeasuredDeviceSize(measuredDeviceSize)
|
setMeasuredDeviceSize(measuredDeviceSize)
|
||||||
@@ -34,13 +38,34 @@ export const DeviceContainer: React.FC<{
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
typeof zoom === 'number' &&
|
typeof zoom === 'number' &&
|
||||||
typeof size.width === 'number' &&
|
typeof desiredSize.width === 'number' &&
|
||||||
typeof size.height === 'number'
|
typeof desiredSize.height === 'number' &&
|
||||||
|
typeof measuredDeviceSize.width === 'number' &&
|
||||||
|
typeof measuredDeviceSize.height === 'number'
|
||||||
) {
|
) {
|
||||||
const scaledWidth = size.width / zoom
|
|
||||||
const difference = scaledWidth - size.width
|
|
||||||
x = `${difference / 2}px`
|
|
||||||
margin = '0 auto'
|
margin = '0 auto'
|
||||||
|
const scaledDesiredWidth = desiredSize.width / zoom
|
||||||
|
const scaledDeviceWidth = measuredDeviceSize.width * zoom
|
||||||
|
const scaledDeviceDifferencePixels = scaledDesiredWidth - desiredSize.width
|
||||||
|
deviceIsLargerThanFrame = scaledDeviceWidth > outerFrameSize.width
|
||||||
|
|
||||||
|
if (deviceIsLargerThanFrame) {
|
||||||
|
if (zoom > 1) {
|
||||||
|
const differenceFromDeviceToFrame = measuredDeviceSize.width - outerFrameSize.width
|
||||||
|
if (differenceFromDeviceToFrame < 0) x = `${differenceFromDeviceToFrame / 2}px`
|
||||||
|
else x = '0'
|
||||||
|
} else {
|
||||||
|
x = '0'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (zoom >= 1) {
|
||||||
|
x = `${scaledDeviceDifferencePixels / 2}px`
|
||||||
|
} else {
|
||||||
|
const differenceFromDeviceToFrame = outerFrameSize.width - scaledDeviceWidth
|
||||||
|
x = `${differenceFromDeviceToFrame / 2}px`
|
||||||
|
margin = '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,21 +73,29 @@ export const DeviceContainer: React.FC<{
|
|||||||
let height = zoom ? `${100 / zoom}%` : '100%'
|
let height = zoom ? `${100 / zoom}%` : '100%'
|
||||||
|
|
||||||
if (breakpoint !== 'responsive') {
|
if (breakpoint !== 'responsive') {
|
||||||
width = `${size?.width / (typeof zoom === 'number' ? zoom : 1)}px`
|
width = `${desiredSize?.width / (typeof zoom === 'number' ? zoom : 1)}px`
|
||||||
height = `${size?.height / (typeof zoom === 'number' ? zoom : 1)}px`
|
height = `${desiredSize?.height / (typeof zoom === 'number' ? zoom : 1)}px`
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={deviceFrameRef}
|
ref={outerFrameRef}
|
||||||
style={{
|
style={{
|
||||||
height,
|
height: '100%',
|
||||||
margin,
|
width: '100%',
|
||||||
transform: `translate3d(${x}, 0, 0)`,
|
|
||||||
width,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
<div
|
||||||
|
ref={deviceFrameRef}
|
||||||
|
style={{
|
||||||
|
height,
|
||||||
|
margin,
|
||||||
|
transform: `translate3d(${x}, 0, 0)`,
|
||||||
|
width,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
/* eslint-disable no-shadow */
|
/* eslint-disable no-shadow */
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
type Intersect = [setNode: React.Dispatch<Element>, entry: IntersectionObserverEntry]
|
type Intersect = [
|
||||||
|
setNode: React.Dispatch<HTMLElement>,
|
||||||
|
entry: IntersectionObserverEntry,
|
||||||
|
node: HTMLElement,
|
||||||
|
]
|
||||||
|
|
||||||
export const useIntersect = (
|
export const useIntersect = (
|
||||||
{ root = null, rootMargin = '0px', threshold = 0 } = {},
|
{ root = null, rootMargin = '0px', threshold = 0 } = {},
|
||||||
@@ -33,5 +37,5 @@ export const useIntersect = (
|
|||||||
return () => currentObserver.disconnect()
|
return () => currentObserver.disconnect()
|
||||||
}, [node, disable])
|
}, [node, disable])
|
||||||
|
|
||||||
return [setNode, entry]
|
return [setNode, entry, node]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,13 @@ interface Resize {
|
|||||||
size?: Size
|
size?: Size
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useResize = (ref: React.MutableRefObject<HTMLElement>): Resize => {
|
export const useResize = (element: HTMLElement): Resize => {
|
||||||
const [size, setSize] = useState<Size>()
|
const [size, setSize] = useState<Size>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let observer: any // eslint-disable-line
|
let observer: any // eslint-disable-line
|
||||||
|
|
||||||
const { current: currentRef } = ref
|
if (element) {
|
||||||
|
|
||||||
if (currentRef) {
|
|
||||||
observer = new ResizeObserver((entries) => {
|
observer = new ResizeObserver((entries) => {
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
const {
|
const {
|
||||||
@@ -53,15 +51,15 @@ export const useResize = (ref: React.MutableRefObject<HTMLElement>): Resize => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(currentRef)
|
observer.observe(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (observer) {
|
if (observer) {
|
||||||
observer.unobserve(currentRef)
|
observer.unobserve(element)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [ref])
|
}, [element])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
size,
|
size,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Footer } from './globals/Footer.js'
|
|||||||
import { Header } from './globals/Header.js'
|
import { Header } from './globals/Header.js'
|
||||||
import { seed } from './seed/index.js'
|
import { seed } from './seed/index.js'
|
||||||
import {
|
import {
|
||||||
|
desktopBreakpoint,
|
||||||
mobileBreakpoint,
|
mobileBreakpoint,
|
||||||
pagesSlug,
|
pagesSlug,
|
||||||
postsSlug,
|
postsSlug,
|
||||||
@@ -25,7 +26,7 @@ export default buildConfigWithDefaults({
|
|||||||
// You can define any of these properties on a per collection or global basis
|
// You can define any of these properties on a per collection or global basis
|
||||||
// The Live Preview config cascades from the top down, properties are inherited from here
|
// The Live Preview config cascades from the top down, properties are inherited from here
|
||||||
url: formatLivePreviewURL,
|
url: formatLivePreviewURL,
|
||||||
breakpoints: [mobileBreakpoint],
|
breakpoints: [mobileBreakpoint, desktopBreakpoint],
|
||||||
collections: [pagesSlug, postsSlug, ssrPagesSlug, ssrAutosavePagesSlug],
|
collections: [pagesSlug, postsSlug, ssrPagesSlug, ssrAutosavePagesSlug],
|
||||||
globals: ['header', 'footer'],
|
globals: ['header', 'footer'],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,19 +8,29 @@ import {
|
|||||||
ensureAutoLoginAndCompilationIsDone,
|
ensureAutoLoginAndCompilationIsDone,
|
||||||
exactText,
|
exactText,
|
||||||
initPageConsoleErrorCatch,
|
initPageConsoleErrorCatch,
|
||||||
navigateToListCellLink,
|
|
||||||
saveDocAndAssert,
|
saveDocAndAssert,
|
||||||
} from '../helpers.js'
|
} from '../helpers.js'
|
||||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||||
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||||
import {
|
import {
|
||||||
|
ensureDeviceIsCentered,
|
||||||
|
ensureDeviceIsLeftAligned,
|
||||||
|
goToCollectionLivePreview,
|
||||||
|
goToDoc,
|
||||||
|
goToGlobalLivePreview,
|
||||||
|
selectLivePreviewBreakpoint,
|
||||||
|
selectLivePreviewZoom,
|
||||||
|
} from './helpers.js'
|
||||||
|
import {
|
||||||
|
desktopBreakpoint,
|
||||||
mobileBreakpoint,
|
mobileBreakpoint,
|
||||||
pagesSlug,
|
pagesSlug,
|
||||||
renderedPageTitleID,
|
renderedPageTitleID,
|
||||||
ssrAutosavePagesSlug,
|
ssrAutosavePagesSlug,
|
||||||
ssrPagesSlug,
|
ssrPagesSlug,
|
||||||
} from './shared.js'
|
} from './shared.js'
|
||||||
|
|
||||||
const filename = fileURLToPath(import.meta.url)
|
const filename = fileURLToPath(import.meta.url)
|
||||||
const dirname = path.dirname(filename)
|
const dirname = path.dirname(filename)
|
||||||
|
|
||||||
@@ -34,25 +44,6 @@ describe('Live Preview', () => {
|
|||||||
let ssrPagesURLUtil: AdminUrlUtil
|
let ssrPagesURLUtil: AdminUrlUtil
|
||||||
let ssrAutosavePostsURLUtil: AdminUrlUtil
|
let ssrAutosavePostsURLUtil: AdminUrlUtil
|
||||||
|
|
||||||
const goToDoc = async (page: Page, urlUtil: AdminUrlUtil) => {
|
|
||||||
await page.goto(urlUtil.list)
|
|
||||||
await page.waitForURL(urlUtil.list)
|
|
||||||
await navigateToListCellLink(page)
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToCollectionPreview = async (page: Page, urlUtil: AdminUrlUtil): Promise<void> => {
|
|
||||||
await goToDoc(page, urlUtil)
|
|
||||||
await page.goto(`${page.url()}/preview`)
|
|
||||||
await page.waitForURL(`**/preview`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToGlobalPreview = async (page: Page, slug: string): Promise<void> => {
|
|
||||||
const global = new AdminUrlUtil(serverURL, slug)
|
|
||||||
const previewURL = `${global.global(slug)}/preview`
|
|
||||||
await page.goto(previewURL)
|
|
||||||
await page.waitForURL(previewURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
;({ serverURL } = await initPayloadE2ENoConfig({ dirname }))
|
;({ serverURL } = await initPayloadE2ENoConfig({ dirname }))
|
||||||
@@ -88,18 +79,18 @@ describe('Live Preview', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('collection — has route', async () => {
|
test('collection — has route', async () => {
|
||||||
await goToCollectionPreview(page, pagesURLUtil)
|
await goToCollectionLivePreview(page, pagesURLUtil)
|
||||||
await expect(page.locator('.live-preview')).toBeVisible()
|
await expect(page.locator('.live-preview')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('collection — renders iframe', async () => {
|
test('collection — renders iframe', async () => {
|
||||||
await goToCollectionPreview(page, pagesURLUtil)
|
await goToCollectionLivePreview(page, pagesURLUtil)
|
||||||
const iframe = page.locator('iframe.live-preview-iframe')
|
const iframe = page.locator('iframe.live-preview-iframe')
|
||||||
await expect(iframe).toBeVisible()
|
await expect(iframe).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('collection — re-renders iframe client-side when form state changes', async () => {
|
test('collection — re-renders iframe client-side when form state changes', async () => {
|
||||||
await goToCollectionPreview(page, pagesURLUtil)
|
await goToCollectionLivePreview(page, pagesURLUtil)
|
||||||
|
|
||||||
const titleField = page.locator('#field-title')
|
const titleField = page.locator('#field-title')
|
||||||
const frame = page.frameLocator('iframe.live-preview-iframe').first()
|
const frame = page.frameLocator('iframe.live-preview-iframe').first()
|
||||||
@@ -129,7 +120,7 @@ describe('Live Preview', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('collection ssr — re-render iframe when save is made', async () => {
|
test('collection ssr — re-render iframe when save is made', async () => {
|
||||||
await goToCollectionPreview(page, ssrPagesURLUtil)
|
await goToCollectionLivePreview(page, ssrPagesURLUtil)
|
||||||
|
|
||||||
const titleField = page.locator('#field-title')
|
const titleField = page.locator('#field-title')
|
||||||
const frame = page.frameLocator('iframe.live-preview-iframe').first()
|
const frame = page.frameLocator('iframe.live-preview-iframe').first()
|
||||||
@@ -159,7 +150,7 @@ describe('Live Preview', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('collection ssr — re-render iframe when autosave is made', async () => {
|
test('collection ssr — re-render iframe when autosave is made', async () => {
|
||||||
await goToCollectionPreview(page, ssrAutosavePostsURLUtil)
|
await goToCollectionLivePreview(page, ssrAutosavePostsURLUtil)
|
||||||
|
|
||||||
const titleField = page.locator('#field-title')
|
const titleField = page.locator('#field-title')
|
||||||
const frame = page.frameLocator('iframe.live-preview-iframe').first()
|
const frame = page.frameLocator('iframe.live-preview-iframe').first()
|
||||||
@@ -189,12 +180,12 @@ describe('Live Preview', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('collection — should show live-preview view-level action in live-preview view', async () => {
|
test('collection — should show live-preview view-level action in live-preview view', async () => {
|
||||||
await goToCollectionPreview(page, pagesURLUtil)
|
await goToCollectionLivePreview(page, pagesURLUtil)
|
||||||
await expect(page.locator('.app-header .collection-live-preview-button')).toHaveCount(1)
|
await expect(page.locator('.app-header .collection-live-preview-button')).toHaveCount(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('global — should show live-preview view-level action in live-preview view', async () => {
|
test('global — should show live-preview view-level action in live-preview view', async () => {
|
||||||
await goToGlobalPreview(page, 'footer')
|
await goToGlobalLivePreview(page, 'footer', serverURL)
|
||||||
await expect(page.locator('.app-header .global-live-preview-button')).toHaveCount(1)
|
await expect(page.locator('.app-header .global-live-preview-button')).toHaveCount(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -220,7 +211,7 @@ describe('Live Preview', () => {
|
|||||||
|
|
||||||
test('global — has route', async () => {
|
test('global — has route', async () => {
|
||||||
const url = page.url()
|
const url = page.url()
|
||||||
await goToGlobalPreview(page, 'header')
|
await goToGlobalLivePreview(page, 'header', serverURL)
|
||||||
|
|
||||||
await expect(() => expect(page.url()).toBe(`${url}/preview`)).toPass({
|
await expect(() => expect(page.url()).toBe(`${url}/preview`)).toPass({
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
timeout: POLL_TOPASS_TIMEOUT,
|
||||||
@@ -228,13 +219,13 @@ describe('Live Preview', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('global — renders iframe', async () => {
|
test('global — renders iframe', async () => {
|
||||||
await goToGlobalPreview(page, 'header')
|
await goToGlobalLivePreview(page, 'header', serverURL)
|
||||||
const iframe = page.locator('iframe.live-preview-iframe')
|
const iframe = page.locator('iframe.live-preview-iframe')
|
||||||
await expect(iframe).toBeVisible()
|
await expect(iframe).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('global — can edit fields', async () => {
|
test('global — can edit fields', async () => {
|
||||||
await goToGlobalPreview(page, 'header')
|
await goToGlobalLivePreview(page, 'header', serverURL)
|
||||||
const field = page.locator('input#field-navItems__0__link__newTab') //field-navItems__0__link__newTab
|
const field = page.locator('input#field-navItems__0__link__newTab') //field-navItems__0__link__newTab
|
||||||
await expect(field).toBeVisible()
|
await expect(field).toBeVisible()
|
||||||
await expect(field).toBeEnabled()
|
await expect(field).toBeEnabled()
|
||||||
@@ -242,14 +233,14 @@ describe('Live Preview', () => {
|
|||||||
await saveDocAndAssert(page)
|
await saveDocAndAssert(page)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('properly measures iframe and displays size', async () => {
|
test('device — properly measures size', async () => {
|
||||||
await page.goto(pagesURLUtil.create)
|
await page.goto(pagesURLUtil.create)
|
||||||
await page.waitForURL(pagesURLUtil.create)
|
await page.waitForURL(pagesURLUtil.create)
|
||||||
await page.locator('#field-title').fill('Title 3')
|
await page.locator('#field-title').fill('Title 3')
|
||||||
await page.locator('#field-slug').fill('slug-3')
|
await page.locator('#field-slug').fill('slug-3')
|
||||||
|
|
||||||
await saveDocAndAssert(page)
|
await saveDocAndAssert(page)
|
||||||
await goToCollectionPreview(page, pagesURLUtil)
|
await goToCollectionLivePreview(page, pagesURLUtil)
|
||||||
|
|
||||||
const iframe = page.locator('iframe')
|
const iframe = page.locator('iframe')
|
||||||
|
|
||||||
@@ -291,37 +282,16 @@ describe('Live Preview', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('resizes iframe to specified breakpoint', async () => {
|
test('device — resizes to specified breakpoint', async () => {
|
||||||
await page.goto(pagesURLUtil.create)
|
await page.goto(pagesURLUtil.create)
|
||||||
await page.waitForURL(pagesURLUtil.create)
|
await page.waitForURL(pagesURLUtil.create)
|
||||||
await page.locator('#field-title').fill('Title 4')
|
await page.locator('#field-title').fill('Title 4')
|
||||||
await page.locator('#field-slug').fill('slug-4')
|
await page.locator('#field-slug').fill('slug-4')
|
||||||
|
|
||||||
await saveDocAndAssert(page)
|
await saveDocAndAssert(page)
|
||||||
await goToCollectionPreview(page, pagesURLUtil)
|
await goToCollectionLivePreview(page, pagesURLUtil)
|
||||||
|
|
||||||
// Check that the breakpoint select is present
|
await selectLivePreviewBreakpoint(page, mobileBreakpoint.label)
|
||||||
const breakpointSelector = page.locator(
|
|
||||||
'.live-preview-toolbar-controls__breakpoint button.popup-button',
|
|
||||||
)
|
|
||||||
|
|
||||||
await expect(() => expect(breakpointSelector).toBeTruthy()).toPass({
|
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Select the mobile breakpoint
|
|
||||||
await breakpointSelector.first().click()
|
|
||||||
await page
|
|
||||||
.locator(`.live-preview-toolbar-controls__breakpoint button.popup-button-list__button`)
|
|
||||||
.filter({ hasText: mobileBreakpoint.label })
|
|
||||||
.click()
|
|
||||||
|
|
||||||
// Make sure the value has been set
|
|
||||||
await expect(breakpointSelector).toContainText(mobileBreakpoint.label)
|
|
||||||
const option = page.locator(
|
|
||||||
'.live-preview-toolbar-controls__breakpoint button.popup-button-list__button--selected',
|
|
||||||
)
|
|
||||||
await expect(option).toHaveText(mobileBreakpoint.label)
|
|
||||||
|
|
||||||
// Measure the size of the iframe against the specified breakpoint
|
// Measure the size of the iframe against the specified breakpoint
|
||||||
const iframe = page.locator('iframe')
|
const iframe = page.locator('iframe')
|
||||||
@@ -382,4 +352,34 @@ describe('Live Preview', () => {
|
|||||||
timeout: POLL_TOPASS_TIMEOUT,
|
timeout: POLL_TOPASS_TIMEOUT,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('device — centers device when smaller than frame despite zoom', async () => {
|
||||||
|
await goToCollectionLivePreview(page, pagesURLUtil)
|
||||||
|
await selectLivePreviewBreakpoint(page, mobileBreakpoint.label)
|
||||||
|
await ensureDeviceIsCentered(page)
|
||||||
|
await selectLivePreviewZoom(page, '75%')
|
||||||
|
await ensureDeviceIsCentered(page)
|
||||||
|
await selectLivePreviewZoom(page, '50%')
|
||||||
|
await ensureDeviceIsCentered(page)
|
||||||
|
await selectLivePreviewZoom(page, '125%')
|
||||||
|
await ensureDeviceIsCentered(page)
|
||||||
|
await selectLivePreviewZoom(page, '200%')
|
||||||
|
await ensureDeviceIsCentered(page)
|
||||||
|
expect(true).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('device — left-aligns device when larger than frame despite zoom', async () => {
|
||||||
|
await goToCollectionLivePreview(page, pagesURLUtil)
|
||||||
|
await selectLivePreviewBreakpoint(page, desktopBreakpoint.label)
|
||||||
|
await ensureDeviceIsLeftAligned(page)
|
||||||
|
await selectLivePreviewZoom(page, '75%')
|
||||||
|
await ensureDeviceIsLeftAligned(page)
|
||||||
|
await selectLivePreviewZoom(page, '50%')
|
||||||
|
await ensureDeviceIsLeftAligned(page)
|
||||||
|
await selectLivePreviewZoom(page, '125%')
|
||||||
|
await ensureDeviceIsLeftAligned(page)
|
||||||
|
await selectLivePreviewZoom(page, '200%')
|
||||||
|
await ensureDeviceIsLeftAligned(page)
|
||||||
|
expect(true).toBeTruthy()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
122
test/live-preview/helpers.ts
Normal file
122
test/live-preview/helpers.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import type { Page } from '@playwright/test'
|
||||||
|
|
||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { exactText, navigateToListCellLink } from '../helpers.js'
|
||||||
|
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||||
|
import { POLL_TOPASS_TIMEOUT } from '../playwright.config.js'
|
||||||
|
|
||||||
|
export const goToDoc = async (page: Page, urlUtil: AdminUrlUtil) => {
|
||||||
|
await page.goto(urlUtil.list)
|
||||||
|
await page.waitForURL(urlUtil.list)
|
||||||
|
await navigateToListCellLink(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const goToCollectionLivePreview = async (
|
||||||
|
page: Page,
|
||||||
|
urlUtil: AdminUrlUtil,
|
||||||
|
): Promise<void> => {
|
||||||
|
await goToDoc(page, urlUtil)
|
||||||
|
await page.goto(`${page.url()}/preview`)
|
||||||
|
await page.waitForURL(`**/preview`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const goToGlobalLivePreview = async (
|
||||||
|
page: Page,
|
||||||
|
slug: string,
|
||||||
|
serverURL: string,
|
||||||
|
): Promise<void> => {
|
||||||
|
const global = new AdminUrlUtil(serverURL, slug)
|
||||||
|
const previewURL = `${global.global(slug)}/preview`
|
||||||
|
await page.goto(previewURL)
|
||||||
|
await page.waitForURL(previewURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const selectLivePreviewBreakpoint = async (page: Page, breakpointLabel: string) => {
|
||||||
|
const breakpointSelector = page.locator(
|
||||||
|
'.live-preview-toolbar-controls__breakpoint button.popup-button',
|
||||||
|
)
|
||||||
|
|
||||||
|
await expect(() => expect(breakpointSelector).toBeTruthy()).toPass({
|
||||||
|
timeout: POLL_TOPASS_TIMEOUT,
|
||||||
|
})
|
||||||
|
|
||||||
|
await breakpointSelector.first().click()
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator(`.live-preview-toolbar-controls__breakpoint button.popup-button-list__button`)
|
||||||
|
.filter({ hasText: breakpointLabel })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
await expect(breakpointSelector).toContainText(breakpointLabel)
|
||||||
|
|
||||||
|
const option = page.locator(
|
||||||
|
'.live-preview-toolbar-controls__breakpoint button.popup-button-list__button--selected',
|
||||||
|
)
|
||||||
|
|
||||||
|
await expect(option).toHaveText(breakpointLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const selectLivePreviewZoom = async (page: Page, zoomLabel: string) => {
|
||||||
|
const zoomSelector = page.locator('.live-preview-toolbar-controls__zoom button.popup-button')
|
||||||
|
|
||||||
|
await expect(() => expect(zoomSelector).toBeTruthy()).toPass({
|
||||||
|
timeout: POLL_TOPASS_TIMEOUT,
|
||||||
|
})
|
||||||
|
|
||||||
|
await zoomSelector.first().click()
|
||||||
|
|
||||||
|
const zoomOption = page.locator(
|
||||||
|
'.live-preview-toolbar-controls__zoom button.popup-button-list__button',
|
||||||
|
{
|
||||||
|
hasText: exactText(zoomLabel),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(zoomOption).toBeTruthy()
|
||||||
|
await zoomOption.click()
|
||||||
|
|
||||||
|
await expect(zoomSelector).toContainText(zoomLabel)
|
||||||
|
|
||||||
|
const option = page.locator(
|
||||||
|
'.live-preview-toolbar-controls__zoom button.popup-button-list__button--selected',
|
||||||
|
)
|
||||||
|
|
||||||
|
await expect(option).toHaveText(zoomLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ensureDeviceIsCentered = async (page: Page) => {
|
||||||
|
const main = page.locator('.live-preview-window__main')
|
||||||
|
const iframe = page.locator('iframe.live-preview-iframe')
|
||||||
|
const mainBoxAfterZoom = await main.boundingBox()
|
||||||
|
const iframeBoxAfterZoom = await iframe.boundingBox()
|
||||||
|
const distanceFromIframeLeftToMainLeftAfterZoom = Math.abs(
|
||||||
|
mainBoxAfterZoom?.x - iframeBoxAfterZoom?.x,
|
||||||
|
)
|
||||||
|
const distanceFromIFrameRightToMainRightAfterZoom = Math.abs(
|
||||||
|
mainBoxAfterZoom?.x +
|
||||||
|
mainBoxAfterZoom?.width -
|
||||||
|
iframeBoxAfterZoom?.x -
|
||||||
|
iframeBoxAfterZoom?.width,
|
||||||
|
)
|
||||||
|
await expect(() =>
|
||||||
|
expect(distanceFromIframeLeftToMainLeftAfterZoom).toBe(
|
||||||
|
distanceFromIFrameRightToMainRightAfterZoom,
|
||||||
|
),
|
||||||
|
).toPass({
|
||||||
|
timeout: POLL_TOPASS_TIMEOUT,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ensureDeviceIsLeftAligned = async (page: Page) => {
|
||||||
|
const main = page.locator('.live-preview-window__main > div')
|
||||||
|
const iframe = page.locator('iframe.live-preview-iframe')
|
||||||
|
const mainBoxAfterZoom = await main.boundingBox()
|
||||||
|
const iframeBoxAfterZoom = await iframe.boundingBox()
|
||||||
|
const distanceFromIframeLeftToMainLeftAfterZoom = Math.abs(
|
||||||
|
mainBoxAfterZoom?.x - iframeBoxAfterZoom?.x,
|
||||||
|
)
|
||||||
|
await expect(() => expect(distanceFromIframeLeftToMainLeftAfterZoom).toBe(0)).toPass({
|
||||||
|
timeout: POLL_TOPASS_TIMEOUT,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -15,4 +15,11 @@ export const mobileBreakpoint = {
|
|||||||
height: 667,
|
height: 667,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const desktopBreakpoint = {
|
||||||
|
label: 'Desktop',
|
||||||
|
name: 'desktop',
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
}
|
||||||
|
|
||||||
export const renderedPageTitleID = 'rendered-page-title'
|
export const renderedPageTitleID = 'rendered-page-title'
|
||||||
|
|||||||
Reference in New Issue
Block a user