From e435340eff439ec1be0f3fdc2a8a8658cabff793 Mon Sep 17 00:00:00 2001 From: creatixchu Date: Sun, 20 Sep 2026 10:41:48 +0800 Subject: [PATCH] test: exercise application selection through the recorded Web session --- apps/web/tests/document-preview.e2e.ts | 25 ++++++++++++++++--- .../tests/open-path-action.client.spec.tsx | 2 +- .../native-command/src/file-applications.ts | 4 +-- .../tests/file-applications.spec.ts | 4 +-- .../document-preview/applications.expected.md | 5 ++++ 5 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 snapshots/web/document-preview/applications.expected.md diff --git a/apps/web/tests/document-preview.e2e.ts b/apps/web/tests/document-preview.e2e.ts index 31b2fd06b6..922ea15992 100644 --- a/apps/web/tests/document-preview.e2e.ts +++ b/apps/web/tests/document-preview.e2e.ts @@ -102,10 +102,17 @@ describe.skipIf(MODE === 'record')('web e2e: document preview through Files', () const command = process.platform === 'darwin' ? 'open' : 'xdg-open' await writeFile(join(nativeRoot, command), `#!/usr/bin/env node const fs = require('node:fs'); -const path = process.argv[2] === '-R' ? process.argv[3] : process.argv[2]; -const action = process.argv[2] === '-R' || fs.statSync(path).isDirectory() ? 'reveal' : 'open'; +const path = process.argv[2] === '-a' ? process.argv[4] : process.argv[2] === '-R' ? process.argv[3] : process.argv[2]; +const action = process.argv[2] === '-a' ? 'application' : process.argv[2] === '-R' || fs.statSync(path).isDirectory() ? 'reveal' : 'open'; fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify({ path, action }) + '\\n'); `, { mode: 0o700 }) + if (process.platform === 'darwin') { + const apps = [ + { id: '/Applications/Test Player.app', name: 'Test Player', default: true, icon: `data:image/png;base64,${TINY_PNG.toString('base64')}` }, + { id: '/Applications/Other Player.app', name: 'Other Player', default: false, icon: null }, + ] + await writeFile(join(nativeRoot, 'osascript'), `#!/usr/bin/env node\nprocess.stdout.write(${JSON.stringify(JSON.stringify(apps))});\n`, { mode: 0o700 }) + } vi.stubEnv('PATH', `${nativeRoot}${delimiter}${process.env.PATH ?? ''}`) } // The Open In rows carry the default-application controls; the SSH marker @@ -756,13 +763,25 @@ fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify({ path, action }) + expect(gestures[0]).toEqual({ path: clip, action: 'open' }) expect(gestures[1]?.action).toBe('reveal') expect([clip, cwd]).toContain(gestures[1]?.path) + if (process.platform === 'darwin') { + await expect.poll(() => headerOpen.locator('img').count()).toBe(1) + await unsupported.locator('[data-open-path-more]').click() + await page.getByRole('menuitem', { name: 'Test Player (default)', exact: true }).waitFor() + await compareOrRefreshGolden(join(SNAPSHOT_DIR, 'applications.expected.md'), await page.getByRole('menu').ariaSnapshot(), MODE) + await page.getByRole('menuitem', { name: 'Other Player', exact: true }).click() + await expect.poll(async () => (await opened()).length).toBe(3) + expect((await opened())[2]).toEqual({ path: clip, action: 'application' }) + await headerOpen.click() + await expect.poll(async () => (await opened()).length).toBe(4) + expect((await opened())[3]).toEqual({ path: clip, action: 'open' }) + } // Gesture facts stay out of the golden: the stub does not run on Windows. expect(await page.getByRole('alert').count()).toBe(0) } expect(tripwire.pageErrors).toEqual([]) expect(tripwire.warnings).toEqual([]) await compareOrRefreshGolden(EXPECTED, sections.join('\n\n'), MODE) - await assertFixtureInventory(SNAPSHOT_DIR, ['document.expected.md', 'paging.patch.yml']) + await assertFixtureInventory(SNAPSHOT_DIR, ['document.expected.md', 'applications.expected.md', 'paging.patch.yml']) }) }) diff --git a/packages/client/ui-open-in-app/tests/open-path-action.client.spec.tsx b/packages/client/ui-open-in-app/tests/open-path-action.client.spec.tsx index f72a9d9780..45dfe9272b 100644 --- a/packages/client/ui-open-in-app/tests/open-path-action.client.spec.tsx +++ b/packages/client/ui-open-in-app/tests/open-path-action.client.spec.tsx @@ -182,7 +182,7 @@ it('keeps default opening available when application discovery fails', async () const b = bench() render( null} />) await act(async () => { fireEvent.click(screen.getByRole('button', { name: zh['path.more'] })) }) - expect(screen.getByRole('menuitem', { name: zh['path.appsError'] }).getAttribute('aria-disabled')).toBe('true') + expect(screen.getByRole('menuitem', { name: zh['path.appsError'] }).hasAttribute('disabled')).toBe(true) await act(async () => { fireEvent.click(screen.getByRole('menuitem', { name: zh['path.defaultApp'] })) }) expect(b.openPath).toHaveBeenLastCalledWith(ABSOLUTE_PATH, 'open', undefined) }) diff --git a/packages/util/native-command/src/file-applications.ts b/packages/util/native-command/src/file-applications.ts index f865d93145..dd6d3d7cad 100644 --- a/packages/util/native-command/src/file-applications.ts +++ b/packages/util/native-command/src/file-applications.ts @@ -50,7 +50,7 @@ export async function nativeFileApplications( const platform = internals.platform ?? process.platform if (platform !== 'darwin') return [] const run = internals.run ?? runNativeCommand - const { stdout } = await run('/usr/bin/osascript', ['-l', 'JavaScript', '-e', MAC_APPLICATIONS, path], signal) + const { stdout } = await run('osascript', ['-l', 'JavaScript', '-e', MAC_APPLICATIONS, path], signal) const value: unknown = JSON.parse(stdout) if (!Array.isArray(value)) throw new Error('Invalid native application list') const applications: NativeFileApplication[] = [] @@ -80,5 +80,5 @@ export async function openNativeFileApplication( const apps = await nativeFileApplications(path, signal, internals) if (!apps.some(app => app.id === application)) throw new Error('Application is not registered for this file') const run = internals.run ?? runNativeCommand - await run('/usr/bin/open', ['-a', application, path], signal) + await run('open', ['-a', application, path], signal) } diff --git a/packages/util/native-command/tests/file-applications.spec.ts b/packages/util/native-command/tests/file-applications.spec.ts index b6f2e3328c..fd87806cc6 100644 --- a/packages/util/native-command/tests/file-applications.spec.ts +++ b/packages/util/native-command/tests/file-applications.spec.ts @@ -10,7 +10,7 @@ describe('native file associations', () => { const run = vi.fn(async () => ({ stdout: JSON.stringify([application]), stderr: '' })) const path = '/tmp/中文 $(touch nope).mp3' await expect(nativeFileApplications(path, signal, { platform: 'darwin', run })).resolves.toEqual([application]) - expect(run).toHaveBeenCalledWith('/usr/bin/osascript', ['-l', 'JavaScript', '-e', expect.any(String), path], signal) + expect(run).toHaveBeenCalledWith('osascript', ['-l', 'JavaScript', '-e', expect.any(String), path], signal) }) it.each(['{}', '[null]', '[{"id":1}]', JSON.stringify([{ ...application, icon: 'javascript:alert(1)' }])])('rejects malformed native output %s', async (stdout) => { @@ -22,7 +22,7 @@ describe('native file associations', () => { it('launches only a currently registered application with argv', async () => { const run = vi.fn(async () => ({ stdout: JSON.stringify([application]), stderr: '' })) await openNativeFileApplication('/file.mp3', application.id, signal, { platform: 'darwin', run }) - expect(run).toHaveBeenLastCalledWith('/usr/bin/open', ['-a', application.id, '/file.mp3'], signal) + expect(run).toHaveBeenLastCalledWith('open', ['-a', application.id, '/file.mp3'], signal) run.mockClear() await expect(openNativeFileApplication('/file.mp3', '/arbitrary.app', signal, { platform: 'darwin', run })).rejects.toThrow('not registered') expect(run).toHaveBeenCalledOnce() diff --git a/snapshots/web/document-preview/applications.expected.md b/snapshots/web/document-preview/applications.expected.md new file mode 100644 index 0000000000..9a6667e3e4 --- /dev/null +++ b/snapshots/web/document-preview/applications.expected.md @@ -0,0 +1,5 @@ +- menu: + - menuitem "Open in default app" + - menuitem "Test Player (default)" + - menuitem "Other Player" + - menuitem "Show file location"