test: parametrize dragDropPaths cases with test.each

Groups the 21 scenarios into four table-driven describes
(empty-result, single-path, multi-path, backslash-escaped) so that
adding a new case is a one-line row instead of a new `test()` block.
Fixture directories are now created synchronously at describe-load
time so their paths are available to the test.each tables, which are
built before any hook runs.
This commit is contained in:
Paulo Reis
2026-04-05 08:24:02 -03:00
parent f25ea81f86
commit c2e32b6f37

View File

@@ -1,145 +1,100 @@
import { afterAll, beforeAll, describe, expect, test } from 'bun:test' import { afterAll, describe, expect, test } from 'bun:test'
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'fs' import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'fs'
import { tmpdir } from 'os' import { tmpdir } from 'os'
import { join } from 'path' import { join } from 'path'
import { extractDraggedFilePaths } from './dragDropPaths.js' import { extractDraggedFilePaths } from './dragDropPaths.js'
describe('extractDraggedFilePaths', () => { describe('extractDraggedFilePaths', () => {
// Use paths that actually exist on any system // Paths that exist on any system.
const thisFile = import.meta.path const thisFile = import.meta.path
const packageJson = `${process.cwd()}/package.json` const packageJson = `${process.cwd()}/package.json`
// Temp dir with a file whose name contains a space, for Finder-drag // Fixtures created synchronously at describe-load time (not in
// backslash-escape tests, and a scoped-package-style subdir so we can // `beforeAll`) so their paths are available to `test.each` tables,
// exercise paths that embed `@` (e.g. `node_modules/@types/...`). // which are built before any hook runs.
let tmpDir: string const tmpDir = mkdtempSync(join(tmpdir(), 'dragdrop-test-'))
let spacedFile: string const spacedFile = join(tmpDir, 'my file.txt')
let atSignFile: string writeFileSync(spacedFile, 'test')
beforeAll(() => { const scopedDir = join(tmpDir, '@types')
tmpDir = mkdtempSync(join(tmpdir(), 'dragdrop-test-')) mkdirSync(scopedDir)
spacedFile = join(tmpDir, 'my file.txt') const atSignFile = join(scopedDir, 'index.d.ts')
writeFileSync(spacedFile, 'test') writeFileSync(atSignFile, 'test')
const scopedDir = join(tmpDir, '@types')
mkdirSync(scopedDir)
atSignFile = join(scopedDir, 'index.d.ts')
writeFileSync(atSignFile, 'test')
})
afterAll(() => { afterAll(() => {
rmSync(tmpDir, { recursive: true, force: true }) rmSync(tmpDir, { recursive: true, force: true })
}) })
test('detects a single absolute file path', () => { describe('returns an empty array', () => {
const result = extractDraggedFilePaths(thisFile) const emptyCases: Array<[string, string]> = [
expect(result).toEqual([thisFile]) ['a non-absolute path', 'relative/path/file.ts'],
}) ['a plain image path', '/Users/foo/image.png'],
['an uppercase image extension', '/Users/foo/SHOT.PNG'],
test('detects newline-separated file paths', () => { ['a double-quoted image path', '"/Users/foo/shot.png"'],
const result = extractDraggedFilePaths(`${thisFile}\n${packageJson}`) ['a single-quoted image path', "'/Users/foo/shot.jpg'"],
expect(result).toEqual([thisFile, packageJson]) ['regular prose text', 'hello world this is text'],
}) ['a nonexistent absolute path', '/definitely/nonexistent/file.ts'],
['a single-quoted nonexistent path', "'/definitely/nonexistent.ts'"],
test('detects space-separated absolute paths (Finder drag)', () => { ['an empty string', ''],
const result = extractDraggedFilePaths(`${thisFile} ${packageJson}`) ['whitespace only', ' \n '],
expect(result).toEqual([thisFile, packageJson]) // Mixed-segment cases: all-or-nothing policy means a single bad
}) // entry disqualifies the whole paste.
['a mix where one path does not exist', `${thisFile}\n/nonexistent/file.ts`],
test('returns empty for non-absolute paths', () => { ['a mix where one segment is an image', `${thisFile}\n/Users/foo/shot.png`],
expect(extractDraggedFilePaths('relative/path/file.ts')).toEqual([]) ]
}) test.each(emptyCases)('for %s', (_label, input) => {
expect(extractDraggedFilePaths(input)).toEqual([])
test('returns empty for image file paths', () => {
expect(extractDraggedFilePaths('/Users/foo/image.png')).toEqual([])
})
test('returns empty for regular text', () => {
expect(extractDraggedFilePaths('hello world this is text')).toEqual([])
})
test('returns empty when file does not exist', () => {
expect(
extractDraggedFilePaths('/definitely/nonexistent/file.ts'),
).toEqual([])
})
test('returns empty for empty string', () => {
expect(extractDraggedFilePaths('')).toEqual([])
})
test('returns empty for whitespace only', () => {
expect(extractDraggedFilePaths(' \n ')).toEqual([])
})
test('returns empty if any path does not exist', () => {
expect(
extractDraggedFilePaths(`${thisFile}\n/nonexistent/file.ts`),
).toEqual([])
})
test('strips outer double quotes from paths', () => {
const result = extractDraggedFilePaths(`"${thisFile}"`)
expect(result).toEqual([thisFile])
})
test('strips outer single quotes from paths', () => {
const result = extractDraggedFilePaths(`'${thisFile}'`)
expect(result).toEqual([thisFile])
})
test('returns empty for a double-quoted image path', () => {
// Regression guard: image detection must see through outer quotes so
// quoted image drops still route to the image paste handler.
expect(extractDraggedFilePaths('"/Users/foo/shot.png"')).toEqual([])
})
test('returns empty for a single-quoted image path', () => {
expect(extractDraggedFilePaths("'/Users/foo/shot.jpg'")).toEqual([])
})
test('returns empty for an uppercase image extension', () => {
expect(extractDraggedFilePaths('/Users/foo/SHOT.PNG')).toEqual([])
})
if (process.platform !== 'win32') {
test('returns empty for a backslash-escaped image path', () => {
// Finder drags escape spaces with backslashes; the image check must
// apply after escape stripping.
expect(
extractDraggedFilePaths('/Users/foo/my\\ shot.png'),
).toEqual([])
}) })
})
test('resolves a backslash-escaped path to a real file on disk', () => { describe('resolves a single path', () => {
// `spacedFile` is an existing file with a space in its name; the const singleCases: Array<[string, string, string]> = [
// raw form matches what a terminal delivers on Finder drag. ['a plain absolute path', thisFile, thisFile],
const escaped = spacedFile.replace(/ /g, '\\ ') ['a double-quoted path', `"${thisFile}"`, thisFile],
expect(extractDraggedFilePaths(escaped)).toEqual([spacedFile]) ['a single-quoted path', `'${thisFile}'`, thisFile],
['a path with leading/trailing whitespace', ` ${thisFile} `, thisFile],
// Realistic: dragging something under `node_modules/@types/...`.
// `@` inside the path must not collide with the mention prefix
// that the caller prepends downstream.
['a path containing an `@` segment', atSignFile, atSignFile],
]
test.each(singleCases)('from %s', (_label, input, expected) => {
expect(extractDraggedFilePaths(input)).toEqual([expected])
})
})
describe('resolves multiple paths', () => {
const multiCases: Array<[string, string, string[]]> = [
[
'newline-separated',
`${thisFile}\n${packageJson}`,
[thisFile, packageJson],
],
[
'space-separated (Finder drag)',
`${thisFile} ${packageJson}`,
[thisFile, packageJson],
],
]
test.each(multiCases)('when input is %s', (_label, input, expected) => {
expect(extractDraggedFilePaths(input)).toEqual(expected)
})
})
// Backslash-escaped paths are a Finder/macOS + Linux convention — on
// Windows the shell-escape step is skipped, so these cases do not apply.
if (process.platform !== 'win32') {
describe('handles backslash-escaped paths', () => {
test('returns empty for an escaped image path', () => {
// The image check must apply after escape stripping so Finder
// image drags still route to the image paste handler.
expect(extractDraggedFilePaths('/Users/foo/my\\ shot.png')).toEqual([])
})
test('resolves an escaped real file with a space in its name', () => {
// Raw form matches what a terminal delivers on Finder drag.
const escaped = spacedFile.replace(/ /g, '\\ ')
expect(extractDraggedFilePaths(escaped)).toEqual([spacedFile])
})
}) })
} }
test('returns empty when mixed segments include an image file', () => {
// All-or-nothing: one image in the group disqualifies the whole paste
// so it can be handled by the image paste handler instead.
expect(
extractDraggedFilePaths(`${thisFile}\n/Users/foo/shot.png`),
).toEqual([])
})
test('returns empty for a single-quoted nonexistent path', () => {
// Quoted but nonexistent — exists check still runs after unquoting.
expect(extractDraggedFilePaths("'/definitely/nonexistent.ts'")).toEqual(
[],
)
})
test('trims surrounding whitespace from the whole paste', () => {
expect(extractDraggedFilePaths(` ${thisFile} `)).toEqual([thisFile])
})
test('resolves a path that embeds an `@` segment', () => {
// Realistic case: dragging something under `node_modules/@types/...`.
// The `@` inside the path must not be confused with the mention prefix
// that the caller prepends downstream — `extractDraggedFilePaths`
// returns raw paths and leaves mention formatting to PromptInput.
expect(extractDraggedFilePaths(atSignFile)).toEqual([atSignFile])
})
}) })