Should exit sketchMode when creating new file in the file tree pane (#3993)
* fix new file sketch mode issue * initial extron app fixture * Add tests for exiting sketch mode on file tree actions * organise files * before all after all clean up * tweak after each * makes typedKeys as unsafe * update mask for draft line snapshots * fix mask * add fix again
@ -1,4 +1,5 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { _test, _expect } from './playwright-deprecated'
|
||||||
|
import { test, expect } from './fixtures/fixtureSetup'
|
||||||
import * as fsp from 'fs/promises'
|
import * as fsp from 'fs/promises'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import {
|
import {
|
||||||
@ -11,14 +12,98 @@ import {
|
|||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { FILE_EXT } from 'lib/constants'
|
import { FILE_EXT } from 'lib/constants'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
_test.beforeEach(async ({ context, page }, testInfo) => {
|
||||||
await setup(context, page, testInfo)
|
await setup(context, page, testInfo)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
_test.afterEach(async ({ page }, testInfo) => {
|
||||||
await tearDown(page, testInfo)
|
await tearDown(page, testInfo)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test.describe('integrations tests', () => {
|
||||||
|
test(
|
||||||
|
'Creating a new file or switching file while in sketchMode should exit sketchMode',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ tronApp, homePage, scene, editor, toolbar }) => {
|
||||||
|
test.skip(
|
||||||
|
process.platform === 'win32',
|
||||||
|
'windows times out will waiting for the execution indicator?'
|
||||||
|
)
|
||||||
|
await tronApp.initialise({
|
||||||
|
fixtures: { homePage, scene, editor, toolbar },
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
const bracketDir = join(dir, 'test-sample')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
|
||||||
|
join(bracketDir, 'main.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const [clickObj] = await scene.makeMouseHelpers(600, 300)
|
||||||
|
|
||||||
|
await test.step('setup test', async () => {
|
||||||
|
await homePage.expectState({
|
||||||
|
projectCards: [
|
||||||
|
{
|
||||||
|
title: 'test-sample',
|
||||||
|
fileCount: 1,
|
||||||
|
folderCount: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sortBy: 'last-modified-desc',
|
||||||
|
})
|
||||||
|
await homePage.openProject('test-sample')
|
||||||
|
// windows times out here, hence the skip above
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
})
|
||||||
|
await test.step('enter sketch mode', async () => {
|
||||||
|
await clickObj()
|
||||||
|
await scene.moveNoWhere()
|
||||||
|
await editor.expectState({
|
||||||
|
activeLines: [
|
||||||
|
'|>startProfileAt([75.8,317.2],%)//[$startCapTag,$EndCapTag]',
|
||||||
|
],
|
||||||
|
highlightedCode: '',
|
||||||
|
diagnostics: [],
|
||||||
|
})
|
||||||
|
await toolbar.editSketch()
|
||||||
|
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||||
|
})
|
||||||
|
await test.step('check sketch mode is exited when creating new file', async () => {
|
||||||
|
await toolbar.fileTreeBtn.click()
|
||||||
|
await toolbar.expectFileTreeState(['main.kcl'])
|
||||||
|
await toolbar.createFile({ wait: true })
|
||||||
|
|
||||||
|
// check we're out of sketch mode
|
||||||
|
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
||||||
|
await expect(toolbar.startSketchBtn).toBeVisible()
|
||||||
|
})
|
||||||
|
await test.step('setup for next assertion', async () => {
|
||||||
|
await toolbar.openFile('main.kcl')
|
||||||
|
await clickObj()
|
||||||
|
await scene.moveNoWhere()
|
||||||
|
await editor.expectState({
|
||||||
|
activeLines: [
|
||||||
|
'|>startProfileAt([75.8,317.2],%)//[$startCapTag,$EndCapTag]',
|
||||||
|
],
|
||||||
|
highlightedCode: '',
|
||||||
|
diagnostics: [],
|
||||||
|
})
|
||||||
|
await toolbar.editSketch()
|
||||||
|
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||||
|
await toolbar.expectFileTreeState(['main.kcl', 'Untitled.kcl'])
|
||||||
|
})
|
||||||
|
await test.step('check sketch mode is exited when opening a different file', async () => {
|
||||||
|
await toolbar.openFile('untitled.kcl', { wait: false })
|
||||||
|
|
||||||
|
// check we're out of sketch mode
|
||||||
|
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
||||||
|
await expect(toolbar.startSketchBtn).toBeVisible()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
test.describe('when using the file tree to', () => {
|
test.describe('when using the file tree to', () => {
|
||||||
const fromFile = 'main.kcl'
|
const fromFile = 'main.kcl'
|
||||||
const toFile = 'hello.kcl'
|
const toFile = 'hello.kcl'
|
||||||
@ -26,11 +111,8 @@ test.describe('when using the file tree to', () => {
|
|||||||
test(
|
test(
|
||||||
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
|
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _, tronApp }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await tronApp.initialise()
|
||||||
testInfo,
|
|
||||||
folderSetupFn: async () => {},
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
panesOpen,
|
panesOpen,
|
||||||
@ -38,10 +120,10 @@ test.describe('when using the file tree to', () => {
|
|||||||
pasteCodeInEditor,
|
pasteCodeInEditor,
|
||||||
renameFile,
|
renameFile,
|
||||||
editorTextMatches,
|
editorTextMatches,
|
||||||
} = await getUtils(page, test)
|
} = await getUtils(tronApp.page, test)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||||
page.on('console', console.log)
|
tronApp.page.on('console', console.log)
|
||||||
|
|
||||||
await panesOpen(['files', 'code'])
|
await panesOpen(['files', 'code'])
|
||||||
|
|
||||||
@ -55,39 +137,38 @@ test.describe('when using the file tree to', () => {
|
|||||||
await pasteCodeInEditor(kclCube)
|
await pasteCodeInEditor(kclCube)
|
||||||
|
|
||||||
await renameFile(fromFile, toFile)
|
await renameFile(fromFile, toFile)
|
||||||
await page.reload()
|
await tronApp.page.reload()
|
||||||
|
|
||||||
await test.step('Postcondition: editor has same content as before the rename', async () => {
|
await test.step('Postcondition: editor has same content as before the rename', async () => {
|
||||||
await editorTextMatches(kclCube)
|
await editorTextMatches(kclCube)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Postcondition: opening and closing settings works', async () => {
|
await test.step('Postcondition: opening and closing settings works', async () => {
|
||||||
const settingsOpenButton = page.getByRole('link', {
|
const settingsOpenButton = tronApp.page.getByRole('link', {
|
||||||
name: 'settings Settings',
|
name: 'settings Settings',
|
||||||
})
|
})
|
||||||
const settingsCloseButton = page.getByTestId('settings-close-button')
|
const settingsCloseButton = tronApp.page.getByTestId(
|
||||||
|
'settings-close-button'
|
||||||
|
)
|
||||||
await settingsOpenButton.click()
|
await settingsOpenButton.click()
|
||||||
await settingsCloseButton.click()
|
await settingsCloseButton.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await tronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
`create many new untitled files they increment their names`,
|
`create many new untitled files they increment their names`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _, tronApp }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await tronApp.initialise()
|
||||||
testInfo,
|
|
||||||
folderSetupFn: async () => {},
|
|
||||||
})
|
|
||||||
|
|
||||||
const { panesOpen, createAndSelectProject, createNewFile } =
|
const { panesOpen, createAndSelectProject, createNewFile } =
|
||||||
await getUtils(page, test)
|
await getUtils(tronApp.page, test)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||||
page.on('console', console.log)
|
tronApp.page.on('console', console.log)
|
||||||
|
|
||||||
await panesOpen(['files'])
|
await panesOpen(['files'])
|
||||||
|
|
||||||
@ -101,23 +182,21 @@ test.describe('when using the file tree to', () => {
|
|||||||
|
|
||||||
await test.step('Postcondition: there are 5 new Untitled-*.kcl files', async () => {
|
await test.step('Postcondition: there are 5 new Untitled-*.kcl files', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
page
|
tronApp.page
|
||||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
.filter({ hasText: /Untitled[-]?[0-5]?/ })
|
.filter({ hasText: /Untitled[-]?[0-5]?/ })
|
||||||
).toHaveCount(5)
|
).toHaveCount(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await tronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'create a new file with the same name as an existing file cancels the operation',
|
'create a new file with the same name as an existing file cancels the operation',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _, tronApp }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await tronApp.initialise()
|
||||||
testInfo,
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
openKclCodePanel,
|
openKclCodePanel,
|
||||||
@ -128,10 +207,10 @@ test.describe('when using the file tree to', () => {
|
|||||||
renameFile,
|
renameFile,
|
||||||
selectFile,
|
selectFile,
|
||||||
editorTextMatches,
|
editorTextMatches,
|
||||||
} = await getUtils(page, test)
|
} = await getUtils(tronApp.page, _test)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||||
page.on('console', console.log)
|
tronApp.page.on('console', console.log)
|
||||||
|
|
||||||
await createAndSelectProject('project-000')
|
await createAndSelectProject('project-000')
|
||||||
await openKclCodePanel()
|
await openKclCodePanel()
|
||||||
@ -159,25 +238,22 @@ test.describe('when using the file tree to', () => {
|
|||||||
await selectFile(kcl1)
|
await selectFile(kcl1)
|
||||||
await editorTextMatches(kclCube)
|
await editorTextMatches(kclCube)
|
||||||
})
|
})
|
||||||
await page.waitForTimeout(500)
|
await tronApp.page.waitForTimeout(500)
|
||||||
|
|
||||||
await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => {
|
await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => {
|
||||||
await selectFile(kcl2)
|
await selectFile(kcl2)
|
||||||
await editorTextMatches(kclCylinder)
|
await editorTextMatches(kclCylinder)
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await tronApp?.close?.()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'deleting all files recreates a default main.kcl with no code',
|
'deleting all files recreates a default main.kcl with no code',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _, tronApp }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await tronApp.initialise()
|
||||||
testInfo,
|
|
||||||
folderSetupFn: async () => {},
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
panesOpen,
|
panesOpen,
|
||||||
@ -185,10 +261,10 @@ test.describe('when using the file tree to', () => {
|
|||||||
pasteCodeInEditor,
|
pasteCodeInEditor,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
editorTextMatches,
|
editorTextMatches,
|
||||||
} = await getUtils(page, test)
|
} = await getUtils(tronApp.page, _test)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||||
page.on('console', console.log)
|
tronApp.page.on('console', console.log)
|
||||||
|
|
||||||
await panesOpen(['files', 'code'])
|
await panesOpen(['files', 'code'])
|
||||||
|
|
||||||
@ -208,7 +284,7 @@ test.describe('when using the file tree to', () => {
|
|||||||
await editorTextMatches('')
|
await editorTextMatches('')
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await tronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -217,10 +293,8 @@ test.describe('when using the file tree to', () => {
|
|||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@electron',
|
||||||
},
|
},
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _, tronApp }, testInfo) => {
|
||||||
const { page } = await setupElectron({
|
await tronApp.initialise()
|
||||||
testInfo,
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
panesOpen,
|
panesOpen,
|
||||||
@ -230,10 +304,10 @@ test.describe('when using the file tree to', () => {
|
|||||||
openDebugPanel,
|
openDebugPanel,
|
||||||
closeDebugPanel,
|
closeDebugPanel,
|
||||||
expectCmdLog,
|
expectCmdLog,
|
||||||
} = await getUtils(page, test)
|
} = await getUtils(tronApp.page, test)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||||
page.on('console', console.log)
|
tronApp.page.on('console', console.log)
|
||||||
|
|
||||||
await panesOpen(['files', 'code'])
|
await panesOpen(['files', 'code'])
|
||||||
await createAndSelectProject('project-000')
|
await createAndSelectProject('project-000')
|
||||||
@ -248,30 +322,30 @@ test.describe('when using the file tree to', () => {
|
|||||||
|
|
||||||
// Create a large lego file
|
// Create a large lego file
|
||||||
await createNewFile('lego')
|
await createNewFile('lego')
|
||||||
const legoFile = page.getByRole('listitem').filter({
|
const legoFile = tronApp.page.getByRole('listitem').filter({
|
||||||
has: page.getByRole('button', { name: 'lego.kcl' }),
|
has: tronApp.page.getByRole('button', { name: 'lego.kcl' }),
|
||||||
})
|
})
|
||||||
await expect(legoFile).toBeVisible({ timeout: 60_000 })
|
await _expect(legoFile).toBeVisible({ timeout: 60_000 })
|
||||||
await legoFile.click()
|
await legoFile.click()
|
||||||
const kclLego = await fsp.readFile(
|
const kclLego = await fsp.readFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/lego.kcl',
|
'src/wasm-lib/tests/executor/inputs/lego.kcl',
|
||||||
'utf-8'
|
'utf-8'
|
||||||
)
|
)
|
||||||
await pasteCodeInEditor(kclLego)
|
await pasteCodeInEditor(kclLego)
|
||||||
const mainFile = page.getByRole('listitem').filter({
|
const mainFile = tronApp.page.getByRole('listitem').filter({
|
||||||
has: page.getByRole('button', { name: 'main.kcl' }),
|
has: tronApp.page.getByRole('button', { name: 'main.kcl' }),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Open settings and enable the debug panel
|
// Open settings and enable the debug panel
|
||||||
await page
|
await tronApp.page
|
||||||
.getByRole('link', {
|
.getByRole('link', {
|
||||||
name: 'settings Settings',
|
name: 'settings Settings',
|
||||||
})
|
})
|
||||||
.click()
|
.click()
|
||||||
await page.locator('#showDebugPanel').getByText('OffOn').click()
|
await tronApp.page.locator('#showDebugPanel').getByText('OffOn').click()
|
||||||
await page.getByTestId('settings-close-button').click()
|
await tronApp.page.getByTestId('settings-close-button').click()
|
||||||
|
|
||||||
await test.step('swap between small and large files', async () => {
|
await _test.step('swap between small and large files', async () => {
|
||||||
await openDebugPanel()
|
await openDebugPanel()
|
||||||
// Previously created a file so we need to start back at main.kcl
|
// Previously created a file so we need to start back at main.kcl
|
||||||
await mainFile.click()
|
await mainFile.click()
|
||||||
@ -283,12 +357,14 @@ test.describe('when using the file tree to', () => {
|
|||||||
await expectCmdLog('[data-message-type="execution-done"]', 60_000)
|
await expectCmdLog('[data-message-type="execution-done"]', 60_000)
|
||||||
await closeDebugPanel()
|
await closeDebugPanel()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await tronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Renaming in the file tree', () => {
|
_test.describe('Renaming in the file tree', () => {
|
||||||
test(
|
_test(
|
||||||
'A file you have open',
|
'A file you have open',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _ }, testInfo) => {
|
||||||
@ -333,56 +409,56 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||||
const codeLocator = page.locator('.cm-content')
|
const codeLocator = page.locator('.cm-content')
|
||||||
|
|
||||||
await test.step('Open project and file pane', async () => {
|
await _test.step('Open project and file pane', async () => {
|
||||||
await expect(projectLink).toBeVisible()
|
await _expect(projectLink).toBeVisible()
|
||||||
await projectLink.click()
|
await projectLink.click()
|
||||||
await expect(projectMenuButton).toBeVisible()
|
await _expect(projectMenuButton).toBeVisible()
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(fileToRename).toBeVisible()
|
await _expect(fileToRename).toBeVisible()
|
||||||
expect(checkUnRenamedFS()).toBeTruthy()
|
_expect(checkUnRenamedFS()).toBeTruthy()
|
||||||
expect(checkRenamedFS()).toBeFalsy()
|
_expect(checkRenamedFS()).toBeFalsy()
|
||||||
await fileToRename.click()
|
await fileToRename.click()
|
||||||
await expect(projectMenuButton).toContainText('fileToRename.kcl')
|
await _expect(projectMenuButton).toContainText('fileToRename.kcl')
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
await expect(codeLocator).toContainText('circle(')
|
await _expect(codeLocator).toContainText('circle(')
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Rename the file', async () => {
|
await _test.step('Rename the file', async () => {
|
||||||
await fileToRename.click({ button: 'right' })
|
await fileToRename.click({ button: 'right' })
|
||||||
await renameMenuItem.click()
|
await renameMenuItem.click()
|
||||||
await expect(renameInput).toBeVisible()
|
await _expect(renameInput).toBeVisible()
|
||||||
await renameInput.fill(newFileName)
|
await renameInput.fill(newFileName)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Verify the file is renamed', async () => {
|
await _test.step('Verify the file is renamed', async () => {
|
||||||
await expect(fileToRename).not.toBeAttached()
|
await _expect(fileToRename).not.toBeAttached()
|
||||||
await expect(renamedFile).toBeVisible()
|
await _expect(renamedFile).toBeVisible()
|
||||||
expect(checkUnRenamedFS()).toBeFalsy()
|
_expect(checkUnRenamedFS()).toBeFalsy()
|
||||||
expect(checkRenamedFS()).toBeTruthy()
|
_expect(checkRenamedFS()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Verify we navigated', async () => {
|
await _test.step('Verify we navigated', async () => {
|
||||||
await expect(projectMenuButton).toContainText(newFileName + FILE_EXT)
|
await _expect(projectMenuButton).toContainText(newFileName + FILE_EXT)
|
||||||
const url = page.url()
|
const url = page.url()
|
||||||
expect(url).toContain(newFileName)
|
_expect(url).toContain(newFileName)
|
||||||
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
await _expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||||
await expect(projectMenuButton).not.toContainText('main.kcl')
|
await _expect(projectMenuButton).not.toContainText('main.kcl')
|
||||||
expect(url).not.toContain('fileToRename.kcl')
|
_expect(url).not.toContain('fileToRename.kcl')
|
||||||
expect(url).not.toContain('main.kcl')
|
_expect(url).not.toContain('main.kcl')
|
||||||
|
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
await expect(codeLocator).toContainText('circle(')
|
await _expect(codeLocator).toContainText('circle(')
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
_test(
|
||||||
'A file you do not have open',
|
'A file you do not have open',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _ }, testInfo) => {
|
||||||
@ -426,54 +502,54 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||||
const codeLocator = page.locator('.cm-content')
|
const codeLocator = page.locator('.cm-content')
|
||||||
|
|
||||||
await test.step('Open project and file pane', async () => {
|
await _test.step('Open project and file pane', async () => {
|
||||||
await expect(projectLink).toBeVisible()
|
await _expect(projectLink).toBeVisible()
|
||||||
await projectLink.click()
|
await projectLink.click()
|
||||||
await expect(projectMenuButton).toBeVisible()
|
await _expect(projectMenuButton).toBeVisible()
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(fileToRename).toBeVisible()
|
await _expect(fileToRename).toBeVisible()
|
||||||
expect(checkUnRenamedFS()).toBeTruthy()
|
_expect(checkUnRenamedFS()).toBeTruthy()
|
||||||
expect(checkRenamedFS()).toBeFalsy()
|
_expect(checkRenamedFS()).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Rename the file', async () => {
|
await _test.step('Rename the file', async () => {
|
||||||
await fileToRename.click({ button: 'right' })
|
await fileToRename.click({ button: 'right' })
|
||||||
await renameMenuItem.click()
|
await renameMenuItem.click()
|
||||||
await expect(renameInput).toBeVisible()
|
await _expect(renameInput).toBeVisible()
|
||||||
await renameInput.fill(newFileName)
|
await renameInput.fill(newFileName)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Verify the file is renamed', async () => {
|
await _test.step('Verify the file is renamed', async () => {
|
||||||
await expect(fileToRename).not.toBeAttached()
|
await _expect(fileToRename).not.toBeAttached()
|
||||||
await expect(renamedFile).toBeVisible()
|
await _expect(renamedFile).toBeVisible()
|
||||||
expect(checkUnRenamedFS()).toBeFalsy()
|
_expect(checkUnRenamedFS()).toBeFalsy()
|
||||||
expect(checkRenamedFS()).toBeTruthy()
|
_expect(checkRenamedFS()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Verify we have not navigated', async () => {
|
await _test.step('Verify we have not navigated', async () => {
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
await expect(projectMenuButton).not.toContainText(
|
await _expect(projectMenuButton).not.toContainText(
|
||||||
newFileName + FILE_EXT
|
newFileName + FILE_EXT
|
||||||
)
|
)
|
||||||
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
await _expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||||
|
|
||||||
const url = page.url()
|
const url = page.url()
|
||||||
expect(url).toContain('main.kcl')
|
_expect(url).toContain('main.kcl')
|
||||||
expect(url).not.toContain(newFileName)
|
_expect(url).not.toContain(newFileName)
|
||||||
expect(url).not.toContain('fileToRename.kcl')
|
_expect(url).not.toContain('fileToRename.kcl')
|
||||||
|
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
await expect(codeLocator).toContainText('fillet(')
|
await _expect(codeLocator).toContainText('fillet(')
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
_test(
|
||||||
`A folder you're not inside`,
|
`A folder you're not inside`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _ }, testInfo) => {
|
||||||
@ -519,48 +595,51 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
return fs.existsSync(folderPath)
|
return fs.existsSync(folderPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
await test.step('Open project and file pane', async () => {
|
await _test.step('Open project and file pane', async () => {
|
||||||
await expect(projectLink).toBeVisible()
|
await _expect(projectLink).toBeVisible()
|
||||||
await projectLink.click()
|
await projectLink.click()
|
||||||
await expect(projectMenuButton).toBeVisible()
|
await _expect(projectMenuButton).toBeVisible()
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
|
||||||
const url = page.url()
|
const url = page.url()
|
||||||
expect(url).toContain('main.kcl')
|
_expect(url).toContain('main.kcl')
|
||||||
expect(url).not.toContain('folderToRename')
|
_expect(url).not.toContain('folderToRename')
|
||||||
|
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(folderToRename).toBeVisible()
|
await _expect(folderToRename).toBeVisible()
|
||||||
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
_expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||||
expect(checkRenamedFolderFS()).toBeFalsy()
|
_expect(checkRenamedFolderFS()).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Rename the folder', async () => {
|
await _test.step('Rename the folder', async () => {
|
||||||
await folderToRename.click({ button: 'right' })
|
await folderToRename.click({ button: 'right' })
|
||||||
await expect(renameMenuItem).toBeVisible()
|
await _expect(renameMenuItem).toBeVisible()
|
||||||
await renameMenuItem.click()
|
await renameMenuItem.click()
|
||||||
await expect(renameInput).toBeVisible()
|
await _expect(renameInput).toBeVisible()
|
||||||
await renameInput.fill(newFolderName)
|
await renameInput.fill(newFolderName)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Verify the folder is renamed, and no navigation occurred', async () => {
|
await _test.step(
|
||||||
const url = page.url()
|
'Verify the folder is renamed, and no navigation occurred',
|
||||||
expect(url).toContain('main.kcl')
|
async () => {
|
||||||
expect(url).not.toContain('folderToRename')
|
const url = page.url()
|
||||||
|
_expect(url).toContain('main.kcl')
|
||||||
|
_expect(url).not.toContain('folderToRename')
|
||||||
|
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
await expect(renamedFolder).toBeVisible()
|
await _expect(renamedFolder).toBeVisible()
|
||||||
await expect(folderToRename).not.toBeAttached()
|
await _expect(folderToRename).not.toBeAttached()
|
||||||
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
_expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||||
expect(checkRenamedFolderFS()).toBeTruthy()
|
_expect(checkRenamedFolderFS()).toBeTruthy()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
_test(
|
||||||
`A folder you are inside`,
|
`A folder you are inside`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _ }, testInfo) => {
|
||||||
@ -609,66 +688,69 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
return fs.existsSync(folderPath)
|
return fs.existsSync(folderPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
await test.step('Open project and navigate into folder', async () => {
|
await _test.step('Open project and navigate into folder', async () => {
|
||||||
await expect(projectLink).toBeVisible()
|
await _expect(projectLink).toBeVisible()
|
||||||
await projectLink.click()
|
await projectLink.click()
|
||||||
await expect(projectMenuButton).toBeVisible()
|
await _expect(projectMenuButton).toBeVisible()
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
|
||||||
const url = page.url()
|
const url = page.url()
|
||||||
expect(url).toContain('main.kcl')
|
_expect(url).toContain('main.kcl')
|
||||||
expect(url).not.toContain('folderToRename')
|
_expect(url).not.toContain('folderToRename')
|
||||||
|
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(folderToRename).toBeVisible()
|
await _expect(folderToRename).toBeVisible()
|
||||||
await folderToRename.click()
|
await folderToRename.click()
|
||||||
await expect(fileWithinFolder).toBeVisible()
|
await _expect(fileWithinFolder).toBeVisible()
|
||||||
await fileWithinFolder.click()
|
await fileWithinFolder.click()
|
||||||
|
|
||||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
await _expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||||
const newUrl = page.url()
|
const newUrl = page.url()
|
||||||
expect(newUrl).toContain('folderToRename')
|
_expect(newUrl).toContain('folderToRename')
|
||||||
expect(newUrl).toContain('someFileWithin.kcl')
|
_expect(newUrl).toContain('someFileWithin.kcl')
|
||||||
expect(newUrl).not.toContain('main.kcl')
|
_expect(newUrl).not.toContain('main.kcl')
|
||||||
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
_expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||||
expect(checkRenamedFolderFS()).toBeFalsy()
|
_expect(checkRenamedFolderFS()).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Rename the folder', async () => {
|
await _test.step('Rename the folder', async () => {
|
||||||
await page.waitForTimeout(60000)
|
await page.waitForTimeout(60000)
|
||||||
await folderToRename.click({ button: 'right' })
|
await folderToRename.click({ button: 'right' })
|
||||||
await expect(renameMenuItem).toBeVisible()
|
await _expect(renameMenuItem).toBeVisible()
|
||||||
await renameMenuItem.click()
|
await renameMenuItem.click()
|
||||||
await expect(renameInput).toBeVisible()
|
await _expect(renameInput).toBeVisible()
|
||||||
await renameInput.fill(newFolderName)
|
await renameInput.fill(newFolderName)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Verify the folder is renamed, and navigated to new path', async () => {
|
await _test.step(
|
||||||
const urlSnippet = encodeURIComponent(
|
'Verify the folder is renamed, and navigated to new path',
|
||||||
join(newFolderName, 'someFileWithin.kcl')
|
async () => {
|
||||||
)
|
const urlSnippet = encodeURIComponent(
|
||||||
await page.waitForURL(new RegExp(urlSnippet))
|
join(newFolderName, 'someFileWithin.kcl')
|
||||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
)
|
||||||
await expect(renamedFolder).toBeVisible()
|
await page.waitForURL(new RegExp(urlSnippet))
|
||||||
await expect(folderToRename).not.toBeAttached()
|
await _expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||||
|
await _expect(renamedFolder).toBeVisible()
|
||||||
|
await _expect(folderToRename).not.toBeAttached()
|
||||||
|
|
||||||
// URL is synchronous, so we check the other stuff first
|
// URL is synchronous, so we check the other stuff first
|
||||||
const url = page.url()
|
const url = page.url()
|
||||||
expect(url).not.toContain('main.kcl')
|
_expect(url).not.toContain('main.kcl')
|
||||||
expect(url).toContain(newFolderName)
|
_expect(url).toContain(newFolderName)
|
||||||
expect(url).toContain('someFileWithin.kcl')
|
_expect(url).toContain('someFileWithin.kcl')
|
||||||
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
_expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||||
expect(checkRenamedFolderFS()).toBeTruthy()
|
_expect(checkRenamedFolderFS()).toBeTruthy()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Deleting items from the file pane', () => {
|
_test.describe('Deleting items from the file pane', () => {
|
||||||
test(
|
_test(
|
||||||
`delete file when main.kcl exists, navigate to main.kcl`,
|
`delete file when main.kcl exists, navigate to main.kcl`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
@ -700,45 +782,48 @@ test.describe('Deleting items from the file pane', () => {
|
|||||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||||
|
|
||||||
await test.step('Open project and navigate to fileToDelete.kcl', async () => {
|
await _test.step(
|
||||||
await projectCard.click()
|
'Open project and navigate to fileToDelete.kcl',
|
||||||
await u.waitForPageLoad()
|
async () => {
|
||||||
await u.openFilePanel()
|
await projectCard.click()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await u.openFilePanel()
|
||||||
|
|
||||||
await fileToDelete.click()
|
await fileToDelete.click()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
await expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
|
await _expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
await test.step('Delete fileToDelete.kcl', async () => {
|
await _test.step('Delete fileToDelete.kcl', async () => {
|
||||||
await fileToDelete.click({ button: 'right' })
|
await fileToDelete.click({ button: 'right' })
|
||||||
await expect(deleteMenuItem).toBeVisible()
|
await _expect(deleteMenuItem).toBeVisible()
|
||||||
await deleteMenuItem.click()
|
await deleteMenuItem.click()
|
||||||
await expect(deleteConfirmation).toBeVisible()
|
await _expect(deleteConfirmation).toBeVisible()
|
||||||
await deleteConfirmation.click()
|
await deleteConfirmation.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Check deletion and navigation', async () => {
|
await _test.step('Check deletion and navigation', async () => {
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
await expect(fileToDelete).not.toBeVisible()
|
await _expect(fileToDelete).not.toBeVisible()
|
||||||
await u.closeFilePanel()
|
await u.closeFilePanel()
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
await expect(u.codeLocator).toContainText('circle(')
|
await _expect(u.codeLocator).toContainText('circle(')
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test.fixme(
|
_test.fixme(
|
||||||
'TODO - delete file we have open when main.kcl does not exist',
|
'TODO - delete file we have open when main.kcl does not exist',
|
||||||
async () => {}
|
async () => {}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
_test(
|
||||||
`Delete folder we are not in, don't navigate`,
|
`Delete folder we are not in, don't navigate`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
@ -772,32 +857,32 @@ test.describe('Deleting items from the file pane', () => {
|
|||||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||||
|
|
||||||
await test.step('Open project and open project pane', async () => {
|
await _test.step('Open project and open project pane', async () => {
|
||||||
await projectCard.click()
|
await projectCard.click()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Delete folderToDelete', async () => {
|
await _test.step('Delete folderToDelete', async () => {
|
||||||
await folderToDelete.click({ button: 'right' })
|
await folderToDelete.click({ button: 'right' })
|
||||||
await expect(deleteMenuItem).toBeVisible()
|
await _expect(deleteMenuItem).toBeVisible()
|
||||||
await deleteMenuItem.click()
|
await deleteMenuItem.click()
|
||||||
await expect(deleteConfirmation).toBeVisible()
|
await _expect(deleteConfirmation).toBeVisible()
|
||||||
await deleteConfirmation.click()
|
await deleteConfirmation.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Check deletion and no navigation', async () => {
|
await _test.step('Check deletion and no navigation', async () => {
|
||||||
await expect(folderToDelete).not.toBeAttached()
|
await _expect(folderToDelete).not.toBeAttached()
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
_test(
|
||||||
`Delete folder we are in, navigate to main.kcl`,
|
`Delete folder we are in, navigate to main.kcl`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
@ -834,36 +919,45 @@ test.describe('Deleting items from the file pane', () => {
|
|||||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||||
|
|
||||||
await test.step('Open project and navigate into folderToDelete', async () => {
|
await _test.step(
|
||||||
await projectCard.click()
|
'Open project and navigate into folderToDelete',
|
||||||
await u.waitForPageLoad()
|
async () => {
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await projectCard.click()
|
||||||
await u.closeKclCodePanel()
|
await u.waitForPageLoad()
|
||||||
await u.openFilePanel()
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
await u.closeKclCodePanel()
|
||||||
|
await u.openFilePanel()
|
||||||
|
|
||||||
await folderToDelete.click()
|
await folderToDelete.click()
|
||||||
await expect(fileWithinFolder).toBeVisible()
|
await _expect(fileWithinFolder).toBeVisible()
|
||||||
await fileWithinFolder.click()
|
await fileWithinFolder.click()
|
||||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
await _expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
await test.step('Delete folderToDelete', async () => {
|
await _test.step('Delete folderToDelete', async () => {
|
||||||
await folderToDelete.click({ button: 'right' })
|
await folderToDelete.click({ button: 'right' })
|
||||||
await expect(deleteMenuItem).toBeVisible()
|
await _expect(deleteMenuItem).toBeVisible()
|
||||||
await deleteMenuItem.click()
|
await deleteMenuItem.click()
|
||||||
await expect(deleteConfirmation).toBeVisible()
|
await _expect(deleteConfirmation).toBeVisible()
|
||||||
await deleteConfirmation.click()
|
await deleteConfirmation.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Check deletion and navigation to main.kcl', async () => {
|
await _test.step(
|
||||||
await expect(folderToDelete).not.toBeAttached()
|
'Check deletion and navigation to main.kcl',
|
||||||
await expect(fileWithinFolder).not.toBeAttached()
|
async () => {
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await _expect(folderToDelete).not.toBeAttached()
|
||||||
})
|
await _expect(fileWithinFolder).not.toBeAttached()
|
||||||
|
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test.fixme('TODO - delete folder we are in, with no main.kcl', async () => {})
|
_test.fixme(
|
||||||
|
'TODO - delete folder we are in, with no main.kcl',
|
||||||
|
async () => {}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
import type { Page } from '@playwright/test'
|
|
||||||
import { test as base } from '@playwright/test'
|
|
||||||
import { getUtils, setup, tearDown } from './test-utils'
|
|
||||||
import fsp from 'fs/promises'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { CmdBarFixture } from './cmdBarFixture'
|
|
||||||
import { EditorFixture } from './editorFixture'
|
|
||||||
import { ToolbarFixture } from './toolbarFixture'
|
|
||||||
import { SceneFixture } from './sceneFixture'
|
|
||||||
|
|
||||||
export class AuthenticatedApp {
|
|
||||||
public readonly page: Page
|
|
||||||
|
|
||||||
constructor(page: Page) {
|
|
||||||
this.page = page
|
|
||||||
}
|
|
||||||
|
|
||||||
async initialise(code = '') {
|
|
||||||
const u = await getUtils(this.page)
|
|
||||||
|
|
||||||
await this.page.addInitScript(async (code) => {
|
|
||||||
localStorage.setItem('persistCode', code)
|
|
||||||
;(window as any).playwrightSkipFilePicker = true
|
|
||||||
}, code)
|
|
||||||
|
|
||||||
await this.page.setViewportSize({ width: 1000, height: 500 })
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
}
|
|
||||||
getInputFile = (fileName: string) => {
|
|
||||||
return fsp.readFile(
|
|
||||||
join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName),
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const test = base.extend<{
|
|
||||||
app: AuthenticatedApp
|
|
||||||
cmdBar: CmdBarFixture
|
|
||||||
editor: EditorFixture
|
|
||||||
toolbar: ToolbarFixture
|
|
||||||
scene: SceneFixture
|
|
||||||
}>({
|
|
||||||
app: async ({ page }, use) => {
|
|
||||||
await use(new AuthenticatedApp(page))
|
|
||||||
},
|
|
||||||
cmdBar: async ({ page }, use) => {
|
|
||||||
await use(new CmdBarFixture(page))
|
|
||||||
},
|
|
||||||
editor: async ({ page }, use) => {
|
|
||||||
await use(new EditorFixture(page))
|
|
||||||
},
|
|
||||||
toolbar: async ({ page }, use) => {
|
|
||||||
await use(new ToolbarFixture(page))
|
|
||||||
},
|
|
||||||
scene: async ({ page }, use) => {
|
|
||||||
await use(new SceneFixture(page))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
|
||||||
await setup(context, page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
export { expect } from '@playwright/test'
|
|
@ -25,11 +25,14 @@ type CmdBarSerialised =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class CmdBarFixture {
|
export class CmdBarFixture {
|
||||||
public readonly page: Page
|
public page: Page
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
}
|
}
|
||||||
|
reConstruct = (page: Page) => {
|
||||||
|
this.page = page
|
||||||
|
}
|
||||||
|
|
||||||
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
||||||
const reviewForm = await this.page.locator('#review-form')
|
const reviewForm = await this.page.locator('#review-form')
|
@ -1,5 +1,6 @@
|
|||||||
import type { Page, Locator } from '@playwright/test'
|
import type { Page, Locator } from '@playwright/test'
|
||||||
import { expect } from '@playwright/test'
|
import { expect } from '@playwright/test'
|
||||||
|
import { sansWhitespace } from '../test-utils'
|
||||||
|
|
||||||
interface EditorState {
|
interface EditorState {
|
||||||
activeLines: Array<string>
|
activeLines: Array<string>
|
||||||
@ -7,19 +8,20 @@ interface EditorState {
|
|||||||
diagnostics: Array<string>
|
diagnostics: Array<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeWhitespace(str: string) {
|
|
||||||
return str.replace(/\s+/g, '').trim()
|
|
||||||
}
|
|
||||||
export class EditorFixture {
|
export class EditorFixture {
|
||||||
public readonly page: Page
|
public page: Page
|
||||||
|
|
||||||
private readonly diagnosticsTooltip: Locator
|
private diagnosticsTooltip!: Locator
|
||||||
private readonly diagnosticsGutterIcon: Locator
|
private diagnosticsGutterIcon!: Locator
|
||||||
private readonly codeContent: Locator
|
private codeContent!: Locator
|
||||||
private readonly activeLine: Locator
|
private activeLine!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
|
this.reConstruct(page)
|
||||||
|
}
|
||||||
|
reConstruct = (page: Page) => {
|
||||||
|
this.page = page
|
||||||
|
|
||||||
this.codeContent = page.locator('.cm-content')
|
this.codeContent = page.locator('.cm-content')
|
||||||
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
|
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
|
||||||
@ -94,16 +96,16 @@ export class EditorFixture {
|
|||||||
this._serialiseDiagnostics(),
|
this._serialiseDiagnostics(),
|
||||||
])
|
])
|
||||||
const state: EditorState = {
|
const state: EditorState = {
|
||||||
activeLines: activeLines.map(removeWhitespace).filter(Boolean),
|
activeLines: activeLines.map(sansWhitespace).filter(Boolean),
|
||||||
highlightedCode: removeWhitespace(highlightedCode),
|
highlightedCode: sansWhitespace(highlightedCode),
|
||||||
diagnostics,
|
diagnostics,
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
.toEqual({
|
.toEqual({
|
||||||
activeLines: expectedState.activeLines.map(removeWhitespace),
|
activeLines: expectedState.activeLines.map(sansWhitespace),
|
||||||
highlightedCode: removeWhitespace(expectedState.highlightedCode),
|
highlightedCode: sansWhitespace(expectedState.highlightedCode),
|
||||||
diagnostics: expectedState.diagnostics.map(removeWhitespace),
|
diagnostics: expectedState.diagnostics.map(sansWhitespace),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
140
e2e/playwright/fixtures/fixtureSetup.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import type {
|
||||||
|
BrowserContext,
|
||||||
|
ElectronApplication,
|
||||||
|
Page,
|
||||||
|
TestInfo,
|
||||||
|
} from '@playwright/test'
|
||||||
|
import { test as base } from '@playwright/test'
|
||||||
|
import { getUtils, setup, setupElectron, tearDown } from '../test-utils'
|
||||||
|
import fsp from 'fs/promises'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { CmdBarFixture } from './cmdBarFixture'
|
||||||
|
import { EditorFixture } from './editorFixture'
|
||||||
|
import { ToolbarFixture } from './toolbarFixture'
|
||||||
|
import { SceneFixture } from './sceneFixture'
|
||||||
|
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||||
|
import { HomePageFixture } from './homePageFixture'
|
||||||
|
import { unsafeTypedKeys } from 'lib/utils'
|
||||||
|
|
||||||
|
export class AuthenticatedApp {
|
||||||
|
public readonly page: Page
|
||||||
|
public readonly context: BrowserContext
|
||||||
|
public readonly testInfo: TestInfo
|
||||||
|
|
||||||
|
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||||
|
this.page = page
|
||||||
|
this.context = context
|
||||||
|
this.testInfo = testInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialise(code = '') {
|
||||||
|
await setup(this.context, this.page, this.testInfo)
|
||||||
|
const u = await getUtils(this.page)
|
||||||
|
|
||||||
|
await this.page.addInitScript(async (code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
;(window as any).playwrightSkipFilePicker = true
|
||||||
|
}, code)
|
||||||
|
|
||||||
|
await this.page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
}
|
||||||
|
getInputFile = (fileName: string) => {
|
||||||
|
return fsp.readFile(
|
||||||
|
join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName),
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Fixtures {
|
||||||
|
app: AuthenticatedApp
|
||||||
|
tronApp: AuthenticatedTronApp
|
||||||
|
cmdBar: CmdBarFixture
|
||||||
|
editor: EditorFixture
|
||||||
|
toolbar: ToolbarFixture
|
||||||
|
scene: SceneFixture
|
||||||
|
homePage: HomePageFixture
|
||||||
|
}
|
||||||
|
export class AuthenticatedTronApp {
|
||||||
|
public readonly _page: Page
|
||||||
|
public page: Page
|
||||||
|
public readonly context: BrowserContext
|
||||||
|
public readonly testInfo: TestInfo
|
||||||
|
public electronApp?: ElectronApplication
|
||||||
|
|
||||||
|
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||||
|
this._page = page
|
||||||
|
this.page = page
|
||||||
|
this.context = context
|
||||||
|
this.testInfo = testInfo
|
||||||
|
}
|
||||||
|
async initialise(
|
||||||
|
arg: {
|
||||||
|
fixtures: Partial<Fixtures>
|
||||||
|
folderSetupFn?: (projectDirName: string) => Promise<void>
|
||||||
|
cleanProjectDir?: boolean
|
||||||
|
appSettings?: Partial<SaveSettingsPayload>
|
||||||
|
} = { fixtures: {} }
|
||||||
|
) {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo: this.testInfo,
|
||||||
|
folderSetupFn: arg.folderSetupFn,
|
||||||
|
cleanProjectDir: arg.cleanProjectDir,
|
||||||
|
appSettings: arg.appSettings,
|
||||||
|
})
|
||||||
|
this.page = page
|
||||||
|
this.electronApp = electronApp
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
for (const key of unsafeTypedKeys(arg.fixtures)) {
|
||||||
|
const fixture = arg.fixtures[key]
|
||||||
|
if (
|
||||||
|
!fixture ||
|
||||||
|
fixture instanceof AuthenticatedApp ||
|
||||||
|
fixture instanceof AuthenticatedTronApp
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
fixture.reConstruct(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close = async () => {
|
||||||
|
await this.electronApp?.close?.()
|
||||||
|
}
|
||||||
|
debugPause = () =>
|
||||||
|
new Promise(() => {
|
||||||
|
console.log('UN-RESOLVING PROMISE')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const test = base.extend<Fixtures>({
|
||||||
|
app: async ({ page, context }, use, testInfo) => {
|
||||||
|
await use(new AuthenticatedApp(context, page, testInfo))
|
||||||
|
},
|
||||||
|
tronApp: async ({ page, context }, use, testInfo) => {
|
||||||
|
await use(new AuthenticatedTronApp(context, page, testInfo))
|
||||||
|
},
|
||||||
|
cmdBar: async ({ page }, use) => {
|
||||||
|
await use(new CmdBarFixture(page))
|
||||||
|
},
|
||||||
|
editor: async ({ page }, use) => {
|
||||||
|
await use(new EditorFixture(page))
|
||||||
|
},
|
||||||
|
toolbar: async ({ page }, use) => {
|
||||||
|
await use(new ToolbarFixture(page))
|
||||||
|
},
|
||||||
|
scene: async ({ page }, use) => {
|
||||||
|
await use(new SceneFixture(page))
|
||||||
|
},
|
||||||
|
homePage: async ({ page }, use) => {
|
||||||
|
await use(new HomePageFixture(page))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
export { expect } from '@playwright/test'
|
103
e2e/playwright/fixtures/homePageFixture.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import type { Page, Locator } from '@playwright/test'
|
||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
|
interface ProjectCardState {
|
||||||
|
title: string
|
||||||
|
fileCount: number
|
||||||
|
folderCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HomePageState {
|
||||||
|
projectCards: ProjectCardState[]
|
||||||
|
sortBy: 'last-modified-desc' | 'last-modified-asc' | 'name-asc' | 'name-desc'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HomePageFixture {
|
||||||
|
public page: Page
|
||||||
|
|
||||||
|
projectCard!: Locator
|
||||||
|
projectCardTitle!: Locator
|
||||||
|
projectCardFile!: Locator
|
||||||
|
projectCardFolder!: Locator
|
||||||
|
sortByDateBtn!: Locator
|
||||||
|
sortByNameBtn!: Locator
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
this.page = page
|
||||||
|
this.reConstruct(page)
|
||||||
|
}
|
||||||
|
reConstruct = (page: Page) => {
|
||||||
|
this.page = page
|
||||||
|
|
||||||
|
this.projectCard = this.page.getByTestId('project-link')
|
||||||
|
this.projectCardTitle = this.page.getByTestId('project-title')
|
||||||
|
this.projectCardFile = this.page.getByTestId('project-file-count')
|
||||||
|
this.projectCardFolder = this.page.getByTestId('project-folder-count')
|
||||||
|
|
||||||
|
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
|
||||||
|
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
||||||
|
}
|
||||||
|
|
||||||
|
private _serialiseSortBy = async (): Promise<
|
||||||
|
HomePageState['sortBy'] | null
|
||||||
|
> => {
|
||||||
|
const [dateBtnDesc, dateBtnAsc, nameBtnDesc, nameBtnAsc] =
|
||||||
|
await Promise.all([
|
||||||
|
this.sortByDateBtn.getByLabel('arrow down').isVisible(),
|
||||||
|
this.sortByDateBtn.getByLabel('arrow up').isVisible(),
|
||||||
|
this.sortByNameBtn.getByLabel('arrow down').isVisible(),
|
||||||
|
this.sortByNameBtn.getByLabel('arrow up').isVisible(),
|
||||||
|
])
|
||||||
|
if (dateBtnDesc) return 'last-modified-desc'
|
||||||
|
if (dateBtnAsc) return 'last-modified-asc'
|
||||||
|
if (nameBtnDesc) return 'name-desc'
|
||||||
|
if (nameBtnAsc) return 'name-asc'
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private _serialiseProjectCards = async (): Promise<
|
||||||
|
Array<ProjectCardState>
|
||||||
|
> => {
|
||||||
|
const projectCards = await this.projectCard.all()
|
||||||
|
const projectCardStates: Array<ProjectCardState> = []
|
||||||
|
for (const projectCard of projectCards) {
|
||||||
|
const [title, fileCount, folderCount] = await Promise.all([
|
||||||
|
(await projectCard.locator(this.projectCardTitle).textContent()) || '',
|
||||||
|
Number(await projectCard.locator(this.projectCardFile).textContent()),
|
||||||
|
Number(await projectCard.locator(this.projectCardFolder).textContent()),
|
||||||
|
])
|
||||||
|
projectCardStates.push({
|
||||||
|
title: title,
|
||||||
|
fileCount,
|
||||||
|
folderCount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return projectCardStates
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date is excluded from expectState, since it changes
|
||||||
|
* Maybe there a good sanity check we can do each time?
|
||||||
|
*/
|
||||||
|
expectState = async (expectedState: HomePageState) => {
|
||||||
|
await expect
|
||||||
|
.poll(async () => {
|
||||||
|
const [projectCards, sortBy] = await Promise.all([
|
||||||
|
this._serialiseProjectCards(),
|
||||||
|
this._serialiseSortBy(),
|
||||||
|
])
|
||||||
|
return {
|
||||||
|
projectCards,
|
||||||
|
sortBy,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toEqual(expectedState)
|
||||||
|
}
|
||||||
|
|
||||||
|
openProject = async (projectTitle: string) => {
|
||||||
|
const projectCard = this.projectCard.locator(
|
||||||
|
this.page.getByText(projectTitle)
|
||||||
|
)
|
||||||
|
await projectCard.click()
|
||||||
|
}
|
||||||
|
}
|
@ -6,18 +6,24 @@ import {
|
|||||||
doAndWaitForImageDiff,
|
doAndWaitForImageDiff,
|
||||||
openAndClearDebugPanel,
|
openAndClearDebugPanel,
|
||||||
sendCustomCmd,
|
sendCustomCmd,
|
||||||
} from './test-utils'
|
} from '../test-utils'
|
||||||
|
|
||||||
type mouseParams = {
|
type mouseParams = {
|
||||||
pixelDiff: number
|
pixelDiff: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SceneFixture {
|
export class SceneFixture {
|
||||||
public readonly page: Page
|
public page: Page
|
||||||
private readonly exeIndicator: Locator
|
|
||||||
|
private exeIndicator!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
|
this.reConstruct(page)
|
||||||
|
}
|
||||||
|
reConstruct = (page: Page) => {
|
||||||
|
this.page = page
|
||||||
|
|
||||||
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,33 +31,38 @@ export class SceneFixture {
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
{ steps }: { steps: number } = { steps: 5000 }
|
{ steps }: { steps: number } = { steps: 5000 }
|
||||||
) => [
|
) =>
|
||||||
(params?: mouseParams) => {
|
[
|
||||||
if (params?.pixelDiff) {
|
(clickParams?: mouseParams) => {
|
||||||
return doAndWaitForImageDiff(
|
if (clickParams?.pixelDiff) {
|
||||||
this.page,
|
return doAndWaitForImageDiff(
|
||||||
() => this.page.mouse.click(x, y),
|
this.page,
|
||||||
params.pixelDiff
|
() => this.page.mouse.click(x, y),
|
||||||
)
|
clickParams.pixelDiff
|
||||||
}
|
)
|
||||||
return this.page.mouse.click(x, y)
|
}
|
||||||
},
|
return this.page.mouse.click(x, y)
|
||||||
(params?: mouseParams) => {
|
},
|
||||||
if (params?.pixelDiff) {
|
(moveParams?: mouseParams) => {
|
||||||
return doAndWaitForImageDiff(
|
if (moveParams?.pixelDiff) {
|
||||||
this.page,
|
return doAndWaitForImageDiff(
|
||||||
() => this.page.mouse.move(x, y, { steps }),
|
this.page,
|
||||||
params.pixelDiff
|
() => this.page.mouse.move(x, y, { steps }),
|
||||||
)
|
moveParams.pixelDiff
|
||||||
}
|
)
|
||||||
return this.page.mouse.move(x, y, { steps })
|
}
|
||||||
},
|
return this.page.mouse.move(x, y, { steps })
|
||||||
]
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
/** Likely no where, there's a chance it will click something in the scene, depending what you have in the scene.
|
/** Likely no where, there's a chance it will click something in the scene, depending what you have in the scene.
|
||||||
*
|
*
|
||||||
* Expects the viewPort to be 1000x500 */
|
* Expects the viewPort to be 1000x500 */
|
||||||
clickNoWhere = () => this.page.mouse.click(998, 60)
|
clickNoWhere = () => this.page.mouse.click(998, 60)
|
||||||
|
/** Likely no where, there's a chance it will click something in the scene, depending what you have in the scene.
|
||||||
|
*
|
||||||
|
* Expects the viewPort to be 1000x500 */
|
||||||
|
moveNoWhere = (steps?: number) => this.page.mouse.move(998, 60, { steps })
|
||||||
|
|
||||||
moveCameraTo = async (
|
moveCameraTo = async (
|
||||||
pos: { x: number; y: number; z: number },
|
pos: { x: number; y: number; z: number },
|
79
e2e/playwright/fixtures/toolbarFixture.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import type { Page, Locator } from '@playwright/test'
|
||||||
|
import { expect } from './fixtureSetup'
|
||||||
|
import { doAndWaitForImageDiff } from '../test-utils'
|
||||||
|
|
||||||
|
export class ToolbarFixture {
|
||||||
|
public page: Page
|
||||||
|
|
||||||
|
extrudeButton!: Locator
|
||||||
|
startSketchBtn!: Locator
|
||||||
|
rectangleBtn!: Locator
|
||||||
|
exitSketchBtn!: Locator
|
||||||
|
editSketchBtn!: Locator
|
||||||
|
fileTreeBtn!: Locator
|
||||||
|
createFileBtn!: Locator
|
||||||
|
fileCreateToast!: Locator
|
||||||
|
filePane!: Locator
|
||||||
|
exeIndicator!: Locator
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
this.page = page
|
||||||
|
this.reConstruct(page)
|
||||||
|
}
|
||||||
|
reConstruct = (page: Page) => {
|
||||||
|
this.page = page
|
||||||
|
this.extrudeButton = page.getByTestId('extrude')
|
||||||
|
this.startSketchBtn = page.getByTestId('sketch')
|
||||||
|
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
||||||
|
this.exitSketchBtn = page.getByTestId('sketch-exit')
|
||||||
|
this.editSketchBtn = page.getByText('Edit Sketch')
|
||||||
|
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
|
||||||
|
this.createFileBtn = page.getByTestId('create-file-button')
|
||||||
|
|
||||||
|
this.filePane = page.locator('#files-pane')
|
||||||
|
this.fileCreateToast = page.getByText('Successfully created')
|
||||||
|
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||||
|
}
|
||||||
|
|
||||||
|
startSketchPlaneSelection = async () =>
|
||||||
|
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
||||||
|
|
||||||
|
editSketch = async () => {
|
||||||
|
await this.editSketchBtn.first().click()
|
||||||
|
// One of the rare times we want to allow a arbitrary wait
|
||||||
|
// this is for the engine animation, as it takes 500ms to complete
|
||||||
|
await this.page.waitForTimeout(600)
|
||||||
|
}
|
||||||
|
|
||||||
|
private _serialiseFileTree = async () => {
|
||||||
|
return this.page
|
||||||
|
.locator('#files-pane')
|
||||||
|
.getByTestId('file-tree-item')
|
||||||
|
.allInnerTexts()
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* TODO folders, in expect state
|
||||||
|
*/
|
||||||
|
expectFileTreeState = async (expected: string[]) => {
|
||||||
|
await expect.poll(this._serialiseFileTree).toEqual(expected)
|
||||||
|
}
|
||||||
|
createFile = async ({ wait }: { wait: boolean } = { wait: false }) => {
|
||||||
|
await this.createFileBtn.click()
|
||||||
|
await expect(this.fileCreateToast).toBeVisible()
|
||||||
|
if (wait) {
|
||||||
|
await this.fileCreateToast.waitFor({ state: 'detached' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Opens file by it's name and waits for execution to finish
|
||||||
|
*/
|
||||||
|
openFile = async (
|
||||||
|
fileName: string,
|
||||||
|
{ wait }: { wait?: boolean } = { wait: true }
|
||||||
|
) => {
|
||||||
|
await this.filePane.getByText(fileName).click()
|
||||||
|
if (wait) {
|
||||||
|
await expect(this.exeIndicator).toBeVisible({ timeout: 15_000 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
e2e/playwright/playwright-deprecated.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
/** @deprecated, import from ./fixtureSetup.ts instead */
|
||||||
|
export const _test = test
|
||||||
|
|
||||||
|
/** @deprecated, import from ./fixtureSetup.ts instead */
|
||||||
|
export const _expect = expect
|
@ -1,7 +1,7 @@
|
|||||||
import { test, expect, AuthenticatedApp } from './fixtureSetup'
|
import { test, expect, AuthenticatedApp } from './fixtures/fixtureSetup'
|
||||||
import { EditorFixture } from './editorFixture'
|
import { EditorFixture } from './fixtures/editorFixture'
|
||||||
import { SceneFixture } from './sceneFixture'
|
import { SceneFixture } from './fixtures/sceneFixture'
|
||||||
import { ToolbarFixture } from './toolbarFixture'
|
import { ToolbarFixture } from './fixtures/toolbarFixture'
|
||||||
|
|
||||||
// test file is for testing point an click code gen functionality that's not sketch mode related
|
// test file is for testing point an click code gen functionality that's not sketch mode related
|
||||||
|
|
||||||
|
@ -474,6 +474,7 @@ test(
|
|||||||
|
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
|
mask: [page.getByTestId('model-state-indicator')],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -531,6 +532,7 @@ test(
|
|||||||
// Ensure the draft rectangle looks the same as it usually does
|
// Ensure the draft rectangle looks the same as it usually does
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
|
mask: [page.getByTestId('model-state-indicator')],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -585,6 +587,7 @@ test(
|
|||||||
// Ensure the draft rectangle looks the same as it usually does
|
// Ensure the draft rectangle looks the same as it usually does
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
|
mask: [page.getByTestId('model-state-indicator')],
|
||||||
})
|
})
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`const sketch001 = startSketchOn('XZ')
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
@ -1066,3 +1066,7 @@ export async function openAndClearDebugPanel(page: Page) {
|
|||||||
await openDebugPanel(page)
|
await openDebugPanel(page)
|
||||||
return clearCommandLogs(page)
|
return clearCommandLogs(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sansWhitespace(str: string) {
|
||||||
|
return str.replace(/\s+/g, '').trim()
|
||||||
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import type { Page, Locator } from '@playwright/test'
|
|
||||||
import { doAndWaitForImageDiff } from './test-utils'
|
|
||||||
|
|
||||||
export class ToolbarFixture {
|
|
||||||
public readonly page: Page
|
|
||||||
readonly extrudeButton: Locator
|
|
||||||
readonly startSketchBtn: Locator
|
|
||||||
readonly rectangleBtn: Locator
|
|
||||||
readonly exitSketchBtn: Locator
|
|
||||||
|
|
||||||
constructor(page: Page) {
|
|
||||||
this.page = page
|
|
||||||
this.extrudeButton = page.getByTestId('extrude')
|
|
||||||
this.startSketchBtn = page.getByTestId('sketch')
|
|
||||||
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
|
||||||
this.exitSketchBtn = page.getByTestId('sketch-exit')
|
|
||||||
}
|
|
||||||
|
|
||||||
startSketchPlaneSelection = async () =>
|
|
||||||
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
|
||||||
}
|
|
@ -194,7 +194,7 @@ const FileTreeItem = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="contents" ref={itemRef}>
|
<div className="contents" data-testid="file-tree-item" ref={itemRef}>
|
||||||
{fileOrDir.children === null ? (
|
{fileOrDir.children === null ? (
|
||||||
<li
|
<li
|
||||||
className={
|
className={
|
||||||
@ -389,12 +389,14 @@ interface FileTreeProps {
|
|||||||
|
|
||||||
export const FileTreeMenu = () => {
|
export const FileTreeMenu = () => {
|
||||||
const { send } = useFileContext()
|
const { send } = useFileContext()
|
||||||
|
const { send: modelingSend } = useModelingContext()
|
||||||
|
|
||||||
function createFile() {
|
function createFile() {
|
||||||
send({
|
send({
|
||||||
type: 'Create file',
|
type: 'Create file',
|
||||||
data: { name: '', makeDir: false, shouldSetToRename: true },
|
data: { name: '', makeDir: false, shouldSetToRename: true },
|
||||||
})
|
})
|
||||||
|
modelingSend({ type: 'Cancel' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFolder() {
|
function createFolder() {
|
||||||
|
@ -104,20 +104,33 @@ function ProjectCard({
|
|||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<h3 className="font-sans relative z-0 p-2">
|
<h3
|
||||||
|
className="font-sans relative z-0 p-2"
|
||||||
|
data-testid="project-title"
|
||||||
|
>
|
||||||
{project.name?.replace(FILE_EXT, '')}
|
{project.name?.replace(FILE_EXT, '')}
|
||||||
</h3>
|
</h3>
|
||||||
)}
|
)}
|
||||||
<span className="px-2 text-chalkboard-60 text-xs">
|
<span className="px-2 text-chalkboard-60 text-xs">
|
||||||
{numberOfFiles} file{numberOfFiles === 1 ? '' : 's'}{' '}
|
<span data-testid="project-file-count">{numberOfFiles}</span> file
|
||||||
{numberOfFolders > 0 &&
|
{numberOfFiles === 1 ? '' : 's'}{' '}
|
||||||
`/ ${numberOfFolders} folder${numberOfFolders === 1 ? '' : 's'}`}
|
{numberOfFolders > 0 && (
|
||||||
|
<>
|
||||||
|
{'/ '}
|
||||||
|
<span data-testid="project-folder-count">
|
||||||
|
{numberOfFolders}
|
||||||
|
</span>{' '}
|
||||||
|
folder{numberOfFolders === 1 ? '' : 's'}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="px-2 text-chalkboard-60 text-xs">
|
<span className="px-2 text-chalkboard-60 text-xs">
|
||||||
Edited{' '}
|
Edited{' '}
|
||||||
{project.metadata && project.metadata.modified
|
<span data-testid="project-edit-date">
|
||||||
? getDisplayedTime(parseInt(project.metadata.modified))
|
{project.metadata && project.metadata.modified
|
||||||
: 'never'}
|
? getDisplayedTime(parseInt(project.metadata.modified))
|
||||||
|
: 'never'}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -15,6 +15,30 @@ export function isArray(val: any): val is unknown[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* An alternative to `Object.keys()` that returns an array of keys with types.
|
||||||
|
*
|
||||||
|
* It's UNSAFE because because of TS's structural subtyping and how at runtime, you can
|
||||||
|
* extend a JS object with whatever keys you want.
|
||||||
|
*
|
||||||
|
* Why we shouldn't be extending objects with arbitrary keys at run time, the structural subtyping
|
||||||
|
* issue could be a confusing bug, for example, in the below snippet `myKeys` is typed as
|
||||||
|
* `('x' | 'y')[]` but is really `('x' | 'y' | 'name')[]`
|
||||||
|
* ```ts
|
||||||
|
* interface Point { x: number; y: number }
|
||||||
|
* interface NamedPoint { x: number; y: number; name: string }
|
||||||
|
*
|
||||||
|
* let point: Point = { x: 1, y: 2 }
|
||||||
|
* let namedPoint: NamedPoint = { x: 1, y: 2, name: 'A' }
|
||||||
|
*
|
||||||
|
* // Structural subtyping allows this assignment
|
||||||
|
* point = namedPoint // This is allowed because NamedPoint has all properties of Point
|
||||||
|
* const myKeys = unsafeTypedKeys(point) // typed as ('x' | 'y')[] but is really ('x' | 'y' | 'name')[]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function unsafeTypedKeys<T extends object>(obj: T): Array<keyof T> {
|
||||||
|
return Object.keys(obj) as Array<keyof T>
|
||||||
|
}
|
||||||
|
/*
|
||||||
* Predicate that checks if a value is not null and not undefined. This is
|
* Predicate that checks if a value is not null and not undefined. This is
|
||||||
* useful for functions like Array::filter() and Array::find() that have
|
* useful for functions like Array::filter() and Array::find() that have
|
||||||
* overloads that accept a type guard.
|
* overloads that accept a type guard.
|
||||||
|
@ -248,6 +248,7 @@ const Home = () => {
|
|||||||
<small>Sort by</small>
|
<small>Sort by</small>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
|
data-testid="home-sort-by-name"
|
||||||
className={
|
className={
|
||||||
'text-xs border-primary/10 ' +
|
'text-xs border-primary/10 ' +
|
||||||
(!sort.includes('name')
|
(!sort.includes('name')
|
||||||
@ -269,6 +270,7 @@ const Home = () => {
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
|
data-testid="home-sort-by-modified"
|
||||||
className={
|
className={
|
||||||
'text-xs border-primary/10 ' +
|
'text-xs border-primary/10 ' +
|
||||||
(!isSortByModified
|
(!isSortByModified
|
||||||
|