|
| 1 | +import {Injectable} from 'angular2/di'; |
| 2 | +import {Request} from 'angular2/src/http/static_request'; |
| 3 | +import {Response} from 'angular2/src/http/static_response'; |
| 4 | +import {ReadyStates} from 'angular2/src/http/enums'; |
| 5 | +import * as Rx from 'rx'; |
| 6 | + |
| 7 | +/** |
| 8 | + * Connection represents a request and response for an underlying transport, like XHR or mock. |
| 9 | + * The mock implementation contains helper methods to respond to connections within tests. |
| 10 | + * API subject to change and expand. |
| 11 | + **/ |
| 12 | +export class Connection { |
| 13 | + /** |
| 14 | + * Observer to call on download progress, if provided in config. |
| 15 | + **/ |
| 16 | + downloadObserver: Rx.Observer<Response>; |
| 17 | + |
| 18 | + /** |
| 19 | + * TODO |
| 20 | + * Name `readyState` should change to be more generic, and states could be made to be more |
| 21 | + * descriptive than XHR states. |
| 22 | + **/ |
| 23 | + |
| 24 | + readyState: ReadyStates; |
| 25 | + request: Request; |
| 26 | + response: Rx.Subject<Response>; |
| 27 | + |
| 28 | + constructor(req: Request) { |
| 29 | + // State |
| 30 | + if (Rx.hasOwnProperty('default')) { |
| 31 | + this.response = new ((<any>Rx).default.Rx.Subject)(); |
| 32 | + } else { |
| 33 | + this.response = new Rx.Subject<Response>(); |
| 34 | + } |
| 35 | + |
| 36 | + this.readyState = ReadyStates.OPEN; |
| 37 | + this.request = req; |
| 38 | + this.dispose = this.dispose.bind(this); |
| 39 | + } |
| 40 | + |
| 41 | + dispose() { |
| 42 | + if (this.readyState !== ReadyStates.DONE) { |
| 43 | + this.readyState = ReadyStates.CANCELLED; |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + /** |
| 48 | + * Called after a connection has been established. |
| 49 | + **/ |
| 50 | + mockRespond(res: Response) { |
| 51 | + if (this.readyState >= ReadyStates.DONE) { |
| 52 | + throw new Error('Connection has already been resolved'); |
| 53 | + } |
| 54 | + this.readyState = ReadyStates.DONE; |
| 55 | + this.response.onNext(res); |
| 56 | + this.response.onCompleted(); |
| 57 | + } |
| 58 | + |
| 59 | + mockDownload(res: Response) { |
| 60 | + this.downloadObserver.onNext(res); |
| 61 | + if (res.bytesLoaded === res.totalBytes) { |
| 62 | + this.downloadObserver.onCompleted(); |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + mockError(err?) { |
| 67 | + // Matches XHR semantics |
| 68 | + this.readyState = ReadyStates.DONE; |
| 69 | + this.response.onError(err); |
| 70 | + this.response.onCompleted(); |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +@Injectable() |
| 75 | +export class MockBackend { |
| 76 | + connections: Rx.Subject<Connection>; |
| 77 | + connectionsArray: Array<Connection>; |
| 78 | + pendingConnections: Rx.Observable<Connection>; |
| 79 | + constructor() { |
| 80 | + this.connectionsArray = []; |
| 81 | + if (Rx.hasOwnProperty('default')) { |
| 82 | + this.connections = new (<any>Rx).default.Rx.Subject(); |
| 83 | + } else { |
| 84 | + this.connections = new Rx.Subject<Connection>(); |
| 85 | + } |
| 86 | + this.connections.subscribe(connection => this.connectionsArray.push(connection)); |
| 87 | + this.pendingConnections = this.connections.filter((c) => c.readyState < ReadyStates.DONE); |
| 88 | + } |
| 89 | + |
| 90 | + verifyNoPendingRequests() { |
| 91 | + let pending = 0; |
| 92 | + this.pendingConnections.subscribe((c) => pending++); |
| 93 | + if (pending > 0) throw new Error(`${pending} pending connections to be resolved`); |
| 94 | + } |
| 95 | + |
| 96 | + resolveAllConnections() { this.connections.subscribe((c) => c.readyState = 4); } |
| 97 | + |
| 98 | + createConnection(req: Request) { |
| 99 | + if (!req || !(req instanceof Request)) { |
| 100 | + throw new Error(`createConnection requires an instance of Request, got ${req}`); |
| 101 | + } |
| 102 | + let connection = new Connection(req); |
| 103 | + this.connections.onNext(connection); |
| 104 | + return connection; |
| 105 | + } |
| 106 | +} |
0 commit comments