|
| 1 | +// Static contract check: CI and local development do not have a Windows host, |
| 2 | +// so this checks the CSS contract rather than rendered geometry. |
| 3 | + |
| 4 | +import { existsSync, readFileSync } from 'node:fs'; |
| 5 | +import { describe, expect, it } from 'vitest'; |
| 6 | + |
| 7 | +const appPath = ['src/App.vue', 'apps/pythinker-web/src/App.vue'].find(existsSync); |
| 8 | +if (!appPath) throw new Error('App.vue source was not found'); |
| 9 | + |
| 10 | +const appSource = readFileSync(appPath, 'utf8'); |
| 11 | +const styleMatch = appSource.match(/<style scoped>([\s\S]*?)<\/style>/); |
| 12 | + |
| 13 | +if (!styleMatch?.[1]) throw new Error('App.vue must have a scoped style block'); |
| 14 | + |
| 15 | +const cssRules = [...styleMatch[1].matchAll(/([^{}]+)\{([^{}]*)\}/g)].map(([, selector, declarations]) => ({ |
| 16 | + selector: selector!, |
| 17 | + declarations: declarations!, |
| 18 | +})); |
| 19 | +const win32 = "data-desktop-platform='win32'"; |
| 20 | +const darwin = "data-desktop-platform='darwin'"; |
| 21 | + |
| 22 | +function rulesFor(platform: string, ...selectors: string[]) { |
| 23 | + return cssRules.filter((rule) => ( |
| 24 | + rule.selector.includes(platform) && selectors.every((selector) => rule.selector.includes(selector)) |
| 25 | + )); |
| 26 | +} |
| 27 | + |
| 28 | +describe('Windows titlebar CSS contract', () => { |
| 29 | + it('keeps the titlebar safe area and sidebar platform-specific', () => { |
| 30 | + expect(appSource).toContain('class="windows-titlebar"'); |
| 31 | + expect(appSource).toContain('aria-hidden="true"'); |
| 32 | + |
| 33 | + const win32DragRules = cssRules.filter((rule) => ( |
| 34 | + rule.selector.includes(win32) && rule.declarations.includes('-webkit-app-region: drag') |
| 35 | + )); |
| 36 | + expect(win32DragRules).toHaveLength(1); |
| 37 | + expect(win32DragRules[0]!.selector).toContain('.windows-titlebar'); |
| 38 | + |
| 39 | + const win32TitlebarRules = rulesFor(win32, '.windows-titlebar'); |
| 40 | + expect(win32TitlebarRules).toHaveLength(1); |
| 41 | + expect(win32TitlebarRules[0]!.declarations).toContain('titlebar-area-x'); |
| 42 | + expect(win32TitlebarRules[0]!.declarations).toContain('titlebar-area-width'); |
| 43 | + |
| 44 | + const win32ShellRules = rulesFor(win32, '.app-shell'); |
| 45 | + expect(win32ShellRules).toHaveLength(1); |
| 46 | + expect(win32ShellRules[0]!.declarations).toContain('titlebar-area-height'); |
| 47 | + |
| 48 | + const win32SidebarRules = rulesFor(win32, ' .side)', '.sidebar-rail'); |
| 49 | + expect(win32SidebarRules).toHaveLength(1); |
| 50 | + expect(win32SidebarRules[0]!.declarations).not.toMatch(/transparent|color-mix/); |
| 51 | + |
| 52 | + const darwinSidebarRules = rulesFor(darwin, ' .side)', '.sidebar-rail'); |
| 53 | + expect(darwinSidebarRules).toHaveLength(1); |
| 54 | + expect(darwinSidebarRules[0]!.declarations).toContain( |
| 55 | + 'color-mix(in srgb, var(--panel) 55%, transparent)', |
| 56 | + ); |
| 57 | + }); |
| 58 | +}); |
0 commit comments