Skip to content

Commit 594964b

Browse files
committed
test(dev): electron more tests
1 parent b195d72 commit 594964b

2 files changed

Lines changed: 424 additions & 0 deletions

File tree

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import { test, expect } from '@playwright/test'
2+
3+
test.describe('Git Worker under Electron-like environment', () => {
4+
test('6.1: Worker boots and reports environment when Electron is present', async ({ page }) => {
5+
// Simulate Electron renderer + preload
6+
await page.addInitScript(() => {
7+
;(window as any).isElectron = true
8+
;(window as any).electron = { invoke: async () => null }
9+
})
10+
11+
// Capture console to observe worker boot diagnostics
12+
const logs: string[] = []
13+
page.on('console', (msg) => {
14+
const t = msg.text()
15+
if (t.includes('[worker → ui]')) logs.push(t)
16+
})
17+
18+
// Provide a minimal mock repo and picker to trigger worker init
19+
await page.addInitScript(() => {
20+
function makeFile(data: string | Uint8Array, name: string): File {
21+
const blob = typeof data === 'string' ? new Blob([data]) : new Blob([data])
22+
// @ts-ignore
23+
return new File([blob], name)
24+
}
25+
function dirEntries(obj: Record<string, any>): [string, any][] { return Object.keys(obj).map((k) => [k, obj[k]]) }
26+
function makeDir(structure: any, name = ''): any {
27+
return {
28+
kind: 'directory',
29+
name,
30+
async getFileHandle(n: string) {
31+
const child = structure[n]
32+
if (!child || typeof child !== 'string') throw Object.assign(new Error('NotFoundError'), { name: 'NotFoundError' })
33+
const file = makeFile(child, n)
34+
return { kind: 'file', async getFile() { return file } }
35+
},
36+
async getDirectoryHandle(n: string) {
37+
const child = structure[n]
38+
if (!child || typeof child === 'string') throw Object.assign(new Error('NotFoundError'), { name: 'NotFoundError' })
39+
return makeDir(child, n)
40+
},
41+
async *entries() {
42+
for (const [n, child] of dirEntries(structure)) {
43+
if (typeof child === 'string') {
44+
const file = makeFile(child, n)
45+
yield [n, { kind: 'file', async getFile() { return file } }]
46+
} else {
47+
yield [n, makeDir(child, n)]
48+
}
49+
}
50+
},
51+
}
52+
}
53+
const structure = {
54+
'.git': {
55+
'HEAD': 'ref: refs/heads/main\n',
56+
'refs': { 'heads': { 'main': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n' } },
57+
},
58+
'README.md': '# Hello\n',
59+
}
60+
;(window as any).__MOCK_DIR__ = makeDir(structure, 'mock-repo')
61+
;(window as any).showDirectoryPicker = async () => (window as any).__MOCK_DIR__
62+
})
63+
64+
await page.goto('/')
65+
await page.getByRole('button', { name: /Select Project Folder/i }).click()
66+
await expect(page.getByRole('heading', { name: /Select branches to diff/i })).toBeVisible()
67+
68+
// Wait briefly for worker logs to flush, then assert boot/env messages were observed
69+
await page.waitForTimeout(300)
70+
const joined = logs.join('\n')
71+
expect(joined).toContain('[worker → ui]')
72+
expect(joined).toMatch(/\[worker\] booted|\[worker\] env:/)
73+
})
74+
75+
test('6.2: Using WORKDIR vs WORKDIR, output paths use forward slashes under Electron-like env', async ({ page }) => {
76+
// Stub Worker to simulate git worker responses and simulate Electron renderer + preload; also stub clipboard
77+
await page.addInitScript(() => {
78+
const fileList = ['sub/dir/file.txt']
79+
class FakeWorker {
80+
public onmessage: ((ev: MessageEvent) => void) | null = null
81+
public onerror: ((ev: any) => void) | null = null
82+
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
83+
constructor(_url?: any, _opts?: any) {}
84+
postMessage(msg: any) {
85+
const id = msg?.id ?? 0
86+
const type = msg?.type
87+
const respond = (payload: any) => {
88+
this.onmessage && this.onmessage({ data: payload } as any)
89+
}
90+
setTimeout(() => {
91+
if (type === 'loadRepo') {
92+
respond({ id, type: 'ok', data: { branches: ['__WORKDIR__', 'main'], defaultBranch: '__WORKDIR__' } })
93+
} else if (type === 'listBranches') {
94+
respond({ id, type: 'ok', data: { branches: ['__WORKDIR__', 'main'], defaultBranch: '__WORKDIR__' } })
95+
} else if (type === 'diff') {
96+
respond({ id, type: 'ok', data: { files: [] } })
97+
} else if (type === 'listFiles') {
98+
respond({ id, type: 'ok', data: { files: fileList } })
99+
} else if (type === 'readFile') {
100+
respond({ id, type: 'ok', data: { binary: false, text: 'content\n', notFound: false } })
101+
} else if (type === 'resolveRef') {
102+
respond({ id, type: 'ok', data: { oid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' } })
103+
} else {
104+
respond({ id, type: 'error', error: 'unknown' })
105+
}
106+
}, 0)
107+
}
108+
terminate() {}
109+
}
110+
;(window as any).Worker = FakeWorker as any
111+
;(window as any).isElectron = true
112+
;(window as any).electron = { invoke: async () => null }
113+
// Robust clipboard override via defineProperty
114+
Object.defineProperty(navigator, 'clipboard', {
115+
configurable: true,
116+
get() {
117+
return {
118+
writeText(txt: string) { (window as any).__copied_text__ = String(txt); return Promise.resolve() },
119+
readText() { return Promise.resolve((window as any).__copied_text__ || '') },
120+
}
121+
}
122+
})
123+
})
124+
125+
// Mock repo with nested paths
126+
await page.addInitScript(() => {
127+
function makeFile(data: string | Uint8Array, name: string): File {
128+
const blob = typeof data === 'string' ? new Blob([data]) : new Blob([data])
129+
// @ts-ignore
130+
return new File([blob], name)
131+
}
132+
function dirEntries(obj: Record<string, any>): [string, any][] { return Object.keys(obj).map((k) => [k, obj[k]]) }
133+
function makeDir(structure: any, name = ''): any {
134+
return {
135+
kind: 'directory',
136+
name,
137+
async getFileHandle(n: string) {
138+
const child = structure[n]
139+
if (!child || typeof child !== 'string') throw Object.assign(new Error('NotFoundError'), { name: 'NotFoundError' })
140+
const file = makeFile(child, n)
141+
return { kind: 'file', async getFile() { return file } }
142+
},
143+
async getDirectoryHandle(n: string) {
144+
const child = structure[n]
145+
if (!child || typeof child === 'string') throw Object.assign(new Error('NotFoundError'), { name: 'NotFoundError' })
146+
return makeDir(child, n)
147+
},
148+
async *entries() {
149+
for (const [n, child] of dirEntries(structure)) {
150+
if (typeof child === 'string') {
151+
const file = makeFile(child, n)
152+
yield [n, { kind: 'file', async getFile() { return file } }]
153+
} else {
154+
yield [n, makeDir(child, n)]
155+
}
156+
}
157+
},
158+
}
159+
}
160+
const structure = {
161+
'.git': {
162+
'HEAD': 'ref: refs/heads/main\n',
163+
'refs': { 'heads': { 'main': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n' } },
164+
},
165+
'sub': { 'dir': { 'file.txt': 'content\n' } },
166+
}
167+
;(window as any).__MOCK_DIR__ = makeDir(structure, 'mock-repo')
168+
;(window as any).showDirectoryPicker = async () => (window as any).__MOCK_DIR__
169+
})
170+
171+
await page.goto('/')
172+
await page.getByRole('button', { name: /Select Project Folder/i }).click()
173+
await expect(page.getByRole('heading', { name: /Select branches to diff/i })).toBeVisible()
174+
175+
const branchesPanel = page.locator('.panel-section', { has: page.getByRole('heading', { name: /Select branches to diff/i }) })
176+
const baseSelect = branchesPanel.locator('select').nth(0)
177+
const compareSelect = branchesPanel.locator('select').nth(1)
178+
await baseSelect.selectOption({ label: 'My Working Directory' })
179+
// Select a different branch to trigger compute; handled by FakeWorker
180+
await compareSelect.selectOption({ label: 'main' })
181+
182+
// Show unchanged files so we can select them, then select the file directly
183+
const fileTreePanel = page.locator('.panel-section', { has: page.getByRole('heading', { name: 'File Tree' }) })
184+
const filterChangedCheckbox = fileTreePanel.locator('label:has-text("Filter Changed Files") >> input[type="checkbox"]')
185+
await filterChangedCheckbox.uncheck()
186+
// Expand may be disabled depending on structure; directly pick the checkbox if visible
187+
const expandAll = fileTreePanel.getByRole('button', { name: 'Expand all' })
188+
if (await expandAll.isEnabled()) await expandAll.click()
189+
const fileCheckbox = fileTreePanel.locator('li:has(span[data-full-path="sub/dir/file.txt"]) input[type="checkbox"]').first()
190+
await fileCheckbox.check()
191+
192+
// Wait until copy is enabled, then copy output
193+
const copyBtn = page.getByTestId('copy-all-selected')
194+
await expect(copyBtn).toBeEnabled()
195+
await copyBtn.click()
196+
await page.waitForFunction(() => !!(window as any).__copied_text__, null, { timeout: 2000 }).catch(() => {})
197+
const copied = await page.evaluate(() => (window as any).__copied_text__ as string)
198+
expect(copied || '').toContain('## FILE: sub/dir/file.txt')
199+
})
200+
})
201+
202+

0 commit comments

Comments
 (0)