Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ecb5b7a

Browse files
committed
Merge branch 'bobpace-undo'
2 parents d4bc650 + 74f52cd commit ecb5b7a

File tree

6 files changed

+278
-15
lines changed

6 files changed

+278
-15
lines changed

.istanbul.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
instrumentation:
2-
# __get__ and __set__ will be stringified and evaled again. Thus it's difficult to include them into the test coverage
3-
excludes: ['lib/__get__.js', 'lib/__set__.js']
2+
# These functions will be stringified and evaled again. Thus it's difficult to include them into the test coverage
3+
excludes: [
4+
'lib/__get__.js',
5+
'lib/__set__.js',
6+
'lib/__with__.js'
7+
]

lib/__set__.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@
55
* All variables within this function are namespaced in the arguments array because every
66
* var declaration could possibly clash with a variable in the module scope.
77
*
8-
* @param {!String|!Object} varName name of the variable to set
8+
* @param {String|Object} varName name of the variable to set
99
* @param {String} varValue new value
10-
* @throws {TypeError}
11-
* @throws {ReferenceError} When the variable is unknown
12-
* @return {*}
10+
* @return {Function}
1311
*/
1412
function __set__() {
1513
arguments.varName = arguments[0];
1614
arguments.varValue = arguments[1];
1715
arguments.src = "";
16+
arguments.snapshot = {};
1817

1918
if (typeof arguments[0] === "object" && arguments.length === 1) {
2019
arguments.env = arguments.varName;
@@ -25,18 +24,24 @@ function __set__() {
2524
if (arguments.env.hasOwnProperty(arguments.varName)) {
2625
arguments.varValue = arguments.env[arguments.varName];
2726
arguments.src += arguments.varName + " = arguments.env." + arguments.varName + "; ";
27+
arguments.snapshot[arguments.varName] = eval(arguments.varName);
2828
}
2929
}
3030
} else if (typeof arguments.varName === "string" && arguments.length === 2) {
3131
if (!arguments.varName) {
3232
throw new TypeError("__set__ expects a non-empty string as a variable name");
3333
}
3434
arguments.src = arguments.varName + " = arguments.varValue;";
35+
arguments.snapshot[arguments.varName] = eval(arguments.varName);
3536
} else {
3637
throw new TypeError("__set__ expects an environment object or a non-empty string as a variable name");
3738
}
3839

3940
eval(arguments.src);
41+
42+
return function (snapshot) {
43+
module.exports.__set__(snapshot);
44+
}.bind(null, arguments.snapshot);
4045
}
4146

42-
module.exports = __set__;
47+
module.exports = __set__;

lib/__with__.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use strict";
2+
3+
/**
4+
* This function will be stringified and then injected into every rewired module.
5+
*
6+
* Calling myModule.__with__("myPrivateVar", newValue) returns a function where
7+
* you can place your tests. As long as the returned function is executed variables
8+
* will be set to the given value, after that all changed variables are reset back to normal.
9+
*
10+
* @param {String|Object} varName name of the variable to set
11+
* @param {String} varValue new value
12+
* @return {Function}
13+
*/
14+
function __with__() {
15+
var args = arguments;
16+
17+
return function (callback) {
18+
var undo,
19+
returned,
20+
isPromise;
21+
22+
if (typeof callback !== "function") {
23+
throw new TypeError("__with__ expects a callback function");
24+
}
25+
26+
undo = module.exports.__set__.apply(null, args);
27+
28+
try {
29+
returned = callback();
30+
isPromise = returned && typeof returned.then === "function";
31+
if (isPromise) {
32+
returned.then(undo, undo);
33+
return returned;
34+
}
35+
} finally {
36+
if (!isPromise) {
37+
undo();
38+
}
39+
}
40+
};
41+
}
42+
43+
module.exports = __with__;

lib/rewire.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
var Module = require("module"),
22
fs = require("fs"),
33
__get__ = require("./__get__.js"),
4-
__set__ = require("./__set__.js"),
4+
__set__ = require ("./__set__.js"),
5+
__with__ = require("./__with__.js"),
56
getImportGlobalsSrc = require("./getImportGlobalsSrc.js"),
67
detectStrictMode = require("./detectStrictMode.js"),
78
moduleEnv = require("./moduleEnv.js");
89

910
var __get__Src = __get__.toString(),
10-
__set__Src = __set__.toString();
11+
__set__Src = __set__.toString(),
12+
__with_Src = __with__.toString();
1113

1214
/**
1315
* Does actual rewiring the module. For further documentation @see index.js
@@ -44,6 +46,7 @@ function internalRewire(parentModulePath, targetPath) {
4446
appendix = "\n";
4547
appendix += "module.exports.__set__ = " + __set__Src + "; ";
4648
appendix += "module.exports.__get__ = " + __get__Src + "; ";
49+
appendix += "module.exports.__with__ = " + __with_Src + "; ";
4750

4851
// Check if the module uses the strict mode.
4952
// If so we must ensure that "use strict"; stays at the beginning of the module.
@@ -58,4 +61,4 @@ function internalRewire(parentModulePath, targetPath) {
5861
return targetModule.exports;
5962
}
6063

61-
module.exports = internalRewire;
64+
module.exports = internalRewire;

test/__set__.test.js

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ var expect = require("expect.js"),
22
__set__ = require("../lib/__set__.js"),
33
vm = require("vm"),
44

5-
expectReferenceError = expectError(ReferenceError),
65
expectTypeError = expectError(TypeError);
76

87
function expectError(ErrConstructor) {
@@ -12,16 +11,21 @@ function expectError(ErrConstructor) {
1211
}
1312

1413
describe("__set__", function () {
15-
var moduleFake;
14+
var moduleFake,
15+
undo;
1616

1717
beforeEach(function () {
1818
moduleFake = {
19+
module: {
20+
exports: {}
21+
},
1922
myValue: 0, // copy by value
2023
myReference: {} // copy by reference
2124
};
2225

2326
vm.runInNewContext(
24-
"__set__ = " + __set__.toString() + "; " +
27+
//__set__ requires __set__ to be present on module.exports
28+
"__set__ = module.exports.__set__ = " + __set__.toString() + "; " +
2529
"getValue = function () { return myValue; }; " +
2630
"getReference = function () { return myReference; }; ",
2731
moduleFake
@@ -69,8 +73,31 @@ describe("__set__", function () {
6973
expect(moduleFake.getValue()).to.be(2);
7074
expect(moduleFake.getReference()).to.be(newObj);
7175
});
72-
it("should return undefined", function () {
73-
expect(moduleFake.__set__("myValue", 4)).to.be(undefined);
76+
it("should return a function that when invoked reverts to the values before set was called", function () {
77+
undo = moduleFake.__set__("myValue", 4);
78+
expect(undo).to.be.a("function");
79+
expect(moduleFake.getValue()).to.be(4);
80+
undo();
81+
expect(moduleFake.getValue()).to.be(0);
82+
});
83+
it("should be able to revert when calling with an env-obj", function () {
84+
var newObj = { hello: "hello" };
85+
86+
expect(moduleFake.getValue()).to.be(0);
87+
expect(moduleFake.getReference()).to.eql({});
88+
89+
undo = moduleFake.__set__({
90+
myValue: 2,
91+
myReference: newObj
92+
});
93+
94+
expect(moduleFake.getValue()).to.be(2);
95+
expect(moduleFake.getReference()).to.be(newObj);
96+
97+
undo();
98+
99+
expect(moduleFake.getValue()).to.be(0);
100+
expect(moduleFake.getReference()).to.eql({});
74101
});
75102
it("should throw a TypeError when passing misfitting params", function () {
76103
expect(function () {

test/__with__.test.js

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
var expect = require("expect.js"),
2+
__with__ = require("../lib/__with__.js"),
3+
__set__ = require("../lib/__set__.js"),
4+
vm = require("vm"),
5+
6+
expectTypeError = expectError(TypeError);
7+
8+
function expectError(ErrConstructor) {
9+
return function expectReferenceError(err) {
10+
expect(err.constructor.name).to.be(ErrConstructor.name);
11+
};
12+
}
13+
14+
describe("__with__", function() {
15+
var moduleFake,
16+
newObj;
17+
18+
beforeEach(function () {
19+
moduleFake = {
20+
module: {
21+
exports: {}
22+
},
23+
myValue: 0, // copy by value
24+
myReference: {} // copy by reference
25+
};
26+
27+
newObj = { hello: "hello" };
28+
29+
vm.runInNewContext(
30+
//__with__ requires __set__ to be present on module.exports
31+
"module.exports.__set__ = " + __set__.toString() + "; " +
32+
"__with__ = " + __with__.toString() + "; " +
33+
"getValue = function () { return myValue; }; " +
34+
"getReference = function () { return myReference; }; ",
35+
moduleFake
36+
);
37+
});
38+
39+
it("should return a function", function () {
40+
expect(moduleFake.__with__({
41+
myValue: 2,
42+
myReference: newObj
43+
})).to.be.a("function");
44+
});
45+
46+
it("should return a function that can be invoked with a callback which guarantees __set__'s undo function is called for you at the end", function () {
47+
expect(moduleFake.getValue()).to.be(0);
48+
expect(moduleFake.getReference()).to.eql({});
49+
50+
moduleFake.__with__({
51+
myValue: 2,
52+
myReference: newObj
53+
})(function () {
54+
// changes will be visible from within this callback function
55+
expect(moduleFake.getValue()).to.be(2);
56+
expect(moduleFake.getReference()).to.be(newObj);
57+
});
58+
59+
// undo will automatically get called for you after returning from your callback function
60+
expect(moduleFake.getValue()).to.be(0);
61+
expect(moduleFake.getReference()).to.eql({});
62+
});
63+
64+
it("should also accept a variable name and a variable value (just like __set__)", function () {
65+
expect(moduleFake.getValue()).to.be(0);
66+
67+
moduleFake.__with__("myValue", 2)(function () {
68+
expect(moduleFake.getValue()).to.be(2);
69+
});
70+
71+
expect(moduleFake.getValue()).to.be(0);
72+
73+
expect(moduleFake.getReference()).to.eql({});
74+
75+
moduleFake.__with__("myReference", newObj)(function () {
76+
expect(moduleFake.getReference()).to.be(newObj);
77+
});
78+
79+
expect(moduleFake.getReference()).to.eql({});
80+
});
81+
82+
it("should still revert values if the callback throws an exception", function(){
83+
expect(function withError() {
84+
moduleFake.__with__({
85+
myValue: 2,
86+
myReference: newObj
87+
})(function () {
88+
throw new Error("something went wrong...");
89+
});
90+
}).to.throwError();
91+
expect(moduleFake.getValue()).to.be(0);
92+
expect(moduleFake.getReference()).to.eql({});
93+
});
94+
95+
it("should throw an error if something other than a function is passed as the callback", function() {
96+
var withFunction = moduleFake.__with__({
97+
myValue: 2,
98+
myReference: newObj
99+
});
100+
101+
function callWithFunction() {
102+
var args = arguments;
103+
104+
return function () {
105+
withFunction.apply(null, args);
106+
};
107+
}
108+
109+
expect(callWithFunction(1)).to.throwError(expectTypeError);
110+
expect(callWithFunction("a string")).to.throwError(expectTypeError);
111+
expect(callWithFunction({})).to.throwError(expectTypeError);
112+
expect(callWithFunction(function(){})).to.not.throwError(expectTypeError);
113+
});
114+
115+
describe("using promises", function () {
116+
var promiseFake;
117+
118+
beforeEach(function () {
119+
promiseFake = {
120+
then: function (onResolve, onReject) {
121+
promiseFake.onResolve = onResolve;
122+
promiseFake.onReject = onReject;
123+
}
124+
};
125+
});
126+
127+
it("should pass the returned promise through", function () {
128+
var fn = moduleFake.__with__({});
129+
130+
expect(fn(function () {
131+
return promiseFake;
132+
})).to.equal(promiseFake);
133+
});
134+
135+
it("should not undo any changes until the promise has been resolved", function () {
136+
expect(moduleFake.getValue()).to.be(0);
137+
expect(moduleFake.getReference()).to.eql({});
138+
139+
moduleFake.__with__({
140+
myValue: 2,
141+
myReference: newObj
142+
})(function () {
143+
return promiseFake;
144+
});
145+
146+
// the change should still be present at this point
147+
expect(moduleFake.getValue()).to.be(2);
148+
expect(moduleFake.getReference()).to.be(newObj);
149+
150+
promiseFake.onResolve();
151+
152+
// now everything should be back to normal
153+
expect(moduleFake.getValue()).to.be(0);
154+
expect(moduleFake.getReference()).to.eql({});
155+
});
156+
157+
it("should also undo any changes if the promise has been rejected", function () {
158+
expect(moduleFake.getValue()).to.be(0);
159+
expect(moduleFake.getReference()).to.eql({});
160+
161+
moduleFake.__with__({
162+
myValue: 2,
163+
myReference: newObj
164+
})(function () {
165+
return promiseFake;
166+
});
167+
168+
// the change should still be present at this point
169+
expect(moduleFake.getValue()).to.be(2);
170+
expect(moduleFake.getReference()).to.be(newObj);
171+
172+
promiseFake.onReject();
173+
174+
// now everything should be back to normal
175+
expect(moduleFake.getValue()).to.be(0);
176+
expect(moduleFake.getReference()).to.eql({});
177+
});
178+
179+
});
180+
181+
});

0 commit comments

Comments
 (0)