UNPKG

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