:lipstick: Organize config-spec

:lipstick: Organize config-spec

* Group tests by method rather than by scoped vs unscoped
* Group deprecated methods together
* Group internal methods together
* Make descriptions more consistent

--- spec/config-spec.coffee
+++ spec/config-spec.coffee
@@ -10,7 +10,7 @@
   beforeEach ->
     dotAtomPath = temp.path('dot-atom-dir')

-  describe ".get(keyPath)", ->
+  describe ".get(keyPath, {scope, sources, excludeSources})", ->
     it "allows a key path's value to be read", ->
       expect(atom.config.set("foo.bar.baz", 42)).toBe true
       expect(atom.config.get("foo.bar.baz")).toBe 42
@@ -65,7 +65,36 @@
         expect(atom.config.get("x.y", excludeSources: ["b", "c", "a", atom.config.getUserConfigPath()], scope: [".foo"])).toBe 4
         expect(atom.config.get("x.y", excludeSources: [atom.config.getUserConfigPath()])).toBe 4

-  describe ".set(keyPath, value)", ->
+    describe "when a 'scope' option is given", ->
+      it "returns the property with the most specific scope selector", ->
+        atom.config.addScopedSettings("config", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42)
+        atom.config.addScopedSettings("config", ".source .string.quoted.double", foo: bar: baz: 22)
+        atom.config.addScopedSettings("config", ".source", foo: bar: baz: 11)
+
+        expect(atom.config.get("foo.bar.baz", scope: [".source.coffee", ".string.quoted.double.coffee"])).toBe 42
+        expect(atom.config.get("foo.bar.baz", scope: [".source.js", ".string.quoted.double.js"])).toBe 22
+        expect(atom.config.get("foo.bar.baz", scope: [".source.js", ".variable.assignment.js"])).toBe 11
+        expect(atom.config.get("foo.bar.baz", scope: [".text"])).toBeUndefined()
+
+      it "favors the most recently added properties in the event of a specificity tie", ->
+        atom.config.addScopedSettings("config", ".source.coffee .string.quoted.single", foo: bar: baz: 42)
+        atom.config.addScopedSettings("config", ".source.coffee .string.quoted.double", foo: bar: baz: 22)
+
+        expect(atom.config.get("foo.bar.baz", scope: [".source.coffee", ".string.quoted.single"])).toBe 42
+        expect(atom.config.get("foo.bar.baz", scope: [".source.coffee", ".string.quoted.single.double"])).toBe 22
+
+      describe 'when there are global defaults', ->
+        it 'falls back to the global when there is no scoped property specified', ->
+          atom.config.setDefaults("foo", hasDefault: 'ok')
+          expect(atom.config.get("foo.hasDefault", scope: [".source.coffee", ".string.quoted.single"])).toBe 'ok'
+
+      describe 'when package settings are added after user settings', ->
+        it "returns the user's setting because the user's setting has higher priority", ->
+          atom.config.set("foo.bar.baz", 100, scopeSelector: ".source.coffee")
+          atom.config.addScopedSettings("some-package", ".source.coffee", foo: bar: baz: 1)
+          expect(atom.config.get("foo.bar.baz", scope: [".source.coffee"])).toBe 100
+
+  describe ".set(keyPath, value, {source, scopeSelector})", ->
     it "allows a key path's value to be written", ->
       expect(atom.config.set("foo.bar.baz", 42)).toBe true
       expect(atom.config.get("foo.bar.baz")).toBe 42
@@ -107,108 +136,18 @@
         atom.config.set('foo.changes', 1)
         expect(atom.config.get("foo.changes", sources: [atom.config.getUserConfigPath()])).toBeUndefined()

-  describe ".getDefault(keyPath)", ->
-    it "returns a clone of the default value", ->
-      atom.config.setDefaults("foo", same: 1, changes: 1)
-
-      spyOn(Grim, 'deprecate')
-      expect(atom.config.getDefault('foo.same')).toBe 1
-      expect(atom.config.getDefault('foo.changes')).toBe 1
-      expect(Grim.deprecate.callCount).toBe 2
-
-      atom.config.set('foo.same', 2)
-      atom.config.set('foo.changes', 3)
-
-      expect(atom.config.getDefault('foo.same')).toBe 1
-      expect(atom.config.getDefault('foo.changes')).toBe 1
-      expect(Grim.deprecate.callCount).toBe 4
-
-      initialDefaultValue = [1, 2, 3]
-      atom.config.setDefaults("foo", bar: initialDefaultValue)
-      expect(atom.config.getDefault('foo.bar')).toEqual initialDefaultValue
-      expect(atom.config.getDefault('foo.bar')).not.toBe initialDefaultValue
-      expect(Grim.deprecate.callCount).toBe 6
-
-    describe "when scoped settings are used", ->
-      it "returns the global default when no scoped default set", ->
-        atom.config.setDefaults("foo", bar: baz: 10)
-
-        spyOn(Grim, 'deprecate')
-        expect(atom.config.getDefault('.source.coffee', 'foo.bar.baz')).toBe 10
-        expect(Grim.deprecate).toHaveBeenCalled()
-
-      it "returns the scoped settings not including the user's config file", ->
-        atom.config.setDefaults("foo", bar: baz: 10)
-        atom.config.addScopedSettings("default", ".source.coffee", foo: bar: baz: 42)
-
-        spyOn(Grim, 'deprecate')
-        expect(atom.config.getDefault('.source.coffee', 'foo.bar.baz')).toBe 42
-        expect(Grim.deprecate.callCount).toBe 1
-
-        atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
-        expect(atom.config.getDefault('.source.coffee', 'foo.bar.baz')).toBe 42
-        expect(Grim.deprecate.callCount).toBe 2
-
-  describe ".isDefault(keyPath)", ->
-    it "returns true when the value of the key path is its default value", ->
-      atom.config.setDefaults("foo", same: 1, changes: 1)
-
-      spyOn(Grim, 'deprecate')
-      expect(atom.config.isDefault('foo.same')).toBe true
-      expect(atom.config.isDefault('foo.changes')).toBe true
-      expect(Grim.deprecate.callCount).toBe 2
-
-      atom.config.set('foo.same', 2)
-      atom.config.set('foo.changes', 3)
-
-      expect(atom.config.isDefault('foo.same')).toBe false
-      expect(atom.config.isDefault('foo.changes')).toBe false
-      expect(Grim.deprecate.callCount).toBe 4
-
-    describe "when scoped settings are used", ->
-      it "returns false when a scoped setting was set by the user", ->
-        spyOn(Grim, 'deprecate')
-        expect(atom.config.isDefault('.source.coffee', 'foo.bar.baz')).toBe true
-        expect(Grim.deprecate.callCount).toBe 1
-
-        atom.config.addScopedSettings("default", ".source.coffee", foo: bar: baz: 42)
-        expect(atom.config.isDefault('.source.coffee', 'foo.bar.baz')).toBe true
-        expect(Grim.deprecate.callCount).toBe 2
-
-        atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
-        expect(atom.config.isDefault('.source.coffee', 'foo.bar.baz')).toBe false
-        expect(Grim.deprecate.callCount).toBe 3
-
-  describe ".setDefaults(keyPath)", ->
-    it "sets a default when the setting's key contains an escaped dot", ->
-      atom.config.setDefaults("foo", 'a\\.b': 1, b: 2)
-      expect(atom.config.get("foo")).toEqual 'a\\.b': 1, b: 2
-
-  describe ".toggle(keyPath)", ->
-    beforeEach ->
-      jasmine.snapshotDeprecations()
-
-    afterEach ->
-      jasmine.restoreDeprecationsSnapshot()
-
-    it "negates the boolean value of the current key path value", ->
-      atom.config.set('foo.a', 1)
-      atom.config.toggle('foo.a')
-      expect(atom.config.get('foo.a')).toBe false
-
-      atom.config.set('foo.a', '')
-      atom.config.toggle('foo.a')
-      expect(atom.config.get('foo.a')).toBe true
-
-      atom.config.set('foo.a', null)
-      atom.config.toggle('foo.a')
-      expect(atom.config.get('foo.a')).toBe true
-
-      atom.config.set('foo.a', true)
-      atom.config.toggle('foo.a')
-      expect(atom.config.get('foo.a')).toBe false
-
-  describe ".unset(keyPath, {scope})", ->
+    describe "when a 'scopeSelector' is given", ->
+      it "sets the value and overrides the others", ->
+        atom.config.addScopedSettings("config", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42)
+        atom.config.addScopedSettings("config", ".source .string.quoted.double", foo: bar: baz: 22)
+        atom.config.addScopedSettings("config", ".source", foo: bar: baz: 11)
+
+        expect(atom.config.get("foo.bar.baz", scope: [".source.coffee", ".string.quoted.double.coffee"])).toBe 42
+
+        expect(atom.config.set("foo.bar.baz", 100, scopeSelector: ".source.coffee .string.quoted.double.coffee")).toBe true
+        expect(atom.config.get("foo.bar.baz", scope: [".source.coffee", ".string.quoted.double.coffee"])).toBe 100
+
+  describe ".unset(keyPath, {source, scopeSelector})", ->
     it "sets the value of the key path to its default", ->
       atom.config.setDefaults('a', b: 3)
       atom.config.set('a.b', 4)
@@ -232,7 +171,7 @@
     it "throws when called with a source but no scope", ->
       expect(-> atom.config.unset("a.b", source: "the-source")).toThrow()

-    describe "when scoped settings are used", ->
+    describe "when a 'scopeSelector' is given", ->
       it "restores the global default when no scoped default set", ->
         atom.config.setDefaults("foo", bar: baz: 10)
         atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
@@ -335,167 +274,7 @@

         expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 10

-  describe ".getSources()", ->
-    it "returns an array of all of the config's source names", ->
-      expect(atom.config.getSources()).toEqual([])
-
-      atom.config.set("a.b", 1, scopeSelector: ".x1", source: "source-1")
-      atom.config.set("a.c", 1, scopeSelector: ".x1", source: "source-1")
-      atom.config.set("a.b", 2, scopeSelector: ".x2", source: "source-2")
-      atom.config.set("a.b", 1, scopeSelector: ".x3", source: "source-3")
-
-      expect(atom.config.getSources()).toEqual([
-        "source-1"
-        "source-2"
-        "source-3"
-      ])
-
-  describe ".getSettings()", ->
-    it "returns all settings including defaults", ->
-      atom.config.setDefaults("foo", bar: baz: 10)
-      atom.config.set("foo.ok", 12)
-
-      jasmine.snapshotDeprecations()
-      expect(atom.config.getSettings().foo).toEqual
-        ok: 12
-        bar:
-          baz: 10
-      jasmine.restoreDeprecationsSnapshot()
-
-  describe ".pushAtKeyPath(keyPath, value)", ->
-    it "pushes the given value to the array at the key path and updates observers", ->
-      atom.config.set("foo.bar.baz", ["a"])
-      observeHandler = jasmine.createSpy "observeHandler"
-      atom.config.observe "foo.bar.baz", observeHandler
-      observeHandler.reset()
-
-      expect(atom.config.pushAtKeyPath("foo.bar.baz", "b")).toBe 2
-      expect(atom.config.get("foo.bar.baz")).toEqual ["a", "b"]
-      expect(observeHandler).toHaveBeenCalledWith atom.config.get("foo.bar.baz")
-
-  describe ".unshiftAtKeyPath(keyPath, value)", ->
-    it "unshifts the given value to the array at the key path and updates observers", ->
-      atom.config.set("foo.bar.baz", ["b"])
-      observeHandler = jasmine.createSpy "observeHandler"
-      atom.config.observe "foo.bar.baz", observeHandler
-      observeHandler.reset()
-
-      expect(atom.config.unshiftAtKeyPath("foo.bar.baz", "a")).toBe 2
-      expect(atom.config.get("foo.bar.baz")).toEqual ["a", "b"]
-      expect(observeHandler).toHaveBeenCalledWith atom.config.get("foo.bar.baz")
-
-  describe ".removeAtKeyPath(keyPath, value)", ->
-    it "removes the given value from the array at the key path and updates observers", ->
-      atom.config.set("foo.bar.baz", ["a", "b", "c"])
-      observeHandler = jasmine.createSpy "observeHandler"
-      atom.config.observe "foo.bar.baz", observeHandler
-      observeHandler.reset()
-
-      expect(atom.config.removeAtKeyPath("foo.bar.baz", "b")).toEqual ["a", "c"]
-      expect(atom.config.get("foo.bar.baz")).toEqual ["a", "c"]
-      expect(observeHandler).toHaveBeenCalledWith atom.config.get("foo.bar.baz")
-
-  describe ".getPositiveInt(keyPath, defaultValue)", ->
-    beforeEach ->
-      jasmine.snapshotDeprecations()
-
-    afterEach ->
-      jasmine.restoreDeprecationsSnapshot()
-
-    it "returns the proper coerced value", ->
-      atom.config.set('editor.preferredLineLength', 0)
-      expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 1
-
-    it "returns the proper coerced value", ->
-      atom.config.set('editor.preferredLineLength', -1234)
-      expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 1
-
-    it "returns the default value when a string is passed in", ->
-      atom.config.set('editor.preferredLineLength', 'abcd')
-      expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80
-
-    it "returns the default value when null is passed in", ->
-      atom.config.set('editor.preferredLineLength', null)
-      expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80
-
-  describe ".save()", ->
-    CSON = require 'season'
-
-    beforeEach ->
-      spyOn(CSON, 'writeFileSync')
-      jasmine.unspy atom.config, 'save'
-
-    describe "when ~/.atom/config.json exists", ->
-      it "writes any non-default properties to ~/.atom/config.json", ->
-        atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.json")
-        atom.config.set("a.b.c", 1)
-        atom.config.set("a.b.d", 2)
-        atom.config.set("x.y.z", 3)
-        atom.config.setDefaults("a.b", e: 4, f: 5)
-
-        CSON.writeFileSync.reset()
-        atom.config.save()
-
-        expect(CSON.writeFileSync.argsForCall[0][0]).toBe path.join(atom.config.configDirPath, "atom.config.json")
-        writtenConfig = CSON.writeFileSync.argsForCall[0][1]
-        expect(writtenConfig).toEqual global: atom.config.settings
-
-    describe "when ~/.atom/config.json doesn't exist", ->
-      it "writes any non-default properties to ~/.atom/config.cson", ->
-        atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.cson")
-        atom.config.set("a.b.c", 1)
-        atom.config.set("a.b.d", 2)
-        atom.config.set("x.y.z", 3)
-        atom.config.setDefaults("a.b", e: 4, f: 5)
-
-        CSON.writeFileSync.reset()
-        atom.config.save()
-
-        expect(CSON.writeFileSync.argsForCall[0][0]).toBe path.join(atom.config.configDirPath, "atom.config.cson")
-        writtenConfig = CSON.writeFileSync.argsForCall[0][1]
-        expect(writtenConfig).toEqual global: atom.config.settings
-
-    describe "when scoped settings are defined", ->
-      it 'writes out explicitly set config settings', ->
-        atom.config.set('foo.bar', 'ruby', scopeSelector: '.source.ruby')
-        atom.config.set('foo.omg', 'wow', scopeSelector: '.source.ruby')
-        atom.config.set('foo.bar', 'coffee', scopeSelector: '.source.coffee')
-
-        CSON.writeFileSync.reset()
-        atom.config.save()
-
-        writtenConfig = CSON.writeFileSync.argsForCall[0][1]
-        expect(writtenConfig).toEqualJson
-          global:
-            atom.config.settings
-          '.ruby.source':
-            foo:
-              bar: 'ruby'
-              omg: 'wow'
-          '.coffee.source':
-            foo:
-              bar: 'coffee'
-
-  describe ".setDefaults(keyPath, defaults)", ->
-    it "assigns any previously-unassigned keys to the object at the key path", ->
-      atom.config.set("foo.bar.baz", a: 1)
-      atom.config.setDefaults("foo.bar.baz", a: 2, b: 3, c: 4)
-      expect(atom.config.get("foo.bar.baz.a")).toBe 1
-      expect(atom.config.get("foo.bar.baz.b")).toBe 3
-      expect(atom.config.get("foo.bar.baz.c")).toBe 4
-
-      atom.config.setDefaults("foo.quux", x: 0, y: 1)
-      expect(atom.config.get("foo.quux.x")).toBe 0
-      expect(atom.config.get("foo.quux.y")).toBe 1
-
-    it "emits an updated event", ->
-      updatedCallback = jasmine.createSpy('updated')
-      atom.config.onDidChange('foo.bar.baz.a', updatedCallback)
-      expect(updatedCallback.callCount).toBe 0
-      atom.config.setDefaults("foo.bar.baz", a: 2)
-      expect(updatedCallback.callCount).toBe 1
-
-  describe ".onDidChange(keyPath)", ->
+  describe ".onDidChange(keyPath, {scope})", ->
     [observeHandler, observeSubscription] = []

     describe 'when a keyPath is specified', ->
@@ -550,7 +329,67 @@
         expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.int).toBe(1)
         expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.int).toBe(undefined)

-  describe ".observe(keyPath)", ->
+    describe "when a 'scope' is given", ->
+      it 'calls the supplied callback when the value at the descriptor/keypath changes', ->
+        keyPath = "foo.bar.baz"
+        changeSpy = jasmine.createSpy('onDidChange callback')
+        atom.config.onDidChange keyPath, scope: [".source.coffee", ".string.quoted.double.coffee"], changeSpy
+
+        atom.config.set("foo.bar.baz", 12)
+        expect(changeSpy).toHaveBeenCalledWith({oldValue: undefined, newValue: 12})
+        changeSpy.reset()
+
+        disposable1 = atom.config.addScopedSettings("a", ".source .string.quoted.double", foo: bar: baz: 22)
+        expect(changeSpy).toHaveBeenCalledWith({oldValue: 12, newValue: 22})
+        changeSpy.reset()
+
+        disposable2 = atom.config.addScopedSettings("b", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42)
+        expect(changeSpy).toHaveBeenCalledWith({oldValue: 22, newValue: 42})
+        changeSpy.reset()
+
+        disposable2.dispose()
+        expect(changeSpy).toHaveBeenCalledWith({oldValue: 42, newValue: 22})
+        changeSpy.reset()
+
+        disposable1.dispose()
+        expect(changeSpy).toHaveBeenCalledWith({oldValue: 22, newValue: 12})
+        changeSpy.reset()
+
+        atom.config.set("foo.bar.baz", undefined)
+        expect(changeSpy).toHaveBeenCalledWith({oldValue: 12, newValue: undefined})
+        changeSpy.reset()
+
+      it 'deprecates using a scope descriptor as an optional first argument', ->
+        keyPath = "foo.bar.baz"
+        spyOn(Grim, 'deprecate')
+        atom.config.onDidChange [".source.coffee", ".string.quoted.double.coffee"], keyPath, changeSpy = jasmine.createSpy()
+        expect(Grim.deprecate).toHaveBeenCalled()
+
+        atom.config.set("foo.bar.baz", 12)
+        expect(changeSpy).toHaveBeenCalledWith({oldValue: undefined, newValue: 12})
+        changeSpy.reset()
+
+        disposable1 = atom.config.addScopedSettings("a", ".source .string.quoted.double", foo: bar: baz: 22)
+        expect(changeSpy).toHaveBeenCalledWith({oldValue: 12, newValue: 22})
+        changeSpy.reset()
+
+        disposable2 = atom.config.addScopedSettings("b", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42)
+        expect(changeSpy).toHaveBeenCalledWith({oldValue: 22, newValue: 42})
+        changeSpy.reset()
+
+        disposable2.dispose()
+        expect(changeSpy).toHaveBeenCalledWith({oldValue: 42, newValue: 22})
+        changeSpy.reset()
+
+        disposable1.dispose()
+        expect(changeSpy).toHaveBeenCalledWith({oldValue: 22, newValue: 12})
+        changeSpy.reset()
+
+        atom.config.set("foo.bar.baz", undefined)
+        expect(changeSpy).toHaveBeenCalledWith({oldValue: 12, newValue: undefined})
+        changeSpy.reset()
+
+  describe ".observe(keyPath, {scope})", ->
     [observeHandler, observeSubscription] = []

     beforeEach ->
@@ -602,7 +441,7 @@
       atom.config.set('foo.bar.baz', "value 10")
       expect(bazCatHandler).not.toHaveBeenCalled()

-    describe "observing scoped settings", ->
+    describe "when a 'scope' is given", ->
       otherHandler = null

       beforeEach ->
@@ -627,6 +466,36 @@
         expect(observeHandler).toHaveBeenCalledWith("value 2")
         expect(otherHandler).not.toHaveBeenCalledWith("value 2")

+      it 'calls the callback when properties with more specific selectors are removed', ->
+        changeSpy = jasmine.createSpy()
+        atom.config.observe("foo.bar.baz", scope: [".source.coffee", ".string.quoted.double.coffee"], changeSpy)
+        expect(changeSpy).toHaveBeenCalledWith("value 1")
+        changeSpy.reset()
+
+        atom.config.set("foo.bar.baz", 12)
+        expect(changeSpy).toHaveBeenCalledWith(12)
+        changeSpy.reset()
+
+        disposable1 = atom.config.addScopedSettings("a", ".source .string.quoted.double", foo: bar: baz: 22)
+        expect(changeSpy).toHaveBeenCalledWith(22)
+        changeSpy.reset()
+
+        disposable2 = atom.config.addScopedSettings("b", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42)
+        expect(changeSpy).toHaveBeenCalledWith(42)
+        changeSpy.reset()
+
+        disposable2.dispose()
+        expect(changeSpy).toHaveBeenCalledWith(22)
+        changeSpy.reset()
+
+        disposable1.dispose()
+        expect(changeSpy).toHaveBeenCalledWith(12)
+        changeSpy.reset()
+
+        atom.config.set("foo.bar.baz", undefined)
+        expect(changeSpy).toHaveBeenCalledWith(undefined)
+        changeSpy.reset()
+
   describe ".transact(callback)", ->
     changeSpy = null

@@ -647,286 +516,413 @@
       atom.config.transact ->
       expect(changeSpy).not.toHaveBeenCalled()

-  describe ".initializeConfigDirectory()", ->
-    beforeEach ->
-      if fs.existsSync(dotAtomPath)
+  describe ".getSources()", ->
+    it "returns an array of all of the config's source names", ->
+      expect(atom.config.getSources()).toEqual([])
+
+      atom.config.set("a.b", 1, scopeSelector: ".x1", source: "source-1")
+      atom.config.set("a.c", 1, scopeSelector: ".x1", source: "source-1")
+      atom.config.set("a.b", 2, scopeSelector: ".x2", source: "source-2")
+      atom.config.set("a.b", 1, scopeSelector: ".x3", source: "source-3")
+
+      expect(atom.config.getSources()).toEqual([
+        "source-1"
+        "source-2"
+        "source-3"
+      ])
+
+  describe "Internal Methods", ->
+    describe ".save()", ->
+      CSON = require 'season'
+
+      beforeEach ->
+        spyOn(CSON, 'writeFileSync')
+        jasmine.unspy atom.config, 'save'
+
+      describe "when ~/.atom/config.json exists", ->
+        it "writes any non-default properties to ~/.atom/config.json", ->
+          atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.json")
+          atom.config.set("a.b.c", 1)
+          atom.config.set("a.b.d", 2)
+          atom.config.set("x.y.z", 3)
+          atom.config.setDefaults("a.b", e: 4, f: 5)
+
+          CSON.writeFileSync.reset()
+          atom.config.save()
+
+          expect(CSON.writeFileSync.argsForCall[0][0]).toBe path.join(atom.config.configDirPath, "atom.config.json")
+          writtenConfig = CSON.writeFileSync.argsForCall[0][1]
+          expect(writtenConfig).toEqual global: atom.config.settings
+
+      describe "when ~/.atom/config.json doesn't exist", ->
+        it "writes any non-default properties to ~/.atom/config.cson", ->
+          atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.cson")
+          atom.config.set("a.b.c", 1)
+          atom.config.set("a.b.d", 2)
+          atom.config.set("x.y.z", 3)
+          atom.config.setDefaults("a.b", e: 4, f: 5)
+
+          CSON.writeFileSync.reset()
+          atom.config.save()
+
+          expect(CSON.writeFileSync.argsForCall[0][0]).toBe path.join(atom.config.configDirPath, "atom.config.cson")
+          writtenConfig = CSON.writeFileSync.argsForCall[0][1]
+          expect(writtenConfig).toEqual global: atom.config.settings
+
+      describe "when scoped settings are defined", ->
+        it 'writes out explicitly set config settings', ->
+          atom.config.set('foo.bar', 'ruby', scopeSelector: '.source.ruby')
+          atom.config.set('foo.omg', 'wow', scopeSelector: '.source.ruby')
+          atom.config.set('foo.bar', 'coffee', scopeSelector: '.source.coffee')
+
+          CSON.writeFileSync.reset()
+          atom.config.save()
+
+          writtenConfig = CSON.writeFileSync.argsForCall[0][1]
+          expect(writtenConfig).toEqualJson
+            global:
+              atom.config.settings
+            '.ruby.source':
+              foo:
+                bar: 'ruby'
+                omg: 'wow'
+            '.coffee.source':
+              foo:
+                bar: 'coffee'
+
+    describe ".loadUserConfig()", ->
+      beforeEach ->
+        atom.config.configDirPath = dotAtomPath
+        atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.cson")
+        expect(fs.existsSync(atom.config.configDirPath)).toBeFalsy()
+        atom.config.setSchema 'foo',
+          type: 'object'
+          properties:
+            bar:
+              type: 'string'
+              default: 'def'
+            int:
+              type: 'integer'
+              default: 12
+
+      afterEach ->
         fs.removeSync(dotAtomPath)

-      atom.config.configDirPath = dotAtomPath
-
-    afterEach ->
-      fs.removeSync(dotAtomPath)
-
-    describe "when the configDirPath doesn't exist", ->
-      it "copies the contents of dot-atom to ~/.atom", ->
-        initializationDone = false
-        jasmine.unspy(window, "setTimeout")
-        atom.config.initializeConfigDirectory ->
-          initializationDone = true
-
-        waitsFor -> initializationDone
-
-        runs ->
-          expect(fs.existsSync(atom.config.configDirPath)).toBeTruthy()
-          expect(fs.existsSync(path.join(atom.config.configDirPath, 'packages'))).toBeTruthy()
-          expect(fs.isFileSync(path.join(atom.config.configDirPath, 'snippets.cson'))).toBeTruthy()
-          expect(fs.isFileSync(path.join(atom.config.configDirPath, 'config.cson'))).toBeTruthy()
-          expect(fs.isFileSync(path.join(atom.config.configDirPath, 'init.coffee'))).toBeTruthy()
-          expect(fs.isFileSync(path.join(atom.config.configDirPath, 'styles.less'))).toBeTruthy()
-
-  describe ".loadUserConfig()", ->
-    beforeEach ->
-      atom.config.configDirPath = dotAtomPath
-      atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.cson")
-      expect(fs.existsSync(atom.config.configDirPath)).toBeFalsy()
-      atom.config.setSchema 'foo',
-        type: 'object'
-        properties:
-          bar:
-            type: 'string'
-            default: 'def'
-          int:
-            type: 'integer'
-            default: 12
-
-    afterEach ->
-      fs.removeSync(dotAtomPath)
-
-    describe "when the config file contains scoped settings", ->
+      describe "when the config file contains scoped settings", ->
+        beforeEach ->
+          fs.writeFileSync atom.config.configFilePath, """
+            global:
+              foo:
+                bar: 'baz'
+
+            '.source.ruby':
+              foo:
+                bar: 'more-specific'
+          """
+          atom.config.loadUserConfig()
+
+        it "updates the config data based on the file contents", ->
+          expect(atom.config.get("foo.bar")).toBe 'baz'
+          expect(atom.config.get("foo.bar", scope: ['.source.ruby'])).toBe 'more-specific'
+
+      describe "when the config file contains valid cson", ->
+        beforeEach ->
+          fs.writeFileSync(atom.config.configFilePath, "foo: bar: 'baz'")
+
+        it "updates the config data based on the file contents", ->
+          atom.config.loadUserConfig()
+          expect(atom.config.get("foo.bar")).toBe 'baz'
+
+        it "notifies observers for updated keypaths on load", ->
+          observeHandler = jasmine.createSpy("observeHandler")
+          observeSubscription = atom.config.observe "foo.bar", observeHandler
+
+          atom.config.loadUserConfig()
+
+          expect(observeHandler).toHaveBeenCalledWith 'baz'
+
+      describe "when the config file contains invalid cson", ->
+        beforeEach ->
+          spyOn(console, 'error')
+          spyOn(atom.notifications, 'addError')
+          fs.writeFileSync(atom.config.configFilePath, "{{{{{")
+
+        it "logs an error to the console and does not overwrite the config file on a subsequent save", ->
+          atom.config.loadUserConfig()
+          expect(console.error).toHaveBeenCalled()
+          expect(atom.notifications.addError.callCount).toBe 1
+          atom.config.set("hair", "blonde") # trigger a save
+          expect(atom.config.save).not.toHaveBeenCalled()
+
+      describe "when the config file does not exist", ->
+        it "creates it with an empty object", ->
+          fs.makeTreeSync(atom.config.configDirPath)
+          atom.config.loadUserConfig()
+          expect(fs.existsSync(atom.config.configFilePath)).toBe true
+          expect(CSON.readFileSync(atom.config.configFilePath)).toEqual {}
+
+      describe "when the config file contains values that do not adhere to the schema", ->
+        warnSpy = null
+        beforeEach ->
+          warnSpy = spyOn console, 'warn'
+          fs.writeFileSync atom.config.configFilePath, """
+            foo:
+              bar: 'baz'
+              int: 'bad value'
+          """
+          atom.config.loadUserConfig()
+
+        it "updates the only the settings that have values matching the schema", ->
+          expect(atom.config.get("foo.bar")).toBe 'baz'
+          expect(atom.config.get("foo.int")).toBe 12
+
+          expect(warnSpy).toHaveBeenCalled()
+          expect(warnSpy.mostRecentCall.args[0]).toContain "foo.int"
+
+    describe ".observeUserConfig()", ->
+      updatedHandler = null
+
       beforeEach ->
+        atom.config.setSchema 'foo',
+          type: 'object'
+          properties:
+            bar:
+              type: 'string'
+              default: 'def'
+            baz:
+              type: 'string'
+            scoped:
+              type: 'boolean'
+            int:
+              type: 'integer'
+              default: 12
+
+        atom.config.configDirPath = dotAtomPath
+        atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.cson")
+        expect(fs.existsSync(atom.config.configDirPath)).toBeFalsy()
         fs.writeFileSync atom.config.configFilePath, """
           global:
             foo:
               bar: 'baz'
-
+              scoped: false
           '.source.ruby':
             foo:
-              bar: 'more-specific'
+              scoped: true
         """
         atom.config.loadUserConfig()
-
-      it "updates the config data based on the file contents", ->
-        expect(atom.config.get("foo.bar")).toBe 'baz'
-        expect(atom.config.get("foo.bar", scope: ['.source.ruby'])).toBe 'more-specific'
-
-    describe "when the config file contains valid cson", ->
-      beforeEach ->
-        fs.writeFileSync(atom.config.configFilePath, "foo: bar: 'baz'")
-
-      it "updates the config data based on the file contents", ->
-        atom.config.loadUserConfig()
-        expect(atom.config.get("foo.bar")).toBe 'baz'
-
-      it "notifies observers for updated keypaths on load", ->
-        observeHandler = jasmine.createSpy("observeHandler")
-        observeSubscription = atom.config.observe "foo.bar", observeHandler
-
-        atom.config.loadUserConfig()
-
-        expect(observeHandler).toHaveBeenCalledWith 'baz'
-
-    describe "when the config file contains invalid cson", ->
-      beforeEach ->
-        spyOn(console, 'error')
-        spyOn(atom.notifications, 'addError')
-        fs.writeFileSync(atom.config.configFilePath, "{{{{{")
-
-      it "logs an error to the console and does not overwrite the config file on a subsequent save", ->
-        atom.config.loadUserConfig()
-        expect(console.error).toHaveBeenCalled()
-        expect(atom.notifications.addError.callCount).toBe 1
-        atom.config.set("hair", "blonde") # trigger a save
-        expect(atom.config.save).not.toHaveBeenCalled()
-
-    describe "when the config file does not exist", ->
-      it "creates it with an empty object", ->
-        fs.makeTreeSync(atom.config.configDirPath)
-        atom.config.loadUserConfig()
-        expect(fs.existsSync(atom.config.configFilePath)).toBe true
-        expect(CSON.readFileSync(atom.config.configFilePath)).toEqual {}
-
-    describe "when the config file contains values that do not adhere to the schema", ->
-      warnSpy = null
-      beforeEach ->
-        warnSpy = spyOn console, 'warn'
-        fs.writeFileSync atom.config.configFilePath, """
-          foo:
-            bar: 'baz'
-            int: 'bad value'
-        """
-        atom.config.loadUserConfig()
-
-      it "updates the only the settings that have values matching the schema", ->
-        expect(atom.config.get("foo.bar")).toBe 'baz'
-        expect(atom.config.get("foo.int")).toBe 12
-
-        expect(warnSpy).toHaveBeenCalled()
-        expect(warnSpy.mostRecentCall.args[0]).toContain "foo.int"
-
-  describe ".observeUserConfig()", ->
-    updatedHandler = null
-
-    beforeEach ->
-      atom.config.setSchema 'foo',
-        type: 'object'
-        properties:
-          bar:
-            type: 'string'
-            default: 'def'
-          baz:
-            type: 'string'
-          scoped:
-            type: 'boolean'
-          int:
-            type: 'integer'
-            default: 12
-
-      atom.config.configDirPath = dotAtomPath
-      atom.config.configFilePath = path.join(atom.config.configDirPath, "atom.config.cson")
-      expect(fs.existsSync(atom.config.configDirPath)).toBeFalsy()
-      fs.writeFileSync atom.config.configFilePath, """
-        global:
-          foo:
-            bar: 'baz'
-            scoped: false
-        '.source.ruby':
-          foo:
-            scoped: true
-      """
-      atom.config.loadUserConfig()
-      atom.config.observeUserConfig()
-      updatedHandler = jasmine.createSpy("updatedHandler")
-      atom.config.onDidChange updatedHandler
-
-    afterEach ->
-      atom.config.unobserveUserConfig()
-      fs.removeSync(dotAtomPath)
-
-    describe "when the config file changes to contain valid cson", ->
-      it "updates the config data", ->
-        fs.writeFileSync(atom.config.configFilePath, "foo: { bar: 'quux', baz: 'bar'}")
-        waitsFor 'update event', -> updatedHandler.callCount > 0
-        runs ->
-          expect(atom.config.get('foo.bar')).toBe 'quux'
-          expect(atom.config.get('foo.baz')).toBe 'bar'
-
-      it "does not fire a change event for paths that did not change", ->
-        atom.config.onDidChange 'foo.bar', noChangeSpy = jasmine.createSpy()
-
-        fs.writeFileSync(atom.config.configFilePath, "foo: { bar: 'baz', baz: 'ok'}")
-        waitsFor 'update event', -> updatedHandler.callCount > 0
-        runs ->
-          expect(noChangeSpy).not.toHaveBeenCalled()
-          expect(atom.config.get('foo.bar')).toBe 'baz'
-          expect(atom.config.get('foo.baz')).toBe 'ok'
-
-      describe 'when the default value is a complex value', ->
-        beforeEach ->
-          atom.config.setSchema 'foo.bar',
-            type: 'array'
-            items:
-              type: 'string'
-          fs.writeFileSync(atom.config.configFilePath, "foo: { bar: ['baz', 'ok']}")
+        atom.config.observeUserConfig()
+        updatedHandler = jasmine.createSpy("updatedHandler")
+        atom.config.onDidChange updatedHandler
+
+      afterEach ->
+        atom.config.unobserveUserConfig()
+        fs.removeSync(dotAtomPath)
+
+      describe "when the config file changes to contain valid cson", ->
+        it "updates the config data", ->
+          fs.writeFileSync(atom.config.configFilePath, "foo: { bar: 'quux', baz: 'bar'}")
           waitsFor 'update event', -> updatedHandler.callCount > 0
-          runs -> updatedHandler.reset()
+          runs ->
+            expect(atom.config.get('foo.bar')).toBe 'quux'
+            expect(atom.config.get('foo.baz')).toBe 'bar'

         it "does not fire a change event for paths that did not change", ->
-          noChangeSpy = jasmine.createSpy()
-          atom.config.onDidChange('foo.bar', noChangeSpy)
-
-          fs.writeFileSync(atom.config.configFilePath, "foo: { bar: ['baz', 'ok'], baz: 'another'}")
+          atom.config.onDidChange 'foo.bar', noChangeSpy = jasmine.createSpy()
+
+          fs.writeFileSync(atom.config.configFilePath, "foo: { bar: 'baz', baz: 'ok'}")
           waitsFor 'update event', -> updatedHandler.callCount > 0
           runs ->
             expect(noChangeSpy).not.toHaveBeenCalled()
-            expect(atom.config.get('foo.bar')).toEqual ['baz', 'ok']
-            expect(atom.config.get('foo.baz')).toBe 'another'
-
-      describe 'when scoped settings are used', ->
-        it "fires a change event for scoped settings that are removed", ->
-          scopedSpy = jasmine.createSpy()
-          atom.config.onDidChange('foo.scoped', scope: ['.source.ruby'], scopedSpy)
-
-          fs.writeFileSync atom.config.configFilePath, """
-            global:
-              foo:
-                scoped: false
-          """
+            expect(atom.config.get('foo.bar')).toBe 'baz'
+            expect(atom.config.get('foo.baz')).toBe 'ok'
+
+        describe 'when the default value is a complex value', ->
+          beforeEach ->
+            atom.config.setSchema 'foo.bar',
+              type: 'array'
+              items:
+                type: 'string'
+            fs.writeFileSync(atom.config.configFilePath, "foo: { bar: ['baz', 'ok']}")
+            waitsFor 'update event', -> updatedHandler.callCount > 0
+            runs -> updatedHandler.reset()
+
+          it "does not fire a change event for paths that did not change", ->
+            noChangeSpy = jasmine.createSpy()
+            atom.config.onDidChange('foo.bar', noChangeSpy)
+
+            fs.writeFileSync(atom.config.configFilePath, "foo: { bar: ['baz', 'ok'], baz: 'another'}")
+            waitsFor 'update event', -> updatedHandler.callCount > 0
+            runs ->
+              expect(noChangeSpy).not.toHaveBeenCalled()
+              expect(atom.config.get('foo.bar')).toEqual ['baz', 'ok']
+              expect(atom.config.get('foo.baz')).toBe 'another'
+
+        describe 'when scoped settings are used', ->
+          it "fires a change event for scoped settings that are removed", ->
+            scopedSpy = jasmine.createSpy()
+            atom.config.onDidChange('foo.scoped', scope: ['.source.ruby'], scopedSpy)
+
+            fs.writeFileSync atom.config.configFilePath, """
+              global:
+                foo:
+                  scoped: false
+            """
+            waitsFor 'update event', -> updatedHandler.callCount > 0
+            runs ->
+              expect(scopedSpy).toHaveBeenCalled()
+              expect(atom.config.get('foo.scoped', scope: ['.source.ruby'])).toBe false
+
+          it "does not fire a change event for paths that did not change", ->
+            noChangeSpy = jasmine.createSpy()
+            atom.config.onDidChange('foo.scoped', scope: ['.source.ruby'], noChangeSpy)
+
+            fs.writeFileSync atom.config.configFilePath, """
+              global:
+                foo:
+                  bar: 'baz'
+              '.source.ruby':
+                foo:
+                  scoped: true
+            """
+            waitsFor 'update event', -> updatedHandler.callCount > 0
+            runs ->
+              expect(noChangeSpy).not.toHaveBeenCalled()
+              expect(atom.config.get('foo.bar', scope: ['.source.ruby'])).toBe 'baz'
+              expect(atom.config.get('foo.scoped', scope: ['.source.ruby'])).toBe true
+
+      describe "when the config file changes to omit a setting with a default", ->
+        it "resets the setting back to the default", ->
+          fs.writeFileSync(atom.config.configFilePath, "foo: { baz: 'new'}")
           waitsFor 'update event', -> updatedHandler.callCount > 0
           runs ->
-            expect(scopedSpy).toHaveBeenCalled()
-            expect(atom.config.get('foo.scoped', scope: ['.source.ruby'])).toBe false
-
-        it "does not fire a change event for paths that did not change", ->
-          noChangeSpy = jasmine.createSpy()
-          atom.config.onDidChange('foo.scoped', scope: ['.source.ruby'], noChangeSpy)
-
-          fs.writeFileSync atom.config.configFilePath, """
-            global:
-              foo:
-                bar: 'baz'
-            '.source.ruby':
-              foo:
-                scoped: true
-          """
+            expect(atom.config.get('foo.bar')).toBe 'def'
+            expect(atom.config.get('foo.baz')).toBe 'new'
+
+      describe "when the config file changes to be empty", ->
+        beforeEach ->
+          fs.writeFileSync(atom.config.configFilePath, "")
           waitsFor 'update event', -> updatedHandler.callCount > 0
+
+        it "resets all settings back to the defaults", ->
+          expect(updatedHandler.callCount).toBe 1
+          expect(atom.config.get('foo.bar')).toBe 'def'
+          atom.config.set("hair", "blonde") # trigger a save
+          expect(atom.config.save).toHaveBeenCalled()
+
+        describe "when the config file subsequently changes again to contain configuration", ->
+          beforeEach ->
+            updatedHandler.reset()
+            fs.writeFileSync(atom.config.configFilePath, "foo: bar: 'newVal'")
+            waitsFor 'update event', -> updatedHandler.callCount > 0
+
+          it "sets the setting to the value specified in the config file", ->
+            expect(atom.config.get('foo.bar')).toBe 'newVal'
+
+      describe "when the config file changes to contain invalid cson", ->
+        beforeEach ->
+          spyOn(console, 'error')
+          fs.writeFileSync(atom.config.configFilePath, "}}}")
+          waitsFor "error to be logged", -> console.error.callCount > 0
+
+        it "logs a warning and does not update config data", ->
+          expect(updatedHandler.callCount).toBe 0
+          expect(atom.config.get('foo.bar')).toBe 'baz'
+          atom.config.set("hair", "blonde") # trigger a save
+          expect(atom.config.save).not.toHaveBeenCalled()
+
+        describe "when the config file subsequently changes again to contain valid cson", ->
+          beforeEach ->
+            fs.writeFileSync(atom.config.configFilePath, "foo: bar: 'newVal'")
+            waitsFor 'update event', -> updatedHandler.callCount > 0
+
+          it "updates the config data and resumes saving", ->
+            atom.config.set("hair", "blonde")
+            expect(atom.config.save).toHaveBeenCalled()
+
+    describe ".initializeConfigDirectory()", ->
+      beforeEach ->
+        if fs.existsSync(dotAtomPath)
+          fs.removeSync(dotAtomPath)
+
+        atom.config.configDirPath = dotAtomPath
+
+      afterEach ->
+        fs.removeSync(dotAtomPath)
+
+      describe "when the configDirPath doesn't exist", ->
+        it "copies the contents of dot-atom to ~/.atom", ->
+          initializationDone = false
+          jasmine.unspy(window, "setTimeout")
+          atom.config.initializeConfigDirectory ->
+            initializationDone = true
+
+          waitsFor -> initializationDone
+
           runs ->
-            expect(noChangeSpy).not.toHaveBeenCalled()
-            expect(atom.config.get('foo.bar', scope: ['.source.ruby'])).toBe 'baz'
-            expect(atom.config.get('foo.scoped', scope: ['.source.ruby'])).toBe true
-
-    describe "when the config file changes to omit a setting with a default", ->
-      it "resets the setting back to the default", ->
-        fs.writeFileSync(atom.config.configFilePath, "foo: { baz: 'new'}")
-        waitsFor 'update event', -> updatedHandler.callCount > 0
-        runs ->
-          expect(atom.config.get('foo.bar')).toBe 'def'
-          expect(atom.config.get('foo.baz')).toBe 'new'
-
-    describe "when the config file changes to be empty", ->
-      beforeEach ->
-        fs.writeFileSync(atom.config.configFilePath, "")
-        waitsFor 'update event', -> updatedHandler.callCount > 0
-
-      it "resets all settings back to the defaults", ->
-        expect(updatedHandler.callCount).toBe 1
-        expect(atom.config.get('foo.bar')).toBe 'def'
-        atom.config.set("hair", "blonde") # trigger a save
-        expect(atom.config.save).toHaveBeenCalled()
-
-      describe "when the config file subsequently changes again to contain configuration", ->
-        beforeEach ->
-          updatedHandler.reset()
-          fs.writeFileSync(atom.config.configFilePath, "foo: bar: 'newVal'")
-          waitsFor 'update event', -> updatedHandler.callCount > 0
-
-        it "sets the setting to the value specified in the config file", ->
-          expect(atom.config.get('foo.bar')).toBe 'newVal'
-
-    describe "when the config file changes to contain invalid cson", ->
-      beforeEach ->
-        spyOn(console, 'error')
-        fs.writeFileSync(atom.config.configFilePath, "}}}")
-        waitsFor "error to be logged", -> console.error.callCount > 0
-
-      it "logs a warning and does not update config data", ->
-        expect(updatedHandler.callCount).toBe 0
-        expect(atom.config.get('foo.bar')).toBe 'baz'
-        atom.config.set("hair", "blonde") # trigger a save
-        expect(atom.config.save).not.toHaveBeenCalled()
-
-      describe "when the config file subsequently changes again to contain valid cson", ->
-        beforeEach ->
-          fs.writeFileSync(atom.config.configFilePath, "foo: bar: 'newVal'")
-          waitsFor 'update event', -> updatedHandler.callCount > 0
-
-        it "updates the config data and resumes saving", ->
-          atom.config.set("hair", "blonde")
-          expect(atom.config.save).toHaveBeenCalled()
-
-  describe "when a schema is specified", ->
-    schema = null
-
-    describe '.setSchema(keyPath, schema)', ->
+            expect(fs.existsSync(atom.config.configDirPath)).toBeTruthy()
+            expect(fs.existsSync(path.join(atom.config.configDirPath, 'packages'))).toBeTruthy()
+            expect(fs.isFileSync(path.join(atom.config.configDirPath, 'snippets.cson'))).toBeTruthy()
+            expect(fs.isFileSync(path.join(atom.config.configDirPath, 'config.cson'))).toBeTruthy()
+            expect(fs.isFileSync(path.join(atom.config.configDirPath, 'init.coffee'))).toBeTruthy()
+            expect(fs.isFileSync(path.join(atom.config.configDirPath, 'styles.less'))).toBeTruthy()
+
+    describe ".pushAtKeyPath(keyPath, value)", ->
+      it "pushes the given value to the array at the key path and updates observers", ->
+        atom.config.set("foo.bar.baz", ["a"])
+        observeHandler = jasmine.createSpy "observeHandler"
+        atom.config.observe "foo.bar.baz", observeHandler
+        observeHandler.reset()
+
+        expect(atom.config.pushAtKeyPath("foo.bar.baz", "b")).toBe 2
+        expect(atom.config.get("foo.bar.baz")).toEqual ["a", "b"]
+        expect(observeHandler).toHaveBeenCalledWith atom.config.get("foo.bar.baz")
+
+    describe ".unshiftAtKeyPath(keyPath, value)", ->
+      it "unshifts the given value to the array at the key path and updates observers", ->
+        atom.config.set("foo.bar.baz", ["b"])
+        observeHandler = jasmine.createSpy "observeHandler"
+        atom.config.observe "foo.bar.baz", observeHandler
+        observeHandler.reset()
+
+        expect(atom.config.unshiftAtKeyPath("foo.bar.baz", "a")).toBe 2
+        expect(atom.config.get("foo.bar.baz")).toEqual ["a", "b"]
+        expect(observeHandler).toHaveBeenCalledWith atom.config.get("foo.bar.baz")
+
+    describe ".removeAtKeyPath(keyPath, value)", ->
+      it "removes the given value from the array at the key path and updates observers", ->
+        atom.config.set("foo.bar.baz", ["a", "b", "c"])
+        observeHandler = jasmine.createSpy "observeHandler"
+        atom.config.observe "foo.bar.baz", observeHandler
+        observeHandler.reset()
+
+        expect(atom.config.removeAtKeyPath("foo.bar.baz", "b")).toEqual ["a", "c"]
+        expect(atom.config.get("foo.bar.baz")).toEqual ["a", "c"]
+        expect(observeHandler).toHaveBeenCalledWith atom.config.get("foo.bar.baz")
+
+    describe ".setDefaults(keyPath, defaults)", ->
+      it "assigns any previously-unassigned keys to the object at the key path", ->
+        atom.config.set("foo.bar.baz", a: 1)
+        atom.config.setDefaults("foo.bar.baz", a: 2, b: 3, c: 4)
+        expect(atom.config.get("foo.bar.baz.a")).toBe 1
+        expect(atom.config.get("foo.bar.baz.b")).toBe 3
+        expect(atom.config.get("foo.bar.baz.c")).toBe 4
+
+        atom.config.setDefaults("foo.quux", x: 0, y: 1)
+        expect(atom.config.get("foo.quux.x")).toBe 0
+        expect(atom.config.get("foo.quux.y")).toBe 1
+
+      it "emits an updated event", ->
+        updatedCallback = jasmine.createSpy('updated')
+        atom.config.onDidChange('foo.bar.baz.a', updatedCallback)
+        expect(updatedCallback.callCount).toBe 0
+        atom.config.setDefaults("foo.bar.baz", a: 2)
+        expect(updatedCallback.callCount).toBe 1
+
+      it "sets a default when the setting's key contains an escaped dot", ->
+        atom.config.setDefaults("foo", 'a\\.b': 1, b: 2)
+        expect(atom.config.get("foo")).toEqual 'a\\.b': 1, b: 2
+
+    describe ".setSchema(keyPath, schema)", ->
       it 'creates a properly nested schema', ->
         schema =
           type: 'object'
@@ -985,286 +981,28 @@
           type: 'integer'
           default: 12

-    describe '.getSchema(keyPath)', ->
-      schema =
-        type: 'object'
-        properties:
-          anInt:
-            type: 'integer'
-            default: 12
-
-      atom.config.setSchema('foo.bar', schema)
-
-      expect(atom.config.getSchema('foo.bar')).toEqual
-        type: 'object'
-        properties:
-          anInt:
-            type: 'integer'
-            default: 12
-
-      expect(atom.config.getSchema('foo.bar.anInt')).toEqual
-        type: 'integer'
-        default: 12
-
-    describe 'when the value has an "integer" type', ->
-      beforeEach ->
-        schema =
-          type: 'integer'
-          default: 12
-        atom.config.setSchema('foo.bar.anInt', schema)
-
-      it 'coerces a string to an int', ->
-        atom.config.set('foo.bar.anInt', '123')
-        expect(atom.config.get('foo.bar.anInt')).toBe 123
-
-      it 'does not allow infinity', ->
-        atom.config.set('foo.bar.anInt', Infinity)
-        expect(atom.config.get('foo.bar.anInt')).toBe 12
-
-      it 'coerces a float to an int', ->
-        atom.config.set('foo.bar.anInt', 12.3)
-        expect(atom.config.get('foo.bar.anInt')).toBe 12
-
-      it 'will not set non-integers', ->
-        atom.config.set('foo.bar.anInt', null)
-        expect(atom.config.get('foo.bar.anInt')).toBe 12
-
-        atom.config.set('foo.bar.anInt', 'nope')
-        expect(atom.config.get('foo.bar.anInt')).toBe 12
-
-      describe 'when the minimum and maximum keys are used', ->
-        beforeEach ->
-          schema =
-            type: 'integer'
-            minimum: 10
-            maximum: 20
-            default: 12
-          atom.config.setSchema('foo.bar.anInt', schema)
-
-        it 'keeps the specified value within the specified range', ->
-          atom.config.set('foo.bar.anInt', '123')
-          expect(atom.config.get('foo.bar.anInt')).toBe 20
-
-          atom.config.set('foo.bar.anInt', '1')
-          expect(atom.config.get('foo.bar.anInt')).toBe 10
-
-    describe 'when the value has an "integer" and "string" type', ->
-      beforeEach ->
-        schema =
-          type: ['integer', 'string']
-          default: 12
-        atom.config.setSchema('foo.bar.anInt', schema)
-
-      it 'can coerce an int, and fallback to a string', ->
-        atom.config.set('foo.bar.anInt', '123')
-        expect(atom.config.get('foo.bar.anInt')).toBe 123
-
-        atom.config.set('foo.bar.anInt', 'cats')
-        expect(atom.config.get('foo.bar.anInt')).toBe 'cats'
-
-    describe 'when the value has an "string" and "boolean" type', ->
-      beforeEach ->
-        schema =
-          type: ['string', 'boolean']
-          default: 'def'
-        atom.config.setSchema('foo.bar', schema)
-
-      it 'can set a string, a boolean, and revert back to the default', ->
-        atom.config.set('foo.bar', 'ok')
-        expect(atom.config.get('foo.bar')).toBe 'ok'
-
-        atom.config.set('foo.bar', false)
-        expect(atom.config.get('foo.bar')).toBe false
-
-        atom.config.set('foo.bar', undefined)
-        expect(atom.config.get('foo.bar')).toBe 'def'
-
-    describe 'when the value has a "number" type', ->
-      beforeEach ->
-        schema =
-          type: 'number'
-          default: 12.1
-        atom.config.setSchema('foo.bar.aFloat', schema)
-
-      it 'coerces a string to a float', ->
-        atom.config.set('foo.bar.aFloat', '12.23')
-        expect(atom.config.get('foo.bar.aFloat')).toBe 12.23
-
-      it 'will not set non-numbers', ->
-        atom.config.set('foo.bar.aFloat', null)
-        expect(atom.config.get('foo.bar.aFloat')).toBe 12.1
-
-        atom.config.set('foo.bar.aFloat', 'nope')
-        expect(atom.config.get('foo.bar.aFloat')).toBe 12.1
-
-      describe 'when the minimum and maximum keys are used', ->
-        beforeEach ->
-          schema =
-            type: 'number'
-            minimum: 11.2
-            maximum: 25.4
-            default: 12.1
-          atom.config.setSchema('foo.bar.aFloat', schema)
-
-        it 'keeps the specified value within the specified range', ->
-          atom.config.set('foo.bar.aFloat', '123.2')
-          expect(atom.config.get('foo.bar.aFloat')).toBe 25.4
-
-          atom.config.set('foo.bar.aFloat', '1.0')
-          expect(atom.config.get('foo.bar.aFloat')).toBe 11.2
-
-    describe 'when the value has a "boolean" type', ->
-      beforeEach ->
-        schema =
-          type: 'boolean'
-          default: true
-        atom.config.setSchema('foo.bar.aBool', schema)
-
-      it 'coerces various types to a boolean', ->
-        atom.config.set('foo.bar.aBool', 'true')
-        expect(atom.config.get('foo.bar.aBool')).toBe true
-        atom.config.set('foo.bar.aBool', 'false')
-        expect(atom.config.get('foo.bar.aBool')).toBe false
-        atom.config.set('foo.bar.aBool', 'TRUE')
-        expect(atom.config.get('foo.bar.aBool')).toBe true
-        atom.config.set('foo.bar.aBool', 'FALSE')
-        expect(atom.config.get('foo.bar.aBool')).toBe false
-        atom.config.set('foo.bar.aBool', 1)
-        expect(atom.config.get('foo.bar.aBool')).toBe false
-        atom.config.set('foo.bar.aBool', 0)
-        expect(atom.config.get('foo.bar.aBool')).toBe false
-        atom.config.set('foo.bar.aBool', {})
-        expect(atom.config.get('foo.bar.aBool')).toBe false
-        atom.config.set('foo.bar.aBool', null)
-        expect(atom.config.get('foo.bar.aBool')).toBe false
-
-      it 'reverts back to the default value when undefined is passed to set', ->
-        atom.config.set('foo.bar.aBool', 'false')
-        expect(atom.config.get('foo.bar.aBool')).toBe false
-
-        atom.config.set('foo.bar.aBool', undefined)
-        expect(atom.config.get('foo.bar.aBool')).toBe true
-
-    describe 'when the value has an "string" type', ->
-      beforeEach ->
-        schema =
-          type: 'string'
-          default: 'ok'
-        atom.config.setSchema('foo.bar.aString', schema)
-
-      it 'allows strings', ->
-        atom.config.set('foo.bar.aString', 'yep')
-        expect(atom.config.get('foo.bar.aString')).toBe 'yep'
-
-      it 'will only set strings', ->
-        expect(atom.config.set('foo.bar.aString', 123)).toBe false
-        expect(atom.config.get('foo.bar.aString')).toBe 'ok'
-
-        expect(atom.config.set('foo.bar.aString', true)).toBe false
-        expect(atom.config.get('foo.bar.aString')).toBe 'ok'
-
-        expect(atom.config.set('foo.bar.aString', null)).toBe false
-        expect(atom.config.get('foo.bar.aString')).toBe 'ok'
-
-        expect(atom.config.set('foo.bar.aString', [])).toBe false
-        expect(atom.config.get('foo.bar.aString')).toBe 'ok'
-
-        expect(atom.config.set('foo.bar.aString', nope: 'nope')).toBe false
-        expect(atom.config.get('foo.bar.aString')).toBe 'ok'
-
-    describe 'when the value has an "object" type', ->
-      beforeEach ->
+      it "allows the schema to be retrieved via ::getSchema", ->
         schema =
           type: 'object'
           properties:
             anInt:
               type: 'integer'
               default: 12
-            nestedObject:
-              type: 'object'
-              properties:
-                nestedBool:
-                  type: 'boolean'
-                  default: false
+
         atom.config.setSchema('foo.bar', schema)

-      it 'converts and validates all the children', ->
-        atom.config.set 'foo.bar',
-          anInt: '23'
-          nestedObject:
-            nestedBool: 'true'
-        expect(atom.config.get('foo.bar')).toEqual
-          anInt: 23
-          nestedObject:
-            nestedBool: true
-
-      it 'will set only the values that adhere to the schema', ->
-        expect(atom.config.set 'foo.bar',
-          anInt: 'nope'
-          nestedObject:
-            nestedBool: true
-        ).toBe true
-        expect(atom.config.get('foo.bar.anInt')).toEqual 12
-        expect(atom.config.get('foo.bar.nestedObject.nestedBool')).toEqual true
-
-    describe 'when the value has an "array" type', ->
-      beforeEach ->
-        schema =
-          type: 'array'
-          default: [1, 2, 3]
-          items:
-            type: 'integer'
-        atom.config.setSchema('foo.bar', schema)
-
-      it 'converts an array of strings to an array of ints', ->
-        atom.config.set 'foo.bar', ['2', '3', '4']
-        expect(atom.config.get('foo.bar')).toEqual  [2, 3, 4]
-
-    describe 'when the `enum` key is used', ->
-      beforeEach ->
-        schema =
+        expect(atom.config.getSchema('foo.bar')).toEqual
           type: 'object'
           properties:
-            str:
-              type: 'string'
-              default: 'ok'
-              enum: ['ok', 'one', 'two']
-            int:
+            anInt:
               type: 'integer'
-              default: 2
-              enum: [2, 3, 5]
-            arr:
-              type: 'array'
-              default: ['one', 'two']
-              items:
-                type: 'string'
-                enum: ['one', 'two', 'three']
-
-        atom.config.setSchema('foo.bar', schema)
-
-      it 'will only set a string when the string is in the enum values', ->
-        expect(atom.config.set('foo.bar.str', 'nope')).toBe false
-        expect(atom.config.get('foo.bar.str')).toBe 'ok'
-
-        expect(atom.config.set('foo.bar.str', 'one')).toBe true
-        expect(atom.config.get('foo.bar.str')).toBe 'one'
-
-      it 'will only set an integer when the integer is in the enum values', ->
-        expect(atom.config.set('foo.bar.int', '400')).toBe false
-        expect(atom.config.get('foo.bar.int')).toBe 2
-
-        expect(atom.config.set('foo.bar.int', '3')).toBe true
-        expect(atom.config.get('foo.bar.int')).toBe 3
-
-      it 'will only set an array when the array values are in the enum values', ->
-        expect(atom.config.set('foo.bar.arr', ['one', 'five'])).toBe true
-        expect(atom.config.get('foo.bar.arr')).toEqual ['one']
-
-        expect(atom.config.set('foo.bar.arr', ['two', 'three'])).toBe true
-        expect(atom.config.get('foo.bar.arr')).toEqual ['two', 'three']
-
-    describe "when scoped settings are used", ->
-      beforeEach ->
+              default: 12
+
+        expect(atom.config.getSchema('foo.bar.anInt')).toEqual
+          type: 'integer'
+          default: 12
+
+      it "respects the schema for scoped settings", ->
         schema =
           type: 'string'
           default: 'ok'
@@ -1273,149 +1011,395 @@
               default: 'omg'
         atom.config.setSchema('foo.bar.str', schema)

-      it 'it respects the scoped defaults', ->
         expect(atom.config.get('foo.bar.str')).toBe 'ok'
         expect(atom.config.get('foo.bar.str', scope: ['.source.js'])).toBe 'omg'
         expect(atom.config.get('foo.bar.str', scope: ['.source.coffee'])).toBe 'ok'

-  describe "scoped settings", ->
-    describe ".get(scopeDescriptor, keyPath)", ->
-      it "returns the property with the most specific scope selector", ->
-        atom.config.addScopedSettings("config", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42)
-        atom.config.addScopedSettings("config", ".source .string.quoted.double", foo: bar: baz: 22)
-        atom.config.addScopedSettings("config", ".source", foo: bar: baz: 11)
-
-        expect(atom.config.get("foo.bar.baz", scope: [".source.coffee", ".string.quoted.double.coffee"])).toBe 42
-        expect(atom.config.get("foo.bar.baz", scope: [".source.js", ".string.quoted.double.js"])).toBe 22
-        expect(atom.config.get("foo.bar.baz", scope: [".source.js", ".variable.assignment.js"])).toBe 11
-        expect(atom.config.get("foo.bar.baz", scope: [".text"])).toBeUndefined()
-
-      it "favors the most recently added properties in the event of a specificity tie", ->
-        atom.config.addScopedSettings("config", ".source.coffee .string.quoted.single", foo: bar: baz: 42)
-        atom.config.addScopedSettings("config", ".source.coffee .string.quoted.double", foo: bar: baz: 22)
-
-        expect(atom.config.get("foo.bar.baz", scope: [".source.coffee", ".string.quoted.single"])).toBe 42
-        expect(atom.config.get("foo.bar.baz", scope: [".source.coffee", ".string.quoted.single.double"])).toBe 22
-
-      describe 'when there are global defaults', ->
-        it 'falls back to the global when there is no scoped property specified', ->
-          atom.config.setDefaults("foo", hasDefault: 'ok')
-          expect(atom.config.get("foo.hasDefault", scope: [".source.coffee", ".string.quoted.single"])).toBe 'ok'
-
-      describe 'setting priority', ->
-        describe 'when package settings are added after user settings', ->
-          it "returns the user's setting because the user's setting has higher priority", ->
-            atom.config.set("foo.bar.baz", 100, scopeSelector: ".source.coffee")
-            atom.config.addScopedSettings("some-package", ".source.coffee", foo: bar: baz: 1)
-            expect(atom.config.get("foo.bar.baz", scope: [".source.coffee"])).toBe 100
-
-    describe ".set(scope, keyPath, value)", ->
-      it "sets the value and overrides the others", ->
-        atom.config.addScopedSettings("config", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42)
-        atom.config.addScopedSettings("config", ".source .string.quoted.double", foo: bar: baz: 22)
-        atom.config.addScopedSettings("config", ".source", foo: bar: baz: 11)
-
-        expect(atom.config.get("foo.bar.baz", scope: [".source.coffee", ".string.quoted.double.coffee"])).toBe 42
-
-        expect(atom.config.set("foo.bar.baz", 100, scopeSelector: ".source.coffee .string.quoted.double.coffee")).toBe true
-        expect(atom.config.get("foo.bar.baz", scope: [".source.coffee", ".string.quoted.double.coffee"])).toBe 100
-
-    describe ".removeScopedSettingsForName(name)", ->
-      it "allows properties to be removed by name", ->
-        disposable1 = atom.config.addScopedSettings("a", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42)
-        disposable2 = atom.config.addScopedSettings("b", ".source .string.quoted.double", foo: bar: baz: 22)
-
-        disposable2.dispose()
-        expect(atom.config.get("foo.bar.baz", scope: [".source.js", ".string.quoted.double.js"])).toBeUndefined()
-        expect(atom.config.get("foo.bar.baz", scope: [".source.coffee", ".string.quoted.double.coffee"])).toBe 42
-
-    describe ".observe(scopeDescriptor, keyPath)", ->
-      it 'calls the supplied callback when the value at the descriptor/keypath changes', ->
-        changeSpy = jasmine.createSpy()
-        atom.config.observe("foo.bar.baz", scope: [".source.coffee", ".string.quoted.double.coffee"], changeSpy)
-        expect(changeSpy).toHaveBeenCalledWith(undefined)
-        changeSpy.reset()
-
-        atom.config.set("foo.bar.baz", 12)
-        expect(changeSpy).toHaveBeenCalledWith(12)
-        changeSpy.reset()
-
-        disposable1 = atom.config.addScopedSettings("a", ".source .string.quoted.double", foo: bar: baz: 22)
-        expect(changeSpy).toHaveBeenCalledWith(22)
-        changeSpy.reset()
-
-        disposable2 = atom.config.addScopedSettings("b", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42)
-        expect(changeSpy).toHaveBeenCalledWith(42)
-        changeSpy.reset()
-
-        disposable2.dispose()
-        expect(changeSpy).toHaveBeenCalledWith(22)
-        changeSpy.reset()
-
-        disposable1.dispose()
-        expect(changeSpy).toHaveBeenCalledWith(12)
-        changeSpy.reset()
-
-        atom.config.set("foo.bar.baz", undefined)
-        expect(changeSpy).toHaveBeenCalledWith(undefined)
-        changeSpy.reset()
-
-    describe ".onDidChange(scopeDescriptor, keyPath)", ->
-      it 'calls the supplied callback when the value at the descriptor/keypath changes', ->
-        keyPath = "foo.bar.baz"
-        changeSpy = jasmine.createSpy('onDidChange callback')
-        atom.config.onDidChange keyPath, scope: [".source.coffee", ".string.quoted.double.coffee"], changeSpy
-
-        atom.config.set("foo.bar.baz", 12)
-        expect(changeSpy).toHaveBeenCalledWith({oldValue: undefined, newValue: 12})
-        changeSpy.reset()
-
-        disposable1 = atom.config.addScopedSettings("a", ".source .string.quoted.double", foo: bar: baz: 22)
-        expect(changeSpy).toHaveBeenCalledWith({oldValue: 12, newValue: 22})
-        changeSpy.reset()
-
-        disposable2 = atom.config.addScopedSettings("b", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42)
-        expect(changeSpy).toHaveBeenCalledWith({oldValue: 22, newValue: 42})
-        changeSpy.reset()
-
-        disposable2.dispose()
-        expect(changeSpy).toHaveBeenCalledWith({oldValue: 42, newValue: 22})
-        changeSpy.reset()
-
-        disposable1.dispose()
-        expect(changeSpy).toHaveBeenCalledWith({oldValue: 22, newValue: 12})
-        changeSpy.reset()
-
-        atom.config.set("foo.bar.baz", undefined)
-        expect(changeSpy).toHaveBeenCalledWith({oldValue: 12, newValue: undefined})
-        changeSpy.reset()
-
-      it 'deprecates using a scope descriptor as an optional first argument', ->
-        keyPath = "foo.bar.baz"
+      describe 'when the value has an "integer" type', ->
+        beforeEach ->
+          schema =
+            type: 'integer'
+            default: 12
+          atom.config.setSchema('foo.bar.anInt', schema)
+
+        it 'coerces a string to an int', ->
+          atom.config.set('foo.bar.anInt', '123')
+          expect(atom.config.get('foo.bar.anInt')).toBe 123
+
+        it 'does not allow infinity', ->
+          atom.config.set('foo.bar.anInt', Infinity)
+          expect(atom.config.get('foo.bar.anInt')).toBe 12
+
+        it 'coerces a float to an int', ->
+          atom.config.set('foo.bar.anInt', 12.3)
+          expect(atom.config.get('foo.bar.anInt')).toBe 12
+
+        it 'will not set non-integers', ->
+          atom.config.set('foo.bar.anInt', null)
+          expect(atom.config.get('foo.bar.anInt')).toBe 12
+
+          atom.config.set('foo.bar.anInt', 'nope')
+          expect(atom.config.get('foo.bar.anInt')).toBe 12
+
+        describe 'when the minimum and maximum keys are used', ->
+          beforeEach ->
+            schema =
+              type: 'integer'
+              minimum: 10
+              maximum: 20
+              default: 12
+            atom.config.setSchema('foo.bar.anInt', schema)
+
+          it 'keeps the specified value within the specified range', ->
+            atom.config.set('foo.bar.anInt', '123')
+            expect(atom.config.get('foo.bar.anInt')).toBe 20
+
+            atom.config.set('foo.bar.anInt', '1')
+            expect(atom.config.get('foo.bar.anInt')).toBe 10
+
+      describe 'when the value has an "integer" and "string" type', ->
+        beforeEach ->
+          schema =
+            type: ['integer', 'string']
+            default: 12
+          atom.config.setSchema('foo.bar.anInt', schema)
+
+        it 'can coerce an int, and fallback to a string', ->
+          atom.config.set('foo.bar.anInt', '123')
+          expect(atom.config.get('foo.bar.anInt')).toBe 123
+
+          atom.config.set('foo.bar.anInt', 'cats')
+          expect(atom.config.get('foo.bar.anInt')).toBe 'cats'
+
+      describe 'when the value has an "string" and "boolean" type', ->
+        beforeEach ->
+          schema =
+            type: ['string', 'boolean']
+            default: 'def'
+          atom.config.setSchema('foo.bar', schema)
+
+        it 'can set a string, a boolean, and revert back to the default', ->
+          atom.config.set('foo.bar', 'ok')
+          expect(atom.config.get('foo.bar')).toBe 'ok'
+
+          atom.config.set('foo.bar', false)
+          expect(atom.config.get('foo.bar')).toBe false
+
+          atom.config.set('foo.bar', undefined)
+          expect(atom.config.get('foo.bar')).toBe 'def'
+
+      describe 'when the value has a "number" type', ->
+        beforeEach ->
+          schema =
+            type: 'number'
+            default: 12.1
+          atom.config.setSchema('foo.bar.aFloat', schema)
+
+        it 'coerces a string to a float', ->
+          atom.config.set('foo.bar.aFloat', '12.23')
+          expect(atom.config.get('foo.bar.aFloat')).toBe 12.23
+
+        it 'will not set non-numbers', ->
+          atom.config.set('foo.bar.aFloat', null)
+          expect(atom.config.get('foo.bar.aFloat')).toBe 12.1
+
+          atom.config.set('foo.bar.aFloat', 'nope')
+          expect(atom.config.get('foo.bar.aFloat')).toBe 12.1
+
+        describe 'when the minimum and maximum keys are used', ->
+          beforeEach ->
+            schema =
+              type: 'number'
+              minimum: 11.2
+              maximum: 25.4
+              default: 12.1
+            atom.config.setSchema('foo.bar.aFloat', schema)
+
+          it 'keeps the specified value within the specified range', ->
+            atom.config.set('foo.bar.aFloat', '123.2')
+            expect(atom.config.get('foo.bar.aFloat')).toBe 25.4
+
+            atom.config.set('foo.bar.aFloat', '1.0')
+            expect(atom.config.get('foo.bar.aFloat')).toBe 11.2
+
+      describe 'when the value has a "boolean" type', ->
+        beforeEach ->
+          schema =
+            type: 'boolean'
+            default: true
+          atom.config.setSchema('foo.bar.aBool', schema)
+
+        it 'coerces various types to a boolean', ->
+          atom.config.set('foo.bar.aBool', 'true')
+          expect(atom.config.get('foo.bar.aBool')).toBe true
+          atom.config.set('foo.bar.aBool', 'false')
+          expect(atom.config.get('foo.bar.aBool')).toBe false
+          atom.config.set('foo.bar.aBool', 'TRUE')
+          expect(atom.config.get('foo.bar.aBool')).toBe true
+          atom.config.set('foo.bar.aBool', 'FALSE')
+          expect(atom.config.get('foo.bar.aBool')).toBe false
+          atom.config.set('foo.bar.aBool', 1)
+          expect(atom.config.get('foo.bar.aBool')).toBe false
+          atom.config.set('foo.bar.aBool', 0)
+          expect(atom.config.get('foo.bar.aBool')).toBe false
+          atom.config.set('foo.bar.aBool', {})
+          expect(atom.config.get('foo.bar.aBool')).toBe false
+          atom.config.set('foo.bar.aBool', null)
+          expect(atom.config.get('foo.bar.aBool')).toBe false
+
+        it 'reverts back to the default value when undefined is passed to set', ->
+          atom.config.set('foo.bar.aBool', 'false')
+          expect(atom.config.get('foo.bar.aBool')).toBe false
+
+          atom.config.set('foo.bar.aBool', undefined)
+          expect(atom.config.get('foo.bar.aBool')).toBe true
+
+      describe 'when the value has an "string" type', ->
+        beforeEach ->
+          schema =
+            type: 'string'
+            default: 'ok'
+          atom.config.setSchema('foo.bar.aString', schema)
+
+        it 'allows strings', ->
+          atom.config.set('foo.bar.aString', 'yep')
+          expect(atom.config.get('foo.bar.aString')).toBe 'yep'
+
+        it 'will only set strings', ->
+          expect(atom.config.set('foo.bar.aString', 123)).toBe false
+          expect(atom.config.get('foo.bar.aString')).toBe 'ok'
+
+          expect(atom.config.set('foo.bar.aString', true)).toBe false
+          expect(atom.config.get('foo.bar.aString')).toBe 'ok'
+
+          expect(atom.config.set('foo.bar.aString', null)).toBe false
+          expect(atom.config.get('foo.bar.aString')).toBe 'ok'
+
+          expect(atom.config.set('foo.bar.aString', [])).toBe false
+          expect(atom.config.get('foo.bar.aString')).toBe 'ok'
+
+          expect(atom.config.set('foo.bar.aString', nope: 'nope')).toBe false
+          expect(atom.config.get('foo.bar.aString')).toBe 'ok'
+
+      describe 'when the value has an "object" type', ->
+        beforeEach ->
+          schema =
+            type: 'object'
+            properties:
+              anInt:
+                type: 'integer'
+                default: 12
+              nestedObject:
+                type: 'object'
+                properties:
+                  nestedBool:
+                    type: 'boolean'
+                    default: false
+          atom.config.setSchema('foo.bar', schema)
+
+        it 'converts and validates all the children', ->
+          atom.config.set 'foo.bar',
+            anInt: '23'
+            nestedObject:
+              nestedBool: 'true'
+          expect(atom.config.get('foo.bar')).toEqual
+            anInt: 23
+            nestedObject:
+              nestedBool: true
+
+        it 'will set only the values that adhere to the schema', ->
+          expect(atom.config.set 'foo.bar',
+            anInt: 'nope'
+            nestedObject:
+              nestedBool: true
+          ).toBe true
+          expect(atom.config.get('foo.bar.anInt')).toEqual 12
+          expect(atom.config.get('foo.bar.nestedObject.nestedBool')).toEqual true
+
+      describe 'when the value has an "array" type', ->
+        beforeEach ->
+          schema =
+            type: 'array'
+            default: [1, 2, 3]
+            items:
+              type: 'integer'
+          atom.config.setSchema('foo.bar', schema)
+
+        it 'converts an array of strings to an array of ints', ->
+          atom.config.set 'foo.bar', ['2', '3', '4']
+          expect(atom.config.get('foo.bar')).toEqual  [2, 3, 4]
+
+      describe 'when the `enum` key is used', ->
+        beforeEach ->
+          schema =
+            type: 'object'
+            properties:
+              str:
+                type: 'string'
+                default: 'ok'
+                enum: ['ok', 'one', 'two']
+              int:
+                type: 'integer'
+                default: 2
+                enum: [2, 3, 5]
+              arr:
+                type: 'array'
+                default: ['one', 'two']
+                items:
+                  type: 'string'
+                  enum: ['one', 'two', 'three']
+
+          atom.config.setSchema('foo.bar', schema)
+
+        it 'will only set a string when the string is in the enum values', ->
+          expect(atom.config.set('foo.bar.str', 'nope')).toBe false
+          expect(atom.config.get('foo.bar.str')).toBe 'ok'
+
+          expect(atom.config.set('foo.bar.str', 'one')).toBe true
+          expect(atom.config.get('foo.bar.str')).toBe 'one'
+
+        it 'will only set an integer when the integer is in the enum values', ->
+          expect(atom.config.set('foo.bar.int', '400')).toBe false
+          expect(atom.config.get('foo.bar.int')).toBe 2
+
+          expect(atom.config.set('foo.bar.int', '3')).toBe true
+          expect(atom.config.get('foo.bar.int')).toBe 3
+
+        it 'will only set an array when the array values are in the enum values', ->
+          expect(atom.config.set('foo.bar.arr', ['one', 'five'])).toBe true
+          expect(atom.config.get('foo.bar.arr')).toEqual ['one']
+
+          expect(atom.config.set('foo.bar.arr', ['two', 'three'])).toBe true
+          expect(atom.config.get('foo.bar.arr')).toEqual ['two', 'three']
+
+  describe "Deprecated Methods", ->
+    describe ".getDefault(keyPath)", ->
+      it "returns a clone of the default value", ->
+        atom.config.setDefaults("foo", same: 1, changes: 1)
+
         spyOn(Grim, 'deprecate')
-        atom.config.onDidChange [".source.coffee", ".string.quoted.double.coffee"], keyPath, changeSpy = jasmine.createSpy()
-        expect(Grim.deprecate).toHaveBeenCalled()
-
-        atom.config.set("foo.bar.baz", 12)
-        expect(changeSpy).toHaveBeenCalledWith({oldValue: undefined, newValue: 12})
-        changeSpy.reset()
-
-        disposable1 = atom.config.addScopedSettings("a", ".source .string.quoted.double", foo: bar: baz: 22)
-        expect(changeSpy).toHaveBeenCalledWith({oldValue: 12, newValue: 22})
-        changeSpy.reset()
-
-        disposable2 = atom.config.addScopedSettings("b", ".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42)
-        expect(changeSpy).toHaveBeenCalledWith({oldValue: 22, newValue: 42})
-        changeSpy.reset()
-
-        disposable2.dispose()
-        expect(changeSpy).toHaveBeenCalledWith({oldValue: 42, newValue: 22})
-        changeSpy.reset()
-
-        disposable1.dispose()
-        expect(changeSpy).toHaveBeenCalledWith({oldValue: 22, newValue: 12})
-        changeSpy.reset()
-
-        atom.config.set("foo.bar.baz", undefined)
-        expect(changeSpy).toHaveBeenCalledWith({oldValue: 12, newValue: undefined})
-        changeSpy.reset()
+        expect(atom.config.getDefault('foo.same')).toBe 1
+        expect(atom.config.getDefault('foo.changes')).toBe 1
+        expect(Grim.deprecate.callCount).toBe 2
+
+        atom.config.set('foo.same', 2)
+        atom.config.set('foo.changes', 3)
+
+        expect(atom.config.getDefault('foo.same')).toBe 1
+        expect(atom.config.getDefault('foo.changes')).toBe 1
+        expect(Grim.deprecate.callCount).toBe 4
+
+        initialDefaultValue = [1, 2, 3]
+        atom.config.setDefaults("foo", bar: initialDefaultValue)
+        expect(atom.config.getDefault('foo.bar')).toEqual initialDefaultValue
+        expect(atom.config.getDefault('foo.bar')).not.toBe initialDefaultValue
+        expect(Grim.deprecate.callCount).toBe 6
+
+      describe "when scoped settings are used", ->
+        it "returns the global default when no scoped default set", ->
+          atom.config.setDefaults("foo", bar: baz: 10)
+
+          spyOn(Grim, 'deprecate')
+          expect(atom.config.getDefault('.source.coffee', 'foo.bar.baz')).toBe 10
+          expect(Grim.deprecate).toHaveBeenCalled()
+
+        it "returns the scoped settings not including the user's config file", ->
+          atom.config.setDefaults("foo", bar: baz: 10)
+          atom.config.addScopedSettings("default", ".source.coffee", foo: bar: baz: 42)
+
+          spyOn(Grim, 'deprecate')
+          expect(atom.config.getDefault('.source.coffee', 'foo.bar.baz')).toBe 42
+          expect(Grim.deprecate.callCount).toBe 1
+
+          atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
+          expect(atom.config.getDefault('.source.coffee', 'foo.bar.baz')).toBe 42
+          expect(Grim.deprecate.callCount).toBe 2
+
+    describe ".isDefault(keyPath)", ->
+      it "returns true when the value of the key path is its default value", ->
+        atom.config.setDefaults("foo", same: 1, changes: 1)
+
+        spyOn(Grim, 'deprecate')
+        expect(atom.config.isDefault('foo.same')).toBe true
+        expect(atom.config.isDefault('foo.changes')).toBe true
+        expect(Grim.deprecate.callCount).toBe 2
+
+        atom.config.set('foo.same', 2)
+        atom.config.set('foo.changes', 3)
+
+        expect(atom.config.isDefault('foo.same')).toBe false
+        expect(atom.config.isDefault('foo.changes')).toBe false
+        expect(Grim.deprecate.callCount).toBe 4
+
+      describe "when scoped settings are used", ->
+        it "returns false when a scoped setting was set by the user", ->
+          spyOn(Grim, 'deprecate')
+          expect(atom.config.isDefault('.source.coffee', 'foo.bar.baz')).toBe true
+          expect(Grim.deprecate.callCount).toBe 1
+
+          atom.config.addScopedSettings("default", ".source.coffee", foo: bar: baz: 42)
+          expect(atom.config.isDefault('.source.coffee', 'foo.bar.baz')).toBe true
+          expect(Grim.deprecate.callCount).toBe 2
+
+          atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
+          expect(atom.config.isDefault('.source.coffee', 'foo.bar.baz')).toBe false
+          expect(Grim.deprecate.callCount).toBe 3
+
+    describe ".toggle(keyPath)", ->
+      beforeEach ->
+        jasmine.snapshotDeprecations()
+
+      afterEach ->
+        jasmine.restoreDeprecationsSnapshot()
+
+      it "negates the boolean value of the current key path value", ->
+        atom.config.set('foo.a', 1)
+        atom.config.toggle('foo.a')
+        expect(atom.config.get('foo.a')).toBe false
+
+        atom.config.set('foo.a', '')
+        atom.config.toggle('foo.a')
+        expect(atom.config.get('foo.a')).toBe true
+
+        atom.config.set('foo.a', null)
+        atom.config.toggle('foo.a')
+        expect(atom.config.get('foo.a')).toBe true
+
+        atom.config.set('foo.a', true)
+        atom.config.toggle('foo.a')
+        expect(atom.config.get('foo.a')).toBe false
+
+    describe ".getSettings()", ->
+      it "returns all settings including defaults", ->
+        atom.config.setDefaults("foo", bar: baz: 10)
+        atom.config.set("foo.ok", 12)
+
+        jasmine.snapshotDeprecations()
+        expect(atom.config.getSettings().foo).toEqual
+          ok: 12
+          bar:
+            baz: 10
+        jasmine.restoreDeprecationsSnapshot()
+
+    describe ".getPositiveInt(keyPath, defaultValue)", ->
+      beforeEach ->
+        jasmine.snapshotDeprecations()
+
+      afterEach ->
+        jasmine.restoreDeprecationsSnapshot()
+
+      it "returns the proper coerced value", ->
+        atom.config.set('editor.preferredLineLength', 0)
+        expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 1
+
+      it "returns the proper coerced value", ->
+        atom.config.set('editor.preferredLineLength', -1234)
+        expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 1
+
+      it "returns the default value when a string is passed in", ->
+        atom.config.set('editor.preferredLineLength', 'abcd')
+        expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80
+
+      it "returns the default value when null is passed in", ->
+        atom.config.set('editor.preferredLineLength', null)
+        expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80


Update spec description

Update spec description

--- spec/editor-spec.coffee
+++ spec/editor-spec.coffee
@@ -2530,7 +2530,7 @@
             expect(editor.indentationForBufferRow(2)).toBe 1

           describe "when the cursor is before whitespace", ->
-            it "indents the whitespace properly on the new line", ->
+            it "retains the whitespace following the cursor on the new line", ->
               editor.setText("  var sort = function() {}")
               editor.setCursorScreenPosition([0, 23])
               editor.insertNewline()


Kill `window.loadPackage` spec helper. We use built-in methods now.

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -92,23 +92,13 @@
   syntax.off()
   waits(0) # yield to ui thread to make screen update more frequently

-window.loadPackage = (name, options={}) ->
-  Package = require 'package'
-  packagePath = _.find atom.getPackagePaths(), (packagePath) -> fs.base(packagePath) == name
-  if pack = Package.build(packagePath)
-    pack.load(options)
-    atom.loadedPackages.push(pack)
-    pack.deferActivation = false if options.activateImmediately
-    pack.activate()
-  pack
-
 # Specs rely on TextMate bundles (but not atom packages)
 window.loadTextMatePackages = ->
   TextMatePackage = require 'text-mate-package'
   config.packageDirPaths.unshift(fixturePackagesPath)
   window.textMatePackages = []
   for path in atom.getPackagePaths() when TextMatePackage.testName(path)
-    window.textMatePackages.push window.loadPackage(fs.base(path), sync: true)
+    window.textMatePackages.push atom.activatePackage(path, sync: true)

 window.loadTextMatePackages()


Added Mongo Placeholder

Added Mongo Placeholder

error: pathspec 'help/mongodb.coffee' did not match any file(s) known to git

--- help/mongodb.coffee
+++ help/mongodb.coffee
@@ -0,0 +1,6 @@
+#
+# # MongoDB Help
+#
+module.exports = """
+Help Coming Soon
+"""


Make Convert a global

Make Convert a global

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/rafalprzywarski/test-status.git/'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

error: pathspec 'test/color2.coffee' did not match any file(s) known to git

--- test/color2.coffee
+++ test/color2.coffee
@@ -0,0 +1,5 @@
+module "Color2"
+
+
+
+module()


Kill console.log

Kill console.log

--- src/stdlib/require.coffee
+++ src/stdlib/require.coffee
@@ -109,7 +109,6 @@
   tmpPath = "/tmp/atom-compiled-scripts"
   cacheFilePath = [tmpPath, $native.md5ForPath(filePath)].join("/")

-  console.log cacheFilePath
   if __exists(cacheFilePath)
     __read(cacheFilePath)
   else


Use skinny arrows

Use skinny arrows

--- src/workspace-view.coffee
+++ src/workspace-view.coffee
@@ -372,13 +372,13 @@
   beforeRemove: ->
     @model.destroy()

-  setEditorFontSize: (fontSize) =>
+  setEditorFontSize: (fontSize) ->
     atom.themes.updateGlobalEditorStyle('font-size', fontSize + 'px')

-  setEditorFontFamily: (fontFamily) =>
+  setEditorFontFamily: (fontFamily) ->
     atom.themes.updateGlobalEditorStyle('font-family', fontFamily)

-  setEditorLineHeight: (lineHeight) =>
+  setEditorLineHeight: (lineHeight) ->
     atom.themes.updateGlobalEditorStyle('line-height', lineHeight)

   # Deprecated


usage and example in js

usage and example in js

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/AppGyver/supersonic.git/'


Remove karma file

Remove karma file

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/leandrooriente/racha-conta.git/'


arXiv search

arXiv search

error: pathspec 'scripts/arxiv.coffee' did not match any file(s) known to git

--- scripts/arxiv.coffee
+++ scripts/arxiv.coffee
@@ -0,0 +1,43 @@
+# Description:
+#   Search today's postings on astro-ph
+
+cheerio = require('cheerio')
+
+String::strip = -> if String::trim? then @trim() else @replace /^\s+|\s+$/g, ""
+
+module.exports = (robot) ->
+    robot.respond /arxiv (.*)/i, (msg) ->
+        text = []
+        keyWords = msg.match[1].split(' ')
+        text.push "Today's astro-ph postings for: " + keyWords.join(', ')
+        text.push ""
+        msg.http('https://arxiv.org/list/astro-ph/new').get() (error, response, body) ->
+            # Load page
+            page = cheerio.load(body)
+            # Ignore children function
+            page.fn.ignore = (sel) ->
+                @clone().find(sel or '>*').remove().end()
+            # Get papers on the agenda
+            papers = page('span.list-identifier')
+            gotNone = true
+            papers.each (elem) ->
+                # Scrape text
+                listing     = page(this).parent()
+                url         = 'https://arxiv.org' + listing.children('span.list-identifier').children('a').first().attr('href')
+                title       = listing.next().children('div.meta').children('div.list-title').ignore('span').text()
+                authors     = listing.next().children('div.meta').children('div.list-authors').ignore('span').text()
+                abstract    = listing.next().children('div.meta').children('p').text()
+                searchText  = title + abstract
+                # Check if keywords in text
+                inText = (word) ->
+                    searchText.includes(word)
+                if keyWords.every(inText)
+                    gotNone = false
+                    text.push '*' + title.strip() + '*'
+                    text.push '_' + authors.strip().replace(/(\r\n|\n|\r)/gm,"") + '_'
+                    text.push abstract.strip().replace(/(\r\n|\n|\r)/gm," ")
+                    text.push url.strip()
+                    text.push ''
+            if gotNone
+                text.push "Nothing found :'("
+            msg.send text.join('\n')


Add like score

Add like score

error: pathspec 'scripts/liked.coffee' did not match any file(s) known to git

--- scripts/liked.coffee
+++ scripts/liked.coffee
@@ -0,0 +1,46 @@
+# Description:
+#   In which Hubot tells you who is more liked
+#
+# Dependencies:
+#   None
+#
+# Configuration:
+#   None
+#
+# Commands:
+#   hubot like score - Asks hubot the like score
+#
+# Author:
+#   greenb
+
+Q = require 'q'
+request = require 'request'
+
+getLikes = (user) ->
+  options = {
+    url: "https://forum.bigfix.com/users/#{user}.json"
+    strictSSL: false,
+  }
+
+  returnLikes = (res, body) ->
+    for stat in JSON.parse(body).user.stats
+      if stat.action_type == 2
+        return stat.count
+
+    throw new Error('no like count in forum response')
+
+  return Q.nfcall(request, options).spread(returnLikes);
+
+module.exports = (robot) ->
+
+  robot.respond /like score/i, (msg) ->
+    respond = (dhwang, dexdexdex) ->
+      msg.send "dexdexdex: #{dexdexdex}, dhwang: #{dhwang}"
+
+    onError = (err) ->
+      msg.send "Failed to get likes: #{err.toString()}"
+
+    Q.all(['dhwang', 'dexdexdex'].map(getLikes))
+      .spread(respond)
+      .fail(onError)
+      .done();


Called atlas.destroy() on reset.

Called atlas.destroy() on reset.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/urbanetic/meteor-atlas-util.git/'


trigger project:load

trigger project:load

--- src/atom/project.coffee
+++ src/atom/project.coffee
@@ -3,6 +3,8 @@
 fs = require 'fs'
 Resource = require 'resource'

+# Events:
+#   project:load (project) -> Called when a project is loaded.
 module.exports =
 class Project extends Resource
   window.resourceTypes.push this
@@ -15,3 +17,6 @@

     @url = url
     @show()
+    atom.trigger 'project:load', this
+
+    true


Update config.coffee

Update config.coffee

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/neophylon/sbadmin.git/'


Server: using 'process.nextTick()' when initializing router

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/SNASW/rendr.git/'


refactoring karma.config

refactoring karma.config

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/cbragard/angular-sid.git/'


Preloaded previous/next heroes in ChooseHeroView.

--- app/views/game-menu/ChooseHeroView.coffee
+++ app/views/game-menu/ChooseHeroView.coffee
@@ -17,8 +17,8 @@
     'change #option-code-language': 'onCodeLanguageChanged'

   shortcuts:
-    'left': -> @$el.find('#hero-carousel').carousel('prev') unless @$el.hasClass 'secret'
-    'right': -> @$el.find('#hero-carousel').carousel('next') unless @$el.hasClass 'secret'
+    'left': -> @$el.find('#hero-carousel').carousel('prev') if @heroes.models.length and not @$el.hasClass 'secret'
+    'right': -> @$el.find('#hero-carousel').carousel('next') if @heroes.models.length and not @$el.hasClass 'secret'

   constructor: (options) ->
     super options
@@ -69,6 +69,7 @@
     direction = e.direction  # 'left' or 'right'
     heroItem = $(e.relatedTarget)
     hero = _.find @heroes.models, (hero) -> hero.get('original') is heroItem.data('hero-id')
+    return console.error "Couldn't find hero from heroItem:", heroItem unless hero
     heroIndex = heroItem.index()
     @$el.find('.hero-indicator').each ->
       distance = Math.min 3, Math.abs $(@).index() - heroIndex
@@ -77,19 +78,33 @@
     heroInfo = temporaryHeroInfo[hero.get('slug')]
     locked = heroInfo.status is 'Locked'
     hero = @loadHero hero, heroIndex
+    @preloadHero heroIndex + 1
+    @preloadHero heroIndex - 1
     @selectedHero = hero unless locked
     Backbone.Mediator.publish 'level:hero-selection-updated', hero: @selectedHero
     $('#choose-inventory-button').prop 'disabled', locked

-  loadHero: (hero, heroIndex) ->
+  getFullHero: (original) ->
+    url = "/db/thang.type/#{original}/version"
+    if fullHero = @supermodel.getModel url
+      return fullHero
+    fullHero = new ThangType()
+    fullHero.setURL url
+    fullHero = (@supermodel.loadModel fullHero, 'thang').model
+    fullHero
+
+  preloadHero: (heroIndex) ->
+    return unless hero = @heroes.models[heroIndex]
+    @loadHero hero, heroIndex, true
+
+  loadHero: (hero, heroIndex, preloading=false) ->
     createjs.Ticker.removeEventListener 'tick', stage for stage in _.values @stages
     if stage = @stages[heroIndex]
-      createjs.Ticker.addEventListener 'tick', stage
-      @playSelectionSound hero
+      unless preloading
+        _.defer -> createjs.Ticker.addEventListener 'tick', stage  # Deferred, otherwise it won't start updating for some reason.
+        @playSelectionSound hero
       return hero
-    fullHero = new ThangType()
-    fullHero.setURL "/db/thang.type/#{hero.get('original')}/version"
-    fullHero = (@supermodel.loadModel fullHero, 'thang').model
+    fullHero = @getFullHero hero.get 'original'
     onLoaded = =>
       return unless canvas = $(".hero-item[data-hero-id='#{fullHero.get('original')}'] canvas")
       canvas.prop width: @canvasWidth, height: @canvasHeight
@@ -104,12 +119,13 @@
       movieClip.x = canvas.prop('width') * 0.5
       movieClip.y = canvas.prop('height') * 0.925  # This is where the feet go.
       stage = new createjs.Stage(canvas[0])
+      @stages[heroIndex] = stage
       stage.addChild movieClip
       stage.update()
-      createjs.Ticker.addEventListener 'tick', stage
       movieClip.gotoAndPlay 0
-      @stages[heroIndex] = stage
-      @playSelectionSound hero
+      unless preloading
+        createjs.Ticker.addEventListener 'tick', stage
+        @playSelectionSound hero
     if fullHero.loaded
       _.defer onLoaded
     else


Pre-release harnessing

Pre-release harnessing

--- x.coffee
+++ x.coffee
@@ -1,6 +0,0 @@
-process.on 'exit', -> console.log 'a'
-process.removeListener 'exit'
-process.on 'exit', -> console.log 'b'
-
-someSSS()
-console.log 123


support for progress notification

support for progress notification

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/blazvi/osdb-hash.git/'


fetching chunks of credentials as an object

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/bubblegroup/bubblebot.git/'


Add main CLI file

Add main CLI file

error: pathspec 'src/cli/index.coffee' did not match any file(s) known to git

--- src/cli/index.coffee
+++ src/cli/index.coffee
@@ -0,0 +1 @@
+judgeCli = module.exports = {}


feat: allow dependencies to be defined in local scope

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/fczbkk/element-monitor.git/'


Adds jQuery integration back.

Adds jQuery integration back.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/kevgo/modularity-framework.git/'


First failing test

First failing test

error: pathspec 'test/lib/when-test.coffee' did not match any file(s) known to git

--- test/lib/when-test.coffee
+++ test/lib/when-test.coffee
@@ -0,0 +1,8 @@
+describe 'when', ->
+  Given -> @when = requireSubject('lib/when')
+
+  describe 'unconditional stubbing', ->
+    Given -> @testDouble = requireSubject('lib/create')
+    Given -> @when(@testDouble()).thenReturn("foo")
+    When -> @result = @testDouble()
+    Then -> @result == "foo"