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
2 changes: 1 addition & 1 deletion .vscode-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function generateConfig(label) {
/** @type {import('@vscode/test-cli').TestConfiguration} */
let config = {
label,
files: ["out/**/*.test.js"],
files: ["dist/**/*.test.js"],
version: "insiders",
srcDir: "src",
launchArgs: [
Expand Down
1 change: 1 addition & 0 deletions .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ node_modules/**
scripts/**
src/**
webviews/**
**/test/**
.eslintcache*
.eslintignore
.eslintrc*
Expand Down
6 changes: 6 additions & 0 deletions src/test/browser/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

// @ts-nocheck
// This file is providing the test runner to use when running extension tests.
import * as vscode from 'vscode';
require('mocha/mocha');
Expand Down
2 changes: 1 addition & 1 deletion src/test/builders/graphql/latestReviewCommitBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { LatestReviewCommitResponse } from '../../../github/graphql';

import { RateLimitBuilder } from './rateLimitBuilder';

type Repository = LatestReviewCommitResponse['repository'];
type Repository = NonNullable<LatestReviewCommitResponse['repository']>;
type PullRequest = Repository['pullRequest'];
type ViewerLatestReview = PullRequest['viewerLatestReview'];
type Commit = ViewerLatestReview['commit'];
Expand Down
2 changes: 1 addition & 1 deletion src/test/builders/graphql/pullRequestBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const RefBuilder = createBuilderClass<Ref>()({
}),
});

type Repository = PullRequestResponse['repository'];
type Repository = NonNullable<PullRequestResponse['repository']>;
type PullRequest = Repository['pullRequest'];
type Author = PullRequest['author'];
type AssigneesConn = PullRequest['assignees'];
Expand Down
7 changes: 6 additions & 1 deletion src/test/builders/graphql/timelineEventsBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { createBuilderClass, createLink } from '../base';
import { TimelineEventsResponse } from '../../../github/graphql';

import { RateLimitBuilder } from './rateLimitBuilder';

type Repository = TimelineEventsResponse['repository'];
type Repository = NonNullable<TimelineEventsResponse['repository']>;
type PullRequest = Repository['pullRequest'];
type TimelineConn = PullRequest['timelineItems'];

Expand Down
11 changes: 8 additions & 3 deletions src/test/builders/rest/pullRequestBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { UserBuilder } from './userBuilder';
import { RefBuilder } from './refBuilder';
import { NonNullUserRefBuilder, RefBuilder } from './refBuilder';
import { createLink, createBuilderClass } from '../base';
import { OctokitCommon } from '../../../github/common';

Expand Down Expand Up @@ -40,8 +45,8 @@ export const PullRequestBuilder = createBuilderClass<PullRequestUnion>()({
closed_at: { default: '' },
merged_at: { default: '' },
merge_commit_sha: { default: '' },
head: { linked: RefBuilder },
base: { linked: RefBuilder },
head: { linked: NonNullUserRefBuilder },
base: { linked: NonNullUserRefBuilder },
draft: { default: false },
merged: { default: false },
mergeable: { default: true },
Expand Down
18 changes: 17 additions & 1 deletion src/test/builders/rest/refBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { UserBuilder } from './userBuilder';
import { RepositoryBuilder } from './repoBuilder';
import { createBuilderClass } from '../base';
import { OctokitCommon } from '../../../github/common';

type RefUnion = OctokitCommon.PullsListResponseItemHead & OctokitCommon.PullsListResponseItemBase;

export const RefBuilder = createBuilderClass<RefUnion>()({
export const RefBuilder = createBuilderClass<NonNullable<RefUnion>>()({
label: { default: 'octocat:new-feature' },
ref: { default: 'new-feature' },
user: { linked: UserBuilder },
Expand All @@ -14,4 +19,15 @@ export const RefBuilder = createBuilderClass<RefUnion>()({
repo: { linked: <any>RepositoryBuilder },
});

// Variant where user is guaranteed non-null.
type NonNullUserRef = Omit<RefUnion, 'user'> & { user: NonNullable<RefUnion['user']> };

export const NonNullUserRefBuilder = createBuilderClass<NonNullUserRef>()({
label: { default: 'octocat:new-feature' },
ref: { default: 'new-feature' },
user: { linked: UserBuilder }, // non-null guarantee
sha: { default: '0000000000000000000000000000000000000000' },
repo: { linked: <any>RepositoryBuilder },
});

export type RefBuilder = InstanceType<typeof RefBuilder>;
8 changes: 4 additions & 4 deletions src/test/builders/rest/repoBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type License = RepoUnion['license'];
type Permissions = RepoUnion['permissions'];
type CodeOfConduct = RepoUnion['code_of_conduct'];

export const RepositoryBuilder = createBuilderClass<RepoUnion>()({
export const RepositoryBuilder = createBuilderClass<NonNullable<RepoUnion>>()({
id: { default: 0 },
node_id: { default: 'node0' },
name: { default: 'reponame' },
Expand Down Expand Up @@ -123,9 +123,9 @@ export const RepositoryBuilder = createBuilderClass<RepoUnion>()({
name: { default: 'name' },
url: { default: 'https://github.com/octocat/reponame' },
}),
forks: { default: null },
open_issues: { default: null },
watchers: { default: null },
forks: { default: 0 },
open_issues: { default: 0 },
watchers: { default: 0 },
});

export type RepositoryBuilder = InstanceType<typeof RepositoryBuilder>;
4 changes: 3 additions & 1 deletion src/test/builders/rest/userBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ type UserUnion =
| OctokitCommon.PullsListResponseItemHeadRepoOwner
| OctokitCommon.IssuesListEventsForTimelineResponseItemActor;

export const UserBuilder = createBuilderClass<Required<UserUnion>>()({
type NonNullUser = NonNullable<UserUnion>;

export const UserBuilder = createBuilderClass<NonNullUser>()({
id: { default: 0 },
node_id: { default: 'node0' },
login: { default: 'octocat' },
Expand Down
1 change: 0 additions & 1 deletion src/test/github/markdownUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import * as assert from 'assert';
import * as marked from 'marked';
import { PlainTextRenderer } from '../../github/markdownUtils';
import { describe, it } from 'mocha';

describe('PlainTextRenderer', () => {
it('should escape inline code by default', () => {
Expand Down
38 changes: 21 additions & 17 deletions src/test/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

// @ts-nocheck
// This file is providing the test runner to use when running extension tests.
import * as path from 'path';
import * as vscode from 'vscode';
Expand All @@ -6,20 +12,6 @@ import Mocha from 'mocha';
import { mockWebviewEnvironment } from './mocks/mockWebviewEnvironment';
import { EXTENSION_ID } from '../constants';

function addTests(mocha: Mocha, root: string): Promise<void> {
return new Promise((resolve, reject) => {
glob('**/**.test.js', { cwd: root }, (error, files) => {
if (error) {
return reject(error);
}

for (const testFile of files) {
mocha.addFile(path.join(root, testFile));
}
resolve();
});
});
}

async function runAllExtensionTests(testsRoot: string, clb: (error: Error | null, failures?: number) => void): Promise<any> {
// Ensure the dev-mode extension is activated
Expand All @@ -31,10 +23,22 @@ async function runAllExtensionTests(testsRoot: string, clb: (error: Error | null
ui: 'bdd',
color: true
});
mocha.addFile(path.resolve(testsRoot, 'globalHooks.js'));
// Load globalHooks if it exists
try {
mocha.addFile(path.resolve(testsRoot, 'globalHooks.js'));
} catch (e) {
// globalHooks might not exist in webpack bundle, ignore
}

await addTests(mocha, testsRoot);
await addTests(mocha, path.resolve(testsRoot, '../../../webviews/'));
// Import all test files using webpack's require.context
try {
// Load tests from src/test directory only
// Webview tests are compiled separately with the webview configuration
const importAll = (r: __WebpackModuleApi.RequireContext) => r.keys().forEach(r);
importAll(require.context('./', true, /\.test$/));
} catch (e) {
console.log('Error loading tests:', e);
}

if (process.env.TEST_JUNIT_XML_PATH) {
mocha.reporter('mocha-multi-reporters', {
Expand Down
9 changes: 4 additions & 5 deletions src/test/mocks/mockGitHubRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ import { MockTelemetry } from './mockTelemetry';
import { Uri } from 'vscode';
import { LoggingOctokit, RateLogger } from '../../github/loggingOctokit';
import { mergeQuerySchemaWithShared } from '../../github/common';
import { PullRequestModel } from '../../github/pullRequestModel';
import { TimelineEvent } from '../../common/timelineEvent';

const queries = mergeQuerySchemaWithShared(require('../../github/queries.gql'), require('../../github/queriesShared.gql')) as any;

export class MockGitHubRepository extends GitHubRepository {
Expand All @@ -35,7 +34,7 @@ export class MockGitHubRepository extends GitHubRepository {

this._hub = {
octokit: new LoggingOctokit(this.queryProvider.octokit, new RateLogger(new MockTelemetry(), true)),
graphql: null,
graphql: {} as any,
};

this._metadata = Promise.resolve({
Expand Down Expand Up @@ -73,8 +72,8 @@ export class MockGitHubRepository extends GitHubRepository {
block(builder);
const responses = builder.build();

const prNumber = responses.pullRequest.repository.pullRequest.number;
const headRef = responses.pullRequest.repository.pullRequest.headRef;
const prNumber = responses.pullRequest.repository!.pullRequest.number;
const headRef = responses.pullRequest.repository?.pullRequest.headRef;

this.queryProvider.expectGraphQLQuery(
{
Expand Down
6 changes: 3 additions & 3 deletions src/test/view/prsTree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe('GitHub Pull Requests view', function () {
userAgent: 'GitHub VSCode Pull Requests',
previews: ['shadow-cat-preview'],
}), new RateLogger(telemetry, true)),
graphql: null,
graphql: {} as any,
};

return github;
Expand Down Expand Up @@ -160,7 +160,7 @@ describe('GitHub Pull Requests view', function () {
);
});
}).pullRequest;
const prItem0 = await parseGraphQLPullRequest(pr0.repository.pullRequest, gitHubRepository);
const prItem0 = await parseGraphQLPullRequest(pr0.repository!.pullRequest, gitHubRepository);
const pullRequest0 = new PullRequestModel(credentialStore, telemetry, gitHubRepository, remote, prItem0);

const pr1 = gitHubRepository.addGraphQLPullRequest(builder => {
Expand All @@ -177,7 +177,7 @@ describe('GitHub Pull Requests view', function () {
);
});
}).pullRequest;
const prItem1 = await parseGraphQLPullRequest(pr1.repository.pullRequest, gitHubRepository);
const prItem1 = await parseGraphQLPullRequest(pr1.repository!.pullRequest, gitHubRepository);
const pullRequest1 = new PullRequestModel(credentialStore, telemetry, gitHubRepository, remote, prItem1);

const repository = new MockRepository();
Expand Down
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
},
"exclude": [
"node_modules",
"src/test",
"webviews"
]
}
67 changes: 64 additions & 3 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ async function getWebviewConfig(mode, env, entry) {
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
// Use absolute paths (file:///) in source maps instead of the default webpack:// scheme
devtoolModuleFilenameTemplate: info => 'file:///' + info.absoluteResourcePath.replace(/\\/g, '/'),
devtoolFallbackModuleFilenameTemplate: 'file:///[absolute-resource-path]'
},
optimization: {
minimizer: [
Expand Down Expand Up @@ -156,14 +159,12 @@ async function getWebviewConfig(mode, env, entry) {
*/
async function getExtensionConfig(target, mode, env) {
const basePath = path.join(__dirname, 'src');
const glob = require('glob');

/**
* @type WebpackConfig['plugins'] | any
*/
const plugins = [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
}),
new ForkTsCheckerPlugin({
async: false,
formatter: 'basic',
Expand All @@ -174,6 +175,43 @@ async function getExtensionConfig(target, mode, env) {
new webpack.ContextReplacementPlugin(/mocha/, /^$/)
];

// Add fixtures copying plugin for node target (which has individual test files)
if (target === 'node') {
const fs = require('fs');
const srcRoot = 'src';
class CopyFixturesPlugin {
apply(compiler) {
compiler.hooks.afterEmit.tap('CopyFixturesPlugin', () => {
this.copyFixtures(srcRoot, compiler.options.output.path);
});
}

copyFixtures(inputDir, outputDir) {
try {
const files = fs.readdirSync(inputDir);
for (const file of files) {
const filePath = path.join(inputDir, file);
const stats = fs.statSync(filePath);
if (stats.isDirectory()) {
if (file === 'fixtures') {
const outputFilePath = path.join(outputDir, inputDir.substring(srcRoot.length), file);
const inputFilePath = path.join(inputDir, file);
fs.cpSync(inputFilePath, outputFilePath, { recursive: true, force: true });
} else {
this.copyFixtures(filePath, outputDir);
}
}
}
} catch (error) {
// Ignore errors during fixtures copying to not break the build
console.warn('Warning: Could not copy fixtures:', error.message);
}
}
}

plugins.push(new CopyFixturesPlugin());
}

if (target === 'webworker') {
plugins.push(new webpack.ProvidePlugin({
process: path.join(
Expand All @@ -190,8 +228,28 @@ async function getExtensionConfig(target, mode, env) {
const entry = {
extension: './src/extension.ts',
};

// Add test entry points
if (target === 'webworker') {
entry['test/index'] = './src/test/browser/index.ts';
} else if (target === 'node') {
// Add main test runner
entry['test/index'] = './src/test/index.ts';

// Add individual test files as separate entry points
const testFiles = glob.sync('src/test/**/*.test.ts', { cwd: __dirname });
testFiles.forEach(testFile => {
// Convert src/test/github/utils.test.ts -> test/github/utils.test
const entryName = testFile.replace('src/', '').replace('.ts', '');
entry[entryName] = `./${testFile}`;
});
}

// Don't limit chunks for node target when we have individual test files
if (target !== 'node' || !('test/index' in entry && Object.keys(entry).some(key => key.endsWith('.test')))) {
plugins.unshift(new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
}));
}

return {
Expand All @@ -205,6 +263,9 @@ async function getExtensionConfig(target, mode, env) {
libraryTarget: 'commonjs2',
filename: '[name].js',
chunkFilename: 'feature-[name].js',
// Use absolute paths (file:///) in source maps for easier debugging of tests & sources
devtoolModuleFilenameTemplate: info => 'file:///' + info.absoluteResourcePath.replace(/\\/g, '/'),
devtoolFallbackModuleFilenameTemplate: 'file:///[absolute-resource-path]',
},
optimization: {
minimizer: [
Expand Down
Loading