@stackoverfloweth/vue-compositions
Version:
A collection of reusable vue compositions.
455 lines • 17.9 kB
JavaScript
import { mount } from '@vue/test-utils';
import { test, describe, it, expect, afterEach, vi } from 'vitest';
import { computed, h, reactive, ref } from 'vue';
import { useSubscription } from '../useSubscription';
import { SubscriptionManager } from '../useSubscription/models/manager';
import { timeout, uniqueSubscribe } from '../utilities/tests';
function numberEqualsOne(number) {
return number === 1;
}
function numberEqualsOnePromise(number, delay) {
return new Promise(resolve => {
setTimeout(() => resolve(numberEqualsOne(number)), delay);
});
}
afterEach(() => {
vi.useRealTimers();
});
describe('subscribe', () => {
it('returns the correct response', async () => {
const subscription = await uniqueSubscribe(numberEqualsOne, [1]);
expect(subscription.response).toBe(true);
});
it('returns the correct response when awaiting the promise', async () => {
vi.useFakeTimers();
const promise = uniqueSubscribe(numberEqualsOnePromise, [1, 1000]).promise();
vi.runAllTimers();
const subscription = await promise;
expect(subscription.response).toBe(true);
});
it('returns response undefined until action promise resolves', () => {
const subscription = uniqueSubscribe(numberEqualsOnePromise, [1, 10]);
expect(subscription.response).toBe(undefined);
});
it('returns the correct response when action promise resolves', async () => {
vi.useFakeTimers();
const subscription = uniqueSubscribe(numberEqualsOnePromise, [1, 10]);
vi.runAllTimers();
vi.useRealTimers();
await timeout();
expect(subscription.response).toBe(true);
});
it('sets loading to true', () => {
const subscription = uniqueSubscribe(numberEqualsOne, [1]);
expect(subscription.loading).toBe(true);
});
it('sets loading to false', async () => {
const subscription = await uniqueSubscribe(numberEqualsOne, [1]);
expect(subscription.loading).toBe(false);
});
it('defaults errored to false', () => {
const action = vi.fn();
const subscription = uniqueSubscribe(action, []);
expect(subscription.errored).toBe(false);
});
it('sets errored to true', () => {
function errors() {
throw 'look! an error';
}
const subscription = uniqueSubscribe(errors, []);
expect(subscription.errored).toBe(true);
});
it('sets error if an error is thrown', () => {
const error = 'look! an error';
function errors() {
throw error;
}
const subscription = uniqueSubscribe(errors, []);
expect(subscription.error).toBe(error);
});
it('rejects promise', async () => {
const error = 'error';
function action() {
throw error;
}
let caught;
try {
await uniqueSubscribe(action).promise();
}
catch (err) {
caught = err;
}
expect(caught).toBe(error);
});
it('executes the action once when two subscriptions are created', () => {
const manager = new SubscriptionManager();
const action = vi.fn();
useSubscription(action, [], { manager });
useSubscription(action, [], { manager });
expect(action).toBeCalledTimes(1);
});
it('calculates the poll interval correctly', () => {
vi.useFakeTimers();
const manager = new SubscriptionManager();
const action = vi.fn();
const initialExecutions = 1;
const additionalExecutions = 3;
const minInterval = 10;
const maxInterval = 20;
useSubscription(action, [], { interval: minInterval, manager });
useSubscription(action, [], { interval: maxInterval, manager });
vi.advanceTimersByTime(additionalExecutions * minInterval);
expect(action).toBeCalledTimes(initialExecutions + additionalExecutions);
});
it('calculates the poll interval correctly when a subscription is unsubscribed', () => {
vi.useFakeTimers();
const manager = new SubscriptionManager();
const action = vi.fn();
const initialExecutions = 1;
const additionalExecutions = 3;
const minInterval = 10;
const maxInterval = 20;
const subscription1 = useSubscription(action, [], { interval: minInterval, manager });
useSubscription(action, [], { interval: maxInterval, manager });
subscription1.unsubscribe();
vi.advanceTimersByTime(additionalExecutions * maxInterval);
expect(action).toBeCalledTimes(initialExecutions + additionalExecutions);
});
it('stops polling when all subscriptions with interval unsubscribe', () => {
vi.useFakeTimers();
const manager = new SubscriptionManager();
const action = vi.fn();
const minInterval = 10;
const maxInterval = 20;
const subscription1 = useSubscription(action, [], { interval: minInterval, manager });
const subscription2 = useSubscription(action, [], { interval: maxInterval, manager });
useSubscription(action, [], { manager });
subscription1.unsubscribe();
subscription2.unsubscribe();
vi.advanceTimersByTime(maxInterval);
expect(action).toBeCalledTimes(1);
});
it('executes the correct number of times when interval is set', () => {
vi.useFakeTimers();
const initialExecutions = 1;
const additionalExecutions = Math.floor(Math.random() * 10) + 1;
const action = vi.fn();
uniqueSubscribe(action, [], { interval: 50 });
for (let i = 0; i < additionalExecutions; i++) {
vi.runOnlyPendingTimers();
}
expect(action).toBeCalledTimes(initialExecutions + additionalExecutions);
});
it('stops executing when unsubscribed', () => {
vi.useFakeTimers();
const action = vi.fn();
const subscription = uniqueSubscribe(action, [], { interval: 50 });
subscription.unsubscribe();
vi.runOnlyPendingTimers();
expect(action).toBeCalledTimes(1);
});
describe('executes action again when args change', () => {
it('when using reactive args', async () => {
const action = vi.fn();
const args = reactive([0]);
uniqueSubscribe(action, args);
args[0] = 1;
await timeout();
expect(action).toBeCalledTimes(2);
});
it('when using ref args', async () => {
const action = vi.fn();
const args = ref([0]);
uniqueSubscribe(action, args);
args.value = [1];
await timeout();
expect(action).toBeCalledTimes(2);
});
it('when using args containing a ref value', async () => {
const action = vi.fn();
const number = ref(0);
const args = [number];
uniqueSubscribe(action, args);
number.value = 1;
await timeout();
expect(action).toBeCalledTimes(2);
});
it('when using args containing a ref value and a non reactive value', async () => {
const action = vi.fn();
const number = ref(0);
const args = [number, 0];
uniqueSubscribe(action, args);
number.value = 1;
await timeout();
expect(action).toBeCalledTimes(2);
});
it('when using args containing a reactive value', async () => {
const action = vi.fn();
const argument = reactive({ number: 0 });
const args = [argument];
uniqueSubscribe(action, args);
argument.number = 1;
await timeout();
expect(action).toBeCalledTimes(2);
});
it('when using args containing a reactive value and a non reactive value', async () => {
const action = vi.fn();
const argument = reactive({ number: 0 });
const args = [argument, 0];
uniqueSubscribe(action, args);
argument.number = 1;
await timeout();
expect(action).toBeCalledTimes(2);
});
});
it('updates response when args change', async () => {
const number = ref(0);
const subscription = uniqueSubscribe(numberEqualsOne, [number]);
number.value = 1;
await timeout();
expect(subscription.response).toBe(true);
});
test.each([
undefined,
{},
{ interval: 3000 },
{ lifecycle: 'component' },
])('calls unsubscribe when component is unmounted with default lifecycle', (options) => {
const action = vi.fn();
let subscription;
const wrapper = mount({
setup() {
subscription = uniqueSubscribe(action, [], options);
return () => h('p');
},
});
const spy = vi.spyOn(subscription, 'unsubscribe');
wrapper.unmount();
expect(spy).toBeCalledTimes(1);
});
it('does not call unsubscribe when component is unmounted with app lifecycle', () => {
const action = vi.fn();
let subscription;
const wrapper = mount({
setup() {
subscription = uniqueSubscribe(action, [], {
lifecycle: 'app',
});
return () => h('p');
},
});
const spy = vi.spyOn(subscription, 'unsubscribe');
wrapper.unmount();
expect(spy).not.toBeCalled();
});
it('does not update subscription when unsubscribed', async () => {
const manager = new SubscriptionManager();
let int = 0;
function action() {
return ++int;
}
const subscription1 = useSubscription(action, [], { manager });
const subscription2 = useSubscription(action, [], { manager });
await timeout();
subscription1.unsubscribe();
await subscription2.refresh();
expect(subscription1.response).toBe(1);
expect(subscription2.response).toBe(2);
});
it('does not update subscription when args change if unsubscribed', async () => {
const number = ref(0);
const subscription = uniqueSubscribe(numberEqualsOne, [number]);
await timeout();
subscription.unsubscribe();
number.value = 1;
await timeout();
expect(subscription.response).toBe(false);
});
it('when using computed args', async () => {
const action = vi.fn();
const valueRef = ref(0);
const valueComputed = computed(() => valueRef.value);
uniqueSubscribe(action, [valueComputed]);
valueRef.value = 1;
await timeout();
expect(action).toBeCalledTimes(2);
});
it('fails when using nested computed args', () => {
const action = vi.fn();
const valueRef = ref(0);
const valueComputed = computed(() => valueRef);
const valueComputedComputed = computed(() => valueComputed);
expect(() => {
uniqueSubscribe(action, [valueComputedComputed]);
}).toThrowError();
});
it('it does not return undefined when a subscription changes', async () => {
vi.useFakeTimers();
function action(value) {
return new Promise((resolve) => setTimeout(() => resolve(value), 10));
}
const originalValue = 0;
const valueArg = ref(originalValue);
const subscription = useSubscription(action, [valueArg]);
vi.runAllTimers();
vi.useRealTimers();
valueArg.value = 1;
await timeout();
expect(subscription.response).toBe(originalValue);
await timeout(15);
expect(subscription.response).toBe(1);
});
it('it does retain previous subscription response when arguments change', async () => {
vi.useFakeTimers();
function action(value) {
return new Promise((resolve) => setTimeout(() => resolve(value), 10));
}
const valueArg = ref(0);
const subscription = useSubscription(action, [valueArg]);
vi.runAllTimers();
vi.useRealTimers();
while (valueArg.value < 2) {
valueArg.value++;
// eslint-disable-next-line no-await-in-loop
await timeout();
expect(subscription.response).toBe(valueArg.value - 1);
// eslint-disable-next-line no-await-in-loop
await timeout(15);
expect(subscription.response).toBe(valueArg.value);
}
});
it('correctly sets response on additional subscriptions', async () => {
function action() {
return 0;
}
const manager = new SubscriptionManager();
useSubscription(action, [], { manager });
await timeout();
const subscription = useSubscription(action, [], { manager });
expect(subscription.response).toBe(0);
});
it('correctly sets executed', async () => {
const action = vi.fn();
const subscription = uniqueSubscribe(action);
await timeout();
expect(subscription.executed).toBe(true);
});
it('correctly sets executed on additional subscriptions', async () => {
const action = vi.fn();
const manager = new SubscriptionManager();
useSubscription(action, [], { manager });
await timeout();
const subscription = useSubscription(action, [], { manager });
expect(subscription.executed).toBe(true);
});
it('stops executing when action errors', () => {
const action = vi.fn(() => {
throw 'error';
});
vi.useFakeTimers();
uniqueSubscribe(action, [], { interval: 10000 });
vi.advanceTimersByTime(10000 * 2);
expect(action).toBeCalledTimes(1);
});
it('is retains subscription when reactive options change', () => {
vi.useFakeTimers();
const action = vi.fn();
const interval = ref(1000);
const options = computed(() => {
return {
interval: interval.value,
};
});
uniqueSubscribe(action, [], options);
expect(action).toBeCalledTimes(1);
interval.value = Infinity;
expect(action).toBeCalledTimes(1);
interval.value = 1000;
vi.advanceTimersByTime(1000);
expect(action).toBeCalledTimes(2);
});
it('unsubscribes nested subscriptions automatically', () => {
const manager = new SubscriptionManager();
const action = vi.fn();
let childSubscription;
const subscription = useSubscription(() => {
childSubscription = useSubscription(action);
}, [], { manager });
const spy = vi.spyOn(childSubscription, 'unsubscribe');
subscription.unsubscribe();
expect(spy).toBeCalledTimes(1);
});
it('unsubscribes nested subscriptions when changing args', async () => {
const number = ref(0);
const manager = new SubscriptionManager();
const action = vi.fn();
let childSubscription;
useSubscription((number) => {
childSubscription = useSubscription(action, [], { manager });
return number;
}, [number], { manager });
const spy = vi.spyOn(childSubscription, 'unsubscribe');
number.value++;
await timeout();
expect(spy).toBeCalledTimes(1);
});
it('it refreshes nested subscriptions when calling refresh', async () => {
const action = vi.fn();
const manager = new SubscriptionManager();
const subscription = useSubscription(() => {
useSubscription(action, [], { manager });
}, [], { manager });
await timeout();
subscription.refresh();
await timeout();
expect(action).toBeCalledTimes(2);
});
it('calls the onError callback when the action errors', async () => {
const error = new Error();
const onError = vi.fn();
const action = () => {
throw error;
};
uniqueSubscribe(action, [], { onError });
await timeout();
expect(onError).toBeCalledTimes(1);
expect(onError).toBeCalledWith(error);
});
it('allows refresh to be called when the a subscription is created with .promise()', async () => {
const action = vi.fn();
const subscription = await uniqueSubscribe(action).promise();
subscription.refresh();
expect(action).toBeCalledTimes(2);
});
it('response is reactive when using .promise()', async () => {
let value = 0;
const action = () => ++value;
const subscription = await uniqueSubscribe(action).promise();
expect(subscription.response).toBe(1);
subscription.refresh();
await timeout();
expect(subscription.response).toBe(2);
});
it('doest not execute if paused', async () => {
vi.useFakeTimers();
const interval = 1000;
const manager = new SubscriptionManager();
const action = vi.fn();
const subscription = useSubscription(action, [], { manager, interval });
expect(action).toBeCalledTimes(1);
vi.advanceTimersByTime(interval);
expect(action).toBeCalledTimes(2);
manager.pause();
expect(subscription.paused).toBe(true);
vi.advanceTimersByTime(interval);
expect(subscription.late).toBe(true);
expect(action).toBeCalledTimes(2);
manager.resume();
expect(action).toBeCalledTimes(3);
expect(subscription.paused).toBe(false);
vi.useRealTimers();
await timeout();
expect(subscription.late).toBe(false);
});
});
//# sourceMappingURL=useSubscription.spec.js.map