Skip to content

Commit 0bfa328

Browse files
authored
test: add some tests for launch-editor package (#135)
1 parent 1b006ae commit 0bfa328

4 files changed

Lines changed: 267 additions & 39 deletions

File tree

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
const assert = require('node:assert/strict')
2+
const { describe, test } = require('node:test')
3+
4+
const getArgumentsForPosition = require('./get-args.js')
5+
6+
describe('getArgumentsForPosition', () => {
7+
describe('editor-specific argument formats', () => {
8+
for (const editor of [
9+
'atom',
10+
'Atom',
11+
'Atom Beta',
12+
'subl',
13+
'sublime',
14+
'sublime_text',
15+
'wstorm',
16+
'charm',
17+
'zed',
18+
]) {
19+
test(`${editor} uses "file:line:column"`, () => {
20+
assert.deepEqual(getArgumentsForPosition(editor, 'file', 10, 5), ['file:10:5'])
21+
})
22+
}
23+
24+
test('notepad++ uses -n/-c flags', () => {
25+
assert.deepEqual(getArgumentsForPosition('notepad++', 'file', 10, 5), ['-n10', '-c5', 'file'])
26+
})
27+
28+
for (const editor of ['vim', 'mvim']) {
29+
test(`${editor} uses +call cursor()`, () => {
30+
assert.deepEqual(getArgumentsForPosition(editor, 'file', 10, 5), [
31+
'+call cursor(10, 5)',
32+
'file',
33+
])
34+
})
35+
}
36+
37+
for (const editor of ['joe', 'gvim']) {
38+
test(`${editor} uses +line`, () => {
39+
assert.deepEqual(getArgumentsForPosition(editor, 'file', 10, 5), ['+10', 'file'])
40+
})
41+
}
42+
43+
for (const editor of ['emacs', 'emacsclient']) {
44+
test(`${editor} uses +line:column`, () => {
45+
assert.deepEqual(getArgumentsForPosition(editor, 'file', 10, 5), ['+10:5', 'file'])
46+
})
47+
}
48+
49+
for (const editor of ['rmate', 'mate', 'mine']) {
50+
test(`${editor} uses --line`, () => {
51+
assert.deepEqual(getArgumentsForPosition(editor, 'file', 10, 5), ['--line', 10, 'file'])
52+
})
53+
}
54+
55+
for (const editor of [
56+
'code',
57+
'Code',
58+
'code-insiders',
59+
'Code - Insiders',
60+
'codium',
61+
'trae',
62+
'antigravity',
63+
'cursor',
64+
'vscodium',
65+
'VSCodium',
66+
]) {
67+
test(`${editor} uses -r -g "file:line:column"`, () => {
68+
assert.deepEqual(getArgumentsForPosition(editor, 'file', 10, 5), ['-r', '-g', 'file:10:5'])
69+
})
70+
}
71+
72+
for (const editor of ['idea', 'idea64', 'webstorm', 'pycharm', 'clion', 'rider']) {
73+
test(`${editor} uses --line/--column`, () => {
74+
assert.deepEqual(getArgumentsForPosition(editor, 'file', 10, 5), [
75+
'--line',
76+
10,
77+
'--column',
78+
5,
79+
'file',
80+
])
81+
})
82+
}
83+
})
84+
85+
test('defaults columnNumber to 1 when omitted', () => {
86+
assert.deepEqual(getArgumentsForPosition('code', 'file', 10), ['-r', '-g', 'file:10:1'])
87+
})
88+
89+
describe('editor resolution via path.basename and extension stripping', () => {
90+
test('resolves a full POSIX path', () => {
91+
assert.deepEqual(getArgumentsForPosition('/usr/local/bin/code', 'file', 10, 5), [
92+
'-r',
93+
'-g',
94+
'file:10:5',
95+
])
96+
})
97+
98+
if (process.platform === 'win32') {
99+
test('resolves a full Windows path with .exe', () => {
100+
assert.deepEqual(getArgumentsForPosition('C:\\path\\Code.exe', 'file', 10, 5), [
101+
'-r',
102+
'-g',
103+
'file:10:5',
104+
])
105+
})
106+
107+
test('resolves notepad++ from a full path with .exe', () => {
108+
assert.deepEqual(getArgumentsForPosition('C:\\tools\\notepad++.exe', 'file', 10, 5), [
109+
'-n10',
110+
'-c5',
111+
'file',
112+
])
113+
})
114+
}
115+
116+
test('strips .cmd extension case-insensitively', () => {
117+
assert.deepEqual(getArgumentsForPosition('code.CMD', 'file', 10, 5), [
118+
'-r',
119+
'-g',
120+
'file:10:5',
121+
])
122+
})
123+
})
124+
})

packages/launch-editor/guess.js

Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,60 @@ const COMMON_EDITORS_MACOS = require('./editor-info/macos')
99
const COMMON_EDITORS_LINUX = require('./editor-info/linux')
1010
const COMMON_EDITORS_WIN = require('./editor-info/windows')
1111

12-
module.exports = function guessEditor(specifiedEditor) {
12+
function getEditorFromMacProcesses(output) {
13+
const processNames = Object.keys(COMMON_EDITORS_MACOS)
14+
const processList = output.split('\n')
15+
for (let i = 0; i < processNames.length; i++) {
16+
const processName = processNames[i]
17+
// Find editor by exact match.
18+
if (processList.includes(processName)) {
19+
return COMMON_EDITORS_MACOS[processName]
20+
}
21+
const processNameWithoutApplications = processName.replace('/Applications', '')
22+
// Find editor installation not in /Applications.
23+
if (output.indexOf(processNameWithoutApplications) !== -1) {
24+
// Use the CLI command if one is specified
25+
if (processName !== COMMON_EDITORS_MACOS[processName]) {
26+
return COMMON_EDITORS_MACOS[processName]
27+
}
28+
// Use a partial match to find the running process path. If one is found, use the
29+
// existing path since it can be running from anywhere.
30+
const runningProcess = processList.find((procName) =>
31+
procName.endsWith(processNameWithoutApplications),
32+
)
33+
if (runningProcess !== undefined) {
34+
return runningProcess
35+
}
36+
}
37+
}
38+
return undefined
39+
}
40+
41+
function getEditorFromWindowsProcesses(output) {
42+
const runningProcesses = output.split('\r\n')
43+
for (let i = 0; i < runningProcesses.length; i++) {
44+
const fullProcessPath = runningProcesses[i].trim()
45+
const shortProcessName = path.win32.basename(fullProcessPath)
46+
47+
if (COMMON_EDITORS_WIN.indexOf(shortProcessName) !== -1) {
48+
return fullProcessPath
49+
}
50+
}
51+
return undefined
52+
}
53+
54+
function getEditorFromLinuxProcesses(output) {
55+
const processNames = Object.keys(COMMON_EDITORS_LINUX)
56+
for (let i = 0; i < processNames.length; i++) {
57+
const processName = processNames[i]
58+
if (output.indexOf(processName) !== -1) {
59+
return COMMON_EDITORS_LINUX[processName]
60+
}
61+
}
62+
return undefined
63+
}
64+
65+
function guessEditor(specifiedEditor) {
1366
if (specifiedEditor) {
1467
return shellQuote.parse(specifiedEditor)
1568
}
@@ -32,30 +85,9 @@ module.exports = function guessEditor(specifiedEditor) {
3285
stdio: ['pipe', 'pipe', 'ignore'],
3386
})
3487
.toString()
35-
const processNames = Object.keys(COMMON_EDITORS_MACOS)
36-
const processList = output.split('\n')
37-
for (let i = 0; i < processNames.length; i++) {
38-
const processName = processNames[i]
39-
// Find editor by exact match.
40-
if (processList.includes(processName)) {
41-
return [COMMON_EDITORS_MACOS[processName]]
42-
}
43-
const processNameWithoutApplications = processName.replace('/Applications', '')
44-
// Find editor installation not in /Applications.
45-
if (output.indexOf(processNameWithoutApplications) !== -1) {
46-
// Use the CLI command if one is specified
47-
if (processName !== COMMON_EDITORS_MACOS[processName]) {
48-
return [COMMON_EDITORS_MACOS[processName]]
49-
}
50-
// Use a partial match to find the running process path. If one is found, use the
51-
// existing path since it can be running from anywhere.
52-
const runningProcess = processList.find((procName) =>
53-
procName.endsWith(processNameWithoutApplications),
54-
)
55-
if (runningProcess !== undefined) {
56-
return [runningProcess]
57-
}
58-
}
88+
const editor = getEditorFromMacProcesses(output)
89+
if (editor !== undefined) {
90+
return [editor]
5991
}
6092
} else if (process.platform === 'win32') {
6193
const output = childProcess
@@ -69,14 +101,9 @@ module.exports = function guessEditor(specifiedEditor) {
69101
},
70102
)
71103
.toString()
72-
const runningProcesses = output.split('\r\n')
73-
for (let i = 0; i < runningProcesses.length; i++) {
74-
const fullProcessPath = runningProcesses[i].trim()
75-
const shortProcessName = path.basename(fullProcessPath)
76-
77-
if (COMMON_EDITORS_WIN.indexOf(shortProcessName) !== -1) {
78-
return [fullProcessPath]
79-
}
104+
const editor = getEditorFromWindowsProcesses(output)
105+
if (editor !== undefined) {
106+
return [editor]
80107
}
81108
} else if (process.platform === 'linux') {
82109
// --no-heading No header line
@@ -87,12 +114,9 @@ module.exports = function guessEditor(specifiedEditor) {
87114
stdio: ['pipe', 'pipe', 'ignore'],
88115
})
89116
.toString()
90-
const processNames = Object.keys(COMMON_EDITORS_LINUX)
91-
for (let i = 0; i < processNames.length; i++) {
92-
const processName = processNames[i]
93-
if (output.indexOf(processName) !== -1) {
94-
return [COMMON_EDITORS_LINUX[processName]]
95-
}
117+
const editor = getEditorFromLinuxProcesses(output)
118+
if (editor !== undefined) {
119+
return [editor]
96120
}
97121
}
98122
} catch (ignoreError) {
@@ -108,3 +132,8 @@ module.exports = function guessEditor(specifiedEditor) {
108132

109133
return [null]
110134
}
135+
136+
module.exports = guessEditor
137+
module.exports.getEditorFromMacProcesses = getEditorFromMacProcesses
138+
module.exports.getEditorFromWindowsProcesses = getEditorFromWindowsProcesses
139+
module.exports.getEditorFromLinuxProcesses = getEditorFromLinuxProcesses
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
const assert = require('node:assert/strict')
2+
const { describe, test } = require('node:test')
3+
4+
const guessEditor = require('./guess.js')
5+
const { getEditorFromMacProcesses, getEditorFromWindowsProcesses, getEditorFromLinuxProcesses } =
6+
guessEditor
7+
8+
describe('getEditorFromMacProcesses', () => {
9+
test('maps an exact process path to its CLI command', () => {
10+
const output = [
11+
'/sbin/launchd',
12+
'/Applications/Visual Studio Code.app/Contents/MacOS/Code',
13+
'/usr/sbin/cfprefsd',
14+
].join('\n')
15+
assert.equal(getEditorFromMacProcesses(output), 'code')
16+
})
17+
18+
test('uses the CLI command for an install outside /Applications', () => {
19+
// `Atom` maps to the short command `atom`, so even when running from a
20+
// custom location we should return that command.
21+
const output = ['/sbin/launchd', '/Users/me/dev/Atom.app/Contents/MacOS/Atom'].join('\n')
22+
assert.equal(getEditorFromMacProcesses(output), 'atom')
23+
})
24+
25+
test('returns the running process path when the map points at itself', () => {
26+
// `CLion` maps to its own full path, so for an install outside
27+
// /Applications we should return the actual running path.
28+
const runningPath = '/Users/me/dev/CLion.app/Contents/MacOS/clion'
29+
const output = ['/sbin/launchd', runningPath].join('\n')
30+
assert.equal(getEditorFromMacProcesses(output), runningPath)
31+
})
32+
33+
test('returns undefined when no editor is running', () => {
34+
const output = ['/sbin/launchd', '/usr/sbin/cfprefsd'].join('\n')
35+
assert.equal(getEditorFromMacProcesses(output), undefined)
36+
})
37+
})
38+
39+
describe('getEditorFromWindowsProcesses', () => {
40+
test('returns the full executable path of a known editor', () => {
41+
const codePath = 'C:\\Program Files\\Microsoft VS Code\\Code.exe'
42+
const output = ['C:\\Windows\\System32\\svchost.exe', codePath].join('\r\n')
43+
assert.equal(getEditorFromWindowsProcesses(output), codePath)
44+
})
45+
46+
test('returns undefined when no editor is running', () => {
47+
const output = ['C:\\Windows\\System32\\svchost.exe', 'C:\\Windows\\explorer.exe'].join('\r\n')
48+
assert.equal(getEditorFromWindowsProcesses(output), undefined)
49+
})
50+
})
51+
52+
describe('getEditorFromLinuxProcesses', () => {
53+
test('maps a process name to its CLI command', () => {
54+
const output = ['systemd', 'code', 'bash'].join('\n')
55+
assert.equal(getEditorFromLinuxProcesses(output), 'code')
56+
})
57+
58+
test('maps sublime_text to subl', () => {
59+
const output = ['systemd', 'sublime_text'].join('\n')
60+
assert.equal(getEditorFromLinuxProcesses(output), 'subl')
61+
})
62+
63+
test('matches code-insiders before code due to key order', () => {
64+
const output = ['systemd', 'code-insiders'].join('\n')
65+
assert.equal(getEditorFromLinuxProcesses(output), 'code-insiders')
66+
})
67+
68+
test('returns undefined when no editor is running', () => {
69+
const output = ['systemd', 'bash', 'sshd'].join('\n')
70+
assert.equal(getEditorFromLinuxProcesses(output), undefined)
71+
})
72+
})

packages/launch-editor/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
"url": "git+https://github.com/vitejs/launch-editor.git"
1818
},
1919
"main": "index.js",
20+
"scripts": {
21+
"test": "node --test --experimental-test-module-mocks"
22+
},
2023
"dependencies": {
2124
"picocolors": "^1.1.1",
2225
"shell-quote": "^1.8.4"

0 commit comments

Comments
 (0)