penpot/frontend/playwright/ui/pages/DashboardPage.js
Eva Marco 33d304c0af
♻️ Use DS icons in the dashboard sidebar (#12534)
* ♻️ Use DS icons in the dashboard sidebar

The search, clear search, pinned projects placeholder and logout icons
of the dashboard sidebar use the DS icons, and they get their color
from the color property. The two search buttons had untranslated test
names as their labels ("dashboard-search"); they are now named "Search"
and "Clear search". The pin keeps its place: the DS small icon draws
12px inside a 16px box, so its margins shrink by 2px.

New key: dashboard.clear-search (en, es). The dashboard page object
looks the search button up by its new name. A visual test covers the
search box with and without a term, the pinned projects placeholder and
the profile menu.

AI-assisted-by: claude-opus-5-5

* ♻️ Move dashboard sidebar styles off the deprecated styles

Legacy color aliases resolve to their DS tokens, deprecated mixins,
sizes and font variables are inlined with DS values, physical
properties become logical ones and the DS typography module is used
without a namespace. An invalid max-width with two values, which the
browser ignored, is gone.

Two rules that match nothing are commented out with a TODO: the profile
menu arrow svg rule (the arrow is the DS icon svg itself) and the
upgrade plan section, which sidebar.cljs does not use.

refactor/ stays only for the profile menu placeholders (%menu-dropdown,
%menu-item-base) and the shared dashboard header styles
(common-dashboard). No visual change.

AI-assisted-by: claude-opus-5-5

* 🔥 Remove unused styles from the dashboard sidebar

The SCSS refactor of the dashboard sidebar left rules commented out with
a TODO because the module does not use them, or because they target an
svg inside a DS icon that is the svg itself. CSS modules scope the
classes to this module and no other source uses them, so they go:
.profile-dropdown-item .open-arrow svg, .upgrade-plan-section,
.penpot-free, .no-limits, .power-up. No visual change.

AI-assisted-by: claude-opus-5-5
2026-10-27 11:41:19 +01:00

332 lines
8.4 KiB
JavaScript

import { expect } from "@playwright/test";
import { BaseWebSocketPage } from "./BaseWebSocketPage";
export class DashboardPage extends BaseWebSocketPage {
static async init(page) {
await super.init(page);
await super.mockConfigFlags(page, ["disable-onboarding"]);
await super.mockRPC(
page,
"get-teams",
"logged-in-user/get-teams-default.json",
);
await super.mockRPC(
page,
"get-font-variants?team-id=*",
"workspace/get-font-variants-empty.json",
);
await super.mockRPC(
page,
"get-projects?team-id=*",
"logged-in-user/get-projects-default.json",
);
await super.mockRPC(
page,
"get-team-members?team-id=*",
"logged-in-user/get-team-members-your-penpot.json",
);
await super.mockRPC(
page,
"get-team-users?team-id=*",
"logged-in-user/get-team-users-single-user.json",
);
await super.mockRPC(
page,
"get-unread-comment-threads?team-id=*",
"logged-in-user/get-team-users-single-user.json",
);
await super.mockRPC(
page,
"get-team-recent-files?team-id=*",
"logged-in-user/get-team-recent-files-empty.json",
);
await super.mockRPC(
page,
"get-profiles-for-file-comments",
"workspace/get-profile-for-file-comments.json",
);
await super.mockRPC(
page,
"get-builtin-templates",
"logged-in-user/get-built-in-templates-empty.json",
);
await super.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in.json",
);
}
static anyTeamId = "c7ce0794-0992-8105-8004-38e630f40f6d";
static secondTeamId = "dd33ff88-f4e5-8033-8003-8096cc07bdf3";
static newTeamId = "0b5bcbca-32ab-81eb-8005-a153d23d7739";
static draftProjectId = "c7ce0794-0992-8105-8004-38e630f7920b";
constructor(page) {
super(page);
this.sidebar = page.getByTestId("dashboard-sidebar");
this.sidebarMenu = this.sidebar.getByRole("menu");
this.mainHeading = page
.getByTestId("dashboard-header")
.getByRole("heading", { level: 1 });
this.addProjectButton = page.getByRole("button", { name: "+ NEW PROJECT" });
this.projectName = page.getByText("Project 1");
this.draftsLink = this.sidebar.getByText("Drafts");
this.fontsLink = this.sidebar.getByText("Fonts");
this.librariesLink = this.sidebar.getByText("Libraries");
this.searchButton = page.getByRole("button", {
name: "Search",
exact: true,
});
this.searchInput = page.getByPlaceholder("Search…");
this.teamDropdown = this.sidebar.getByRole("button", {
name: "Personal Projects",
});
this.userAccount = this.sidebar.getByRole("button", {
name: /Princesa Leia/,
});
this.userProfileOption = this.sidebarMenu.getByText("Your account");
}
async setupDraftsEmpty() {
await this.mockRPC(
"get-project-files?project-id=*",
"dashboard/get-project-files-empty.json",
);
}
async setupSearchEmpty() {
await this.mockRPC("search-files", "dashboard/search-files-empty.json", {
method: "POST",
});
}
async setupLibrariesEmpty() {
await this.mockRPC(
"get-team-shared-files?team-id=*",
"dashboard/get-shared-files-empty.json",
);
}
async setupDeletedFiles() {
await this.mockRPC(
"get-team-deleted-files?team-id=*",
"dashboard/get-team-deleted-files.json",
);
}
async setupDrafts() {
await this.mockRPC(
"get-project-files?project-id=*",
"dashboard/get-project-files.json",
);
await this.mockRPC(/assets\/by-id/gi, "dashboard/thumbnail.png", {
contentType: "image/png",
});
}
async setupNewProject() {
await this.mockRPC("create-project", "dashboard/create-project.json", {
method: "POST",
});
await this.mockRPC(
"get-projects?team-id=*",
"dashboard/get-projects-new.json",
);
}
async setupDashboardFull() {
await this.mockRPC(
"get-projects?team-id=*",
"dashboard/get-projects-full.json",
);
await this.mockRPC(
"get-project-files?project-id=*",
"dashboard/get-project-files.json",
);
await this.mockRPC(
"get-team-shared-files?team-id=*",
"dashboard/get-shared-files.json",
);
await this.mockRPC(
"get-team-shared-files?project-id=*",
"dashboard/get-shared-files.json",
);
await this.mockRPC(
"get-team-recent-files?team-id=*",
"dashboard/get-team-recent-files.json",
);
await this.mockRPC(
"get-font-variants?team-id=*",
"dashboard/get-font-variants.json",
);
await this.mockRPC("search-files", "dashboard/search-files.json", {
method: "POST",
});
await this.mockRPC("delete-team", "dashboard/delete-team.json", {
method: "POST",
});
await this.mockRPC("search-files", "dashboard/search-files.json");
await this.mockRPC("get-teams", "logged-in-user/get-teams-complete.json");
await this.mockRPC(
"get-team-deleted-files?team-id=*",
"dashboard/get-team-deleted-files.json",
);
}
async setupAccessTokensEmpty() {
await this.mockRPC(
"get-access-tokens",
"dashboard/get-access-tokens-empty.json",
);
}
async createAccessToken() {
await this.mockRPC(
"create-access-token",
"dashboard/create-access-token.json",
{ method: "POST" },
);
}
async setupAccessTokens() {
await this.mockRPC("get-access-tokens", "dashboard/get-access-tokens.json");
}
async setupTeamInvitationsEmpty() {
await this.mockRPC(
"get-team-invitations?team-id=*",
"dashboard/get-team-invitations-empty.json",
);
}
async setupTeamInvitations() {
await this.mockRPC(
"get-team-invitations?team-id=*",
"dashboard/get-team-invitations.json",
);
}
async setupTeamWebhooksEmpty() {
await this.mockRPC(
"get-webhooks?team-id=*",
"dashboard/get-webhooks-empty.json",
);
}
async setupTeamWebhooks() {
await this.mockRPC("get-webhooks?team-id=*", "dashboard/get-webhooks.json");
}
async setupTeamSettings() {
await this.mockRPC(
"get-team-stats?team-id=*",
"dashboard/get-team-stats.json",
);
}
async goToDashboard() {
await this.page.goto(
`/?screen=dashboard-recent&team-id=${DashboardPage.anyTeamId}`,
);
await expect(this.mainHeading).toBeVisible();
}
async goToSecondTeamDashboard() {
await this.page.goto(
`/?screen=dashboard-recent&team-id=${DashboardPage.secondTeamId}`,
);
}
async goToSecondTeamMembersSection() {
await this.page.goto(
`/?screen=dashboard-members&team-id=${DashboardPage.secondTeamId}`,
);
}
async goToSecondTeamInvitationsSection() {
await this.page.goto(
`/?screen=dashboard-invitations&team-id=${DashboardPage.secondTeamId}`,
);
}
async goToSecondTeamWebhooksSection() {
await this.page.goto(
`/?screen=dashboard-webhooks&team-id=${DashboardPage.secondTeamId}`,
);
}
async goToSecondTeamWebhooksSection() {
await this.page.goto(
`/?screen=dashboard-webhooks&team-id=${DashboardPage.secondTeamId}`,
);
}
async goToSecondTeamSettingsSection() {
await this.page.goto(
`/?screen=dashboard-settings&team-id=${DashboardPage.secondTeamId}`,
);
}
async goToSearch() {
await this.page.goto(
`/?screen=dashboard-search&team-id=${DashboardPage.anyTeamId}`,
);
}
async goToDrafts() {
await this.page.goto(
`/?screen=dashboard-files&team-id=${DashboardPage.anyTeamId}&project-id=${DashboardPage.draftProjectId}`,
);
await expect(this.mainHeading).toHaveText("Drafts");
}
async goToFonts() {
await this.page.goto(
`/?screen=dashboard-fonts&team-id=${DashboardPage.anyTeamId}`,
);
await expect(this.mainHeading).toHaveText("Fonts");
}
async goToAccount() {
await this.userAccount.click();
await this.userProfileOption.click();
}
async goToLibraries() {
await this.page.goto(
`/?screen=dashboard-libraries&team-id=${DashboardPage.anyTeamId}`,
);
await expect(this.mainHeading).toHaveText("Libraries");
}
async goToDeleted() {
await this.page.goto(
`/?screen=dashboard-deleted&team-id=${DashboardPage.anyTeamId}`,
);
await expect(this.mainHeading).toHaveText("Projects");
}
async openProfileMenu() {
await this.userAccount.click();
}
async clickProfileMenuItem(menuSection) {
await this.sidebarMenu.getByText(menuSection).click();
}
}
export default DashboardPage;