From 2f92cb3ff481ac1ae03f47ceb4a9660954418cd7 Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Sat, 17 Oct 2015 18:59:26 +0300 Subject: [PATCH 1/4] Implement XHR addEventListener/removeEventListener events. - Used by the Angular XHRBackend service. - Only supporting 'load' and 'error' now. - Not implemented as weak events. Client responsible for detaching or not keeping a reference to the XHR object forever. (NG XHRBackend does so) --- apps/tests/xhr-tests.ts | 29 +++++++++++++++++++++++++ xhr/xhr.ts | 48 +++++++++++++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/apps/tests/xhr-tests.ts b/apps/tests/xhr-tests.ts index 811ae4769..9756fa880 100644 --- a/apps/tests/xhr-tests.ts +++ b/apps/tests/xhr-tests.ts @@ -221,6 +221,35 @@ export function test_raises_onload_Event(done) { xhr.send(); } +export function test_xhr_events(done) { + let xhr = new XMLHttpRequest(); + + let loadCallbackFired = false, loadEventFired = false; + xhr.onload = () => loadCallbackFired = true; + let badEvent = () => { throw new Error("Shouldn't call me") } + xhr.addEventListener('load', () => loadEventFired = true); + xhr.addEventListener('load', badEvent); + xhr.removeEventListener('load', badEvent); + + xhr._errorFlag = false; + xhr._setReadyState(xhr.DONE); + TKUnit.assertTrue(loadCallbackFired); + TKUnit.assertTrue(loadEventFired); + + let errorCallbackData = null, errorEventData = null; + xhr.onerror = (e) => errorCallbackData = e; + xhr.addEventListener('error', (e) => errorEventData = e); + xhr.addEventListener('error', badEvent); + xhr.removeEventListener('error', badEvent); + + xhr._errorFlag = true; + xhr._setReadyState(xhr.DONE, 'error data'); + TKUnit.assertEqual(errorCallbackData, 'error data'); + TKUnit.assertEqual(errorEventData, 'error data'); + + done(null); +} + export function test_sets_status_and_statusText(done) { let xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { diff --git a/xhr/xhr.ts b/xhr/xhr.ts index b9dc5b2e5..f4268cfed 100644 --- a/xhr/xhr.ts +++ b/xhr/xhr.ts @@ -15,7 +15,7 @@ export class XMLHttpRequest { public DONE = 4; public onload: () => void; - public onerror: () => void; + public onerror: (any) => void; private _options: http.HttpRequestOptions; private _readyState: number; @@ -99,11 +99,36 @@ export class XMLHttpRequest { }).catch(e => { this._errorFlag = true; - this._setReadyState(this.DONE); + this._setReadyState(this.DONE, e); }); } } + private _listeners: Map> = new Map>(); + + public addEventListener(eventName: string, handler: Function) { + if (eventName !== 'load' && eventName !== 'error') { + throw new Error('Event not supported: ' + eventName); + } + + let handlers = this._listeners.get(eventName) || []; + handlers.push(handler); + this._listeners.set(eventName, handlers); + } + + public removeEventListener(eventName: string, toDetach: Function) { + let handlers = this._listeners.get(eventName) || []; + handlers = handlers.filter((handler) => handler !== toDetach); + this._listeners.set(eventName, handlers); + } + + private emitEvent(eventName: string, ...args: Array) { + let handlers = this._listeners.get(eventName) || []; + handlers.forEach((handler) => { + handler(...args); + }); + } + public setRequestHeader(header: string, value: string) { if (types.isDefined(this._options) && types.isString(header) && types.isString(value)) { this._options.headers[header] = value; @@ -158,7 +183,7 @@ export class XMLHttpRequest { } } - private _setReadyState(value: number) { + private _setReadyState(value: number, error?: any) { if (this._readyState !== value) { this._readyState = value; @@ -168,11 +193,16 @@ export class XMLHttpRequest { } if (this._readyState === this.DONE) { - if (this._errorFlag && types.isFunction(this.onerror)) { - this.onerror(); - } - if (!this._errorFlag && types.isFunction(this.onload)) { - this.onload(); + if (this._errorFlag) { + if (types.isFunction(this.onerror)) { + this.onerror(error); + } + this.emitEvent('error', error); + } else { + if (types.isFunction(this.onload)) { + this.onload(); + } + this.emitEvent('load'); } } } @@ -264,4 +294,4 @@ export class FormData { return arr.join("&"); } -} \ No newline at end of file +} From 75796ea14cb22650b137ec6f3f98867125b23334 Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Sat, 17 Oct 2015 19:04:00 +0300 Subject: [PATCH 2/4] Ignore request body for XHR GET requests. Raises an error on Android. Caused by the Angular XHR backend sending an empty string. --- apps/tests/xhr-tests.ts | 8 ++++++++ xhr/xhr.ts | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/tests/xhr-tests.ts b/apps/tests/xhr-tests.ts index 9756fa880..cab72997f 100644 --- a/apps/tests/xhr-tests.ts +++ b/apps/tests/xhr-tests.ts @@ -212,6 +212,14 @@ export var test_XMLHttpRequest_requestShouldBePossibleAfterAbort = function (don xhr.send(JSON.stringify({ MyVariableOne: "ValueOne", MyVariableTwo: "ValueTwo" })); }; +export function test_ignore_zero_length_request_body(done) { + let xhr = new XMLHttpRequest(); + xhr.open("GET", "https://httpbin.org/get"); + + xhr.send(''); + done(null); +} + export function test_raises_onload_Event(done) { let xhr = new XMLHttpRequest(); xhr.onload = () => { diff --git a/xhr/xhr.ts b/xhr/xhr.ts index f4268cfed..bd07470c1 100644 --- a/xhr/xhr.ts +++ b/xhr/xhr.ts @@ -72,7 +72,10 @@ export class XMLHttpRequest { this._status = null; if (types.isDefined(this._options)) { - if (types.isString(data)) { + if (types.isString(data) && this._options.method !== 'GET') { + //The Android Java HTTP lib throws an exception if we provide a + //a request body for GET requests, so we avoid doing that. + //Browser implementations silently ignore it as well. this._options.content = data; } else if (data instanceof FormData) { this._options.content = (data).toString(); From 7e6921c6332f9e84a82e25953a2cd8f42f197b5e Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Mon, 19 Oct 2015 09:51:52 +0300 Subject: [PATCH 3/4] Make two XHR tests non-async. --- apps/tests/xhr-tests.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/tests/xhr-tests.ts b/apps/tests/xhr-tests.ts index cab72997f..2663b72ee 100644 --- a/apps/tests/xhr-tests.ts +++ b/apps/tests/xhr-tests.ts @@ -212,12 +212,11 @@ export var test_XMLHttpRequest_requestShouldBePossibleAfterAbort = function (don xhr.send(JSON.stringify({ MyVariableOne: "ValueOne", MyVariableTwo: "ValueTwo" })); }; -export function test_ignore_zero_length_request_body(done) { +export function test_ignore_zero_length_request_body() { let xhr = new XMLHttpRequest(); xhr.open("GET", "https://httpbin.org/get"); xhr.send(''); - done(null); } export function test_raises_onload_Event(done) { @@ -229,7 +228,7 @@ export function test_raises_onload_Event(done) { xhr.send(); } -export function test_xhr_events(done) { +export function test_xhr_events() { let xhr = new XMLHttpRequest(); let loadCallbackFired = false, loadEventFired = false; @@ -254,8 +253,6 @@ export function test_xhr_events(done) { xhr._setReadyState(xhr.DONE, 'error data'); TKUnit.assertEqual(errorCallbackData, 'error data'); TKUnit.assertEqual(errorEventData, 'error data'); - - done(null); } export function test_sets_status_and_statusText(done) { From f01a28f9eb2fee1556d70a2499bb79ac2d1a9d04 Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Thu, 29 Oct 2015 15:47:03 +0200 Subject: [PATCH 4/4] Show the test results in a TextView at the end of the test run. --- apps/tests/testRunner.ts | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/tests/testRunner.ts b/apps/tests/testRunner.ts index 10e40c628..f3888245b 100644 --- a/apps/tests/testRunner.ts +++ b/apps/tests/testRunner.ts @@ -1,11 +1,12 @@ /* tslint:disable */ -import TKUnit = require("./TKUnit"); -import trace = require("trace"); -import frameModule = require("ui/frame"); -import platform = require("platform"); -import uiTestModule = require("./ui-test"); +import * as TKUnit from"./TKUnit"; +import {messageType} from "trace"; +import {topmost, Frame} from "ui/frame"; +import {TextView} from "ui/text-view"; +import * as platform from "platform"; +import "./ui-test"; -frameModule.Frame.defaultAnimatedNavigation = false; +Frame.defaultAnimatedNavigation = false; export function isRunningOnEmulator(): boolean { // This checks are not good enough to be added to modules but keeps unittests green. @@ -123,22 +124,29 @@ function printRunTestStats() { } } } - TKUnit.write("=== ALL TESTS COMPLETE === " + (testsCount - failedTestCount) + " OK, " + failedTestCount + " failed", trace.messageType.info); + let finalMessage = "=== ALL TESTS COMPLETE === \n" + (testsCount - failedTestCount) + " OK, " + failedTestCount + " failed" + "\n"; + TKUnit.write(finalMessage, messageType.info); for (j = 0; j < failedTestInfo.length; j++) { - TKUnit.write(failedTestInfo[j], trace.messageType.error); + let failureMessage = failedTestInfo[j]; + TKUnit.write(failureMessage, messageType.error); + finalMessage += "\n" + failureMessage; } + + let messageContainer = new TextView(); + messageContainer.text = finalMessage; + topmost().currentPage.content = messageContainer; } function startLog(): void { let testsName: string = this.name; - TKUnit.write("START " + testsName + " TESTS.", trace.messageType.info); + TKUnit.write("START " + testsName + " TESTS.", messageType.info); this.start = TKUnit.time(); } function log(): void { let testsName: string = this.name; let duration = TKUnit.time() - this.start; - TKUnit.write(testsName + " COMPLETED for " + duration, trace.messageType.info); + TKUnit.write(testsName + " COMPLETED for " + duration, messageType.info); } export var runAll = function (moduleName?: string) {