Wrapper

Wrapper

Wrapper is the main class for candy-wrapper. A wrapper goes around an existing function, method, or property enabling you to monitor the behavior of the underlying wrapped thing or optionally changing the behavior of the wrapped thing when you want to. If you are new to candy-wrapper, or wrappers in general, you may want to check out the Getting Started tutorial.

Below are all the different forms of the contructor, which are basically syntactic sugar for creating wrappers around lots of different kinds of things

// creates a wrapper around an object's method
new Wrapper(obj, method);

// createa a wrapper around an object's property
new Wrapper(obj, property);

// creates a wrapper around a function and returns the new wrapped function
// calling the old, unwrapped function doesn't trigger the wrapper
var func = new Wrapper(func);

// creates a new wrapped empty, anonymous function
var stub = new Wrapper();

// recursively wraps every property and method in an object
new Wrapper(obj);

// wraps a method but calls `func` instead of the underlying method
new Wrapper(obj, method, func);

// wraps a property but cals `func` instead of the setter / getter for the property
new Wrapper(obj, property, func)

Once you have wrapped a function or property, there are two main ways to work with it. One is through the historyList which contains all the historical calls to the Wrapper. The other is through Triggers which can change the behavior of the Wrapper as well as validating expectations every time the Wrapper is called. The historyList is a Filter array of all the calls that have been made, where each call is represented as a Operation. Filters have a number of convenience methods that make it easier to manipulate the historyList and select only the Operations that you are interested in validating.

Likewise, Triggers are executed on the Operation as it is being used during the call. A Trigger has the opportunity to set the arguments or context for functions or the set value for properties before the wrapped function or property is called, and Triggers are called a second time to modify the return value or exceptions that are thrown after the wrapped function or property has executed. Triggers can be created on a Wrapper using the methods that start with trigger and Triggers are evaluated in the order they are defined on the Wrapper.

Wrappers also hhave a number of configuration functions that define their behavior, such as when attempting to "rewrap" the same function or property. The config functions enable you to modify these behaviors. By default, rewrapping a function or property will return the existing Wrapper without throwing an error and without reseting any of the Triggers or historyList.

Note that the Wrapper class is generic to wrapping both functions and properties; however, not all methods are equally applicable to functions and properties. It does not make any sense to specify a "set value" for a function, nor does it make any sense to supply a "context" (this value) for a property. When attempting to use a function-specific method on a wrapped property, the method will detect this and throw an Error immediately; and the same is true for attempting to use property-specific methods on functions.

Here are some example of how to use a Wrapper:

// a simple test object
var myDrone = {
    name: "DJI",
    fly: function(direction) {
        return true;
    }
}

new Wrapper(myDrone, "name");
new Wrapper(myDrone, "fly");

myDrone.fly("north");
myDrone.fly("west");

// evaluating previous calls through the 'historyList' and 'Filters'
myDrone.fly.historyList.filterFirst().expectCallArgs("north"); // true
myDrone.fly.historyList.filterFirst().expectCallArgs("east"); // false
myDrone.fly.expectReportAllFailtures(); // throws an error about the "east" expecation failing

// modifying behavior using 'Triggers'
myDrone.fly.triggerOnCallArgs("east").actionReturn(false); // will return 'false' when called with "east"
myDrone.fly("east"); // false
myDrone.fly("west"); // true (the default return value)

// working with properties
myDrone.name.triggerOnSet().actionThrowException(new Error("do not set the name"));
myDrone.name = "Bob"; // throws Error: "do not set the name"
var ret = myDrone.name; // ret = "DJI"

// rewrapping
var flyWrapper = new Wrapper(myDrone, "fly"); // doesn't change anything, just returns the original wrapper
flyWrapper.configAllowRewrap(false); // disallow rewrapping
new Wrapper(myDrone, "fly"); // throws an error

// these sorts of things won't work
myDrone.fly.triggerOnSet()... // throws an error -- functions don't have 'set'
myDrone.fly.triggerOnCallContext()... // throws an error -- properties don't have 'this' values

Constructor

new Wrapper()

Source:

Creates a new Wrapper instance. Currently, wrapping properties have some notable corner cases:

  1. Wrapping a non-configurable property will result in an Error being thrown.
  2. Wrapping a non-writable property will not prevent that property from being written, and if getOwnPropertyDescriptor is called on the property, the writable attribute will shown up as undefined (since setters and getters) are being used by the Wrapper).
Throws:

If arguments aren't one of the valid signatures for the constructor.

Type
TypeError

Extends

  • Function

Members

chainable :Proxy.<Wrapper>

Source:

The thing that is returned representing the Wrapped and is intended to be used for chainable Wrapper calls

Type:

expectErrorList :Array.<String>

Source:

List of messages from failed expect calls.

Type:
  • Array.<String>

expectPassed :Boolean

Source:

Whether or not all expectations have passed on this wrapper.

Type:
  • Boolean

getterFn :function

Source:

An optional custom function that will be called when getting or setting the property.

Type:
  • function

historyList :Filter

Source:

A list of all the every time the Wrapper was used

Type:

origObj :Object

Source:

The original object that the property belongs to

Type:
  • Object

origPropDesc :Object

Source:

The results of getOwnPropertyDescriptor on the original property, saved so that it can be restored later.

Type:
  • Object

propValue :any

Source:

The current value of the property.

Type:
  • any

setterFn :function

Source:

An optional custom function that will be called when getting or setting the property.

Type:
  • function

triggerList :Array.<Trigger>

Source:

A list of Triggers to be run whenever this Wrapper is called.

Type:

type :String

Source:

The type of this Wrapper object. Options are "property" or "function".

Type:
  • String

uniquePlaceToKeepAGuidForCandyWrapper :String

Source:
Default Value:
  • "193fe616-09d1-4d5c-a5b9-ff6f3e79714c"

A nonsensical key and value used to identify if this object is a Wrapper

Type:
  • String

Methods

(static) getWrapperFromProperty(obj, key) → {Wrapper|null}

Source:

If the property key of Object obj is a wrapped property, the Wrapper for that property will be returned. Otherwise null will be returned. It is safe to use this in all instances, including if the property key isn't defined on the Object. Note that this will not return a Wrapper for a wrapped function / method.

Example
var testObj = {
    location: "home"
};
new Wrapper(testObj, "location"); // wrap the property 'testObj.location'
var wrapper = Wrapper.getWrapperFromProperty(testObj, "location"); // returns the wrapper created above
Parameters:
Name Type Description
obj Object

The Object for the obj[key] combination for which a Wrapper will attempt to be retrieved.

key String

The String for the obj[key] combination for which a Wrapper will attempt to be retrieved.

Returns:

Returns a Wrapper if the property has been wrapped, null otherwise.

Type
Wrapper | null

(static) isWrapper() → {Boolean}

Source:

This static method checks whether something is a Wrapper or not, similar to Array.isArray. Works on functions, methods, and properties. This constructor has multiple signatures as illustrated in the example below:

Example
var testFunction = function(){};

var testObject {
   prop: 1,
   meth: function() {}
}

Wrapper.isWrapper(testFunction); // false
testFunction = new Wrapper(testFunction);
Wrapper.isWrapper(testFunction); // true

Wrapper.isWrapper(testObject, "prop"); // false
new Wrapper(testObject, "prop");
Wrapper.isWrapper(testObject, "prop"); // true

Wrapper.isWrapper(testObject, "meth"); // false
new Wrapper(testObject, "meth");
Wrapper.isWrapper(testObject, "meth"); // true
Returns:

Returns true if the arguments are a Wrapper, false otherwise.

Type
Boolean

(static) unwrap(…args) → {function|Object|any}

Source:
See:

This static method attempts to remove and destroy the Wrapper on the function, method, property or object that is passed to it. If an object has a mix of wrapped and non-wrapped methods and properties this unwrap will automatically detect and unwrap just those items which hvae been wrapped.

Example
func = Wrapper.unwrap(func); // unwraps this function
Wrapper.unwrap(obj, "property"); // unwraps the property or method at obj[property]
Wrapper.unwrap(obj); // recursively unwraps all properties and methods on 'obj'
Parameters:
Name Type Attributes Description
args any <repeatable>

See examples for the various signatures for this method.

Returns:

The original function, object, or the value of the property that has now been unwrapped.

Type
function | Object | any

configAllowRewrap(allow)

Source:

Determines whether wrapping a Wrapper should be allowed. Default is true, meaning that any attempt to wrap a function or property that is already wrapped will result in the currently-in-place Wrapper being returned (a new Wrapper will not be created). By passing false to configAllowRewrap, any attempt to rewrap a funciton or property will result in an Error being thrown.

Parameters:
Name Type Description
allow Boolean

If true rewrapping will be allowed. If false an error will be thrown when attempting to rewrap.

Throws:

If wrong number of args, or non-Boolean arguments

Type
TyperError

configCallUnderlying(callUnderlying)

Source:

By default, the wrapped function or properter setter / getter will get called when the Wrapper gets called. This configuration option allows disabling calling of the underlying function or property setter / getter.

Parameters:
Name Type Description
callUnderlying Boolean

If true, future calls to the Wrapper will invoke the underlying function or proprty setter / getter. If false, future calls to the Wrapper will NOT invoke the underlying function or proprty setter / getter.

Throws:

If wrong number of args, or non-Boolean arguments

Type
TyperError

configDefault()

Source:

Resets the wrapper to the default configuration, undoing the effects of any config calls.

configExpectThrows(doesThrow)

Source:

By default, failed expectations will not throw on Filters, but they will fail on Triggers. configExpectThrows can be used to configure a Wrapper so that all of its expectations will throw an ExpectError.

Parameters:
Name Type Description
doesThrow Boolean

If true all expect functions will throw an ExpectError immediately upon failure. If false, expect functions will maintain their default behavior.

Throws:

If wrong number of args, or non-Boolean arguments

Type
TyperError

configExpectThrowsOnTrigger(doesThrow)

Source:

Configures whether or not Triggers will throw an ExpectError immediately if the expectation fails.

Parameters:
Name Type Description
doesThrow Boolean

If true the expectation will throw an ExpectError immediately if the expecation fails. If false, it will save the failure message to be retrieved later with expectReportAllFailures.

Throws:

If wrong number of args, or non-Boolean arguments

Type
TyperError

configReset() → {Wrapper}

Source:

Resets the wrapper to its default state. Clears out all Operation records of previous calls, expected behaviors, pass / fail results, etc. This does not reset any configuration options, which configDefault can be used for.

Returns:

Returns the Wrapper object so that this call can be chained

Type
Wrapper

configSwallowExpectException(swallowException)

Source:

When a Trigger expects an exception, it won't stop the exception from being thrown. When the swallowException argument to configSwallowExpectException is true, it will prevent the specific exception that was expected from being thrown. This is useful for testing, where tests may expect an error to be thrown -- which is actually indicates a successful test case. Allowing the error to be thrown would cause the test to fail. Note that this only swallows the specific Error that is expected, and all other errors will still be thrown.

Note that even if an exception is swallowed, it will still be recorded to the Wrapper's historyList.

Parameters:
Name Type Description
swallowException Boolean

If set to true any expected exceptions won't be thrown; if false, the Error will be thrown anyway.

Throws:

If wrong number of args, or non-Boolean arguments

Type
TyperError

configUnwrap() → {function|Object}

Source:

Restores the wrapped property or function to its original value, and destroys this Wrapper.

Example
var w = new Wrapper(obj, "method"); // create a new wrapper
w.configUnwrap(); // destroys the wrapper
Returns:

The original function or object that was wrapped.

Type
function | Object

expectReportAllFailures(clear) → {Boolean}

Source:

The typical behavior for expectations called against a Filter, Operation is that they will return true or false immediately. This allows them to be used with assertion libraries, such as Chai. Alternatively, expectReportAllFailures allows you to run all your expectations and then report all the expectations that did not pass.

If all your expectations passed, this function will return true. If one or more of your expectations failed this function will aggregate all the failure messages and throw a ExpectError that includes all the error messages.

An example error message might look something like:

ExpectError: 2 expectation(s) failed:
    expectReturn: expectation failed for: 10
    expectReturn: expectation failed for: 23

expectReportAllFailures draws upon a list of messages stored in the wrapper at Wrapper.expectErrorList. This list will continue to be added to as new expectations fail. expectReportAllFailures includes a convenience argument, clear, that will clear out the previous expectation messages and status so that a fresh set of expectations can be built up.

Note that the default behavior for Triggers is to immediately throw an ExpectError if the expectation fails. This prevents them from adding expectation failures to the list for future reporting. This behavior may be changed by calling configExpectThrowsOnTrigger. Likewise, if configExpectThrows has been set so that expecations always throw, there will be nothing cached for future validation.

Parameters:
Name Type Description
clear Boolean

If truthy, the list of previous exception results will be cleared out. If absent or falsey the results will continue to be added to as new expectations are run.

Throws:

If any expectations have been evaluated and failed, but not immediately thrown.

Type
ExpectError
Returns:

Returns true if all expectations have successfully passed.

Type
Boolean

triggerAlways() → {Trigger}

Source:

Creates a new Trigger on the Wrapper that always executes.

Returns:

The Trigger that was created.

Type
Trigger

triggerOnCallArgs(…args) → {Trigger}

Source:

Creates a new Trigger on the Wrapper that executes when the arguments to the wrapper match the arguments to this call.

Example
// create a Wrapper around something
var w = new Wrapper(obj, "method");

// create a trigger that executes whenever the arguments exactly "beer"
w.triggerOnCallArgs("drink", "beer")
     .actionReturn("yum!");

// call the wrapped method and see what happens...
obj.method("drink", "beer") // returns "yum!"
Parameters:
Name Type Attributes Description
args any <repeatable>

A list of any arguments that, when matched, will cause this Trigger to execute.

Throws:

If called on a wrapped property

Type
TypeError
Returns:

The Trigger that was created.

Type
Trigger

triggerOnCallContext(context) → {Trigger}

Source:

Creates a new Trigger on the Wrapper that executes when the this value of the wrapped function matches the context argument.

Example
// create a Wrapper around something
var w = new Wrapper(obj, "method");

// create a trigger that executes whenever the arguments exactly "beer"
w.triggerOnCallContext({location: "home"})
     .actionThrowException(new Error("You should be at work right now."));

// call the wrapped method and see what happens...
obj.method.call({location: "home"}, "hi mom") // throws "You should be at work right now."
Parameters:
Name Type Description
context Object

An Object that, when matched to the this value of the function, will cause the Trigger to execute.

Throws:

If called on a wrapped property

Type
TypeError
Returns:

The Trigger that was created.

Type
Trigger

triggerOnCallNumber(num) → {Trigger}

Source:

Creates a new Trigger on the Wrapper that executes the Nth time the wrapper is called.

Example
// create a Wrapper around something
var w = new Wrapper(obj, "method");

// create a trigger that executes whenever the arguments exactly "beer"
w.triggerOnCallNumber(2)
     .actionThrowException(new Error("BOOM!"));

// call the wrapped method and see what happens...
obj.method(); // nothing...
obj.method(); // still nothing...
obj.method(); // throws "BOOM!"
Parameters:
Name Type Description
num Number

What call number this Trigger should execute on. Counting starts from zero, so the first call is 0, the second is 1, etc.

Throws:

If called on a wrapped property or if first argument isn't a number

Type
TypeError
Returns:

The Trigger that was created.

Type
Trigger

triggerOnCustom(cb) → {Trigger}

Source:

Creates a Trigger that gets executed based on the callback function cb.

Parameters:
Name Type Description
cb Trigger~triggerCustomCallback

The callback function. See triggerCustomCallback for the description of the expected syntax and behavior of the callback.

Throws:

If the cb argument isn't a Function

Type
TypeError
Returns:

The Trigger that was created

Type
Trigger

triggerOnException(err) → {Trigger}

Source:

Creates a Trigger on the Wrapper that executes whenever the exception matching err is thrown by the wrapped function / getter / setter.

Parameters:
Name Type Description
err Error | null

Any Error (or class that inherits from Error, such as TypeError, RangeError, or custom Errors). If err exactly matches the error thrown then this trigger will execute. Exactly matching an Error requires that the Error.name and Error.message are strictly equal. If err is null, this Trigger will execute when there was no error.

Throws:

If argument isn't an Error or null

Type
TypeError
Returns:

The Trigger that was created.

Type
Trigger

triggerOnGet() → {Trigger}

Source:

Creates a Trigger on the Wrapper that executes when the value of the property is retrieved.

Example
var myObj = {
    name: "Bob"
}

new Wrapper(myObj, "name");
myObj.name.triggerOnGet()
    .actionThrowException(new Error("BOOM!"));

myObj.name = "Mary"; // doesn't throw
var ret = myObj.name; // throws "BOOM!"
Throws:

If called on a wrapped function

Type
TypeError
Returns:

The Trigger that was created.

Type
Trigger

triggerOnGetNumber(num) → {Trigger}

Source:

Creates a Trigger on the Wrapper that executes the Nth time that the property value is retrieved.

Example
var myObj = {
    name: "Bob"
}

new Wrapper(myObj, "name");
myObj.name.triggerOnGetNumber(2)
    .actionThrowException(new Error("BOOM!"));

var ret;
ret = myObj.name; // doesn't throw
ret = myObj.name; // doesn't throw
ret = myObj.name; // throws "BOOM!"
Parameters:
Name Type Description
num number

The Nth get that will cause this Trigger to execute. Note that 0 would be the first assignment, 1 the second, etc.

Throws:

If called on a wrapped function, or first argument isn't a number

Type
TypeError
Returns:

The Trigger that was created.

Type
Trigger

triggerOnReturn(retVal) → {Trigger}

Source:

Creates a Trigger on the Wrapper that executes whenever the return value of the wrapped function / getter / setter matches the value specified here.

Parameters:
Name Type Description
retVal any

The return value that will cause this Trigger to execute. Note that undefined is allowable, but should be explicitly passed as undefined rather than simply not passing an argument.

Returns:

The Trigger that was created.

Type
Trigger

triggerOnSet() → {Trigger}

Source:

Creates a Trigger on the Wrapper that executes whenever a property is set. Only valid for Wrappers around a property.

Example
var myObj = {
    name: "Bob"
}

new Wrapper(myObj, "name");
myObj.name.triggerOnSet()
    .actionThrowException(new Error("BOOM!"));

var ret = myObj.name; // doesn't throw
myObj.name = "Mary"; // throws "BOOM!"
Throws:

If called on a wrapped function

Type
TypeError
Returns:

The Trigger that was created.

Type
Trigger

triggerOnSetNumber(num) → {Trigger}

Source:

Creates a Trigger on the Wrapper that executes the Nth time that the property is assigned to, where Nth corresponds to num

Parameters:
Name Type Description
num Number

The Nth assignment that will cause this Trigger to execute. Note that 0 would be the first assignment, 1 the second, etc.

Throws:

If called on a wrapped function, or argument isn't a number

Type
TypeError
Returns:

The Trigger that was created.

Type
Trigger

triggerOnSetVal(setVal) → {Trigger}

Source:

Creates a Trigger on the Wrapper that executes when a property is set to the value corresponding to setVal

Parameters:
Name Type Description
setVal any

When a property is set to this value, the Trigger will execute.

Throws:

If called on a wrapped function

Type
TypeError
Returns:

The Trigger that was created.

Type
Trigger

triggerOnTouchNumber(num) → {Trigger}

Source:

Creates a Trigger on the Wrapper that execuates the Nth time that the property is set OR get.

Example
var myObj = {
    name: "Bob"
}

new Wrapper(myObj, "name");
myObj.name.triggerOnTouchNumber(2)
    .actionThrowException(new Error("BOOM!"));

var ret;
ret = myObj.name; // doesn't throw
myObj.name = "Mary"; // doesn't throw
ret = myObj.name; // throws "BOOM!"
Parameters:
Name Type Description
num Number

The Nth get that will cause this Trigger to execute. Note that 0 would be the first assignment, 1 the second, etc.

Throws:

If called on a wrapped function, or first argument isn't a number

Type
TypeError
Returns:

The Trigger that was created.

Type
Trigger