├── .gitignore
├── LICENSE
├── README.md
├── package.json
├── src
├── dom_consumer.js
└── tape_css.js
├── tape-dom.png
├── tape.css
└── test
├── index.html
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | test/test-dist.js
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2014 Victor Grishchenko, Citrea LLC
2 | Copyright (c) 2012-2014 Aleksei Balandin, Citrea LLC
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DOM output formatter for tape tests
2 |
3 | Convert tape's TAP output to nicely formatted DOM.
4 |
5 | 
6 |
7 | Tape is an [NPM package](https://www.npmjs.com/package/tape) for
8 | making [Test Anything Protocol](https://testanything.org/) tests in node.js.
9 | Tape nicely runs in a browser using browserify or in a browser-based
10 | debugger like [IronNode](https://github.com/s-a/iron-node).
11 |
12 | Isomorphic use:
13 |
14 | var tape = require('tape');
15 | // If DOM tree is available (browser, IronNode) then render
16 | // results to DOM. Otherwise, do nothing.
17 | require('tape-dom')(tape);
18 |
19 | Then `browserify my_js_test.js -o browserified_test.js`
20 |
21 | The HTML side:
22 |
23 |
24 |
25 | tape-dom example
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tape-dom",
3 | "version": "0.0.12",
4 | "homepage": "http://github.com/gritzko/tape-dom",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/gritzko/tape-dom.git"
8 | },
9 | "author": {
10 | "email": "victor.grishchenko@gmail.com",
11 | "name": "Victor Grishchenko"
12 | },
13 | "email": "swarm.js@gmail.com",
14 | "license": "MIT",
15 | "files": [
16 | "src/*.js",
17 | "test/test.js",
18 | "test/index.html",
19 | "LICENSE",
20 | "README.md",
21 | "tape.css"
22 | ],
23 | "main": "src/dom_consumer.js",
24 | "browser": "src/dom_consumer.js",
25 | "dependencies": {
26 | "googlediff": "^0.1.0",
27 | "tap-parser": "^1.1.6"
28 | },
29 | "devDependencies": {
30 | "browserify": "13.1.0",
31 | "tape": "^4.0.2",
32 | "tape-dom": "^0.0.4"
33 | },
34 | "scripts": {
35 | "pretest": "browserify test/test.js -o test/test-dist.js",
36 | "test": "open test/index.html"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/dom_consumer.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var DiffMatchPatch = require('googlediff');
3 | var dmp = new DiffMatchPatch();
4 | //var tape = require('tape');
5 | var tape_css = require('./tape_css');
6 |
7 |
8 | function html_diff (a, b) {
9 | var diff = dmp.diff_main(b, a);
10 | dmp.diff_cleanupSemantic(diff);
11 | var ret = '', tag;
12 | diff.forEach(function(chunk){
13 | switch (chunk[0]) {
14 | case 0: tag = 'span'; break;
15 | case 1: tag = 'ins'; break;
16 | case -1: tag = 'del'; break;
17 | }
18 | ret += '<'+tag+'>' + chunk[1] + ''+tag+'>';
19 | });
20 | return ret;
21 | }
22 |
23 |
24 | function startTestDiv (row) {
25 | var test_div = document.createElement('DIV');
26 | test_div.id = row.id;
27 | test_div.setAttribute('class', 'test');
28 |
29 | var name = document.createElement('P');
30 | name.setAttribute('class', 'name');
31 | name.appendChild(document.createTextNode(row.name));
32 | test_div.appendChild(name);
33 |
34 | current_test = test_div;
35 | test_root.appendChild(test_div);
36 |
37 | return test_div;
38 | }
39 |
40 |
41 | function assertDiv (row, root) {
42 | var p = document.createElement('P');
43 | p.setAttribute('class', 'assert '+(row.ok?'ok':'fail'));
44 |
45 | var ok = document.createElement('SPAN');
46 | ok.setAttribute('class', 'ok');
47 | ok.appendChild(document.createTextNode(row.ok ? 'OK' : 'FAIL'));
48 | p.appendChild(ok);
49 |
50 | if (row.name) {
51 | var nam = document.createElement('SPAN');
52 | nam.setAttribute('class', 'name');
53 | nam.appendChild(document.createTextNode(row.name));
54 | p.appendChild(nam);
55 | }
56 | root.appendChild(p);
57 | p.scrollIntoView({block: "end", behavior: "smooth"});
58 | return p;
59 | }
60 |
61 | function commentDiv (row, root) {
62 | var p = document.createElement('P');
63 | p.setAttribute('class', 'comment');
64 | p.appendChild(document.createTextNode(row));
65 | root.appendChild(p);
66 | return p;
67 | }
68 |
69 | function endDiv(row, current_test) {
70 | var p = document.createElement('P');
71 | p.setAttribute('class', 'end');
72 | current_test.appendChild(p);
73 | return p;
74 | }
75 |
76 | function assertFailDiv (row, assert_element) {
77 | var actual = row.actual;
78 | var expected = row.expected;
79 | if (actual && expected &&
80 | typeof(actual)=='object' &&
81 | typeof(expected)=='object') {
82 | actual = JSON.stringify(actual);
83 | expected = JSON.stringify(expected);
84 | }
85 |
86 | var actual_span = document.createElement('SPAN');
87 | actual_span.setAttribute('class', 'actual');
88 | actual_span.appendChild(document.createTextNode(actual));
89 | assert_element.appendChild(actual_span);
90 |
91 | var expected_span = document.createElement('SPAN');
92 | expected_span.setAttribute('class', 'expected');
93 | expected_span.appendChild(document.createTextNode(expected));
94 | assert_element.appendChild(expected_span);
95 |
96 | if (row.file) {
97 | var line = document.createElement('SPAN');
98 | line.setAttribute('class', 'line');
99 | var m = /\/([^\/]+)$/.exec(row.file);
100 | var file_line = m[1];
101 | line.appendChild(document.createTextNode(file_line));
102 | assert_element.appendChild(line);
103 | // this way the user may meaningfully navigate the code
104 | console.warn(row.error.stack);
105 | }
106 |
107 | if (actual && expected &&
108 | actual.constructor==String &&
109 | expected.constructor==String)
110 | {
111 | var diff = document.createElement('P');
112 | diff.setAttribute('class', 'diff');
113 | diff.innerHTML = html_diff(actual, expected);
114 | assert_element.appendChild(diff);
115 | }
116 |
117 | }
118 |
119 | var test_root, current_test;
120 | if (typeof(document)==='object') {
121 | test_root = document.getElementById('tests');
122 | if (!test_root) {
123 | test_root = document.createElement('div');
124 | test_root.setAttribute('id','tests');
125 | document.body.appendChild(test_root);
126 | }
127 | current_test = test_root;
128 | }
129 |
130 | function add_some_dom (row) {
131 | if (row.type==='test') {
132 | current_test = startTestDiv(row, current_test);
133 | } else if (row.type==='assert') {
134 | var assert_element = assertDiv(row, current_test);
135 |
136 | if (!row.ok) {
137 | assertFailDiv(row, assert_element);
138 | }
139 | } else if (row.type==='end') {
140 | endDiv(row, current_test);
141 | current_test = current_test.parentNode;
142 | } else if (row.constructor===String) {
143 | commentDiv(row, current_test);
144 | } else {
145 | console.warn('tape-dom row', row.type, row);
146 | }
147 | }
148 |
149 | function stream (tape) {
150 | var stream = tape.createStream({ objectMode: true });
151 | stream.on('data', add_some_dom);
152 | }
153 |
154 | function installCSS () {
155 | var link = document.createElement('style');
156 | link.setAttribute("type", "text/css");
157 | var css_body = document.createTextNode(tape_css);
158 | link.appendChild(css_body);
159 | document.head.appendChild(link);
160 | }
161 |
162 | function init (tape) {
163 | if (typeof(window)==='object') {
164 | installCSS();
165 | stream(tape);
166 | }
167 | return init;
168 | }
169 |
170 | init.installCSS = installCSS;
171 | init.stream = stream;
172 |
173 | module.exports = init;
174 |
--------------------------------------------------------------------------------
/src/tape_css.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | module.exports =
3 | "div.test{width:100%;border-top:1px dotted grey;margin-top:1em;background:#eef;font-weight:700;margin-bottom:1em}"+
4 | "div#tests{margin-bottom:5em}"+
5 | "p.assert{margin-top:0;margin-bottom:0;font-weight:400;width:100%}"+
6 | "p.assert.ok{background:#efe;color:grey}"+
7 | "p.assert.fail{background:#faa}"+
8 | "p.comment{background:#eef;color:grey;font-style:italic;margin-top:.2em;margin-bottom:.2em;padding-left:2.4em}"+
9 | "p.end{border-bottom:1px dotted grey;margin-top:1em;background:#eef;margin-top:.2em;margin-bottom:.2em}"+
10 | "p.diff{background:#ffe;margin-top:0;margin-bottom:0;padding-top:.25em;padding-bottom:.25em;padding-left:4em;font-weight:400;white-space:pre-wrap}"+
11 | "ins{background:#afa}"+
12 | "del{background:#faa;text-decoration:strike}"+
13 | "span.ok{font-weight:700}"+
14 | "span.actual,span.expected,span.name{padding-left:1em;font-style:italic;max-width:25em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block}"+
15 | "p.assert.fail span.name{color:#555}"+
16 | "span.expected:before{content:'!=';padding-right:1em}"+
17 | "span.line{padding-left:1em;float:right;text-align:right;overflow:hidden;text-overflow:ellipsis;font-family:monospace;color:#888}"
18 | ;
19 |
--------------------------------------------------------------------------------
/tape-dom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gritzko/tape-dom/700774416fae6d18f4639ca3251e05942fb3838e/tape-dom.png
--------------------------------------------------------------------------------
/tape.css:
--------------------------------------------------------------------------------
1 | div.test {
2 | width: 100%;
3 | border-top: 1px dotted grey;
4 | margin-top: 1em;
5 | background: #eef;
6 | font-weight: bold;
7 | }
8 | p.assert {
9 | margin-top: 0px;
10 | margin-bottom: 0px;
11 | font-weight: normal;
12 | width: 100%;
13 | }
14 | p.assert.ok {
15 | background: #efe;
16 | color: grey;
17 | }
18 | p.assert.fail {
19 | background: #faa;
20 | }
21 | p.comment {
22 | background: #eef;
23 | color: grey;
24 | font-style: italic;
25 | margin-top: 0.2em;
26 | margin-bottom: 0.2em;
27 | padding-left: 2.4em;
28 | }
29 | p.end {
30 | border-bottom: 1px dotted grey;
31 | margin-top: 1em;
32 | background: #eef;
33 | margin-top: 0.2em;
34 | margin-bottom: 0.2em;
35 | }
36 | p.diff {
37 | background: #ffe;
38 | margin-top: 0px;
39 | margin-bottom: 0px;
40 | padding-top: 0.25em;
41 | padding-bottom: 0.25em;
42 | padding-left: 4em;
43 | font-weight: normal;
44 | white-space: pre-wrap;
45 | }
46 | ins {
47 | background: #afa;
48 | }
49 | del {
50 | background: #faa;
51 | text-decoration: strike;
52 | }
53 | span.ok {
54 | font-weight: bold;
55 | }
56 | span.actual, span.expected, span.name {
57 | padding-left: 1em;
58 | font-style: italic;
59 | max-width: 25em;
60 | overflow: hidden;
61 | text-overflow: ellipsis;
62 | white-space: nowrap;
63 | display: inline-block;
64 | }
65 | p.assert.fail span.name {
66 | color: #555;
67 | }
68 | span.expected:before {
69 | content: '!=';
70 | padding-right: 1em;
71 | }
72 | span.line {
73 | padding-left: 1em;
74 | float: right;
75 | text-align: right;
76 | overflow: hidden;
77 | text-overflow: ellipsis;
78 | font-family: monospace;
79 | color: #888;
80 | }
81 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | tape-dom tests
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var tape = require('tape');
4 | if (typeof(window)==='object') {
5 | var tape_dom = require('..');
6 | tape_dom.installCSS();
7 | tape_dom.stream(tape);
8 | // This also works
9 | // require('..')(tape)
10 | }
11 |
12 |
13 | tape('trivial matches', function perfect_matches (tap) {
14 | tap.plan(5);
15 | tap.equal(5,5);
16 | tap.equal("string","string");
17 | tap.deepEqual({x:1, y:{z:3}}, {x:1, y:{z:3}});
18 | tap.ok(true);
19 | tap.throws(function evil(){
20 | throw new Error("i vil kil you");
21 | }, /kil/, "it throws");
22 | tap.comment("all those are OK");
23 | });
24 |
25 | tape('simple mismatches', function complete_mismatches (tap) {
26 | tap.plan(4);
27 | tap.equal(5,6, 'comparing 5 to 6');
28 | tap.equal("string","");
29 | tap.deepEqual({x:1, y:{z:3}}, {x:1, y:{z:2}});
30 | tap.ok(false);
31 | tap.throws(function evil(){
32 | throw new Error("i vil mis you");
33 | }, /kil/, "it throws");
34 | tap.comment("all those are not OK");
35 | });
36 |
--------------------------------------------------------------------------------