tldraw/apps/examples/e2e/shared-e2e.ts

87 lines
2.7 KiB
TypeScript
Raw Normal View History

import { PlaywrightTestArgs, PlaywrightWorkerArgs } from '@playwright/test'
import { Editor } from '@tldraw/tldraw'
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
// export async function expectPathToBe(page: Page, path: string) {
// expect(await page.evaluate(() => editor.root.path.value)).toBe(path)
// }
// export async function expectToHaveNShapes(page: Page, numberOfShapes: number) {
// expect(await page.evaluate(() => editor.currentPageShapes.length)).toBe(numberOfShapes)
// }
// export async function expectToHaveNSelectedShapes(page: Page, numberOfSelectedShapes: number) {
`ShapeUtil.getGeometry`, selection rewrite (#1751) This PR is a significant rewrite of our selection / hit testing logic. It - replaces our current geometric helpers (`getBounds`, `getOutline`, `hitTestPoint`, and `hitTestLineSegment`) with a new geometry API - moves our hit testing entirely to JS using geometry - improves selection logic, especially around editing shapes, groups and frames - fixes many minor selection bugs (e.g. shapes behind frames) - removes hit-testing DOM elements from ShapeFill etc. - adds many new tests around selection - adds new tests around selection - makes several superficial changes to surface editor APIs This PR is hard to evaluate. The `selection-omnibus` test suite is intended to describe all of the selection behavior, however all existing tests are also either here preserved and passing or (in a few cases around editing shapes) are modified to reflect the new behavior. ## Geometry All `ShapeUtils` implement `getGeometry`, which returns a single geometry primitive (`Geometry2d`). For example: ```ts class BoxyShapeUtil { getGeometry(shape: BoxyShape) { return new Rectangle2d({ width: shape.props.width, height: shape.props.height, isFilled: true, margin: shape.props.strokeWidth }) } } ``` This geometric primitive is used for all bounds calculation, hit testing, intersection with arrows, etc. There are several geometric primitives that extend `Geometry2d`: - `Arc2d` - `Circle2d` - `CubicBezier2d` - `CubicSpline2d` - `Edge2d` - `Ellipse2d` - `Group2d` - `Polygon2d` - `Rectangle2d` - `Stadium2d` For shapes that have more complicated geometric representations, such as an arrow with a label, the `Group2d` can accept other primitives as its children. ## Hit testing Previously, we did all hit testing via events set on shapes and other elements. In this PR, I've replaced those hit tests with our own calculation for hit tests in JavaScript. This removed the need for many DOM elements, such as hit test area borders and fills which only existed to trigger pointer events. ## Selection We now support selecting "hollow" shapes by clicking inside of them. This involves a lot of new logic but it should work intuitively. See `Editor.getShapeAtPoint` for the (thoroughly commented) implementation. ![Kapture 2023-07-23 at 23 27 27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6) every sunset is actually the sun hiding in fear and respect of tldraw's quality of interactions This PR also fixes several bugs with scribble selection, in particular around the shift key modifier. ![Kapture 2023-07-24 at 23 34 07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5) ...as well as issues with labels and editing. There are **over 100 new tests** for selection covering groups, frames, brushing, scribbling, hovering, and editing. I'll add a few more before I feel comfortable merging this PR. ## Arrow binding Using the same "hollow shape" logic as selection, arrow binding is significantly improved. ![Kapture 2023-07-22 at 07 46 25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c) a thousand wise men could not improve on this ## Moving focus between editing shapes Previously, this was handled in the `editing_shapes` state. This is moved to `useEditableText`, and should generally be considered an advanced implementation detail on a shape-by-shape basis. This addresses a bug that I'd never noticed before, but which can be reproduced by selecting an shape—but not focusing its input—while editing a different shape. Previously, the new shape became the editing shape but its input did not focus. ![Kapture 2023-07-23 at 23 19 09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c) In this PR, you can select a shape by clicking on its edge or body, or select its input to transfer editing / focus. ![Kapture 2023-07-23 at 23 22 21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a) tldraw, glorious tldraw ### Change Type - [x] `major` — Breaking change ### Test Plan 1. Erase shapes 2. Select shapes 3. Calculate their bounding boxes - [ ] Unit Tests // todo - [ ] End to end tests // todo ### Release Notes - [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`, `ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment` - [editor] Add `ShapeUtil.getGeometry` - [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
// expect(await page.evaluate(() => editor.selectedShapeIds.length)).toBe(numberOfSelectedShapes)
// }
declare const editor: Editor
export async function setup({ page }: PlaywrightTestArgs & PlaywrightWorkerArgs) {
await setupPage(page)
}
export async function setupWithShapes({ page }: PlaywrightTestArgs & PlaywrightWorkerArgs) {
await setupPage(page)
await setupPageWithShapes(page)
}
export async function cleanup({ page }: PlaywrightTestArgs) {
await cleanupPage(page)
}
export async function setupPage(page: PlaywrightTestArgs['page']) {
[refactor] User-facing APIs (#1478) This PR updates our user-facing APIs for the Tldraw and TldrawEditor components, as well as the Editor (App). It mainly incorporates surface changes from #1450 without any changes to validators or migrators, incorporating feedback / discussion with @SomeHats and @ds300. Here we: - remove the TldrawEditorConfig - bring back a loose version of shape definitions - make a separation between "core" shapes and "default" shapes - do not allow custom shapes, migrators or validators to overwrite core shapes - but _do_ allow new shapes ## `<Tldraw>` component In this PR, the `Tldraw` component wraps both the `TldrawEditor` component and our `TldrawUi` component. It accepts a union of props for both components. Previously, this component also added local syncing via a `useLocalSyncClient` hook call, however that has been pushed down to the `TldrawEditor` component. ## `<TldrawEditor>` component The `TldrawEditor` component now more neatly wraps up the different ways that the editor can be configured. ## The store prop (`TldrawEditorProps.store`) There are three main ways for the `TldrawEditor` component to be run: 1. with an externally defined store 2. with an externally defined syncing store (local or remote) 3. with an internally defined store 4. with an internally defined locally syncing store The `store` prop allows for these configurations. If the `store` prop is defined, it may be defined either as a `TLStore` or as a `SyncedStore`. If the store is a `TLStore`, then the Editor will assume that the store is ready to go; if it is defined as a SyncedStore, then the component will display the loading / error screens as needed, or the final editor once the store's status is "synced". When the store is left undefined, then the `TldrawEditor` will create its own internal store using the optional `instanceId`, `initialData`, or `shapes` props to define the store / store schema. If the `persistenceKey` prop is left undefined, then the store will not be synced. If the `persistenceKey` is defined, then the store will be synced locally. In the future, we may also here accept the API key / roomId / etc for creating a remotely synced store. The `SyncedStore` type has been expanded to also include types used for remote syncing, e.g. with `ConnectionStatus`. ## Tools By default, the App has two "baked-in" tools: the select tool and the zoom tool. These cannot (for now) be replaced or removed. The default tools are used by default, but may be replaced by other tools if provided. ## Shapes By default, the App has a set of "core" shapes: - group - embed - bookmark - image - video - text That cannot by overwritten because they're created by the app at different moments, such as when double clicking on the canvas or via a copy and paste event. In follow up PRs, we'll split these out so that users can replace parts of the code where these shapes are created. ### Change Type - [x] `major` — Breaking Change ### Test Plan - [x] Unit Tests
2023-06-01 15:47:34 +00:00
await page.goto('http://localhost:5420/end-to-end')
await page.waitForSelector('.tl-canvas')
await page.evaluate(() => {
Remove helpers / extraneous API methods. (#1745) This PR removes several extraneous computed values from the editor. It adds some silly instance state onto the instance state record and unifies a few methods which were inconsistent. This is fit and finish work 🧽 ## Computed Values In general, where once we had a getter and setter for `isBlahMode`, which really masked either an `_isBlahMode` atom on the editor or `instanceState.isBlahMode`, these are merged into `instanceState`; they can be accessed / updated via `editor.instanceState` / `editor.updateInstanceState`. ## tldraw select tool specific things This PR also removes some tldraw specific state checks and creates new component overrides to allow us to include them in tldraw/tldraw. ### Change Type - [x] `major` — Breaking change ### Test Plan - [x] Unit Tests - [x] End to end tests ### Release Notes - [tldraw] rename `useReadonly` to `useReadOnly` - [editor] remove `Editor.isDarkMode` - [editor] remove `Editor.isChangingStyle` - [editor] remove `Editor.isCoarsePointer` - [editor] remove `Editor.isDarkMode` - [editor] remove `Editor.isFocused` - [editor] remove `Editor.isGridMode` - [editor] remove `Editor.isPenMode` - [editor] remove `Editor.isReadOnly` - [editor] remove `Editor.isSnapMode` - [editor] remove `Editor.isToolLocked` - [editor] remove `Editor.locale` - [editor] rename `Editor.pageState` to `Editor.currentPageState` - [editor] add `Editor.pageStates` - [editor] add `Editor.setErasingIds` - [editor] add `Editor.setEditingId` - [editor] add several new component overrides
2023-07-18 21:50:23 +00:00
editor.user.updateUserPreferences({ animationSpeed: 0 })
})
}
export async function setupPageWithShapes(page: PlaywrightTestArgs['page']) {
// delete everything
await page.keyboard.press('Control+a')
await page.keyboard.press('Backspace')
// create shapes
await page.keyboard.press('r')
await page.mouse.click(200, 200)
await page.keyboard.press('r')
await page.mouse.click(200, 250)
await page.keyboard.press('r')
await page.mouse.click(250, 300)
// deselect everything
await page.evaluate(() => editor.selectNone())
}
export async function cleanupPage(page: PlaywrightTestArgs['page']) {
await page.keyboard.press('Control+a')
await page.keyboard.press('Delete')
}
export async function getAllShapeLabels(page: PlaywrightTestArgs['page']) {
return await page
.locator('.tl-shapes')
.first()
.evaluate((e) => {
const labels: { index: string; label: string }[] = []
for (const child of e.children) {
const index = (child as HTMLDivElement).style.zIndex
const label = child.querySelector('.tl-text-content') as HTMLDivElement
labels.push({ index, label: label.innerText })
}
labels.sort((a, b) => (a.index > b.index ? 1 : -1))
return labels.map((l) => l.label)
})
}
export async function getAllShapeTypes(page: PlaywrightTestArgs['page']) {
return await page
.locator('.tl-shape')
.elementHandles()
.then((handles) => Promise.all(handles.map((h) => h.getAttribute('data-shape-type'))))
}