1 var assert = require('assert'),
3 path = require('path'),
4 read = require('fs').readFileSync,
5 glob = require('glob'),
6 rimraf = require('rimraf'),
7 stream = require('stream'),
8 spawn = require('cross-spawn'),
9 cli = path.join(__dirname, '..', 'bin', 'node-sass'),
10 fixture = path.join.bind(null, __dirname, 'fixtures');
12 describe('cli', function() {
13 // For some reason we experience random timeout failures in CI
14 // due to spawn hanging/failing silently. See #1692.
17 describe('node-sass < in.scss', function() {
18 it('should read data from stdin', function(done) {
19 var src = fs.createReadStream(fixture('simple/index.scss'));
20 var expected = read(fixture('simple/expected.css'), 'utf8').trim();
23 bin.stdout.setEncoding('utf8');
24 bin.stdout.once('data', function(data) {
25 assert.equal(data.trim(), expected.replace(/\r\n/g, '\n'));
32 it('should compile sass using the --indented-syntax option', function(done) {
33 var src = fs.createReadStream(fixture('indent/index.sass'));
34 var expected = read(fixture('indent/expected.css'), 'utf8').trim();
35 var bin = spawn(cli, ['--indented-syntax']);
37 bin.stdout.setEncoding('utf8');
38 bin.stdout.once('data', function(data) {
39 assert.equal(data.trim(), expected.replace(/\r\n/g, '\n'));
46 it('should compile with the --quiet option', function(done) {
47 var src = fs.createReadStream(fixture('simple/index.scss'));
48 var expected = read(fixture('simple/expected.css'), 'utf8').trim();
49 var bin = spawn(cli, ['--quiet']);
51 bin.stdout.setEncoding('utf8');
52 bin.stdout.once('data', function(data) {
53 assert.equal(data.trim(), expected.replace(/\r\n/g, '\n'));
60 it('should compile with the --output-style option', function(done) {
61 var src = fs.createReadStream(fixture('compressed/index.scss'));
62 var expected = read(fixture('compressed/expected.css'), 'utf8').trim();
63 var bin = spawn(cli, ['--output-style', 'compressed']);
65 bin.stdout.setEncoding('utf8');
66 bin.stdout.once('data', function(data) {
67 assert.equal(data.trim(), expected.replace(/\r\n/g, '\n'));
74 it('should compile with the --source-comments option', function(done) {
75 var src = fs.createReadStream(fixture('source-comments/index.scss'));
76 var expected = read(fixture('source-comments/expected.css'), 'utf8').trim();
77 var bin = spawn(cli, ['--source-comments']);
79 bin.stdout.setEncoding('utf8');
80 bin.stdout.once('data', function(data) {
81 assert.equal(data.trim(), expected.replace(/\r\n/g, '\n'));
88 it('should render with indentWidth and indentType options', function(done) {
89 var src = new stream.Readable();
90 var bin = spawn(cli, ['--indent-width', 7, '--indent-type', 'tab']);
92 src._read = function() { };
93 src.push('div { color: transparent; }');
96 bin.stdout.setEncoding('utf8');
97 bin.stdout.once('data', function(data) {
98 assert.equal(data.trim(), 'div {\n\t\t\t\t\t\t\tcolor: transparent; }');
105 it('should render with linefeed option', function(done) {
106 var src = new stream.Readable();
107 var bin = spawn(cli, ['--linefeed', 'lfcr']);
109 src._read = function() { };
110 src.push('div { color: transparent; }');
113 bin.stdout.setEncoding('utf8');
114 bin.stdout.once('data', function(data) {
115 assert.equal(data.trim(), 'div {\n\r color: transparent; }');
123 describe('node-sass in.scss', function() {
124 it('should compile a scss file', function(done) {
125 process.chdir(fixture('simple'));
127 var src = fixture('simple/index.scss');
128 var dest = fixture('simple/index.css');
129 var bin = spawn(cli, [src, dest]);
131 bin.once('close', function() {
132 assert(fs.existsSync(dest));
134 process.chdir(__dirname);
139 it('should compile a scss file to custom destination', function(done) {
140 process.chdir(fixture('simple'));
142 var src = fixture('simple/index.scss');
143 var dest = fixture('simple/index-custom.css');
144 var bin = spawn(cli, [src, dest]);
146 bin.once('close', function() {
147 assert(fs.existsSync(dest));
149 process.chdir(__dirname);
154 it('should compile with the --include-path option', function(done) {
156 '--include-path', fixture('include-path/functions'),
157 '--include-path', fixture('include-path/lib')
160 var src = fixture('include-path/index.scss');
161 var expected = read(fixture('include-path/expected.css'), 'utf8').trim();
162 var bin = spawn(cli, [src].concat(includePaths));
164 bin.stdout.setEncoding('utf8');
165 bin.stdout.once('data', function(data) {
166 assert.equal(data.trim(), expected.replace(/\r\n/g, '\n'));
171 it('should compile silently using the --quiet option', function(done) {
172 process.chdir(fixture('simple'));
174 var src = fixture('simple/index.scss');
175 var dest = fixture('simple/index.css');
176 var bin = spawn(cli, [src, dest, '--quiet']);
179 bin.stderr.once('data', function() {
183 bin.once('close', function() {
184 assert.equal(didEmit, false);
186 process.chdir(__dirname);
191 it('should still report errors with the --quiet option', function(done) {
192 process.chdir(fixture('invalid'));
194 var src = fixture('invalid/index.scss');
195 var dest = fixture('invalid/index.css');
196 var bin = spawn(cli, [src, dest, '--quiet']);
199 bin.stderr.once('data', function() {
203 bin.once('close', function() {
204 assert.equal(didEmit, true);
205 process.chdir(__dirname);
210 it('should not exit with the --watch option', function(done) {
211 var src = fixture('simple/index.scss');
212 var bin = spawn(cli, [src, '--watch']);
215 bin.once('close', function() {
219 setTimeout(function() {
221 throw new Error('Watch ended too early!');
229 it.skip('should emit `warn` on file change when using --watch option', function(done) {
230 var src = fixture('simple/tmp.scss');
232 fs.writeFileSync(src, '');
234 var bin = spawn(cli, ['--watch', src]);
236 bin.stderr.setEncoding('utf8');
237 bin.stderr.once('data', function(data) {
238 assert.strictEqual(data.trim(), '=> changed: ' + src);
244 setTimeout(function() {
245 fs.appendFileSync(src, 'body {}');
249 it.skip('should emit nothing on file change when using --watch and --quiet options', function(done) {
250 var src = fixture('simple/tmp.scss');
252 fs.writeFileSync(src, '');
254 var bin = spawn(cli, ['--watch', '--quiet', src]);
256 bin.stderr.setEncoding('utf8');
257 bin.stderr.once('data', function() {
261 setTimeout(function() {
262 fs.appendFileSync(src, 'body {}');
263 setTimeout(function() {
264 assert.equal(didEmit, false);
272 it.skip('should render all watched files', function(done) {
273 var src = fixture('simple/bar.scss');
275 fs.writeFileSync(src, '');
277 var bin = spawn(cli, [
278 '--output-style', 'compressed',
282 bin.stdout.setEncoding('utf8');
283 bin.stdout.once('data', function(data) {
284 assert.strictEqual(data.trim(), 'body{background:white}');
290 setTimeout(function() {
291 fs.appendFileSync(src, 'body{background:white}');
295 it.skip('should watch the full scss dep tree for a single file (scss)', function(done) {
296 var src = fixture('watching/index.scss');
297 var foo = fixture('watching/white.scss');
299 fs.writeFileSync(foo, '');
301 var bin = spawn(cli, [
302 '--output-style', 'compressed',
306 bin.stdout.setEncoding('utf8');
307 bin.stdout.once('data', function(data) {
308 assert.strictEqual(data.trim(), 'body{background:blue}');
313 setTimeout(function() {
314 fs.appendFileSync(foo, 'body{background:blue}\n');
318 it.skip('should watch the full sass dep tree for a single file (sass)', function(done) {
319 var src = fixture('watching/index.sass');
320 var foo = fixture('watching/bar.sass');
322 fs.writeFileSync(foo, '');
324 var bin = spawn(cli, [
325 '--output-style', 'compressed',
329 bin.stdout.setEncoding('utf8');
330 bin.stdout.once('data', function(data) {
331 assert.strictEqual(data.trim(), 'body{background:red}');
336 setTimeout(function() {
337 fs.appendFileSync(foo, 'body\n\tbackground: red\n');
342 describe('node-sass --output directory', function() {
343 it.skip('should watch whole directory', function(done) {
344 var destDir = fixture('watching-css-out-01/');
345 var srcDir = fixture('watching-dir-01/');
346 var srcFile = path.join(srcDir, 'index.scss');
348 fs.writeFileSync(srcFile, '');
350 var bin = spawn(cli, [
351 '--output-style', 'compressed',
356 setTimeout(function() {
357 fs.appendFileSync(srcFile, 'a {color:green;}\n');
358 setTimeout(function() {
360 var files = fs.readdirSync(destDir);
361 assert.deepEqual(files, ['index.css']);
362 rimraf(destDir, done);
367 it.skip('should compile all changed files in watched directory', function(done) {
368 var destDir = fixture('watching-css-out-02/');
369 var srcDir = fixture('watching-dir-02/');
370 var srcFile = path.join(srcDir, 'foo.scss');
372 fs.writeFileSync(srcFile, '');
374 var bin = spawn(cli, [
375 '--output-style', 'compressed',
380 setTimeout(function () {
381 fs.appendFileSync(srcFile, 'body{background:white}\n');
382 setTimeout(function () {
384 var files = fs.readdirSync(destDir);
385 assert.deepEqual(files, ['foo.css', 'index.css']);
386 rimraf(destDir, done);
392 describe('node-sass in.scss --output out.css', function() {
393 it('should compile a scss file to build.css', function(done) {
394 var src = fixture('simple/index.scss');
395 var dest = fixture('simple/index.css');
396 var bin = spawn(cli, [src, '--output', path.dirname(dest)]);
398 bin.once('close', function() {
399 assert(fs.existsSync(dest));
405 it('should compile with the --source-map option', function(done) {
406 var src = fixture('source-map/index.scss');
407 var destCss = fixture('source-map/index.css');
408 var destMap = fixture('source-map/index.map');
409 var expectedCss = read(fixture('source-map/expected.css'), 'utf8').trim().replace(/\r\n/g, '\n');
410 var expectedMap = read(fixture('source-map/expected.map'), 'utf8').trim().replace(/\r\n/g, '\n');
411 var bin = spawn(cli, [src, '--output', path.dirname(destCss), '--source-map', destMap]);
413 bin.once('close', function() {
414 assert.equal(read(destCss, 'utf8').trim(), expectedCss);
415 assert.equal(read(destMap, 'utf8').trim(), expectedMap);
416 fs.unlinkSync(destCss);
417 fs.unlinkSync(destMap);
422 it('should omit sourceMappingURL if --omit-source-map-url flag is used', function(done) {
423 var src = fixture('source-map/index.scss');
424 var dest = fixture('source-map/index.css');
425 var map = fixture('source-map/index.map');
426 var bin = spawn(cli, [
427 src, '--output', path.dirname(dest),
428 '--source-map', map, '--omit-source-map-url'
431 bin.once('close', function() {
432 assert.strictEqual(read(dest, 'utf8').indexOf('sourceMappingURL'), -1);
433 assert(fs.existsSync(map));
440 it('should compile with the --source-root option', function(done) {
441 var src = fixture('source-map/index.scss');
442 var destCss = fixture('source-map/index.css');
443 var destMap = fixture('source-map/index.map');
444 var expectedCss = read(fixture('source-map/expected.css'), 'utf8').trim().replace(/\r\n/g, '\n');
445 var expectedUrl = 'http://test/';
446 var bin = spawn(cli, [
447 src, '--output', path.dirname(destCss),
448 '--source-map-root', expectedUrl,
449 '--source-map', destMap
452 bin.once('close', function() {
453 assert.equal(read(destCss, 'utf8').trim(), expectedCss);
454 assert.equal(JSON.parse(read(destMap, 'utf8')).sourceRoot, expectedUrl);
455 fs.unlinkSync(destCss);
456 fs.unlinkSync(destMap);
461 it('should compile with the --source-map-embed option and no outfile', function(done) {
462 var src = fixture('source-map-embed/index.scss');
463 var expectedCss = read(fixture('source-map-embed/expected.css'), 'utf8').trim().replace(/\r\n/g, '\n');
465 var bin = spawn(cli, [
467 '--source-map-embed',
468 '--source-map', 'true'
471 bin.stdout.on('data', function(data) {
475 bin.once('close', function() {
476 assert.equal(result.trim().replace(/\r\n/g, '\n'), expectedCss);
482 describe('node-sass sass/ --output css/', function() {
483 it('should create the output directory', function(done) {
484 var src = fixture('input-directory/sass');
485 var dest = fixture('input-directory/css');
486 var bin = spawn(cli, [src, '--output', dest]);
488 bin.once('close', function() {
489 assert(fs.existsSync(dest));
495 it('should compile all files in the folder', function(done) {
496 var src = fixture('input-directory/sass');
497 var dest = fixture('input-directory/css');
498 var bin = spawn(cli, [src, '--output', dest]);
500 bin.once('close', function() {
501 var files = fs.readdirSync(dest).sort();
502 assert.deepEqual(files, ['one.css', 'two.css', 'nested'].sort());
503 var nestedFiles = fs.readdirSync(path.join(dest, 'nested'));
504 assert.deepEqual(nestedFiles, ['three.css']);
510 it('should compile with --source-map set to directory', function(done) {
511 var src = fixture('input-directory/sass');
512 var dest = fixture('input-directory/css');
513 var destMap = fixture('input-directory/map');
514 var bin = spawn(cli, [src, '--output', dest, '--source-map', destMap]);
516 bin.once('close', function() {
517 var map = JSON.parse(read(fixture('input-directory/map/nested/three.css.map'), 'utf8'));
519 assert.equal(map.file, '../../css/nested/three.css');
521 rimraf.sync(destMap);
526 it('should skip files with an underscore', function(done) {
527 var src = fixture('input-directory/sass');
528 var dest = fixture('input-directory/css');
529 var bin = spawn(cli, [src, '--output', dest]);
531 bin.once('close', function() {
532 var files = fs.readdirSync(dest);
533 assert.equal(files.indexOf('_skipped.css'), -1);
539 it('should ignore nested files if --recursive false', function(done) {
540 var src = fixture('input-directory/sass');
541 var dest = fixture('input-directory/css');
542 var bin = spawn(cli, [
543 src, '--output', dest,
547 bin.once('close', function() {
548 var files = fs.readdirSync(dest);
549 assert.deepEqual(files, ['one.css', 'two.css']);
555 it('should error if no output directory is provided', function(done) {
556 var src = fixture('input-directory/sass');
557 var bin = spawn(cli, [src]);
559 bin.once('close', function(code) {
560 assert.notStrictEqual(code, 0);
561 assert.strictEqual(glob.sync(fixture('input-directory/**/*.css')).length, 0);
566 it('should error if output directory is not a directory', function(done) {
567 var src = fixture('input-directory/sass');
568 var dest = fixture('input-directory/sass/one.scss');
569 var bin = spawn(cli, [src, '--output', dest]);
571 bin.once('close', function(code) {
572 assert.notStrictEqual(code, 0);
573 assert.equal(glob.sync(fixture('input-directory/**/*.css')).length, 0);
578 it('should not error if output directory is a symlink', function(done) {
579 var outputDir = fixture('input-directory/css');
580 var src = fixture('input-directory/sass');
581 var symlink = fixture('symlinked-css');
582 fs.mkdirSync(outputDir);
583 fs.symlinkSync(outputDir, symlink);
584 var bin = spawn(cli, [src, '--output', symlink]);
586 bin.once('close', function() {
587 var files = fs.readdirSync(outputDir).sort();
588 assert.deepEqual(files, ['one.css', 'two.css', 'nested'].sort());
589 var nestedFiles = fs.readdirSync(path.join(outputDir, 'nested'));
590 assert.deepEqual(nestedFiles, ['three.css']);
591 rimraf.sync(outputDir);
592 fs.unlinkSync(symlink);
598 describe('node-sass in.scss --output path/to/file/out.css', function() {
599 it('should create the output directory', function(done) {
600 var src = fixture('output-directory/index.scss');
601 var dest = fixture('output-directory/path/to/file/index.css');
602 var bin = spawn(cli, [src, '--output', path.dirname(dest)]);
604 bin.once('close', function() {
605 assert(fs.existsSync(path.dirname(dest)));
607 fs.rmdirSync(path.dirname(dest));
608 dest = path.dirname(dest);
609 fs.rmdirSync(path.dirname(dest));
610 dest = path.dirname(dest);
611 fs.rmdirSync(path.dirname(dest));
618 describe('node-sass --follow --output output-dir input-dir', function() {
619 it('should compile with the --follow option', function(done) {
620 var src = fixture('follow/input-dir');
621 var dest = fixture('follow/output-dir');
624 fs.symlinkSync(path.join(path.dirname(src), 'foo'), path.join(src, 'foo'), 'dir');
626 var bin = spawn(cli, [src, '--follow', '--output', dest]);
628 bin.once('close', function() {
629 var expected = path.join(dest, 'foo/bar/index.css');
630 fs.unlinkSync(path.join(src, 'foo'));
632 assert(fs.existsSync(expected));
633 fs.unlinkSync(expected);
634 expected = path.dirname(expected);
635 fs.rmdirSync(expected);
636 expected = path.dirname(expected);
637 fs.rmdirSync(expected);
644 describe('importer', function() {
645 var dest = fixture('include-files/index.css');
646 var src = fixture('include-files/index.scss');
647 var expected = read(fixture('include-files/expected-importer.css'), 'utf8').trim().replace(/\r\n/g, '\n');
649 it('should override imports and fire callback with file and contents', function(done) {
650 var bin = spawn(cli, [
651 src, '--output', path.dirname(dest),
652 '--importer', fixture('extras/my_custom_importer_file_and_data_cb.js')
655 bin.once('close', function() {
656 assert.equal(read(dest, 'utf8').trim(), expected);
662 it('should override imports and fire callback with file', function(done) {
663 var bin = spawn(cli, [
664 src, '--output', path.dirname(dest),
665 '--importer', fixture('extras/my_custom_importer_file_cb.js')
668 bin.once('close', function() {
669 if (fs.existsSync(dest)) {
670 assert.equal(read(dest, 'utf8').trim(), '');
678 it('should override imports and fire callback with data', function(done) {
679 var bin = spawn(cli, [
680 src, '--output', path.dirname(dest),
681 '--importer', fixture('extras/my_custom_importer_data_cb.js')
684 bin.once('close', function() {
685 assert.equal(read(dest, 'utf8').trim(), expected);
691 it('should override imports and return file and contents', function(done) {
692 var bin = spawn(cli, [
693 src, '--output', path.dirname(dest),
694 '--importer', fixture('extras/my_custom_importer_file_and_data.js')
697 bin.once('close', function() {
698 assert.equal(read(dest, 'utf8').trim(), expected);
704 it('should override imports and return file', function(done) {
705 var bin = spawn(cli, [
706 src, '--output', path.dirname(dest),
707 '--importer', fixture('extras/my_custom_importer_file.js')
710 bin.once('close', function() {
711 if (fs.existsSync(dest)) {
712 assert.equal(read(dest, 'utf8').trim(), '');
720 it('should override imports and return data', function(done) {
721 var bin = spawn(cli, [
722 src, '--output', path.dirname(dest),
723 '--importer', fixture('extras/my_custom_importer_data.js')
726 bin.once('close', function() {
727 assert.equal(read(dest, 'utf8').trim(), expected);
733 it('should accept arrays of importers and return respect the order', function(done) {
734 var bin = spawn(cli, [
735 src, '--output', path.dirname(dest),
736 '--importer', fixture('extras/my_custom_arrays_of_importers.js')
739 bin.once('close', function() {
740 assert.equal(read(dest, 'utf8').trim(), expected);
746 it('should return error for invalid importer file path', function(done) {
747 var bin = spawn(cli, [
748 src, '--output', path.dirname(dest),
749 '--importer', fixture('non/existing/path')
752 bin.once('close', function(code) {
753 assert.notStrictEqual(code, 0);
758 it('should reflect user-defined Error', function(done) {
759 var bin = spawn(cli, [
760 src, '--output', path.dirname(dest),
761 '--importer', fixture('extras/my_custom_importer_error.js')
764 bin.stderr.once('data', function(code) {
765 assert.equal(JSON.parse(code).message, 'doesn\'t exist!');
771 describe('functions', function() {
772 it('should let custom functions call setter methods on wrapped sass values (number)', function(done) {
773 var dest = fixture('custom-functions/setter.css');
774 var src = fixture('custom-functions/setter.scss');
775 var expected = read(fixture('custom-functions/setter-expected.css'), 'utf8').trim().replace(/\r\n/g, '\n');
776 var bin = spawn(cli, [
777 src, '--output', path.dirname(dest),
778 '--functions', fixture('extras/my_custom_functions_setter.js')
781 bin.once('close', function() {
782 assert.equal(read(dest, 'utf8').trim(), expected);
788 it('should properly convert strings when calling custom functions', function(done) {
789 var dest = fixture('custom-functions/string-conversion.css');
790 var src = fixture('custom-functions/string-conversion.scss');
791 var expected = read(fixture('custom-functions/string-conversion-expected.css'), 'utf8').trim().replace(/\r\n/g, '\n');
792 var bin = spawn(cli, [
793 src, '--output', path.dirname(dest),
794 '--functions', fixture('extras/my_custom_functions_string_conversion.js')
797 bin.once('close', function() {
798 assert.equal(read(dest, 'utf8').trim(), expected);