repackaging: all the file moves

This commit is contained in:
Igor Minar
2016-04-28 08:02:15 -07:00
committed by Misko Hevery
parent 4fe0f1fa65
commit 505da6c0a8
739 changed files with 0 additions and 52 deletions

View File

@ -0,0 +1,304 @@
/**
* @module
* @description
* The http module provides services to perform http requests. To get started, see the {@link Http}
* class.
*/
import {provide, Provider} from 'angular2/core';
import {Http, Jsonp} from './src/http/http';
import {XHRBackend, XHRConnection} from './src/http/backends/xhr_backend';
import {JSONPBackend, JSONPBackend_, JSONPConnection} from './src/http/backends/jsonp_backend';
import {BrowserXhr} from './src/http/backends/browser_xhr';
import {BrowserJsonp} from './src/http/backends/browser_jsonp';
import {BaseRequestOptions, RequestOptions} from './src/http/base_request_options';
import {ConnectionBackend} from './src/http/interfaces';
import {BaseResponseOptions, ResponseOptions} from './src/http/base_response_options';
export {Request} from './src/http/static_request';
export {Response} from './src/http/static_response';
export {
RequestOptionsArgs,
ResponseOptionsArgs,
Connection,
ConnectionBackend
} from './src/http/interfaces';
export {BrowserXhr} from './src/http/backends/browser_xhr';
export {BaseRequestOptions, RequestOptions} from './src/http/base_request_options';
export {BaseResponseOptions, ResponseOptions} from './src/http/base_response_options';
export {XHRBackend, XHRConnection} from './src/http/backends/xhr_backend';
export {JSONPBackend, JSONPConnection} from './src/http/backends/jsonp_backend';
export {Http, Jsonp} from './src/http/http';
export {Headers} from './src/http/headers';
export {ResponseType, ReadyState, RequestMethod} from './src/http/enums';
export {URLSearchParams} from './src/http/url_search_params';
/**
* Provides a basic set of injectables to use the {@link Http} service in any application.
*
* The `HTTP_PROVIDERS` should be included either in a component's injector,
* or in the root injector when bootstrapping an application.
*
* ### Example ([live demo](http://plnkr.co/edit/snj7Nv?p=preview))
*
* ```
* import {Component} from 'angular2/core';
* import {bootstrap} from 'angular2/platform/browser';
* import {NgFor} from 'angular2/common';
* import {HTTP_PROVIDERS, Http} from 'angular2/http';
*
* @Component({
* selector: 'app',
* providers: [HTTP_PROVIDERS],
* template: `
* <div>
* <h1>People</h1>
* <ul>
* <li *ngFor="let person of people">
* {{person.name}}
* </li>
* </ul>
* </div>
* `,
* directives: [NgFor]
* })
* export class App {
* people: Object[];
* constructor(http:Http) {
* http.get('people.json').subscribe(res => {
* this.people = res.json();
* });
* }
* active:boolean = false;
* toggleActiveState() {
* this.active = !this.active;
* }
* }
*
* bootstrap(App)
* .catch(err => console.error(err));
* ```
*
* The primary public API included in `HTTP_PROVIDERS` is the {@link Http} class.
* However, other providers required by `Http` are included,
* which may be beneficial to override in certain cases.
*
* The providers included in `HTTP_PROVIDERS` include:
* * {@link Http}
* * {@link XHRBackend}
* * `BrowserXHR` - Private factory to create `XMLHttpRequest` instances
* * {@link RequestOptions} - Bound to {@link BaseRequestOptions} class
* * {@link ResponseOptions} - Bound to {@link BaseResponseOptions} class
*
* There may be cases where it makes sense to extend the base request options,
* such as to add a search string to be appended to all URLs.
* To accomplish this, a new provider for {@link RequestOptions} should
* be added in the same injector as `HTTP_PROVIDERS`.
*
* ### Example ([live demo](http://plnkr.co/edit/aCMEXi?p=preview))
*
* ```
* import {provide} from 'angular2/core';
* import {bootstrap} from 'angular2/platform/browser';
* import {HTTP_PROVIDERS, BaseRequestOptions, RequestOptions} from 'angular2/http';
*
* class MyOptions extends BaseRequestOptions {
* search: string = 'coreTeam=true';
* }
*
* bootstrap(App, [HTTP_PROVIDERS, provide(RequestOptions, {useClass: MyOptions})])
* .catch(err => console.error(err));
* ```
*
* Likewise, to use a mock backend for unit tests, the {@link XHRBackend}
* provider should be bound to {@link MockBackend}.
*
* ### Example ([live demo](http://plnkr.co/edit/7LWALD?p=preview))
*
* ```
* import {provide} from 'angular2/core';
* import {bootstrap} from 'angular2/platform/browser';
* import {HTTP_PROVIDERS, Http, Response, XHRBackend} from 'angular2/http';
* import {MockBackend} from 'angular2/http/testing';
*
* var people = [{name: 'Jeff'}, {name: 'Tobias'}];
*
* var injector = Injector.resolveAndCreate([
* HTTP_PROVIDERS,
* MockBackend,
* provide(XHRBackend, {useExisting: MockBackend})
* ]);
* var http = injector.get(Http);
* var backend = injector.get(MockBackend);
*
* // Listen for any new requests
* backend.connections.observer({
* next: connection => {
* var response = new Response({body: people});
* setTimeout(() => {
* // Send a response to the request
* connection.mockRespond(response);
* });
* }
* });
*
* http.get('people.json').observer({
* next: res => {
* // Response came from mock backend
* console.log('first person', res.json()[0].name);
* }
* });
* ```
*/
export const HTTP_PROVIDERS: any[] = [
// TODO(pascal): use factory type annotations once supported in DI
// issue: https://github.com/angular/angular/issues/3183
provide(Http,
{
useFactory: (xhrBackend: XHRBackend, requestOptions: RequestOptions) =>
new Http(xhrBackend, requestOptions),
deps: [XHRBackend, RequestOptions]
}),
BrowserXhr,
provide(RequestOptions, {useClass: BaseRequestOptions}),
provide(ResponseOptions, {useClass: BaseResponseOptions}),
XHRBackend
];
/**
* See {@link HTTP_PROVIDERS} instead.
*
* @deprecated
*/
export const HTTP_BINDINGS = HTTP_PROVIDERS;
/**
* Provides a basic set of providers to use the {@link Jsonp} service in any application.
*
* The `JSONP_PROVIDERS` should be included either in a component's injector,
* or in the root injector when bootstrapping an application.
*
* ### Example ([live demo](http://plnkr.co/edit/vmeN4F?p=preview))
*
* ```
* import {Component} from 'angular2/core';
* import {NgFor} from 'angular2/common';
* import {JSONP_PROVIDERS, Jsonp} from 'angular2/http';
*
* @Component({
* selector: 'app',
* providers: [JSONP_PROVIDERS],
* template: `
* <div>
* <h1>People</h1>
* <ul>
* <li *ngFor="let person of people">
* {{person.name}}
* </li>
* </ul>
* </div>
* `,
* directives: [NgFor]
* })
* export class App {
* people: Array<Object>;
* constructor(jsonp:Jsonp) {
* jsonp.request('people.json').subscribe(res => {
* this.people = res.json();
* })
* }
* }
* ```
*
* The primary public API included in `JSONP_PROVIDERS` is the {@link Jsonp} class.
* However, other providers required by `Jsonp` are included,
* which may be beneficial to override in certain cases.
*
* The providers included in `JSONP_PROVIDERS` include:
* * {@link Jsonp}
* * {@link JSONPBackend}
* * `BrowserJsonp` - Private factory
* * {@link RequestOptions} - Bound to {@link BaseRequestOptions} class
* * {@link ResponseOptions} - Bound to {@link BaseResponseOptions} class
*
* There may be cases where it makes sense to extend the base request options,
* such as to add a search string to be appended to all URLs.
* To accomplish this, a new provider for {@link RequestOptions} should
* be added in the same injector as `JSONP_PROVIDERS`.
*
* ### Example ([live demo](http://plnkr.co/edit/TFug7x?p=preview))
*
* ```
* import {provide} from 'angular2/core';
* import {bootstrap} from 'angular2/platform/browser';
* import {JSONP_PROVIDERS, BaseRequestOptions, RequestOptions} from 'angular2/http';
*
* class MyOptions extends BaseRequestOptions {
* search: string = 'coreTeam=true';
* }
*
* bootstrap(App, [JSONP_PROVIDERS, provide(RequestOptions, {useClass: MyOptions})])
* .catch(err => console.error(err));
* ```
*
* Likewise, to use a mock backend for unit tests, the {@link JSONPBackend}
* provider should be bound to {@link MockBackend}.
*
* ### Example ([live demo](http://plnkr.co/edit/HDqZWL?p=preview))
*
* ```
* import {provide, Injector} from 'angular2/core';
* import {JSONP_PROVIDERS, Jsonp, Response, JSONPBackend} from 'angular2/http';
* import {MockBackend} from 'angular2/http/testing';
*
* var people = [{name: 'Jeff'}, {name: 'Tobias'}];
* var injector = Injector.resolveAndCreate([
* JSONP_PROVIDERS,
* MockBackend,
* provide(JSONPBackend, {useExisting: MockBackend})
* ]);
* var jsonp = injector.get(Jsonp);
* var backend = injector.get(MockBackend);
*
* // Listen for any new requests
* backend.connections.observer({
* next: connection => {
* var response = new Response({body: people});
* setTimeout(() => {
* // Send a response to the request
* connection.mockRespond(response);
* });
* }
* });
* jsonp.get('people.json').observer({
* next: res => {
* // Response came from mock backend
* console.log('first person', res.json()[0].name);
* }
* });
* ```
*/
export const JSONP_PROVIDERS: any[] = [
// TODO(pascal): use factory type annotations once supported in DI
// issue: https://github.com/angular/angular/issues/3183
provide(Jsonp,
{
useFactory: (jsonpBackend: JSONPBackend, requestOptions: RequestOptions) =>
new Jsonp(jsonpBackend, requestOptions),
deps: [JSONPBackend, RequestOptions]
}),
BrowserJsonp,
provide(RequestOptions, {useClass: BaseRequestOptions}),
provide(ResponseOptions, {useClass: BaseResponseOptions}),
provide(JSONPBackend, {useClass: JSONPBackend_})
];
/**
* See {@link JSONP_PROVIDERS} instead.
*
* @deprecated
*/
export const JSON_BINDINGS = JSONP_PROVIDERS;

View File

@ -0,0 +1,48 @@
import {Injectable} from 'angular2/core';
import {global} from 'angular2/src/facade/lang';
let _nextRequestId = 0;
export const JSONP_HOME = '__ng_jsonp__';
var _jsonpConnections: {[key: string]: any} = null;
function _getJsonpConnections(): {[key: string]: any} {
if (_jsonpConnections === null) {
_jsonpConnections = (<{[key: string]: any}>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));
}
}
}

View File

@ -0,0 +1,12 @@
import {Injectable} from 'angular2/core';
/**
* A backend for http that uses the `XMLHttpRequest` browser API.
*
* Take care not to evaluate this in non-browser contexts.
*/
@Injectable()
export class BrowserXhr {
constructor() {}
build(): any { return <any>(new XMLHttpRequest()); }
}

View File

@ -0,0 +1,148 @@
import {ConnectionBackend, Connection} from '../interfaces';
import {ReadyState, RequestMethod, ResponseType} from '../enums';
import {Request} from '../static_request';
import {Response} from '../static_response';
import {ResponseOptions, BaseResponseOptions} from '../base_response_options';
import {Injectable} from 'angular2/core';
import {BrowserJsonp} from './browser_jsonp';
import {makeTypeError} from 'angular2/src/facade/exceptions';
import {StringWrapper, isPresent} from 'angular2/src/facade/lang';
import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
const JSONP_ERR_NO_CALLBACK = 'JSONP injected script did not invoke callback.';
const JSONP_ERR_WRONG_METHOD = 'JSONP requests must use GET request method.';
/**
* Abstract base class for an in-flight JSONP request.
*/
export abstract class JSONPConnection implements Connection {
/**
* The {@link ReadyState} of this request.
*/
readyState: ReadyState;
/**
* The outgoing HTTP request.
*/
request: Request;
/**
* An observable that completes with the response, when the request is finished.
*/
response: Observable<Response>;
/**
* Callback called when the JSONP request completes, to notify the application
* of the new data.
*/
abstract finished(data?: any): void;
}
export class JSONPConnection_ extends JSONPConnection {
private _id: string;
private _script: Element;
private _responseData: any;
private _finished: boolean = false;
constructor(req: Request, private _dom: BrowserJsonp,
private baseResponseOptions?: ResponseOptions) {
super();
if (req.method !== RequestMethod.Get) {
throw makeTypeError(JSONP_ERR_WRONG_METHOD);
}
this.request = req;
this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
this.readyState = ReadyState.Loading;
let id = this._id = _dom.nextRequestID();
_dom.exposeConnection(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 = url.substring(0, url.length - '=JSONP_CALLBACK'.length) + `=${callback}`;
}
let script = this._script = _dom.build(url);
let onLoad = (event: Event) => {
if (this.readyState === ReadyState.Cancelled) return;
this.readyState = ReadyState.Done;
_dom.cleanup(script);
if (!this._finished) {
let responseOptions =
new ResponseOptions({body: JSONP_ERR_NO_CALLBACK, type: ResponseType.Error, url});
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
responseObserver.error(new Response(responseOptions));
return;
}
let responseOptions = new ResponseOptions({body: this._responseData, url});
if (isPresent(this.baseResponseOptions)) {
responseOptions = this.baseResponseOptions.merge(responseOptions);
}
responseObserver.next(new Response(responseOptions));
responseObserver.complete();
};
let onError = (error: Error) => {
if (this.readyState === ReadyState.Cancelled) return;
this.readyState = ReadyState.Done;
_dom.cleanup(script);
let responseOptions = new ResponseOptions({body: error.message, type: ResponseType.Error});
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
responseObserver.error(new Response(responseOptions));
};
script.addEventListener('load', onLoad);
script.addEventListener('error', onError);
_dom.send(script);
return () => {
this.readyState = ReadyState.Cancelled;
script.removeEventListener('load', onLoad);
script.removeEventListener('error', onError);
if (isPresent(script)) {
this._dom.cleanup(script);
}
};
});
}
finished(data?: any) {
// Don't leak connections
this._finished = true;
this._dom.removeConnection(this._id);
if (this.readyState === ReadyState.Cancelled) return;
this._responseData = data;
}
}
/**
* A {@link ConnectionBackend} that uses the JSONP strategy of making requests.
*/
export abstract class JSONPBackend extends ConnectionBackend {}
@Injectable()
export class JSONPBackend_ extends JSONPBackend {
constructor(private _browserJSONP: BrowserJsonp, private _baseResponseOptions: ResponseOptions) {
super();
}
createConnection(request: Request): JSONPConnection {
return new JSONPConnection_(request, this._browserJSONP, this._baseResponseOptions);
}
}

View File

@ -0,0 +1,127 @@
import {ConnectionBackend, Connection} from '../interfaces';
import {ReadyState, RequestMethod, ResponseType} from '../enums';
import {Request} from '../static_request';
import {Response} from '../static_response';
import {Headers} from '../headers';
import {ResponseOptions, BaseResponseOptions} from '../base_response_options';
import {Injectable} from 'angular2/core';
import {BrowserXhr} from './browser_xhr';
import {isPresent} from 'angular2/src/facade/lang';
import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
import {isSuccess, getResponseURL} from '../http_utils';
/**
* 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: Observable<Response>;
readyState: ReadyState;
constructor(req: Request, browserXHR: BrowserXhr, baseResponseOptions?: ResponseOptions) {
this.request = req;
this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
let _xhr: XMLHttpRequest = browserXHR.build();
_xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
// load event handler
let onLoad = () => {
// 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 body = isPresent(_xhr.response) ? _xhr.response : _xhr.responseText;
let headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
let url = getResponseURL(_xhr);
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
let status: number = _xhr.status === 1223 ? 204 : _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 = body ? 200 : 0;
}
var responseOptions = new ResponseOptions({body, status, headers, url});
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
let response = new Response(responseOptions);
if (isSuccess(status)) {
responseObserver.next(response);
// TODO(gdi2290): defer complete if array buffer until done
responseObserver.complete();
return;
}
responseObserver.error(response);
};
// error event handler
let onError = (err: any) => {
var responseOptions = new ResponseOptions({body: err, type: ResponseType.Error});
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
responseObserver.error(new Response(responseOptions));
};
if (isPresent(req.headers)) {
req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(',')));
}
_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);
_xhr.send(this.request.text());
return () => {
_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_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, HTTP_PROVIDERS, BaseRequestOptions} from 'angular2/http';
* @Component({
* viewProviders: [
* HTTP_PROVIDERS,
* provide(Http, {useFactory: (backend, options) => {
* return new Http(backend, options);
* }, deps: [MyNodeBackend, BaseRequestOptions]})]
* })
* class MyComponent {
* constructor(http:Http) {
* http.request('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);
}
}

View File

@ -0,0 +1,153 @@
import {isPresent, isString} from 'angular2/src/facade/lang';
import {Headers} from './headers';
import {RequestMethod} from './enums';
import {RequestOptionsArgs} from './interfaces';
import {Injectable} from 'angular2/core';
import {URLSearchParams} from './url_search_params';
import {normalizeMethodName} from './http_utils';
/**
* Creates a request options object to be optionally provided when instantiating a
* {@link Request}.
*
* This class is based on the `RequestInit` description in the [Fetch
* Spec](https://fetch.spec.whatwg.org/#requestinit).
*
* All values are null by default. Typical defaults can be found in the {@link BaseRequestOptions}
* class, which sub-classes `RequestOptions`.
*
* ### Example ([live demo](http://plnkr.co/edit/7Wvi3lfLq41aQPKlxB4O?p=preview))
*
* ```typescript
* import {RequestOptions, Request, RequestMethod} from 'angular2/http';
*
* var options = new RequestOptions({
* method: RequestMethod.Post,
* url: 'https://google.com'
* });
* var req = new Request(options);
* console.log('req.method:', RequestMethod[req.method]); // Post
* console.log('options.url:', options.url); // https://google.com
* ```
*/
export class RequestOptions {
/**
* Http method with which to execute a {@link Request}.
* Acceptable methods are defined in the {@link RequestMethod} enum.
*/
method: RequestMethod | string;
/**
* {@link Headers} to be attached to a {@link Request}.
*/
headers: Headers;
/**
* Body to be used when creating a {@link Request}.
*/
// TODO: support FormData, Blob, URLSearchParams
body: string;
/**
* Url with which to perform a {@link Request}.
*/
url: string;
/**
* Search parameters to be included in a {@link Request}.
*/
search: URLSearchParams;
constructor({method, headers, body, url, search}: RequestOptionsArgs = {}) {
this.method = isPresent(method) ? normalizeMethodName(method) : null;
this.headers = isPresent(headers) ? headers : null;
this.body = isPresent(body) ? body : 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. This method will not change the values of the instance on which it is being
* called.
*
* Note that `headers` and `search` will override existing values completely if present in
* the `options` object. If these values should be merged, it should be done prior to calling
* `merge` on the `RequestOptions` instance.
*
* ### Example ([live demo](http://plnkr.co/edit/6w8XA8YTkDRcPYpdB9dk?p=preview))
*
* ```typescript
* import {RequestOptions, Request, RequestMethod} from 'angular2/http';
*
* var options = new RequestOptions({
* method: RequestMethod.Post
* });
* var req = new Request(options.merge({
* url: 'https://google.com'
* }));
* console.log('req.method:', RequestMethod[req.method]); // Post
* console.log('options.url:', options.url); // null
* console.log('req.url:', req.url); // https://google.com
* ```
*/
merge(options?: RequestOptionsArgs): 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,
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
});
}
}
/**
* Subclass of {@link RequestOptions}, with default values.
*
* Default values:
* * method: {@link RequestMethod RequestMethod.Get}
* * headers: empty {@link Headers} object
*
* This class could be extended and bound to the {@link RequestOptions} class
* when configuring an {@link Injector}, in order to override the default options
* used by {@link Http} to create and send {@link Request Requests}.
*
* ### Example ([live demo](http://plnkr.co/edit/LEKVSx?p=preview))
*
* ```typescript
* import {provide} from 'angular2/core';
* import {bootstrap} from 'angular2/platform/browser';
* import {HTTP_PROVIDERS, Http, BaseRequestOptions, RequestOptions} from 'angular2/http';
* import {App} from './myapp';
*
* class MyOptions extends BaseRequestOptions {
* search: string = 'coreTeam=true';
* }
*
* bootstrap(App, [HTTP_PROVIDERS, provide(RequestOptions, {useClass: MyOptions})]);
* ```
*
* The options could also be extended when manually creating a {@link Request}
* object.
*
* ### Example ([live demo](http://plnkr.co/edit/oyBoEvNtDhOSfi9YxaVb?p=preview))
*
* ```
* import {BaseRequestOptions, Request, RequestMethod} from 'angular2/http';
*
* var options = new BaseRequestOptions();
* var req = new Request(options.merge({
* method: RequestMethod.Post,
* url: 'https://google.com'
* }));
* console.log('req.method:', RequestMethod[req.method]); // Post
* console.log('options.url:', options.url); // null
* console.log('req.url:', req.url); // https://google.com
* ```
*/
@Injectable()
export class BaseRequestOptions extends RequestOptions {
constructor() { super({method: RequestMethod.Get, headers: new Headers()}); }
}

View File

@ -0,0 +1,153 @@
import {Injectable} from 'angular2/core';
import {isPresent, isJsObject} from 'angular2/src/facade/lang';
import {Headers} from './headers';
import {ResponseType} from './enums';
import {ResponseOptionsArgs} from './interfaces';
/**
* Creates a response options object to be optionally provided when instantiating a
* {@link Response}.
*
* This class is based on the `ResponseInit` description in the [Fetch
* Spec](https://fetch.spec.whatwg.org/#responseinit).
*
* All values are null by default. Typical defaults can be found in the
* {@link BaseResponseOptions} class, which sub-classes `ResponseOptions`.
*
* This class may be used in tests to build {@link Response Responses} for
* mock responses (see {@link MockBackend}).
*
* ### Example ([live demo](http://plnkr.co/edit/P9Jkk8e8cz6NVzbcxEsD?p=preview))
*
* ```typescript
* import {ResponseOptions, Response} from 'angular2/http';
*
* var options = new ResponseOptions({
* body: '{"name":"Jeff"}'
* });
* var res = new Response(options);
*
* console.log('res.json():', res.json()); // Object {name: "Jeff"}
* ```
*/
export class ResponseOptions {
// TODO: ArrayBuffer | FormData | Blob
/**
* String or Object representing the body of the {@link Response}.
*/
body: string | Object;
/**
* Http {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html status code}
* associated with the response.
*/
status: number;
/**
* Response {@link Headers headers}
*/
headers: Headers;
/**
* @internal
*/
statusText: string;
/**
* @internal
*/
type: ResponseType;
url: string;
constructor({body, status, headers, statusText, type, url}: ResponseOptionsArgs = {}) {
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;
}
/**
* Creates a copy of the `ResponseOptions` instance, using the optional input as values to
* override
* existing values. This method will not change the values of the instance on which it is being
* called.
*
* This may be useful when sharing a base `ResponseOptions` object inside tests,
* where certain properties may change from test to test.
*
* ### Example ([live demo](http://plnkr.co/edit/1lXquqFfgduTFBWjNoRE?p=preview))
*
* ```typescript
* import {ResponseOptions, Response} from 'angular2/http';
*
* var options = new ResponseOptions({
* body: {name: 'Jeff'}
* });
* var res = new Response(options.merge({
* url: 'https://google.com'
* }));
* console.log('options.url:', options.url); // null
* console.log('res.json():', res.json()); // Object {name: "Jeff"}
* console.log('res.url:', res.url); // https://google.com
* ```
*/
merge(options?: ResponseOptionsArgs): 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,
});
}
}
/**
* Subclass of {@link ResponseOptions}, with default values.
*
* Default values:
* * status: 200
* * headers: empty {@link Headers} object
*
* This class could be extended and bound to the {@link ResponseOptions} class
* when configuring an {@link Injector}, in order to override the default options
* used by {@link Http} to create {@link Response Responses}.
*
* ### Example ([live demo](http://plnkr.co/edit/qv8DLT?p=preview))
*
* ```typescript
* import {provide} from 'angular2/core';
* import {bootstrap} from 'angular2/platform/browser';
* import {HTTP_PROVIDERS, Headers, Http, BaseResponseOptions, ResponseOptions} from
* 'angular2/http';
* import {App} from './myapp';
*
* class MyOptions extends BaseResponseOptions {
* headers:Headers = new Headers({network: 'github'});
* }
*
* bootstrap(App, [HTTP_PROVIDERS, provide(ResponseOptions, {useClass: MyOptions})]);
* ```
*
* The options could also be extended when manually creating a {@link Response}
* object.
*
* ### Example ([live demo](http://plnkr.co/edit/VngosOWiaExEtbstDoix?p=preview))
*
* ```
* import {BaseResponseOptions, Response} from 'angular2/http';
*
* var options = new BaseResponseOptions();
* var res = new Response(options.merge({
* body: 'Angular2',
* headers: new Headers({framework: 'angular'})
* }));
* console.log('res.headers.get("framework"):', res.headers.get('framework')); // angular
* console.log('res.text():', res.text()); // Angular2;
* ```
*/
@Injectable()
export class BaseResponseOptions extends ResponseOptions {
constructor() {
super({status: 200, statusText: 'Ok', type: ResponseType.Default, headers: new Headers()});
}
}

View File

@ -0,0 +1,40 @@
import {StringMapWrapper} from 'angular2/src/facade/collection';
/**
* Supported http methods.
*/
export enum RequestMethod {
Get,
Post,
Put,
Delete,
Options,
Head,
Patch
}
/**
* 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 ReadyState {
Unsent,
Open,
HeadersReceived,
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 ResponseType {
Basic,
Cors,
Default,
Error,
Opaque
}

View File

@ -0,0 +1,159 @@
import {
isPresent,
isBlank,
isJsObject,
isType,
StringWrapper,
Json
} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {
isListLikeIterable,
iterateListLike,
Map,
MapWrapper,
StringMapWrapper,
ListWrapper,
} 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 between this `Headers` implementation and the spec is the
* lack of an `entries` method.
*
* ### Example ([live demo](http://plnkr.co/edit/MTdwT6?p=preview))
*
* ```
* import {Headers} from 'angular2/http';
*
* var firstHeaders = new Headers();
* firstHeaders.append('Content-Type', 'image/jpeg');
* console.log(firstHeaders.get('Content-Type')) //'image/jpeg'
*
* // Create headers from Plain Old JavaScript Object
* var secondHeaders = new Headers({
* 'X-My-Custom-Header': 'Angular'
* });
* console.log(secondHeaders.get('X-My-Custom-Header')); //'Angular'
*
* var thirdHeaders = new Headers(secondHeaders);
* console.log(thirdHeaders.get('X-My-Custom-Header')); //'Angular'
* ```
*/
export class Headers {
/** @internal */
_headersMap: Map<string, string[]>;
constructor(headers?: Headers | {[key: string]: any}) {
if (headers instanceof Headers) {
this._headersMap = (<Headers>headers)._headersMap;
return;
}
this._headersMap = new Map<string, string[]>();
if (isBlank(headers)) {
return;
}
// headers instanceof StringMap
StringMapWrapper.forEach(headers, (v: any, k: string) => {
this._headersMap.set(k, isListLikeIterable(v) ? v : [v]);
});
}
/**
* Returns a new Headers instance from the given DOMString of Response Headers
*/
static fromResponseHeaderString(headersString: string): Headers {
return headersString.trim()
.split('\n')
.map(val => val.split(':'))
.map(([key, ...parts]) => ([key.trim(), parts.join(':').trim()]))
.reduce((headers, [key, value]) => !headers.set(key, value) && headers, new Headers());
}
/**
* 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 { this._headersMap.delete(name); }
forEach(fn: (values: string[], name: string, headers: Map<string, string[]>) => void): void {
this._headersMap.forEach(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(): string[] { return MapWrapper.keys(this._headersMap); }
/**
* Sets or overrides header value for given name.
*/
set(header: string, value: string | string[]): void {
var list: string[] = [];
if (isListLikeIterable(value)) {
var pushValue = (<string[]>value).join(',');
list.push(pushValue);
} else {
list.push(<string>value);
}
this._headersMap.set(header, list);
}
/**
* Returns values of all headers.
*/
values(): string[][] { return MapWrapper.values(this._headersMap); }
/**
* Returns string of all headers.
*/
toJSON(): {[key: string]: any} {
let serializableHeaders = {};
this._headersMap.forEach((values: string[], name: string) => {
let list = [];
iterateListLike(values, val => list = ListWrapper.concat(list, val.split(',')));
serializableHeaders[name] = list;
});
return serializableHeaders;
}
/**
* Returns list of header values for a given name.
*/
getAll(header: string): 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'); }
}

View File

@ -0,0 +1,200 @@
import {isString, isPresent, isBlank} from 'angular2/src/facade/lang';
import {makeTypeError} from 'angular2/src/facade/exceptions';
import {Injectable} from 'angular2/core';
import {RequestOptionsArgs, Connection, ConnectionBackend} from './interfaces';
import {Request} from './static_request';
import {Response} from './static_response';
import {BaseRequestOptions, RequestOptions} from './base_request_options';
import {RequestMethod} from './enums';
import {Observable} from 'rxjs/Observable';
function httpRequest(backend: ConnectionBackend, request: Request): Observable<Response> {
return backend.createConnection(request).response;
}
function mergeOptions(defaultOpts: BaseRequestOptions, providedOpts: RequestOptionsArgs,
method: RequestMethod, url: string): RequestOptions {
var newOptions = defaultOpts;
if (isPresent(providedOpts)) {
// Hack so Dart can used named parameters
return newOptions.merge(new RequestOptions({
method: providedOpts.method || method,
url: providedOpts.url || url,
search: providedOpts.search,
headers: providedOpts.headers,
body: providedOpts.body
}));
}
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 `Observable` which will emit a single {@link Response} when a
* response is received.
*
* ### Example
*
* ```typescript
* import {Http, HTTP_PROVIDERS} from 'angular2/http';
* @Component({
* selector: 'http-app',
* viewProviders: [HTTP_PROVIDERS],
* templateUrl: 'people.html'
* })
* class PeopleComponent {
* constructor(http: Http) {
* http.get('people.json')
* // 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);
* }
* }
* ```
*
*
* ### Example
*
* ```
* http.get('people.json').observer({next: (value) => this.people = value});
* ```
*
* 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} provider, as in the following example:
*
* ### Example
*
* ```typescript
* import {BaseRequestOptions, Http} from 'angular2/http';
* import {MockBackend} from 'angular2/http/testing';
* var injector = Injector.resolveAndCreate([
* BaseRequestOptions,
* MockBackend,
* provide(Http, {useFactory:
* function(backend, defaultOptions) {
* return new Http(backend, defaultOptions);
* },
* deps: [MockBackend, BaseRequestOptions]})
* ]);
* var http = injector.get(Http);
* http.get('request-from-mock-backend.json').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?: RequestOptionsArgs): Observable<Response> {
var responseObservable: any;
if (isString(url)) {
responseObservable = httpRequest(
this._backend,
new Request(mergeOptions(this._defaultOptions, options, RequestMethod.Get, <string>url)));
} else if (url instanceof Request) {
responseObservable = httpRequest(this._backend, url);
} else {
throw makeTypeError('First argument must be a url string or Request instance.');
}
return responseObservable;
}
/**
* Performs a request with `get` http method.
*/
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return httpRequest(this._backend, new Request(mergeOptions(this._defaultOptions, options,
RequestMethod.Get, url)));
}
/**
* Performs a request with `post` http method.
*/
post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return httpRequest(
this._backend,
new Request(mergeOptions(this._defaultOptions.merge(new RequestOptions({body: body})),
options, RequestMethod.Post, url)));
}
/**
* Performs a request with `put` http method.
*/
put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return httpRequest(
this._backend,
new Request(mergeOptions(this._defaultOptions.merge(new RequestOptions({body: body})),
options, RequestMethod.Put, url)));
}
/**
* Performs a request with `delete` http method.
*/
delete (url: string, options?: RequestOptionsArgs): Observable<Response> {
return httpRequest(this._backend, new Request(mergeOptions(this._defaultOptions, options,
RequestMethod.Delete, url)));
}
/**
* Performs a request with `patch` http method.
*/
patch(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return httpRequest(
this._backend,
new Request(mergeOptions(this._defaultOptions.merge(new RequestOptions({body: body})),
options, RequestMethod.Patch, url)));
}
/**
* Performs a request with `head` http method.
*/
head(url: string, options?: RequestOptionsArgs): Observable<Response> {
return httpRequest(this._backend, new Request(mergeOptions(this._defaultOptions, options,
RequestMethod.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?: RequestOptionsArgs): Observable<Response> {
var responseObservable: any;
if (isString(url)) {
url =
new Request(mergeOptions(this._defaultOptions, options, RequestMethod.Get, <string>url));
}
if (url instanceof Request) {
if (url.method !== RequestMethod.Get) {
makeTypeError('JSONP requests must use GET request method.');
}
responseObservable = httpRequest(this._backend, url);
} else {
throw makeTypeError('First argument must be a url string or Request instance.');
}
return responseObservable;
}
}

View File

@ -0,0 +1,32 @@
import {isString} from 'angular2/src/facade/lang';
import {RequestMethod} from './enums';
import {makeTypeError} from 'angular2/src/facade/exceptions';
import {Response} from './static_response';
export function normalizeMethodName(method: string | RequestMethod): RequestMethod {
if (isString(method)) {
var originalMethod = method;
method = (<string>method)
.replace(/(\w)(\w*)/g, (g0: string, g1: string, g2: string) =>
g1.toUpperCase() + g2.toLowerCase());
method = <number>(<{[key: string]: any}>RequestMethod)[method];
if (typeof method !== 'number')
throw makeTypeError(
`Invalid request method. The method "${originalMethod}" is not supported.`);
}
return <RequestMethod>method;
}
export const isSuccess = (status: number): boolean => (status >= 200 && status < 300);
export function getResponseURL(xhr: any): string {
if ('responseURL' in xhr) {
return xhr.responseURL;
}
if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) {
return xhr.getResponseHeader('X-Request-URL');
}
return;
}
export {isJsObject} from 'angular2/src/facade/lang';

View File

@ -0,0 +1,13 @@
// Index to be used if Http is ever configured as a standalone npm package.
// require('reflect-metadata');
// require('es6-shim');
// import {HTTP_PROVIDERS, JSONP_PROVIDERS, Http, Jsonp} from './http';
// import {Injector} from 'angular2/core';
// export * from './http';
// /**
// * TODO(jeffbcross): export each as their own top-level file, to require as:
// * require('angular2/http'); require('http/jsonp');
// */
// export var http = Injector.resolveAndCreate([HTTP_PROVIDERS]).get(Http);
// export var jsonp = Injector.resolveAndCreate([JSONP_PROVIDERS]).get(Jsonp);

View File

@ -0,0 +1,55 @@
import {ReadyState, RequestMethod, ResponseType} from './enums';
import {Headers} from './headers';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {EventEmitter} from 'angular2/src/facade/async';
import {Request} from './static_request';
import {URLSearchParams} from './url_search_params';
/**
* 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 abstract class ConnectionBackend { abstract createConnection(request: any): Connection; }
/**
* Abstract class from which real connections are derived.
*/
export abstract class Connection {
readyState: ReadyState;
request: Request;
response: any; // TODO: generic of <Response>;
}
/**
* Interface for options to construct a RequestOptions, based on
* [RequestInit](https://fetch.spec.whatwg.org/#requestinit) from the Fetch spec.
*/
export interface RequestOptionsArgs {
url?: string;
method?: string | RequestMethod;
search?: string | URLSearchParams;
headers?: Headers;
// TODO: Support Blob, ArrayBuffer, JSON, URLSearchParams, FormData
body?: string;
}
/**
* Required structure when constructing new Request();
*/
export interface RequestArgs extends RequestOptionsArgs { url: string; }
/**
* Interface for options to construct a Response, based on
* [ResponseInit](https://fetch.spec.whatwg.org/#responseinit) from the Fetch spec.
*/
export type ResponseOptionsArgs = {
// TODO: Support Blob, ArrayBuffer, JSON
body?: string | Object | FormData;
status?: number;
statusText?: string;
headers?: Headers;
type?: ResponseType;
url?: string;
}

View File

@ -0,0 +1,15 @@
{
"name": "ngHttp",
"version": "<%= packageJson.version %>",
"description": "Http module for Angular 2",
"homepage": "<%= packageJson.homepage %>",
"bugs": "<%= packageJson.bugs %>",
"contributors": <%= JSON.stringify(packageJson.contributors) %>,
"license": "<%= packageJson.license %>",
"repository": <%= JSON.stringify(packageJson.repository) %>,
"devDependencies": <%= JSON.stringify(packageJson.defaultDevDependencies) %>,
"peerDependencies": {
"angular2": "<%= packageJson.version %>",
"rxjs": "<%= packageJson.dependencies['rxjs'] %>"
}
}

View File

@ -0,0 +1,88 @@
import {RequestMethod} from './enums';
import {RequestArgs} from './interfaces';
import {Headers} from './headers';
import {normalizeMethodName} from './http_utils';
import {RegExpWrapper, 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.
*
* `Request` instances are typically created by higher-level classes, like {@link Http} and
* {@link Jsonp}, but it may occasionally be useful to explicitly create `Request` instances.
* One such example is when creating services that wrap higher-level services, like {@link Http},
* where it may be useful to generate a `Request` with arbitrary headers and search params.
*
* ```typescript
* import {Injectable, Injector} from 'angular2/core';
* import {HTTP_PROVIDERS, Http, Request, RequestMethod} from 'angular2/http';
*
* @Injectable()
* class AutoAuthenticator {
* constructor(public http:Http) {}
* request(url:string) {
* return this.http.request(new Request({
* method: RequestMethod.Get,
* url: url,
* search: 'password=123'
* }));
* }
* }
*
* var injector = Injector.resolveAndCreate([HTTP_PROVIDERS, AutoAuthenticator]);
* var authenticator = injector.get(AutoAuthenticator);
* authenticator.request('people.json').subscribe(res => {
* //URL should have included '?password=123'
* console.log('people', res.json());
* });
* ```
*/
export class Request {
/**
* Http method with which to perform the request.
*/
method: RequestMethod;
/**
* {@link Headers} instance
*/
headers: Headers;
/** Url of the remote resource */
url: string;
// TODO: support URLSearchParams | FormData | Blob | ArrayBuffer
private _body: string;
constructor(requestOptions: RequestArgs) {
// 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 = normalizeMethodName(requestOptions.method);
// TODO(jeffbcross): implement behavior
// Defaults to 'omit', consistent with browser
// TODO(jeffbcross): implement behavior
this.headers = new Headers(requestOptions.headers);
}
/**
* 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() : ''; }
}

View File

@ -0,0 +1,117 @@
import {ResponseType} from './enums';
import {isString, isPresent, Json} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
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: ResponseType;
/**
* 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.ok = (this.status >= 200 && this.status <= 299);
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(): any {
var jsonResponse: string | Object;
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');
}
}

View File

@ -0,0 +1,130 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {Map, MapWrapper, ListWrapper, isListLikeIterable} from 'angular2/src/facade/collection';
function paramParser(rawParams: string = ''): Map<string, string[]> {
var map = new Map<string, string[]>();
if (rawParams.length > 0) {
var params: string[] = rawParams.split('&');
params.forEach((param: string) => {
var split: string[] = param.split('=');
var key = split[0];
var val = split[1];
var list = isPresent(map.get(key)) ? map.get(key) : [];
list.push(val);
map.set(key, list);
});
}
return map;
}
/**
* 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, 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): 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) {
searchParams.paramsMap.forEach((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) {
searchParams.paramsMap.forEach((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) {
searchParams.paramsMap.forEach((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: string[] = [];
this.paramsMap.forEach((values, k) => { values.forEach(v => paramsList.push(k + '=' + v)); });
return paramsList.join('&');
}
delete (param: string): void { this.paramsMap.delete(param); }
}

View File

@ -0,0 +1,187 @@
import {
AsyncTestCompleter,
afterEach,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
SpyObject
} from 'angular2/testing_internal';
import {ObservableWrapper, TimerWrapper} from 'angular2/src/facade/async';
import {BrowserJsonp} from 'angular2/src/http/backends/browser_jsonp';
import {
JSONPConnection,
JSONPConnection_,
JSONPBackend,
JSONPBackend_
} from 'angular2/src/http/backends/jsonp_backend';
import {provide, Injector, ReflectiveInjector} from 'angular2/core';
import {isPresent, StringWrapper} from 'angular2/src/facade/lang';
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 {ResponseType, ReadyState, RequestMethod} from 'angular2/src/http/enums';
var addEventListenerSpy: any;
var existingScripts: MockBrowserJsonp[] = [];
var unused: Response;
class MockBrowserJsonp extends BrowserJsonp {
src: string;
callbacks = new Map<string, (data: any) => any>();
constructor() { super(); }
addEventListener(type: string, cb: (data: any) => any) { this.callbacks.set(type, cb); }
removeEventListener(type: string, cb: Function) { this.callbacks.delete(type); }
dispatchEvent(type: string, argument?: any) {
if (!isPresent(argument)) {
argument = {};
}
let cb = this.callbacks.get(type);
if (isPresent(cb)) {
cb(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: JSONPBackend_;
let sampleRequest: Request;
beforeEach(() => {
let injector = ReflectiveInjector.resolveAndCreate([
provide(ResponseOptions, {useClass: BaseResponseOptions}),
provide(BrowserJsonp, {useClass: MockBrowserJsonp}),
provide(JSONPBackend, {useClass: 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: JSONPConnection;
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: AsyncTestCompleter) => {
let connection = new JSONPConnection_(sampleRequest, new MockBrowserJsonp(),
new ResponseOptions({type: ResponseType.Error}));
connection.response.subscribe(res => {
expect(res.type).toBe(ResponseType.Error);
async.done();
});
connection.finished();
existingScripts[0].dispatchEvent('load');
}));
it('should ignore load/callback when disposed',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
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');
let request = connection.response.subscribe(loadSpy, errorSpy, returnSpy);
request.unsubscribe();
connection.finished('Fake data');
existingScripts[0].dispatchEvent('load');
TimerWrapper.setTimeout(() => {
expect(connection.readyState).toBe(ReadyState.Cancelled);
expect(loadSpy).not.toHaveBeenCalled();
expect(errorSpy).not.toHaveBeenCalled();
expect(returnSpy).not.toHaveBeenCalled();
async.done();
}, 10);
}));
it('should report error if loaded without invoking callback',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let connection = new JSONPConnection_(sampleRequest, new MockBrowserJsonp());
connection.response.subscribe(
res => {
expect("response listener called").toBe(false);
async.done();
},
err => {
expect(err.text()).toEqual('JSONP injected script did not invoke callback.');
async.done();
});
existingScripts[0].dispatchEvent('load');
}));
it('should report error if script contains error',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let connection = new JSONPConnection_(sampleRequest, new MockBrowserJsonp());
connection.response.subscribe(
res => {
expect("response listener called").toBe(false);
async.done();
},
err => {
expect(err.text()).toBe('Oops!');
async.done();
});
existingScripts[0].dispatchEvent('error', ({message: "Oops!"}));
}));
it('should throw if request method is not GET', () => {
[RequestMethod.Post, RequestMethod.Put, RequestMethod.Delete, RequestMethod.Options,
RequestMethod.Head, RequestMethod.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()).response.subscribe())
.toThrowError();
});
});
it('should respond with data passed to callback',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let connection = new JSONPConnection_(sampleRequest, new MockBrowserJsonp());
connection.response.subscribe(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');
}));
});
});
}

View File

@ -0,0 +1,125 @@
import {
AsyncTestCompleter,
afterEach,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
SpyObject
} from 'angular2/testing_internal';
import {ObservableWrapper} from 'angular2/src/facade/async';
import {BrowserXhr} from 'angular2/src/http/backends/browser_xhr';
import {MockConnection, MockBackend} from 'angular2/src/http/backends/mock_backend';
import {provide, Injector, ReflectiveInjector} from 'angular2/core';
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 {ResponseType} from 'angular2/src/http/enums';
import {ReplaySubject} from 'rxjs/ReplaySubject';
export function main() {
describe('MockBackend', () => {
var backend: MockBackend;
var sampleRequest1: Request;
var sampleResponse1: Response;
var sampleRequest2: Request;
var sampleResponse2: Response;
beforeEach(() => {
var injector = ReflectiveInjector.resolveAndCreate(
[provide(ResponseOptions, {useClass: BaseResponseOptions}), MockBackend]);
backend = injector.get(MockBackend);
var base = new BaseRequestOptions();
sampleRequest1 = new Request(base.merge(new RequestOptions({url: 'https://google.com'})));
sampleResponse1 = new Response(new ResponseOptions({body: 'response1'}));
sampleRequest2 = new Request(base.merge(new RequestOptions({url: 'https://google.com'})));
sampleResponse2 = new Response(new ResponseOptions({body: 'response2'}));
});
it('should create a new MockBackend', () => {expect(backend).toBeAnInstanceOf(MockBackend)});
it('should create a new MockConnection',
() => {expect(backend.createConnection(sampleRequest1)).toBeAnInstanceOf(MockConnection)});
it('should create a new connection and allow subscription', () => {
let connection: MockConnection = backend.createConnection(sampleRequest1);
connection.response.subscribe(() => {});
});
it('should allow responding after subscription',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let connection: MockConnection = backend.createConnection(sampleRequest1);
connection.response.subscribe(() => { async.done(); });
connection.mockRespond(sampleResponse1);
}));
it('should allow subscribing after responding',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let connection: MockConnection = backend.createConnection(sampleRequest1);
connection.mockRespond(sampleResponse1);
connection.response.subscribe(() => { async.done(); });
}));
it('should allow responding after subscription with an error',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let connection: MockConnection = backend.createConnection(sampleRequest1);
connection.response.subscribe(null, () => { async.done(); });
connection.mockError(new Error('nope'));
}));
it('should not throw when there are no unresolved requests',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let connection: MockConnection = backend.createConnection(sampleRequest1);
connection.response.subscribe(() => { async.done(); });
connection.mockRespond(sampleResponse1);
backend.verifyNoPendingRequests();
}));
xit('should throw when there are unresolved requests',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let connection: MockConnection = backend.createConnection(sampleRequest1);
connection.response.subscribe(() => { async.done(); });
backend.verifyNoPendingRequests();
}));
it('should work when requests are resolved out of order',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let connection1: MockConnection = backend.createConnection(sampleRequest1);
let connection2: MockConnection = backend.createConnection(sampleRequest1);
connection1.response.subscribe(() => { async.done(); });
connection2.response.subscribe(() => {});
connection2.mockRespond(sampleResponse1);
connection1.mockRespond(sampleResponse1);
backend.verifyNoPendingRequests();
}));
xit('should allow double subscribing',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let responses: Response[] = [sampleResponse1, sampleResponse2];
backend.connections.subscribe((c: MockConnection) => c.mockRespond(responses.shift()));
let responseObservable: ReplaySubject<Response> =
backend.createConnection(sampleRequest1).response;
responseObservable.subscribe(res => expect(res.text()).toBe('response1'));
responseObservable.subscribe(res => expect(res.text()).toBe('response2'), null,
async.done);
}));
// TODO(robwormald): readyStates are leaving?
it('should allow resolution of requests manually', () => {
let connection1: MockConnection = backend.createConnection(sampleRequest1);
let connection2: MockConnection = backend.createConnection(sampleRequest1);
connection1.response.subscribe(() => {});
connection2.response.subscribe(() => {});
backend.resolveAllConnections();
backend.verifyNoPendingRequests();
});
});
}

View File

@ -0,0 +1,338 @@
import {
AsyncTestCompleter,
afterEach,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
SpyObject
} from 'angular2/testing_internal';
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 {provide, Injector, ReflectiveInjector} from 'angular2/core';
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 {ResponseType} from 'angular2/src/http/enums';
var abortSpy: any;
var sendSpy: any;
var openSpy: any;
var setRequestHeaderSpy: any;
var addEventListenerSpy: any;
var existingXHRs: MockBrowserXHR[] = [];
var unused: Response;
class MockBrowserXHR extends BrowserXhr {
abort: any;
send: any;
open: any;
response: any;
responseText: string;
setRequestHeader: any;
callbacks = new Map<string, Function>();
status: number;
responseHeaders: string;
responseURL: string;
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');
}
setStatusCode(status: number) { this.status = status; }
setResponse(value: string) { this.response = value; }
setResponseText(value: string) { this.responseText = value; }
setResponseURL(value: string) { this.responseURL = value; }
setResponseHeaders(value: string) { this.responseHeaders = value; }
getAllResponseHeaders() { return this.responseHeaders || ''; }
getResponseHeader(key: string) {
return Headers.fromResponseHeaderString(this.responseHeaders).get(key);
}
addEventListener(type: string, cb: Function) { this.callbacks.set(type, cb); }
removeEventListener(type: string, cb: Function) { this.callbacks.delete(type); }
dispatchEvent(type: string) { this.callbacks.get(type)({}); }
build() {
var xhr = new MockBrowserXHR();
existingXHRs.push(xhr);
return xhr;
}
}
export function main() {
describe('XHRBackend', () => {
var backend: XHRBackend;
var sampleRequest: Request;
beforeEach(() => {
var injector = ReflectiveInjector.resolveAndCreate([
provide(ResponseOptions, {useClass: BaseResponseOptions}),
provide(BrowserXhr, {useClass: 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: AsyncTestCompleter) => {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({type: ResponseType.Error}));
connection.response.subscribe((res: Response) => {
expect(res.type).toBe(ResponseType.Error);
async.done();
});
existingXHRs[0].setStatusCode(200);
existingXHRs[0].dispatchEvent('load');
}));
it('should complete a request', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({type: ResponseType.Error}));
connection.response.subscribe((res: Response) => {
expect(res.type).toBe(ResponseType.Error);
}, null, () => { async.done(); });
existingXHRs[0].setStatusCode(200);
existingXHRs[0].dispatchEvent('load');
}));
it('should call abort when disposed', () => {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR());
var request = connection.response.subscribe();
request.unsubscribe();
expect(abortSpy).toHaveBeenCalled();
});
it('should create an error Response on error',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({type: ResponseType.Error}));
connection.response.subscribe(null, (res: Response) => {
expect(res.type).toBe(ResponseType.Error);
async.done();
});
existingXHRs[0].dispatchEvent('error');
}));
it('should call open with method and url when subscribed to', () => {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR());
expect(openSpy).not.toHaveBeenCalled();
connection.response.subscribe();
expect(openSpy).toHaveBeenCalledWith('GET', sampleRequest.url);
});
it('should call send on the backend with request body when subscribed to', () => {
var body = 'Some body to love';
var base = new BaseRequestOptions();
var connection = new XHRConnection(
new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR());
expect(sendSpy).not.toHaveBeenCalled();
connection.response.subscribe();
expect(sendSpy).toHaveBeenCalledWith(body);
});
it('should attach headers to the request', () => {
var headers =
new Headers({'Content-Type': 'text/xml', 'Breaking-Bad': '<3', 'X-Multi': ['a', 'b']});
var base = new BaseRequestOptions();
var connection = new XHRConnection(
new Request(base.merge(new RequestOptions({headers: headers}))), new MockBrowserXHR());
connection.response.subscribe();
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', 'text/xml');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Breaking-Bad', '<3');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('X-Multi', 'a,b');
});
it('should return the correct status code',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var statusCode = 418;
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({status: statusCode}));
connection.response.subscribe(
(res: Response) => {
},
errRes => {
expect(errRes.status).toBe(statusCode);
async.done();
});
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load');
}));
it('should call next and complete on 200 codes',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var nextCalled = false;
var errorCalled = false;
var statusCode = 200;
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({status: statusCode}));
connection.response.subscribe(
(res: Response) => {
nextCalled = true;
expect(res.status).toBe(statusCode);
},
errRes => { errorCalled = true; }, () => {
expect(nextCalled).toBe(true);
expect(errorCalled).toBe(false);
async.done();
});
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load');
}));
it('should call error and not complete on 300+ codes',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var nextCalled = false;
var errorCalled = false;
var statusCode = 301;
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({status: statusCode}));
connection.response.subscribe((res: Response) => { nextCalled = true; }, errRes => {
expect(errRes.status).toBe(statusCode);
expect(nextCalled).toBe(false);
async.done();
}, () => { throw 'should not be called'; });
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load');
}));
it('should normalize IE\'s 1223 status code into 204',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var statusCode = 1223;
var normalizedCode = 204;
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({status: statusCode}));
connection.response.subscribe((res: Response) => {
expect(res.status).toBe(normalizedCode);
async.done();
});
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load');
}));
it('should normalize responseText and response',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var responseBody = 'Doge';
var connection1 =
new XHRConnection(sampleRequest, new MockBrowserXHR(), new ResponseOptions());
var connection2 =
new XHRConnection(sampleRequest, new MockBrowserXHR(), new ResponseOptions());
connection1.response.subscribe((res: Response) => {
expect(res.text()).toBe(responseBody);
connection2.response.subscribe(ress => {
expect(ress.text()).toBe(responseBody);
async.done();
});
existingXHRs[1].setStatusCode(200);
existingXHRs[1].setResponse(responseBody);
existingXHRs[1].dispatchEvent('load');
});
existingXHRs[0].setStatusCode(200);
existingXHRs[0].setResponseText(responseBody);
existingXHRs[0].dispatchEvent('load');
}));
it('should parse response headers and add them to the response',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var statusCode = 200;
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({status: statusCode}));
let responseHeaderString =
`Date: Fri, 20 Nov 2015 01:45:26 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive`
connection.response.subscribe((res: Response) => {
expect(res.headers.get('Date')).toEqual('Fri, 20 Nov 2015 01:45:26 GMT');
expect(res.headers.get('Content-Type')).toEqual('application/json; charset=utf-8');
expect(res.headers.get('Transfer-Encoding')).toEqual('chunked');
expect(res.headers.get('Connection')).toEqual('keep-alive');
async.done();
});
existingXHRs[0].setResponseHeaders(responseHeaderString);
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load');
}));
it('should add the responseURL to the response',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var statusCode = 200;
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({status: statusCode}));
connection.response.subscribe((res: Response) => {
expect(res.url).toEqual('http://google.com');
async.done();
});
existingXHRs[0].setResponseURL('http://google.com');
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load');
}));
it('should add use the X-Request-URL in CORS situations',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var statusCode = 200;
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({status: statusCode}));
var responseHeaders = `X-Request-URL: http://somedomain.com
Foo: Bar`
connection.response.subscribe((res: Response) => {
expect(res.url).toEqual('http://somedomain.com');
async.done();
});
existingXHRs[0].setResponseHeaders(responseHeaders);
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load');
}));
});
});
}

View File

@ -0,0 +1,30 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit
} from 'angular2/testing_internal';
import {BaseRequestOptions, RequestOptions} from 'angular2/src/http/base_request_options';
import {RequestMethod} 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: RequestMethod.Delete}));
expect(options2).not.toBe(options1);
expect(options2.method).toBe(RequestMethod.Delete);
});
it('should retain previously merged values when merging again', () => {
var options1 = new BaseRequestOptions();
var options2 = options1.merge(new RequestOptions({method: RequestMethod.Delete}));
expect(options2.method).toBe(RequestMethod.Delete);
});
});
}

View File

@ -0,0 +1,122 @@
import {Headers} from 'angular2/src/http/headers';
import {Json} from 'angular2/src/facade/lang';
import {Map, StringMapWrapper} from 'angular2/src/facade/collection';
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit
} from 'angular2/testing_internal';
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);
});
});
describe('.toJSON()', () => {
let headers = null;
let inputArr = null;
let obj = null;
beforeEach(() => {
headers = new Headers();
inputArr = ['application/jeisen', 'application/jason', 'application/patrickjs'];
obj = {'Accept': inputArr};
headers.set('Accept', inputArr);
});
it('should be serializable with toJSON', () => {
let stringifed = Json.stringify(obj);
let serializedHeaders = Json.stringify(headers);
expect(serializedHeaders).toEqual(stringifed);
});
it('should be able to parse serialized header', () => {
let stringifed = Json.stringify(obj);
let serializedHeaders = Json.stringify(headers);
expect(Json.parse(serializedHeaders)).toEqual(Json.parse(stringifed));
});
it('should be able to recreate serializedHeaders', () => {
let serializedHeaders = Json.stringify(headers);
let parsedHeaders = Json.parse(serializedHeaders);
let recreatedHeaders = new Headers(parsedHeaders);
expect(Json.stringify(parsedHeaders)).toEqual(Json.stringify(recreatedHeaders));
});
});
});
describe('.fromResponseHeaderString()', () => {
it('should parse a response header string', () => {
let responseHeaderString = `Date: Fri, 20 Nov 2015 01:45:26 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive`;
let responseHeaders = Headers.fromResponseHeaderString(responseHeaderString);
expect(responseHeaders.get('Date')).toEqual('Fri, 20 Nov 2015 01:45:26 GMT');
expect(responseHeaders.get('Content-Type')).toEqual('application/json; charset=utf-8');
expect(responseHeaders.get('Transfer-Encoding')).toEqual('chunked');
expect(responseHeaders.get('Connection')).toEqual('keep-alive');
});
});
}

View File

@ -0,0 +1,406 @@
import {
AsyncTestCompleter,
afterEach,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit
} from 'angular2/testing_internal';
import {Injector, provide, ReflectiveInjector} from 'angular2/core';
import {MockBackend, MockConnection} from 'angular2/src/http/backends/mock_backend';
import {
BaseRequestOptions,
ConnectionBackend,
Request,
RequestMethod,
RequestOptions,
Response,
ResponseOptions,
URLSearchParams,
JSONP_PROVIDERS,
HTTP_PROVIDERS,
XHRBackend,
JSONPBackend,
Http,
Jsonp
} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
export function main() {
describe('injectables', () => {
var url = 'http://foo.bar';
var http: Http;
var parentInjector: ReflectiveInjector;
var childInjector: ReflectiveInjector;
var jsonpBackend: MockBackend;
var xhrBackend: MockBackend;
var jsonp: Jsonp;
it('should allow using jsonpInjectables and httpInjectables in same injector',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
parentInjector = ReflectiveInjector.resolveAndCreate([
provide(XHRBackend, {useClass: MockBackend}),
provide(JSONPBackend, {useClass: MockBackend})
]);
childInjector = parentInjector.resolveAndCreateChild([
HTTP_PROVIDERS,
JSONP_PROVIDERS,
provide(XHRBackend, {useClass: MockBackend}),
provide(JSONPBackend, {useClass: MockBackend})
]);
http = childInjector.get(Http);
jsonp = childInjector.get(Jsonp);
jsonpBackend = childInjector.get(JSONPBackend);
xhrBackend = childInjector.get(XHRBackend);
var xhrCreatedConnections = 0;
var jsonpCreatedConnections = 0;
xhrBackend.connections.subscribe(() => {
xhrCreatedConnections++;
expect(xhrCreatedConnections).toEqual(1);
if (jsonpCreatedConnections) {
async.done();
}
});
http.get(url).subscribe(() => {});
jsonpBackend.connections.subscribe(() => {
jsonpCreatedConnections++;
expect(jsonpCreatedConnections).toEqual(1);
if (xhrCreatedConnections) {
async.done();
}
});
jsonp.request(url).subscribe(() => {});
}));
});
describe('http', () => {
var url = 'http://foo.bar';
var http: Http;
var injector: Injector;
var backend: MockBackend;
var baseResponse: Response;
var jsonp: Jsonp;
beforeEach(() => {
injector = ReflectiveInjector.resolveAndCreate([
BaseRequestOptions,
MockBackend,
provide(
Http,
{
useFactory: function(backend: ConnectionBackend, defaultOptions: BaseRequestOptions) {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
}),
provide(
Jsonp,
{
useFactory: function(backend: ConnectionBackend, defaultOptions: BaseRequestOptions) {
return new Jsonp(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
})
]);
http = injector.get(Http);
jsonp = injector.get(Jsonp);
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(http.request(url)).toBeAnInstanceOf(Observable); });
it('should accept a fully-qualified request as its only parameter',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.url).toBe('https://google.com');
c.mockRespond(new Response(new ResponseOptions({body: 'Thank you'})));
async.done();
});
http.request(new Request(new RequestOptions({url: 'https://google.com'})))
.subscribe((res: Response) => {});
}));
it('should accept a fully-qualified request as its only parameter',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.url).toBe('https://google.com');
expect(c.request.method).toBe(RequestMethod.Post);
c.mockRespond(new Response(new ResponseOptions({body: 'Thank you'})));
async.done();
});
http.request(new Request(new RequestOptions(
{url: 'https://google.com', method: RequestMethod.Post})))
.subscribe((res: Response) => {});
}));
it('should perform a get request for given url if only passed a string',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => c.mockRespond(baseResponse));
http.request('http://basic.connection')
.subscribe((res: Response) => {
expect(res.text()).toBe('base response');
async.done();
});
}));
it('should perform a post request for given url if options include a method',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.method).toEqual(RequestMethod.Post);
c.mockRespond(baseResponse);
});
let requestOptions = new RequestOptions({method: RequestMethod.Post});
http.request('http://basic.connection', requestOptions)
.subscribe((res: Response) => {
expect(res.text()).toBe('base response');
async.done();
});
}));
it('should perform a post request for given url if options include a method',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.method).toEqual(RequestMethod.Post);
c.mockRespond(baseResponse);
});
let requestOptions = {method: RequestMethod.Post};
http.request('http://basic.connection', requestOptions)
.subscribe((res: Response) => {
expect(res.text()).toBe('base response');
async.done();
});
}));
it('should perform a get request and complete the response',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => c.mockRespond(baseResponse));
http.request('http://basic.connection')
.subscribe((res: Response) => { expect(res.text()).toBe('base response'); }, null,
() => { async.done(); });
}));
it('should perform multiple get requests and complete the responses',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => c.mockRespond(baseResponse));
http.request('http://basic.connection')
.subscribe((res: Response) => { expect(res.text()).toBe('base response'); });
http.request('http://basic.connection')
.subscribe((res: Response) => { expect(res.text()).toBe('base response'); }, null,
() => { async.done(); });
}));
it('should throw if url is not a string or Request', () => {
var req = <Request>{};
expect(() => http.request(req))
.toThrowError('First argument must be a url string or Request instance.');
});
});
describe('.get()', () => {
it('should perform a get request for given url',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.method).toBe(RequestMethod.Get);
backend.resolveAllConnections();
async.done();
});
http.get(url).subscribe((res: Response) => {});
}));
});
describe('.post()', () => {
it('should perform a post request for given url',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.method).toBe(RequestMethod.Post);
backend.resolveAllConnections();
async.done();
});
http.post(url, 'post me').subscribe((res: Response) => {});
}));
it('should attach the provided body to the request',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var body = 'this is my post body';
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.text()).toBe(body);
backend.resolveAllConnections();
async.done();
});
http.post(url, body).subscribe((res: Response) => {});
}));
});
describe('.put()', () => {
it('should perform a put request for given url',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.method).toBe(RequestMethod.Put);
backend.resolveAllConnections();
async.done();
});
http.put(url, 'put me').subscribe((res: Response) => {});
}));
it('should attach the provided body to the request',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var body = 'this is my put body';
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.text()).toBe(body);
backend.resolveAllConnections();
async.done();
});
http.put(url, body).subscribe((res: Response) => {});
}));
});
describe('.delete()', () => {
it('should perform a delete request for given url',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.method).toBe(RequestMethod.Delete);
backend.resolveAllConnections();
async.done();
});
http.delete(url).subscribe((res: Response) => {});
}));
});
describe('.patch()', () => {
it('should perform a patch request for given url',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.method).toBe(RequestMethod.Patch);
backend.resolveAllConnections();
async.done();
});
http.patch(url, 'this is my patch body').subscribe((res: Response) => {});
}));
it('should attach the provided body to the request',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var body = 'this is my patch body';
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.text()).toBe(body);
backend.resolveAllConnections();
async.done();
});
http.patch(url, body).subscribe((res: Response) => {});
}));
});
describe('.head()', () => {
it('should perform a head request for given url',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.method).toBe(RequestMethod.Head);
backend.resolveAllConnections();
async.done();
});
http.head(url).subscribe((res: Response) => {});
}));
});
describe('searchParams', () => {
it('should append search params to url',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var params = new URLSearchParams();
params.append('q', 'puppies');
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.url).toEqual('https://www.google.com?q=puppies');
backend.resolveAllConnections();
async.done();
});
http.get('https://www.google.com', new RequestOptions({search: params}))
.subscribe((res: Response) => {});
}));
it('should append string search params to url',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.url).toEqual('https://www.google.com?q=piggies');
backend.resolveAllConnections();
async.done();
});
http.get('https://www.google.com', new RequestOptions({search: 'q=piggies'}))
.subscribe((res: Response) => {});
}));
it('should produce valid url when url already contains a query',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.url).toEqual('https://www.google.com?q=angular&as_eq=1.x');
backend.resolveAllConnections();
async.done();
});
http.get('https://www.google.com?q=angular', new RequestOptions({search: 'as_eq=1.x'}))
.subscribe((res: Response) => {});
}));
});
describe('string method names', () => {
it('should allow case insensitive strings for method names', () => {
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.method)
.toBe(RequestMethod.Post)
c.mockRespond(new Response(new ResponseOptions({body: 'Thank you'})));
async.done();
});
http.request(
new Request(new RequestOptions({url: 'https://google.com', method: 'PosT'})))
.subscribe((res: Response) => {});
});
});
it('should throw when invalid string parameter is passed for method name', () => {
expect(() => {
http.request(
new Request(new RequestOptions({url: 'https://google.com', method: 'Invalid'})));
}).toThrowError('Invalid request method. The method "Invalid" is not supported.');
});
});
});
describe('Jsonp', () => {
describe('.request()', () => {
it('should throw if url is not a string or Request', () => {
var req = <Request>{};
expect(() => jsonp.request(req))
.toThrowError('First argument must be a url string or Request instance.');
});
});
});
});
}

View File

@ -0,0 +1,24 @@
import {
describe,
expect,
it,
} from 'angular2/testing_internal';
import {ResponseOptions} from 'angular2/src/http/base_response_options';
import {Response} from 'angular2/src/http/static_response';
export function main() {
describe('Response', () => {
it('should be ok for 200 statuses', () => {
expect(new Response(new ResponseOptions({status: 200})).ok).toEqual(true);
expect(new Response(new ResponseOptions({status: 299})).ok).toEqual(true);
});
it('should not be ok for non 200 statuses', () => {
expect(new Response(new ResponseOptions({status: 199})).ok).toEqual(false);
expect(new Response(new ResponseOptions({status: 300})).ok).toEqual(false);
});
});
}

View File

@ -0,0 +1,80 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit
} from 'angular2/testing_internal';
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');
});
});
}

View File

@ -0,0 +1 @@
export * from 'angular2/src/http/backends/mock_backend';

View File

@ -0,0 +1,218 @@
import {Injectable} from 'angular2/core';
import {Request} from '../static_request';
import {Response} from '../static_response';
import {ReadyState} from '../enums';
import {Connection, ConnectionBackend} from '../interfaces';
import {isPresent} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {Subject} from 'rxjs/Subject';
import {ReplaySubject} from 'rxjs/ReplaySubject';
import {take} from 'rxjs/operator/take';
/**
*
* Mock Connection to represent a {@link Connection} for tests.
*
**/
export class MockConnection implements Connection {
// 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: ReadyState;
/**
* {@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: ReplaySubject<Response>;
constructor(req: Request) {
this.response = <any>take.call(new ReplaySubject(1), 1);
this.readyState = ReadyState.Open;
this.request = req;
}
/**
* 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 === ReadyState.Done || this.readyState === ReadyState.Cancelled) {
throw new BaseException('Connection has already been resolved');
}
this.readyState = ReadyState.Done;
this.response.next(res);
this.response.complete();
}
/**
* 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 = ReadyState.Done;
this.response.error(err);
}
}
/**
* A mock backend for testing the {@link Http} service.
*
* This class can be injected in tests, and should be used to override providers
* to other backends, such as {@link XHRBackend}.
*
* ### Example
*
* ```
* import {BaseRequestOptions, Http} from 'angular2/http';
* import {MockBackend} from 'angular2/http/testing';
* it('should get some data', inject([AsyncTestCompleter], (async) => {
* var connection;
* var injector = Injector.resolveAndCreate([
* MockBackend,
* provide(Http, {useFactory: (backend, options) => {
* return new Http(backend, options);
* }, deps: [MockBackend, BaseRequestOptions]})]);
* 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()
export class MockBackend implements ConnectionBackend {
/**
* {@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 {Http, BaseRequestOptions} from 'angular2/http';
* import {MockBackend} from 'angular2/http/testing';
* import {Injector} from 'angular2/core';
*
* 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,
* provide(Http, {useFactory: (backend, options) => {
* return new Http(backend, options);
* }, deps: [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: any; //<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: 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: any; // Subject<MockConnection>
constructor() {
this.connectionsArray = [];
this.connections = new Subject();
this.connections.subscribe((connection: MockConnection) =>
this.connectionsArray.push(connection));
this.pendingConnections = new Subject();
}
/**
* 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;
this.pendingConnections.subscribe((c: MockConnection) => 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() { this.connections.subscribe((c: MockConnection) => 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): MockConnection {
if (!isPresent(req) || !(req instanceof Request)) {
throw new BaseException(`createConnection requires an instance of Request, got ${req}`);
}
let connection = new MockConnection(req);
this.connections.next(connection);
return connection;
}
}