mirror of
https://github.com/penpot/penpot.git
synced 2026-07-29 01:16:14 +00:00
329 lines
8.9 KiB
JavaScript
329 lines
8.9 KiB
JavaScript
import { expect } from "@playwright/test";
|
|
import { readFile } from "node:fs/promises";
|
|
import { BaseWebSocketPage } from "./BaseWebSocketPage";
|
|
|
|
function decodeKeyword(value) {
|
|
if (typeof value === "string" && value.startsWith("~:")) {
|
|
return value.slice(2);
|
|
}
|
|
if (typeof value === "string" && value.startsWith("~$")) {
|
|
return value.slice(2);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function decodeTransit(data) {
|
|
if (Array.isArray(data)) {
|
|
if (data[0] === "^ ") {
|
|
const result = {};
|
|
for (let i = 1; i < data.length; i += 2) {
|
|
const key = decodeTransit(data[i]);
|
|
const value = decodeTransit(data[i + 1]);
|
|
result[key] = value;
|
|
}
|
|
return result;
|
|
}
|
|
return data.map(decodeTransit);
|
|
}
|
|
|
|
if (data !== null && typeof data === "object") {
|
|
const result = {};
|
|
for (const [key, value] of Object.entries(data)) {
|
|
result[decodeKeyword(key)] = decodeTransit(value);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
return decodeKeyword(data);
|
|
}
|
|
|
|
function encodeKeyword(key) {
|
|
return `~:${key}`;
|
|
}
|
|
|
|
function encodeTransit(data) {
|
|
if (data === null || typeof data === "boolean" || typeof data === "number") {
|
|
return data;
|
|
}
|
|
|
|
if (typeof data === "string") {
|
|
return data;
|
|
}
|
|
|
|
if (Array.isArray(data)) {
|
|
return data.map(encodeTransit);
|
|
}
|
|
|
|
const result = {};
|
|
for (const [key, value] of Object.entries(data)) {
|
|
result[encodeKeyword(key)] = encodeTransit(value);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function decodeRequestBody(body) {
|
|
try {
|
|
return decodeTransit(JSON.parse(body));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export class ShortcutsPage extends BaseWebSocketPage {
|
|
static async init(page) {
|
|
await super.init(page);
|
|
|
|
const profileText = await readFile(
|
|
"playwright/data/logged-in-user/get-profile-logged-in.json",
|
|
"utf-8",
|
|
);
|
|
const baseProfile = decodeTransit(JSON.parse(profileText));
|
|
let customShortcuts = null;
|
|
|
|
await page.route("**/api/main/methods/get-profile", (route) => {
|
|
const profile = JSON.parse(JSON.stringify(baseProfile));
|
|
if (customShortcuts) {
|
|
profile.props["custom-shortcuts"] = customShortcuts;
|
|
}
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: "application/transit+json",
|
|
body: JSON.stringify(encodeTransit(profile)),
|
|
});
|
|
});
|
|
|
|
await page.route(
|
|
"**/api/main/methods/update-profile-props",
|
|
async (route, request) => {
|
|
const decoded = decodeRequestBody(request.postData() ?? "{}");
|
|
if (decoded?.props && "custom-shortcuts" in decoded.props) {
|
|
customShortcuts = decoded.props["custom-shortcuts"];
|
|
}
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: "application/transit+json",
|
|
body: "{}",
|
|
});
|
|
},
|
|
);
|
|
|
|
await super.mockRPC(
|
|
page,
|
|
"get-teams",
|
|
"logged-in-user/get-teams-default.json",
|
|
);
|
|
}
|
|
|
|
static async initWithShortcuts(page) {
|
|
await super.init(page);
|
|
|
|
await super.mockRPCs(page, {
|
|
"get-profile": "logged-in-user/get-profile-logged-in.json",
|
|
"get-teams": "logged-in-user/get-teams-default.json",
|
|
"update-profile-props":
|
|
"logged-in-user/update-profile-with-shortcuts.json",
|
|
});
|
|
}
|
|
|
|
static async initWithNoShortcuts(page) {
|
|
await super.init(page);
|
|
|
|
await super.mockRPCs(page, {
|
|
"get-profile": "logged-in-user/get-profile-no-shortcuts.json",
|
|
"get-teams": "logged-in-user/get-teams-default.json",
|
|
"update-profile-props":
|
|
"logged-in-user/update-profile-with-shortcuts.json",
|
|
});
|
|
}
|
|
|
|
constructor(page) {
|
|
super(page);
|
|
|
|
this.shortcutsSection = page.getByRole("region", { name: /shortcuts/i });
|
|
this.searchInput = page.getByRole("textbox", { name: /shortcuts/i });
|
|
|
|
this.allTab = page.getByRole("tab", { name: "All" });
|
|
this.personalizedTab = page.getByRole("tab", { name: "Personalized" });
|
|
this.disabledTab = page.getByRole("tab", { name: "Disabled" });
|
|
|
|
this.restoreAllButton = page.getByRole("button", {
|
|
name: /restore all/i,
|
|
});
|
|
|
|
this.importExportButton = page.getByRole("button", {
|
|
name: /import\/export/i,
|
|
});
|
|
|
|
this.fileInput = page.locator('input[type="file"]');
|
|
}
|
|
|
|
async goToShortcuts() {
|
|
await this.page.goto("#/settings/shortcuts");
|
|
await expect(this.shortcutsSection).toBeVisible();
|
|
}
|
|
|
|
async searchForShortcut(term) {
|
|
await this.searchInput.fill(term);
|
|
}
|
|
|
|
async clearSearch() {
|
|
await this.searchInput.clear();
|
|
}
|
|
|
|
async clickTab(tabName) {
|
|
const tab = this.page.getByRole("tab", { name: tabName });
|
|
await tab.click();
|
|
}
|
|
|
|
async getShortcutRow(shortcutName) {
|
|
return this.page.getByRole("listitem", {
|
|
name: new RegExp(`^${shortcutName}$`, "i"),
|
|
includeHidden: true,
|
|
});
|
|
}
|
|
|
|
async expandSubsection(subsectionName) {
|
|
const subsectionButton = this.page.getByRole("button", {
|
|
name: subsectionName,
|
|
});
|
|
await subsectionButton.click();
|
|
}
|
|
|
|
async clickEditShortcut(shortcutName) {
|
|
const button = this.page.getByRole("button", {
|
|
name: new RegExp(`Edit ${shortcutName}`, "i"),
|
|
});
|
|
await button.click();
|
|
const recordingArea = this.page.getByText("Press the key combination");
|
|
await expect(recordingArea).toBeVisible();
|
|
await recordingArea.focus();
|
|
}
|
|
|
|
async pressKey(key) {
|
|
await this.page.keyboard.press(key);
|
|
}
|
|
|
|
async pressKeyCombo(keys) {
|
|
for (const key of keys) {
|
|
await this.page.keyboard.down(key);
|
|
}
|
|
for (const key of [...keys].reverse()) {
|
|
await this.page.keyboard.up(key);
|
|
}
|
|
}
|
|
|
|
async saveShortcut() {
|
|
const saveButton = this.page.getByRole("button", { name: /save/i });
|
|
await saveButton.click();
|
|
}
|
|
|
|
async cancelEdit() {
|
|
const cancelButton = this.page.getByRole("button", { name: /cancel/i });
|
|
await cancelButton.click();
|
|
}
|
|
|
|
async resetShortcut(shortcutName) {
|
|
const row = await this.getShortcutRow(shortcutName);
|
|
const resetButton = row.getByRole("button", { name: /reset/i });
|
|
await resetButton.click();
|
|
}
|
|
|
|
async disableShortcut(shortcutName) {
|
|
const row = await this.getShortcutRow(shortcutName);
|
|
await row
|
|
.getByRole("button", { name: new RegExp(`Edit ${shortcutName}`, "i") })
|
|
.click();
|
|
const disableButton = this.page.getByRole("button", { name: /disable/i });
|
|
await disableButton.click();
|
|
}
|
|
|
|
async expectShortcutCustomized(shortcutName) {
|
|
const row = await this.getShortcutRow(shortcutName);
|
|
await expect(row).toHaveAttribute("data-customized", "true");
|
|
}
|
|
|
|
async expectShortcutNotCustomized(shortcutName) {
|
|
const row = await this.getShortcutRow(shortcutName);
|
|
await expect(row).not.toHaveAttribute("data-customized", "true");
|
|
}
|
|
|
|
async expectShortcutHasConflict(shortcutName) {
|
|
const row = await this.getShortcutRow(shortcutName);
|
|
await expect(row).toHaveAttribute("data-conflict", "true");
|
|
}
|
|
|
|
async expectShortcutVisible(shortcutName) {
|
|
const row = await this.getShortcutRow(shortcutName);
|
|
await expect(row).toBeVisible();
|
|
}
|
|
|
|
async expectShortcutHidden(shortcutName) {
|
|
const row = await this.getShortcutRow(shortcutName);
|
|
await expect(row).not.toBeVisible();
|
|
}
|
|
|
|
async restoreAllShortcuts() {
|
|
await this.restoreAllButton.click();
|
|
const confirmButton = this.page.getByRole("button", {
|
|
name: "Restore",
|
|
exact: true,
|
|
});
|
|
await confirmButton.click();
|
|
}
|
|
|
|
async exportShortcuts() {
|
|
await this.importExportButton.click();
|
|
const exportButton = this.page.getByRole("menuitem", { name: /export/i });
|
|
await exportButton.click();
|
|
}
|
|
|
|
async importShortcuts(jsonData) {
|
|
await this.importExportButton.click();
|
|
const importButton = this.page.getByRole("menuitem", { name: /import$/i });
|
|
await importButton.click();
|
|
|
|
const buffer = Buffer.from(JSON.stringify(jsonData), "utf-8");
|
|
await this.fileInput.setInputFiles({
|
|
name: "shortcuts.json",
|
|
mimeType: "application/json",
|
|
buffer,
|
|
});
|
|
}
|
|
|
|
async importShortcutsRaw(content) {
|
|
await this.importExportButton.click();
|
|
const importButton = this.page.getByRole("menuitem", { name: /import$/i });
|
|
await importButton.click();
|
|
|
|
const data =
|
|
typeof content === "string" ? content : JSON.stringify(content);
|
|
const buffer = Buffer.from(data, "utf-8");
|
|
await this.fileInput.setInputFiles({
|
|
name: "shortcuts.json",
|
|
mimeType: "application/json",
|
|
buffer,
|
|
});
|
|
}
|
|
|
|
async getExportedJson() {
|
|
const [download] = await Promise.all([
|
|
this.page.waitForEvent("download"),
|
|
this.exportShortcuts(),
|
|
]);
|
|
|
|
expect(download.suggestedFilename()).toBe("penpot-shortcuts.json");
|
|
|
|
const path = await download.path();
|
|
const content = await readFile(path, "utf-8");
|
|
return JSON.parse(content);
|
|
}
|
|
|
|
async expectShortcutDisabled(shortcutName) {
|
|
const row = await this.getShortcutRow(shortcutName);
|
|
await expect(row).toHaveAttribute("data-customized", "true");
|
|
await expect(row.locator("use[href*='broken-link']")).toBeVisible();
|
|
}
|
|
}
|
|
|
|
export default ShortcutsPage;
|