Files
modeling-app/e2e/playwright/fixtures/editorFixture.ts
Jess Frazelle d168ef94e9 Sort imports (#6101)
* add package.json

Signed-off-by: Jess Frazelle <github@jessfraz.com>

initial run;

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

more fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

clientsidescne

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

paths

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix styles

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

combine

Signed-off-by: Jess Frazelle <github@jessfraz.com>

eslint rule

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

my ocd

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

constants file

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

no more import sceneInfra

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

try fix circular import

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-02 06:54:26 +00:00

226 lines
7.0 KiB
TypeScript

import type { Locator, Page } from '@playwright/test'
import { expect } from '@playwright/test'
import {
checkIfPaneIsOpen,
closePane,
openPane,
sansWhitespace,
} from '@e2e/playwright/test-utils'
interface EditorState {
activeLines: Array<string>
highlightedCode: string
diagnostics: Array<string>
}
export class EditorFixture {
public page: Page
private paneButtonTestId = 'code-pane-button'
private diagnosticsTooltip!: Locator
private diagnosticsGutterIcon!: Locator
private codeContent!: Locator
public activeLine!: Locator
constructor(page: Page) {
this.page = page
this.codeContent = page.locator('.cm-content[data-language="kcl"]')
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')
this.activeLine = this.page.locator('.cm-activeLine')
}
private _expectEditorToContain =
(not = false) =>
async (
code: string,
{
shouldNormalise = false,
timeout = 5_000,
}: { shouldNormalise?: boolean; timeout?: number } = {}
) => {
const wasPaneOpen = await this.checkIfPaneIsOpen()
if (!wasPaneOpen) {
await this.openPane()
}
const resetPane = async () => {
if (!wasPaneOpen) {
await this.closePane()
}
}
if (!shouldNormalise) {
const expectStart = expect.poll(() => this.codeContent.textContent())
if (not) {
const result = await expectStart.not.toContain(code)
await resetPane()
return result
}
const result = await expectStart.toContain(code)
await resetPane()
return result
}
const normalisedCode = code.replaceAll(/\s+/g, '').trim()
const expectStart = expect.poll(
async () => {
const editorText = await this.codeContent.textContent()
return editorText?.replaceAll(/\s+/g, '').trim()
},
{
timeout,
}
)
if (not) {
const result = await expectStart.not.toContain(normalisedCode)
await resetPane()
return result
}
const result = await expectStart.toContain(normalisedCode)
await resetPane()
return result
}
expectEditor = {
toContain: this._expectEditorToContain(),
not: { toContain: this._expectEditorToContain(true) },
toBe: async (code: string) => {
const currentCode = await this.getCurrentCode()
return expect(currentCode).toBe(code)
},
}
getCurrentCode = async () => {
return await this.codeContent.innerText()
}
snapshot = async (options?: { timeout?: number; name?: string }) => {
const wasPaneOpen = await this.checkIfPaneIsOpen()
if (!wasPaneOpen) {
await this.openPane()
}
try {
// Use expect.poll to implement retry logic
await expect
.poll(
async () => {
const code = await this.codeContent.textContent()
return code || ''
},
{ timeout: options?.timeout || 5000 }
)
.toMatchSnapshot(options?.name || 'editor-content')
} finally {
// Reset pane state if needed
if (!wasPaneOpen) {
await this.closePane()
}
}
}
private _serialiseDiagnostics = async (): Promise<Array<string>> => {
const diagnostics = await this.diagnosticsGutterIcon.all()
const diagnosticsContent: string[] = []
for (const diag of diagnostics) {
await diag.hover()
const content = await this.diagnosticsTooltip.allTextContents()
diagnosticsContent.push(content.join(''))
}
return [...new Set(diagnosticsContent)].map((d) => sansWhitespace(d))
}
private _getHighlightedCode = async () => {
const texts = (
await this.page.getByTestId('hover-highlight').allInnerTexts()
).map((s) => s.replace(/\s+/g, '').trim())
return texts.join('')
}
private _getActiveLines = async () =>
(await this.activeLine.allInnerTexts()).map((l) => l.trim())
expectActiveLinesToBe = async (lines: Array<string>) => {
await expect.poll(this._getActiveLines).toEqual(lines.map((l) => l.trim()))
}
/** assert all editor state EXCEPT the code content */
expectState = async (expectedState: EditorState) => {
await expect
.poll(async () => {
const [activeLines, highlightedCode, diagnostics] = await Promise.all([
this._getActiveLines(),
this._getHighlightedCode(),
this._serialiseDiagnostics(),
])
const state: EditorState = {
activeLines: activeLines.map(sansWhitespace).filter(Boolean),
highlightedCode: sansWhitespace(highlightedCode),
diagnostics,
}
return state
})
.toEqual({
activeLines: expectedState.activeLines.map(sansWhitespace),
highlightedCode: sansWhitespace(expectedState.highlightedCode),
diagnostics: expectedState.diagnostics.map(sansWhitespace),
})
}
replaceCode = async (findCode: string, replaceCode: string) => {
const lines = await this.page.locator('.cm-line').all()
let code = (await Promise.all(lines.map((c) => c.textContent()))).join('\n')
if (!findCode) {
// nuke everything
code = replaceCode
} else {
if (!lines) return
code = code.replace(findCode, replaceCode)
}
await this.codeContent.fill(code)
}
checkIfPaneIsOpen() {
return checkIfPaneIsOpen(this.page, this.paneButtonTestId)
}
closePane() {
return closePane(this.page, this.paneButtonTestId)
}
openPane() {
return openPane(this.page, this.paneButtonTestId)
}
scrollToText(text: string, placeCursor?: boolean) {
return this.page.evaluate(
(args: { text: string; placeCursor?: boolean }) => {
// error TS2339: Property 'docView' does not exist on type 'EditorView'.
// Except it does so :shrug:
// @ts-ignore
let index = window.editorManager._editorView?.docView.view.state.doc
.toString()
.indexOf(args.text)
window.editorManager._editorView?.focus()
window.editorManager._editorView?.dispatch({
selection: window.EditorSelection.create([
window.EditorSelection.cursor(index),
]),
effects: [
window.EditorView.scrollIntoView(
window.EditorSelection.range(index, index + 1)
),
],
})
},
{ text, placeCursor }
)
}
async selectText(text: string) {
// First make sure the code pane is open
const wasPaneOpen = await this.checkIfPaneIsOpen()
if (!wasPaneOpen) {
await this.openPane()
}
// Use Playwright's built-in text selection on the code content
// it seems to only select whole divs, which works out to align with syntax highlighting
// for code mirror, so you can probably select "sketch002 = startSketchOn(XZ)"
// but less so for exactly "sketch002 = startS"
await this.codeContent.getByText(text).first().selectText()
// Reset pane state if needed
if (!wasPaneOpen) {
await this.closePane()
}
}
}