E2e tests for the toolbar (#2709)

This PR adds some e2e tests for the toolbar.

Fixtures have been set up for the toolbar and style panel, and are
fairly barebones at the moment. Eventually each menu should have a
fixture associated with it, and all tests will use the class defined in
the fixtures file.

### Change Type

- [x] `tests` — Changes to any test code only[^2]

### Release Notes

- Add e2e tests for the toolbar
text-padding
Taha 2024-02-16 14:15:00 +00:00 zatwierdzone przez GitHub
rodzic c039d44f72
commit 7e673b5e37
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
16 zmienionych plików z 216 dodań i 48 usunięć

Wyświetl plik

@ -46,6 +46,7 @@ const config: PlaywrightTestConfig = {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
headless: true, // !process.env.CI,
video: 'retain-on-failure',
},
/* Configure projects for major browsers */

Wyświetl plik

@ -0,0 +1,21 @@
import { test as base } from '@playwright/test'
import { StylePanel } from './menus/StylePanel'
import { Toolbar } from './menus/Toolbar'
type Fixtures = {
toolbar: Toolbar
stylePanel: StylePanel
}
const test = base.extend<Fixtures>({
toolbar: async ({ page }, use) => {
const toolbar = new Toolbar(page)
await use(toolbar)
},
stylePanel: async ({ page }, use) => {
const stylePanel = new StylePanel(page)
await use(stylePanel)
},
})
export default test

Wyświetl plik

@ -0,0 +1,21 @@
import { Page } from '@playwright/test'
export class StylePanel {
readonly stylesArray: string[]
constructor(public readonly page: Page) {
this.page = page
this.stylesArray = [
'style.color',
'style.opacity',
'style.fill',
'style.dash',
'style.size',
'style.font',
'style.align',
]
}
getElement() {
return this.page.locator('[data-testid="style.panel"]')
}
}

Wyświetl plik

@ -0,0 +1,37 @@
import { Locator, Page, expect } from '@playwright/test'
export class Toolbar {
readonly toolLock: Locator
readonly moreToolsButton: Locator
readonly moreToolsPopover: Locator
readonly mobileStylesButton: Locator
readonly tools: { [key: string]: Locator }
readonly popOverTools: { [key: string]: Locator }
constructor(public readonly page: Page) {
this.page = page
this.toolLock = this.page.locator('[data-testid="tool-lock"]')
this.moreToolsButton = this.page.locator('[data-testid="tools.more-button"]')
this.moreToolsPopover = this.page.locator('[data-testid="tools.more-content"]')
this.mobileStylesButton = this.page.locator('[data-testid="mobile-styles.button"]')
this.tools = {
select: this.page.locator('[data-testid="tools.select"]'),
draw: this.page.locator('[data-testid="tools.draw"]'),
arrow: this.page.locator('[data-testid="tools.arrow"]'),
cloud: this.page.locator('[data-testid="tools.cloud"]'),
eraser: this.page.locator('[data-testid="tools.eraser"]'),
}
this.popOverTools = {
popoverCloud: this.page.locator('[data-testid="tools.more.cloud"]'),
popoverFrame: this.page.locator('[data-testid="tools.more.frame"]'),
}
}
async clickTool(tool: Locator) {
await tool.click()
}
async isSelected(tool: Locator, isSelected: boolean) {
// pseudo elements aren't exposed to the DOM, but we can check the color as a proxy
const expectedColor = isSelected ? 'rgb(255, 255, 255)' : 'rgb(46, 46, 46)'
await expect(tool).toHaveCSS('color', expectedColor)
}
}

Wyświetl plik

@ -0,0 +1,28 @@
import { expect } from '@playwright/test'
import { setup } from '../shared-e2e'
import test from './fixtures/fixtures'
test.describe('mobile ui', () => {
test.beforeEach(setup)
test('style panel opens and closes as expected', async ({
isMobile,
page,
toolbar,
stylePanel,
}) => {
test.skip(!isMobile, 'only run on mobile')
await expect(stylePanel.getElement()).toBeHidden()
await toolbar.mobileStylesButton.click()
await expect(stylePanel.getElement()).toBeVisible()
// clicking off the style panel should close it
page.mouse.click(200, 200)
await expect(stylePanel.getElement()).toBeHidden()
})
test('style menu button is disabled for the eraser tool', async ({ isMobile, toolbar }) => {
test.skip(!isMobile, 'only run on mobile')
const { eraser } = toolbar.tools
await eraser.click()
await expect(toolbar.mobileStylesButton).toBeDisabled()
})
})

Wyświetl plik

@ -1,5 +1,6 @@
import test, { Page, expect } from '@playwright/test'
import { getAllShapeTypes, setupPage } from '../shared-e2e'
import { expect } from '@playwright/test'
import { getAllShapeTypes, setup } from '../shared-e2e'
import test from './fixtures/fixtures'
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
@ -57,15 +58,9 @@ const draggableShapeCreators = [
const otherTools = [{ tool: 'select' }, { tool: 'eraser' }, { tool: 'laser' }]
let page: Page
test.describe('Shape Tools', () => {
test.beforeAll(async ({ browser }) => {
page = await browser.newPage()
await setupPage(page)
})
test('creates shapes with other tools', async () => {
test.beforeEach(setup)
test('creates shapes with other tools', async ({ toolbar, page }) => {
await page.keyboard.press('Control+a')
await page.keyboard.press('Backspace')
expect(await getAllShapeTypes(page)).toEqual([])
@ -73,17 +68,17 @@ test.describe('Shape Tools', () => {
for (const { tool } of otherTools) {
// Find and click the button
if (!(await page.getByTestId(`tools.${tool}`).isVisible())) {
if (!(await page.getByTestId(`tools.more`).isVisible())) {
if (!(await toolbar.moreToolsButton.isVisible())) {
throw Error(`Tool more is not visible`)
}
await page.getByTestId('tools.more').click()
await toolbar.moreToolsButton.click()
if (!(await page.getByTestId(`tools.more.${tool}`).isVisible())) {
throw Error(`Tool in more panel is not visible`)
}
await page.getByTestId(`tools.more.${tool}`).click()
await page.getByTestId(`tools.more`).click()
await toolbar.moreToolsButton.click()
}
if (!(await page.getByTestId(`tools.${tool}`).isVisible())) {
@ -102,7 +97,7 @@ test.describe('Shape Tools', () => {
}
})
test('creates shapes clickable tools', async () => {
test('creates shapes clickable tools', async ({ page, toolbar }) => {
await page.keyboard.press('v')
await page.keyboard.press('Control+a')
await page.keyboard.press('Backspace')
@ -111,9 +106,9 @@ test.describe('Shape Tools', () => {
for (const { tool, shape } of clickableShapeCreators) {
// Find and click the button
if (!(await page.getByTestId(`tools.${tool}`).isVisible())) {
await page.getByTestId('tools.more').click()
await toolbar.moreToolsButton.click()
await page.getByTestId(`tools.more.${tool}`).click()
await page.getByTestId('tools.more').click()
await toolbar.moreToolsButton.click()
}
await page.getByTestId(`tools.${tool}`).click()
@ -141,7 +136,7 @@ test.describe('Shape Tools', () => {
expect(await getAllShapeTypes(page)).toEqual([])
})
test('creates shapes with draggable tools', async () => {
test('creates shapes with draggable tools', async ({ page, toolbar }) => {
await page.keyboard.press('Control+a')
await page.keyboard.press('Backspace')
expect(await getAllShapeTypes(page)).toEqual([])
@ -149,9 +144,9 @@ test.describe('Shape Tools', () => {
for (const { tool, shape } of draggableShapeCreators) {
// Find and click the button
if (!(await page.getByTestId(`tools.${tool}`).isVisible())) {
await page.getByTestId('tools.more').click()
await toolbar.moreToolsButton.click()
await page.getByTestId(`tools.more.${tool}`).click()
await page.getByTestId('tools.more').click()
await toolbar.moreToolsButton.click()
}
await page.getByTestId(`tools.${tool}`).click()

Wyświetl plik

@ -1,6 +1,7 @@
import test, { expect } from '@playwright/test'
import { expect } from '@playwright/test'
import { Editor, TLGeoShape } from '@tldraw/tldraw'
import { getAllShapeTypes, setup } from '../shared-e2e'
import test from './fixtures/fixtures'
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
@ -57,7 +58,7 @@ test.describe('smoke tests', () => {
expect(await page.getByTestId('quick-actions.redo').isDisabled()).toBe(true)
})
test('style panel + undo and redo squashing', async ({ page }) => {
test('style panel + undo and redo squashing', async ({ page, toolbar }) => {
await page.keyboard.press('r')
await page.mouse.move(100, 100)
await page.mouse.down()
@ -71,12 +72,11 @@ test.describe('smoke tests', () => {
expect(await getSelectedShapeColor()).toBe('black')
// when on a mobile device...
const mobileStylesButton = page.getByTestId('mobile.styles')
const hasMobileMenu = await mobileStylesButton.isVisible()
const hasMobileMenu = await toolbar.mobileStylesButton.isVisible()
if (hasMobileMenu) {
// open the style menu
await page.getByTestId('mobile.styles').click()
await toolbar.mobileStylesButton.click()
}
// Click the light-blue color

Wyświetl plik

@ -0,0 +1,76 @@
import { expect } from '@playwright/test'
import { setup } from '../shared-e2e'
import test from './fixtures/fixtures'
test.describe('when selecting a tool from the toolbar', () => {
test.beforeEach(setup)
test('tool selection behaviors', async ({ toolbar }) => {
const { select, draw, arrow, cloud } = toolbar.tools
const { popoverCloud } = toolbar.popOverTools
await test.step('selecting a tool changes the button color', async () => {
await select.click()
await toolbar.isSelected(select, true)
await toolbar.isSelected(draw, false)
await draw.click()
await toolbar.isSelected(select, false)
await toolbar.isSelected(draw, true)
})
await test.step('selecting certain tools exposes the tool-lock button', async () => {
await draw.click()
expect(toolbar.toolLock).toBeHidden()
await arrow.click()
expect(toolbar.toolLock).toBeVisible()
})
await test.step('selecting a tool from the popover makes it appear on toolbar', async () => {
await expect(cloud).toBeHidden()
await expect(toolbar.moreToolsPopover).toBeHidden()
await toolbar.moreToolsButton.click()
await expect(toolbar.moreToolsPopover).toBeVisible()
await popoverCloud.click()
await expect(toolbar.moreToolsPopover).toBeHidden()
await expect(cloud).toBeVisible()
})
})
test('the correct styles are exposed for the selected tool', async ({
isMobile,
page,
toolbar,
stylePanel,
}) => {
const toolsStylesArr = [
{
name: 'tools.select',
styles: ['style.color', 'style.opacity', 'style.fill', 'style.dash', 'style.size'],
},
{ name: 'tools.more.frame', styles: ['style.opacity'] },
{
name: 'tools.text',
styles: ['style.size', 'style.color', 'style.opacity', 'style.font', 'style.align'],
},
]
for (const tool of toolsStylesArr) {
await test.step(`Check tool ${tool.name}`, async () => {
if (tool.name === 'tools.more.frame') {
await toolbar.moreToolsButton.click()
}
await page.getByTestId(tool.name).click()
if (isMobile) {
await toolbar.mobileStylesButton.click()
}
for (const style of stylePanel.stylesArray) {
const styleElement = page.getByTestId(style)
const isVisible = await styleElement.isVisible()
const isExpected = tool.styles.includes(style)
expect(isVisible).toBe(isExpected)
}
})
}
})
})

Wyświetl plik

@ -1,10 +0,0 @@
import test from '@playwright/test'
import { setup } from '../shared-e2e'
test.describe('ui', () => {
test.beforeEach(setup)
test('mobile style panel opens and closes when tapped', async ({ isMobile }) => {
test.skip(!isMobile, 'only run on mobile')
})
})

Wyświetl plik

@ -1,7 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"packages": [
"packages/*"
],
"packages": ["packages/*"],
"version": "2.0.0-beta.3"
}

Wyświetl plik

@ -51,10 +51,12 @@ export function MobileStylePanel() {
<TldrawUiPopoverTrigger>
<TldrawUiButton
type="tool"
data-testid="mobile-styles.button"
style={{
color: disableStylePanel ? 'var(--color-muted-1)' : currentColor,
}}
title={msg('style-panel.title')}
data-testid="mobile.styles"
disabled={disableStylePanel}
style={{ color: disableStylePanel ? 'var(--color-muted-1)' : currentColor }}
>
<TldrawUiButtonIcon
icon={disableStylePanel ? 'blob' : color?.type === 'mixed' ? 'mixed' : 'blob'}

Wyświetl plik

@ -299,6 +299,7 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
)}
</div>
<div
data-testid="page-menu.list"
className="tlui-page-menu__list tlui-menu__group"
style={{ height: ITEM_HEIGHT * pages.length + 4 }}
ref={rSortableContainer}
@ -312,7 +313,7 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
return isEditing ? (
<div
key={page.id + '_editing'}
data-testid={`page-menu-item-${page.id}`}
data-testid="page-menu.item"
className="tlui-page_menu__item__sortable"
style={{
zIndex: page.id === currentPage.id ? 888 : index,
@ -370,11 +371,7 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
)}
</div>
) : (
<div
key={page.id}
data-testid={`page-menu-item-${page.id}`}
className="tlui-page-menu__item"
>
<div key={page.id} data-testid="page-menu.item" className="tlui-page-menu__item">
<TldrawUiButton
type="normal"
className="tlui-page-menu__item__button"

Wyświetl plik

@ -136,6 +136,7 @@ function CommonStylePickerSet({
tabIndex={-1}
className="tlui-style-panel__section__common"
aria-label="style panel styles"
data-testid="style.panel"
>
{color === undefined ? null : (
<TldrawUiButtonPicker

Wyświetl plik

@ -150,7 +150,7 @@ export const DefaultToolbar = memo(function DefaultToolbar() {
title={msg('tool-panel.more')}
type="tool"
className="tlui-toolbar__overflow"
data-testid="tools.more"
data-testid="tools.more-button"
>
<TldrawUiButtonIcon icon="chevron-up" />
</TldrawUiButton>
@ -181,7 +181,7 @@ const OverflowToolsContent = track(function OverflowToolsContent({
const msg = useTranslation()
return (
<div className="tlui-buttons__grid">
<div className="tlui-buttons__grid" data-testid="tools.more-content">
{toolbarItems.map(({ toolItem: { id, meta, kbd, label, onSelect, icon } }) => {
return (
<TldrawUiDropdownMenuItem

Wyświetl plik

@ -36,6 +36,7 @@ export function ToggleToolLockedButton({ activeToolId }: ToggleToolLockedButtonP
<TldrawUiButton
type="normal"
title={msg('action.toggle-tool-lock')}
data-testid="tool-lock"
className={classNames('tlui-toolbar__lock-button', {
'tlui-toolbar__lock-button__mobile': breakpoint < PORTRAIT_BREAKPOINT.TABLET_SM,
})}

Wyświetl plik

@ -99,7 +99,7 @@ export const TldrawUiButtonPicker = memo(function TldrawUiButtonPicker<T extends
)
return (
<div className={classNames('tlui-buttons__grid')}>
<div data-testid={`style.${uiType}`} className={classNames('tlui-buttons__grid')}>
{items.map((item) => (
<TldrawUiButton
type="icon"