Mounts live preview to `../:id` instead `../:id/preview`. This is a huge win for both UX and a maintainability standpoint. Here are just a few of those wins: 1. If you edit a document, _then_ decide you want to preview those changes, you are currently presented with the `LeaveWithoutSaving` modal and are forced to either save your edits or clear them. This is because you are being navigated to an entirely new page with it's own form context. Instead, you should be able to freely navigate back and forth between the two. 2. If you are an editor who most often uses Live Preview, or you are editing a collection that typically requires it, you likely want it to automatically enter live preview mode when you open a document. Currently, the user has to navigate to the document _first_, then use the live preview tab. Instead, you should be able to set a preference and avoid this extra step. 3. Since the inception of Live Preview, we've been maintaining largely the same code across the default edit view and the live preview view, which often became out of sync and inconsistent—but they're essentially doing the same thing. While we could abstract a lot of this out, it is no longer necessary if the two views are combined into one. This change does also include some small modifications to UI. The "Live Preview" tab no longer exists, and instead has been replaced with a button placed next to the document controls (subject to change). Before: https://github.com/user-attachments/assets/48518b02-87ba-4750-ba7b-b21b5c75240a After: https://github.com/user-attachments/assets/a8ec8657-a6d6-4ee1-b9a7-3c1173bcfa96
148 lines
4.4 KiB
TypeScript
148 lines
4.4 KiB
TypeScript
import type { Page } from '@playwright/test'
|
|
|
|
import { expect } from '@playwright/test'
|
|
|
|
import { exactText } from '../helpers.js'
|
|
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
|
import { navigateToDoc } from '../helpers/e2e/navigateToDoc.js'
|
|
import { POLL_TOPASS_TIMEOUT } from '../playwright.config.js'
|
|
|
|
export const toggleLivePreview = async (
|
|
page: Page,
|
|
options?: {
|
|
targetState?: 'off' | 'on'
|
|
},
|
|
): Promise<void> => {
|
|
const toggler = page.locator('#live-preview-toggler')
|
|
await expect(toggler).toBeVisible()
|
|
|
|
const isActive = await toggler.evaluate((el) =>
|
|
el.classList.contains('live-preview-toggler--active'),
|
|
)
|
|
|
|
if (isActive && (options?.targetState === 'off' || !options?.targetState)) {
|
|
await toggler.click()
|
|
await expect(toggler).not.toHaveClass(/live-preview-toggler--active/)
|
|
await expect(page.locator('iframe.live-preview-iframe')).toBeHidden()
|
|
}
|
|
|
|
if (!isActive && (options?.targetState === 'on' || !options?.targetState)) {
|
|
await toggler.click()
|
|
await expect(toggler).toHaveClass(/live-preview-toggler--active/)
|
|
await expect(page.locator('iframe.live-preview-iframe')).toBeVisible()
|
|
}
|
|
}
|
|
|
|
export const goToCollectionLivePreview = async (
|
|
page: Page,
|
|
urlUtil: AdminUrlUtil,
|
|
): Promise<void> => {
|
|
await navigateToDoc(page, urlUtil)
|
|
|
|
await toggleLivePreview(page, {
|
|
targetState: 'on',
|
|
})
|
|
}
|
|
|
|
export const goToGlobalLivePreview = async (
|
|
page: Page,
|
|
slug: string,
|
|
serverURL: string,
|
|
): Promise<void> => {
|
|
const globalUrlUtil = new AdminUrlUtil(serverURL, slug)
|
|
await page.goto(globalUrlUtil.global(slug))
|
|
|
|
await toggleLivePreview(page, {
|
|
targetState: 'on',
|
|
})
|
|
}
|
|
|
|
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,
|
|
})
|
|
}
|