More tests.

More tests.

--- tests.coffee
+++ tests.coffee
@@ -11639,4 +11639,1992 @@
         ]
         body: 'FooBar5a'
       ]
-]+
+      # Add same which already exist
+      Post.documents.update @postId2,
+        $push:
+          nested:
+            required:
+              _id: @person3Id
+            body: 'NestedFooBarNew'
+      ,
+        expect (error, res) =>
+          test.isFalse error, error?.toString?() or error
+          test.isTrue res
+
+      # Sleep so that observers have time to update documents
+      Meteor.setTimeout expect(), WAIT_TIME
+,
+  (test, expect) ->
+    @post2 = Post.documents.findOne @postId2,
+      transform: null # So that we can use test.equal
+
+    test.equal @post2,
+      _id: @postId2
+      _schema: '1.0.0'
+      author:
+        _id: @person1Id
+        username: 'person1'
+        displayName: 'Person 1'
+      subdocument:
+        person:
+          _id: @person2Id
+          displayName: 'Person 2'
+          username: 'person2'
+        persons: [
+          _id: @person2Id
+          displayName: 'Person 2'
+          username: 'person2'
+        ,
+          _id: @person2Id
+          displayName: 'Person 2'
+          username: 'person2'
+        ,
+          _id: @person2Id
+          displayName: 'Person 2'
+          username: 'person2'
+        ,
+          _id: @person1Id
+          displayName: 'Person 1'
+          username: 'person1'
+        ,
+          _id: @person2Id
+          displayName: 'Person 2'
+          username: 'person2'
+        ,
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        ]
+        slug: 'subdocument-prefix-foobar2a-subdocumentfoobar2a-suffix'
+        body: 'SubdocumentFooBar2a'
+      nested: [
+        required:
+          _id: @person3Id
+          username: 'person3'
+          displayName: 'Person 3'
+        slug: 'nested-prefix-foobar2a-nestedfoobar2a-suffix'
+        body: 'NestedFooBar2a'
+      ,
+        required:
+          _id: @person3Id
+          username: 'person3'
+          displayName: 'Person 3'
+        slug: 'nested-prefix-foobar2a-nestedfoobarnew-suffix'
+        body: 'NestedFooBarNew'
+      ]
+      body: 'FooBar2a'
+      slug: 'prefix-foobar2a-subdocumentfoobar2a-suffix'
+      tags: [
+        'tag-0-prefix-foobar2a-subdocumentfoobar2a-suffix'
+        'tag-1-prefix-foobar2a-nestedfoobar2a-suffix'
+        'tag-2-prefix-foobar2a-nestedfoobarnew-suffix'
+      ]
+
+    @person1 = Person.documents.findOne @person1Id,
+      transform: null # So that we can use test.equal
+    @person2 = Person.documents.findOne @person2Id,
+      transform: null # So that we can use test.equal
+    @person3 = Person.documents.findOne @person3Id,
+      transform: null # So that we can use test.equal
+
+    test.equal _.omit(@person1, 'posts', 'subdocumentPosts', 'subdocumentsPosts', 'nestedPosts'),
+      _id: @person1Id
+      _schema: '1.0.0'
+      username: 'person1'
+      displayName: 'Person 1'
+      count: 13
+
+    testSetEqual test, @person1.posts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ,
+          body: 'NestedFooBarNew'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person1.subdocumentPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+    testSetEqual test, @person1.subdocumentsPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ,
+          body: 'NestedFooBarNew'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+    testSetEqual test, @person1.nestedPosts,
+      [
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+
+    test.equal _.omit(@person2, 'posts', 'subdocumentPosts', 'subdocumentsPosts', 'nestedPosts'),
+      _id: @person2Id
+      _schema: '1.0.0'
+      username: 'person2'
+      displayName: 'Person 2'
+      count: 8
+
+    testSetEqual test, @person2.posts, []
+    testSetEqual test, @person2.subdocumentPosts,
+      [
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ,
+          body: 'NestedFooBarNew'
+        ]
+        body: 'FooBar2a'
+      ]
+    testSetEqual test, @person2.subdocumentsPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ,
+          body: 'NestedFooBarNew'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person2.nestedPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+
+    test.equal _.omit(@person3, 'posts', 'subdocumentPosts', 'subdocumentsPosts', 'nestedPosts'),
+      _id: @person3Id
+      _schema: '1.0.0'
+      username: 'person3'
+      displayName: 'Person 3'
+      count: 9
+
+    testSetEqual test, @person3.posts, []
+    testSetEqual test, @person3.subdocumentPosts,
+      [
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person3.subdocumentsPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ,
+          body: 'NestedFooBarNew'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person3.nestedPosts,
+      [
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ,
+          body: 'NestedFooBarNew'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+
+      Post.documents.update @postId2,
+        $pop:
+          nested: 1
+      ,
+        expect (error, res) =>
+          test.isFalse error, error?.toString?() or error
+          test.isTrue res
+
+      # Sleep so that observers have time to update documents
+      Meteor.setTimeout expect(), WAIT_TIME
+,
+  (test, expect) ->
+    @post2 = Post.documents.findOne @postId2,
+      transform: null # So that we can use test.equal
+
+    test.equal @post2,
+      _id: @postId2
+      _schema: '1.0.0'
+      author:
+        _id: @person1Id
+        username: 'person1'
+        displayName: 'Person 1'
+      subdocument:
+        person:
+          _id: @person2Id
+          displayName: 'Person 2'
+          username: 'person2'
+        persons: [
+          _id: @person2Id
+          displayName: 'Person 2'
+          username: 'person2'
+        ,
+          _id: @person2Id
+          displayName: 'Person 2'
+          username: 'person2'
+        ,
+          _id: @person2Id
+          displayName: 'Person 2'
+          username: 'person2'
+        ,
+          _id: @person1Id
+          displayName: 'Person 1'
+          username: 'person1'
+        ,
+          _id: @person2Id
+          displayName: 'Person 2'
+          username: 'person2'
+        ,
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        ]
+        slug: 'subdocument-prefix-foobar2a-subdocumentfoobar2a-suffix'
+        body: 'SubdocumentFooBar2a'
+      nested: [
+        required:
+          _id: @person3Id
+          username: 'person3'
+          displayName: 'Person 3'
+        slug: 'nested-prefix-foobar2a-nestedfoobar2a-suffix'
+        body: 'NestedFooBar2a'
+      ]
+      body: 'FooBar2a'
+      slug: 'prefix-foobar2a-subdocumentfoobar2a-suffix'
+      tags: [
+        'tag-0-prefix-foobar2a-subdocumentfoobar2a-suffix'
+        'tag-1-prefix-foobar2a-nestedfoobar2a-suffix'
+      ]
+
+    @person1 = Person.documents.findOne @person1Id,
+      transform: null # So that we can use test.equal
+    @person2 = Person.documents.findOne @person2Id,
+      transform: null # So that we can use test.equal
+    @person3 = Person.documents.findOne @person3Id,
+      transform: null # So that we can use test.equal
+
+    test.equal _.omit(@person1, 'posts', 'subdocumentPosts', 'subdocumentsPosts', 'nestedPosts'),
+      _id: @person1Id
+      _schema: '1.0.0'
+      username: 'person1'
+      displayName: 'Person 1'
+      count: 13
+
+    testSetEqual test, @person1.posts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person1.subdocumentPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+    testSetEqual test, @person1.subdocumentsPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+    testSetEqual test, @person1.nestedPosts,
+      [
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+
+    test.equal _.omit(@person2, 'posts', 'subdocumentPosts', 'subdocumentsPosts', 'nestedPosts'),
+      _id: @person2Id
+      _schema: '1.0.0'
+      username: 'person2'
+      displayName: 'Person 2'
+      count: 8
+
+    testSetEqual test, @person2.posts, []
+    testSetEqual test, @person2.subdocumentPosts,
+      [
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ]
+    testSetEqual test, @person2.subdocumentsPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person2.nestedPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+
+    test.equal _.omit(@person3, 'posts', 'subdocumentPosts', 'subdocumentsPosts', 'nestedPosts'),
+      _id: @person3Id
+      _schema: '1.0.0'
+      username: 'person3'
+      displayName: 'Person 3'
+      count: 9
+
+    testSetEqual test, @person3.posts, []
+    testSetEqual test, @person3.subdocumentPosts,
+      [
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person3.subdocumentsPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person3.nestedPosts,
+      [
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+
+      Post.documents.update @postId5,
+        $set:
+          'nested.0.required._id': @person2Id
+      ,
+        expect (error, res) =>
+          test.isFalse error, error?.toString?() or error
+          test.isTrue res
+
+      # Sleep so that observers have time to update documents
+      Meteor.setTimeout expect(), WAIT_TIME
+,
+  (test, expect) ->
+    @post5 = Post.documents.findOne @postId5,
+      transform: null # So that we can use test.equal
+
+    test.equal @post5,
+      _id: @postId5
+      _schema: '1.0.0'
+      author:
+        _id: @person1Id
+        username: 'person1'
+        displayName: 'Person 1'
+      subdocument:
+        person:
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        persons: [
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        ,
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        ,
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        ,
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        ,
+          _id: @person2Id
+          displayName: 'Person 2'
+          username: 'person2'
+        ,
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        ]
+        slug: 'subdocument-prefix-foobar5a-subdocumentfoobar5a-suffix'
+        body: 'SubdocumentFooBar5a'
+      nested: [
+        required:
+          _id: @person2Id
+          username: 'person2'
+          displayName: 'Person 2'
+        slug: 'nested-prefix-foobar5a-nestedfoobar5-suffix'
+        body: 'NestedFooBar5'
+      ,
+        required:
+          _id: @person3Id
+          username: 'person3'
+          displayName: 'Person 3'
+        slug: 'nested-prefix-foobar5a-nestedfoobar5a-suffix'
+        body: 'NestedFooBar5a'
+      ,
+        required:
+          _id: @person3Id
+          username: 'person3'
+          displayName: 'Person 3'
+        slug: 'nested-prefix-foobar5a-nestedfoobar5-suffix'
+        body: 'NestedFooBar5'
+      ]
+      body: 'FooBar5a'
+      slug: 'prefix-foobar5a-subdocumentfoobar5a-suffix'
+      tags: [
+        'tag-0-prefix-foobar5a-subdocumentfoobar5a-suffix'
+        'tag-1-prefix-foobar5a-nestedfoobar5-suffix'
+        'tag-2-prefix-foobar5a-nestedfoobar5a-suffix'
+        'tag-3-prefix-foobar5a-nestedfoobar5-suffix'
+      ]
+
+    @person1 = Person.documents.findOne @person1Id,
+      transform: null # So that we can use test.equal
+    @person2 = Person.documents.findOne @person2Id,
+      transform: null # So that we can use test.equal
+    @person3 = Person.documents.findOne @person3Id,
+      transform: null # So that we can use test.equal
+
+    test.equal _.omit(@person1, 'posts', 'subdocumentPosts', 'subdocumentsPosts', 'nestedPosts'),
+      _id: @person1Id
+      _schema: '1.0.0'
+      username: 'person1'
+      displayName: 'Person 1'
+      count: 13
+
+    testSetEqual test, @person1.posts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person1.subdocumentPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+    testSetEqual test, @person1.subdocumentsPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+    testSetEqual test, @person1.nestedPosts,
+      [
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+
+    test.equal _.omit(@person2, 'posts', 'subdocumentPosts', 'subdocumentsPosts', 'nestedPosts'),
+      _id: @person2Id
+      _schema: '1.0.0'
+      username: 'person2'
+      displayName: 'Person 2'
+      count: 9
+
+    testSetEqual test, @person2.posts, []
+    testSetEqual test, @person2.subdocumentPosts,
+      [
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ]
+    testSetEqual test, @person2.subdocumentsPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person2.nestedPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+
+    test.equal _.omit(@person3, 'posts', 'subdocumentPosts', 'subdocumentsPosts', 'nestedPosts'),
+      _id: @person3Id
+      _schema: '1.0.0'
+      username: 'person3'
+      displayName: 'Person 3'
+      count: 9
+
+    testSetEqual test, @person3.posts, []
+    testSetEqual test, @person3.subdocumentPosts,
+      [
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person3.subdocumentsPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person3.nestedPosts,
+      [
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+
+      Post.documents.update @postId5,
+        $set:
+          'nested.0.required._id': @person3Id
+      ,
+        expect (error, res) =>
+          test.isFalse error, error?.toString?() or error
+          test.isTrue res
+
+      # Sleep so that observers have time to update documents
+      Meteor.setTimeout expect(), WAIT_TIME
+,
+  (test, expect) ->
+    @post5 = Post.documents.findOne @postId5,
+      transform: null # So that we can use test.equal
+
+    test.equal @post5,
+      _id: @postId5
+      _schema: '1.0.0'
+      author:
+        _id: @person1Id
+        username: 'person1'
+        displayName: 'Person 1'
+      subdocument:
+        person:
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        persons: [
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        ,
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        ,
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        ,
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        ,
+          _id: @person2Id
+          displayName: 'Person 2'
+          username: 'person2'
+        ,
+          _id: @person3Id
+          displayName: 'Person 3'
+          username: 'person3'
+        ]
+        slug: 'subdocument-prefix-foobar5a-subdocumentfoobar5a-suffix'
+        body: 'SubdocumentFooBar5a'
+      nested: [
+        required:
+          _id: @person3Id
+          username: 'person3'
+          displayName: 'Person 3'
+        slug: 'nested-prefix-foobar5a-nestedfoobar5-suffix'
+        body: 'NestedFooBar5'
+      ,
+        required:
+          _id: @person3Id
+          username: 'person3'
+          displayName: 'Person 3'
+        slug: 'nested-prefix-foobar5a-nestedfoobar5a-suffix'
+        body: 'NestedFooBar5a'
+      ,
+        required:
+          _id: @person3Id
+          username: 'person3'
+          displayName: 'Person 3'
+        slug: 'nested-prefix-foobar5a-nestedfoobar5-suffix'
+        body: 'NestedFooBar5'
+      ]
+      body: 'FooBar5a'
+      slug: 'prefix-foobar5a-subdocumentfoobar5a-suffix'
+      tags: [
+        'tag-0-prefix-foobar5a-subdocumentfoobar5a-suffix'
+        'tag-1-prefix-foobar5a-nestedfoobar5-suffix'
+        'tag-2-prefix-foobar5a-nestedfoobar5a-suffix'
+        'tag-3-prefix-foobar5a-nestedfoobar5-suffix'
+      ]
+
+    @person1 = Person.documents.findOne @person1Id,
+      transform: null # So that we can use test.equal
+    @person2 = Person.documents.findOne @person2Id,
+      transform: null # So that we can use test.equal
+    @person3 = Person.documents.findOne @person3Id,
+      transform: null # So that we can use test.equal
+
+    test.equal _.omit(@person1, 'posts', 'subdocumentPosts', 'subdocumentsPosts', 'nestedPosts'),
+      _id: @person1Id
+      _schema: '1.0.0'
+      username: 'person1'
+      displayName: 'Person 1'
+      count: 13
+
+    testSetEqual test, @person1.posts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person1.subdocumentPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+    testSetEqual test, @person1.subdocumentsPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+    testSetEqual test, @person1.nestedPosts,
+      [
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+
+    test.equal _.omit(@person2, 'posts', 'subdocumentPosts', 'subdocumentsPosts', 'nestedPosts'),
+      _id: @person2Id
+      _schema: '1.0.0'
+      username: 'person2'
+      displayName: 'Person 2'
+      count: 8
+
+    testSetEqual test, @person2.posts, []
+    testSetEqual test, @person2.subdocumentPosts,
+      [
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ]
+    testSetEqual test, @person2.subdocumentsPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person2.nestedPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ]
+
+    test.equal _.omit(@person3, 'posts', 'subdocumentPosts', 'subdocumentsPosts', 'nestedPosts'),
+      _id: @person3Id
+      _schema: '1.0.0'
+      username: 'person3'
+      displayName: 'Person 3'
+      count: 9
+
+    testSetEqual test, @person3.posts, []
+    testSetEqual test, @person3.subdocumentPosts,
+      [
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person3.subdocumentsPosts,
+      [
+        _id: @postId1
+        subdocument:
+          body: 'SubdocumentFooBar1a'
+        nested: [
+          body: 'NestedFooBar1a'
+        ]
+        body: 'FooBar1a'
+      ,
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+    testSetEqual test, @person3.nestedPosts,
+      [
+        _id: @postId2
+        subdocument:
+          body: 'SubdocumentFooBar2a'
+        nested: [
+          body: 'NestedFooBar2a'
+        ]
+        body: 'FooBar2a'
+      ,
+        _id: @postId3
+        subdocument:
+          body: 'SubdocumentFooBar3a'
+        nested: [
+          body: 'NestedFooBar3a'
+        ]
+        body: 'FooBar3a'
+      ,
+        _id: @postId4
+        subdocument:
+          body: 'SubdocumentFooBar4a'
+        nested: [
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4a'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ,
+          body: 'NestedFooBar4'
+        ]
+        body: 'FooBar4a'
+      ,
+        _id: @postId5
+        subdocument:
+          body: 'SubdocumentFooBar5a'
+        nested: [
+          body: 'NestedFooBar5'
+        ,
+          body: 'NestedFooBar5a'
+        ,
+          body: 'NestedFooBar5'
+        ]
+        body: 'FooBar5a'
+      ]
+]


Disabled autocomplete on date range fields

--- app/assets/javascripts/reports.coffee
+++ app/assets/javascripts/reports.coffee
@@ -7,6 +7,8 @@
     todayBtn: true
     orientation: 'auto'
     keyboardNavigation: false
+  .find('input')
+  .attr('autocomplete', 'off')

   # Check and update download status
   $reportsStatus = $('.reports.status')


Tmp fix so visualisations don't change between renders

--- client/src/views/visualisation_view.coffee
+++ client/src/views/visualisation_view.coffee
@@ -6,7 +6,9 @@

   initialize: (options) ->
     @visualisation = options.visualisation
-    @visualisation.set('data', Backbone.Faker.Reports.createFakeData())
+    # If we haven't fixed the JSON saving bug - will be removed
+    if typeof @visualisation.get('data') is 'string'
+      @visualisation.set('data', Backbone.Faker.Reports.createFakeData())

   render: =>
     @closeSubViews()


Fix menu show/hide behavior.

Fix menu show/hide behavior.

--- app/client/js/react/pages/song.coffee
+++ app/client/js/react/pages/song.coffee
@@ -16,7 +16,7 @@
     @constructor
   ###
   constructor: (routes, store, touch) ->
-    {div,menu,h1} = React.DOM
+    {div,menu} = React.DOM
     {article} = touch.scroll 'article'
     {a} = touch.none 'a'

@@ -30,7 +30,9 @@
             dangerouslySetInnerHTML: '__html': @lyricsHtml()
             onPointerUp: @onArticlePointerUp
             ref: 'article'
-          menu ref: 'menu',
+          menu
+            ref: 'menu',
+          ,
             a
               href: routes.home.createUrl(),
             , Song.MSG_BACK
@@ -48,7 +50,11 @@
         @toggleMenu()

       toggleMenu: ->
-        goog.dom.classlist.toggle @refs['menu'].getDOMNode(), 'este-hidden'
+        menuEl = @refs['menu'].getDOMNode()
+        isHidden = goog.dom.classlist.contains menuEl, 'este-hidden'
+        goog.dom.classlist.enable menuEl, 'este-hidden', !isHidden
+        clearTimeout @hideMenuTimer
+        @hideMenuAfterWhile() if isHidden

       componentDidMount: ->
         @setLyricsMaxFontSize()
@@ -66,7 +72,11 @@

       hideMenuAfterWhile: ->
         clearTimeout @hideMenuTimer
-        @hideMenuTimer = setTimeout @toggleMenu, Song.HIDE_MENU_DELAY
+        @hideMenuTimer = setTimeout @hideMenu, Song.HIDE_MENU_DELAY
+
+      hideMenu: ->
+        menuEl = @refs['menu'].getDOMNode()
+        goog.dom.classlist.add menuEl, 'este-hidden'

       componentDidUpdate: ->
         # TODO(steida): Implement fontSize increase and decrease.


Tests: Collaborion: Start: fixes for PR notes.

--- client/test/lib/collaboration/start.coffee
+++ client/test/lib/collaboration/start.coffee
@@ -188,7 +188,7 @@
     browser.end()

-  openTerminalForInviteUser: (browser) ->
+  openTerminalWithInvitedUser: (browser) ->

     host         = utils.getUser no, 0
     hostBrowser  = process.env.__NIGHTWATCH_ENV_KEY is 'host_1'
@@ -202,9 +202,7 @@
     browser.elements 'css selector', terminalTabs, (result) =>
       length = result.value.length

-      if hostBrowser
-        browser.pause 10000
-
+      if hostBrowser then browser.pause 10000
       else
         terminalHelpers.openNewTerminalMenu(browser)
         terminalHelpers.openTerminal(browser)


confirmation dialogs on warn actions

confirmation dialogs on warn actions

--- src/coffee/controllers/player/player-guild-ctrl.coffee
+++ src/coffee/controllers/player/player-guild-ctrl.coffee
@@ -1,7 +1,7 @@
 angular.module 'IdleLands'
 .controller 'PlayerGuild', [
-  '$scope', 'CurrentGuild', 'CurrentGuildInvites', 'CurrentPlayer', 'API'
-  ($scope, CurrentGuild, CurrentGuildInvites, CurrentPlayer, API) ->
+  '$scope', '$mdDialog', 'CurrentGuild', 'CurrentGuildInvites', 'CurrentPlayer', 'API'
+  ($scope, $mdDialog, CurrentGuild, CurrentGuildInvites, CurrentPlayer, API) ->

     $scope.initialize = ->
       $scope.guild = CurrentGuild.getGuild()
@@ -147,10 +147,24 @@
       API.guild.buff {type: type, tier: $scope.editable.buffLevel}

     $scope.kickMember = (name) ->
-      API.guild.kick {memberName: name}
+      confirm = $mdDialog.confirm()
+      .title 'Kick Member'
+      .content "Are you sure you want to kick #{name}?"
+      .ok 'Yes'
+      .cancel 'No'
+
+      $mdDialog.show(confirm).then ->
+        API.guild.kick {memberName: name}

     $scope.rescindInvite = (invIdent) ->
-      API.guild.rescind {invIdent: invIdent}
+      confirm = $mdDialog.confirm()
+      .title 'Rescind Invite'
+      .content "Are you sure you want to take an invite away from #{invIdent}?"
+      .ok 'Yes'
+      .cancel 'No'
+
+      $mdDialog.show(confirm).then ->
+        API.guild.rescind {invIdent: invIdent}

     $scope.promoteMember = (name) ->
       API.guild.promote {memberName: name}
@@ -165,10 +179,24 @@
       yes

     $scope.leaveGuild = ->
-      API.guild.leave()
+      confirm = $mdDialog.confirm()
+      .title 'Leave Guild'
+      .content 'Are you sure you want to leave your guild?'
+      .ok 'Yes'
+      .cancel 'No'
+
+      $mdDialog.show(confirm).then ->
+        API.guild.leave()

     $scope.disbandGuild = ->
-      API.guild.disband()
+      confirm = $mdDialog.confirm()
+      .title 'Disband Guild'
+      .content 'Are you sure you want to disband your guild?'
+      .ok 'Yes'
+      .cancel 'No'
+
+      $mdDialog.show(confirm).then ->
+        API.guild.disband()

     $scope.donateGold = (gold) ->
       API.guild.donate {gold: gold}


Work around hubot-slack bug that prevents hubot from starting a new conversation with customMessage: see issue: https://github.com/slackhq/hubot-slack/issues/256

--- src/adapters/slack.coffee
+++ src/adapters/slack.coffee
@@ -29,12 +29,21 @@
         @onReactionRemoved msg

   send: (context, message) ->
-    payload = channel: context.message.room
+    payload = channel: context.message.room + "fail"
     if _(message).isString()
       payload.text = message
     else
       payload = _(payload).extend message
-    @robot.adapter.customMessage payload
+    rc = @robot.adapter.customMessage payload
+
+    # Could not find existing conversation
+    # so client bails on sending see issue: https://github.com/slackhq/hubot-slack/issues/256
+    # detect the bail by checking the response and attempt
+    # a fallback using adapter.send
+    if rc is undefined
+      text = payload.text
+      text += "\n#{a.fallback}" for a in payload.attachments
+      @robot.adapter.send context.message, text

   onJiraTicketCreationMessage: (msg) =>
     reactions = ["point_up_2", "point_down", "eyes", "raising_hand", "soon", "fast_forward"]


Add class documentation to Keymap

Add class documentation to Keymap

--- src/keymap.coffee
+++ src/keymap.coffee
@@ -10,6 +10,56 @@
 Platforms = ['darwin', 'freebsd', 'linux', 'sunos', 'win32']
 OtherPlatforms = Platforms.filter (platform) -> platform isnt process.platform

+# Public: Allows commands to be associated with keystrokes in a
+# context-sensitive way. In Atom, you can access a global instance of this
+# object via `atom.keymap`.
+#
+# Key bindings are plain JavaScript objects containing **CSS selectors** as
+# their top level keys, then **keystroke patterns** mapped to commands.
+#
+# ```cson
+# '.workspace':
+#   'ctrl-l': 'package:do-something'
+#   'ctrl-z': 'package:do-something-else'
+# '.mini.editor':
+#   'enter': 'core:confirm'
+# ```
+#
+# When a keystroke sequence matches a binding in a given context, a custom DOM
+# event with a type based on the command is dispatched on the target of the
+# keyboard event.
+#
+# To match a keystroke sequence, the keymap starts at the target element for the
+# keyboard event. It looks for key bindings associated with selectors that match
+# the target element. If multiple match, the most specific is selected. If there
+# is a tie in specificity, the most recently added binding wins. If no bindings
+# are found for the events target, the search is repeated again for the target's
+# parent node and so on recursively until a binding is found or we traverse off
+# the top of the document.
+#
+# When a binding is found, its command event is always dispatched on the
+# original target of the keyboard event, even if the matching element is higher
+# up in the DOM. In addition, `.preventDefault()` is called on the keyboard
+# event to prevent the browser from taking action. `.preventDefault` is only
+# called if a matching binding is found.
+#
+# Command event objects have a non-standard method called `.abortKeyBinding()`.
+# If your command handler is invoked but you programmatically determine that no
+# action can be taken and you want to allow other bindings to be matched, call
+# `.abortKeyBinding()` on the event object. An example of where this is useful
+# is binding snippet expansion to `tab`. If `snippets:expand` is invoked when
+# the cursor does not follow a valid snippet prefix, we abort the binding and
+# allow `tab` to be handled by the default handler, which inserts whitespace.
+#
+# Multi-keystroke bindings are possible. If a sequence of one or more keystrokes
+# *partially* matches a multi-keystroke binding, the keymap enters a pending
+# state. The pending state is terminated on the next keystroke, or after
+# {::partialMatchTimeout} milliseconds has elapsed. When the pending state is
+# terminated via a timeout or a keystroke that leads to no matches, the longest
+# ambiguous bindings that caused the pending state are temporarily disabled and
+# the previous keystrokes are replayed. If there is ambiguity again during the
+# replay, the next longest bindings are disabled and the keystrokes are replayed
+# again.
 module.exports =
 class Keymap
   Emitter.includeInto(this)


fix(threads): Show the archive button in the bulk action bar

--- internal_packages/thread-list/lib/thread-buttons.cjsx
+++ internal_packages/thread-list/lib/thread-buttons.cjsx
@@ -17,6 +17,7 @@
     selection: React.PropTypes.object.isRequired

   render: ->
+    mailViewFilter = FocusedMailViewStore.mailView()
     return false unless mailViewFilter?.canArchiveThreads()

     <button style={order:-107}


activity feed: move activity type filters to outside method

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -15,6 +15,17 @@
     'Everything'
   ]

+  newActivitiesArrivedTypes = [
+    'CStatusActivity'
+    'CCodeSnipActivity'
+    'CFollowerBucketActivity'
+    'CNewMemberBucketActivity'
+    'CDiscussionActivity'
+    'CTutorialActivity'
+    'CInstallerBucketActivity'
+    'CBlogPostActivity'
+  ]
+
   @clearQuotes = clearQuotes = (activities)->
     return activities = for activityId, activity of activities
       activity.snapshot = activity.snapshot?.replace /"/g, '\"'
@@ -148,18 +159,7 @@
       CActivity.fetchFolloweeContents options, callback

   activitiesArrived:(activities)->
-    filter = [
-      'CStatusActivity'
-      'CCodeSnipActivity'
-      'CFollowerBucketActivity'
-      'CNewMemberBucketActivity'
-      'CDiscussionActivity'
-      'CTutorialActivity'
-      'CInstallerBucketActivity'
-      'CBlogPostActivity'
-    ]
-
-    for activity in activities when activity.bongo_.constructorName in filter
+    for activity in activities when activity.bongo_.constructorName in newActivitiesArrivedTypes
       @listController?.newActivityArrived activity

   teasersLoaded:->


remove console.log

remove console.log

--- components/article/client/image_set.coffee
+++ components/article/client/image_set.coffee
@@ -18,7 +18,6 @@
     @followArtists = new FollowArtists []
     @render()
     @setupFollowButtons()
-    console.log('setupFollowButtons')

   render: ->
     $('body').prepend template


act cache wip

act cache wip

--- workers/social/lib/social/models/activity/cache.coffee
+++ workers/social/lib/social/models/activity/cache.coffee
@@ -336,6 +336,66 @@
         $set    : activitiesModifier
       }, (err)-> callback?()

+    # JActivityCache.fetchOverviewTeasers overview, (err, activityHash)=>
+
+    #   overview.reverse()
+
+    #   newActivitiesHasNewMemberBucket   = no
+    #   currentOverviewHasNewMemberBucket = no
+
+    #   activitiesModifier = Object.keys(activityHash).reduce (acc, activityId)->
+    #     activity = activityHash[activityId]
+    #     updatedActivity = activity.prune()
+    #     updatedActivity.snapshotIds = [].slice.call activity.snapshotIds
+    #     acc["activities.#{activity.getId()}"] = updatedActivity
+    #     if updatedActivity.type is "CNewMemberBucketActivity"
+    #       newActivitiesHasNewMemberBucket = yes
+    #     return acc
+    #   , {}
+
+    #   newMemberBucketIndex = null
+
+    #   if newActivitiesHasNewMemberBucket
+    #     @overview.forEach (overviewItem, index)->
+    #       if overviewItem.type is "CNewMemberBucketActivity"
+    #         currentOverviewHasNewMemberBucket = yes
+    #         newMemberBucketIndex              = index
+
+
+
+    #   if currentOverviewHasNewMemberBucket and newActivitiesHasNewMemberBucket
+    #     @overview.forEach (item)->
+    #       log item
+    #     # freshOverview = overview.first
+
+    #     # if freshOverview.type is "CNewMemberBucketActivity"
+    #     #   @overview.forEach ()
+
+    #     #   updatedOverview = @overview
+    #     #   updatedOverview.ids.push freshOverview.ids.first
+    #     #   updatedOverview.createdAt.push freshOverview.createdAt.first
+    #     #   updatedOverview.count++
+
+    #     #   activitiesModifier.to = freshOverview.createdAt.first
+
+    #     #   log updatedOverview
+
+    #     #   # @update {
+    #     #   #   $pushAll: {overview}
+    #     #   #   $set    : activitiesModifier
+    #     #   # }, (err)-> callback?()
+
+    #       return
+
+    #   activitiesModifier.to = overview[overview.length-1].createdAt[overview[overview.length-1].createdAt.length-1]
+
+    #   @update {
+    #     $pushAll: {overview}
+    #     $set    : activitiesModifier
+    #   }, (err)-> callback?()
+
+    #   # log overview, @overview
+

   @modifyByTeaser = (teaser, callback)->


Remove jasmine.js lines from displayed stack trace

This removes the noise from the setup and compare lines that occur in
jasmine.js and only displays the lines that are generated from within
the failing spec.

--- spec/atom-reporter.coffee
+++ spec/atom-reporter.coffee
@@ -166,12 +166,25 @@
     @description.html @spec.description

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

   attach: ->
     @parentSuiteView().append this
+
+  formatStackTrace: (stackTrace) ->
+    return stackTrace unless stackTrace
+
+    jasminePath = require.resolve('jasmine')
+    jasminePattern = new RegExp("\\(#{_.escapeRegExp(jasminePath)}:\\d+:\\d+\\)\\s*$")
+    convertedLines = []
+    for line in stackTrace.split('\n')
+      unless jasminePattern.test(line)
+        convertedLines.push(line)
+
+    convertedLines.join('\n')

   parentSuiteView: ->
     if not suiteView = $(".suite-view-#{@spec.suite.id}").view()


Fixed a bug in Elyssa.Storage where item was undefined

--- src/pipeline/storage.coffee
+++ src/pipeline/storage.coffee
@@ -11,6 +11,8 @@
     load = ->

     save = ->
+
+    item = (key, value) ->

     {toString, item, load, save, clear}


Conditionally remove HZ line in Artwork auctions

--- desktop/apps/artwork/client/index.coffee
+++ desktop/apps/artwork/client/index.coffee
@@ -34,34 +34,35 @@
   setup: setup = (context = {}) ->
     if context.__typename is 'ArtworkContextAuction'
       query: """
-          query artwork(
-            $id: String!,
-            $isClosed: Boolean!,
-            $auctionId: ID,
-            $saleSize: Int = 4,
-            $saleSort: SaleSorts = TIMELY_AT_NAME_ASC
-          ) {
-            artwork(id: $id) {
-              ... partner
-              ... auction_artworks @skip(if: $isClosed)
-              ... artist_artworks @include(if: $isClosed)
-              ... related_artworks @include(if: $isClosed)
-            }
+        query artwork(
+          $id: String!,
+          $isClosed: Boolean!,
+          $auctionId: ID,
+          $saleSize: Int = 4,
+          $saleSort: SaleSorts = TIMELY_AT_NAME_ASC
+        ) {
+          artwork(id: $id) {
+            ... partner
+            ... auction_artworks @skip(if: $isClosed)
+            ... artist_artworks @include(if: $isClosed)
+            ... related_artworks @include(if: $isClosed)
+          }

-            ... followed_artist_ids @skip(if: $isClosed)
+          ... followed_artist_ids @skip(if: $isClosed)

-            sales(size: $saleSize, sort: $saleSort) {
-              ... current_auctions
-            }
+          sales(size: $saleSize, sort: $saleSort) {
+            ... current_auctions
           }
-          #{require '../../../components/artwork_brick/query.coffee'}
-          #{require '../components/partner/query.coffee'}
-          #{require('../components/auction_artworks/query.coffee').auction_artworks}
-          #{require('../components/auction_artworks/query.coffee').followed_artist_ids(CurrentUser.orNull())}
-          #{require('../../../components/current_auctions/query.js').default}
-          #{require '../components/artist_artworks/query.coffee'}
-          #{require '../components/related_artworks/query.coffee'}
-        """
+        }
+        #{require '../../../components/artwork_brick/query.coffee'}
+        #{require '../components/partner/query.coffee'}
+        #{require('../components/auction_artworks/query.coffee').auction_artworks}
+        #{require('../components/auction_artworks/query.coffee').followed_artist_ids(CurrentUser.orNull())}
+        #{require('../../../components/current_auctions/query.js').default}
+        #{require '../components/artist_artworks/query.coffee'}
+        #{require '../components/related_artworks/query.coffee'}
+      """
+
       variables:
         isClosed: context.is_closed
         auctionId: sd.AUCTION.id
@@ -72,8 +73,19 @@
           require('../../../components/current_auctions/index.jsx').default unless context.is_closed
           require '../components/artist_artworks/index.coffee' if context.is_closed
           require '../components/related_artworks/index.coffee' if context.is_closed
-          require '../components/related_artists/index.coffee'
+          require '../components/related_artists/index.coffee',
         ]
+
+      # Make some Auctions-only layout tweaks. See https://github.com/artsy/auctions/issues/360#event-1017559326
+      # for more info.
+      postInit: ->
+        $currentAuctions = $ '.artwork-current-auctions'
+        $relatedRail = $ '.artwork-artist-related-rail'
+        paddingTop = if $currentAuctions.length then '42px' else '31px'
+
+        $relatedRail
+          .css('border-top', 0)
+          .css('padding-top', paddingTop)

     else if context.__typename is 'ArtworkContextFair'
       query: """
@@ -162,7 +174,7 @@
     exec sharedInit

     context = CLIENT.context or {}
-    { query, init, variables } = setup(context)
+    { query, init, postInit, variables } = setup(context)

     return unless query? and init?
     variables ?= {}
@@ -177,3 +189,4 @@
         })

         exec init, data
+        postInit() if postInit


:art: Tweak naming of test snippets

:art: Tweak naming of test snippets

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -165,11 +165,11 @@
                 ${2:placeholder ending second line}
             """

-          "tab stops at the ends of snippets":
+          "tab stops at beginning and then end of snippet":
             prefix: "t6b"
             body: "$1expanded$0"

-          "tab stops at the ends of snippets - alternative":
+          "tab stops at end and then beginning of snippet":
             prefix: "t6c"
             body: "$0expanded$1"


Add specs for multiple cursors

Add specs for multiple cursors

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -150,7 +150,6 @@
           expect(editor.getCursorScreenPosition()).toEqual [3, 15]

           # tab through all tab stops, then tab on last stop to terminate snippet
-          editorView.trigger keydownEvent('tab', target: editorView[0])
           editorView.trigger keydownEvent('tab', target: editorView[0])
           editorView.trigger keydownEvent('tab', target: editorView[0])
           editorView.trigger keydownEvent('tab', target: editorView[0])


pretty success message

pretty success message

--- src/steroids.coffee
+++ src/steroids.coffee
@@ -206,7 +206,7 @@

         projectCreator.generate(folder).then ->
           projectCreator.update().then ->
-            steroidsCli.log "\nSuccesfully created a new Steroids project!"
+            steroidsCli.log "\n#{chalk.bold.green('\nSuccesfully created a new Steroids project!')}"
           .catch (err) ->
             steroidsCli.log err.message
             process.exit 1


Remove Friendly iFrame from loader

Remove Friendly iFrame from loader

From now on we will insert an async script in the body of the document
in order to load the payload. Friendly iFrame is an old technique so we
decided to proceed with a more simple implementation for the loader.

--- src/loader.coffee
+++ src/loader.coffee
@@ -1,12 +1,7 @@
 ###
 Analytics Payload Loader

-This is the `analytics.js` file that loads the actual Analytics payload. It
-utilizes the Friendly iFrames (FiF) technique in order to load the JavaScript
-library without blocking the `window.onload` event.
-
-@see http://goo.gl/VLDc3F More information on FiF technique
-@see https://developer.akamai.com/blog/2012/12/12/non-blocking-script-loader-pattern
+This is the `analytics.js` file that loads the actual Analytics payload.
 ###
 ((url)->
   try
@@ -18,43 +13,16 @@
     if window["@@flavorAnalyticsLoader"].count > 1
       return console.warn("@@flavorAnalytics loaded #{window["@@flavorAnalyticsLoader"].count} times")

-  # Section 1
-  iframe = document.createElement('iframe')
-  iframe.src = "javascript:false"
-  iframe.title = ""
-  iframe.name = "frame-#{new Date().getTime()}" # https://stackoverflow.com/a/26191196/4375736
-  # iframe.role = "presentation" # a11y
-
-  (iframe.frameElement or iframe).style.cssText =
-    "position: absolute; top: 0; left: 0; width: 1px; height: 1px; opacity: 0; border: none;"
+  # Append the payload to the body of the document.
+  js = document.createElement("script")
+  js.type = "text/javascript"
+  js.async = true
+  js.src = "@@analytics_base_url/#{url}"

   if document.body
-    document.body.appendChild(iframe)
+    document.body.appendChild(js)
   else
     where = document.getElementsByTagName('script')
     where = where[where.length - 1]
-    where.parentNode.insertBefore(iframe, where)
-
-  # Section 2
-  try
-    doc = iframe.contentWindow.document
-  catch err
-    # In IE < 11 there is an issue if the main page sets document.domain, even
-    # if document.domain is set to itself (document.domain=document.domain).
-    # Solution: https://developer.akamai.com/blog/2012/12/12/non-blocking-script-loader-pattern
-    dom = document.domain
-    iframe.src = "javascript:var d=document.open();d.domain='#{dom}';void(0);"
-    doc = iframe.contentWindow.document
-
-  doc.open()._l = ->
-    js = this.createElement("script")
-
-    this.domain = dom if(dom)
-    js.id = "js-iframe-async"
-    js.src = "@@analytics_base_url/#{url}"
-    this.body.appendChild(js)
-
-  doc.write('<body onload="window.inDapIF=true; document._l();">')
-  doc.close()
+    where.parentNode.insertBefore(js, where)
 )('@@payload_hash')
-


See if tests pass when minimized

See if tests pass when minimized

--- src/atom.coffee
+++ src/atom.coffee
@@ -84,7 +84,6 @@
       browserWindow.center()

   restoreDimensions: (defaultDimensions={width: 800, height: 600})->
-    return if @getLoadSettings().exitWhenDone
     dimensions = @getWindowState().getObject('dimensions')
     dimensions = defaultDimensions unless dimensions?.width and dimensions?.height
     @setDimensions(dimensions)
@@ -189,6 +188,7 @@

   show: ->
     @getCurrentWindow().show()
+    (_.nextTick => @getCurrentWindow().minimize()) if @getLoadSettings().exitWhenDone

   hide: ->
     @getCurrentWindow().hide()


Update numeric prefix binding spec with proper triggers

--- spec/vim-mode-spec.coffee
+++ spec/vim-mode-spec.coffee
@@ -322,16 +322,16 @@
         editor.setText("12345")
         editor.setCursorScreenPosition([0,1])

-        editor.trigger keydownEvent('3')
-        editor.trigger keydownEvent('x')
+        keydown('3', element: editor[0])
+        keydown('x', element: editor[0])

         expect(editor.getText()).toBe '15'

         editor.setText("123456789abc")
         editor.setCursorScreenPosition([0,0])
-        editor.trigger keydownEvent('1')
-        editor.trigger keydownEvent('0')
-        editor.trigger keydownEvent('x')
+        keydown('1', element: editor[0])
+        keydown('0', element: editor[0])
+        keydown('x', element: editor[0])

         expect(editor.getText()).toBe 'bc'


unused; use isModalShowing in modal-helper instead

--- resources/assets/coffee/osu_common.coffee
+++ resources/assets/coffee/osu_common.coffee
@@ -67,10 +67,6 @@
     $alert.appendTo($popup).fadeIn()

-  popupShowing: ->
-    $('#overlay').is(':visible')
-
-
   presence: (string) ->
     if osu.present(string) then string else null


Finish unit tests for SubscriptionUpdater

--- services/web/test/unit/coffee/Subscription/SubscriptionUpdaterTests.coffee
+++ services/web/test/unit/coffee/Subscription/SubscriptionUpdaterTests.coffee
@@ -209,7 +209,7 @@
 		it "should call updateFeatures with the overleaf subscription if set", (done)->
 			@SubscriptionLocator.getUsersSubscription.callsArgWith(1, null)
 			@SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null, null)
-			@Modules.hooks.fire = sinon.stub().callsArgWith(2, null, 'ol_pro')
+			@Modules.hooks.fire = sinon.stub().callsArgWith(2, null, ['ol_pro'])

 			@SubscriptionUpdater._setUsersMinimumFeatures @adminUser._id, (err)=>
 				args = @UserFeaturesUpdater.updateFeatures.args[0]
@@ -278,3 +278,13 @@
 				@SubscriptionUpdater._setUsersMinimumFeatures
 					.calledWith(user_id)
 					.should.equal true
+
+	describe 'refreshSubscription', ->
+		beforeEach ->
+			@SubscriptionUpdater._setUsersMinimumFeatures = sinon.stub()
+				.callsArgWith(1, null)
+
+		it 'should call to _setUsersMinimumFeatures', ->
+			@SubscriptionUpdater.refreshSubscription(@adminUser._id, ()->)
+			@SubscriptionUpdater._setUsersMinimumFeatures.callCount.should.equal 1
+			@SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@adminUser._id).should.equal true


Make to_date test case work on Travis CI

--- _includes/test.coffee
+++ _includes/test.coffee
@@ -10,7 +10,7 @@
 )

 it('test to_date', ->
-  assert.equal(to_date('Tue Jan 31 21:01:55 EST 2017'),
+  assert.equal(to_date('Tue Jan 31 12:01:55 EST 2017'),
                'Jan 31, 2017')
 )


convert tree to code list

convert tree to code list

--- tree2.coffee
+++ tree2.coffee
@@ -336,11 +336,17 @@
     name: 'tidy',
     action: (c, t) ->
       console.log('tidy trees')
-      if c.nodes.length == 1
-        n = c.nodes[0]
+      for n in c.nodes
         c.tidy(n, (x: n.x, y: n.y))
+  },
+  {
+    name: 'convert to code table'
+    action: (c, t) ->
+      console.log('convert to code table')
+      if c.nodes.length != 1
+        console.log('only can convert a single tree')
       else
-        console.log('not attempting to tidy multiple trees')
+        c.convertToCode()
   },
   {
     name: 'delete'
@@ -355,11 +361,8 @@
     name: 'tidy',
     action: (c, t) ->
       console.log('tidy trees')
-      if c.nodes.length == 1
-        n = c.nodes[0]
+      for n in c.nodes
         c.tidy(n, (x: n.x, y: n.y))
-      else
-        console.log('not attempting to tidy multiple trees')
   },
   {
     name: 'Shannon-Fano',
@@ -709,6 +712,20 @@

     @addAnimations([new CollectionAnimation(anims, -1)])

+  convertToCode: () ->
+    codes = {}
+    walkTree = (node, base = '') ->
+      if node.child0?
+        walkTree(node.child0, base+'0')
+      if node.child1?
+        walkTree(node.child1, base+'1')
+
+      if node.label? && node.label.length > 0
+        codes[node.label] = base
+
+    walkTree(@nodes[0])
+
+    construct_queue.push((const: CodeListCollection, add_from: codes))

 class WeightedNodeCollection extends NodeCollection
   constructor: (shape) ->
@@ -999,6 +1016,8 @@

     @arrangeCodes(pos.x, pos.y)

+  setNodes: (codes, pos) -> @addCodes(codes, pos)
+
   addCode: (symbol, code) ->
     oldval = @codes[symbol]
     newval = new CodeNode(symbol, code)


Allow users to horizontal scroll through the location names divs.

--- overview.client.coffee
+++ overview.client.coffee
@@ -38,7 +38,9 @@
 		# 	_boxShadow: 'rgba(0, 0, 0, 0.4) 0px 1px 3px'
 		Dom.style
 			fontSize: '80%'
-			height: (rowHeight) + 'px'
+			# height: (rowHeight) + 'px'
+			height: '20px'
+			marginBottom: (rowHeight-20) + 'px'
 			padding: '4px'
 			# boxSizing: 'border-box'

@@ -105,6 +107,7 @@
 			listStyle: 'none'
 			padding: 0
 			Box: 'vertical'
+			pointerEvents: 'none'
 			position: 'absolute'
 			top: '27px'
 			margin: "0px"


Fix behavior of tabs. Last commit for issue #388

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -303,6 +303,7 @@
                 lineNumbers: true
                 lineWrapping: true
                 matchBrackets: true
+                tabSize: 2
             @codemirror.on 'blur', @on_blur
             @codemirror.on 'gutterClick', @handle_gutter_click

@@ -376,41 +377,40 @@
                 else if event.which is 9
                     event.preventDefault()
                     if event.type is 'keydown'
-                        if @$('.suggestion_name_list').css('display') is 'none'
-                            @show_suggestion()
-                            return true
-                        else
-                            # Switch throught the suggestions
-                            if event.shiftKey
-                                @current_highlighted_suggestion--
-                                if @current_highlighted_suggestion < -1
-                                    @current_highlighted_suggestion = @current_suggestions.length-1
-                                else if @current_highlighted_suggestion < 0
+                        if @current_suggestions?.length > 0
+                            if @$('.suggestion_name_list').css('display') is 'none'
+                                @show_suggestion()
+                                return true
+                            else
+                                # Switch throught the suggestions
+                                if event.shiftKey
+                                    @current_highlighted_suggestion--
+                                    if @current_highlighted_suggestion < -1
+                                        @current_highlighted_suggestion = @current_suggestions.length-1
+                                    else if @current_highlighted_suggestion < 0
+                                        @show_suggestion_without_moving()
+                                        @remove_highlight_suggestion()
+                                        @write_suggestion
+                                            suggestion_to_write: @current_element
+                                        @ignore_tab_keyup = true # If we are switching suggestion, we don't want to do anything else related to tab
+                                        return true
+                                else
+                                    @current_highlighted_suggestion++
+                                    if @current_highlighted_suggestion >= @current_suggestions.length
+                                        @show_suggestion_without_moving()
+                                        @remove_highlight_suggestion()
+                                        @write_suggestion
+                                            suggestion_to_write: @current_element
+                                        @ignore_tab_keyup = true # If we are switching suggestion, we don't want to do anything else related to tab
+                                        @current_highlighted_suggestion = -1
+                                        return true
+                                if @current_suggestions[@current_highlighted_suggestion]?
                                     @show_suggestion_without_moving()
-                                    @remove_highlight_suggestion()
+                                    @highlight_suggestion @current_highlighted_suggestion # Highlight the current suggestion
                                     @write_suggestion
-                                        suggestion_to_write: @current_element
+                                        suggestion_to_write: @current_suggestions[@current_highlighted_suggestion] # Auto complete with the highlighted suggestion
                                     @ignore_tab_keyup = true # If we are switching suggestion, we don't want to do anything else related to tab
                                     return true
-                            else
-                                @current_highlighted_suggestion++
-                                if @current_highlighted_suggestion >= @current_suggestions.length
-                                    @show_suggestion_without_moving()
-                                    @remove_highlight_suggestion()
-                                    @write_suggestion
-                                        suggestion_to_write: @current_element
-                                    @ignore_tab_keyup = true # If we are switching suggestion, we don't want to do anything else related to tab
-                                    @current_highlighted_suggestion = -1
-                                    return true
-
-
-                            if @current_suggestions[@current_highlighted_suggestion]?
-                                @show_suggestion_without_moving()
-                                @highlight_suggestion @current_highlighted_suggestion # Highlight the current suggestion
-                                @write_suggestion
-                                    suggestion_to_write: @current_suggestions[@current_highlighted_suggestion] # Auto complete with the highlighted suggestion
-                                @ignore_tab_keyup = true # If we are switching suggestion, we don't want to do anything else related to tab
-                                return true
                 # If the user hit enter and (Ctrl or Shift)
                 else if event.which is 13 and (event.shiftKey or event.ctrlKey or event.metaKey)
                     @hide_suggestion_and_description()
@@ -544,6 +544,7 @@
                         query_after_cursor += query_lines[i]
             @query_last_part = query_after_cursor

+            @current_element = '' # Initialize @current_element. We need it for tabs (when the user loop over ALL suggestions)
             stack = @extract_data_from_query
                 query: query_before_cursor
                 position: 0
@@ -586,18 +587,12 @@
             else
                 @hide_suggestion_and_description()

-            if event?.which is 9 # If we catch it here again, it means that we are not showing any suggestions
-                query_lines = @codemirror.getValue().split '\n'
-
-                query_before_cursor = ''
-                if @codemirror.getCursor().line > 0
-                    for i in [0..@codemirror.getCursor().line-1]
-                        query_before_cursor += query_lines[i] + '\n'
-                query_before_cursor += query_lines[@codemirror.getCursor().line].slice 0, @codemirror.getCursor().ch
-                if query_before_cursor[query_before_cursor.length-1] isnt '(' # This regex is a little too constraining /\((\s*)$/
+            if event?.which is 9
+                if @last_element_type_if_incomplete(stack) isnt 'string' and @regex.white_or_empty.test(query_lines[@codemirror.getCursor().line].slice(0, @codemirror.getCursor().ch)) isnt true
+                    return true
+                else
                     return false
-
-
+
             return true

         # Extract information from the current query
@@ -610,6 +605,7 @@
             object: /^(\s)*{(\s)*/
             array: /^(\s)*\[(\s)*/
             white: /^(\s)+$/
+            white_or_empty: /^(\s)*$/
             white_replace: /\s/g
             white_start: /^(\s)+/
             comma: /^(\s)*,(\s)*/
@@ -628,6 +624,20 @@
                 ')': '(' # Match the opening character
                 '}': '{'
                 ']': '['
+
+        # Return the type of the last incomplete object or an empty string
+        last_element_type_if_incomplete: (stack) =>
+            if stack.length is 0
+                return ''
+
+            element = stack[stack.length-1]
+            if element.body?
+                return @last_element_type_if_incomplete(element.body)
+            else
+                if element.complete is false
+                    return element.type
+                else
+                    return ''

         # We build a stack of the query.


upgrade atom and set a bigger font size

upgrade atom and set a bigger font size

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,11 +1,11 @@
 'release-notes':
-  'viewedVersion': '0.94.0'
+  'viewedVersion': '0.95.0'
 'welcome':
   'showOnStartup': false
 'editor':
   'showIndentGuide': false
   'fontFamily': 'Source Code Pro'
-  'fontSize': 12
+  'fontSize': 13
 'core':
   'audioBeep': false
   'disabledPackages': [


Unset that stupid preview setting for selected files

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,5 +1,6 @@
 "*":
   core:
+    allowPendingPaneItems: false
     autoHideMenuBar: false
     disabledPackages: [
       "whitespace"


updated atom config

updated atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -9,7 +9,11 @@
   editor:
     showIndentGuide: true
     softWrap: true
+    softWrapAtPreferredLineLength: true
   "exception-reporting":
     userId: "23b098d2-0be9-4130-941f-408e814f6fc5"
   welcome:
     showOnStartup: false
+".editorconfig.source":
+  editor:
+    softWrapAtPreferredLineLength: true


for some reason this moved ¯\_(ツ)_/¯

for some reason this moved ¯\_(ツ)_/¯


Add Marker::isEqual

Add Marker::isEqual

--- src/marker.coffee
+++ src/marker.coffee
@@ -159,6 +159,9 @@

   isDestroyed: ->
     @destroyed
+
+  isEqual: (other) ->
+    isEqual(@toParams(true), other.toParams(true))

   getInvalidationStrategy: ->
     @invalidate
@@ -208,8 +211,10 @@
       else
         isEqual(@properties[key], value)

-  toParams: ->
-    {@id, @range, @reversed, @tailed, @invalidate, @persistent, @properties}
+  toParams: (omitId) ->
+    params = {@range, @reversed, @tailed, @invalidate, @persistent, @properties}
+    params.id = @id unless omitId
+    params

   update: (params) ->
     if patch = @buildPatch(params)


fixed length of stay calculation

fixed length of stay calculation


Update `user.orders` attribute to use `completed_orders` from json response


We now use Moosh.disableTouchScrolling()

--- scripts/coffee/lib/player/SimplePlayer.coffee
+++ scripts/coffee/lib/player/SimplePlayer.coffee
@@ -19,6 +19,14 @@
 		do @_layout

 		@display.on 'layout', => do @_layout
+
+		@display.on 'fullscreen', =>
+
+			@moosh.disableTouchScrolling()
+
+		@display.on 'restore', =>
+
+			@moosh.enableTouchScrolling()

 		@timeControl.on 'play-state-change', => do @_updatePlayState


update charts

update charts

--- app/assets/javascripts/app/chart.coffee
+++ app/assets/javascripts/app/chart.coffee
@@ -3,8 +3,8 @@
 window.loadInvoicesChart = ->
   if $('#invoices-chart').length && invoicesChartData
     ctx = document.getElementById("invoices-chart").getContext("2d")
-    console.log invoicesChartData
     invoicesChart = new Chart(ctx).Line invoicesChartData,
+      bezierCurve: false
       scaleLabel: (data) ->
         {label: accounting.formatMoney(data.value)}
       tooltipTemplate: (data) ->


Also click the radiobutton to transmitt form value

--- app/assets/javascripts/flames.js.coffee
+++ app/assets/javascripts/flames.js.coffee
@@ -3,6 +3,7 @@
     select_flame = ->
       amount = $(this).data('amount')
       $(".donation_amount .value").html(amount)
+      $(this).prev().click()

     $("label").click(select_flame)
     $(".flames label")[0].click()


Fix javascript.

Fix javascript.

--- app/assets/javascripts/groups.js.coffee
+++ app/assets/javascripts/groups.js.coffee
@@ -26,7 +26,7 @@

 #*** ajax for discussions on group page ***
 $ ->
-  if $("body.groups.show").length > 0
+  if $("body.groups.show").length > 0 && $('#group-discussions').html() != null
     idStr = new Array
     idStr = $('#group-discussions').children().attr('class').split('_')
     params = Application.getPageParam()
@@ -39,7 +39,7 @@
     $("#all-discussions-loading").addClass('hidden')

 $ ->
-  if $("body.groups.show").length > 0 && $('#group-discussions').html() != null
+  if $("body.groups.show").length > 0
     $(document).on('click', '#group-discussions .pagination a', (e)->
       unless $(this).parent().hasClass("gap")
         if Application.html5.supported


Remove version checking from test Logger

--- packages/deft/test/coffee/log/Logger.coffee
+++ packages/deft/test/coffee/log/Logger.coffee
@@ -7,168 +7,57 @@

 	describe( 'log()', ->

-		if Ext.getVersion( 'extjs' )?
-			# Ext JS
+		describe( 'logs a message with the specified priority', ->
+			logFunction = null

-			describe( 'logs a message with the specified priority', ->
-				logFunction = null
-
-				beforeEach( ->
-					logFunction = sinon.stub( Ext, 'log' )
-					return
-				)
-
-				afterEach( ->
-					logFunction.restore()
-					return
-				)
-
-				specify( 'no priority specified', ->
-					Deft.Logger.log( 'message', 'info' )
-
-					expect( logFunction ).to.be.calledOnce.and.calledWith( { level: 'info', msg: 'message' } )
-					return
-				)
-
-				specify( 'verbose', ->
-					Deft.Logger.log( 'message', 'verbose' )
-
-					expect( logFunction ).to.be.calledOnce.and.calledWith( { level: 'info', msg: 'message' } )
-					return
-				)
-
-				specify( 'deprecate', ->
-					Deft.Logger.log( 'message', 'deprecate' )
-
-					expect( logFunction ).to.be.calledOnce.and.calledWith( { level: 'warn', msg: 'message' } )
-					return
-				)
-
-				specify( 'warn', ->
-					Deft.Logger.log( 'message', 'warn' )
-
-					expect( logFunction ).to.be.calledOnce.and.calledWith( { level: 'warn', msg: 'message' } )
-					return
-				)
-
-				specify( 'error', ->
-					Deft.Logger.log( 'message', 'error' )
-
-					expect( logFunction ).to.be.calledOnce.and.calledWith( { level: 'error', msg: 'message' } )
-					return
-				)
-
+			beforeEach( ->
+				logFunction = sinon.stub( Ext, 'log' )
 				return
 			)

-		else
-			# Sencha Touch
-
-			describe( 'logs a message with the specified priority, when Ext.Logger is available', ->
-				logFunction = null
-
-				beforeEach( ->
-					if not Ext.Logger?
-						Ext.define( 'Ext.Logger',
-							singleton: true
-							log: Ext.emptyFn
-							isMock: true
-						)
-					logFunction = sinon.stub( Ext.Logger, 'log' )
-				)
-
-				afterEach( ->
-					logFunction.restore()
-					if Ext.Logger.isMock
-						Ext.Logger = null
-				)
-
-				specify( 'no priority specified', ->
-					Deft.Logger.log( 'message', 'info' )
-
-					expect( logFunction ).to.be.calledOnce.and.calledWith( 'message', 'info' )
-					return
-				)
-
-				specify( 'verbose', ->
-					Deft.Logger.log( 'message', 'verbose' )
-
-					expect( logFunction ).to.be.calledOnce.and.calledWith( 'message', 'verbose' )
-					return
-				)
-
-				specify( 'info', ->
-					Deft.Logger.log( 'message', 'info' )
-
-					expect( logFunction ).to.be.calledOnce.and.calledWith( 'message', 'info' )
-					return
-				)
-
-				specify( 'deprecate', ->
-					Deft.Logger.log( 'message', 'deprecate' )
-
-					expect( logFunction ).to.be.calledOnce.and.calledWith( 'message', 'deprecate' )
-					return
-				)
-
-				specify( 'warn', ->
-					Deft.Logger.log( 'message', 'warn' )
-
-					expect( logFunction ).to.be.calledOnce.and.calledWith( 'message', 'warn' )
-					return
-				)
-
-				specify( 'error', ->
-					Deft.Logger.log( 'message', 'error' )
-
-					expect( logFunction ).to.be.calledOnce.and.calledWith( 'message', 'error' )
-					return
-				)
-
+			afterEach( ->
+				logFunction.restore()
 				return
 			)

-			describe( 'silently ignores messages when Ext.Logger is unavailable', ->
-				logger = null
+			specify( 'no priority specified', ->
+				Deft.Logger.log( 'message', 'info' )

-				beforeEach( ->
-					logger = Ext.Logger
-					Ext.Logger = null
-					return
-				)
-
-				afterEach( ->
-					Ext.Logger = logger
-					return
-				)
-
-				specify( 'no priority specified', ->
-					expect( -> Deft.Logger.log( 'message', 'info' ) ).to.not.throw( Error )
-					return
-				)
-
-				specify( 'verbose', ->
-					expect( -> Deft.Logger.log( 'message', 'verbose' ) ).to.not.throw( Error )
-					return
-				)
-
-				specify( 'deprecate', ->
-					expect( -> Deft.Logger.log( 'message', 'deprecate' ) ).to.not.throw( Error )
-					return
-				)
-
-				specify( 'warn', ->
-					expect( -> Deft.Logger.log( 'message', 'warn' ) ).to.not.throw( Error )
-					return
-				)
-
-				specify( 'error', ->
-					expect( -> Deft.Logger.log( 'message', 'error' ) ).to.not.throw( Error )
-					return
-				)
-
+				expect( logFunction ).to.be.calledOnce.and.calledWith( { level: 'info', msg: 'message' } )
 				return
 			)
+
+			specify( 'verbose', ->
+				Deft.Logger.log( 'message', 'verbose' )
+
+				expect( logFunction ).to.be.calledOnce.and.calledWith( { level: 'info', msg: 'message' } )
+				return
+			)
+
+			specify( 'deprecate', ->
+				Deft.Logger.log( 'message', 'deprecate' )
+
+				expect( logFunction ).to.be.calledOnce.and.calledWith( { level: 'warn', msg: 'message' } )
+				return
+			)
+
+			specify( 'warn', ->
+				Deft.Logger.log( 'message', 'warn' )
+
+				expect( logFunction ).to.be.calledOnce.and.calledWith( { level: 'warn', msg: 'message' } )
+				return
+			)
+
+			specify( 'error', ->
+				Deft.Logger.log( 'message', 'error' )
+
+				expect( logFunction ).to.be.calledOnce.and.calledWith( { level: 'error', msg: 'message' } )
+				return
+			)
+
+			return
+		)
+
 	)

 	describe( 'verbose()', ->


ReportScore doesn't need to be shimmed

ReportScore doesn't need to be shimmed

--- resources/assets/lib/import-shims.coffee
+++ resources/assets/lib/import-shims.coffee
@@ -21,7 +21,6 @@
 import { BackToTop } from 'back-to-top'
 import { PlayDetailList } from 'play-detail-list'
 import { PlayDetailMenu } from 'play-detail-menu'
-import { ReportScore } from 'report-score'
 import { ReportUser } from 'report-user'
 import { ScoreHelper } from 'score-helper'
 import { SelectOptions } from 'select-options'
@@ -38,7 +37,6 @@
   BackToTop
   PlayDetailList
   PlayDetailMenu
-  ReportScore
   ReportUser
   ScoreHelper
   SelectOptions


update fallback selenium version to 2.41.0

--- src/versions.coffee
+++ src/versions.coffee
@@ -31,7 +31,7 @@
 ###

 FALLBACK_CHROMEDRIVER_VERSION = '2.9'
-FALLBACK_SELENIUM_VERSION = '2.39.0'
+FALLBACK_SELENIUM_VERSION = '2.41.0'

 async = require 'async'
 request = require 'request'


Quick typo fix

Quick typo fix

--- src/moltin.coffee
+++ src/moltin.coffee
@@ -94,9 +94,9 @@
 			catch e
 				return false;

-		if args.type == "GET"
+		if args.type == 'GET'
 			args.url += '?' + @Serialize args.data
-			data.data = null
+			args.data = null
 		else
 			args.data = @Serialize args.data


trigger change on option text change and dont trigger if survey not found

--- jsapp/xlform/src/view.choices.coffee
+++ jsapp/xlform/src/view.choices.coffee
@@ -43,7 +43,7 @@
           deactivate: =>
             if @hasReordered
               @reordered()
-              @model.getSurvey().trigger('change')
+              @model.getSurvey()?.trigger('change')
             true
           change: => @hasReordered = true
         })
@@ -51,7 +51,7 @@
       btn.click(() =>
         i = @model.options.length
         @addEmptyOption("Option #{i+1}")
-        @model.getSurvey().trigger('change')
+        @model.getSurvey()?.trigger('change')
       )

       @$el.append(btn)
@@ -162,7 +162,7 @@
     remove: ()->
       $parent = @$el.parent()

-      @model.getSurvey().trigger('change')
+      @model.getSurvey()?.trigger('change')

       @$el.remove()
       @model.destroy()
@@ -190,6 +190,7 @@
             validXmlTag: true
           @model.set("name", $modelUtils.sluggify(nval, sluggifyOpts))
         @$el.trigger("choice-list-update", @options.cl.cid)
+        @model.getSurvey()?.trigger('change')
         return
       else
         return newValue: @model.get "label"


i18n for vega (#423)

i18n for vega (#423)

--- app/core/store/modules/game.ozar.coffee
+++ app/core/store/modules/game.ozar.coffee
@@ -1,5 +1,6 @@
 levelSchema = require('schemas/models/level')
 api = require('core/api')
+utils = require('core/utils')

 # TODO: Be explicit about the properties being stored
 emptyLevel = _.zipObject(([key, null] for key in _.keys(levelSchema.properties)))
@@ -72,8 +73,13 @@
     addTutorialStepsFromSayEvents: ({ commit, rootState }, sayEvents) ->
       sayEvents.forEach((sayEvent) ->
         { say, tutorial } = sayEvent
+        if say.i18n
+          text = utils.i18n(say, 'text')
+        if not text
+          text = say.text
+
         commit('addTutorialStep', {
-          message: say.text
+          message: text
           # To stay backwards compatible with old Vega messages,
           # they are turned into stationary Vega messages with no other qualities:
           position: tutorial?.position or 'stationary'