From 285e9df1684d969570e7c3d6d432bd4dc5343353 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Wed, 11 Nov 2026 17:42:51 -0300 Subject: [PATCH 1/5] test: bump shared tests to common-runtime-tests-app master Picks up 2cb9c23, which keeps worker errors and failure paths version-agnostic across Jasmine releases, and 234f5f3 (case-insensitive require expectations). --- test-app/app/src/main/assets/app/shared | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-app/app/src/main/assets/app/shared b/test-app/app/src/main/assets/app/shared index aa7f8cfcc..2cb9c23ce 160000 --- a/test-app/app/src/main/assets/app/shared +++ b/test-app/app/src/main/assets/app/shared @@ -1 +1 @@ -Subproject commit aa7f8cfcc29ac2945e190fafe1259827ab6ab2e6 +Subproject commit 2cb9c23ceae51f05e37b170cbbe0bafa4788c9ce From fb27d9d65c6b7b2bbe86e6d5f49ebd582a26e0ef Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Wed, 11 Nov 2026 17:42:51 -0300 Subject: [PATCH 2/5] test: run the test app on jasmine-core 7 from npm Replaces the vendored Jasmine 2.0.1 with jasmine-core 7.0.2, a pinned root devDependency, mirroring NativeScript/ios#547. - test-app/test-runner-deps is a test-app-only local plugin listed in test-app/dependencies.json. Its include.gradle copies jasmine.js and its license from the repo's node_modules into a generated assets folder before preBuild, so runtests, :app:installDebug and Android Studio builds all get it, and none of the gradle files shipped to apps change. A missing npm install fails the build with a pointer to the fix. - New Infrastructure/Jasmine/boot.js: installs the globals, keeps declaration order (random: false), works around jasmine-core's pretty-printer throwing on null-prototype tagged objects, and exports execute(). - globalErrorSpy.js: raisesGlobalErrors() wraps specs that raise global error / unhandledrejection events on purpose. - Reporters: excluded/notApplicable specs count as skipped; suite- and run-level errors become failing JUnit testcases and fail the summary; XML escaping no longer double-escapes and survives ]]> in stacks. --- README.md | 1 + package.json | 1 + .../assets/app/Infrastructure/Jasmine/boot.js | 44 + .../Infrastructure/Jasmine/globalErrorSpy.js | 29 + .../Jasmine/jasmine-2.0.1/boot.js | 139 - .../Jasmine/jasmine-2.0.1/jasmine.js | 2521 ----------------- .../jasmine-reporters/junit_reporter.js | 87 +- .../jasmine-reporters/terminal_reporter.js | 43 +- .../app/Infrastructure/Jasmine/jasmine.d.ts | 434 --- .../app/src/main/assets/app/MyActivity.js | 2 +- test-app/app/src/main/assets/app/boot.js | 2 +- test-app/dependencies.json | 7 +- test-app/test-runner-deps/package.json | 5 + .../platforms/android/include.gradle | 42 + 14 files changed, 237 insertions(+), 3120 deletions(-) create mode 100644 test-app/app/src/main/assets/app/Infrastructure/Jasmine/boot.js create mode 100644 test-app/app/src/main/assets/app/Infrastructure/Jasmine/globalErrorSpy.js delete mode 100644 test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-2.0.1/boot.js delete mode 100644 test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-2.0.1/jasmine.js delete mode 100644 test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine.d.ts create mode 100644 test-app/test-runner-deps/package.json create mode 100644 test-app/test-runner-deps/platforms/android/include.gradle diff --git a/README.md b/README.md index e4170a6f6..395fedc3d 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ Note: You might need to run the Android Studio from the command line in order to ## How to Run Tests +* Run `npm install` in the repository root. The test app's Jasmine (`jasmine-core`) comes from there and is copied into the APK at build time. * Go to subfolder test-app after you built the runtime. * Start an emulator or connect a device. diff --git a/package.json b/package.json index 826505027..4a292a289 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "dayjs": "^1.11.7", "eslint": "^9.15.0", "globals": "^15.12.0", + "jasmine-core": "7.0.2", "semver": "^7.5.0" } } diff --git a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/boot.js b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/boot.js new file mode 100644 index 000000000..a21b4d55e --- /dev/null +++ b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/boot.js @@ -0,0 +1,44 @@ +// Boots jasmine-core for the test app. The test-runner-deps plugin +// (test-app/test-runner-deps) stages jasmine-core from the repo's node_modules +// into ./vendor at build time. +var jasmineCore = require("./vendor/jasmine-core/jasmine"); +var JUnitXmlReporter = require("./jasmine-reporters/junit_reporter").JUnitXmlReporter; +var TerminalReporter = require("./jasmine-reporters/terminal_reporter").TerminalReporter; + +jasmineCore.installGlobals(global); + +var env = jasmine.getEnv(); + +env.configure({ + // Suites carry state from one spec to the next (the shared Workers suite + // counts live runtimes across its specs), so they run in declaration order. + random: false +}); + +// jasmine-core's pretty-printer falls back to value.toString() for tagged objects +// it does not recognize, which throws for null-prototype ones such as module +// namespaces. toBe() builds its message even when it passes, so without this an +// expect(namespace).toBe(sameNamespace) throws. +beforeEach(function () { + jasmine.addCustomObjectFormatter(function (value) { + if (value !== null && typeof value === "object" && typeof value.toString !== "function") { + var tag = Object.prototype.toString.call(value); + if (tag !== "[object Object]") { + return tag; + } + } + return undefined; + }); +}); + +env.addReporter(new TerminalReporter({ + verbosity: 2 +})); +env.addReporter(new JUnitXmlReporter()); + +exports.execute = function () { + env.execute().catch(function (e) { + // No JUnit report follows; the runtests harness times out waiting for it. + console.log("Jasmine could not run the suite: " + ((e && e.stack) || e)); + }); +}; diff --git a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/globalErrorSpy.js b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/globalErrorSpy.js new file mode 100644 index 000000000..a802c5d4c --- /dev/null +++ b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/globalErrorSpy.js @@ -0,0 +1,29 @@ +// Wraps a done-style spec body that raises global `error` events or unhandled +// rejections on purpose. Jasmine fails the running spec on those unless its +// global error spy is installed, which it is for as long as the body runs; the +// spy is passed as the body's second argument. Jasmine's spy never cancels the +// event, so listeners, preventDefault() and the __onUncaughtError fallback behave +// as they do in production. +// +// The body must call done() only after every error it expects was dispatched: +// one that arrives later is charged to whatever spec is running then. +exports.raisesGlobalErrors = function (body) { + return function () { + var context = this; + return jasmine.spyOnGlobalErrorsAsync(function (globalErrorSpy) { + return new Promise(function (resolve, reject) { + function done(reason) { + if (reason === undefined) { + resolve(); + } else { + reject(reason instanceof Error ? reason : new Error(String(reason))); + } + } + done.fail = function (reason) { + done(reason === undefined ? new Error("Failed") : reason); + }; + body.call(context, done, globalErrorSpy); + }); + }); + }; +}; diff --git a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-2.0.1/boot.js b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-2.0.1/boot.js deleted file mode 100644 index 01ed0dcbc..000000000 --- a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-2.0.1/boot.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. - - If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. - - The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. - - [jasmine-gem]: http://github.com/pivotal/jasmine-gem - */ - -var jasmineRequire = require('./jasmine'); -var JUnitXmlReporter = require('../jasmine-reporters/junit_reporter').JUnitXmlReporter; -var TerminalReporter = require('../jasmine-reporters/terminal_reporter').TerminalReporter; - -(function() { - /** - * ## Require & Instantiate - * - * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. - */ - var jasmine = jasmineRequire.core(jasmineRequire); - - /** - * Create the Jasmine environment. This is used to run all specs in a project. - */ - var env = jasmine.getEnv(); - - /** - * ## The Global Interface - * - * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. - */ - var jasmineInterface = { - describe: function(description, specDefinitions) { - return env.describe(description, specDefinitions); - }, - - xdescribe: function(description, specDefinitions) { - return env.xdescribe(description, specDefinitions); - }, - - it: function(desc, func) { - return env.it(desc, func); - }, - - xit: function(desc, func) { - return env.xit(desc, func); - }, - - beforeEach: function(beforeEachFunction) { - return env.beforeEach(beforeEachFunction); - }, - - afterEach: function(afterEachFunction) { - return env.afterEach(afterEachFunction); - }, - - expect: function(actual) { - return env.expect(actual); - }, - - pending: function() { - return env.pending(); - }, - - spyOn: function(obj, methodName) { - return env.spyOn(obj, methodName); - }, - - jsApiReporter: new jasmine.JsApiReporter({ - timer: new jasmine.Timer() - }), - - execute: function() { - return env.execute(); - }, - - jasmine: jasmine - }; - - /** - * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. - */ - if (typeof window == "undefined" && typeof global == "object") { - extend(global, jasmineInterface); - } else { - extend(window, jasmineInterface); - } - - /** - * Expose the interface for adding custom equality testers. - */ - jasmine.addCustomEqualityTester = function(tester) { - env.addCustomEqualityTester(tester); - }; - - /** - * Expose the interface for adding custom expectation matchers - */ - jasmine.addMatchers = function(matchers) { - return env.addMatchers(matchers); - }; - - /** - * Expose the mock interface for the JavaScript timeout functions - */ - jasmine.clock = function() { - return env.clock; - }; - - /** - * ## Runner Parameters - */ - - env.catchExceptions(true); - - /** - * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. - */ - env.addReporter(jasmineInterface.jsApiReporter); -// - env.addReporter(new TerminalReporter({ - verbosity: 2 // Show failures and summary, but not individual passes - })); - - env.addReporter(new JUnitXmlReporter()); - - env.specFilter = function(spec) { - return true; - }; - - /** - * Helper function for readability above. - */ - function extend(destination, source) { - for (var property in source) destination[property] = source[property]; - return destination; - } -}()); diff --git a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-2.0.1/jasmine.js b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-2.0.1/jasmine.js deleted file mode 100644 index 63614709d..000000000 --- a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-2.0.1/jasmine.js +++ /dev/null @@ -1,2521 +0,0 @@ -/* -Copyright (c) 2008-2014 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -function getJasmineRequireObj() { - if (typeof module !== 'undefined' && module.exports) { - return exports; - } else { - window.jasmineRequire = window.jasmineRequire || {}; - return window.jasmineRequire; - } -} - -getJasmineRequireObj().core = function(jRequire) { - var j$ = {}; - - jRequire.base(j$); - j$.util = jRequire.util(); - j$.Any = jRequire.Any(); - j$.CallTracker = jRequire.CallTracker(); - j$.MockDate = jRequire.MockDate(); - j$.Clock = jRequire.Clock(); - j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); - j$.Env = jRequire.Env(j$); - j$.ExceptionFormatter = jRequire.ExceptionFormatter(); - j$.Expectation = jRequire.Expectation(); - j$.buildExpectationResult = jRequire.buildExpectationResult(); - j$.JsApiReporter = jRequire.JsApiReporter(); - j$.matchersUtil = jRequire.matchersUtil(j$); - j$.ObjectContaining = jRequire.ObjectContaining(j$); - j$.pp = jRequire.pp(j$); - j$.QueueRunner = jRequire.QueueRunner(j$); - j$.ReportDispatcher = jRequire.ReportDispatcher(); - j$.Spec = jRequire.Spec(j$); - j$.SpyStrategy = jRequire.SpyStrategy(); - j$.Suite = jRequire.Suite(); - j$.Timer = jRequire.Timer(); - j$.version = jRequire.version(); - - j$.matchers = jRequire.requireMatchers(jRequire, j$); - - return j$; -}; - -getJasmineRequireObj().requireMatchers = function(jRequire, j$) { - var availableMatchers = [ - 'toBe', - 'toBeCloseTo', - 'toBeDefined', - 'toBeFalsy', - 'toBeGreaterThan', - 'toBeLessThan', - 'toBeNaN', - 'toBeNull', - 'toBeTruthy', - 'toBeUndefined', - 'toContain', - 'toEqual', - 'toHaveBeenCalled', - 'toHaveBeenCalledWith', - 'toMatch', - 'toThrow', - 'toThrowError' - ], - matchers = {}; - - for (var i = 0; i < availableMatchers.length; i++) { - var name = availableMatchers[i]; - matchers[name] = jRequire[name](j$); - } - - return matchers; -}; - -getJasmineRequireObj().base = (function (jasmineGlobal) { - if (typeof module !== 'undefined' && module.exports) { - jasmineGlobal = global; - } - - return function(j$) { - j$.unimplementedMethod_ = function() { - throw new Error('unimplemented method'); - }; - - j$.MAX_PRETTY_PRINT_DEPTH = 40; - j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; - j$.DEFAULT_TIMEOUT_INTERVAL = 5000; - - j$.getGlobal = function() { - return jasmineGlobal; - }; - - j$.getEnv = function(options) { - var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); - //jasmine. singletons in here (setTimeout blah blah). - return env; - }; - - j$.isArray_ = function(value) { - return j$.isA_('Array', value); - }; - - j$.isString_ = function(value) { - return j$.isA_('String', value); - }; - - j$.isNumber_ = function(value) { - return j$.isA_('Number', value); - }; - - j$.isA_ = function(typeName, value) { - return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; - }; - - j$.isDomNode = function(obj) { - return obj.nodeType > 0; - }; - - j$.any = function(clazz) { - return new j$.Any(clazz); - }; - - j$.objectContaining = function(sample) { - return new j$.ObjectContaining(sample); - }; - - j$.createSpy = function(name, originalFn) { - - var spyStrategy = new j$.SpyStrategy({ - name: name, - fn: originalFn, - getSpy: function() { return spy; } - }), - callTracker = new j$.CallTracker(), - spy = function() { - callTracker.track({ - object: this, - args: Array.prototype.slice.apply(arguments) - }); - return spyStrategy.exec.apply(this, arguments); - }; - - for (var prop in originalFn) { - if (prop === 'and' || prop === 'calls') { - throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon'); - } - - spy[prop] = originalFn[prop]; - } - - spy.and = spyStrategy; - spy.calls = callTracker; - - return spy; - }; - - j$.isSpy = function(putativeSpy) { - if (!putativeSpy) { - return false; - } - return putativeSpy.and instanceof j$.SpyStrategy && - putativeSpy.calls instanceof j$.CallTracker; - }; - - j$.createSpyObj = function(baseName, methodNames) { - if (!j$.isArray_(methodNames) || methodNames.length === 0) { - throw 'createSpyObj requires a non-empty array of method names to create spies for'; - } - var obj = {}; - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); - } - return obj; - }; - }; -})(this); - -getJasmineRequireObj().util = function() { - - var util = {}; - - util.inherit = function(childClass, parentClass) { - var Subclass = function() { - }; - Subclass.prototype = parentClass.prototype; - childClass.prototype = new Subclass(); - }; - - util.htmlEscape = function(str) { - if (!str) { - return str; - } - return str.replace(/&/g, '&') - .replace(//g, '>'); - }; - - util.argsToArray = function(args) { - var arrayOfArgs = []; - for (var i = 0; i < args.length; i++) { - arrayOfArgs.push(args[i]); - } - return arrayOfArgs; - }; - - util.isUndefined = function(obj) { - return obj === void 0; - }; - - util.arrayContains = function(array, search) { - var i = array.length; - while (i--) { - if (array[i] == search) { - return true; - } - } - return false; - }; - - return util; -}; - -getJasmineRequireObj().Spec = function(j$) { - function Spec(attrs) { - this.expectationFactory = attrs.expectationFactory; - this.resultCallback = attrs.resultCallback || function() {}; - this.id = attrs.id; - this.description = attrs.description || ''; - this.fn = attrs.fn; - this.beforeFns = attrs.beforeFns || function() { return []; }; - this.afterFns = attrs.afterFns || function() { return []; }; - this.onStart = attrs.onStart || function() {}; - this.exceptionFormatter = attrs.exceptionFormatter || function() {}; - this.getSpecName = attrs.getSpecName || function() { return ''; }; - this.expectationResultFactory = attrs.expectationResultFactory || function() { }; - this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; - this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; - - if (!this.fn) { - this.pend(); - } - - this.result = { - id: this.id, - description: this.description, - fullName: this.getFullName(), - failedExpectations: [], - passedExpectations: [] - }; - } - - Spec.prototype.addExpectationResult = function(passed, data) { - var expectationResult = this.expectationResultFactory(data); - if (passed) { - this.result.passedExpectations.push(expectationResult); - } else { - this.result.failedExpectations.push(expectationResult); - } - }; - - Spec.prototype.expect = function(actual) { - return this.expectationFactory(actual, this); - }; - - Spec.prototype.execute = function(onComplete) { - var self = this; - - this.onStart(this); - - if (this.markedPending || this.disabled) { - complete(); - return; - } - - var allFns = this.beforeFns().concat(this.fn).concat(this.afterFns()); - - this.queueRunnerFactory({ - fns: allFns, - onException: onException, - onComplete: complete, - enforceTimeout: function() { return true; } - }); - - function onException(e) { - if (Spec.isPendingSpecException(e)) { - self.pend(); - return; - } - - self.addExpectationResult(false, { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: e - }); - } - - function complete() { - self.result.status = self.status(); - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } - }; - - Spec.prototype.disable = function() { - this.disabled = true; - }; - - Spec.prototype.pend = function() { - this.markedPending = true; - }; - - Spec.prototype.status = function() { - if (this.disabled) { - return 'disabled'; - } - - if (this.markedPending) { - return 'pending'; - } - - if (this.result.failedExpectations.length > 0) { - return 'failed'; - } else { - return 'passed'; - } - }; - - Spec.prototype.getFullName = function() { - return this.getSpecName(this); - }; - - Spec.pendingSpecExceptionMessage = '=> marked Pending'; - - Spec.isPendingSpecException = function(e) { - return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); - }; - - return Spec; -}; - -if (typeof window == void 0 && typeof exports == 'object') { - exports.Spec = jasmineRequire.Spec; -} - -getJasmineRequireObj().Env = function(j$) { - function Env(options) { - options = options || {}; - - var self = this; - var global = options.global || j$.getGlobal(); - - var totalSpecsDefined = 0; - - var catchExceptions = true; - - var realSetTimeout = j$.getGlobal().setTimeout; - var realClearTimeout = j$.getGlobal().clearTimeout; - - this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler(), new j$.MockDate(global)); - - var runnableLookupTable = {}; - - var spies = []; - - var currentSpec = null; - var currentSuite = null; - - var reporter = new j$.ReportDispatcher([ - 'jasmineStarted', - 'jasmineDone', - 'suiteStarted', - 'suiteDone', - 'specStarted', - 'specDone' - ]); - - this.specFilter = function() { - return true; - }; - - var equalityTesters = []; - - var customEqualityTesters = []; - this.addCustomEqualityTester = function(tester) { - customEqualityTesters.push(tester); - }; - - j$.Expectation.addCoreMatchers(j$.matchers); - - var nextSpecId = 0; - var getNextSpecId = function() { - return 'spec' + nextSpecId++; - }; - - var nextSuiteId = 0; - var getNextSuiteId = function() { - return 'suite' + nextSuiteId++; - }; - - var expectationFactory = function(actual, spec) { - return j$.Expectation.Factory({ - util: j$.matchersUtil, - customEqualityTesters: customEqualityTesters, - actual: actual, - addExpectationResult: addExpectationResult - }); - - function addExpectationResult(passed, result) { - return spec.addExpectationResult(passed, result); - } - }; - - var specStarted = function(spec) { - currentSpec = spec; - reporter.specStarted(spec.result); - }; - - var beforeFns = function(suite) { - return function() { - var befores = []; - while(suite) { - befores = befores.concat(suite.beforeFns); - suite = suite.parentSuite; - } - return befores.reverse(); - }; - }; - - var afterFns = function(suite) { - return function() { - var afters = []; - while(suite) { - afters = afters.concat(suite.afterFns); - suite = suite.parentSuite; - } - return afters; - }; - }; - - var getSpecName = function(spec, suite) { - return suite.getFullName() + ' ' + spec.description; - }; - - // TODO: we may just be able to pass in the fn instead of wrapping here - var buildExpectationResult = j$.buildExpectationResult, - exceptionFormatter = new j$.ExceptionFormatter(), - expectationResultFactory = function(attrs) { - attrs.messageFormatter = exceptionFormatter.message; - attrs.stackFormatter = exceptionFormatter.stack; - - return buildExpectationResult(attrs); - }; - - // TODO: fix this naming, and here's where the value comes in - this.catchExceptions = function(value) { - catchExceptions = !!value; - return catchExceptions; - }; - - this.catchingExceptions = function() { - return catchExceptions; - }; - - /* - set maximumSpecCallbackDepth to a large number in order to execute tests in very large batches, - e.g. it looks like tests are executed synchronously; this guarantees that test order is preserved. - */ - var maximumSpecCallbackDepth = 20000; - var currentSpecCallbackDepth = 0; - - function clearStack(fn) { - currentSpecCallbackDepth++; - if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { - currentSpecCallbackDepth = 0; - realSetTimeout(fn, 0); - } else { - fn(); - } - } - - var catchException = function(e) { - return j$.Spec.isPendingSpecException(e) || catchExceptions; - }; - - var queueRunnerFactory = function(options) { - options.catchException = catchException; - options.clearStack = options.clearStack || clearStack; - options.timer = {setTimeout: setTimeout, clearTimeout: clearTimeout}; - - new j$.QueueRunner(options).execute(); - }; - - var topSuite = new j$.Suite({ - env: this, - id: getNextSuiteId(), - description: 'Jasmine__TopLevel__Suite', - queueRunner: queueRunnerFactory, - resultCallback: function() {} // TODO - hook this up - }); - runnableLookupTable[topSuite.id] = topSuite; - currentSuite = topSuite; - - this.topSuite = function() { - return topSuite; - }; - - this.execute = function(runnablesToRun) { - runnablesToRun = runnablesToRun || [topSuite.id]; - - var allFns = []; - for(var i = 0; i < runnablesToRun.length; i++) { - var runnable = runnableLookupTable[runnablesToRun[i]]; - allFns.push((function(runnable) { return function(done) { runnable.execute(done); }; })(runnable)); - } - - reporter.jasmineStarted({ - totalSpecsDefined: totalSpecsDefined - }); - - queueRunnerFactory({fns: allFns, onComplete: reporter.jasmineDone}); - }; - - this.addReporter = function(reporterToAdd) { - reporter.addReporter(reporterToAdd); - }; - - this.addMatchers = function(matchersToAdd) { - j$.Expectation.addMatchers(matchersToAdd); - }; - - this.spyOn = function(obj, methodName) { - if (j$.util.isUndefined(obj)) { - throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); - } - - if (j$.util.isUndefined(obj[methodName])) { - throw new Error(methodName + '() method does not exist'); - } - - if (obj[methodName] && j$.isSpy(obj[methodName])) { - //TODO?: should this return the current spy? Downside: may cause user confusion about spy state - throw new Error(methodName + ' has already been spied upon'); - } - - var spy = j$.createSpy(methodName, obj[methodName]); - - spies.push({ - spy: spy, - baseObj: obj, - methodName: methodName, - originalValue: obj[methodName] - }); - - obj[methodName] = spy; - - return spy; - }; - - var suiteFactory = function(description) { - var suite = new j$.Suite({ - env: self, - id: getNextSuiteId(), - description: description, - parentSuite: currentSuite, - queueRunner: queueRunnerFactory, - onStart: suiteStarted, - resultCallback: function(attrs) { - reporter.suiteDone(attrs); - } - }); - - runnableLookupTable[suite.id] = suite; - return suite; - }; - - this.describe = function(description, specDefinitions) { - var suite = suiteFactory(description); - - var parentSuite = currentSuite; - parentSuite.addChild(suite); - currentSuite = suite; - - var declarationError = null; - try { - specDefinitions.call(suite); - } catch (e) { - declarationError = e; - } - - if (declarationError) { - this.it('encountered a declaration exception', function() { - throw declarationError; - }); - } - - currentSuite = parentSuite; - - return suite; - }; - - this.xdescribe = function(description, specDefinitions) { - var suite = this.describe(description, specDefinitions); - suite.disable(); - return suite; - }; - - var specFactory = function(description, fn, suite) { - totalSpecsDefined++; - - var spec = new j$.Spec({ - id: getNextSpecId(), - beforeFns: beforeFns(suite), - afterFns: afterFns(suite), - expectationFactory: expectationFactory, - exceptionFormatter: exceptionFormatter, - resultCallback: specResultCallback, - getSpecName: function(spec) { - return getSpecName(spec, suite); - }, - onStart: specStarted, - description: description, - expectationResultFactory: expectationResultFactory, - queueRunnerFactory: queueRunnerFactory, - fn: fn - }); - - runnableLookupTable[spec.id] = spec; - - if (!self.specFilter(spec)) { - spec.disable(); - } - - return spec; - - function removeAllSpies() { - for (var i = 0; i < spies.length; i++) { - var spyEntry = spies[i]; - spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; - } - spies = []; - } - - function specResultCallback(result) { - removeAllSpies(); - j$.Expectation.resetMatchers(); - customEqualityTesters = []; - currentSpec = null; - reporter.specDone(result); - } - }; - - var suiteStarted = function(suite) { - reporter.suiteStarted(suite.result); - }; - - this.it = function(description, fn) { - var spec = specFactory(description, fn, currentSuite); - currentSuite.addChild(spec); - return spec; - }; - - this.xit = function(description, fn) { - var spec = this.it(description, fn); - spec.pend(); - return spec; - }; - - this.expect = function(actual) { - if (!currentSpec) { - throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); - } - - return currentSpec.expect(actual); - }; - - this.beforeEach = function(beforeEachFunction) { - currentSuite.beforeEach(beforeEachFunction); - }; - - this.afterEach = function(afterEachFunction) { - currentSuite.afterEach(afterEachFunction); - }; - - this.pending = function() { - throw j$.Spec.pendingSpecExceptionMessage; - }; - } - - return Env; -}; - -getJasmineRequireObj().JsApiReporter = function() { - - var noopTimer = { - start: function(){}, - elapsed: function(){ return 0; } - }; - - function JsApiReporter(options) { - var timer = options.timer || noopTimer, - status = 'loaded'; - - this.started = false; - this.finished = false; - - this.jasmineStarted = function() { - this.started = true; - status = 'started'; - timer.start(); - }; - - var executionTime; - - this.jasmineDone = function() { - this.finished = true; - executionTime = timer.elapsed(); - status = 'done'; - }; - - this.status = function() { - return status; - }; - - var suites = {}; - - this.suiteStarted = function(result) { - storeSuite(result); - }; - - this.suiteDone = function(result) { - storeSuite(result); - }; - - function storeSuite(result) { - suites[result.id] = result; - } - - this.suites = function() { - return suites; - }; - - var specs = []; - this.specStarted = function(result) { }; - - this.specDone = function(result) { - specs.push(result); - }; - - this.specResults = function(index, length) { - return specs.slice(index, index + length); - }; - - this.specs = function() { - return specs; - }; - - this.executionTime = function() { - return executionTime; - }; - - } - - return JsApiReporter; -}; - -getJasmineRequireObj().Any = function() { - - function Any(expectedObject) { - this.expectedObject = expectedObject; - } - - Any.prototype.jasmineMatches = function(other) { - if (this.expectedObject == String) { - return typeof other == 'string' || other instanceof String; - } - - if (this.expectedObject == Number) { - return typeof other == 'number' || other instanceof Number; - } - - if (this.expectedObject == Function) { - return typeof other == 'function' || other instanceof Function; - } - - if (this.expectedObject == Object) { - return typeof other == 'object'; - } - - if (this.expectedObject == Boolean) { - return typeof other == 'boolean'; - } - - return other instanceof this.expectedObject; - }; - - Any.prototype.jasmineToString = function() { - return ''; - }; - - return Any; -}; - -getJasmineRequireObj().CallTracker = function() { - - function CallTracker() { - var calls = []; - - this.track = function(context) { - calls.push(context); - }; - - this.any = function() { - return !!calls.length; - }; - - this.count = function() { - return calls.length; - }; - - this.argsFor = function(index) { - var call = calls[index]; - return call ? call.args : []; - }; - - this.all = function() { - return calls; - }; - - this.allArgs = function() { - var callArgs = []; - for(var i = 0; i < calls.length; i++){ - callArgs.push(calls[i].args); - } - - return callArgs; - }; - - this.first = function() { - return calls[0]; - }; - - this.mostRecent = function() { - return calls[calls.length - 1]; - }; - - this.reset = function() { - calls = []; - }; - } - - return CallTracker; -}; - -getJasmineRequireObj().Clock = function() { - function Clock(global, delayedFunctionScheduler, mockDate) { - var self = this, - realTimingFunctions = { - setTimeout: global.setTimeout, - clearTimeout: global.clearTimeout, - setInterval: global.setInterval, - clearInterval: global.clearInterval - }, - fakeTimingFunctions = { - setTimeout: setTimeout, - clearTimeout: clearTimeout, - setInterval: setInterval, - clearInterval: clearInterval - }, - installed = false, - timer; - - - self.install = function() { - replace(global, fakeTimingFunctions); - timer = fakeTimingFunctions; - installed = true; - - return self; - }; - - self.uninstall = function() { - delayedFunctionScheduler.reset(); - mockDate.uninstall(); - replace(global, realTimingFunctions); - - timer = realTimingFunctions; - installed = false; - }; - - self.mockDate = function(initialDate) { - mockDate.install(initialDate); - }; - - self.setTimeout = function(fn, delay, params) { - if (legacyIE()) { - if (arguments.length > 2) { - throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); - } - return timer.setTimeout(fn, delay); - } - return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); - }; - - self.setInterval = function(fn, delay, params) { - if (legacyIE()) { - if (arguments.length > 2) { - throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); - } - return timer.setInterval(fn, delay); - } - return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); - }; - - self.clearTimeout = function(id) { - return Function.prototype.call.apply(timer.clearTimeout, [global, id]); - }; - - self.clearInterval = function(id) { - return Function.prototype.call.apply(timer.clearInterval, [global, id]); - }; - - self.tick = function(millis) { - if (installed) { - mockDate.tick(millis); - delayedFunctionScheduler.tick(millis); - } else { - throw new Error('Mock clock is not installed, use jasmine.clock().install()'); - } - }; - - return self; - - function legacyIE() { - //if these methods are polyfilled, apply will be present - return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; - } - - function replace(dest, source) { - for (var prop in source) { - dest[prop] = source[prop]; - } - } - - function setTimeout(fn, delay) { - return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); - } - - function clearTimeout(id) { - return delayedFunctionScheduler.removeFunctionWithId(id); - } - - function setInterval(fn, interval) { - return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); - } - - function clearInterval(id) { - return delayedFunctionScheduler.removeFunctionWithId(id); - } - - function argSlice(argsObj, n) { - return Array.prototype.slice.call(argsObj, n); - } - } - - return Clock; -}; - -getJasmineRequireObj().DelayedFunctionScheduler = function() { - function DelayedFunctionScheduler() { - var self = this; - var scheduledLookup = []; - var scheduledFunctions = {}; - var currentTime = 0; - var delayedFnCount = 0; - - self.tick = function(millis) { - millis = millis || 0; - var endTime = currentTime + millis; - - runScheduledFunctions(endTime); - currentTime = endTime; - }; - - self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { - var f; - if (typeof(funcToCall) === 'string') { - /* jshint evil: true */ - f = function() { return eval(funcToCall); }; - /* jshint evil: false */ - } else { - f = funcToCall; - } - - millis = millis || 0; - timeoutKey = timeoutKey || ++delayedFnCount; - runAtMillis = runAtMillis || (currentTime + millis); - - var funcToSchedule = { - runAtMillis: runAtMillis, - funcToCall: f, - recurring: recurring, - params: params, - timeoutKey: timeoutKey, - millis: millis - }; - - if (runAtMillis in scheduledFunctions) { - scheduledFunctions[runAtMillis].push(funcToSchedule); - } else { - scheduledFunctions[runAtMillis] = [funcToSchedule]; - scheduledLookup.push(runAtMillis); - scheduledLookup.sort(function (a, b) { - return a - b; - }); - } - - return timeoutKey; - }; - - self.removeFunctionWithId = function(timeoutKey) { - for (var runAtMillis in scheduledFunctions) { - var funcs = scheduledFunctions[runAtMillis]; - var i = indexOfFirstToPass(funcs, function (func) { - return func.timeoutKey === timeoutKey; - }); - - if (i > -1) { - if (funcs.length === 1) { - delete scheduledFunctions[runAtMillis]; - deleteFromLookup(runAtMillis); - } else { - funcs.splice(i, 1); - } - - // intervals get rescheduled when executed, so there's never more - // than a single scheduled function with a given timeoutKey - break; - } - } - }; - - self.reset = function() { - currentTime = 0; - scheduledLookup = []; - scheduledFunctions = {}; - delayedFnCount = 0; - }; - - return self; - - function indexOfFirstToPass(array, testFn) { - var index = -1; - - for (var i = 0; i < array.length; ++i) { - if (testFn(array[i])) { - index = i; - break; - } - } - - return index; - } - - function deleteFromLookup(key) { - var value = Number(key); - var i = indexOfFirstToPass(scheduledLookup, function (millis) { - return millis === value; - }); - - if (i > -1) { - scheduledLookup.splice(i, 1); - } - } - - function reschedule(scheduledFn) { - self.scheduleFunction(scheduledFn.funcToCall, - scheduledFn.millis, - scheduledFn.params, - true, - scheduledFn.timeoutKey, - scheduledFn.runAtMillis + scheduledFn.millis); - } - - function runScheduledFunctions(endTime) { - if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { - return; - } - - do { - currentTime = scheduledLookup.shift(); - - var funcsToRun = scheduledFunctions[currentTime]; - delete scheduledFunctions[currentTime]; - - for (var i = 0; i < funcsToRun.length; ++i) { - var funcToRun = funcsToRun[i]; - funcToRun.funcToCall.apply(null, funcToRun.params || []); - - if (funcToRun.recurring) { - reschedule(funcToRun); - } - } - } while (scheduledLookup.length > 0 && - // checking first if we're out of time prevents setTimeout(0) - // scheduled in a funcToRun from forcing an extra iteration - currentTime !== endTime && - scheduledLookup[0] <= endTime); - } - } - - return DelayedFunctionScheduler; -}; - -getJasmineRequireObj().ExceptionFormatter = function() { - function ExceptionFormatter() { - this.message = function(error) { - var message = ''; - - if (error.name && error.message) { - message += error.name + ': ' + error.message; - } else { - message += error.toString() + ' thrown'; - } - - if (error.fileName || error.sourceURL) { - message += ' in ' + (error.fileName || error.sourceURL); - } - - if (error.line || error.lineNumber) { - message += ' (line ' + (error.line || error.lineNumber) + ')'; - } - - return message; - }; - - this.stack = function(error) { - return error ? error.stack : null; - }; - } - - return ExceptionFormatter; -}; - -getJasmineRequireObj().Expectation = function() { - - var matchers = {}; - - function Expectation(options) { - this.util = options.util || { buildFailureMessage: function() {} }; - this.customEqualityTesters = options.customEqualityTesters || []; - this.actual = options.actual; - this.addExpectationResult = options.addExpectationResult || function(){}; - this.isNot = options.isNot; - - for (var matcherName in matchers) { - this[matcherName] = matchers[matcherName]; - } - } - - Expectation.prototype.wrapCompare = function(name, matcherFactory) { - return function() { - var args = Array.prototype.slice.call(arguments, 0), - expected = args.slice(0), - message = ''; - - args.unshift(this.actual); - - var matcher = matcherFactory(this.util, this.customEqualityTesters), - matcherCompare = matcher.compare; - - function defaultNegativeCompare() { - var result = matcher.compare.apply(null, args); - result.pass = !result.pass; - return result; - } - - if (this.isNot) { - matcherCompare = matcher.negativeCompare || defaultNegativeCompare; - } - - var result = matcherCompare.apply(null, args); - - if (!result.pass) { - if (!result.message) { - args.unshift(this.isNot); - args.unshift(name); - message = this.util.buildFailureMessage.apply(null, args); - } else { - if (Object.prototype.toString.apply(result.message) === '[object Function]') { - message = result.message(); - } else { - message = result.message; - } - } - } - - if (expected.length == 1) { - expected = expected[0]; - } - - // TODO: how many of these params are needed? - this.addExpectationResult( - result.pass, - { - matcherName: name, - passed: result.pass, - message: message, - actual: this.actual, - expected: expected // TODO: this may need to be arrayified/sliced - } - ); - }; - }; - - Expectation.addCoreMatchers = function(matchers) { - var prototype = Expectation.prototype; - for (var matcherName in matchers) { - var matcher = matchers[matcherName]; - prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); - } - }; - - Expectation.addMatchers = function(matchersToAdd) { - for (var name in matchersToAdd) { - var matcher = matchersToAdd[name]; - matchers[name] = Expectation.prototype.wrapCompare(name, matcher); - } - }; - - Expectation.resetMatchers = function() { - for (var name in matchers) { - delete matchers[name]; - } - }; - - Expectation.Factory = function(options) { - options = options || {}; - - var expect = new Expectation(options); - - // TODO: this would be nice as its own Object - NegativeExpectation - // TODO: copy instead of mutate options - options.isNot = true; - expect.not = new Expectation(options); - - return expect; - }; - - return Expectation; -}; - -//TODO: expectation result may make more sense as a presentation of an expectation. -getJasmineRequireObj().buildExpectationResult = function() { - function buildExpectationResult(options) { - var messageFormatter = options.messageFormatter || function() {}, - stackFormatter = options.stackFormatter || function() {}; - - return { - matcherName: options.matcherName, - expected: options.expected, - actual: options.actual, - message: message(), - stack: stack(), - passed: options.passed - }; - - function message() { - if (options.passed) { - return 'Passed.'; - } else if (options.message) { - return options.message; - } else if (options.error) { - return messageFormatter(options.error); - } - return ''; - } - - function stack() { - if (options.passed) { - return ''; - } - - var error = options.error; - if (!error) { - try { - throw new Error(message()); - } catch (e) { - error = e; - } - } - return stackFormatter(error); - } - } - - return buildExpectationResult; -}; - -getJasmineRequireObj().MockDate = function() { - function MockDate(global) { - var self = this; - var currentTime = 0; - - if (!global || !global.Date) { - self.install = function() {}; - self.tick = function() {}; - self.uninstall = function() {}; - return self; - } - - var GlobalDate = global.Date; - - self.install = function(mockDate) { - if (mockDate instanceof GlobalDate) { - currentTime = mockDate.getTime(); - } else { - currentTime = new GlobalDate().getTime(); - } - - global.Date = FakeDate; - }; - - self.tick = function(millis) { - millis = millis || 0; - currentTime = currentTime + millis; - }; - - self.uninstall = function() { - currentTime = 0; - global.Date = GlobalDate; - }; - - createDateProperties(); - - return self; - - function FakeDate() { - if (arguments.length === 0) { - return new GlobalDate(currentTime); - } else { - return new GlobalDate(arguments[0], arguments[1], arguments[2], - arguments[3], arguments[4], arguments[5], arguments[6]); - } - } - - function createDateProperties() { - - FakeDate.now = function() { - if (GlobalDate.now) { - return currentTime; - } else { - throw new Error('Browser does not support Date.now()'); - } - }; - - FakeDate.toSource = GlobalDate.toSource; - FakeDate.toString = GlobalDate.toString; - FakeDate.parse = GlobalDate.parse; - FakeDate.UTC = GlobalDate.UTC; - } - } - - return MockDate; -}; - -getJasmineRequireObj().ObjectContaining = function(j$) { - - function ObjectContaining(sample) { - this.sample = sample; - } - - ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { - if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } - - mismatchKeys = mismatchKeys || []; - mismatchValues = mismatchValues || []; - - var hasKey = function(obj, keyName) { - return obj !== null && !j$.util.isUndefined(obj[keyName]); - }; - - for (var property in this.sample) { - if (!hasKey(other, property) && hasKey(this.sample, property)) { - mismatchKeys.push('expected has key \'' + property + '\', but missing from actual.'); - } - else if (!j$.matchersUtil.equals(other[property], this.sample[property])) { - mismatchValues.push('\'' + property + '\' was \'' + (other[property] ? j$.util.htmlEscape(other[property].toString()) : other[property]) + '\' in actual, but was \'' + (this.sample[property] ? j$.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + '\' in expected.'); - } - } - - return (mismatchKeys.length === 0 && mismatchValues.length === 0); - }; - - ObjectContaining.prototype.jasmineToString = function() { - return ''; - }; - - return ObjectContaining; -}; - -getJasmineRequireObj().pp = function(j$) { - - function PrettyPrinter() { - this.ppNestLevel_ = 0; - this.seen = []; - } - - PrettyPrinter.prototype.format = function(value) { - this.ppNestLevel_++; - try { - if (j$.util.isUndefined(value)) { - this.emitScalar('undefined'); - } else if (value === null) { - this.emitScalar('null'); - } else if (value === 0 && 1/value === -Infinity) { - this.emitScalar('-0'); - } else if (value === j$.getGlobal()) { - this.emitScalar(''); - } else if (value.jasmineToString) { - this.emitScalar(value.jasmineToString()); - } else if (typeof value === 'string') { - this.emitString(value); - } else if (j$.isSpy(value)) { - this.emitScalar('spy on ' + value.and.identity()); - } else if (value instanceof RegExp) { - this.emitScalar(value.toString()); - } else if (typeof value === 'function') { - this.emitScalar('Function'); - } else if (typeof value.nodeType === 'number') { - this.emitScalar('HTMLNode'); - } else if (value instanceof Date) { - this.emitScalar('Date(' + value + ')'); - } else if (j$.util.arrayContains(this.seen, value)) { - this.emitScalar(''); - } else if (j$.isArray_(value) || j$.isA_('Object', value)) { - this.seen.push(value); - if (j$.isArray_(value)) { - this.emitArray(value); - } else { - this.emitObject(value); - } - this.seen.pop(); - } else { - this.emitScalar(value.toString()); - } - } finally { - this.ppNestLevel_--; - } - }; - - PrettyPrinter.prototype.iterateObject = function(obj, fn) { - for (var property in obj) { - if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } - fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && - obj.__lookupGetter__(property) !== null) : false); - } - }; - - PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; - - function StringPrettyPrinter() { - PrettyPrinter.call(this); - - this.string = ''; - } - - j$.util.inherit(StringPrettyPrinter, PrettyPrinter); - - StringPrettyPrinter.prototype.emitScalar = function(value) { - this.append(value); - }; - - StringPrettyPrinter.prototype.emitString = function(value) { - this.append('\'' + value + '\''); - }; - - StringPrettyPrinter.prototype.emitArray = function(array) { - if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { - this.append('Array'); - return; - } - var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); - this.append('[ '); - for (var i = 0; i < length; i++) { - if (i > 0) { - this.append(', '); - } - this.format(array[i]); - } - if(array.length > length){ - this.append(', ...'); - } - this.append(' ]'); - }; - - StringPrettyPrinter.prototype.emitObject = function(obj) { - if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { - this.append('Object'); - return; - } - - var self = this; - this.append('{ '); - var first = true; - - this.iterateObject(obj, function(property, isGetter) { - if (first) { - first = false; - } else { - self.append(', '); - } - - self.append(property); - self.append(': '); - if (isGetter) { - self.append(''); - } else { - self.format(obj[property]); - } - }); - - this.append(' }'); - }; - - StringPrettyPrinter.prototype.append = function(value) { - this.string += value; - }; - - return function(value) { - var stringPrettyPrinter = new StringPrettyPrinter(); - stringPrettyPrinter.format(value); - return stringPrettyPrinter.string; - }; -}; - -getJasmineRequireObj().QueueRunner = function(j$) { - - function once(fn) { - var called = false; - return function() { - if (!called) { - called = true; - fn(); - } - }; - } - - function QueueRunner(attrs) { - this.fns = attrs.fns || []; - this.onComplete = attrs.onComplete || function() {}; - this.clearStack = attrs.clearStack || function(fn) {fn();}; - this.onException = attrs.onException || function() {}; - this.catchException = attrs.catchException || function() { return true; }; - this.enforceTimeout = attrs.enforceTimeout || function() { return false; }; - this.userContext = {}; - this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; - } - - QueueRunner.prototype.execute = function() { - this.run(this.fns, 0); - }; - - QueueRunner.prototype.run = function(fns, recursiveIndex) { - var length = fns.length, - self = this, - iterativeIndex; - - for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { - var fn = fns[iterativeIndex]; - if (fn.length > 0) { - return attemptAsync(fn); - } else { - attemptSync(fn); - } - } - - var runnerDone = iterativeIndex >= length; - - if (runnerDone) { - this.clearStack(this.onComplete); - } - - function attemptSync(fn) { - try { - fn.call(self.userContext); - } catch (e) { - handleException(e); - } - } - - function attemptAsync(fn) { - var clearTimeout = function () { - Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]); - }, - next = once(function () { - clearTimeout(timeoutId); - self.run(fns, iterativeIndex + 1); - }), - timeoutId; - - if (self.enforceTimeout()) { - timeoutId = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() { - self.onException(new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.')); - next(); - }, j$.DEFAULT_TIMEOUT_INTERVAL]]); - } - - try { - fn.call(self.userContext, next); - } catch (e) { - handleException(e); - next(); - } - } - - function handleException(e) { - self.onException(e); - if (!self.catchException(e)) { - //TODO: set a var when we catch an exception and - //use a finally block to close the loop in a nice way.. - throw e; - } - } - }; - - return QueueRunner; -}; - -getJasmineRequireObj().ReportDispatcher = function() { - function ReportDispatcher(methods) { - - var dispatchedMethods = methods || []; - - for (var i = 0; i < dispatchedMethods.length; i++) { - var method = dispatchedMethods[i]; - this[method] = (function(m) { - return function() { - dispatch(m, arguments); - }; - }(method)); - } - - var reporters = []; - - this.addReporter = function(reporter) { - reporters.push(reporter); - }; - - return this; - - function dispatch(method, args) { - for (var i = 0; i < reporters.length; i++) { - var reporter = reporters[i]; - if (reporter[method]) { - reporter[method].apply(reporter, args); - } - } - } - } - - return ReportDispatcher; -}; - - -getJasmineRequireObj().SpyStrategy = function() { - - function SpyStrategy(options) { - options = options || {}; - - var identity = options.name || 'unknown', - originalFn = options.fn || function() {}, - getSpy = options.getSpy || function() {}, - plan = function() {}; - - this.identity = function() { - return identity; - }; - - this.exec = function() { - return plan.apply(this, arguments); - }; - - this.callThrough = function() { - plan = originalFn; - return getSpy(); - }; - - this.returnValue = function(value) { - plan = function() { - return value; - }; - return getSpy(); - }; - - this.throwError = function(something) { - var error = (something instanceof Error) ? something : new Error(something); - plan = function() { - throw error; - }; - return getSpy(); - }; - - this.callFake = function(fn) { - plan = fn; - return getSpy(); - }; - - this.stub = function(fn) { - plan = function() {}; - return getSpy(); - }; - } - - return SpyStrategy; -}; - -getJasmineRequireObj().Suite = function() { - function Suite(attrs) { - this.env = attrs.env; - this.id = attrs.id; - this.parentSuite = attrs.parentSuite; - this.description = attrs.description; - this.onStart = attrs.onStart || function() {}; - this.resultCallback = attrs.resultCallback || function() {}; - this.clearStack = attrs.clearStack || function(fn) {fn();}; - - this.beforeFns = []; - this.afterFns = []; - this.queueRunner = attrs.queueRunner || function() {}; - this.disabled = false; - - this.children = []; - - this.result = { - id: this.id, - status: this.disabled ? 'disabled' : '', - description: this.description, - fullName: this.getFullName() - }; - } - - Suite.prototype.getFullName = function() { - var fullName = this.description; - for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { - if (parentSuite.parentSuite) { - fullName = parentSuite.description + ' ' + fullName; - } - } - return fullName; - }; - - Suite.prototype.disable = function() { - this.disabled = true; - }; - - Suite.prototype.beforeEach = function(fn) { - this.beforeFns.unshift(fn); - }; - - Suite.prototype.afterEach = function(fn) { - this.afterFns.unshift(fn); - }; - - Suite.prototype.addChild = function(child) { - this.children.push(child); - }; - - Suite.prototype.execute = function(onComplete) { - var self = this; - if (this.disabled) { - complete(); - return; - } - - var allFns = []; - - for (var i = 0; i < this.children.length; i++) { - allFns.push(wrapChildAsAsync(this.children[i])); - } - - this.onStart(this); - - this.queueRunner({ - fns: allFns, - onComplete: complete - }); - - function complete() { - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } - - function wrapChildAsAsync(child) { - return function(done) { child.execute(done); }; - } - }; - - return Suite; -}; - -if (typeof window == void 0 && typeof exports == 'object') { - exports.Suite = jasmineRequire.Suite; -} - -getJasmineRequireObj().Timer = function() { - var defaultNow = (function(Date) { - return function() { return new Date().getTime(); }; - })(Date); - - function Timer(options) { - options = options || {}; - - var now = options.now || defaultNow, - startTime; - - this.start = function() { - startTime = now(); - }; - - this.elapsed = function() { - return now() - startTime; - }; - } - - return Timer; -}; - -getJasmineRequireObj().matchersUtil = function(j$) { - // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? - - return { - equals: function(a, b, customTesters) { - customTesters = customTesters || []; - - return eq(a, b, [], [], customTesters); - }, - - contains: function(haystack, needle, customTesters) { - customTesters = customTesters || []; - - if (Object.prototype.toString.apply(haystack) === '[object Array]') { - for (var i = 0; i < haystack.length; i++) { - if (eq(haystack[i], needle, [], [], customTesters)) { - return true; - } - } - return false; - } - return !!haystack && haystack.indexOf(needle) >= 0; - }, - - buildFailureMessage: function() { - var args = Array.prototype.slice.call(arguments, 0), - matcherName = args[0], - isNot = args[1], - actual = args[2], - expected = args.slice(3), - englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - - var message = 'Expected ' + - j$.pp(actual) + - (isNot ? ' not ' : ' ') + - englishyPredicate; - - if (expected.length > 0) { - for (var i = 0; i < expected.length; i++) { - if (i > 0) { - message += ','; - } - message += ' ' + j$.pp(expected[i]); - } - } - - return message + '.'; - } - }; - - // Equality function lovingly adapted from isEqual in - // [Underscore](http://underscorejs.org) - function eq(a, b, aStack, bStack, customTesters) { - var result = true; - - for (var i = 0; i < customTesters.length; i++) { - var customTesterResult = customTesters[i](a, b); - if (!j$.util.isUndefined(customTesterResult)) { - return customTesterResult; - } - } - - if (a instanceof j$.Any) { - result = a.jasmineMatches(b); - if (result) { - return true; - } - } - - if (b instanceof j$.Any) { - result = b.jasmineMatches(a); - if (result) { - return true; - } - } - - if (b instanceof j$.ObjectContaining) { - result = b.jasmineMatches(a); - if (result) { - return true; - } - } - - if (a instanceof Error && b instanceof Error) { - return a.message == b.message; - } - - // Identical objects are equal. `0 === -0`, but they aren't identical. - // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). - if (a === b) { return a !== 0 || 1 / a == 1 / b; } - // A strict comparison is necessary because `null == undefined`. - if (a === null || b === null) { return a === b; } - var className = Object.prototype.toString.call(a); - if (className != Object.prototype.toString.call(b)) { return false; } - switch (className) { - // Strings, numbers, dates, and booleans are compared by value. - case '[object String]': - // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is - // equivalent to `new String("5")`. - return a == String(b); - case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for - // other numeric values. - return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); - case '[object Date]': - case '[object Boolean]': - // Coerce dates and booleans to numeric primitive values. Dates are compared by their - // millisecond representations. Note that invalid dates with millisecond representations - // of `NaN` are not equivalent. - return +a == +b; - // RegExps are compared by their source patterns and flags. - case '[object RegExp]': - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; - } - if (typeof a != 'object' || typeof b != 'object') { return false; } - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = aStack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (aStack[length] == a) { return bStack[length] == b; } - } - // Add the first object to the stack of traversed objects. - aStack.push(a); - bStack.push(b); - var size = 0; - // Recursively compare objects and arrays. - if (className == '[object Array]') { - // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size == b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - if (!(result = eq(a[size], b[size], aStack, bStack, customTesters))) { break; } - } - } - } else { - // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) && - isFunction(bCtor) && (bCtor instanceof bCtor))) { - return false; - } - // Deep compare objects. - for (var key in a) { - if (has(a, key)) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; } - } - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (has(b, key) && !(size--)) { break; } - } - result = !size; - } - } - // Remove the first object from the stack of traversed objects. - aStack.pop(); - bStack.pop(); - - return result; - - function has(obj, key) { - return obj.hasOwnProperty(key); - } - - function isFunction(obj) { - return typeof obj === 'function'; - } - } -}; - -getJasmineRequireObj().toBe = function() { - function toBe() { - return { - compare: function(actual, expected) { - return { - pass: actual === expected - }; - } - }; - } - - return toBe; -}; - -getJasmineRequireObj().toBeCloseTo = function() { - - function toBeCloseTo() { - return { - compare: function(actual, expected, precision) { - if (precision !== 0) { - precision = precision || 2; - } - - return { - pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) - }; - } - }; - } - - return toBeCloseTo; -}; - -getJasmineRequireObj().toBeDefined = function() { - function toBeDefined() { - return { - compare: function(actual) { - return { - pass: (void 0 !== actual) - }; - } - }; - } - - return toBeDefined; -}; - -getJasmineRequireObj().toBeFalsy = function() { - function toBeFalsy() { - return { - compare: function(actual) { - return { - pass: !!!actual - }; - } - }; - } - - return toBeFalsy; -}; - -getJasmineRequireObj().toBeGreaterThan = function() { - - function toBeGreaterThan() { - return { - compare: function(actual, expected) { - return { - pass: actual > expected - }; - } - }; - } - - return toBeGreaterThan; -}; - - -getJasmineRequireObj().toBeLessThan = function() { - function toBeLessThan() { - return { - - compare: function(actual, expected) { - return { - pass: actual < expected - }; - } - }; - } - - return toBeLessThan; -}; -getJasmineRequireObj().toBeNaN = function(j$) { - - function toBeNaN() { - return { - compare: function(actual) { - var result = { - pass: (actual !== actual) - }; - - if (result.pass) { - result.message = 'Expected actual not to be NaN.'; - } else { - result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; - } - - return result; - } - }; - } - - return toBeNaN; -}; - -getJasmineRequireObj().toBeNull = function() { - - function toBeNull() { - return { - compare: function(actual) { - return { - pass: actual === null - }; - } - }; - } - - return toBeNull; -}; - -getJasmineRequireObj().toBeTruthy = function() { - - function toBeTruthy() { - return { - compare: function(actual) { - return { - pass: !!actual - }; - } - }; - } - - return toBeTruthy; -}; - -getJasmineRequireObj().toBeUndefined = function() { - - function toBeUndefined() { - return { - compare: function(actual) { - return { - pass: void 0 === actual - }; - } - }; - } - - return toBeUndefined; -}; - -getJasmineRequireObj().toContain = function() { - function toContain(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - - return { - compare: function(actual, expected) { - - return { - pass: util.contains(actual, expected, customEqualityTesters) - }; - } - }; - } - - return toContain; -}; - -getJasmineRequireObj().toEqual = function() { - - function toEqual(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - - return { - compare: function(actual, expected) { - var result = { - pass: false - }; - - result.pass = util.equals(actual, expected, customEqualityTesters); - - return result; - } - }; - } - - return toEqual; -}; - -getJasmineRequireObj().toHaveBeenCalled = function(j$) { - - function toHaveBeenCalled() { - return { - compare: function(actual) { - var result = {}; - - if (!j$.isSpy(actual)) { - throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); - } - - if (arguments.length > 1) { - throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); - } - - result.pass = actual.calls.any(); - - result.message = result.pass ? - 'Expected spy ' + actual.and.identity() + ' not to have been called.' : - 'Expected spy ' + actual.and.identity() + ' to have been called.'; - - return result; - } - }; - } - - return toHaveBeenCalled; -}; - -getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { - - function toHaveBeenCalledWith(util, customEqualityTesters) { - return { - compare: function() { - var args = Array.prototype.slice.call(arguments, 0), - actual = args[0], - expectedArgs = args.slice(1), - result = { pass: false }; - - if (!j$.isSpy(actual)) { - throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); - } - - if (!actual.calls.any()) { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; - return result; - } - - if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { - result.pass = true; - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; - } else { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; - } - - return result; - } - }; - } - - return toHaveBeenCalledWith; -}; - -getJasmineRequireObj().toMatch = function() { - - function toMatch() { - return { - compare: function(actual, expected) { - var regexp = new RegExp(expected); - - return { - pass: regexp.test(actual) - }; - } - }; - } - - return toMatch; -}; - -getJasmineRequireObj().toThrow = function(j$) { - - function toThrow(util) { - return { - compare: function(actual, expected) { - var result = { pass: false }, - threw = false, - thrown; - - if (typeof actual != 'function') { - throw new Error('Actual is not a Function'); - } - - try { - actual(); - } catch (e) { - threw = true; - thrown = e; - } - - if (!threw) { - result.message = 'Expected function to throw an exception.'; - return result; - } - - if (arguments.length == 1) { - result.pass = true; - result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; - - return result; - } - - if (util.equals(thrown, expected)) { - result.pass = true; - result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; - } else { - result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; - } - - return result; - } - }; - } - - return toThrow; -}; - -getJasmineRequireObj().toThrowError = function(j$) { - function toThrowError (util) { - return { - compare: function(actual) { - var threw = false, - pass = {pass: true}, - fail = {pass: false}, - thrown, - errorType, - message, - regexp, - name, - constructorName; - - if (typeof actual != 'function') { - throw new Error('Actual is not a Function'); - } - - extractExpectedParams.apply(null, arguments); - - try { - actual(); - } catch (e) { - threw = true; - thrown = e; - } - - if (!threw) { - fail.message = 'Expected function to throw an Error.'; - return fail; - } - - if (!(thrown instanceof Error)) { - fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; - return fail; - } - - if (arguments.length == 1) { - pass.message = 'Expected function not to throw an Error, but it threw ' + fnNameFor(thrown) + '.'; - return pass; - } - - if (errorType) { - name = fnNameFor(errorType); - constructorName = fnNameFor(thrown.constructor); - } - - if (errorType && message) { - if (thrown.constructor == errorType && util.equals(thrown.message, message)) { - pass.message = function() { return 'Expected function not to throw ' + name + ' with message ' + j$.pp(message) + '.'; }; - return pass; - } else { - fail.message = function() { return 'Expected function to throw ' + name + ' with message ' + j$.pp(message) + - ', but it threw ' + constructorName + ' with message ' + j$.pp(thrown.message) + '.'; }; - return fail; - } - } - - if (errorType && regexp) { - if (thrown.constructor == errorType && regexp.test(thrown.message)) { - pass.message = function() { return 'Expected function not to throw ' + name + ' with message matching ' + j$.pp(regexp) + '.'; }; - return pass; - } else { - fail.message = function() { return 'Expected function to throw ' + name + ' with message matching ' + j$.pp(regexp) + - ', but it threw ' + constructorName + ' with message ' + j$.pp(thrown.message) + '.'; }; - return fail; - } - } - - if (errorType) { - if (thrown.constructor == errorType) { - pass.message = 'Expected function not to throw ' + name + '.'; - return pass; - } else { - fail.message = 'Expected function to throw ' + name + ', but it threw ' + constructorName + '.'; - return fail; - } - } - - if (message) { - if (thrown.message == message) { - pass.message = function() { return 'Expected function not to throw an exception with message ' + j$.pp(message) + '.'; }; - return pass; - } else { - fail.message = function() { return 'Expected function to throw an exception with message ' + j$.pp(message) + - ', but it threw an exception with message ' + j$.pp(thrown.message) + '.'; }; - return fail; - } - } - - if (regexp) { - if (regexp.test(thrown.message)) { - pass.message = function() { return 'Expected function not to throw an exception with a message matching ' + j$.pp(regexp) + '.'; }; - return pass; - } else { - fail.message = function() { return 'Expected function to throw an exception with a message matching ' + j$.pp(regexp) + - ', but it threw an exception with message ' + j$.pp(thrown.message) + '.'; }; - return fail; - } - } - - function fnNameFor(func) { - return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; - } - - function extractExpectedParams() { - if (arguments.length == 1) { - return; - } - - if (arguments.length == 2) { - var expected = arguments[1]; - - if (expected instanceof RegExp) { - regexp = expected; - } else if (typeof expected == 'string') { - message = expected; - } else if (checkForAnErrorType(expected)) { - errorType = expected; - } - - if (!(errorType || message || regexp)) { - throw new Error('Expected is not an Error, string, or RegExp.'); - } - } else { - if (checkForAnErrorType(arguments[1])) { - errorType = arguments[1]; - } else { - throw new Error('Expected error type is not an Error.'); - } - - if (arguments[2] instanceof RegExp) { - regexp = arguments[2]; - } else if (typeof arguments[2] == 'string') { - message = arguments[2]; - } else { - throw new Error('Expected error message is not a string or RegExp.'); - } - } - } - - function checkForAnErrorType(type) { - if (typeof type !== 'function') { - return false; - } - - var Surrogate = function() {}; - Surrogate.prototype = type.prototype; - return (new Surrogate()) instanceof Error; - } - } - }; - } - - return toThrowError; -}; - -getJasmineRequireObj().version = function() { - return '2.0.1'; -}; diff --git a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-reporters/junit_reporter.js b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-reporters/junit_reporter.js index ccddaaf84..eafcd8bed 100644 --- a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-reporters/junit_reporter.js +++ b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-reporters/junit_reporter.js @@ -12,7 +12,9 @@ function trim(str) { return str.replace(/^\s+/, "" ).replace(/\s+$/, "" ); } function elapsed(start, end) { return (end - start)/1000; } function isFailed(obj) { return obj.status === "failed"; } - function isSkipped(obj) { return obj.status === "pending"; } + function isSkipped(obj) { + return obj.status === "pending" || obj.status === "excluded" || obj.status === "notApplicable"; + } function pad(n) { return n < 10 ? '0'+n : n; } function extend(dupe, obj) { // performs a shallow copy of all props of `obj` onto `dupe` for (var prop in obj) { @@ -30,12 +32,23 @@ pad(d.getMinutes()) + ':' + pad(d.getSeconds()); } + // XML 1.0 has no way to represent these, escaped or not, and a single one + // makes the whole report unparseable; they are written out as \uXXXX text. + var XML_FORBIDDEN = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?/g, ">") .replace(/\"/g, """) - .replace(/\'/g, "'") - .replace(/\&/g, "&"); + .replace(/\'/g, "'"); + } + function cdata(str) { + return '/g, ']]]]>') + ']]>'; } function getQualifiedFilename(path, filename, separator) { if (path && path.substr(-1) !== separator && filename.substr(0) !== separator) { @@ -143,6 +156,9 @@ }; self.suiteDone = function(suite) { suite = getSuite(suite); + // Errors that belong to the suite itself (an afterAll, or an error + // reported while no spec was running), not to any one spec. + suite._ownFailures = suite.failedExpectations || []; // disabled suite (xdescribe) -- suiteStarted was never called if (suite._parent === UNDEFINED) { self.suiteStarted(suite); @@ -151,11 +167,12 @@ suite._endTime = new Date(); currentSuite = suite._parent; }; - self.jasmineDone = function() { + self.jasmineDone = function(result) { var output = ''; for (var i = 0; i < suites.length; i++) { output += self.getOrWriteNestedOutput(suites[i]); } + output += runFailuresAsXml(result || {}); // if we have anything to write here, write out the consolidated file if (output) { wrapOutputAndWriteFile(self.filePrefix, output); @@ -265,26 +282,67 @@ } function suiteAsXml(suite) { - - var fullyQualifiedSuiteName = getFullyQualifiedSuiteName(suite); + var ownFailures = suite._ownFailures || []; + var extraCase = ownFailures.length ? 1 : 0; var xml = '\n '; - xml += '\n '; - } + xml += failuresAsXml(spec.failedExpectations); } xml += '\n '; return xml; diff --git a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-reporters/terminal_reporter.js b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-reporters/terminal_reporter.js index cda6e0627..78b7d2fce 100644 --- a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-reporters/terminal_reporter.js +++ b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine-reporters/terminal_reporter.js @@ -10,7 +10,9 @@ function elapsed(start, end) { return (end - start)/1000; } function isFailed(obj) { return obj.status === "failed"; } - function isSkipped(obj) { return obj.status === "pending"; } + function isSkipped(obj) { + return obj.status === "pending" || obj.status === "excluded" || obj.status === "notApplicable"; + } function extend(dupe, obj) { // performs a shallow copy of all props of `obj` onto `dupe` for (var prop in obj) { if (obj.hasOwnProperty(prop)) { @@ -66,8 +68,19 @@ totalSpecsExecuted = 0, totalSpecsSkipped = 0, totalSpecsFailed = 0, + totalOtherFailures = 0, totalSpecsDefined; + function logFailures(depth, header, failures) { + log(indentWithLevel(depth, inColor(header, 'red'))); + for (var i = 0; i < failures.length; i++) { + log(inColor(indentWithLevel(depth, indent_string + failures[i].message), 'red')); + if (failures[i].stack) { + log(indentWithLevel(depth, indent_string + failures[i].stack)); + } + } + } + var __suites = {}, __specs = {}; function getSuite(suite) { __suites[suite.id] = extend(__suites[suite.id] || {}, suite); @@ -189,6 +202,10 @@ }; self.suiteDone = function(suite) { suite = getSuite(suite); + if (suite.failedExpectations && suite.failedExpectations.length) { + totalOtherFailures++; + logFailures(1, 'Suite-level errors in "' + suite.fullName + '":', suite.failedExpectations); + } // disabled suite (xdescribe) -- suiteStarted was never called if (suite._parent === UNDEFINED) { self.suiteStarted(suite); @@ -212,7 +229,21 @@ str = passed + ' of ' + total + ' passed (' + skipped + ' skipped)'; log(indentWithLevel(suite._depth, inColor(str+'.', color))); }; - self.jasmineDone = function() { + self.jasmineDone = function(result) { + result = result || {}; + if (result.failedExpectations && result.failedExpectations.length) { + totalOtherFailures++; + logFailures(1, 'Run-level errors:', result.failedExpectations); + } + if (result.overallStatus === "incomplete") { + totalOtherFailures++; + log(inColor('Run incomplete: ' + result.incompleteReason, 'red')); + } + var deprecations = result.deprecationWarnings || []; + for (var d = 0; d < deprecations.length; d++) { + log('DEPRECATION: ' + deprecations[d].message); + } + var failedOverall = totalSpecsFailed > 0 || totalOtherFailures > 0; var now = new Date(), dur = elapsed(startTime, now), total = totalSpecsDefined || totalSpecsExecuted, @@ -222,9 +253,11 @@ fail_str = totalSpecsFailed + (totalSpecsFailed === 1 ? " failure, " : " failures, "), skip_str = skipped + " skipped, ", disabled_str = disabled + " disabled in ", - summary_str = spec_str + fail_str + skip_str + disabled_str + dur + "s.", - result_str = (totalSpecsFailed && "FAILURE: " || "SUCCESS: ") + summary_str, - result_color = totalSpecsFailed && "red+bold" || "green+bold"; + other_str = totalOtherFailures === 0 ? "" : + totalOtherFailures + (totalOtherFailures === 1 ? " suite/run-level error, " : " suite/run-level errors, "), + summary_str = spec_str + fail_str + other_str + skip_str + disabled_str + dur + "s.", + result_str = (failedOverall ? "FAILURE: " : "SUCCESS: ") + summary_str, + result_color = failedOverall ? "red+bold" : "green+bold"; if (self.verbosity === 2) { log(''); diff --git a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine.d.ts b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine.d.ts deleted file mode 100644 index 7ccfa9594..000000000 --- a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/jasmine.d.ts +++ /dev/null @@ -1,434 +0,0 @@ -// Type definitions for Jasmine 2.0 -// Project: http://pivotal.github.com/jasmine/ -// Definitions by: Boris Yankov , Theodore Brown -// Definitions: https://github.com/borisyankov/DefinitelyTyped - - -// For ddescribe / iit use : https://github.com/borisyankov/DefinitelyTyped/blob/master/karma-jasmine/karma-jasmine.d.ts - -declare function describe(description: string, specDefinitions: () => void): void; -// declare function ddescribe(description: string, specDefinitions: () => void): void; Not a part of jasmine. Angular team adds these -declare function xdescribe(description: string, specDefinitions: () => void): void; - -declare function it(expectation: string, assertion?: () => void): void; -declare function it(expectation: string, assertion?: (done: () => void) => void): void; -// declare function iit(expectation: string, assertion?: () => void): void; Not a part of jasmine. Angular team adds these -// declare function iit(expectation: string, assertion?: (done: () => void) => void): void; Not a part of jasmine. Angular team adds these -declare function xit(expectation: string, assertion?: () => void): void; -declare function xit(expectation: string, assertion?: (done: () => void) => void): void; - -/** If you call the function pending anywhere in the spec body, no matter the expectations, the spec will be marked pending. */ -declare function pending(): void; - -declare function beforeEach(action: () => void): void; -declare function beforeEach(action: (done: () => void) => void): void; -declare function afterEach(action: () => void): void; -declare function afterEach(action: (done: () => void) => void): void; - -declare function expect(spy: Function): jasmine.Matchers; -declare function expect(actual: any): jasmine.Matchers; - -declare function spyOn(object: any, method: string): jasmine.Spy; - -declare function runs(asyncMethod: Function): void; -declare function waitsFor(latchMethod: () => boolean, failureMessage?: string, timeout?: number): void; -declare function waits(timeout?: number): void; - -declare module jasmine { - - var clock: () => Clock; - - function any(aclass: any): Any; - function objectContaining(sample: any): ObjectContaining; - function createSpy(name: string, originalFn?: Function): Spy; - function createSpyObj(baseName: string, methodNames: any[]): any; - function createSpyObj(baseName: string, methodNames: any[]): T; - function pp(value: any): string; - function getEnv(): Env; - function addMatchers(matchers: any): Any; - - interface Any { - - new (expectedClass: any): any; - - jasmineMatches(other: any): boolean; - jasmineToString(): string; - } - - interface ObjectContaining { - new (sample: any): any; - - jasmineMatches(other: any, mismatchKeys: any[], mismatchValues: any[]): boolean; - jasmineToString(): string; - } - - interface Block { - - new (env: Env, func: SpecFunction, spec: Spec): any; - - execute(onComplete: () => void): void; - } - - interface WaitsBlock extends Block { - new (env: Env, timeout: number, spec: Spec): any; - } - - interface WaitsForBlock extends Block { - new (env: Env, timeout: number, latchFunction: SpecFunction, message: string, spec: Spec): any; - } - - interface Clock { - install(): void; - uninstall(): void; - /** Calls to any registered callback are triggered when the clock is ticked forward via the jasmine.clock().tick function, which takes a number of milliseconds. */ - tick(ms: number): void; - } - - interface Env { - setTimeout: any; - clearTimeout: void; - setInterval: any; - clearInterval: void; - updateInterval: number; - - currentSpec: Spec; - - matchersClass: Matchers; - - version(): any; - versionString(): string; - nextSpecId(): number; - addReporter(reporter: Reporter): void; - execute(): void; - describe(description: string, specDefinitions: () => void): Suite; - // ddescribe(description: string, specDefinitions: () => void): Suite; Not a part of jasmine. Angular team adds these - beforeEach(beforeEachFunction: () => void): void; - currentRunner(): Runner; - afterEach(afterEachFunction: () => void): void; - xdescribe(desc: string, specDefinitions: () => void): XSuite; - it(description: string, func: () => void): Spec; - // iit(description: string, func: () => void): Spec; Not a part of jasmine. Angular team adds these - xit(desc: string, func: () => void): XSpec; - compareRegExps_(a: RegExp, b: RegExp, mismatchKeys: string[], mismatchValues: string[]): boolean; - compareObjects_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean; - equals_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean; - contains_(haystack: any, needle: any): boolean; - addEqualityTester(equalityTester: (a: any, b: any, env: Env, mismatchKeys: string[], mismatchValues: string[]) => boolean): void; - specFilter(spec: Spec): boolean; - } - - interface FakeTimer { - - new (): any; - - reset(): void; - tick(millis: number): void; - runFunctionsWithinRange(oldMillis: number, nowMillis: number): void; - scheduleFunction(timeoutKey: any, funcToCall: () => void, millis: number, recurring: boolean): void; - } - - interface HtmlReporter { - new (): any; - } - - interface HtmlSpecFilter { - new (): any; - } - - interface Result { - type: string; - } - - interface NestedResults extends Result { - description: string; - - totalCount: number; - passedCount: number; - failedCount: number; - - skipped: boolean; - - rollupCounts(result: NestedResults): void; - log(values: any): void; - getItems(): Result[]; - addResult(result: Result): void; - passed(): boolean; - } - - interface MessageResult extends Result { - values: any; - trace: Trace; - } - - interface ExpectationResult extends Result { - matcherName: string; - passed(): boolean; - expected: any; - actual: any; - message: string; - trace: Trace; - } - - interface Trace { - name: string; - message: string; - stack: any; - } - - interface PrettyPrinter { - - new (): any; - - format(value: any): void; - iterateObject(obj: any, fn: (property: string, isGetter: boolean) => void): void; - emitScalar(value: any): void; - emitString(value: string): void; - emitArray(array: any[]): void; - emitObject(obj: any): void; - append(value: any): void; - } - - interface StringPrettyPrinter extends PrettyPrinter { - } - - interface Queue { - - new (env: any): any; - - env: Env; - ensured: boolean[]; - blocks: Block[]; - running: boolean; - index: number; - offset: number; - abort: boolean; - - addBefore(block: Block, ensure?: boolean): void; - add(block: any, ensure?: boolean): void; - insertNext(block: any, ensure?: boolean): void; - start(onComplete?: () => void): void; - isRunning(): boolean; - next_(): void; - results(): NestedResults; - } - - interface Matchers { - - new (env: Env, actual: any, spec: Env, isNot?: boolean): any; - - env: Env; - actual: any; - spec: Env; - isNot?: boolean; - message(): any; - - toBe(expected: any): boolean; - toNotBe(expected: any): boolean; - toEqual(expected: any): boolean; - toNotEqual(expected: any): boolean; - toMatch(expected: any): boolean; - toNotMatch(expected: any): boolean; - toBeDefined(): boolean; - toBeUndefined(): boolean; - toBeNull(): boolean; - toBeNaN(): boolean; - toBeTruthy(): boolean; - toBeFalsy(): boolean; - toHaveBeenCalled(): boolean; - wasNotCalled(): boolean; - toHaveBeenCalledWith(...params: any[]): boolean; - toContain(expected: any): boolean; - toNotContain(expected: any): boolean; - toBeLessThan(expected: any): boolean; - toBeGreaterThan(expected: any): boolean; - toBeCloseTo(expected: any, precision: any): boolean; - toContainHtml(expected: string): boolean; - toContainText(expected: string): boolean; - toThrow(expected?: any): boolean; - toThrowError(expected?: any): boolean; - not: Matchers; - - Any: Any; - } - - interface Reporter { - reportRunnerStarting(runner: Runner): void; - reportRunnerResults(runner: Runner): void; - reportSuiteResults(suite: Suite): void; - reportSpecStarting(spec: Spec): void; - reportSpecResults(spec: Spec): void; - log(str: string): void; - } - - interface MultiReporter extends Reporter { - addReporter(reporter: Reporter): void; - } - - interface Runner { - - new (env: Env): any; - - execute(): void; - beforeEach(beforeEachFunction: SpecFunction): void; - afterEach(afterEachFunction: SpecFunction): void; - finishCallback(): void; - addSuite(suite: Suite): void; - add(block: Block): void; - specs(): Spec[]; - suites(): Suite[]; - topLevelSuites(): Suite[]; - results(): NestedResults; - } - - interface SpecFunction { - (spec?: Spec): void; - } - - interface SuiteOrSpec { - id: number; - env: Env; - description: string; - queue: Queue; - } - - interface Spec extends SuiteOrSpec { - - new (env: Env, suite: Suite, description: string): any; - - suite: Suite; - - afterCallbacks: SpecFunction[]; - spies_: Spy[]; - - results_: NestedResults; - matchersClass: Matchers; - - getFullName(): string; - results(): NestedResults; - log(arguments: any): any; - runs(func: SpecFunction): Spec; - addToQueue(block: Block): void; - addMatcherResult(result: Result): void; - expect(actual: any): any; - waits(timeout: number): Spec; - waitsFor(latchFunction: SpecFunction, timeoutMessage?: string, timeout?: number): Spec; - fail(e?: any): void; - getMatchersClass_(): Matchers; - addMatchers(matchersPrototype: any): void; - finishCallback(): void; - finish(onComplete?: () => void): void; - after(doAfter: SpecFunction): void; - execute(onComplete?: () => void): any; - addBeforesAndAftersToQueue(): void; - explodes(): void; - spyOn(obj: any, methodName: string, ignoreMethodDoesntExist: boolean): Spy; - removeAllSpies(): void; - } - - interface XSpec { - id: number; - runs(): void; - } - - interface Suite extends SuiteOrSpec { - - new (env: Env, description: string, specDefinitions: () => void, parentSuite: Suite): any; - - parentSuite: Suite; - - getFullName(): string; - finish(onComplete?: () => void): void; - beforeEach(beforeEachFunction: SpecFunction): void; - afterEach(afterEachFunction: SpecFunction): void; - results(): NestedResults; - add(suiteOrSpec: SuiteOrSpec): void; - specs(): Spec[]; - suites(): Suite[]; - children(): any[]; - execute(onComplete?: () => void): void; - } - - interface XSuite { - execute(): void; - } - - interface Spy { - (...params: any[]): any; - - identity: string; - and: SpyAnd; - calls: Calls; - mostRecentCall: { args: any[]; }; - argsForCall: any[]; - wasCalled: boolean; - callCount: number; - } - - interface SpyAnd { - /** By chaining the spy with and.callThrough, the spy will still track all calls to it but in addition it will delegate to the actual implementation. */ - callThrough(): void; - /** By chaining the spy with and.returnValue, all calls to the function will return a specific value. */ - returnValue(val: any): void; - /** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied function. */ - callFake(fn: Function): void; - /** By chaining the spy with and.throwError, all calls to the spy will throw the specified value. */ - throwError(msg: string): void; - /** When a calling strategy is used for a spy, the original stubbing behavior can be returned at any time with and.stub. */ - stub(): void; - } - - interface Calls { - /** By chaining the spy with calls.any(), will return false if the spy has not been called at all, and then true once at least one call happens. **/ - any(): boolean; - /** By chaining the spy with calls.count(), will return the number of times the spy was called **/ - count(): number; - /** By chaining the spy with calls.argsFor(), will return the arguments passed to call number index **/ - argsFor(index: number): any[]; - /** By chaining the spy with calls.allArgs(), will return the arguments to all calls **/ - allArgs(): any[]; - /** By chaining the spy with calls.all(), will return the context (the this) and arguments passed all calls **/ - all(): any; - /** By chaining the spy with calls.mostRecent(), will return the context (the this) and arguments for the most recent call **/ - mostRecent(): any; - /** By chaining the spy with calls.first(), will return the context (the this) and arguments for the first call **/ - first(): any; - /** By chaining the spy with calls.reset(), will clears all tracking for a spy **/ - reset(): void; - } - - interface Util { - inherit(childClass: Function, parentClass: Function): any; - formatException(e: any): any; - htmlEscape(str: string): string; - argsToArray(args: any): any; - extend(destination: any, source: any): any; - } - - interface JsApiReporter extends Reporter { - - started: boolean; - finished: boolean; - result: any; - messages: any; - - new (): any; - - suites(): Suite[]; - summarize_(suiteOrSpec: SuiteOrSpec): any; - results(): any; - resultsForSpec(specId: any): any; - log(str: any): any; - resultsForSpecs(specIds: any): any; - summarizeResult_(result: any): any; - } - - interface Jasmine { - Spec: Spec; - clock: Clock; - util: Util; - } - - export var HtmlReporter: HtmlReporter; - export var HtmlSpecFilter: HtmlSpecFilter; - export var DEFAULT_TIMEOUT_INTERVAL: number; -} diff --git a/test-app/app/src/main/assets/app/MyActivity.js b/test-app/app/src/main/assets/app/MyActivity.js index fbea5688e..9ff51cf96 100644 --- a/test-app/app/src/main/assets/app/MyActivity.js +++ b/test-app/app/src/main/assets/app/MyActivity.js @@ -30,7 +30,7 @@ var MyActivity = (function (_super) { _super.prototype.onCreate.call(this, bundle); require('./tests/testsWithContext').run(this); - execute(); //run jasmine + require('./Infrastructure/Jasmine/boot').execute(); var layout = new android.widget.LinearLayout(this); layout.setOrientation(1); diff --git a/test-app/app/src/main/assets/app/boot.js b/test-app/app/src/main/assets/app/boot.js index 8d38bfad7..62f840c46 100644 --- a/test-app/app/src/main/assets/app/boot.js +++ b/test-app/app/src/main/assets/app/boot.js @@ -32,7 +32,7 @@ global.__JUnitSaveResults = function (unitTestResults) { if (com.tns.tests.JniBench.isEnabled()) { require("./tests/benchmarks/jniBenchmarks").run(); } else { - require('./Infrastructure/Jasmine/jasmine-2.0.1/boot'); //runs jasmine, attaches the junitOutputter + require('./Infrastructure/Jasmine/boot'); require("./mainpage"); } \ No newline at end of file diff --git a/test-app/dependencies.json b/test-app/dependencies.json index 32960f8ce..a9ebb3108 100644 --- a/test-app/dependencies.json +++ b/test-app/dependencies.json @@ -1,2 +1,7 @@ [ -] \ No newline at end of file + { + "name": "test-runner-deps", + "directory": "test-runner-deps", + "dependencies": [] + } +] diff --git a/test-app/test-runner-deps/package.json b/test-app/test-runner-deps/package.json new file mode 100644 index 000000000..47b6fae96 --- /dev/null +++ b/test-app/test-runner-deps/package.json @@ -0,0 +1,5 @@ +{ + "name": "test-runner-deps", + "private": true, + "description": "Test app only: stages the npm packages the Jasmine harness loads at runtime into the APK's assets." +} diff --git a/test-app/test-runner-deps/platforms/android/include.gradle b/test-app/test-runner-deps/platforms/android/include.gradle new file mode 100644 index 000000000..7733e59f8 --- /dev/null +++ b/test-app/test-runner-deps/platforms/android/include.gradle @@ -0,0 +1,42 @@ +// Copies the npm packages the test app's Jasmine harness loads at runtime from +// the repo's node_modules (`npm install` at the repo root) into a generated +// assets folder. Each package's files land flat in +// app/Infrastructure/Jasmine/vendor//. +def testRunnerDepsNodeModules = new File(rootDir, "../node_modules").canonicalFile +def testRunnerDepsPackages = [ + "jasmine-core": ["lib/jasmine-core/jasmine.js", "LICENSE"], +] +def testRunnerDepsAssetsDir = new File(project.buildDir, "generated/test-runner-deps/assets") + +def stageTestRunnerDeps = project.tasks.register("stageTestRunnerDeps") { + def sources = [:] + testRunnerDepsPackages.each { name, files -> + files.each { path -> + sources[new File(testRunnerDepsNodeModules, "$name/$path")] = "app/Infrastructure/Jasmine/vendor/$name/${new File(path).name}" + } + } + def outDir = testRunnerDepsAssetsDir + def nodeModules = testRunnerDepsNodeModules + + inputs.files(sources.keySet()) + outputs.dir(outDir) + + doLast { + def missing = sources.keySet().findAll { !it.isFile() } + if (!missing.isEmpty()) { + throw new GradleException("Test runner dependencies are not installed (missing ${missing.join(', ')}). " + + "Run 'npm install' in ${nodeModules.parentFile}.") + } + outDir.deleteDir() + sources.each { from, to -> + def dest = new File(outDir, to) + dest.parentFile.mkdirs() + java.nio.file.Files.copy(from.toPath(), dest.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING) + } + } +} + +android.sourceSets.main.assets.srcDir(testRunnerDepsAssetsDir) +project.tasks.matching { it.name == "preBuild" }.configureEach { + dependsOn stageTestRunnerDeps +} From 8716c50c74112680a54302d3a6efdd85b66aeabd Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Wed, 11 Nov 2026 17:42:51 -0300 Subject: [PATCH 3/5] test: make the test app's specs pass on Jasmine 7 - Duplicate names are a declaration error now: rename a copied suite in the Kotlin enums tests and the console.log tests, give two specs that test different things distinct names, and drop a copied timer spec. - Specs that raise global errors or unhandled rejections on purpose (error events, unhandled rejections, uncaughtErrorPolicy, discarded exceptions, worker errors left unhandled) run under raisesGlobalErrors. - The worker OOM spec cancels the worker error on the parent. - The UI-thread dispatch spec declared an it() from inside a callback, which never asserted anything; it now waits for the three callbacks. - Rejection handlers use done.fail() instead of a failing expect. --- .../main/assets/app/tests/console/logTests.js | 2 +- .../app/tests/discardedExceptionsTest.js | 7 +++-- .../tests/dispatchCallbacksOnUiThreadTests.js | 26 +++++++------------ .../tests/kotlin/enums/testEnumsSupport.js | 2 +- .../main/assets/app/tests/testErrorEvents.js | 25 +++++++++++------- .../assets/app/tests/testEsmHttpLoader.js | 6 +---- .../main/assets/app/tests/testMessaging.js | 6 +++-- .../main/assets/app/tests/testNativeTimers.js | 8 ------ .../src/main/assets/app/tests/testNsModule.js | 5 +--- .../app/tests/testUncaughtErrorPolicy.js | 18 +++++++------ .../app/tests/testUnhandledRejections.js | 26 ++++++++++--------- .../app/tests/testWorkerResourceLimits.js | 5 +++- .../app/src/main/assets/app/tests/tests.js | 4 +-- .../assets/app/tests/testsForTypescript.js | 4 +-- 14 files changed, 70 insertions(+), 74 deletions(-) diff --git a/test-app/app/src/main/assets/app/tests/console/logTests.js b/test-app/app/src/main/assets/app/tests/console/logTests.js index 6f4ea0e2f..c10fe0ef7 100644 --- a/test-app/app/src/main/assets/app/tests/console/logTests.js +++ b/test-app/app/src/main/assets/app/tests/console/logTests.js @@ -1,4 +1,4 @@ -describe("Test JSONObject conversions", () => { +describe("Test console.log arguments", () => { it("console.log with number param should not crash", () => { console.log(123); }); diff --git a/test-app/app/src/main/assets/app/tests/discardedExceptionsTest.js b/test-app/app/src/main/assets/app/tests/discardedExceptionsTest.js index ddeee9bf0..0fb80f9de 100644 --- a/test-app/app/src/main/assets/app/tests/discardedExceptionsTest.js +++ b/test-app/app/src/main/assets/app/tests/discardedExceptionsTest.js @@ -1,3 +1,5 @@ +const { raisesGlobalErrors } = require("../Infrastructure/Jasmine/globalErrorSpy"); + describe("Tests discarded exception ", function () { var originalOnDiscardedError = null; @@ -5,7 +7,7 @@ describe("Tests discarded exception ", function () { originalOnDiscardedError = global.__onDiscardedError; }); - it("should report discarded exception", function () { + it("should report discarded exception", raisesGlobalErrors(function (done) { var reportedException = null; global.__onDiscardedError = (error) => { @@ -20,7 +22,8 @@ describe("Tests discarded exception ", function () { expect(reportedException.nativeException.getMessage()).toBe('Exception to suppress'); expect(reportedException.message).toBe('Exception to suppress'); expect(reportedException.stackTrace).toContain('com.tns.tests.DiscardedExceptionTest.reportSupressedException'); - }); + done(); + })); afterEach(function() { global.__onDiscardedError = originalOnDiscardedError; diff --git a/test-app/app/src/main/assets/app/tests/dispatchCallbacksOnUiThreadTests.js b/test-app/app/src/main/assets/app/tests/dispatchCallbacksOnUiThreadTests.js index fba118c8e..828ae2cd9 100644 --- a/test-app/app/src/main/assets/app/tests/dispatchCallbacksOnUiThreadTests.js +++ b/test-app/app/src/main/assets/app/tests/dispatchCallbacksOnUiThreadTests.js @@ -1,28 +1,22 @@ describe("Tests dispatch callbacks on UI thread ", function () { - it("TestProcessDataCallbackShouldBeExecutedOnUiThread", function () { + it("TestProcessDataCallbackShouldBeExecutedOnUiThread", function (done) { - __log("TEST: TestProcessDataCallbackShouldBeExecutedOnUiThread"); + __log("TEST: TestProcessDataCallbackShouldBeExecutedOnUiThread"); + var received = []; var D = com.tns.tests.DispatchAsyncOpOnUIThreadTest.extend("DispatchAsyncOpOnUIThreadTest", { processData: function(index, data) { - it("inner spec", function () { - if (index === 0) { - expect(data).toBe(123); - } else if (index === 1) { - expect(data).toBe(456); - } else if (index === 2) { - expect(data).toBe(789); - } else { - //one of the above has to be called ... else fail - expect(true).toBe(false); - } - }); + received.push([index, data]); + if (received.length === 3) { + expect(received).toEqual([[0, 123], [1, 456], [2, 789]]); + done(); + } } }); - + var d = new D(); - + d.processDataAsync([123, 456, 789]); }); }); diff --git a/test-app/app/src/main/assets/app/tests/kotlin/enums/testEnumsSupport.js b/test-app/app/src/main/assets/app/tests/kotlin/enums/testEnumsSupport.js index 3ff0bd04d..e57b03ed5 100644 --- a/test-app/app/src/main/assets/app/tests/kotlin/enums/testEnumsSupport.js +++ b/test-app/app/src/main/assets/app/tests/kotlin/enums/testEnumsSupport.js @@ -1,4 +1,4 @@ -describe("Tests Kotlin extension functions support", function () { +describe("Tests Kotlin enums support", function () { it("Test Kotlin enum entries are accessible", function () { var enumEntry = com.tns.tests.kotlin.enums.KotlinEnum.TEST_ENTRY; diff --git a/test-app/app/src/main/assets/app/tests/testErrorEvents.js b/test-app/app/src/main/assets/app/tests/testErrorEvents.js index 2e1cbd988..509db10c8 100644 --- a/test-app/app/src/main/assets/app/tests/testErrorEvents.js +++ b/test-app/app/src/main/assets/app/tests/testErrorEvents.js @@ -1,3 +1,5 @@ +const { raisesGlobalErrors } = require("../Infrastructure/Jasmine/globalErrorSpy"); + describe("WHATWG error events", function () { // Many tests exercise the global error path, which ends in the // __onUncaughtError / __onDiscardedError hooks when a listener does not @@ -47,7 +49,7 @@ describe("WHATWG error events", function () { }, 25); } - it("reportError fires an 'error' listener with an ErrorEvent carrying error and message", function (done) { + it("reportError fires an 'error' listener with an ErrorEvent carrying error and message", raisesGlobalErrors(function (done) { var err = new Error("x"); var received = null; onGlobal("error", function (e) { @@ -67,9 +69,9 @@ describe("WHATWG error events", function () { expect(uncaught.length).toBe(0); done(); }); - }); + })); - it("reportError without preventDefault still invokes __onUncaughtError (back-compat)", function () { + it("reportError without preventDefault still invokes __onUncaughtError (back-compat)", raisesGlobalErrors(function (done) { var err = new Error("back-compat"); var received = null; onGlobal("error", function (e) { @@ -82,7 +84,8 @@ describe("WHATWG error events", function () { expect(received.error).toBe(err); expect(uncaught.length).toBe(1); expect(uncaught[0]).toBe(err); - }); + done(); + })); it("reportError throws TypeError when called with no arguments", function () { expect(function () { @@ -90,7 +93,7 @@ describe("WHATWG error events", function () { }).toThrowError(TypeError); }); - it("a discarded Java exception dispatches an 'error' event before __onDiscardedError", function () { + it("a discarded Java exception dispatches an 'error' event before __onDiscardedError", raisesGlobalErrors(function (done) { var received = null; onGlobal("error", function (e) { received = e; @@ -106,9 +109,10 @@ describe("WHATWG error events", function () { // Unprevented, so the existing hook still fires (back-compat). expect(discarded.length).toBe(1); expect(discarded[0]).toBe(received.error); - }); + done(); + })); - it("preventDefault() on the 'error' event suppresses __onDiscardedError", function () { + it("preventDefault() on the 'error' event suppresses __onDiscardedError", raisesGlobalErrors(function (done) { var received = null; onGlobal("error", function (e) { received = e; @@ -120,7 +124,8 @@ describe("WHATWG error events", function () { expect(received).not.toBeNull(); expect(discarded.length).toBe(0); - }); + done(); + })); describe("constructors and EventTarget semantics", function () { it("Event is spec-sane and cancelable via preventDefault", function () { @@ -219,7 +224,7 @@ describe("WHATWG error events", function () { }); }); - it("reportError still fires listeners after globalThis.dispatchEvent is overwritten", function (done) { + it("reportError still fires listeners after globalThis.dispatchEvent is overwritten", raisesGlobalErrors(function (done) { var err = new Error("resilient"); var received = null; onGlobal("error", function (e) { @@ -241,5 +246,5 @@ describe("WHATWG error events", function () { expect(uncaught.length).toBe(0); done(); }); - }); + })); }); diff --git a/test-app/app/src/main/assets/app/tests/testEsmHttpLoader.js b/test-app/app/src/main/assets/app/tests/testEsmHttpLoader.js index 839a1674e..576e4f086 100644 --- a/test-app/app/src/main/assets/app/tests/testEsmHttpLoader.js +++ b/test-app/app/src/main/assets/app/tests/testEsmHttpLoader.js @@ -18,12 +18,8 @@ describe("HTTP ESM Loader", function () { } } - // This Jasmine's fail() throws, which inside a promise reaction surfaces as - // an opaque spec timeout instead of a diff. Every rejection handler below - // reports through a non-throwing expect for that reason. function reportRejection(error, done) { - expect("rejected: " + formatError(error)).toBe("resolved"); - done(); + done.fail("rejected: " + formatError(error)); } function withTimeout(promise, ms, label) { diff --git a/test-app/app/src/main/assets/app/tests/testMessaging.js b/test-app/app/src/main/assets/app/tests/testMessaging.js index 6e4d30e99..c1a94a44e 100644 --- a/test-app/app/src/main/assets/app/tests/testMessaging.js +++ b/test-app/app/src/main/assets/app/tests/testMessaging.js @@ -1,3 +1,5 @@ +const { raisesGlobalErrors } = require("../Infrastructure/Jasmine/globalErrorSpy"); + // Android-side regression specs for the messaging tier. The shared suites under // app/shared cover the specified behavior; these pin runtime edges that need // native wrappers, a worker that never settles, or a collection. @@ -540,7 +542,7 @@ describe("Messaging runtime edges", function () { jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; }); - it("reports an error the Worker object left unhandled to the parent scope", function (done) { + it("reports an error the Worker object left unhandled to the parent scope", raisesGlobalErrors(function (done) { var seen = []; var worker = null; var listener = function (event) { @@ -561,7 +563,7 @@ describe("Messaging runtime edges", function () { }; addEventListener("error", listener); worker = new Worker("./messaging/throwingWorker.js"); - }); + })); it("forwards the error a throwing scope onerror raised for a rejection, once", function (done) { var worker = new Worker("./messaging/rejectingWorker.js"); diff --git a/test-app/app/src/main/assets/app/tests/testNativeTimers.js b/test-app/app/src/main/assets/app/tests/testNativeTimers.js index a40734ced..80b5b27f6 100644 --- a/test-app/app/src/main/assets/app/tests/testNativeTimers.js +++ b/test-app/app/src/main/assets/app/tests/testNativeTimers.js @@ -51,14 +51,6 @@ describe('native timer', () => { }, 100); }); - it('triggers timeout', (done) => { - const now = Date.now(); - setTimeout(() => { - expect(Date.now() - now).not.toBeLessThan(100); - done(); - }, 100); - }); - it('triggers interval', (done) => { let calls = 0; const itv = setInterval(() => { diff --git a/test-app/app/src/main/assets/app/tests/testNsModule.js b/test-app/app/src/main/assets/app/tests/testNsModule.js index 7d4d75866..b3597024e 100644 --- a/test-app/app/src/main/assets/app/tests/testNsModule.js +++ b/test-app/app/src/main/assets/app/tests/testNsModule.js @@ -29,10 +29,7 @@ describe("ns:module", function () { expect(ns.configureLoader).toBe(nsModule.configureLoader); done(); }).catch(function (error) { - // fail() throws in this Jasmine, which inside a promise chain - // surfaces as an opaque timeout instead of the real reason. - expect("rejected: " + String((error && error.message) || error)).toBe("resolved"); - done(); + done.fail("rejected: " + String((error && error.message) || error)); }); }); diff --git a/test-app/app/src/main/assets/app/tests/testUncaughtErrorPolicy.js b/test-app/app/src/main/assets/app/tests/testUncaughtErrorPolicy.js index 655bd76fb..aa77d380c 100644 --- a/test-app/app/src/main/assets/app/tests/testUncaughtErrorPolicy.js +++ b/test-app/app/src/main/assets/app/tests/testUncaughtErrorPolicy.js @@ -1,3 +1,5 @@ +const { raisesGlobalErrors } = require("../Infrastructure/Jasmine/globalErrorSpy"); + describe("uncaughtErrorPolicy (default: report)", function () { // Under the default "report" policy, an uncaught JS throw in a // native-initiated callback is contained: reported through the `error` @@ -53,7 +55,7 @@ describe("uncaughtErrorPolicy (default: report)", function () { }, 20); } - it("contains an uncaught throw in a native-initiated callback and keeps the app alive", function (done) { + it("contains an uncaught throw in a native-initiated callback and keeps the app alive", raisesGlobalErrors(function (done) { var reason = new Error("contained-post-throw"); var received = null; onGlobal("error", function (e) { @@ -83,9 +85,9 @@ describe("uncaughtErrorPolicy (default: report)", function () { expect(1 + 1).toBe(2); done(); }); - }); + })); - it("preventDefault() on the error event suppresses the legacy hook", function (done) { + it("preventDefault() on the error event suppresses the legacy hook", raisesGlobalErrors(function (done) { var reason = new Error("contained-prevented-throw"); var ran = false; onGlobal("error", function (e) { @@ -108,9 +110,9 @@ describe("uncaughtErrorPolicy (default: report)", function () { done(); }); }); - }); + })); - it("contains an uncaught throw in a setTimeout callback", function (done) { + it("contains an uncaught throw in a setTimeout callback", raisesGlobalErrors(function (done) { var reason = new Error("contained-timer-throw"); setTimeout(function () { throw reason; @@ -121,9 +123,9 @@ describe("uncaughtErrorPolicy (default: report)", function () { expect(1 + 1).toBe(2); done(); }); - }); + })); - it("a contained throw in a primitive-returning override yields the type's default", function (done) { + it("a contained throw in a primitive-returning override yields the type's default", raisesGlobalErrors(function (done) { var reason = new Error("contained-compare-throw"); var doneRan = false; var comparator = new java.util.Comparator({ @@ -144,7 +146,7 @@ describe("uncaughtErrorPolicy (default: report)", function () { expect(uncaughtSeen(reason)).toBe(true); done(); }); - }); + })); it("propagates a JS-initiated chain back to the outer JS catch with identity", function () { var reason = new Error("chain-identity"); diff --git a/test-app/app/src/main/assets/app/tests/testUnhandledRejections.js b/test-app/app/src/main/assets/app/tests/testUnhandledRejections.js index e847aa6d0..ab65ebfd3 100644 --- a/test-app/app/src/main/assets/app/tests/testUnhandledRejections.js +++ b/test-app/app/src/main/assets/app/tests/testUnhandledRejections.js @@ -1,3 +1,5 @@ +const { raisesGlobalErrors } = require("../Infrastructure/Jasmine/globalErrorSpy"); + describe("unhandled promise rejections", function () { // Unhandled rejections are tracked per-isolate and reported once per // looper turn through the same uncaught-error machinery exposed via @@ -57,14 +59,14 @@ describe("unhandled promise rejections", function () { }, 20); } - it("reports an unhandled Promise.reject through __onUncaughtError", function (done) { + it("reports an unhandled Promise.reject through __onUncaughtError", raisesGlobalErrors(function (done) { var reason = new Error("unhandled-promise-reject"); Promise.reject(reason); pollUntil(function () { return reportedSeen(reason); }, function () { expect(reportedSeen(reason)).toBe(true); done(); }); - }); + })); it("does not report when .catch is attached synchronously in the same turn", function (done) { var reason = new Error("handled-same-turn"); @@ -76,7 +78,7 @@ describe("unhandled promise rejections", function () { }); }); - it("reports an uncaught throw from an async function", function (done) { + it("reports an uncaught throw from an async function", raisesGlobalErrors(function (done) { var reason = new Error("async-function-throw"); (async () => { throw reason; @@ -85,9 +87,9 @@ describe("unhandled promise rejections", function () { expect(reportedSeen(reason)).toBe(true); done(); }); - }); + })); - it("reports an unhandled rejection thrown from a .then callback", function (done) { + it("reports an unhandled rejection thrown from a .then callback", raisesGlobalErrors(function (done) { var reason = new Error("then-callback-throw"); Promise.resolve().then(function () { throw reason; @@ -96,9 +98,9 @@ describe("unhandled promise rejections", function () { expect(reportedSeen(reason)).toBe(true); done(); }); - }); + })); - it("ignores a late .catch attached after the rejection was already reported", function (done) { + it("ignores a late .catch attached after the rejection was already reported", raisesGlobalErrors(function (done) { var reason = new Error("late-catch"); var p = Promise.reject(reason); pollUntil(function () { return reportedSeen(reason); }, function () { @@ -112,9 +114,9 @@ describe("unhandled promise rejections", function () { done(); }); }); - }); + })); - it("unhandledrejection listener receives reason and promise; preventDefault suppresses the hook", function (done) { + it("unhandledrejection listener receives reason and promise; preventDefault suppresses the hook", raisesGlobalErrors(function (done) { var reason = new Error("rejected-with-listener"); var received = null; onGlobal("unhandledrejection", function (e) { @@ -141,9 +143,9 @@ describe("unhandled promise rejections", function () { done(); }); }); - }); + })); - it("fires rejectionhandled when a handler is attached after the rejection was reported", function (done) { + it("fires rejectionhandled when a handler is attached after the rejection was reported", raisesGlobalErrors(function (done) { var reason = new Error("late-handler"); var rejectionHandled = null; onGlobal("rejectionhandled", function (e) { @@ -181,5 +183,5 @@ describe("unhandled promise rejections", function () { }); }, 20); }); - }); + })); }); diff --git a/test-app/app/src/main/assets/app/tests/testWorkerResourceLimits.js b/test-app/app/src/main/assets/app/tests/testWorkerResourceLimits.js index 6dfeac03f..bf03867ba 100644 --- a/test-app/app/src/main/assets/app/tests/testWorkerResourceLimits.js +++ b/test-app/app/src/main/assets/app/tests/testWorkerResourceLimits.js @@ -61,9 +61,11 @@ describe("Worker resourceLimits", function () { expect("worker posted a message").toBe("worker exhausted its heap"); }; + // Returns true so the error is not re-reported on the main global scope, + // where Jasmine would charge it to whatever spec runs next. worker.onerror = function (e) { if (settled) { - return; + return true; } settled = true; var message = String(e && e.message ? e.message : e); @@ -73,6 +75,7 @@ describe("Worker resourceLimits", function () { expect(message).toMatch(/maxOldGenerationSizeMb: 32/); worker.terminate(); done(); + return true; }; }); diff --git a/test-app/app/src/main/assets/app/tests/tests.js b/test-app/app/src/main/assets/app/tests/tests.js index 6475937e9..e2e4f0166 100644 --- a/test-app/app/src/main/assets/app/tests/tests.js +++ b/test-app/app/src/main/assets/app/tests/tests.js @@ -927,9 +927,9 @@ describe("Tests ", function () { expect(s).toBe("1, test, false"); }); - it("TestCallMethodWithByteParameter", function () { + it("TestCallByteValueOfWithByteParameter", function () { - __log("TEST: TestCallMethodWithByteParameter"); + __log("TEST: TestCallByteValueOfWithByteParameter"); var b = java.lang.Byte.valueOf(byte(123)); diff --git a/test-app/app/src/main/assets/app/tests/testsForTypescript.js b/test-app/app/src/main/assets/app/tests/testsForTypescript.js index fa76b7d31..ae80690ec 100644 --- a/test-app/app/src/main/assets/app/tests/testsForTypescript.js +++ b/test-app/app/src/main/assets/app/tests/testsForTypescript.js @@ -201,9 +201,9 @@ describe("Tests typescript", function () { expect(isInstanceOf).toEqual(true); }); - it("When_creating_a_typescript_instance_it_should_be_a_valid_nativescript_instance", function () { + it("When_creating_a_typescript_instance_with_constructor_arguments_it_should_be_a_valid_nativescript_instance", function () { - __log("TEST: When_creating_a_typescript_instance_it_should_be_a_valid_nativescript_instance"); + __log("TEST: When_creating_a_typescript_instance_with_constructor_arguments_it_should_be_a_valid_nativescript_instance"); var MyButtonWithArgs = (function (_super) { __extends(MyButtonWithArgs, _super); From b3961a74fd975145443e009f3df0a79a26411e52 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Wed, 11 Nov 2026 17:42:51 -0300 Subject: [PATCH 4/5] test: drop GC specs that assert full marking-mode reachability test1-test5, 'should keep extended instances alive after GC' and 'should keep array-enclosed objects alive after GC' hand a Java callback a closure over a Java object and expect it to survive a GC. Only markingMode "full" provided that, and #2093 removed it. Under "none" every one of their 13 delayed callbacks fails with 'The JavaScript instance no longer has available Java instance counterpart'; the specs are synchronous, so Jasmine 2 never saw it. --- .../app/src/main/assets/app/tests/testGC.js | 219 ------------------ 1 file changed, 219 deletions(-) diff --git a/test-app/app/src/main/assets/app/tests/testGC.js b/test-app/app/src/main/assets/app/tests/testGC.js index 43c5b9ae5..20b0b6a74 100644 --- a/test-app/app/src/main/assets/app/tests/testGC.js +++ b/test-app/app/src/main/assets/app/tests/testGC.js @@ -37,179 +37,6 @@ describe("Tests garbage collection", function () { normalTest(); }); - // this test has implicit assert in com.tns.Runtime.getJavaObjectByID method - it("test1", function () { - - function createObjects(name) { - var c1 = new com.tns.tests.Class1(); - - var cb1 = new com.tns.tests.Class1.Callback1(name, { - getMessage: function() { - var msg = c1.getMessage(); - return msg; - } - }); - - return com.tns.tests.Class1.Class2.printMessageWithDelay(cb1, 5 * 1000); - } - - expect(createObjects("Callback5")).toBe(true); - expect(createObjects("Callback26")).toBe(true); - - gc(); - java.lang.System.gc(); - }); - - // this test has implicit assert in com.tns.Runtime.getJavaObjectByID method - it("test2", function () { - - function indref1() { - this.class1 = new com.tns.tests.Class1(); - } - indref1.prototype.getMessage = function() { - return "~~~" + this.class1.getMessage(); - } - - function createObjects(name) { - var c1 = new indref1(); - - var cb1 = new com.tns.tests.Class1.Callback1(name, { - getMessage: function() { - var msg = c1.getMessage(); - return msg; - } - }); - - return com.tns.tests.Class1.Class2.printMessageWithDelay(cb1, 5 * 1000); - } - - expect(createObjects("Callback55")).toBe(true); - expect(createObjects("Callback56")).toBe(true); - gc(); - java.lang.System.gc(); - }); - - // this test has implicit assert in com.tns.Runtime.getJavaObjectByID method - it("test3", function () { - - function indref2() { - this.helper = new indref2helper(); - } - indref2.prototype.getMessage = function() { - return "---" + this.helper.getMessage(); - } - function indref2helper() { - this.class1 = new com.tns.tests.Class1(); - } - indref2helper.prototype.getMessage = function() { - return "***" + this.class1.getMessage(); - } - - function createObjects(name) { - var c1 = new indref2(); - - var cb1 = new com.tns.tests.Class1.Callback1(name, { - getMessage: function() { - var msg = c1.getMessage(); - return msg; - } - }); - - return com.tns.tests.Class1.Class2.printMessageWithDelay(cb1, 5 * 1000); - } - - expect(createObjects("Callback91")).toBe(true); - expect(createObjects("Callback92")).toBe(true); - gc(); - java.lang.System.gc(); - }); - - // this test has implicit assert in com.tns.Runtime.getJavaObjectByID method - it("test4", function () { - - function indref3() { - this.helper = new indref3helper(); - } - indref3.prototype.getMessage = function() { - return "+++" + this.helper.getMessage(); - } - function indref3helper() { - this._class1 = new com.tns.tests.Class1(); - - Object.defineProperty(this, "class1", { - get: function() { - return this._class1 - } - }); - } - indref3helper.prototype.getMessage = function() { - return "^^^" + this.class1.getMessage(); - } - - function createObjects(name) { - var c1 = new indref3(); - - var cb1 = new com.tns.tests.Class1.Callback1(name, { - getMessage: function() { - var msg = c1.getMessage(); - return msg; - } - }); - - return com.tns.tests.Class1.Class2.printMessageWithDelay(cb1, 5 * 1000); - } - - expect(createObjects("Callback1133")).toBe(true); - expect(createObjects("Callback1134")).toBe(true); - gc(); - java.lang.System.gc(); - }); - - // this test has implicit assert in com.tns.Runtime.getJavaObjectByID method - //originally test was commented out - xit("test5", function () { - - function indref4() { - this.helper = new indref4helper(); - } - indref4.prototype.getMessage = function() { - return "&&&" + this.helper.getMessageZZZ(); - } - function indref4helper() { - var _class1 = new com.tns.tests.Class1(); - - __log("indref4helper _class1=" + _class1); - - Object.defineProperty(this, "class1", { - get: function() { - return _class1 - } - ,enumerable: false - }); - } - indref4helper.prototype.getMessageZZZ = function() { - return "```" + this.class1.getMessage(); - } - - function createObjects(name) { - var c1 = new indref4(); - - var cb1 = new com.tns.tests.Class1.Callback1(name, { - getMessage: function() { - var msg = c1.getMessage(); - return msg; - } - }); - - return com.tns.tests.Class1.Class2.printMessageWithDelay(cb1, 5 * 1000); - } - - expect(createObjects("Callback1178")).toBe(true); - expect(createObjects("Callback1179")).toBe(true); - gc(); - java.lang.System.gc(); - }); - it("testAccessingStringFieldWontLeak", function () { __log("TEST: testAccessingStringFieldWontLeak"); @@ -241,52 +68,6 @@ describe("Tests garbage collection", function () { expect(o.x).toBe(123); }); - it("should keep extended instances alive after GC", function () { - var C1 = com.tns.tests.Class1.extend({}); - - function createObjects(name) { - var c1 = new C1(); - - var cb1 = new com.tns.tests.Class1.Callback1(name, { - getMessage: function() { - var msg = c1.getMessage(); - return msg; - } - }); - - return com.tns.tests.Class1.Class2.printMessageWithDelay(cb1, 5 * 1000); - } - - expect(createObjects("Callback452356")).toBe(true); - expect(createObjects("Callback417892")).toBe(true); - - gc(); - java.lang.System.gc(); - }); - - it("should keep array-enclosed objects alive after GC", function () { - function createObjects(name) { - var arr = new Array(); - arr.push(new com.tns.tests.Class1()); - - var cb1 = new com.tns.tests.Class1.Callback1(name, { - getMessage: function() { - var msg = arr[0].getMessage(); - return msg; - } - }); - - return com.tns.tests.Class1.Class2.printMessageWithDelay(cb1, 2 * 1000); - } - - expect(createObjects("Callback1")).toBe(true); - expect(createObjects("Callback2")).toBe(true); - expect(createObjects("Callback3")).toBe(true); - - gc(); - java.lang.System.gc(); - }) - it("should properly reintroduce Java object back in a callback", function () { function getTestObject() { return new com.tns.tests.BadEqualsTest( From 61745969a744a63138e707dc47a013055941f502 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Wed, 11 Nov 2026 18:32:36 -0300 Subject: [PATCH 5/5] test: harden the Jasmine run against restarts and startup failures - MyActivity registers and runs the suite once per process; a recreated activity would otherwise hit Jasmine's rejection of a second run. - When Jasmine cannot start the run, boot.js writes a failing JUnit result so the runtests harness reports it instead of timing out. - The UI-thread dispatch spec is named for what it checks: with multithreaded JavaScript on, the callback may run on the calling thread. --- .../assets/app/Infrastructure/Jasmine/boot.js | 17 +++++++++++++++-- test-app/app/src/main/assets/app/MyActivity.js | 11 +++++++++-- .../tests/dispatchCallbacksOnUiThreadTests.js | 4 ++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/boot.js b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/boot.js index a21b4d55e..f6bc7915d 100644 --- a/test-app/app/src/main/assets/app/Infrastructure/Jasmine/boot.js +++ b/test-app/app/src/main/assets/app/Infrastructure/Jasmine/boot.js @@ -36,9 +36,22 @@ env.addReporter(new TerminalReporter({ })); env.addReporter(new JUnitXmlReporter()); +// Without a results file the runtests harness only gives up after its full +// timeout, so a run that cannot start still writes one failing testcase. +function saveRunFailure(message) { + var xml = '\n' + + '\n ' + + '\n ' + + '\n ' + + '/g, ']]]]>') + ']]>' + + '\n \n \n \n'; + global.__JUnitSaveResults(xml); +} + exports.execute = function () { env.execute().catch(function (e) { - // No JUnit report follows; the runtests harness times out waiting for it. - console.log("Jasmine could not run the suite: " + ((e && e.stack) || e)); + var message = String((e && e.stack) || e); + console.log("Jasmine could not run the suite: " + message); + saveRunFailure(message); }); }; diff --git a/test-app/app/src/main/assets/app/MyActivity.js b/test-app/app/src/main/assets/app/MyActivity.js index 9ff51cf96..05f1139b7 100644 --- a/test-app/app/src/main/assets/app/MyActivity.js +++ b/test-app/app/src/main/assets/app/MyActivity.js @@ -21,6 +21,8 @@ } } */ +var testRunStarted = false; + var MyActivity = (function (_super) { __extends(MyActivity, _super); function MyActivity() { @@ -29,8 +31,13 @@ var MyActivity = (function (_super) { MyActivity.prototype.onCreate = function (bundle) { _super.prototype.onCreate.call(this, bundle); - require('./tests/testsWithContext').run(this); - require('./Infrastructure/Jasmine/boot').execute(); + // A recreated activity in the same process must not register or run the + // suite again: Jasmine rejects both once the run has started. + if (!testRunStarted) { + testRunStarted = true; + require('./tests/testsWithContext').run(this); + require('./Infrastructure/Jasmine/boot').execute(); + } var layout = new android.widget.LinearLayout(this); layout.setOrientation(1); diff --git a/test-app/app/src/main/assets/app/tests/dispatchCallbacksOnUiThreadTests.js b/test-app/app/src/main/assets/app/tests/dispatchCallbacksOnUiThreadTests.js index 828ae2cd9..cb9da41e8 100644 --- a/test-app/app/src/main/assets/app/tests/dispatchCallbacksOnUiThreadTests.js +++ b/test-app/app/src/main/assets/app/tests/dispatchCallbacksOnUiThreadTests.js @@ -1,8 +1,8 @@ describe("Tests dispatch callbacks on UI thread ", function () { - it("TestProcessDataCallbackShouldBeExecutedOnUiThread", function (done) { + it("TestProcessDataCallbackFromBackgroundThreadReceivesAllData", function (done) { - __log("TEST: TestProcessDataCallbackShouldBeExecutedOnUiThread"); + __log("TEST: TestProcessDataCallbackFromBackgroundThreadReceivesAllData"); var received = []; var D = com.tns.tests.DispatchAsyncOpOnUIThreadTest.extend("DispatchAsyncOpOnUIThreadTest", {