Skip to content

Commit b23b235

Browse files
authored
feat: Add API to support history feature (#28)
1 parent 759e605 commit b23b235

File tree

20 files changed

+402
-223
lines changed

20 files changed

+402
-223
lines changed

integration-tests/assetStates.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

integration-tests/index.ts

Lines changed: 31 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,43 @@
1-
import initCreator from '../src/index'
2-
import SampleImg from './image-sample.png'
1+
import initCreator, { SerializedAsset } from '../src/index'
32

43
declare global {
54
interface Window {
6-
testLastAssetUpdate: {
7-
calledTimes: number
8-
assets: object[] | null
9-
}
5+
assetsSnapshot: SerializedAsset[]
106
}
117
}
128

13-
const params = new URLSearchParams(document.location.search)
14-
const isSampleParam = params.has('sample') // is the string "Jonathan"
9+
const assetsUpdatesHistory: SerializedAsset[][] = [[]]
1510

1611
async function test() {
1712
const canvas = document.querySelector<HTMLCanvasElement>('canvas')!
1813

1914
const selectedAssetEl = document.querySelector<HTMLSpanElement>('#selected-asset-id')!
15+
const isProcessingEventsEl = document.querySelector<HTMLSpanElement>('#is-processing-events')!
2016
const removeAssetBtn = document.querySelector<HTMLSpanElement>('#remove-btn')!
17+
const undoBtn = document.querySelector<HTMLSpanElement>('#undo-btn')!
18+
const redoBtn = document.querySelector<HTMLSpanElement>('#redo-btn')!
2119

22-
window.testLastAssetUpdate = {
23-
calledTimes: 0,
24-
assets: null,
25-
}
20+
window.assetsSnapshot = []
21+
let currentHistoryIndex = 0
2622

2723
const creator = await initCreator(
2824
canvas,
29-
isSampleParam
30-
? [
31-
{
32-
points: [
33-
{
34-
u: 0,
35-
v: 1,
36-
x: 106.5999984741211,
37-
y: 693.9000244140625,
38-
},
39-
{
40-
u: 1,
41-
v: 1,
42-
x: 723.4000244140625,
43-
y: 693.9000244140625,
44-
},
45-
{
46-
u: 1,
47-
v: 0,
48-
x: 723.4000244140625,
49-
y: 77.0999984741211,
50-
},
51-
{
52-
u: 0,
53-
v: 0,
54-
x: 106.5999984741211,
55-
y: 77.0999984741211,
56-
},
57-
],
58-
url: SampleImg,
59-
},
60-
]
61-
: [],
25+
[],
6226
(assets) => {
63-
window.testLastAssetUpdate = {
64-
calledTimes: window.testLastAssetUpdate.calledTimes + 1,
65-
assets,
27+
window.assetsSnapshot = assets
28+
// we had to implement this whole history logic because there is no way
29+
// to call creator.resetCanvas(newAssets) from test code file
30+
if (currentHistoryIndex === assetsUpdatesHistory.length - 1) {
31+
assetsUpdatesHistory.push(assets)
32+
currentHistoryIndex = assetsUpdatesHistory.length - 1
6633
}
6734
console.log(assets)
6835
},
6936
(assetId) => {
7037
selectedAssetEl.textContent = assetId.toString()
38+
},
39+
(inProgress) => {
40+
isProcessingEventsEl.textContent = inProgress ? 'true' : 'false'
7141
}
7242
)
7343

@@ -81,11 +51,24 @@ async function test() {
8151
img.onload = () => {
8252
creator.addImage(img)
8353
}
54+
fileInput.value = '' // reset input value to allow re-uploading the same file
8455
})
8556

8657
removeAssetBtn.addEventListener('click', () => {
8758
creator.removeAsset()
8859
})
60+
61+
undoBtn.addEventListener('click', () => {
62+
currentHistoryIndex = Math.max(0, currentHistoryIndex - 1)
63+
const assets = assetsUpdatesHistory[currentHistoryIndex]
64+
creator.resetAssets(assets)
65+
})
66+
67+
redoBtn.addEventListener('click', () => {
68+
currentHistoryIndex = Math.min(assetsUpdatesHistory.length - 1, currentHistoryIndex + 1)
69+
const assets = assetsUpdatesHistory[currentHistoryIndex]
70+
creator.resetAssets(assets)
71+
})
8972
}
9073

9174
test()

integration-tests/init.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import type { Page } from '@playwright/test'
2+
import { expect } from '@playwright/test'
3+
import path from 'path'
4+
import { fileURLToPath } from 'url'
5+
6+
const PAGE_WIDTH = 1000
7+
const PAGE_HEIGHT = 700
8+
let page: Page
9+
let move: (x: number, y: number) => Promise<void>
10+
let canvasBox: { x: number; y: number; width: number; height: number }
11+
12+
interface SerializedAsset {
13+
id: number
14+
points: { u: number; v: number; x: number; y: number }[]
15+
url: string
16+
}
17+
18+
// SyntaxError: TypeScript enum is not supported in strip-only mode
19+
export const TransformHandle = {
20+
TOP_LEFT: 0,
21+
TOP_RIGHT: 1,
22+
BOTTOM_RIGHT: 2,
23+
BOTTOM_LEFT: 3,
24+
}
25+
26+
export default async function init(receivedPage: Page) {
27+
page = receivedPage
28+
29+
// To check in the future if WebGPU is supported
30+
// await page.goto('https://webgpureport.org/');
31+
// await expect(page).toHaveScreenshot('webgpu-report.png');
32+
33+
await page.setViewportSize({ width: PAGE_WIDTH, height: PAGE_HEIGHT })
34+
await page.goto('/')
35+
await page.waitForLoadState('networkidle')
36+
await page.evaluate(() =>
37+
window.addEventListener('mousemove', (e) => console.log(e.clientX, e.clientY))
38+
) // to display cursor position during debugging
39+
// helps to copy position here
40+
41+
const canvas = (await page.$('canvas'))!
42+
canvasBox = (await canvas.boundingBox())!
43+
44+
move = async (x: number, y: number) => {
45+
await page.mouse.move(x + canvasBox.x, y + canvasBox.y)
46+
}
47+
48+
return {
49+
getAssetsState,
50+
uploadAsset,
51+
resizeAsset,
52+
selectAsset,
53+
}
54+
}
55+
56+
async function getAssetsState(): Promise<SerializedAsset[]> {
57+
const isProcessingEventsEl = page.locator('#is-processing-events')
58+
await expect(isProcessingEventsEl).toHaveText('false')
59+
60+
const assetsSnapshot = await page.evaluate(() => window.assetsSnapshot)
61+
return assetsSnapshot
62+
}
63+
64+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
65+
const testImagePath = path.join(__dirname, './image-sample.png')
66+
67+
async function uploadAsset() {
68+
const fileInput = (await page.$('input[type="file"]'))!
69+
await fileInput.setInputFiles(testImagePath)
70+
const assets = await getAssetsState()
71+
return assets[assets.length - 1] // return the last uploaded asset
72+
}
73+
74+
async function selectAsset({ points }: SerializedAsset) {
75+
await move((points[0].x + points[1].x) / 2, canvasBox.height - (points[0].y + points[3].y) / 2)
76+
await page.mouse.down()
77+
await page.mouse.up()
78+
}
79+
80+
// handles are counted clock-wise starting from top left corner
81+
async function resizeAsset(
82+
{ points }: SerializedAsset,
83+
width: number,
84+
height: number,
85+
handle: number
86+
) {
87+
const currentWidth = Math.abs(points[0].x - points[1].x)
88+
const currentHeight = Math.abs(points[0].y - points[3].y) // (canvasHeight - 140)
89+
const directionX =
90+
handle === TransformHandle.TOP_LEFT || handle === TransformHandle.BOTTOM_LEFT ? 1 : -1
91+
const directionY =
92+
handle === TransformHandle.TOP_LEFT || handle === TransformHandle.TOP_RIGHT ? 1 : -1
93+
94+
await move(points[handle].x, canvasBox.height - points[handle].y)
95+
await page.mouse.down()
96+
await move(
97+
points[handle].x + (currentWidth - width) * directionX,
98+
canvasBox.height - points[handle].y + (currentHeight - height) * directionY
99+
)
100+
await page.mouse.up()
101+
}

integration-tests/template.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@
3232
<aside>
3333
<input type="file" />
3434
<p>Selected asset: <span id="selected-asset-id">0</span></p>
35+
<p>Is processing events: <span id="is-processing-events">false</span></p>
3536
<button id="remove-btn">Remove Asset</button>
37+
<button id="undo-btn">Undo</button>
38+
<button id="redo-btn">Redo</button>
3639
</aside>
3740
<canvas></canvas>
3841
</body>

integration-tests/tests/asset-basic-transform.spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// npx playwright test asset-basic-transform.spec.ts --debug
1+
// npm run test-e2e -- asset-basic-transform.spec.ts --debug
22

33
import { test, expect } from '@playwright/test'
4-
import * as Utils from '../utils'
4+
import init from '../init'
55

66
test('asset performs basic transformations', async ({ page }, testinfo) => {
77
if (process.env.CI) {
@@ -11,14 +11,15 @@ test('asset performs basic transformations', async ({ page }, testinfo) => {
1111

1212
testinfo.snapshotSuffix = '' // by default is `process.platform`
1313

14-
await Utils.init(page)
15-
await Utils.uploadAsset(page)
14+
const utils = await init(page)
15+
await utils.uploadAsset()
1616
const canvas = page.locator('canvas')
1717

1818
// select asset
1919
await page.mouse.move(550, 275)
2020
await page.mouse.down()
2121
await page.mouse.up()
22+
await expect(canvas).toHaveScreenshot('select-image.png')
2223

2324
// move
2425
await page.mouse.down()
Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// npx playwright test asset-removal.spec.ts --debug
1+
// npm run test-e2e -- asset-removal.spec.ts --debug
22

33
import { test, expect } from '@playwright/test'
4-
import * as Utils from '../utils'
4+
import init from '../init'
55

66
test('asset removal', async ({ page }, testinfo) => {
77
if (process.env.CI) {
@@ -11,9 +11,8 @@ test('asset removal', async ({ page }, testinfo) => {
1111

1212
testinfo.snapshotSuffix = '' // by default is `process.platform`
1313

14-
const expectLastUpdate = await Utils.init(page)
15-
await Utils.uploadAsset(page)
16-
const canvas = page.locator('canvas')
14+
const utils = await init(page)
15+
await utils.uploadAsset()
1716
const removeBtn = page.locator('#remove-btn')
1817
const assetIdEl = page.locator('#selected-asset-id')
1918

@@ -23,8 +22,6 @@ test('asset removal', async ({ page }, testinfo) => {
2322
await page.mouse.up()
2423

2524
await removeBtn.click()
26-
await expect(canvas).toHaveScreenshot('removed-image.png')
2725
await expect(assetIdEl).toHaveText('0')
28-
29-
await expectLastUpdate(3, [])
26+
expect(await utils.getAssetsState()).toStrictEqual([])
3027
})
Binary file not shown.

integration-tests/tests/asset-selection.spec.ts

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)