Files
privacy.sexy/tests/unit/presentation/components/Shared/Throttle.spec.ts
undergroundwires 5b1fbe1e2f Refactor code to comply with ESLint rules
Major refactoring using ESLint with rules from AirBnb and Vue.

Enable most of the ESLint rules and do necessary linting in the code.
Also add more information for rules that are disabled to describe what
they are and why they are disabled.

Allow logging (`console.log`) in test files, and in development mode
(e.g. when working with `npm run serve`), but disable it when
environment is production (as pre-configured by Vue). Also add flag
(`--mode production`) in `lint:eslint` command so production linting is
executed earlier in lifecycle.

Disable rules that requires a separate work. Such as ESLint rules that
are broken in TypeScript: no-useless-constructor (eslint/eslint#14118)
and no-shadow (eslint/eslint#13014).
2022-01-02 18:20:14 +01:00

159 lines
4.8 KiB
TypeScript

import 'mocha';
import { expect } from 'chai';
import { throttle, ITimer, TimeoutType } from '@/presentation/components/Shared/Throttle';
import { EventSource } from '@/infrastructure/Events/EventSource';
import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
describe('throttle', () => {
it('throws if callback is undefined', () => {
// arrange
const expectedError = 'undefined callback';
const callback = undefined;
// act
const act = () => throttle(callback, 500);
// assert
expect(act).to.throw(expectedError);
});
describe('throws if waitInMs is negative or zero', () => {
// arrange
const testCases = [
{ value: 0, expectedError: 'no delay to throttle' },
{ value: -2, expectedError: 'negative delay' },
];
const noopCallback = () => { /* do nothing */ };
for (const testCase of testCases) {
it(`"${testCase.value}" throws "${testCase.expectedError}"`, () => {
// act
const waitInMs = testCase.value;
const act = () => throttle(noopCallback, waitInMs);
// assert
expect(act).to.throw(testCase.expectedError);
});
}
});
it('should call the callback immediately', () => {
// arrange
const timer = new TimerMock();
let totalRuns = 0;
const callback = () => totalRuns++;
const throttleFunc = throttle(callback, 500, timer);
// act
throttleFunc();
// assert
expect(totalRuns).to.equal(1);
});
it('should call the callback again after the timeout', () => {
// arrange
const timer = new TimerMock();
let totalRuns = 0;
const callback = () => totalRuns++;
const waitInMs = 500;
const throttleFunc = throttle(callback, waitInMs, timer);
// act
throttleFunc();
totalRuns--; // So we don't count the initial run
throttleFunc();
timer.tickNext(waitInMs);
// assert
expect(totalRuns).to.equal(1);
});
it('should call the callback at most once at given time', () => {
// arrange
const timer = new TimerMock();
let totalRuns = 0;
const callback = () => totalRuns++;
const waitInMs = 500;
const totalCalls = 10;
const throttleFunc = throttle(callback, waitInMs, timer);
// act
for (let currentCall = 0; currentCall < totalCalls; currentCall++) {
const currentTime = (waitInMs / totalCalls) * currentCall;
timer.setCurrentTime(currentTime);
throttleFunc();
}
// assert
expect(totalRuns).to.equal(2); // one initial and one at the end
});
it('should call the callback as long as delay is waited', () => {
// arrange
const timer = new TimerMock();
let totalRuns = 0;
const callback = () => totalRuns++;
const waitInMs = 500;
const expectedTotalRuns = 10;
const throttleFunc = throttle(callback, waitInMs, timer);
// act
for (let i = 0; i < expectedTotalRuns; i++) {
throttleFunc();
timer.tickNext(waitInMs);
}
// assert
expect(totalRuns).to.equal(expectedTotalRuns);
});
it('should call arguments as expected', () => {
// arrange
const timer = new TimerMock();
const expected = [1, 2, 3];
const actual = new Array<number>();
const callback = (arg: number) => { actual.push(arg); };
const waitInMs = 500;
const throttleFunc = throttle(callback, waitInMs, timer);
// act
for (const arg of expected) {
throttleFunc(arg);
timer.tickNext(waitInMs);
}
// assert
expect(expected).to.deep.equal(actual);
});
});
class TimerMock implements ITimer {
private timeChanged = new EventSource<number>();
private subscriptions = new Array<IEventSubscription>();
private currentTime = 0;
public setTimeout(callback: () => void, ms: number): TimeoutType {
const runTime = this.currentTime + ms;
const subscription = this.timeChanged.on((time) => {
if (time >= runTime) {
callback();
subscription.unsubscribe();
}
});
this.subscriptions.push(subscription);
const id = this.subscriptions.length - 1;
return TimerMock.mockTimeout(id);
}
public clearTimeout(timeoutId: TimeoutType): void {
this.subscriptions[+timeoutId].unsubscribe();
}
public dateNow(): number {
return this.currentTime;
}
public tickNext(ms: number): void {
this.setCurrentTime(this.currentTime + ms);
}
public setCurrentTime(ms: number): void {
this.currentTime = ms;
this.timeChanged.notify(this.currentTime);
}
private static mockTimeout(subscriptionId: number): TimeoutType {
const throwNodeSpecificCode = () => { throw new Error('node specific code'); };
return {
[Symbol.toPrimitive]: () => subscriptionId,
hasRef: throwNodeSpecificCode,
refresh: throwNodeSpecificCode,
ref: throwNodeSpecificCode,
unref: throwNodeSpecificCode,
};
}
}