Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
name: Node.js CI

on:
push
- push
- pull_request
Comment on lines +7 to +8
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hogashi, I'd like to verify in my PR that the tests I added work. Can you approve this workflow change (not the PR itself yet)? See also https://docs.github.com/en/actions/how-tos/manage-workflow-runs/approve-runs-from-forks

Copy link
Contributor Author

@cbachhuber cbachhuber Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the tests, they now pass locally. I'm opening the PR for review. Still, I'd appreciate having it gated by CI.

$ jest
 PASS  src/__tests__/makeHttpsUrl.test.ts
 PASS  src/__tests__/extension.test.ts

Test Suites: 2 passed, 2 total
Tests:       15 passed, 15 total
Snapshots:   0 total
Time:        2.945 s
Ran all test suites.
Done in 11.55s.


jobs:
test:
Expand Down
18 changes: 18 additions & 0 deletions src/__mocks__/vscode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Mock for VS Code API
module.exports = {
commands: {
registerCommand: jest.fn()
},
extensions: {
getExtension: jest.fn()
},
window: {
activeTextEditor: null,
showInformationMessage: jest.fn()
},
env: {
clipboard: {
writeText: jest.fn()
}
}
};
137 changes: 137 additions & 0 deletions src/__tests__/extension.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Unit tests for the activate function

// Mock dependencies first
const mockNormalizeGitUrl = jest.fn();
jest.mock('normalize-git-url', () => mockNormalizeGitUrl);

const mockMakeHttpsUrl = jest.fn();
jest.mock('../makeHttpsUrl', () => ({
makeHttpsUrl: mockMakeHttpsUrl
}));

// Mock VS Code module
const mockCommands = {
registerCommand: jest.fn()
};

const mockExtensions = {
getExtension: jest.fn()
};

const mockWindow = {
activeTextEditor: null as any,
showInformationMessage: jest.fn()
};

const mockEnv = {
clipboard: {
writeText: jest.fn()
}
};

jest.mock('vscode', () => ({
commands: mockCommands,
extensions: mockExtensions,
window: mockWindow,
env: mockEnv
}), { virtual: true });

import { activate } from '../extension';

describe('extension activate function', () => {
const mockContext = {
subscriptions: [] as any[]
} as any;

beforeEach(() => {
jest.clearAllMocks();
mockContext.subscriptions = [];
mockNormalizeGitUrl.mockReturnValue({
url: 'https://github.com/test/repo',
branch: 'main'
});
mockMakeHttpsUrl.mockImplementation((url: string) => url);
});

describe('successful permalink generation with file extensions', () => {
let commandCallback: Function;
const mockRepository = {
rootUri: { fsPath: '/test/repo' },
state: {
remotes: [{ name: 'origin', fetchUrl: 'https://github.com/test/repo.git' }],
submodules: []
},
getCommit: jest.fn().mockResolvedValue({ hash: 'abc123' })
};

const mockGitExtension = {
exports: {
getAPI: jest.fn().mockReturnValue({
repositories: [mockRepository]
})
}
};

beforeEach(() => {
mockExtensions.getExtension.mockReturnValue(mockGitExtension);
activate(mockContext);
commandCallback = mockCommands.registerCommand.mock.calls[0][1];
});

it('should add ?plain=1 for markdown files', async () => {
mockWindow.activeTextEditor = {
document: { fileName: '/test/repo/README.md' },
selection: { start: { line: 0 }, end: { line: 0 } }
};

await commandCallback();
await new Promise(resolve => setImmediate(resolve));

expect(mockEnv.clipboard.writeText).toHaveBeenCalledWith(
expect.stringContaining('README.md?plain=1#L1')
);
});

it('should not add ?plain=1 for other file types', async () => {
mockWindow.activeTextEditor = {
document: { fileName: '/test/repo/script.js' },
selection: { start: { line: 0 }, end: { line: 0 } }
};

await commandCallback();
await new Promise(resolve => setImmediate(resolve));

expect(mockEnv.clipboard.writeText).toHaveBeenCalledWith(
expect.not.stringContaining('?plain=1')
);
});

it('should handle single line selection correctly', async () => {
mockWindow.activeTextEditor = {
document: { fileName: '/test/repo/test.js' },
selection: { start: { line: 5 }, end: { line: 5 } }
};

await commandCallback();
await new Promise(resolve => setImmediate(resolve));

expect(mockEnv.clipboard.writeText).toHaveBeenCalledWith(
expect.stringMatching(/#L6$/)
);
});

it('should handle multi-line selection correctly', async () => {
mockWindow.activeTextEditor = {
document: { fileName: '/test/repo/test.js' },
selection: { start: { line: 5 }, end: { line: 10 } }
};

await commandCallback();
await new Promise(resolve => setImmediate(resolve));

expect(mockEnv.clipboard.writeText).toHaveBeenCalledWith(
expect.stringContaining('#L6-L11')
);
});
});
});
6 changes: 6 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ export function activate(context: vscode.ExtensionContext) {
const relativePath = path.relative(upperPath, absolutePath);
let filePath = upath.toUnix(relativePath);

const fileExtension = path.extname(absolutePath).toLowerCase();
const fileExtensionsWithGitHubPreview = ['.md', '.rst', '.asciidoc', '.adoc'];
if (fileExtensionsWithGitHubPreview.includes(fileExtension)) {
filePath += '?plain=1';
}

const selection = activeTextEditor.selection;
if (selection) {
const start = selection.start.line + 1;
Expand Down