331cf761d621e1720dfe86fc9fade54bd77c3553
[redakcja.git] /
1 /*jshint latedef: nofunc */
2 var path = require('path'),
3     fs = require('fs'),
4     sys = require('util');
5
6 var less = require('../lib/less');
7 var stylize = require('../lib/less/lessc_helper').stylize;
8
9 var globals = Object.keys(global);
10
11 var oneTestOnly = process.argv[2];
12
13 var isVerbose = process.env.npm_config_loglevel === 'verbose';
14
15 var totalTests = 0,
16     failedTests = 0,
17     passedTests = 0;
18
19 less.tree.functions.add = function (a, b) {
20     return new(less.tree.Dimension)(a.value + b.value);
21 };
22 less.tree.functions.increment = function (a) {
23     return new(less.tree.Dimension)(a.value + 1);
24 };
25 less.tree.functions._color = function (str) {
26     if (str.value === "evil red") { return new(less.tree.Color)("600"); }
27 };
28
29 console.log("\n" + stylize("LESS", 'underline') + "\n");
30
31 runTestSet({strictMath: true, relativeUrls: true, silent: true});
32 runTestSet({strictMath: true, strictUnits: true}, "errors/",
33             testErrors, null, getErrorPathReplacementFunction("errors"));
34 runTestSet({strictMath: true, strictUnits: true, javascriptEnabled: false}, "no-js-errors/",
35             testErrors, null, getErrorPathReplacementFunction("no-js-errors"));
36 runTestSet({strictMath: true, dumpLineNumbers: 'comments'}, "debug/", null,
37            function(name) { return name + '-comments'; });
38 runTestSet({strictMath: true, dumpLineNumbers: 'mediaquery'}, "debug/", null,
39            function(name) { return name + '-mediaquery'; });
40 runTestSet({strictMath: true, dumpLineNumbers: 'all'}, "debug/", null,
41            function(name) { return name + '-all'; });
42 runTestSet({strictMath: true, relativeUrls: false, rootpath: "folder (1)/"}, "static-urls/");
43 runTestSet({strictMath: true, compress: true}, "compression/");
44 runTestSet({}, "legacy/");
45 runTestSet({strictMath: true, strictUnits: true, sourceMap: true }, "sourcemaps/",
46     testSourcemap, null, null, function(filename) { return path.join('test/sourcemaps', filename) + '.json'; });
47
48 testNoOptions();
49
50 function getErrorPathReplacementFunction(dir) {
51     return function(input) {
52         return input.replace(
53                 "{path}", path.join(process.cwd(), "/test/less/" + dir + "/"))
54             .replace("{pathrel}", path.join("test", "less", dir + "/"))
55             .replace("{pathhref}", "")
56             .replace("{404status}", "")
57             .replace(/\r\n/g, '\n');
58     };
59 }
60
61 function testSourcemap(name, err, compiledLess, doReplacements, sourcemap) {
62     fs.readFile(path.join('test/', name) + '.json', 'utf8', function (e, expectedSourcemap) {
63         sys.print("- " + name + ": ");
64         if (sourcemap === expectedSourcemap) {
65             ok('OK');
66         } else if (err) {
67             fail("ERROR: " + (err && err.message));
68             if (isVerbose) {
69                 console.error();
70                 console.error(err.stack);
71             }
72         } else {
73             difference("FAIL", expectedSourcemap, sourcemap);
74         }
75     });
76 }
77
78 function testErrors(name, err, compiledLess, doReplacements) {
79     fs.readFile(path.join('test/less/', name) + '.txt', 'utf8', function (e, expectedErr) {
80         sys.print("- " + name + ": ");
81         expectedErr = doReplacements(expectedErr, 'test/less/errors/');
82         if (!err) {
83             if (compiledLess) {
84                 fail("No Error", 'red');
85             } else {
86                 fail("No Error, No Output");
87             }
88         } else {
89             var errMessage = less.formatError(err);
90             if (errMessage === expectedErr) {
91                 ok('OK');
92             } else {
93                 difference("FAIL", expectedErr, errMessage);
94             }
95         }
96     });
97 }
98
99 function globalReplacements(input, directory) {
100     var p = path.join(process.cwd(), directory),
101         pathimport = path.join(process.cwd(), directory + "import/"),
102         pathesc = p.replace(/[.:/\\]/g, function(a) { return '\\' + (a=='\\' ? '\/' : a); }),
103         pathimportesc = pathimport.replace(/[.:/\\]/g, function(a) { return '\\' + (a=='\\' ? '\/' : a); });
104
105     return input.replace(/\{path\}/g, p)
106             .replace(/\{pathesc\}/g, pathesc)
107             .replace(/\{pathimport\}/g, pathimport)
108             .replace(/\{pathimportesc\}/g, pathimportesc)
109             .replace(/\r\n/g, '\n');
110 }
111
112 function checkGlobalLeaks() {
113     return Object.keys(global).filter(function(v) {
114         return globals.indexOf(v) < 0;
115     });
116 }
117
118 function runTestSet(options, foldername, verifyFunction, nameModifier, doReplacements, getFilename) {
119     foldername = foldername || "";
120
121     if(!doReplacements) {
122         doReplacements = globalReplacements;
123     }
124
125     fs.readdirSync(path.join('test/less/', foldername)).forEach(function (file) {
126         if (! /\.less/.test(file)) { return; }
127         
128         var name = foldername + path.basename(file, '.less');
129         
130         if (oneTestOnly && name !== oneTestOnly) {
131             return;
132         }
133
134         totalTests++;
135
136         if (options.sourceMap) {
137             var sourceMapOutput;
138             options.writeSourceMap = function(output) {
139                 sourceMapOutput = output;
140             };
141             options.sourceMapOutputFilename = name + ".css";
142             options.sourceMapBasepath = path.join(process.cwd(), "test/less");
143             options.sourceMapRootpath = "testweb/";
144         }
145
146         toCSS(options, path.join('test/less/', foldername + file), function (err, less) {
147
148             if (verifyFunction) {
149                 return verifyFunction(name, err, less, doReplacements, sourceMapOutput);
150             }
151             var css_name = name;
152             if(nameModifier) { css_name = nameModifier(name); }
153             fs.readFile(path.join('test/css', css_name) + '.css', 'utf8', function (e, css) {
154                 sys.print("- " + css_name + ": ");
155                 
156                 css = css && doReplacements(css, 'test/less/' + foldername);
157                 if (less === css) { ok('OK'); }
158                 else if (err) {
159                     fail("ERROR: " + (err && err.message));
160                     if (isVerbose) {
161                         console.error();
162                         console.error(err.stack);
163                     }
164                 } else {
165                     difference("FAIL", css, less);
166                 }
167             });
168         });
169     });
170 }
171
172 function diff(left, right) {
173     require('diff').diffLines(left, right).forEach(function(item) {
174       if(item.added || item.removed) {
175         var text = item.value.replace("\n", String.fromCharCode(182) + "\n");
176         sys.print(stylize(text, item.added ? 'green' : 'red'));
177       } else {
178         sys.print(item.value);
179       }
180     });
181     console.log("");
182 }
183
184 function fail(msg) {
185     console.error(stylize(msg, 'red'));
186     failedTests++;
187     endTest();
188 }
189
190 function difference(msg, left, right) {
191     console.warn(stylize(msg, 'yellow'));
192     failedTests++;
193                 
194     diff(left, right);
195     endTest();
196 }
197
198 function ok(msg) {
199     console.log(stylize(msg, 'green'));
200     passedTests++;
201     endTest();
202 }
203
204 function endTest() {
205     var leaked = checkGlobalLeaks();
206     if (failedTests + passedTests === totalTests) {
207         console.log("");
208         if (failedTests > 0) {
209             console.error(failedTests + stylize(" Failed", "red") + ", " + passedTests + " passed");
210         } else {
211             console.log(stylize("All Passed ", "green") + passedTests + " run");
212         }
213         if (leaked.length > 0) {
214             console.log("");
215             console.warn(stylize("Global leak detected: ", "red") + leaked.join(', '));
216         }
217
218         if (leaked.length || failedTests) {
219             //process.exit(1);
220             process.on('exit', function() { process.reallyExit(1) });
221         }
222     }
223 }
224
225 function toCSS(options, path, callback) {
226     var css;
227     options = options || {};
228     fs.readFile(path, 'utf8', function (e, str) {
229         if (e) { return callback(e); }
230         
231         options.paths = [require('path').dirname(path)];
232         options.filename = require('path').resolve(process.cwd(), path);
233         options.optimization = options.optimization || 0;
234
235         new(less.Parser)(options).parse(str, function (err, tree) {
236             if (err) {
237                 callback(err);
238             } else {
239                 try {
240                     css = tree.toCSS(options);
241                     callback(null, css);
242                 } catch (e) {
243                     callback(e);
244                 }
245             }
246         });
247     });
248 }
249
250 function testNoOptions() {
251     totalTests++;
252     try {
253         sys.print("- Integration - creating parser without options: ");
254         new(less.Parser)();
255     } catch(e) {
256         fail(stylize("FAIL\n", "red"));
257         return;
258     }
259     ok(stylize("OK\n", "green"));
260 }