mirror of
https://github.com/Kong/insomnia.git
synced 2026-11-07 13:34:09 -05:00
126 lines
3.9 KiB
TypeScript
126 lines
3.9 KiB
TypeScript
import { DEFAULT_TIMEOUT } from "@package/misc/constants";
|
|
import type { Locator, Page } from "@playwright/test";
|
|
import { expect } from "@playwright/test";
|
|
|
|
import { BaseComponent } from "./base.component";
|
|
|
|
/**
|
|
* A CodeMirror-backed editor (`.client/codemirror/code-editor.tsx` or
|
|
* `one-line-editor.tsx`). `root` is the editor container
|
|
* (`data-testid="CodeEditor"` / `"OneLineEditor"`) or anything that
|
|
* contains exactly one `.CodeMirror`.
|
|
*
|
|
* Reads and writes go through the editor's own `CodeMirror` instance API
|
|
* instead of keystrokes, which is both faster and immune to autocomplete.
|
|
*/
|
|
export class CodeEditorComponent extends BaseComponent {
|
|
/** The n-th `CodeEditor` on the page (or within `scope`). */
|
|
static nth(page: Page, index = 0, scope?: Locator): CodeEditorComponent {
|
|
return new CodeEditorComponent(
|
|
page,
|
|
(scope ?? page).getByTestId("CodeEditor").nth(index),
|
|
);
|
|
}
|
|
|
|
private directCodeMirror?: Locator;
|
|
|
|
/**
|
|
* Wraps a locator that already points at the `.CodeMirror` element
|
|
* itself (rather than a container holding one).
|
|
*/
|
|
static fromCodeMirror(page: Page, codeMirror: Locator): CodeEditorComponent {
|
|
const editor = new CodeEditorComponent(page, codeMirror);
|
|
editor.directCodeMirror = codeMirror;
|
|
return editor;
|
|
}
|
|
|
|
get codeMirror(): Locator {
|
|
return this.directCodeMirror ?? this.root.locator(".CodeMirror").first();
|
|
}
|
|
|
|
async getValue(): Promise<string> {
|
|
return this.codeMirror.evaluate(
|
|
(el) => (el as any).CodeMirror?.getValue() ?? "",
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sets the value and confirms it holds for 200ms, retrying the whole
|
|
* write if a background re-render (e.g. a `{{ }}` tag widget redraw or a
|
|
* still-initializing document patch) reverts it.
|
|
*/
|
|
async setValue(text: string): Promise<void> {
|
|
const deadline = Date.now() + DEFAULT_TIMEOUT;
|
|
let attempt = 0;
|
|
do {
|
|
attempt++;
|
|
await expect(async () => {
|
|
await this.codeMirror.evaluate(
|
|
(el, value) => (el as any).CodeMirror?.setValue(value),
|
|
text,
|
|
);
|
|
expect(await this.getValue()).toBe(text);
|
|
}).toPass({ timeout: DEFAULT_TIMEOUT });
|
|
if (await this.valueHolds(text, 200)) return;
|
|
await new Promise<void>((resolve) =>
|
|
setTimeout(resolve, Math.min(250 * attempt, 1000)),
|
|
);
|
|
} while (Date.now() < deadline);
|
|
throw new Error(
|
|
`CodeMirror value "${text}" did not hold after ${attempt} attempts`,
|
|
);
|
|
}
|
|
|
|
private async valueHolds(
|
|
expected: string,
|
|
durationMs: number,
|
|
): Promise<boolean> {
|
|
const deadline = Date.now() + durationMs;
|
|
while (Date.now() < deadline) {
|
|
if ((await this.getValue()) !== expected) return false;
|
|
await this.waitForRendererIdle();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async clear(): Promise<void> {
|
|
await this.setValue("");
|
|
}
|
|
|
|
async focus(): Promise<void> {
|
|
await this.codeMirror.evaluate((el) => (el as any).CodeMirror?.focus());
|
|
}
|
|
|
|
/** The app sets `data-focused="on"` on the container on focus/blur. */
|
|
async hasFocus(): Promise<boolean> {
|
|
return (await this.root.getAttribute("data-focused")) === "on";
|
|
}
|
|
|
|
async expectValue(
|
|
expected: string | RegExp,
|
|
timeout = DEFAULT_TIMEOUT,
|
|
): Promise<void> {
|
|
await expect(async () => {
|
|
const value = await this.getValue();
|
|
if (typeof expected === "string") expect(value).toBe(expected);
|
|
else expect(value).toMatch(expected);
|
|
}).toPass({ timeout });
|
|
}
|
|
|
|
/** Types through the keyboard; use only when the key events matter. */
|
|
async type(text: string): Promise<void> {
|
|
await this.codeMirror.click();
|
|
await this.page.keyboard.type(text);
|
|
}
|
|
}
|
|
|
|
/** Single-line variant (`data-testid="OneLineEditor"`): url bar, kv cells. */
|
|
export class OneLineEditorComponent extends CodeEditorComponent {
|
|
static nth(page: Page, index = 0, scope?: Locator): OneLineEditorComponent {
|
|
return new OneLineEditorComponent(
|
|
page,
|
|
(scope ?? page).getByTestId("OneLineEditor").nth(index),
|
|
);
|
|
}
|
|
}
|