From 16fdf0548456e919519a6e0f61df7b64acdd8b07 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 7 Aug 2026 20:55:23 -0700 Subject: [PATCH 1/2] feat: vitest support --- .gitignore | 45 +- .npmignore | 19 - README.md | 148 +- app/app-root.xml | 2 - app/app.css | 81 - app/app.ts | 3 - app/bundle-app-root.xml | 2 - app/bundle-app.ts | 13 - app/bundle-main-page.ts | 5 - app/bundle-main-page.xml | 7 - app/main-page.ts | 5 - app/main-page.xml | 7 - app/main-view-model.ts | 327 -- app/main.d.ts | 5 - app/main.ts | 23 - app/nativescript.png | Bin 6709 -> 0 bytes app/run-details.ts | 6 - app/run-details.xml | 19 - app/services/karma-files-service.ts | 89 - app/services/karma-host-resolver.ts | 36 - app/services/test-execution-service.ts | 82 - app/services/webpack-test-runner.ts | 10 - app/stop-process.android.ts | 10 - app/stop-process.d.ts | 1 - app/stop-process.ios.ts | 3 - app/test-run-page.ts | 8 - app/test-run-page.xml | 25 - declarations.d.ts | 63 - docs/migrating-from-karma.md | 81 + lib/after-prepare.ts | 30 - lib/before-build-task-args.ts | 8 - lib/before-liveSync.ts | 13 - loaders/unit-test-loader.js | 36 - makeTestApp.sh | 17 - nativescript.webpack.compat.js | 143 - nativescript.webpack.js | 206 +- package-lock.json | 5366 +++++++----------------- package.json | 116 +- platforms/android/AndroidManifest.xml | 5 - platforms/android/unit_test_runner.aar | Bin 12178 -> 0 bytes platforms/ios/Info.plist | 11 - postinstall.ts | 2 - preuninstall.ts | 2 - references.d.ts | 2 - src/index.ts | 28 + src/node/options.spec.ts | 129 + src/node/options.ts | 134 + src/node/plugin.spec.ts | 89 + src/node/plugin.ts | 68 + src/node/pool-worker.spec.ts | 128 + src/node/pool-worker.ts | 83 + src/node/session.ts | 303 ++ src/protocol.spec.ts | 96 + src/protocol.ts | 128 + src/runtime/coordinator.spec.ts | 365 ++ src/runtime/coordinator.ts | 315 ++ src/runtime/expect.spec.ts | 40 + src/runtime/expect.ts | 92 + src/runtime/index.ts | 28 + src/runtime/loopback.spec.ts | 79 + src/runtime/loopback.ts | 37 + src/runtime/registry.spec.ts | 63 + src/runtime/registry.ts | 68 + src/runtime/runner.spec.ts | 110 + src/runtime/runner.ts | 158 + src/runtime/shim.ts | 24 + src/runtime/web-event-polyfill.spec.ts | 88 + src/runtime/web-event-polyfill.ts | 117 + src/runtime/worker.spec.ts | 128 + src/runtime/worker.ts | 406 ++ src/testing/host.ts | 53 + src/testing/index.ts | 16 + src/testing/interact.ts | 75 + src/testing/mount.ts | 86 + src/testing/wait.ts | 52 + src/threading.spec.ts | 27 + src/threading.ts | 29 + src/ui/index.ts | 11 + src/ui/logo.ts | 7 + src/ui/result-model.spec.ts | 246 ++ src/ui/result-model.ts | 156 + src/ui/results-view.ts | 602 +++ tsconfig.build.json | 11 + tsconfig.json | 34 +- tsconfig.lib.json | 8 - vitest.config.mts | 8 + 86 files changed, 6733 insertions(+), 5074 deletions(-) delete mode 100644 .npmignore delete mode 100644 app/app-root.xml delete mode 100644 app/app.css delete mode 100644 app/app.ts delete mode 100644 app/bundle-app-root.xml delete mode 100644 app/bundle-app.ts delete mode 100644 app/bundle-main-page.ts delete mode 100644 app/bundle-main-page.xml delete mode 100644 app/main-page.ts delete mode 100644 app/main-page.xml delete mode 100644 app/main-view-model.ts delete mode 100644 app/main.d.ts delete mode 100644 app/main.ts delete mode 100644 app/nativescript.png delete mode 100644 app/run-details.ts delete mode 100644 app/run-details.xml delete mode 100644 app/services/karma-files-service.ts delete mode 100644 app/services/karma-host-resolver.ts delete mode 100644 app/services/test-execution-service.ts delete mode 100644 app/services/webpack-test-runner.ts delete mode 100644 app/stop-process.android.ts delete mode 100644 app/stop-process.d.ts delete mode 100644 app/stop-process.ios.ts delete mode 100644 app/test-run-page.ts delete mode 100644 app/test-run-page.xml delete mode 100644 declarations.d.ts create mode 100644 docs/migrating-from-karma.md delete mode 100644 lib/after-prepare.ts delete mode 100644 lib/before-build-task-args.ts delete mode 100644 lib/before-liveSync.ts delete mode 100644 loaders/unit-test-loader.js delete mode 100755 makeTestApp.sh delete mode 100644 nativescript.webpack.compat.js delete mode 100644 platforms/android/AndroidManifest.xml delete mode 100644 platforms/android/unit_test_runner.aar delete mode 100644 platforms/ios/Info.plist delete mode 100644 postinstall.ts delete mode 100644 preuninstall.ts delete mode 100644 references.d.ts create mode 100644 src/index.ts create mode 100644 src/node/options.spec.ts create mode 100644 src/node/options.ts create mode 100644 src/node/plugin.spec.ts create mode 100644 src/node/plugin.ts create mode 100644 src/node/pool-worker.spec.ts create mode 100644 src/node/pool-worker.ts create mode 100644 src/node/session.ts create mode 100644 src/protocol.spec.ts create mode 100644 src/protocol.ts create mode 100644 src/runtime/coordinator.spec.ts create mode 100644 src/runtime/coordinator.ts create mode 100644 src/runtime/expect.spec.ts create mode 100644 src/runtime/expect.ts create mode 100644 src/runtime/index.ts create mode 100644 src/runtime/loopback.spec.ts create mode 100644 src/runtime/loopback.ts create mode 100644 src/runtime/registry.spec.ts create mode 100644 src/runtime/registry.ts create mode 100644 src/runtime/runner.spec.ts create mode 100644 src/runtime/runner.ts create mode 100644 src/runtime/shim.ts create mode 100644 src/runtime/web-event-polyfill.spec.ts create mode 100644 src/runtime/web-event-polyfill.ts create mode 100644 src/runtime/worker.spec.ts create mode 100644 src/runtime/worker.ts create mode 100644 src/testing/host.ts create mode 100644 src/testing/index.ts create mode 100644 src/testing/interact.ts create mode 100644 src/testing/mount.ts create mode 100644 src/testing/wait.ts create mode 100644 src/threading.spec.ts create mode 100644 src/threading.ts create mode 100644 src/ui/index.ts create mode 100644 src/ui/logo.ts create mode 100644 src/ui/result-model.spec.ts create mode 100644 src/ui/result-model.ts create mode 100644 src/ui/results-view.ts create mode 100644 tsconfig.build.json delete mode 100644 tsconfig.lib.json create mode 100644 vitest.config.mts diff --git a/.gitignore b/.gitignore index a074caf..fc51bac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,6 @@ -.DS_Store - -test-app/ - -*.js -*.js.map -!nativescript.webpack.js -!nativescript.webpack.compat.js -!loaders/unit-test-loader.js - -coverage -lib-cov -*.seed -*.log -*.csv -*.dat -*.out -*.pid -*.gz +node_modules/ +dist/ *.tgz -*.tmp -*.sublime-workspace -tscommand*.tmp.txt -.tscache/ - -pids -logs -results -scratch/ -.idea/ -.settings/ -.vscode/ -test-reports.xml -package-lock.json - -npm-debug.log -node_modules -.d.ts - -platforms/android/nativescript_unit_test_runner.aar -platforms/android/unit_test_runner.aar +*.tsbuildinfo +.DS_Store +coverage/ diff --git a/.npmignore b/.npmignore deleted file mode 100644 index e75726b..0000000 --- a/.npmignore +++ /dev/null @@ -1,19 +0,0 @@ -.idea -.gitattributes -.gitmodules -*.sublime-project -lint.* -.jshint* -.npmignore -*.tgz -test-reports.xml -tscommand.tmp.txt -.tscache/ -.vscode/ -*.ts -!app/main.d.ts -*.js.map -definitions -platforms/android/* -!platforms/android/*.aar -*.tgz diff --git a/README.md b/README.md index a8d7f92..76cb39a 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,146 @@ -Unit test runner for NativeScript -================================= +# @nativescript/unit-test-runner -Refer to the documentation of NativeScript CLI's `ns test init` command for usage. +Run [Vitest](https://vitest.dev) unit **and UI** tests inside real NativeScript +runtimes on Android and iOS. -If you encounter an issue, please log it at https://github.com/NativeScript/nativescript-cli/ +Vitest stays on your machine as the orchestrator — configuration, CLI, +reporters, `--ui`, coverage, and editor integrations all work as usual — while +your specs execute on device/emulator inside the actual V8/JSC runtimes, with +full access to native APIs and the NativeScript UI layer. -### Troubleshooting +> Version 5 is a complete rewrite. Karma-based testing (v4 and below) is +> deprecated; see the [migration guide](./docs/migrating-from-karma.md). -If you see an error like this: +## Quick start +```bash +ns test init --framework vitest +ns test ios # or: ns test android ``` -Error: connect ECONNREFUSED ::1:9876 - at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1195:16) + +Or run Vitest directly (what editor extensions and CI use): + +```bash +NS_PLATFORM=ios npx vitest run +``` + +## How it works + +- A Vitest plugin installs a custom pool that forwards Vitest's standard + worker protocol over a WebSocket bridge (bound to `127.0.0.1`). +- The plugin launches your app via `ns run --no-hmr + --env.unitTesting`; the webpack helper (auto-discovered from this package) + swaps the bundle entry to your `test.ts` for test builds only. +- On device, a coordinator connects back to the host and executes specs: + - **Main-thread context (default):** specs run on the UI thread, so they can + create Views, navigate Frames, and use every NativeScript API. + - **Worker contexts (opt-in):** additional isolated NativeScript `Worker` + runtimes for parallel, non-UI specs. +- Results flow through Vitest's normal RPC, so every reporter works unchanged. + +## Configuration + +```ts +// vitest.config.mts +import { defineConfig } from 'vitest/config'; +import { nativeScript } from '@nativescript/unit-test-runner'; + +export default defineConfig({ + plugins: [ + nativeScript({ + platform: (process.env.NS_PLATFORM as 'android' | 'ios') || 'ios', + device: process.env.NS_DEVICE || undefined, + // workers: 2, // extra Worker runtimes for non-UI specs + // mainThread: false, // disable the UI-capable slot (workers only) + // port: 17878, + // launch: false, // attach to an app you run yourself + }), + ], +}); +``` + +Per-platform setups are possible with [Vitest projects](https://vitest.dev/guide/projects) +— give each project its own `nativeScript({ platform })` plugin instance. + +## UI testing + +```ts +import { describe, expect, it } from 'vitest'; +import { Button } from '@nativescript/core'; +import { mount, tap } from '@nativescript/unit-test-runner/testing'; + +describe('counter button', () => { + it('increments on tap', async () => { + let count = 0; + const { view } = await mount(() => { + const button = new Button(); + button.text = 'Count'; + button.on('tap', () => (count += 1)); + return button; + }); + + await tap(view); + expect(count).toBe(1); + expect(view.isLayoutValid).toBe(true); + }); +}); ``` -When using node 17 or higher, make sure your `karma.conf.js` contains a server hostname setting, for example: +`mount()` attaches the view to the host page scaffolded in your `test.ts`, +waits for `loaded` + a real layout pass, and auto-unmounts when the test +finishes. Also available from `./testing`: `tap`, `doubleTap`, `longPress`, +`enterText`, `returnPress`, `waitForLayout`, `waitUntil`, `nextRenderPass`. + +UI specs require the main-thread context (the default). Files routed to +Worker contexts must not touch Views. +## Coverage + +```bash +ns test ios --env.codeCoverage +# or: NS_PLATFORM=ios npx vitest run --coverage ``` -// web server hostname (ensure this is present) -hostname: '127.0.0.1', -// web server port -port: 9876, +Use the `istanbul` provider — device runtimes do not expose V8 coverage: + +```ts +test: { + coverage: { provider: 'istanbul', reporter: ['text', 'lcov'] }, +}, ``` -See [here](https://github.com/NativeScript/nativescript-cli/commit/81cb9c37cdd4e24115be79b24b68dfbaf8cdcfd2) for changeset in CLI which adds that to all newly initialized unit test setups. + +## Devices and networking + +| Target | Transport | +| --- | --- | +| iOS simulator | host loopback (`127.0.0.1`) | +| Android emulator | `10.0.2.2` → host loopback | +| Physical Android | USB via automatic `adb reverse` | +| Physical iOS | pass a LAN-reachable `url` to the coordinator in `test.ts` | + +The host server binds `127.0.0.1` by default. Test builds on Android may need +a scoped cleartext exception for `10.0.2.2`/`127.0.0.1`; on iOS, +`NSAllowsLocalNetworking`. + +## Support matrix + +| Feature | Status | +| --- | --- | +| `describe` / `it` / hooks / `expect` | ✅ | +| Reporters, `vitest --ui`, JUnit output | ✅ | +| Istanbul coverage | ✅ | +| UI testing (`mount`, gestures) | ✅ main-thread context | +| `vi.fn` / `vi.spyOn` / fake timers | 🚧 planned | +| Snapshots | 🚧 planned | +| Watch mode | 🚧 planned (one-shot `vitest run` today) | +| `vi.mock` module mocking | ❌ not supported (webpack static bundle) — prefer DI | + +## Credits + +The host↔device bridge design originates from +[`@cross-code/vitest-ns`](https://github.com/listepo/cross-code) by +[@listepo](https://github.com/listepo) (MIT). Thank you! + +## License + +Apache-2.0 diff --git a/app/app-root.xml b/app/app-root.xml deleted file mode 100644 index 6151214..0000000 --- a/app/app-root.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/app/app.css b/app/app.css deleted file mode 100644 index ae15960..0000000 --- a/app/app.css +++ /dev/null @@ -1,81 +0,0 @@ -.landingContainer { - background-color: white; -} - -.landingBackground { - horizontal-align: center; - vertical-align: top; -} - -.landingText { - color: white; - horizontal-align: center; - vertical-align: center; - font-size: 24; -} - -.landingButton { - color: white; - vertical-align: bottom; -} -.statusBar { - background-color: #65ADF1; - color: white; - padding: 10; -} - -.statusText { - horizontal-align: right; -} - -.testsTotal { - font-size: 18; -} - -.testsPassed { - font-size: 18; - color: green; -} - -.testsFailed { - font-size: 18; - color: red; -} - -.testProgress { -} - -.passedTest { - background-color: green; - color: white; -} - -.passedTestTitle { - font-size: 14; -} - -.passedTestDetails { - font-size: 10; -} - -.failedTest { - background-color: red; - color: white; -} - -.failedTestTitle { - font-size: 14; -} - -.failedTestDetails { - font-size: 10; - color: black; -} - -.btn-primary { - background-color: #65ADF1; - padding: 8 5 8 5; - width: 250; - color: white; - border-radius: 6; -} \ No newline at end of file diff --git a/app/app.ts b/app/app.ts deleted file mode 100644 index 3d50b25..0000000 --- a/app/app.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Application } from "@nativescript/core"; -Application.setCssFileName("./tns_modules/@nativescript/unit-test-runner/app.css"); -Application.run({ moduleName: "./tns_modules/@nativescript/unit-test-runner/app-root" }); diff --git a/app/bundle-app-root.xml b/app/bundle-app-root.xml deleted file mode 100644 index 40ea463..0000000 --- a/app/bundle-app-root.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/app/bundle-app.ts b/app/bundle-app.ts deleted file mode 100644 index 7429ba9..0000000 --- a/app/bundle-app.ts +++ /dev/null @@ -1,13 +0,0 @@ -declare let require: any, global: any; - -import { Application } from "@nativescript/core"; -import "./app.css"; - -const context = require.context("./", true, /.*\.(js|css|xml)/); -if (typeof global.registerWebpackModules !== "undefined") { - global.registerWebpackModules(context); -} else { - global.registerBundlerModules(context); -} - -Application.run({ moduleName: "bundle-app-root" }); diff --git a/app/bundle-main-page.ts b/app/bundle-main-page.ts deleted file mode 100644 index 1df42c3..0000000 --- a/app/bundle-main-page.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { mainViewModel} from "./main-view-model"; -export function pageLoaded(args) { - var page = args.object; - page.bindingContext = mainViewModel; -} diff --git a/app/bundle-main-page.xml b/app/bundle-main-page.xml deleted file mode 100644 index b273365..0000000 --- a/app/bundle-main-page.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/main-page.ts b/app/main-page.ts deleted file mode 100644 index 1df42c3..0000000 --- a/app/main-page.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { mainViewModel} from "./main-view-model"; -export function pageLoaded(args) { - var page = args.object; - page.bindingContext = mainViewModel; -} diff --git a/app/main-page.xml b/app/main-page.xml deleted file mode 100644 index b273365..0000000 --- a/app/main-page.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/main-view-model.ts b/app/main-view-model.ts deleted file mode 100644 index 5464412..0000000 --- a/app/main-view-model.ts +++ /dev/null @@ -1,327 +0,0 @@ -/// -import { Observable, ObservableArray, Http, Device, Frame, isAndroid } from "@nativescript/core"; -import { KarmaHostResolver } from './services/karma-host-resolver'; -import { KarmaFilesService } from './services/karma-files-service'; -import { TestExecutionService } from './services/test-execution-service'; -import { killProcess } from "./stop-process"; -import { executeWebpackTests } from "./services/webpack-test-runner"; - -declare var global: any; -declare const __TEST_RUNNER_STAY_OPEN__: boolean; - -function enableSocketIoDebugging() { - console.log('enabling socket.io debugging'); - - global.localStorage = { - debug: "*" - }; - - global.window = global; -} - -var config: INetworkConfiguration = require('../config'); -config.options = config.options || {}; -// For whatever reason, Android emulators sometime struggle to find a -// reachable host ip and make a successful call for the `context.json` file. -// This happens in the test runner, even though mobile browsers can make successful calls to the -// already discovered ips. According to -// https://developer.android.com/studio/run/emulator-networking#networkaddresses -// ip addr: `10.0.2.2` is a special alias to your host loopback interface -// (i.e., 127.0.0.1 on your development machine) -// adding this ipaddr eases the struggle. (no effect on actual devices) -if (isAndroid) - config.ips = (config.ips || []).concat(['10.0.2.2']) - -if (!config.options.appDirectoryRelativePath) { - config.options.appDirectoryRelativePath = "app"; -} - -var bundle = config.options && config.options.bundle; - -export class TestBrokerViewModel extends Observable { - private startEmitted: boolean; - private executed: boolean; - private hasError: boolean; - private karmaRequestedRun: boolean; - private baseUrl: string; - private testResults: ObservableArray; - - private socket: any; //socket.io socket - private config: any; //karma config - - private karmaHostResolver: IKarmaHostResolver; - private karmaFilesService: IKarmaFilesService; - private testExecutionService: ITestExecutionService; - - constructor() { - super(); - - global.__karma__ = this; - - if (config.options.debugTransport) { - enableSocketIoDebugging(); - } - //debugger; - - this.testResults = new ObservableArray(); - this.updateView({ - testResults: this.testResults, - serverInfo: 'disconnected', - imageSrc: this.getImageSrc(), - goToTestsText: 'Run Tests', - isConnected: false, - testsPassed: '-', - testsFailed: '-', - testsRan: 0, - testsTotal: 0 - }); - - this.startEmitted = false; - - this.karmaHostResolver = new KarmaHostResolver(Http); - this.karmaFilesService = new KarmaFilesService(Http, config); - this.testExecutionService = new TestExecutionService(); - - this.karmaHostResolver.resolveKarmaHost(config.ips, config.port) - .then(hostIP => { - const serverInfo = hostIP ? `found karma at ${hostIP}` : 'no reachable hosts'; - this.updateView({ serverInfo }); - this.connectToKarma(hostIP); - }) - .catch(e => console.log(e.toString())); - } - - public viewTestRunDetails() { - this.navigateTo('run-details'); - } - - public beginLocalRun() { - this.config = this.config || { args: [] }; - this.navigateTo('test-run-page'); - } - - public onKarmaExecute(cfg) { - this.karmaRequestedRun = true; - this.config = cfg; - - this.beginLocalRun(); - } - - public executeTestRun() { - if (this.executed) { - console.log('NSUTR: disregarding second execution'); - return; - } - - this.executed = true; - this.startEmitted = false; - this.hasError = false; - - this.set('goToTestsText', 'View Test Run'); - - this.karmaFilesService.getServedFilesData(this.baseUrl) - .then((scriptsContents: IScriptInfo[]) => setTimeout(() => this.runTests(scriptsContents), 0)); - } - - public async runTests(scripts: IScriptInfo[]): Promise { - const errors = this.testExecutionService.runTests(scripts); - errors.forEach(err => this.error(err.msg, err.url, err.line)); - - try { - await executeWebpackTests(); - } catch (e) { - this.error(`${e?.message || e}`); - } - - if (!this.hasError) { - console.log('NSUTR: beginning test run'); - if (config.options.debugBrk) { - /// HINT: If you need to place breakpoints in your tests, navigate to your test files in the Sources panel. - /// Hit the 'Resume script execution' button or F8 to continue to your tests. - debugger; - } - this.start(this.config); - } - } - - public start(cfg: any) { - this.error("You need to include a test adapter for the testing framework you're using"); - } - - public info(data) { - this.emitInfoToSocketIfNeeded(data); - this.emitStartToSocketIfNeeded(data); - - if (Object.hasOwnProperty.call(data, "total")) { // data.total only appears when tests are booting up and represent the total number of tests - this.updateView({ - testsRunning: true, - testsPassed: 0, - testsFailed: 0, - testsRan: 0, - testsTotal: data.total - }); - } - } - - public result(data) { - this.emitStartToSocketIfNeeded({ total: null }); - this.emitToSocket('result', data); - - this.updateView({ - testsPassed: data.success ? this.get('testsPassed') + 1 : undefined, - testsFailed: data.success ? undefined : this.get('testsFailed') + 1, - testsRan: this.get('testsRan') + 1 - }); - - this.testResults.push(data); - } - - public complete(data?: any) { - console.log("NSUTR: completed test run."); - this.set('testsRunning', false); - - delete this.start; - - let acknowledged = false; - const ackFn = () => { - if (acknowledged) { - return; - } - acknowledged = true; - console.log('NSUTR: completeAck'); - this.emitToSocket('disconnect'); - if (typeof __TEST_RUNNER_STAY_OPEN__ === 'undefined' || !__TEST_RUNNER_STAY_OPEN__) { - setTimeout(() => killProcess(), 500); - } - }; - this.emitToSocket('complete', data || {}, ackFn); - setTimeout(ackFn, 1000); // acknowledge is no longer sent by the karma server, so we use a timeout to ensure it runs - } - - public error(msg: string, url?: string, line?: number) { - this.hasError = true; - var fullMsg = url ? msg + '\nat ' + url + (line ? ':' + line : '') : msg; - console.log("NSUTR: this.error: " + fullMsg); - this.result({ - id: url, - description: `${url} at line ${line}` || "", - log: [msg], - time: 0, - success: false, - suite: [], - }) - this.complete(); - return false; - } - - private connectToKarma(hostIP: string) { - if (!hostIP) { - return; - } - - this.baseUrl = `http://${hostIP}:${config.port}`; - console.log(`NSUTR: connecting to karma at ${this.baseUrl}`); - - // shims for engine.io-parser - global.navigator = { - userAgent: 'nativescript', - }; - global.document = { - documentElement: { - style: {} - } - }; - - this.updateView({ serverInfo: `connected to ${this.baseUrl}` }); - let io = require('../socket.io'); - const socket = this.socket = io.connect(this.baseUrl, { forceBase64: true }); - - socket.on('connect', err => { - console.log('NSUTR: successfully connected to karma'); - - delete global.navigator; - delete global.document; - - this.updateBanner('connected'); - this.updateView({ isConnected: true }); - - socket.emit('register', { - id: 'NativeScriptUnit-' + (0 | (Math.random() * 10000)), - name: `NativeScript / ${Device.sdkVersion} (${Device.osVersion}; ${Device.model})`, - }); - }); - - this.attachOnServerSocketEvents(socket); - } - - private attachOnServerSocketEvents(socket): void { - socket.on('disconnect', this.updateBanner('disconnected')); - socket.on('reconnecting', this.updateBanner('reconnecting in $ ms...')); - socket.on('reconnect_failed', this.updateBanner('failed to reconnect')); - socket.on('connect_failed', this.updateBanner('connection failed')); - socket.on('reconnect', this.updateBanner('connected')); - socket.on('connect_error', data => console.log('NSUTR: socket.io error on connect: ' + data)); - socket.on('execute', this.onKarmaExecute.bind(this)); - } - - private emitToSocket(...args: any[]) { - if (this.karmaRequestedRun) { - if (args.length > 0 && args[0] === 'disconnect') { - this.socket.disconnect(); - return; - } - this.socket.emit.apply(this.socket, arguments); - } - } - - private emitStartToSocketIfNeeded(data) { - if (!this.startEmitted) { - this.emitToSocket('start', data); - this.startEmitted = true; - } - } - - private emitInfoToSocketIfNeeded(data) { - if (this.startEmitted) { - this.emitToSocket('info', data); - } - } - - private updateView(data): void { - Object.keys(data).forEach(key => { - if (data[key] !== null && data[key] !== undefined) { - this.set(key, data[key]); - } - }); - } - - private updateBanner(message: string): (err?: any) => void { - return err => { - this.updateView({ serverInfo: message }); - if (err) { - console.log('NSUTR-socket.io: ' + err.toString()); - } - } - } - - private navigateTo(pageName: string): void { - const url = bundle ? pageName : `tns_modules/@nativescript/unit-test-runner/${pageName}`; - Frame.getFrameById('root-frame').navigate(url); - } - - private getImageSrc(): string { - let result = '~/tns_modules/@nativescript/unit-test-runner/nativescript.png'; - if (bundle) { - result = "data:image/png;base64,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"; - } - - return result; - } -} - -export var mainViewModel = new TestBrokerViewModel(); - -global.__onUncaughtError = error => { - console.log("NSUTR: uncaught error"); - mainViewModel.error(error.message); -} \ No newline at end of file diff --git a/app/main.d.ts b/app/main.d.ts deleted file mode 100644 index 29107f4..0000000 --- a/app/main.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface RunTestAppOptions { - runTests?: () => unknown -} - -export function runTestApp(options?: RunTestAppOptions); diff --git a/app/main.ts b/app/main.ts deleted file mode 100644 index ba8c74f..0000000 --- a/app/main.ts +++ /dev/null @@ -1,23 +0,0 @@ -declare let require: any, global: any; - -import { Application } from "@nativescript/core"; -import "./app.css"; -import { registerTestRunner } from "./services/webpack-test-runner"; - -const context = require.context("./", true, /.*\.(js|css|xml)/); -if (typeof global.registerWebpackModules !== "undefined") { - global.registerWebpackModules(context); -} else { - global.registerBundlerModules(context); -} - -export interface RunTestAppOptions { - runTests?: () => unknown; -} - -export function runTestApp(options: RunTestAppOptions = {}) { - if (options?.runTests) { - registerTestRunner(options.runTests); - } - Application.run({ moduleName: "bundle-app-root" }); -} diff --git a/app/nativescript.png b/app/nativescript.png deleted file mode 100644 index 344ef343b78ee0c1fb142e64bfd6e6a9ad45e98d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6709 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 - - - - - - - diff --git a/app/services/karma-files-service.ts b/app/services/karma-files-service.ts deleted file mode 100644 index df3f9ba..0000000 --- a/app/services/karma-files-service.ts +++ /dev/null @@ -1,89 +0,0 @@ -export class KarmaFilesService { - private readonly extensionRegex = /\.([^.\/]+)$/; - private readonly appPrefix = null; - private readonly testsPrefix = null; - private readonly absoluteTestsPrefix = null; - private readonly nodeModulesPrefix = null; - private readonly bundle: boolean = false; - - constructor(private http, config: IHostConfiguration) { - this.appPrefix = `/base/${config.options.appDirectoryRelativePath}/`; - this.testsPrefix = `/base/${config.options.appDirectoryRelativePath}/tests`; - this.absoluteTestsPrefix = `/absolute`; - this.nodeModulesPrefix = `/base/node_modules/`; - this.bundle = config.options.bundle; - } - - public getServedFilesData(baseUrl: string): Promise { - const contextUrl = `${baseUrl}/context.json`; - console.log("NSUTR: downloading " + contextUrl); - - return this.http.getString(contextUrl) - .then(content => { - const parsedContent: IKarmaContext = JSON.parse(content); - return parsedContent.files; - }) - .then(scriptUrls => { - return Promise.all(scriptUrls.map((url): Promise => { - const { extension, localPath, type } = this.getScriptData(url); - if (localPath) { - return Promise.resolve({ - url, - type, - localPath, - }); - } else { - return this.http.getString(baseUrl + url) - .then(contents => { - return { - url, - type, - contents, - shouldEval: !extension || extension.toLowerCase() === "js" || extension.toLowerCase() === "ts" - }; - }); - } - })); - }); - } - - private getScriptData(url: string): { extension: string, localPath: string, type: ScriptTypes } { - const queryStringStartIndex = url.lastIndexOf('?'); - const pathWithoutQueryString = url.substring(0, queryStringStartIndex); - const extension = this.extensionRegex.exec(pathWithoutQueryString)[1]; - - const type = this.getScriptType(url); - - let localPath = null; - if (!this.bundle && url.startsWith(this.appPrefix)) { - localPath = this.getScriptLocalPath(url, extension); - } - - return { extension, localPath, type }; - } - - private getScriptType(url: string): ScriptTypes { - let type = ScriptTypes.CodeUnderTestType; - - if (url.startsWith(this.testsPrefix) || url.startsWith(this.absoluteTestsPrefix)) { - type = ScriptTypes.TestType; - } else if (url.startsWith(this.nodeModulesPrefix)) { - type = ScriptTypes.FrameworkAdapterType; - } - - return type; - } - - private getScriptLocalPath(url: string, scriptExtension: string): string { - let localPath = null; - const queryStringStartIndex = url.lastIndexOf('?'); - const relativePath = url.substring(this.appPrefix.length, queryStringStartIndex); - localPath = '../../../../' + relativePath; - - if (scriptExtension === "ts") { - localPath = localPath.substring(0, localPath.length - 2) + "js"; - } - - return localPath; - } -} diff --git a/app/services/karma-host-resolver.ts b/app/services/karma-host-resolver.ts deleted file mode 100644 index 8f9f133..0000000 --- a/app/services/karma-host-resolver.ts +++ /dev/null @@ -1,36 +0,0 @@ -export class KarmaHostResolver implements IKarmaHostResolver { - constructor(private http) { } - - public resolveKarmaHost(ips: string[], port: number): Promise { - return new Promise(resolve => { - let foundKarma = false; - - const resolvers = ips.map(ip => { - const karmaClientUrl = `http://${ip}:${port}/context.json`; - console.log(`NSUTR: fetching ${karmaClientUrl}`); - - return this.http.getString({ - url: karmaClientUrl, - method: 'GET', - timeout: 3000, - }).then(() => { - console.log(`NSUTR: found karma at ${ip}`); - if (!foundKarma) { - foundKarma = true; - resolve(ip); - } - }, (err) => { - console.log(`NSUTR: error fetching ${karmaClientUrl}`, err); - return undefined; - }) - }); - - Promise.all(resolvers) - .then(() => { - if (!foundKarma) { - resolve(null); - } - }) - }); - } -} diff --git a/app/services/test-execution-service.ts b/app/services/test-execution-service.ts deleted file mode 100644 index 261372b..0000000 --- a/app/services/test-execution-service.ts +++ /dev/null @@ -1,82 +0,0 @@ -declare var global: any; - -export class TestExecutionService implements ITestExecutionService { - public runTests(scripts: IScriptInfo[]): ITestExecutionError[] { - const errors = []; - const testScripts = scripts.filter(script => script.type === ScriptTypes.FrameworkAdapterType || script.type === ScriptTypes.TestType); - testScripts - .forEach(script => { - try { - this.runTest(script); - } catch (err) { - errors.push({ - msg: err.toString(), - url: script.localPath || script.url, - line: err.lineNumber || 0 - }); - } - }); - - return errors; - } - - private runTest(script: IScriptInfo): void { - if (script.localPath) { - console.log('NSUTR: require script ' + script.url + ' from ' + script.localPath); - // Add this check in order to prevent the following warning from webpack compiler: - // WARNING in ../node_modules/@nativescript/unit-test-runner/main-view-model.js 204:28-53 - // Critical dependency: the request of a dependency is an expression - if (!global.TNS_WEBPACK) { - // @ts-ignore - require(script.localPath); - } - } else { - if (script.shouldEval) { - console.log('NSUTR: eval script ' + script.url); - this.loadShim(script.url); - //call eval indirectly to execute the scripts in the global scope - var geval = eval; - geval(script.contents); - this.completeLoading(script.url); - } else { - console.log('NSUTR: ignoring evaluation of script ' + script.url); - } - } - } - - private loadShim(url: string) { - if (url.indexOf('mocha') !== -1) { - global.window = global; - global.location = { href: '/' }; - global.document = { - getElementById: id => null - } - } else if (url.indexOf('chai') !== -1) { - global.__shim_require = global.require; - global.require = function () { - throw Error(); - } - global.window = global; - } else if (url.indexOf('qunit.js') !== -1) { - global.define = function (factory) { - global.QUnit = factory(); - } - global.define.amd = true; - } - } - - private completeLoading(url: string) { - if (url.indexOf('mocha') !== -1) { - delete global.window; - // delete global.location; - delete global.document; - } - if (url.indexOf('chai') !== -1) { - delete global.window; - global.require = global.__shim_require; - delete global.__shim_require; - } else if (url.indexOf('qunit.js') !== -1) { - delete global.define; - } - } -} diff --git a/app/services/webpack-test-runner.ts b/app/services/webpack-test-runner.ts deleted file mode 100644 index 3cfa817..0000000 --- a/app/services/webpack-test-runner.ts +++ /dev/null @@ -1,10 +0,0 @@ -let runTests: () => unknown; - -export function registerTestRunner(testRunner: () => unknown | Promise) { - runTests = testRunner; -} - - -export async function executeWebpackTests() { - await runTests?.(); -} diff --git a/app/stop-process.android.ts b/app/stop-process.android.ts deleted file mode 100644 index 8dc9fc6..0000000 --- a/app/stop-process.android.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Application } from '@nativescript/core'; - -export function killProcess() { - if(android.os.Build.VERSION.SDK_INT >= 21) { - Application.android.startActivity?.finishAndRemoveTask(); - } else { - Application.android.startActivity?.finishAffinity(); - } - java.lang.System.exit(0); -} diff --git a/app/stop-process.d.ts b/app/stop-process.d.ts deleted file mode 100644 index 8d2a3ea..0000000 --- a/app/stop-process.d.ts +++ /dev/null @@ -1 +0,0 @@ -export function killProcess(); \ No newline at end of file diff --git a/app/stop-process.ios.ts b/app/stop-process.ios.ts deleted file mode 100644 index cd60b38..0000000 --- a/app/stop-process.ios.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function killProcess() { - exit(0); -} diff --git a/app/test-run-page.ts b/app/test-run-page.ts deleted file mode 100644 index 5fdd26f..0000000 --- a/app/test-run-page.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { mainViewModel } from "./main-view-model"; - -export function pageNavigatedTo(args) { - var page = args.object; - var broker = mainViewModel; - page.bindingContext = broker; - broker.executeTestRun(); -} diff --git a/app/test-run-page.xml b/app/test-run-page.xml deleted file mode 100644 index 3694e01..0000000 --- a/app/test-run-page.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - -