зеркало из https://github.com/microsoft/satcheljs.git
Init Commit
This commit is contained in:
Коммит
fd86e9e793
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
dist/
|
|
@ -0,0 +1 @@
|
|||
node_modules/
|
|
@ -0,0 +1,7 @@
|
|||
SatchelJS
|
||||
Copyright (c) Microsoft Corporation
|
||||
All rights reserved.
|
||||
MIT License
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,119 @@
|
|||
# Satchel
|
||||
|
||||
Satchel is a data store based on the [Flux architecture](http://facebook.github.io/react/blog/2014/05/06/flux.html). It is characterized by exposing an observable state that makes view updates painless and efficient.
|
||||
|
||||
|
||||
## Influences
|
||||
|
||||
Satchel is an attempt to synthesize the best of several dataflow patterns typically used to drive a React-based UI. In particular:
|
||||
* [Flux](http://facebook.github.io/react/blog/2014/05/06/flux.html) is not a library itself, but is a dataflow pattern conceived for use with React. In Flux, dataflow is unidirectional, and the only way to modify state is by dispatching actions through a central dispatcher.
|
||||
* [Redux](http://redux.js.org/index.html) is an implementation of Flux that consolidates stores into a single state tree and attempts to simplify state changes by making all mutations via pure functions called reducers. Ultimately, however, we found that reducers and immutable state were difficult to reason about, particularly in a large, interconnected app.
|
||||
* [MobX](http://mobxjs.github.io/mobx/index.html) provides a seamless way to make state observable, and allows React to listen to state changes and rerender in a very performant way. Satchel uses MobX under the covers to allow React components to observe the data they depend on.
|
||||
|
||||
|
||||
## Advantages
|
||||
|
||||
There are a number of advantages to using Satchel to maintain your application state. (Each of the frameworks above has some, but not all, of these qualities.)
|
||||
|
||||
* Satchel enables a very **performant UI**, only rerendering the minimal amount necessary. MobX makes UI updates very efficient by automatically detecting specifically what components need to rerender for a given state change.
|
||||
* Satchel's datastore allows for **isomorphic JavaScript** by making it feasible to render on the server and then serialize and pass the application state down to the client.
|
||||
* Satchel supports **middleware** that can act on each action that is dispatched. (For example, for tracing or performance instrumentation.)
|
||||
* Satchel requires **minimal boilerplate** code.
|
||||
|
||||
|
||||
## Installation
|
||||
Install via NPM:
|
||||
|
||||
`npm install satcheljs --save`
|
||||
|
||||
In order to use Satchel with React, you'll also need MobX and the MobX React bindings:
|
||||
|
||||
`npm install mobx --save`
|
||||
|
||||
`npm install mobx-react --save`
|
||||
|
||||
|
||||
## Usage
|
||||
The following examples assume you're developing in Typescript.
|
||||
|
||||
### Create a store with some initial state
|
||||
```typescript
|
||||
interface MyStoreSchema {
|
||||
foo: number;
|
||||
bar: string;
|
||||
}
|
||||
|
||||
var myStore = createStore<MyStoreSchema>(
|
||||
"mystore",
|
||||
{
|
||||
foo: 1,
|
||||
bar: "baz"
|
||||
});
|
||||
```
|
||||
|
||||
### Create a component that consumes your state
|
||||
Notice the @observer decorator on the component---this is what tells MobX to rerender the component if any of the data it relies on changes.
|
||||
|
||||
```typescript
|
||||
@observer
|
||||
class ApplicationComponent extends React.Component<any, any> {
|
||||
render() {
|
||||
return (<div>foo is {myStore.foo}</div>);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Implement an action to update the store
|
||||
|
||||
```typescript
|
||||
let updateFoo =
|
||||
function updateFoo(newFoo: number) {
|
||||
myStore.foo = newFoo;
|
||||
};
|
||||
|
||||
updateFoo = action("updateFoo")(updateFoo);
|
||||
```
|
||||
|
||||
Note that the above is just syntactic sugar for applying an @action decorator. Typescript doesn't support decorators on function expressions yet, but it will in 2.0. At that point the syntax for creating an action will be simply:
|
||||
```typescript
|
||||
let updateFoo =
|
||||
@action("updateFoo")
|
||||
function updateFoo(newFoo: number) {
|
||||
myStore.foo = newFoo;
|
||||
};
|
||||
```
|
||||
|
||||
### Call the action
|
||||
|
||||
It's just a function:
|
||||
|
||||
```typescript
|
||||
updateFoo(2);
|
||||
```
|
||||
|
||||
### Asynchronous actions
|
||||
|
||||
Often actions will need to do some sort of asynchronous work (such as making a server request) and then update the state based on the result.
|
||||
Since the asynchronous callback happens outside of the context of the original action the callback itself must be an action too.
|
||||
(Again, this syntax will be simplified once Typescript 2.0 is available.)
|
||||
|
||||
```typescript
|
||||
let updateFooAsync =
|
||||
function updateFooAsync(newFoo: number) {
|
||||
// You can modify the state in the original action
|
||||
myStore.loading = true;
|
||||
|
||||
// doSomethingAsync returns a promise
|
||||
doSomethingAsync().then(
|
||||
action("doSomethingAsyncCallback")(
|
||||
() => {
|
||||
// Modify the state again in the callback
|
||||
myStore.loading = false;
|
||||
myStore.foo = newFoo;
|
||||
}));
|
||||
};
|
||||
|
||||
updateFooAsync = action("updateFooAsync")(updateFooAsync);
|
||||
```
|
||||
|
||||
### License - MIT
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"spec_dir": "dist/test",
|
||||
"spec_files": [
|
||||
"**/*.js"
|
||||
],
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": false
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "satcheljs",
|
||||
"version": "0.6.0",
|
||||
"description": "Store implementation for functional reactive flux.",
|
||||
"main": "./dist/src/index.js",
|
||||
"scripts": {
|
||||
"build": "node_modules/.bin/tsc",
|
||||
"test": "npm run build && jasmine JASMINE_CONFIG_PATH=jasmine.json --verbose"
|
||||
},
|
||||
"author": "Scott Mikula <smikula@microsoft.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mobx": "~2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jasmine": "^2.4.1",
|
||||
"typescript": "~1.8.9"
|
||||
},
|
||||
"typings": "./dist/src/index.d.ts"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
interface ActionFunction {
|
||||
(): Promise<any> | void;
|
||||
}
|
||||
|
||||
export default ActionFunction;
|
|
@ -0,0 +1,7 @@
|
|||
import ActionFunction from './ActionFunction';
|
||||
|
||||
interface DispatchFunction {
|
||||
(action: ActionFunction, actionType: string, args: IArguments): Promise<any> | void;
|
||||
}
|
||||
|
||||
export default DispatchFunction;
|
|
@ -0,0 +1,8 @@
|
|||
import DispatchFunction from './DispatchFunction';
|
||||
import ActionFunction from './ActionFunction';
|
||||
|
||||
interface Middleware {
|
||||
(next: DispatchFunction, action: ActionFunction, actionType: string, args: IArguments): void;
|
||||
}
|
||||
|
||||
export default Middleware;
|
|
@ -0,0 +1,23 @@
|
|||
import dispatch from './dispatch';
|
||||
|
||||
export interface RawAction {
|
||||
(... args: any[]): Promise<any> | void;
|
||||
}
|
||||
|
||||
export default function action(actionType: string) {
|
||||
return function action<T extends RawAction>(target: T): T {
|
||||
let decoratedTarget = <T>function() {
|
||||
let returnValue: any;
|
||||
let passedArguments = arguments;
|
||||
|
||||
dispatch(
|
||||
() => { returnValue = target.apply(undefined, passedArguments); },
|
||||
actionType,
|
||||
arguments);
|
||||
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
return decoratedTarget;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import ActionFunction from './ActionFunction';
|
||||
import DispatchFunction from './DispatchFunction';
|
||||
import Middleware from './Middleware';
|
||||
|
||||
let internalDispatchWithMiddleware = finalDispatch;
|
||||
|
||||
export default function applyMiddleware(...middleware: Middleware[]) {
|
||||
var next: DispatchFunction = finalDispatch;
|
||||
for (var i = middleware.length - 1; i >= 0; i--) {
|
||||
next = applyMiddlewareInternal(middleware[i], next);
|
||||
}
|
||||
|
||||
internalDispatchWithMiddleware = next;
|
||||
}
|
||||
|
||||
function applyMiddlewareInternal(middleware: Middleware, next: DispatchFunction): DispatchFunction {
|
||||
return (action, actionType, args) => middleware(next, action, actionType, args);
|
||||
}
|
||||
|
||||
export function dispatchWithMiddleware(action: ActionFunction, actionType: string, args: IArguments) {
|
||||
internalDispatchWithMiddleware(action, actionType, args);
|
||||
}
|
||||
|
||||
function finalDispatch(action: ActionFunction, actionType: string, args: IArguments) {
|
||||
return action();
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import rootStore from './rootStore';
|
||||
import action from './action';
|
||||
|
||||
let internalCreateStore =
|
||||
function internalCreateStore(key: string, initialState: any) {
|
||||
rootStore.set(key, initialState);
|
||||
};
|
||||
|
||||
internalCreateStore = <any>action("createStore")(internalCreateStore);
|
||||
|
||||
export default function createStore<T>(key: string, initialState: T): T {
|
||||
internalCreateStore(key, initialState);
|
||||
return <T>rootStore.get(key);
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
import {useStrict, spy, action as mobxAction} from 'mobx';
|
||||
import ActionFunction from './ActionFunction';
|
||||
import DispatchFunction from './DispatchFunction';
|
||||
import { dispatchWithMiddleware } from './applyMiddleware';
|
||||
|
||||
var inDispatch: number = 0;
|
||||
|
||||
export default function dispatch(action: ActionFunction, actionType: string, args: IArguments): void {
|
||||
inDispatch++;
|
||||
|
||||
mobxAction(
|
||||
actionType ? actionType : "(anonymous action)",
|
||||
() => {
|
||||
dispatchWithMiddleware(action, actionType, args);
|
||||
})();
|
||||
|
||||
inDispatch--;
|
||||
}
|
||||
|
||||
// Guard against state changes happening outside of SatchelJS actions
|
||||
useStrict(true);
|
||||
|
||||
spy((change) => {
|
||||
if (!inDispatch && change.type == "action") {
|
||||
throw new Error('The state may only be changed by a SatchelJS action.');
|
||||
}
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
export { default as rootStore } from './rootStore';
|
||||
export { default as initializeState } from './initializeState';
|
||||
export { default as applyMiddleware } from './applyMiddleware';
|
||||
export { default as Middleware } from './Middleware';
|
||||
export { default as ActionFunction } from './ActionFunction';
|
||||
export { default as DispatchFunction } from './DispatchFunction';
|
||||
export { default as createStore } from './createStore';
|
||||
export { default as action } from './action';
|
||||
|
||||
|
||||
import initialize from './initialize';
|
||||
initialize();
|
|
@ -0,0 +1,10 @@
|
|||
declare var global: any;
|
||||
|
||||
export default function initialize() {
|
||||
// Multiple instances of SatchelJS won't play nicely with each other, so guard against that.
|
||||
if (global._isSatchelJsLoaded) {
|
||||
throw new Error("Another instance of SatchelJS is already loaded.");
|
||||
}
|
||||
|
||||
global._isSatchelJsLoaded = true;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import rootStore from './rootStore';
|
||||
import action from './action';
|
||||
|
||||
// initializeState can be used to completely replace the existing state object, e.g. to restore
|
||||
// the stores from a serialized value or to provide a clean starting state for test cases.
|
||||
let initializeState =
|
||||
function initializeState(initialState: any) {
|
||||
rootStore.clear();
|
||||
rootStore.merge(initialState);
|
||||
};
|
||||
|
||||
initializeState = <any>action("initializeState")(initializeState);
|
||||
export default initializeState;
|
|
@ -0,0 +1,4 @@
|
|||
import { map, ObservableMap } from 'mobx';
|
||||
|
||||
var rootStore: ObservableMap<any> = map({});
|
||||
export default rootStore;
|
|
@ -0,0 +1,52 @@
|
|||
import 'jasmine';
|
||||
import action from '../src/action';
|
||||
import * as dispatchImports from '../src/dispatch';
|
||||
|
||||
describe("action", () => {
|
||||
it("wraps the function call in a dispatch", () => {
|
||||
let testFunctionCalled = false;
|
||||
let testFunction = (a: string) => { testFunctionCalled = true; };
|
||||
|
||||
spyOn(dispatchImports, "default").and.callThrough();
|
||||
|
||||
testFunction = action("testFunction")(testFunction);
|
||||
testFunction("testArgument");
|
||||
|
||||
expect(testFunctionCalled).toBeTruthy();
|
||||
expect(dispatchImports.default).toHaveBeenCalledTimes(1);
|
||||
|
||||
// The second argument to dispatch should be the actionType
|
||||
expect((<jasmine.Spy>dispatchImports.default).calls.argsFor(0)[1]).toBe("testFunction");
|
||||
|
||||
// The third argument to dispatch should be the IArguments object for the action
|
||||
expect((<jasmine.Spy>dispatchImports.default).calls.argsFor(0)[2].length).toBe(1);
|
||||
expect((<jasmine.Spy>dispatchImports.default).calls.argsFor(0)[2][0]).toBe("testArgument");
|
||||
});
|
||||
|
||||
it("passes on the original arguments", () => {
|
||||
let passedArguments: IArguments;
|
||||
|
||||
let testFunction = function(a: number, b: number) {
|
||||
passedArguments = arguments;
|
||||
};
|
||||
|
||||
testFunction = action("testFunction")(testFunction);
|
||||
testFunction(0, 1);
|
||||
|
||||
expect(passedArguments[0]).toEqual(0);
|
||||
expect(passedArguments[1]).toEqual(1);
|
||||
});
|
||||
|
||||
it("returns the original return value", () => {
|
||||
let originalReturnValue = new Promise<any>(() => {});
|
||||
|
||||
let testFunction = function() {
|
||||
return originalReturnValue;
|
||||
};
|
||||
|
||||
testFunction = action("testFunction")(testFunction);
|
||||
let returnValue = testFunction();
|
||||
|
||||
expect(returnValue).toBe(originalReturnValue);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,82 @@
|
|||
import 'jasmine';
|
||||
|
||||
import { default as applyMiddleware, dispatchWithMiddleware } from '../src/applyMiddleware';
|
||||
import ActionFunction from '../src/ActionFunction';
|
||||
|
||||
|
||||
describe("applyMiddleware", () => {
|
||||
beforeEach(() => {
|
||||
applyMiddleware();
|
||||
});
|
||||
|
||||
it("Calls middleware during dispatchWithMiddleware", () => {
|
||||
let actionCalled = false;
|
||||
let middlewareCalled = false;
|
||||
|
||||
applyMiddleware(
|
||||
(next, action, actionType) => {
|
||||
middlewareCalled = true;
|
||||
next(action, actionType, null);
|
||||
});
|
||||
|
||||
dispatchWithMiddleware(() => { actionCalled = true; }, null, null);
|
||||
expect(actionCalled).toBeTruthy();
|
||||
expect(middlewareCalled).toBeTruthy();
|
||||
});
|
||||
|
||||
it("Calls middleware in order", () => {
|
||||
var middleware0Called = false;
|
||||
var middleware1Called = false;
|
||||
|
||||
applyMiddleware(
|
||||
(next, action, actionType) => {
|
||||
expect(middleware1Called).toBeFalsy();
|
||||
middleware0Called = true;
|
||||
next(action, actionType, null);
|
||||
},
|
||||
(next, action, actionType) => {
|
||||
expect(middleware0Called).toBeTruthy();
|
||||
middleware1Called = true;
|
||||
next(action, actionType, null);
|
||||
});
|
||||
|
||||
dispatchWithMiddleware(() => { }, null, null);
|
||||
expect(middleware1Called).toBeTruthy();
|
||||
});
|
||||
|
||||
it("Passes action parameters to middleware", () => {
|
||||
let originalAction = () => {};
|
||||
let originalActionType = "testAction";
|
||||
let originalArguments = <IArguments>{};
|
||||
|
||||
var passedAction: ActionFunction;
|
||||
var passedActionType: string;
|
||||
var passedArguments: IArguments;
|
||||
|
||||
applyMiddleware(
|
||||
(next, action, actionType, args) => {
|
||||
passedAction = action;
|
||||
passedActionType = actionType;
|
||||
passedArguments = args;
|
||||
});
|
||||
|
||||
dispatchWithMiddleware(originalAction, originalActionType, originalArguments);
|
||||
expect(passedAction).toBe(originalAction);
|
||||
expect(passedActionType).toBe(originalActionType);
|
||||
expect(passedArguments).toBe(originalArguments);
|
||||
});
|
||||
|
||||
it("Returns the action return value to middleware", () => {
|
||||
let originalReturnValue = Promise.resolve({});
|
||||
let originalAction = () => { return originalReturnValue; }
|
||||
let receivedReturnValue: Promise<any> | void;
|
||||
|
||||
applyMiddleware(
|
||||
(next, action, actionType, args) => {
|
||||
receivedReturnValue = next(action, actionType, args)
|
||||
});
|
||||
|
||||
dispatchWithMiddleware(originalAction, null, null);
|
||||
expect(receivedReturnValue).toBe(originalReturnValue);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,73 @@
|
|||
import 'jasmine';
|
||||
import {action as mobxAction, autorun, _} from 'mobx';
|
||||
|
||||
import rootStore from '../src/rootStore';
|
||||
import initializeState from '../src/initializeState';
|
||||
import dispatch from '../src/dispatch';
|
||||
import * as applyMiddlewareImports from '../src/applyMiddleware';
|
||||
|
||||
|
||||
var backupConsoleError = console.error;
|
||||
|
||||
describe("dispatch", () => {
|
||||
beforeEach(() => {
|
||||
_.resetGlobalState();
|
||||
initializeState({});
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
// Some of these tests cause MobX to write to console.error, so we need to supress that output
|
||||
console.error = (message) => null;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
console.error = backupConsoleError;
|
||||
});
|
||||
|
||||
it("calls dispatchWithMiddleware with same arguments", () => {
|
||||
spyOn(applyMiddlewareImports, "dispatchWithMiddleware");
|
||||
let originalAction = () => {};
|
||||
let originalActionType = "testAction";
|
||||
let originalArguments: IArguments = <IArguments>{};
|
||||
dispatch(originalAction, originalActionType, originalArguments);
|
||||
expect(applyMiddlewareImports.dispatchWithMiddleware).toHaveBeenCalledWith(originalAction, originalActionType, originalArguments);
|
||||
});
|
||||
|
||||
it("changing state outside of an action causes an exception", () => {
|
||||
initializeState({ foo: 1 });
|
||||
var delegate = () => { rootStore.set("foo", 2); };
|
||||
expect(delegate).toThrow();
|
||||
});
|
||||
|
||||
it("changing state in a MobX action causes an exception", () => {
|
||||
initializeState({ foo: 1 });
|
||||
var delegate = mobxAction(() => { rootStore.set("foo", 2); });
|
||||
expect(delegate).toThrow();
|
||||
});
|
||||
|
||||
it("executes middleware in the same transaction as the action", () => {
|
||||
initializeState({ foo: 0 });
|
||||
|
||||
// Count how many times the autorun gets executed
|
||||
let count = 0;
|
||||
autorun(() => {
|
||||
rootStore.get("foo");
|
||||
count++;
|
||||
});
|
||||
|
||||
// Autorun executes once when it is defined
|
||||
expect(count).toBe(1);
|
||||
|
||||
// Change the state twice, once in middleware and once in the action
|
||||
applyMiddlewareImports.default(
|
||||
(next, action, actionType) => {
|
||||
rootStore.set("foo", 1);
|
||||
next(action, actionType, null);
|
||||
});
|
||||
|
||||
dispatch(() => { rootStore.set("foo", 2); }, null, null);
|
||||
|
||||
// Autorun should have executed exactly one more time
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import 'jasmine';
|
||||
import rootStore from '../src/rootStore';
|
||||
import initializeState from '../src/initializeState';
|
||||
|
||||
describe("initializeState", () => {
|
||||
it("replaces the state value", () => {
|
||||
var initialState = { foo: 1 };
|
||||
initializeState(initialState);
|
||||
expect(rootStore.get("foo")).toEqual(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
import 'jasmine';
|
||||
import initialize from '../src/initialize';
|
||||
|
||||
declare var global: any;
|
||||
|
||||
describe("initialize", () => {
|
||||
beforeEach(() => {
|
||||
delete global._isSatchelJsLoaded;
|
||||
});
|
||||
|
||||
it("passes the first time it is called", () => {
|
||||
initialize();
|
||||
});
|
||||
|
||||
it("throws if called multiple times", () => {
|
||||
expect(() => {
|
||||
initialize();
|
||||
initialize();
|
||||
})
|
||||
.toThrow();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
import 'jasmine';
|
||||
import { isObservableMap } from 'mobx';
|
||||
import rootStore from '../src/rootStore';
|
||||
|
||||
describe("rootStore", () => {
|
||||
it("is an ObservableMap", () => {
|
||||
expect(isObservableMap(rootStore)).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"outDir": "dist",
|
||||
"sourceMap": false,
|
||||
"declaration": true,
|
||||
"removeComments": false,
|
||||
"noImplicitAny": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
// Type definitions for es6-promise
|
||||
// Project: https://github.com/jakearchibald/ES6-Promise
|
||||
// Definitions by: François de Campredon <https://github.com/fdecampredon/>, vvakame <https://github.com/vvakame>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
interface Thenable<R> {
|
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Thenable<U>;
|
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => void): Thenable<U>;
|
||||
}
|
||||
|
||||
declare class Promise<R> implements Thenable<R> {
|
||||
/**
|
||||
* If you call resolve in the body of the callback passed to the constructor,
|
||||
* your promise is fulfilled with result object passed to resolve.
|
||||
* If you call reject your promise is rejected with the object passed to reject.
|
||||
* For consistency and debugging (eg stack traces), obj should be an instanceof Error.
|
||||
* Any errors thrown in the constructor callback will be implicitly passed to reject().
|
||||
*/
|
||||
constructor(callback: (resolve : (value?: R | Thenable<R>) => void, reject: (error?: any) => void) => void);
|
||||
|
||||
/**
|
||||
* onFulfilled is called when/if "promise" resolves. onRejected is called when/if "promise" rejects.
|
||||
* Both are optional, if either/both are omitted the next onFulfilled/onRejected in the chain is called.
|
||||
* Both callbacks have a single parameter , the fulfillment value or rejection reason.
|
||||
* "then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve.
|
||||
* If an error is thrown in the callback, the returned promise rejects with that error.
|
||||
*
|
||||
* @param onFulfilled called when/if "promise" resolves
|
||||
* @param onRejected called when/if "promise" rejects
|
||||
*/
|
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Promise<U>;
|
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => void): Promise<U>;
|
||||
|
||||
/**
|
||||
* Sugar for promise.then(undefined, onRejected)
|
||||
*
|
||||
* @param onRejected called when/if "promise" rejects
|
||||
*/
|
||||
catch<U>(onRejected?: (error: any) => U | Thenable<U>): Promise<U>;
|
||||
}
|
||||
|
||||
declare module Promise {
|
||||
/**
|
||||
* Make a new promise from the thenable.
|
||||
* A thenable is promise-like in as far as it has a "then" method.
|
||||
*/
|
||||
function resolve<R>(value?: R | Thenable<R>): Promise<R>;
|
||||
|
||||
/**
|
||||
* Make a promise that rejects to obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error
|
||||
*/
|
||||
function reject(error: any): Promise<any>;
|
||||
|
||||
/**
|
||||
* Make a promise that fulfills when every item in the array fulfills, and rejects if (and when) any item rejects.
|
||||
* the array passed to all can be a mixture of promise-like objects and other objects.
|
||||
* The fulfillment value is an array (in order) of fulfillment values. The rejection value is the first rejection value.
|
||||
*/
|
||||
function all<R>(promises: (R | Thenable<R>)[]): Promise<R[]>;
|
||||
|
||||
/**
|
||||
* Make a Promise that fulfills when any item fulfills, and rejects if any item rejects.
|
||||
*/
|
||||
function race<R>(promises: (R | Thenable<R>)[]): Promise<R>;
|
||||
}
|
||||
|
||||
declare module 'es6-promise' {
|
||||
var foo: typeof Promise; // Temp variable to reference Promise in local context
|
||||
module rsvp {
|
||||
export var Promise: typeof foo;
|
||||
}
|
||||
export = rsvp;
|
||||
}
|
|
@ -0,0 +1,497 @@
|
|||
// Type definitions for Jasmine 2.2
|
||||
// Project: http://jasmine.github.io/
|
||||
// Definitions by: Boris Yankov <https://github.com/borisyankov/>, Theodore Brown <https://github.com/theodorejb>, David Pärsson <https://github.com/davidparsson/>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
|
||||
// For ddescribe / iit use : https://github.com/borisyankov/DefinitelyTyped/blob/master/karma-jasmine/karma-jasmine.d.ts
|
||||
|
||||
declare function describe(description: string, specDefinitions: () => void): void;
|
||||
declare function fdescribe(description: string, specDefinitions: () => void): void;
|
||||
declare function xdescribe(description: string, specDefinitions: () => void): void;
|
||||
|
||||
declare function it(expectation: string, assertion?: () => void, timeout?: number): void;
|
||||
declare function it(expectation: string, assertion?: (done: () => void) => void, timeout?: number): void;
|
||||
declare function fit(expectation: string, assertion?: () => void, timeout?: number): void;
|
||||
declare function fit(expectation: string, assertion?: (done: () => void) => void, timeout?: number): void;
|
||||
declare function xit(expectation: string, assertion?: () => void, timeout?: number): void;
|
||||
declare function xit(expectation: string, assertion?: (done: () => void) => void, timeout?: number): void;
|
||||
|
||||
/** If you call the function pending anywhere in the spec body, no matter the expectations, the spec will be marked pending. */
|
||||
declare function pending(reason?: string): void;
|
||||
|
||||
declare function beforeEach(action: () => void, timeout?: number): void;
|
||||
declare function beforeEach(action: (done: () => void) => void, timeout?: number): void;
|
||||
declare function afterEach(action: () => void, timeout?: number): void;
|
||||
declare function afterEach(action: (done: () => void) => void, timeout?: number): void;
|
||||
|
||||
declare function beforeAll(action: () => void, timeout?: number): void;
|
||||
declare function beforeAll(action: (done: () => void) => void, timeout?: number): void;
|
||||
declare function afterAll(action: () => void, timeout?: number): void;
|
||||
declare function afterAll(action: (done: () => void) => void, timeout?: number): void;
|
||||
|
||||
declare function expect(spy: Function): jasmine.Matchers;
|
||||
declare function expect(actual: any): jasmine.Matchers;
|
||||
|
||||
declare function fail(e?: any): void;
|
||||
|
||||
declare function spyOn(object: any, method: string): jasmine.Spy;
|
||||
|
||||
declare function runs(asyncMethod: Function): void;
|
||||
declare function waitsFor(latchMethod: () => boolean, failureMessage?: string, timeout?: number): void;
|
||||
declare function waits(timeout?: number): void;
|
||||
|
||||
declare module jasmine {
|
||||
|
||||
var clock: () => Clock;
|
||||
|
||||
function any(aclass: any): Any;
|
||||
function anything(): Any;
|
||||
function arrayContaining(sample: any[]): ArrayContaining;
|
||||
function objectContaining(sample: any): ObjectContaining;
|
||||
function createSpy(name: string, originalFn?: Function): Spy;
|
||||
function createSpyObj(baseName: string, methodNames: any[]): any;
|
||||
function createSpyObj<T>(baseName: string, methodNames: any[]): T;
|
||||
function pp(value: any): string;
|
||||
function getEnv(): Env;
|
||||
function addCustomEqualityTester(equalityTester: CustomEqualityTester): void;
|
||||
function addMatchers(matchers: CustomMatcherFactories): void;
|
||||
function stringMatching(str: string): Any;
|
||||
function stringMatching(str: RegExp): Any;
|
||||
|
||||
interface Any {
|
||||
|
||||
new (expectedClass: any): any;
|
||||
|
||||
jasmineMatches(other: any): boolean;
|
||||
jasmineToString(): string;
|
||||
}
|
||||
|
||||
// taken from TypeScript lib.core.es6.d.ts, applicable to CustomMatchers.contains()
|
||||
interface ArrayLike<T> {
|
||||
length: number;
|
||||
[n: number]: T;
|
||||
}
|
||||
|
||||
interface ArrayContaining {
|
||||
new (sample: any[]): any;
|
||||
|
||||
asymmetricMatch(other: any): boolean;
|
||||
jasmineToString(): string;
|
||||
}
|
||||
|
||||
interface ObjectContaining {
|
||||
new (sample: any): any;
|
||||
|
||||
jasmineMatches(other: any, mismatchKeys: any[], mismatchValues: any[]): boolean;
|
||||
jasmineToString(): string;
|
||||
}
|
||||
|
||||
interface Block {
|
||||
|
||||
new (env: Env, func: SpecFunction, spec: Spec): any;
|
||||
|
||||
execute(onComplete: () => void): void;
|
||||
}
|
||||
|
||||
interface WaitsBlock extends Block {
|
||||
new (env: Env, timeout: number, spec: Spec): any;
|
||||
}
|
||||
|
||||
interface WaitsForBlock extends Block {
|
||||
new (env: Env, timeout: number, latchFunction: SpecFunction, message: string, spec: Spec): any;
|
||||
}
|
||||
|
||||
interface Clock {
|
||||
install(): void;
|
||||
uninstall(): void;
|
||||
/** Calls to any registered callback are triggered when the clock is ticked forward via the jasmine.clock().tick function, which takes a number of milliseconds. */
|
||||
tick(ms: number): void;
|
||||
mockDate(date?: Date): void;
|
||||
}
|
||||
|
||||
interface CustomEqualityTester {
|
||||
(first: any, second: any): boolean;
|
||||
}
|
||||
|
||||
interface CustomMatcher {
|
||||
compare<T>(actual: T, expected: T): CustomMatcherResult;
|
||||
compare(actual: any, expected: any): CustomMatcherResult;
|
||||
}
|
||||
|
||||
interface CustomMatcherFactory {
|
||||
(util: MatchersUtil, customEqualityTesters: Array<CustomEqualityTester>): CustomMatcher;
|
||||
}
|
||||
|
||||
interface CustomMatcherFactories {
|
||||
[index: string]: CustomMatcherFactory;
|
||||
}
|
||||
|
||||
interface CustomMatcherResult {
|
||||
pass: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface MatchersUtil {
|
||||
equals(a: any, b: any, customTesters?: Array<CustomEqualityTester>): boolean;
|
||||
contains<T>(haystack: ArrayLike<T> | string, needle: any, customTesters?: Array<CustomEqualityTester>): boolean;
|
||||
buildFailureMessage(matcherName: string, isNot: boolean, actual: any, ...expected: Array<any>): string;
|
||||
}
|
||||
|
||||
interface Env {
|
||||
setTimeout: any;
|
||||
clearTimeout: void;
|
||||
setInterval: any;
|
||||
clearInterval: void;
|
||||
updateInterval: number;
|
||||
|
||||
currentSpec: Spec;
|
||||
|
||||
matchersClass: Matchers;
|
||||
|
||||
version(): any;
|
||||
versionString(): string;
|
||||
nextSpecId(): number;
|
||||
addReporter(reporter: Reporter): void;
|
||||
execute(): void;
|
||||
describe(description: string, specDefinitions: () => void): Suite;
|
||||
// ddescribe(description: string, specDefinitions: () => void): Suite; Not a part of jasmine. Angular team adds these
|
||||
beforeEach(beforeEachFunction: () => void): void;
|
||||
beforeAll(beforeAllFunction: () => void): void;
|
||||
currentRunner(): Runner;
|
||||
afterEach(afterEachFunction: () => void): void;
|
||||
afterAll(afterAllFunction: () => void): void;
|
||||
xdescribe(desc: string, specDefinitions: () => void): XSuite;
|
||||
it(description: string, func: () => void): Spec;
|
||||
// iit(description: string, func: () => void): Spec; Not a part of jasmine. Angular team adds these
|
||||
xit(desc: string, func: () => void): XSpec;
|
||||
compareRegExps_(a: RegExp, b: RegExp, mismatchKeys: string[], mismatchValues: string[]): boolean;
|
||||
compareObjects_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean;
|
||||
equals_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean;
|
||||
contains_(haystack: any, needle: any): boolean;
|
||||
addCustomEqualityTester(equalityTester: CustomEqualityTester): void;
|
||||
addMatchers(matchers: CustomMatcherFactories): void;
|
||||
specFilter(spec: Spec): boolean;
|
||||
}
|
||||
|
||||
interface FakeTimer {
|
||||
|
||||
new (): any;
|
||||
|
||||
reset(): void;
|
||||
tick(millis: number): void;
|
||||
runFunctionsWithinRange(oldMillis: number, nowMillis: number): void;
|
||||
scheduleFunction(timeoutKey: any, funcToCall: () => void, millis: number, recurring: boolean): void;
|
||||
}
|
||||
|
||||
interface HtmlReporter {
|
||||
new (): any;
|
||||
}
|
||||
|
||||
interface HtmlSpecFilter {
|
||||
new (): any;
|
||||
}
|
||||
|
||||
interface Result {
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface NestedResults extends Result {
|
||||
description: string;
|
||||
|
||||
totalCount: number;
|
||||
passedCount: number;
|
||||
failedCount: number;
|
||||
|
||||
skipped: boolean;
|
||||
|
||||
rollupCounts(result: NestedResults): void;
|
||||
log(values: any): void;
|
||||
getItems(): Result[];
|
||||
addResult(result: Result): void;
|
||||
passed(): boolean;
|
||||
}
|
||||
|
||||
interface MessageResult extends Result {
|
||||
values: any;
|
||||
trace: Trace;
|
||||
}
|
||||
|
||||
interface ExpectationResult extends Result {
|
||||
matcherName: string;
|
||||
passed(): boolean;
|
||||
expected: any;
|
||||
actual: any;
|
||||
message: string;
|
||||
trace: Trace;
|
||||
}
|
||||
|
||||
interface Trace {
|
||||
name: string;
|
||||
message: string;
|
||||
stack: any;
|
||||
}
|
||||
|
||||
interface PrettyPrinter {
|
||||
|
||||
new (): any;
|
||||
|
||||
format(value: any): void;
|
||||
iterateObject(obj: any, fn: (property: string, isGetter: boolean) => void): void;
|
||||
emitScalar(value: any): void;
|
||||
emitString(value: string): void;
|
||||
emitArray(array: any[]): void;
|
||||
emitObject(obj: any): void;
|
||||
append(value: any): void;
|
||||
}
|
||||
|
||||
interface StringPrettyPrinter extends PrettyPrinter {
|
||||
}
|
||||
|
||||
interface Queue {
|
||||
|
||||
new (env: any): any;
|
||||
|
||||
env: Env;
|
||||
ensured: boolean[];
|
||||
blocks: Block[];
|
||||
running: boolean;
|
||||
index: number;
|
||||
offset: number;
|
||||
abort: boolean;
|
||||
|
||||
addBefore(block: Block, ensure?: boolean): void;
|
||||
add(block: any, ensure?: boolean): void;
|
||||
insertNext(block: any, ensure?: boolean): void;
|
||||
start(onComplete?: () => void): void;
|
||||
isRunning(): boolean;
|
||||
next_(): void;
|
||||
results(): NestedResults;
|
||||
}
|
||||
|
||||
interface Matchers {
|
||||
|
||||
new (env: Env, actual: any, spec: Env, isNot?: boolean): any;
|
||||
|
||||
env: Env;
|
||||
actual: any;
|
||||
spec: Env;
|
||||
isNot?: boolean;
|
||||
message(): any;
|
||||
|
||||
toBe(expected: any, expectationFailOutput?: any): boolean;
|
||||
toEqual(expected: any, expectationFailOutput?: any): boolean;
|
||||
toMatch(expected: any, expectationFailOutput?: any): boolean;
|
||||
toBeDefined(expectationFailOutput?: any): boolean;
|
||||
toBeUndefined(expectationFailOutput?: any): boolean;
|
||||
toBeNull(expectationFailOutput?: any): boolean;
|
||||
toBeNaN(): boolean;
|
||||
toBeTruthy(expectationFailOutput?: any): boolean;
|
||||
toBeFalsy(expectationFailOutput?: any): boolean;
|
||||
toHaveBeenCalledTimes(times: number): boolean;
|
||||
toHaveBeenCalled(): boolean;
|
||||
toHaveBeenCalledWith(...params: any[]): boolean;
|
||||
toContain(expected: any, expectationFailOutput?: any): boolean;
|
||||
toBeLessThan(expected: any, expectationFailOutput?: any): boolean;
|
||||
toBeGreaterThan(expected: any, expectationFailOutput?: any): boolean;
|
||||
toBeCloseTo(expected: any, precision: any, expectationFailOutput?: any): boolean;
|
||||
toContainHtml(expected: string): boolean;
|
||||
toContainText(expected: string): boolean;
|
||||
toThrow(expected?: any): boolean;
|
||||
toThrowError(expected?: any, message?: string): boolean;
|
||||
not: Matchers;
|
||||
|
||||
Any: Any;
|
||||
}
|
||||
|
||||
interface Reporter {
|
||||
reportRunnerStarting(runner: Runner): void;
|
||||
reportRunnerResults(runner: Runner): void;
|
||||
reportSuiteResults(suite: Suite): void;
|
||||
reportSpecStarting(spec: Spec): void;
|
||||
reportSpecResults(spec: Spec): void;
|
||||
log(str: string): void;
|
||||
}
|
||||
|
||||
interface MultiReporter extends Reporter {
|
||||
addReporter(reporter: Reporter): void;
|
||||
}
|
||||
|
||||
interface Runner {
|
||||
|
||||
new (env: Env): any;
|
||||
|
||||
execute(): void;
|
||||
beforeEach(beforeEachFunction: SpecFunction): void;
|
||||
afterEach(afterEachFunction: SpecFunction): void;
|
||||
beforeAll(beforeAllFunction: SpecFunction): void;
|
||||
afterAll(afterAllFunction: SpecFunction): void;
|
||||
finishCallback(): void;
|
||||
addSuite(suite: Suite): void;
|
||||
add(block: Block): void;
|
||||
specs(): Spec[];
|
||||
suites(): Suite[];
|
||||
topLevelSuites(): Suite[];
|
||||
results(): NestedResults;
|
||||
}
|
||||
|
||||
interface SpecFunction {
|
||||
(spec?: Spec): void;
|
||||
}
|
||||
|
||||
interface SuiteOrSpec {
|
||||
id: number;
|
||||
env: Env;
|
||||
description: string;
|
||||
queue: Queue;
|
||||
}
|
||||
|
||||
interface Spec extends SuiteOrSpec {
|
||||
|
||||
new (env: Env, suite: Suite, description: string): any;
|
||||
|
||||
suite: Suite;
|
||||
|
||||
afterCallbacks: SpecFunction[];
|
||||
spies_: Spy[];
|
||||
|
||||
results_: NestedResults;
|
||||
matchersClass: Matchers;
|
||||
|
||||
getFullName(): string;
|
||||
results(): NestedResults;
|
||||
log(arguments: any): any;
|
||||
runs(func: SpecFunction): Spec;
|
||||
addToQueue(block: Block): void;
|
||||
addMatcherResult(result: Result): void;
|
||||
expect(actual: any): any;
|
||||
waits(timeout: number): Spec;
|
||||
waitsFor(latchFunction: SpecFunction, timeoutMessage?: string, timeout?: number): Spec;
|
||||
fail(e?: any): void;
|
||||
getMatchersClass_(): Matchers;
|
||||
addMatchers(matchersPrototype: CustomMatcherFactories): void;
|
||||
finishCallback(): void;
|
||||
finish(onComplete?: () => void): void;
|
||||
after(doAfter: SpecFunction): void;
|
||||
execute(onComplete?: () => void): any;
|
||||
addBeforesAndAftersToQueue(): void;
|
||||
explodes(): void;
|
||||
spyOn(obj: any, methodName: string, ignoreMethodDoesntExist: boolean): Spy;
|
||||
removeAllSpies(): void;
|
||||
}
|
||||
|
||||
interface XSpec {
|
||||
id: number;
|
||||
runs(): void;
|
||||
}
|
||||
|
||||
interface Suite extends SuiteOrSpec {
|
||||
|
||||
new (env: Env, description: string, specDefinitions: () => void, parentSuite: Suite): any;
|
||||
|
||||
parentSuite: Suite;
|
||||
|
||||
getFullName(): string;
|
||||
finish(onComplete?: () => void): void;
|
||||
beforeEach(beforeEachFunction: SpecFunction): void;
|
||||
afterEach(afterEachFunction: SpecFunction): void;
|
||||
beforeAll(beforeAllFunction: SpecFunction): void;
|
||||
afterAll(afterAllFunction: SpecFunction): void;
|
||||
results(): NestedResults;
|
||||
add(suiteOrSpec: SuiteOrSpec): void;
|
||||
specs(): Spec[];
|
||||
suites(): Suite[];
|
||||
children(): any[];
|
||||
execute(onComplete?: () => void): void;
|
||||
}
|
||||
|
||||
interface XSuite {
|
||||
execute(): void;
|
||||
}
|
||||
|
||||
interface Spy {
|
||||
(...params: any[]): any;
|
||||
|
||||
identity: string;
|
||||
and: SpyAnd;
|
||||
calls: Calls;
|
||||
mostRecentCall: { args: any[]; };
|
||||
argsForCall: any[];
|
||||
wasCalled: boolean;
|
||||
}
|
||||
|
||||
interface SpyAnd {
|
||||
/** By chaining the spy with and.callThrough, the spy will still track all calls to it but in addition it will delegate to the actual implementation. */
|
||||
callThrough(): Spy;
|
||||
/** By chaining the spy with and.returnValue, all calls to the function will return a specific value. */
|
||||
returnValue(val: any): void;
|
||||
/** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied function. */
|
||||
callFake(fn: Function): Spy;
|
||||
/** By chaining the spy with and.throwError, all calls to the spy will throw the specified value. */
|
||||
throwError(msg: string): void;
|
||||
/** When a calling strategy is used for a spy, the original stubbing behavior can be returned at any time with and.stub. */
|
||||
stub(): Spy;
|
||||
}
|
||||
|
||||
interface Calls {
|
||||
/** By chaining the spy with calls.any(), will return false if the spy has not been called at all, and then true once at least one call happens. **/
|
||||
any(): boolean;
|
||||
/** By chaining the spy with calls.count(), will return the number of times the spy was called **/
|
||||
count(): number;
|
||||
/** By chaining the spy with calls.argsFor(), will return the arguments passed to call number index **/
|
||||
argsFor(index: number): any[];
|
||||
/** By chaining the spy with calls.allArgs(), will return the arguments to all calls **/
|
||||
allArgs(): any[];
|
||||
/** By chaining the spy with calls.all(), will return the context (the this) and arguments passed all calls **/
|
||||
all(): CallInfo[];
|
||||
/** By chaining the spy with calls.mostRecent(), will return the context (the this) and arguments for the most recent call **/
|
||||
mostRecent(): CallInfo;
|
||||
/** By chaining the spy with calls.first(), will return the context (the this) and arguments for the first call **/
|
||||
first(): CallInfo;
|
||||
/** By chaining the spy with calls.reset(), will clears all tracking for a spy **/
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
interface CallInfo {
|
||||
/** The context (the this) for the call */
|
||||
object: any;
|
||||
/** All arguments passed to the call */
|
||||
args: any[];
|
||||
}
|
||||
|
||||
interface Util {
|
||||
inherit(childClass: Function, parentClass: Function): any;
|
||||
formatException(e: any): any;
|
||||
htmlEscape(str: string): string;
|
||||
argsToArray(args: any): any;
|
||||
extend(destination: any, source: any): any;
|
||||
}
|
||||
|
||||
interface JsApiReporter extends Reporter {
|
||||
|
||||
started: boolean;
|
||||
finished: boolean;
|
||||
result: any;
|
||||
messages: any;
|
||||
|
||||
new (): any;
|
||||
|
||||
suites(): Suite[];
|
||||
summarize_(suiteOrSpec: SuiteOrSpec): any;
|
||||
results(): any;
|
||||
resultsForSpec(specId: any): any;
|
||||
log(str: any): any;
|
||||
resultsForSpecs(specIds: any): any;
|
||||
summarizeResult_(result: any): any;
|
||||
}
|
||||
|
||||
interface Jasmine {
|
||||
Spec: Spec;
|
||||
clock: Clock;
|
||||
util: Util;
|
||||
}
|
||||
|
||||
export var HtmlReporter: HtmlReporter;
|
||||
export var HtmlSpecFilter: HtmlSpecFilter;
|
||||
export var DEFAULT_TIMEOUT_INTERVAL: number;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
/// <reference path="es6-promise/es6-promise.d.ts" />
|
||||
/// <reference path="jasmine/jasmine.d.ts" />
|
Загрузка…
Ссылка в новой задаче