cocholate
Version:
Small and fast DOM manipulation library.
477 lines (386 loc) • 26.9 kB
HTML
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf8">
<title>Cocholate!</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/gh/douglascrockford/JSON-js@aef828bfcd7d5efaa41270f831f8d27d5eef3845/json2.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/fpereiro/dale@3199cebc19ec639abf242fd8788481b65c7dc3a3/dale.js"></script>
<script src="https://cdn.jsdelivr.net/gh/fpereiro/teishi@31a9cf552dbaee79fb1c2b7d12c6fad20f987983/teishi.js"></script>
<script src="https://cdn.jsdelivr.net/gh/fpereiro/lith@206ca67469ff0a8d6dcbc28593b3978e908c6cca/lith.js"></script>
<script src="cocholate.js"></script>
<script>
window.onerror = function () {
alert (dale.go (arguments, function (v) {return v + ''}).join (' '));
}
window.c.ready (function () {
var dale = window.dale;
var teishi = window.teishi;
var lith = window.lith;
var c = window.c;
// We override dale.clog to avoid seeing a ton of alerts on old browsers.
try {
dale.clog = console.log.bind (console);
}
catch (error) {
dale.clog = function () {
var output = dale.go (arguments, function (v) {return v === undefined ? 'undefined' : v}).join (' ');
if (window.console) window.console.log (output);
}
}
var type = teishi.type, clog = teishi.clog, inc = teishi.inc;
var done;
var Test = function (fun) {
var error = fun ();
if (error) {
alert (error);
throw new Error (error);
}
setInterval (function () {
if (! done) return;
done = false;
var perf = {dev: 0, prod: 0};
dale.go (dale.times (5), function () {
dale.go (['dev', 'prod'], function (mode) {
c.prod = mode === 'prod';
var time = teishi.time ();
fun (true);
perf [mode] += teishi.time () - time;
});
});
c.fill ('body', lith.g (['All tests were successful!', ['br'], 'Performance: ', perf.dev, 'ms (dev), ', perf.prod, 'ms (prod)']));
}, 50);
};
Test (function (perf) {
var counter = {};
window.increment = function (ev) {
if (! counter [ev]) counter [ev] = 0;
counter [ev]++;
}
c.fill ('body', lith.g ([
['input', {id: 'button', type: 'button', onclick: 'increment (event.type)'}],
['input', {id: 'text', type: 'text', onchange: 'increment (event.type)'}],
]));
if (c.fire ('#button', /notastring/) !== false) return 'c.fire didn\'t validate its input #1.';
if (c.fire ('#button', 1337) !== false) return 'c.fire didn\'t validate its input #2.';
if (c.fire ('#button') !== false) return 'c.fire didn\'t validate its input #3.';
c.fire ('#button', 'click');
c.fire ('#text', 'change');
if (! teishi.eq (counter, {click: 1, change: 1})) return 'c.fire didn\'t work.';
if (c ('selector', /invalidfun/) !== false) return 'Invalid function was considered valid #1.';
if (c (document.body, /invalidfun/) !== false) return 'Invalid function was considered valid #2.';
if (c ('body') !== document.body) return 'Find could not locate body #1.';
if (c (document.body) !== document.body) return 'When passing DOM element, c is not returning it.';
if (! document.querySelectorAll) {
if (c ('#a-b') !== undefined) return 'Valid selector with special characters wasn\'t accepted #1.';
if (c ('#в') !== undefined) return 'Valid selector with special characters wasn\'t accepted #2.';
if (c ('a>b') !== false) return 'Invalid selector with special characters was accepted #1.';
if (c ('a,b') !== false) return 'Invalid selector with special characters was accepted #2.';
if (c ('div[data=b]') !== false) return 'Invalid selector with special characters was accepted #3.';
}
c.fill ('body', lith.g ([
['div', {"class": 'hola'}, 'Content'],
['div', {id: 'hola'}, 'Content']
]));
if (c ('.hola') [0].innerHTML !== 'Content') return 'Class selector didn\'t work.';
if (c ('#hola').innerHTML !== 'Content') return 'Id selector didn\'t work.';
if (c ('div#hola').innerHTML !== 'Content') return 'Id selector didn\'t work #1.';
if (c (c ('div#hola')).innerHTML !== 'Content') return 'Id selector didn\'t work #2.';
if (document.querySelectorAll) {
if (type (c ('#hola[name="name"]')) !== 'array') return 'Non id selector starting with # didn\'t work #1.';
if (type (c ('#hola>div')) !== 'array') return 'Non id selector starting with # didn\'t work #2.';
}
else {
if (c ('#hola[name="name"]') !== false) return 'Non id selector starting with # wasn\'t rejected #1.';
if (c ('#hola>div') !== false) return 'Non id selector starting with # wasn\'t rejected #2.';
}
c.fill ('body', lith.g ([
['div', {id: 'hola'}, [
['p', {"class": 'a'}, 'Yes.'],
['p', {"class": 'b'}, 'No.'],
]]
]));
if (c ('div').length !== 1) return 'c.empty didn\'t work.';
if (c ('p').length !== 2) return 'c.fill didn\'t work.';
if (document.querySelectorAll) {
if (c ('#hola p.a').length !== 1) return 'Non-id selector with id first element didn\'t work.';
}
else {
if (c ({from: c ('#hola'), selector: 'p.a'}).length !== 1) return 'Non-id selector with id first element didn\'t work.';
}
if (c.fill ('body', /notastring/) !== false) return 'c.fill accepted invalid HTML #1.';
if (c.fill (document.body, /notastring/) !== false) return 'c.fill accepted invalid HTML #2.';
if (c (['invalid', 'div']) !== false) return 'invalid array selector was accepted.';
if (c ([':or', 'div', 'p']).length !== 3) return 'or selector didn\'t work.';
if (c ([':or', 'div', [':or', 'div', 'p']]).length !== 3) return 'nested or selector didn\'t work.';
if (document.querySelectorAll) {
if (c ([':and', 'div *', 'p']).length !== 2) return 'and selector didn\'t work.';
}
if (c ([':and', 'div', 'p']).length !== 0) return 'and selector didn\'t work.';
if (c ([':and', [':or', 'div', 'p']]).length !== 3) return 'and selector didn\'t work.';
var all = c ([':not', 'h5']).length;
if (c ([':not', 'div#hola']).length !== all - 1) return 'not selector didn\'t work #1.';
if (c ([':not', 'body']).length !== all - 1) return 'not selector didn\'t work #2.';
if (c ([':not', 'div#hola', 'body']).length !== all - 2) return 'not selector didn\'t work #3.';
if (document.querySelectorAll) {
if (c ([':not', 'div#hola *']).length !== all - 2) return 'not selector didn\'t work #4.';
}
if (c ([':not', 'div', 'p']).length !== all - 3) return 'not selector didn\'t work #5.';
if (c ([':not', [':or', 'div', 'p']]).length !== all - 3) return 'not selector didn\'t work #6.';
if (c ([':and', [':not', 'div#hola'], [':not', 'p']]).length !== all - 3) return 'not selector didn\'t work #7.';
if (document.querySelectorAll) {
if (c ([':and', 'body *', [':or', 'div', 'p']]).length !== 3) return 'nested selector didn\'t work #8.';
}
else {
if (c ([':and', {from: c ('body'), selector: '*'}, [':or', 'div', 'p']]).length !== 3) return 'nested selector didn\'t work #8.';
}
c.fill ('body', lith.g ('a'));
c.empty ('body');
if (document.body.innerHTML !== '') return 'c.empty didn\'t work.';
c.fill ('body', lith.g ([
['div', {"class": 'inner'}],
['div', {id: 'container'}, [
['div', {"class": 'inner foobar'}],
['div', {"class": 'innerTree'}],
['div', {"class": 'foobar inner'}],
]]
]));
if (c ({selector: 'div.inner'}) !== false) return 'undefined from selector accepted.';
if (c ({selector: 'div.inner', from: [c ('body')]}) !== false) return 'array from selector accepted.';
if (c ({selector: 'div.inner', from: c ('#container')}).length !== 2) return 'from selector didn\'t work.';
c.fill ('body', lith.g (['div', {id: 'container', style: 'border: solid 1px lime'}, [['div', {id: 'inner', style: 'border: solid 1px blue'}, 'inner'], 'container']]));
c.place ('#container', 'beforeBegin', lith.g (['div', {id: 'beforecontainer'}, 'beforecontainer']));
if (c ('div') [0].getAttribute ('id') !== 'beforecontainer') return 'c.place beforeBegin didn\'t work';
c.place ('#container', 'afterEnd', lith.g (['div', {id: 'aftercontainer'}, 'aftercontainer']));
if (c ('div') [c ('div').length - 1].getAttribute ('id') !== 'aftercontainer') return 'c.place afterEnd didn\'t work';
c.place ('#container', 'afterBegin', lith.g (['div', {id: 'beforeinner'}, 'beforeinner']));
if (c ({from: c ('#container'), selector: 'div'}) [0].getAttribute ('id') !== 'beforeinner') return 'c.place afterBegin didn\'t work';
c.place ('#container', 'beforeEnd', lith.g ([['div', {id: 'afterinner'}, 'afterinner'], ['div', {id: 'afterinner2'}, 'afterinner2']]));
if (c ({from: c ('#container'), selector: 'div'}) [2].getAttribute ('id') !== 'afterinner') return 'c.place beforeEnd didn\'t work';
if (c ({from: c ('#container'), selector: 'div'}) [3].getAttribute ('id') !== 'afterinner2') return 'c.place beforeEnd didn\'t work';
var cdiv = document.querySelectorAll ? '#container div' : {from: c ('#container'), selector: 'div'};
if (c.set (cdiv, {'^': 'someclass'}) !== false) return 'c.set accepted invalid input';
c.set (cdiv, {"class": 'someclass'});
if (c (cdiv).length !== 3) 'c.set didn\'t work.';
if (c.get (cdiv, 'class') [0] ['class'] !== 'someclass') return 'c.get didn\'t work #1.';
var allAttrs = c.get (cdiv) [0];
// We delete the `contentEditable` property because IE6-7 will add this property that we haven't set ourselves.
delete allAttrs.contentEditable;
if (! teishi.eq (allAttrs, {id: 'beforeinner', 'class': 'someclass'})) return 'c.get didn\'t bring all attributes.';
var allAttrsNoClass = c.get ('div') [0];
delete allAttrsNoClass.contentEditable;
if (! teishi.eq (allAttrsNoClass, {id: 'beforecontainer'})) return 'c.get didn\'t bring all attributes on element with no class.';
c.set (cdiv, {width: '200px', opacity: 0.9}, true);
if (c.get (cdiv, ['height', 'width'], true) [0].height !== null) return 'multiple c.get didn\'t work #1.';
if (c.get (cdiv, ['height', 'width'], true) [0].width !== '200px') return 'multiple c.get didn\'t work #2.';
var allcssattrs = c.get (cdiv, undefined, true) [0];
// All major browsers except for Internet Explorer stringify the opacity, so we do that too.
if (! teishi.eq ({width: allcssattrs.width, opacity: allcssattrs.opacity + ''}, {width: '200px', opacity: '0.9'})) return 'c.get didn\'t bring all CSS attributes.';
c.fill ('body', lith.g (['div', {id: 'test'}]));
// Test that we can pass a DOM element as the target.
c.set (c ('#test'), {'class': 'parte'});
if (! teishi.eq (c.get ('#test', 'class'), {'class': 'parte'})) return 'Could not pass a DOM element as target.';
c.fill ('body', lith.g (dale.go (['red', 'blue', 'green'], function (v, k) {
return ['p', {id: 'a' + k, "class": v}];
})));
if (document.querySelectorAll) {
if (JSON.stringify (c.get ('p', ['id', 'class'])) !== '[{"id":"a0","class":"red"},{"id":"a1","class":"blue"},{"id":"a2","class":"green"}]') return 'c.get didn\'t work #3.';
if (JSON.stringify (c.get ('#a0', ['id', 'class'])) !== '{"id":"a0","class":"red"}') return 'c.get didn\'t work #4.';
if (JSON.stringify (c.get ('p#a0', ['id', 'class'])) !== '{"id":"a0","class":"red"}') return 'c.get didn\'t work #5.';
}
else {
// IE6-7 use className, FF3 uses class
if (JSON.stringify (c.get ('p', ['id', 'className'])) !== '[{"id":"a0","className":"red"},{"id":"a1","className":"blue"},{"id":"a2","className":"green"}]' && JSON.stringify (c.get ('p', ['id', 'class'])) !== '[{"id":"a0","class":"red"},{"id":"a1","class":"blue"},{"id":"a2","class":"green"}]') return 'c.get didn\'t work #3.';
if (JSON.stringify (c.get ('#a0', ['id', 'className'])) !== '{"id":"a0","className":"red"}' && JSON.stringify (c.get ('#a0', ['id', 'class'])) !== '{"id":"a0","class":"red"}') return 'c.get didn\'t work #4.';
if (JSON.stringify (c.get ('p#a0', ['id', 'className'])) !== '{"id":"a0","className":"red"}' && JSON.stringify (c.get ('p#a0', ['id', 'class'])) !== '{"id":"a0","class":"red"}') return 'c.get didn\'t work #5.';
}
c.fill ('body', lith.g (dale.go (['red', 'blue', 'green'], function (v) {
return ['p', {style: 'color: ' + v}];
})));
// Interestingly enough, Opera 11.1 and below convert CSS colors to hexadecimal, hence the need for the second comparison.
if (JSON.stringify (c.get ('p', 'color', true)) !== '[{"color":"red"},{"color":"blue"},{"color":"green"}]' && JSON.stringify (c.get ('p', 'color', true)) !== '[{"color":"#ff0000"},{"color":"#0000ff"},{"color":"#008000"}]') return 'c.get didn\'t work #6.';
c.fill ('body', lith.g (['p']));
var bodyp = document.querySelectorAll ? 'body p' : {from: document.body, selector: 'p'};
c.set (bodyp, {"class": 'someclass'});
if (JSON.stringify (c.get (bodyp, 'class')) !== '[{"class":"someclass"}]') return 'c.set didn\'t work #1.';
c.set (bodyp, {"class": null});
if (JSON.stringify (c.get (bodyp, ['class', 'another'])) !== '[{"class":null,"another":null}]') return 'c.set didn\'t work #2.';
c.set (bodyp, {color: 'red'}, true);
if (JSON.stringify (c.get (bodyp, 'color', true)) !== '[{"color":"red"}]' && JSON.stringify (c.get (bodyp, 'color', true)) !== '[{"color":"#ff0000"}]') return 'c.set didn\'t work #3.';
c.set (bodyp, {color: null}, true);
if (JSON.stringify (c.get (bodyp, 'color', true)) !== '[{"color":null}]') return 'c.set didn\'t work #4.';
c.fill ('body', lith.g ([
['input', {'class': 'noclass'}]
]));
var result = c.get ('input') [0];
if (result ['class'] !== 'noclass') return 'c.get didn\'t return class properly.';
c.fill ('body', lith.g ([
['input', {id: 'wha', value: 1}],
['input', {id: 'other'}]
]));
// Check that DOM functions can take an invalid selector without throwing an exception.
c.empty (/abc/);
c.fill (/abc/, 'abc');
c.place (/abc/, 'beforeBegin', 'abc');
c.get (/abc/, 'class');
c.set (/abc/, {'class': null});
var wha, styl, evType;
c ('#wha').onchange = function (ev) {
wha = this.value;
styl = this.style.color;
// IE<=8 doesn't seem to pass arguments to event handlers, even when passed explicitly.
evType = arguments.length ? ev.type : 'change';
}
c.set ('#wha', {value: 2});
if (wha !== '2') return 'onchange wasn\'t fired by c.set #1.';
if (evType !== 'change') return 'onchange event wasn\'t passed by c.set #1.';
c.set ('#wha', {color: 'lime'}, true);
if (styl !== 'lime' && styl !== '#00ff00') return 'onchange wasn\'t fired by c.set #2.';
if (evType !== 'change') return 'onchange event wasn\'t passed by c.set #2.';
c.set ('#wha', {value: 3}, false, true);
c.set ('#wha', {color: 'magenta'}, true, true);
if (wha !== '2') return 'onchange wasn\'t detriggered by c.set.';
if (styl !== 'lime' && styl !== '#00ff00') return 'onchange wasn\'t fired by c.set #3.';
if (evType !== 'change') return 'onchange event wasn\'t passed by c.set #3.';
// Check insertAdjacentElement against empty div.
c.fill ('body', lith.g (['div', {id: 'reference'}, ['a']]));
c.place ('#reference', 'beforeBegin', 'a');
if (! inc ([
'a<div id="reference"><a></a></div>',
// Compatibility: IE<=8 removes the quotes of id, uppercases the tags and adds \r\n
'a\r\n<DIV id=reference><A></A></DIV>'
], document.body.innerHTML)) return 'insertAdjacentElement beforeBegin generated incorrect HTML: ' + document.body.innerHTML;
c.fill ('body', lith.g (['div', {id: 'reference'}, ['a']]));
c.place ('#reference', 'afterBegin', 'b');
if (! inc ([
'<div id="reference">b<a></a></div>',
// Compatibility: IE<=8 removes the quotes of id and uppercases the tags
'<DIV id=reference>b<A></A></DIV>'
], document.body.innerHTML)) return 'insertAdjacentElement afterBegin generated incorrect HTML: ' + document.body.innerHTML;
c.fill ('body', lith.g (['div', {id: 'reference'}, ['a']]));
c.place ('#reference', 'beforeEnd', 'c');
if (! inc ([
'<div id="reference"><a></a>c</div>',
// Compatibility: IE<=8 removes the quotes of id and uppercases the tags
'<DIV id=reference><A></A>c</DIV>'
], document.body.innerHTML)) return 'insertAdjacentElement beforeEnd generated incorrect HTML: ' + document.body.innerHTML;
c.fill ('body', lith.g (['div', {id: 'reference'}, ['a']]));
c.place ('#reference', 'afterEnd', 'd');
if (! inc ([
'<div id="reference"><a></a></div>d',
// Compatibility: IE<=8 removes the quotes of id and uppercases the tags
'<DIV id=reference><A></A></DIV>d'
], document.body.innerHTML)) return 'insertAdjacentElement afterEnd generated incorrect HTML: ' + document.body.innerHTML;
c.fill ('body', '');
c.fill ('body', 'text');
if (c.cookie ('username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC').username !== 'John Doe') return 'c.cookie didn\'t work.';
if (dale.stopNot ([
undefined,
{},
'a',
/a/,
['a', function () {}, function () {}],
[[]],
[['a', function () {}, function () {}], []],
[['a', function () {}, function () {}, function () {}]],
], false, c.test) !== false) return 'c.test didn\'t refuse invalid `tests` array.';
var errorThrown;
try {
c.test ([['a', function (wait) {wait (-1)}, function () {}]]);
}
catch (err) {
errorThrown = true;
}
if (! errorThrown) return 'c.test didn\'t validate `wait` parameter passed to `next` function #1.';
errorThrown = false;
try {
c.test ([['b', function (wait) {wait ('foo')}, function () {}]]);
}
catch (err) {
errorThrown = true;
}
if (! errorThrown) return 'c.test didn\'t validate `wait` parameter passed to `next` function #2.';
if (c.test ([]) !== undefined) return 'c.test didn\'t accept empty `tests` array.';
c.test ([['unfinished', function () {}, function () {return 'Check function was executed synchronously.'}]]);
try {
c.test ([['Invalid `wait` passed to `next`', function (wait) {
wait (null);
}, function () {}]]);
return 'Invalid `wait` parameter accepted';
}
catch (error) {}
try {
c.test ([['Invalid `ms` passed to `next`', function (wait) {
wait (10, -5);
}, function () {}]]);
return 'Invalid `ms` parameter accepted';
}
catch (error) {}
if (perf) return;
var testObject = {};
c.test ([
['a1', function () {testObject.a = 0; return true}, function () {
return testObject.a === 0;
}],
['a2', function () {testObject.b = testObject.a; return true}, function () {
return testObject.b === 0;
}],
['a3', function () {
testObject.c = 0;
return testObject.a === testObject.b;
}],
['a4', function (next) {testObject.d = 0; next ()}, function () {
testObject.t = teishi.time ();
return testObject.d === 0;
}],
['a5', function (next) {next (10)}, function () {
if ((teishi.time () - testObject.t) < 10) return 'c.test didn\'t specify timeout properly.';
return true;
}],
// On a modern browser, 11ms should be enough to do the check five times; but good-ol' IE6 needs closer to 150ms.
['a6', function (next) {next (150, 2)}, function () {
if (! testObject.times) testObject.times = 0;
testObject.times++;
return testObject.times === 5;
}],
['a7', function () {
if (testObject.times > 5) return 'next with `ms` did not stop at the first successful check.';
if (c.ajax (/invalidmethod/, /invalidpath/) !== false) return 'c.ajax didn\'t validate its input.';
var jsonajax = c.ajax ('post', 'notaroute', {}, {a: 'b'}, function () {});
if (type (jsonajax) !== 'object' || ! teishi.eq ({headers: jsonajax.headers, body: jsonajax.body}, {headers: {'content-type': 'application/json'}, body: '{"a":"b"}'})) return 'Error when sending a JSON request through c.ajax.';
// We place an `error` variable here so that if the synchronous assertions below the `ajax` call fail, the asynchronous ajax block won't give a thumbs up if it itself ran properly but the synchronous assertions failed.
var error;
var ajax = c.ajax ('get', 'notaroute', null, null, function () {
if (error) return;
// From now on, we need to throw since the `check` function of `c.test` must be synchronous and we're in an asynchronous function.
c.loadScript ('cocholate.js', function () {
if (arguments [1] && type (arguments [1].headers) !== 'object') throw new Error ('Empty headers are not processed correctly');
// We ignore CORS errors when running the test locally.
if (! (arguments [0] && arguments [0].status === 0)) {
if (arguments.length !== 2) throw new Error ('Script was not loaded.');
}
c.loadScript ('nosuchscript.js', function () {
if (arguments.length !== 1) throw new Error ('Invalid script was loaded.');
if (c.loadScript (false, function () {}) !== false) throw new Error ('Invalid invocation to c.ajax wasn\'t returned by c.loadScript.');
// In IE6, repeating the call to `nosuchscript.js` generates an alert with a warning about a missing semicolon (perhaps because the script is attempted to be loaded twice) - by changing the name to something else, the warning is avoided.
var result = c.loadScript ('nosuchscript2.js');
if (type (result) !== 'object') throw new Error ('c.loadScript didn\'t return the request object returned by c.ajax.');
setTimeout (function () {
if (! error) done = true;
}, 200);
});
});
});
if (type (ajax) !== 'object' || type (ajax.headers) !== 'object' || type (ajax.body) !== 'string' || type (ajax.xhr) !== 'object') {
error = true;
return 'c.ajax didn\'t return a valid object.';
}
return true;
}]
]);
});
});
</script>
</body>
</html>