diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..ff2d636
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,36 @@
+
+name: Unit tests
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ types:
+ - "opened"
+ - "synchronize"
+ - "reopened"
+env:
+ NODE_VERSION: '16'
+
+jobs:
+ test:
+ name: Test
+ strategy:
+ fail-fast: true
+ matrix:
+ os: [macos-latest, ubuntu-latest, windows-latest]
+ runs-on: ${{matrix.os}}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Install Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ - run: yarn install
+ - run: xvfb-run -a yarn test
+ if: runner.os == 'Linux'
+ - run: yarn test
+ if: runner.os != 'Linux'
+
diff --git a/package.json b/package.json
index 35b8955..7f0a1eb 100644
--- a/package.json
+++ b/package.json
@@ -175,7 +175,9 @@
"compile": "tsc -p ./ && yarn run lint",
"compileDev": "tsc -p ./ && yarn run lint && webpack --mode development",
"watch": "tsc -watch -p ./",
- "lint": "eslint src --ext ts"
+ "lint": "eslint src --ext ts",
+ "pretest": "tsc -p ./",
+ "test": "node ./out/test/runTest.js"
},
"devDependencies": {
"@types/glob": "^7.1.3",
@@ -186,12 +188,13 @@
"@types/xml2js": "^0.4.9",
"@typescript-eslint/eslint-plugin": "^4.28.5",
"@typescript-eslint/parser": "^4.28.5",
+ "@vscode/test-electron": "^2.1.3",
"eslint": "^7.19.0",
"glob": "^7.1.6",
"mocha": "^8.2.1",
"ts-loader": "^9.2.6",
"tsc": "^2.0.4",
- "typescript": "^4.1.3",
+ "typescript": "^4.6.3",
"vsce": "^2.6.3",
"vscode-test": "^1.5.0",
"webpack": "^5.58.2",
diff --git a/test/csprojReader.test.ts b/test/csprojReader.test.ts
deleted file mode 100644
index 6dc56e1..0000000
--- a/test/csprojReader.test.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import * as assert from "assert";
-import CsprojReader from "../src/project/csprojReader";
-
-describe("CsprojReader", () => {
- it("getNamespace for valid csproj file with RootNamespace attribute in the first PropertyGroup should return Xamarin.Forms", () => {
- const csproj =
- '\
- \
- Xamarin.Forms\
- \
- ';
- const detector = new CsprojReader(csproj);
-
- const actual = detector.getRootNamespace();
-
- assert.equal(actual, "Xamarin.Forms");
- });
-
- it("getNamespace for valid csproj file with RootNamespace attribute in the third PropertyGroup should return System.Linq", () => {
- const csproj =
- '\
- \
- \
- \
- System.Linq\
- \
- ';
- const detector = new CsprojReader(csproj);
-
- const actual = detector.getRootNamespace();
-
- assert.equal(actual, "System.Linq");
- });
-
- it("getNamespace for valid csproj file without RootNamespace attribute should return undefined", () => {
- const csproj =
- '\
- \
- \
- ';
- const detector = new CsprojReader(csproj);
-
- const actual = detector.getRootNamespace();
-
- assert.equal(actual, undefined);
- });
-
- it("getNamespace for valid csproj file without PropertyGroup attribute should return undefined", () => {
- const csproj = '';
- const detector = new CsprojReader(csproj);
-
- const actual = detector.getRootNamespace();
-
- assert.equal(actual, undefined);
- });
-
- it("getNamespace for invalid csproj file should return undefined", () => {
- const csproj = "lorem ipsum";
- const detector = new CsprojReader(csproj);
-
- const actual = detector.getRootNamespace();
-
- assert.equal(actual, undefined);
- });
-});
diff --git a/test/index.ts b/test/index.ts
deleted file mode 100644
index 50bae45..0000000
--- a/test/index.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-//
-// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
-//
-// This file is providing the test runner to use when running extension tests.
-// By default the test runner in use is Mocha based.
-//
-// You can provide your own test runner if you want to override it by exporting
-// a function run(testRoot: string, clb: (error:Error) => void) that the extension
-// host can call to run the tests. The test runner is expected to use console.log
-// to report the results back to the caller. When the tests are finished, return
-// a possible error to the callback or null if none.
-
-var testRunner = require('vscode/lib/testrunner');
-
-// You can directly control Mocha options by uncommenting the following lines
-// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
-testRunner.configure({
- ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
- useColors: true // colored output from test results
-});
-
-module.exports = testRunner;
\ No newline at end of file
diff --git a/test/projectJsonReader.test.ts b/test/projectJsonReader.test.ts
deleted file mode 100644
index 529566d..0000000
--- a/test/projectJsonReader.test.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import * as assert from "assert";
-import ProjectJsonReader from "../src/project/projectJsonReader";
-
-describe("ProjectJsonReader", () => {
- it("getNamespace for valid project.json with defaultNamespace attribute should return Xamarin.Forms", () => {
- const json = '{"tooling": {"defaultNamespace":"Xamarin.Forms"}}';
- const detector = new ProjectJsonReader(json);
-
- const actual = detector.getRootNamespace();
-
- assert.equal(actual, "Xamarin.Forms");
- });
-
- it("getNamespace for valid project.json without defaultNamespace attribute should return undefined", () => {
- const json = '{"tooling": {}}';
- const detector = new ProjectJsonReader(json);
-
- const actual = detector.getRootNamespace();
-
- assert.equal(actual, undefined);
- });
-
- it("getNamespace for valid project.json without tooling attribute should return undefined", () => {
- const json = "{}";
- const detector = new ProjectJsonReader(json);
-
- const actual = detector.getRootNamespace();
-
- assert.equal(actual, undefined);
- });
-
- it("getNamespace for invalid project.json should return undefined", () => {
- const json = "lorem ipsum";
- const detector = new ProjectJsonReader(json);
-
- const actual = detector.getRootNamespace();
-
- assert.equal(actual, undefined);
- });
-});
diff --git a/test/runTest.ts b/test/runTest.ts
new file mode 100644
index 0000000..b49e8c1
--- /dev/null
+++ b/test/runTest.ts
@@ -0,0 +1,24 @@
+import * as path from 'path';
+
+import { runTests } from '@vscode/test-electron';
+
+async function main() {
+ try {
+ // The folder containing the Extension Manifest package.json
+ // Passed to `--extensionDevelopmentPath`
+ const extensionDevelopmentPath = path.resolve(__dirname, '../../');
+
+ // The path to the extension test runner script
+ // Passed to --extensionTestsPath
+ const extensionTestsPath = path.resolve(__dirname, './suite/index');
+
+ // Download VS Code, unzip it and run the integration test
+ await runTests({ extensionDevelopmentPath, extensionTestsPath });
+ } catch (err) {
+ console.error(err);
+ console.error('Failed to run tests');
+ process.exit(1);
+ }
+}
+
+main();
diff --git a/test/suite/csprojReader.test.ts b/test/suite/csprojReader.test.ts
new file mode 100644
index 0000000..0aa0dc5
--- /dev/null
+++ b/test/suite/csprojReader.test.ts
@@ -0,0 +1,194 @@
+import * as assert from 'assert';
+import * as path from 'path';
+import * as fs from 'fs';
+import CsprojReader from '../../src/project/csprojReader';
+
+const fixture_path= path.resolve(__dirname, '../suite/');
+interface Fixture {
+ filename: string,
+ csproj : string,
+ expected : string | undefined,
+}
+
+suite('CsprojReader', () => {
+ const validTargetFramework : Array = [
+ 'netcoreapp1.0',
+ 'netcoreapp1.1',
+ 'netcoreapp2.0',
+ 'netcoreapp2.1',
+ 'netcoreapp2.2',
+ 'netcoreapp3.0',
+ 'netcoreapp3.1',
+ 'net5.0',
+ 'net6.0',
+ ];
+
+ const rootNameSpacefixtures : Array = [
+ {
+ filename: 'xamarin.csproj',
+ csproj: `
+
+
+ Xamarin.Forms
+
+ `,
+ expected: 'Xamarin.Forms',
+ },
+ {
+ filename: 'linq.csproj',
+ csproj: `
+
+
+
+ System.Linq
+
+ `,
+ expected: 'System.Linq',
+ },
+ {
+ filename: 'empty-group.csproj',
+ csproj: `
+
+
+
+ `,
+ expected: undefined,
+ },
+ {
+ filename: 'only-project-node.csproj',
+ csproj: '',
+ expected: undefined,
+ },
+ ];
+
+ const targetFrameworkFixtures: Array = [
+ {
+ filename: 'first-node.csproj',
+ csproj: `
+
+
+ %PLACE_HOLDER%
+
+
+ `,
+ expected: '%PLACE_HOLDER%',
+ },
+ {
+ filename: 'last-node.csproj',
+ csproj: `
+
+
+
+ %PLACE_HOLDER%
+
+ `,
+ expected: '%PLACE_HOLDER%',
+ },
+ {
+ filename: 'empty-group.csproj',
+ csproj: `
+
+
+
+ `,
+ expected: undefined,
+ },
+ {
+ filename: 'only-project-node.csproj',
+ csproj: '',
+ expected: undefined,
+ },
+ ];
+
+ const invalidCsProjFixtures : Array = [
+ {
+ filename: 'empty.csproj',
+ csproj: '',
+ expected: undefined,
+ },
+ {
+ filename: 'random-text.csproj',
+ csproj: 'lorem ipsum',
+ expected: undefined,
+ },
+ {
+ filename: 'malformed-xml-1.csproj',
+ csproj: '<',
+ expected: undefined,
+ },
+ {
+ filename: 'malformed-xml-2.csproj',
+ csproj: '<>',
+ expected: undefined,
+ },
+ {
+ filename: 'malformed-xml-3.csproj',
+ csproj: '/>',
+ expected: undefined,
+ },
+ {
+ filename: 'malformed-xml-missing-end-tag.csproj',
+ csproj: '',
+ expected: undefined,
+ },
+ {
+ filename: 'malformed-xml-missing-start-tag.csproj',
+ csproj: '',
+ expected: undefined,
+ },
+ ];
+ invalidCsProjFixtures.forEach(({ filename, csproj, expected }) => {
+ test(`getRootNamespace from ${filename} with invalid content ${csproj} should return expected result ${expected}`, async () => {
+ const filePath = `${fixture_path}/${filename}`;
+ fs.writeFileSync(filePath, csproj);
+ const detector = new CsprojReader(filePath);
+ const actual = await detector.getRootNamespace();
+
+ fs.unlinkSync(filePath);
+ assert.strictEqual(actual, expected);
+ });
+ test(`getTargetFramework from ${filename} with invalid content ${csproj} should return expected result ${expected}`, async () => {
+ const filePath = `${fixture_path}/${filename}`;
+ fs.writeFileSync(filePath, csproj);
+ const detector = new CsprojReader(filePath);
+ const actual = await detector.getTargetFramework();
+
+ fs.unlinkSync(filePath);
+ assert.strictEqual(actual, expected);
+ });
+ });
+
+ rootNameSpacefixtures.forEach(({ filename, csproj, expected }) => {
+ test(`getNamespace from ${filename} with content ${csproj} should return expected result ${expected}`, async () => {
+ const filePath = `${fixture_path}/${filename}`;
+ fs.writeFileSync(filePath, csproj);
+ const detector = new CsprojReader(filePath);
+ const actual = await detector.getRootNamespace();
+
+ fs.unlinkSync(filePath);
+ assert.strictEqual(actual, expected);
+ });
+ });
+
+ targetFrameworkFixtures.forEach(({ filename, csproj, expected }) => {
+ validTargetFramework.forEach((targetFramework, index) =>{
+ test(`getTargetFramework from ${filename} with content ${csproj} should return expected result ${expected}`, async () => {
+ const filePath = `${fixture_path}/${index}-${filename}`;
+ fs.writeFileSync(filePath, csproj.replace('%PLACE_HOLDER%', targetFramework));
+ const detector = new CsprojReader(filePath);
+ const actual = await detector.getTargetFramework();
+
+ fs.unlinkSync(filePath);
+ assert.strictEqual(actual, expected?.replace('%PLACE_HOLDER%', targetFramework));
+ });
+ });
+ });
+
+ test('getFilePath return expected result',() => {
+ const filePath = `${fixture_path}/my-fancy-csproj-file`;
+ const detector = new CsprojReader(filePath);
+ const actual = detector.getFilePath();
+
+ assert.strictEqual(actual, filePath);
+ });
+});
diff --git a/test/suite/index.ts b/test/suite/index.ts
new file mode 100644
index 0000000..2ce3229
--- /dev/null
+++ b/test/suite/index.ts
@@ -0,0 +1,38 @@
+import * as path from 'path';
+import * as Mocha from 'mocha';
+import * as glob from 'glob';
+
+export function run(): Promise {
+ // Create the mocha test
+ const mocha = new Mocha({
+ ui: 'tdd',
+ color: true
+ });
+
+ const testsRoot = path.resolve(__dirname, '..');
+
+ return new Promise((c, e) => {
+ glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
+ if (err) {
+ return e(err);
+ }
+
+ // Add files to the test suite
+ files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
+
+ try {
+ // Run the mocha test
+ mocha.run(failures => {
+ if (failures > 0) {
+ e(new Error(`${failures} tests failed.`));
+ } else {
+ c();
+ }
+ });
+ } catch (err) {
+ console.error(err);
+ e(err);
+ }
+ });
+ });
+}
diff --git a/test/suite/projectJsonReader.test.ts b/test/suite/projectJsonReader.test.ts
new file mode 100644
index 0000000..4268548
--- /dev/null
+++ b/test/suite/projectJsonReader.test.ts
@@ -0,0 +1,55 @@
+import * as assert from 'assert';
+import * as path from 'path';
+import * as fs from 'fs';
+import ProjectJsonReader from '../../src/project/projectJsonReader';
+
+const fixture_path= path.resolve(__dirname, '../suite/');
+interface Fixture {
+ filename: string,
+ json : string,
+ expected : string | undefined,
+}
+
+suite('ProjectJsonReader', () => {
+ const fixtures : Array = [
+ {
+ filename: 'xamarin.json',
+ json: '{"tooling": {"defaultNamespace":"Xamarin.Forms"}}',
+ expected: 'Xamarin.Forms',
+ },
+ {
+ filename: 'empty-configuration.json',
+ json: '{"tooling": {}}',
+ expected: undefined,
+ },
+ {
+ filename: 'empty-json.json',
+ json: '{}',
+ expected: undefined,
+ },
+ {
+ filename: 'wrong-json.json',
+ json: 'lorem ipsum',
+ expected: undefined,
+ },
+ ];
+ fixtures.forEach(({ filename, json, expected }) => {
+ test(`getNamespace from ${filename} with content ${json} should return expected result ${expected}`, async () => {
+ const filePath = `${fixture_path}/${filename}`;
+ fs.writeFileSync(filePath, json);
+ const detector = new ProjectJsonReader(filePath);
+ const actual = await detector.getRootNamespace();
+
+ fs.unlinkSync(filePath);
+ assert.strictEqual(actual, expected);
+ });
+ });
+
+ test('getFilePath return expected result',() => {
+ const filePath = `${fixture_path}/my-fancy-csproj-file`;
+ const detector = new ProjectJsonReader(filePath);
+ const actual = detector.getFilePath();
+
+ assert.strictEqual(actual, filePath);
+ });
+});
diff --git a/tsconfig.json b/tsconfig.json
index 5c32be4..c1ad01e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,4 +17,4 @@
"node_modules",
".vscode-test"
]
-}
\ No newline at end of file
+}
diff --git a/yarn.lock b/yarn.lock
index 1d37254..9d73178 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -214,6 +214,16 @@
resolved "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz"
integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
+"@vscode/test-electron@^2.1.3":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@vscode/test-electron/-/test-electron-2.1.3.tgz#c66c4a29ede1f940c2fa204d269b660b0126dc7f"
+ integrity sha512-ps/yJ/9ToUZtR1dHfWi1mDXtep1VoyyrmGKC3UnIbScToRQvbUjyy1VMqnMEW3EpMmC3g7+pyThIPtPyCLHyow==
+ dependencies:
+ http-proxy-agent "^4.0.1"
+ https-proxy-agent "^5.0.0"
+ rimraf "^3.0.2"
+ unzipper "^0.10.11"
+
"@webassemblyjs/ast@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
@@ -2747,10 +2757,10 @@ typed-rest-client@^1.8.4:
tunnel "0.0.6"
underscore "^1.12.1"
-typescript@^4.1.3:
- version "4.2.4"
- resolved "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz"
- integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==
+typescript@^4.6.3:
+ version "4.6.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
+ integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"