gristlabs_grist-core/test/common/gutil2.ts

158 lines
6.0 KiB
TypeScript
Raw Normal View History

import {delay} from 'app/common/delay';
import * as gutil from 'app/common/gutil';
import {assert} from 'chai';
import {Observable} from 'grainjs';
import * as ko from 'knockout';
import * as sinon from 'sinon';
describe('gutil2', function() {
describe('waitObs', function() {
it('should resolve promise when predicate matches', async function() {
const obs: ko.Observable<number|null> = ko.observable<number|null>(null);
const promise1 = gutil.waitObs(obs, (val) => Boolean(val));
const promise2 = gutil.waitObs(obs, (val) => (val === null));
const promise3 = gutil.waitObs(obs, (val) => (val! > 20));
const spy1 = sinon.spy(), spy2 = sinon.spy(), spy3 = sinon.spy();
const done = Promise.all([
promise1.then((val) => { spy1(); assert.strictEqual(val, 17); }),
promise2.then((val) => { spy2(); assert.strictEqual(val, null); }),
promise3.then((val) => { spy3(); assert.strictEqual(val, 30); }),
]);
await delay(1);
obs(17);
await delay(1);
obs(30);
await delay(1);
await done;
sinon.assert.callOrder(spy2, spy1, spy3);
});
});
describe('waitGrainObs', function() {
it('should resolve promise when predicate matches', async function() {
const obs = Observable.create<number|null>(null, null);
const promise1 = gutil.waitGrainObs(obs, (val) => Boolean(val));
const promise2 = gutil.waitGrainObs(obs, (val) => (val === null));
const promise3 = gutil.waitGrainObs(obs, (val) => (val! > 20));
const spy1 = sinon.spy(), spy2 = sinon.spy(), spy3 = sinon.spy();
const done = Promise.all([
promise1.then((val) => { spy1(); assert.strictEqual(val, 17); }),
promise2.then((val) => { spy2(); assert.strictEqual(val, null); }),
promise3.then((val) => { spy3(); assert.strictEqual(val, 30); }),
]);
await delay(1);
obs.set(17);
await delay(1);
obs.set(30);
await delay(1);
await done;
sinon.assert.callOrder(spy2, spy1, spy3);
});
});
describe('PromiseChain', function() {
it('should resolve promises in order', async function() {
const chain = new gutil.PromiseChain();
const spy1 = sinon.spy(), spy2 = sinon.spy(), spy3 = sinon.spy();
const done = Promise.all([
chain.add(() => delay(30).then(spy1).then(() => 1)),
chain.add(() => delay(20).then(spy2).then(() => 2)),
chain.add(() => delay(10).then(spy3).then(() => 3)),
]);
assert.deepEqual(await done, [1, 2, 3]);
sinon.assert.callOrder(spy1, spy2, spy3);
});
it('should skip pending callbacks, but not new callbacks, on error', async function() {
const chain = new gutil.PromiseChain();
const spy1 = sinon.spy(), spy2 = sinon.spy(), spy3 = sinon.spy();
let res1: any, res2: any, res3: any;
await assert.isRejected(Promise.all([
res1 = chain.add(() => delay(30).then(spy1).then(() => { throw new Error('Err1'); })),
res2 = chain.add(() => delay(20).then(spy2)),
res3 = chain.add(() => delay(10).then(spy3)),
]), /Err1/);
// Check that already-scheduled callbacks did not get called.
sinon.assert.calledOnce(spy1);
sinon.assert.notCalled(spy2);
sinon.assert.notCalled(spy3);
spy1.resetHistory();
// Ensure skipped add() calls return a rejection.
await assert.isRejected(res1, /^Err1/);
await assert.isRejected(res2, /^Skipped due to an earlier error/);
await assert.isRejected(res3, /^Skipped due to an earlier error/);
// New promises do get scheduled.
await assert.isRejected(Promise.all([
res1 = chain.add(() => delay(1).then(spy1).then(() => 17)),
res2 = chain.add(() => delay(1).then(spy2).then(() => { throw new Error('Err2'); })),
res3 = chain.add(() => delay(1).then(spy3)),
]), /Err2/);
sinon.assert.callOrder(spy1, spy2);
sinon.assert.notCalled(spy3);
// Check the return values of add() calls.
assert.strictEqual(await res1, 17);
await assert.isRejected(res2, /^Err2/);
await assert.isRejected(res3, /^Skipped due to an earlier error/);
});
});
describe("isLongerThan", function() {
it('should work correctly', async function() {
assert.equal(await gutil.isLongerThan(delay(200), 100), true);
assert.equal(await gutil.isLongerThan(delay(10), 100), false);
// A promise that throws before the timeout, causes the returned promise to resolve to false.
const errorObj = {};
let promise = delay(10).then(() => { throw errorObj; });
assert.equal(await gutil.isLongerThan(promise, 100), false);
await assert.isRejected(promise);
// A promise that throws after the timeout, causes the returned promise to resolve to true.
promise = delay(200).then(() => { throw errorObj; });
assert.equal(await gutil.isLongerThan(promise, 100), true);
await assert.isRejected(promise);
});
});
describe("isValidHex", function() {
it('should work correctly', async function() {
assert.equal(gutil.isValidHex('#FF00FF'), true);
assert.equal(gutil.isValidHex('#FF00FFF'), false);
assert.equal(gutil.isValidHex('#FF0'), false);
assert.equal(gutil.isValidHex('#FF00'), false);
assert.equal(gutil.isValidHex('FF00FF'), false);
assert.equal(gutil.isValidHex('#FF00FG'), false);
});
});
describe("pruneArray", function() {
function check<T>(arr: T[], indexes: number[], expect: T[]) {
gutil.pruneArray(arr, indexes);
assert.deepEqual(arr, expect);
}
it('should remove correct elements', function() {
check(['a', 'b', 'c'], [], ['a', 'b', 'c']);
check(['a', 'b', 'c'], [0], ['b', 'c']);
check(['a', 'b', 'c'], [1], ['a', 'c']);
check(['a', 'b', 'c'], [2], ['a', 'b']);
check(['a', 'b', 'c'], [0, 1], ['c']);
check(['a', 'b', 'c'], [0, 2], ['b']);
check(['a', 'b', 'c'], [1, 2], ['a']);
check(['a', 'b', 'c'], [0, 1, 2], []);
check([], [], []);
check(['a'], [], ['a']);
check(['a'], [0], []);
});
});
});