Skip to content

Commit

Permalink
improve tests and add more badges
Browse files Browse the repository at this point in the history
up version
  • Loading branch information
vankovilija committed Feb 5, 2022
1 parent d1f1447 commit b27bd3c
Show file tree
Hide file tree
Showing 13 changed files with 198 additions and 62 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![Build Status](https://app.travis-ci.com/vankovilija/redux-action-promise.svg?branch=master)](https://app.travis-ci.com/vankovilija/redux-action-promise)
[![Build Status](https://app.travis-ci.com/vankovilija/redux-action-promise.svg?branch=master)](https://app.travis-ci.com/vankovilija/redux-action-promise) [![Coverage Status](https://coveralls.io/repos/github/vankovilija/redux-action-promise/badge.svg?branch=master)](https://coveralls.io/github/vankovilija/redux-action-promise?branch=master) [![NPM Package Version](https://badgen.net/npm/v/redux-action-promise-enhancer)](https://www.npmjs.com/package/redux-promise-middleware-actions) [![NPM Package Version](https://badgen.net/bundlephobia/minzip/redux-action-promise-enhancer)](https://bundlephobia.com/package/redux-action-promise-enhancer)
# Redux Action Promise
Create a promise from a redux store and a list of expected actions that will resolve in the future

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "redux-action-promise-enhancer",
"version": "1.2.4",
"version": "1.2.5",
"description": "Create a promise from a redux store and a list of expected actions that will resolve in the future",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
8 changes: 8 additions & 0 deletions src/must-not-contain-array.util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,12 @@ describe('mustNotContainArray is a functionality to determine that no elements f
it ('continues if the input is a array with at least one element', () => {
expect(() => mustNotContainArray(['one', 'two'], ['four', 'five'], (item) => `${item} is contained in both arrays`)).not.toThrowError();
});

it ('it works with non-array objects', () => {
expect(() => mustNotContainArray('one', 'four', (item) => `${item} is contained in both arrays`)).not.toThrowError();
});

it ('it works with undefined', () => {
expect(() => mustNotContainArray(undefined, undefined, (item) => `${item} is contained in both arrays`)).not.toThrowError();
});
});
15 changes: 15 additions & 0 deletions src/queue/create-queue-item.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {createQueueItem} from "./create-queue-item";
import {createRequestAction} from "../create-request-action";
import {createAction} from "@reduxjs/toolkit";

describe('createQueueItem', () => {
it('creates a queue item from a request action and adds priority', () => {
const queueItem = createQueueItem(createRequestAction({type: 'test'}), 1);
expect(queueItem.priority).toBe(1);
});

it('creates a queue item from a request action creator and adds priority', () => {
const queueItemCreator = createQueueItem(createRequestAction(createAction('test')), 1);
expect(queueItemCreator().priority).toBe(1);
});
})
26 changes: 25 additions & 1 deletion src/queue/dispatch-in-queue.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {QueueType} from "./queue.interface";
import {QueueState} from "./queue-state.enum";
import {QueueItem} from "./queue-item.interface";
import {dispatchInQueue, DispatchInQueue} from "./dispatch-in-queue";
import {dispatchInQueue, DispatchInQueue, MAX_QUEUE_ITEMS} from "./dispatch-in-queue";
import {createQueueItem} from "./create-queue-item";
import {createRequestAction} from "../create-request-action";

Expand Down Expand Up @@ -49,6 +49,13 @@ describe('dispatchInQueue adds items to queue, dispatches, and sorts items', ()
expect(processFunction).toBeCalled();
});

it('can dispatch normal redux actions in a queue', () => {
const normalAction = {type: 'testNormal'};
dispatchFunction(normalAction);
expect(queue.items.length).toBe(1);
expect(queue.items[0].type).toBe(normalAction.type);
});

it('adds queue items when dispatched at the end of the queue, and sorts them', () => {
dispatchFunction(queueItems[2]);
dispatchFunction(queueItems[0]);
Expand All @@ -58,4 +65,21 @@ describe('dispatchInQueue adds items to queue, dispatches, and sorts items', ()
expect(queue.items[1].startAction).toBe(queueItems[1].startAction);
expect(queue.items[2].startAction).toBe(queueItems[0].startAction);
});

it('throws an invariant error if invalid parameters are provided', () => {
try {
dispatchFunction('test' as any);
} catch (e) {
expect(e.message).toBe('[Redux Action Promise Invariant Error]: Must provide Action or QueueItem as first parameter of dispatch in a queue')
}
});


it('cycles ids back down to 0 if out of item ids', () => {
dispatchFunction = dispatchInQueue(queue, processFunction, MAX_QUEUE_ITEMS - 1);
dispatchFunction(queueItems[1]);
dispatchFunction(queueItems[0]);
expect(queue.items[0].id).toBe(MAX_QUEUE_ITEMS);
expect(queue.items[1].id).toBe(0);
});
})
99 changes: 51 additions & 48 deletions src/queue/dispatch-in-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {ProcessQueue} from './process-queue';
import {removeFromQueue} from "./remove-from-queue";
import {createRequestAction, isRequestAction} from "../create-request-action";
import {ArrayOrSingleAnyTypeOfAction, CancelablePromise, RequestAction} from "../action-promise-store.interface";
import {convertToArray} from "../convert-to-array.util";
import {isActionCreator} from "../subscribe-to-actions/is-action-creator.util";
import {createQueueItem} from "./create-queue-item";

Expand All @@ -21,57 +20,61 @@ export type DispatchInQueue<A extends Action = AnyAction> = ((
priority?: Number
) => CancelablePromise);

export const MAX_QUEUE_ITEMS = 100000000;

export const dispatchInQueue = <A extends Action = AnyAction>(
queue: QueueType,
processQueue: ProcessQueue<A>
) => (<A extends Action = AnyAction>(
action: (A | RequestAction | QueueItem),
completeActions?: ArrayOrSingleAnyTypeOfAction<A>,
errorActions?: ArrayOrSingleAnyTypeOfAction<A>,
priority?: number
processQueue: ProcessQueue<A>,
startIndex?: number
) => {
let queueItem: QueueItem;
let action1;
if (isActionCreator(action)) {
action1 = action();
} else {
action1 = action;
}
if (isQueueItem(action1)) {
queueItem = action1;
} else if (isRequestAction(action1)) {
queueItem = createQueueItem(action1, priority) as QueueItem;
} else if (isActionObject(action1)) {
queueItem = createQueueItem(createRequestAction(action1, completeActions, errorActions), priority) as QueueItem;
} else {
invariant(false, 'Must provide Action or QueueItem as first parameter of dispatch in a queue');
if (startIndex !== undefined) {
latestItemID = startIndex;
}
return (<A extends Action = AnyAction>(
action: (A | RequestAction | QueueItem),
completeActions?: ArrayOrSingleAnyTypeOfAction<A>,
errorActions?: ArrayOrSingleAnyTypeOfAction<A>,
priority?: number
) => {
invariant(queue.items.length < MAX_QUEUE_ITEMS, 'Exceeded max queue items');
let queueItem: QueueItem;
let action1;
if (isActionCreator(action)) {
action1 = action();
} else {
action1 = action;
}
if (isQueueItem(action1)) {
queueItem = action1;
} else if (isRequestAction(action1)) {
queueItem = createQueueItem(action1, priority) as QueueItem;
} else if (isActionObject(action1)) {
queueItem = createQueueItem(createRequestAction(action1, completeActions, errorActions), priority) as QueueItem;
} else {
invariant(false, 'Must provide Action or QueueItem as first parameter of dispatch in a queue');
}

queueItem.endActions = queueItem.endActions ? convertToArray(queueItem.endActions) : undefined;
queueItem.errorActions = queueItem.errorActions ? convertToArray(queueItem.errorActions) : undefined;
let i = queue.items.length - 1;
while (i > -1) {
if (queue.items[i] === undefined) {
break;
let i = queue.items.length - 1;
while (i > -1) {
if (queueItem.priority === undefined || queue.items[i].priority === undefined || queue.items[i].priority >= queueItem.priority) {
i++;
break;
}
i--;
}
if (queueItem.priority === undefined || queue.items[i].priority === undefined || queue.items[i].priority >= queueItem.priority) {
i++;
break;
latestItemID ++;
if (latestItemID > MAX_QUEUE_ITEMS) {
latestItemID = 0;
}
i--;
}
latestItemID ++;
if (latestItemID === Number.MAX_VALUE - 1) {
latestItemID = 0;
}
let resolve: (result: any) => void;
let reject: (error: Error) => void;
const returnPromise = new Promise((rs, rj) => {
resolve = rs;
reject = rj;
}) as CancelablePromise;
queue.items.splice(i, 0, Object.assign({}, queueItem, {id: latestItemID, resolve, reject, processingPromise: undefined}));
processQueue();
returnPromise.cancel = removeFromQueue(queue, processQueue, latestItemID);
return returnPromise
})
let resolve: (result: any) => void;
let reject: (error: Error) => void;
const returnPromise = new Promise((rs, rj) => {
resolve = rs;
reject = rj;
}) as CancelablePromise;
queue.items.splice(i, 0, Object.assign({}, queueItem, {id: latestItemID, resolve, reject, processingPromise: undefined}));
processQueue();
returnPromise.cancel = removeFromQueue(queue, processQueue, latestItemID);
return returnPromise
})
}
39 changes: 39 additions & 0 deletions src/queue/find-item-in-queue.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {QueueMemberItem, QueueType} from "./queue.interface";
import {createQueueItem} from "./create-queue-item";
import {createRequestAction} from "../create-request-action";
import {QueueItem} from "./queue-item.interface";
import {QueueState} from "./queue-state.enum";
import {findItemInQueue} from "./find-item-in-queue.util";

describe('findItemInQueue', () => {
let queue: QueueType, queueItems: QueueMemberItem[];
beforeEach(() => {
const resolveMocks = [];
const rejectMocks = [];
queueItems = [];
for (let i = 0; i < 3; i++) {
resolveMocks.push(jest.fn());
rejectMocks.push(jest.fn());
const queueItem: QueueMemberItem = {
...createQueueItem(createRequestAction({type: `startAction${i}`}, {type: `endAction${i}`}), i) as QueueItem,
id: i,
processingPromise: undefined,
resolve: resolveMocks[i],
reject: rejectMocks[i],
}
queueItems.push(queueItem);
}
queue = {
items: queueItems,
state: QueueState.WAITING
};
})
it('locates an item in a queue', () => {
const item = findItemInQueue(queue, queueItems[1].id);
expect(item).toBe(queueItems[1]);
});
it('returns null if no item is found', () => {
const notFoundItem = findItemInQueue(queue, 54);
expect(notFoundItem).toBe(null)
})
})
14 changes: 14 additions & 0 deletions src/queue/is-queue-item.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {createQueueItem} from "./create-queue-item";
import {createRequestAction} from "../create-request-action";
import {isQueueItem} from "./is-queue-item.util";

describe('isQueueItem', () => {
it('Checks if an item is a queue item', () => {
const queueItem = createQueueItem(createRequestAction({type: 'test'}, {type: 'testResponse'}));
expect(isQueueItem(queueItem)).toBe(true);
const nonQueueItemRequest = createRequestAction({type: 'test2'}, {type: 'test2response'});
expect(isQueueItem(nonQueueItemRequest)).toBe(false);
const nonQueueItem = {type: 'test3'};
expect(isQueueItem(nonQueueItem)).toBe(false);
})
});
17 changes: 17 additions & 0 deletions src/queue/process-queue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,23 @@ describe('processQueue picks up the first item in a queue if the queue is in sta
expect(queue.state).toBe(QueueState.ACTIVE);
});

it ('if queue item is worked on and its promise is rejected, the next item will be picked up', async () => {
queue.items = queueItems.slice();
const result = processQueueFunction();
expect(result).toBe(true);
expect(dispatchFunction).toBeCalled();
const queueStartLength = queue.items.length;
dispatchFunction.rejects[0](new Error('test'));
try {
await dispatchFunction.promises[0];
}catch (e) {
} finally {
expect(queueStartLength - queue.items.length).toBe(1);
expect(queue.items[0].processingPromise !== undefined).toBe(true);
expect(queue.state).toBe(QueueState.ACTIVE);
}
});

it ('if queue item is worked on and its promise is resolved, and the queue is emptied, state goes back to WAITING', async () => {
queue.items.push(queueItems[0]);
processQueueFunction();
Expand Down
14 changes: 5 additions & 9 deletions src/queue/process-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,14 @@ export const processQueue = <A extends Action = AnyAction>(
) {
return false;
}
if (!queueItem.promise.resolveActions && !queueItem.promise.rejectActions) {
dispatchFunction<A>(queueItem as any);
const action = dispatchFunction<A>(queueItem as any);
if (isActionObject(action)) {
onActionComplete(queue, dispatchFunction, queueItem.id, false)(undefined);
} else {
queue.state = QueueState.ACTIVE;
const action = dispatchFunction<A>(queueItem as any);
if (!isActionObject(action)) {
queueItem.processingPromise = action;
queueItem.processingPromise
.then(onActionComplete(queue, dispatchFunction, queueItem.id, false))
.catch(onActionComplete(queue, dispatchFunction, queueItem.id, true));
}
queueItem.processingPromise = action;
queueItem.processingPromise
.then(onActionComplete(queue, dispatchFunction, queueItem.id, false), onActionComplete(queue, dispatchFunction, queueItem.id, true));
}
return true;
}
Expand Down
9 changes: 9 additions & 0 deletions src/reject-action-error.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {RejectActionError} from "./reject-action-error";

describe('RejectActionError', () => {
it('creates a RejectActionError instance', () => {
const rejectActionError = new RejectActionError({type: 'test'});
expect(rejectActionError.message).toBe('Rejected action test');
expect(rejectActionError).toBeInstanceOf(Error);
})
})
3 changes: 1 addition & 2 deletions src/subscribe-to-actions/unsubscribe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ describe('unsubscribe function that remove a subscription of listeners', () => {
listeners = [];
activeSubscriptionIndex = {
[action1]: [listeners],
[action2]: [listeners],
[action3]: [listeners]
[action3]: []
};
unsubscribe = unsubscribeFactory(subscriptionState, actions, activeSubscriptionIndex, listeners);
});
Expand Down
12 changes: 12 additions & 0 deletions src/timeout-error.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {TimeoutError} from "./timeout-error";

describe('TimeoutError', () => {
it('Creates a timeout error class', () => {
const timeoutError = new TimeoutError('test');
expect(timeoutError.name).toBe('TimeoutError');
expect(timeoutError.message).toBe('test');
expect(timeoutError).toBeInstanceOf(Error);
const timeoutError2 = new TimeoutError();
expect(timeoutError2.message).toBe('Timed out promise');
})
})

0 comments on commit b27bd3c

Please sign in to comment.