import { Position, Range, Selection, Uri, window, workspace } from 'vscode'; import fs from 'fs'; import path from 'path'; import * as utils from './utils'; import { containsImageExt, containsMarkdownExt, containsOtherKnownExts, containsUnknownExt, ensureDirectoryExists, escapeForRegExp, extractDanglingRefs, extractEmbedRefs, extractExt, findAllUrisWithUnknownExts, findDanglingRefsByFsPath, findFilesByExts, findNonIgnoredFiles, findReferences, findUriByRef, fsPathToRef, getConfigProperty, getFileUrlForMarkdownPreview, getImgUrlForMarkdownPreview, getMemoConfigProperty, getReferenceAtPosition, getRefUriUnderCursor, getWorkspaceFolder, isLongRef, isUncPath, matchAll, normalizeSlashes, parseRef, trimLeadingSlash, trimSlashes, trimTrailingSlash, getDirRelativeToWorkspace, extractRefsFromText, } from './utils'; import { cache } from '../workspace'; import { closeEditorsAndCleanWorkspace, createFile, openTextDocument, rndName, toPlainObject, updateConfigProperty, } from '../test/utils'; describe('containsImageExt()', () => { test.each(['png', 'jpg', 'jpeg', 'gif'])( 'should return true when input contains .%s image extension', (imgExt) => { expect(containsImageExt(`/Users/memo/Diary/Attachments/image.${imgExt}`)).toBe(true); }, ); it('should return false when input does not contain image extension', () => { expect(containsImageExt('/Users/memo/Diary/Notes/note.md')).toBe(false); }); }); describe('containsMarkdownExt()', () => { it('should return true when input contains markdown extension', () => { expect(containsMarkdownExt('/Users/memo/Diary/Notes/note.md')).toBe(true); }); it('should return false when input does not contain markdown extension', () => { expect(containsMarkdownExt('/Users/memo/Diary/Attachments/image.png')).toBe(false); }); }); describe('containsOtherKnownExts()', () => { it('should return true when input contains one of the other known extensions', () => { expect(containsOtherKnownExts('/Users/memo/Diary/Notes/note.txt')).toBe(true); }); it('should return false when input does not contain one of the other known extensions', () => { expect(containsOtherKnownExts('/Users/memo/Diary/Attachments/image.psd')).toBe(false); }); }); describe('containsUnknownExt()', () => { it('should return true when input contains unknown extension', () => { expect(containsUnknownExt('/Users/memo/Diary/Notes/note.unknown')).toBe(true); }); it('should return false when input does not contain unknown extension', () => { expect(containsUnknownExt('/Users/memo/Diary/Attachments/image.md')).toBe(false); }); }); describe('trimLeadingSlash()', () => { it('should trim leading slash', () => { expect(trimLeadingSlash('/usr/local/bin')).toBe('usr/local/bin'); }); it('should trim leading backslash', () => { expect(trimLeadingSlash('\\Windows\\System32')).toBe('Windows\\System32'); }); }); describe('trimTrailingSlash()', () => { it('should trim trailing slash', () => { expect(trimTrailingSlash('/usr/local/bin/')).toBe('/usr/local/bin'); }); it('should trim trailing backslash', () => { expect(trimTrailingSlash('\\Windows\\System32\\')).toBe('\\Windows\\System32'); }); }); describe('trimSlashes()', () => { it('should trim leading & trailing slashes', () => { expect(trimSlashes('/usr/local/bin/')).toBe('usr/local/bin'); }); it('should trim leading & trailing backslashes', () => { expect(trimSlashes('\\Windows\\System32\\')).toBe('Windows\\System32'); }); }); describe('fsPathToRef()', () => { it('should return short ref', () => { expect(fsPathToRef({ path: '/Users/memo/Diary/Notes/note.md' })).toEqual('note'); }); it('should return short ref with extension', () => { expect(fsPathToRef({ path: '/Users/memo/Diary/Attachments/image.png', keepExt: true })).toEqual( 'image.png', ); }); it('should omit arbitrary extension from short ref', () => { expect(fsPathToRef({ path: '/Users/memo/Diary/Notes/note.any-extension' })).toEqual('note'); }); it('should return short ref for arbitrary extension', () => { expect( fsPathToRef({ path: '/Users/memo/Diary/Notes/note.any-extension', keepExt: true }), ).toEqual('note.any-extension'); }); describe('with basePath', () => { it('should return long ref', () => { expect( fsPathToRef({ path: '/Users/memo/Diary/Notes/note.md', basePath: '/Users/memo' }), ).toEqual('Diary/Notes/note'); }); it('should return long ref with extension', () => { expect( fsPathToRef({ path: '/Users/memo/Diary/Attachments/image.png', basePath: '/Users/memo', keepExt: true, }), ).toEqual('Diary/Attachments/image.png'); }); it('should omit arbitrary extension from long ref', () => { expect( fsPathToRef({ path: '/Users/memo/Diary/Notes/note.any-extension', basePath: '/Users/memo', }), ).toEqual('Diary/Notes/note'); }); it('should return long ref for arbitrary extension', () => { expect( fsPathToRef({ path: '/Users/memo/Diary/Notes/note.any-extension', basePath: '/Users/memo', keepExt: true, }), ).toEqual('Diary/Notes/note.any-extension'); }); }); }); describe('getRefUriUnderCursor()', () => { beforeEach(closeEditorsAndCleanWorkspace); afterEach(closeEditorsAndCleanWorkspace); it('should return reference uri under cursor', async () => { const name0 = rndName(); const name1 = rndName(); await createFile(`${name0}.md`); await createFile(`${name1}.md`, `[[${name0}]]`); const doc = await openTextDocument(`${name1}.md`); const editor = await window.showTextDocument(doc); editor.selection = new Selection(0, 2, 0, 2); expect(getRefUriUnderCursor()!.fsPath).toContain(`${name0}.md`); }); it('should not return reference uri under cursor', async () => { const name0 = rndName(); const name1 = rndName(); await createFile(`${name0}.md`); await createFile(`${name1}.md`, ` [[${name0}]]`); const doc = await openTextDocument(`${name1}.md`); const editor = await window.showTextDocument(doc); editor.selection = new Selection(0, 0, 0, 0); expect(getRefUriUnderCursor()).toBeNull(); }); }); describe('parseRef()', () => { it('should fail on providing wrong parameter type', () => { expect(() => parseRef(undefined as unknown as string)).toThrow(); }); it('should return empty ref and label', () => { expect(parseRef('')).toEqual({ ref: '', label: '' }); }); it('should parse raw ref and return ref and label', () => { expect(parseRef('link|Label')).toEqual({ ref: 'link', label: 'Label' }); }); it('should favour only first divider', () => { expect(parseRef('link|||Label')).toEqual({ ref: 'link', label: '||Label' }); }); it('should ignore escape symbol', () => { expect(parseRef('link\\|Label')).toEqual({ ref: 'link', label: 'Label' }); }); }); describe('isLongRef()', () => { it('should return false if ref is short', () => { expect(isLongRef('short-ref')).toBe(false); }); it('should return true if ref is long', () => { expect(isLongRef('/long/ref')).toBe(true); }); }); describe('normalizeSlashes()', () => { it('should replace backslashes with forward slashes', () => { expect(normalizeSlashes('\\Windows\\System32')).toBe('/Windows/System32'); }); }); describe('fsPathToRef()', () => { it('should transform path to short ref', () => { expect(fsPathToRef({ path: path.join('Desktop', 'Diary', 'Note') })).toBe('Note'); }); it('should transform path to long ref', () => { expect( fsPathToRef({ path: path.join('Desktop', 'Diary', 'Note'), basePath: path.join('Desktop') }), ).toBe('Diary/Note'); }); it('should transform path to long ref', () => { expect( fsPathToRef({ path: path.join('Desktop', 'Diary', 'Note'), basePath: path.join('Desktop') }), ).toBe('Diary/Note'); }); it('should preserve extension in short ref', () => { expect( fsPathToRef({ path: path.join('Desktop', 'Diary', 'Attachments', 'image.png'), keepExt: true, }), ).toBe('image.png'); }); it('should preserve extension in long ref', () => { expect( fsPathToRef({ path: path.join('Desktop', 'Diary', 'Attachments', 'image.png'), basePath: path.join('Desktop'), keepExt: true, }), ).toBe('Diary/Attachments/image.png'); }); }); describe('getWorkspaceFolder()', () => { it('should return workspace folder', () => { expect(getWorkspaceFolder()).not.toBeUndefined(); }); }); describe('getMemoConfigProperty()', () => { it('should return config property', () => { expect(getMemoConfigProperty('links.preview.imageMaxHeight', null)).toBe('200'); }); it('should return default property on getting unknown config property', () => { expect(getMemoConfigProperty('unknownProperty' as any, 'default' as any)).toBe('default'); }); }); describe('matchAll()', () => { it('should find all matches', () => { expect( matchAll( new RegExp(`\\[\\[(([^\\[\\]]+?)(\\|.*)?)\\]\\]`, 'gi'), ` [[ref|Test Label]] [[ref 2|Test Label 2]] `, ), ).toMatchInlineSnapshot(` Array [ Array [ "[[ref|Test Label]]", "ref|Test Label", "ref", "|Test Label", ], Array [ "[[ref 2|Test Label 2]]", "ref 2|Test Label 2", "ref 2", "|Test Label 2", ], ] `); }); }); describe('getReferenceAtPosition()', () => { it('should get reference at position for short link', async () => { expect( getReferenceAtPosition( await workspace.openTextDocument({ language: 'markdown', content: '[[test]]' }), new Position(0, 4), ), ).toMatchInlineSnapshot(` Object { "label": "", "range": Array [ Object { "character": 0, "line": 0, }, Object { "character": 8, "line": 0, }, ], "ref": "test", } `); }); it('should get reference at position for long link', async () => { expect( getReferenceAtPosition( await workspace.openTextDocument({ language: 'markdown', content: '[[folder/test]]' }), new Position(0, 4), ), ).toMatchInlineSnapshot(` Object { "label": "", "range": Array [ Object { "character": 0, "line": 0, }, Object { "character": 15, "line": 0, }, ], "ref": "folder/test", } `); }); it('should get reference at position for short resource link', async () => { expect( getReferenceAtPosition( await workspace.openTextDocument({ language: 'markdown', content: '![[test]]' }), new Position(0, 4), ), ).toMatchInlineSnapshot(` Object { "label": "", "range": Array [ Object { "character": 1, "line": 0, }, Object { "character": 9, "line": 0, }, ], "ref": "test", } `); }); it('should get reference at position for long resource link', async () => { expect( getReferenceAtPosition( await workspace.openTextDocument({ language: 'markdown', content: '![[folder/test]]' }), new Position(0, 4), ), ).toMatchInlineSnapshot(` Object { "label": "", "range": Array [ Object { "character": 1, "line": 0, }, Object { "character": 16, "line": 0, }, ], "ref": "folder/test", } `); }); it('should get reference at position for a short link with dots', async () => { expect( getReferenceAtPosition( await workspace.openTextDocument({ language: 'markdown', content: '![[test-v1.1.0]]', }), new Position(0, 4), ), ).toMatchInlineSnapshot(` Object { "label": "", "range": Array [ Object { "character": 1, "line": 0, }, Object { "character": 16, "line": 0, }, ], "ref": "test-v1.1.0", } `); }); it('should get reference at position for a long link with dots', async () => { expect( getReferenceAtPosition( await workspace.openTextDocument({ language: 'markdown', content: '![[test/test-v1.1.0]]', }), new Position(0, 4), ), ).toMatchInlineSnapshot(` Object { "label": "", "range": Array [ Object { "character": 1, "line": 0, }, Object { "character": 21, "line": 0, }, ], "ref": "test/test-v1.1.0", } `); }); }); describe('escapeForRegExp()', () => { it.each` actual | expected ${'.'} | ${'\\.'} ${'|'} | ${'\\|'} ${'*'} | ${'\\*'} ${'+'} | ${'\\+'} ${'?'} | ${'\\?'} ${'^'} | ${'\\^'} ${'$'} | ${'\\$'} ${'{'} | ${'\\{'} ${'}'} | ${'\\}'} ${'('} | ${'\\('} ${')'} | ${'\\)'} ${'['} | ${'\\['} ${']'} | ${'\\]'} ${'\\'} | ${'\\\\'} `('should escape $actual symbol for regexp', ({ actual, expected }) => { expect(escapeForRegExp(actual)).toBe(expected); }); }); describe('extractEmbedRefs()', () => { it('should not extract embed refs', () => { expect( extractEmbedRefs(` [[image.png]] [[note]] [note] `), ).toHaveLength(0); }); it('should extract embed refs', () => { expect( extractEmbedRefs(` ![[image.png]] ![[note]] `), ).toMatchInlineSnapshot(` Array [ "image.png", "note", ] `); }); }); describe('findReferences()', () => { beforeEach(closeEditorsAndCleanWorkspace); afterEach(closeEditorsAndCleanWorkspace); it('should find references', async () => { const name0 = rndName(); const name1 = rndName(); await createFile( `${name0}.md`, `[[test]] ![[test]]`, ); await createFile(`${name1}.md`, '[[test1]]'); const refs = await findReferences('test'); expect(refs).toHaveLength(2); expect(toPlainObject(refs)).toMatchObject([ { location: { uri: { $mid: 1, path: expect.toEndWith(`${name0}.md`), scheme: 'file', }, range: [ { line: 0, character: 2 }, { line: 0, character: 6 }, ], }, matchText: '[[test]]', }, { location: { uri: { $mid: 1, path: expect.toEndWith(`${name0}.md`), scheme: 'file', }, range: [ { line: 1, character: 7 }, { line: 1, character: 11 }, ], }, matchText: '[[test]]', }, ]); }); it('should find multiple references', async () => { const name = `note-${rndName()}`; await createFile( `${name}.md`, `[[test1]] ![[nested-folder/test]] [[nested-folder/test-2]] [[test1-1]]`, ); const refs = await findReferences(['test1', 'nested-folder/test']); expect(refs).toHaveLength(2); expect(toPlainObject(refs)).toMatchObject([ { location: { uri: { $mid: 1, path: expect.toEndWith(`${name}.md`), scheme: 'file', }, range: [ { line: 0, character: 2 }, { line: 0, character: 7 }, ], }, matchText: '[[test1]]', }, { location: { uri: { $mid: 1, path: expect.toEndWith(`${name}.md`), scheme: 'file', }, range: [ { line: 1, character: 7 }, { line: 1, character: 25 }, ], }, matchText: '[[nested-folder/test]]', }, ]); }); it('should find references bypassing excluded paths', async () => { const name0 = rndName(); const name1 = rndName(); await createFile(`${name0}.md`, '[[test]]'); await createFile(`${name1}.md`, '[[test1]]'); const refs = await findReferences('test', [path.join(getWorkspaceFolder()!, `${name1}.md`)]); expect(refs).toHaveLength(1); expect(toPlainObject(refs)).toMatchObject([ { location: { uri: { $mid: 1, path: expect.toEndWith(`${name0}.md`), scheme: 'file', }, range: [ { line: 0, character: 2 }, { line: 0, character: 6 }, ], }, matchText: '[[test]]', }, ]); }); it('should detect position properly for the first reference', async () => { const name = rndName(); await createFile( `${name}.md`, '[[README|Start here]] or refer to any other [[Note]]. Use (cmd or ctrl) + click on the [[Note]] to create a new note to the disk on the fly.', ); const refs = await findReferences('README'); expect(refs).toHaveLength(1); expect(toPlainObject(refs)).toMatchObject([ { location: { uri: { $mid: 1, path: expect.stringContaining(`${name}.md`), scheme: 'file', }, range: [ { line: 0, character: 2 }, { line: 0, character: 19 }, ], }, matchText: '[[README|Start here]] or refer to any other [[Note]]. Use (cmd or ctrl) + click on the [[Note]] to create a new note to the disk on the fly.', }, ]); }); }); describe('getFileUrlForMarkdownPreview()', () => { it('should get file url for markdown preview', () => { expect(getFileUrlForMarkdownPreview('/Users/Memo/Diary/Note.md')).toBe( '/Users/Memo/Diary/Note.md', ); }); }); describe('getImgUrlForMarkdownPreview()', () => { it('should get img url for markdown preview', () => { expect(getImgUrlForMarkdownPreview('/Users/Memo/Diary/image.png')).toBe( '/Users/Memo/Diary/image.png', ); }); }); describe('isUncPath()', () => { it('should return true for UNC path', () => { expect(isUncPath('\\\\servername\\path')).toBe(true); }); it('should return false for non-UNC path', () => { expect(isUncPath('/servername/path')).toBe(false); }); }); describe('findFilesByExts()', () => { beforeEach(closeEditorsAndCleanWorkspace); afterEach(closeEditorsAndCleanWorkspace); it('should not find anything with empty exts param', async () => { expect(await findFilesByExts([])).toHaveLength(0); }); it('should find files by exts', async () => { const name0 = rndName(); const name1 = rndName(); const name2 = rndName(); await createFile(`a-${name0}.md`); await createFile(`b-${name1}.png`); await createFile(`${name2}.psd`); expect( toPlainObject( (await findFilesByExts(['md', 'png'])).sort((a, b) => a.fsPath.localeCompare(b.fsPath)), ), ).toMatchObject([ { $mid: 1, path: expect.toEndWith(`a-${name0}.md`), scheme: 'file', }, { $mid: 1, path: expect.toEndWith(`b-${name1}.png`), scheme: 'file', }, ]); }); }); describe('findAllUrisWithUnknownExts()', () => { beforeEach(closeEditorsAndCleanWorkspace); afterEach(closeEditorsAndCleanWorkspace); it('should find no uris when none provided', async () => { const name0 = rndName(); await createFile(`${name0}.txt`); await createFile(`${name0}.md`); await createFile(`${name0}.png`); await createFile(`${name0}.psd`); await createFile(`${name0}.html`); expect(await findAllUrisWithUnknownExts([])).toHaveLength(0); }); it('should find all uris with unknown exts', async () => { const name0 = rndName(); await createFile(`${name0}.txt`); await createFile(`${name0}.md`); await createFile(`${name0}.png`); await createFile(`${name0}.psd`); await createFile(`${name0}.html`); expect( toPlainObject( ( await findAllUrisWithUnknownExts([ Uri.file(path.join(getWorkspaceFolder()!, `${name0}.txt`)), Uri.file(path.join(getWorkspaceFolder()!, `${name0}.md`)), Uri.file(path.join(getWorkspaceFolder()!, `${name0}.png`)), Uri.file(path.join(getWorkspaceFolder()!, `${name0}.psd`)), Uri.file(path.join(getWorkspaceFolder()!, `${name0}.html`)), ]) ).sort((a, b) => a.fsPath.localeCompare(b.fsPath)), ), ).toMatchObject([ { $mid: 1, path: expect.toEndWith(`${name0}.html`), scheme: 'file', }, { $mid: 1, path: expect.toEndWith(`${name0}.psd`), scheme: 'file', }, ]); }); }); describe('extractExt()', () => { it('should not extract ext for dot file', () => { expect(extractExt('/Users/Memo/Diary/.vscode')).toBe(''); }); it('should extract ext', () => { expect(extractExt('/Users/Memo/Diary/Note.md')).toBe('md'); }); it('should extract ext 2', () => { expect(extractExt('C:\\Users\\Memo\\Desktop\\Diary\\image.png')).toBe('png'); }); }); describe('findUriByRef()', () => { it('should find markdown uri by short ref', async () => { expect( toPlainObject( await findUriByRef( [ Uri.file('/Users/Memo/Diary/File.txt'), Uri.file('/Users/Memo/Diary/File.md'), Uri.file('/Users/Memo/Diary/File.png'), ], 'File', ), ), ).toEqual( expect.objectContaining({ $mid: 1, fsPath: expect.toEndWith('File.md'), path: expect.toEndWith('File.md'), scheme: 'file', }), ); }); it('should find image uri by short ref', async () => { expect( toPlainObject( await findUriByRef( [ Uri.file('/Users/Memo/Diary/File.txt'), Uri.file('/Users/Memo/Diary/File.md'), Uri.file('/Users/Memo/Diary/File.png'), ], 'File.png', ), ), ).toEqual( expect.objectContaining({ $mid: 1, fsPath: expect.toEndWith('File.png'), path: expect.toEndWith('File.png'), scheme: 'file', }), ); }); it('should find uri by long ref', async () => { expect( toPlainObject( await findUriByRef( [ Uri.file('/Users/Memo/Diary/File.txt'), Uri.file('/Users/Memo/Diary/File.md'), Uri.file('/Users/Memo/Diary/File.png'), ], 'Memo/Diary/File', ), ), ).toEqual( expect.objectContaining({ $mid: 1, fsPath: expect.toEndWith('File.md'), path: expect.toEndWith('File.md'), scheme: 'file', }), ); }); it('should find uri by long image ref', async () => { expect( toPlainObject( await findUriByRef( [ Uri.file('/Users/Memo/Diary/File.txt'), Uri.file('/Users/Memo/Diary/File.md'), Uri.file('/Users/Memo/Diary/File.png'), ], 'Memo/Diary/File.png', ), ), ).toEqual( expect.objectContaining({ $mid: 1, fsPath: expect.toEndWith('File.png'), path: expect.toEndWith('File.png'), scheme: 'file', }), ); }); it('should find uri by ref regardless of the case', async () => { expect( toPlainObject( await findUriByRef( [ Uri.file('/Users/Memo/Diary/File.txt'), Uri.file('/Users/Memo/Diary/File.md'), Uri.file('/Users/Memo/Diary/File.png'), ], 'memo/diary/file', ), ), ).toEqual( expect.objectContaining({ $mid: 1, fsPath: expect.toEndWith('File.md'), path: expect.toEndWith('File.md'), scheme: 'file', }), ); }); it('should find uri by ref even if contains unknown ext', async () => { expect( toPlainObject( await findUriByRef( [ Uri.file('/Users/Memo/Diary/File.txt'), Uri.file('/Users/Memo/Diary/File.md'), Uri.file('/Users/Memo/Diary/File.png'), Uri.file('/Users/Memo/Diary/File.unknown'), ], 'memo/diary/file.unknown', ), ), ).toEqual( expect.objectContaining({ $mid: 1, fsPath: expect.toEndWith('File.unknown'), path: expect.toEndWith('File.unknown'), scheme: 'file', }), ); }); it('should match long refs against workspace root', async () => { const getWorkspaceFolderMock = jest.spyOn(utils, 'getWorkspaceFolder'); getWorkspaceFolderMock.mockReturnValue('/Users/Memo/Test'); expect( await findUriByRef([Uri.file('/Users/Memo/Test/hello.md')], 'Test/hello'), ).toBeUndefined(); getWorkspaceFolderMock.mockRestore(); }); it('should find uri by ref with explicit markdown extension in ref', async () => { expect( toPlainObject(await findUriByRef([Uri.file('/Users/Memo/Diary/File.md.md')], 'File.md')), ).toEqual( expect.objectContaining({ $mid: 1, fsPath: expect.toEndWith('File.md.md'), path: expect.toEndWith('File.md.md'), scheme: 'file', }), ); }); it('should find uri from a ref with unknown extension', async () => { expect( toPlainObject(await findUriByRef([Uri.file('/Users/Memo/Diary/File.any.md')], 'File.any')), ).toEqual( expect.objectContaining({ $mid: 1, fsPath: expect.toEndWith('File.any.md'), path: expect.toEndWith('File.any.md'), scheme: 'file', }), ); }); it('should not find a dot file', async () => { expect( toPlainObject(await findUriByRef([Uri.file('/Users/Memo/Diary/.md')], '.md')), ).toBeUndefined(); }); it('should find uri by ref for a short link with dots', async () => { expect( toPlainObject( await findUriByRef([Uri.file('/Users/Memo/Diary/test/test-v1.1.0.md')], 'test-v1.1.0'), ), ).toEqual( expect.objectContaining({ $mid: 1, fsPath: expect.toEndWith('test-v1.1.0.md'), path: expect.toEndWith('test-v1.1.0.md'), scheme: 'file', }), ); }); it('should find uri by ref for a long link with dots', async () => { expect( toPlainObject( await findUriByRef([Uri.file('/Users/Memo/Diary/test/test-v1.1.0.md')], 'test/test-v1.1.0'), ), ).toEqual( expect.objectContaining({ $mid: 1, fsPath: expect.toEndWith('test-v1.1.0.md'), path: expect.toEndWith('test-v1.1.0.md'), scheme: 'file', }), ); }); }); describe('ensureDirectoryExists()', () => { beforeEach(closeEditorsAndCleanWorkspace); afterEach(closeEditorsAndCleanWorkspace); it('should create all necessary directories', () => { const dirPath = path.join(getWorkspaceFolder()!, 'folder1', 'folder2'); expect(fs.existsSync(dirPath)).toBe(false); ensureDirectoryExists(path.join(dirPath, 'file.md')); expect(fs.existsSync(dirPath)).toBe(true); }); }); describe('extractDanglingRefs()', () => { beforeEach(closeEditorsAndCleanWorkspace); afterEach(closeEditorsAndCleanWorkspace); it('should extract dangling refs', async () => { const name0 = rndName(); await createFile(`${name0}.md`); expect( extractDanglingRefs( ` [[dangling-ref]] [[dangling-ref]] [[dangling-ref2|Test Label]] [[folder1/long-dangling-ref]] ![[dangling-ref3]] \`[[dangling-ref-within-code-span]]\` \`\`\` Preceding text [[dangling-ref-within-fenced-code-block]] Following text \`\`\` [[${name0}]] `, ), ).toEqual(['dangling-ref', 'dangling-ref2', 'folder1/long-dangling-ref', 'dangling-ref3']); }); }); describe('findDanglingRefsByFsPath()', () => { beforeEach(closeEditorsAndCleanWorkspace); afterEach(closeEditorsAndCleanWorkspace); it('should find dangling refs by fs path', async () => { const name0 = rndName(); const name1 = rndName(); await createFile( `${name0}.md`, ` [[dangling-ref]] [[dangling-ref]] [[dangling-ref2|Test Label]] [[folder1/long-dangling-ref]] ![[dangling-ref3]] \`[[dangling-ref-within-code-span]]\` \`\`\` Preceding text [[dangling-ref-within-fenced-code-block]] Following text \`\`\` [[${name1}]] `, ); await createFile(`${name1}.md`); const refsByFsPath = await findDanglingRefsByFsPath(cache.getWorkspaceCache().markdownUris); expect(Object.keys(refsByFsPath)).toHaveLength(1); expect(Object.values(refsByFsPath)[0]).toEqual([ 'dangling-ref', 'dangling-ref2', 'folder1/long-dangling-ref', 'dangling-ref3', ]); }); it('should find dangling refs from the just edited document', async () => { const name0 = rndName(); await createFile(`${name0}.md`, '[[dangling-ref]]'); const doc = await openTextDocument(`${name0}.md`); const editor = await window.showTextDocument(doc); const refsByFsPath = await findDanglingRefsByFsPath(cache.getWorkspaceCache().markdownUris); expect(Object.keys(refsByFsPath)).toHaveLength(1); expect(Object.values(refsByFsPath)[0]).toEqual(['dangling-ref']); await editor.edit((edit) => edit.insert(new Position(1, 0), '[[dangling-ref2]]')); const refsByFsPath2 = await findDanglingRefsByFsPath(cache.getWorkspaceCache().markdownUris); expect(Object.keys(refsByFsPath2)).toHaveLength(1); expect(Object.values(refsByFsPath2)[0]).toEqual(['dangling-ref', 'dangling-ref2']); await editor.edit((edit) => edit.delete(new Range(new Position(0, 0), new Position(2, 0)))); expect(await findDanglingRefsByFsPath(cache.getWorkspaceCache().markdownUris)).toEqual({}); }); }); describe('findNonIgnoredFiles()', () => { beforeEach(closeEditorsAndCleanWorkspace); afterEach(closeEditorsAndCleanWorkspace); it('should find non-ignored files', async () => { const prevConfig = getConfigProperty('search.exclude', {}); await updateConfigProperty('search.exclude', { '**/search-ignored': true }); const allowedName = rndName(); const ignoredName = rndName(); await createFile(`${allowedName}.md`); await createFile(`search-ignored/some-package/${ignoredName}.md`); const files = await findNonIgnoredFiles('**/*.md'); expect(files).toHaveLength(1); expect(path.basename(files[0].fsPath)).toBe(`${allowedName}.md`); await updateConfigProperty('search.exclude', prevConfig); }); describe('when exclude param passed explicitly', () => { it('should find non-ignored files', async () => { const allowedName = rndName(); const ignoredName = rndName(); await createFile(`${allowedName}.md`); await createFile(`search-ignored/some-package/${ignoredName}.md`); const files = await findNonIgnoredFiles('**/*.md', '**/search-ignored'); expect(files).toHaveLength(1); expect(path.basename(files[0].fsPath)).toBe(`${allowedName}.md`); }); }); describe('when exclude param passed explicitly and search.exclude set', () => { it('should find non-ignored files', async () => { const prevConfig = getConfigProperty('search.exclude', {}); await updateConfigProperty('search.exclude', { '**/search-ignored': true }); const allowedName = rndName(); const ignoredName = rndName(); await createFile(`${allowedName}.md`); await createFile(`search-ignored/some-package/${ignoredName}.md`); await createFile(`search-ignored-2/some-package/${ignoredName}.md`); const files = await findNonIgnoredFiles('**/*.md', '**/search-ignored-2'); expect(files).toHaveLength(1); expect(path.basename(files[0].fsPath)).toBe(`${allowedName}.md`); await updateConfigProperty('search.exclude', prevConfig); }); }); }); describe('getDirRelativeToWorkspace()', () => { it('should get directory relative to workspace', async () => { const dir1 = rndName(); const dir2 = rndName(); const name1 = rndName(); const uri = await createFile(`/${dir1}/${dir2}/${name1}.md`); expect(getDirRelativeToWorkspace(uri)).toBe(path.join('/', dir1, dir2)); }); }); describe('extractRefsFromText()', () => { const refRegExp = new RegExp('\\[\\[([^\\[\\]]+?)\\]\\]', 'g'); it('should extract ref from text', () => { const text = ` [[hello-world]] [[adjacent-ref]] [[any-ref]] `; expect(extractRefsFromText('hello-world', text)).toMatchInlineSnapshot(` Array [ Object { "line": Object { "trailingText": "[[hello-world]] [[adjacent-ref]]", }, "ref": Object { "position": Object { "end": Object { "character": 19, "line": 1, }, "start": Object { "character": 8, "line": 1, }, }, "text": "hello-world", }, }, ] `); }); it('should extract refs from text', () => { const text = ` [[hello-world]] [[adjacent-ref]] [[new-world]] [[any-ref]] `; expect(extractRefsFromText(['hello-world', 'new-world'], text)).toMatchInlineSnapshot(` Array [ Object { "line": Object { "trailingText": "[[hello-world]] [[adjacent-ref]] [[new-world]]", }, "ref": Object { "position": Object { "end": Object { "character": 19, "line": 1, }, "start": Object { "character": 8, "line": 1, }, }, "text": "hello-world", }, }, Object { "line": Object { "trailingText": "[[new-world]]", }, "ref": Object { "position": Object { "end": Object { "character": 50, "line": 1, }, "start": Object { "character": 41, "line": 1, }, }, "text": "new-world", }, }, ] `); }); it('should not extract empty refs', () => { const text = 'leading text [[]] trailing text'; expect(extractRefsFromText(refRegExp, text)).toHaveLength(0); }); it('should not extract refs within fenced block', () => { const text = ` \`\`\` Preceding text [[some-ref]] Following text \`\`\` `; expect(extractRefsFromText(refRegExp, text)).toHaveLength(0); }); it('should not extract refs within code span', () => { expect(extractRefsFromText('test-ref', '`[[test-ref]]`')).toHaveLength(0); }); it('should extract refs from text using RegExp', () => { const text = ` [[hello-world]] [[test-ref]] some text here... [[any-ref]] `; expect(extractRefsFromText(refRegExp, text)).toMatchInlineSnapshot(` Array [ Object { "line": Object { "trailingText": "[[hello-world]] [[test-ref]]", }, "ref": Object { "position": Object { "end": Object { "character": 19, "line": 1, }, "start": Object { "character": 8, "line": 1, }, }, "text": "hello-world", }, }, Object { "line": Object { "trailingText": "[[test-ref]]", }, "ref": Object { "position": Object { "end": Object { "character": 32, "line": 1, }, "start": Object { "character": 24, "line": 1, }, }, "text": "test-ref", }, }, Object { "line": Object { "trailingText": "[[any-ref]]", }, "ref": Object { "position": Object { "end": Object { "character": 15, "line": 5, }, "start": Object { "character": 8, "line": 5, }, }, "text": "any-ref", }, }, ] `); }); });