node-sass-chokidar
Version:
A thin wrapper around node-sass to replicate the --watch --recursive option using chokidar instead of gaze for significantly better performance
813 lines (677 loc) • 30.1 kB
JavaScript
var assert = require("assert"),
fs = require("fs"),
path = require("path"),
read = require("fs").readFileSync,
glob = require("glob"),
rimraf = require("rimraf"),
stream = require("stream"),
spawn = require("cross-spawn"),
cli = path.join(__dirname, "..", "bin", "node-sass-chokidar"),
fixture = path.join.bind(null, __dirname, "fixtures");
describe("cli", function () {
// For some reason we experience random timeout failures in CI
// due to spawn hanging/failing silently. See #1692.
this.retries(4);
describe("node-sass < in.scss", function () {
it("should read data from stdin", function (done) {
var src = fs.createReadStream(fixture("simple/index.scss"));
var expected = read(fixture("simple/expected.css"), "utf8").trim();
var bin = spawn(cli);
bin.stdout.setEncoding("utf8");
bin.stdout.once("data", function (data) {
assert.equal(data.trim(), expected.replace(/\r\n/g, "\n"));
done();
});
src.pipe(bin.stdin);
});
it("should compile sass using the --indented-syntax option", function (done) {
var src = fs.createReadStream(fixture("indent/index.sass"));
var expected = read(fixture("indent/expected.css"), "utf8").trim();
var bin = spawn(cli, ["--indented-syntax"]);
bin.stdout.setEncoding("utf8");
bin.stdout.once("data", function (data) {
assert.equal(data.trim(), expected.replace(/\r\n/g, "\n"));
done();
});
src.pipe(bin.stdin);
});
it("should compile with the --quiet option", function (done) {
var src = fs.createReadStream(fixture("simple/index.scss"));
var expected = read(fixture("simple/expected.css"), "utf8").trim();
var bin = spawn(cli, ["--quiet"]);
bin.stdout.setEncoding("utf8");
bin.stdout.once("data", function (data) {
assert.equal(data.trim(), expected.replace(/\r\n/g, "\n"));
done();
});
src.pipe(bin.stdin);
});
it("should compile with the --output-style option", function (done) {
var src = fs.createReadStream(fixture("compressed/index.scss"));
var expected = read(fixture("compressed/expected.css"), "utf8").trim();
var bin = spawn(cli, ["--output-style", "compressed"]);
bin.stdout.setEncoding("utf8");
bin.stdout.once("data", function (data) {
assert.equal(data.trim(), expected.replace(/\r\n/g, "\n"));
done();
});
src.pipe(bin.stdin);
});
it("should compile with the --source-comments option", function (done) {
var src = fs.createReadStream(fixture("source-comments/index.scss"));
var expected = read(fixture("source-comments/expected.css"), "utf8").trim();
var bin = spawn(cli, ["--source-comments"]);
bin.stdout.setEncoding("utf8");
bin.stdout.once("data", function (data) {
assert.equal(data.trim(), expected.replace(/\r\n/g, "\n"));
done();
});
src.pipe(bin.stdin);
});
it("should render with indentWidth and indentType options", function (done) {
var src = new stream.Readable();
var bin = spawn(cli, ["--indent-width", 7, "--indent-type", "tab"]);
src._read = function () {};
src.push("div { color: transparent; }");
src.push(null);
bin.stdout.setEncoding("utf8");
bin.stdout.once("data", function (data) {
assert.equal(data.trim(), "div {\n\t\t\t\t\t\t\tcolor: transparent; }");
done();
});
src.pipe(bin.stdin);
});
it("should render with linefeed option", function (done) {
var src = new stream.Readable();
var bin = spawn(cli, ["--linefeed", "lfcr"]);
src._read = function () {};
src.push("div { color: transparent; }");
src.push(null);
bin.stdout.setEncoding("utf8");
bin.stdout.once("data", function (data) {
assert.equal(data.trim(), "div {\n\r color: transparent; }");
done();
});
src.pipe(bin.stdin);
});
});
describe("node-sass in.scss", function () {
it("should compile a scss file", function (done) {
process.chdir(fixture("simple"));
var src = fixture("simple/index.scss");
var dest = fixture("simple/index.css");
var bin = spawn(cli, [src, dest]);
bin.once("close", function () {
assert(fs.existsSync(dest));
fs.unlinkSync(dest);
process.chdir(__dirname);
done();
});
});
it("should compile a scss file to custom destination", function (done) {
process.chdir(fixture("simple"));
var src = fixture("simple/index.scss");
var dest = fixture("simple/index-custom.css");
var bin = spawn(cli, [src, dest]);
bin.once("close", function () {
assert(fs.existsSync(dest));
fs.unlinkSync(dest);
process.chdir(__dirname);
done();
});
});
it("should compile with the --include-path option", function (done) {
var includePaths = [
"--include-path",
fixture("include-path/functions"),
"--include-path",
fixture("include-path/lib"),
];
var src = fixture("include-path/index.scss");
var expected = read(fixture("include-path/expected.css"), "utf8").trim();
var bin = spawn(cli, [src].concat(includePaths));
bin.stdout.setEncoding("utf8");
bin.stdout.once("data", function (data) {
assert.equal(data.trim(), expected.replace(/\r\n/g, "\n"));
done();
});
});
it("should compile silently using the --quiet option", function (done) {
process.chdir(fixture("simple"));
var src = fixture("simple/index.scss");
var dest = fixture("simple/index.css");
var bin = spawn(cli, [src, dest, "--quiet"]);
var didEmit = false;
bin.stderr.once("data", function () {
didEmit = true;
});
bin.once("close", function () {
assert.equal(didEmit, false);
fs.unlinkSync(dest);
process.chdir(__dirname);
done();
});
});
it("should still report errors with the --quiet option", function (done) {
process.chdir(fixture("invalid"));
var src = fixture("invalid/index.scss");
var dest = fixture("invalid/index.css");
var bin = spawn(cli, [src, dest, "--quiet"]);
var didEmit = false;
bin.stderr.once("data", function () {
didEmit = true;
});
bin.once("close", function () {
assert.equal(didEmit, true);
process.chdir(__dirname);
done();
});
});
it("should not exit with the --watch option", function (done) {
var src = fixture("simple/index.scss");
var bin = spawn(cli, [src, "--watch"]);
var exited;
bin.once("close", function () {
exited = true;
});
setTimeout(function () {
if (exited) {
throw new Error("Watch ended too early!");
} else {
bin.kill();
done();
}
}, 100);
});
it("should emit `warn` on file change when using --watch option", function (done) {
var src = fixture("simple/tmp.scss");
fs.writeFileSync(src, "");
var bin = spawn(cli, ["--watch", src]);
bin.stderr.setEncoding("utf8");
bin.stderr.once("data", function (data) {
assert.strictEqual(data.trim(), "=> changed: " + src);
fs.unlinkSync(src);
bin.kill();
done();
});
setTimeout(function () {
fs.appendFileSync(src, "body {}");
}, 500);
});
it("should emit nothing on file change when using --watch and --quiet options", function (done) {
var src = fixture("simple/tmp.scss");
var didEmit = false;
fs.writeFileSync(src, "");
var bin = spawn(cli, ["--watch", "--quiet", src]);
bin.stderr.setEncoding("utf8");
bin.stderr.once("data", function () {
didEmit = true;
});
setTimeout(function () {
fs.appendFileSync(src, "body {}");
setTimeout(function () {
assert.equal(didEmit, false);
bin.kill();
done();
fs.unlinkSync(src);
}, 200);
}, 500);
});
it("should render all watched files", function (done) {
var src = fixture("simple/bar.scss");
fs.writeFileSync(src, "");
var bin = spawn(cli, ["--output-style", "compressed", "--watch", src]);
bin.stdout.setEncoding("utf8");
bin.stdout.once("data", function (data) {
assert.strictEqual(data.trim(), "body{background:white}");
fs.unlinkSync(src);
bin.kill();
done();
});
setTimeout(function () {
fs.appendFileSync(src, "body{background:white}");
}, 500);
});
it("should watch the full scss dep tree for a single file (scss)", function (done) {
var src = fixture("watching/index.scss");
var foo = fixture("watching/white.scss");
fs.writeFileSync(foo, "");
var bin = spawn(cli, ["--output-style", "compressed", "--watch", src]);
bin.stdout.setEncoding("utf8");
bin.stdout.once("data", function (data) {
assert.strictEqual(data.trim(), "body{background:blue}");
bin.kill();
done();
});
setTimeout(function () {
fs.appendFileSync(foo, "body{background:blue}\n");
}, 500);
});
it("should watch the full sass dep tree for a single file (sass)", function (done) {
var src = fixture("watching/index.sass");
var foo = fixture("watching/bar.sass");
fs.writeFileSync(foo, "");
var bin = spawn(cli, ["--output-style", "compressed", "--watch", src]);
bin.stdout.setEncoding("utf8");
bin.stdout.once("data", function (data) {
assert.strictEqual(data.trim(), "body{background:red}");
bin.kill();
done();
});
setTimeout(function () {
fs.appendFileSync(foo, "body\n\tbackground: red\n");
}, 500);
});
});
describe("node-sass --output directory", function () {
it("should watch whole directory", function (done) {
var destDir = fixture("watching-css-out-01/");
var srcDir = fixture("watching-dir-01/");
var srcFile = path.join(srcDir, "index.scss");
fs.writeFileSync(srcFile, "");
var bin = spawn(cli, ["--output-style", "compressed", "--output", destDir, "--watch", srcDir]);
setTimeout(function () {
fs.appendFileSync(srcFile, "a {color:green;}\n");
setTimeout(function () {
bin.kill();
var files = fs.readdirSync(destDir);
assert.deepEqual(files, ["index.css"]);
rimraf(destDir, done);
}, 200);
}, 500);
});
it("should compile all changed files in watched directory", function (done) {
var destDir = fixture("watching-css-out-02/");
var srcDir = fixture("watching-dir-02/");
var srcFile = path.join(srcDir, "foo.scss");
fs.writeFileSync(srcFile, "");
var bin = spawn(cli, ["--output-style", "compressed", "--output", destDir, "--watch", srcDir]);
setTimeout(function () {
fs.appendFileSync(srcFile, "body{background:white}\n");
setTimeout(function () {
bin.kill();
var files = fs.readdirSync(destDir);
assert.deepEqual(files, ["foo.css", "index.css"]);
rimraf(destDir, done);
}, 200);
}, 500);
});
});
describe("node-sass in.scss --output out.css", function () {
it("should compile a scss file to build.css", function (done) {
var src = fixture("simple/index.scss");
var dest = fixture("simple/index.css");
var bin = spawn(cli, [src, "--output", path.dirname(dest)]);
bin.once("close", function () {
assert(fs.existsSync(dest));
fs.unlinkSync(dest);
done();
});
});
it("should compile with the --source-map option", function (done) {
var src = fixture("source-map/index.scss");
var destCss = fixture("source-map/index.css");
var destMap = fixture("source-map/index.map");
var expectedCss = read(fixture("source-map/expected.css"), "utf8").trim().replace(/\r\n/g, "\n");
var expectedMap = read(fixture("source-map/expected.map"), "utf8").trim().replace(/\r\n/g, "\n");
var bin = spawn(cli, [src, "--output", path.dirname(destCss), "--source-map", destMap]);
bin.once("close", function () {
assert.equal(read(destCss, "utf8").trim(), expectedCss);
assert.equal(read(destMap, "utf8").trim(), expectedMap);
fs.unlinkSync(destCss);
fs.unlinkSync(destMap);
done();
});
});
it("should omit sourceMappingURL if --omit-source-map-url flag is used", function (done) {
var src = fixture("source-map/index.scss");
var dest = fixture("source-map/index.css");
var map = fixture("source-map/index.map");
var bin = spawn(cli, [src, "--output", path.dirname(dest), "--source-map", map, "--omit-source-map-url"]);
bin.once("close", function () {
assert.strictEqual(read(dest, "utf8").indexOf("sourceMappingURL"), -1);
assert(fs.existsSync(map));
fs.unlinkSync(map);
fs.unlinkSync(dest);
done();
});
});
it("should compile with the --source-root option", function (done) {
var src = fixture("source-map/index.scss");
var destCss = fixture("source-map/index.css");
var destMap = fixture("source-map/index.map");
var expectedCss = read(fixture("source-map/expected.css"), "utf8").trim().replace(/\r\n/g, "\n");
var expectedUrl = "http://test/";
var bin = spawn(cli, [
src,
"--output",
path.dirname(destCss),
"--source-map-root",
expectedUrl,
"--source-map",
destMap,
]);
bin.once("close", function () {
assert.equal(read(destCss, "utf8").trim(), expectedCss);
assert.equal(JSON.parse(read(destMap, "utf8")).sourceRoot, expectedUrl);
fs.unlinkSync(destCss);
fs.unlinkSync(destMap);
done();
});
});
it("should compile with the --source-map-embed option and no outfile", function (done) {
var src = fixture("source-map-embed/index.scss");
var expectedCss = read(fixture("source-map-embed/expected.css"), "utf8").trim().replace(/\r\n/g, "\n");
var result = "";
var bin = spawn(cli, [src, "--source-map-embed", "--source-map", "true"]);
bin.stdout.on("data", function (data) {
result += data;
});
bin.once("close", function () {
assert.equal(result.trim().replace(/\r\n/g, "\n"), expectedCss);
done();
});
});
});
describe("node-sass sass/ --output css/", function () {
it("should create the output directory", function (done) {
var src = fixture("input-directory/sass");
var dest = fixture("input-directory/css");
var bin = spawn(cli, [src, "--output", dest]);
bin.once("close", function () {
assert(fs.existsSync(dest));
rimraf.sync(dest);
done();
});
});
it("should compile all files in the folder", function (done) {
var src = fixture("input-directory/sass");
var dest = fixture("input-directory/css");
var bin = spawn(cli, [src, "--output", dest]);
bin.once("close", function () {
var files = fs.readdirSync(dest).sort();
assert.deepEqual(files, ["one.css", "two.css", "nested"].sort());
var nestedFiles = fs.readdirSync(path.join(dest, "nested"));
assert.deepEqual(nestedFiles, ["three.css"]);
rimraf.sync(dest);
done();
});
});
it("should compile with --source-map set to directory", function (done) {
var src = fixture("input-directory/sass");
var dest = fixture("input-directory/css");
var destMap = fixture("input-directory/map");
var bin = spawn(cli, [src, "--output", dest, "--source-map", destMap]);
bin.once("close", function () {
var map = JSON.parse(read(fixture("input-directory/map/nested/three.css.map"), "utf8"));
assert.equal(map.file, "../../css/nested/three.css");
rimraf.sync(dest);
rimraf.sync(destMap);
done();
});
});
it("should skip files with an underscore", function (done) {
var src = fixture("input-directory/sass");
var dest = fixture("input-directory/css");
var bin = spawn(cli, [src, "--output", dest]);
bin.once("close", function () {
var files = fs.readdirSync(dest);
assert.equal(files.indexOf("_skipped.css"), -1);
rimraf.sync(dest);
done();
});
});
it("should ignore nested files if --recursive false", function (done) {
var src = fixture("input-directory/sass");
var dest = fixture("input-directory/css");
var bin = spawn(cli, [src, "--output", dest, "--recursive", false]);
bin.once("close", function () {
var files = fs.readdirSync(dest);
assert.deepEqual(files, ["one.css", "two.css"]);
rimraf.sync(dest);
done();
});
});
it("should error if no output directory is provided", function (done) {
var src = fixture("input-directory/sass");
var bin = spawn(cli, [src]);
bin.once("close", function (code) {
assert.notStrictEqual(code, 0);
assert.strictEqual(glob.sync(fixture("input-directory/**/*.css")).length, 0);
done();
});
});
it("should error if output directory is not a directory", function (done) {
var src = fixture("input-directory/sass");
var dest = fixture("input-directory/sass/one.scss");
var bin = spawn(cli, [src, "--output", dest]);
bin.once("close", function (code) {
assert.notStrictEqual(code, 0);
assert.equal(glob.sync(fixture("input-directory/**/*.css")).length, 0);
done();
});
});
it("should not error if output directory is a symlink", function (done) {
var outputDir = fixture("input-directory/css");
var src = fixture("input-directory/sass");
var symlink = fixture("symlinked-css");
fs.mkdirSync(outputDir);
fs.symlinkSync(outputDir, symlink);
var bin = spawn(cli, [src, "--output", symlink]);
bin.once("close", function () {
var files = fs.readdirSync(outputDir).sort();
assert.deepEqual(files, ["one.css", "two.css", "nested"].sort());
var nestedFiles = fs.readdirSync(path.join(outputDir, "nested"));
assert.deepEqual(nestedFiles, ["three.css"]);
rimraf.sync(outputDir);
fs.unlinkSync(symlink);
done();
});
});
});
describe("node-sass in.scss --output path/to/file/out.css", function () {
it("should create the output directory", function (done) {
var src = fixture("output-directory/index.scss");
var dest = fixture("output-directory/path/to/file/index.css");
var bin = spawn(cli, [src, "--output", path.dirname(dest)]);
bin.once("close", function () {
assert(fs.existsSync(path.dirname(dest)));
fs.unlinkSync(dest);
fs.rmdirSync(path.dirname(dest));
dest = path.dirname(dest);
fs.rmdirSync(path.dirname(dest));
dest = path.dirname(dest);
fs.rmdirSync(path.dirname(dest));
done();
});
});
});
describe("node-sass --follow --output output-dir input-dir", function () {
it("should compile with the --follow option", function (done) {
var src = fixture("follow/input-dir");
var dest = fixture("follow/output-dir");
fs.mkdirSync(src);
fs.symlinkSync(path.join(path.dirname(src), "foo"), path.join(src, "foo"), "dir");
var bin = spawn(cli, [src, "--follow", "--output", dest]);
bin.once("close", function () {
var expected = path.join(dest, "foo/bar/index.css");
fs.unlinkSync(path.join(src, "foo"));
fs.rmdirSync(src);
assert(fs.existsSync(expected));
fs.unlinkSync(expected);
expected = path.dirname(expected);
fs.rmdirSync(expected);
expected = path.dirname(expected);
fs.rmdirSync(expected);
fs.rmdirSync(dest);
done();
});
});
});
describe("importer", function () {
var dest = fixture("include-files/index.css");
var src = fixture("include-files/index.scss");
var expected = read(fixture("include-files/expected-importer.css"), "utf8").trim().replace(/\r\n/g, "\n");
it("should override imports and fire callback with file and contents", function (done) {
var bin = spawn(cli, [
src,
"--output",
path.dirname(dest),
"--importer",
fixture("extras/my_custom_importer_file_and_data_cb.js"),
]);
bin.once("close", function () {
assert.equal(read(dest, "utf8").trim(), expected);
fs.unlinkSync(dest);
done();
});
});
it("should override imports and fire callback with file", function (done) {
var bin = spawn(cli, [
src,
"--output",
path.dirname(dest),
"--importer",
fixture("extras/my_custom_importer_file_cb.js"),
]);
bin.once("close", function () {
if (fs.existsSync(dest)) {
assert.equal(read(dest, "utf8").trim(), "");
fs.unlinkSync(dest);
}
done();
});
});
it("should override imports and fire callback with data", function (done) {
var bin = spawn(cli, [
src,
"--output",
path.dirname(dest),
"--importer",
fixture("extras/my_custom_importer_data_cb.js"),
]);
bin.once("close", function () {
assert.equal(read(dest, "utf8").trim(), expected);
fs.unlinkSync(dest);
done();
});
});
it("should override imports and return file and contents", function (done) {
var bin = spawn(cli, [
src,
"--output",
path.dirname(dest),
"--importer",
fixture("extras/my_custom_importer_file_and_data.js"),
]);
bin.once("close", function () {
assert.equal(read(dest, "utf8").trim(), expected);
fs.unlinkSync(dest);
done();
});
});
it("should override imports and return file", function (done) {
var bin = spawn(cli, [
src,
"--output",
path.dirname(dest),
"--importer",
fixture("extras/my_custom_importer_file.js"),
]);
bin.once("close", function () {
if (fs.existsSync(dest)) {
assert.equal(read(dest, "utf8").trim(), "");
fs.unlinkSync(dest);
}
done();
});
});
it("should override imports and return data", function (done) {
var bin = spawn(cli, [
src,
"--output",
path.dirname(dest),
"--importer",
fixture("extras/my_custom_importer_data.js"),
]);
bin.once("close", function () {
assert.equal(read(dest, "utf8").trim(), expected);
fs.unlinkSync(dest);
done();
});
});
it("should accept arrays of importers and return respect the order", function (done) {
var bin = spawn(cli, [
src,
"--output",
path.dirname(dest),
"--importer",
fixture("extras/my_custom_arrays_of_importers.js"),
]);
bin.once("close", function () {
assert.equal(read(dest, "utf8").trim(), expected);
console.log("expected", read(dest, "utf8").trim());
fs.unlinkSync(dest);
done();
});
});
it("should return error for invalid importer file path", function (done) {
var bin = spawn(cli, [src, "--output", path.dirname(dest), "--importer", fixture("non/existing/path")]);
bin.once("close", function (code) {
assert.notStrictEqual(code, 0);
done();
});
});
it("should reflect user-defined Error", function (done) {
var bin = spawn(cli, [
src,
"--output",
path.dirname(dest),
"--importer",
fixture("extras/my_custom_importer_error.js"),
]);
bin.stderr.once("data", function (code) {
assert.equal(JSON.parse(code).message, "doesn't exist!");
done();
});
});
});
describe("functions", function () {
it("should let custom functions call setter methods on wrapped sass values (number)", function (done) {
var dest = fixture("custom-functions/setter.css");
var src = fixture("custom-functions/setter.scss");
var expected = read(fixture("custom-functions/setter-expected.css"), "utf8").trim().replace(/\r\n/g, "\n");
var bin = spawn(cli, [
src,
"--output",
path.dirname(dest),
"--functions",
fixture("extras/my_custom_functions_setter.js"),
]);
bin.once("close", function () {
assert.equal(read(dest, "utf8").trim(), expected);
fs.unlinkSync(dest);
done();
});
});
it("should properly convert strings when calling custom functions", function (done) {
var dest = fixture("custom-functions/string-conversion.css");
var src = fixture("custom-functions/string-conversion.scss");
var expected = read(fixture("custom-functions/string-conversion-expected.css"), "utf8")
.trim()
.replace(/\r\n/g, "\n");
var bin = spawn(cli, [
src,
"--output",
path.dirname(dest),
"--functions",
fixture("extras/my_custom_functions_string_conversion.js"),
]);
bin.once("close", function () {
assert.equal(read(dest, "utf8").trim(), expected);
fs.unlinkSync(dest);
done();
});
});
});
});