How do I declare a variable in a specific scope in coffeescript? - coffeescript

I'm trying to write a jasmine test in coffeescript that uses a beforeEach block. This runs into a problem with coffeescript's variable scoping. Here's what I'd like to write:
describe 'PhoneDetailCtrl', () ->
beforeEach () ->
scope = angular.scope()
$browser = scope.$service('$browser')
it 'should fetch phone detail', () ->
scope.params = {phoneId:'xyz'}
$browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'})
ctrl = scope.$new(PhoneDetailCtrl)
expect(ctrl.phone).toEqualData({})
$browser.xhr.flush()
expect(ctrl.phone).toEqualData({name:'phone xyz'})
This doesn't work, though, because the scope and $browser will get declared with var in the innermost scope. That is, once in the beforeEach and then again in the it block. I can force the variables to be declared in the right scope by initializing them, but this seems very strange:
describe 'PhoneDetailCtrl', () ->
$browser = {}
scope = {}
beforeEach () ->
scope = angular.scope()
$browser = scope.$service('$browser')
it 'should fetch phone detail', () ->
scope.params = {phoneId:'xyz'}
...
This works, but the javascript it compiles to is actually
describe('PhoneListCtrl', function() {
var $browser, ctrl, scope;
$browser = {};
ctrl = {};
scope = {};
where all I need is the line var $browser, ctrl, scope;. Can I write this more concisely in coffeescript?

You are doing it the right way.
This is described in the CoffeeScript documentation. I wouldn't worry about the JS that it creates. Yes, it is a bit messy if you were to write it yourself, but this is one of the things that you have to live with when you use a re-writer like CoffeeScript.
You do, however, have a couple of options which are pretty nice.
You can put the variables in the current context if you wish (which happens to be your jasmine.Spec object for the curious, so it is a relatively safe and appropriate place to be putting variables... just don't overwrite existing vars in the context.):
describe 'PhoneDetailCtrl', () ->
beforeEach () ->
#scope = angular.scope()
#$browser = #scope.$service('$browser')
it 'should fetch phone detail', () ->
#scope.params = {phoneId:'xyz'}
#... etc
You can also setup your own variable in which to store things
describe 'PhoneDetailCtrl', () ->
setup = {}
beforeEach () ->
setup.scope = angular.scope()
setup.$browser = setup.scope.$service('$browser')
it 'should fetch phone detail', () ->
setup.scope.params = {phoneId:'xyz'}
#... etc

Your test could be written like the following:
describe "MyGame", ->
mygame = null
beforeEach inject (_MyGame_) ->
mygame = _MyGame_
it "should have two players", ->
expect(mygame.opponents.length).toEqual 2
Much cleaner syntax - without the need to make things global.

Related

With function in Coffee Script

Is the following code possible to coffee script?
With pex
.simpleObject = {}
.explicitFunction = ()->
alert "Hello world!"
.CONSTANTPI = 3.16
Endwith
Please disregard contents inside with. I am just referring how to do that in coffee script? instead of repeatedly typing the word pex.
As JavaScript's with statement should not be used, it's better to implement it with nested function and this accessor:
_with = (object, block) -> block.call object
_with pex, ->
#simpleObject = {}
#explicitFunction = ()->
alert "Hello world!"
#CONSTANTPI = 3.16
you could write a little helper function, since your goal seems to be assigning properties to an object, this one should suffice:
addprops = (obj, addthis) ->
for own key,value of addthis
obj[key] = value
pex = {}
addprops pex,
simpleObject: {}
explicitFunction: ()->
alert "Hello world!"
CONSTANTPI: 3.16
console.log pex
###
OUTPUT:
{ simpleObject: {},
explicitFunction: [Function],
CONSTANTPI: 3.16 }
###
You also could have a look at one of the several mixin libraries out there.

Writing specs for Atom editor

I'm trying to write a simple package for Atom.io editor. This is my first experience with Coffeescript.
So I'm probably missing something trivial.
Anyway, this is my index.coffee
module.exports =
activate: ->
atom.workspaceView.command "md-utils:unorderedList", => #unorderedList()
unorderedList: ->
out = ""
editor = atom.workspace.activePaneItem
selection = editor.getSelection()
lines = selection.getText().split "\n"
for line in lines
out += "- " + line + "\n"
console.log(lines)
selection.insertText(out)
And here it is my index-spec.coffee
{WorkspaceView} = require 'atom'
describe "Markdown Utilities", ->
[editor, editorView] = []
unorderedList = (callback) ->
editorView.trigger "md-utils:unorderedList"
runs(callback)
beforeEach ->
atom.workspaceView = new WorkspaceView
atom.workspaceView.openSync()
editorView = atom.workspaceView.getActiveView()
editor = editorView.getEditor()
describe "when text is selected", ->
it "formats it correctly", ->
console.log = jasmine.createSpy("log")
editor.setText """
a
b
c
d
"""
editor.selectAll()
unorderedList ->
expect(console.log).toHaveBeenCalled()
expect(editor.getText()).toBe """
- a
- b
- c
- d
"""
Now, when I run the spec looks like the method in the index.coffee is not even called.
Both expectations failed :
Expected spy log to have been called.
Expected 'a b c d' to be '-a -b -c -d"
The method in itself works , so I do not understand why test fails.
Any suggestion is much appreciated
Actually your specs lack of a package activation, which is generally done with something like this:
beforeEach ->
atom.workspaceView = new WorkspaceView
atom.workspaceView.openSync()
editorView = atom.workspaceView.getActiveView()
editor = editorView.getEditor()
# Package activation is done within a promise so this will wait for the end of it
# before actually running the tests.
waitsForPromise -> atom.packages.activatePackage('your-package-name')
As your package is never activated, the command you define in the activate method is never registered, so the event triggered in your unorderedList helper never reach it.

missing context in coffeescript => operator

App.WebNotificationComponent = Em.Component.extend
subscriptionQueue: null
connection: (->
App.StompConnection.create(subscriptionQueue: #get('subscriptionQueue'))
).property('subscriptionQueue')
willDestroy: ->
#get('connection').destroy()
App.AlertsIndexAlertWebNotificationComponent = App.WebNotificationComponent.extend
subscriptionQueue: "notifications"
didInsertElement: ->
#get('connection').onMessage = ((message) ->
result = JSON.parse(message.body)
App.AlertNotification.store.find('alert_notification', result.id).then (notification) =>
debugger
).bind(#get('targetObject'))
#get('connection').connect()
At the debugger breakpoint, I am no longer able to access message or result.
Is there anything that I am doing wrong or another way to do this ?
You can't access those values in the debugger because you didn't close over them, so they're not available in the closure. If you use them in the closure, they'll be available. Javascript has to know that you're going to use a variable in order to save it in the closed scope.
In other words, this will work as expected:
result = JSON.parse(message.body)
App.AlertNotification.store.find('alert_notification', result.id).then (notification) =>
console.log(message)
console.log(result)

How to use setTimeout() in Coffeescript

I can't seem to use setTimeout() to call one of my own functions. I can use setTimeout to call alert(), but not a function that I've written myself. Here's the simplest code that reproduces the problem:
I have the following coffeeScript
setTimeout(run, 1000)
run = () ->
console.log("run was called!")
Which generates the following Javascript
// Generated by CoffeeScript 1.6.3
(function() {
var run;
setTimeout(run, 1000);
run = function() {
return console.log("run was called!");
};
}).call(this);
Nothing is printed to the console.
run = () ->
console.log("run was called!")
setTimeout(run, 1000)
You are relying on javascript function hoisting for functions declared with the syntax function run(){}, but coffeescript declares them as variables: var run = function(){}, so you have to define the function before you reference it, otherwise it's still undefined when you pass it to setTimeout.
Anonymous Option:
Peter is exactly right. But you can also use setTimeout without declaring a variable:
setTimeout ->
console.log 'run was called!'
, 1000
Yields:
(function() {
setTimeout(function() {
return console.log("run was called!")
}, 1e3)
}).call(this);

Can't get query results outside the function

I have this class in which i try to initialize array attributes with query results:
class data
minute: []
hour: []
constructor: () ->
findMin = events.find({"aggr":"minute"}).sort({$natural:-1}).limit(120)
findHour = events.find({"aggr":"hour"}).sort({$natural:-1}).limit(14)
findMin.execFind (errMin, resMin) ->
for recMin in resMin
#minute.push recMin
findHour.execFind (errH, resH) ->
for recH in resH
#hour.push recH
So i call smth = new data() and console.log smth and get an empty attributes and an error about undefined not having 'push' method. While i can understand an error i cant get why my arrays are empty. Dont get me wrong - i know this error causes them to be empty, but i tried several kinds of variants. And ive read about acync and callbacks, but still don't have a clue how to use callbacks not to 'alert' smth, but to use it afterwards. If you could help me with that or with some links that could - i would appreciate it SO much.
You have two issues. The one that is causing the error you are observing is that '#' inside of your two callbacks is not bound to your data instance, so you need to use =>. Secondly, and as pointed out by #AaronDufour, your hour and minute arrays are declared at the class level so they will be shared between every instance of data, which I doubt is what you want, so you need to move them into your constructor.
class data
constructor: () ->
#minute = []
#hour = []
findMin = events.find({"aggr":"minute"}).sort({$natural:-1}).limit(120)
findHour = events.find({"aggr":"hour"}).sort({$natural:-1}).limit(14)
findMin.execFind (errMin, resMin) =>
for recMin in resMin
#minute.push recMin
findHour.execFind (errH, resH) =>
for recH in resH
#hour.push recH
I assume you want minute and hour to be instance variables? They must be initialized in the constructor. The way you're doing it now, they're on the prototype, so it won't work properly. Try this:
class data
constructor: () ->
#minute = []
#hour = []
findMin = events.find({"aggr":"minute"}).sort({$natural:-1}).limit(120)
findHour = events.find({"aggr":"hour"}).sort({$natural:-1}).limit(14)
findMin.execFind (errMin, resMin) =>
for recMin in resMin
#minute.push recMin
findHour.execFind (errH, resH) =>
for recH in resH
#hour.push recH