Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
563 lines
18 KiB
TypeScript
563 lines
18 KiB
TypeScript
import { expect } from '@playwright/test'
|
|
import { type Editor } from 'tldraw'
|
|
import test from '../fixtures/fixtures'
|
|
import { setupOrReset, sleep } from '../shared-e2e'
|
|
|
|
declare const editor: Editor
|
|
|
|
test.describe('Rich text behaviour', () => {
|
|
test.beforeEach(setupOrReset)
|
|
test.beforeEach(async ({ page, toolbar, isMobile }) => {
|
|
// TODO: the mobile e2e test doesn't have the virtual keyboard at the moment.
|
|
if (isMobile) return
|
|
|
|
const { rectangle } = toolbar.tools
|
|
await rectangle.click()
|
|
await page.getByTestId('style.font.mono').first().click()
|
|
|
|
await page.mouse.click(150, 150)
|
|
await page.mouse.click(150, 150)
|
|
|
|
const isEditing = await page.evaluate(
|
|
() => editor.getEditingShapeId() === editor.getOnlySelectedShape()?.id
|
|
)
|
|
expect(isEditing).toBe(true)
|
|
|
|
// Wait for the toolbar to animate in.
|
|
await page.waitForTimeout(150)
|
|
|
|
// Type into the rich text editor.
|
|
const text = 'Hello, world!'
|
|
await page.keyboard.type(text)
|
|
|
|
// Select all the text.
|
|
const locator = page.getByTestId('rich-text-area').getByText('Hello, world!')
|
|
await locator.selectText()
|
|
|
|
await page.waitForTimeout(150)
|
|
})
|
|
|
|
test('selecting a style changes the style of the text', async ({
|
|
page,
|
|
richTextToolbar,
|
|
isMobile,
|
|
}) => {
|
|
// TODO: the mobile e2e test doesn't have the virtual keyboard at the moment.
|
|
if (isMobile) return
|
|
|
|
const toolsForHTMLStyle = [
|
|
{ name: 'bold', tag: 'strong' },
|
|
{ name: 'italic', tag: 'em' },
|
|
// // { name: 'strike', tag: 's' },
|
|
{ name: 'highlight', tag: 'mark' },
|
|
{ name: 'code', tag: 'code' },
|
|
// // Link is tested separately
|
|
// { name: 'heading', tag: 'h3' },
|
|
{ name: 'bulletList', tag: 'ul' },
|
|
]
|
|
|
|
for (const tool of toolsForHTMLStyle) {
|
|
await richTextToolbar.clickTool(richTextToolbar.tools[tool.name])
|
|
await page.waitForTimeout(33)
|
|
await richTextToolbar.isSelected(richTextToolbar.tools[tool.name])
|
|
|
|
// Find the contenteditable on the page and check the `tag`
|
|
const isTagEnabled = await page.evaluate(
|
|
async (toolTag) => {
|
|
const richTextArea = document.querySelector('[data-testid="rich-text-area"]')?.innerHTML
|
|
const renderedText = document.querySelector('.tl-rich-text')?.innerHTML
|
|
// Check tool.tag is in the innerHTML of the richTextArea and renderedText.
|
|
return richTextArea?.includes(`<${toolTag}`) && renderedText?.includes(`<${toolTag}`)
|
|
},
|
|
[tool.tag]
|
|
)
|
|
expect(isTagEnabled).toBe(true)
|
|
|
|
// Turn it off for the next thing to be tested.
|
|
await richTextToolbar.clickTool(richTextToolbar.tools[tool.name])
|
|
}
|
|
})
|
|
|
|
test('keeps text visible when ProseMirror hides the selection', async ({ page, isMobile }) => {
|
|
// TODO: the mobile e2e test doesn't have the virtual keyboard at the moment.
|
|
if (isMobile) return
|
|
|
|
// Triple clicking a list's bullet gives ProseMirror a NodeSelection over the whole list. It
|
|
// then hides the native selection, and our ::selection rule would go on painting the text in
|
|
// the selected-contrast color with no highlight behind it, leaving it invisible.
|
|
await page.keyboard.type('- aeouaeou')
|
|
await page.keyboard.press('Enter')
|
|
await page.keyboard.type('second item')
|
|
await sleep(150)
|
|
|
|
const item = page.locator('.ProseMirror ul > li').first()
|
|
const box = (await item.boundingBox())!
|
|
await page.mouse.click(box.x - 4, box.y + box.height / 2, { clickCount: 3 })
|
|
await sleep(250)
|
|
|
|
const state = await page.evaluate(() => {
|
|
// Which ::selection rules that set a text color still apply to the editor right now?
|
|
// Reading it off the live stylesheet rather than a hard-coded selector, so the test
|
|
// keeps meaning what it says if the rule is rewritten.
|
|
const input = document.querySelector('.tl-text-input')!
|
|
const colouring: string[] = []
|
|
for (const sheet of Array.from(document.styleSheets)) {
|
|
let rules
|
|
try {
|
|
rules = sheet.cssRules
|
|
} catch {
|
|
continue // cross-origin sheet
|
|
}
|
|
for (const rule of Array.from(rules) as CSSStyleRule[]) {
|
|
if (!rule.selectorText?.includes('::selection')) continue
|
|
if (!rule.style?.color) continue
|
|
const originating = rule.selectorText.replaceAll('::selection', '')
|
|
if (input.matches(originating)) colouring.push(rule.selectorText)
|
|
}
|
|
}
|
|
return {
|
|
selection: editor.getRichTextEditor()!.state.selection.constructor.name,
|
|
hidden: !!document.querySelector('.ProseMirror.ProseMirror-hideselection'),
|
|
colouring,
|
|
}
|
|
})
|
|
|
|
expect(state.selection).toBe('NodeSelection')
|
|
expect(state.hidden).toBe(true)
|
|
// With no highlight to sit behind it, nothing may recolor the text.
|
|
expect(state.colouring).toEqual([])
|
|
})
|
|
|
|
test('adding and removing a link', async ({ page, toolbar, richTextToolbar, isMobile }) => {
|
|
// TODO: the mobile e2e test doesn't have the virtual keyboard at the moment.
|
|
if (isMobile) return
|
|
|
|
const { rectangle } = toolbar.tools
|
|
await richTextToolbar.clickTool(richTextToolbar.tools.link)
|
|
await page.keyboard.type('example.com')
|
|
await page.keyboard.press('Enter')
|
|
|
|
// Click away to get out of edit mode.
|
|
await page.mouse.click(150, 400)
|
|
await rectangle.click()
|
|
await page.mouse.click(150, 400)
|
|
await page.waitForTimeout(150)
|
|
|
|
// Check the link has been rendered.
|
|
// We could test this without clicking away but I want to
|
|
// test that the link is still there after clicking away.
|
|
const isLinkSet = await page.evaluate(async () => {
|
|
return !!document.querySelector('.tl-rich-text a[href="https://example.com"]')
|
|
})
|
|
expect(isLinkSet).toBe(true)
|
|
|
|
// Now, let's test removing the link.
|
|
// Click on the shape to go into edit mode again.
|
|
await page.mouse.dblclick(250, 200)
|
|
await page.waitForTimeout(150)
|
|
|
|
const locator = page.getByTestId('rich-text-area').getByText('Hello, world!')
|
|
await locator.selectText()
|
|
|
|
// Make the toolbar show up (isMousingAround needs to be true)
|
|
await page.mouse.move(100, 100)
|
|
await page.waitForTimeout(150)
|
|
|
|
await richTextToolbar.clickTool(richTextToolbar.tools.link)
|
|
await page.waitForTimeout(150)
|
|
await richTextToolbar.clickTool(richTextToolbar.tools.linkRemove)
|
|
|
|
const isLinkRemoved = await page.evaluate(async () => {
|
|
return !document.querySelector('.tl-rich-text a[href="https://example.com"]')
|
|
})
|
|
expect(isLinkRemoved).toBe(true)
|
|
})
|
|
|
|
test('clicking outside the link editor saves the link', async ({
|
|
page,
|
|
toolbar,
|
|
richTextToolbar,
|
|
isMobile,
|
|
}) => {
|
|
// TODO: the mobile e2e test doesn't have the virtual keyboard at the moment.
|
|
if (isMobile) return
|
|
|
|
const { rectangle } = toolbar.tools
|
|
await richTextToolbar.clickTool(richTextToolbar.tools.link)
|
|
await page.keyboard.type('example.com')
|
|
|
|
// Click away to blur the link input (instead of pressing Enter)
|
|
await page.mouse.click(150, 400)
|
|
await rectangle.click()
|
|
await page.mouse.click(150, 400)
|
|
await page.waitForTimeout(150)
|
|
|
|
// Check the link has been saved despite not pressing Enter
|
|
const isLinkSet = await page.evaluate(async () => {
|
|
return !!document.querySelector('.tl-rich-text a[href="https://example.com"]')
|
|
})
|
|
expect(isLinkSet).toBe(true)
|
|
})
|
|
|
|
// Text selection (the auto select all) doesn't seem to be working in our playwright tests
|
|
test('shows the toolbar when double clicking a shape to select all', async ({
|
|
page,
|
|
richTextToolbar,
|
|
isMobile,
|
|
}) => {
|
|
if (isMobile) return
|
|
|
|
await page.keyboard.press('Escape')
|
|
await page.keyboard.press('Escape')
|
|
await page.mouse.click(550, 550)
|
|
|
|
// bar should not be visible when nothing is selected
|
|
|
|
await sleep(200)
|
|
await expect(richTextToolbar.container).toHaveCount(0)
|
|
|
|
await page.mouse.move(150, 150)
|
|
await page.mouse.dblclick(150, 150)
|
|
|
|
// bar should be visible (all of the text should be selected)
|
|
await sleep(200)
|
|
await expect(richTextToolbar.container).toHaveCount(1)
|
|
await expect(richTextToolbar.container).toHaveAttribute('data-visible', 'true')
|
|
|
|
await page.keyboard.type('alex chilton')
|
|
|
|
expect(page.getByTestId('rich-text-area')).toHaveText('alex chilton')
|
|
|
|
// bar should not be visible when nothing is selected
|
|
|
|
await sleep(200)
|
|
await expect(richTextToolbar.container).toHaveCount(1)
|
|
await expect(richTextToolbar.container).toHaveAttribute('data-visible', 'false')
|
|
|
|
await page.keyboard.press('Escape')
|
|
await page.keyboard.press('Escape')
|
|
|
|
// bar should not be visible when nothing is selected / not editing
|
|
|
|
await sleep(400)
|
|
await expect(richTextToolbar.container).toHaveCount(0)
|
|
|
|
await page.mouse.move(150, 150)
|
|
await page.mouse.click(150, 150)
|
|
await page.mouse.click(150, 150)
|
|
|
|
// bar should be visible (all of the text should be selected)
|
|
await sleep(200)
|
|
await expect(richTextToolbar.container).toHaveCount(1)
|
|
await expect(richTextToolbar.container).toHaveAttribute('data-visible', 'true')
|
|
|
|
await page.keyboard.type('big star')
|
|
|
|
expect(page.getByTestId('rich-text-area')).toHaveText('big star')
|
|
|
|
await sleep(200)
|
|
await expect(richTextToolbar.container).toHaveCount(1)
|
|
await expect(richTextToolbar.container).toHaveAttribute('data-visible', 'false')
|
|
})
|
|
|
|
// ...but it does seem to work with the enter key
|
|
test('shows the toolbar when pressing enter on a shape to select all', async ({
|
|
page,
|
|
isMobile,
|
|
richTextToolbar,
|
|
}) => {
|
|
if (isMobile) return
|
|
|
|
await page.keyboard.press('Escape')
|
|
await page.keyboard.press('Escape')
|
|
|
|
// bar should not be visible when nothing is selected
|
|
await sleep(200)
|
|
await expect(richTextToolbar.container).toHaveCount(0)
|
|
|
|
await page.mouse.click(150, 150) // select the shape
|
|
await page.locator('.tl-container').first().focus() // explicitly focus the container
|
|
await page.keyboard.press('Enter') // to start editing the shape
|
|
|
|
// bar should be visible (all of the text should be selected)
|
|
await sleep(200)
|
|
await expect(richTextToolbar.container).toHaveAttribute('data-visible', 'true')
|
|
|
|
await page.keyboard.type('alex chilton')
|
|
|
|
expect(page.getByTestId('rich-text-area')).toHaveText('alex chilton')
|
|
await expect(richTextToolbar.container).toHaveAttribute('data-visible', 'false')
|
|
|
|
await sleep(200)
|
|
await expect(richTextToolbar.container).toHaveCount(1)
|
|
|
|
// deselect the shape
|
|
await page.keyboard.press('Escape')
|
|
await page.keyboard.press('Escape')
|
|
await page.keyboard.press('Escape')
|
|
await sleep(200)
|
|
|
|
// select it and start editing it again
|
|
await page.mouse.click(150, 150)
|
|
await page.locator('.tl-container').first().focus() // explicitly focus the container
|
|
await page.keyboard.press('Enter')
|
|
|
|
// bar should be visible (all of the text should be selected)
|
|
await sleep(200)
|
|
await expect(richTextToolbar.container).toHaveAttribute('data-visible', 'true')
|
|
|
|
await page.keyboard.type('big star')
|
|
|
|
expect(page.getByTestId('rich-text-area')).toHaveText('big star')
|
|
|
|
// bar should not be visible when nothing is selected
|
|
|
|
await sleep(200)
|
|
await expect(richTextToolbar.container).toHaveAttribute('data-visible', 'false')
|
|
})
|
|
|
|
test('positions correctly with selected text', async ({ isMobile, page, richTextToolbar }) => {
|
|
if (isMobile) return // can't test this on mobile
|
|
|
|
const PARA1 = 'whats that song?'
|
|
const PARA2 = 'Im in love with that song'
|
|
|
|
await page.keyboard.type(PARA1)
|
|
await sleep(200)
|
|
await page.keyboard.press('Enter')
|
|
await page.keyboard.type(PARA2)
|
|
await sleep(200)
|
|
|
|
await expect(richTextToolbar.container).toHaveAttribute('data-visible', 'false')
|
|
|
|
// selects an empty space between "in" and "love"
|
|
await page.mouse.dblclick(150, 150)
|
|
|
|
// takes a moment for the toolbar to show up
|
|
await sleep(200)
|
|
await expect(richTextToolbar.container).toHaveAttribute('data-visible', 'true')
|
|
|
|
const originalSelectedTextBounds = await richTextToolbar.getSelectionBounds()
|
|
|
|
{
|
|
// Expect it to be above the currently selected text (the second paragraph)
|
|
const toolbarRect = await page.getByTestId('rich-text.bold').boundingBox()
|
|
expect(Math.round(toolbarRect!.y)).toBe(
|
|
Math.round(originalSelectedTextBounds!.y - toolbarRect!.height - 8)
|
|
)
|
|
}
|
|
|
|
// Select the next line too
|
|
await page.keyboard.down('Shift')
|
|
await page.keyboard.press('ArrowUp')
|
|
await page.keyboard.press('ArrowUp')
|
|
|
|
// There's a delay here before the toolbar position moves
|
|
|
|
{
|
|
// Even though we're now selecting both paragraphs, the toolbar should still
|
|
// be above the second paragraph because the timer hasn't expired
|
|
const toolbarRect = await page.getByTestId('rich-text.bold').boundingBox()
|
|
expect(Math.round(toolbarRect!.y)).toBe(
|
|
Math.round(originalSelectedTextBounds!.y - toolbarRect!.height - 8)
|
|
)
|
|
}
|
|
|
|
// Let's test to see if the timer works by moving the second back to where it was.
|
|
await sleep(100)
|
|
|
|
// And go down instead
|
|
await page.keyboard.press('ArrowDown')
|
|
await page.keyboard.press('ArrowDown')
|
|
await page.keyboard.press('ArrowDown')
|
|
await page.keyboard.press('ArrowDown')
|
|
|
|
// Wait for the delay to expire
|
|
await sleep(500)
|
|
|
|
{
|
|
// Expect it to be above the currently selected text (the second paragraph)
|
|
const toolbarRect = await page.getByTestId('rich-text.bold').boundingBox()
|
|
const selectedTextRect = await richTextToolbar.getSelectionBounds()
|
|
expect(Math.round(toolbarRect!.y)).toBe(
|
|
Math.round(selectedTextRect!.y - toolbarRect!.height - 8)
|
|
)
|
|
}
|
|
|
|
// Go all the way back up again and wait for the delay
|
|
await page.keyboard.press('ArrowUp')
|
|
await page.keyboard.press('ArrowUp')
|
|
await page.keyboard.press('ArrowUp')
|
|
|
|
// Let the delay expire again
|
|
await sleep(500)
|
|
|
|
{
|
|
// now the toolbar should be above the first paragraph
|
|
const toolbarRect = await page.getByTestId('rich-text.bold').boundingBox()
|
|
const selectedTextRect = await richTextToolbar.getSelectionBounds()
|
|
expect(Math.round(toolbarRect!.y)).toBe(
|
|
Math.round(selectedTextRect!.y - toolbarRect!.height - 8)
|
|
)
|
|
}
|
|
})
|
|
|
|
test('positions correctly when rotated', async ({ isMobile, page, richTextToolbar }) => {
|
|
if (isMobile) return // can't test this on mobile
|
|
|
|
const PARA1 = 'Never traveled far'
|
|
const PARA2 = 'Without a little star'
|
|
|
|
await page.keyboard.type(PARA1)
|
|
await page.keyboard.press('Enter')
|
|
await page.keyboard.type(PARA2)
|
|
|
|
// Deselct the shape
|
|
await page.keyboard.press('Escape')
|
|
|
|
// Rotate the shape
|
|
await page.getByTestId('actions-menu.button').click()
|
|
await page.getByTestId('actions-menu.rotate-cw').click()
|
|
await page.mouse.click(300, 300)
|
|
|
|
await page.keyboard.press('Escape')
|
|
|
|
await sleep(150)
|
|
|
|
// Select all text
|
|
await page.mouse.dblclick(150, 150)
|
|
await sleep(150)
|
|
|
|
// Select two lines at the end of the text
|
|
await page.keyboard.press('ArrowRight')
|
|
await page.keyboard.down('Shift')
|
|
await page.keyboard.press('ArrowUp')
|
|
|
|
await sleep(500)
|
|
|
|
// The toolbar should be just above the rotated selected text
|
|
const toolbarRect = await page.getByTestId('rich-text.bold').boundingBox()
|
|
const selectedTextRect = await richTextToolbar.getSelectionBounds()
|
|
expect(Math.round(toolbarRect!.y)).toBe(
|
|
Math.round(selectedTextRect!.y - toolbarRect!.height - 8)
|
|
)
|
|
|
|
// historically this has been flaky without the sleep
|
|
await sleep(2000)
|
|
})
|
|
|
|
test('hides and shows based on selection changes', async ({
|
|
isMobile,
|
|
page,
|
|
richTextToolbar,
|
|
}) => {
|
|
if (isMobile) return // can't test this on mobile
|
|
|
|
// clear selection
|
|
await page.keyboard.press('ArrowRight')
|
|
|
|
await sleep(200)
|
|
|
|
await expect(richTextToolbar.container).toHaveCSS('opacity', '0')
|
|
await expect(richTextToolbar.container).toHaveCSS('pointer-events', 'none')
|
|
|
|
await page.keyboard.down('Shift')
|
|
await page.keyboard.press('ArrowLeft')
|
|
await page.keyboard.up('Shift')
|
|
|
|
await sleep(200)
|
|
|
|
// now it should be visible and interactive
|
|
await expect(richTextToolbar.container).toHaveCSS('opacity', '1')
|
|
await expect(richTextToolbar.container).toHaveCSS('pointer-events', 'all')
|
|
|
|
// clear selection again
|
|
await page.keyboard.press('ArrowRight')
|
|
|
|
// should still be visible for a moment
|
|
await expect(richTextToolbar.container).toHaveCSS('opacity', '1')
|
|
// But not interactive
|
|
await expect(richTextToolbar.container).toHaveCSS('pointer-events', 'none')
|
|
|
|
// Without waiting, select the text again
|
|
await page.keyboard.down('Shift')
|
|
await page.keyboard.press('ArrowLeft')
|
|
await page.keyboard.up('Shift')
|
|
|
|
// Should be back to normal now
|
|
await expect(richTextToolbar.container).toHaveCSS('opacity', '1')
|
|
await expect(richTextToolbar.container).toHaveCSS('pointer-events', 'all')
|
|
|
|
// no changes...
|
|
await sleep(200)
|
|
|
|
await expect(richTextToolbar.container).toHaveCSS('opacity', '1')
|
|
await expect(richTextToolbar.container).toHaveCSS('pointer-events', 'all')
|
|
|
|
// clear selection again
|
|
await page.keyboard.press('ArrowRight')
|
|
|
|
await sleep(200)
|
|
|
|
// And gone gain after a delay
|
|
await expect(richTextToolbar.container).toHaveCSS('opacity', '0')
|
|
await expect(richTextToolbar.container).toHaveCSS('pointer-events', 'none')
|
|
})
|
|
|
|
test('hides and shows based on mouse down changes', async ({
|
|
isMobile,
|
|
page,
|
|
richTextToolbar,
|
|
}) => {
|
|
if (isMobile) return // can't test this on mobile
|
|
|
|
// Select the end of the text
|
|
await page.keyboard.press('ArrowRight')
|
|
await page.keyboard.down('Shift')
|
|
await page.keyboard.press('ArrowLeft')
|
|
await page.keyboard.up('Shift')
|
|
|
|
await sleep(200)
|
|
|
|
await expect(richTextToolbar.container).toHaveCSS('opacity', '1')
|
|
await expect(richTextToolbar.container).toHaveCSS('pointer-events', 'all')
|
|
|
|
// Now mouse down on the text
|
|
|
|
await page.mouse.move(100, 150)
|
|
await page.mouse.down()
|
|
|
|
await expect(richTextToolbar.container).toHaveCSS('opacity', '1')
|
|
await expect(richTextToolbar.container).toHaveCSS('pointer-events', 'none')
|
|
|
|
await sleep(200)
|
|
|
|
await expect(richTextToolbar.container).toHaveCSS('opacity', '0')
|
|
await expect(richTextToolbar.container).toHaveCSS('pointer-events', 'none')
|
|
|
|
await page.mouse.move(150, 150)
|
|
await page.mouse.up()
|
|
|
|
await expect(richTextToolbar.container).toHaveCSS('opacity', '0')
|
|
await expect(richTextToolbar.container).toHaveCSS('pointer-events', 'none')
|
|
|
|
await sleep(200)
|
|
|
|
await expect(richTextToolbar.container).toHaveCSS('opacity', '1')
|
|
await expect(richTextToolbar.container).toHaveCSS('pointer-events', 'all')
|
|
|
|
// historically this has been flaky without the sleep
|
|
await sleep(2000)
|
|
})
|
|
|
|
test.skip('positions correctly at the edges of the screen', async ({ isMobile }) => {
|
|
if (isMobile) return // can't test this on mobile
|
|
})
|
|
|
|
test.skip('positions correctly when shape size changes while selection is open', async ({
|
|
isMobile,
|
|
}) => {
|
|
if (isMobile) return // can't test this on mobile
|
|
})
|
|
})
|