Work on HW2

This commit is contained in:
Claudio Maggioni 2019-09-26 20:49:05 +02:00
parent 9c1c7ad12c
commit 0af248a3d6
10 changed files with 29212 additions and 0 deletions

View File

@ -0,0 +1,4 @@
Implemented bonuses:
- Gallery
- one column mobile form layout

View File

@ -0,0 +1,226 @@
function jsc_utils(jsc) {
function copyArgs(f) {
function f_(...args) {
let copies = args.map(a => {
if (typeof a === "function") return a;
else return _.cloneDeep(a);
});
return f.apply(this, copies);
};
return f_;
}
// patch the function to use copied arbitrary values
// jsverify goes crazy if arbitrary arguments are mutated
function patchArgs(args) {
let args_ = _.initial(args);
let f = _.last(args);
args_.push(copyArgs(f));
return args_;
}
function mocha_property(name, ...args) {
let args_ = patchArgs(args);
args_.unshift(name);
return jsc.property.apply(this, args_);
}
function pretty_function(data) {
function display(pair) {
let [as, r] = pair;
if (!_.isArray(as)) as = [as];
return '\t\tcase ' + JSON.stringify(as) + ': return ' +
JSON.stringify(r) + ';';
}
return 'function(...args) {\n\tswitch(args) {\n' + data.map(display).join('\n') + '}\n}';
}
function pretty(v) {
if (typeof v === 'function') {
if (v.original_fun !== undefined) v = v.original_fun;
return pretty_function(v.internalMap.data);
return JSON.stringify(v.internalMap.data, undefined, 2);
}
return JSON.stringify(v, undefined, 2);
}
function qunit_property_result(name, ...args) {
let args_ = patchArgs(args);
let res = jsc.check(jsc.forall.apply(this, args_));
if (res == true)
return QUnit.assert.pushResult({result: true, message: name});
let ce = '(' + _.take(res.counterexample, args.length - 1).map(pretty).join(',\n ') + ')\n';
return QUnit.assert.pushResult(
{result: false,
actual: res.exc.actual,
expected: res.exc.expected,
message: name + ' failed after ' +
res.tests + ' tests, counterexample:\n' + ce,
expectedLog: res.exc.expectedLog,
resultLog: res.exc.resultLog,
outputLog: res.exc.outputLog});
}
function qunit_property(name, ...args) {
let args_ = patchArgs(args);
return ok(jsc.assert(jsc.forall.apply(this,args_)) || true, name);
}
function propEqual(x,y,rl,el,ol) {
if (_.isEqual(x,y)) return true;
throw { actual: x, expected: y, expectedLog: el, resultLog: rl, outputLog: ol};
}
return {
mocha_property: mocha_property,
qunit_property: qunit_property,
qunit_property_result: qunit_property_result,
propEqual: propEqual
};
}
function arbitrary_utils(jsc) {
/* custom arbitraries */
function set(arb) {
return arb.smap(_.uniq, _.identity);
}
function oneOfWeight(...args) {
return jsc.oneof(_.flatMap(args, x => _.fill(Array(x[1]), x[0])));
}
function equalLengthArrays(...args) {
// generate an array of tuples
let arbA = jsc.array(jsc.tuple(args));
return arbA.smap(
x => {
if (x.length == 0) return _.map(args, x => []);
return _.unzip(x);
},
y => _.zip.apply(null, y)
);
}
function sortedArray(t) {
return jsc.array(t).smap(_.sortBy,_.identity);
}
function vect(t, n) {
return jsc.tuple(_.fill(Array(n), t));
}
function roseTree(t, max_level) {
if (max_level <= 0) return jsc.array(t);
return jsc.array(jsc.oneof(t, roseTree(t, max_level - 1)));
}
function charRange(min,max) {
return jsc.nonshrink(jsc.integer(min.charCodeAt(0), max.charCodeAt(0)).smap(
String.fromCharCode,
c => c.charCodeAt(0)));
}
let lLetter = charRange('a', 'z');
let uLetter = charRange('A', 'Z');
let letter = jsc.oneof(lLetter, uLetter);
let whitespace = oneOfWeight([jsc.constant(' '), 5],
[jsc.constant(' '), 4],
[jsc.constant(' '), 1],
[jsc.constant('\t'), 1]);
let punctuation = oneOfWeight([jsc.constant('.'), 3],
[jsc.constant('...'),1],
[jsc.constant('!'), 1],
[jsc.constant('?'), 1]);
function permutation(n) {
return jsc.tuple(_.times(n-1, i => jsc.nat(n-i-1)));
}
function some(arb,min,max) {
return jsc.bless({
generator: jsc.integer(min,max).generator
.flatmap(n => vect(arb,n).generator)
});
}
function word(l,min,max) {
return some(l,min,max).smap(a => a.join(''), s => s.split(''));
}
function sentence(w,wSp,initWSp,trailWSp,p,min,max) {
return jsc.bless({
generator: jsc.integer(min,max).generator.flatmap(
n => jsc.generator.tuple([vect(w,n).generator,
vect(wSp, n-1).generator,
initWSp.generator,
trailWSp.generator,
p.generator]).map(a => {
let [ws,wSps,iWs,tWs,p] = a;
wSps.push(p + tWs);
return iWs +
_.zipWith(ws, wSps, _.add).join('');
}))
});
}
let posInteger = jsc.nat.smap(x => x+1, x => x-1);
// jsverify doesn't have arbitrary multi-argument functions
// but it has curried functions and functions accepting tuples
// to get usual multi-argument functions we can either uncurry
// the generated function (in case we choose to generated
// curried function) or grab all functions arguments as an
// array and pass it to the generated function accepting tuples.
// Here we choose to use tuple-based version to get better counterexamples.
// There is a small catch: we have to ensure the right length of the tuple,
// otherwise f(1,2,ununsed_arg) won't be the same as f(1,2).
function fixManyArgFun(n,f) {
let fixed_f = function(...args) {
let as = _.take(args,n);
let d = n - as.length;
if (d) as.concat(_.fill(Array(d), undefined));
return f(as);
};
fixed_f.original_fun = f;
return fixed_f;
}
// arbitrary function taking n arguments
function fnN(n, returnArb) {
let funArb = jsc.fn(returnArb);
let oldShow = funArb.show;
return funArb.smap(f => fixManyArgFun(n,f),
f => f.original_fun,
f => oldShow(f.original_fun));
}
function fn2(returnArb) { return fnN(2, returnArb); }
function fn3(returnArb) { return fnN(3, returnArb); }
let anything = jsc.oneof(jsc.json, jsc.fn(jsc.json), jsc.falsy);
return {
set: set,
oneOfWeight: oneOfWeight,
vect: vect,
equalLengthArrays: equalLengthArrays,
sortedArray: sortedArray,
roseTree: roseTree,
charRange: charRange,
permutation: permutation,
uLetter: uLetter,
lLetter: lLetter,
letter: letter,
whitespace: whitespace,
punctuation: punctuation,
some: some,
word: word,
sentence: sentence,
posInteger: posInteger,
fnN : fnN,
fn2 : fn2,
fn3 : fn3,
anything
};
}

View File

@ -0,0 +1,5 @@
let test = QUnit.test;
let equal = QUnit.assert.equal.bind(QUnit.assert);
let notEqual = QUnit.assert.notEqual.bind(QUnit.assert);
let deepEqual = QUnit.assert.deepEqual.bind(QUnit.assert);
let notDeepEqual = QUnit.assert.notDeepEqual.bind(QUnit.assert);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,235 @@
/**
* QUnit v1.10.0 - A JavaScript Unit Testing Framework
*
* http://qunitjs.com
*
* Copyright 2012 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
line-height: 1em;
font-weight: normal;
border-radius: 5px 5px 0 0;
-moz-border-radius: 5px 5px 0 0;
-webkit-border-top-right-radius: 5px;
-webkit-border-top-left-radius: 5px;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-testrunner-toolbar label {
display: inline-block;
padding: 0 .5em 0 .1em;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
overflow: hidden;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
#qunit-modulefilter-container {
float: right;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests ol {
margin-top: 0.5em;
padding: 0.5em;
background-color: #fff;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
padding: 5px;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #3c510c;
background-color: #fff;
border-left: 10px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 10px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 5px 5px;
-webkit-border-bottom-right-radius: 5px;
-webkit-border-bottom-left-radius: 5px;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
#qunit-testresult .module-name {
font-weight: bold;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
width: 1000px;
height: 1000px;
}

View File

@ -0,0 +1,239 @@
/**
* JavaScript Exercise 1
* vim: set ts=2 sw=2 et tw=80:
*/
/**
* @param {number[]} a - The array of numbers.
* @param {number} c - The scalar multiplier.
* @return {number[]} An array computed by multiplying each element of the
* input array `a` with the input scalar value `c`.
*/
function scalar_product(a, c) {
if (!Array.isArray(a)) return undefined;
if (!c && c != 0) return a;
return a.map(e => e * c);
}
/**
* @param {number[]} a - The first array of numbers.
* @param {number[]} b - The second array of numbers.
* @return {number} A value computed by summing the products of each pair
* of elements of its input arrays `a`, `b` in the same position.
*/
function inner_product(a, b) {
if (a.length != b.length) return undefined;
return a.map((e, i) => e * b[i]).reduce((a, e) => a + e, 0);
}
/**
* @param {*[]} a - The array.
* @param {function} mapfn - The function for the map step.
* @param {function} [reducefn= function(x,y) { return x+y; }] - The
* function for the reduce step.
* @param {string} [seed=""] - The accumulator for the reduce step.
* @return {*} The reduced value after the map and reduce steps.
*/
function mapReduce(a, mapfn, reducefn, seed = "") {
if (!Array.isArray(a)) return undefined;
if (typeof mapfn != 'function') return undefined;
reducefn = reducefn || ((x, y) => x + y);
for (let i = 0; i < a.length; i++) {
seed = reducefn(seed, mapfn(a[i]));
}
return seed;
}
/**
* @param {number[]} a - The first sorted array of numbers.
* @param {number[]} b - The second sorted array of numbers.
* @return {number[]} A sorted array with all the elements from
* both `a` and `b`.
*/
function mergeSortedArrays(a, b) {
const m = new Array(a.length + b.length);
let ac = a.length - 1, bc = b.length - 1, i = ac + bc + 1;
while (i >= 0) {
if (ac < 0 || b[bc] > a[ac]) m[i--] = b[bc--];
else m[i--] = a[ac--];
}
return m;
}
/**
* @param {integer} x - The first integer.
* @param {integer} y - The second integer.
* @param {integer} [step=1] - The value to add at each step.
* @return {integer[]} An array containing numbers x, x+step, last, where:
* - last equals x + n*step for some n,
* - last <= y < last + step if step > 0 and
* - last + step < y <= last if step < 0.
*/
function range(x, y, step = 1) {
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(step)
|| step == 0) {
return undefined;
}
console.log(x, y, step);
if ((x > y && step > 0) || (x < y && step < 0)) {
return [];
}
const a = [];
while (x < y) {
a.push(x);
x += step;
}
return a;
}
/**
* @param {*[]} a - The array to flatten.
* @return {*[]} A flattened array.
*/
function flatten(arr) {
if (!Array.isArray(arr)) {
return arr;
}
const a = [];
arr.forEach(e => {
e = flatten(e); Array.isArray(e) ? a.push(...e) : a.push(e); });
return a;
}
/**
* @param {integer} [line_size=72] - The line size.
* @return {function} A function that takes a string as an argument
* and returns an array of strings where:
* a. each string length is no more than `line_size` and
* b. doesn't contain line breaks, tabs, double spaces and initial/trailing
* white spaces.
*/
function mkPrettyPrinter(line_size = 72) {
if (!Number.isFinite(line_size)) {
return undefined;
}
return (s) => {
if (typeof s !== 'string') {
return undefined;
}
s = s.replace('\n', ' ').trim().replace(/\s+/g, ' ');
const a = [];
let i = -1;
const words = s.split(' ');
for (const word of words) {
if (i == -1 || a[i].length + 1 + word.length > line_size) {
a[++i] = word;
} else {
a[i] += ' ' + word;
}
}
return a;
}
}
/**
* @param {integer} line_size - The line size.
* @param {integer} [level=0] level - The indentation level.
* @return {function} A function twith the following behavior:
* - If called with an integer `n`, change the indentation level by
* adding `n`to the current indentation level.
* - If called with `true`, return the current indentation level.
* - If called with a string:
* - break it into lines with length (after adding the indentation)
* no more than `line_size`,
* - add spaces in front of each line according to the current
* indentation level and
* - store the resulting lines internally.
* - [optional] If called with an array of strings, create an
* bullet list (using `*`) taking current indentation level into
* account. Also, each element should be properly broken into lines and indented.
* - If called with no arguments, produce an array with the lines stored so far.
* Internal storage must be emptied. Indentation level must not be changed.
*/
function mkIndenter(line_size, level=0) {
if (!Number.isFinite(line_size) || !Number.isFinite(level)) {
return undefined;
}
let state = [];
return (param) => {
if (Number.isInteger(param)) {
level += param;
} else if (param === true) {
return level;
} else if (typeof param === 'string') {
console.log(param);
let words = mkPrettyPrinter(line_size - level)(param);
words = words.map(e => ' ' * level + e);
state.push(...words);
console.log(line_size, level, param, state);
} else if (Array.isArray(param)) {
for (const e of param) {
if (typeof e !== 'string') {
return;
}
const words = mkPrettyPrinter(line_size - level - 2)(param);
state.push('* ' + words[0]);
for (let i = 1; i < words.length; i++) {
state.push(' ' + words[i]);
}
}
} else if (param === void(0)) {
const a = state;
state = [];
return a;
}
}
}
/**
* JavaScript Exercise 2
*/
/**
* Calculates the number of occurrences of letters in a given string
* @param {string} a - The input string.
* @return {number[]} An array indexed by the letter characters found in the string.
*/
function letter_frequency(s) {
}
/**
* Displays the output of the letter frequency analysis in an HTML table
* generated within the `dom` element passed as parameter
* @param {number[]} a - The array indexed by the letter characters as returned
* from `letter_frequency()`.
* @param {Object} dom - The DOM element that will contain the resulting table.
* @return {undefined}
*/
function display_letter_frequency(a, dom) {
}
/**
* Links the provided input text field in the test page with the table.
* @param {Object} inputEl - The DOM object of the input element in test.html.
* @return {undefined}
*/
function online_frequency_analysis(inputEl) {
}
/**
* JavaScript Exercise 3
*/
function clock(){
}

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Exercise 2 - Javascript</title>
<link rel="stylesheet" href="resources/qunit.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="script.js" type="text/javascript"></script>
<script src="resources/qunit-2.4.0.js"></script>
<script src="resources/lodash.js"></script>
<script src="resources/jsverify.standalone.js"></script>
<script src="jsverify-util.js"></script>
<script src="qunit-compat.js"></script>
<script src="test.js"></script>
<script>
let jsc = window.jsc;
let xmlParser = new window.DOMParser();
let utils = jsc_utils(jsc);
let arbs = arbitrary_utils(jsc);
tests(utils.qunit_property_result,
utils.propEqual,
jsc,
arbs,
xmlParser);
</script>
<div>
Frequency Analysis Input: <input type="text" onkeyup="online_frequency_analysis(this);"/>
<div id="frequency_table"></div>
<div id="clock"></div>
</div>
<script>
document.getElementById("clock").addEventListener('click', clock() );
</script>
</body>
</html>

View File

@ -0,0 +1,730 @@
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);
});
});
}