Dust off (some of) the core tests
This commit is contained in:
parent
919ee891f6
commit
56ac38f7e4
|
@ -1,8 +1,6 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import 'preserves';
|
||||
|
||||
export default {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
|
|
|
@ -1,117 +1,50 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
"use strict";
|
||||
import { KeyedSet } from '@preserves/core';
|
||||
import { Bag, ChangeDescription } from '../src/runtime/bag';
|
||||
|
||||
const assert = require('assert');
|
||||
const Immutable = require('immutable');
|
||||
const Bag = require('../src/bag.js');
|
||||
|
||||
describe('immutable bag', function () {
|
||||
it('should be initializable from a set', function () {
|
||||
var b = Bag.fromSet(Immutable.Set(['a', 'b', 'c']));
|
||||
assert.strictEqual(b.count(), 3);
|
||||
assert.strictEqual(Bag.get(b, 'a'), 1);
|
||||
assert.strictEqual(Bag.get(b, 'z'), 0);
|
||||
describe('bag', () => {
|
||||
it('should be initializable from a set', () => {
|
||||
const b = new Bag(new KeyedSet<string>(['a', 'b', 'c']));
|
||||
expect(b.size).toBe(3);
|
||||
expect(b.get('a')).toBe(1);
|
||||
expect(b.get('z')).toBe(0);
|
||||
});
|
||||
|
||||
it('should be initializable from an array', function () {
|
||||
var b = Bag.fromSet(['a', 'b', 'c', 'a']);
|
||||
assert.strictEqual(b.count(), 3);
|
||||
assert.strictEqual(Bag.get(b, 'a'), 1);
|
||||
assert.strictEqual(Bag.get(b, 'z'), 0);
|
||||
it('should be mutable', () => {
|
||||
const b = new Bag();
|
||||
b.change('a', 1);
|
||||
b.change('a', 1);
|
||||
expect(b.get('a')).toBe(2);
|
||||
});
|
||||
|
||||
it('should be immutable', function () {
|
||||
var b = Bag.Bag();
|
||||
Bag.change(b, 'a', 1);
|
||||
Bag.change(b, 'a', 1);
|
||||
assert(Immutable.is(b, Bag.Bag()));
|
||||
it('should count up', () => {
|
||||
const b = new Bag();
|
||||
expect(b.change('a', 1)).toBe(ChangeDescription.ABSENT_TO_PRESENT);
|
||||
expect(b.change('a', 1)).toBe(ChangeDescription.PRESENT_TO_PRESENT);
|
||||
expect(b.get('a')).toBe(2);
|
||||
expect(b.get('z')).toBe(0);
|
||||
});
|
||||
|
||||
it('should count up', function () {
|
||||
var b = Bag.Bag();
|
||||
var change1, change2;
|
||||
({bag: b, net: change1} = Bag.change(b, 'a', 1));
|
||||
({bag: b, net: change2} = Bag.change(b, 'a', 1));
|
||||
assert.strictEqual(change1, Bag.ABSENT_TO_PRESENT);
|
||||
assert.strictEqual(change2, Bag.PRESENT_TO_PRESENT);
|
||||
assert.strictEqual(Bag.get(b, 'a'), 2);
|
||||
assert.strictEqual(Bag.get(b, 'z'), 0);
|
||||
});
|
||||
|
||||
it('should count down', function () {
|
||||
var b = Bag.fromSet(['a']);
|
||||
var c1, c2, c3, c4;
|
||||
({bag: b, net: c1} = Bag.change(b, 'a', 1));
|
||||
({bag: b, net: c2} = Bag.change(b, 'a', -1));
|
||||
assert.strictEqual(b.count(), 1);
|
||||
assert.strictEqual(c1, Bag.PRESENT_TO_PRESENT);
|
||||
assert.strictEqual(c2, Bag.PRESENT_TO_PRESENT);
|
||||
({bag: b, net: c3} = Bag.change(b, 'a', -1));
|
||||
assert.strictEqual(b.count(), 0);
|
||||
assert.strictEqual(c3, Bag.PRESENT_TO_ABSENT);
|
||||
assert.strictEqual(Bag.get(b, 'a'), 0);
|
||||
assert.strictEqual(Bag.get(b, 'z'), 0);
|
||||
({bag: b, net: c4} = Bag.change(b, 'a', -1));
|
||||
assert.strictEqual(b.count(), 1);
|
||||
assert.strictEqual(c4, Bag.ABSENT_TO_PRESENT);
|
||||
assert.strictEqual(Bag.get(b, 'a'), -1);
|
||||
it('should count down', () => {
|
||||
const b = new Bag(new KeyedSet<string>(['a']));
|
||||
expect(b.change('a', 1)).toBe(ChangeDescription.PRESENT_TO_PRESENT);
|
||||
expect(b.change('a', -1)).toBe(ChangeDescription.PRESENT_TO_PRESENT);
|
||||
expect(b.size).toBe(1);
|
||||
expect(b.change('a', -1)).toBe(ChangeDescription.PRESENT_TO_ABSENT);
|
||||
expect(b.size).toBe(0);
|
||||
expect(b.get('a')).toBe(0);
|
||||
expect(b.get('z')).toBe(0);
|
||||
expect(b.change('a', -1)).toBe(ChangeDescription.ABSENT_TO_PRESENT);
|
||||
expect(b.size).toBe(1);
|
||||
expect(b.get('a')).toBe(-1);
|
||||
});
|
||||
|
||||
it('should be clamped', function() {
|
||||
var b = Bag.fromSet(['a']);
|
||||
({bag: b} = Bag.change(b, 'a', -1, true));
|
||||
({bag: b} = Bag.change(b, 'a', -1, true));
|
||||
({bag: b} = Bag.change(b, 'a', -1, true));
|
||||
({bag: b} = Bag.change(b, 'a', -1, true));
|
||||
assert.strictEqual(b.count(), 0);
|
||||
assert.strictEqual(Bag.get(b, 'a'), 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mutable bag', function () {
|
||||
it('should be initializable from a set', function () {
|
||||
var b = new Bag.MutableBag(Immutable.Set(['a', 'b', 'c']));
|
||||
assert.strictEqual(b.count(), 3);
|
||||
assert.strictEqual(b.get('a'), 1);
|
||||
assert.strictEqual(b.get('z'), 0);
|
||||
});
|
||||
|
||||
it('should be initializable from an array', function () {
|
||||
var b = new Bag.MutableBag(['a', 'b', 'c', 'a']);
|
||||
assert.strictEqual(b.count(), 3);
|
||||
assert.strictEqual(b.get('a'), 1);
|
||||
assert.strictEqual(b.get('z'), 0);
|
||||
});
|
||||
|
||||
it('should be mutable', function () {
|
||||
var b = new Bag.MutableBag();
|
||||
b.change('a', 1);
|
||||
b.change('a', 1);
|
||||
assert.strictEqual(b.get('a'), 2);
|
||||
assert.strictEqual(b.get('z'), 0);
|
||||
});
|
||||
|
||||
it('should count up', function () {
|
||||
var b = new Bag.MutableBag();
|
||||
assert.strictEqual(b.change('a', 1), Bag.ABSENT_TO_PRESENT);
|
||||
assert.strictEqual(b.change('a', 1), Bag.PRESENT_TO_PRESENT);
|
||||
assert.strictEqual(b.get('a'), 2);
|
||||
assert.strictEqual(b.get('z'), 0);
|
||||
});
|
||||
|
||||
it('should count down', function () {
|
||||
var b = new Bag.MutableBag(['a']);
|
||||
assert.strictEqual(b.change('a', 1), Bag.PRESENT_TO_PRESENT);
|
||||
assert.strictEqual(b.change('a', -1), Bag.PRESENT_TO_PRESENT);
|
||||
assert.strictEqual(b.count(), 1);
|
||||
assert.strictEqual(b.change('a', -1), Bag.PRESENT_TO_ABSENT);
|
||||
assert.strictEqual(b.count(), 0);
|
||||
assert.strictEqual(b.get('a'), 0);
|
||||
assert.strictEqual(b.get('z'), 0);
|
||||
assert.strictEqual(b.change('a', -1), Bag.ABSENT_TO_PRESENT);
|
||||
assert.strictEqual(b.count(), 1);
|
||||
assert.strictEqual(b.get('a'), -1);
|
||||
const b = new Bag(new KeyedSet(['a']));
|
||||
for (let i = 0; i < 4; i++) b.change('a', -1, true);
|
||||
expect(b.size).toBe(0);
|
||||
expect(b.get('a')).toBe(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,197 +1,228 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
"use strict";
|
||||
import { Canonicalizer, embeddedId, FlexSet } from '@preserves/core';
|
||||
import { Cell, Field, Graph } from '../src/runtime/dataflow';
|
||||
import './test-utils';
|
||||
|
||||
const assert = require('assert');
|
||||
var Immutable = require('immutable');
|
||||
class CountingField<V> extends Field<V> {
|
||||
readCount = 0;
|
||||
writeCount = 0;
|
||||
|
||||
var Dataflow = require('../src/dataflow.js');
|
||||
get value(): V {
|
||||
this.readCount++;
|
||||
return super.value;
|
||||
}
|
||||
|
||||
function Cell(graph, initialValue, name) {
|
||||
this.objectId = graph.defineObservableProperty(this, 'value', initialValue, {
|
||||
objectId: name,
|
||||
noopGuard: (a, b) => a === b
|
||||
});
|
||||
set value(v: V) {
|
||||
this.writeCount++;
|
||||
super.value = v;
|
||||
}
|
||||
|
||||
spy(): V {
|
||||
// Retrieve the value without bumping the counters!
|
||||
return this.__value as V;
|
||||
}
|
||||
|
||||
checkCounts(expectedReadCount: number, expectedWriteCount: number) {
|
||||
expect(this.readCount).toBe(expectedReadCount);
|
||||
expect(this.writeCount).toBe(expectedWriteCount);
|
||||
}
|
||||
}
|
||||
|
||||
function DerivedCell(graph, name, valueThunk) {
|
||||
var c = new Cell(graph, undefined, name);
|
||||
c.refresh = function () { c.value = valueThunk(); };
|
||||
graph.withSubject(c, function () { c.refresh(); });
|
||||
return c;
|
||||
class TestGraph<SubjectId> extends Graph<SubjectId, Cell> {
|
||||
constructor(subjectIdCanonicalizer: Canonicalizer<SubjectId>) {
|
||||
super(subjectIdCanonicalizer,
|
||||
Cell.canonicalizer,
|
||||
g => Array.from(g.values()));
|
||||
}
|
||||
|
||||
checkDamagedNodes(expectedObjects: FlexSet<Cell>) {
|
||||
expect(this.damagedNodes).is(expectedObjects);
|
||||
}
|
||||
}
|
||||
|
||||
function expectSetsEqual(a, bArray) {
|
||||
assert(Immutable.is(a, Immutable.Set(bArray)));
|
||||
class BlockGraph extends TestGraph<() => void> {
|
||||
constructor() {
|
||||
super(b => '' + embeddedId(b));
|
||||
}
|
||||
|
||||
run(subject: () => void): () => void {
|
||||
this.withSubject(subject, subject);
|
||||
return subject;
|
||||
}
|
||||
|
||||
eqn<V>(name: string, f: () => V): CountingField<V> {
|
||||
const field = new CountingField<V>(this, null!, name);
|
||||
this.run(() => field.value = f());
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
function checkDamagedNodes(g, expectedObjects) {
|
||||
expectSetsEqual(g.damagedNodes, expectedObjects);
|
||||
class StringGraph extends TestGraph<string> {
|
||||
constructor() {
|
||||
super(s => s);
|
||||
}
|
||||
}
|
||||
|
||||
describe('dataflow', () => {
|
||||
describe('edges, damage and subjects', () => {
|
||||
it('should be recorded', () => {
|
||||
var g = new Dataflow.Graph();
|
||||
var c = new Cell(g, 123);
|
||||
const g = new StringGraph();
|
||||
const c = new CountingField(g, 123);
|
||||
|
||||
g.withSubject('s', () => { c.value; });
|
||||
g.withSubject('t', () => { c.value; });
|
||||
g.withSubject('s', () => { c.value; });
|
||||
|
||||
c.value = 234;
|
||||
assert.strictEqual(g.damagedNodes.size, 1);
|
||||
expect(g.damagedNodes.size).toBe(1);
|
||||
|
||||
var subjects = Immutable.Set();
|
||||
g.repairDamage(function (subjectId) { subjects = subjects.add(subjectId); });
|
||||
expectSetsEqual(subjects, ['s', 't']);
|
||||
const subjects = new FlexSet<string>(s => s);
|
||||
g.repairDamage(s => subjects.add(s));
|
||||
expect(subjects).is(new Set(['s', 't']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('DerivedCell', () => {
|
||||
describe('dataflow blocks', () => {
|
||||
describe('simple case', () => {
|
||||
var g = new Dataflow.Graph();
|
||||
var c = DerivedCell(g, 'c', () => 123);
|
||||
var d = DerivedCell(g, 'd', () => c.value * 2);
|
||||
const g = new BlockGraph();
|
||||
const c = new CountingField(g, 0);
|
||||
const d = new CountingField(g, 0);
|
||||
g.run(() => c.value = 123);
|
||||
g.run(() => d.value = c.value * 2);
|
||||
it('should be properly initialized', () => {
|
||||
assert.strictEqual(c.value, 123);
|
||||
assert.strictEqual(d.value, 246);
|
||||
expect(c.value).toBe(123);
|
||||
expect(d.value).toBe(246);
|
||||
});
|
||||
it('should lead initially to damaged everything', () => {
|
||||
assert.strictEqual(g.damagedNodes.size, 2);
|
||||
expect(g.damagedNodes.size).toBe(2);
|
||||
});
|
||||
it('should repair idempotently after initialization', () => {
|
||||
g.repairDamage(function (c) { c.refresh(); });
|
||||
assert.strictEqual(c.value, 123);
|
||||
assert.strictEqual(d.value, 246);
|
||||
g.repairDamage(c => g.run(c));
|
||||
expect(c.value).toBe(123);
|
||||
expect(d.value).toBe(246);
|
||||
});
|
||||
it('should be inconsistent after modification but before repair', () => {
|
||||
c.value = 124;
|
||||
assert.strictEqual(c.value, 124);
|
||||
assert.strictEqual(d.value, 246);
|
||||
expect(c.value).toBe(124);
|
||||
expect(d.value).toBe(246);
|
||||
});
|
||||
it('should repair itself properly', () => {
|
||||
g.repairDamage(function (c) { c.refresh(); });
|
||||
assert.strictEqual(c.value, 124);
|
||||
assert.strictEqual(d.value, 248);
|
||||
g.repairDamage(c => g.run(c));
|
||||
expect(c.value).toBe(124);
|
||||
expect(d.value).toBe(248);
|
||||
});
|
||||
});
|
||||
|
||||
describe('a more complex case', () => {
|
||||
var g = new Dataflow.Graph();
|
||||
const g = new BlockGraph();
|
||||
|
||||
function add(a, b) { return a + b; }
|
||||
var xs = new Cell(g, Immutable.List.of(1, 2, 3, 4), 'xs');
|
||||
var sum = DerivedCell(g, 'sum', () => xs.value.reduce(add, 0));
|
||||
var len = DerivedCell(g, 'len', () => xs.value.size);
|
||||
var avg = DerivedCell(g, 'avg', () => {
|
||||
const xs = new CountingField(g, [1, 2, 3, 4]);
|
||||
const sum = g.eqn('sum', () => xs.value.reduce((a, b) => a + b, 0));
|
||||
const len = g.eqn('len', () => xs.value.length);
|
||||
const avg = g.eqn('avg', () => {
|
||||
if (len.value === 0) return null;
|
||||
return sum.value / len.value;
|
||||
});
|
||||
var scale = new Cell(g, 1, 'scale');
|
||||
var ans = DerivedCell(g, 'ans', () => {
|
||||
const scale = new CountingField(g, 1, 'scale');
|
||||
const ans = g.eqn('ans', () => {
|
||||
if (scale.value === 0) return null;
|
||||
return typeof avg.value === 'number' && avg.value / scale.value;
|
||||
});
|
||||
|
||||
function expectValues(vs) {
|
||||
g.repairDamage(function (c) { c.refresh(); });
|
||||
assert.deepStrictEqual(
|
||||
[xs.value.toJS(), sum.value, len.value, avg.value, scale.value, ans.value],
|
||||
vs);
|
||||
function expectValues(vs: [
|
||||
typeof xs.value,
|
||||
typeof sum.value,
|
||||
typeof len.value,
|
||||
typeof avg.value,
|
||||
typeof scale.value,
|
||||
typeof ans.value,
|
||||
]) {
|
||||
g.repairDamage(c => g.run(c));
|
||||
expect([
|
||||
xs.value,
|
||||
sum.value,
|
||||
len.value,
|
||||
avg.value,
|
||||
scale.value,
|
||||
ans.value,
|
||||
]).is(vs);
|
||||
}
|
||||
|
||||
it('initially', () => {
|
||||
expectValues([ [1,2,3,4], 10, 4, 2.5, 1, 2.5 ]);
|
||||
expectValues([[1, 2, 3, 4], 10, 4, 2.5, 1, 2.5]);
|
||||
});
|
||||
it('at scale zero', () => {
|
||||
scale.value = 0;
|
||||
expectValues([ [1,2,3,4], 10, 4, 2.5, 0, null ]);
|
||||
expectValues([[1, 2, 3, 4], 10, 4, 2.5, 0, null]);
|
||||
});
|
||||
it('with nine and zero', () => {
|
||||
xs.value = xs.value.concat([9, 0]);
|
||||
expectValues([ [1,2,3,4,9,0], 19, 6, 19/6, 0, null ]);
|
||||
xs.value = [... xs.value, 9, 0];
|
||||
expectValues([[1, 2, 3, 4, 9, 0], 19, 6, 19 / 6, 0, null]);
|
||||
});
|
||||
it('with five and four', () => {
|
||||
xs.value = xs.value.skipLast(2).concat([5, 4]);
|
||||
expectValues([ [1,2,3,4,5,4], 19, 6, 19/6, 0, null ]);
|
||||
xs.value = [... xs.value.slice(0, -2), 5, 4];
|
||||
expectValues([[1, 2, 3, 4, 5, 4], 19, 6, 19 / 6, 0, null]);
|
||||
});
|
||||
it('at scale one', () => {
|
||||
scale.value = 1;
|
||||
expectValues([ [1,2,3,4,5,4], 19, 6, 19/6, 1, 19/6 ]);
|
||||
expectValues([[1, 2, 3, 4, 5, 4], 19, 6, 19 / 6, 1, 19 / 6]);
|
||||
});
|
||||
it('empty', () => {
|
||||
xs.value = Immutable.List();
|
||||
expectValues([ [], 0, 0, null, 1, false ]);
|
||||
xs.value = [];
|
||||
expectValues([[], 0, 0, null, 1, false]);
|
||||
});
|
||||
it('four, five, and six', () => {
|
||||
xs.value = Immutable.List.of(4, 5, 6);
|
||||
expectValues([ [4,5,6], 15, 3, 15/3, 1, 15/3 ]);
|
||||
xs.value = [4, 5, 6];
|
||||
expectValues([[4, 5, 6], 15, 3, 15 / 3, 1, 15 / 3]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('scopes', () => {
|
||||
var g = new Dataflow.Graph();
|
||||
|
||||
function buildScopes() {
|
||||
var rootScope = {};
|
||||
var midScope = Dataflow.Graph.newScope(rootScope);
|
||||
var outerScope = Dataflow.Graph.newScope(midScope);
|
||||
return {root: rootScope, mid: midScope, outer: outerScope};
|
||||
}
|
||||
|
||||
it('should make rootward props visible further out', () => {
|
||||
var ss = buildScopes();
|
||||
g.defineObservableProperty(ss.root, 'p', 123);
|
||||
assert.strictEqual(ss.root.p, 123);
|
||||
assert.strictEqual(ss.mid.p, 123);
|
||||
assert.strictEqual(ss.outer.p, 123);
|
||||
assert('p' in ss.root);
|
||||
assert('p' in ss.mid);
|
||||
assert('p' in ss.outer);
|
||||
describe('update batching', () => {
|
||||
const g = new BlockGraph();
|
||||
const a = new CountingField(g, 1);
|
||||
const b = new CountingField(g, 2);
|
||||
const c = g.eqn('c', () => a.value + b.value);
|
||||
it('has correct initial values', () => {
|
||||
expect(a.spy()).toBe(1);
|
||||
expect(b.spy()).toBe(2);
|
||||
expect(c.spy()).toBe(3);
|
||||
});
|
||||
|
||||
it('should make changes at root visible at leaves', () => {
|
||||
var ss = buildScopes();
|
||||
g.defineObservableProperty(ss.root, 'p', 123);
|
||||
assert.strictEqual(ss.outer.p, 123);
|
||||
ss.root.p = 234;
|
||||
assert.strictEqual(ss.root.p, 234);
|
||||
assert.strictEqual(ss.outer.p, 234);
|
||||
it('has correct initial counts', () => {
|
||||
a.checkCounts(1, 0);
|
||||
b.checkCounts(1, 0);
|
||||
c.checkCounts(0, 1);
|
||||
});
|
||||
|
||||
it('should make changes at leaves visible at root', () => {
|
||||
var ss = buildScopes();
|
||||
g.defineObservableProperty(ss.root, 'p', 123);
|
||||
assert.strictEqual(ss.outer.p, 123);
|
||||
ss.outer.p = 234;
|
||||
assert.strictEqual(ss.root.p, 234);
|
||||
assert.strictEqual(ss.outer.p, 234);
|
||||
it('should repair idempotently after initialization', () => {
|
||||
g.repairDamage(c => g.run(c));
|
||||
a.checkCounts(1, 0);
|
||||
b.checkCounts(1, 0);
|
||||
c.checkCounts(0, 1);
|
||||
});
|
||||
|
||||
it('should hide definitions at leaves from roots', () => {
|
||||
var ss = buildScopes();
|
||||
g.defineObservableProperty(ss.outer, 'p', 123);
|
||||
assert.strictEqual(ss.outer.p, 123);
|
||||
assert.strictEqual(ss.mid.p, undefined);
|
||||
assert.strictEqual(ss.root.p, undefined);
|
||||
assert(!('p' in ss.root));
|
||||
assert(!('p' in ss.mid));
|
||||
assert('p' in ss.outer);
|
||||
it('should update on the left', () => {
|
||||
a.value = 3;
|
||||
g.repairDamage(c => g.run(c));
|
||||
a.checkCounts(2, 1);
|
||||
b.checkCounts(2, 0);
|
||||
c.checkCounts(0, 2);
|
||||
});
|
||||
|
||||
it('should hide middle definitions from roots but show to leaves', () => {
|
||||
var ss = buildScopes();
|
||||
g.defineObservableProperty(ss.mid, 'p', 123);
|
||||
assert.strictEqual(ss.outer.p, 123);
|
||||
assert.strictEqual(ss.mid.p, 123);
|
||||
assert.strictEqual(ss.root.p, undefined);
|
||||
assert(!('p' in ss.root));
|
||||
assert('p' in ss.mid);
|
||||
assert('p' in ss.outer);
|
||||
it('should update on the right', () => {
|
||||
b.value = 3;
|
||||
g.repairDamage(c => g.run(c));
|
||||
a.checkCounts(3, 1);
|
||||
b.checkCounts(3, 1);
|
||||
c.checkCounts(0, 3);
|
||||
});
|
||||
it('should batch simultaneous updates on the left and right', () => {
|
||||
a.value = 4;
|
||||
b.value = 4;
|
||||
g.repairDamage(c => g.run(c));
|
||||
expect(c.spy()).toBe(8);
|
||||
c.checkCounts(0, 4);
|
||||
a.checkCounts(4, 2);
|
||||
b.checkCounts(4, 2);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { is, preserves } from '@preserves/core';
|
||||
|
||||
declare global {
|
||||
namespace jest {
|
||||
interface Matchers<R> {
|
||||
is(expected: any): R;
|
||||
toThrowFilter(f: (e: Error) => boolean): R;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect.extend({
|
||||
is(actual, expected) {
|
||||
return is(actual, expected)
|
||||
? { message: () => preserves`expected ${actual} not to be Preserves.is to ${expected}`,
|
||||
pass: true }
|
||||
: { message: () => preserves`expected ${actual} to be Preserves.is to ${expected}`,
|
||||
pass: false };
|
||||
},
|
||||
|
||||
toThrowFilter(thunk, f) {
|
||||
try {
|
||||
thunk();
|
||||
return { message: () => preserves`expected an exception`, pass: false };
|
||||
} catch (e) {
|
||||
if (f(e)) {
|
||||
return { message: () => preserves`expected an exception not matching the filter`,
|
||||
pass: true };
|
||||
} else {
|
||||
return { message: () => preserves`expected an exception matching the filter: ${(e as any)?.constructor?.name}`,
|
||||
pass: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue