Mailspring/spec/config-spec.coffee

1654 lines
69 KiB
CoffeeScript
Raw Normal View History

fix(drafts): Various improvements and fixes to drafts, draft state management Summary: This diff contains a few major changes: 1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario: - View thread with draft, edit draft - Move to another thread - Move back to thread with draft - Move to another thread. Notice that one or more messages from thread with draft are still there. There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great. 2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here: - In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI. - Previously, when you added a contact to To/CC/BCC, this happened: <input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state. To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source! This diff includes a few minor fixes as well: 1. Draft.state is gone—use Message.object = draft instead 2. String model attributes should never be null 3. Pre-send checks that can cancel draft send 4. Put the entire curl history and task queue into feedback reports 5. Cache localIds for extra speed 6. Move us up to latest React Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
path = require 'path'
temp = require 'temp'
CSON = require 'season'
fs = require 'fs-plus'
Grim = require 'grim'
describe "Config", ->
dotAtomPath = null
beforeEach ->
dotAtomPath = temp.path('dot-inbox-dir')
fix(drafts): Various improvements and fixes to drafts, draft state management Summary: This diff contains a few major changes: 1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario: - View thread with draft, edit draft - Move to another thread - Move back to thread with draft - Move to another thread. Notice that one or more messages from thread with draft are still there. There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great. 2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here: - In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI. - Previously, when you added a contact to To/CC/BCC, this happened: <input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state. To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source! This diff includes a few minor fixes as well: 1. Draft.state is gone—use Message.object = draft instead 2. String model attributes should never be null 3. Pre-send checks that can cancel draft send 4. Put the entire curl history and task queue into feedback reports 5. Cache localIds for extra speed 6. Move us up to latest React Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
atom.config.configDirPath = dotAtomPath
atom.config.configFilePath = path.join(atom.config.configDirPath, "config.cson")
fix(drafts): Various improvements and fixes to drafts, draft state management Summary: This diff contains a few major changes: 1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario: - View thread with draft, edit draft - Move to another thread - Move back to thread with draft - Move to another thread. Notice that one or more messages from thread with draft are still there. There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great. 2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here: - In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI. - Previously, when you added a contact to To/CC/BCC, this happened: <input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state. To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source! This diff includes a few minor fixes as well: 1. Draft.state is gone—use Message.object = draft instead 2. String model attributes should never be null 3. Pre-send checks that can cancel draft send 4. Put the entire curl history and task queue into feedback reports 5. Cache localIds for extra speed 6. Move us up to latest React Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
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
expect(atom.config.get("foo.quux")).toBeUndefined()
it "returns a deep clone of the key path's value", ->
atom.config.set('value', array: [1, b: 2, 3])
retrievedValue = atom.config.get('value')
retrievedValue.array[0] = 4
retrievedValue.array[1].b = 2.1
expect(atom.config.get('value')).toEqual(array: [1, b: 2, 3])
it "merges defaults into the returned value if both the assigned value and the default value are objects", ->
atom.config.setDefaults("foo.bar", baz: 1, ok: 2)
atom.config.set("foo.bar", baz: 3)
expect(atom.config.get("foo.bar")).toEqual {baz: 3, ok: 2}
atom.config.setDefaults("other", baz: 1)
atom.config.set("other", 7)
expect(atom.config.get("other")).toBe 7
atom.config.set("bar.baz", a: 3)
atom.config.setDefaults("bar", baz: 7)
expect(atom.config.get("bar.baz")).toEqual {a: 3}
describe "when a 'sources' option is specified", ->
it "only retrieves values from the specified sources", ->
atom.config.set("x.y", 1, scopeSelector: ".foo", source: "a")
atom.config.set("x.y", 2, scopeSelector: ".foo", source: "b")
atom.config.set("x.y", 3, scopeSelector: ".foo", source: "c")
atom.config.setSchema("x.y", type: "integer", default: 4)
expect(atom.config.get("x.y", sources: ["a"], scope: [".foo"])).toBe 1
expect(atom.config.get("x.y", sources: ["b"], scope: [".foo"])).toBe 2
expect(atom.config.get("x.y", sources: ["c"], scope: [".foo"])).toBe 3
# Schema defaults never match a specific source. We could potentially add a special "schema" source.
expect(atom.config.get("x.y", sources: ["x"], scope: [".foo"])).toBeUndefined()
expect(atom.config.get(null, sources: ['a'], scope: [".foo"]).x.y).toBe 1
describe "when an 'excludeSources' option is specified", ->
it "only retrieves values from the specified sources", ->
atom.config.set("x.y", 0)
atom.config.set("x.y", 1, scopeSelector: ".foo", source: "a")
atom.config.set("x.y", 2, scopeSelector: ".foo", source: "b")
atom.config.set("x.y", 3, scopeSelector: ".foo", source: "c")
atom.config.setSchema("x.y", type: "integer", default: 4)
expect(atom.config.get("x.y", excludeSources: ["a"], scope: [".foo"])).toBe 3
expect(atom.config.get("x.y", excludeSources: ["c"], scope: [".foo"])).toBe 2
expect(atom.config.get("x.y", excludeSources: ["b", "c"], scope: [".foo"])).toBe 1
expect(atom.config.get("x.y", excludeSources: ["b", "c", "a"], scope: [".foo"])).toBe 0
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 "when a 'scope' option is given", ->
it "returns the property with the most specific scope selector", ->
atom.config.set("foo.bar.baz", 42, scopeSelector: ".source.coffee .string.quoted.double.coffee")
atom.config.set("foo.bar.baz", 22, scopeSelector: ".source .string.quoted.double")
atom.config.set("foo.bar.baz", 11, scopeSelector: ".source")
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.set("foo.bar.baz", 42, scopeSelector: ".source.coffee .string.quoted.single")
atom.config.set("foo.bar.baz", 22, scopeSelector: ".source.coffee .string.quoted.double")
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.set("foo.bar.baz", 1, scopeSelector: ".source.coffee", source: "some-package")
expect(atom.config.get("foo.bar.baz", scope: [".source.coffee"])).toBe 100
describe ".getAll(keyPath, {scope, sources, excludeSources})", ->
it "reads all of the values for a given key-path", ->
expect(atom.config.set("foo", 41)).toBe true
expect(atom.config.set("foo", 43, scopeSelector: ".a .b")).toBe true
expect(atom.config.set("foo", 42, scopeSelector: ".a")).toBe true
expect(atom.config.set("foo", 44, scopeSelector: ".a .b.c")).toBe true
expect(atom.config.set("foo", -44, scopeSelector: ".d")).toBe true
expect(atom.config.getAll("foo", scope: [".a", ".b.c"])).toEqual [
{scopeSelector: '.a .b.c', value: 44}
{scopeSelector: '.a .b', value: 43}
{scopeSelector: '.a', value: 42}
{scopeSelector: '*', value: 41}
]
it "includes the schema's default value", ->
atom.config.setSchema("foo", type: 'number', default: 40)
expect(atom.config.set("foo", 43, scopeSelector: ".a .b")).toBe true
expect(atom.config.getAll("foo", scope: [".a", ".b.c"])).toEqual [
{scopeSelector: '.a .b', value: 43}
{scopeSelector: '*', value: 40}
]
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
it "saves the user's config to disk after it stops changing", ->
atom.config.set("foo.bar.baz", 42)
advanceClock(50)
expect(atom.config.save).not.toHaveBeenCalled()
atom.config.set("foo.bar.baz", 43)
advanceClock(50)
expect(atom.config.save).not.toHaveBeenCalled()
atom.config.set("foo.bar.baz", 44)
advanceClock(150)
expect(atom.config.save).toHaveBeenCalled()
it "does not save when a non-default 'source' is given", ->
atom.config.set("foo.bar.baz", 42, source: 'some-other-source', scopeSelector: '.a')
advanceClock(500)
expect(atom.config.save).not.toHaveBeenCalled()
it "does not allow a 'source' option without a 'scopeSelector'", ->
expect(-> atom.config.set("foo", 1, source: [".source.ruby"])).toThrow()
describe "when the key-path is null", ->
it "sets the root object", ->
expect(atom.config.set(null, editor: tabLength: 6)).toBe true
expect(atom.config.get("editor.tabLength")).toBe 6
expect(atom.config.set(null, editor: tabLength: 8, scopeSelector: ['.source.js'])).toBe true
expect(atom.config.get("editor.tabLength", scope: ['.source.js'])).toBe 8
describe "when the value equals the default value", ->
it "does not store the value in the user's config", ->
atom.config.setDefaults "foo",
same: 1
changes: 1
sameArray: [1, 2, 3]
sameObject: {a: 1, b: 2}
null: null
undefined: undefined
expect(atom.config.settings.foo).toBeUndefined()
atom.config.set('foo.same', 1)
atom.config.set('foo.changes', 2)
atom.config.set('foo.sameArray', [1, 2, 3])
atom.config.set('foo.null', undefined)
atom.config.set('foo.undefined', null)
atom.config.set('foo.sameObject', {b: 2, a: 1})
expect(atom.config.get("foo.same", sources: [atom.config.getUserConfigPath()])).toBeUndefined()
expect(atom.config.get("foo.changes", sources: [atom.config.getUserConfigPath()])).toBe 2
atom.config.set('foo.changes', 1)
expect(atom.config.get("foo.changes", sources: [atom.config.getUserConfigPath()])).toBeUndefined()
describe "when a 'scopeSelector' is given", ->
it "sets the value and overrides the others", ->
atom.config.set("foo.bar.baz", 42, scopeSelector: ".source.coffee .string.quoted.double.coffee")
atom.config.set("foo.bar.baz", 22, scopeSelector: ".source .string.quoted.double")
atom.config.set("foo.bar.baz", 11, scopeSelector: ".source")
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})", ->
beforeEach ->
atom.config.setSchema 'foo',
type: 'object'
properties:
bar:
type: 'object'
properties:
baz:
type: 'integer'
default: 0
ok:
type: 'integer'
default: 0
quux:
type: 'integer'
default: 0
it "sets the value of the key path to its default", ->
atom.config.setDefaults('a', b: 3)
atom.config.set('a.b', 4)
expect(atom.config.get('a.b')).toBe 4
atom.config.unset('a.b')
expect(atom.config.get('a.b')).toBe 3
atom.config.set('a.c', 5)
expect(atom.config.get('a.c')).toBe 5
atom.config.unset('a.c')
expect(atom.config.get('a.c')).toBeUndefined()
it "calls ::save()", ->
atom.config.setDefaults('a', b: 3)
atom.config.set('a.b', 4)
atom.config.save.reset()
atom.config.unset('a.c')
advanceClock(500)
expect(atom.config.save.callCount).toBe 1
describe "when no 'scopeSelector' is given", ->
describe "when a 'source' but no key-path is given", ->
it "removes all scoped settings with the given source", ->
atom.config.set("foo.bar.baz", 1, scopeSelector: ".a", source: "source-a")
atom.config.set("foo.bar.quux", 2, scopeSelector: ".b", source: "source-a")
expect(atom.config.get("foo.bar", scope: [".a.b"])).toEqual(baz: 1, quux: 2)
atom.config.unset(null, source: "source-a")
expect(atom.config.get("foo.bar", scope: [".a"])).toEqual(baz: 0, ok: 0)
describe "when a 'source' and a key-path is given", ->
it "removes all scoped settings with the given source and key-path", ->
atom.config.set("foo.bar.baz", 1)
atom.config.set("foo.bar.baz", 2, scopeSelector: ".a", source: "source-a")
atom.config.set("foo.bar.baz", 3, scopeSelector: ".a.b", source: "source-b")
expect(atom.config.get("foo.bar.baz", scope: [".a.b"])).toEqual(3)
atom.config.unset("foo.bar.baz", source: "source-b")
expect(atom.config.get("foo.bar.baz", scope: [".a.b"])).toEqual(2)
expect(atom.config.get("foo.bar.baz")).toEqual(1)
describe "when no 'source' is given", ->
it "removes all scoped and unscoped properties for that key-path", ->
atom.config.setDefaults("foo.bar", baz: 100)
atom.config.set("foo.bar", { baz: 1, ok: 2 }, scopeSelector: ".a")
atom.config.set("foo.bar", { baz: 11, ok: 12 }, scopeSelector: ".b")
atom.config.set("foo.bar", { baz: 21, ok: 22 })
atom.config.unset("foo.bar.baz")
expect(atom.config.get("foo.bar.baz", scope: [".a"])).toBe 100
expect(atom.config.get("foo.bar.baz", scope: [".b"])).toBe 100
expect(atom.config.get("foo.bar.baz")).toBe 100
expect(atom.config.get("foo.bar.ok", scope: [".a"])).toBe 2
expect(atom.config.get("foo.bar.ok", scope: [".b"])).toBe 12
expect(atom.config.get("foo.bar.ok")).toBe 22
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')
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 55
atom.config.unset('foo.bar.baz', scopeSelector: '.source.coffee')
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 10
it "restores the scoped default when a scoped default is set", ->
atom.config.setDefaults("foo", bar: baz: 10)
atom.config.set("foo.bar.baz", 42, scopeSelector: ".source.coffee", source: "some-source")
atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
atom.config.set('foo.bar.ok', 100, scopeSelector: '.source.coffee')
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 55
atom.config.unset('foo.bar.baz', scopeSelector: '.source.coffee')
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 42
expect(atom.config.get('foo.bar.ok', scope: ['.source.coffee'])).toBe 100
it "calls ::save()", ->
atom.config.setDefaults("foo", bar: baz: 10)
atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
atom.config.save.reset()
atom.config.unset('foo.bar.baz', scopeSelector: '.source.coffee')
advanceClock(150)
expect(atom.config.save.callCount).toBe 1
it "allows removing settings for a specific source and scope selector", ->
atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee', source: "source-a")
atom.config.set('foo.bar.baz', 65, scopeSelector: '.source.coffee', source: "source-b")
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 65
atom.config.unset('foo.bar.baz', source: "source-b", scopeSelector: ".source.coffee")
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee', '.string'])).toBe 55
it "allows removing all settings for a specific source", ->
atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee', source: "source-a")
atom.config.set('foo.bar.baz', 65, scopeSelector: '.source.coffee', source: "source-b")
atom.config.set('foo.bar.ok', 65, scopeSelector: '.source.coffee', source: "source-b")
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 65
atom.config.unset(null, source: "source-b", scopeSelector: ".source.coffee")
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee', '.string'])).toBe 55
expect(atom.config.get('foo.bar.ok', scope: ['.source.coffee', '.string'])).toBe 0
it "does not call ::save or add a scoped property when no value has been set", ->
# see https://github.com/atom/atom/issues/4175
atom.config.setDefaults("foo", bar: baz: 10)
atom.config.unset('foo.bar.baz', scopeSelector: '.source.coffee')
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 10
expect(atom.config.save).not.toHaveBeenCalled()
scopedProperties = atom.config.scopedSettingsStore.propertiesForSource('user-config')
expect(scopedProperties['.coffee.source']).toBeUndefined()
it "removes the scoped value when it was the only set value on the object", ->
spyOn(CSON, 'writeFileSync')
atom.config.save.andCallThrough()
atom.config.setDefaults("foo", bar: baz: 10)
atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
atom.config.set('foo.bar.ok', 20, scopeSelector: '.source.coffee')
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 55
advanceClock(150)
CSON.writeFileSync.reset()
atom.config.unset('foo.bar.baz', scopeSelector: '.source.coffee')
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 10
expect(atom.config.get('foo.bar.ok', scope: ['.source.coffee'])).toBe 20
advanceClock(150)
expect(CSON.writeFileSync).toHaveBeenCalled()
properties = CSON.writeFileSync.mostRecentCall.args[1]
expect(properties['.coffee.source']).toEqual
foo:
bar:
ok: 20
CSON.writeFileSync.reset()
atom.config.unset('foo.bar.ok', scopeSelector: '.source.coffee')
advanceClock(150)
expect(CSON.writeFileSync).toHaveBeenCalled()
properties = CSON.writeFileSync.mostRecentCall.args[1]
expect(properties['.coffee.source']).toBeUndefined()
it "does not call ::save when the value is already at the default", ->
atom.config.setDefaults("foo", bar: baz: 10)
atom.config.set('foo.bar.baz', 55)
atom.config.save.reset()
atom.config.unset('foo.bar.ok', scopeSelector: '.source.coffee')
expect(atom.config.save).not.toHaveBeenCalled()
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 55
it "deprecates passing a scope selector as the first argument", ->
atom.config.setDefaults("foo", bar: baz: 10)
atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
spyOn(Grim, 'deprecate')
atom.config.unset('.source.coffee', 'foo.bar.baz')
expect(Grim.deprecate).toHaveBeenCalled()
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 10
describe ".onDidChange(keyPath, {scope})", ->
[observeHandler, observeSubscription] = []
describe 'when a keyPath is specified', ->
beforeEach ->
observeHandler = jasmine.createSpy("observeHandler")
atom.config.set("foo.bar.baz", "value 1")
observeSubscription = atom.config.onDidChange "foo.bar.baz", observeHandler
it "does not fire the given callback with the current value at the keypath", ->
expect(observeHandler).not.toHaveBeenCalled()
it "fires the callback every time the observed value changes", ->
atom.config.set('foo.bar.baz', "value 2")
expect(observeHandler).toHaveBeenCalledWith({newValue: 'value 2', oldValue: 'value 1'})
observeHandler.reset()
observeHandler.andCallFake -> throw new Error("oops")
expect(-> atom.config.set('foo.bar.baz', "value 1")).toThrow("oops")
expect(observeHandler).toHaveBeenCalledWith({newValue: 'value 1', oldValue: 'value 2'})
observeHandler.reset()
# Regression: exception in earlier handler shouldn't put observer
# into a bad state.
atom.config.set('something.else', "new value")
expect(observeHandler).not.toHaveBeenCalled()
describe 'when a keyPath is not specified', ->
beforeEach ->
observeHandler = jasmine.createSpy("observeHandler")
atom.config.set("foo.bar.baz", "value 1")
observeSubscription = atom.config.onDidChange observeHandler
it "does not fire the given callback initially", ->
expect(observeHandler).not.toHaveBeenCalled()
it "fires the callback every time any value changes", ->
observeHandler.reset() # clear the initial call
atom.config.set('foo.bar.baz', "value 2")
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.baz).toBe("value 2")
expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.baz).toBe("value 1")
observeHandler.reset()
atom.config.set('foo.bar.baz', "value 1")
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.baz).toBe("value 1")
expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.baz).toBe("value 2")
observeHandler.reset()
atom.config.set('foo.bar.int', 1)
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.int).toBe(1)
expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.int).toBe(undefined)
describe "when a 'scope' is given", ->
it 'calls the supplied callback when the value at the descriptor/keypath changes', ->
changeSpy = jasmine.createSpy('onDidChange callback')
atom.config.onDidChange "foo.bar.baz", scope: [".source.coffee", ".string.quoted.double.coffee"], changeSpy
atom.config.set("foo.bar.baz", 12)
expect(changeSpy).toHaveBeenCalledWith({oldValue: undefined, newValue: 12})
changeSpy.reset()
atom.config.set("foo.bar.baz", 22, scopeSelector: ".source .string.quoted.double", source: "a")
expect(changeSpy).toHaveBeenCalledWith({oldValue: 12, newValue: 22})
changeSpy.reset()
atom.config.set("foo.bar.baz", 42, scopeSelector: ".source.coffee .string.quoted.double.coffee", source: "b")
expect(changeSpy).toHaveBeenCalledWith({oldValue: 22, newValue: 42})
changeSpy.reset()
atom.config.unset(null, scopeSelector: ".source.coffee .string.quoted.double.coffee", source: "b")
expect(changeSpy).toHaveBeenCalledWith({oldValue: 42, newValue: 22})
changeSpy.reset()
atom.config.unset(null, scopeSelector: ".source .string.quoted.double", source: "a")
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})
describe ".observe(keyPath, {scope})", ->
[observeHandler, observeSubscription] = []
beforeEach ->
observeHandler = jasmine.createSpy("observeHandler")
atom.config.set("foo.bar.baz", "value 1")
observeSubscription = atom.config.observe("foo.bar.baz", observeHandler)
it "fires the given callback with the current value at the keypath", ->
expect(observeHandler).toHaveBeenCalledWith("value 1")
it "fires the callback every time the observed value changes", ->
observeHandler.reset() # clear the initial call
atom.config.set('foo.bar.baz', "value 2")
expect(observeHandler).toHaveBeenCalledWith("value 2")
observeHandler.reset()
atom.config.set('foo.bar.baz', "value 1")
expect(observeHandler).toHaveBeenCalledWith("value 1")
observeHandler.reset()
atom.config.loadUserConfig()
expect(observeHandler).toHaveBeenCalledWith(undefined)
it "fires the callback when the observed value is deleted", ->
observeHandler.reset() # clear the initial call
atom.config.set('foo.bar.baz', undefined)
expect(observeHandler).toHaveBeenCalledWith(undefined)
it "fires the callback when the full key path goes into and out of existence", ->
observeHandler.reset() # clear the initial call
atom.config.set("foo.bar", undefined)
expect(observeHandler).toHaveBeenCalledWith(undefined)
observeHandler.reset()
atom.config.set("foo.bar.baz", "i'm back")
expect(observeHandler).toHaveBeenCalledWith("i'm back")
it "does not fire the callback once the subscription is disposed", ->
observeHandler.reset() # clear the initial call
observeSubscription.dispose()
atom.config.set('foo.bar.baz', "value 2")
expect(observeHandler).not.toHaveBeenCalled()
it 'does not fire the callback for a similarly named keyPath', ->
bazCatHandler = jasmine.createSpy("bazCatHandler")
observeSubscription = atom.config.observe "foo.bar.bazCat", bazCatHandler
bazCatHandler.reset()
atom.config.set('foo.bar.baz', "value 10")
expect(bazCatHandler).not.toHaveBeenCalled()
describe "when a 'scope' is given", ->
otherHandler = null
beforeEach ->
observeSubscription.dispose()
otherHandler = jasmine.createSpy('otherHandler')
it "allows settings to be observed in a specific scope", ->
atom.config.observe("foo.bar.baz", scope: [".some.scope"], observeHandler)
atom.config.observe("foo.bar.baz", scope: [".another.scope"], otherHandler)
atom.config.set('foo.bar.baz', "value 2", scopeSelector: ".some")
expect(observeHandler).toHaveBeenCalledWith("value 2")
expect(otherHandler).not.toHaveBeenCalledWith("value 2")
it "deprecates using a scope descriptor as the first argument", ->
spyOn(Grim, 'deprecate')
atom.config.observe([".some.scope"], "foo.bar.baz", observeHandler)
atom.config.observe([".another.scope"], "foo.bar.baz", otherHandler)
expect(Grim.deprecate).toHaveBeenCalled()
atom.config.set('foo.bar.baz', "value 2", scopeSelector: ".some")
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()
atom.config.set("foo.bar.baz", 22, scopeSelector: ".source .string.quoted.double", source: "a")
expect(changeSpy).toHaveBeenCalledWith(22)
changeSpy.reset()
atom.config.set("foo.bar.baz", 42, scopeSelector: ".source.coffee .string.quoted.double.coffee", source: "b")
expect(changeSpy).toHaveBeenCalledWith(42)
changeSpy.reset()
atom.config.unset(null, scopeSelector: ".source.coffee .string.quoted.double.coffee", source: "b")
expect(changeSpy).toHaveBeenCalledWith(22)
changeSpy.reset()
atom.config.unset(null, scopeSelector: ".source .string.quoted.double", source: "a")
expect(changeSpy).toHaveBeenCalledWith(12)
changeSpy.reset()
atom.config.set("foo.bar.baz", undefined)
expect(changeSpy).toHaveBeenCalledWith(undefined)
changeSpy.reset()
describe ".transact(callback)", ->
changeSpy = null
beforeEach ->
changeSpy = jasmine.createSpy('onDidChange callback')
atom.config.onDidChange("foo.bar.baz", changeSpy)
it "allows only one change event for the duration of the given callback", ->
atom.config.transact ->
atom.config.set("foo.bar.baz", 1)
atom.config.set("foo.bar.baz", 2)
atom.config.set("foo.bar.baz", 3)
expect(changeSpy.callCount).toBe(1)
expect(changeSpy.argsForCall[0][0]).toEqual(newValue: 3, oldValue: undefined)
it "does not emit an event if no changes occur while paused", ->
atom.config.transact ->
expect(changeSpy).not.toHaveBeenCalled()
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.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 atom.config.configFilePath
writtenConfig = CSON.writeFileSync.argsForCall[0][1]
expect(writtenConfig).toEqual '*': atom.config.settings
describe "when ~/.atom/config.json doesn't exist", ->
it "writes any non-default properties to ~/.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 '*': 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
'*':
atom.config.settings
'.ruby.source':
foo:
bar: 'ruby'
omg: 'wow'
'.coffee.source':
foo:
bar: 'coffee'
describe ".loadUserConfig()", ->
beforeEach ->
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", ->
beforeEach ->
fs.writeFileSync atom.config.configFilePath, """
'*':
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 does not conform to the schema", ->
beforeEach ->
fs.writeFileSync atom.config.configFilePath, """
'*':
foo:
bar: 'omg'
int: 'baz'
'.source.ruby':
foo:
bar: 'scoped'
int: 'nope'
"""
it "validates and does not load the incorrect values", ->
atom.config.loadUserConfig()
expect(atom.config.get("foo.int")).toBe 12
expect(atom.config.get("foo.bar")).toBe 'omg'
expect(atom.config.get("foo.int", scope: ['.source.ruby'])).toBe 12
expect(atom.config.get("foo.bar", scope: ['.source.ruby'])).toBe 'scoped'
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", ->
addErrorHandler = null
beforeEach ->
atom.notifications.onDidAddNotification addErrorHandler = jasmine.createSpy()
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(addErrorHandler.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
writeConfigFile = (data) ->
previousSetTimeoutCallCount = setTimeout.callCount
runs ->
fs.writeFileSync(atom.config.configFilePath, data)
waitsFor "debounced config file load", ->
setTimeout.callCount > previousSetTimeoutCallCount
runs ->
advanceClock(1000)
beforeEach ->
atom.config.setSchema 'foo',
type: 'object'
properties:
bar:
type: 'string'
default: 'def'
baz:
type: 'string'
scoped:
type: 'boolean'
int:
type: 'integer'
default: 12
expect(fs.existsSync(atom.config.configDirPath)).toBeFalsy()
fs.writeFileSync atom.config.configFilePath, """
'*':
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", ->
writeConfigFile("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()
writeConfigFile("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'
writeConfigFile("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)
writeConfigFile("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)
writeConfigFile """
'*':
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)
writeConfigFile """
'*':
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", ->
writeConfigFile("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 ->
writeConfigFile("")
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
advanceClock(500)
expect(atom.config.save).toHaveBeenCalled()
describe "when the config file subsequently changes again to contain configuration", ->
beforeEach ->
updatedHandler.reset()
writeConfigFile("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", ->
addErrorHandler = null
beforeEach ->
atom.notifications.onDidAddNotification addErrorHandler = jasmine.createSpy()
writeConfigFile("}}}")
waitsFor "error to be logged", -> addErrorHandler.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 ->
writeConfigFile("foo: bar: 'newVal'")
waitsFor 'update event', -> updatedHandler.callCount > 0
it "updates the config data and resumes saving", ->
atom.config.set("hair", "blonde")
advanceClock(500)
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-inbox to ~/.inbox", ->
fix(drafts): Various improvements and fixes to drafts, draft state management Summary: This diff contains a few major changes: 1. Scribe is no longer used for the text editor. It's just a plain contenteditable region. The toolbar items (bold, italic, underline) still work. Scribe was causing React inconcistency issues in the following scenario: - View thread with draft, edit draft - Move to another thread - Move back to thread with draft - Move to another thread. Notice that one or more messages from thread with draft are still there. There may be a way to fix this, but I tried for hours and there are Github Issues open on it's repository asking for React compatibility, so it may be fixed soon. For now contenteditable is working great. 2. Action.saveDraft() is no longer debounced in the DraftStore. Instead, firing that action causes the save to happen immediately, and the DraftStoreProxy has a new "DraftChangeSet" class which is responsbile for batching saves as the user interacts with the ComposerView. There are a couple big wins here: - In the future, we may want to be able to call Action.saveDraft() in other situations and it should behave like a normal action. We may also want to expose the DraftStoreProxy as an easy way of backing interactive draft UI. - Previously, when you added a contact to To/CC/BCC, this happened: <input> -> Action.saveDraft -> (delay!!) -> Database -> DraftStore -> DraftStoreProxy -> View Updates Increasing the delay to something reasonable like 200msec meant there was 200msec of lag before you saw the new view state. To fix this, I created a new class called DraftChangeSet which is responsible for accumulating changes as they're made and firing Action.saveDraft. "Adding" a change to the change set also causes the Draft provided by the DraftStoreProxy to change immediately (the changes are a temporary layer on top of the database object). This means no delay while changes are being applied. There's a better explanation in the source! This diff includes a few minor fixes as well: 1. Draft.state is gone—use Message.object = draft instead 2. String model attributes should never be null 3. Pre-send checks that can cancel draft send 4. Put the entire curl history and task queue into feedback reports 5. Cache localIds for extra speed 6. Move us up to latest React Test Plan: No new tests - once we lock down this new design I'll write tests for the DraftChangeSet Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1125
2015-02-04 08:24:31 +08:00
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 ".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'
properties:
anInt:
type: 'integer'
default: 12
atom.config.setSchema('foo.bar', schema)
expect(atom.config.getSchema('foo')).toEqual
type: 'object'
properties:
bar:
type: 'object'
properties:
anInt:
type: 'integer'
default: 12
it 'sets defaults specified by the schema', ->
schema =
type: 'object'
properties:
anInt:
type: 'integer'
default: 12
anObject:
type: 'object'
properties:
nestedInt:
type: 'integer'
default: 24
nestedObject:
type: 'object'
properties:
superNestedInt:
type: 'integer'
default: 36
atom.config.setSchema('foo.bar', schema)
expect(atom.config.get("foo.bar.anInt")).toBe 12
expect(atom.config.get("foo.bar.anObject")).toEqual
nestedInt: 24
nestedObject:
superNestedInt: 36
it 'can set a non-object schema', ->
schema =
type: 'integer'
default: 12
atom.config.setSchema('foo.bar.anInt', schema)
expect(atom.config.get("foo.bar.anInt")).toBe 12
expect(atom.config.getSchema('foo.bar.anInt')).toEqual
type: 'integer'
default: 12
it "allows the schema to be retrieved via ::getSchema", ->
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
expect(atom.config.getSchema('foo.baz')).toBeUndefined()
expect(atom.config.getSchema('foo.bar.anInt.baz')).toBeUndefined()
it "respects the schema for scoped settings", ->
schema =
type: 'string'
default: 'ok'
scopes:
'.source.js':
default: 'omg'
atom.config.setSchema('foo.bar.str', schema)
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 'when a schema is added after config values have been set', ->
schema = null
beforeEach ->
schema =
type: 'object'
properties:
int:
type: 'integer'
default: 2
str:
type: 'string'
default: 'def'
it "respects the new schema when values are set", ->
expect(atom.config.set('foo.bar.str', 'global')).toBe true
expect(atom.config.set('foo.bar.str', 'scoped', scopeSelector: '.source.js')).toBe true
expect(atom.config.get('foo.bar.str')).toBe 'global'
expect(atom.config.get('foo.bar.str', scope: ['.source.js'])).toBe 'scoped'
expect(atom.config.set('foo.bar.noschema', 'nsGlobal')).toBe true
expect(atom.config.set('foo.bar.noschema', 'nsScoped', scopeSelector: '.source.js')).toBe true
expect(atom.config.get('foo.bar.noschema')).toBe 'nsGlobal'
expect(atom.config.get('foo.bar.noschema', scope: ['.source.js'])).toBe 'nsScoped'
expect(atom.config.set('foo.bar.int', 'nope')).toBe true
expect(atom.config.set('foo.bar.int', 'notanint', scopeSelector: '.source.js')).toBe true
expect(atom.config.set('foo.bar.int', 23, scopeSelector: '.source.coffee')).toBe true
expect(atom.config.get('foo.bar.int')).toBe 'nope'
expect(atom.config.get('foo.bar.int', scope: ['.source.js'])).toBe 'notanint'
expect(atom.config.get('foo.bar.int', scope: ['.source.coffee'])).toBe 23
atom.config.setSchema('foo.bar', schema)
expect(atom.config.get('foo.bar.str')).toBe 'global'
expect(atom.config.get('foo.bar.str', scope: ['.source.js'])).toBe 'scoped'
expect(atom.config.get('foo.bar.noschema')).toBe 'nsGlobal'
expect(atom.config.get('foo.bar.noschema', scope: ['.source.js'])).toBe 'nsScoped'
expect(atom.config.get('foo.bar.int')).toBe 2
expect(atom.config.get('foo.bar.int', scope: ['.source.js'])).toBe 2
expect(atom.config.get('foo.bar.int', scope: ['.source.coffee'])).toBe 23
it "sets all values that adhere to the schema", ->
expect(atom.config.set('foo.bar.int', 10)).toBe true
expect(atom.config.set('foo.bar.int', 15, scopeSelector: '.source.js')).toBe true
expect(atom.config.set('foo.bar.int', 23, scopeSelector: '.source.coffee')).toBe true
expect(atom.config.get('foo.bar.int')).toBe 10
expect(atom.config.get('foo.bar.int', scope: ['.source.js'])).toBe 15
expect(atom.config.get('foo.bar.int', scope: ['.source.coffee'])).toBe 23
atom.config.setSchema('foo.bar', schema)
expect(atom.config.get('foo.bar.int')).toBe 10
expect(atom.config.get('foo.bar.int', scope: ['.source.js'])).toBe 15
expect(atom.config.get('foo.bar.int', scope: ['.source.coffee'])).toBe 23
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 value has a "color" type', ->
beforeEach ->
schema =
type: 'color'
default: 'white'
atom.config.setSchema('foo.bar.aColor', schema)
it 'returns a Color object', ->
color = atom.config.get('foo.bar.aColor')
expect(color.toHexString()).toBe '#ffffff'
expect(color.toRGBAString()).toBe 'rgba(255, 255, 255, 1)'
color.red = 0
color.green = 0
color.blue = 0
color.alpha = 0
atom.config.set('foo.bar.aColor', color)
color = atom.config.get('foo.bar.aColor')
expect(color.toHexString()).toBe '#000000'
expect(color.toRGBAString()).toBe 'rgba(0, 0, 0, 0)'
color.red = 300
color.green = -200
color.blue = -1
color.alpha = 'not see through'
atom.config.set('foo.bar.aColor', color)
color = atom.config.get('foo.bar.aColor')
expect(color.toHexString()).toBe '#ff0000'
expect(color.toRGBAString()).toBe 'rgba(255, 0, 0, 1)'
it 'coerces various types to a color object', ->
atom.config.set('foo.bar.aColor', 'red')
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 255, green: 0, blue: 0, alpha: 1}
atom.config.set('foo.bar.aColor', '#020')
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 0, green: 34, blue: 0, alpha: 1}
atom.config.set('foo.bar.aColor', '#abcdef')
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 171, green: 205, blue: 239, alpha: 1}
atom.config.set('foo.bar.aColor', 'rgb(1,2,3)')
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 1, green: 2, blue: 3, alpha: 1}
atom.config.set('foo.bar.aColor', 'rgba(4,5,6,.7)')
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 4, green: 5, blue: 6, alpha: .7}
atom.config.set('foo.bar.aColor', 'hsl(120,100%,50%)')
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 0, green: 255, blue: 0, alpha: 1}
atom.config.set('foo.bar.aColor', 'hsla(120,100%,50%,0.3)')
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 0, green: 255, blue: 0, alpha: .3}
atom.config.set('foo.bar.aColor', {red: 100, green: 255, blue: 2, alpha: .5})
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 100, green: 255, blue: 2, alpha: .5}
atom.config.set('foo.bar.aColor', {red: 255})
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 255, green: 0, blue: 0, alpha: 1}
atom.config.set('foo.bar.aColor', {red: 1000})
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 255, green: 0, blue: 0, alpha: 1}
atom.config.set('foo.bar.aColor', {red: 'dark'})
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 0, green: 0, blue: 0, alpha: 1}
it 'reverts back to the default value when undefined is passed to set', ->
atom.config.set('foo.bar.aColor', undefined)
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 255, green: 255, blue: 255, alpha: 1}
it 'will not set non-colors', ->
atom.config.set('foo.bar.aColor', null)
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 255, green: 255, blue: 255, alpha: 1}
atom.config.set('foo.bar.aColor', 'nope')
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 255, green: 255, blue: 255, alpha: 1}
atom.config.set('foo.bar.aColor', 30)
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 255, green: 255, blue: 255, alpha: 1}
atom.config.set('foo.bar.aColor', false)
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 255, green: 255, blue: 255, alpha: 1}
it "returns a clone of the Color when returned in a parent object", ->
color1 = atom.config.get('foo.bar').aColor
color2 = atom.config.get('foo.bar').aColor
expect(color1.toRGBAString()).toBe 'rgba(255, 255, 255, 1)'
expect(color2.toRGBAString()).toBe 'rgba(255, 255, 255, 1)'
expect(color1).not.toBe color2
expect(color1).toEqual color2
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')
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.set("foo.bar.baz", 42, scopeSelector: ".source.coffee", source: "some-source")
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.set("foo.bar.baz", 42, scopeSelector: ".source.coffee", source: "something-else")
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