refactor and export createStacTemplate helper function

--- workers/social/testhelper/models/computeproviders/stacktemplatehelper.coffee
+++ workers/social/testhelper/models/computeproviders/stacktemplatehelper.coffee
@@ -2,7 +2,9 @@
   expect
   withConvertedUser
   generateRandomString } = require '../../index'
-JStackTemplate = require '../../../../social/lib/social/models/computeproviders/stacktemplate'
+
+JStackTemplate = require  \
+  '../../../../social/lib/social/models/computeproviders/stacktemplate'

 generateStackTemplateData = (client, data) ->
@@ -32,6 +34,14 @@
   return stackTemplate

+createStackTemplate = (client, options, callback) ->
+
+  stackTemplateData = generateStackTemplateData client, options
+
+  JStackTemplate.create client, stackTemplateData, (err, stackTemplate) ->
+    callback err, { stackTemplate, stackTemplateData }
+
+
 withConvertedUserAndStackTemplate = (options, callback) ->

   [options, callback] = [callback, options]  unless callback
@@ -41,14 +51,15 @@
     { client }        = data
     stackTemplateData = generateStackTemplateData client, options

-    JStackTemplate.create client, stackTemplateData, (err, stackTemplate) ->
+    createStackTemplate client, options, (err, template) ->
       expect(err).to.not.exist
-      data.stackTemplate     = stackTemplate
-      data.stackTemplateData = stackTemplateData
+      data.stackTemplate     = template.stackTemplate
+      data.stackTemplateData = template.stackTemplateData
       callback data

 module.exports = {
+  createStackTemplate
   generateStackTemplateData
   withConvertedUserAndStackTemplate
 }


Integrate grid clearing and add random background image.

--- jigs/grid/jig.coffee
+++ jigs/grid/jig.coffee
@@ -9,9 +9,16 @@
     grid = $stout.get('#basic-grid')

     $stout.get('#btn-add').click = ->
-      randomSize = -> Math.max 5, Math.ceil(Math.random() * 8)
+      randomSize = -> Math.max 1, Math.ceil(Math.random() * 3)
       i = 2
       while i--
         height = randomSize()
         width = randomSize()
-        grid.context.createItem {height, width}
+        item = grid.context.createItem {height, width}
+        image = Math.ceil(Math.round(Math.random() * 1000))
+        item.root.style.backgroundImage = "url('https://unsplash.it/800?image=#{image}')"
+        #item.click = -> @remove()
+
+
+    $stout.get('#btn-clear').click = ->
+      grid.context.destroyGridItems()


Fix mock test in CORS check

Fix mock test in CORS check

--- bucky.coffee
+++ bucky.coffee
@@ -136,7 +136,7 @@
       maxTimeout = setTimeout flush, options.maxInterval

   makeRequest = (data) ->
-    corsSupport = window.XMLHttpRequest and (XMLHttpRequest.defake or 'withCredentials' of new XMLHttpRequest())
+    corsSupport = window._XMLHttpRequest and (_XMLHttpRequest.defake or 'withCredentials' of new _XMLHttpRequest())

     match = /^(https?:\/\/[^\/]+)/i.exec options.host


Remove default telephone number.

Remove default telephone number.

--- server/user_initialization.coffee
+++ server/user_initialization.coffee
@@ -1,5 +1,4 @@
 Accounts.onCreateUser (options, user) ->
-  user.telephone_nr = '123456'
   tutorial =
     title: "Welcome to Mijn M&I/Partners, #{user.username}!"
     description: "Mijn M&I/Partners is #{user.telephone_nr}..."


remove some log messages

remove some log messages

--- shared/ironTableController.coffee
+++ shared/ironTableController.coffee
@@ -59,10 +59,10 @@
                     console.log('ironTable_' +  @_collectionName() + '_recordCount error:', error)

     load: ->
-        console.log("load", @_collectionName())
+        #console.log("load", @_collectionName())

     unload: ->
-        console.log("unload", @_collectionName())
+        #console.log("unload", @_collectionName())

     _tableTitle: ->
         @tableTitle or @_collectionName()


Move document title specs to up.change

Move document title specs to up.change

--- spec_app/spec/javascripts/up/fragment_spec.js.coffee
+++ spec_app/spec/javascripts/up/fragment_spec.js.coffee
@@ -247,16 +247,32 @@

       describe 'choice of target', ->

-        describe 'when no { target } options is given', ->
-
-          it 'uses a default target for the layer that is being updated'
-
-          it 'allows to pass an element as { target } option', asyncSpec (next) ->
-            target = fixture('.target', text: 'old-text')
-            up.change(target, document: '
new-text
')
-
-            next =>
-              expect('.target').toHaveText('new-text')
+        it 'uses a selector given as { target } option'
+
+        it 'uses a selector given as first argument'
+
+        it 'derives a selector from an element given as { target } option', asyncSpec (next) ->
+          target = fixture('.target', text: 'old-text')
+          up.change(target, document: '
new-text
')
+
+          next =>
+            expect('.target').toHaveText('new-text')
+
+        it 'derives a selector from an element given as first argument'
+
+        it 'uses a default target for the layer that is being updated if nothing else is specified'
+
+      describe 'choice of layer', ->
+
+        it 'updates the layer given as { layer } option'
+
+        it 'updates the layer of the given target, if the target is given as an element (and not a selector)'
+
+        it 'updates the layer of the given { origin }'
+
+        it 'opens a new layer when given { layer: "new" }'
+
+        it 'updates the current layer if nothing else is specified'

       describe 'browser location URL', ->

@@ -349,98 +365,60 @@
             next => @respondWithSelector('.failure-target', status: 500)
             next => expect(location.href).toMatchURL('/path6')

-      describe 'source', ->
-
-        it 'remembers the source the fragment was retrieved from', asyncSpec (next) ->
-          fixture('.target')
-          up.change('.target', url: '/path')
-          next =>
-            @respondWithSelector('.target')
-          next =>
-            expect(up.fragment.source('.target')).toMatchURL('/path')
-
-        it 'keeps the previous source for a non-GET request (since that is reloadable)', asyncSpec (next) ->
-          fixture('.target', 'up-source': '/previous-source')
-          up.change('.target', url: '/path', method: 'post')
-          next =>
-            @respondWithSelector('.target')
-          next =>
-            expect(up.fragment.source('.target')).toMatchURL('/previous-source')
-
-        describe 'with { source } option', ->
-
-          it 'uses that URL as the source for a GET request', asyncSpec (next) ->
-            fixture('.target')
-            up.change('.target', url: '/path', source: '/given-path')
-            next =>
-              @respondWithSelector('.target')
-            next =>
-              expect(up.fragment.source('.target')).toMatchURL('/given-path')
-
-          it 'uses that URL as the source after a non-GET request', asyncSpec (next) ->
-            fixture('.target')
-            up.change('.target', url: '/path', method: 'post', source: '/given-path')
-            next =>
-              @respondWithSelector('.target')
-            next =>
-              expect(up.fragment.source('.target')).toMatchURL('/given-path')
-
-          it 'ignores the option and reuses the previous source after a failed non-GET request', asyncSpec (next) ->
-            fixture('.target', 'up-source': '/previous-source')
-            up.replace('.target', '/path', method: 'post', source: '/given-path', failTarget: '.target')
-            next =>
-              @respondWithSelector('target', status: 500)
-            next =>
-              expect(up.fragment.source('.target')).toMatchURL('/previous-source')
-
-
-      describe 'with { transition } option', ->
-
-        it 'returns a promise that will be fulfilled once the server response was received and the swap transition has completed', asyncSpec (next) ->
-          fixture('.target', text: 'old-text')
-          resolution = jasmine.createSpy()
-          promise = up.change(target: '.target', url: '/path', transition: 'cross-fade', duration: 200)
-          promise.then(resolution)
-
-          next =>
-            expect(resolution).not.toHaveBeenCalled()
-            expect('.target').toHaveText('old-text')
-            @respondWith '
new-text
'
-
-          next.after 100, =>
-            target = e.first('.target')
-            opacity = e.styleNumber(target, 'opacity')
-            expect(opacity).toBeAround(0.5, 0.4)
-            expect(resolution).not.toHaveBeenCalled()
-
-          next.after 200, =>
-            expect(resolution).toHaveBeenCalled()
-            expect('.target').toHaveText('new-text')
-
-
-
-    ##############################################################################
-    ##############################################################################
-    ##############################################################################
-    ##############################################################################
-    ##############################################################################
-
-    describe 'up.replace', ->
-
-      describeCapability 'canPushState', ->
-
-
-        describe 'document title', ->
-
-          beforeEach ->
-            up.history.config.enabled = true
-
-          it "sets the document title to the response <title> xxx", asyncSpec (next) ->
-            $fixture('.container').text('old container text')
-            up.replace('.container', '/path')
-
-            next =>
-              @respondWith """
+      describe 'document title', ->
+
+        beforeEach ->
+          up.history.config.enabled = true
+
+        it "sets the document title to the response <title> xxx", asyncSpec (next) ->
+          $fixture('.container').text('old container text')
+          up.change('.container', url: '/path')
+
+          next =>
+            @respondWith """
+
+
+                  <title>Title from HTML</title>
+
+
+

+                    new container text
+

+
+
+            """
+
+          next =>
+            expect($('.container')).toHaveText('new container text')
+            expect(document.title).toBe('Title from HTML')
+
+        it "sets the document title to an 'X-Up-Title' header in the response", asyncSpec (next) ->
+          $fixture('.container').text('old container text')
+          up.change('.container', url: '/path')
+
+          next =>
+            @respondWith
+              responseHeaders:
+                'X-Up-Title': 'Title from header'
+              responseText: """
+

+                  new container text
+

+                """
+
+          next =>
+            expect($('.container')).toHaveText('new container text')
+            expect(document.title).toBe('Title from header')
+
+        it "prefers the X-Up-Title header to the response <title>", asyncSpec (next) ->
+          $fixture('.container').text('old container text')
+          up.change('.container', url: '/path')
+
+          next =>
+            @respondWith
+              responseHeaders:
+                'X-Up-Title': 'Title from header'
+              responseText: """

                     <title>Title from HTML</title>
@@ -453,144 +431,186 @@

               """

-            next =>
-              expect($('.container')).toHaveText('new container text')
-              expect(document.title).toBe('Title from HTML')
-
-          it "sets the document title to an 'X-Up-Title' header in the response", asyncSpec (next) ->
-            $fixture('.container').text('old container text')
-            up.replace('.container', '/path')
-
-            next =>
-              @respondWith
-                responseHeaders:
-                  'X-Up-Title': 'Title from header'
-                responseText: """
+          next =>
+            expect($('.container')).toHaveText('new container text')
+            expect(document.title).toBe('Title from header')
+
+        it "sets the document title to the response <title> with { location: false, title: true } options (bugfix) xxx", asyncSpec (next) ->
+          $fixture('.container').text('old container text')
+          up.change('.container', url: '/path', location: false, title: true)
+
+          next =>
+            @respondWith """
+
+
+                  <title>Title from HTML</title>
+
+

                     new container text

-                  """
-
-            next =>
-              expect($('.container')).toHaveText('new container text')
-              expect(document.title).toBe('Title from header')
-
-          it "prefers the X-Up-Title header to the response <title>", asyncSpec (next) ->
-            $fixture('.container').text('old container text')
-            up.replace('.container', '/path')
-
-            next =>
-              @respondWith
-                responseHeaders:
-                  'X-Up-Title': 'Title from header'
-                responseText: """
-
-
-                      <title>Title from HTML</title>
-
-
-

-                        new container text
-

-
-
-                """
-
-            next =>
-              expect($('.container')).toHaveText('new container text')
-              expect(document.title).toBe('Title from header')
-
-          it "sets the document title to the response <title> with { location: false, title: true } options (bugfix) xxx", asyncSpec (next) ->
-            $fixture('.container').text('old container text')
-            up.replace('.container', '/path', location: false, title: true)
-
-            next =>
-              @respondWith """
-
-
-                    <title>Title from HTML</title>
-
-
-

-                      new container text
-

-
-
-              """
-
-            next =>
-              expect($('.container')).toHaveText('new container text')
-              expect(document.title).toBe('Title from HTML')
-
-          it 'does not update the document title if the response has a <title> tag inside an inline SVG image (bugfix)', asyncSpec (next) ->
-            $fixture('.container').text('old container text')
-            oldTitle = document.title
-            up.replace('.container', '/path', history: false, title: true)
-
-            next =>
-              @respondWith """
-                <svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
-                  <g>
-                    <title>SVG Title Demo example</title>
-                    <rect x="10" y="10" width="200" height="50" style="fill:none; stroke:blue; stroke-width:1px"/>
-                  </g>
-                </svg>
-
-

-                  new container text
-

-              """
-
-            next =>
-              expect($('.container')).toHaveText('new container text')
-              expect(document.title).toBe(oldTitle)
-
-          it "does not extract the title from the response or HTTP header if history isn't updated", asyncSpec (next) ->
-            $fixture('.container').text('old container text')
-            oldTitle = document.title
-            up.replace('.container', '/path', history: false)
-
-            next =>
-              @respondWith
-                responseHeaders:
-                  'X-Up-Title': 'Title from header'
-                responseText: """
-
-
-                    <title>Title from HTML</title>
-
-
-

-                      new container text
-

-
-
-              """
-
-            next =>
-              expect(document.title).toEqual(oldTitle)
-
-          it 'allows to pass an explicit title as { title } option', asyncSpec (next) ->
-            $fixture('.container').text('old container text')
-            up.replace('.container', '/path', title: 'Title from options')
-
-            next =>
-              @respondWith """
-
-
-                    <title>Title from HTML</title>
-
-
-

-                      new container text
-

-
-
-              """
-
-            next =>
-              expect($('.container')).toHaveText('new container text')
-              expect(document.title).toBe('Title from options')
+
+
+            """
+
+          next =>
+            expect($('.container')).toHaveText('new container text')
+            expect(document.title).toBe('Title from HTML')
+
+        it 'does not update the document title if the response has a <title> tag inside an inline SVG image (bugfix)', asyncSpec (next) ->
+          $fixture('.container').text('old container text')
+          oldTitle = document.title
+          up.change('.container', url: '/path', history: false, title: true)
+
+          next =>
+            @respondWith """
+              <svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
+                <g>
+                  <title>SVG Title Demo example</title>
+                  <rect x="10" y="10" width="200" height="50" style="fill:none; stroke:blue; stroke-width:1px"/>
+                </g>
+              </svg>
+
+

+                new container text
+

+            """
+
+          next =>
+            expect($('.container')).toHaveText('new container text')
+            expect(document.title).toBe(oldTitle)
+
+        it "does not extract the title from the response or HTTP header if history isn't updated", asyncSpec (next) ->
+          $fixture('.container').text('old container text')
+          oldTitle = document.title
+          up.change('.container', url: '/path', history: false)
+
+          next =>
+            @respondWith
+              responseHeaders:
+                'X-Up-Title': 'Title from header'
+              responseText: """
+
+
+                  <title>Title from HTML</title>
+
+
+

+                    new container text
+

+
+
+            """
+
+          next =>
+            expect(document.title).toEqual(oldTitle)
+
+        it 'allows to pass an explicit title as { title } option', asyncSpec (next) ->
+          $fixture('.container').text('old container text')
+          up.change('.container', url: '/path', title: 'Title from options')
+
+          next =>
+            @respondWith """
+
+
+                  <title>Title from HTML</title>
+
+
+

+                    new container text
+

+
+
+            """
+
+          next =>
+            expect($('.container')).toHaveText('new container text')
+            expect(document.title).toBe('Title from options')
+
+
+      describe 'source', ->
+
+        it 'remembers the source the fragment was retrieved from', asyncSpec (next) ->
+          fixture('.target')
+          up.change('.target', url: '/path')
+          next =>
+            @respondWithSelector('.target')
+          next =>
+            expect(up.fragment.source('.target')).toMatchURL('/path')
+
+        it 'keeps the previous source for a non-GET request (since that is reloadable)', asyncSpec (next) ->
+          fixture('.target', 'up-source': '/previous-source')
+          up.change('.target', url: '/path', method: 'post')
+          next =>
+            @respondWithSelector('.target')
+          next =>
+            expect(up.fragment.source('.target')).toMatchURL('/previous-source')
+
+        describe 'with { source } option', ->
+
+          it 'uses that URL as the source for a GET request', asyncSpec (next) ->
+            fixture('.target')
+            up.change('.target', url: '/path', source: '/given-path')
+            next =>
+              @respondWithSelector('.target')
+            next =>
+              expect(up.fragment.source('.target')).toMatchURL('/given-path')
+
+          it 'uses that URL as the source after a non-GET request', asyncSpec (next) ->
+            fixture('.target')
+            up.change('.target', url: '/path', method: 'post', source: '/given-path')
+            next =>
+              @respondWithSelector('.target')
+            next =>
+              expect(up.fragment.source('.target')).toMatchURL('/given-path')
+
+          it 'ignores the option and reuses the previous source after a failed non-GET request', asyncSpec (next) ->
+            fixture('.target', 'up-source': '/previous-source')
+            up.replace('.target', '/path', method: 'post', source: '/given-path', failTarget: '.target')
+            next =>
+              @respondWithSelector('target', status: 500)
+            next =>
+              expect(up.fragment.source('.target')).toMatchURL('/previous-source')
+
+
+      describe 'with { transition } option', ->
+
+        it 'returns a promise that will be fulfilled once the server response was received and the swap transition has completed', asyncSpec (next) ->
+          fixture('.target', text: 'old-text')
+          resolution = jasmine.createSpy()
+          promise = up.change(target: '.target', url: '/path', transition: 'cross-fade', duration: 200)
+          promise.then(resolution)
+
+          next =>
+            expect(resolution).not.toHaveBeenCalled()
+            expect('.target').toHaveText('old-text')
+            @respondWith '
new-text
'
+
+          next.after 100, =>
+            target = e.first('.target')
+            opacity = e.styleNumber(target, 'opacity')
+            expect(opacity).toBeAround(0.5, 0.4)
+            expect(resolution).not.toHaveBeenCalled()
+
+          next.after 200, =>
+            expect(resolution).toHaveBeenCalled()
+            expect('.target').toHaveText('new-text')
+
+
+
+    ##############################################################################
+    ##############################################################################
+    ##############################################################################
+    ##############################################################################
+    ##############################################################################
+
+    describe 'up.replace', ->
+
+      describeCapability 'canPushState', ->
+
+
+        describe 'document title', ->
+

         describe 'selector processing', ->


assert_error: non exhaustive by default

assert_error: non exhaustive by default

--- test/api.assert_error.coffee
+++ test/api.assert_error.coffee
@@ -2,11 +2,11 @@
 {CsvError} = require '..'
 ResizeableBuffer = require '../lib/ResizeableBuffer'

-module.exports = assert_error = (err, assert = {}) ->
+module.exports = assert_error = (err, assert = {}, exhaustive = false) ->
   if Array.isArray err
     assert_error e, assert[i] for e, i in err
     return
-  for key, value of err
+  if exhaustive then for key, value of err
     assert.should.have.keys(key)
   for key, expect of assert
     value = err[key]


Use stubs in IjaxRequest specs

Use stubs in IjaxRequest specs

--- spec/ijax_src/request_spec.coffee
+++ spec/ijax_src/request_spec.coffee
@@ -1,16 +1,13 @@
 IjaxRequest = modula.require('ijax/request')

 describe 'IjaxRequest', ->
+
   beforeEach ->
-    @originalCreateIframeRequest = IjaxRequest::createIframeRequest
-    IjaxRequest::createIframeRequest = sinon.spy ->
-      $('<iframe />')[0]
-
+    sinon.stub(IjaxRequest::, 'createIframeRequest').returns $('<iframe />')[0]
     @request = new IjaxRequest('/some_path')

   afterEach ->
-    IjaxRequest::createIframeRequest = @originalCreateIframeRequest
-
+    IjaxRequest::createIframeRequest.restore?()

   describe '#constructor', ->
     it 'sets unique id for request', ->
@@ -61,7 +58,7 @@
   describe '#createIframeRequest', ->
     it 'creates iframe with @id in name/id and @path in src', ->
       @request = new IjaxRequest('/some_path')
-      @request.createIframeRequest = @originalCreateIframeRequest
+      @request.createIframeRequest.restore()

       iframe = @request.createIframeRequest(false)


observable spec

observable spec

--- spec/observable/observable.coffee
+++ spec/observable/observable.coffee
@@ -17,10 +17,9 @@

       ary: [1, 2, 3]
       nested:
-        prop: 4
-        computed: -> @get('bar') + 30
-        obj:
-          prop: 5
+        prop1: 4
+        prop2: 5
+        computed: -> @get('prop1') + 30

   describe '#get(keypath)', ->
@@ -41,12 +40,12 @@
       expect(val).toBe 11

     it 'should return nested property values', ->
-      val = observableObj.get 'nested.prop'
+      val = observableObj.get 'nested.prop1'

       expect(val).toBe 4

     it 'should return nested property values using dot operator', ->
-      val = observableObj.nested.get 'prop'
+      val = observableObj.nested.get 'prop1'

       expect(val).toBe 4

@@ -70,16 +69,31 @@
       expect(val).toBe 100

     it 'should set nested property values', ->
-      observableObj.set 'nested.prop', 200
-      val = observableObj.get 'nested.prop'
+      observableObj.set 'nested.prop1', 200
+      val = observableObj.get 'nested.prop1'

       expect(val).toBe 200

     it 'should set nested property values using dot operator', ->
-      observableObj.nested.set 'prop', 200
-      val = observableObj.get 'nested.prop'
+      observableObj.nested.set 'prop1', 200
+      val = observableObj.get 'nested.prop1'

       expect(val).toBe 200
+
+    it 'should set multiple property values by hash', ->
+      observableObj.set
+        foo: 100
+        'nested.prop1', 200
+        nested:
+          prop2: 300
+
+      foo = observableObj.get 'foo'
+      prop1 = observableObj.get 'nested.prop1'
+      prop2 = observableObj.get 'nested.prop2'
+
+      expect(foo).toBe 100
+      expect(prop1).toBe 200
+      expect(prop2).toBe 300

     it 'should set computed property values', ->
       observableObj.set 'settableComputed', 300
@@ -90,5 +104,174 @@
       expect(val2).toBe 280

-    describe 'Observable Array', ->
-
+  describe '#observe(keypath, callback)', ->
+
+    callback = null
+
+    beforeEach ->
+      callback = jasmine.createSpy 'observer'
+
+
+    it 'should call registered observers when setting a property value', ->
+      observableObj.observe 'foo', callback
+
+      observableObj.set 'foo', 100
+
+      expect(callback).toHaveBeenCalled()
+
+    it 'should call registered observer with new value', ->
+      observableObj.observe 'foo', callback
+
+      observableObj.set 'foo', 100
+
+      expect(callback).toHaveBeenCalledWith 100
+
+    it 'should call registered observers every time when setting a property value', ->
+      n = 0
+      callback = jasmine.createSpy('observer').andCallFake -> ++n
+
+      observableObj.observe 'foo', callback
+
+      observableObj.set 'foo', 100
+      observableObj.set 'foo', 100
+
+      expect(callback).toHaveBeenCalled()
+      expect(n).toBe 2
+
+    it 'should not call unregistered observers', ->
+      observableObj.observe 'foo', callback
+      observableObj.unobserve 'foo', callback
+
+      observableObj.set 'foo', 100
+
+      expect(callback).not.toHaveBeenCalled()
+
+
+    describe '#update(keypath)', ->
+
+      it 'should call registered observers immediately', ->
+        observableObj.observe 'foo', callback
+        observableObj.update 'foo'
+
+
+    describe 'Event bubbling on nested properties', ->
+
+      it 'should call registered observer of parent properties when setting children values', ->
+        observableObj.observe 'nested', callback
+
+        observableObj.set 'nested.prop1', 200
+
+        expect(callback).toHaveBeenCalled()
+
+
+    describe 'Computed properties', ->
+
+      it 'should call registered observers of computed property when settings its dependent property values', ->
+        observableObj.observe 'dependentComputed', callback
+
+        observableObj.set 'foo', 100
+
+        expect(callback).toHaveBeenCalledWith 110
+
+      it 'should call registered observers of dependent properties when setting a computed property value', ->
+        callbackComp = jasmine.createSpy 'observer of computed property'
+        callbackDep = jasmine.createSpy 'observer of dependent property'
+
+        observableObj.observe 'dependentComputed', callbackComp
+        observableObj.observe 'foo', callbackDep
+
+        observableObj.set 'dependentComputed', 100
+
+        expect(callbackComp).toHaveBeenCalled()
+        expect(callbackDep).toHaveBeenCalled()
+
+
+    describe 'Batch: #beginBatch(), #endBatch()', ->
+
+      it 'should not call registered observers within a batch', ->
+        observableObj.observe 'foo', callback
+
+        observableObj.beginBatch()
+        observableObj.set 'foo', 100
+        observableObj.set 'foo', 200
+
+        expect(callback).not.toHaveBeenCalled()
+
+      it 'should call registered observers once after ending batch', ->
+        n = 0
+        callback = jasmine.createSpy('observer').andCallFake -> ++n
+        observableObj.observe 'foo', callback
+
+        observableObj.beginBatch()
+        observableObj.set 'foo', 100
+        observableObj.set 'foo', 200
+
+        expect(callback).not.toHaveBeenCalled()
+
+
+    describe 'Array operation', ->
+
+      it 'should call registered observers when updating elements via `push`', ->
+        observableObj.observe 'ary', callback
+
+        ary = observableObj.get 'ary'
+        ary.push 4
+
+        expect(ary).toEqual [1, 2, 3, 4]
+        expect(callback).toHaveBeenCalled()
+
+      it 'should call registered observers when updating elements via `unshift`', ->
+        observableObj.observe 'ary', callback
+
+        ary = observableObj.get 'ary'
+        ary.push 0
+
+        expect(ary).toEqual [0, 1, 2, 3]
+        expect(callback).toHaveBeenCalled()
+
+      it 'should call registered observers when updating elements via `pop`', ->
+        observableObj.observe 'ary', callback
+
+        ary = observableObj.get 'ary'
+        ary.pop()
+
+        expect(ary).toEqual [1, 2]
+        expect(callback).toHaveBeenCalled()
+
+      it 'should call registered observers when updating elements via `shift`', ->
+        observableObj.observe 'ary', callback
+
+        ary = observableObj.get 'ary'
+        ary.shift()
+
+        expect(ary).toEqual [2, 3]
+        expect(callback).toHaveBeenCalled()
+
+      it 'should call registered observers when updating elements via `sort`', ->
+        observableObj.observe 'ary', callback
+
+        ary = observableObj.get 'ary'
+        ary.sort()
+
+        expect(ary).toEqual [1, 2, 3]
+        expect(callback).toHaveBeenCalled()
+
+      it 'should call registered observers when updating elements via `reverse`', ->
+        observableObj.observe 'ary', callback
+
+        ary = observableObj.get 'ary'
+        ary.reverse()
+
+        expect(ary).toEqual [3, 2, 1]
+        expect(callback).toHaveBeenCalled()
+
+      it 'should call registered observers when updating elements via `splice`', ->
+        observableObj.observe 'ary', callback
+
+        ary = observableObj.get 'ary'
+        ary.splice 1, 1
+
+        expect(ary).toEqual [1, 3]
+        expect(callback).toHaveBeenCalled()
+
+


Accpetance tests because it did not seem to work on the live server... works in the tests however with a message copied straight from the logs

--- spec/serverAcceptance.spec.coffee
+++ spec/serverAcceptance.spec.coffee
@@ -10,6 +10,17 @@
 DOMAIN_ID = 477
 VERSION = '1.0'
 RESULT_MESSAGE = 'result'
+TARGET_ID = 55
+
+feedbackJSON = ->
+  msg: 'feedback'
+  client: { id: 7345 }
+  domain: { id: DOMAIN_ID }
+  source: { id: ITEM_ID }
+  target: { id: TARGET_ID }
+  context: { category: { id: 74 } }
+  config: { team: { id: '48' } }
+  version: '1.0'

 testJSON = ->
   msg:"impression"
@@ -42,9 +53,8 @@
   json.config.team = undefined
   json

-
-testJSONString = ->
-  JSON.stringify(testJSON())
+string = (json) ->
+  JSON.stringify json

 itemStorage = new ItemStorage
 recommender = new Recommender(itemStorage)
@@ -52,30 +62,44 @@

 describe 'Acceptance tests for server and recommendation engine', ->

-  it 'handles the example JSON well and does not respond when items are empty', ->
-    helper.sendAndExpect testJSONString(), (responseObject) ->
-      expect(responseObject.items).toEqual([])
+  describe 'Impression test messages', ->

-  it 'sets the correct version number', ->
-    helper.sendAndExpect testJSONString(), (responseObject) ->
-      expect(responseObject.version).toEqual(VERSION)
+    it 'handles the example JSON well and does not respond when items are empty', ->
+      helper.sendAndExpect string(testJSON()), (responseObject) ->
+        expect(responseObject.items).toEqual([])

-  it 'sets the correct tem id', ->
-    helper.sendAndExpect testJSONString(), (responseObject) ->
-      expect(responseObject.team.id).toEqual(TEAM_ID)
+    it 'sets the correct version number', ->
+      helper.sendAndExpect string(testJSON()), (responseObject) ->
+        expect(responseObject.version).toEqual(VERSION)

-  it 'sets the right msg type', ->
-    helper.sendAndExpect testJSONString(), (responseObject) ->
-      expect(responseObject.msg).toEqual(RESULT_MESSAGE)
+    it 'sets the correct tem id', ->
+      helper.sendAndExpect string(testJSON()), (responseObject) ->
+        expect(responseObject.team.id).toEqual(TEAM_ID)

-  it 'handles messages without a team id', ->
-    helper.sendAndExpect JSON.stringify(jsonWithoutTeam()), (responseObject) ->
-      expect(responseObject.team.id).toEqual(OUR_TEAM_ID)
+    it 'sets the right msg type', ->
+      helper.sendAndExpect string(testJSON()), (responseObject) ->
+        expect(responseObject.msg).toEqual(RESULT_MESSAGE)

-  it 'saves the category of an item appropriately', ->
-    helper.sendAndExpect testJSONString(), (responseObject) -> # do nothing
-    expect(itemStorage[DOMAIN_ID][ITEM_ID].category).toEqual CATEGORY_ID
+    it 'handles messages without a team id', ->
+      helper.sendAndExpect string(jsonWithoutTeam()), (responseObject) ->
+        expect(responseObject.team.id).toEqual(OUR_TEAM_ID)

+    it 'saves the category of an item appropriately', ->
+      helper.sendAndExpect string(testJSON()), (responseObject) -> # do nothing
+      expect(itemStorage[DOMAIN_ID][ITEM_ID].category).toEqual CATEGORY_ID

+  describe 'feedback messages', ->
+
+    beforeEach ->
+      # we need an item in our db
+      helper.sendAndExpect string(testJSON()), (responseObject) -> #nothing
+
+    it 'increases the recommends attribute of the item appropriately', ->
+      helper.sendAndExpect string(feedbackJSON()), (response) ->
+        expect(itemStorage[DOMAIN_ID][ITEM_ID].recommends[TARGET_ID].count).toEqual 1
+
+    it 'sets the id of the item appropriately', ->
+      helper.sendAndExpect string(feedbackJSON()), (response) ->
+        expect(itemStorage[DOMAIN_ID][ITEM_ID].recommends[TARGET_ID].id).toEqual TARGET_ID

 runs -> helper.stopServer()


make datastore usage meter a bar graph

make datastore usage meter a bar graph

--- imports/ui/viz/DataStoreUsageMeter.cjsx
+++ imports/ui/viz/DataStoreUsageMeter.cjsx
@@ -22,8 +22,9 @@
     ]

     <AnnotatedMeter legend={true}
-      type='circle'
+      type='bar'
       size='small'
+      legend=false
       units={used.suffix}
       max={total}
       series={series} />


spec: asar: fs.readdir

spec: asar: fs.readdir

--- spec/asar-spec.coffee
+++ spec/asar-spec.coffee
@@ -25,7 +25,7 @@
         p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'
         assert.equal fs.readFileSync(p).toString(), 'file1\n'

-      it 'throws ENOENT error when can not find a file', ->
+      it 'throws ENOENT error when can not find file', ->
         p = path.join fixtures, 'asar', 'a.asar', 'not-exist'
         throws = -> fs.readFileSync p
         assert.throws throws, /ENOENT/
@@ -52,7 +52,7 @@
           assert.equal String(content), 'file1\n'
           done()

-      it 'throws ENOENT error when can not find a file', (done) ->
+      it 'throws ENOENT error when can not find file', (done) ->
         p = path.join fixtures, 'asar', 'a.asar', 'not-exist'
         fs.readFile p, (err, content) ->
           assert.equal err.code, 'ENOENT'
@@ -103,7 +103,7 @@
           assert.equal stats.isSymbolicLink(), true
           assert.equal stats.size, 0

-      it 'throws ENOENT error when can not find a file', ->
+      it 'throws ENOENT error when can not find file', ->
         for file in ['file4', 'file5', path.join('dir1', 'file4')]
           p = path.join fixtures, 'asar', 'a.asar', file
           throws = -> fs.lstatSync p
@@ -160,7 +160,7 @@
           assert.equal stats.size, 0
           done()

-      it 'throws ENOENT error when can not find a file', (done) ->
+      it 'throws ENOENT error when can not find file', (done) ->
         p = path.join fixtures, 'asar', 'a.asar', 'file4'
         stats = fs.lstat p, (err, stats) ->
           assert.equal err.code, 'ENOENT'
@@ -181,3 +181,36 @@
         p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2'
         dirs = fs.readdirSync p
         assert.deepEqual dirs, ['file1', 'file2', 'file3', 'link1', 'link2']
+
+      it 'throws ENOENT error when can not find file', ->
+        p = path.join fixtures, 'asar', 'a.asar', 'not-exist'
+        throws = -> fs.readdirSync p
+        assert.throws throws, /ENOENT/
+
+    describe 'fs.readdir', ->
+      it 'reads dirs from root', (done) ->
+        p = path.join fixtures, 'asar', 'a.asar'
+        dirs = fs.readdir p, (err, dirs) ->
+          assert.equal err, null
+          assert.deepEqual dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2']
+          done()
+
+      it 'reads dirs from a normal dir', (done) ->
+        p = path.join fixtures, 'asar', 'a.asar', 'dir1'
+        dirs = fs.readdir p, (err, dirs) ->
+          assert.equal err, null
+          assert.deepEqual dirs, ['file1', 'file2', 'file3', 'link1', 'link2']
+          done()
+
+      it 'reads dirs from a linked dir', (done) ->
+        p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2'
+        dirs = fs.readdir p, (err, dirs) ->
+          assert.equal err, null
+          assert.deepEqual dirs, ['file1', 'file2', 'file3', 'link1', 'link2']
+          done()
+
+      it 'throws ENOENT error when can not find file', (done) ->
+        p = path.join fixtures, 'asar', 'a.asar', 'not-exist'
+        stats = fs.readdir p, (err, stats) ->
+          assert.equal err.code, 'ENOENT'
+          done()


Don't run symlink spec on Windows

Don't run symlink spec on Windows

--- spec/file-spec.coffee
+++ spec/file-spec.coffee
@@ -144,19 +144,24 @@
           changeHandler.callCount > 0

   describe "getRealPathSync()", ->
-    it "returns the resolved path to the file", ->
+    tempDir = null
+
+    beforeEach ->
       tempDir = temp.mkdirSync('node-pathwatcher-directory')
       fs.writeFileSync(path.join(tempDir, 'file'), '')
       fs.writeFileSync(path.join(tempDir, 'file2'), '')

+    it "returns the resolved path to the file", ->
       tempFile = new File(path.join(tempDir, 'file'))
       expect(tempFile.getRealPathSync()).toBe fs.realpathSync(path.join(tempDir, 'file'))
       tempFile.setPath(path.join(tempDir, 'file2'))
       expect(tempFile.getRealPathSync()).toBe fs.realpathSync(path.join(tempDir, 'file2'))

-      fs.symlinkSync(path.join(tempDir, 'file2'), path.join(tempDir, 'file3'))
-      tempFile.setPath(path.join(tempDir, 'file3'))
-      expect(tempFile.getRealPathSync()).toBe fs.realpathSync(path.join(tempDir, 'file2'))
+    describe "on #darwin and #linux", ->
+      it "returns the target path for symlinks", ->
+        fs.symlinkSync(path.join(tempDir, 'file2'), path.join(tempDir, 'file3'))
+        tempFile = new File(path.join(tempDir, 'file3'))
+        expect(tempFile.getRealPathSync()).toBe fs.realpathSync(path.join(tempDir, 'file2'))

   describe "getParent()", ->
     it "gets the parent Directory", ->


Support pagerDutyIncidents specifying what statuses to return

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -85,7 +85,7 @@
       msg.send formatIncident(incident)

   robot.respond /(pager|major)( me)? (inc|incidents|sup|problems)$/i, (msg) ->
-    pagerDutyIncidents msg, (incidents) ->
+    pagerDutyIncidents msg, "triggered,acknowledged", (incidents) ->
       if incidents.length > 0
         buffer = "Triggered:\n----------\n"
         for junk, incident of incidents.reverse()
@@ -263,9 +263,9 @@
     pagerDutyGet msg, "/incidents/#{encodeURIComponent incident}", {}, (json) ->
       cb(json)

-  pagerDutyIncidents = (msg, cb) ->
+  pagerDutyIncidents = (msg, status, cb) ->
     query =
-      status:  "triggered,acknowledged"
+      status:  status
       sort_by: "incident_number:asc"
     pagerDutyGet msg, "/incidents", query, (json) ->
       cb(json.incidents)
@@ -312,31 +312,36 @@

     "#{inc.incident_number}: #{inc.created_on} #{summary} #{assigned_to}\n"

+  formatIncidentForIncidentsPut = (requesterId, incident, status) ->
+    # loljson
+    data = {
+      requester_id: requesterId
+      incidents: [
+        {
+          'id':     incident.id,
+          'status': status
+        }
+      ]
+    }
+
   updateIncident = (msg, incident_number, status) ->
     withPagerDutyUsers msg, (users) ->
-      userId = pagerDutyUserId(msg, users)
-      return unless userId
-
-      pagerDutyIncidents msg, (incidents) ->
+      requesterId = pagerDutyUserId(msg, users)
+      return unless requesterId
+
+      pagerDutyIncidents msg, "triggered,acknowledged", (incidents) ->
         foundIncidents = []
         for incident in incidents
           if "#{incident.incident_number}" == incident_number
             foundIncidents = [ incident ]
-            # loljson
-            data = {
-              requester_id: userId
-              incidents: [
-                {
-                  'id':     incident.id,
-                  'status': status
-                }
-              ]
-            }
-            pagerDutyPut msg, "/incidents", data, (json) ->
+
+            data = formatIncidentForIncidentsPut(requesterId, incident, status)
+            pagerDutyPut msg, "/incidents", data , (json) ->
               if incident = json.incidents[0]
                 msg.reply "Incident #{incident.incident_number} #{incident.status}."
               else
                 msg.reply "Problem updating incident #{incident_number}"
+            break
         if foundIncidents.length == 0
           msg.reply "Couldn't find incident #{incident_number}"


hook up sectionizer controls

hook up sectionizer controls

--- src/components/task-plan/homework/add-exercises.cjsx
+++ src/components/task-plan/homework/add-exercises.cjsx
@@ -79,6 +79,9 @@
   getExerciseIsSelected: (exercise) ->
     TaskPlanStore.hasExercise(@props.planId, exercise.id)

+  setCurrentSection: (currentSection) ->
+    @setState({currentSection})
+
   render: ->
     return @renderLoading() if @exercisesAreLoading()

@@ -95,6 +98,8 @@
         <ExerciseDetails
           {...sharedProps}
           selectedExercise={@state.selectedExercise}
+          onSectionChange={@setCurrentSection}
+          selectedSection={@state.currentSection}
           displayFeedback={@state.displayFeedback}
           onShowCardViewClick={@onShowCardViewClick}
         />


Redirect index of viewer to project GitHub repo.

--- viewer/app.coffee
+++ viewer/app.coffee
@@ -16,8 +16,9 @@

 handleIndex = (request, response, next) ->
     if request.url is '/'
-        html_content = 'Hello World!'
-        htmlResponse(response, html_content)
+        response.writeHead 303,
+            'Location': 'https://github.com/droptype/proto'
+        response.end()
     else
         next()


disable show_uncommitted warning for now

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -1358,8 +1358,9 @@
         else
             if not @_has_uncommitted_changes? or @_has_uncommitted_changes != val
                 if val and not @_show_uncommitted_warning_timeout
-                    @_show_uncommitted_warning_timeout = true
-                    setTimeout((()=>@_show_uncommitted_warning()), 12000)
+                    #@_show_uncommitted_warning_timeout = true
+                    #setTimeout((()=>@_show_uncommitted_warning()), 12000)
+                    # DO NOTHING FOR NOW -- needs more work to be reliable
                 else
                     @_when_had_no_uncommitted_changes = new Date()  # when we last knew for a fact there are no uncommitted changes
                     @uncommitted_element?.hide()


fix https://github.com/sagemathinc/cocalc/issues/3583 by making projects/projects_all more robust

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -100,11 +100,29 @@
     # Returns true only if we are a collaborator/user of this project and have loaded it.
     # Should check this before changing anything in the projects table!  Otherwise, bad
     # things will happen.
+    # This may also trigger load_all_projects.
+    # async
     have_project: (project_id) =>
-        return @redux.getTable('projects')?._table?.get(project_id)?  # dangerous use of _table!
+        t = @redux.getTable('projects')?._table
+        if not t? # called before initialization... -- shouldn't ever happen
+            return false
+        if t.get_state() != 'connected'
+            console.log("t.get_state == ", t.get_state())
+            # table isn't ready to be used yet -- wait for it.
+            await once(t, 'connected')
+        # now t is ready and we can query it.
+        if t.get(project_id)?
+            # we know this project
+            return true
+        if store.get('load_all_projects_done')
+            return false
+        # be sure by first loading all projects
+        await @load_all_projects()
+        # and try again.  Because we loaded all projects, we won't hit infinite recurse.
+        return await @have_project(project_id)

     set_project_title: (project_id, title) =>
-        if not @have_project(project_id)
+        if not await @have_project(project_id)
             console.warn("Can't set title -- you are not a collaborator on project '#{project_id}'.")
             return
         if store.get_title(project_id) == title
@@ -118,7 +136,7 @@
             title : title

     set_project_description: (project_id, description) =>
-        if not @have_project(project_id)
+        if not await @have_project(project_id)
             console.warn("Can't set description -- you are not a collaborator on project '#{project_id}'.")
             return
         if store.get_description(project_id) == description
@@ -180,7 +198,7 @@

     # only owner can set course description.
     set_project_course_info: (project_id, course_project_id, path, pay, account_id, email_address) =>
-        if not @have_project(project_id)
+        if not await @have_project(project_id)
             msg = "Can't set description -- you are not a collaborator on project '#{project_id}'."
             console.warn(msg)
             return


show message to user when creating project if non-paying user (note embedding of PaymentMethods not working)

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -666,6 +666,19 @@
         if e.keyCode == 13 and @state.title_text != ''
             @create_project()

+    render_become_a_subscriber : ->
+        {PaymentMethods} = require('./billing')
+        <Col sm=12>
+
Upgrade to give your project internet access and more resources

+
Free projects don't have internet access.
+            Starting at just $7/month you can give your project(s)
+            internet access, members only hosting, 1 day Idle timeout,
+            3 GB Memory, 5 GB Disk space, and half CPU share.

+

+                <PaymentMethods redux={redux} sources={data:[]} default='' />
+

+        </Col>
+
     render_input_section : ->
         <Well style={backgroundColor: '#FFF', color:'#666'}>
             <Row>
@@ -706,7 +719,7 @@
                             disabled = {@state.title_text == '' or @state.state == 'saving'}
                             bsStyle  = 'success'
                             onClick  = {@create_project} >
-                            Create project
+                            Create project without upgrades
                         </Button>
                         <Button
                             disabled = {@state.state is 'saving'}
@@ -728,6 +741,9 @@
                         share with others and upgrade.

                 </Col>
+            </Row>
+            <Row>
+                {@render_become_a_subscriber() if not redux.getStore('account').is_paying_member()}
             </Row>
         </Well>


sync2: re-implement sagews interrupt

sync2: re-implement sagews interrupt

--- src/smc-webapp/sagews.coffee
+++ src/smc-webapp/sagews.coffee
@@ -1901,8 +1901,27 @@
                 cm?.refresh()
         @_refresh_soon = setTimeout(do_refresh, wait)

-    interrupt: () =>
+    interrupt: (opts) =>
+        opts = defaults opts,
+            maxtime : 15
+            cb      : undefined
+        if @readonly
+            opts.cb?()
+            return
         @close_on_action()
+        t = misc.walltime()
+        async.series([
+            (cb) =>
+                @send_signal
+                    signal : 2
+                    cb     : cb
+            (cb) =>
+                @start
+                    maxtime : opts.maxtime - misc.walltime(t)
+                    cb      : cb
+        ], (err) =>
+            opts.cb?(err)
+        )

     close_on_action: (element) =>
         # Close popups (e.g., introspection) that are set to be closed when an


Add pane:close to atom-pane context menu (Win)

--- menus/win32.cson
+++ menus/win32.cson
@@ -196,6 +196,7 @@
     {label: 'Split Down', command: 'pane:split-down'}
     {label: 'Split Left', command: 'pane:split-left'}
     {label: 'Split Right', command: 'pane:split-right'}
+    {label: 'Close Pane', command: 'pane:close'}
     {type: 'separator'}
   ]
   'atom-pane': [
@@ -204,5 +205,6 @@
     {label: 'Split Down', command: 'pane:split-down'}
     {label: 'Split Left', command: 'pane:split-left'}
     {label: 'Split Right', command: 'pane:split-right'}
+    {label: 'Close Pane', command: 'pane:close'}
     {type: 'separator'}
   ]


Removed 2 lines, where happens exception

--- js/media/audioplayer/audioplayer.coffee
+++ js/media/audioplayer/audioplayer.coffee
@@ -41,8 +41,8 @@
   skipBack: ->
     @seek()
   idle: ->
-    if @playing and @_mediaElement?
-      console.log('position:',@_mediaElement.currentPosition())
+    # if @playing and @_mediaElement?
+    #   console.log('position:',@_mediaElement.currentPosition())
   playStop: ->
     if @playing
       @stop()


Add support for htmlroff(6)'s `.if h` extension

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -373,7 +373,7 @@

 				(?:
 					# One-character built-in comparison name
-					([notev])        # 4: constant.language.builtin-comparison.$4.roff
+					([notevh])       # 4: constant.language.builtin-comparison.$4.roff

 					|


The array is nested in anObj.

The array is nested in anObj.

--- test/unit/helpersSpec.coffee
+++ test/unit/helpersSpec.coffee
@@ -37,7 +37,7 @@
           ObjectHelper = _ObjectHelper_
         ]

-      describe.only 'fromJSON', ->
+      describe 'fromJSON', ->
         mock =
           _id: 1
           an_int: 1
@@ -82,11 +82,11 @@
             .to.eql(mock.inner.an_obj.an_int)

         it 'should add camelCase aliases to arrays', ->
-          expect('anArray' in Object.keys(obj.inner),
+          expect('anArray' in Object.keys(obj.inner.anObj),
                  "the inner underscore array property is not aliased").to.be.true
-          expect(obj.inner.anArray.length, "the inner array length is incorrect")
+          expect(obj.inner.anObj.anArray.length, "the inner array length is incorrect")
             .to.equal(1)
-          item = obj.inner.anArray[0]
+          item = obj.inner.anObj.anArray[0]
           expect('anInt' in Object.keys(item), "the inner array item scalar property" +
                                                " is not aliased").to.be.true


Sanitize duplicate error and [object Object].<anonymous> lines

--- spec/atom-reporter.coffee
+++ spec/atom-reporter.coffee
@@ -3,7 +3,7 @@
 {convertStackTrace} = require 'coffeestack'

 sourceMaps = {}
-formatStackTrace = (stackTrace) ->
+formatStackTrace = (message='', stackTrace) ->
   return stackTrace unless stackTrace

   jasminePattern = /^\s*at\s+.*\(?.*\/jasmine(-[^\/]*)?\.js:\d+:\d+\)?\s*$/
@@ -11,7 +11,19 @@
   for line in stackTrace.split('\n')
     convertedLines.push(line) unless jasminePattern.test(line)

-  convertStackTrace(convertedLines.join('\n'), sourceMaps)
+  stackTrace = convertStackTrace(convertedLines.join('\n'), sourceMaps)
+  lines = stackTrace.split('\n')
+
+  # Remove first line of stack when it is the same as the error message
+  errorMatch = lines[0]?.match(/^Error: (.*)/)
+  lines.shift() if message.trim() is errorMatch?[1]?.trim()
+
+  # Remove prefix of lines matching: at [object Object].<anonymous> (path:1:2)
+  for line, index in lines
+    prefixMatch = line.match(/at \[object Object\]\.<anonymous> \(([^\)]+)\)/)
+    lines[index] = "at #{prefixMatch[1]}" if prefixMatch
+
+  lines.join('\n')

 module.exports =
 class AtomReporter extends View
@@ -215,7 +227,7 @@
     @description.text @spec.description

     for result in @spec.results().getItems() when not result.passed()
-      stackTrace = formatStackTrace(result.trace.stack)
+      stackTrace = formatStackTrace(result.message, result.trace.stack)
       @specFailures.append $$ ->
         @div result.message, class: 'resultMessage fail'
         @div stackTrace, class: 'stack-trace padded' if stackTrace


Remove logging

Remove logging

--- spec/atom-reporter.coffee
+++ spec/atom-reporter.coffee
@@ -174,9 +174,6 @@
       event.currentTarget.classList.toggle('expanded')

     @reloadButton.addEventListener('click', -> require('ipc').send('call-window-method', 'restart'))
-
-    document.addEventListener 'keydown', (event) ->
-      console.log event

   updateSpecCounts: ->
     if @skippedCount


spec: Suppress navigator.webkitGetUserMedia test

It is stressing the whole test case.

--- spec/chromium-spec.coffee
+++ spec/chromium-spec.coffee
@@ -45,7 +45,7 @@
         done()
       w.loadURL url

-  describe 'navigator.webkitGetUserMedia', ->
+  xdescribe 'navigator.webkitGetUserMedia', ->
     it 'calls its callbacks', (done) ->
       @timeout 5000
       navigator.webkitGetUserMedia audio: true, video: false,


Assert and Should are no longer needed.

Assert and Should are no longer needed.

--- spec/compiler_spec.coffee
+++ spec/compiler_spec.coffee
@@ -1,5 +1,3 @@
-assert = require 'assert'
-should = require 'should'
 fs     = require 'fs'

 CoffeeScript  = require 'coffee-script'


Rearrange test order

Rearrange test order

--- spec/html-img-spec.coffee
+++ spec/html-img-spec.coffee
@@ -201,6 +201,67 @@
       editorView = open 'htdocs/ejs/tag-closed.ejs'
       assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if (foo < bar && bar > baz) { %><%= bar %><% } else { %><%- baz %><% } %>" width="800" height="500"/>'

+  describe "in ECO", ->
+
+    it "supports base-absolute", ->
+      editorView = open 'htdocs/eco/base-absolute.eco'
+      assert editorView, 'html-img:fill', '<img src="/images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500">'
+      assert editorView, 'html-img:fill-half', '<img src="/images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400" height="250">'
+      assert editorView, 'html-img:fill-width', '<img src="/images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800">'
+      assert editorView, 'html-img:fill-width-half', '<img src="/images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400">'
+      assert editorView, 'html-img:fill-height', '<img src="/images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="500">'
+      assert editorView, 'html-img:fill-height-half', '<img src="/images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="250">'
+
+    it "supports base-relative", ->
+      editorView = open 'htdocs/eco/base-relative.eco'
+      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500">'
+      assert editorView, 'html-img:fill-half', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400" height="250">'
+      assert editorView, 'html-img:fill-width', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800">'
+      assert editorView, 'html-img:fill-width-half', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400">'
+      assert editorView, 'html-img:fill-height', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="500">'
+      assert editorView, 'html-img:fill-height-half', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="250">'
+
+    it "supports protocol-absolute", ->
+      editorView = open 'htdocs/eco/protocol-absolute.eco'
+      assert editorView, 'html-img:fill', '<img src="https://cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500">'
+      assert editorView, 'html-img:fill-half', '<img src="https://cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400" height="250">'
+      assert editorView, 'html-img:fill-width', '<img src="https://cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800">'
+      assert editorView, 'html-img:fill-width-half', '<img src="https://cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400">'
+      assert editorView, 'html-img:fill-height', '<img src="https://cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="500">'
+      assert editorView, 'html-img:fill-height-half', '<img src="https://cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="250">'
+
+    it "supports protocol-relative", ->
+      editorView = open 'htdocs/eco/protocol-relative.eco'
+      assert editorView, 'html-img:fill', '<img src="//cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500">'
+      assert editorView, 'html-img:fill-half', '<img src="//cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400" height="250">'
+      assert editorView, 'html-img:fill-width', '<img src="//cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800">'
+      assert editorView, 'html-img:fill-width-half', '<img src="//cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400">'
+      assert editorView, 'html-img:fill-height', '<img src="//cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="500">'
+      assert editorView, 'html-img:fill-height-half', '<img src="//cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="250">'
+
+    it "recognizes tag range", ->
+      editorView = open 'htdocs/eco/tag-range.eco'
+      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>">', [0, 0]
+      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>">', [0, 1]
+      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>">', [0, 112]
+      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>">', [0, 113]
+      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500">', [0, 114]
+      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500">', [0, 225]
+      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>">', [0, 226]
+
+    it "recognizes spaced tag", ->
+      editorView = open 'htdocs/eco/tag-spaced.eco'
+      assert editorView, 'html-img:fill', '<img\n  src = "../images/example.png"\n  alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"\n  >', [0, 0]
+      assert editorView, 'html-img:fill', '<img\n  src = "../images/example.png"\n  alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"\n  width="800" height="500">', [0, 1]
+      assert editorView, 'html-img:fill', '<img\n  src = "../images/example.png"\n  alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"\n  width="800" height="500">', [1, 1]
+      assert editorView, 'html-img:fill', '<img\n  src = "../images/example.png"\n  alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"\n  width="800" height="500">', [2, 1]
+      assert editorView, 'html-img:fill', '<img\n  src = "../images/example.png"\n  alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"\n  width="800" height="500">', [3, 1]
+      assert editorView, 'html-img:fill', '<img\n  src = "../images/example.png"\n  alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"\n  >', [4, 0]
+
+    it "recognizes closed tag", ->
+      editorView = open 'htdocs/eco/tag-closed.eco'
+      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500"/>'
+
   describe "in ERB", ->

     it "supports base-absolute", ->
@@ -261,64 +322,3 @@
     it "recognizes closed tag", ->
       editorView = open 'htdocs/erb/tag-closed.erb'
       assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar && bar > baz then %><%= bar %><% else %><%- baz %><% end %>" width="800" height="500"/>'
-
-  describe "in ECO", ->
-
-    it "supports base-absolute", ->
-      editorView = open 'htdocs/eco/base-absolute.eco'
-      assert editorView, 'html-img:fill', '<img src="/images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500">'
-      assert editorView, 'html-img:fill-half', '<img src="/images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400" height="250">'
-      assert editorView, 'html-img:fill-width', '<img src="/images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800">'
-      assert editorView, 'html-img:fill-width-half', '<img src="/images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400">'
-      assert editorView, 'html-img:fill-height', '<img src="/images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="500">'
-      assert editorView, 'html-img:fill-height-half', '<img src="/images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="250">'
-
-    it "supports base-relative", ->
-      editorView = open 'htdocs/eco/base-relative.eco'
-      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500">'
-      assert editorView, 'html-img:fill-half', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400" height="250">'
-      assert editorView, 'html-img:fill-width', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800">'
-      assert editorView, 'html-img:fill-width-half', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400">'
-      assert editorView, 'html-img:fill-height', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="500">'
-      assert editorView, 'html-img:fill-height-half', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="250">'
-
-    it "supports protocol-absolute", ->
-      editorView = open 'htdocs/eco/protocol-absolute.eco'
-      assert editorView, 'html-img:fill', '<img src="https://cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500">'
-      assert editorView, 'html-img:fill-half', '<img src="https://cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400" height="250">'
-      assert editorView, 'html-img:fill-width', '<img src="https://cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800">'
-      assert editorView, 'html-img:fill-width-half', '<img src="https://cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400">'
-      assert editorView, 'html-img:fill-height', '<img src="https://cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="500">'
-      assert editorView, 'html-img:fill-height-half', '<img src="https://cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="250">'
-
-    it "supports protocol-relative", ->
-      editorView = open 'htdocs/eco/protocol-relative.eco'
-      assert editorView, 'html-img:fill', '<img src="//cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500">'
-      assert editorView, 'html-img:fill-half', '<img src="//cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400" height="250">'
-      assert editorView, 'html-img:fill-width', '<img src="//cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800">'
-      assert editorView, 'html-img:fill-width-half', '<img src="//cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="400">'
-      assert editorView, 'html-img:fill-height', '<img src="//cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="500">'
-      assert editorView, 'html-img:fill-height-half', '<img src="//cloud.githubusercontent.com/assets/514164/3367904/47f2f0ce-fb6b-11e3-9b0e-8f836f031d85.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" height="250">'
-
-    it "recognizes tag range", ->
-      editorView = open 'htdocs/eco/tag-range.eco'
-      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>">', [0, 0]
-      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>">', [0, 1]
-      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>">', [0, 112]
-      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>">', [0, 113]
-      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500">', [0, 114]
-      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500">', [0, 225]
-      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"><img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>">', [0, 226]
-
-    it "recognizes spaced tag", ->
-      editorView = open 'htdocs/eco/tag-spaced.eco'
-      assert editorView, 'html-img:fill', '<img\n  src = "../images/example.png"\n  alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"\n  >', [0, 0]
-      assert editorView, 'html-img:fill', '<img\n  src = "../images/example.png"\n  alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"\n  width="800" height="500">', [0, 1]
-      assert editorView, 'html-img:fill', '<img\n  src = "../images/example.png"\n  alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"\n  width="800" height="500">', [1, 1]
-      assert editorView, 'html-img:fill', '<img\n  src = "../images/example.png"\n  alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"\n  width="800" height="500">', [2, 1]
-      assert editorView, 'html-img:fill', '<img\n  src = "../images/example.png"\n  alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"\n  width="800" height="500">', [3, 1]
-      assert editorView, 'html-img:fill', '<img\n  src = "../images/example.png"\n  alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>"\n  >', [4, 0]
-
-    it "recognizes closed tag", ->
-      editorView = open 'htdocs/eco/tag-closed.eco'
-      assert editorView, 'html-img:fill', '<img src="../images/example.png" alt="<% if foo < bar and bar > baz: %><%= bar %><% else: %><%- baz %><% end %>" width="800" height="500"/>'


testing console.log removed

testing console.log removed

--- app/assets/javascripts/backbone_js/ladas_editable_table_aligner.js.coffee
+++ app/assets/javascripts/backbone_js/ladas_editable_table_aligner.js.coffee
@@ -81,7 +81,7 @@
       width_align_id = th.data("width-align-id")

       td = $("#" + obj.form_id).find('td[data-width-align-id="' + width_align_id + '"]').first()
-      console.log(td)
+      #console.log(td)
       #careful delete one by one, or scrolling will be lost
       # have to delete it from elements, otherwise the width would increase (alwazs adding padding nad vbroder to outerWidth)
       th.css({


charts: fixed fce chart

charts: fixed fce chart

--- app/assets/javascripts/lib/views/horizontal_stacked_bar_chart_view.coffee
+++ app/assets/javascripts/lib/views/horizontal_stacked_bar_chart_view.coffee
@@ -11,34 +11,28 @@

   # the horizontal stacked graph expects data in this format:
   # [[value1, 1], [value1, 2]],[[value2, 1], [value2, 2]] ...
-  results: ->
+  results: =>
     series = {}
     # parse all the results into a hash by group and add the serie number to it
     @model.series.each (s) ->
       group = s.get('group')
-      if (group)
-        s[group] = [] unless series[group]
-        s[group].push([s.safe_present_value(),(series[group].length + 1)])
+      if group
+        series[group] = [] unless series[group]
+        series[group].push [s.safe_future_value(), (series[group].length + 1)]
     #jqplot needs and array so we create this by mapping the hash
     _.map series, (values, group) -> values

-  clear_results_cache: ->
-    @cached_results = null
+  clear_results_cache: -> @cached_results = null

-  axis_scale: ->
-    min = @model.get('min_axis_value')
-    max = @model.get('max_axis_value')
-    [min, max]
+  axis_scale: -> [@model.get('min_axis_value'), @model.get('max_axis_value')]

   ticks: ->
     groups = @model.series.map (serie) -> serie.get('group_translated')
     _.uniq(groups)

-  colors: ->
-    _.uniq(@model.colors())
+  colors: -> _.uniq(@model.colors())

-  labels: ->
-    _.uniq(@model.labels())
+  labels: -> _.uniq(@model.labels())

   render_chart: =>
     $.jqplot @container_id(), @results(), @chart_opts()


a cell can have a bottom

a cell can have a bottom

--- spec/lib/cell-spec.coffee
+++ spec/lib/cell-spec.coffee
@@ -27,3 +27,9 @@
     When -> @res = @cell.bottomRight()
     Then -> expect(@res.x).toBe 5
     And -> expect(@res.y).toBe 5
+
+  describe 'has a bottom', ->
+
+    When -> @res = @cell.bottom()
+    Then -> expect(@res.x).toBe 4
+    And -> expect(@res.y).toBe 5


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -137,7 +137,7 @@
 @name startsWith
 @methodOf String#

-@returns {Boolean} True if
+@returns {Boolean} True if this string starts with the
 ###
 String::startsWith = (str) ->
   @lastIndexOf(str, 0) is 0


:green_heart: Add default settings for package's main specs

--- spec/pigments-spec.coffee
+++ spec/pigments-spec.coffee
@@ -10,13 +10,12 @@
     workspaceElement = atom.views.getView(atom.workspace)
     jasmine.attachToDOM(workspaceElement)

-
-    # atom.config.set('pigments.sourceNames', ['**/*.sass', '**/*.styl'])
-    # atom.config.set('pigments.ignoredNames', [])
-    # atom.config.set('pigments.autocompleteScopes', [])
+    atom.config.set('pigments.sourceNames', ['**/*.sass', '**/*.styl'])
+    atom.config.set('pigments.ignoredNames', [])
+    atom.config.set('pigments.ignoredScopes', [])
+    atom.config.set('pigments.autocompleteScopes', [])

     waitsForPromise -> atom.packages.activatePackage('pigments').then (pkg) ->
-      console.log atom.config.get('pigments')
       pigments = pkg.mainModule
       project = pigments.getProject()


S54849 - Filter grid by selected iteration.

--- iterationtrackingboard/IterationTrackingBoardAppSpec.coffee
+++ iterationtrackingboard/IterationTrackingBoardAppSpec.coffee
@@ -1,8 +1,7 @@
 Ext = window.Ext4 || window.Ext

 Ext.require [
-  'Rally.util.DateTime',
-  'Rally.alm.FeatureToggle'
+  'Rally.util.DateTime'
 ]

 describe 'Rally.apps.iterationtrackingboard.IterationTrackingBoardApp', ->
@@ -33,17 +32,41 @@

       @waitForComponentReady(@app)

+    getIterationFilter: ->
+      iteration = @iterationData[0]
+
+      [
+        { property: 'Iteration.Name', operator: '=', value: iteration.Name },
+        { property: "Iteration.StartDate", operator: '=', value: iteration.StartDate }
+        { property: "Iteration.EndDate", operator: '=', value: iteration.EndDate }
+      ]
+
+    stubRequests: ->
+      userstoryStub = @ajax.whenReading('userstory').respondWith()
+      defectStub = @ajax.whenQuerying('defect').respondWith()
+      defectsuiteStub = @ajax.whenQuerying('defectsuite').respondWith()
+      testsetStub = @ajax.whenQuerying('testset').respondWith()
+      @ajax.whenQueryingAllowedValues('userstory', 'ScheduleState').respondWith(["Defined", "In-Progress", "Completed", "Accepted"]);
+
+      [userstoryStub, defectStub, defectsuiteStub, testsetStub]
+
+    toggleToGridOrBoard: (view) ->
+      toggler = @app.gridboard.down('rallygridboardtoggle')
+      toggler.fireEvent 'toggle', view
+
+    toggleToBoard: ->
+      @toggleToGridOrBoard('board')
+
+    toggleToGrid: ->
+      @toggleToGridOrBoard('grid')
+
   beforeEach ->
     @ajax.whenReading('project').respondWith {
       TeamMembers: []
       Editors: []
     }

-    @ajax.whenQuerying('userstory').respondWith()
-    @ajax.whenQuerying('defect').respondWith()
-    @ajax.whenQuerying('defectsuite').respondWith()
-    @ajax.whenQuerying('testset').respondWith()
-    @ajax.whenQueryingAllowedValues('userstory', 'ScheduleState').respondWith(["Defined", "In-Progress", "Completed", "Accepted"]);
+    @stubRequests()

     @tooltipHelper = new Helpers.TooltipHelper this

@@ -95,4 +118,22 @@
     it 'should not show field picker in settings ', ->
       @createApp(isShowingBlankSlate: -> true).then =>
         @app.showFieldPicker = true
-        expect(Ext.isEmpty(_.find(@app.getSettingsFields(), name: 'cardFields'))).toBe true+        expect(Ext.isEmpty(_.find(@app.getSettingsFields(), name: 'cardFields'))).toBe true
+
+  it 'should filter the grid to the currently selected iteration', ->
+    @stub(Rally.app.Context.prototype, 'isFeatureEnabled').withArgs('ITERATION_TRACKING_BOARD_GRID_TOGGLE').returns(true)
+    requests = @stubRequests()
+
+    @createApp().then =>
+      @toggleToGrid()
+
+      expect(request).toBeWsapiRequestWith(filters: @getIterationFilter()) for request in requests
+
+  it 'should filter the board to the currently selected iteration', ->
+    @stub(Rally.app.Context.prototype, 'isFeatureEnabled').withArgs('ITERATION_TRACKING_BOARD_GRID_TOGGLE').returns(true)
+    requests = @stubRequests()
+
+    @createApp().then =>
+      @toggleToBoard()
+
+      expect(request).toBeWsapiRequestWith(filters: @getIterationFilter()) for request in requests


Eliminate fixturesProject global. Use project global instead.

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -300,7 +300,7 @@
       jasmine.unspy(LoadSnippetsTask.prototype, 'loadTextMateSnippets')
       snippets.loaded = false
       task = new LoadSnippetsTask(snippets)
-      task.packages = [Package.build(fixturesProject.resolve('packages/package-with-a-cson-grammar.tmbundle'))]
+      task.packages = [Package.build(project.resolve('packages/package-with-a-cson-grammar.tmbundle'))]
       task.start()

       waitsFor "CSON snippets to load", 5000, -> snippets.loaded


:new: Add more validation specs

:new: Add more validation specs

--- spec/validate-spec.coffee
+++ spec/validate-spec.coffee
@@ -8,11 +8,11 @@
     it 'throws if lint is missing', ->
       expect ->
         validate.linter({grammarScopes: []})
-      .toThrow("Missing linter.lint")
+      .toThrow()
     it 'throws if lint is not a function', ->
       expect ->
         validate.linter({grammarScopes: [], lint: true})
-      .toThrow("linter.lint isn't a function")
+      .toThrow()

   describe '::messages', ->
     it 'throws if messages is not an array', ->
@@ -25,8 +25,31 @@
     it 'throws if type field is not present', ->
       expect ->
         validate.messages([{}])
-      .toThrow("Missing type field on Linter Response")
+      .toThrow()
+    it "throws if type field is invalid", ->
+      expect ->
+        validate.messages([{type: 1}])
+      .toThrow()
     it "throws if there's no html/text field on message", ->
       expect ->
         validate.messages([{type: 'Error'}])
-      .toThrow('Missing html/text field on Linter Response')
+      .toThrow()
+    it "throws if html/text is invalid", ->
+      expect ->
+        validate.messages([{type: 'Error', html: 1}])
+      .toThrow()
+      expect ->
+        validate.messages([{type: 'Error', text: 1}])
+      .toThrow()
+      expect ->
+        validate.messages([{type: 'Error', html: false}])
+      .toThrow()
+      expect ->
+        validate.messages([{type: 'Error', text: false}])
+      .toThrow()
+      expect ->
+        validate.messages([{type: 'Error', html: []}])
+      .toThrow()
+      expect ->
+        validate.messages([{type: 'Error', text: []}])
+      .toThrow()


change getBounds

change getBounds

--- syslibs/library.coffee
+++ syslibs/library.coffee
@@ -86,7 +86,7 @@

 # get browser size(include scrolling bar)
 getBounds=->
-    width = document.documentElement.clientWidth - 18
+    width = document.documentElement.clientWidth - 2
     height = document.documentElement.clientHeight - 2
     frame = JSRectMake(0, 0, width, height)
     return frame


wip groups

wip groups

--- client/landing/app/Applications/Groups.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Groups.kdapplication/AppController.coffee
@@ -133,19 +133,26 @@

     if err.accessCode is ERROR_POLICY
       defaultOptions.buttons['Request access'] =
-        cssClass  : 'cupid-green'
-        callback  : -> @getDelegate().emit 'AccessIsRequested'
+        cssClass    : 'modal-clean-green'
+        loader      :
+          color     : "#ffffff"
+          diameter  : 12
+        callback    : -> @getDelegate().emit 'AccessIsRequested'

     _.extend defaultOptions, customOptions

   showErrorModal:(group, err)->
     modal = new KDModalView getErrorModalOptions err
-    modal.on 'AccessIsRequested', @requestAccess.bind this, group
-    console.log {modal}
-
-  requestAccess:(group)->
+    modal.on 'AccessIsRequested', =>
+      @requestAccess group, (err)-> modal.destroy()
+
+  requestAccess:(group, callback)->
     group.requestInvitation (err)->
-      console.log 'invitation is requested', arguments
+      callback err
+      new KDNotificationView title:
+        if err then err.message
+        else "Invitation has been requested!"
+

   openPrivateGroup:(group)->
     group.canOpenGroup (err, policy)=>
@@ -321,6 +328,8 @@
       if isPrivateGroup
         modalOptions.tabs.forms['Membership policy'] =
           title   : "Membership policy"
+        modalOptions.tabs.forms['Invitations'] =
+          title   : "Invitations"

     modal = new KDModalViewWithForms modalOptions, group

@@ -345,6 +354,9 @@
               membershipPolicyView.emit 'MembershipPolicyChangeSaved'

           forms["Membership policy"].addSubView membershipPolicyView
+
+        invitationRequestView = new GroupsInvitationRequestsView {}, group
+        forms["Invitations"] = invitationRequestView

       forms["Members"].addSubView new GroupsMemberPermissionsView {}, group


Protected suggestUniqueSlug method and changed UI as Gokmen requested.

--- client/landing/app/Applications/Groups.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Groups.kdapplication/AppController.coffee
@@ -403,9 +403,16 @@
         name: slugInput.getValue()
       , (err, name)->
         if name
-          slugInput.setClass 'slug-taken'
+          slugInput.setValidationResult 'slug', "Slug is already being used.", yes
+          KD.remote.api.JGroup.suggestUniqueSlug name.name, (err, newSlug)->
+            unless err
+              slugInput.setTooltip
+                title     : "Suggestion: #{newSlug}"
+                placement : 'right'
+              slugInput.tooltip.show()
         else
-          slugInput.unsetClass 'slug-taken'
+          slugInput.setValidationResult 'slug', null
+          delete slugInput.tooltip

     makeSlug = =>
       titleInput = modal.modalTabs.forms["General Settings"].inputs.Title
@@ -414,6 +421,7 @@
         if err then slugInput.setValue ''
         else
           slugInput.setValue newSlug
+          verifySlug()

     getGroupType = ->
       modal.modalTabs.forms["Select group type"].inputs.type.getValue()
@@ -562,7 +570,7 @@
     modal = new KDModalViewWithForms modalOptions
     form = modal.modalTabs.forms["General Settings"]
     form.on "FormValidationFailed", ->
-      form.buttons.Save.hideLoader()
+    form.buttons.Save.hideLoader()

   handleError =(err, buttons)->
     unless buttons


start router after successfully loading messages

--- web/src/coffee/malm.coffee
+++ web/src/coffee/malm.coffee
@@ -15,9 +15,7 @@
     )
     messageRouter.messageController = messageController

-    messageList.fetch("add": true)
-
-    Backbone.history.start()
+    messageList.fetch("add": true, "success": () -> Backbone.history.start())

 class Message extends Backbone.Model

@@ -35,7 +33,6 @@
     @collection.bind('add', @renderItem)

   renderItem: (message) =>
-    console.log(message.id)
     view = new MessageView("model": message, "messageRouter": @messageRouter)
     $(@el).append(view.el)


disabled some sources

disabled some sources

--- web/src/cs/controls.coffee
+++ web/src/cs/controls.coffee
@@ -13,9 +13,9 @@

 sources = [
 	require './sources/reddit.coffee'
-	require './sources/testdata.coffee'
-	require './sources/dreamlastnight.coffee'
-	require './sources/twitter.coffee'
+	#require './sources/testdata.coffee'
+	#require './sources/dreamlastnight.coffee'
+	#require './sources/twitter.coffee'

 ]


Don't reset the username on submit

Don't reset the username on submit

--- web2/src/stores/poi.coffee
+++ web2/src/stores/poi.coffee
@@ -57,7 +57,6 @@
         @setState
             loading: false
             error: false
-            username: null

     handleSubmitFail: ->
         @setState


<test> Preparing fixtures for Driver Controller Test.

--- tests/fixtures/driver.coffee
+++ tests/fixtures/driver.coffee
@@ -5,10 +5,88 @@
 # Test Fixtures for Drivers.
 ###

+os = require 'os'
+fs = require 'fs'
+
 module.exports =
 	basicSensorDriver:
 		name: "basicDriver"
 		file: __dirname + "/basicDriver.coffee"
+	basicSensorDriverJS:
+		name: "basicDriver"
+		file: __dirname + "/basicDriver.js"
+	basicSensorDriverTar:
+		name: "basicDriver"
+		file: __dirname + "/basicDriver.tar"
+	basicSensorDriverZip:
+		name: "basicDriver"
+		file: __dirname + "/basicDriver.zip"
+	basicSensorDriverTarGz:
+		name: "basicDriver"
+		file: __dirname + "/basicDriver.tar.gz"
+	basicSensorDriverTarBz2:
+		name: "basicDriver"
+		file: __dirname + "/basicDriver.tar.bz2"

 	unexistingSensorDriver:
-		name: "Unexisting Driver"+		name: "Unexisting Driver"
+
+	driverChunks: {
+		isSensor: "module.exports.isSensor = true"
+		isActuator: "module.exports.isActuator = true"
+
+		init: """
+		module.exports.init = (callback) ->
+			return callback null
+		"""
+
+		add: """
+		devices = []
+		module.exports.add = (customId, id, callback) ->
+			if devices[customId]
+				callback 'Custom ID already used.'
+			else
+				devices[customId] = id
+				callback null
+		"""
+
+		remove: """
+		module.exports.remove = (customId, id, callback) ->
+			delete sensors[customId]
+			callback null
+		"""
+
+		update: """
+		module.exports.update = (oldCustomId, newCustomId, callback) ->
+			if devices[newCustomId]
+				callback 'Custom ID already used.'
+			else
+
+				if isCustomIDValidExample newCustomId
+					devices[newCustomId] = devices[oldCustomId]
+					devices[oldCustomId] = null
+					callback null
+				else
+					callback "Invalid CustomId"
+		"""
+
+		apply: """
+		module.exports.apply = (customId, value, callback) ->
+			console.log('DRIVER - Applying "+ value +" to Actuator ' + customId)
+			callback null
+		"""
+
+		invalidSyntax: """
+		XXXXXXXXXXXXX
+		"""
+	}
+	generateDriver: (name, chunks, callback) ->
+		content = ''
+		content += @driverChunks[c] + '\n' for c in chunks
+		path = os.tmpdir() + "/" + name + ".coffee"
+		fs.writeFile path, content, (err) ->
+			if err
+				return callback null, err
+			else
+				return callback path, null
+


Use long opts for readability

Use long opts for readability

--- src/packages/fuzzy-finder/lib/load-paths-task.coffee
+++ src/packages/fuzzy-finder/lib/load-paths-task.coffee
@@ -13,9 +13,9 @@
     ignoreGitIgnoredFiles =  config.get('core.hideGitIgnoredFiles')

     command = require.resolve 'nak'
-    args = ['-l', rootPath]
+    args = ['--list', rootPath]
     args.unshift('--addVCSIgnores') if config.get('nak.addVCSIgnores')
-    args.unshift('-d', ignoredNames.join(',')) if ignoredNames.length > 0
+    args.unshift('--ignore', ignoredNames.join(',')) if ignoredNames.length > 0
     args.unshift('--follow')

     paths = []


Clean up some coffescript

Clean up some coffescript

--- app/assets/javascripts/tours.coffee
+++ app/assets/javascripts/tours.coffee
@@ -2,37 +2,36 @@
   fromMarkers = []
   toMarkers   = []

-  map = new google.maps.Map(
-  	document.getElementById('map-canvas'),
-  	mapTypeId: google.maps.MapTypeId.ROADMAP
+  window.map = new google.maps.Map(
+    document.getElementById('map-canvas'),
+    mapTypeId: google.maps.MapTypeId.ROADMAP
   )

-  defaultBounds = new google.maps.LatLngBounds(
-  	new google.maps.LatLng(-23.1772377, -47.3251997),
-  	new google.maps.LatLng(-23.2196102, -47.2785018)
+  map.fitBounds new google.maps.LatLngBounds(
+    new google.maps.LatLng(-22, -47),
+    new google.maps.LatLng(-24, -47)
   )

-  map.fitBounds defaultBounds
+  setEventListener (getSearchBox 'pac-from-input'), fromMarkers
+  setEventListener (getSearchBox 'pac-to-input'), toMarkers

-  # Create the 'from' search box and link it to the UI element.
-  fromInput = document.getElementById('pac-from-input')
-  map.controls[google.maps.ControlPosition.TOP_LEFT].push(fromInput)
-  fromSearchBox = new google.maps.places.SearchBox(fromInput)
+getSearchBox = (elementId) ->
+  # Create the search box and link it to the given elementId.
+  input = document.getElementById(elementId)
+  map.controls[google.maps.ControlPosition.TOP_LEFT].push(input)

-  # Create the 'to' search box and link it to the UI element.
-  toInput = document.getElementById('pac-to-input')
-  map.controls[google.maps.ControlPosition.TOP_LEFT].push(toInput)
-  toSearchBox = new google.maps.places.SearchBox(toInput)
+  new google.maps.places.SearchBox(input)

+setEventListener = (searchInput, markers) ->
   # Listen for the event fired when the user selects an item from the
   # search pick list. Retrieve the matching places for that item.
-  google.maps.event.addListener fromSearchBox, 'places_changed', ->
-    places = fromSearchBox.getPlaces()
+  google.maps.event.addListener searchInput, 'places_changed', ->
+    places = searchInput.getPlaces()

     if places.length == 0
       return

-    for marker in fromMarkers
+    for marker in markers
       marker.setMap null

     bounds = new google.maps.LatLngBounds()
@@ -52,7 +51,7 @@
         title: place.name
         position: place.geometry.location)

-      fromMarkers.push marker
+      markers.push marker
       bounds.extend place.geometry.location

     map.fitBounds bounds
@@ -61,46 +60,6 @@
   # current map's viewport.
   google.maps.event.addListener map, 'bounds_changed', ->
     bounds = map.getBounds()
-    fromSearchBox.setBounds bounds
-
-
-  # Listen for the event fired when the user selects an item from the
-  # search pick list. Retrieve the matching places for that item.
-  google.maps.event.addListener toSearchBox, 'places_changed', ->
-    places = toSearchBox.getPlaces()
-
-    if places.length == 0
-      return
-
-    for marker in toMarkers
-      marker.setMap null
-
-    bounds = new google.maps.LatLngBounds()
-
-    for place in places
-      image =
-        url: place.icon
-        size: new google.maps.Size(100, 100)
-        origin: new google.maps.Point(0, 0)
-        anchor: new google.maps.Point(17, 34)
-        scaledSize: new google.maps.Size(25, 25)
-
-      # Create a marker for each place.
-      marker = new google.maps.Marker(
-        map: map
-        icon: image
-        title: place.name
-        position: place.geometry.location)
-
-      toMarkers.push marker
-      bounds.extend place.geometry.location
-
-    map.fitBounds bounds
-
-  # Bias the SearchBox results towards places that are within the bounds of the
-  # current map's viewport.
-  google.maps.event.addListener map, 'bounds_changed', ->
-    bounds = map.getBounds()
-    toSearchBox.setBounds bounds
+    searchInput.setBounds bounds

 google.maps.event.addDomListener(window, 'load', initialize)


More test cases for inverse trigonometric and inverse hyperbolic functions.


Dispose of WorkspaceElement subscriptions when detached

Signed-off-by: Max Brunsfeld <78036c9b69b887700d5846f26a788d53b201ffbb@gmail.com>

--- src/workspace-element.coffee
+++ src/workspace-element.coffee
@@ -23,6 +23,7 @@
     @focus()

   detachedCallback: ->
+    @subscriptions.dispose()
     @model.destroy()

   initializeGlobalTextEditorStyleSheet: ->


Minor Background refactor with de-structuring.

--- src/background.coffee
+++ src/background.coffee
@@ -34,9 +34,10 @@
       @_initialize game

     _initialize: (game) ->
-      @group = game.add.group()
+      {@add, @camera} = game

-      @camera = game.camera
+      @group = @add.group()
+      @group.visible = no

       @_initDebugging()

@@ -56,7 +57,7 @@
       @_topZIndex = topZIndex
       for zIndex in [bottomZIndex..topZIndex]
         name = nameTemplate { zIndex }
-        image = @group.game.add.image 0, 0, name, @group
+        image = @add.image 0, 0, name, @group
         @layers.push { image, zIndex }

     layout: ->


Stops search bar animation if it's clicked lots

--- app/assets/javascripts/modules/search/bar.js.coffee
+++ app/assets/javascripts/modules/search/bar.js.coffee
@@ -10,6 +10,7 @@

     addEventListeners: ->
       @$triggerEl.click( (ev) =>
+        @$el.stop()
         @$el.slideToggle()
         @$triggerEl.toggleClass('opened')
         @options.relatedEls?.forEach(($el) -> $el?.toggleClass('opened'))


Same for adding "not-started" class

Same for adding "not-started" class

--- app/assets/javascripts/player/player_view.js.coffee
+++ app/assets/javascripts/player/player_view.js.coffee
@@ -3,6 +3,8 @@
     'click .start-prompt': 'onStartPromptClick'

   initialize: (options) ->
+    @$el.addClass('not-started')
+
     @rendererView = new options.rendererClass(
       cols:  this.options.cols
       lines: this.options.lines
@@ -25,7 +27,6 @@
     @showLoadingIndicator()

   appendChildViews: ->
-    @$el.addClass('not-started')
     @$el.append(@rendererView.$el)
     @rendererView.afterInsertedToDom()
     @$el.append(@hudView.$el)


correct a pair of issues in the method examples on outcome creation ui

fixes CNVS-19447

patch corrects math error on decaying_average example and adds the
mastery score being used in the n_mastery example in outcome creation ui

Change-Id: Ie7158906af69a563a23e98c43a4680c478a66129
Reviewed-on: https://gerrit.instructure.com/50787
Reviewed-by: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>
Product-Review: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>
QA-Review: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>
Tested-by: Jenkins

--- app/coffeescripts/views/outcomes/OutcomeView.coffee
+++ app/coffeescripts/views/outcomes/OutcomeView.coffee
@@ -114,8 +114,8 @@
         calcIntLabel: I18n.t("Last Item: ")
         calcIntRangeInfo: I18n.t('Between 1% and 99%')
         calcIntExample: I18n.t("Last item is 75% of mastery.  Average of 'the rest' is 25% of mastery")
-        calcIntExampleLine1: I18n.t("1 - Item scores: 2, 4, 2, 5")
-        calcIntExampleLine2: I18n.t("2 - 'The rest' item average: (2 + 4 + 2) / 3 = 3")
+        calcIntExampleLine1: I18n.t("1 - Item scores: 4, 3, 2, 5")
+        calcIntExampleLine2: I18n.t("2 - 'The rest' item average: (4 + 3 + 2) / 3 = 3")
         calcIntExampleLine3: I18n.t("3 - Calculated mastery score: 5(0.75) + 3(0.25) = 4.5")
       },
       'n_mastery' : {
@@ -124,7 +124,7 @@
         calcIntLabel: I18n.t('Items: ')
         calcIntRangeInfo: I18n.t('Between 2 and 5')
         calcIntExample: I18n.t("Must achieve mastery at least 2 times.  Must also complete 2 items for calculation. Scores above mastery will be averaged to calculate final score.")
-        calcIntExampleLine1: I18n.t("1- Item Scores: 1, 3, 2, 4, 5, 3, 6")
+        calcIntExampleLine1: I18n.t("1- Item Scores: 1, 3, 2, 4, 5, 3, 6. Mastery score set at 5.")
         calcIntExampleLine2: I18n.t("2- Final score: 5.5")
         calcIntExampleLine3: ""
       },