UNPKG

globwatcher

Version:

watch a set of files for changes (including create/delete) by glob patterns

466 lines (430 loc) 18 kB
"use strict"; var fs = require("fs"); var globwatcher = require("../../lib/globwatcher/globwatcher"); var minimatch = require("minimatch"); var mocha_sprinkles = require("mocha-sprinkles"); var path = require("path"); var Promise = require("bluebird"); var shell = require("shelljs"); var should = require("should"); var touch = require("touch"); var util = require("util"); var future = mocha_sprinkles.future; var withTempFolder = mocha_sprinkles.withTempFolder; require("source-map-support").install(); function makeFixtures(folder, ts) { if (!ts) ts = Date.now() - 1000; ["" + folder + "/one.x", "" + folder + "/sub/one.x", "" + folder + "/sub/two.x", "" + folder + "/nested/three.x", "" + folder + "/nested/weird.jpg"].map(function (file) { shell.mkdir("-p", path.dirname(file)); touch.sync(file, { mtime: ts }); }); } function fixtures(f) { return future(withTempFolder(function (folder) { makeFixtures(folder); return f(folder); })); } // create a new globwatch, run a test, and close it function withGlobwatcher(pattern, options, f) { if (f == null) { f = options; options = {}; } options.persistent = false; var g = globwatcher.globwatcher(pattern, options); return g.ready["finally"](function () { return f(g); })["finally"](function () { return g.close(); }); } // capture add/remove/change into an object for later inspection function capture(g) { var summary = {}; function push(name, item) { if (summary[name] == null) summary[name] = []; summary[name].push(item); summary[name].sort(); } g.on("added", function (filename) { return push("added", filename); }); g.on("deleted", function (filename) { return push("deleted", filename); }); g.on("changed", function (filename) { return push("changed", filename); }); return summary; } describe("globwatcher", function () { it("folderMatchesMinimatchPrefix", function () { var set = new minimatch.Minimatch("/home/commie/**/*.js", { nonegate: true }).set[0]; globwatcher.folderMatchesMinimatchPrefix(["", "home"], set).should.equal(true); globwatcher.folderMatchesMinimatchPrefix(["", "home", "commie"], set).should.equal(true); globwatcher.folderMatchesMinimatchPrefix(["", "home", "robey"], set).should.equal(false); globwatcher.folderMatchesMinimatchPrefix(["", "home", "commie", "rus"], set).should.equal(true); set = new minimatch.Minimatch("/home/commie/l*/*.js", { nonegate: true }).set[0]; globwatcher.folderMatchesMinimatchPrefix(["", "home"], set).should.equal(true); globwatcher.folderMatchesMinimatchPrefix(["", "home", "commie"], set).should.equal(true); globwatcher.folderMatchesMinimatchPrefix(["", "home", "robey"], set).should.equal(false); globwatcher.folderMatchesMinimatchPrefix(["", "home", "commie", "rus"], set).should.equal(false); globwatcher.folderMatchesMinimatchPrefix(["", "home", "commie", "lola"], set).should.equal(true); globwatcher.folderMatchesMinimatchPrefix(["", "home", "commie", "lola", "prissy"], set).should.equal(false); }); it("addWatch", future(function () { return withGlobwatcher("/~~nonexistent~~", function (g) { ["/absolute.txt", "/sub/absolute.txt", "/deeply/nested/file/why/nobody/knows.txt"].forEach(function (f) { return g.addWatch(f); }); g.watchMap.getFolders().sort().should.eql(["/", "/deeply/nested/file/why/nobody/", "/sub/"]); g.watchMap.getFilenames("/").should.eql(["/absolute.txt"]); g.watchMap.getFilenames("/sub/").should.eql(["/sub/absolute.txt"]); g.watchMap.getFilenames("/deeply/nested/file/why/nobody/").should.eql(["/deeply/nested/file/why/nobody/knows.txt"]); }); })); it("can parse patterns", fixtures(function (folder) { return withGlobwatcher("" + folder + "/**/*.x", function (g) { g.patterns.should.eql(["" + folder + "/**/*.x"]); Object.keys(g.watchers).sort().should.eql(["" + folder + "/", "" + folder + "/nested/", "" + folder + "/sub/"]); g.watchMap.getFolders().sort().should.eql(["" + folder + "/", "" + folder + "/nested/", "" + folder + "/sub/"]); g.watchMap.getFilenames("" + folder + "/").should.eql(["" + folder + "/one.x"]); g.watchMap.getFilenames("" + folder + "/nested/").should.eql(["" + folder + "/nested/three.x"]); g.watchMap.getFilenames("" + folder + "/sub/").should.eql(["" + folder + "/sub/one.x", "" + folder + "/sub/two.x"]); }); })); it("can parse patterns relative to cwd", fixtures(function (folder) { return withGlobwatcher("**/*.x", { cwd: "" + folder + "/sub" }, function (g) { g.patterns.should.eql(["" + folder + "/sub/**/*.x"]); Object.keys(g.watchers).sort().should.eql(["" + folder + "/sub/"]); g.watchMap.getFolders().sort().should.eql(["" + folder + "/sub/"]); g.watchMap.getFilenames("" + folder + "/sub/").should.eql(["" + folder + "/sub/one.x", "" + folder + "/sub/two.x"]); }); })); it("handles odd relative paths", fixtures(function (folder) { return withGlobwatcher("../sub/**/*.x", { cwd: "" + folder + "/nested" }, function (g) { Object.keys(g.watchers).sort().should.eql(["" + folder + "/sub/"]); g.watchMap.getFolders().sort().should.eql(["" + folder + "/sub/"]); g.watchMap.getFilenames("" + folder + "/sub/").should.eql(["" + folder + "/sub/one.x", "" + folder + "/sub/two.x"]); }); })); it("notices new files", fixtures(function (folder) { var summary = null; return withGlobwatcher("" + folder + "/**/*.x", function (g) { summary = capture(g); touch.sync("" + folder + "/nested/four.x"); touch.sync("" + folder + "/sub/not-me.txt"); return g.check().then(function () { summary.should.eql({ added: ["" + folder + "/nested/four.x"] }); }); }); })); it("doesn't emit signals when turned off", fixtures(function (folder) { var summary = null; return withGlobwatcher("" + folder + "/**/*.x", function (g) { summary = capture(g); g.stopWatches(); return Promise.delay(50).then(function () { touch.sync("" + folder + "/nested/four.x"); touch.sync("" + folder + "/sub/not-me.txt"); g.check(); }).then(function () { // just in case, make sure the timer goes off too return Promise.delay(g.interval * 2); }).then(function () { summary.should.eql({}); }); }); })); it("notices new files only in cwd", fixtures(function (folder) { var summary = null; return withGlobwatcher("**/*.x", { cwd: "" + folder + "/sub" }, function (g) { summary = capture(g); touch.sync("" + folder + "/nested/four.x"); touch.sync("" + folder + "/sub/not-me.txt"); touch.sync("" + folder + "/sub/four.x"); return g.check().then(function () { summary.should.eql({ added: ["" + folder + "/sub/four.x"] }); }); }); })); it("notices new files nested deeply", fixtures(function (folder) { var summary = null; return withGlobwatcher("" + folder + "/**/*.x", function (g) { summary = capture(g); shell.mkdir("-p", "" + folder + "/nested/more/deeply"); touch.sync("" + folder + "/nested/more/deeply/nine.x"); return g.check().then(function () { summary.should.eql({ added: ["" + folder + "/nested/more/deeply/nine.x"] }); }); }); })); it("notices deleted files", fixtures(function (folder) { var summary = null; return withGlobwatcher("**/*.x", { cwd: "" + folder }, function (g) { summary = capture(g); fs.unlinkSync("" + folder + "/sub/one.x"); return g.check().then(function () { summary.should.eql({ deleted: ["" + folder + "/sub/one.x"] }); }); }); })); it("notices a rename as an add + delete", fixtures(function (folder) { var summary = null; return withGlobwatcher("**/*.x", { cwd: "" + folder }, function (g) { summary = capture(g); fs.renameSync("" + folder + "/sub/two.x", "" + folder + "/sub/twelve.x"); return g.check().then(function () { summary.should.eql({ added: ["" + folder + "/sub/twelve.x"], deleted: ["" + folder + "/sub/two.x"] }); }); }); })); it("handles a nested delete", fixtures(function (folder) { shell.mkdir("-p", "" + folder + "/nested/more/deeply"); touch.sync("" + folder + "/nested/more/deeply/here.x"); var summary = null; return withGlobwatcher("**/*.x", { cwd: "" + folder }, function (g) { summary = capture(g); shell.rm("-r", "" + folder + "/nested"); return g.check().then(function () { summary.should.eql({ deleted: ["" + folder + "/nested/more/deeply/here.x", "" + folder + "/nested/three.x"] }); }); }); })); it("handles a changed file", fixtures(function (folder) { var summary = null; return withGlobwatcher("**/*.x", { cwd: "" + folder }, function (g) { summary = capture(g); fs.writeFileSync("" + folder + "/sub/one.x", "gahhhhhh"); return g.check().then(function () { summary.should.eql({ changed: ["" + folder + "/sub/one.x"] }); }); }); })); it("follows a safe-write", fixtures(function (folder) { var summary = null; var savee = "" + folder + "/one.x"; var backup = "" + folder + "/one.x~"; return withGlobwatcher("**/*.x", { cwd: "" + folder }, function (g) { summary = capture(g); fs.writeFileSync(backup, fs.readFileSync(savee)); fs.unlinkSync(savee); fs.renameSync(backup, savee); return g.check().then(function () { summary.should.eql({ changed: [savee] }); }); }); })); it("only emits once for a changed file", fixtures(function (folder) { var summary = null; return withGlobwatcher("**/*.x", { cwd: "" + folder }, function (g) { summary = capture(g); fs.writeFileSync("" + folder + "/one.x", "whee1"); return g.check().then(function () { summary.should.eql({ changed: ["" + folder + "/one.x"] }); return g.check(); }).then(function () { summary.should.eql({ changed: ["" + folder + "/one.x"] }); }); }); })); it("emits twice if a file was changed twice", fixtures(function (folder) { var summary = null; return withGlobwatcher("**/*.x", { cwd: "" + folder }, function (g) { summary = capture(g); fs.writeFileSync("" + folder + "/one.x", "whee1"); return g.check().then(function () { summary.should.eql({ changed: ["" + folder + "/one.x"] }); fs.writeFileSync("" + folder + "/one.x", "whee123"); return g.check(); }).then(function () { summary.should.eql({ changed: ["" + folder + "/one.x", "" + folder + "/one.x"] }); }); }); })); it("doesn't mind watching a nonexistent folder", fixtures(function (folder) { return withGlobwatcher("" + folder + "/not/there/*", function (g) { 3..should.equal(3); }); })); it("sees a new matching file even if the whole folder was missing when it started", future(withTempFolder(function (folder) { var summary = null; return withGlobwatcher("" + folder + "/not/there/*", function (g) { summary = capture(g); shell.mkdir("-p", "" + folder + "/not/there"); fs.writeFileSync("" + folder + "/not/there/ten.x", "wheeeeeee"); return g.check().then(function () { summary.should.eql({ added: ["" + folder + "/not/there/ten.x"] }); }); }); }))); it("sees a new matching file even if nested folders were missing when it started", fixtures(function (folder) { var summary = null; return withGlobwatcher("" + folder + "/sub/deeper/*.x", function (g) { summary = capture(g); shell.mkdir("-p", "" + folder + "/sub/deeper"); fs.writeFileSync("" + folder + "/sub/deeper/ten.x", "wheeeeeee"); return g.check().then(function () { summary.should.eql({ added: ["" + folder + "/sub/deeper/ten.x"] }); }); }); })); it("sees a new matching file even if the entire tree was erased and re-created", fixtures(function (folder) { shell.rm("-rf", "" + folder + "/nested"); shell.mkdir("-p", "" + folder + "/nested/deeper/still"); touch.sync("" + folder + "/nested/deeper/still/four.x"); var summary = null; return withGlobwatcher("" + folder + "/**/*", function (g) { summary = capture(g); shell.rm("-r", "" + folder + "/nested"); return g.check().then(function () { summary.should.eql({ deleted: ["" + folder + "/nested/deeper/still/four.x"] }); delete summary.deleted; shell.mkdir("-p", "" + folder + "/nested/deeper/still"); fs.writeFileSync("" + folder + "/nested/deeper/still/ten.x", "wheeeeeee"); return g.check(); }).then(function () { summary.should.eql({ added: ["" + folder + "/nested/deeper/still/ten.x"] }); }); }); })); it("sees a new matching file even if the folder exists but was empty", fixtures(function (folder) { shell.mkdir("-p", "" + folder + "/nested/deeper"); var summary = null; return withGlobwatcher("" + folder + "/nested/deeper/*.x", function (g) { summary = capture(g); fs.writeFileSync("" + folder + "/nested/deeper/ten.x", "wheeeeeee"); return g.check().then(function () { summary.should.eql({ added: ["" + folder + "/nested/deeper/ten.x"] }); }); }); })); it("emits signals for folders when asked", fixtures(function (folder) { var summary = null; return withGlobwatcher("" + folder + "/**/*", { emitFolders: true }, function (g) { summary = capture(g); shell.mkdir("-p", "" + folder + "/newfolder"); shell.rm("-r", "" + folder + "/nested"); return g.check().then(function () { summary.should.eql({ added: ["" + folder + "/newfolder/"], deleted: ["" + folder + "/nested/", "" + folder + "/nested/three.x", "" + folder + "/nested/weird.jpg"] }); }); }); })); it("will watch a single, non-globbed file that doesn't exist", fixtures(function (folder) { var summary = null; return withGlobwatcher("" + folder + "/nothing.x", function (g) { summary = capture(g); fs.writeFileSync("" + folder + "/nothing.x", "hi!"); return Promise.delay(g.interval).then(function () { summary.should.eql({ added: ["" + folder + "/nothing.x"] }); }); }); })); it("returns a currentSet", fixtures(function (folder) { return withGlobwatcher("" + folder + "/**/*.x", function (g) { g.currentSet().sort().should.eql(["" + folder + "/nested/three.x", "" + folder + "/one.x", "" + folder + "/sub/one.x", "" + folder + "/sub/two.x"]); shell.rm("" + folder + "/sub/one.x"); fs.writeFileSync("" + folder + "/whatevs.x"); return g.check().then(function () { g.currentSet().sort().should.eql(["" + folder + "/nested/three.x", "" + folder + "/one.x", "" + folder + "/sub/two.x", "" + folder + "/whatevs.x"]); }); }); })); describe("takes a snapshot", function () { it("of globs", fixtures(function (folder) { return withGlobwatcher("" + folder + "/**/*.x", { snapshot: {} }, function (g) { var ts = fs.statSync("" + folder + "/one.x").mtime.getTime(); fs.writeFileSync("" + folder + "/wut.x", "hello"); touch.sync("" + folder + "/wut.x", { mtime: ts }); return g.check().then(function () { var snapshot = g.snapshot(); snapshot["" + folder + "/one.x"].should.eql({ mtime: ts, size: 0 }); snapshot["" + folder + "/wut.x"].should.eql({ mtime: ts, size: 5 }); snapshot["" + folder + "/nested/three.x"].should.eql({ mtime: ts, size: 0 }); snapshot["" + folder + "/sub/one.x"].should.eql({ mtime: ts, size: 0 }); snapshot["" + folder + "/sub/two.x"].should.eql({ mtime: ts, size: 0 }); }); }); })); it("of normal files", fixtures(function (folder) { return withGlobwatcher("" + folder + "/sub/two.x", { snapshot: {} }, function (g) { var ts = fs.statSync("" + folder + "/sub/two.x").mtime.getTime(); return g.check().then(function () { var snapshot = g.snapshot(); snapshot["" + folder + "/sub/two.x"].should.eql({ mtime: ts, size: 0 }); fs.writeFileSync("" + folder + "/sub/two.x", "new!"); ts = fs.statSync("" + folder + "/sub/two.x").mtime.getTime(); return withGlobwatcher("" + folder + "/sub/two.x", { snapshot: snapshot }, function (g) { return g.check().then(function () { snapshot = g.snapshot(); snapshot["" + folder + "/sub/two.x"].should.eql({ mtime: ts, size: 4 }); }); }); }); }); })); }); it("resumes from a snapshot", fixtures(function (folder) { return withGlobwatcher("" + folder + "/**/*.x", function (g) { var summary = null; var snapshot = g.snapshot(); g.close(); return Promise.delay(100).then(function () { fs.writeFileSync("" + folder + "/one.x", "hello"); shell.rm("" + folder + "/sub/two.x"); touch.sync("" + folder + "/sub/nine.x"); g = globwatcher.globwatcher("" + folder + "/**/*.x", { persistent: false, snapshot: snapshot }); summary = capture(g); return g.ready; }).then(function () { summary.should.eql({ added: ["" + folder + "/sub/nine.x"], changed: ["" + folder + "/one.x"], deleted: ["" + folder + "/sub/two.x"] }); }); }); })); }); //# sourceMappingURL=test_globwatcher.js.map