Skip to content

Commit

Permalink
intermediate
Browse files Browse the repository at this point in the history
  • Loading branch information
teneko committed Nov 25, 2021
1 parent 245b7de commit c9cd772
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 35 deletions.
68 changes: 43 additions & 25 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,36 +185,54 @@ function createPreparedPromiseAction<V, PA extends PrepareAction<any> = PrepareA
);
}

interface PromiseActionFactory<V> {
/**
* A utility function to create an action creator for the given action type
* string. The action creator accepts a single argument, which will be included
* in the action object as a field called payload. The action creator function
* will also have its toString() overriden so that it returns the action type,
* allowing it to be used in reducer logic that is looking for that action type.
* The created action contains promise actions to make redux-saga-promise work.
*
* @param type The action type to use for created actions.
*/
create<P = void, T extends string = string>(type: T): SagaPromiseActionCreator<V, P, T, ActionCreatorWithPayload<P, T>>
/**
* A utility function to create an action creator for the given action type
* string. The action creator accepts a single argument, which will be included
* in the action object as a field called payload. The action creator function
* will also have its toString() overriden so that it returns the action type,
* allowing it to be used in reducer logic that is looking for that action type.
* The created action contains promise actions to make redux-saga-promise work.
*
* @param type The action type to use for created actions.
* @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }.
* If this is given, the resulting action creator will pass its arguments to this method to calculate payload & meta.
*/
create<PA extends PrepareAction<any> = PrepareAction<any>, P = ReturnType<PA>["payload"], T extends string = string>(type: T, prepareAction: PA): SagaPromiseActionCreator<V, P, T, ActionCreatorWithPayload<P, T>>
}

/**
* @template V Resolve type contraint for promise.
*/
export function promiseActionFactory<V = any>() {
return {
/**
* A utility function to create an action creator for the given action type
* string. The action creator accepts a single argument, which will be included
* in the action object as a field called payload. The action creator function
* will also have its toString() overriden so that it returns the action type,
* allowing it to be used in reducer logic that is looking for that action type.
* The created action contains promise actions to make redux-saga-promise work.
*
* @param type The action type to use for created actions.
*/
simple: <P = void, T extends string = string>(type: T) => createPromiseAction<V, P>(type),
/**
* A utility function to create an action creator for the given action type
* string. The action creator accepts a single argument, which will be included
* in the action object as a field called payload. The action creator function
* will also have its toString() overriden so that it returns the action type,
* allowing it to be used in reducer logic that is looking for that action type.
* The created action contains promise actions to make redux-saga-promise work.
*
* @param type The action type to use for created actions.
* @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }.
* If this is given, the resulting action creator will pass its arguments to this method to calculate payload & meta.
*/
advanced: <PA extends PrepareAction<any> = PrepareAction<any>, T extends string = string>(type: T, prepareAction: PA) => createPreparedPromiseAction<V, PA>(type, prepareAction),
};
create: (type: any, prepareAction?: any) => {
if (arguments.length === 0) {
throw new ArgumentError("Type was expected");
}

if (arguments.length > 2) {
throw new ArgumentError("Too many arguments");
}

if (arguments.length === 2) {
return createPreparedPromiseAction(type, prepareAction);
}

return createPromiseAction(type);
},
} as any as PromiseActionFactory<V>;
}

/**
Expand Down
14 changes: 7 additions & 7 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {

describe("promiseAction", function () {
describe.each([
promiseActionFactory<string>().simple("simple"),
promiseActionFactory<string>().advanced("prepared", null),
promiseActionFactory<string>().create("simple"),
promiseActionFactory<string>().create("prepared", null),
])("creator", function (actionCreator) {
it(`${actionCreator.type} should have keys`, function () {
expect(typeof actionCreator?.trigger).toBe("function");
Expand All @@ -33,8 +33,8 @@ describe("promiseAction", function () {
});

describe.each([
promiseActionFactory<string>().simple("simple")(),
promiseActionFactory<string>().advanced("prepared", () => ({ payload: { test: "" } }))(),
promiseActionFactory<string>().create("simple")(),
promiseActionFactory<string>().create("prepared", () => ({ payload: { test: "" } }))(),
])("action", function (action) {
it(`${action.type} should have keys`, function () {
expect(action?.meta?.promiseActions?.resolved).toBeTruthy();
Expand Down Expand Up @@ -96,7 +96,7 @@ function setup(saga, { withMiddleware = true } = {}) {
// Define the promise action we'll use in our tests. To avoid possible
// contamination, create a new one for each test
//
const promiseAction = promiseActionFactory<string>().simple<string>("promiseAction");
const promiseAction = promiseActionFactory<string>().create<string>("promiseAction");

//
// Define a reducer that records the payloads of each phase
Expand Down Expand Up @@ -192,7 +192,7 @@ describe("implementPromiseAction", function () {
});

it("should correctly infer value type", function () {
const promiseAction = promiseActionFactory<string>().simple("test");
const promiseAction = promiseActionFactory<string>().create("test");
call(promiseAction.sagas.implement, promiseAction(), async () => Promise.resolve(""));
call(promiseAction.sagas.implement, promiseAction(), function* () { yield "dummy"; return ""; });
call(promiseAction.sagas.implement, promiseAction(), () => "");
Expand Down Expand Up @@ -241,7 +241,7 @@ describe("resolvePromiseAction", function () {
});

it("should correctly infer value type", function () {
const promiseAction = promiseActionFactory<string>().simple("test");
const promiseAction = promiseActionFactory<string>().create("test");
call(promiseAction.sagas.resolve, promiseAction(), "");
call(promiseAction.sagas.resolve, promiseAction.trigger(), "");
});
Expand Down
6 changes: 3 additions & 3 deletions test/index.types.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { promiseActionFactory } from "../src";

const promiseAction = promiseActionFactory<number>().simple("MY_ACTION");
const promiseAction = promiseActionFactory<number>().create("MY_ACTION");

declare const typeOfTriggerActionThatGotCreatedFromTheSimpleOrAdvancedActionCreator: typeof promiseAction.types.triggerAction;
declare const typeOfResolvedActionThatGotCreatedFromTheSimpleOrAdvancedActionCreator: typeof promiseAction.types.resolvedAction;
Expand All @@ -13,5 +13,5 @@ interface Payload {
data: any
}

const mustExpectPayload = promiseActionFactory<number>().simple<Payload>("MY_ACTION")({ data: {} });
const mustExpectInferredPayload = promiseActionFactory<number>().advanced("MY_ACTION", (payload: Payload) => ({ payload }))({ data: {} });
const mustExpectPayload = promiseActionFactory<number>().create<Payload>("MY_ACTION")({ data: {} });
const mustExpectInferredPayload = promiseActionFactory<number>().create("MY_ACTION", (payload: Payload) => ({ payload }))({ data: {} });

0 comments on commit c9cd772

Please sign in to comment.