refactor(http): move http files to top-level module
Closes #2680 Closes #3417
This commit is contained in:
@ -12,7 +12,6 @@ export * from './change_detection';
|
||||
export * from './core';
|
||||
export * from './di';
|
||||
export * from './directives';
|
||||
export * from './http';
|
||||
export * from './forms';
|
||||
export * from './render';
|
||||
export * from './profile';
|
||||
|
@ -3,7 +3,6 @@ export * from './change_detection';
|
||||
export * from './core';
|
||||
export * from './di';
|
||||
export * from './directives';
|
||||
export * from './http';
|
||||
export * from './forms';
|
||||
export * from './render';
|
||||
export * from './profile';
|
||||
|
@ -12,6 +12,5 @@ export * from './change_detection';
|
||||
export * from './core';
|
||||
export * from './di';
|
||||
export * from './directives';
|
||||
export * from './http';
|
||||
export * from './forms';
|
||||
export * from './render';
|
||||
|
@ -1,80 +0,0 @@
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* The http module provides services to perform http requests. To get started, see the {@link Http}
|
||||
* class.
|
||||
*/
|
||||
import {bind, Binding} from 'angular2/di';
|
||||
import {Http, Jsonp} from 'angular2/src/http/http';
|
||||
import {XHRBackend, XHRConnection} from 'angular2/src/http/backends/xhr_backend';
|
||||
import {JSONPBackend, JSONPConnection} from 'angular2/src/http/backends/jsonp_backend';
|
||||
import {BrowserXhr} from 'angular2/src/http/backends/browser_xhr';
|
||||
import {BrowserJsonp} from 'angular2/src/http/backends/browser_jsonp';
|
||||
import {BaseRequestOptions, RequestOptions} from 'angular2/src/http/base_request_options';
|
||||
import {ConnectionBackend} from 'angular2/src/http/interfaces';
|
||||
import {BaseResponseOptions, ResponseOptions} from 'angular2/src/http/base_response_options';
|
||||
|
||||
export {MockConnection, MockBackend} from 'angular2/src/http/backends/mock_backend';
|
||||
export {Request} from 'angular2/src/http/static_request';
|
||||
export {Response} from 'angular2/src/http/static_response';
|
||||
|
||||
export {
|
||||
IRequestOptions,
|
||||
IResponseOptions,
|
||||
Connection,
|
||||
ConnectionBackend
|
||||
} from 'angular2/src/http/interfaces';
|
||||
|
||||
export {BrowserXhr} from 'angular2/src/http/backends/browser_xhr';
|
||||
export {BaseRequestOptions, RequestOptions} from 'angular2/src/http/base_request_options';
|
||||
export {BaseResponseOptions, ResponseOptions} from 'angular2/src/http/base_response_options';
|
||||
export {XHRBackend, XHRConnection} from 'angular2/src/http/backends/xhr_backend';
|
||||
export {JSONPBackend, JSONPConnection} from 'angular2/src/http/backends/jsonp_backend';
|
||||
export {Http, Jsonp} from 'angular2/src/http/http';
|
||||
|
||||
export {Headers} from 'angular2/src/http/headers';
|
||||
|
||||
export {
|
||||
ResponseTypes,
|
||||
ReadyStates,
|
||||
RequestMethods,
|
||||
RequestCredentialsOpts,
|
||||
RequestCacheOpts,
|
||||
RequestModesOpts
|
||||
} from 'angular2/src/http/enums';
|
||||
export {URLSearchParams} from 'angular2/src/http/url_search_params';
|
||||
|
||||
/**
|
||||
* Provides a basic set of injectables to use the {@link Http} service in any application.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {httpInjectables, Http} from 'angular2/http';
|
||||
* @Component({selector: 'http-app', viewBindings: [httpInjectables]})
|
||||
* @View({template: '{{data}}'})
|
||||
* class MyApp {
|
||||
* constructor(http:Http) {
|
||||
* http.request('data.txt').subscribe(res => this.data = res.text());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
export var httpInjectables: List<any> = [
|
||||
bind(ConnectionBackend)
|
||||
.toClass(XHRBackend),
|
||||
BrowserXhr,
|
||||
bind(RequestOptions).toClass(BaseRequestOptions),
|
||||
bind(ResponseOptions).toClass(BaseResponseOptions),
|
||||
Http
|
||||
];
|
||||
|
||||
export var jsonpInjectables: List<any> = [
|
||||
bind(ConnectionBackend)
|
||||
.toClass(JSONPBackend),
|
||||
BrowserJsonp,
|
||||
bind(RequestOptions).toClass(BaseRequestOptions),
|
||||
bind(ResponseOptions).toClass(BaseResponseOptions),
|
||||
Jsonp
|
||||
];
|
@ -1,62 +0,0 @@
|
||||
library angular2.src.http.backends.browser_jsonp;
|
||||
|
||||
import 'package:angular2/di.dart';
|
||||
import 'dart:html' show document;
|
||||
import 'dart:js' show context, JsObject, JsArray;
|
||||
|
||||
int _nextRequestId = 0;
|
||||
const JSONP_HOME = '__ng_jsonp__';
|
||||
|
||||
var _jsonpConnections = null;
|
||||
|
||||
JsObject _getJsonpConnections() {
|
||||
if (_jsonpConnections == null) {
|
||||
_jsonpConnections = context[JSONP_HOME] = new JsObject(context['Object']);
|
||||
}
|
||||
return _jsonpConnections;
|
||||
}
|
||||
|
||||
// Make sure not to evaluate this in a non-browser environment!
|
||||
@Injectable()
|
||||
class BrowserJsonp {
|
||||
// Construct a <script> element with the specified URL
|
||||
dynamic build(String url) {
|
||||
var node = document.createElement('script');
|
||||
node.src = url;
|
||||
return node;
|
||||
}
|
||||
|
||||
nextRequestID() {
|
||||
return "__req${_nextRequestId++}";
|
||||
}
|
||||
|
||||
requestCallback(String id) {
|
||||
return """${JSONP_HOME}.${id}.finished""";
|
||||
}
|
||||
|
||||
exposeConnection(String id, dynamic connection) {
|
||||
var connections = _getJsonpConnections();
|
||||
var wrapper = new JsObject(context['Object']);
|
||||
|
||||
wrapper['_id'] = id;
|
||||
wrapper['__dart__'] = connection;
|
||||
wrapper['finished'] = ([dynamic data]) => connection.finished(data);
|
||||
|
||||
connections[id] = wrapper;
|
||||
}
|
||||
|
||||
removeConnection(String id) {
|
||||
var connections = _getJsonpConnections();
|
||||
connections[id] = null;
|
||||
}
|
||||
|
||||
// Attach the <script> element to the DOM
|
||||
send(dynamic node) {
|
||||
document.body.append(node);
|
||||
}
|
||||
|
||||
// Remove <script> element from the DOM
|
||||
cleanup(dynamic node) {
|
||||
node.remove();
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {global} from 'angular2/src/facade/lang';
|
||||
|
||||
let _nextRequestId = 0;
|
||||
export const JSONP_HOME = '__ng_jsonp__';
|
||||
var _jsonpConnections = null;
|
||||
|
||||
function _getJsonpConnections(): {[key: string]: any} {
|
||||
if (_jsonpConnections === null) {
|
||||
_jsonpConnections = global[JSONP_HOME] = {};
|
||||
}
|
||||
return _jsonpConnections;
|
||||
}
|
||||
|
||||
// Make sure not to evaluate this in a non-browser environment!
|
||||
@Injectable()
|
||||
export class BrowserJsonp {
|
||||
// Construct a <script> element with the specified URL
|
||||
build(url: string): any {
|
||||
let node = document.createElement('script');
|
||||
node.src = url;
|
||||
return node;
|
||||
}
|
||||
|
||||
nextRequestID(): string { return `__req${_nextRequestId++}`; }
|
||||
|
||||
requestCallback(id: string): string { return `${JSONP_HOME}.${id}.finished`; }
|
||||
|
||||
exposeConnection(id: string, connection: any) {
|
||||
let connections = _getJsonpConnections();
|
||||
connections[id] = connection;
|
||||
}
|
||||
|
||||
removeConnection(id: string) {
|
||||
var connections = _getJsonpConnections();
|
||||
connections[id] = null;
|
||||
}
|
||||
|
||||
// Attach the <script> element to the DOM
|
||||
send(node: any) { document.body.appendChild(<Node>(node)); }
|
||||
|
||||
// Remove <script> element from the DOM
|
||||
cleanup(node: any) {
|
||||
if (node.parentNode) {
|
||||
node.parentNode.removeChild(<Node>(node));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
library angular2.src.http.backends.browser_xhr;
|
||||
|
||||
import 'dart:html' show HttpRequest;
|
||||
import 'package:angular2/di.dart';
|
||||
|
||||
@Injectable()
|
||||
class BrowserXhr {
|
||||
HttpRequest build() {
|
||||
return new HttpRequest();
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
|
||||
// Make sure not to evaluate this in a non-browser environment!
|
||||
@Injectable()
|
||||
export class BrowserXhr {
|
||||
constructor() {}
|
||||
build(): any { return <any>(new XMLHttpRequest()); }
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
import {ConnectionBackend, Connection} from '../interfaces';
|
||||
import {ReadyStates, RequestMethods, RequestMethodsMap} from '../enums';
|
||||
import {Request} from '../static_request';
|
||||
import {Response} from '../static_response';
|
||||
import {ResponseOptions, BaseResponseOptions} from '../base_response_options';
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {BrowserJsonp} from './browser_jsonp';
|
||||
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||
import {StringWrapper, isPresent, ENUM_INDEX, makeTypeError} from 'angular2/src/facade/lang';
|
||||
|
||||
export class JSONPConnection implements Connection {
|
||||
readyState: ReadyStates;
|
||||
request: Request;
|
||||
response: EventEmitter;
|
||||
private _id: string;
|
||||
private _script: Element;
|
||||
private _responseData: any;
|
||||
private _finished: boolean = false;
|
||||
|
||||
constructor(req: Request, private _dom: BrowserJsonp,
|
||||
private baseResponseOptions?: ResponseOptions) {
|
||||
if (req.method !== RequestMethods.GET) {
|
||||
throw makeTypeError("JSONP requests must use GET request method.");
|
||||
}
|
||||
this.request = req;
|
||||
this.response = new EventEmitter();
|
||||
this.readyState = ReadyStates.LOADING;
|
||||
this._id = _dom.nextRequestID();
|
||||
|
||||
_dom.exposeConnection(this._id, this);
|
||||
|
||||
// Workaround Dart
|
||||
// url = url.replace(/=JSONP_CALLBACK(&|$)/, `generated method`);
|
||||
let callback = _dom.requestCallback(this._id);
|
||||
let url: string = req.url;
|
||||
if (url.indexOf('=JSONP_CALLBACK&') > -1) {
|
||||
url = StringWrapper.replace(url, '=JSONP_CALLBACK&', `=${callback}&`);
|
||||
} else if (url.lastIndexOf('=JSONP_CALLBACK') === url.length - '=JSONP_CALLBACK'.length) {
|
||||
url = StringWrapper.substring(url, 0, url.length - '=JSONP_CALLBACK'.length) + `=${callback}`;
|
||||
}
|
||||
|
||||
let script = this._script = _dom.build(url);
|
||||
|
||||
script.addEventListener('load', (event) => {
|
||||
if (this.readyState === ReadyStates.CANCELLED) return;
|
||||
this.readyState = ReadyStates.DONE;
|
||||
_dom.cleanup(script);
|
||||
if (!this._finished) {
|
||||
ObservableWrapper.callThrow(
|
||||
this.response, makeTypeError('JSONP injected script did not invoke callback.'));
|
||||
return;
|
||||
}
|
||||
|
||||
let responseOptions = new ResponseOptions({body: this._responseData});
|
||||
if (isPresent(this.baseResponseOptions)) {
|
||||
responseOptions = this.baseResponseOptions.merge(responseOptions);
|
||||
}
|
||||
|
||||
ObservableWrapper.callNext(this.response, new Response(responseOptions));
|
||||
});
|
||||
|
||||
script.addEventListener('error', (error) => {
|
||||
if (this.readyState === ReadyStates.CANCELLED) return;
|
||||
this.readyState = ReadyStates.DONE;
|
||||
_dom.cleanup(script);
|
||||
ObservableWrapper.callThrow(this.response, error);
|
||||
});
|
||||
|
||||
_dom.send(script);
|
||||
}
|
||||
|
||||
finished(data?: any) {
|
||||
// Don't leak connections
|
||||
this._finished = true;
|
||||
this._dom.removeConnection(this._id);
|
||||
if (this.readyState === ReadyStates.CANCELLED) return;
|
||||
this._responseData = data;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.readyState = ReadyStates.CANCELLED;
|
||||
let script = this._script;
|
||||
this._script = null;
|
||||
if (isPresent(script)) {
|
||||
this._dom.cleanup(script);
|
||||
}
|
||||
ObservableWrapper.callReturn(this.response);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class JSONPBackend implements ConnectionBackend {
|
||||
constructor(private _browserJSONP: BrowserJsonp, private _baseResponseOptions: ResponseOptions) {}
|
||||
createConnection(request: Request): JSONPConnection {
|
||||
return new JSONPConnection(request, this._browserJSONP, this._baseResponseOptions);
|
||||
}
|
||||
}
|
@ -1,228 +0,0 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {Request} from 'angular2/src/http/static_request';
|
||||
import {Response} from 'angular2/src/http/static_response';
|
||||
import {ReadyStates} from 'angular2/src/http/enums';
|
||||
import {Connection, ConnectionBackend} from 'angular2/src/http/interfaces';
|
||||
import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {IMPLEMENTS, BaseException} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
*
|
||||
* Mock Connection to represent a {@link Connection} for tests.
|
||||
*
|
||||
**/
|
||||
@IMPLEMENTS(Connection)
|
||||
export class MockConnection {
|
||||
// TODO Name `readyState` should change to be more generic, and states could be made to be more
|
||||
// descriptive than XHR states.
|
||||
/**
|
||||
* Describes the state of the connection, based on `XMLHttpRequest.readyState`, but with
|
||||
* additional states. For example, state 5 indicates an aborted connection.
|
||||
*/
|
||||
readyState: ReadyStates;
|
||||
|
||||
/**
|
||||
* {@link Request} instance used to create the connection.
|
||||
*/
|
||||
request: Request;
|
||||
|
||||
/**
|
||||
* {@link EventEmitter} of {@link Response}. Can be subscribed to in order to be notified when a
|
||||
* response is available.
|
||||
*/
|
||||
response: EventEmitter;
|
||||
|
||||
constructor(req: Request) {
|
||||
this.response = new EventEmitter();
|
||||
this.readyState = ReadyStates.OPEN;
|
||||
this.request = req;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the `readyState` of the connection to a custom state of 5 (cancelled).
|
||||
*/
|
||||
dispose() {
|
||||
if (this.readyState !== ReadyStates.DONE) {
|
||||
this.readyState = ReadyStates.CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a mock response to the connection. This response is the value that is emitted to the
|
||||
* {@link EventEmitter} returned by {@link Http}.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* var connection;
|
||||
* backend.connections.subscribe(c => connection = c);
|
||||
* http.request('data.json').subscribe(res => console.log(res.text()));
|
||||
* connection.mockRespond(new Response('fake response')); //logs 'fake response'
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
mockRespond(res: Response) {
|
||||
if (this.readyState === ReadyStates.DONE || this.readyState === ReadyStates.CANCELLED) {
|
||||
throw new BaseException('Connection has already been resolved');
|
||||
}
|
||||
this.readyState = ReadyStates.DONE;
|
||||
ObservableWrapper.callNext(this.response, res);
|
||||
ObservableWrapper.callReturn(this.response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Not yet implemented!
|
||||
*
|
||||
* Sends the provided {@link Response} to the `downloadObserver` of the `Request`
|
||||
* associated with this connection.
|
||||
*/
|
||||
mockDownload(res: Response) {
|
||||
// this.request.downloadObserver.onNext(res);
|
||||
// if (res.bytesLoaded === res.totalBytes) {
|
||||
// this.request.downloadObserver.onCompleted();
|
||||
// }
|
||||
}
|
||||
|
||||
// TODO(jeffbcross): consider using Response type
|
||||
/**
|
||||
* Emits the provided error object as an error to the {@link Response} {@link EventEmitter}
|
||||
* returned
|
||||
* from {@link Http}.
|
||||
*/
|
||||
mockError(err?: Error) {
|
||||
// Matches XHR semantics
|
||||
this.readyState = ReadyStates.DONE;
|
||||
ObservableWrapper.callThrow(this.response, err);
|
||||
ObservableWrapper.callReturn(this.response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A mock backend for testing the {@link Http} service.
|
||||
*
|
||||
* This class can be injected in tests, and should be used to override bindings
|
||||
* to other backends, such as {@link XHRBackend}.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {MockBackend, DefaultOptions, Http} from 'angular2/http';
|
||||
* it('should get some data', inject([AsyncTestCompleter], (async) => {
|
||||
* var connection;
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* MockBackend,
|
||||
* bind(Http).toFactory((backend, defaultOptions) => {
|
||||
* return new Http(backend, defaultOptions)
|
||||
* }, [MockBackend, DefaultOptions])]);
|
||||
* var http = injector.get(Http);
|
||||
* var backend = injector.get(MockBackend);
|
||||
* //Assign any newly-created connection to local variable
|
||||
* backend.connections.subscribe(c => connection = c);
|
||||
* http.request('data.json').subscribe((res) => {
|
||||
* expect(res.text()).toBe('awesome');
|
||||
* async.done();
|
||||
* });
|
||||
* connection.mockRespond(new Response('awesome'));
|
||||
* }));
|
||||
* ```
|
||||
*
|
||||
* This method only exists in the mock implementation, not in real Backends.
|
||||
**/
|
||||
@Injectable()
|
||||
@IMPLEMENTS(ConnectionBackend)
|
||||
export class MockBackend {
|
||||
/**
|
||||
* {@link EventEmitter}
|
||||
* of {@link MockConnection} instances that have been created by this backend. Can be subscribed
|
||||
* to in order to respond to connections.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {MockBackend, Http, BaseRequestOptions} from 'angular2/http';
|
||||
* import {Injector} from 'angular2/di';
|
||||
*
|
||||
* it('should get a response', () => {
|
||||
* var connection; //this will be set when a new connection is emitted from the backend.
|
||||
* var text; //this will be set from mock response
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* MockBackend,
|
||||
* bind(Http).toFactory(backend, options) {
|
||||
* return new Http(backend, options);
|
||||
* }, [MockBackend, BaseRequestOptions]]);
|
||||
* var backend = injector.get(MockBackend);
|
||||
* var http = injector.get(Http);
|
||||
* backend.connections.subscribe(c => connection = c);
|
||||
* http.request('something.json').subscribe(res => {
|
||||
* text = res.text();
|
||||
* });
|
||||
* connection.mockRespond(new Response({body: 'Something'}));
|
||||
* expect(text).toBe('Something');
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This property only exists in the mock implementation, not in real Backends.
|
||||
*/
|
||||
connections: EventEmitter; //<MockConnection>
|
||||
|
||||
/**
|
||||
* An array representation of `connections`. This array will be updated with each connection that
|
||||
* is created by this backend.
|
||||
*
|
||||
* This property only exists in the mock implementation, not in real Backends.
|
||||
*/
|
||||
connectionsArray: Array<MockConnection>;
|
||||
/**
|
||||
* {@link EventEmitter} of {@link MockConnection} instances that haven't yet been resolved (i.e.
|
||||
* with a `readyState`
|
||||
* less than 4). Used internally to verify that no connections are pending via the
|
||||
* `verifyNoPendingRequests` method.
|
||||
*
|
||||
* This property only exists in the mock implementation, not in real Backends.
|
||||
*/
|
||||
pendingConnections: EventEmitter; //<MockConnection>
|
||||
constructor() {
|
||||
this.connectionsArray = [];
|
||||
this.connections = new EventEmitter();
|
||||
ObservableWrapper.subscribe<MockConnection>(
|
||||
this.connections, connection => this.connectionsArray.push(connection));
|
||||
this.pendingConnections = new EventEmitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks all connections, and raises an exception if any connection has not received a response.
|
||||
*
|
||||
* This method only exists in the mock implementation, not in real Backends.
|
||||
*/
|
||||
verifyNoPendingRequests() {
|
||||
let pending = 0;
|
||||
ObservableWrapper.subscribe(this.pendingConnections, c => pending++);
|
||||
if (pending > 0) throw new BaseException(`${pending} pending connections to be resolved`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used in conjunction with `verifyNoPendingRequests` to resolve any not-yet-resolve
|
||||
* connections, if it's expected that there are connections that have not yet received a response.
|
||||
*
|
||||
* This method only exists in the mock implementation, not in real Backends.
|
||||
*/
|
||||
resolveAllConnections() {
|
||||
ObservableWrapper.subscribe<MockConnection>(this.connections, c => c.readyState = 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MockConnection}. This is equivalent to calling `new
|
||||
* MockConnection()`, except that it also will emit the new `Connection` to the `connections`
|
||||
* emitter of this `MockBackend` instance. This method will usually only be used by tests
|
||||
* against the framework itself, not by end-users.
|
||||
*/
|
||||
createConnection(req: Request): Connection {
|
||||
if (!isPresent(req) || !(req instanceof Request)) {
|
||||
throw new BaseException(`createConnection requires an instance of Request, got ${req}`);
|
||||
}
|
||||
let connection = new MockConnection(req);
|
||||
ObservableWrapper.callNext(this.connections, connection);
|
||||
return connection;
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
import {ConnectionBackend, Connection} from '../interfaces';
|
||||
import {ReadyStates, RequestMethods, RequestMethodsMap} from '../enums';
|
||||
import {Request} from '../static_request';
|
||||
import {Response} from '../static_response';
|
||||
import {ResponseOptions, BaseResponseOptions} from '../base_response_options';
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {BrowserXhr} from './browser_xhr';
|
||||
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||
import {isPresent, ENUM_INDEX} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* Creates connections using `XMLHttpRequest`. Given a fully-qualified
|
||||
* request, an `XHRConnection` will immediately create an `XMLHttpRequest` object and send the
|
||||
* request.
|
||||
*
|
||||
* This class would typically not be created or interacted with directly inside applications, though
|
||||
* the {@link MockConnection} may be interacted with in tests.
|
||||
*/
|
||||
export class XHRConnection implements Connection {
|
||||
request: Request;
|
||||
/**
|
||||
* Response {@link EventEmitter} which emits a single {@link Response} value on load event of
|
||||
* `XMLHttpRequest`.
|
||||
*/
|
||||
response: EventEmitter; // TODO: Make generic of <Response>;
|
||||
readyState: ReadyStates;
|
||||
private _xhr; // TODO: make type XMLHttpRequest, pending resolution of
|
||||
// https://github.com/angular/ts2dart/issues/230
|
||||
constructor(req: Request, browserXHR: BrowserXhr, baseResponseOptions?: ResponseOptions) {
|
||||
// TODO: get rid of this when enum lookups are available in ts2dart
|
||||
// https://github.com/angular/ts2dart/issues/221
|
||||
var requestMethodsMap = new RequestMethodsMap();
|
||||
this.request = req;
|
||||
this.response = new EventEmitter();
|
||||
this._xhr = browserXHR.build();
|
||||
// TODO(jeffbcross): implement error listening/propagation
|
||||
this._xhr.open(requestMethodsMap.getMethod(ENUM_INDEX(req.method)), req.url);
|
||||
this._xhr.addEventListener('load', (_) => {
|
||||
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
|
||||
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
|
||||
let response = isPresent(this._xhr.response) ? this._xhr.response : this._xhr.responseText;
|
||||
|
||||
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
|
||||
let status = this._xhr.status === 1223 ? 204 : this._xhr.status;
|
||||
|
||||
// fix status code when it is 0 (0 status is undocumented).
|
||||
// Occurs when accessing file resources or on Android 4.1 stock browser
|
||||
// while retrieving files from application cache.
|
||||
if (status === 0) {
|
||||
status = response ? 200 : 0;
|
||||
}
|
||||
|
||||
var responseOptions = new ResponseOptions({body: response, status: status});
|
||||
if (isPresent(baseResponseOptions)) {
|
||||
responseOptions = baseResponseOptions.merge(responseOptions);
|
||||
}
|
||||
|
||||
ObservableWrapper.callNext(this.response, new Response(responseOptions));
|
||||
// TODO(gdi2290): defer complete if array buffer until done
|
||||
ObservableWrapper.callReturn(this.response);
|
||||
});
|
||||
// TODO(jeffbcross): make this more dynamic based on body type
|
||||
|
||||
if (isPresent(req.headers)) {
|
||||
req.headers.forEach((value, name) => { this._xhr.setRequestHeader(name, value); });
|
||||
}
|
||||
|
||||
this._xhr.send(this.request.text());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls abort on the underlying XMLHttpRequest.
|
||||
*/
|
||||
dispose(): void { this._xhr.abort(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link XHRConnection} instances.
|
||||
*
|
||||
* This class would typically not be used by end users, but could be
|
||||
* overridden if a different backend implementation should be used,
|
||||
* such as in a node backend.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {Http, MyNodeBackend, httpInjectables, BaseRequestOptions} from 'angular2/http';
|
||||
* @Component({
|
||||
* viewBindings: [
|
||||
* httpInjectables,
|
||||
* bind(Http).toFactory((backend, options) => {
|
||||
* return new Http(backend, options);
|
||||
* }, [MyNodeBackend, BaseRequestOptions])]
|
||||
* })
|
||||
* class MyComponent {
|
||||
* constructor(http:Http) {
|
||||
* http('people.json').subscribe(res => this.people = res.json());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
**/
|
||||
@Injectable()
|
||||
export class XHRBackend implements ConnectionBackend {
|
||||
constructor(private _browserXHR: BrowserXhr, private _baseResponseOptions: ResponseOptions) {}
|
||||
createConnection(request: Request): XHRConnection {
|
||||
return new XHRConnection(request, this._browserXHR, this._baseResponseOptions);
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
import {CONST_EXPR, CONST, isPresent, isString} from 'angular2/src/facade/lang';
|
||||
import {Headers} from './headers';
|
||||
import {RequestModesOpts, RequestMethods, RequestCacheOpts, RequestCredentialsOpts} from './enums';
|
||||
import {IRequestOptions} from './interfaces';
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {URLSearchParams} from './url_search_params';
|
||||
|
||||
/**
|
||||
* Creates a request options object similar to the `RequestInit` description
|
||||
* in the [Fetch
|
||||
* Spec](https://fetch.spec.whatwg.org/#requestinit) to be optionally provided when instantiating a
|
||||
* {@link Request}.
|
||||
*
|
||||
* All values are null by default.
|
||||
*/
|
||||
export class RequestOptions implements IRequestOptions {
|
||||
/**
|
||||
* Http method with which to execute the request.
|
||||
*
|
||||
* Defaults to "GET".
|
||||
*/
|
||||
method: RequestMethods;
|
||||
/**
|
||||
* Headers object based on the `Headers` class in the [Fetch
|
||||
* Spec](https://fetch.spec.whatwg.org/#headers-class).
|
||||
*/
|
||||
headers: Headers;
|
||||
/**
|
||||
* Body to be used when creating the request.
|
||||
*/
|
||||
// TODO: support FormData, Blob, URLSearchParams
|
||||
body: string;
|
||||
mode: RequestModesOpts;
|
||||
credentials: RequestCredentialsOpts;
|
||||
cache: RequestCacheOpts;
|
||||
url: string;
|
||||
search: URLSearchParams;
|
||||
constructor({method, headers, body, mode, credentials, cache, url, search}:
|
||||
IRequestOptions = {}) {
|
||||
this.method = isPresent(method) ? method : null;
|
||||
this.headers = isPresent(headers) ? headers : null;
|
||||
this.body = isPresent(body) ? body : null;
|
||||
this.mode = isPresent(mode) ? mode : null;
|
||||
this.credentials = isPresent(credentials) ? credentials : null;
|
||||
this.cache = isPresent(cache) ? cache : null;
|
||||
this.url = isPresent(url) ? url : null;
|
||||
this.search = isPresent(search) ? (isString(search) ? new URLSearchParams(<string>(search)) :
|
||||
<URLSearchParams>(search)) :
|
||||
null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the `RequestOptions` instance, using the optional input as values to override
|
||||
* existing values.
|
||||
*/
|
||||
merge(options?: IRequestOptions): RequestOptions {
|
||||
return new RequestOptions({
|
||||
method: isPresent(options) && isPresent(options.method) ? options.method : this.method,
|
||||
headers: isPresent(options) && isPresent(options.headers) ? options.headers : this.headers,
|
||||
body: isPresent(options) && isPresent(options.body) ? options.body : this.body,
|
||||
mode: isPresent(options) && isPresent(options.mode) ? options.mode : this.mode,
|
||||
credentials: isPresent(options) && isPresent(options.credentials) ? options.credentials :
|
||||
this.credentials,
|
||||
cache: isPresent(options) && isPresent(options.cache) ? options.cache : this.cache,
|
||||
url: isPresent(options) && isPresent(options.url) ? options.url : this.url,
|
||||
search: isPresent(options) && isPresent(options.search) ?
|
||||
(isString(options.search) ? new URLSearchParams(<string>(options.search)) :
|
||||
(<URLSearchParams>(options.search)).clone()) :
|
||||
this.search
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injectable version of {@link RequestOptions}, with overridable default values.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {Http, BaseRequestOptions, Request} from 'angular2/http';
|
||||
* ...
|
||||
* class MyComponent {
|
||||
* constructor(baseRequestOptions:BaseRequestOptions, http:Http) {
|
||||
* var options = baseRequestOptions.merge({body: 'foobar', url: 'https://foo'});
|
||||
* var request = new Request(options);
|
||||
* http.request(request).subscribe(res => this.bars = res.json());
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@Injectable()
|
||||
export class BaseRequestOptions extends RequestOptions {
|
||||
constructor() {
|
||||
super({method: RequestMethods.GET, headers: new Headers(), mode: RequestModesOpts.Cors});
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {isPresent, isJsObject} from 'angular2/src/facade/lang';
|
||||
import {Headers} from './headers';
|
||||
import {ResponseTypes} from './enums';
|
||||
import {IResponseOptions} from './interfaces';
|
||||
|
||||
/**
|
||||
* Creates a response options object similar to the
|
||||
* [ResponseInit](https://fetch.spec.whatwg.org/#responseinit) description
|
||||
* in the Fetch
|
||||
* Spec to be optionally provided when instantiating a
|
||||
* {@link Response}.
|
||||
*
|
||||
* All values are null by default.
|
||||
*/
|
||||
export class ResponseOptions implements IResponseOptions {
|
||||
// TODO: ArrayBuffer | FormData | Blob
|
||||
body: string | Object;
|
||||
status: number;
|
||||
headers: Headers;
|
||||
statusText: string;
|
||||
type: ResponseTypes;
|
||||
url: string;
|
||||
constructor({body, status, headers, statusText, type, url}: IResponseOptions = {}) {
|
||||
this.body = isPresent(body) ? body : null;
|
||||
this.status = isPresent(status) ? status : null;
|
||||
this.headers = isPresent(headers) ? headers : null;
|
||||
this.statusText = isPresent(statusText) ? statusText : null;
|
||||
this.type = isPresent(type) ? type : null;
|
||||
this.url = isPresent(url) ? url : null;
|
||||
}
|
||||
|
||||
merge(options?: IResponseOptions): ResponseOptions {
|
||||
return new ResponseOptions({
|
||||
body: isPresent(options) && isPresent(options.body) ? options.body : this.body,
|
||||
status: isPresent(options) && isPresent(options.status) ? options.status : this.status,
|
||||
headers: isPresent(options) && isPresent(options.headers) ? options.headers : this.headers,
|
||||
statusText: isPresent(options) && isPresent(options.statusText) ? options.statusText :
|
||||
this.statusText,
|
||||
type: isPresent(options) && isPresent(options.type) ? options.type : this.type,
|
||||
url: isPresent(options) && isPresent(options.url) ? options.url : this.url,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injectable version of {@link ResponseOptions}, with overridable default values.
|
||||
*/
|
||||
@Injectable()
|
||||
export class BaseResponseOptions extends ResponseOptions {
|
||||
// TODO: Object | ArrayBuffer | JSON | FormData | Blob
|
||||
body: string | Object | ArrayBuffer | JSON | FormData | Blob;
|
||||
status: number;
|
||||
headers: Headers;
|
||||
statusText: string;
|
||||
type: ResponseTypes;
|
||||
url: string;
|
||||
|
||||
constructor() {
|
||||
super({status: 200, statusText: 'Ok', type: ResponseTypes.Default, headers: new Headers()});
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
import {StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
/**
|
||||
* Acceptable origin modes to be associated with a {@link Request}, based on
|
||||
* [RequestMode](https://fetch.spec.whatwg.org/#requestmode) from the Fetch spec.
|
||||
*/
|
||||
export enum RequestModesOpts {
|
||||
Cors,
|
||||
NoCors,
|
||||
SameOrigin
|
||||
}
|
||||
|
||||
/**
|
||||
* Acceptable cache option to be associated with a {@link Request}, based on
|
||||
* [RequestCache](https://fetch.spec.whatwg.org/#requestcache) from the Fetch spec.
|
||||
*/
|
||||
export enum RequestCacheOpts {
|
||||
Default,
|
||||
NoStore,
|
||||
Reload,
|
||||
NoCache,
|
||||
ForceCache,
|
||||
OnlyIfCached
|
||||
}
|
||||
|
||||
/**
|
||||
* Acceptable credentials option to be associated with a {@link Request}, based on
|
||||
* [RequestCredentials](https://fetch.spec.whatwg.org/#requestcredentials) from the Fetch spec.
|
||||
*/
|
||||
export enum RequestCredentialsOpts {
|
||||
Omit,
|
||||
SameOrigin,
|
||||
Include
|
||||
}
|
||||
|
||||
/**
|
||||
* Supported http methods.
|
||||
*/
|
||||
export enum RequestMethods {
|
||||
GET,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE,
|
||||
OPTIONS,
|
||||
HEAD,
|
||||
PATCH
|
||||
}
|
||||
|
||||
// TODO: Remove this when enum lookups are available in ts2dart
|
||||
// https://github.com/angular/ts2dart/issues/221
|
||||
export class RequestMethodsMap {
|
||||
private _methods: List<string>;
|
||||
constructor() { this._methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH']; }
|
||||
getMethod(method: int): string { return this._methods[method]; }
|
||||
}
|
||||
/**
|
||||
* All possible states in which a connection can be, based on
|
||||
* [States](http://www.w3.org/TR/XMLHttpRequest/#states) from the `XMLHttpRequest` spec, but with an
|
||||
* additional "CANCELLED" state.
|
||||
*/
|
||||
export enum ReadyStates {
|
||||
UNSENT,
|
||||
OPEN,
|
||||
HEADERS_RECEIVED,
|
||||
LOADING,
|
||||
DONE,
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
/**
|
||||
* Acceptable response types to be associated with a {@link Response}, based on
|
||||
* [ResponseType](https://fetch.spec.whatwg.org/#responsetype) from the Fetch spec.
|
||||
*/
|
||||
export enum ResponseTypes {
|
||||
Basic,
|
||||
Cors,
|
||||
Default,
|
||||
Error,
|
||||
Opaque
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
import {
|
||||
isPresent,
|
||||
isBlank,
|
||||
isJsObject,
|
||||
isType,
|
||||
StringWrapper,
|
||||
BaseException
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
isListLikeIterable,
|
||||
List,
|
||||
Map,
|
||||
MapWrapper,
|
||||
ListWrapper,
|
||||
StringMap
|
||||
} from 'angular2/src/facade/collection';
|
||||
|
||||
/**
|
||||
* Polyfill for [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers/Headers), as
|
||||
* specified in the [Fetch Spec](https://fetch.spec.whatwg.org/#headers-class). The only known
|
||||
* difference from the spec is the lack of an `entries` method.
|
||||
*/
|
||||
export class Headers {
|
||||
_headersMap: Map<string, List<string>>;
|
||||
constructor(headers?: Headers | StringMap<string, any>) {
|
||||
if (isBlank(headers)) {
|
||||
this._headersMap = new Map();
|
||||
return;
|
||||
}
|
||||
|
||||
if (headers instanceof Headers) {
|
||||
this._headersMap = (<Headers>headers)._headersMap;
|
||||
} else if (headers instanceof StringMap) {
|
||||
this._headersMap = MapWrapper.createFromStringMap<List<string>>(headers);
|
||||
MapWrapper.forEach(this._headersMap, (v, k) => {
|
||||
if (!isListLikeIterable(v)) {
|
||||
var list = [];
|
||||
list.push(v);
|
||||
this._headersMap.set(k, list);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a header to existing list of header values for a given header name.
|
||||
*/
|
||||
append(name: string, value: string): void {
|
||||
var mapName = this._headersMap.get(name);
|
||||
var list = isListLikeIterable(mapName) ? mapName : [];
|
||||
list.push(value);
|
||||
this._headersMap.set(name, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all header values for the given name.
|
||||
*/
|
||||
delete (name: string): void { MapWrapper.delete(this._headersMap, name); }
|
||||
|
||||
forEach(fn: Function) { MapWrapper.forEach(this._headersMap, fn); }
|
||||
|
||||
/**
|
||||
* Returns first header that matches given name.
|
||||
*/
|
||||
get(header: string): string { return ListWrapper.first(this._headersMap.get(header)); }
|
||||
|
||||
/**
|
||||
* Check for existence of header by given name.
|
||||
*/
|
||||
has(header: string): boolean { return this._headersMap.has(header); }
|
||||
|
||||
/**
|
||||
* Provides names of set headers
|
||||
*/
|
||||
keys(): List<string> { return MapWrapper.keys(this._headersMap); }
|
||||
|
||||
/**
|
||||
* Sets or overrides header value for given name.
|
||||
*/
|
||||
set(header: string, value: string | List<string>): void {
|
||||
var list = [];
|
||||
|
||||
if (isListLikeIterable(value)) {
|
||||
var pushValue = (<List<string>>value).join(',');
|
||||
list.push(pushValue);
|
||||
} else {
|
||||
list.push(value);
|
||||
}
|
||||
|
||||
this._headersMap.set(header, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns values of all headers.
|
||||
*/
|
||||
values(): List<List<string>> { return MapWrapper.values(this._headersMap); }
|
||||
|
||||
/**
|
||||
* Returns list of header values for a given name.
|
||||
*/
|
||||
getAll(header: string): Array<string> {
|
||||
var headers = this._headersMap.get(header);
|
||||
return isListLikeIterable(headers) ? headers : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is not implemented.
|
||||
*/
|
||||
entries() { throw new BaseException('"entries" method is not implemented on Headers class'); }
|
||||
}
|
@ -1,206 +0,0 @@
|
||||
import {isString, isPresent, isBlank, makeTypeError} from 'angular2/src/facade/lang';
|
||||
import {Injectable} from 'angular2/src/di/decorators';
|
||||
import {IRequestOptions, Connection, ConnectionBackend} from './interfaces';
|
||||
import {Request} from './static_request';
|
||||
import {BaseRequestOptions, RequestOptions} from './base_request_options';
|
||||
import {RequestMethods} from './enums';
|
||||
import {EventEmitter} from 'angular2/src/facade/async';
|
||||
|
||||
function httpRequest(backend: ConnectionBackend, request: Request): EventEmitter {
|
||||
return backend.createConnection(request).response;
|
||||
}
|
||||
|
||||
function mergeOptions(defaultOpts, providedOpts, method, url): RequestOptions {
|
||||
var newOptions = defaultOpts;
|
||||
if (isPresent(providedOpts)) {
|
||||
// Hack so Dart can used named parameters
|
||||
newOptions = newOptions.merge(new RequestOptions({
|
||||
method: providedOpts.method,
|
||||
url: providedOpts.url,
|
||||
search: providedOpts.search,
|
||||
headers: providedOpts.headers,
|
||||
body: providedOpts.body,
|
||||
mode: providedOpts.mode,
|
||||
credentials: providedOpts.credentials,
|
||||
cache: providedOpts.cache
|
||||
}));
|
||||
}
|
||||
if (isPresent(method)) {
|
||||
return newOptions.merge(new RequestOptions({method: method, url: url}));
|
||||
} else {
|
||||
return newOptions.merge(new RequestOptions({url: url}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs http requests using `XMLHttpRequest` as the default backend.
|
||||
*
|
||||
* `Http` is available as an injectable class, with methods to perform http requests. Calling
|
||||
* `request` returns an {@link EventEmitter} which will emit a single {@link Response} when a
|
||||
* response is received.
|
||||
*
|
||||
*
|
||||
* ## Breaking Change
|
||||
*
|
||||
* Previously, methods of `Http` would return an RxJS Observable directly. For now,
|
||||
* the `toRx()` method of {@link EventEmitter} needs to be called in order to get the RxJS
|
||||
* Subject. `EventEmitter` does not provide combinators like `map`, and has different semantics for
|
||||
* subscribing/observing. This is temporary; the result of all `Http` method calls will be either an
|
||||
* Observable
|
||||
* or Dart Stream when [issue #2794](https://github.com/angular/angular/issues/2794) is resolved.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {Http, httpInjectables} from 'angular2/http';
|
||||
* @Component({selector: 'http-app', viewBindings: [httpInjectables]})
|
||||
* @View({templateUrl: 'people.html'})
|
||||
* class PeopleComponent {
|
||||
* constructor(http: Http) {
|
||||
* http.get('people.json')
|
||||
* //Get the RxJS Subject
|
||||
* .toRx()
|
||||
* // Call map on the response observable to get the parsed people object
|
||||
* .map(res => res.json())
|
||||
* // Subscribe to the observable to get the parsed people object and attach it to the
|
||||
* // component
|
||||
* .subscribe(people => this.people = people);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* To use the {@link EventEmitter} returned by `Http`, simply pass a generator (See "interface
|
||||
*Generator" in the Async Generator spec: https://github.com/jhusain/asyncgenerator) to the
|
||||
*`observer` method of the returned emitter, with optional methods of `next`, `throw`, and `return`.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* http.get('people.json').observer({next: (value) => this.people = people});
|
||||
* ```
|
||||
*
|
||||
* The default construct used to perform requests, `XMLHttpRequest`, is abstracted as a "Backend" (
|
||||
* {@link XHRBackend} in this case), which could be mocked with dependency injection by replacing
|
||||
* the {@link XHRBackend} binding, as in the following example:
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {MockBackend, BaseRequestOptions, Http} from 'angular2/http';
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* BaseRequestOptions,
|
||||
* MockBackend,
|
||||
* bind(Http).toFactory(
|
||||
* function(backend, defaultOptions) {
|
||||
* return new Http(backend, defaultOptions);
|
||||
* },
|
||||
* [MockBackend, BaseRequestOptions])
|
||||
* ]);
|
||||
* var http = injector.get(Http);
|
||||
* http.get('request-from-mock-backend.json').toRx().subscribe((res:Response) => doSomething(res));
|
||||
* ```
|
||||
*
|
||||
**/
|
||||
@Injectable()
|
||||
export class Http {
|
||||
constructor(protected _backend: ConnectionBackend, protected _defaultOptions: RequestOptions) {}
|
||||
|
||||
/**
|
||||
* Performs any type of http request. First argument is required, and can either be a url or
|
||||
* a {@link Request} instance. If the first argument is a url, an optional {@link RequestOptions}
|
||||
* object can be provided as the 2nd argument. The options object will be merged with the values
|
||||
* of {@link BaseRequestOptions} before performing the request.
|
||||
*/
|
||||
request(url: string | Request, options?: IRequestOptions): EventEmitter {
|
||||
var responseObservable: EventEmitter;
|
||||
if (isString(url)) {
|
||||
responseObservable = httpRequest(
|
||||
this._backend,
|
||||
new Request(mergeOptions(this._defaultOptions, options, RequestMethods.GET, url)));
|
||||
} else if (url instanceof Request) {
|
||||
responseObservable = httpRequest(this._backend, url);
|
||||
}
|
||||
return responseObservable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request with `get` http method.
|
||||
*/
|
||||
get(url: string, options?: IRequestOptions): EventEmitter {
|
||||
return httpRequest(this._backend, new Request(mergeOptions(this._defaultOptions, options,
|
||||
RequestMethods.GET, url)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request with `post` http method.
|
||||
*/
|
||||
post(url: string, body: string, options?: IRequestOptions): EventEmitter {
|
||||
return httpRequest(
|
||||
this._backend,
|
||||
new Request(mergeOptions(this._defaultOptions.merge(new RequestOptions({body: body})),
|
||||
options, RequestMethods.POST, url)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request with `put` http method.
|
||||
*/
|
||||
put(url: string, body: string, options?: IRequestOptions): EventEmitter {
|
||||
return httpRequest(
|
||||
this._backend,
|
||||
new Request(mergeOptions(this._defaultOptions.merge(new RequestOptions({body: body})),
|
||||
options, RequestMethods.PUT, url)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request with `delete` http method.
|
||||
*/
|
||||
delete (url: string, options?: IRequestOptions): EventEmitter {
|
||||
return httpRequest(this._backend, new Request(mergeOptions(this._defaultOptions, options,
|
||||
RequestMethods.DELETE, url)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request with `patch` http method.
|
||||
*/
|
||||
patch(url: string, body: string, options?: IRequestOptions): EventEmitter {
|
||||
return httpRequest(
|
||||
this._backend,
|
||||
new Request(mergeOptions(this._defaultOptions.merge(new RequestOptions({body: body})),
|
||||
options, RequestMethods.PATCH, url)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request with `head` http method.
|
||||
*/
|
||||
head(url: string, options?: IRequestOptions): EventEmitter {
|
||||
return httpRequest(this._backend, new Request(mergeOptions(this._defaultOptions, options,
|
||||
RequestMethods.HEAD, url)));
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class Jsonp extends Http {
|
||||
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
|
||||
super(backend, defaultOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs any type of http request. First argument is required, and can either be a url or
|
||||
* a {@link Request} instance. If the first argument is a url, an optional {@link RequestOptions}
|
||||
* object can be provided as the 2nd argument. The options object will be merged with the values
|
||||
* of {@link BaseRequestOptions} before performing the request.
|
||||
*/
|
||||
request(url: string | Request, options?: IRequestOptions): EventEmitter {
|
||||
var responseObservable: EventEmitter;
|
||||
if (isString(url)) {
|
||||
url = new Request(mergeOptions(this._defaultOptions, options, RequestMethods.GET, url));
|
||||
}
|
||||
if (url instanceof Request) {
|
||||
if (url.method !== RequestMethods.GET) {
|
||||
makeTypeError('JSONP requests must use GET request method.');
|
||||
}
|
||||
responseObservable = httpRequest(this._backend, url);
|
||||
}
|
||||
return responseObservable;
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
library angular2.src.http.http_utils;
|
||||
|
||||
import 'dart:js' show JsObject;
|
||||
import 'dart:collection' show LinkedHashMap, LinkedHashSet;
|
||||
|
||||
bool isJsObject(o) {
|
||||
return o is JsObject || o is LinkedHashMap || o is LinkedHashSet;
|
||||
}
|
@ -1 +0,0 @@
|
||||
export {isJsObject} from 'angular2/src/facade/lang';
|
@ -1,69 +0,0 @@
|
||||
/// <reference path="../../typings/rx/rx.d.ts" />
|
||||
|
||||
import {
|
||||
ReadyStates,
|
||||
RequestModesOpts,
|
||||
RequestMethods,
|
||||
RequestCacheOpts,
|
||||
RequestCredentialsOpts,
|
||||
ResponseTypes
|
||||
} from './enums';
|
||||
import {Headers} from './headers';
|
||||
import {BaseException} from 'angular2/src/facade/lang';
|
||||
import {EventEmitter} from 'angular2/src/facade/async';
|
||||
import {Request} from './static_request';
|
||||
import {URLSearchParamsUnionFixer, URLSearchParams} from './url_search_params';
|
||||
|
||||
// Work around Dartanalyzer problem :(
|
||||
const URLSearchParams_UnionFixer = URLSearchParamsUnionFixer;
|
||||
|
||||
/**
|
||||
* Abstract class from which real backends are derived.
|
||||
*
|
||||
* The primary purpose of a `ConnectionBackend` is to create new connections to fulfill a given
|
||||
* {@link Request}.
|
||||
*/
|
||||
export class ConnectionBackend {
|
||||
constructor() {}
|
||||
createConnection(request: any): Connection { throw new BaseException('Abstract!'); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract class from which real connections are derived.
|
||||
*/
|
||||
export class Connection {
|
||||
readyState: ReadyStates;
|
||||
request: Request;
|
||||
response: EventEmitter; // TODO: generic of <Response>;
|
||||
dispose(): void { throw new BaseException('Abstract!'); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for options to construct a Request, based on
|
||||
* [RequestInit](https://fetch.spec.whatwg.org/#requestinit) from the Fetch spec.
|
||||
*/
|
||||
export interface IRequestOptions {
|
||||
url?: string;
|
||||
method?: RequestMethods;
|
||||
search?: string | URLSearchParams;
|
||||
headers?: Headers;
|
||||
// TODO: Support Blob, ArrayBuffer, JSON, URLSearchParams, FormData
|
||||
body?: string;
|
||||
mode?: RequestModesOpts;
|
||||
credentials?: RequestCredentialsOpts;
|
||||
cache?: RequestCacheOpts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for options to construct a Response, based on
|
||||
* [ResponseInit](https://fetch.spec.whatwg.org/#responseinit) from the Fetch spec.
|
||||
*/
|
||||
export interface IResponseOptions {
|
||||
// TODO: Support Blob, ArrayBuffer, JSON
|
||||
body?: string | Object | FormData;
|
||||
status?: number;
|
||||
statusText?: string;
|
||||
headers?: Headers;
|
||||
type?: ResponseTypes;
|
||||
url?: string;
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
import {RequestMethods, RequestModesOpts, RequestCredentialsOpts, RequestCacheOpts} from './enums';
|
||||
import {RequestOptions} from './base_request_options';
|
||||
import {Headers} from './headers';
|
||||
import {
|
||||
BaseException,
|
||||
RegExpWrapper,
|
||||
CONST_EXPR,
|
||||
isPresent,
|
||||
isJsObject,
|
||||
StringWrapper
|
||||
} from 'angular2/src/facade/lang';
|
||||
|
||||
// TODO(jeffbcross): properly implement body accessors
|
||||
/**
|
||||
* Creates `Request` instances from provided values.
|
||||
*
|
||||
* The Request's interface is inspired by the Request constructor defined in the [Fetch
|
||||
* Spec](https://fetch.spec.whatwg.org/#request-class),
|
||||
* but is considered a static value whose body can be accessed many times. There are other
|
||||
* differences in the implementation, but this is the most significant.
|
||||
*/
|
||||
export class Request {
|
||||
/**
|
||||
* Http method with which to perform the request.
|
||||
*
|
||||
* Defaults to GET.
|
||||
*/
|
||||
method: RequestMethods;
|
||||
mode: RequestModesOpts;
|
||||
credentials: RequestCredentialsOpts;
|
||||
/**
|
||||
* Headers object based on the `Headers` class in the [Fetch
|
||||
* Spec](https://fetch.spec.whatwg.org/#headers-class). {@link Headers} class reference.
|
||||
*/
|
||||
headers: Headers;
|
||||
/** Url of the remote resource */
|
||||
url: string;
|
||||
// TODO: support URLSearchParams | FormData | Blob | ArrayBuffer
|
||||
private _body: string;
|
||||
cache: RequestCacheOpts;
|
||||
constructor(requestOptions: RequestOptions) {
|
||||
// TODO: assert that url is present
|
||||
let url = requestOptions.url;
|
||||
this.url = requestOptions.url;
|
||||
if (isPresent(requestOptions.search)) {
|
||||
let search = requestOptions.search.toString();
|
||||
if (search.length > 0) {
|
||||
let prefix = '?';
|
||||
if (StringWrapper.contains(this.url, '?')) {
|
||||
prefix = (this.url[this.url.length - 1] == '&') ? '' : '&';
|
||||
}
|
||||
// TODO: just delete search-query-looking string in url?
|
||||
this.url = url + prefix + search;
|
||||
}
|
||||
}
|
||||
this._body = requestOptions.body;
|
||||
this.method = requestOptions.method;
|
||||
// TODO(jeffbcross): implement behavior
|
||||
this.mode = requestOptions.mode;
|
||||
// Defaults to 'omit', consistent with browser
|
||||
// TODO(jeffbcross): implement behavior
|
||||
this.credentials = requestOptions.credentials;
|
||||
this.headers = new Headers(requestOptions.headers);
|
||||
this.cache = requestOptions.cache;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the request's body as string, assuming that body exists. If body is undefined, return
|
||||
* empty
|
||||
* string.
|
||||
*/
|
||||
text(): String { return isPresent(this._body) ? this._body.toString() : ''; }
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
import {ResponseTypes} from './enums';
|
||||
import {BaseException, CONST_EXPR, isString, isPresent, Json} from 'angular2/src/facade/lang';
|
||||
import {Headers} from './headers';
|
||||
import {ResponseOptions} from './base_response_options';
|
||||
import {isJsObject} from './http_utils';
|
||||
|
||||
/**
|
||||
* Creates `Response` instances from provided values.
|
||||
*
|
||||
* Though this object isn't
|
||||
* usually instantiated by end-users, it is the primary object interacted with when it comes time to
|
||||
* add data to a view.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* http.request('my-friends.txt').subscribe(response => this.friends = response.text());
|
||||
* ```
|
||||
*
|
||||
* The Response's interface is inspired by the Response constructor defined in the [Fetch
|
||||
* Spec](https://fetch.spec.whatwg.org/#response-class), but is considered a static value whose body
|
||||
* can be accessed many times. There are other differences in the implementation, but this is the
|
||||
* most significant.
|
||||
*/
|
||||
export class Response {
|
||||
/**
|
||||
* One of "basic", "cors", "default", "error, or "opaque".
|
||||
*
|
||||
* Defaults to "default".
|
||||
*/
|
||||
type: ResponseTypes;
|
||||
/**
|
||||
* True if the response's status is within 200-299
|
||||
*/
|
||||
ok: boolean;
|
||||
/**
|
||||
* URL of response.
|
||||
*
|
||||
* Defaults to empty string.
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Status code returned by server.
|
||||
*
|
||||
* Defaults to 200.
|
||||
*/
|
||||
status: number;
|
||||
/**
|
||||
* Text representing the corresponding reason phrase to the `status`, as defined in [ietf rfc 2616
|
||||
* section 6.1.1](https://tools.ietf.org/html/rfc2616#section-6.1.1)
|
||||
*
|
||||
* Defaults to "OK"
|
||||
*/
|
||||
statusText: string;
|
||||
/**
|
||||
* Non-standard property
|
||||
*
|
||||
* Denotes how many of the response body's bytes have been loaded, for example if the response is
|
||||
* the result of a progress event.
|
||||
*/
|
||||
bytesLoaded: number;
|
||||
/**
|
||||
* Non-standard property
|
||||
*
|
||||
* Denotes how many bytes are expected in the final response body.
|
||||
*/
|
||||
totalBytes: number;
|
||||
/**
|
||||
* Headers object based on the `Headers` class in the [Fetch
|
||||
* Spec](https://fetch.spec.whatwg.org/#headers-class).
|
||||
*/
|
||||
headers: Headers;
|
||||
// TODO: Support ArrayBuffer, JSON, FormData, Blob
|
||||
private _body: string | Object;
|
||||
constructor(responseOptions: ResponseOptions) {
|
||||
this._body = responseOptions.body;
|
||||
this.status = responseOptions.status;
|
||||
this.statusText = responseOptions.statusText;
|
||||
this.headers = responseOptions.headers;
|
||||
this.type = responseOptions.type;
|
||||
this.url = responseOptions.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not yet implemented
|
||||
*/
|
||||
// TODO: Blob return type
|
||||
blob(): any { throw new BaseException('"blob()" method not implemented on Response superclass'); }
|
||||
|
||||
/**
|
||||
* Attempts to return body as parsed `JSON` object, or raises an exception.
|
||||
*/
|
||||
json(): Object {
|
||||
var jsonResponse;
|
||||
if (isJsObject(this._body)) {
|
||||
jsonResponse = this._body;
|
||||
} else if (isString(this._body)) {
|
||||
jsonResponse = Json.parse(<string>this._body);
|
||||
}
|
||||
return jsonResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the body as a string, presuming `toString()` can be called on the response body.
|
||||
*/
|
||||
text(): string { return this._body.toString(); }
|
||||
|
||||
/**
|
||||
* Not yet implemented
|
||||
*/
|
||||
// TODO: ArrayBuffer return type
|
||||
arrayBuffer(): any {
|
||||
throw new BaseException('"arrayBuffer()" method not implemented on Response superclass');
|
||||
}
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
import {CONST_EXPR, isPresent, isBlank, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
Map,
|
||||
MapWrapper,
|
||||
List,
|
||||
ListWrapper,
|
||||
isListLikeIterable
|
||||
} from 'angular2/src/facade/collection';
|
||||
|
||||
function paramParser(rawParams: string = ''): Map<string, List<string>> {
|
||||
var map: Map<string, List<string>> = new Map();
|
||||
if (rawParams.length > 0) {
|
||||
var params: List<string> = StringWrapper.split(rawParams, new RegExp('&'));
|
||||
ListWrapper.forEach(params, (param: string) => {
|
||||
var split: List<string> = StringWrapper.split(param, new RegExp('='));
|
||||
var key = ListWrapper.get(split, 0);
|
||||
var val = ListWrapper.get(split, 1);
|
||||
var list = isPresent(map.get(key)) ? map.get(key) : [];
|
||||
list.push(val);
|
||||
map.set(key, list);
|
||||
});
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// TODO(caitp): This really should not be needed. Issue with ts2dart.
|
||||
export const URLSearchParamsUnionFixer: string = CONST_EXPR("UnionFixer");
|
||||
|
||||
/**
|
||||
* Map-like representation of url search parameters, based on
|
||||
* [URLSearchParams](https://url.spec.whatwg.org/#urlsearchparams) in the url living standard,
|
||||
* with several extensions for merging URLSearchParams objects:
|
||||
* - setAll()
|
||||
* - appendAll()
|
||||
* - replaceAll()
|
||||
*/
|
||||
export class URLSearchParams {
|
||||
paramsMap: Map<string, List<string>>;
|
||||
constructor(public rawParams: string = '') { this.paramsMap = paramParser(rawParams); }
|
||||
|
||||
clone(): URLSearchParams {
|
||||
var clone = new URLSearchParams();
|
||||
clone.appendAll(this);
|
||||
return clone;
|
||||
}
|
||||
|
||||
has(param: string): boolean { return this.paramsMap.has(param); }
|
||||
|
||||
get(param: string): string {
|
||||
var storedParam = this.paramsMap.get(param);
|
||||
if (isListLikeIterable(storedParam)) {
|
||||
return ListWrapper.first(storedParam);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
getAll(param: string): List<string> {
|
||||
var mapParam = this.paramsMap.get(param);
|
||||
return isPresent(mapParam) ? mapParam : [];
|
||||
}
|
||||
|
||||
set(param: string, val: string) {
|
||||
var mapParam = this.paramsMap.get(param);
|
||||
var list = isPresent(mapParam) ? mapParam : [];
|
||||
ListWrapper.clear(list);
|
||||
list.push(val);
|
||||
this.paramsMap.set(param, list);
|
||||
}
|
||||
|
||||
// A merge operation
|
||||
// For each name-values pair in `searchParams`, perform `set(name, values[0])`
|
||||
//
|
||||
// E.g: "a=[1,2,3], c=[8]" + "a=[4,5,6], b=[7]" = "a=[4], c=[8], b=[7]"
|
||||
//
|
||||
// TODO(@caitp): document this better
|
||||
setAll(searchParams: URLSearchParams) {
|
||||
MapWrapper.forEach(searchParams.paramsMap, (value, param) => {
|
||||
var mapParam = this.paramsMap.get(param);
|
||||
var list = isPresent(mapParam) ? mapParam : [];
|
||||
ListWrapper.clear(list);
|
||||
list.push(value[0]);
|
||||
this.paramsMap.set(param, list);
|
||||
});
|
||||
}
|
||||
|
||||
append(param: string, val: string): void {
|
||||
var mapParam = this.paramsMap.get(param);
|
||||
var list = isPresent(mapParam) ? mapParam : [];
|
||||
list.push(val);
|
||||
this.paramsMap.set(param, list);
|
||||
}
|
||||
|
||||
// A merge operation
|
||||
// For each name-values pair in `searchParams`, perform `append(name, value)`
|
||||
// for each value in `values`.
|
||||
//
|
||||
// E.g: "a=[1,2], c=[8]" + "a=[3,4], b=[7]" = "a=[1,2,3,4], c=[8], b=[7]"
|
||||
//
|
||||
// TODO(@caitp): document this better
|
||||
appendAll(searchParams: URLSearchParams) {
|
||||
MapWrapper.forEach(searchParams.paramsMap, (value, param) => {
|
||||
var mapParam = this.paramsMap.get(param);
|
||||
var list = isPresent(mapParam) ? mapParam : [];
|
||||
for (var i = 0; i < value.length; ++i) {
|
||||
list.push(value[i]);
|
||||
}
|
||||
this.paramsMap.set(param, list);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// A merge operation
|
||||
// For each name-values pair in `searchParams`, perform `delete(name)`,
|
||||
// followed by `set(name, values)`
|
||||
//
|
||||
// E.g: "a=[1,2,3], c=[8]" + "a=[4,5,6], b=[7]" = "a=[4,5,6], c=[8], b=[7]"
|
||||
//
|
||||
// TODO(@caitp): document this better
|
||||
replaceAll(searchParams: URLSearchParams) {
|
||||
MapWrapper.forEach(searchParams.paramsMap, (value, param) => {
|
||||
var mapParam = this.paramsMap.get(param);
|
||||
var list = isPresent(mapParam) ? mapParam : [];
|
||||
ListWrapper.clear(list);
|
||||
for (var i = 0; i < value.length; ++i) {
|
||||
list.push(value[i]);
|
||||
}
|
||||
this.paramsMap.set(param, list);
|
||||
});
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
var paramsList = [];
|
||||
MapWrapper.forEach(this.paramsMap, (values, k) => {
|
||||
ListWrapper.forEach(values, v => { paramsList.push(k + '=' + v); });
|
||||
});
|
||||
return ListWrapper.join(paramsList, '&');
|
||||
}
|
||||
|
||||
delete (param: string): void { MapWrapper.delete(this.paramsMap, param); }
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
afterEach,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
SpyObject
|
||||
} from 'angular2/test_lib';
|
||||
import {ObservableWrapper} from 'angular2/src/facade/async';
|
||||
import {BrowserJsonp} from 'angular2/src/http/backends/browser_jsonp';
|
||||
import {JSONPConnection, JSONPBackend} from 'angular2/src/http/backends/jsonp_backend';
|
||||
import {bind, Injector} from 'angular2/di';
|
||||
import {isPresent, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {TimerWrapper} from 'angular2/src/facade/async';
|
||||
import {Request} from 'angular2/src/http/static_request';
|
||||
import {Response} from 'angular2/src/http/static_response';
|
||||
import {Map} from 'angular2/src/facade/collection';
|
||||
import {RequestOptions, BaseRequestOptions} from 'angular2/src/http/base_request_options';
|
||||
import {BaseResponseOptions, ResponseOptions} from 'angular2/src/http/base_response_options';
|
||||
import {ResponseTypes, ReadyStates, RequestMethods} from 'angular2/src/http/enums';
|
||||
|
||||
var addEventListenerSpy;
|
||||
var existingScripts = [];
|
||||
var unused: Response;
|
||||
|
||||
class MockBrowserJsonp extends BrowserJsonp {
|
||||
src: string;
|
||||
callbacks: Map<string, (data: any) => any>;
|
||||
constructor() {
|
||||
super();
|
||||
this.callbacks = new Map();
|
||||
}
|
||||
|
||||
addEventListener(type: string, cb: (data: any) => any) { this.callbacks.set(type, cb); }
|
||||
|
||||
dispatchEvent(type: string, argument?: any) {
|
||||
if (!isPresent(argument)) {
|
||||
argument = {};
|
||||
}
|
||||
this.callbacks.get(type)(argument);
|
||||
}
|
||||
|
||||
build(url: string) {
|
||||
var script = new MockBrowserJsonp();
|
||||
script.src = url;
|
||||
existingScripts.push(script);
|
||||
return script;
|
||||
}
|
||||
|
||||
send(node: any) { /* noop */
|
||||
}
|
||||
cleanup(node: any) { /* noop */
|
||||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('JSONPBackend', () => {
|
||||
let backend;
|
||||
let sampleRequest;
|
||||
|
||||
beforeEach(() => {
|
||||
let injector = Injector.resolveAndCreate([
|
||||
bind(ResponseOptions)
|
||||
.toClass(BaseResponseOptions),
|
||||
bind(BrowserJsonp).toClass(MockBrowserJsonp),
|
||||
JSONPBackend
|
||||
]);
|
||||
backend = injector.get(JSONPBackend);
|
||||
let base = new BaseRequestOptions();
|
||||
sampleRequest = new Request(base.merge(new RequestOptions({url: 'https://google.com'})));
|
||||
});
|
||||
|
||||
afterEach(() => { existingScripts = []; });
|
||||
|
||||
it('should create a connection', () => {
|
||||
var instance;
|
||||
expect(() => instance = backend.createConnection(sampleRequest)).not.toThrow();
|
||||
expect(instance).toBeAnInstanceOf(JSONPConnection);
|
||||
});
|
||||
|
||||
|
||||
describe('JSONPConnection', () => {
|
||||
it('should use the injected BaseResponseOptions to create the response',
|
||||
inject([AsyncTestCompleter], async => {
|
||||
let connection = new JSONPConnection(sampleRequest, new MockBrowserJsonp(),
|
||||
new ResponseOptions({type: ResponseTypes.Error}));
|
||||
ObservableWrapper.subscribe<Response>(connection.response, res => {
|
||||
expect(res.type).toBe(ResponseTypes.Error);
|
||||
async.done();
|
||||
});
|
||||
connection.finished();
|
||||
existingScripts[0].dispatchEvent('load');
|
||||
}));
|
||||
|
||||
it('should ignore load/callback when disposed', inject([AsyncTestCompleter], async => {
|
||||
var connection = new JSONPConnection(sampleRequest, new MockBrowserJsonp());
|
||||
let spy = new SpyObject();
|
||||
let loadSpy = spy.spy('load');
|
||||
let errorSpy = spy.spy('error');
|
||||
let returnSpy = spy.spy('cancelled');
|
||||
|
||||
ObservableWrapper.subscribe(connection.response, loadSpy, errorSpy, returnSpy);
|
||||
connection.dispose();
|
||||
expect(connection.readyState).toBe(ReadyStates.CANCELLED);
|
||||
|
||||
connection.finished('Fake data');
|
||||
existingScripts[0].dispatchEvent('load');
|
||||
|
||||
TimerWrapper.setTimeout(() => {
|
||||
expect(loadSpy).not.toHaveBeenCalled();
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
expect(returnSpy).toHaveBeenCalled();
|
||||
async.done();
|
||||
}, 10);
|
||||
}));
|
||||
|
||||
it('should report error if loaded without invoking callback',
|
||||
inject([AsyncTestCompleter], async => {
|
||||
let connection = new JSONPConnection(sampleRequest, new MockBrowserJsonp());
|
||||
ObservableWrapper.subscribe(
|
||||
connection.response,
|
||||
res => {
|
||||
expect("response listener called").toBe(false);
|
||||
async.done();
|
||||
},
|
||||
err => {
|
||||
expect(StringWrapper.contains(err.message, 'did not invoke callback')).toBe(true);
|
||||
async.done();
|
||||
});
|
||||
|
||||
existingScripts[0].dispatchEvent('load');
|
||||
}));
|
||||
|
||||
it('should report error if script contains error', inject([AsyncTestCompleter], async => {
|
||||
let connection = new JSONPConnection(sampleRequest, new MockBrowserJsonp());
|
||||
|
||||
ObservableWrapper.subscribe(connection.response,
|
||||
res => {
|
||||
expect("response listener called").toBe(false);
|
||||
async.done();
|
||||
},
|
||||
err => {
|
||||
expect(err['message']).toBe('Oops!');
|
||||
async.done();
|
||||
});
|
||||
|
||||
existingScripts[0].dispatchEvent('error', ({message: "Oops!"}));
|
||||
}));
|
||||
|
||||
it('should throw if request method is not GET', () => {
|
||||
[RequestMethods.POST, RequestMethods.PUT, RequestMethods.DELETE, RequestMethods.OPTIONS,
|
||||
RequestMethods.HEAD, RequestMethods.PATCH]
|
||||
.forEach(method => {
|
||||
let base = new BaseRequestOptions();
|
||||
let req = new Request(
|
||||
base.merge(new RequestOptions({url: 'https://google.com', method: method})));
|
||||
expect(() => new JSONPConnection(req, new MockBrowserJsonp())).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond with data passed to callback', inject([AsyncTestCompleter], async => {
|
||||
let connection = new JSONPConnection(sampleRequest, new MockBrowserJsonp());
|
||||
|
||||
ObservableWrapper.subscribe<Response>(connection.response, res => {
|
||||
expect(res.json()).toEqual(({fake_payload: true, blob_id: 12345}));
|
||||
async.done();
|
||||
});
|
||||
|
||||
connection.finished(({fake_payload: true, blob_id: 12345}));
|
||||
existingScripts[0].dispatchEvent('load');
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
@ -1,202 +0,0 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
afterEach,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
SpyObject
|
||||
} from 'angular2/test_lib';
|
||||
import {ObservableWrapper} from 'angular2/src/facade/async';
|
||||
import {BrowserXhr} from 'angular2/src/http/backends/browser_xhr';
|
||||
import {XHRConnection, XHRBackend} from 'angular2/src/http/backends/xhr_backend';
|
||||
import {bind, Injector} from 'angular2/di';
|
||||
import {Request} from 'angular2/src/http/static_request';
|
||||
import {Response} from 'angular2/src/http/static_response';
|
||||
import {Headers} from 'angular2/src/http/headers';
|
||||
import {Map} from 'angular2/src/facade/collection';
|
||||
import {RequestOptions, BaseRequestOptions} from 'angular2/src/http/base_request_options';
|
||||
import {BaseResponseOptions, ResponseOptions} from 'angular2/src/http/base_response_options';
|
||||
import {ResponseTypes} from 'angular2/src/http/enums';
|
||||
|
||||
var abortSpy;
|
||||
var sendSpy;
|
||||
var openSpy;
|
||||
var setRequestHeaderSpy;
|
||||
var addEventListenerSpy;
|
||||
var existingXHRs = [];
|
||||
var unused: Response;
|
||||
|
||||
class MockBrowserXHR extends BrowserXhr {
|
||||
abort: any;
|
||||
send: any;
|
||||
open: any;
|
||||
response: any;
|
||||
responseText: string;
|
||||
setRequestHeader: any;
|
||||
callbacks: Map<string, Function>;
|
||||
status: number;
|
||||
constructor() {
|
||||
super();
|
||||
var spy = new SpyObject();
|
||||
this.abort = abortSpy = spy.spy('abort');
|
||||
this.send = sendSpy = spy.spy('send');
|
||||
this.open = openSpy = spy.spy('open');
|
||||
this.setRequestHeader = setRequestHeaderSpy = spy.spy('setRequestHeader');
|
||||
this.callbacks = new Map();
|
||||
}
|
||||
|
||||
setStatusCode(status) { this.status = status; }
|
||||
|
||||
setResponse(value) { this.response = value; }
|
||||
|
||||
setResponseText(value) { this.responseText = value; }
|
||||
|
||||
addEventListener(type: string, cb: Function) { this.callbacks.set(type, cb); }
|
||||
|
||||
dispatchEvent(type: string) { this.callbacks.get(type)({}); }
|
||||
|
||||
build() {
|
||||
var xhr = new MockBrowserXHR();
|
||||
existingXHRs.push(xhr);
|
||||
return xhr;
|
||||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('XHRBackend', () => {
|
||||
var backend;
|
||||
var sampleRequest;
|
||||
|
||||
beforeEach(() => {
|
||||
var injector = Injector.resolveAndCreate([
|
||||
bind(ResponseOptions)
|
||||
.toClass(BaseResponseOptions),
|
||||
bind(BrowserXhr).toClass(MockBrowserXHR),
|
||||
XHRBackend
|
||||
]);
|
||||
backend = injector.get(XHRBackend);
|
||||
var base = new BaseRequestOptions();
|
||||
sampleRequest = new Request(base.merge(new RequestOptions({url: 'https://google.com'})));
|
||||
});
|
||||
|
||||
afterEach(() => { existingXHRs = []; });
|
||||
|
||||
it('should create a connection',
|
||||
() => { expect(() => backend.createConnection(sampleRequest)).not.toThrow(); });
|
||||
|
||||
|
||||
describe('XHRConnection', () => {
|
||||
it('should use the injected BaseResponseOptions to create the response',
|
||||
inject([AsyncTestCompleter], async => {
|
||||
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
|
||||
new ResponseOptions({type: ResponseTypes.Error}));
|
||||
ObservableWrapper.subscribe<Response>(connection.response, res => {
|
||||
expect(res.type).toBe(ResponseTypes.Error);
|
||||
async.done();
|
||||
});
|
||||
existingXHRs[0].dispatchEvent('load');
|
||||
}));
|
||||
|
||||
it('should complete a request', inject([AsyncTestCompleter], async => {
|
||||
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
|
||||
new ResponseOptions({type: ResponseTypes.Error}));
|
||||
ObservableWrapper.subscribe<Response>(connection.response, res => {
|
||||
expect(res.type).toBe(ResponseTypes.Error);
|
||||
}, null, () => { async.done(); });
|
||||
|
||||
existingXHRs[0].dispatchEvent('load');
|
||||
}));
|
||||
|
||||
it('should call abort when disposed', () => {
|
||||
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR());
|
||||
connection.dispose();
|
||||
expect(abortSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should automatically call open with method and url', () => {
|
||||
new XHRConnection(sampleRequest, new MockBrowserXHR());
|
||||
expect(openSpy).toHaveBeenCalledWith('GET', sampleRequest.url);
|
||||
});
|
||||
|
||||
|
||||
it('should automatically call send on the backend with request body', () => {
|
||||
var body = 'Some body to love';
|
||||
var base = new BaseRequestOptions();
|
||||
new XHRConnection(new Request(base.merge(new RequestOptions({body: body}))),
|
||||
new MockBrowserXHR());
|
||||
expect(sendSpy).toHaveBeenCalledWith(body);
|
||||
});
|
||||
|
||||
it('should attach headers to the request', () => {
|
||||
var headers = new Headers({'Content-Type': 'text/xml', 'Breaking-Bad': '<3'});
|
||||
|
||||
var base = new BaseRequestOptions();
|
||||
new XHRConnection(new Request(base.merge(new RequestOptions({headers: headers}))),
|
||||
new MockBrowserXHR());
|
||||
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', ['text/xml']);
|
||||
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Breaking-Bad', ['<3']);
|
||||
});
|
||||
|
||||
it('should return the correct status code', inject([AsyncTestCompleter], async => {
|
||||
var statusCode = 418;
|
||||
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
|
||||
new ResponseOptions({status: statusCode}));
|
||||
|
||||
ObservableWrapper.subscribe<Response>(connection.response, res => {
|
||||
expect(res.status).toBe(statusCode);
|
||||
async.done();
|
||||
});
|
||||
|
||||
existingXHRs[0].setStatusCode(statusCode);
|
||||
existingXHRs[0].dispatchEvent('load');
|
||||
}));
|
||||
|
||||
it('should normalize IE\'s 1223 status code into 204', inject([AsyncTestCompleter], async => {
|
||||
var statusCode = 1223;
|
||||
var normalizedCode = 204;
|
||||
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
|
||||
new ResponseOptions({status: statusCode}));
|
||||
|
||||
ObservableWrapper.subscribe<Response>(connection.response, res => {
|
||||
expect(res.status).toBe(normalizedCode);
|
||||
async.done();
|
||||
});
|
||||
|
||||
existingXHRs[0].setStatusCode(statusCode);
|
||||
existingXHRs[0].dispatchEvent('load');
|
||||
}));
|
||||
|
||||
it('should normalize responseText and response', inject([AsyncTestCompleter], async => {
|
||||
var responseBody = 'Doge';
|
||||
|
||||
var connection1 =
|
||||
new XHRConnection(sampleRequest, new MockBrowserXHR(), new ResponseOptions());
|
||||
|
||||
var connection2 =
|
||||
new XHRConnection(sampleRequest, new MockBrowserXHR(), new ResponseOptions());
|
||||
|
||||
ObservableWrapper.subscribe<Response>(connection1.response, res => {
|
||||
expect(res.text()).toBe(responseBody);
|
||||
|
||||
ObservableWrapper.subscribe<Response>(connection2.response, ress => {
|
||||
expect(ress.text()).toBe(responseBody);
|
||||
async.done();
|
||||
});
|
||||
existingXHRs[1].dispatchEvent('load');
|
||||
});
|
||||
|
||||
existingXHRs[0].setResponseText(responseBody);
|
||||
existingXHRs[1].setResponse(responseBody);
|
||||
|
||||
existingXHRs[0].dispatchEvent('load');
|
||||
}));
|
||||
|
||||
});
|
||||
});
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit
|
||||
} from 'angular2/test_lib';
|
||||
import {BaseRequestOptions, RequestOptions} from 'angular2/src/http/base_request_options';
|
||||
import {RequestMethods, RequestModesOpts} from 'angular2/src/http/enums';
|
||||
|
||||
export function main() {
|
||||
describe('BaseRequestOptions', () => {
|
||||
it('should create a new object when calling merge', () => {
|
||||
var options1 = new BaseRequestOptions();
|
||||
var options2 = options1.merge(new RequestOptions({method: RequestMethods.DELETE}));
|
||||
expect(options2).not.toBe(options1);
|
||||
expect(options2.method).toBe(RequestMethods.DELETE);
|
||||
});
|
||||
|
||||
it('should retain previously merged values when merging again', () => {
|
||||
var options1 = new BaseRequestOptions();
|
||||
var options2 = options1.merge(new RequestOptions({method: RequestMethods.DELETE}));
|
||||
var options3 = options2.merge(new RequestOptions({mode: RequestModesOpts.NoCors}));
|
||||
expect(options3.mode).toBe(RequestModesOpts.NoCors);
|
||||
expect(options3.method).toBe(RequestMethods.DELETE);
|
||||
});
|
||||
});
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
import {Headers} from 'angular2/src/http/headers';
|
||||
import {Map, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit
|
||||
} from 'angular2/test_lib';
|
||||
|
||||
export function main() {
|
||||
describe('Headers', () => {
|
||||
it('should conform to spec', () => {
|
||||
// Examples borrowed from https://developer.mozilla.org/en-US/docs/Web/API/Headers/Headers
|
||||
// Spec at https://fetch.spec.whatwg.org/#dom-headers
|
||||
var firstHeaders = new Headers(); // Currently empty
|
||||
firstHeaders.append('Content-Type', 'image/jpeg');
|
||||
expect(firstHeaders.get('Content-Type')).toBe('image/jpeg');
|
||||
var httpHeaders = StringMapWrapper.create();
|
||||
StringMapWrapper.set(httpHeaders, 'Content-Type', 'image/jpeg');
|
||||
StringMapWrapper.set(httpHeaders, 'Accept-Charset', 'utf-8');
|
||||
StringMapWrapper.set(httpHeaders, 'X-My-Custom-Header', 'Zeke are cool');
|
||||
var secondHeaders = new Headers(httpHeaders);
|
||||
var secondHeadersObj = new Headers(secondHeaders);
|
||||
expect(secondHeadersObj.get('Content-Type')).toBe('image/jpeg');
|
||||
});
|
||||
|
||||
|
||||
describe('initialization', () => {
|
||||
it('should merge values in provided dictionary', () => {
|
||||
var map = StringMapWrapper.create();
|
||||
StringMapWrapper.set(map, 'foo', 'bar');
|
||||
var headers = new Headers(map);
|
||||
expect(headers.get('foo')).toBe('bar');
|
||||
expect(headers.getAll('foo')).toEqual(['bar']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('.set()', () => {
|
||||
it('should clear all values and re-set for the provided key', () => {
|
||||
var map = StringMapWrapper.create();
|
||||
StringMapWrapper.set(map, 'foo', 'bar');
|
||||
var headers = new Headers(map);
|
||||
expect(headers.get('foo')).toBe('bar');
|
||||
expect(headers.getAll('foo')).toEqual(['bar']);
|
||||
headers.set('foo', 'baz');
|
||||
expect(headers.get('foo')).toBe('baz');
|
||||
expect(headers.getAll('foo')).toEqual(['baz']);
|
||||
});
|
||||
|
||||
|
||||
it('should convert input array to string', () => {
|
||||
var headers = new Headers();
|
||||
var inputArr = ['bar', 'baz'];
|
||||
headers.set('foo', inputArr);
|
||||
expect(/bar, ?baz/g.test(headers.get('foo'))).toBe(true);
|
||||
expect(/bar, ?baz/g.test(headers.getAll('foo')[0])).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,249 +0,0 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
afterEach,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
SpyObject
|
||||
} from 'angular2/test_lib';
|
||||
import {Http} from 'angular2/src/http/http';
|
||||
import {Injector, bind} from 'angular2/di';
|
||||
import {MockBackend, MockConnection} from 'angular2/src/http/backends/mock_backend';
|
||||
import {Response} from 'angular2/src/http/static_response';
|
||||
import {RequestMethods} from 'angular2/src/http/enums';
|
||||
import {BaseRequestOptions, RequestOptions} from 'angular2/src/http/base_request_options';
|
||||
import {ResponseOptions} from 'angular2/src/http/base_response_options';
|
||||
import {Request} from 'angular2/src/http/static_request';
|
||||
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||
import {ConnectionBackend} from 'angular2/src/http/interfaces';
|
||||
import {URLSearchParams} from 'angular2/src/http/url_search_params';
|
||||
|
||||
class SpyObserver extends SpyObject {
|
||||
onNext: Function;
|
||||
onError: Function;
|
||||
onCompleted: Function;
|
||||
constructor() {
|
||||
super();
|
||||
this.onNext = this.spy('onNext');
|
||||
this.onError = this.spy('onError');
|
||||
this.onCompleted = this.spy('onCompleted');
|
||||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('http', () => {
|
||||
var url = 'http://foo.bar';
|
||||
var http: Http;
|
||||
var injector: Injector;
|
||||
var backend: MockBackend;
|
||||
var baseResponse;
|
||||
beforeEach(() => {
|
||||
injector = Injector.resolveAndCreate([
|
||||
BaseRequestOptions,
|
||||
MockBackend,
|
||||
bind(Http).toFactory(
|
||||
function(backend: ConnectionBackend, defaultOptions: BaseRequestOptions) {
|
||||
return new Http(backend, defaultOptions);
|
||||
},
|
||||
[MockBackend, BaseRequestOptions])
|
||||
]);
|
||||
http = injector.get(Http);
|
||||
backend = injector.get(MockBackend);
|
||||
baseResponse = new Response(new ResponseOptions({body: 'base response'}));
|
||||
});
|
||||
|
||||
afterEach(() => backend.verifyNoPendingRequests());
|
||||
|
||||
describe('Http', () => {
|
||||
describe('.request()', () => {
|
||||
it('should return an Observable',
|
||||
() => { expect(ObservableWrapper.isObservable(http.request(url))).toBe(true); });
|
||||
|
||||
|
||||
it('should accept a fully-qualified request as its only parameter',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.url).toBe('https://google.com');
|
||||
c.mockRespond(new Response(new ResponseOptions({body: 'Thank you'})));
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(
|
||||
http.request(new Request(new RequestOptions({url: 'https://google.com'}))),
|
||||
(res) => {});
|
||||
}));
|
||||
|
||||
|
||||
it('should perform a get request for given url if only passed a string',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections,
|
||||
c => c.mockRespond(baseResponse));
|
||||
ObservableWrapper.subscribe<Response>(http.request('http://basic.connection'), res => {
|
||||
expect(res.text()).toBe('base response');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
// TODO: make dart not complain about "argument type 'Map' cannot be assigned to the
|
||||
// parameter type 'IRequestOptions'"
|
||||
// xit('should perform a get request for given url if passed a dictionary',
|
||||
// inject([AsyncTestCompleter], async => {
|
||||
// ObservableWrapper.subscribe(backend.connections, c => c.mockRespond(baseResponse));
|
||||
// ObservableWrapper.subscribe(http.request(url, {method: RequestMethods.GET}), res =>
|
||||
// {
|
||||
// expect(res.text()).toBe('base response');
|
||||
// async.done();
|
||||
// });
|
||||
// }));
|
||||
});
|
||||
|
||||
|
||||
describe('.get()', () => {
|
||||
it('should perform a get request for given url', inject([AsyncTestCompleter], async => {
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.method).toBe(RequestMethods.GET);
|
||||
backend.resolveAllConnections();
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(http.get(url), res => {});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('.post()', () => {
|
||||
it('should perform a post request for given url', inject([AsyncTestCompleter], async => {
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.method).toBe(RequestMethods.POST);
|
||||
backend.resolveAllConnections();
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(http.post(url, 'post me'), res => {});
|
||||
}));
|
||||
|
||||
|
||||
it('should attach the provided body to the request', inject([AsyncTestCompleter], async => {
|
||||
var body = 'this is my post body';
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.text()).toBe(body);
|
||||
backend.resolveAllConnections();
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(http.post(url, body), res => {});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('.put()', () => {
|
||||
it('should perform a put request for given url', inject([AsyncTestCompleter], async => {
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.method).toBe(RequestMethods.PUT);
|
||||
backend.resolveAllConnections();
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(http.put(url, 'put me'), res => {});
|
||||
}));
|
||||
|
||||
it('should attach the provided body to the request', inject([AsyncTestCompleter], async => {
|
||||
var body = 'this is my put body';
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.text()).toBe(body);
|
||||
backend.resolveAllConnections();
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(http.put(url, body), res => {});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('.delete()', () => {
|
||||
it('should perform a delete request for given url', inject([AsyncTestCompleter], async => {
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.method).toBe(RequestMethods.DELETE);
|
||||
backend.resolveAllConnections();
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(http.delete(url), res => {});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('.patch()', () => {
|
||||
it('should perform a patch request for given url', inject([AsyncTestCompleter], async => {
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.method).toBe(RequestMethods.PATCH);
|
||||
backend.resolveAllConnections();
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(http.patch(url, 'this is my patch body'), res => {});
|
||||
}));
|
||||
|
||||
it('should attach the provided body to the request', inject([AsyncTestCompleter], async => {
|
||||
var body = 'this is my patch body';
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.text()).toBe(body);
|
||||
backend.resolveAllConnections();
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(http.patch(url, body), res => {});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('.head()', () => {
|
||||
it('should perform a head request for given url', inject([AsyncTestCompleter], async => {
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.method).toBe(RequestMethods.HEAD);
|
||||
backend.resolveAllConnections();
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(http.head(url), res => {});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('searchParams', () => {
|
||||
it('should append search params to url', inject([AsyncTestCompleter], async => {
|
||||
var params = new URLSearchParams();
|
||||
params.append('q', 'puppies');
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.url).toEqual('https://www.google.com?q=puppies');
|
||||
backend.resolveAllConnections();
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(
|
||||
http.get('https://www.google.com', new RequestOptions({search: params})),
|
||||
res => {});
|
||||
}));
|
||||
|
||||
|
||||
it('should append string search params to url', inject([AsyncTestCompleter], async => {
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.url).toEqual('https://www.google.com?q=piggies');
|
||||
backend.resolveAllConnections();
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(
|
||||
http.get('https://www.google.com', new RequestOptions({search: 'q=piggies'})),
|
||||
res => {});
|
||||
}));
|
||||
|
||||
|
||||
it('should produce valid url when url already contains a query',
|
||||
inject([AsyncTestCompleter], async => {
|
||||
ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
|
||||
expect(c.request.url).toEqual('https://www.google.com?q=angular&as_eq=1.x');
|
||||
backend.resolveAllConnections();
|
||||
async.done();
|
||||
});
|
||||
ObservableWrapper.subscribe(http.get('https://www.google.com?q=angular',
|
||||
new RequestOptions({search: 'as_eq=1.x'})),
|
||||
res => {});
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit
|
||||
} from 'angular2/test_lib';
|
||||
import {URLSearchParams} from 'angular2/src/http/url_search_params';
|
||||
|
||||
export function main() {
|
||||
describe('URLSearchParams', () => {
|
||||
it('should conform to spec', () => {
|
||||
var paramsString = "q=URLUtils.searchParams&topic=api";
|
||||
var searchParams = new URLSearchParams(paramsString);
|
||||
|
||||
// Tests borrowed from example at
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
|
||||
// Compliant with spec described at https://url.spec.whatwg.org/#urlsearchparams
|
||||
expect(searchParams.has("topic")).toBe(true);
|
||||
expect(searchParams.has("foo")).toBe(false);
|
||||
expect(searchParams.get("topic")).toEqual("api");
|
||||
expect(searchParams.getAll("topic")).toEqual(["api"]);
|
||||
expect(searchParams.get("foo")).toBe(null);
|
||||
searchParams.append("topic", "webdev");
|
||||
expect(searchParams.getAll("topic")).toEqual(["api", "webdev"]);
|
||||
expect(searchParams.toString()).toEqual("q=URLUtils.searchParams&topic=api&topic=webdev");
|
||||
searchParams.delete("topic");
|
||||
expect(searchParams.toString()).toEqual("q=URLUtils.searchParams");
|
||||
|
||||
// Test default constructor
|
||||
expect(new URLSearchParams().toString()).toBe("");
|
||||
});
|
||||
|
||||
|
||||
it('should support map-like merging operation via setAll()', () => {
|
||||
var mapA = new URLSearchParams('a=1&a=2&a=3&c=8');
|
||||
var mapB = new URLSearchParams('a=4&a=5&a=6&b=7');
|
||||
mapA.setAll(mapB);
|
||||
expect(mapA.has('a')).toBe(true);
|
||||
expect(mapA.has('b')).toBe(true);
|
||||
expect(mapA.has('c')).toBe(true);
|
||||
expect(mapA.getAll('a')).toEqual(['4']);
|
||||
expect(mapA.getAll('b')).toEqual(['7']);
|
||||
expect(mapA.getAll('c')).toEqual(['8']);
|
||||
expect(mapA.toString()).toEqual('a=4&c=8&b=7');
|
||||
});
|
||||
|
||||
|
||||
it('should support multimap-like merging operation via appendAll()', () => {
|
||||
var mapA = new URLSearchParams('a=1&a=2&a=3&c=8');
|
||||
var mapB = new URLSearchParams('a=4&a=5&a=6&b=7');
|
||||
mapA.appendAll(mapB);
|
||||
expect(mapA.has('a')).toBe(true);
|
||||
expect(mapA.has('b')).toBe(true);
|
||||
expect(mapA.has('c')).toBe(true);
|
||||
expect(mapA.getAll('a')).toEqual(['1', '2', '3', '4', '5', '6']);
|
||||
expect(mapA.getAll('b')).toEqual(['7']);
|
||||
expect(mapA.getAll('c')).toEqual(['8']);
|
||||
expect(mapA.toString()).toEqual('a=1&a=2&a=3&a=4&a=5&a=6&c=8&b=7');
|
||||
});
|
||||
|
||||
|
||||
it('should support multimap-like merging operation via replaceAll()', () => {
|
||||
var mapA = new URLSearchParams('a=1&a=2&a=3&c=8');
|
||||
var mapB = new URLSearchParams('a=4&a=5&a=6&b=7');
|
||||
mapA.replaceAll(mapB);
|
||||
expect(mapA.has('a')).toBe(true);
|
||||
expect(mapA.has('b')).toBe(true);
|
||||
expect(mapA.has('c')).toBe(true);
|
||||
expect(mapA.getAll('a')).toEqual(['4', '5', '6']);
|
||||
expect(mapA.getAll('b')).toEqual(['7']);
|
||||
expect(mapA.getAll('c')).toEqual(['8']);
|
||||
expect(mapA.toString()).toEqual('a=4&a=5&a=6&c=8&b=7');
|
||||
});
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user