Initial commit
[yaffs-website] / node_modules / node-sass / test / api.js
1 /*eslint new-cap: ["error", {"capIsNewExceptions": ["Color"]}]*/
2
3 var assert = require('assert'),
4   fs = require('fs'),
5   path = require('path'),
6   read = fs.readFileSync,
7   sassPath = process.env.NODESASS_COV
8       ? require.resolve('../lib-cov')
9       : require.resolve('../lib'),
10   sass = require(sassPath),
11   fixture = path.join.bind(null, __dirname, 'fixtures'),
12   resolveFixture = path.resolve.bind(null, __dirname, 'fixtures');
13
14 describe('api', function() {
15
16   describe('.render(options, callback)', function() {
17
18     beforeEach(function() {
19       delete process.env.SASS_PATH;
20     });
21
22     it('should compile sass to css with file', function(done) {
23       var expected = read(fixture('simple/expected.css'), 'utf8').trim();
24
25       sass.render({
26         file: fixture('simple/index.scss')
27       }, function(error, result) {
28         assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n'));
29         done();
30       });
31     });
32
33     it('should compile sass to css with outFile set to absolute url', function(done) {
34       sass.render({
35         file: fixture('simple/index.scss'),
36         sourceMap: true,
37         outFile: fixture('simple/index-test.css')
38       }, function(error, result) {
39         assert.equal(JSON.parse(result.map).file, 'index-test.css');
40         done();
41       });
42     });
43
44     it('should compile sass to css with outFile set to relative url', function(done) {
45       sass.render({
46         file: fixture('simple/index.scss'),
47         sourceMap: true,
48         outFile: './index-test.css'
49       }, function(error, result) {
50         assert.equal(JSON.parse(result.map).file, 'index-test.css');
51         done();
52       });
53     });
54
55     it('should compile sass to css with outFile and sourceMap set to relative url', function(done) {
56       sass.render({
57         file: fixture('simple/index.scss'),
58         sourceMap: './deep/nested/index.map',
59         outFile: './index-test.css'
60       }, function(error, result) {
61         assert.equal(JSON.parse(result.map).file, '../../index-test.css');
62         done();
63       });
64     });
65
66     it('should compile generate map with sourceMapRoot pass-through option', function(done) {
67       sass.render({
68         file: fixture('simple/index.scss'),
69         sourceMap: './deep/nested/index.map',
70         sourceMapRoot: 'http://test.com/',
71         outFile: './index-test.css'
72       }, function(error, result) {
73         assert.equal(JSON.parse(result.map).sourceRoot, 'http://test.com/');
74         done();
75       });
76     });
77
78     it('should compile sass to css with data', function(done) {
79       var src = read(fixture('simple/index.scss'), 'utf8');
80       var expected = read(fixture('simple/expected.css'), 'utf8').trim();
81
82       sass.render({
83         data: src
84       }, function(error, result) {
85         assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n'));
86         done();
87       });
88     });
89
90     it('should compile sass to css using indented syntax', function(done) {
91       var src = read(fixture('indent/index.sass'), 'utf8');
92       var expected = read(fixture('indent/expected.css'), 'utf8').trim();
93
94       sass.render({
95         data: src,
96         indentedSyntax: true
97       }, function(error, result) {
98         assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n'));
99         done();
100       });
101     });
102
103     it('should NOT compile empty data string', function(done) {
104       sass.render({
105         data: ''
106       }, function(error) {
107         assert.equal(error.message, 'No input specified: provide a file name or a source string to process');
108         done();
109       });
110     });
111
112     it('should NOT compile without any input', function(done) {
113       sass.render({ }, function(error) {
114         assert.equal(error.message, 'No input specified: provide a file name or a source string to process');
115         done();
116       });
117     });
118
119     it('should returnn error status 1 for bad input', function(done) {
120       sass.render({
121         data: '#navbar width 80%;'
122       }, function(error) {
123         assert(error.message);
124         assert.equal(error.status, 1);
125         done();
126       });
127     });
128
129     it('should compile with include paths', function(done) {
130       var src = read(fixture('include-path/index.scss'), 'utf8');
131       var expected = read(fixture('include-path/expected.css'), 'utf8').trim();
132
133       sass.render({
134         data: src,
135         includePaths: [
136           fixture('include-path/functions'),
137           fixture('include-path/lib')
138         ]
139       }, function(error, result) {
140         assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n'));
141         done();
142       });
143     });
144
145     it('should add cwd to the front on include paths', function(done) {
146       var src = fixture('cwd-include-path/root/index.scss');
147       var expected = read(fixture('cwd-include-path/expected.css'), 'utf8').trim();
148       var cwd = process.cwd();
149
150       process.chdir(fixture('cwd-include-path'));
151       sass.render({
152         file: src,
153         includePaths: []
154       }, function(error, result) {
155         assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n'));
156
157         process.chdir(cwd);
158         done();
159       });
160     });
161
162     it('should check SASS_PATH in the specified order', function(done) {
163       var src = read(fixture('sass-path/index.scss'), 'utf8');
164       var expectedRed = read(fixture('sass-path/expected-red.css'), 'utf8').trim();
165       var expectedOrange = read(fixture('sass-path/expected-orange.css'), 'utf8').trim();
166
167       var envIncludes = [
168         fixture('sass-path/red'),
169         fixture('sass-path/orange')
170       ];
171
172       process.env.SASS_PATH = envIncludes.join(path.delimiter);
173       sass.render({
174         data: src,
175         includePaths: []
176       }, function(error, result) {
177         assert.equal(result.css.toString().trim(), expectedRed.replace(/\r\n/g, '\n'));
178       });
179
180       process.env.SASS_PATH = envIncludes.reverse().join(path.delimiter);
181       sass.render({
182         data: src,
183         includePaths: []
184       }, function(error, result) {
185         assert.equal(result.css.toString().trim(), expectedOrange.replace(/\r\n/g, '\n'));
186         done();
187       });
188     });
189
190     it('should prefer include path over SASS_PATH', function(done) {
191       var src = read(fixture('sass-path/index.scss'), 'utf8');
192       var expectedRed = read(fixture('sass-path/expected-red.css'), 'utf8').trim();
193       var expectedOrange = read(fixture('sass-path/expected-orange.css'), 'utf8').trim();
194
195       var envIncludes = [
196         fixture('sass-path/red')
197       ];
198       process.env.SASS_PATH = envIncludes.join(path.delimiter);
199
200       sass.render({
201         data: src,
202         includePaths: []
203       }, function(error, result) {
204         assert.equal(result.css.toString().trim(), expectedRed.replace(/\r\n/g, '\n'));
205       });
206       sass.render({
207         data: src,
208         includePaths: [fixture('sass-path/orange')]
209       }, function(error, result) {
210         assert.equal(result.css.toString().trim(), expectedOrange.replace(/\r\n/g, '\n'));
211         done();
212       });
213     });
214
215     it('should render with precision option', function(done) {
216       var src = read(fixture('precision/index.scss'), 'utf8');
217       var expected = read(fixture('precision/expected.css'), 'utf8').trim();
218
219       sass.render({
220         data: src,
221         precision: 10
222       }, function(error, result) {
223         assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n'));
224         done();
225       });
226     });
227
228     it('should contain all included files in stats when data is passed', function(done) {
229       var src = read(fixture('include-files/index.scss'), 'utf8');
230       var expected = [
231         fixture('include-files/bar.scss').replace(/\\/g, '/'),
232         fixture('include-files/foo.scss').replace(/\\/g, '/')
233       ];
234
235       sass.render({
236         data: src,
237         includePaths: [fixture('include-files')]
238       }, function(error, result) {
239         assert.deepEqual(result.stats.includedFiles, expected);
240         done();
241       });
242     });
243
244     it('should render with indentWidth and indentType options', function(done) {
245       sass.render({
246         data: 'div { color: transparent; }',
247         indentWidth: 7,
248         indentType: 'tab'
249       }, function(error, result) {
250         assert.equal(result.css.toString().trim(), 'div {\n\t\t\t\t\t\t\tcolor: transparent; }');
251         done();
252       });
253     });
254
255     it('should render with linefeed option', function(done) {
256       sass.render({
257         data: 'div { color: transparent; }',
258         linefeed: 'lfcr'
259       }, function(error, result) {
260         assert.equal(result.css.toString().trim(), 'div {\n\r  color: transparent; }');
261         done();
262       });
263     });
264   });
265
266   describe('.render(importer)', function() {
267     var src = read(fixture('include-files/index.scss'), 'utf8');
268
269     it('should respect the order of chained imports when using custom importers and one file is custom imported and the other is not.', function(done) {
270       sass.render({
271         file: fixture('include-files/chained-imports-with-custom-importer.scss'),
272         importer: function(url, prev, done) {
273           // NOTE: to see that this test failure is only due to the stated
274           // issue do each of the following and see that the tests pass.
275           //
276           //   a) add `return sass.NULL;` as the first line in this function to
277           //      cause non-custom importers to always be used.
278           //   b) comment out the conditional below to force our custom
279           //      importer to always be used.
280           //
281           //  You will notice that the tests pass when either all native, or
282           //  all custom importers are used, but not when a native + custom
283           //  import chain is used.
284           if (url !== 'file-processed-by-loader') {
285             return sass.NULL;
286           }
287           done({
288             file: fixture('include-files/' + url + '.scss')
289           });
290         }
291       }, function(err, data) {
292         assert.equal(err, null);
293
294         assert.equal(
295           data.css.toString().trim(),
296           'body {\n  color: "red"; }'
297         );
298
299         done();
300       });
301     });
302
303     it('should still call the next importer with the resolved prev path when the previous importer returned both a file and contents property - issue #1219', function(done) {
304       sass.render({
305         data: '@import "a";',
306         importer: function(url, prev, done) {
307           if (url === 'a') {
308             done({
309               file: '/Users/me/sass/lib/a.scss',
310               contents: '@import "b"'
311             });
312           } else {
313             console.log(prev);
314             assert.equal(prev, '/Users/me/sass/lib/a.scss');
315             done({
316               file: '/Users/me/sass/lib/b.scss',
317               contents: 'div {color: yellow;}'
318             });
319           }
320         }
321       }, function() {
322         done();
323       });
324     });
325
326     it('should override imports with "data" as input and fires callback with file and contents', function(done) {
327       sass.render({
328         data: src,
329         importer: function(url, prev, done) {
330           done({
331             file: '/some/other/path.scss',
332             contents: 'div {color: yellow;}'
333           });
334         }
335       }, function(error, result) {
336         assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
337         done();
338       });
339     });
340
341     it('should should resolve imports depth first', function (done) {
342       var actualImportOrder = [];
343       var expectedImportOrder = [
344         'a', '_common', 'vars', 'struct', 'a1', 'common', 'vars', 'struct', 'b', 'b1'
345       ];
346       var expected = read(fixture('depth-first/expected.css'));
347
348       sass.render({
349         file: fixture('depth-first/index.scss'),
350         importer: function (url, prev, done) {
351           actualImportOrder.push(url);
352           done();
353         }
354       }, function(error, result) {
355         assert.equal(result.css.toString().trim(), expected);
356         assert.deepEqual(actualImportOrder, expectedImportOrder);
357         done();
358       });
359     });
360
361     it('should override imports with "file" as input and fires callback with file and contents', function(done) {
362       sass.render({
363         file: fixture('include-files/index.scss'),
364         importer: function(url, prev, done) {
365           done({
366             file: '/some/other/path.scss',
367             contents: 'div {color: yellow;}'
368           });
369         }
370       }, function(error, result) {
371         assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
372         done();
373       });
374     });
375
376     it('should override imports with "data" as input and returns file and contents', function(done) {
377       sass.render({
378         data: src,
379         importer: function(url, prev) {
380           return {
381             file: prev + url,
382             contents: 'div {color: yellow;}'
383           };
384         }
385       }, function(error, result) {
386         assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
387         done();
388       });
389     });
390
391     it('should override imports with "file" as input and returns file and contents', function(done) {
392       sass.render({
393         file: fixture('include-files/index.scss'),
394         importer: function(url, prev) {
395           return {
396             file: prev + url,
397             contents: 'div {color: yellow;}'
398           };
399         }
400       }, function(error, result) {
401         assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
402         done();
403       });
404     });
405
406     it('should override imports with "data" as input and fires callback with file', function(done) {
407       sass.render({
408         data: src,
409         importer: function(url, /* jshint unused:false */ prev, done) {
410           done({
411             file: path.resolve(path.dirname(fixture('include-files/index.scss')), url + (path.extname(url) ? '' : '.scss'))
412           });
413         }
414       }, function(error, result) {
415         assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
416         done();
417       });
418     });
419
420     it('should override imports with "file" as input and fires callback with file', function(done) {
421       sass.render({
422         file: fixture('include-files/index.scss'),
423         importer: function(url, prev, done) {
424           done({
425             file: path.resolve(path.dirname(prev), url + (path.extname(url) ? '' : '.scss'))
426           });
427         }
428       }, function(error, result) {
429         assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
430         done();
431       });
432     });
433
434     it('should override imports with "data" as input and returns file', function(done) {
435       sass.render({
436         data: src,
437         importer: function(url) {
438           return {
439             file: path.resolve(path.dirname(fixture('include-files/index.scss')), url + (path.extname(url) ? '' : '.scss'))
440           };
441         }
442       }, function(error, result) {
443         assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
444         done();
445       });
446     });
447
448     it('should override imports with "file" as input and returns file', function(done) {
449       sass.render({
450         file: fixture('include-files/index.scss'),
451         importer: function(url, prev) {
452           return {
453             file: path.resolve(path.dirname(prev), url + (path.extname(url) ? '' : '.scss'))
454           };
455         }
456       }, function(error, result) {
457         assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
458         done();
459       });
460     });
461
462     it('should fallback to default import behaviour if importer returns sass.NULL', function(done) {
463       sass.render({
464         file: fixture('include-files/index.scss'),
465         importer: function(url, prev, done) {
466           done(sass.NULL);
467         }
468       }, function(error, result) {
469         assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
470         done();
471       });
472     });
473
474     it('should fallback to default import behaviour if importer returns null for backwards compatibility', function(done) {
475       sass.render({
476         file: fixture('include-files/index.scss'),
477         importer: function(url, prev, done) {
478           done(null);
479         }
480       }, function(error, result) {
481         assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
482         done();
483       });
484     });
485
486     it('should fallback to default import behaviour if importer returns undefined for backwards compatibility', function(done) {
487       sass.render({
488         file: fixture('include-files/index.scss'),
489         importer: function(url, prev, done) {
490           done(undefined);
491         }
492       }, function(error, result) {
493         assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
494         done();
495       });
496     });
497
498     it('should fallback to default import behaviour if importer returns false for backwards compatibility', function(done) {
499       sass.render({
500         file: fixture('include-files/index.scss'),
501         importer: function(url, prev, done) {
502           done(false);
503         }
504       }, function(error, result) {
505         assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
506         done();
507       });
508     });
509
510     it('should override imports with "data" as input and fires callback with contents', function(done) {
511       sass.render({
512         data: src,
513         importer: function(url, prev, done) {
514           done({
515             contents: 'div {color: yellow;}'
516           });
517         }
518       }, function(error, result) {
519         assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
520         done();
521       });
522     });
523
524     it('should override imports with "file" as input and fires callback with contents', function(done) {
525       sass.render({
526         file: fixture('include-files/index.scss'),
527         importer: function(url, prev, done) {
528           done({
529             contents: 'div {color: yellow;}'
530           });
531         }
532       }, function(error, result) {
533         assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
534         done();
535       });
536     });
537
538     it('should override imports with "data" as input and returns contents', function(done) {
539       sass.render({
540         data: src,
541         importer: function() {
542           return {
543             contents: 'div {color: yellow;}'
544           };
545         }
546       }, function(error, result) {
547         assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
548         done();
549       });
550     });
551
552     it('should override imports with "file" as input and returns contents', function(done) {
553       sass.render({
554         file: fixture('include-files/index.scss'),
555         importer: function() {
556           return {
557             contents: 'div {color: yellow;}'
558           };
559         }
560       }, function(error, result) {
561         assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
562         done();
563       });
564     });
565
566     it('should accept arrays of importers and return respect the order', function(done) {
567       sass.render({
568         file: fixture('include-files/index.scss'),
569         importer: [
570           function() {
571             return sass.NULL;
572           },
573           function() {
574             return {
575               contents: 'div {color: yellow;}'
576             };
577           }
578         ]
579       }, function(error, result) {
580         assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
581         done();
582       });
583     });
584
585     it('should be able to see its options in this.options', function(done) {
586       var fxt = fixture('include-files/index.scss');
587       sass.render({
588         file: fxt,
589         importer: function() {
590           assert.equal(fxt, this.options.file);
591           return {};
592         }
593       }, function() {
594         assert.equal(fxt, this.options.file);
595         done();
596       });
597     });
598
599     it('should be able to access a persistent options object', function(done) {
600       sass.render({
601         data: src,
602         importer: function() {
603           this.state = this.state || 0;
604           this.state++;
605           return {
606             contents: 'div {color: yellow;}'
607           };
608         }
609       }, function() {
610         assert.equal(this.state, 2);
611         done();
612       });
613     });
614
615     it('should wrap importer options', function(done) {
616       var options;
617       options = {
618         data: src,
619         importer: function() {
620           assert.notStrictEqual(this.options.importer, options.importer);
621           return {
622             contents: 'div {color: yellow;}'
623           };
624         }
625       };
626       sass.render(options, function() {
627         done();
628       });
629     });
630
631     it('should reflect user-defined error when returned as callback', function(done) {
632       sass.render({
633         data: src,
634         importer: function(url, prev, done) {
635           done(new Error('doesn\'t exist!'));
636         }
637       }, function(error) {
638         assert(/doesn\'t exist!/.test(error.message));
639         done();
640       });
641     });
642
643     it('should reflect user-defined error with return', function(done) {
644       sass.render({
645         data: src,
646         importer: function() {
647           return new Error('doesn\'t exist!');
648         }
649       }, function(error) {
650         assert(/doesn\'t exist!/.test(error.message));
651         done();
652       });
653     });
654
655     it('should throw exception when importer returns an invalid value', function(done) {
656       sass.render({
657         data: src,
658         importer: function() {
659           return { contents: new Buffer('i am not a string!') };
660         }
661       }, function(error) {
662         assert(/returned value of `contents` must be a string/.test(error.message));
663         done();
664       });
665     });
666   });
667
668   describe('.render(functions)', function() {
669     it('should call custom defined nullary function', function(done) {
670       sass.render({
671         data: 'div { color: foo(); }',
672         functions: {
673           'foo()': function() {
674             return new sass.types.Number(42, 'px');
675           }
676         }
677       }, function(error, result) {
678         assert.equal(result.css.toString().trim(), 'div {\n  color: 42px; }');
679         done();
680       });
681     });
682
683     it('should call custom function with multiple args', function(done) {
684       sass.render({
685         data: 'div { color: foo(3, 42px); }',
686         functions: {
687           'foo($a, $b)': function(factor, size) {
688             return new sass.types.Number(factor.getValue() * size.getValue(), size.getUnit());
689           }
690         }
691       }, function(error, result) {
692         assert.equal(result.css.toString().trim(), 'div {\n  color: 126px; }');
693         done();
694       });
695     });
696
697     it('should work with custom functions that return data asynchronously', function(done) {
698       sass.render({
699         data: 'div { color: foo(42px); }',
700         functions: {
701           'foo($a)': function(size, done) {
702             setTimeout(function() {
703               done(new sass.types.Number(66, 'em'));
704             }, 50);
705           }
706         }
707       }, function(error, result) {
708         assert.equal(result.css.toString().trim(), 'div {\n  color: 66em; }');
709         done();
710       });
711     });
712
713     it('should let custom functions call setter methods on wrapped sass values (number)', function(done) {
714       sass.render({
715         data: 'div { width: foo(42px); height: bar(42px); }',
716         functions: {
717           'foo($a)': function(size) {
718             size.setUnit('rem');
719             return size;
720           },
721           'bar($a)': function(size) {
722             size.setValue(size.getValue() * 2);
723             return size;
724           }
725         }
726       }, function(error, result) {
727         assert.equal(result.css.toString().trim(), 'div {\n  width: 42rem;\n  height: 84px; }');
728         done();
729       });
730     });
731
732     it('should properly convert strings when calling custom functions', function(done) {
733       sass.render({
734         data: 'div { color: foo("bar"); }',
735         functions: {
736           'foo($a)': function(str) {
737             str = str.getValue().replace(/['"]/g, '');
738             return new sass.types.String('"' + str + str + '"');
739           }
740         }
741       }, function(error, result) {
742         assert.equal(result.css.toString().trim(), 'div {\n  color: "barbar"; }');
743         done();
744       });
745     });
746
747     it('should let custom functions call setter methods on wrapped sass values (string)', function(done) {
748       sass.render({
749         data: 'div { width: foo("bar"); }',
750         functions: {
751           'foo($a)': function(str) {
752             var unquoted = str.getValue().replace(/['"]/g, '');
753             str.setValue('"' + unquoted + unquoted + '"');
754             return str;
755           }
756         }
757       }, function(error, result) {
758         assert.equal(result.css.toString().trim(), 'div {\n  width: "barbar"; }');
759         done();
760       });
761     });
762
763     it('should properly convert colors when calling custom functions', function(done) {
764       sass.render({
765         data: 'div { color: foo(#f00); background-color: bar(); border-color: baz(); }',
766         functions: {
767           'foo($a)': function(color) {
768             assert.equal(color.getR(), 255);
769             assert.equal(color.getG(), 0);
770             assert.equal(color.getB(), 0);
771             assert.equal(color.getA(), 1.0);
772
773             return new sass.types.Color(255, 255, 0, 0.5);
774           },
775           'bar()': function() {
776             return new sass.types.Color(0x33ff00ff);
777           },
778           'baz()': function() {
779             return new sass.types.Color(0xffff0000);
780           }
781         }
782       }, function(error, result) {
783         assert.equal(
784           result.css.toString().trim(),
785           'div {\n  color: rgba(255, 255, 0, 0.5);' +
786           '\n  background-color: rgba(255, 0, 255, 0.2);' +
787           '\n  border-color: red; }'
788         );
789         done();
790       });
791     });
792
793     it('should properly convert boolean when calling custom functions', function(done) {
794       sass.render({
795         data: 'div { color: if(foo(true, false), #fff, #000);' +
796           '\n  background-color: if(foo(true, true), #fff, #000); }',
797         functions: {
798           'foo($a, $b)': function(a, b) {
799             return sass.types.Boolean(a.getValue() && b.getValue());
800           }
801         }
802       }, function(error, result) {
803         assert.equal(result.css.toString().trim(), 'div {\n  color: #000;\n  background-color: #fff; }');
804         done();
805       });
806     });
807
808     it('should let custom functions call setter methods on wrapped sass values (boolean)', function(done) {
809       sass.render({
810         data: 'div { color: if(foo(false), #fff, #000); background-color: if(foo(true), #fff, #000); }',
811         functions: {
812           'foo($a)': function(a) {
813             return a.getValue() ? sass.types.Boolean.FALSE : sass.types.Boolean.TRUE;
814           }
815         }
816       }, function(error, result) {
817         assert.equal(result.css.toString().trim(), 'div {\n  color: #fff;\n  background-color: #000; }');
818         done();
819       });
820     });
821
822     it('should properly convert lists when calling custom functions', function(done) {
823       sass.render({
824         data: '$test-list: (bar, #f00, 123em); @each $item in foo($test-list) { .#{$item} { color: #fff; } }',
825         functions: {
826           'foo($l)': function(list) {
827             assert.equal(list.getLength(), 3);
828             assert.ok(list.getValue(0) instanceof sass.types.String);
829             assert.equal(list.getValue(0).getValue(), 'bar');
830             assert.ok(list.getValue(1) instanceof sass.types.Color);
831             assert.equal(list.getValue(1).getR(), 0xff);
832             assert.equal(list.getValue(1).getG(), 0);
833             assert.equal(list.getValue(1).getB(), 0);
834             assert.ok(list.getValue(2) instanceof sass.types.Number);
835             assert.equal(list.getValue(2).getValue(), 123);
836             assert.equal(list.getValue(2).getUnit(), 'em');
837
838             var out = new sass.types.List(3);
839             out.setValue(0, new sass.types.String('foo'));
840             out.setValue(1, new sass.types.String('bar'));
841             out.setValue(2, new sass.types.String('baz'));
842             return out;
843           }
844         }
845       }, function(error, result) {
846         assert.equal(
847           result.css.toString().trim(),
848           '.foo {\n  color: #fff; }\n\n.bar {\n  color: #fff; }\n\n.baz {\n  color: #fff; }'
849         );
850         done();
851       });
852     });
853
854     it('should properly convert maps when calling custom functions', function(done) {
855       sass.render({
856         data: '$test-map: foo((abc: 123, #def: true)); div { color: if(map-has-key($test-map, hello), #fff, #000); }' +
857           'span { color: map-get($test-map, baz); }',
858         functions: {
859           'foo($m)': function(map) {
860             assert.equal(map.getLength(), 2);
861             assert.ok(map.getKey(0) instanceof sass.types.String);
862             assert.ok(map.getKey(1) instanceof sass.types.Color);
863             assert.ok(map.getValue(0) instanceof sass.types.Number);
864             assert.ok(map.getValue(1) instanceof sass.types.Boolean);
865             assert.equal(map.getKey(0).getValue(), 'abc');
866             assert.equal(map.getValue(0).getValue(), 123);
867             assert.equal(map.getKey(1).getR(), 0xdd);
868             assert.equal(map.getValue(1).getValue(), true);
869
870             var out = new sass.types.Map(3);
871             out.setKey(0, new sass.types.String('hello'));
872             out.setValue(0, new sass.types.String('world'));
873             out.setKey(1, new sass.types.String('foo'));
874             out.setValue(1, new sass.types.String('bar'));
875             out.setKey(2, new sass.types.String('baz'));
876             out.setValue(2, new sass.types.String('qux'));
877             return out;
878           }
879         }
880       }, function(error, result) {
881         assert.equal(result.css.toString().trim(), 'div {\n  color: #fff; }\n\nspan {\n  color: qux; }');
882         done();
883       });
884     });
885
886     it('should properly convert null when calling custom functions', function(done) {
887       sass.render({
888         data: 'div { color: if(foo("bar"), #fff, #000); } ' +
889           'span { color: if(foo(null), #fff, #000); }' +
890           'table { color: if(bar() == null, #fff, #000); }',
891         functions: {
892           'foo($a)': function(a) {
893             return sass.types.Boolean(a instanceof sass.types.Null);
894           },
895           'bar()': function() {
896             return sass.NULL;
897           }
898         }
899       }, function(error, result) {
900         assert.equal(
901           result.css.toString().trim(),
902           'div {\n  color: #000; }\n\nspan {\n  color: #fff; }\n\ntable {\n  color: #fff; }'
903         );
904         done();
905       });
906     });
907
908     it('should be possible to carry sass values across different renders', function(done) {
909       var persistentMap;
910
911       sass.render({
912         data: 'div { color: foo((abc: #112233, #ddeeff: true)); }',
913         functions: {
914           foo: function(m) {
915             persistentMap = m;
916             return sass.types.Color(0, 0, 0);
917           }
918         }
919       }, function() {
920         sass.render({
921           data: 'div { color: map-get(bar(), abc); background-color: baz(); }',
922           functions: {
923             bar: function() {
924               return persistentMap;
925             },
926             baz: function() {
927               return persistentMap.getKey(1);
928             }
929           }
930         }, function(errror, result) {
931           assert.equal(result.css.toString().trim(), 'div {\n  color: #112233;\n  background-color: #ddeeff; }');
932           done();
933         });
934       });
935     });
936
937     it('should let us register custom functions without signatures', function(done) {
938       sass.render({
939         data: 'div { color: foo(20, 22); }',
940         functions: {
941           foo: function(a, b) {
942             return new sass.types.Number(a.getValue() + b.getValue(), 'em');
943           }
944         }
945       }, function(error, result) {
946         assert.equal(result.css.toString().trim(), 'div {\n  color: 42em; }');
947         done();
948       });
949     });
950
951     it('should fail when returning anything other than a sass value from a custom function', function(done) {
952       sass.render({
953         data: 'div { color: foo(); }',
954         functions: {
955           'foo()': function() {
956             return {};
957           }
958         }
959       }, function(error) {
960         assert.ok(/A SassValue object was expected/.test(error.message));
961         done();
962       });
963     });
964
965     it('should properly bubble up standard JS errors thrown by custom functions', function(done) {
966       sass.render({
967         data: 'div { color: foo(); }',
968         functions: {
969           'foo()': function() {
970             throw new RangeError('This is a test error');
971           }
972         }
973       }, function(error) {
974         assert.ok(/This is a test error/.test(error.message));
975         done();
976       });
977     });
978
979     it('should properly bubble up unknown errors thrown by custom functions', function(done) {
980       sass.render({
981         data: 'div { color: foo(); }',
982         functions: {
983           'foo()': function() {
984             throw {};
985           }
986         }
987       }, function(error) {
988         assert.ok(/unexpected error/.test(error.message));
989         done();
990       });
991     });
992
993     it('should call custom functions with correct context', function(done) {
994       function assertExpected(result) {
995         assert.equal(result.css.toString().trim(), 'div {\n  foo1: 1;\n  foo2: 2; }');
996       }
997       var options = {
998         data: 'div { foo1: foo(); foo2: foo(); }',
999         functions: {
1000           // foo() is stateful and will persist an incrementing counter
1001           'foo()': function() {
1002             assert(this);
1003             this.fooCounter = (this.fooCounter || 0) + 1;
1004             return new sass.types.Number(this.fooCounter);
1005           }
1006         }
1007       };
1008
1009       sass.render(options, function(error, result) {
1010         assertExpected(result);
1011         done();
1012       });
1013     });
1014
1015     describe('should properly bubble up errors from sass color constructor', function() {
1016       it('four booleans', function(done) {
1017         sass.render({
1018           data: 'div { color: foo(); }',
1019           functions: {
1020             'foo()': function() {
1021               return new sass.types.Color(false, false, false, false);
1022             }
1023           }
1024         }, function(error) {
1025           assert.ok(/Constructor arguments should be numbers exclusively/.test(error.message));
1026           done();
1027         });
1028       });
1029
1030       it('two arguments', function(done) {
1031         sass.render({
1032           data: 'div { color: foo(); }',
1033           functions: {
1034             'foo()': function() {
1035               return sass.types.Color(2,3);
1036             }
1037           }
1038         }, function(error) {
1039           assert.ok(/Constructor should be invoked with either 0, 1, 3 or 4 arguments/.test(error.message));
1040           done();
1041         });
1042       });
1043
1044       it('single string argument', function(done) {
1045         sass.render({
1046           data: 'div { color: foo(); }',
1047           functions: {
1048             'foo()': function() {
1049               return sass.types.Color('foo');
1050             }
1051           }
1052         }, function(error) {
1053           assert.ok(/Only argument should be an integer/.test(error.message));
1054           done();
1055         });
1056       });
1057     });
1058
1059     it('should properly bubble up errors from sass value constructors', function(done) {
1060       sass.render({
1061         data: 'div { color: foo(); }',
1062         functions: {
1063           'foo()': function() {
1064             return sass.types.Boolean('foo');
1065           }
1066         }
1067       }, function(error) {
1068         assert.ok(/Expected one boolean argument/.test(error.message));
1069         done();
1070       });
1071     });
1072
1073     it('should properly bubble up errors from sass value setters', function(done) {
1074       sass.render({
1075         data: 'div { color: foo(); }',
1076         functions: {
1077           'foo()': function() {
1078             var ret = new sass.types.Number(42);
1079             ret.setUnit(123);
1080             return ret;
1081           }
1082         }
1083       }, function(error) {
1084         assert.ok(/Supplied value should be a string/.test(error.message));
1085         done();
1086       });
1087     });
1088
1089     it('should fail when trying to set a bare number as the List item', function(done) {
1090       sass.render({
1091         data: 'div { color: foo(); }',
1092         functions: {
1093           'foo()': function() {
1094             var out = new sass.types.List(1);
1095             out.setValue(0, 2);
1096             return out;
1097           }
1098         }
1099       }, function(error) {
1100         assert.ok(/Supplied value should be a SassValue object/.test(error.message));
1101         done();
1102       });
1103     });
1104
1105     it('should fail when trying to set a bare Object as the List item', function(done) {
1106       sass.render({
1107         data: 'div { color: foo(); }',
1108         functions: {
1109           'foo()': function() {
1110             var out = new sass.types.List(1);
1111             out.setValue(0, {});
1112             return out;
1113           }
1114         }
1115       }, function(error) {
1116         assert.ok(/A SassValue is expected as the list item/.test(error.message));
1117         done();
1118       });
1119     });
1120
1121     it('should fail when trying to set a bare Object as the Map key', function(done) {
1122       sass.render({
1123         data: 'div { color: foo(); }',
1124         functions: {
1125           'foo()': function() {
1126             var out = new sass.types.Map(1);
1127             out.setKey(0, {});
1128             out.setValue(0, new sass.types.String('aaa'));
1129             return out;
1130           }
1131         }
1132       }, function(error) {
1133         assert.ok(/A SassValue is expected as a map key/.test(error.message));
1134         done();
1135       });
1136     });
1137
1138     it('should fail when trying to set a bare Object as the Map value', function(done) {
1139       sass.render({
1140         data: 'div { color: foo(); }',
1141         functions: {
1142           'foo()': function() {
1143             var out = new sass.types.Map(1);
1144             out.setKey(0, new sass.types.String('aaa'));
1145             out.setValue(0, {});
1146             return out;
1147           }
1148         }
1149       }, function(error) {
1150         assert.ok(/A SassValue is expected as a map value/.test(error.message));
1151         done();
1152       });
1153     });
1154
1155     it('should always map null, true and false to the same (immutable) object', function(done) {
1156       var counter = 0;
1157
1158       sass.render({
1159         data: 'div { color: foo(bar(null)); background-color: baz("foo" == "bar"); }',
1160         functions: {
1161           foo: function(a) {
1162             assert.strictEqual(a, sass.TRUE,
1163               'Supplied value should be the same instance as sass.TRUE'
1164             );
1165
1166             assert.strictEqual(
1167               sass.types.Boolean(true), sass.types.Boolean(true),
1168               'sass.types.Boolean(true) should return a singleton');
1169
1170             assert.strictEqual(
1171               sass.types.Boolean(true), sass.TRUE,
1172               'sass.types.Boolean(true) should be the same instance as sass.TRUE');
1173
1174             counter++;
1175
1176             return sass.types.String('foo');
1177           },
1178           bar: function(a) {
1179             assert.strictEqual(a, sass.NULL,
1180                 'Supplied value should be the same instance as sass.NULL');
1181
1182             assert.throws(function() {
1183               return new sass.types.Null();
1184             }, /Cannot instantiate SassNull/);
1185
1186             counter++;
1187
1188             return sass.TRUE;
1189           },
1190           baz: function(a) {
1191             assert.strictEqual(a, sass.FALSE,
1192               'Supplied value should be the same instance as sass.FALSE');
1193
1194             assert.throws(function() {
1195               return new sass.types.Boolean(false);
1196             }, /Cannot instantiate SassBoolean/);
1197
1198             assert.strictEqual(
1199               sass.types.Boolean(false), sass.types.Boolean(false),
1200               'sass.types.Boolean(false) should return a singleton');
1201
1202             assert.strictEqual(
1203               sass.types.Boolean(false), sass.FALSE,
1204               'sass.types.Boolean(false) should return singleton identical to sass.FALSE');
1205
1206             counter++;
1207
1208             return sass.types.String('baz');
1209           }
1210         }
1211       }, function() {
1212         assert.strictEqual(counter, 3);
1213         done();
1214       });
1215     });
1216   });
1217
1218   describe('.render({stats: {}})', function() {
1219     var start = Date.now();
1220
1221     it('should provide a start timestamp', function(done) {
1222       sass.render({
1223         file: fixture('include-files/index.scss')
1224       }, function(error, result) {
1225         assert(!error);
1226         assert.strictEqual(typeof result.stats.start, 'number');
1227         assert(result.stats.start >= start);
1228         done();
1229       });
1230     });
1231
1232     it('should provide an end timestamp', function(done) {
1233       sass.render({
1234         file: fixture('include-files/index.scss')
1235       }, function(error, result) {
1236         assert(!error);
1237         assert.strictEqual(typeof result.stats.end, 'number');
1238         assert(result.stats.end >= result.stats.start);
1239         done();
1240       });
1241     });
1242
1243     it('should provide a duration', function(done) {
1244       sass.render({
1245         file: fixture('include-files/index.scss')
1246       }, function(error, result) {
1247         assert(!error);
1248         assert.strictEqual(typeof result.stats.duration, 'number');
1249         assert.equal(result.stats.end - result.stats.start, result.stats.duration);
1250         done();
1251       });
1252     });
1253
1254     it('should contain the given entry file', function(done) {
1255       sass.render({
1256         file: fixture('include-files/index.scss')
1257       }, function(error, result) {
1258         assert(!error);
1259         assert.equal(result.stats.entry, fixture('include-files/index.scss'));
1260         done();
1261       });
1262     });
1263
1264     it('should contain an array of all included files', function(done) {
1265       var expected = [
1266         fixture('include-files/bar.scss').replace(/\\/g, '/'),
1267         fixture('include-files/foo.scss').replace(/\\/g, '/'),
1268         fixture('include-files/index.scss').replace(/\\/g, '/')
1269       ];
1270
1271       sass.render({
1272         file: fixture('include-files/index.scss')
1273       }, function(error, result) {
1274         assert(!error);
1275         assert.deepEqual(result.stats.includedFiles.sort(), expected.sort());
1276         done();
1277       });
1278     });
1279
1280     it('should contain array with the entry if there are no import statements', function(done) {
1281       var expected = fixture('simple/index.scss').replace(/\\/g, '/');
1282
1283       sass.render({
1284         file: fixture('simple/index.scss')
1285       }, function(error, result) {
1286         assert.deepEqual(result.stats.includedFiles, [expected]);
1287         done();
1288       });
1289     });
1290
1291     it('should state `data` as entry file', function(done) {
1292       sass.render({
1293         data: read(fixture('simple/index.scss'), 'utf8')
1294       }, function(error, result) {
1295         assert.equal(result.stats.entry, 'data');
1296         done();
1297       });
1298     });
1299
1300     it('should contain an empty array as includedFiles', function(done) {
1301       sass.render({
1302         data: read(fixture('simple/index.scss'), 'utf8')
1303       }, function(error, result) {
1304         assert.deepEqual(result.stats.includedFiles, []);
1305         done();
1306       });
1307     });
1308   });
1309
1310   describe('.renderSync(options)', function() {
1311     it('should compile sass to css with file', function(done) {
1312       var expected = read(fixture('simple/expected.css'), 'utf8').trim();
1313       var result = sass.renderSync({ file: fixture('simple/index.scss') });
1314
1315       assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n'));
1316       done();
1317     });
1318
1319     it('should compile sass to css with outFile set to absolute url', function(done) {
1320       var result = sass.renderSync({
1321         file: fixture('simple/index.scss'),
1322         sourceMap: true,
1323         outFile: fixture('simple/index-test.css')
1324       });
1325
1326       assert.equal(JSON.parse(result.map).file, 'index-test.css');
1327       done();
1328     });
1329
1330     it('should compile sass to css with outFile set to relative url', function(done) {
1331       var result = sass.renderSync({
1332         file: fixture('simple/index.scss'),
1333         sourceMap: true,
1334         outFile: './index-test.css'
1335       });
1336
1337       assert.equal(JSON.parse(result.map).file, 'index-test.css');
1338       done();
1339     });
1340
1341     it('should compile sass to css with outFile and sourceMap set to relative url', function(done) {
1342       var result = sass.renderSync({
1343         file: fixture('simple/index.scss'),
1344         sourceMap: './deep/nested/index.map',
1345         outFile: './index-test.css'
1346       });
1347
1348       assert.equal(JSON.parse(result.map).file, '../../index-test.css');
1349       done();
1350     });
1351
1352     it('should compile generate map with sourceMapRoot pass-through option', function(done) {
1353       var result = sass.renderSync({
1354         file: fixture('simple/index.scss'),
1355         sourceMap: './deep/nested/index.map',
1356         sourceMapRoot: 'http://test.com/',
1357         outFile: './index-test.css'
1358       });
1359
1360       assert.equal(JSON.parse(result.map).sourceRoot, 'http://test.com/');
1361       done();
1362     });
1363
1364     it('should compile sass to css with data', function(done) {
1365       var src = read(fixture('simple/index.scss'), 'utf8');
1366       var expected = read(fixture('simple/expected.css'), 'utf8').trim();
1367       var result = sass.renderSync({ data: src });
1368
1369       assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n'));
1370       done();
1371     });
1372
1373     it('should compile sass to css using indented syntax', function(done) {
1374       var src = read(fixture('indent/index.sass'), 'utf8');
1375       var expected = read(fixture('indent/expected.css'), 'utf8').trim();
1376       var result = sass.renderSync({
1377         data: src,
1378         indentedSyntax: true
1379       });
1380
1381       assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n'));
1382       done();
1383     });
1384
1385     it('should NOT compile empty data string', function(done) {
1386       assert.throws(function() {
1387         sass.renderSync({ data: '' });
1388       }, /No input specified: provide a file name or a source string to process/ );
1389       done();
1390     });
1391
1392     it('should NOT compile without any input', function(done) {
1393       assert.throws(function() {
1394         sass.renderSync({});
1395       }, /No input specified: provide a file name or a source string to process/);
1396       done();
1397     });
1398
1399     it('should throw error for bad input', function(done) {
1400       assert.throws(function() {
1401         sass.renderSync('somestring');
1402       });
1403       assert.throws(function() {
1404         sass.renderSync({ data: '#navbar width 80%;' });
1405       });
1406
1407       done();
1408     });
1409
1410     it('should compile with include paths', function(done) {
1411       var src = read(fixture('include-path/index.scss'), 'utf8');
1412       var expected = read(fixture('include-path/expected.css'), 'utf8').trim();
1413       var result = sass.renderSync({
1414         data: src,
1415         includePaths: [
1416           fixture('include-path/functions'),
1417           fixture('include-path/lib')
1418         ]
1419       });
1420
1421       assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n'));
1422       done();
1423     });
1424
1425     it('should add cwd to the front on include paths', function(done) {
1426       var src = fixture('cwd-include-path/root/index.scss');
1427       var expected = read(fixture('cwd-include-path/expected.css'), 'utf8').trim();
1428       var cwd = process.cwd();
1429
1430       process.chdir(fixture('cwd-include-path'));
1431       var result = sass.renderSync({
1432         file: src,
1433         includePaths: [
1434           fixture('include-path/functions'),
1435           fixture('include-path/lib')
1436         ]
1437       });
1438       process.chdir(cwd);
1439
1440       assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n'));
1441       done();
1442     });
1443
1444     it('should check SASS_PATH in the specified order', function(done) {
1445       var src = read(fixture('sass-path/index.scss'), 'utf8');
1446       var expectedRed = read(fixture('sass-path/expected-red.css'), 'utf8').trim();
1447       var expectedOrange = read(fixture('sass-path/expected-orange.css'), 'utf8').trim();
1448
1449       var envIncludes = [
1450         fixture('sass-path/red'),
1451         fixture('sass-path/orange')
1452       ];
1453
1454       process.env.SASS_PATH = envIncludes.join(path.delimiter);
1455       var result = sass.renderSync({
1456         data: src,
1457         includePaths: []
1458       });
1459
1460       assert.equal(result.css.toString().trim(), expectedRed.replace(/\r\n/g, '\n'));
1461
1462       process.env.SASS_PATH = envIncludes.reverse().join(path.delimiter);
1463       result = sass.renderSync({
1464         data: src,
1465         includePaths: []
1466       });
1467
1468       assert.equal(result.css.toString().trim(), expectedOrange.replace(/\r\n/g, '\n'));
1469       done();
1470     });
1471
1472     it('should prefer include path over SASS_PATH', function(done) {
1473       var src = read(fixture('sass-path/index.scss'), 'utf8');
1474       var expectedRed = read(fixture('sass-path/expected-red.css'), 'utf8').trim();
1475       var expectedOrange = read(fixture('sass-path/expected-orange.css'), 'utf8').trim();
1476
1477       var envIncludes = [
1478         fixture('sass-path/red')
1479       ];
1480       process.env.SASS_PATH = envIncludes.join(path.delimiter);
1481
1482       var result = sass.renderSync({
1483         data: src,
1484         includePaths: []
1485       });
1486
1487       assert.equal(result.css.toString().trim(), expectedRed.replace(/\r\n/g, '\n'));
1488
1489       result = sass.renderSync({
1490         data: src,
1491         includePaths: [fixture('sass-path/orange')]
1492       });
1493
1494       assert.equal(result.css.toString().trim(), expectedOrange.replace(/\r\n/g, '\n'));
1495       done();
1496     });
1497
1498     it('should render with precision option', function(done) {
1499       var src = read(fixture('precision/index.scss'), 'utf8');
1500       var expected = read(fixture('precision/expected.css'), 'utf8').trim();
1501       var result = sass.renderSync({
1502         data: src,
1503         precision: 10
1504       });
1505
1506       assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n'));
1507       done();
1508     });
1509
1510     it('should contain all included files in stats when data is passed', function(done) {
1511       var src = read(fixture('include-files/index.scss'), 'utf8');
1512       var expected = [
1513         fixture('include-files/bar.scss').replace(/\\/g, '/'),
1514         fixture('include-files/foo.scss').replace(/\\/g, '/')
1515       ];
1516
1517       var result = sass.renderSync({
1518         data: src,
1519         includePaths: [fixture('include-files')]
1520       });
1521
1522       assert.deepEqual(result.stats.includedFiles, expected);
1523       done();
1524     });
1525
1526     it('should render with indentWidth and indentType options', function(done) {
1527       var result = sass.renderSync({
1528         data: 'div { color: transparent; }',
1529         indentWidth: 7,
1530         indentType: 'tab'
1531       });
1532
1533       assert.equal(result.css.toString().trim(), 'div {\n\t\t\t\t\t\t\tcolor: transparent; }');
1534       done();
1535     });
1536
1537     it('should render with linefeed option', function(done) {
1538       var result = sass.renderSync({
1539         data: 'div { color: transparent; }',
1540         linefeed: 'lfcr'
1541       });
1542
1543       assert.equal(result.css.toString().trim(), 'div {\n\r  color: transparent; }');
1544       done();
1545     });
1546   });
1547
1548   describe('.renderSync(importer)', function() {
1549     var src = read(fixture('include-files/index.scss'), 'utf8');
1550
1551     it('should override imports with "data" as input and returns file and contents', function(done) {
1552       var result = sass.renderSync({
1553         data: src,
1554         importer: function(url, prev) {
1555           return {
1556             file: prev + url,
1557             contents: 'div {color: yellow;}'
1558           };
1559         }
1560       });
1561
1562       assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
1563       done();
1564     });
1565
1566     it('should override imports with "file" as input and returns file and contents', function(done) {
1567       var result = sass.renderSync({
1568         file: fixture('include-files/index.scss'),
1569         importer: function(url, prev) {
1570           return {
1571             file: prev + url,
1572             contents: 'div {color: yellow;}'
1573           };
1574         }
1575       });
1576
1577       assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
1578       done();
1579     });
1580
1581     it('should override imports with "data" as input and returns file', function(done) {
1582       var result = sass.renderSync({
1583         data: src,
1584         importer: function(url) {
1585           return {
1586             file: path.resolve(path.dirname(fixture('include-files/index.scss')), url + (path.extname(url) ? '' : '.scss'))
1587           };
1588         }
1589       });
1590
1591       assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
1592       done();
1593     });
1594
1595     it('should override imports with "file" as input and returns file', function(done) {
1596       var result = sass.renderSync({
1597         file: fixture('include-files/index.scss'),
1598         importer: function(url, prev) {
1599           return {
1600             file: path.resolve(path.dirname(prev), url + (path.extname(url) ? '' : '.scss'))
1601           };
1602         }
1603       });
1604
1605       assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
1606       done();
1607     });
1608
1609     it('should override imports with "data" as input and returns contents', function(done) {
1610       var result = sass.renderSync({
1611         data: src,
1612         importer: function() {
1613           return {
1614             contents: 'div {color: yellow;}'
1615           };
1616         }
1617       });
1618
1619       assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
1620       done();
1621     });
1622
1623     it('should override imports with "file" as input and returns contents', function(done) {
1624       var result = sass.renderSync({
1625         file: fixture('include-files/index.scss'),
1626         importer: function() {
1627           return {
1628             contents: 'div {color: yellow;}'
1629           };
1630         }
1631       });
1632
1633       assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
1634       done();
1635     });
1636
1637
1638
1639     it('should fallback to default import behaviour if importer returns sass.NULL', function(done) {
1640       var result = sass.renderSync({
1641         file: fixture('include-files/index.scss'),
1642         importer: function() {
1643           return sass.NULL;
1644         }
1645       });
1646
1647       assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
1648       done();
1649     });
1650
1651     it('should fallback to default import behaviour if importer returns null for backwards compatibility', function(done) {
1652       var result = sass.renderSync({
1653         file: fixture('include-files/index.scss'),
1654         importer: function() {
1655           return null;
1656         }
1657       });
1658
1659       assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
1660       done();
1661     });
1662
1663     it('should fallback to default import behaviour if importer returns undefined for backwards compatibility', function(done) {
1664       var result = sass.renderSync({
1665         file: fixture('include-files/index.scss'),
1666         importer: function() {
1667           return undefined;
1668         }
1669       });
1670
1671       assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
1672       done();
1673     });
1674
1675     it('should fallback to default import behaviour if importer returns false for backwards compatibility', function(done) {
1676       var result = sass.renderSync({
1677         file: fixture('include-files/index.scss'),
1678         importer: function() {
1679           return false;
1680         }
1681       });
1682
1683       assert.equal(result.css.toString().trim(), '/* foo.scss */\n/* bar.scss */');
1684       done();
1685     });
1686
1687     it('should accept arrays of importers and return respect the order', function(done) {
1688       var result = sass.renderSync({
1689         file: fixture('include-files/index.scss'),
1690         importer: [
1691           function() {
1692             return sass.NULL;
1693           },
1694           function() {
1695             return {
1696               contents: 'div {color: yellow;}'
1697             };
1698           }
1699         ]
1700       });
1701
1702       assert.equal(result.css.toString().trim(), 'div {\n  color: yellow; }\n\ndiv {\n  color: yellow; }');
1703       done();
1704     });
1705
1706     it('should be able to see its options in this.options', function(done) {
1707       var fxt = fixture('include-files/index.scss');
1708       var sync = false;
1709       sass.renderSync({
1710         file: fixture('include-files/index.scss'),
1711         importer: function() {
1712           assert.equal(fxt, this.options.file);
1713           sync = true;
1714           return {};
1715         }
1716       });
1717       assert.equal(sync, true);
1718       done();
1719     });
1720
1721     it('should throw user-defined error', function(done) {
1722       assert.throws(function() {
1723         sass.renderSync({
1724           data: src,
1725           importer: function() {
1726             return new Error('doesn\'t exist!');
1727           }
1728         });
1729       }, /doesn\'t exist!/);
1730
1731       done();
1732     });
1733
1734     it('should throw exception when importer returns an invalid value', function(done) {
1735       assert.throws(function() {
1736         sass.renderSync({
1737           data: src,
1738           importer: function() {
1739             return { contents: new Buffer('i am not a string!') };
1740           }
1741         });
1742       }, /returned value of `contents` must be a string/);
1743
1744       done();
1745     });
1746   });
1747
1748   describe('.renderSync(functions)', function() {
1749     it('should call custom function in sync mode', function(done) {
1750       var result = sass.renderSync({
1751         data: 'div { width: cos(0) * 50px; }',
1752         functions: {
1753           'cos($a)': function(angle) {
1754             if (!(angle instanceof sass.types.Number)) {
1755               throw new TypeError('Unexpected type for "angle"');
1756             }
1757             return new sass.types.Number(Math.cos(angle.getValue()));
1758           }
1759         }
1760       });
1761
1762       assert.equal(result.css.toString().trim(), 'div {\n  width: 50px; }');
1763       done();
1764     });
1765
1766     it('should return a list of selectors after calling the headings custom function', function(done) {
1767       var result = sass.renderSync({
1768         data: '#{headings(2,5)} { color: #08c; }',
1769         functions: {
1770           'headings($from: 0, $to: 6)': function(from, to) {
1771             var i, f = from.getValue(), t = to.getValue(),
1772               list = new sass.types.List(t - f + 1);
1773
1774             for (i = f; i <= t; i++) {
1775               list.setValue(i - f, new sass.types.String('h' + i));
1776             }
1777
1778             return list;
1779           }
1780         }
1781       });
1782
1783       assert.equal(result.css.toString().trim(), 'h2, h3, h4, h5 {\n  color: #08c; }');
1784       done();
1785     });
1786
1787     it('should let custom function invoke sass types constructors without the `new` keyword', function(done) {
1788       var result = sass.renderSync({
1789         data: 'div { color: foo(); }',
1790         functions: {
1791           'foo()': function() {
1792             return sass.types.Number(42, 'em');
1793           }
1794         }
1795       });
1796
1797       assert.equal(result.css.toString().trim(), 'div {\n  color: 42em; }');
1798       done();
1799     });
1800
1801     it('should let us register custom functions without signatures', function(done) {
1802       var result = sass.renderSync({
1803         data: 'div { color: foo(20, 22); }',
1804         functions: {
1805           foo: function(a, b) {
1806             return new sass.types.Number(a.getValue() + b.getValue(), 'em');
1807           }
1808         }
1809       });
1810
1811       assert.equal(result.css.toString().trim(), 'div {\n  color: 42em; }');
1812       done();
1813     });
1814
1815     it('should fail when returning anything other than a sass value from a custom function', function(done) {
1816       assert.throws(function() {
1817         sass.renderSync({
1818           data: 'div { color: foo(); }',
1819           functions: {
1820             'foo()': function() {
1821               return {};
1822             }
1823           }
1824         });
1825       }, /A SassValue object was expected/);
1826
1827       done();
1828     });
1829
1830     it('should properly bubble up standard JS errors thrown by custom functions', function(done) {
1831       assert.throws(function() {
1832         sass.renderSync({
1833           data: 'div { color: foo(); }',
1834           functions: {
1835             'foo()': function() {
1836               throw new RangeError('This is a test error');
1837             }
1838           }
1839         });
1840       }, /This is a test error/);
1841
1842       done();
1843     });
1844
1845     it('should properly bubble up unknown errors thrown by custom functions', function(done) {
1846       assert.throws(function() {
1847         sass.renderSync({
1848           data: 'div { color: foo(); }',
1849           functions: {
1850             'foo()': function() {
1851               throw {};
1852             }
1853           }
1854         });
1855       }, /unexpected error/);
1856
1857       done();
1858     });
1859
1860     it('should properly bubble up errors from sass value getters/setters/constructors', function(done) {
1861       assert.throws(function() {
1862         sass.renderSync({
1863           data: 'div { color: foo(); }',
1864           functions: {
1865             'foo()': function() {
1866               return sass.types.Boolean('foo');
1867             }
1868           }
1869         });
1870       }, /Expected one boolean argument/);
1871
1872       assert.throws(function() {
1873         sass.renderSync({
1874           data: 'div { color: foo(); }',
1875           functions: {
1876             'foo()': function() {
1877               var ret = new sass.types.Number(42);
1878               ret.setUnit(123);
1879               return ret;
1880             }
1881           }
1882         });
1883       }, /Supplied value should be a string/);
1884
1885       done();
1886     });
1887
1888     it('should call custom functions with correct context', function(done) {
1889       function assertExpected(result) {
1890         assert.equal(result.css.toString().trim(), 'div {\n  foo1: 1;\n  foo2: 2; }');
1891       }
1892       var options = {
1893         data: 'div { foo1: foo(); foo2: foo(); }',
1894         functions: {
1895           // foo() is stateful and will persist an incrementing counter
1896           'foo()': function() {
1897             assert(this);
1898             this.fooCounter = (this.fooCounter || 0) + 1;
1899             return new sass.types.Number(this.fooCounter);
1900           }
1901         }
1902       };
1903       assertExpected(sass.renderSync(options));
1904       done();
1905     });
1906   });
1907
1908   describe('.renderSync({stats: {}})', function() {
1909     var start = Date.now();
1910     var result = sass.renderSync({
1911       file: fixture('include-files/index.scss')
1912     });
1913
1914     it('should provide a start timestamp', function(done) {
1915       assert.strictEqual(typeof result.stats.start, 'number');
1916       assert(result.stats.start >= start);
1917       done();
1918     });
1919
1920     it('should provide an end timestamp', function(done) {
1921       assert.strictEqual(typeof result.stats.end, 'number');
1922       assert(result.stats.end >= result.stats.start);
1923       done();
1924     });
1925
1926     it('should provide a duration', function(done) {
1927       assert.strictEqual(typeof result.stats.duration, 'number');
1928       assert.equal(result.stats.end - result.stats.start, result.stats.duration);
1929       done();
1930     });
1931
1932     it('should contain the given entry file', function(done) {
1933       assert.equal(result.stats.entry, resolveFixture('include-files/index.scss'));
1934       done();
1935     });
1936
1937     it('should contain an array of all included files', function(done) {
1938       var expected = [
1939         fixture('include-files/bar.scss').replace(/\\/g, '/'),
1940         fixture('include-files/foo.scss').replace(/\\/g, '/'),
1941         fixture('include-files/index.scss').replace(/\\/g, '/')
1942       ].sort();
1943       var actual = result.stats.includedFiles.sort();
1944
1945       assert.equal(actual[0], expected[0]);
1946       assert.equal(actual[1], expected[1]);
1947       assert.equal(actual[2], expected[2]);
1948       done();
1949     });
1950
1951     it('should contain array with the entry if there are no import statements', function(done) {
1952       var expected = fixture('simple/index.scss').replace(/\\/g, '/');
1953
1954       var result = sass.renderSync({
1955         file: fixture('simple/index.scss')
1956       });
1957
1958       assert.deepEqual(result.stats.includedFiles, [expected]);
1959       done();
1960     });
1961
1962     it('should state `data` as entry file', function(done) {
1963       var result = sass.renderSync({
1964         data: read(fixture('simple/index.scss'), 'utf8')
1965       });
1966
1967       assert.equal(result.stats.entry, 'data');
1968       done();
1969     });
1970
1971     it('should contain an empty array as includedFiles', function(done) {
1972       var result = sass.renderSync({
1973         data: read(fixture('simple/index.scss'), 'utf8')
1974       });
1975
1976       assert.deepEqual(result.stats.includedFiles, []);
1977       done();
1978     });
1979   });
1980
1981   describe('.info', function() {
1982     var package = require('../package.json'),
1983       info = sass.info;
1984
1985     it('should return a correct version info', function(done) {
1986       assert(info.indexOf(package.version) > 0);
1987       assert(info.indexOf('(Wrapper)') > 0);
1988       assert(info.indexOf('[JavaScript]') > 0);
1989       assert(info.indexOf('[NA]') < 0);
1990       assert(info.indexOf('(Sass Compiler)') > 0);
1991       assert(info.indexOf('[C/C++]') > 0);
1992
1993       done();
1994     });
1995   });
1996 });