Fix jpg export and tests (#3198)

Fix a bug that was preventing JPG and webp exports from working. Also:
- Re-enable our export snapshot tests which got commented out again
- Fix some react act errors when running tests

### Change Type

- [x] `sdk` — Changes the tldraw SDK
- [x] `bugfix` — Bug fix
This commit is contained in:
alex 2024-03-18 15:08:09 +00:00 committed by GitHub
parent cef70d6a81
commit 16a28bfd90
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 127 additions and 61 deletions

View file

@ -1,14 +1,14 @@
import test from '@playwright/test' import { Page, expect } from '@playwright/test'
import { TLShapeId, TLShapePartial } from 'tldraw' import assert from 'assert'
import { rename, writeFile } from 'fs/promises'
import { Editor, TLShapeId, TLShapePartial } from 'tldraw'
import { setup } from '../shared-e2e'
import test, { ApiFixture } from './fixtures/fixtures'
// import test, { Page, expect } from '@playwright/test' declare const editor: Editor
// import assert from 'assert'
// import { rename, writeFile } from 'fs/promises'
// import { setupPage } from '../shared-e2e'
// import { Editor, TLShapeId, TLShapePartial } from 'tldraw'
// declare const editor: Editor
// hi steve. please don't comment these out. they stop us getting bugs. u can just ask if they're
// holding u up <3
test.describe('Export snapshots', () => { test.describe('Export snapshots', () => {
const snapshots = { const snapshots = {
'Exports geo text with leading line breaks': [ 'Exports geo text with leading line breaks': [
@ -189,50 +189,50 @@ test.describe('Export snapshots', () => {
] ]
} }
// const snapshotsToTest = Object.entries(snapshots) const snapshotsToTest = Object.entries(snapshots)
// const filteredSnapshots = snapshotsToTest // maybe we filter these down, there are a lot of them const filteredSnapshots = snapshotsToTest // maybe we filter these down, there are a lot of them
// for (const [name, shapes] of filteredSnapshots) { test.beforeEach(setup)
// test(`Exports with ${name} in dark mode`, async ({ browser }) => {
// const page = await browser.newPage()
// await setupPage(page)
// await page.evaluate((shapes) => {
// editor.user.updateUserPreferences({ isDarkMode: true })
// editor
// .updateInstanceState({ exportBackground: false })
// .selectAll()
// .deleteShapes(editor.getSelectedShapeIds())
// .createShapes(shapes)
// }, shapes as any)
// await snapshotTest(page) for (const [name, shapes] of filteredSnapshots) {
// }) test(`Exports with ${name} in dark mode`, async ({ page, api }) => {
// } await page.evaluate((shapes) => {
editor.user.updateUserPreferences({ isDarkMode: true })
editor
.updateInstanceState({ exportBackground: false })
.selectAll()
.deleteShapes(editor.getSelectedShapeIds())
.createShapes(shapes)
}, shapes as any)
// async function snapshotTest(page: Page) { await snapshotTest(page, api)
// const downloadAndSnapshot = page.waitForEvent('download').then(async (download) => { })
// const path = (await download.path()) as string }
// assert(path)
// await rename(path, path + '.svg')
// await writeFile(
// path + '.html',
// `
// <!DOCTYPE html>
// <meta charset="utf-8" />
// <meta name="viewport" content="width=device-width, initial-scale=1" />
// <img src="${path}.svg" />
// `,
// 'utf-8'
// )
// await page.goto(`file://${path}.html`) async function snapshotTest(page: Page, api: ApiFixture) {
// const clip = await page.$eval('img', (img) => img.getBoundingClientRect()) const downloadAndSnapshot = page.waitForEvent('download').then(async (download) => {
// await expect(page).toHaveScreenshot({ const path = (await download.path()) as string
// omitBackground: true, assert(path)
// clip, await rename(path, path + '.svg')
// }) await writeFile(
// }) path + '.html',
// await page.evaluate(() => (window as any)['tldraw-export']()) `
// await downloadAndSnapshot <!DOCTYPE html>
// } <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<img src="${path}.svg" />
`,
'utf-8'
)
await page.goto(`file://${path}.html`)
const clip = await page.$eval('img', (img) => img.getBoundingClientRect())
await expect(page).toHaveScreenshot({
omitBackground: true,
clip,
})
})
await api.exportAsSvg()
await downloadAndSnapshot
}
}) })

View file

@ -1,4 +1,5 @@
import { test as base } from '@playwright/test' import { Page, test as base } from '@playwright/test'
import { EndToEndApi } from '../../../src/misc/EndToEndApi'
import { ActionsMenu } from './menus/ActionsMenu' import { ActionsMenu } from './menus/ActionsMenu'
import { HelpMenu } from './menus/HelpMenu' import { HelpMenu } from './menus/HelpMenu'
import { MainMenu } from './menus/MainMenu' import { MainMenu } from './menus/MainMenu'
@ -15,6 +16,30 @@ type Fixtures = {
mainMenu: MainMenu mainMenu: MainMenu
pageMenu: PageMenu pageMenu: PageMenu
navigationPanel: NavigationPanel navigationPanel: NavigationPanel
api: ReturnType<typeof makeApiFixture>
}
export type ApiFixture = {
[K in keyof EndToEndApi]: (
...args: Parameters<EndToEndApi[K]>
) => Promise<ReturnType<EndToEndApi[K]>>
}
function makeApiFixture(keys: { [K in keyof EndToEndApi]: true }, page: Page): ApiFixture {
const result = {} as any
for (const key of Object.keys(keys)) {
result[key] = (...args: any[]) => {
return page.evaluate(
([key, ...args]) => {
return (window as any).tldrawApi[key](...args)
},
[key, ...args]
)
}
}
return result
} }
const test = base.extend<Fixtures>({ const test = base.extend<Fixtures>({
@ -46,6 +71,16 @@ const test = base.extend<Fixtures>({
const navigationPanel = new NavigationPanel(page) const navigationPanel = new NavigationPanel(page)
await use(navigationPanel) await use(navigationPanel)
}, },
api: async ({ page }, use) => {
const api = makeApiFixture(
{
exportAsSvg: true,
exportAsFormat: true,
},
page
)
await use(api)
},
}) })
export default test export default test

View file

@ -0,0 +1,14 @@
import { setupWithShapes } from '../shared-e2e'
import test from './fixtures/fixtures'
test.beforeEach(setupWithShapes)
test.describe('api', () => {
for (const format of ['svg', 'png', 'jpeg', 'webp', 'json'] as const) {
test(`export as ${format}`, async ({ page, api }) => {
const downloadEvent = page.waitForEvent('download')
await api.exportAsFormat(format)
await downloadEvent
})
}
})

View file

@ -0,0 +1,6 @@
import { TLExportType } from 'tldraw/src/lib/utils/export/exportAs'
export interface EndToEndApi {
exportAsSvg: () => void
exportAsFormat: (format: TLExportType) => void
}

View file

@ -1,6 +1,7 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { Tldraw, useActions } from 'tldraw' import { Tldraw, exportAs, useActions, useEditor } from 'tldraw'
import 'tldraw/tldraw.css' import 'tldraw/tldraw.css'
import { EndToEndApi } from './EndToEndApi'
;(window as any).__tldraw_ui_event = { id: 'NOTHING_YET' } ;(window as any).__tldraw_ui_event = { id: 'NOTHING_YET' }
;(window as any).__tldraw_editor_events = [] ;(window as any).__tldraw_editor_events = []
@ -27,11 +28,17 @@ export default function EndToEnd() {
} }
function SneakyExportButton() { function SneakyExportButton() {
const editor = useEditor()
const actions = useActions() const actions = useActions()
useEffect(() => { useEffect(() => {
;(window as any)['tldraw-export'] = () => actions['export-as-svg'].onSelect('unknown') const api: EndToEndApi = {
}, [actions]) exportAsSvg: () => actions['export-as-svg'].onSelect('unknown'),
exportAsFormat: (format) =>
exportAs(editor, editor.selectAll().getSelectedShapeIds(), format, 'test'),
}
;(window as any).tldrawApi = api
}, [actions, editor])
return null return null
} }

View file

@ -10,7 +10,7 @@ describe('<Tldraw />', () => {
<Tldraw> <Tldraw>
<div data-testid="canvas-1" /> <div data-testid="canvas-1" />
</Tldraw>, </Tldraw>,
{ waitForPatterns: false } { waitForPatterns: true }
) )
await screen.findByTestId('canvas-1') await screen.findByTestId('canvas-1')
@ -27,7 +27,7 @@ describe('<Tldraw />', () => {
) )
} }
await renderTldrawComponent(<TestComponent />, { waitForPatterns: false }) await renderTldrawComponent(<TestComponent />, { waitForPatterns: true })
await screen.findByTestId('canvas-1') await screen.findByTestId('canvas-1')
}) })

View file

@ -86,10 +86,14 @@ export async function getSvgAsImage(
if (!blob) return null if (!blob) return null
if (type === 'png') {
const view = new DataView(await blob.arrayBuffer()) const view = new DataView(await blob.arrayBuffer())
return PngHelpers.setPhysChunk(view, effectiveScale, { return PngHelpers.setPhysChunk(view, effectiveScale, {
type: 'image/' + type, type: 'image/' + type,
}) })
} else {
return blob
}
} }
/** @public */ /** @public */