@data-client/core
Version:
Async State Management without the Management. REST, GraphQL, SSE, Websockets, Fetch
197 lines (165 loc) • 6.39 kB
text/typescript
import { Article, PollingArticleResource } from '__tests__/new';
import { actionTypes, Controller, initialState } from '../..';
import { SubscribeAction, UnsubscribeAction } from '../../types';
import SubscriptionManager, { Subscription } from '../SubscriptionManager.js';
const { UNSUBSCRIBE, SUBSCRIBE, SET_RESPONSE } = actionTypes;
function onError(e: any) {
e.preventDefault();
}
beforeEach(() => {
if (typeof addEventListener === 'function')
addEventListener('error', onError);
});
afterEach(() => {
if (typeof removeEventListener === 'function')
removeEventListener('error', onError);
});
describe('SubscriptionManager', () => {
class TestSubscription implements Subscription {
add = jest.fn();
remove = jest.fn(() => true);
cleanup = jest.fn();
}
const manager = new SubscriptionManager(TestSubscription);
const getState = () => initialState;
describe('middleware', () => {
it('should return the same value every call', () => {
const a = manager.middleware;
expect(a).toBe(manager.middleware);
});
});
describe('cleanup()', () => {
it('should cleanup all Subcription members', () => {
const sub = new TestSubscription();
(manager as any).subscriptions[
PollingArticleResource.get.key({ id: 5 })
] = sub;
manager.cleanup();
expect(sub.cleanup.mock.calls.length).toBe(1);
});
});
describe('middleware', () => {
function createSubscribeAction(
response: Record<string, any>,
reject = false,
): SubscribeAction {
const fetch =
reject ?
() => Promise.reject(new Error('Failed'))
: () => Promise.resolve(response);
return {
type: SUBSCRIBE,
endpoint: PollingArticleResource.get.extend({ fetch }),
args: [{ id: response.id }],
key: PollingArticleResource.get.key({ id: response.id }),
};
}
function createUnsubscribeAction(
response: Record<string, any>,
): UnsubscribeAction {
return {
type: UNSUBSCRIBE,
endpoint: PollingArticleResource.get,
key: PollingArticleResource.get.key({ id: response.id }),
args: [{ id: response.id }],
};
}
const manager = new SubscriptionManager(TestSubscription);
const next = jest.fn();
const dispatch = () => Promise.resolve();
const controller = new Controller({ dispatch, getState });
const API: Controller & { controller: Controller } = Object.create(
controller,
{
controller: { value: controller },
},
);
it('subscribe should add a subscription', () => {
const action = createSubscribeAction({ id: 5 });
manager.middleware(API)(next)(action);
expect(next).not.toHaveBeenCalled();
expect((manager as any).subscriptions[action.key]).toBeDefined();
});
it('subscribe should add a subscription (no frequency)', () => {
const action = createSubscribeAction({ id: 597 });
manager.middleware(API)(next)(action);
expect(next).not.toHaveBeenCalled();
expect((manager as any).subscriptions[action.key]).toBeDefined();
});
it('subscribe with same should call subscription.add', () => {
const action = createSubscribeAction({ id: 5, title: 'four' });
manager.middleware(API)(next)(action);
expect(
(manager as any).subscriptions[action.key].add.mock.calls.length,
).toBe(1);
manager.middleware(API)(next)(action);
expect(
(manager as any).subscriptions[action.key].add.mock.calls.length,
).toBe(2);
});
it('subscribe with another should create another', () => {
const action = createSubscribeAction({ id: 7, title: 'four cakes' });
manager.middleware(API)(next)(action);
expect((manager as any).subscriptions[action.key]).toBeDefined();
expect(
(manager as any).subscriptions[action.key].add.mock.calls.length,
).toBe(0);
});
it('subscribe with another should not call previous', () => {
expect(
(manager as any).subscriptions[
PollingArticleResource.get.key({ id: 5 })
].add.mock.calls.length,
).toBe(2);
});
it('unsubscribe should delete when remove returns true', () => {
const action = createUnsubscribeAction({ id: 7, title: 'four cakes' });
(manager as any).subscriptions[action.key].remove.mockImplementation(
() => true,
);
manager.middleware(API)(next)(action);
expect((manager as any).subscriptions[action.key]).not.toBeDefined();
});
it('unsubscribe should delete when remove returns true (no frequency)', () => {
manager.middleware(API)(next)(
createSubscribeAction({ id: 50, title: 'four cakes' }),
);
const action = createUnsubscribeAction({ id: 50, title: 'four cakes' });
(manager as any).subscriptions[action.key].remove.mockImplementation(
() => true,
);
manager.middleware(API)(next)(action);
expect((manager as any).subscriptions[action.key]).not.toBeDefined();
});
it('unsubscribe should not delete when remove returns false', () => {
const action = createUnsubscribeAction({ id: 5, title: 'four cakes' });
(manager as any).subscriptions[action.key].remove.mockImplementation(
() => false,
);
manager.middleware(API)(next)(action);
expect((manager as any).subscriptions[action.key]).toBeDefined();
expect(
(manager as any).subscriptions[action.key].remove.mock.calls.length,
).toBe(1);
});
it('unsubscribe should do nothing when there was no subscription already', () => {
const oldError = console.error;
const spy = (console.error = jest.fn());
const action = createUnsubscribeAction({ id: 25 });
manager.middleware(API)(next)(action);
expect((manager as any).subscriptions[action.key]).not.toBeDefined();
expect(spy.mock.calls[0]).toMatchInlineSnapshot(`
[
"Mismatched unsubscribe: GET http://test.com/article/25 is not subscribed",
]
`);
console.error = oldError;
});
it('should let other actions pass through', () => {
const action = { type: SET_RESPONSE };
next.mockReset();
manager.middleware(API)(next)(action as any);
expect(next.mock.calls.length).toBe(1);
});
});
});