added tests for version 2

pull/120/head
Andrea Giammarchi 2016-02-28 12:53:01 +00:00
parent fd5a5c2d34
commit ed59f2ddfe
9 changed files with 942 additions and 403 deletions

65
2/test/index.html Normal file
View File

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html>
<head>
<title>twemoji test</title>
<meta http-equiv="content-language" content="en-US"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<!--
### what is this ?
this is a battle test script called wru:
https://github.com/WebReflection/wru
It is compatible with every JS engine you can think of
including ancient browsers.
If you see all tests as green, it means it worked.
-->
<style type="text/css">
#wru {
font-family: sans-serif;
font-size: 11pt;
border: 1px solid #333;
}
#wru div {
cursor: default;
padding: 0;
color: #000;
}
#wru div span,
#wru div strong {
display: block;
padding: 4px;
margin: 0;
}
#wru div ul {
margin: 0;
padding-bottom: 4px;
}
#wru div.pass {
background: #90EE90;
}
#wru div.fail {
background: #FF6347;
}
#wru div.error {
background: #000;
color: #FFF;
}
</style>
</head>
<body>
<div id="wru"></div>
<script src="../twemoji.js"></script>
<script>
/*!
(C) Andrea Giammarchi, @WebReflection - Mit Style License
*/
(function(wru){
window.wru = wru;
}(function(Y){function j(){A=K.call(m);if(A){if(typeof A=="function"){A={name:A[S]||"anonymous",test:A}}(P=l(l(Z.node,"div"),"span"))[E]=((ag(A,S)&&A[S])||(ag(A,e)&&A[e])||Q)+i+i;a=[];u=[];T=[];ab={};b("setup");T[ah]||b("test");N||r()}else{t()}}function p(aj){try{return O.call(h,aj)}catch(ai){return h.createElement(aj)}}function l(ai,aj){return ai.appendChild(p(aj))}function g(ai){P[E]=x.call(P[E],0,-2)+i+ai}function t(){var ak=Z.node.insertBefore(p("div"),Z.node.firstChild),al,aj,ai;if(ad){ai=aj="error";al="There Are Errors: "+ad}else{if(C){ai=aj="fail";al=C+" Tests Failed"}else{ai=aj="pass";al="Passed "+s+" Tests"}}Z.status=ai;ak[E]="<strong>"+al+"</strong>";ak.className=aj}function G(){var ai=this.lastChild.style;ai.display=ai.display=="none"?"block":"none"}function c(ai){P[E]+="<ul>"+D+v.call(ai,d+D)+d+"</ul>";(P.onclick=G).call(P)}function r(){f();s+=a[ah];C+=u[ah];ad+=T[ah];g("("+v.call([a[ah],M=u[ah],T[ah]],", ")+")");P=P.parentNode;T[ah]?c(T,W="error"):(M?c(u,W="fail"):W="pass");P.className=W;M=0;W=i;j()}function b(ai){if(ag(A,ai)){try{A[ai](ab)}catch(aj){aa.call(T,i+aj)}}}function ag(aj,ai){return q.call(aj,ai)}function w(){return F()<0.5?-1:1}function f(){if(R){H(R);R=0}b("teardown")}var Z={assert:function U(aj,ai){if(arguments[ah]==1){ai=aj;aj=Q}z=I;aa.call(ai?a:u,W+aj);return ai},async:function V(aj,am,ak,al){al=++N;if(typeof aj=="function"){ak=am;am=aj;aj="asynchronous test #"+al}ak=X(function(){al=0;aa.call(u,aj);--N||(R=X(r,0))},L(ak||y)||y);return function ai(){if(!al){return}z=ae;W=aj+": ";try{am.apply(this,arguments)}catch(an){z=I;aa.call(T,W+an)}W=i;if(z){H(ak);--N||(R=X(r,0))}}},test:function n(ai,aj){Z.after=aj||function(){};m=J.apply(m,[ai]);Z.random&&af.call(m,w);N||j()}},I=true,ae=!I,y=100,i=" ",Q="unknown",ah="length",S="name",e="description",D="<li>",d="</li>",k="\\|/-",q=Z.hasOwnProperty,W=i,ac=W.charAt,x=W.slice,m=[],J=m.concat,v=m.join,aa=m.push,K=m.shift,af=m.sort,N=0,M=0,s=0,C=0,ad=0,R=0,E="innerHTML",h=Y.document,O=h.createElement,B,L,F,X,H,A,P,a,u,T,ab,z;B=Y.Math;L=B.abs;F=B.random;X=Y.setTimeout;H=Y.clearTimeout;Z.node=(h.getElementById("wru")||h.body||h.documentElement);Y.setInterval(function(){N&&g(ac.call(k,M++%4))},y);undefined;Z.log=function o(aj,ai){ai?alert(aj):(typeof console!="undefined")&&console.log(aj)};y*=y;Z.random=ae;return Z}(this)));
</script>
<script src="test.js"></script>
</body>
</html>

553
2/test/test.js Normal file
View File

@ -0,0 +1,553 @@
/*! Copyright Twitter Inc. and other contributors. Licensed under MIT *//*
https://github.com/twitter/twemoji/blob/gh-pages/LICENSE
*/
var base = twemoji.base;
wru.test([{
name: 'string parsing',
test: function () {
// without variant
wru.assert(
'default parsing works',
twemoji.parse('I \u2764 emoji!') ===
'I <img class="emoji" draggable="false" alt="\u2764" src="' + base + '36x36/2764.png"> emoji!'
);
// with "as image" variant
wru.assert(
'default \uFE0F variant parsing works',
twemoji.parse('I \u2764\uFE0F emoji!') ===
'I <img class="emoji" draggable="false" alt="\u2764\uFE0F" src="' + base + '36x36/2764.png"> emoji!'
);
// with "as text" variant
wru.assert(
'default \uFE0E variant parsing works',
twemoji.parse('I \u2764\uFE0E emoji!') ===
'I \u2764\uFE0E emoji!'
);
}
},{
name: 'string parsing + size',
test: function () {
wru.assert(
'number is squared',
twemoji.parse('I \u2764 emoji!', {size: 72}) ===
'I <img class="emoji" draggable="false" alt="\u2764" src="' + base + '72x72/2764.png"> emoji!'
);
wru.assert(
'string is preserved',
twemoji.parse('I \u2764 emoji!', {size: 'any-size'}) ===
'I <img class="emoji" draggable="false" alt="\u2764" src="' + base + 'any-size/2764.png"> emoji!'
);
}
},{
name: 'string parsing + callback',
test: function () {
var result = false;
twemoji.parse('I \u2764 emoji!', function (icon, options) {
result = icon === '2764' && options.size === '36x36';
});
wru.assert('works OK without variant', result);
result = false;
twemoji.parse('I \u2764\uFE0F emoji!', function (icon, options) {
result = icon === '2764' && options.size === '36x36';
});
wru.assert('works OK with variant', result);
result = true;
twemoji.parse('I \u2764\uFE0E emoji!', function (icon, options) {
result = false;
});
wru.assert('not invoked when \uFE0E is matched', result);
}
},{
name: 'string parsing + callback returning `falsy`',
test: function () {
wru.assert(
'does not add an image',
'I \u2764\uFE0F emoji!' ===
twemoji.parse('I \u2764\uFE0F emoji!', function () {})
);
}
},{
name: 'string parsing + callback + size',
test: function () {
wru.assert(
'size is overwritten',
'I <img class="emoji" draggable="false" alt="\u2764" src="72x72/2764.png"> emoji!' ===
twemoji.parse(
'I \u2764 emoji!',
{
base: '',
size: 72
}
)
);
}
},{
name: 'twemoji.replace(str, callback)',
test: function () {
var parsed = false;
var original = 'I \u2764\uFE0E emoji!';
var str = twemoji.replace(original, function (match, emoji, variant) {
parsed = true;
});
wru.assert('variant \\uFE0E has been ignored', !parsed);
wru.assert('returned value is the expected', str === original);
}
},{
name: 'twemoji.test(str)',
test: function () {
wru.assert(
twemoji.test('I \u2764 emoji!') &&
twemoji.test('I \u2764\uFE0F emoji!') &&
!twemoji.test('I \u2764\uFE0E emoji!') &&
!twemoji.test('nope')
);
}
},{
name: 'DOM parsing',
test: function () {
var img,
// without variant
div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764 emoji!'));
twemoji.parse(div);
wru.assert('default parsing works creating 3 nodes', div.childNodes.length === 3);
wru.assert('first child is the expected one', div.removeChild(div.firstChild).nodeValue === 'I ');
img = div.removeChild(div.firstChild);
wru.assert('second child is the image', img.nodeName === 'IMG');
wru.assert('img attributes are OK',
img.className === 'emoji' &&
img.getAttribute('draggable') === 'false' &&
img.src === base + '36x36/2764.png' &&
img.alt === '\u2764' &&
img.onerror === twemoji.onerror
);
wru.assert('last child is the expected one', div.removeChild(div.firstChild).nodeValue === ' emoji!');
// with "as image" variant
div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764\uFE0F emoji!'));
twemoji.parse(div);
wru.assert('default parsing created 3 nodes', div.childNodes.length === 3);
wru.assert('first child is the expected one', div.removeChild(div.firstChild).nodeValue === 'I ');
img = div.removeChild(div.firstChild);
wru.assert('second child is the image', img.nodeName === 'IMG');
wru.assert('img attributes are OK',
img.className === 'emoji' &&
img.getAttribute('draggable') === 'false' &&
img.src === base + '36x36/2764.png' &&
img.alt === '\u2764\uFE0F' &&
img.onerror === twemoji.onerror
);
wru.assert('last child is the expected one', div.removeChild(div.firstChild).nodeValue === ' emoji!');
// with "as text" variant
div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764\uFE0E emoji!'));
twemoji.parse(div);
wru.assert('default parsing did NOT create 3 nodes anyway', div.childNodes.length === 1);
}
},{
name: 'DOM parsing + size',
test: function () {
var img,
div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764 emoji!'));
twemoji.parse(div, {size: 16});
wru.assert('default parsing works creating 3 nodes', div.childNodes.length === 3);
wru.assert('first child is the expected one', div.removeChild(div.firstChild).nodeValue === 'I ');
img = div.removeChild(div.firstChild);
wru.assert('second child is the image', img.nodeName === 'IMG');
wru.assert('img attributes are OK',
img.className === 'emoji' &&
img.getAttribute('draggable') === 'false' &&
img.src === base + '16x16/2764.png' &&
img.alt === '\u2764' &&
img.onerror === twemoji.onerror
);
wru.assert('last child is the expected one', div.removeChild(div.firstChild).nodeValue === ' emoji!');
}
},{
name: 'DOM parsing + callback',
test: function () {
var result = false,
div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764 emoji!'));
twemoji.parse(div, function (icon, options) {
result = icon === '2764' && options.size === '36x36';
});
wru.assert('works OK without variant', result);
result = false;
div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764\uFE0F emoji!'));
twemoji.parse(div, function (icon, options) {
result = icon === '2764' && options.size === '36x36';
});
wru.assert('works OK with variant', result);
result = true;
div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764\uFE0E emoji!'));
twemoji.parse(div, function (icon, options, variant) {
result = false;
});
wru.assert('not invoked when \uFE0E is matched', result);
}
},{
name: 'DOM parsing + callback returning `falsy`',
test: function () {
var div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764 emoji!'));
twemoji.parse(div, function () {});
wru.assert(div.innerHTML === 'I \u2764 emoji!');
}
},{
name: 'DOM parsing + callback + size',
test: function () {
var result = false,
div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764 emoji!'));
twemoji.parse(div, {
size: 16,
callback: function (icon, options) {
result = icon === '2764' && options.size === '16x16';
}
});
wru.assert('works OK without variant', result);
result = false;
div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764\uFE0F emoji!'));
twemoji.parse(div, {
size: 72,
callback: function (icon, options) {
result = icon === '2764' && options.size === '72x72';
}
});
wru.assert('works OK with variant', result);
}
},{
name: 'nested nodes',
test: function () {
var str = '<img class="emoji" draggable="false" alt="\u2764" src="https://twemoji.maxcdn.com/36x36/2764.png">',
div = document.createElement('div'),
p,
img;
div.innerHTML = '<p>I \u2764 emoji<strong>!</strong></p><p>I \u2764 them too</p>';
p = div.getElementsByTagName('p');
twemoji.parse(div);
wru.assert('preserved structure', p.length === 2);
img = div.getElementsByTagName('img');
wru.assert('correct amount of images found', img.length === 2);
wru.assert('images are in the right place',
img[0].parentNode === p[0] &&
img[1].parentNode === p[1]
);
}
},{
name: 'only nodes are affected',
test: function () {
var div = document.createElement('div');
var innerHTML = '<script>/*\u2764*/</script><style>/*\u2764*/</style><hr class="\u2764">';
div.innerHTML = innerHTML;
twemoji.parse(div);
wru.assert(!/<img/i.test(div.innerHTML));
}
},{
name: 'DOM parsing multiple per node',
test: function () {
var div = document.createElement('div');
div.innerHTML = 'I \u2764\ufe0f emoji, you should \u2764 emoji too!';
twemoji.parse(div);
wru.assert('default parsing works creating 5 nodes', div.childNodes.length === 5);
wru.assert('first child is the expected one', div.removeChild(div.firstChild).nodeValue === 'I ');
wru.assert('second child is the expected one', div.removeChild(div.firstChild).alt === '\u2764\ufe0f');
wru.assert('third child is the expected one', div.removeChild(div.firstChild).nodeValue === ' emoji, you should ');
wru.assert('fourth child is the expected one', div.removeChild(div.firstChild).alt === '\u2764');
wru.assert('fifth child is the expected one', div.removeChild(div.firstChild).nodeValue === ' emoji too!');
}
},{
name: 'DOM parsing does not create XSS',
test: function () {
var div = document.createElement('div'), text, html;
div.innerHTML = 'I \u2764\ufe0f emoji, you shuold &lt;3 them too!';
text = div.childNodes[0].nodeValue.slice('I \u2764\ufe0f'.length);
html = div.innerHTML.replace('\u2764\ufe0f', '');
twemoji.parse(div);
wru.assert('third child is the expected one', div.childNodes[2].nodeValue === text);
wru.assert('html unaltered', div.innerHTML.replace(/<img[^>]+?>/i, '') === html);
}
},{
name: 'string parsing + className',
test: function () {
var className = 'img-' + Math.random();
var img = 'I <img class="' + className + '" draggable="false" alt="\u2764" src="36x36/2764.png"> emoji!';
wru.assert(
'className is overwritten',
img ===
twemoji.parse(
'I \u2764 emoji!',
{
className: className,
base: ''
}
)
);
}
},{
name: 'DOM parsing + className',
test: function () {
var className = 'img-' + Math.random();
var img,
div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764 emoji!'));
twemoji.parse(div, {className: className});
wru.assert(
'className is overwritten',
div.getElementsByTagName('img')[0].className === className
);
}
},{
name: 'string parsing + attributes callback',
test: function () {
wru.assert(
'custom attributes are inserted',
'I <img class="emoji" draggable="false" alt="\u2764" src="' + base + '36x36/2764.png" title="Emoji: \u2764" data-test="We all &lt;3 emoji"> emoji!' ===
twemoji.parse(
'I \u2764 emoji!',
{
attributes: function(rawText, iconId) {
return {
title: 'Emoji: ' + rawText,
'data-test': 'We all <3 emoji'
};
}
}
)
);
}
},{
name: 'string parsing + attributes callback icon id',
test: function () {
wru.assert(
'custom attributes are inserted',
'I <img class="emoji" draggable="false" alt="\u2764" src="' + base + '36x36/2764.png" title="Emoji: 2764" data-test="We all &lt;3 emoji"> emoji!' ===
twemoji.parse(
'I \u2764 emoji!',
{
attributes: function(rawText, iconId) {
return {
title: 'Emoji: ' + iconId,
'data-test': 'We all <3 emoji'
};
}
}
)
);
}
},{ name: 'string parsing + attributes callback content properly encoded',
test: function () {
wru.assert(
'custom attributes are inserted',
'I <img class="emoji" draggable="false" alt="\u2764" src="' + base + '36x36/2764.png" title="&amp;amp;lt;script&amp;amp;gt;alert(&quot;yo&quot;)&amp;amp;lt;/script&amp;amp;gt;"> emoji!' ===
twemoji.parse(
'I \u2764 emoji!',
{
attributes: function(rawText, iconId) {
return {
title: '&amp;lt;script&amp;gt;alert("yo")&amp;lt;/script&amp;gt;'
};
}
}
)
);
}
},{
name: 'string parsing + attributes callback "on" attributes are omitted',
test: function () {
wru.assert(
'custom attributes are inserted',
'I <img class="emoji" draggable="false" alt="❤" src="' + base + '36x36/2764.png" title="test"> emoji!' ===
twemoji.parse(
'I \u2764 emoji!',
{
attributes: function(rawText, iconId) {
return {
title: 'test',
onsomething: 'whoops!',
onclick: 'nope',
onmousedown: 'nada'
};
}
}
)
);
}
},{
name: 'DOM parsing + attributes callback',
test: function () {
var img,
// without variant
div = document.createElement('div');
div.appendChild(document.createTextNode('I \u2764 emoji!'));
twemoji.parse(
div, {
attributes: function(rawText, iconId) {
return {
title: 'Emoji: ' + rawText,
'data-test': 'We all <3 emoji',
onclick: 'nope',
onmousedown: 'nada'
};
}
}
);
wru.assert('default parsing works creating 3 nodes', div.childNodes.length === 3);
wru.assert('first child is the expected one', div.removeChild(div.firstChild).nodeValue === 'I ');
img = div.removeChild(div.firstChild);
wru.assert('second child is the image', img.nodeName === 'IMG');
wru.assert('img attributes are OK',
img.className === 'emoji' &&
img.getAttribute('draggable') === 'false' &&
img.src === base + '36x36/2764.png' &&
img.alt === '\u2764' &&
img.onerror === twemoji.onerror &&
img.getAttribute('title') === 'Emoji: \u2764' &&
img.getAttribute('data-test') === 'We all <3 emoji'
);
wru.assert('img on attributes are omitted',
img.onclick === null &&
img.onmousedown === null
);
}
},{
name: 'folder option',
test: function () {
var img = 'I <img class="emoji" draggable="false" alt="\u2764" src="svg/2764.svg"> emoji!';
wru.assert(
'folder is accepted',
img ===
twemoji.parse(
'I \u2764 emoji!',
{
folder: 'svg',
ext: '.svg',
base: ''
}
)
);
wru.assert(
'folder overwrites size',
img ===
twemoji.parse(
'I \u2764 emoji!',
{
size: 72,
folder: 'svg',
ext: '.svg',
base: ''
}
)
);
}
},{
name: 'non standard OSX variant',
test: function () {
var div = document.createElement('div');
div.innerHTML = '5\ufe0f\u20e3';
twemoji.parse(div);
wru.assert('recognized as graphical',
div.firstChild.className === 'emoji' &&
div.firstChild.getAttribute('draggable') === 'false' &&
div.firstChild.getAttribute('alt') === "5⃣" &&
div.firstChild.src === 'http://twemoji.maxcdn.com/36x36/35-20e3.png'
);
wru.assert('the length is preserved',
div.getElementsByTagName('img')[0].alt.length === 3);
}
},{
name: 'same but standard OSX without variant',
test: function () {
var div = document.createElement('div');
div.innerHTML = '5\u20e3';
twemoji.parse(div);
wru.assert('recognized as graphical',
div.firstChild.className === 'emoji' &&
div.firstChild.getAttribute('draggable') === 'false' &&
div.firstChild.getAttribute('alt') === "5⃣" &&
div.firstChild.src === 'http://twemoji.maxcdn.com/36x36/35-20e3.png'
);
wru.assert('the length is preserved',
div.getElementsByTagName('img')[0].alt.length === 2);
}
}, {
name: 'multiple parsing using a callback',
test: function () {
wru.assert(
'FE0E is still ignored',
twemoji.parse('\u25c0 \u25c0\ufe0e \u25c0\ufe0f', {
callback: function(iconId, options){return 'icon';}
}) ===
'<img class="emoji" draggable="false" alt="\u25c0" src="icon"> \u25c0\ufe0e <img class="emoji" draggable="false" alt="\u25c0\ufe0f" src="icon">'
);
}
},{
name: 'invalid variants and chars',
test: function () {
var div = document.createElement('div');
var img;
div.innerHTML = twemoji.parse('"\u2b1c\uFE0F"');
img = div.getElementsByTagName('img')[0];
wru.assert('correct img.alt 1', img.alt === "\u2b1c\uFE0F");
wru.assert('correct img.src 1', img.src.slice(-8) === '2b1c.png');
// other variants should be ignored
div.innerHTML = twemoji.parse('"\u2b1c\uFE00"');
img = div.getElementsByTagName('img')[0];
wru.assert('correct img.alt 2', img.alt === "\u2b1c");
wru.assert('correct img.src 2', img.src.slice(-8) === '2b1c.png');
div.removeChild(img);
// the variant without meanings are still there
div.innerHTML === '"\uFE00"';
// when there is a trailing \uFE0E there should be no image
div.innerHTML = twemoji.parse('"\u2b1c\uFE0E"');
wru.assert('correct length', div.getElementsByTagName('img').length === 0);
wru.assert('expected html', div.innerHTML === '"\u2b1c\uFE0E"');
}
}, {
name: 'SVG Elements are ignored',
test: function () {
if (typeof SVGElement !== 'undefined') {
var innerHTML, div = document.createElement('div');
div.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40">' +
'<switch>' +
'<circle cx="20" cy="20" r="18" stroke="grey" stroke-width="2" fill="#99FF66" />' +
'<foreignObject>' +
'<div>I \u2764 emoji!</div>' +
'</foreignObject>' +
'</switch>' +
'</svg>';
// grab the normalized one
innerHTML = div.innerHTML;
twemoji.parse(div);
wru.assert('nothing changed', innerHTML === div.innerHTML);
} else {
wru.assert('nothing to do here');
}
}
}, {
name: 'using a different onerror',
test: function () {
var Image = window.Image;
window.Image = function () {
var self = new Image;
setTimeout(function () {
window.Image = Image;
self.onerror();
}, 10);
return self;
};
var div = document.createElement('div');
div.innerHTML = '5\ufe0f\u20e3';
twemoji.parse(div, {onerror: wru.async(function () {
wru.assert('OK');
})});
}
}]);

34
2/test/testrunner.js Normal file
View File

@ -0,0 +1,34 @@
console.log('Loading: test.html');
var page = require('webpage').create();
var url = '2/test/index.html';
page.open(url, function (status) {
if (status === 'success') {
var results = page.evaluate(function() {
// remove the first node with the total from the following counts
var passed = Math.max(0, document.querySelectorAll('.pass').length - 1);
return {
// retrieve the total executed tests number
total: ''.concat(
passed,
' blocks (',
document.querySelector('#wru strong').textContent.replace(/\D/g, ''),
' single tests)'
),
passed: passed,
failed: Math.max(0, document.querySelectorAll('.fail').length - 1),
errored: Math.max(0, document.querySelectorAll('.error').length - 1)
};
});
console.log('- - - - - - - - - -');
console.log('total: ' + results.total);
console.log('- - - - - - - - - -');
console.log('passed: ' + results.passed);
console.log('failed: ' + results.failed);
console.log('errored: ' + results.errored);
console.log('- - - - - - - - - -');
if (0 < results.failed + results.errored) {
status = 'failed';
}
}
phantom.exit(status === 'success' ? 0 : 1);
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
2/twemoji.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,10 @@ var fs = require('fs');
var http = require('http'); var http = require('http');
var path = require('path'); var path = require('path');
function file(which) {
return path.join(__dirname, '../../', which);
}
// Twitter assets by property name // Twitter assets by property name
var assets = { var assets = {
'16x16': [], '16x16': [],
@ -37,17 +41,17 @@ Queue([
function grabAllAssets(q) { function grabAllAssets(q) {
console.log('analyzing all assets ... '); console.log('analyzing all assets ... ');
// per each path/folder // per each path/folder
Object.keys(assets).forEach(function (p, i, paths) { Object.keys(assets).forEach(function (path, i, paths) {
// grab all files in that folder // grab all files in that folder
fs.readdir(path.join(__dirname, '../../', p), function (err, files) { fs.readdir(file(path), function (err, files) {
// and add them to the assets path // and add them to the assets path
assets[p].push.apply( assets[path].push.apply(
assets[p], assets[path],
files.map(upperCaseWithoutExtension) files.map(upperCaseWithoutExtension)
); );
// once all assets arrays have been populated // once all assets arrays have been populated
if (paths.reduce(completed, true)) { if (paths.reduce(completed, true)) {
console.log('[INFO] assets contains ' + assets[p].length + ' emoji.'); console.log('[INFO] assets contains ' + assets[path].length + ' emoji.');
q.next(); q.next();
} }
}); });
@ -108,9 +112,9 @@ Queue([
// do not loop for emoji we know we should ignore // do not loop for emoji we know we should ignore
if (ignoreMissing.indexOf(emoji) < 0) { if (ignoreMissing.indexOf(emoji) < 0) {
// verify all others per each folder // verify all others per each folder
this.forEach(function (p) { this.forEach(function (path) {
if (assets[p].indexOf(emoji) < 0) { if (assets[path].indexOf(emoji) < 0) {
(missing[p] || (missing[p] = [])).push(emoji); (missing[path] || (missing[path] = [])).push(emoji);
missingGrouped[emoji] = true; missingGrouped[emoji] = true;
} }
}); });
@ -178,8 +182,8 @@ Queue([
// add our own assets that are not part of the Unicode standard // add our own assets that are not part of the Unicode standard
function addMissingEmojiAndSort(q) { function addMissingEmojiAndSort(q) {
q.nonStandard = []; q.nonStandard = [];
Object.keys(assets).forEach(function (p, i) { Object.keys(assets).forEach(function (path, i) {
assets[p].forEach(function (emoji) { assets[path].forEach(function (emoji) {
if ( if (
q.emojiSource.indexOf(emoji) < 0 && q.emojiSource.indexOf(emoji) < 0 &&
q.variantsSensitive.indexOf(emoji) < 0 && q.variantsSensitive.indexOf(emoji) < 0 &&
@ -230,11 +234,10 @@ Queue([
} }
}); });
// create a RegExp with properly ordered matches // the sensitive ones may be followed by U+FE0F but not U+FE0E
q.re = '((?:' + q.re = regular.join('|') + '|(?:' +
regular.join('|') + ')|(?:(?:' +
sensitive.join('|') + sensitive.join('|') +
')([\\uFE0E\\uFE0F]?)))'; ')(?:\\ufe0f|(?!\\ufe0e))';
q.next(); q.next();
@ -414,7 +417,7 @@ function createTwemoji(re) {
* those follwed by the invariant \uFE0E ("as text"). * those follwed by the invariant \uFE0E ("as text").
* Once invoked, parameters will be: * Once invoked, parameters will be:
* *
* codePoint:string the lower case HEX code point * iconId:string the lower case HEX code point
* i.e. "1f4a9" * i.e. "1f4a9"
* *
* options:Object all info for this parsing operation * options:Object all info for this parsing operation
@ -442,16 +445,16 @@ function createTwemoji(re) {
* // I <img class="emoji" draggable="false" alt="❤️" src="/assets/2764.gif"> emoji! * // I <img class="emoji" draggable="false" alt="❤️" src="/assets/2764.gif"> emoji!
* *
* *
* twemoji.parse("I \u2764\uFE0F emoji!", function(icon, options, variant) { * twemoji.parse("I \u2764\uFE0F emoji!", function(iconId, options) {
* return '/assets/' + icon + '.gif'; * return '/assets/' + iconId + '.gif';
* }); * });
* // I <img class="emoji" draggable="false" alt="❤️" src="/assets/2764.gif"> emoji! * // I <img class="emoji" draggable="false" alt="❤️" src="/assets/2764.gif"> emoji!
* *
* *
* twemoji.parse("I \u2764\uFE0F emoji!", { * twemoji.parse("I \u2764\uFE0F emoji!", {
* size: 72, * size: 72,
* callback: function(icon, options, variant) { * callback: function(iconId, options) {
* return '/assets/' + options.size + '/' + icon + options.ext; * return '/assets/' + options.size + '/' + iconId + options.ext;
* } * }
* }); * });
* // I <img class="emoji" draggable="false" alt="❤️" src="/assets/72x72/2764.png"> emoji! * // I <img class="emoji" draggable="false" alt="❤️" src="/assets/72x72/2764.png"> emoji!
@ -472,17 +475,10 @@ function createTwemoji(re) {
* String.prototype.replace(str, callback) * String.prototype.replace(str, callback)
* arguments such: * arguments such:
* callback( * callback(
* match, // the emoji match * rawText, // the emoji match
* icon, // the emoji text (same as text)
* variant // either '\uFE0E' or '\uFE0F', if present
* ); * );
* *
* and others commonly received via replace. * and others commonly received via replace.
*
* NOTE: When the variant \uFE0E is found, remember this is an explicit intent
* from the user: the emoji should **not** be replaced with an image.
* In \uFE0F case one, it's the opposite, it should be graphic.
* This utility convetion is that only \uFE0E are not translated into images.
*/ */
replace: replace, replace: replace,
@ -554,7 +550,6 @@ function createTwemoji(re) {
* based on Twitter CDN * based on Twitter CDN
* @param string the emoji codepoint string * @param string the emoji codepoint string
* @param string the default size to use, i.e. "36x36" * @param string the default size to use, i.e. "36x36"
* @param string optional "\uFE0F" variant char, ignored by default
* @return string the image source to use * @return string the image source to use
*/ */
function defaultImageSrcGenerator(icon, options) { function defaultImageSrcGenerator(icon, options) {
@ -593,19 +588,16 @@ function createTwemoji(re) {
/** /**
* Used to both remove the possible variant * Used to both remove the possible variant
* and to convert utf16 into code points * and to convert utf16 into code points.
* @param string the emoji surrogate pair * If there is a zero-width-joiner, leave the variant in.
* @param string the optional variant char, if any * @param string the raw text of the emoji match
*/ */
function grabTheRightIcon(icon, variant) { function grabTheRightIcon(rawText) {
// if variant is present as \uFE0F // if variant is present as \uFE0F
return toCodePoint( return toCodePoint(
variant === '\uFE0F' ? rawText.indexOf('\u200D') < 0 ?
// the icon should not contain it rawText.replace(/\uFE0F/g, '') :
icon.slice(0, -1) : rawText
// fix non standard OSX behavior
(icon.length === 3 && icon.charAt(1) === '\uFE0F' ?
icon.charAt(0) + icon.charAt(2) : icon)
); );
} }
@ -636,9 +628,8 @@ function createTwemoji(re) {
i, i,
index, index,
img, img,
alt, rawText,
icon, iconId,
variant,
src; src;
while (length--) { while (length--) {
modified = false; modified = false;
@ -653,39 +644,32 @@ function createTwemoji(re) {
createText(text.slice(i, index)) createText(text.slice(i, index))
); );
} }
alt = match[0]; rawText = match[0];
icon = match[1]; iconId = grabTheRightIcon(rawText);
variant = match[2]; i = index + rawText.length;
i = index + alt.length; src = options.callback(iconId, options);
if (variant !== '\uFE0E') { if (src) {
src = options.callback( img = new Image();
grabTheRightIcon(icon, variant), img.onerror = options.onerror;
options, img.setAttribute('draggable', 'false');
variant attrib = options.attributes(rawText, iconId);
); for (attrname in attrib) {
if (src) { if (
img = new Image(); attrib.hasOwnProperty(attrname) &&
img.onerror = options.onerror; // don't allow any handlers to be set + don't allow overrides
img.setAttribute('draggable', 'false'); attrname.indexOf('on') !== 0 &&
attrib = options.attributes(icon, variant); !img.hasAttribute(attrname)
for (attrname in attrib) { ) {
if ( img.setAttribute(attrname, attrib[attrname]);
attrib.hasOwnProperty(attrname) &&
// don't allow any handlers to be set + don't allow overrides
attrname.indexOf('on') !== 0 &&
!img.hasAttribute(attrname)
) {
img.setAttribute(attrname, attrib[attrname]);
}
} }
img.className = options.className;
img.alt = alt;
img.src = src;
modified = true;
fragment.appendChild(img);
} }
img.className = options.className;
img.alt = rawText;
img.src = src;
modified = true;
fragment.appendChild(img);
} }
if (!img) fragment.appendChild(createText(alt)); if (!img) fragment.appendChild(createText(rawText));
img = null; img = null;
} }
// is there actually anything to replace in here ? // is there actually anything to replace in here ?
@ -718,50 +702,40 @@ function createTwemoji(re) {
* @return the string with <img tags> replacing all found and parsed emoji * @return the string with <img tags> replacing all found and parsed emoji
*/ */
function parseString(str, options) { function parseString(str, options) {
return replace(str, function (match, icon, variant) { return replace(str, function (rawText) {
var var
ret = match, ret = rawText,
iconId = grabTheRightIcon(rawText),
src = options.callback(iconId, options),
attrib, attrib,
attrname, attrname;
src; if (src) {
// verify the variant is not the FE0E one // recycle the match string replacing the emoji
// this variant means "emoji as text" and should not // with its image counter part
// require any action/replacement ret = '<img '.concat(
// http://unicode.org/Public/UNIDATA/StandardizedVariants.html 'class="', options.className, '" ',
if (variant !== '\uFE0E') { 'draggable="false" ',
src = options.callback( // needs to preserve user original intent
grabTheRightIcon(icon, variant), // when variants should be copied and pasted too
options, 'alt="',
variant rawText,
'"',
' src="',
src,
'"'
); );
if (src) { attrib = options.attributes(rawText, iconId);
// recycle the match string replacing the emoji for (attrname in attrib) {
// with its image counter part if (
ret = '<img '.concat( attrib.hasOwnProperty(attrname) &&
'class="', options.className, '" ', // don't allow any handlers to be set + don't allow overrides
'draggable="false" ', attrname.indexOf('on') !== 0 &&
// needs to preserve user original intent ret.indexOf(' ' + attrname + '=') === -1
// when variants should be copied and pasted too ) {
'alt="', ret = ret.concat(' ', attrname, '="', escapeHTML(attrib[attrname]), '"');
match,
'"',
' src="',
src,
'"'
);
attrib = options.attributes(icon, variant);
for (attrname in attrib) {
if (
attrib.hasOwnProperty(attrname) &&
// don't allow any handlers to be set + don't allow overrides
attrname.indexOf('on') !== 0 &&
ret.indexOf(' ' + attrname + '=') === -1
) {
ret = ret.concat(' ', attrname, '="', escapeHTML(attrib[attrname]), '"');
}
} }
ret = ret.concat('>');
} }
ret = ret.concat('>');
} }
return ret; return ret;
}); });

View File

@ -27,6 +27,6 @@
}, },
"main": "./twemoji.npm.js", "main": "./twemoji.npm.js",
"scripts": { "scripts": {
"test": "phantomjs testrunner.js" "test": "phantomjs testrunner.js && phantomjs 2/test/testrunner.js"
} }
} }