|
| 1 | +import { readFileSync } from "node:fs"; |
| 2 | +import { resolve } from "node:path"; |
| 3 | +import { expect, test } from "@playwright/test"; |
| 4 | + |
| 5 | +function sanitizeCssModules(css: string): string { |
| 6 | + return css.replaceAll(/:global\(([^)]+)\)/g, "$1"); |
| 7 | +} |
| 8 | + |
| 9 | +const componentsCss = readFileSync( |
| 10 | + resolve(process.cwd(), "../packages/web/src/styles/components.css"), |
| 11 | + "utf8" |
| 12 | +); |
| 13 | +const buttonCss = sanitizeCssModules( |
| 14 | + readFileSync( |
| 15 | + resolve(process.cwd(), "../packages/web/src/components/ui/button/index.module.css"), |
| 16 | + "utf8" |
| 17 | + ) |
| 18 | +); |
| 19 | +const iconButtonCss = sanitizeCssModules( |
| 20 | + readFileSync( |
| 21 | + resolve(process.cwd(), "../packages/web/src/components/ui/icon-button/index.module.css"), |
| 22 | + "utf8" |
| 23 | + ) |
| 24 | +); |
| 25 | + |
| 26 | +test("topbar close button stays vertically centered while hovered", async ({ page }) => { |
| 27 | + await page.setContent(` |
| 28 | + <html> |
| 29 | + <head> |
| 30 | + <style> |
| 31 | + :root { |
| 32 | + --bg-page: #0f1720; |
| 33 | + --bg-surface: #16212c; |
| 34 | + --bg-hover: #223140; |
| 35 | + --bg-active: #1e2c38; |
| 36 | + --border: rgba(123, 152, 180, 0.24); |
| 37 | + --border-light: rgba(173, 201, 229, 0.42); |
| 38 | + --text-primary: #edf4fb; |
| 39 | + --text-secondary: #c8d5e2; |
| 40 | + --text-tertiary: #8fa4b8; |
| 41 | + --accent-blue: #79b8ff; |
| 42 | + --shadow-xl: 0 8px 24px rgba(0, 0, 0, 0.22); |
| 43 | + --radius-sm: 5px; |
| 44 | + --radius-md: 8px; |
| 45 | + --radius-lg: 12px; |
| 46 | + --duration-fast: 120ms; |
| 47 | + --duration-normal: 180ms; |
| 48 | + --ease-out: ease; |
| 49 | + --sp-1: 4px; |
| 50 | + --sp-2: 8px; |
| 51 | + --sp-4: 16px; |
| 52 | + --btn-height-md: 32px; |
| 53 | + --text-base: 14px; |
| 54 | + --text-xs: 12px; |
| 55 | + --font-medium: 500; |
| 56 | + --font-sans: ui-sans-serif, system-ui, sans-serif; |
| 57 | + } |
| 58 | + body { |
| 59 | + margin: 0; |
| 60 | + padding: 24px; |
| 61 | + background: #0b1218; |
| 62 | + } |
| 63 | + ${buttonCss} |
| 64 | + ${iconButtonCss} |
| 65 | + ${componentsCss} |
| 66 | + </style> |
| 67 | + </head> |
| 68 | + <body> |
| 69 | + <div class="topbar-tab-shell active" style="width: 160px;"> |
| 70 | + <button class="topbar-tab active" type="button"> |
| 71 | + <span class="topbar-dot active"></span> |
| 72 | + <span class="topbar-tab-name">coder-studio</span> |
| 73 | + </button> |
| 74 | + <button class="root btn btn-ghost btn-sm topbar-close" type="button" aria-label="Close Workspace"> |
| 75 | + <span class="icon" aria-hidden="true"> |
| 76 | + <svg width="14" height="14" viewBox="0 0 24 24"> |
| 77 | + <path |
| 78 | + d="M18 6 6 18M6 6l12 12" |
| 79 | + stroke="currentColor" |
| 80 | + stroke-width="2" |
| 81 | + stroke-linecap="round" |
| 82 | + /> |
| 83 | + </svg> |
| 84 | + </span> |
| 85 | + </button> |
| 86 | + </div> |
| 87 | + </body> |
| 88 | + </html> |
| 89 | + `); |
| 90 | + |
| 91 | + const tabShell = page.locator(".topbar-tab-shell"); |
| 92 | + await tabShell.hover(); |
| 93 | + |
| 94 | + const closeButton = page.locator(".topbar-close"); |
| 95 | + await expect(closeButton).toBeVisible(); |
| 96 | + |
| 97 | + const beforeHoverBox = await closeButton.boundingBox(); |
| 98 | + expect(beforeHoverBox).not.toBeNull(); |
| 99 | + |
| 100 | + await closeButton.hover(); |
| 101 | + |
| 102 | + const afterHoverBox = await closeButton.boundingBox(); |
| 103 | + expect(afterHoverBox).not.toBeNull(); |
| 104 | + expect(Math.abs((afterHoverBox?.y ?? 0) - (beforeHoverBox?.y ?? 0))).toBeLessThan(0.5); |
| 105 | +}); |
0 commit comments