I'm attempting to DRY up some jasmine tests by extracting out shared examples.
#sharedExamplesForThing = (thing) ->
beforeEach ->
#thingy = new thing
it "is neat", ->
expect(#thingy.neat).toBeTruthy()
describe "widget with shared behavior", ->
sharedExamplesForThing(-> new Widget)
This works nicely when everything is defined in one file. The problems I'm encountering occur when I try to move the sharedExamples to a separate file. I get Can't find variable: sharedExamplesForThing ...
So in the interest of debugging, I tried the following:
describe "widget with shared behavior", ->
it "is acting like a meany", ->
console.log sharedExamplesForThing
expect(false).toBeTruthy()
sharedExamplesForThing(-> new Widget)
In the is acting like a meany block, the log shows sharedExamplesForThing as [Function] but I still get the Can't find variable outside the it. I feel like this might have something to do with a scoping issue outside of my current experience, but I could be completely wrong about that. What am I missing here?
(using rails, jasminerice, guard-jasmine)
I found the piece on shared examples from thoughtbot really useful.
I implemented it in coffeescript as follows:
1) In some helper file that is loaded before all spec files:
window.FooSpec =
sharedExamples: {}
window.itShouldBehaveLike = (->
exampleName = _.first(arguments)
exampleArguments =
_.select(_.rest(arguments), ((arg) => return !_.isFunction(arg)))
innerBlock = _.detect(arguments, ((arg) => return _.isFunction(arg)))
exampleGroup = FooSpec.sharedExamples[exampleName]
if(exampleGroup)
return describe(exampleName, (->
exampleGroup.apply(this, exampleArguments)
if(innerBlock) then innerBlock()
)
)
else
return it("cannot find shared behavior: '" + exampleName + "'", (->
expect(false).toEqual(true)
)
)
)
2) For my specs:
(a) I can define a shared behaviour:
FooSpec.sharedExamples["had a good day"] = (->
it "finds good code examples", ->
expect(this.thoughtbot_spy).toHaveBeenCalled()
)
(b) And use it anywhere in some spec as:
itShouldBehaveLike("had a good day")
(Note: I am assuming the spec has defined this.thoughtbot_spy accordingly before the above line)
When you assign a top-level member variable in CoffeeScript it is assigned as a property of the global object (window in a Browser). So it generates the following JavaScript:
window.sharedExamplesForThing = ...;
That means that you can refer to it outside the file as window.sharedExamplesForThing or just sharedExamplesForThing. So what you are doing should work assuming the shared example file has been loaded before the spec file. I think the problem you have having is that the spec file is loaded first (because describe functions are run as the file is loaded whereas it functions are run after all the files have loaded). So you might need to adjust the load order, you could try putting your shared examples files in a support directory and then requiring this first.
Rather than assigning variables directly to the window object it may be better to set up a namespace to export your shared variables into (so that you don't clutter the global object):
window.MyNamespace = {}
MyNamespace.sharedExamplesForThing = ...
Then in your spec file you can refer to it as MyNamespace.sharedExamplesForThing.
I find it helpful to look at the generated JavaScript to try to understand how CoffeeScript exports variables between files.
Here's the blog post I wrote about how best to do shared examples. Hope this helps.
http://pivotallabs.com/drying-up-jasmine-specs-with-shared-behavior/
Related
This is an ES6-specific duplicate of this SO thread, which details how to create a javascript module that can be exported for use in both a browser and node context.
I would be just as glad for an answer to appear there, if that's more appropriate.
For background, here's a quote of the ES5 solution, as well as the problems that arise in trying to translate it to ES6. (Credit to users #caolan and #broesch.)
(function(exports){
// Your code goes here. For example:
exports.test = function(){
return 'hello world'
};
})(typeof exports === 'undefined'? this.mymodule = {} : exports);
Thus, if exports is undefined, you must be in the browser, in which case mymodule is declared on the window (i.e., this). Or, if exports is defined, it's in a node context, in which case you can just var mymodule = require('mymodule'). And in either environment, you can then use it as mymodule.test(). Great.
One problem with this is that exports.whatever doesn't expose whatever in the current scope, so if you want to use whatever within the module, you end up needing to do
var whatever = 3;
exports.whatever = whatever;
which can get cumbersome, and is easy to forget.
In ES6, on the other hand, you can do export const whatever = 3, which would both export and expose whatever, which is DRYer and thus easier to maintain.
The new problems are that
export must be at the top level of the file, which means you can't use the syntax from the ES5 answer
export isn't a function (is it?), so you can't use type of to achieve the conditional browser/node context.
So, my question is: what is the ES6 version of creating a .js file that can exported to both the browser and node?
I have my HTML setup like this:
<script type="module" src="main.js"></script>
and all the ES6 modules work fine. The only problem is I now can't refer to anything from within DevTools (like using the Console and typing in a variable to see it's value or using a function manually).
How do I import modules whilst being able to use the DevTools? Thanks!
One way to make a variable accessable within DevTools is to create it on the window object:
// Variable in your module
var importantNumber = 1;
window.importantNumber = importantNumber;
This method works fine if you just have a couple of variables, but if you need to have access to a lot more variables within DevTools, I would recommend you go to the sources-tab in DevTools, search for your module and adding a breakpoint. When the execution pauses, you have access to all the variables within that module on the DevTools console.
If you want to be able to refer to variables created within the module from the console's global scope, you'll have to deliberately expose each such variable that you want to be visible from the console. Either assign each variable to window (probably not a good idea - the whole point of modules is to make things more modular, without global pollution), or perhaps assign a single object to window, to which you assign module variables. For example:
// in the console:
setTimeout(() => {
window.myModule.foo();
console.log(window.myModule.bar);
});
<script type="module">
function foo() {
console.log('doing foo');
}
const bar = 'a string bar';
const thisModule = { foo, bar };
window.myModule = thisModule;
// If you ever reassign variables, you'll have to reassign them on thisModule too
// or, only reference and reassign properties of thisModule, rather than create independent variables
</script>
For anyone else interested, if you're comfortable with it, use a bundler like Webpack. I don't believe (at least at this point) that the browser will by itself be able to use the DevTools on modules (the other solutions are quite janky, and aren't fantastic to work with).
Hopefully in the future, when all major browsers will be able to support ES6 modules without a bundler, we'll be able to use DevTools.
Using a Helper
I personally use a little helper function in development that allows me to expose a bunch a variables in a single expression. For example, it makes the following two blocks equivalent:
window.playerOne = playerOne;
window.someClass = someClass;
window.localData = localData;
globalize({playerOne, someClass, localData});
The helper looks like this:
const globalize = function(variables) {
Object.entries(variables).forEach(([name, value]) => window[name] = value);
};
This is my second weekend playing with Node, so this is a bit newbie.
I have a js file full of common utilities that provide stuff that JavaScript doesn't. Severely clipped, it looks like this:
module.exports = {
Round: function(num, dec) {
return Math.round(num * Math.pow(10,dec)) / Math.pow(10,dec);
}
};
Many other custom code modules - also included via require() statements - need to call the utility functions. They make calls like this:
module.exports = {
Init: function(pie) {
// does lots of other stuff, but now needs to round a number
// using the custom rounding fn provided in the common util code
console.log(util.Round(pie, 2)); // ReferenceError: util is not defined
}
};
The node.js file that is actually run is very simple (well, for this example). It just require()'s in the code and kicks off the custom code's Init() fn, like this:
var util = require("./utilities.js");
var customCode = require("./programCode.js");
customCode.Init(Math.PI);
Well, this doesn't work, I get a "ReferenceError: util is not defined" coming from the customCode. I know everything in each required file is "private" and this is why the error is occuring, but I also know that the variable holding the utility code object has GOT to be stored somewhere, perhaps hanging off of global?
I searched through global but didn't see any reference to utils in there. I was thinking of using something like global.utils.Round in the custom code.
So the question is, given that the utility code could be referred to as anything really (var u, util, or utility), how in heck can I organize this so that other code modules can see these utilities?
There are at least two ways to solve this:
If you need something from another module in a file, just require it. That's the easy one.
Provide something which actually builds the module for you. I will explain this in a second.
However, your current approach won't work as the node.js module system doesn't provide globals as you might expect them from other languages. Except for the things exported with module.exports you get nothing from the required module, and the required module doesn't know anything of the requiree's environment.
Just require it
To avoid the gap mentioned above, you need to require the other module beforehand:
// -- file: ./programCode.js
var util = require(...);
module.exports = {
Init: function(pie) {
console.log(util.Round(pie, 2));
}
};
requires are cached, so don't think too much about performance at this point.
Keep it flexible
In this case you don't directly export the contents of your module. Instead, you provide a constructor that will create the actual content. This enables you to give some additional arguments, for example another version of your utility library:
// -- file: ./programCode.js
module.exports = {
create: function(util){
return {
Init: function(pie) {
console.log(util.Round(pie, 2));
}
}
}
};
// --- other file
var util = require(...);
var myModule = require('./module').create(util);
As you can see this will create a new object when you call create. As such it will consume more memory as the first approach. Thus I recommend you to just require() things.
Someone on the RubyRogues podcast once said "Learn CoffeeScript because CoffeeScript writes better javascript than you do." Sorry, can't remember who said it...
So, I took a very simple WORKING javascript function:
togglingii.js
function pdtogglejs(id) { $('div .partybackground').removeClass("hidden"); }
Which is being called by this line:
Read More...
Then I converted it into this coffeescript:
toggling.js.coffee
pdtogglecs(id) ->
jQuery('div .partybackground').removeClass("hidden")
and changed the html to reference the pdtoggle*c*s instead of pdtoggle*j*s.
I can see BOTH of them just fine in my application.js file:
(function() {
pdtogglecs(id)(function() {
return jQuery('div .partybackground').removeClass("hidden");
});
}).call(this);
function pdtogglejs(id) { $('div .partybackground').removeClass("hidden"); }
;
(function() {
}).call(this);
However, only the pure javascript works. The coffeescript always returns Uncaught ReferenceError: pdtogglecs is not defined.
Based on other stackoverflow questions it must be some sort of namespace error. Probably because of the way pdtogglecs is, itself, inside of a function?? However, I have tried defining the coffeescript function with: window.pdtogglecs, this.pdtogglecs, root.pdtogglecs and the coffescript one always fails with that error.
What am I missing??
Thanks!!
You have two problems, one is a bit of CoffeeScript syntax confusion and the other is the namespace problem that you know about.
We'll start by sorting out your syntax confusion. This:
f(x) -> ...
is interpreted like this:
f(x)(-> ...)
So when given this:
pdtogglecs(id) ->
jQuery('div .partybackground').removeClass("hidden")
CoffeeScript thinks you're trying to call pdtogglecs as a function with id as an argument. Then it thinks that pdtogglecs(id) returns a function and you want to call that function with your -> jQuery(...) function as an argument. So it ends up sort of like this:
callback = -> jQuery(...)
returned_function = pdtogglecs(id)
returned_function(callback)
And that's nothing like your original JavaScript. You want to create a function named pdtogglecs which takes id as an argument and then runs your jQuery stuff:
pdtogglecs = (id) ->
# -----^ this is sort of important
jQuery('div .partybackground').removeClass("hidden")
You can see what's going on by looking at the generated JavaScript.
The namespace problem is easy and you can probably figure that out based on the other question you found. However, I'll take care of it right here for completeness.
CoffeeScript wraps each .coffee file in a self-executing function to avoid polluting the global namespace:
(function() {
// JavaScript version of your CoffeeScript goes here...
})();
That wrapper makes everything scoped to the .coffee file. If you want to pollute the global namespace then you have to say so:
window.pdtogglecs = (id) -> ...
You can also say:
#pdtogglecs = (id) -> ...
but I prefer the explicitness of directly referencing window, that also saves you from worrying about what # (AKA this) is when you're code is parsed.
I'd like to write Javascript scripts for Google Apps Script using CoffeeScript, and I'm having trouble generating functions in the expected form.
Google Apps Script expects a script to contain top-level, named functions. (I may be using the wrong terminology, so I'll illustrate what I mean with examples...)
For example, this function is happily recognised by Google Apps Script:
function triggerableFunction() {
// ...
}
... while this function is not (it will parse, but won't you won't be able to trigger it):
var nonTriggerableFunction;
nonTriggerableFunction = function() {
// ...
};
I've found that with CoffeeScript, the closest I'm able to get is the nonTriggerableFunction form above. What's the best approach to generating a named function like triggerableFunction above?
I'm already using the 'bare' option (the -b switch), to compile
without the top-level function safety wrapper.
The one project I've found on the web which combines CoffeeScript and Google App Script is Gmail GTD Bot, which appears to do this using a combination of back-ticks, and by asking the user to manually remove some lines from the resulting code. (See the end of the script, and the 'Installation' section of the README). I'm hoping for a simpler and cleaner solution.
CoffeeScript does not allow you to create anything in the global namespace implicitly; but, you can do this by directly specifying the global namespace.
window.someFunc = (someParam) ->
alert(someParam)
Turns out this can be done using a single line of embedded Javascript for each function.
E.g. this CoffeeScript:
myNonTriggerableFunction = ->
Logger.log("Hello World!")
`function myTriggerableFunction() { myNonTriggerableFunction(); }`
... will produce this JavaScript, when invoking the coffee compiler with the 'bare' option (the -b switch):
var myNonTriggerableFunction;
myNonTriggerableFunction = function() {
return Logger.log("Hello World!");
};
function myTriggerableFunction() { myNonTriggerableFunction(); };
With the example above, Google Apps Script is able to trigger myTriggerableFunction directly.
This should give you a global named function (yes, it's a little hacky, but far less that using backticks):
# wrap in a self invoking function to capture global context
do ->
# use a class to create named function
class #triggerableFunction
# the constructor is invoked at instantiation, this should be the function body
constructor: (arg1, arg2) ->
# whatever
juste use # in script, exemple of my code :
#isArray = (o)->
Array.isArray(o)
it will be compiled in :
(function() {
this.isArray = function(o) {
return Array.isArray(o);
};
}).call(this);
this = window in this case, so it's global function