1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-29 09:59:48 +02:00

Switch from cypress to playwright. (#5248)

* empty

* empty

* empty

* Begin move from cypress to playwright.

* Switch remaining tests to playwright, remove old cypress suppport files.

* Clean up playwright config

* Enable ff, and safari when on mac.

* Fix safari/ff mentions test

* Fix code-highlighting test on ff/safari

* Add a local retry as a few tests are flaky.

* Replace cypress w/ playwright in gitignore.

* Update to latest yarn to fix ci install?

* Update yarn.lock w/ yarn command.

* Fix mocha tests.

* Fix prettier
This commit is contained in:
Gary Borton
2023-01-13 19:36:04 -08:00
committed by GitHub
parent cb133a785f
commit 5dc4396f6b
53 changed files with 787 additions and 1058 deletions

View File

@@ -0,0 +1,50 @@
import { test, expect } from '@playwright/test'
test.describe('Check-lists example', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/check-lists')
})
test('checks the bullet when clicked', async ({ page }) => {
const slateNodeElement = 'div[data-slate-node="element"]'
expect(
await page
.locator(slateNodeElement)
.nth(3)
.textContent()
).toBe('Criss-cross!')
await expect(
page
.locator(slateNodeElement)
.nth(3)
.locator('span')
.nth(1)
).toHaveCSS('text-decoration-line', 'line-through')
// Unchecking the checkboxes should un-cross the corresponding text.
await page
.locator(slateNodeElement)
.nth(3)
.locator('span')
.nth(0)
.locator('input')
.uncheck()
expect(
await page
.locator(slateNodeElement)
.nth(3)
.textContent()
).toBe('Criss-cross!')
await expect(
page
.locator(slateNodeElement)
.nth(3)
.locator('span')
.nth(1)
).toHaveCSS('text-decoration-line', 'none')
expect(await page.locator('p[data-slate-node="element"]').count()).toBe(2)
})
})

View File

@@ -0,0 +1,57 @@
import { test, expect } from '@playwright/test'
test.describe('code highlighting', () => {
const slateEditor = '[data-slate-node="element"]'
const leafNode = 'span[data-slate-leaf="true"]'
test.beforeEach(async ({ page }) => {
page.goto('http://localhost:3000/examples/code-highlighting')
})
test('highlights HTML tags', async ({ page }) => {
const outer = page
.locator(slateEditor)
.locator('span')
.nth(0)
.locator(leafNode)
.nth(0)
await expect(await outer.textContent()).toContain('<h1>')
await expect(outer).toHaveCSS('color', 'rgb(153, 0, 85)')
})
test('highlights javascript syntax', async ({ page }) => {
const JSCode = 'const slateVar = 30;'
await page.locator('select').selectOption('JavaScript') // Select the 'JavaScript' option
await expect(await page.locator('select').inputValue()).toBe('js') // Confirm value to avoid race condition
await page.locator(slateEditor).click() // focus on the editor
const isMac = await page.evaluate(() => {
return /Mac|iPhone|iPod|iPad/i.test(navigator.platform)
})
if (isMac) {
await page.keyboard.press('Meta+A')
} else {
await page.keyboard.press('Control+A')
}
await page.keyboard.type(JSCode) // Type JavaScript code
await page.keyboard.press('Enter')
expect(
await page
.locator(slateEditor)
.locator('span')
.nth(0)
.locator(leafNode)
.nth(0)
.textContent()
).toContain('const')
await expect(
page
.locator(slateEditor)
.locator('span')
.nth(0)
.locator(leafNode)
.nth(0)
).toHaveCSS('color', 'rgb(0, 119, 170)')
})
})

View File

@@ -0,0 +1,39 @@
import { test, expect } from '@playwright/test'
test.describe('editable voids', () => {
const input = 'input[type="text"]'
const elements = [
{ tag: 'h4', count: 3 },
{ tag: input, count: 1 },
{ tag: 'input[type="radio"]', count: 2 },
]
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/editable-voids')
})
test('checks for the elements', async ({ page }) => {
for (const elem of elements) {
const { tag, count } = elem
expect(await page.locator(tag).count()).toBe(count)
}
})
test('should double the elements', async ({ page }) => {
// click the `+` sign to duplicate the editable void
await page
.locator('span.material-icons')
.nth(1)
.click()
for (const elem of elements) {
const { tag, count } = elem
expect(await page.locator(tag).count()).toBe(count * 2)
}
})
test('make sure you can edit editable void', async ({ page }) => {
await page.locator(input).type('Typing')
expect(await page.locator(input).inputValue()).toBe('Typing')
})
})

View File

@@ -0,0 +1,18 @@
import { test, expect } from '@playwright/test'
test.describe('embeds example', () => {
const slateEditor = 'div[data-slate-editor="true"]'
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/embeds')
})
test('contains embeded', async ({ page }) => {
expect(
await page
.locator(slateEditor)
.locator('iframe')
.count()
).toBe(1)
})
})

View File

@@ -0,0 +1,28 @@
import { test, expect } from '@playwright/test'
test.describe('forced layout example', () => {
const elements = [
{ tag: '#__next h2', count: 1 },
{ tag: '#__next p', count: 1 },
]
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/forced-layout')
})
test('checks for the elements', async ({ page }) => {
for (const { tag, count } of elements) {
expect(await page.locator(tag).count()).toBe(count)
}
})
test('checks if elements persist even after everything is deleted', async ({
page,
}) => {
// clear the textbox
await page.locator('div[role="textbox"]').clear()
for (const { tag, count } of elements) {
expect(await page.locator(tag).count()).toBe(count)
}
})
})

View File

@@ -0,0 +1,49 @@
import { test, expect } from '@playwright/test'
test.describe('hovering toolbar example', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/hovering-toolbar')
})
test('hovering toolbar appears', async ({ page }) => {
await page.pause()
await expect(page.locator('div').nth(12)).toHaveCSS('opacity', '0')
await page
.locator('span[data-slate-string="true"]')
.nth(0)
.selectText()
expect(
await page
.locator('div')
.nth(12)
.count()
).toBe(1)
await expect(page.locator('div').nth(12)).toHaveCSS('opacity', '1')
expect(
await page
.locator('div')
.nth(12)
.locator('span.material-icons')
.count()
).toBe(3)
})
test('hovering toolbar disappears', async ({ page }) => {
await page
.locator('span[data-slate-string="true"]')
.nth(0)
.selectText()
await expect(page.locator('div').nth(12)).toHaveCSS('opacity', '1')
await page
.locator('span[data-slate-string="true"]')
.nth(0)
.selectText()
await page
.locator('div')
.nth(0)
.click({ force: true })
await expect(page.locator('div').nth(12)).toHaveCSS('opacity', '0')
})
})

View File

@@ -0,0 +1,18 @@
import { test, expect } from '@playwright/test'
test.describe('huge document example', () => {
const elements = [
{ tag: '#__next h1', count: 100 },
{ tag: '#__next p', count: 700 },
]
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/huge-document')
})
test('contains image', async ({ page }) => {
for (const { tag, count } of elements) {
expect(await page.locator(tag).count()).toBe(count)
}
})
})

View File

@@ -0,0 +1,24 @@
import { test, expect, Page } from '@playwright/test'
test.describe('iframe editor', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/iframe')
})
test('should be editable', async ({ page }) => {
await page
.frameLocator('iframe')
.locator('body')
.getByRole('textbox')
.focus()
await page.keyboard.press('Home')
await page.keyboard.type('Hello World')
expect(
await page
.frameLocator('iframe')
.locator('body')
.getByRole('textbox')
.textContent()
).toContain('Hello World')
})
})

View File

@@ -0,0 +1,16 @@
import { test, expect } from '@playwright/test'
test.describe('images example', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/images')
})
test('contains image', async ({ page }) => {
expect(
await page
.getByRole('textbox')
.locator('img')
.count()
).toBe(2)
})
})

View File

@@ -0,0 +1,17 @@
import { test, expect } from '@playwright/test'
test.describe('Inlines example', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/inlines')
})
test('contains link', async ({ page }) => {
expect(
await page
.getByRole('textbox')
.locator('a')
.nth(0)
.innerText()
).toContain('hyperlink')
})
})

View File

@@ -0,0 +1,32 @@
import { test, expect } from '@playwright/test'
test.describe('markdown preview', () => {
const slateEditor = 'div[data-slate-editor="true"]'
const markdown = 'span[data-slate-string="true"]'
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/markdown-preview')
})
test('checks for markdown', async ({ page }) => {
expect(
await page
.locator(slateEditor)
.locator(markdown)
.count()
).toBe(9)
await page.locator(slateEditor).click()
await page.keyboard.press('End')
await page.keyboard.press('Enter')
await page.keyboard.type('## Try it out!')
await page.keyboard.press('Enter')
await page.pause()
expect(
await page
.locator(slateEditor)
.locator(markdown)
.count()
).toBe(10)
})
})

View File

@@ -0,0 +1,78 @@
import { test, expect } from '@playwright/test'
test.describe('On markdown-shortcuts example', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/markdown-shortcuts')
})
test('contains quote', async ({ page }) => {
expect(
await page
.getByRole('textbox')
.locator('blockquote')
.textContent()
).toContain('A wise quote.')
})
test('can add list items', async ({ page }) => {
expect(
await page
.getByRole('textbox')
.locator('ul')
.count()
).toBe(0)
await page.getByRole('textbox').click()
await page.getByRole('textbox').press('Home')
await page.getByRole('textbox').type('* 1st Item')
await page.keyboard.press('Enter')
await page.getByRole('textbox').type('2nd Item')
await page.keyboard.press('Enter')
await page.getByRole('textbox').type('3rd Item')
await page.keyboard.press('Enter')
await page.keyboard.press('Backspace')
expect(await page.locator('ul > li').count()).toBe(3)
expect(
await page
.locator('ul > li')
.nth(0)
.innerText()
).toContain('1st Item')
expect(
await page
.locator('ul > li')
.nth(1)
.innerText()
).toContain('2nd Item')
expect(
await page
.locator('ul > li')
.nth(2)
.innerText()
).toContain('3rd Item')
})
test('can add a h1 item', async ({ page }) => {
expect(
await page
.getByRole('textbox')
.locator('h1')
.count()
).toBe(0)
await page.getByRole('textbox').press('Enter')
await page.getByRole('textbox').press('ArrowLeft')
await page.getByRole('textbox').type('# Heading')
expect(await page.locator('h1').count()).toBe(1)
expect(
await page
.getByRole('textbox')
.locator('h1')
.textContent()
).toContain('Heading')
})
})

View File

@@ -0,0 +1,30 @@
import { test, expect } from '@playwright/test'
test.describe('mentions example', () => {
test.beforeEach(
async ({ page }) =>
await page.goto('http://localhost:3000/examples/mentions')
)
test('renders mention element', async ({ page }) => {
expect(await page.locator('[data-cy="mention-R2-D2"]').count()).toBe(1)
expect(await page.locator('[data-cy="mention-Mace-Windu"]').count()).toBe(1)
})
test('shows list of mentions', async ({ page }) => {
await page.getByRole('textbox').click()
await page.getByRole('textbox').selectText()
await page.getByRole('textbox').press('Backspace')
await page.getByRole('textbox').type(' @ma')
expect(await page.locator('[data-cy="mentions-portal"]').count()).toBe(1)
})
test('inserts on enter from list', async ({ page }) => {
await page.getByRole('textbox').click()
await page.getByRole('textbox').selectText()
await page.getByRole('textbox').press('Backspace')
await page.getByRole('textbox').type(' @Ja')
await page.getByRole('textbox').press('Enter')
expect(await page.locator('[data-cy="mention-Jabba"]').count()).toBe(1)
})
})

View File

@@ -0,0 +1,38 @@
import { test, expect, Page } from '@playwright/test'
test.describe('paste html example', () => {
test.beforeEach(
async ({ page }) =>
await page.goto('http://localhost:3000/examples/paste-html')
)
const pasteHtml = async (page: Page, htmlContent: string) => {
await page.getByRole('textbox').click()
await page.getByRole('textbox').selectText()
await page.keyboard.press('Backspace')
await page
.getByRole('textbox')
.evaluate((el: HTMLElement, htmlContent: string) => {
const clipboardEvent = Object.assign(
new Event('paste', { bubbles: true, cancelable: true }),
{
clipboardData: {
getData: (type = 'text/html') => htmlContent,
types: ['text/html'],
},
}
)
el.dispatchEvent(clipboardEvent)
}, htmlContent)
}
test('pasted bold text uses <strong>', async ({ page }) => {
await pasteHtml(page, '<strong>Hello Bold</strong>')
expect(await page.locator('strong').textContent()).toContain('Hello')
})
test('pasted code uses <code>', async ({ page }) => {
await pasteHtml(page, '<code>console.log("hello from slate!")</code>')
expect(await page.locator('code').textContent()).toContain('slate!')
})
})

View File

@@ -0,0 +1,17 @@
import { test, expect } from '@playwright/test'
test.describe('placeholder example', () => {
test.beforeEach(
async ({ page }) =>
await page.goto('http://localhost:3000/examples/custom-placeholder')
)
test('renders custom placeholder', async ({ page }) => {
const placeholderElement = page.locator('[data-slate-placeholder=true]')
expect(await placeholderElement.textContent()).toContain('Type something')
expect(await page.locator('pre').textContent()).toContain(
'renderPlaceholder'
)
})
})

View File

@@ -0,0 +1,16 @@
import { test, expect } from '@playwright/test'
test.describe('plaintext example', () => {
test.beforeEach(
async ({ page }) =>
await page.goto('http://localhost:3000/examples/plaintext')
)
test('inserts text when typed', async ({ page }) => {
await page.getByRole('textbox').press('Home')
await page.getByRole('textbox').type('Hello World')
expect(await page.getByRole('textbox').textContent()).toContain(
'Hello World'
)
})
})

View File

@@ -0,0 +1,17 @@
import { test, expect } from '@playwright/test'
test.describe('readonly editor', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/read-only')
})
test('should not be editable', async ({ page }) => {
const slateEditor = '[data-slate-editor="true"]'
expect(
await page.locator(slateEditor).getAttribute('contentEditable')
).toBe('false')
expect(await page.locator(slateEditor).getAttribute('role')).toBe(null)
await page.locator(slateEditor).click()
await expect(page.locator(slateEditor)).not.toBeFocused()
})
})

View File

@@ -0,0 +1,29 @@
import { test, expect } from '@playwright/test'
test.describe('On richtext example', () => {
test.beforeEach(
async ({ page }) =>
await page.goto('http://localhost:3000/examples/richtext')
)
test('renders rich text', async ({ page }) => {
expect(
await page
.locator('strong')
.nth(0)
.textContent()
).toContain('rich')
expect(await page.locator('blockquote').textContent()).toContain(
'wise quote'
)
})
test('inserts text when typed', async ({ page }) => {
await page.getByRole('textbox').press('Home')
await page.getByRole('textbox').type('Hello World')
expect(await page.getByRole('textbox').textContent()).toContain(
'Hello World'
)
})
})

View File

@@ -0,0 +1,16 @@
import { test, expect } from '@playwright/test'
test.describe('search highlighting', () => {
test.beforeEach(
async ({ page }) =>
await page.goto('http://localhost:3000/examples/search-highlighting')
)
test('highlights the searched text', async ({ page }) => {
const searchField = 'input[type="search"]'
const highlightedText = 'search-highlighted'
await page.locator(searchField).type('text')
expect(await page.locator(`[data-cy="${highlightedText}"]`).count()).toBe(2)
})
})

View File

@@ -0,0 +1,28 @@
import { test, expect } from '@playwright/test'
test.describe('selection', () => {
const slateEditor = '[data-slate-node="element"]'
test.beforeEach(
async ({ page }) =>
await page.goto('http://localhost:3000/examples/richtext')
)
test('select the correct block when triple clicking', async ({ page }) => {
// triple clicking the second block (paragraph) shouldn't highlight the
// quote button
for (let i = 0; i < 3; i++) {
await page
.locator(slateEditor)
.nth(1)
.click()
}
await page.pause()
// .css-1vdn1ty is the gray, unselected button
expect(
await page
.locator('.css-1vdn1ty')
.nth(6)
.textContent()
).toBe('format_quote')
})
})

View File

@@ -0,0 +1,15 @@
import { test, expect } from '@playwright/test'
test.describe('shadow-dom example', () => {
test.beforeEach(
async ({ page }) =>
await page.goto('http://localhost:3000/examples/shadow-dom')
)
test('renders slate editor inside nested shadow', async ({ page }) => {
const outerShadow = page.locator('[data-cy="outer-shadow-root"]')
const innerShadow = outerShadow.locator('> div')
expect(await innerShadow.getByRole('textbox').count()).toBe(1)
})
})

View File

@@ -0,0 +1,16 @@
import { test, expect } from '@playwright/test'
test.describe('table example', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/examples/tables')
})
test('table tag rendered', async ({ page }) => {
expect(
await page
.getByRole('textbox')
.locator('table')
.count()
).toBe(1)
})
})

26
playwright/tsconfig.json Normal file
View File

@@ -0,0 +1,26 @@
{
"include": ["**/*.ts"],
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
"downlevelIteration": true,
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"lib": [
"DOM",
"ES2020"
] /* Specify library files to be included in the compilation. */,
"allowJs": true /* Allow javascript files to be compiled. */,
"noEmit": true,
"sourceMap": true /* Generates corresponding '.map' file. */,
"strict": true /* Enable all strict type-checking options. */,
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
"resolveJsonModule": true,
"skipLibCheck": true /* Skip type checking of declaration files. */,
"isolatedModules": false,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}