Skip to content

Commit

Permalink
feat(ts): use generic rest params with TS 3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
omichelsen committed Aug 16, 2018
1 parent 6a8133a commit 8ac4814
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 75 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"nyc": "^12.0.2",
"ts-node": "^7.0.0",
"tslint": "^5.11.0",
"typescript": "^2.9.2"
"typescript": "^3.0.1"
},
"nyc": {
"extension": [
Expand Down
77 changes: 14 additions & 63 deletions src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,21 @@ export interface IAction<Payload> {
error?: boolean;
}

export type ActionFunction0<R> = () => R;
export type ActionFunction1<T1, R> = (t1: T1) => R;
export type ActionFunction2<T1, T2, R> = (t1: T1, t2: T2) => R;
export type ActionFunction3<T1, T2, T3, R> = (t1: T1, t2: T2, t3: T3) => R;
export type ActionFunction4<T1, T2, T3, T4, R> = (t1: T1, t2: T2, t3: T3, t4: T4) => R;
export type ActionFunctionAny<R> = (...args: any[]) => R;

export function createAction(
type: string
): ActionFunction0<IAction<undefined>>;

export function createAction<Payload>(
type: string,
payloadCreator: () => Payload
): ActionFunction0<IAction<Payload>>;

export function createAction<Payload, Arg1>(
type: string,
payloadCreator: ActionFunction1<Arg1, Payload>
): ActionFunction1<Arg1, IAction<Payload>>;
): () => IAction<undefined>;

export function createAction<Payload, Arg1, Arg2>(
export function createAction<Payload, U extends any[]>(
type: string,
payloadCreator: ActionFunction2<Arg1, Arg2, Payload>
): ActionFunction2<Arg1, Arg2, IAction<Payload>>;
payloadCreator: (...args: U) => Payload
): (...args: U) => IAction<Payload>;

export function createAction<Payload, Arg1, Arg2, Arg3>(
export function createAction<Payload, U extends any[]>(
type: string,
payloadCreator: ActionFunction3<Arg1, Arg2, Arg3, Payload>
): ActionFunction3<Arg1, Arg2, Arg3, IAction<Payload>>;

export function createAction<Payload, Arg1, Arg2, Arg3, Arg4>(
type: string,
payloadCreator: ActionFunction4<Arg1, Arg2, Arg3, Arg4, Payload>
): ActionFunction4<Arg1, Arg2, Arg3, Arg4, IAction<Payload>>;

export function createAction<Payload>(type: string, payloadCreator?: (...args: any[]) => Payload) {
payloadCreator?: (...args: U) => Payload
): (...args: U) => IAction<Payload> {
return Object.assign(
(...args: any[]) => ({
(...args: U) => ({
type,
...(payloadCreator && { payload: payloadCreator(...args) }),
}),
Expand All @@ -55,42 +31,17 @@ export function createAction<Payload>(type: string, payloadCreator?: (...args: a
}

export interface IAsyncActionFunction<Payload> extends Function {
pending: ActionFunction0<IAction<undefined>>;
fulfilled: ActionFunction1<Payload, IAction<Payload>>;
pending: () => IAction<undefined>;
fulfilled: (payload: Payload) => IAction<Payload>;
rejected: (payload?: any) => IAction<any>;
}

export function createAsyncAction<Payload>(
type: string,
payloadCreator: () => Promise<Payload>
): ActionFunction0<IAction<Promise<Payload>>> & IAsyncActionFunction<Payload>;

export function createAsyncAction<Payload, Arg1>(
export function createAsyncAction<Payload, U extends any[]>(
type: string,
payloadCreator: ActionFunction1<Arg1, Promise<Payload>>
): ActionFunction1<Arg1, IAction<Promise<Payload>>> & IAsyncActionFunction<Payload>;

export function createAsyncAction<Payload, Arg1, Arg2>(
type: string,
payloadCreator: ActionFunction2<Arg1, Arg2, Promise<Payload>>
): ActionFunction2<Arg1, Arg2, IAction<Promise<Payload>>> & IAsyncActionFunction<Payload>;

export function createAsyncAction<Payload, Arg1, Arg2, Arg3>(
type: string,
payloadCreator: ActionFunction3<Arg1, Arg2, Arg3, Promise<Payload>>
): ActionFunction3<Arg1, Arg2, Arg3, IAction<Promise<Payload>>> & IAsyncActionFunction<Payload>;

export function createAsyncAction<Payload, Arg1, Arg2, Arg3, Arg4>(
type: string,
payloadCreator: ActionFunction4<Arg1, Arg2, Arg3, Arg4, Promise<Payload>>
): ActionFunction4<Arg1, Arg2, Arg3, Arg4, IAction<Promise<Payload>>> & IAsyncActionFunction<Payload>;

export function createAsyncAction<Payload>(type: string, payloadCreator?: (...args: any[]) => Payload) {
payloadCreator: (...args: U) => Promise<Payload>
) {
return Object.assign(
(...args: any[]) => ({
type,
...(payloadCreator && { payload: payloadCreator(...args) }),
}),
createAction(type, payloadCreator),
{
toString: () => {
throw new Error(`Async action ${type} must be handled with pending, fulfilled or rejected`);
Expand Down
34 changes: 26 additions & 8 deletions test/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ describe('actions', () => {
const TYPE = 'TEST_ACTION';

describe('createAction', () => {
it('should create an action object with type', () => {
assert.equal(createAction(TYPE)().type, TYPE);
});

it('should create an action object with no payload', () => {
assert.equal(createAction(TYPE)().payload, undefined);
it('should create an action object with type and no payload', () => {
const action = createAction(TYPE);
assert.deepEqual(action(), { type: TYPE });
});

it('should output action name on toString()', () => {
assert.equal(createAction(TYPE).toString(), TYPE);
const action = createAction(TYPE);
assert.equal(action.toString(), TYPE);
});

it('should add the input to the payload', () => {
Expand Down Expand Up @@ -52,10 +50,30 @@ describe('actions', () => {
it('should throw on toString()', () => {
assert.throws(
() => action.toString(),
`Async action ${TYPE} must be handled with pending, fulfilled or rejected`
new RegExp(`Async action ${TYPE} must be handled with pending, fulfilled or rejected`)
);
});

it('should create action with 0 arguments', async () => {
const action0 = createAsyncAction(TYPE, () => Promise.resolve(42));
assert.equal(await action0().payload, 42);
});

it('should create action with 1 arguments', async () => {
const action1 = createAsyncAction(TYPE, (n: number) => Promise.resolve(n));
assert.equal(await action1(42).payload, 42);
});

it('should create action with 2 arguments', async () => {
const action1 = createAsyncAction(TYPE, (n: number, s: string) => Promise.resolve({ n, s }));
assert.deepEqual(await action1(42, 'hello').payload, { n: 42, s: 'hello' });
});

it('should create action with 3 arguments', async () => {
const action1 = createAsyncAction(TYPE, (n: number, s: string, b: boolean) => Promise.resolve({ n, s, b }));
assert.deepEqual(await action1(42, 'hello', true).payload, { n: 42, s: 'hello', b: true });
});

describe('pending', () => {
it('should have a pending action prop', () => {
assert('pending' in action);
Expand Down

0 comments on commit 8ac4814

Please sign in to comment.