This repository has been archived on 2021-10-31. You can view files and clone it, but cannot push or open issues or pull requests.
SA3/hw2/Claudio_Maggioni/test.js

731 lines
32 KiB
JavaScript

function tests(property,
propEqual,
jsc,
arbs,
xmlParser) {
// simple utility functions
function words(s) { return _.words(s, /[^\s]+/g); }
function compose(f, g) { return x => f(g(x)); }
function vectorSum(arr1,arr2) { return _.zipWith(arr1, arr2, _.add); }
window.vectorSum = vectorSum
// how to generate sentences (for mkPrettyPrinter & mkIndenter)
let sentence =
// words are 2-12 lower letters
arbs.sentence(arbs.word(arbs.lLetter,2,12),
// prefer white space between words
arbs.oneOfWeight([arbs.whitespace, 8],
// but rarely put '\n'
[jsc.constant('\n'), 1]),
// prefer no leading whitespace
arbs.oneOfWeight([jsc.constant(''), 3],
// but sometimes add some
[arbs.whitespace, 1]),
// slightly prefer adding trailing whitespace
arbs.oneOfWeight([arbs.whitespace, 2],
// but sometimes don't
[jsc.constant(''), 1]),
// use '.', '!', '?' or '...' at the end
arbs.punctuation,
// sentence has 5 to 30 words
5,
30);
test("Scalar product", function() {
property(
"doesn't change the source array",
"array number", "number",
function (arr,num) {
let copy = _.cloneDeep(arr);
let t = scalar_product(copy,num);
return propEqual(copy, arr, `scalar_product([${copy}],${num})`, `[${arr}]`);
});
property(
"preserves the length of the array",
"array number", "number",
function (arr,num) {
return propEqual(scalar_product(arr,num).length, arr.length, `scalar_product([${arr}],${num}).length`, `${arr}.length`);
});
property(
"returns zero array if the factor is zero",
"array number",
function (arr) {
let z = _.cloneDeep(arr);
_.fill(z,0);
return propEqual(scalar_product(arr,0),z, `scalar_product([${arr}],0)`, `[${z}]`);
});
property(
"preserves the array if the factor is one",
"array number",
function (arr) {
let c = _.cloneDeep(arr);
return propEqual(scalar_product(arr,1),c, `scalar_product([${arr}],1))`, `[${c}]`);
});
// have to restrict it to integers because of the rounding errors
// better to compare up to rounding error
property(
"allows factors to be combined",
"array integer", "integer", "integer",
function (arr, n1, n2) {
return propEqual(scalar_product(scalar_product(arr,n1),n2),
scalar_product(arr, n1*n2), `scalar_product(scalar_product([${arr}],${n1}),${n2})`, `scalar_product([${arr}], ${n1}*${n2})`);
});
property(
"returns undefined if first argument is not an array",
jsc.suchthat(arbs.anything, x => !_.isArray(x)), arbs.anything,
function(notArr, x) {
return propEqual(scalar_product(notArr, x), undefined, `scalar_product(${notArr}, ${x})`, `undefined`);
});
property(
"if factor is not passed, array is returned as is",
"array number",
function(arr) {
return propEqual(scalar_product(arr), arr, `scalar_product([${arr}])`, `[${arr}]`);
});
});
test("Inner product", function() {
property(
"doesn't change the source arrays",
arbs.equalLengthArrays(jsc.number, jsc.number),
function (arrs) {
let [a1,a2] = arrs;
let copy1 = _.cloneDeep(a1);
let copy2 = _.cloneDeep(a2);
var t = inner_product(a1,a2);
return propEqual(a1,copy1) && propEqual(a2,copy2);
});
property(
"is symmetric",
arbs.equalLengthArrays(jsc.number, jsc.number),
function (arrs) {
let [a1,a2] = arrs;
return propEqual(inner_product(a1,a2), inner_product(a2,a1), `inner_product([${a1}],[${a2}])`, `inner_product([${a2}],[${a1}])`);
});
property(
"returns non-negative number when multiplying vector to itself",
"array number",
function (arr) {
return inner_product(arr,arr) >= 0;
});
property(
"returns 0 if one of the vectors is a zero-filled [0,...,0] vector",
"array number",
function (arr) {
let z = new Array(arr.length);
_.fill(z,0);
return propEqual(inner_product(arr,z), 0, `inner_product([${arr}],[${z}])`, `0`);
});
property(
"if argument arrays sizes don't match, returns undefined",
jsc.array(arbs.anything),
function(arr1) {
return jsc.forall(
jsc.suchthat(jsc.array(arbs.anything),
x => x.length !== arr1.length),
function(arr2) {
return propEqual(inner_product(arr1,arr2), undefined, `inner_product([${arr1}],[${arr2}])` , `undefined`);
});
});
});
test("mapReduce", function() {
// mapReduce is pretty agnostic to the array contents
// so we could use "json" arbitrary instead of "number"
// to get better coverage
// Unfortunately, it is also quite slower
property(
"doesn't change the source array",
"array integer", "integer -> integer",
function (arr, f) {
let copy = _.cloneDeep(arr);
let t = mapReduce(copy, f);
return propEqual(copy, arr, `[${copy}]`,`[${arr}]`);
});
property(
"mapReduce([],f,g,seed) == seed",
"integer -> integer", arbs.fn2(jsc.integer), "integer",
function(f, combine, seed) {
return propEqual(mapReduce([],f,combine,seed), seed, `mapReduce([],${f},${combine},${seed})`, `${seed}`);
});
property(
"mapReduce(arr,identity,combine,seed) == reduce(arr,combine,seed)",
"array integer", arbs.fn2(jsc.integer), "integer",
function (arr, combine, seed) {
return propEqual(mapReduce(arr,_.identity,combine,seed),
_.reduce(arr,combine,seed), `mapReduce([${arr}],${_.identity},${combine},${seed})`, `_.reduce([${arr}],${combine},${seed})`);
});
property(
"mapReduce(arr,f,combine,seed) == " +
"mapReduce(arr,identity,(a,x) => combine(a,f(x)),f(seed))",
"array integer", "integer -> integer", arbs.fn2(jsc.integer),
"integer",
function (arr, f, combine, seed) {
return propEqual(
mapReduce(arr,f,combine,seed),
mapReduce(arr,_.identity,(a,x) => combine(a,f(x)),seed), `mapReduce([${arr}],${f},${combine},${seed})`, `mapReduce([${arr}],${_.identity},(a,x) => ${combine}(a,${f}(x)),${seed})`);
});
property(
"mapReduce(arr1.concat(arr2),f) == mapReduce(arr1,f) + mapReduce(arr2,f)",
"array integer", "array integer", "integer -> integer",
function (arr1, arr2, f) {
return propEqual(
mapReduce(arr1.concat(arr2),f),
mapReduce(arr1,f) + mapReduce(arr2,f), `mapReduce([${arr1}].concat([${arr2}]),${f})`, `mapReduce([${arr1}],${f}) + mapReduce([${arr2}],${f})`);
});
property(
"mapReduce(arr1.concat(arr2),f,+,0) == " +
"mapReduce(arr1,f,+,0) + mapReduce(arr2,f,+,0)",
"array integer", "array integer", "integer -> integer",
function (arr1, arr2, f) {
return propEqual(
mapReduce(arr1.concat(arr2),f, _.add, 0),
mapReduce(arr1,f,_.add,0) + mapReduce(arr2,f,_.add,0), `mapReduce([${arr1}].concat([${arr2}]),${f}, ${_.add}, 0)`, `mapReduce([${arr1}],${f},${_.add},0) + mapReduce([${arr2}],${f},${_.add},0)`);
});
property(
"mapReduce(arr,f,(a,x) => a.concat([x]),[]) == map(arr,f)",
"array integer", "integer -> integer",
function (arr, f) {
return propEqual(mapReduce(arr,f,(a,x) => a.concat([x]), []),
_.map(arr,f), `mapReduce([${arr}],${f},(a,x) => a.concat([x]), [])`, `${_.map(arr,f)}`);
});
property(
"mapReduce(arr,f,(a,x) => a.concat(x),[]) == flatMap(arr,f)",
"array integer", "integer -> array integer",
function (arr,f) {
return propEqual(mapReduce(arr,f,(a,x) => a.concat(x),[]),
_.flatMap(arr,f), `mapReduce([${arr}],${f},(a,x) => a.concat(x),[])`,
`[${_.flatMap(arr,f)}]`);
});
property(
"mapReduce([1,2,3],f,g,0) == g(g(g(0,f(1)),f(2)),f(3))",
"integer -> integer", arbs.fn2(jsc.integer),
function (f, g) {
return propEqual(mapReduce([1,2,3],f,g,0),
g(g(g(0,f(1)),f(2)),f(3)), `mapReduce([1,2,3],${f},${g},0)`, `${g(g(g(0,f(1)),f(2)),f(3))}`);
});
property(
"if the first argument is not an array, returns undefined",
jsc.suchthat(arbs.anything, x => !Array.isArray(x)),
"number -> number",
function(notArr, f) {
return propEqual(mapReduce(notArr,f), undefined);
});
property(
"if the second argument is not a function, returns undefined",
"array integer", jsc.suchthat(arbs.anything, x => typeof x !== 'function'),
function(arr, notF) {
return propEqual(mapReduce(arr,notF), undefined);
});
});
test("mergeSortedArrays", function() {
property(
"doesn't change the source arrays",
arbs.sortedArray(jsc.integer), arbs.sortedArray(jsc.integer),
function(a, b) {
let copy1 = _.cloneDeep(a);
let copy2 = _.cloneDeep(b);
let t = mergeSortedArrays(copy1,copy2);
return propEqual(copy1,a) && propEqual(copy2,b);
});
property(
"produces sorted results",
arbs.sortedArray(jsc.integer), arbs.sortedArray(jsc.integer),
function(a, b) {
let t = mergeSortedArrays(a,b);
return propEqual(t, _.sortBy(t), `[${t}]`,`mergeSortedArrays([${a}],[${b}])`);
});
property(
"has length equal to the sum of individual lengths",
arbs.sortedArray(jsc.integer), arbs.sortedArray(jsc.integer),
function(a, b) {
return propEqual(mergeSortedArrays(a,b).length, a.length + b.length, `mergeSortedArrays([${a}],[${b}]).length`, `[${a}].length + [${b}].length`);
});
});
test("range", function() {
property(
"results in undefined if step is zero",
"integer", "integer",
function(x,y) {
return propEqual(range(x,y,0), undefined, `range(${x},${y},0)`, undefined, `[${range(x,y,0)}]`);
});
property(
"results in empty array if x > y and step > 0 or x < y and step < 0",
"integer",
function(x) {
return jsc.forall(
jsc.suchthat(jsc.integer, y => y != x), arbs.posInteger,
function(y,stepMod) {
let s = x>y ? stepMod : -stepMod
return propEqual(range(x,y,s), [], `range(${x},${y},${s})`, undefined, `[${range(x,y,s)}]`);
});
});
property(
"produces array without repetitions",
"integer", "integer", jsc.suchthat(jsc.integer, x => x != 0),
function(x,y,step) {
let r = range(x,y,step);
return propEqual(r.length, _.uniq(r).length, `range(${x},${y},${step})`, undefined, `[${range(x,y,step)}]`);
});
property(
"produces array with all elements >= min(x,y)",
"integer", "integer", jsc.suchthat(jsc.integer, x => x != 0),
function(x,y,step) {
let r = range(x,y,step);
let l = _.min([x,y]);
return propEqual(_.every(r,a => a >= l), true, `range(${x},${y},${step})`, undefined, `[${range(x,y,step)}]`);
});
property(
"produces array with all elements <= max(x,y)",
"integer", "integer", jsc.suchthat(jsc.integer, x => x != 0),
function(x,y,step) {
let r = range(x,y,step);
let h = _.max([x,y]);
return propEqual(_.every(r,a => a <= h), true, `range(${x},${y},${step})`, undefined, `[${range(x,y,step)}]`);
});
property(
"returns undefined, if either argument is not a number",
"number", "number", jsc.suchthat(jsc.number, x => x != 0),
jsc.suchthat(arbs.anything, x => !_.isNumber(x) || _.isNaN(x)),
jsc.suchthat(arbs.anything, x => !_.isNumber(x) || _.isNaN(x)),
jsc.suchthat(arbs.anything, x => !_.isNumber(x) || _.isNaN(x)),
jsc.suchthat(arbs.anything, x => !_.isUndefined(x) &&
(!_.isNumber(x) || _.isNaN(x))),
function(x,y,step,nN1,nN2,nN3,nN4) {
return propEqual(range(nN1,nN2,step), undefined) &&
propEqual(range(nN1,y,step), undefined) &&
propEqual(range(x,nN2,step), undefined) &&
propEqual(range(nN1,nN2,nN3), undefined) &&
propEqual(range(x,y,nN4), undefined) &&
propEqual(range(nN1,y,nN3), undefined) &&
propEqual(range(x,nN2,nN3), undefined);
});
});
test("flatten", function() {
property(
"doesn't change the input array",
"array json",
function(a) {
let copy = _.cloneDeep(a);
let t = flatten(copy);
return propEqual(copy, a, `flatten([${a}])`);
});
property(
"simply returns the input, if it is not an array",
jsc.oneof(jsc.number, jsc.bool, jsc.falsy, jsc.string),
function(inp) {
return propEqual(flatten(inp), inp, `flatten(${inp})`);
});
property(
"simply returns the input, if it is already flattened",
jsc.array(jsc.oneof(jsc.number, jsc.bool, jsc.falsy, jsc.string)),
function(a) {
return propEqual(flatten(a), a, `flatten([${a}])`, `[${a}]`);
});
property(
"flatten([arr]) == flatten(arr)",
"array json",
function(a) {
return propEqual(flatten([a]), flatten(a), `flatten([[${a}]])`, `flatten([${a}])`);
});
});
test("mkPrettyPrinter", function() {
property(
"output has no too long lines",
jsc.integer(20,80), jsc.array(sentence),
function(ls, input) {
let inp = input.join('\n');
let pp = mkPrettyPrinter(ls);
let r = pp(inp);
return propEqual(_.every(r, l => l.length <= ls), true, `mkPrettyPrinter(${ls})("${inp.replace(/\n/, '\\n')}")`, undefined, `[${mkPrettyPrinter(ls)(inp)}]`);
});
property(
"output has all input words in the same order",
jsc.integer(20,80), jsc.array(sentence),
function(ls, input) {
let inp = input.join('\n');
let ws = words(inp);
let pp = mkPrettyPrinter(ls);
let r = pp(inp);
return propEqual(words(r.join('\n')), ws, `mkPrettyPrinter(${ls})("${inp.replace(/\n/, '\\n')}")`, undefined);
});
property(
"output has no leading or trailing white spaces",
jsc.integer(20,80), jsc.array(sentence),
function(ls, input) {
let inp = input.join('\n');
let pp = mkPrettyPrinter(ls);
let r = pp(inp);
return propEqual(_.every(r, l => (l == _.trim(l))), true, `mkPrettyPrinter(${ls})("${inp.replace(/\n/, '\\n')}")`, undefined, `[${mkPrettyPrinter(ls)(inp)}]`);
});
property(
"does not use global variables",
arbs.vect(jsc.integer(20,80),5),
arbs.permutation(5), jsc.array(sentence),
function(lss, perm, input) {
let inp = input.join('\n');
let pps = lss.map(mkPrettyPrinter);
for (let i in perm) {
let j = perm[i];
let r = pps[j](inp);
_.every(r, l => propEqual(l.length <= lss[j], true));
pps.splice(j,1);
lss.splice(j,1);
}
return true;
});
property(
"returns undefined if called with not a number",
jsc.suchthat(arbs.anything, x => !_.isUndefined(x) &&
(!_.isNumber(x) || _.isNaN(x))),
function(nN) {
return propEqual(mkPrettyPrinter(nN), undefined, `mkPrettyPrinter(${nN})`);
});
property(
"if pretty-printer is called with non-string, it returns undefined",
jsc.integer(50,80),
jsc.suchthat(arbs.anything, x => !_.isString(x)),
function(ls, nStr) {
return propEqual(mkPrettyPrinter(ls)(nStr), undefined, `mkPrettyPrinter(${ls})(${nStr})`);
});
});
test("mkIndenter", function() {
let indenterInput = jsc.oneof(jsc.nat(20), sentence, jsc.array(sentence));
// it's easier to generate absolute indentation levels
// and then turn them into relative ones
function fixIndentationLevels(arr,curLvl) {
for (let i in arr) {
if (typeof arr[i] == "number") {
let t = arr[i];
arr[i] -= curLvl;
curLvl = t;
}
}
return curLvl;
}
function properlyIndented(il) {
return l => {
let b = l.substr(0,il);
let e = l.slice(il);
return propEqual(b, ' '.repeat(il)) &&
propEqual(e, _.trim(e)) &&
! _.includes(e, '\n') &&
! _.includes(e, '\t') &&
! _.includes(e, ' ');
};
}
function properlyIndentedLI(il) {
return (l,ix) => {
let b = l.substr(0,il+2);
let e = l.slice(il+2);
let expectedIndent = i ? ' '.repeat(il+2) : ' '.repeat(il) + '* ';
return propEqual(b, expectedIndent) &&
propEqual(e, _.trim(e)) &&
! _.includes(e, '\n') &&
! _.includes(e, '\t') &&
! _.includes(e, ' ');
};
}
function noEarlyWraps(ls, ss, rl, el, ol) {
let lss = _.initial(ss).map(x => x.length);
let nexts = _.tail(ss).map(x => words(x)[0].length);
return propEqual(_.every(_.zipWith(lss,nexts,_.add), s => s >= ls),true,rl,el,ol);
}
property(
"returned function doesn't change its argument",
jsc.integer(50,80), jsc.nat(12), jsc.array(sentence),
function(ls, il, arr) {
let copy = _.cloneDeep(arr);
let i = mkIndenter(ls, il);
let t = i(copy);
return propEqual(copy,arr, `mkIndenter(${ls}, ${il})([${arr}])`);
});
property(
"freshly created indenter returns indentation level passed " +
"during creation",
jsc.integer(50,80), jsc.nat(20),
function(ls, il) {
let i = mkIndenter(ls, il);
return propEqual(i(true), il, `mkIndenter(${ls}, ${il})(true)`);
});
property(
"freshly created indenter returns an empty array",
jsc.integer(50,80), jsc.nat(20),
function(ls, il) {
let i = mkIndenter(ls, il);
return propEqual(i(), [], `mkIndenter(${ls}, ${il})()`);
});
property(
"content request doesn't change the indentation level",
jsc.integer(50,80), jsc.nat(20), jsc.array(indenterInput),
function(ls, il, arr) {
fixIndentationLevels(arr,il);
let i = mkIndenter(ls, il);
arr.forEach(i);
let il2 = i(true);
i();
return propEqual(i(true), il2, `var i = mkIndenter(${ls}, ${il});i(true);i(true)`);
});
property(
"output doesn't have too long lines",
jsc.integer(50,80), jsc.nat(20), jsc.array(indenterInput),
function(ls, il, arr) {
fixIndentationLevels(arr,il);
let i = mkIndenter(ls, il);
arr.forEach(i);
let r = i();
return propEqual(_.every(r, l => l.length <= ls), true, `mkIndenter(${ls}, ${il})()`);
});
property(
"integer input doesn't produce any output and changes the " +
"indentation level",
jsc.integer(50,80), jsc.nat(12), jsc.integer(-12,12),
function(ls, il, int) {
let i = mkIndenter(ls, il);
i(int);
return propEqual(i(true), il + int, `var i = mkIndenter(${ls}, ${il}); i(${int}); i(true); i()`) &&
propEqual(i(), [], `var i = mkIndenter(${ls}, ${il}); i(${int}); i(true); i()`);
});
property(
"string input produces properly indented results " +
"and doesn't change the indentation level",
jsc.integer(50,80), jsc.nat(20), sentence,
function(ls, il, s) {
let i = mkIndenter(ls, il);
i(s);
let r = i();
return propEqual(_.every(r, properlyIndented(il)), true, `var i = mkIndenter(${ls}, ${il}); i("${s}"); i(); i(true)`) &&
propEqual(i(true), il, `var i = mkIndenter(${ls}, ${il}); i("${s}"); i(); i(true)`);
});
property(
"string input doesn't wrap lines too early",
jsc.integer(50,80), jsc.nat(20), sentence,
function(ls, il, s) {
let i = mkIndenter(ls, il);
i(s);
let r = i();
return noEarlyWraps(ls, r, `var i = mkIndenter(${ls}, ${il}); i("${s.replace(/\n/, '\\n')}"); i()`, undefined, `[${i()}]`);
});
property(
"empty array input does nothing",
jsc.integer(50,80), jsc.nat(20),
function(ls, il) {
let i = mkIndenter(ls, il);
i([]);
return propEqual(i(), [], `var i = mkIndenter(${ls}, ${il}); i([]); i(); i(true)`) && propEqual(i(true), il, `var i = mkIndenter(${ls}, ${il}); i([]); i(); i(true)`);
});
property(
"it doesn't use global variables",
jsc.integer(2,5),
function(n) {
let inputsArb = arbs.vect(jsc.tuple([jsc.integer(50,80),
jsc.nat(20),
jsc.array(sentence)]),
n);
return jsc.forall(
inputsArb,
function(inputs) {
let seqOuts = _.map(inputs, inp => {
let i = mkIndenter(inp[0],inp[1]);
inp[2].forEach(i);
return i();
});
let sz = _.sum(_.map(inputs, x => x[2].length)) + 2*n;
return jsc.forall(
arbs.vect(jsc.nat(n-1), sz),
function(schedule) {
let cnts = _.fill(Array(n), -1);
let outs = _.fill(Array(n), undefined);
let is = [];
schedule.forEach(i_ => {
let i = i_;
for (; cnts[i] > inputs[i][2].length; i++, i %= n);
let inp = inputs[i];
if (cnts[i] == -1)
is[i] = mkIndenter(inp[0], inp[1]);
else if (cnts[i] == inp[2].length)
outs[i] = is[i]();
else
is[i](inp[2][cnts[i]]);
cnts[i]++;
});
let os = _.zip(seqOuts, outs);
return _.every(os, o => propEqual(o[0], o[1]));
});
});
});
property(
"returns undefined if either argument is not a number",
jsc.suchthat(arbs.anything, x => !_.isNumber(x) || _.isNaN(x)),
jsc.suchthat(arbs.anything, x => !_.isUndefined(x) &&
(!_.isNumber(x) || _.isNaN(x))),
jsc.integer(50,80), jsc.nat(20),
function(nN1, nN2, ls, il) {
return propEqual(mkIndenter(nN1), undefined) &&
propEqual(mkIndenter(nN1,il), undefined) &&
propEqual(mkIndenter(ls,nN2), undefined) &&
propEqual(mkIndenter(nN1,nN2), undefined);
});
property(
"sample input works",
"unit",
function(u) {
let i = mkIndenter(20);
i("Short line");
i("This line is longer than 20 characters");
i(8);
i("Big indent");
i("Not so long line");
i(-6);
i("Small indent");
i(["Item 1", "Item 2", "Really really long Item 3"]);
return propEqual(i(),
[ 'Short line',
'This line is longer',
'than 20 characters',
' Big indent',
' Not so long',
' line',
' Small indent',
' * Item 1',
' * Item 2',
' * Really really',
' long Item 3' ]);
});
});
test("letter_frequency", function() {
property(
"Concatenation of strings produces merge of maps",
jsc.array(arbs.letter), jsc.array(arbs.letter),
function(arr1, arr2) {
let s1 = arr1.join('');
let s2 = arr2.join('');
let expected = _.assignWith(letter_frequency(s1),
letter_frequency(s2),
_.add);
return propEqual(letter_frequency(s1 + s2), expected);
});
property(
"Map values are always positive",
jsc.array(arbs.letter),
function(arr) {
let s = arr.join('');
let r = letter_frequency(s);
return _.every(r, x => x > 0);
});
property(
"Hello example works",
"unit",
function(u) {
let a = []; a.H = 1; a.E = 1; a.L = 2; a.O = 1;
let expected = {'H' : 1,'E' : 1,'L' : 2,'O' : 1}
try {
propEqual(letter_frequency('Hello'), expected)
} catch(e) {
return propEqual(letter_frequency('Hello'),a)
}
try {
propEqual(letter_frequency('Hello'),a)
} catch(e) {
return propEqual(letter_frequency('Hello'), expected)
}
return true
});
});
test("display_letter_frequency", function() {
// how to generate frequency table
let fTable = jsc.array(jsc.tuple([arbs.uLetter, arbs.posInteger])).smap(
_.fromPairs,
_.toPairs);
property(
"first arguments is not modified",
fTable,
function(ft) {
let copy = _.cloneDeep(ft);
let dom = {};
display_letter_frequency(copy, dom);
return propEqual(copy, ft);
});
property(
"Output is a table with right number of rows and 2 columns",
fTable,
function(ft) {
let dom = {};
display_letter_frequency(ft, dom);
let out = xmlParser.parseFromString(dom.innerHTML, 'text/xml');
let d = out.documentElement;
let cs = d.children;
return propEqual(d.tagName, 'table') &&
propEqual(cs.length, _.size(ft)) &&
_.every(_.range(cs.length).map(i => {
let c = cs.item(i);
let subcs = c.children;
return propEqual(c.tagName, 'tr') &&
propEqual(subcs.length, 2) &&
_.every(_.range(subcs.length).map(j => {
let subc = subcs.item(j);
return propEqual(subc.tagName, 'td') &&
propEqual(subc.children.length, 0);
}));
}));
});
property(
"Every row has key/value pair from the frequency table",
fTable,
function(ft) {
function isInMap(k,v) {
let i = parseInt(v);
return propEqual(i + '', v) &&
propEqual(ft[k], i);
}
let dom = {};
display_letter_frequency(ft, dom);
let out = xmlParser.parseFromString(dom.innerHTML, 'text/xml');
let d = out.documentElement;
let cs = d.children;
return propEqual(d.tagName, 'table') &&
_.every(_.range(cs.length).map(i => {
let c = cs.item(i);
let subcs = c.children;
return propEqual(c.tagName, 'tr') &&
propEqual(subcs.length,2) &&
propEqual(subcs.item(0).tagName, 'td') &&
propEqual(subcs.item(1).tagName, 'td') &&
isInMap(subcs.item(0).innerHTML, subcs.item(1).innerHTML);
}));
});
property(
"Does nothing if either argument is not an object",
fTable,
jsc.dict(arbs.anything),
jsc.suchthat(arbs.anything, x => !_.isObject(x)),
jsc.suchthat(arbs.anything, x => !_.isObject(x)),
jsc.suchthat(arbs.anything, x => !_.isObject(x)),
function(ft, nO1, nO2, nO3, o) {
let copyO = _.cloneDeep(o);
let copyNO2 = _.cloneDeep(nO2);
let copyNO3 = _.cloneDeep(nO3);
display_letter_frequency(nO1, copyNO2);
display_letter_frequency(nO1, copyO);
display_letter_frequency(ft, copyNO3);
return propEqual(copyNO2, nO2) &&
propEqual(copyO, o) &&
propEqual(copyNO3, nO3);
});
});
}