forked from jest-community/vscode-jest
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added path based auto completion for jest module methods (jest-commun…
…ity#963) * added path based auto completion for jest module methods * fix test error on windows
- Loading branch information
1 parent
0c2e21b
commit 3b5c02e
Showing
5 changed files
with
214 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import * as vscode from 'vscode'; | ||
import * as path from 'path'; | ||
|
||
const JestMockModuleApi = | ||
/jest\.(mock|unmock|domock|dontmock|setmock|requireActual|requireMock|createMockFromModule|)\(['"](.*)/gi; | ||
const ImportFileRegex = /^([^\\.].*)\.(json|jsx|tsx|mjs|cjs|js|ts)$/gi; | ||
|
||
const toCompletionItem = ( | ||
label: string, | ||
kind = vscode.CompletionItemKind.File, | ||
detail?: string | ||
): vscode.CompletionItem => { | ||
const cItem = new vscode.CompletionItem(label, kind); | ||
cItem.detail = detail ?? label; | ||
return cItem; | ||
}; | ||
|
||
/** | ||
* auto complete path-based parameter for jest module-related methods | ||
*/ | ||
export class LocalFileCompletionItemProvider | ||
implements vscode.CompletionItemProvider<vscode.CompletionItem> | ||
{ | ||
public async provideCompletionItems( | ||
document: vscode.TextDocument, | ||
position: vscode.Position | ||
): Promise<vscode.CompletionItem[] | null | undefined> { | ||
const linePrefix = document.lineAt(position).text.slice(0, position.character); | ||
const matched = [...linePrefix.matchAll(JestMockModuleApi)][0]; | ||
if (!matched) { | ||
return undefined; | ||
} | ||
const userInput: string = Array.from(matched)[2]; | ||
const documentDir = path.dirname(document.uri.fsPath); | ||
const targetDir = path.resolve(documentDir, userInput); | ||
|
||
const results = await vscode.workspace.fs.readDirectory(vscode.Uri.file(targetDir)); | ||
|
||
const items: vscode.CompletionItem[] = []; | ||
results.forEach(([p, fType]) => { | ||
if (fType === vscode.FileType.Directory) { | ||
items.push(toCompletionItem(p, vscode.CompletionItemKind.Folder)); | ||
} else if (fType === vscode.FileType.File) { | ||
const matched = [...p.matchAll(ImportFileRegex)][0]; | ||
if (matched) { | ||
const [, module, ext] = matched; | ||
if (ext === 'json') { | ||
items.push(toCompletionItem(p)); | ||
} else { | ||
items.push(toCompletionItem(module, vscode.CompletionItemKind.File, p)); | ||
} | ||
} | ||
} | ||
}); | ||
return items; | ||
} | ||
} | ||
|
||
export function register(): vscode.Disposable[] { | ||
const selector = [ | ||
{ scheme: 'file', language: 'typescript' }, | ||
{ scheme: 'file', language: 'javascript' }, | ||
]; | ||
return [ | ||
vscode.languages.registerCompletionItemProvider( | ||
selector, | ||
new LocalFileCompletionItemProvider(), | ||
'/' | ||
), | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
jest.unmock('../src/language-provider'); | ||
|
||
const vscodeMock = { | ||
FileType: { | ||
Unknown: 0, | ||
File: 1, | ||
Directory: 2, | ||
SymbolicLink: 64, | ||
}, | ||
CompletionItemKind: { | ||
File: 0, | ||
Folder: 1, | ||
}, | ||
CompletionItem: jest.fn().mockImplementation((label, kind) => ({ label, kind })), | ||
workspace: { | ||
fs: { | ||
readDirectory: jest.fn(), | ||
}, | ||
}, | ||
Uri: { | ||
file: jest.fn(), | ||
}, | ||
languages: { | ||
registerCompletionItemProvider: jest.fn(), | ||
}, | ||
}; | ||
jest.mock('vscode', () => vscodeMock); | ||
|
||
import { LocalFileCompletionItemProvider, register } from '../src/language-provider'; | ||
import * as vscode from 'vscode'; | ||
import * as path from 'path'; | ||
|
||
describe('LocalFileCompletionItemProvider', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
describe('can auto complete for jest module-path methods', () => { | ||
it.each` | ||
jestMethod | isValid | ||
${'jest.mock'} | ${true} | ||
${'jest.unmock'} | ${true} | ||
${'jest.doMock'} | ${true} | ||
${'jest.dontMock'} | ${true} | ||
${'jest.setMock'} | ${true} | ||
${'jest.requireActual'} | ${true} | ||
${'jest.requireMock'} | ${true} | ||
${'jest.createMockFromModule'} | ${true} | ||
${'jest.spyOn'} | ${false} | ||
${'jest.fn'} | ${false} | ||
`('for $jestMethod', async ({ jestMethod, isValid }) => { | ||
const dirContent = [['file.ts', vscode.FileType.File]]; | ||
vscodeMock.workspace.fs.readDirectory.mockResolvedValue(dirContent); | ||
const srcPath = path.join(path.sep, 'project-root', 'src'); | ||
const docPath = path.join(srcPath, '__tests__', 'app.test.ts'); | ||
const text = `${jestMethod}('../`; | ||
const doc: any = { | ||
uri: { fsPath: docPath }, | ||
lineAt: () => ({ text }), | ||
}; | ||
const pos: any = { character: text.length }; | ||
const provider = new LocalFileCompletionItemProvider(); | ||
const items = await provider.provideCompletionItems(doc, pos); | ||
if (!isValid) { | ||
expect(items).toBeUndefined(); | ||
} else { | ||
expect(items).not.toBeUndefined(); | ||
// resolve relative directory correctly | ||
expect(vscodeMock.Uri.file).toHaveBeenCalledTimes(1); | ||
const targetFolder = (vscodeMock.Uri.file as jest.Mocked<any>).mock.calls[0][0]; | ||
expect(targetFolder.endsWith(srcPath)).toBeTruthy(); | ||
} | ||
}); | ||
}); | ||
describe('will only return the vallid file/directories completion items', () => { | ||
it.each` | ||
case | fileInfo | completionItem | ||
${1} | ${['file.ts', vscode.FileType.File]} | ${{ label: 'file', kind: vscode.CompletionItemKind.File }} | ||
${2} | ${['file.tsx', vscode.FileType.File]} | ${{ label: 'file', kind: vscode.CompletionItemKind.File }} | ||
${3} | ${['file.jsx', vscode.FileType.File]} | ${{ label: 'file', kind: vscode.CompletionItemKind.File }} | ||
${4} | ${['file.js', vscode.FileType.File]} | ${{ label: 'file', kind: vscode.CompletionItemKind.File }} | ||
${5} | ${['file.json', vscode.FileType.File]} | ${{ label: 'file.json', kind: vscode.CompletionItemKind.File }} | ||
${6} | ${['.config.json', vscode.FileType.File]} | ${undefined} | ||
${7} | ${['file.js.save', vscode.FileType.File]} | ${undefined} | ||
${8} | ${['dir', vscode.FileType.Directory]} | ${{ label: 'dir', kind: vscode.CompletionItemKind.Folder }} | ||
${9} | ${['file.mjs', vscode.FileType.File]} | ${{ label: 'file', kind: vscode.CompletionItemKind.File }} | ||
${10} | ${['file.cjs', vscode.FileType.File]} | ${{ label: 'file', kind: vscode.CompletionItemKind.File }} | ||
${11} | ${['image.jpg', vscode.FileType.File]} | ${undefined} | ||
${12} | ${['webpack.config', vscode.FileType.File]} | ${undefined} | ||
`('case $case', async ({ fileInfo, completionItem }) => { | ||
vscodeMock.workspace.fs.readDirectory.mockResolvedValue([fileInfo]); | ||
const srcPath = path.join(path.sep, 'project-root', 'src'); | ||
const docPath = path.join(srcPath, '__tests__', 'app.test.ts'); | ||
const text = `jest.mock('../`; | ||
const doc: any = { | ||
uri: { fsPath: docPath }, | ||
lineAt: () => ({ text }), | ||
}; | ||
const pos: any = { character: text.length }; | ||
const provider = new LocalFileCompletionItemProvider(); | ||
const items = await provider.provideCompletionItems(doc, pos); | ||
if (!completionItem) { | ||
expect(items).toEqual([]); | ||
} else { | ||
expect(items[0]).toEqual(expect.objectContaining(completionItem)); | ||
} | ||
}); | ||
}); | ||
}); | ||
describe('register', () => { | ||
it('will register the language provider', () => { | ||
register(); | ||
expect(vscodeMock.languages.registerCompletionItemProvider).toHaveBeenCalledWith( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ language: 'javascript' }), | ||
expect.objectContaining({ language: 'typescript' }), | ||
]), | ||
expect.any(LocalFileCompletionItemProvider), | ||
'/' | ||
); | ||
}); | ||
}); |