config-spec.js 77 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448
  1. describe('Config', () => {
  2. let savedSettings;
  3. beforeEach(() => {
  4. spyOn(console, 'warn');
  5. atom.config.settingsLoaded = true;
  6. savedSettings = [];
  7. atom.config.saveCallback = function (settings) {
  8. savedSettings.push(settings);
  9. };
  10. });
  11. describe('.get(keyPath, {scope, sources, excludeSources})', () => {
  12. it("allows a key path's value to be read", () => {
  13. expect(atom.config.set('foo.bar.baz', 42)).toBe(true);
  14. expect(atom.config.get('foo.bar.baz')).toBe(42);
  15. expect(atom.config.get('foo.quux')).toBeUndefined();
  16. });
  17. it("returns a deep clone of the key path's value", () => {
  18. atom.config.set('value', { array: [1, { b: 2 }, 3] });
  19. const retrievedValue = atom.config.get('value');
  20. retrievedValue.array[0] = 4;
  21. retrievedValue.array[1].b = 2.1;
  22. expect(atom.config.get('value')).toEqual({ array: [1, { b: 2 }, 3] });
  23. });
  24. it('merges defaults into the returned value if both the assigned value and the default value are objects', () => {
  25. atom.config.setDefaults('foo.bar', { baz: 1, ok: 2 });
  26. atom.config.set('foo.bar', { baz: 3 });
  27. expect(atom.config.get('foo.bar')).toEqual({ baz: 3, ok: 2 });
  28. atom.config.setDefaults('other', { baz: 1 });
  29. atom.config.set('other', 7);
  30. expect(atom.config.get('other')).toBe(7);
  31. atom.config.set('bar.baz', { a: 3 });
  32. atom.config.setDefaults('bar', { baz: 7 });
  33. expect(atom.config.get('bar.baz')).toEqual({ a: 3 });
  34. });
  35. describe("when a 'sources' option is specified", () => {
  36. afterEach(() => {
  37. atom.project.replace(null);
  38. });
  39. it('only retrieves values from the specified sources', () => {
  40. atom.config.set('x.y', 1, { scopeSelector: '.foo', source: 'a' });
  41. atom.config.set('x.y', 2, { scopeSelector: '.foo', source: 'b' });
  42. atom.config.set('x.y', 3, { scopeSelector: '.foo', source: 'c' });
  43. atom.config.setSchema('x.y', { type: 'integer', default: 4 });
  44. expect(
  45. atom.config.get('x.y', { sources: ['a'], scope: ['.foo'] })
  46. ).toBe(1);
  47. expect(
  48. atom.config.get('x.y', { sources: ['b'], scope: ['.foo'] })
  49. ).toBe(2);
  50. expect(
  51. atom.config.get('x.y', { sources: ['c'], scope: ['.foo'] })
  52. ).toBe(3);
  53. // Schema defaults never match a specific source. We could potentially add a special "schema" source.
  54. expect(
  55. atom.config.get('x.y', { sources: ['x'], scope: ['.foo'] })
  56. ).toBeUndefined();
  57. expect(
  58. atom.config.get(null, { sources: ['a'], scope: ['.foo'] }).x.y
  59. ).toBe(1);
  60. })
  61. it(`ignores project-specific settings unless specified in the "sources" option`, () => {
  62. atom.config.set('x.y', 1);
  63. atom.config.set('u.v', 5);
  64. atom.project.replace({
  65. originPath: 'TEST',
  66. paths: atom.project.getPaths(),
  67. config: {
  68. "*": {
  69. "x": {
  70. "y": 4
  71. }
  72. }
  73. }
  74. });
  75. expect(
  76. atom.config.get('x.y', { sources: [atom.config.mainSource] })
  77. ).toBe(1);
  78. expect(
  79. atom.config.get('x.y', { sources: [atom.config.mainSource, atom.config.projectFile] })
  80. ).toBe(4);
  81. expect(
  82. atom.config.get('x.y', { sources: [atom.config.projectFile] })
  83. ).toBe(4);
  84. expect(
  85. atom.config.get('u.v', {
  86. sources: [atom.config.projectFile],
  87. excludeSources: [atom.config.mainSource]
  88. })
  89. ).toBeUndefined();
  90. });
  91. });
  92. describe("when an 'excludeSources' option is specified", () => {
  93. afterEach(() => {
  94. atom.project.replace(null);
  95. });
  96. it('only retrieves values from the specified sources', () => {
  97. atom.config.set('x.y', 0);
  98. atom.config.set('x.y', 1, { scopeSelector: '.foo', source: 'a' });
  99. atom.config.set('x.y', 2, { scopeSelector: '.foo', source: 'b' });
  100. atom.config.set('x.y', 3, { scopeSelector: '.foo', source: 'c' });
  101. atom.config.setSchema('x.y', { type: 'integer', default: 4 });
  102. expect(
  103. atom.config.get('x.y', { excludeSources: ['a'], scope: ['.foo'] })
  104. ).toBe(3);
  105. expect(
  106. atom.config.get('x.y', { excludeSources: ['c'], scope: ['.foo'] })
  107. ).toBe(2);
  108. expect(
  109. atom.config.get('x.y', {
  110. excludeSources: ['b', 'c'],
  111. scope: ['.foo']
  112. })
  113. ).toBe(1);
  114. expect(
  115. atom.config.get('x.y', {
  116. excludeSources: ['b', 'c', 'a'],
  117. scope: ['.foo']
  118. })
  119. ).toBe(0);
  120. expect(
  121. atom.config.get('x.y', {
  122. excludeSources: ['b', 'c', 'a', atom.config.getUserConfigPath()],
  123. scope: ['.foo']
  124. })
  125. ).toBe(4);
  126. expect(
  127. atom.config.get('x.y', {
  128. excludeSources: [atom.config.getUserConfigPath()]
  129. })
  130. ).toBe(4);
  131. });
  132. it("ignores the project-specific source when 'excludeSources' tells it to", () => {
  133. atom.config.set('x.y', 1);
  134. atom.project.replace({
  135. originPath: 'TEST',
  136. paths: atom.project.getPaths(),
  137. config: {
  138. "*": {
  139. "x": {
  140. "y": 4
  141. }
  142. }
  143. }
  144. });
  145. expect( atom.config.get('x.y') ).toBe(4);
  146. expect(
  147. atom.config.get('x.y', { excludeSources: [atom.config.projectFile] })
  148. ).toBe(1);
  149. });
  150. });
  151. describe("when a 'scope' option is given", () => {
  152. it('returns the property with the most specific scope selector', () => {
  153. atom.config.set('foo.bar.baz', 42, {
  154. scopeSelector: '.source.coffee .string.quoted.double.coffee'
  155. });
  156. atom.config.set('foo.bar.baz', 22, {
  157. scopeSelector: '.source .string.quoted.double'
  158. });
  159. atom.config.set('foo.bar.baz', 11, { scopeSelector: '.source' });
  160. expect(
  161. atom.config.get('foo.bar.baz', {
  162. scope: ['.source.coffee', '.string.quoted.double.coffee']
  163. })
  164. ).toBe(42);
  165. expect(
  166. atom.config.get('foo.bar.baz', {
  167. scope: ['.source.js', '.string.quoted.double.js']
  168. })
  169. ).toBe(22);
  170. expect(
  171. atom.config.get('foo.bar.baz', {
  172. scope: ['.source.js', '.variable.assignment.js']
  173. })
  174. ).toBe(11);
  175. expect(
  176. atom.config.get('foo.bar.baz', { scope: ['.text'] })
  177. ).toBeUndefined();
  178. });
  179. it('favors the most recently added properties in the event of a specificity tie', () => {
  180. atom.config.set('foo.bar.baz', 42, {
  181. scopeSelector: '.source.coffee .string.quoted.single'
  182. });
  183. atom.config.set('foo.bar.baz', 22, {
  184. scopeSelector: '.source.coffee .string.quoted.double'
  185. });
  186. expect(
  187. atom.config.get('foo.bar.baz', {
  188. scope: ['.source.coffee', '.string.quoted.single']
  189. })
  190. ).toBe(42);
  191. expect(
  192. atom.config.get('foo.bar.baz', {
  193. scope: ['.source.coffee', '.string.quoted.single.double']
  194. })
  195. ).toBe(22);
  196. });
  197. describe('when there are global defaults', () =>
  198. it('falls back to the global when there is no scoped property specified', () => {
  199. atom.config.setDefaults('foo', { hasDefault: 'ok' });
  200. expect(
  201. atom.config.get('foo.hasDefault', {
  202. scope: ['.source.coffee', '.string.quoted.single']
  203. })
  204. ).toBe('ok');
  205. }));
  206. describe('when package settings are added after user settings', () =>
  207. it("returns the user's setting because the user's setting has higher priority", () => {
  208. atom.config.set('foo.bar.baz', 100, {
  209. scopeSelector: '.source.coffee'
  210. });
  211. atom.config.set('foo.bar.baz', 1, {
  212. scopeSelector: '.source.coffee',
  213. source: 'some-package'
  214. });
  215. expect(
  216. atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] })
  217. ).toBe(100);
  218. }));
  219. });
  220. });
  221. describe('.getAll(keyPath, {scope, sources, excludeSources})', () => {
  222. it('reads all of the values for a given key-path', () => {
  223. expect(atom.config.set('foo', 41)).toBe(true);
  224. expect(atom.config.set('foo', 43, { scopeSelector: '.a .b' })).toBe(true);
  225. expect(atom.config.set('foo', 42, { scopeSelector: '.a' })).toBe(true);
  226. expect(atom.config.set('foo', 44, { scopeSelector: '.a .b.c' })).toBe(
  227. true
  228. );
  229. expect(atom.config.set('foo', -44, { scopeSelector: '.d' })).toBe(true);
  230. expect(atom.config.getAll('foo', { scope: ['.a', '.b.c'] })).toEqual([
  231. { scopeSelector: '.a .b.c', value: 44 },
  232. { scopeSelector: '.a .b', value: 43 },
  233. { scopeSelector: '.a', value: 42 },
  234. { scopeSelector: '*', value: 41 }
  235. ]);
  236. });
  237. it("includes the schema's default value", () => {
  238. atom.config.setSchema('foo', { type: 'number', default: 40 });
  239. expect(atom.config.set('foo', 43, { scopeSelector: '.a .b' })).toBe(true);
  240. expect(atom.config.getAll('foo', { scope: ['.a', '.b.c'] })).toEqual([
  241. { scopeSelector: '.a .b', value: 43 },
  242. { scopeSelector: '*', value: 40 }
  243. ]);
  244. });
  245. });
  246. describe('.set(keyPath, value, {source, scopeSelector})', () => {
  247. it("allows a key path's value to be written", () => {
  248. expect(atom.config.set('foo.bar.baz', 42)).toBe(true);
  249. expect(atom.config.get('foo.bar.baz')).toBe(42);
  250. });
  251. it("saves the user's config to disk after it stops changing", () => {
  252. atom.config.set('foo.bar.baz', 42);
  253. expect(savedSettings.length).toBe(0);
  254. atom.config.set('foo.bar.baz', 43);
  255. expect(savedSettings.length).toBe(0);
  256. atom.config.set('foo.bar.baz', 44);
  257. advanceClock(10);
  258. expect(savedSettings.length).toBe(1);
  259. });
  260. it("does not save when a non-default 'source' is given", () => {
  261. atom.config.set('foo.bar.baz', 42, {
  262. source: 'some-other-source',
  263. scopeSelector: '.a'
  264. });
  265. advanceClock(500);
  266. expect(savedSettings.length).toBe(0);
  267. });
  268. it("does not allow a 'source' option without a 'scopeSelector'", () => {
  269. expect(() =>
  270. atom.config.set('foo', 1, { source: ['.source.ruby'] })
  271. ).toThrow();
  272. });
  273. describe('when the key-path is null', () =>
  274. it('sets the root object', () => {
  275. expect(atom.config.set(null, { editor: { tabLength: 6 } })).toBe(true);
  276. expect(atom.config.get('editor.tabLength')).toBe(6);
  277. expect(
  278. atom.config.set(null, {
  279. editor: { tabLength: 8, scopeSelector: ['.source.js'] }
  280. })
  281. ).toBe(true);
  282. expect(
  283. atom.config.get('editor.tabLength', { scope: ['.source.js'] })
  284. ).toBe(8);
  285. }));
  286. describe('when the value equals the default value', () =>
  287. it("does not store the value in the user's config", () => {
  288. atom.config.setSchema('foo', {
  289. type: 'object',
  290. properties: {
  291. same: {
  292. type: 'number',
  293. default: 1
  294. },
  295. changes: {
  296. type: 'number',
  297. default: 1
  298. },
  299. sameArray: {
  300. type: 'array',
  301. default: [1, 2, 3]
  302. },
  303. sameObject: {
  304. type: 'object',
  305. default: { a: 1, b: 2 }
  306. },
  307. null: {
  308. type: '*',
  309. default: null
  310. },
  311. undefined: {
  312. type: '*',
  313. default: undefined
  314. }
  315. }
  316. });
  317. expect(atom.config.settings.foo).toBeUndefined();
  318. atom.config.set('foo.same', 1);
  319. atom.config.set('foo.changes', 2);
  320. atom.config.set('foo.sameArray', [1, 2, 3]);
  321. atom.config.set('foo.null', undefined);
  322. atom.config.set('foo.undefined', null);
  323. atom.config.set('foo.sameObject', { b: 2, a: 1 });
  324. const userConfigPath = atom.config.getUserConfigPath();
  325. expect(
  326. atom.config.get('foo.same', { sources: [userConfigPath] })
  327. ).toBeUndefined();
  328. expect(atom.config.get('foo.changes')).toBe(2);
  329. expect(
  330. atom.config.get('foo.changes', { sources: [userConfigPath] })
  331. ).toBe(2);
  332. atom.config.set('foo.changes', 1);
  333. expect(
  334. atom.config.get('foo.changes', { sources: [userConfigPath] })
  335. ).toBeUndefined();
  336. }));
  337. describe("when a 'scopeSelector' is given", () =>
  338. it('sets the value and overrides the others', () => {
  339. atom.config.set('foo.bar.baz', 42, {
  340. scopeSelector: '.source.coffee .string.quoted.double.coffee'
  341. });
  342. atom.config.set('foo.bar.baz', 22, {
  343. scopeSelector: '.source .string.quoted.double'
  344. });
  345. atom.config.set('foo.bar.baz', 11, { scopeSelector: '.source' });
  346. expect(
  347. atom.config.get('foo.bar.baz', {
  348. scope: ['.source.coffee', '.string.quoted.double.coffee']
  349. })
  350. ).toBe(42);
  351. expect(
  352. atom.config.set('foo.bar.baz', 100, {
  353. scopeSelector: '.source.coffee .string.quoted.double.coffee'
  354. })
  355. ).toBe(true);
  356. expect(
  357. atom.config.get('foo.bar.baz', {
  358. scope: ['.source.coffee', '.string.quoted.double.coffee']
  359. })
  360. ).toBe(100);
  361. }));
  362. });
  363. describe('.unset(keyPath, {source, scopeSelector})', () => {
  364. beforeEach(() =>
  365. atom.config.setSchema('foo', {
  366. type: 'object',
  367. properties: {
  368. bar: {
  369. type: 'object',
  370. properties: {
  371. baz: {
  372. type: 'integer',
  373. default: 0
  374. },
  375. ok: {
  376. type: 'integer',
  377. default: 0
  378. }
  379. }
  380. },
  381. quux: {
  382. type: 'integer',
  383. default: 0
  384. }
  385. }
  386. })
  387. );
  388. it('sets the value of the key path to its default', () => {
  389. atom.config.setDefaults('a', { b: 3 });
  390. atom.config.set('a.b', 4);
  391. expect(atom.config.get('a.b')).toBe(4);
  392. atom.config.unset('a.b');
  393. expect(atom.config.get('a.b')).toBe(3);
  394. atom.config.set('a.c', 5);
  395. expect(atom.config.get('a.c')).toBe(5);
  396. atom.config.unset('a.c');
  397. expect(atom.config.get('a.c')).toBeUndefined();
  398. });
  399. it('calls ::save()', () => {
  400. atom.config.setDefaults('a', { b: 3 });
  401. atom.config.set('a.b', 4);
  402. savedSettings.length = 0;
  403. atom.config.unset('a.c');
  404. advanceClock(500);
  405. expect(savedSettings.length).toBe(1);
  406. });
  407. describe("when no 'scopeSelector' is given", () => {
  408. describe("when a 'source' but no key-path is given", () =>
  409. it('removes all scoped settings with the given source', () => {
  410. atom.config.set('foo.bar.baz', 1, {
  411. scopeSelector: '.a',
  412. source: 'source-a'
  413. });
  414. atom.config.set('foo.bar.quux', 2, {
  415. scopeSelector: '.b',
  416. source: 'source-a'
  417. });
  418. expect(atom.config.get('foo.bar', { scope: ['.a.b'] })).toEqual({
  419. baz: 1,
  420. quux: 2
  421. });
  422. atom.config.unset(null, { source: 'source-a' });
  423. expect(atom.config.get('foo.bar', { scope: ['.a'] })).toEqual({
  424. baz: 0,
  425. ok: 0
  426. });
  427. }));
  428. describe("when a 'source' and a key-path is given", () =>
  429. it('removes all scoped settings with the given source and key-path', () => {
  430. atom.config.set('foo.bar.baz', 1);
  431. atom.config.set('foo.bar.baz', 2, {
  432. scopeSelector: '.a',
  433. source: 'source-a'
  434. });
  435. atom.config.set('foo.bar.baz', 3, {
  436. scopeSelector: '.a.b',
  437. source: 'source-b'
  438. });
  439. expect(atom.config.get('foo.bar.baz', { scope: ['.a.b'] })).toEqual(
  440. 3
  441. );
  442. atom.config.unset('foo.bar.baz', { source: 'source-b' });
  443. expect(atom.config.get('foo.bar.baz', { scope: ['.a.b'] })).toEqual(
  444. 2
  445. );
  446. expect(atom.config.get('foo.bar.baz')).toEqual(1);
  447. }));
  448. describe("when no 'source' is given", () =>
  449. it('removes all scoped and unscoped properties for that key-path', () => {
  450. atom.config.setDefaults('foo.bar', { baz: 100 });
  451. atom.config.set(
  452. 'foo.bar',
  453. { baz: 1, ok: 2 },
  454. { scopeSelector: '.a' }
  455. );
  456. atom.config.set(
  457. 'foo.bar',
  458. { baz: 11, ok: 12 },
  459. { scopeSelector: '.b' }
  460. );
  461. atom.config.set('foo.bar', { baz: 21, ok: 22 });
  462. atom.config.unset('foo.bar.baz');
  463. expect(atom.config.get('foo.bar.baz', { scope: ['.a'] })).toBe(100);
  464. expect(atom.config.get('foo.bar.baz', { scope: ['.b'] })).toBe(100);
  465. expect(atom.config.get('foo.bar.baz')).toBe(100);
  466. expect(atom.config.get('foo.bar.ok', { scope: ['.a'] })).toBe(2);
  467. expect(atom.config.get('foo.bar.ok', { scope: ['.b'] })).toBe(12);
  468. expect(atom.config.get('foo.bar.ok')).toBe(22);
  469. }));
  470. });
  471. describe("when a 'scopeSelector' is given", () => {
  472. it('restores the global default when no scoped default set', () => {
  473. atom.config.setDefaults('foo', { bar: { baz: 10 } });
  474. atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee' });
  475. expect(
  476. atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] })
  477. ).toBe(55);
  478. atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' });
  479. expect(
  480. atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] })
  481. ).toBe(10);
  482. });
  483. it('restores the scoped default when a scoped default is set', () => {
  484. atom.config.setDefaults('foo', { bar: { baz: 10 } });
  485. atom.config.set('foo.bar.baz', 42, {
  486. scopeSelector: '.source.coffee',
  487. source: 'some-source'
  488. });
  489. atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee' });
  490. atom.config.set('foo.bar.ok', 100, { scopeSelector: '.source.coffee' });
  491. expect(
  492. atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] })
  493. ).toBe(55);
  494. atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' });
  495. expect(
  496. atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] })
  497. ).toBe(42);
  498. expect(
  499. atom.config.get('foo.bar.ok', { scope: ['.source.coffee'] })
  500. ).toBe(100);
  501. });
  502. it('calls ::save()', () => {
  503. atom.config.setDefaults('foo', { bar: { baz: 10 } });
  504. atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee' });
  505. savedSettings.length = 0;
  506. atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' });
  507. advanceClock(150);
  508. expect(savedSettings.length).toBe(1);
  509. });
  510. it('allows removing settings for a specific source and scope selector', () => {
  511. atom.config.set('foo.bar.baz', 55, {
  512. scopeSelector: '.source.coffee',
  513. source: 'source-a'
  514. });
  515. atom.config.set('foo.bar.baz', 65, {
  516. scopeSelector: '.source.coffee',
  517. source: 'source-b'
  518. });
  519. expect(
  520. atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] })
  521. ).toBe(65);
  522. atom.config.unset('foo.bar.baz', {
  523. source: 'source-b',
  524. scopeSelector: '.source.coffee'
  525. });
  526. expect(
  527. atom.config.get('foo.bar.baz', {
  528. scope: ['.source.coffee', '.string']
  529. })
  530. ).toBe(55);
  531. });
  532. it('allows removing all settings for a specific source', () => {
  533. atom.config.set('foo.bar.baz', 55, {
  534. scopeSelector: '.source.coffee',
  535. source: 'source-a'
  536. });
  537. atom.config.set('foo.bar.baz', 65, {
  538. scopeSelector: '.source.coffee',
  539. source: 'source-b'
  540. });
  541. atom.config.set('foo.bar.ok', 65, {
  542. scopeSelector: '.source.coffee',
  543. source: 'source-b'
  544. });
  545. expect(
  546. atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] })
  547. ).toBe(65);
  548. atom.config.unset(null, {
  549. source: 'source-b',
  550. scopeSelector: '.source.coffee'
  551. });
  552. expect(
  553. atom.config.get('foo.bar.baz', {
  554. scope: ['.source.coffee', '.string']
  555. })
  556. ).toBe(55);
  557. expect(
  558. atom.config.get('foo.bar.ok', {
  559. scope: ['.source.coffee', '.string']
  560. })
  561. ).toBe(0);
  562. });
  563. it('does not call ::save or add a scoped property when no value has been set', () => {
  564. // see https://github.com/atom/atom/issues/4175
  565. atom.config.setDefaults('foo', { bar: { baz: 10 } });
  566. atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' });
  567. expect(
  568. atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] })
  569. ).toBe(10);
  570. expect(savedSettings.length).toBe(0);
  571. const scopedProperties = atom.config.scopedSettingsStore.propertiesForSource(
  572. 'user-config'
  573. );
  574. expect(scopedProperties['.coffee.source']).toBeUndefined();
  575. });
  576. it('removes the scoped value when it was the only set value on the object', () => {
  577. atom.config.setDefaults('foo', { bar: { baz: 10 } });
  578. atom.config.set('foo.bar.baz', 55, { scopeSelector: '.source.coffee' });
  579. atom.config.set('foo.bar.ok', 20, { scopeSelector: '.source.coffee' });
  580. expect(
  581. atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] })
  582. ).toBe(55);
  583. advanceClock(150);
  584. savedSettings.length = 0;
  585. atom.config.unset('foo.bar.baz', { scopeSelector: '.source.coffee' });
  586. expect(
  587. atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] })
  588. ).toBe(10);
  589. expect(
  590. atom.config.get('foo.bar.ok', { scope: ['.source.coffee'] })
  591. ).toBe(20);
  592. advanceClock(150);
  593. expect(savedSettings[0]['.coffee.source']).toEqual({
  594. foo: {
  595. bar: {
  596. ok: 20
  597. }
  598. }
  599. });
  600. atom.config.unset('foo.bar.ok', { scopeSelector: '.source.coffee' });
  601. advanceClock(150);
  602. expect(savedSettings.length).toBe(2);
  603. expect(savedSettings[1]['.coffee.source']).toBeUndefined();
  604. });
  605. it('does not call ::save when the value is already at the default', () => {
  606. atom.config.setDefaults('foo', { bar: { baz: 10 } });
  607. atom.config.set('foo.bar.baz', 55);
  608. advanceClock(150);
  609. savedSettings.length = 0;
  610. atom.config.unset('foo.bar.ok', { scopeSelector: '.source.coffee' });
  611. advanceClock(150);
  612. expect(savedSettings.length).toBe(0);
  613. expect(
  614. atom.config.get('foo.bar.baz', { scope: ['.source.coffee'] })
  615. ).toBe(55);
  616. });
  617. });
  618. });
  619. describe('.onDidChange(keyPath, {scope})', () => {
  620. let observeHandler = [];
  621. describe('when a keyPath is specified', () => {
  622. beforeEach(() => {
  623. observeHandler = jasmine.createSpy('observeHandler');
  624. atom.config.set('foo.bar.baz', 'value 1');
  625. atom.config.onDidChange('foo.bar.baz', observeHandler);
  626. });
  627. it('does not fire the given callback with the current value at the keypath', () =>
  628. expect(observeHandler).not.toHaveBeenCalled());
  629. it('fires the callback every time the observed value changes', () => {
  630. atom.config.set('foo.bar.baz', 'value 2');
  631. expect(observeHandler).toHaveBeenCalledWith({
  632. newValue: 'value 2',
  633. oldValue: 'value 1'
  634. });
  635. observeHandler.reset();
  636. observeHandler.andCallFake(() => {
  637. throw new Error('oops');
  638. });
  639. expect(() => atom.config.set('foo.bar.baz', 'value 1')).toThrow('oops');
  640. expect(observeHandler).toHaveBeenCalledWith({
  641. newValue: 'value 1',
  642. oldValue: 'value 2'
  643. });
  644. observeHandler.reset();
  645. // Regression: exception in earlier handler shouldn't put observer
  646. // into a bad state.
  647. atom.config.set('something.else', 'new value');
  648. expect(observeHandler).not.toHaveBeenCalled();
  649. });
  650. });
  651. describe('when a keyPath is not specified', () => {
  652. beforeEach(() => {
  653. observeHandler = jasmine.createSpy('observeHandler');
  654. atom.config.set('foo.bar.baz', 'value 1');
  655. atom.config.onDidChange(observeHandler);
  656. });
  657. it('does not fire the given callback initially', () =>
  658. expect(observeHandler).not.toHaveBeenCalled());
  659. it('fires the callback every time any value changes', () => {
  660. observeHandler.reset(); // clear the initial call
  661. atom.config.set('foo.bar.baz', 'value 2');
  662. expect(observeHandler).toHaveBeenCalled();
  663. expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.baz).toBe(
  664. 'value 2'
  665. );
  666. expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.baz).toBe(
  667. 'value 1'
  668. );
  669. observeHandler.reset();
  670. atom.config.set('foo.bar.baz', 'value 1');
  671. expect(observeHandler).toHaveBeenCalled();
  672. expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.baz).toBe(
  673. 'value 1'
  674. );
  675. expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.baz).toBe(
  676. 'value 2'
  677. );
  678. observeHandler.reset();
  679. atom.config.set('foo.bar.int', 1);
  680. expect(observeHandler).toHaveBeenCalled();
  681. expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.int).toBe(
  682. 1
  683. );
  684. expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.int).toBe(
  685. undefined
  686. );
  687. });
  688. });
  689. describe("when a 'scope' is given", () =>
  690. it('calls the supplied callback when the value at the descriptor/keypath changes', () => {
  691. const changeSpy = jasmine.createSpy('onDidChange callback');
  692. atom.config.onDidChange(
  693. 'foo.bar.baz',
  694. { scope: ['.source.coffee', '.string.quoted.double.coffee'] },
  695. changeSpy
  696. );
  697. atom.config.set('foo.bar.baz', 12);
  698. expect(changeSpy).toHaveBeenCalledWith({
  699. oldValue: undefined,
  700. newValue: 12
  701. });
  702. changeSpy.reset();
  703. atom.config.set('foo.bar.baz', 22, {
  704. scopeSelector: '.source .string.quoted.double',
  705. source: 'a'
  706. });
  707. expect(changeSpy).toHaveBeenCalledWith({ oldValue: 12, newValue: 22 });
  708. changeSpy.reset();
  709. atom.config.set('foo.bar.baz', 42, {
  710. scopeSelector: '.source.coffee .string.quoted.double.coffee',
  711. source: 'b'
  712. });
  713. expect(changeSpy).toHaveBeenCalledWith({ oldValue: 22, newValue: 42 });
  714. changeSpy.reset();
  715. atom.config.unset(null, {
  716. scopeSelector: '.source.coffee .string.quoted.double.coffee',
  717. source: 'b'
  718. });
  719. expect(changeSpy).toHaveBeenCalledWith({ oldValue: 42, newValue: 22 });
  720. changeSpy.reset();
  721. atom.config.unset(null, {
  722. scopeSelector: '.source .string.quoted.double',
  723. source: 'a'
  724. });
  725. expect(changeSpy).toHaveBeenCalledWith({ oldValue: 22, newValue: 12 });
  726. changeSpy.reset();
  727. atom.config.set('foo.bar.baz', undefined);
  728. expect(changeSpy).toHaveBeenCalledWith({
  729. oldValue: 12,
  730. newValue: undefined
  731. });
  732. changeSpy.reset();
  733. }));
  734. });
  735. describe('.observe(keyPath, {scope})', () => {
  736. let [observeHandler, observeSubscription] = [];
  737. beforeEach(() => {
  738. observeHandler = jasmine.createSpy('observeHandler');
  739. atom.config.set('foo.bar.baz', 'value 1');
  740. observeSubscription = atom.config.observe('foo.bar.baz', observeHandler);
  741. });
  742. it('fires the given callback with the current value at the keypath', () =>
  743. expect(observeHandler).toHaveBeenCalledWith('value 1'));
  744. it('fires the callback every time the observed value changes', () => {
  745. observeHandler.reset(); // clear the initial call
  746. atom.config.set('foo.bar.baz', 'value 2');
  747. expect(observeHandler).toHaveBeenCalledWith('value 2');
  748. observeHandler.reset();
  749. atom.config.set('foo.bar.baz', 'value 1');
  750. expect(observeHandler).toHaveBeenCalledWith('value 1');
  751. advanceClock(100); // complete pending save that was requested in ::set
  752. observeHandler.reset();
  753. atom.config.resetUserSettings({ foo: {} });
  754. expect(observeHandler).toHaveBeenCalledWith(undefined);
  755. });
  756. it('fires the callback when the observed value is deleted', () => {
  757. observeHandler.reset(); // clear the initial call
  758. atom.config.set('foo.bar.baz', undefined);
  759. expect(observeHandler).toHaveBeenCalledWith(undefined);
  760. });
  761. it('fires the callback when the full key path goes into and out of existence', () => {
  762. observeHandler.reset(); // clear the initial call
  763. atom.config.set('foo.bar', undefined);
  764. expect(observeHandler).toHaveBeenCalledWith(undefined);
  765. observeHandler.reset();
  766. atom.config.set('foo.bar.baz', "i'm back");
  767. expect(observeHandler).toHaveBeenCalledWith("i'm back");
  768. });
  769. it('does not fire the callback once the subscription is disposed', () => {
  770. observeHandler.reset(); // clear the initial call
  771. observeSubscription.dispose();
  772. atom.config.set('foo.bar.baz', 'value 2');
  773. expect(observeHandler).not.toHaveBeenCalled();
  774. });
  775. it('does not fire the callback for a similarly named keyPath', () => {
  776. const bazCatHandler = jasmine.createSpy('bazCatHandler');
  777. observeSubscription = atom.config.observe(
  778. 'foo.bar.bazCat',
  779. bazCatHandler
  780. );
  781. bazCatHandler.reset();
  782. atom.config.set('foo.bar.baz', 'value 10');
  783. expect(bazCatHandler).not.toHaveBeenCalled();
  784. });
  785. describe("when a 'scope' is given", () => {
  786. let otherHandler = null;
  787. beforeEach(() => {
  788. observeSubscription.dispose();
  789. otherHandler = jasmine.createSpy('otherHandler');
  790. });
  791. it('allows settings to be observed in a specific scope', () => {
  792. atom.config.observe(
  793. 'foo.bar.baz',
  794. { scope: ['.some.scope'] },
  795. observeHandler
  796. );
  797. atom.config.observe(
  798. 'foo.bar.baz',
  799. { scope: ['.another.scope'] },
  800. otherHandler
  801. );
  802. atom.config.set('foo.bar.baz', 'value 2', { scopeSelector: '.some' });
  803. expect(observeHandler).toHaveBeenCalledWith('value 2');
  804. expect(otherHandler).not.toHaveBeenCalledWith('value 2');
  805. });
  806. it('calls the callback when properties with more specific selectors are removed', () => {
  807. const changeSpy = jasmine.createSpy();
  808. atom.config.observe(
  809. 'foo.bar.baz',
  810. { scope: ['.source.coffee', '.string.quoted.double.coffee'] },
  811. changeSpy
  812. );
  813. expect(changeSpy).toHaveBeenCalledWith('value 1');
  814. changeSpy.reset();
  815. atom.config.set('foo.bar.baz', 12);
  816. expect(changeSpy).toHaveBeenCalledWith(12);
  817. changeSpy.reset();
  818. atom.config.set('foo.bar.baz', 22, {
  819. scopeSelector: '.source .string.quoted.double',
  820. source: 'a'
  821. });
  822. expect(changeSpy).toHaveBeenCalledWith(22);
  823. changeSpy.reset();
  824. atom.config.set('foo.bar.baz', 42, {
  825. scopeSelector: '.source.coffee .string.quoted.double.coffee',
  826. source: 'b'
  827. });
  828. expect(changeSpy).toHaveBeenCalledWith(42);
  829. changeSpy.reset();
  830. atom.config.unset(null, {
  831. scopeSelector: '.source.coffee .string.quoted.double.coffee',
  832. source: 'b'
  833. });
  834. expect(changeSpy).toHaveBeenCalledWith(22);
  835. changeSpy.reset();
  836. atom.config.unset(null, {
  837. scopeSelector: '.source .string.quoted.double',
  838. source: 'a'
  839. });
  840. expect(changeSpy).toHaveBeenCalledWith(12);
  841. changeSpy.reset();
  842. atom.config.set('foo.bar.baz', undefined);
  843. expect(changeSpy).toHaveBeenCalledWith(undefined);
  844. changeSpy.reset();
  845. });
  846. });
  847. });
  848. describe('.transact(callback)', () => {
  849. let changeSpy = null;
  850. beforeEach(() => {
  851. changeSpy = jasmine.createSpy('onDidChange callback');
  852. atom.config.onDidChange('foo.bar.baz', changeSpy);
  853. });
  854. it('allows only one change event for the duration of the given callback', () => {
  855. atom.config.transact(() => {
  856. atom.config.set('foo.bar.baz', 1);
  857. atom.config.set('foo.bar.baz', 2);
  858. atom.config.set('foo.bar.baz', 3);
  859. });
  860. expect(changeSpy.callCount).toBe(1);
  861. expect(changeSpy.argsForCall[0][0]).toEqual({
  862. newValue: 3,
  863. oldValue: undefined
  864. });
  865. });
  866. it('does not emit an event if no changes occur while paused', () => {
  867. atom.config.transact(() => {});
  868. expect(changeSpy).not.toHaveBeenCalled();
  869. });
  870. });
  871. describe('.transactAsync(callback)', () => {
  872. let changeSpy = null;
  873. beforeEach(() => {
  874. changeSpy = jasmine.createSpy('onDidChange callback');
  875. atom.config.onDidChange('foo.bar.baz', changeSpy);
  876. });
  877. it('allows only one change event for the duration of the given promise if it gets resolved', () => {
  878. let promiseResult = null;
  879. const transactionPromise = atom.config.transactAsync(() => {
  880. atom.config.set('foo.bar.baz', 1);
  881. atom.config.set('foo.bar.baz', 2);
  882. atom.config.set('foo.bar.baz', 3);
  883. return Promise.resolve('a result');
  884. });
  885. waitsForPromise(() =>
  886. transactionPromise.then(result => {
  887. promiseResult = result;
  888. })
  889. );
  890. runs(() => {
  891. expect(promiseResult).toBe('a result');
  892. expect(changeSpy.callCount).toBe(1);
  893. expect(changeSpy.argsForCall[0][0]).toEqual({
  894. newValue: 3,
  895. oldValue: undefined
  896. });
  897. });
  898. });
  899. it('allows only one change event for the duration of the given promise if it gets rejected', () => {
  900. let promiseError = null;
  901. const transactionPromise = atom.config.transactAsync(() => {
  902. atom.config.set('foo.bar.baz', 1);
  903. atom.config.set('foo.bar.baz', 2);
  904. atom.config.set('foo.bar.baz', 3);
  905. return Promise.reject(new Error('an error'));
  906. });
  907. waitsForPromise(() =>
  908. transactionPromise.catch(error => {
  909. promiseError = error;
  910. })
  911. );
  912. runs(() => {
  913. expect(promiseError.message).toBe('an error');
  914. expect(changeSpy.callCount).toBe(1);
  915. expect(changeSpy.argsForCall[0][0]).toEqual({
  916. newValue: 3,
  917. oldValue: undefined
  918. });
  919. });
  920. });
  921. it('allows only one change event even when the given callback throws', () => {
  922. const error = new Error('Oops!');
  923. let promiseError = null;
  924. const transactionPromise = atom.config.transactAsync(() => {
  925. atom.config.set('foo.bar.baz', 1);
  926. atom.config.set('foo.bar.baz', 2);
  927. atom.config.set('foo.bar.baz', 3);
  928. throw error;
  929. });
  930. waitsForPromise(() =>
  931. transactionPromise.catch(e => {
  932. promiseError = e;
  933. })
  934. );
  935. runs(() => {
  936. expect(promiseError).toBe(error);
  937. expect(changeSpy.callCount).toBe(1);
  938. expect(changeSpy.argsForCall[0][0]).toEqual({
  939. newValue: 3,
  940. oldValue: undefined
  941. });
  942. });
  943. });
  944. });
  945. describe('.getSources()', () => {
  946. it("returns an array of all of the config's source names", () => {
  947. expect(atom.config.getSources()).toEqual([]);
  948. atom.config.set('a.b', 1, { scopeSelector: '.x1', source: 'source-1' });
  949. atom.config.set('a.c', 1, { scopeSelector: '.x1', source: 'source-1' });
  950. atom.config.set('a.b', 2, { scopeSelector: '.x2', source: 'source-2' });
  951. atom.config.set('a.b', 1, { scopeSelector: '.x3', source: 'source-3' });
  952. expect(atom.config.getSources()).toEqual([
  953. 'source-1',
  954. 'source-2',
  955. 'source-3'
  956. ]);
  957. });
  958. });
  959. describe('.save()', () => {
  960. it('calls the save callback with any non-default properties', () => {
  961. atom.config.set('a.b.c', 1);
  962. atom.config.set('a.b.d', 2);
  963. atom.config.set('x.y.z', 3);
  964. atom.config.setDefaults('a.b', { e: 4, f: 5 });
  965. atom.config.save();
  966. expect(savedSettings).toEqual([{ '*': atom.config.settings }]);
  967. });
  968. it('serializes properties in alphabetical order', () => {
  969. atom.config.set('foo', 1);
  970. atom.config.set('bar', 2);
  971. atom.config.set('baz.foo', 3);
  972. atom.config.set('baz.bar', 4);
  973. savedSettings.length = 0;
  974. atom.config.save();
  975. const writtenConfig = savedSettings[0];
  976. expect(writtenConfig).toEqual({ '*': atom.config.settings });
  977. let expectedKeys = ['bar', 'baz', 'foo'];
  978. let foundKeys = [];
  979. for (const key in writtenConfig['*']) {
  980. if (expectedKeys.includes(key)) {
  981. foundKeys.push(key);
  982. }
  983. }
  984. expect(foundKeys).toEqual(expectedKeys);
  985. expectedKeys = ['bar', 'foo'];
  986. foundKeys = [];
  987. for (const key in writtenConfig['*']['baz']) {
  988. if (expectedKeys.includes(key)) {
  989. foundKeys.push(key);
  990. }
  991. }
  992. expect(foundKeys).toEqual(expectedKeys);
  993. });
  994. describe('when scoped settings are defined', () => {
  995. it('serializes any explicitly set config settings', () => {
  996. atom.config.set('foo.bar', 'ruby', { scopeSelector: '.source.ruby' });
  997. atom.config.set('foo.omg', 'wow', { scopeSelector: '.source.ruby' });
  998. atom.config.set('foo.bar', 'coffee', {
  999. scopeSelector: '.source.coffee'
  1000. });
  1001. savedSettings.length = 0;
  1002. atom.config.save();
  1003. const writtenConfig = savedSettings[0];
  1004. expect(writtenConfig).toEqualJson({
  1005. '*': atom.config.settings,
  1006. '.ruby.source': {
  1007. foo: {
  1008. bar: 'ruby',
  1009. omg: 'wow'
  1010. }
  1011. },
  1012. '.coffee.source': {
  1013. foo: {
  1014. bar: 'coffee'
  1015. }
  1016. }
  1017. });
  1018. });
  1019. });
  1020. });
  1021. describe('.resetUserSettings()', () => {
  1022. beforeEach(() => {
  1023. atom.config.setSchema('foo', {
  1024. type: 'object',
  1025. properties: {
  1026. bar: {
  1027. type: 'string',
  1028. default: 'def'
  1029. },
  1030. int: {
  1031. type: 'integer',
  1032. default: 12
  1033. }
  1034. }
  1035. });
  1036. });
  1037. describe('when the config file contains scoped settings', () => {
  1038. it('updates the config data based on the file contents', () => {
  1039. atom.config.resetUserSettings({
  1040. '*': {
  1041. foo: {
  1042. bar: 'baz'
  1043. }
  1044. },
  1045. '.source.ruby': {
  1046. foo: {
  1047. bar: 'more-specific'
  1048. }
  1049. }
  1050. });
  1051. expect(atom.config.get('foo.bar')).toBe('baz');
  1052. expect(atom.config.get('foo.bar', { scope: ['.source.ruby'] })).toBe(
  1053. 'more-specific'
  1054. );
  1055. });
  1056. });
  1057. describe('when the config file does not conform to the schema', () => {
  1058. it('validates and does not load the incorrect values', () => {
  1059. atom.config.resetUserSettings({
  1060. '*': {
  1061. foo: {
  1062. bar: 'omg',
  1063. int: 'baz'
  1064. }
  1065. },
  1066. '.source.ruby': {
  1067. foo: {
  1068. bar: 'scoped',
  1069. int: 'nope'
  1070. }
  1071. }
  1072. });
  1073. expect(atom.config.get('foo.int')).toBe(12);
  1074. expect(atom.config.get('foo.bar')).toBe('omg');
  1075. expect(atom.config.get('foo.int', { scope: ['.source.ruby'] })).toBe(
  1076. 12
  1077. );
  1078. expect(atom.config.get('foo.bar', { scope: ['.source.ruby'] })).toBe(
  1079. 'scoped'
  1080. );
  1081. });
  1082. });
  1083. it('updates the config data based on the file contents', () => {
  1084. atom.config.resetUserSettings({ foo: { bar: 'baz' } });
  1085. expect(atom.config.get('foo.bar')).toBe('baz');
  1086. });
  1087. it('notifies observers for updated keypaths on load', () => {
  1088. const observeHandler = jasmine.createSpy('observeHandler');
  1089. atom.config.observe('foo.bar', observeHandler);
  1090. atom.config.resetUserSettings({ foo: { bar: 'baz' } });
  1091. expect(observeHandler).toHaveBeenCalledWith('baz');
  1092. });
  1093. describe('when the config file contains values that do not adhere to the schema', () => {
  1094. it('updates the only the settings that have values matching the schema', () => {
  1095. atom.config.resetUserSettings({
  1096. foo: {
  1097. bar: 'baz',
  1098. int: 'bad value'
  1099. }
  1100. });
  1101. expect(atom.config.get('foo.bar')).toBe('baz');
  1102. expect(atom.config.get('foo.int')).toBe(12);
  1103. expect(console.warn).toHaveBeenCalled();
  1104. expect(console.warn.mostRecentCall.args[0]).toContain('foo.int');
  1105. });
  1106. });
  1107. it('does not fire a change event for paths that did not change', () => {
  1108. atom.config.resetUserSettings({
  1109. foo: { bar: 'baz', int: 3 }
  1110. });
  1111. const noChangeSpy = jasmine.createSpy('unchanged');
  1112. atom.config.onDidChange('foo.bar', noChangeSpy);
  1113. atom.config.resetUserSettings({
  1114. foo: { bar: 'baz', int: 4 }
  1115. });
  1116. expect(noChangeSpy).not.toHaveBeenCalled();
  1117. expect(atom.config.get('foo.bar')).toBe('baz');
  1118. expect(atom.config.get('foo.int')).toBe(4);
  1119. });
  1120. it('does not fire a change event for paths whose non-primitive values did not change', () => {
  1121. atom.config.setSchema('foo.bar', {
  1122. type: 'array',
  1123. items: {
  1124. type: 'string'
  1125. }
  1126. });
  1127. atom.config.resetUserSettings({
  1128. foo: { bar: ['baz', 'quux'], int: 2 }
  1129. });
  1130. const noChangeSpy = jasmine.createSpy('unchanged');
  1131. atom.config.onDidChange('foo.bar', noChangeSpy);
  1132. atom.config.resetUserSettings({
  1133. foo: { bar: ['baz', 'quux'], int: 2 }
  1134. });
  1135. expect(noChangeSpy).not.toHaveBeenCalled();
  1136. expect(atom.config.get('foo.bar')).toEqual(['baz', 'quux']);
  1137. });
  1138. describe('when a setting with a default is removed', () => {
  1139. it('resets the setting back to the default', () => {
  1140. atom.config.resetUserSettings({
  1141. foo: { bar: ['baz', 'quux'], int: 2 }
  1142. });
  1143. const events = [];
  1144. atom.config.onDidChange('foo.int', event => events.push(event));
  1145. atom.config.resetUserSettings({
  1146. foo: { bar: ['baz', 'quux'] }
  1147. });
  1148. expect(events.length).toBe(1);
  1149. expect(events[0]).toEqual({ oldValue: 2, newValue: 12 });
  1150. });
  1151. });
  1152. it('keeps all the global scope settings after overriding one', () => {
  1153. atom.config.resetUserSettings({
  1154. '*': {
  1155. foo: {
  1156. bar: 'baz',
  1157. int: 99
  1158. }
  1159. }
  1160. });
  1161. atom.config.set('foo.int', 50, { scopeSelector: '*' });
  1162. advanceClock(100);
  1163. expect(savedSettings[0]['*'].foo).toEqual({
  1164. bar: 'baz',
  1165. int: 50
  1166. });
  1167. expect(atom.config.get('foo.int', { scope: ['*'] })).toEqual(50);
  1168. expect(atom.config.get('foo.bar', { scope: ['*'] })).toEqual('baz');
  1169. expect(atom.config.get('foo.int')).toEqual(50);
  1170. });
  1171. });
  1172. describe('.pushAtKeyPath(keyPath, value)', () => {
  1173. it('pushes the given value to the array at the key path and updates observers', () => {
  1174. atom.config.set('foo.bar.baz', ['a']);
  1175. const observeHandler = jasmine.createSpy('observeHandler');
  1176. atom.config.observe('foo.bar.baz', observeHandler);
  1177. observeHandler.reset();
  1178. expect(atom.config.pushAtKeyPath('foo.bar.baz', 'b')).toBe(2);
  1179. expect(atom.config.get('foo.bar.baz')).toEqual(['a', 'b']);
  1180. expect(observeHandler).toHaveBeenCalledWith(
  1181. atom.config.get('foo.bar.baz')
  1182. );
  1183. });
  1184. });
  1185. describe('.unshiftAtKeyPath(keyPath, value)', () => {
  1186. it('unshifts the given value to the array at the key path and updates observers', () => {
  1187. atom.config.set('foo.bar.baz', ['b']);
  1188. const observeHandler = jasmine.createSpy('observeHandler');
  1189. atom.config.observe('foo.bar.baz', observeHandler);
  1190. observeHandler.reset();
  1191. expect(atom.config.unshiftAtKeyPath('foo.bar.baz', 'a')).toBe(2);
  1192. expect(atom.config.get('foo.bar.baz')).toEqual(['a', 'b']);
  1193. expect(observeHandler).toHaveBeenCalledWith(
  1194. atom.config.get('foo.bar.baz')
  1195. );
  1196. });
  1197. });
  1198. describe('.removeAtKeyPath(keyPath, value)', () => {
  1199. it('removes the given value from the array at the key path and updates observers', () => {
  1200. atom.config.set('foo.bar.baz', ['a', 'b', 'c']);
  1201. const observeHandler = jasmine.createSpy('observeHandler');
  1202. atom.config.observe('foo.bar.baz', observeHandler);
  1203. observeHandler.reset();
  1204. expect(atom.config.removeAtKeyPath('foo.bar.baz', 'b')).toEqual([
  1205. 'a',
  1206. 'c'
  1207. ]);
  1208. expect(atom.config.get('foo.bar.baz')).toEqual(['a', 'c']);
  1209. expect(observeHandler).toHaveBeenCalledWith(
  1210. atom.config.get('foo.bar.baz')
  1211. );
  1212. });
  1213. });
  1214. describe('.setDefaults(keyPath, defaults)', () => {
  1215. it('assigns any previously-unassigned keys to the object at the key path', () => {
  1216. atom.config.set('foo.bar.baz', { a: 1 });
  1217. atom.config.setDefaults('foo.bar.baz', { a: 2, b: 3, c: 4 });
  1218. expect(atom.config.get('foo.bar.baz.a')).toBe(1);
  1219. expect(atom.config.get('foo.bar.baz.b')).toBe(3);
  1220. expect(atom.config.get('foo.bar.baz.c')).toBe(4);
  1221. atom.config.setDefaults('foo.quux', { x: 0, y: 1 });
  1222. expect(atom.config.get('foo.quux.x')).toBe(0);
  1223. expect(atom.config.get('foo.quux.y')).toBe(1);
  1224. });
  1225. it('emits an updated event', () => {
  1226. const updatedCallback = jasmine.createSpy('updated');
  1227. atom.config.onDidChange('foo.bar.baz.a', updatedCallback);
  1228. expect(updatedCallback.callCount).toBe(0);
  1229. atom.config.setDefaults('foo.bar.baz', { a: 2 });
  1230. expect(updatedCallback.callCount).toBe(1);
  1231. });
  1232. });
  1233. describe('.setSchema(keyPath, schema)', () => {
  1234. it('creates a properly nested schema', () => {
  1235. const schema = {
  1236. type: 'object',
  1237. properties: {
  1238. anInt: {
  1239. type: 'integer',
  1240. default: 12
  1241. }
  1242. }
  1243. };
  1244. atom.config.setSchema('foo.bar', schema);
  1245. expect(atom.config.getSchema('foo')).toEqual({
  1246. type: 'object',
  1247. properties: {
  1248. bar: {
  1249. type: 'object',
  1250. properties: {
  1251. anInt: {
  1252. type: 'integer',
  1253. default: 12
  1254. }
  1255. }
  1256. }
  1257. }
  1258. });
  1259. });
  1260. it('sets defaults specified by the schema', () => {
  1261. const schema = {
  1262. type: 'object',
  1263. properties: {
  1264. anInt: {
  1265. type: 'integer',
  1266. default: 12
  1267. },
  1268. anObject: {
  1269. type: 'object',
  1270. properties: {
  1271. nestedInt: {
  1272. type: 'integer',
  1273. default: 24
  1274. },
  1275. nestedObject: {
  1276. type: 'object',
  1277. properties: {
  1278. superNestedInt: {
  1279. type: 'integer',
  1280. default: 36
  1281. }
  1282. }
  1283. }
  1284. }
  1285. }
  1286. }
  1287. };
  1288. atom.config.setSchema('foo.bar', schema);
  1289. expect(atom.config.get('foo.bar.anInt')).toBe(12);
  1290. expect(atom.config.get('foo.bar.anObject')).toEqual({
  1291. nestedInt: 24,
  1292. nestedObject: {
  1293. superNestedInt: 36
  1294. }
  1295. });
  1296. expect(atom.config.get('foo')).toEqual({
  1297. bar: {
  1298. anInt: 12,
  1299. anObject: {
  1300. nestedInt: 24,
  1301. nestedObject: {
  1302. superNestedInt: 36
  1303. }
  1304. }
  1305. }
  1306. });
  1307. atom.config.set('foo.bar.anObject.nestedObject.superNestedInt', 37);
  1308. expect(atom.config.get('foo')).toEqual({
  1309. bar: {
  1310. anInt: 12,
  1311. anObject: {
  1312. nestedInt: 24,
  1313. nestedObject: {
  1314. superNestedInt: 37
  1315. }
  1316. }
  1317. }
  1318. });
  1319. });
  1320. it('can set a non-object schema', () => {
  1321. const schema = {
  1322. type: 'integer',
  1323. default: 12
  1324. };
  1325. atom.config.setSchema('foo.bar.anInt', schema);
  1326. expect(atom.config.get('foo.bar.anInt')).toBe(12);
  1327. expect(atom.config.getSchema('foo.bar.anInt')).toEqual({
  1328. type: 'integer',
  1329. default: 12
  1330. });
  1331. });
  1332. it('allows the schema to be retrieved via ::getSchema', () => {
  1333. const schema = {
  1334. type: 'object',
  1335. properties: {
  1336. anInt: {
  1337. type: 'integer',
  1338. default: 12
  1339. }
  1340. }
  1341. };
  1342. atom.config.setSchema('foo.bar', schema);
  1343. expect(atom.config.getSchema('foo.bar')).toEqual({
  1344. type: 'object',
  1345. properties: {
  1346. anInt: {
  1347. type: 'integer',
  1348. default: 12
  1349. }
  1350. }
  1351. });
  1352. expect(atom.config.getSchema('foo.bar.anInt')).toEqual({
  1353. type: 'integer',
  1354. default: 12
  1355. });
  1356. expect(atom.config.getSchema('foo.baz')).toEqual({ type: 'any' });
  1357. expect(atom.config.getSchema('foo.bar.anInt.baz')).toBe(null);
  1358. });
  1359. it('respects the schema for scoped settings', () => {
  1360. const schema = {
  1361. type: 'string',
  1362. default: 'ok',
  1363. scopes: {
  1364. '.source.js': {
  1365. default: 'omg'
  1366. }
  1367. }
  1368. };
  1369. atom.config.setSchema('foo.bar.str', schema);
  1370. expect(atom.config.get('foo.bar.str')).toBe('ok');
  1371. expect(atom.config.get('foo.bar.str', { scope: ['.source.js'] })).toBe(
  1372. 'omg'
  1373. );
  1374. expect(
  1375. atom.config.get('foo.bar.str', { scope: ['.source.coffee'] })
  1376. ).toBe('ok');
  1377. });
  1378. describe('when a schema is added after config values have been set', () => {
  1379. let schema = null;
  1380. beforeEach(() => {
  1381. schema = {
  1382. type: 'object',
  1383. properties: {
  1384. int: {
  1385. type: 'integer',
  1386. default: 2
  1387. },
  1388. str: {
  1389. type: 'string',
  1390. default: 'def'
  1391. }
  1392. }
  1393. };
  1394. });
  1395. it('respects the new schema when values are set', () => {
  1396. expect(atom.config.set('foo.bar.str', 'global')).toBe(true);
  1397. expect(
  1398. atom.config.set('foo.bar.str', 'scoped', {
  1399. scopeSelector: '.source.js'
  1400. })
  1401. ).toBe(true);
  1402. expect(atom.config.get('foo.bar.str')).toBe('global');
  1403. expect(atom.config.get('foo.bar.str', { scope: ['.source.js'] })).toBe(
  1404. 'scoped'
  1405. );
  1406. expect(atom.config.set('foo.bar.noschema', 'nsGlobal')).toBe(true);
  1407. expect(
  1408. atom.config.set('foo.bar.noschema', 'nsScoped', {
  1409. scopeSelector: '.source.js'
  1410. })
  1411. ).toBe(true);
  1412. expect(atom.config.get('foo.bar.noschema')).toBe('nsGlobal');
  1413. expect(
  1414. atom.config.get('foo.bar.noschema', { scope: ['.source.js'] })
  1415. ).toBe('nsScoped');
  1416. expect(atom.config.set('foo.bar.int', 'nope')).toBe(true);
  1417. expect(
  1418. atom.config.set('foo.bar.int', 'notanint', {
  1419. scopeSelector: '.source.js'
  1420. })
  1421. ).toBe(true);
  1422. expect(
  1423. atom.config.set('foo.bar.int', 23, {
  1424. scopeSelector: '.source.coffee'
  1425. })
  1426. ).toBe(true);
  1427. expect(atom.config.get('foo.bar.int')).toBe('nope');
  1428. expect(atom.config.get('foo.bar.int', { scope: ['.source.js'] })).toBe(
  1429. 'notanint'
  1430. );
  1431. expect(
  1432. atom.config.get('foo.bar.int', { scope: ['.source.coffee'] })
  1433. ).toBe(23);
  1434. atom.config.setSchema('foo.bar', schema);
  1435. expect(atom.config.get('foo.bar.str')).toBe('global');
  1436. expect(atom.config.get('foo.bar.str', { scope: ['.source.js'] })).toBe(
  1437. 'scoped'
  1438. );
  1439. expect(atom.config.get('foo.bar.noschema')).toBe('nsGlobal');
  1440. expect(
  1441. atom.config.get('foo.bar.noschema', { scope: ['.source.js'] })
  1442. ).toBe('nsScoped');
  1443. expect(atom.config.get('foo.bar.int')).toBe(2);
  1444. expect(atom.config.get('foo.bar.int', { scope: ['.source.js'] })).toBe(
  1445. 2
  1446. );
  1447. expect(
  1448. atom.config.get('foo.bar.int', { scope: ['.source.coffee'] })
  1449. ).toBe(23);
  1450. });
  1451. it('sets all values that adhere to the schema', () => {
  1452. expect(atom.config.set('foo.bar.int', 10)).toBe(true);
  1453. expect(
  1454. atom.config.set('foo.bar.int', 15, { scopeSelector: '.source.js' })
  1455. ).toBe(true);
  1456. expect(
  1457. atom.config.set('foo.bar.int', 23, {
  1458. scopeSelector: '.source.coffee'
  1459. })
  1460. ).toBe(true);
  1461. expect(atom.config.get('foo.bar.int')).toBe(10);
  1462. expect(atom.config.get('foo.bar.int', { scope: ['.source.js'] })).toBe(
  1463. 15
  1464. );
  1465. expect(
  1466. atom.config.get('foo.bar.int', { scope: ['.source.coffee'] })
  1467. ).toBe(23);
  1468. atom.config.setSchema('foo.bar', schema);
  1469. expect(atom.config.get('foo.bar.int')).toBe(10);
  1470. expect(atom.config.get('foo.bar.int', { scope: ['.source.js'] })).toBe(
  1471. 15
  1472. );
  1473. expect(
  1474. atom.config.get('foo.bar.int', { scope: ['.source.coffee'] })
  1475. ).toBe(23);
  1476. });
  1477. });
  1478. describe('when the value has an "integer" type', () => {
  1479. beforeEach(() => {
  1480. const schema = {
  1481. type: 'integer',
  1482. default: 12
  1483. };
  1484. atom.config.setSchema('foo.bar.anInt', schema);
  1485. });
  1486. it('coerces a string to an int', () => {
  1487. atom.config.set('foo.bar.anInt', '123');
  1488. expect(atom.config.get('foo.bar.anInt')).toBe(123);
  1489. });
  1490. it('does not allow infinity', () => {
  1491. atom.config.set('foo.bar.anInt', Infinity);
  1492. expect(atom.config.get('foo.bar.anInt')).toBe(12);
  1493. });
  1494. it('coerces a float to an int', () => {
  1495. atom.config.set('foo.bar.anInt', 12.3);
  1496. expect(atom.config.get('foo.bar.anInt')).toBe(12);
  1497. });
  1498. it('will not set non-integers', () => {
  1499. atom.config.set('foo.bar.anInt', null);
  1500. expect(atom.config.get('foo.bar.anInt')).toBe(12);
  1501. atom.config.set('foo.bar.anInt', 'nope');
  1502. expect(atom.config.get('foo.bar.anInt')).toBe(12);
  1503. });
  1504. describe('when the minimum and maximum keys are used', () => {
  1505. beforeEach(() => {
  1506. const schema = {
  1507. type: 'integer',
  1508. minimum: 10,
  1509. maximum: 20,
  1510. default: 12
  1511. };
  1512. atom.config.setSchema('foo.bar.anInt', schema);
  1513. });
  1514. it('keeps the specified value within the specified range', () => {
  1515. atom.config.set('foo.bar.anInt', '123');
  1516. expect(atom.config.get('foo.bar.anInt')).toBe(20);
  1517. atom.config.set('foo.bar.anInt', '1');
  1518. expect(atom.config.get('foo.bar.anInt')).toBe(10);
  1519. });
  1520. });
  1521. });
  1522. describe('when the value has an "integer" and "string" type', () => {
  1523. beforeEach(() => {
  1524. const schema = {
  1525. type: ['integer', 'string'],
  1526. default: 12
  1527. };
  1528. atom.config.setSchema('foo.bar.anInt', schema);
  1529. });
  1530. it('can coerce an int, and fallback to a string', () => {
  1531. atom.config.set('foo.bar.anInt', '123');
  1532. expect(atom.config.get('foo.bar.anInt')).toBe(123);
  1533. atom.config.set('foo.bar.anInt', 'cats');
  1534. expect(atom.config.get('foo.bar.anInt')).toBe('cats');
  1535. });
  1536. });
  1537. describe('when the value has an "string" and "boolean" type', () => {
  1538. beforeEach(() => {
  1539. const schema = {
  1540. type: ['string', 'boolean'],
  1541. default: 'def'
  1542. };
  1543. atom.config.setSchema('foo.bar', schema);
  1544. });
  1545. it('can set a string, a boolean, and revert back to the default', () => {
  1546. atom.config.set('foo.bar', 'ok');
  1547. expect(atom.config.get('foo.bar')).toBe('ok');
  1548. atom.config.set('foo.bar', false);
  1549. expect(atom.config.get('foo.bar')).toBe(false);
  1550. atom.config.set('foo.bar', undefined);
  1551. expect(atom.config.get('foo.bar')).toBe('def');
  1552. });
  1553. });
  1554. describe('when the value has a "number" type', () => {
  1555. beforeEach(() => {
  1556. const schema = {
  1557. type: 'number',
  1558. default: 12.1
  1559. };
  1560. atom.config.setSchema('foo.bar.aFloat', schema);
  1561. });
  1562. it('coerces a string to a float', () => {
  1563. atom.config.set('foo.bar.aFloat', '12.23');
  1564. expect(atom.config.get('foo.bar.aFloat')).toBe(12.23);
  1565. });
  1566. it('will not set non-numbers', () => {
  1567. atom.config.set('foo.bar.aFloat', null);
  1568. expect(atom.config.get('foo.bar.aFloat')).toBe(12.1);
  1569. atom.config.set('foo.bar.aFloat', 'nope');
  1570. expect(atom.config.get('foo.bar.aFloat')).toBe(12.1);
  1571. });
  1572. describe('when the minimum and maximum keys are used', () => {
  1573. beforeEach(() => {
  1574. const schema = {
  1575. type: 'number',
  1576. minimum: 11.2,
  1577. maximum: 25.4,
  1578. default: 12.1
  1579. };
  1580. atom.config.setSchema('foo.bar.aFloat', schema);
  1581. });
  1582. it('keeps the specified value within the specified range', () => {
  1583. atom.config.set('foo.bar.aFloat', '123.2');
  1584. expect(atom.config.get('foo.bar.aFloat')).toBe(25.4);
  1585. atom.config.set('foo.bar.aFloat', '1.0');
  1586. expect(atom.config.get('foo.bar.aFloat')).toBe(11.2);
  1587. });
  1588. });
  1589. });
  1590. describe('when the value has a "boolean" type', () => {
  1591. beforeEach(() => {
  1592. const schema = {
  1593. type: 'boolean',
  1594. default: true
  1595. };
  1596. atom.config.setSchema('foo.bar.aBool', schema);
  1597. });
  1598. it('coerces various types to a boolean', () => {
  1599. atom.config.set('foo.bar.aBool', 'true');
  1600. expect(atom.config.get('foo.bar.aBool')).toBe(true);
  1601. atom.config.set('foo.bar.aBool', 'false');
  1602. expect(atom.config.get('foo.bar.aBool')).toBe(false);
  1603. atom.config.set('foo.bar.aBool', 'TRUE');
  1604. expect(atom.config.get('foo.bar.aBool')).toBe(true);
  1605. atom.config.set('foo.bar.aBool', 'FALSE');
  1606. expect(atom.config.get('foo.bar.aBool')).toBe(false);
  1607. atom.config.set('foo.bar.aBool', 1);
  1608. expect(atom.config.get('foo.bar.aBool')).toBe(false);
  1609. atom.config.set('foo.bar.aBool', 0);
  1610. expect(atom.config.get('foo.bar.aBool')).toBe(false);
  1611. atom.config.set('foo.bar.aBool', {});
  1612. expect(atom.config.get('foo.bar.aBool')).toBe(false);
  1613. atom.config.set('foo.bar.aBool', null);
  1614. expect(atom.config.get('foo.bar.aBool')).toBe(false);
  1615. });
  1616. it('reverts back to the default value when undefined is passed to set', () => {
  1617. atom.config.set('foo.bar.aBool', 'false');
  1618. expect(atom.config.get('foo.bar.aBool')).toBe(false);
  1619. atom.config.set('foo.bar.aBool', undefined);
  1620. expect(atom.config.get('foo.bar.aBool')).toBe(true);
  1621. });
  1622. });
  1623. describe('when the value has an "string" type', () => {
  1624. beforeEach(() => {
  1625. const schema = {
  1626. type: 'string',
  1627. default: 'ok'
  1628. };
  1629. atom.config.setSchema('foo.bar.aString', schema);
  1630. });
  1631. it('allows strings', () => {
  1632. atom.config.set('foo.bar.aString', 'yep');
  1633. expect(atom.config.get('foo.bar.aString')).toBe('yep');
  1634. });
  1635. it('will only set strings', () => {
  1636. expect(atom.config.set('foo.bar.aString', 123)).toBe(false);
  1637. expect(atom.config.get('foo.bar.aString')).toBe('ok');
  1638. expect(atom.config.set('foo.bar.aString', true)).toBe(false);
  1639. expect(atom.config.get('foo.bar.aString')).toBe('ok');
  1640. expect(atom.config.set('foo.bar.aString', null)).toBe(false);
  1641. expect(atom.config.get('foo.bar.aString')).toBe('ok');
  1642. expect(atom.config.set('foo.bar.aString', [])).toBe(false);
  1643. expect(atom.config.get('foo.bar.aString')).toBe('ok');
  1644. expect(atom.config.set('foo.bar.aString', { nope: 'nope' })).toBe(
  1645. false
  1646. );
  1647. expect(atom.config.get('foo.bar.aString')).toBe('ok');
  1648. });
  1649. it('does not allow setting children of that key-path', () => {
  1650. expect(atom.config.set('foo.bar.aString.something', 123)).toBe(false);
  1651. expect(atom.config.get('foo.bar.aString')).toBe('ok');
  1652. });
  1653. describe('when the schema has a "maximumLength" key', () =>
  1654. it('trims the string to be no longer than the specified maximum', () => {
  1655. const schema = {
  1656. type: 'string',
  1657. default: 'ok',
  1658. maximumLength: 3
  1659. };
  1660. atom.config.setSchema('foo.bar.aString', schema);
  1661. atom.config.set('foo.bar.aString', 'abcdefg');
  1662. expect(atom.config.get('foo.bar.aString')).toBe('abc');
  1663. }));
  1664. });
  1665. describe('when the value has an "object" type', () => {
  1666. beforeEach(() => {
  1667. const schema = {
  1668. type: 'object',
  1669. properties: {
  1670. anInt: {
  1671. type: 'integer',
  1672. default: 12
  1673. },
  1674. nestedObject: {
  1675. type: 'object',
  1676. properties: {
  1677. nestedBool: {
  1678. type: 'boolean',
  1679. default: false
  1680. }
  1681. }
  1682. }
  1683. }
  1684. };
  1685. atom.config.setSchema('foo.bar', schema);
  1686. });
  1687. it('converts and validates all the children', () => {
  1688. atom.config.set('foo.bar', {
  1689. anInt: '23',
  1690. nestedObject: {
  1691. nestedBool: 'true'
  1692. }
  1693. });
  1694. expect(atom.config.get('foo.bar')).toEqual({
  1695. anInt: 23,
  1696. nestedObject: {
  1697. nestedBool: true
  1698. }
  1699. });
  1700. });
  1701. it('will set only the values that adhere to the schema', () => {
  1702. expect(
  1703. atom.config.set('foo.bar', {
  1704. anInt: 'nope',
  1705. nestedObject: {
  1706. nestedBool: true
  1707. }
  1708. })
  1709. ).toBe(true);
  1710. expect(atom.config.get('foo.bar.anInt')).toEqual(12);
  1711. expect(atom.config.get('foo.bar.nestedObject.nestedBool')).toEqual(
  1712. true
  1713. );
  1714. });
  1715. describe('when the value has additionalProperties set to false', () =>
  1716. it('does not allow other properties to be set on the object', () => {
  1717. atom.config.setSchema('foo.bar', {
  1718. type: 'object',
  1719. properties: {
  1720. anInt: {
  1721. type: 'integer',
  1722. default: 12
  1723. }
  1724. },
  1725. additionalProperties: false
  1726. });
  1727. expect(
  1728. atom.config.set('foo.bar', { anInt: 5, somethingElse: 'ok' })
  1729. ).toBe(true);
  1730. expect(atom.config.get('foo.bar.anInt')).toBe(5);
  1731. expect(atom.config.get('foo.bar.somethingElse')).toBeUndefined();
  1732. expect(atom.config.set('foo.bar.somethingElse', { anInt: 5 })).toBe(
  1733. false
  1734. );
  1735. expect(atom.config.get('foo.bar.somethingElse')).toBeUndefined();
  1736. }));
  1737. describe('when the value has an additionalProperties schema', () =>
  1738. it('validates properties of the object against that schema', () => {
  1739. atom.config.setSchema('foo.bar', {
  1740. type: 'object',
  1741. properties: {
  1742. anInt: {
  1743. type: 'integer',
  1744. default: 12
  1745. }
  1746. },
  1747. additionalProperties: {
  1748. type: 'string'
  1749. }
  1750. });
  1751. expect(
  1752. atom.config.set('foo.bar', { anInt: 5, somethingElse: 'ok' })
  1753. ).toBe(true);
  1754. expect(atom.config.get('foo.bar.anInt')).toBe(5);
  1755. expect(atom.config.get('foo.bar.somethingElse')).toBe('ok');
  1756. expect(atom.config.set('foo.bar.somethingElse', 7)).toBe(false);
  1757. expect(atom.config.get('foo.bar.somethingElse')).toBe('ok');
  1758. expect(
  1759. atom.config.set('foo.bar', { anInt: 6, somethingElse: 7 })
  1760. ).toBe(true);
  1761. expect(atom.config.get('foo.bar.anInt')).toBe(6);
  1762. expect(atom.config.get('foo.bar.somethingElse')).toBe(undefined);
  1763. }));
  1764. });
  1765. describe('when the value has an "array" type', () => {
  1766. beforeEach(() => {
  1767. const schema = {
  1768. type: 'array',
  1769. default: [1, 2, 3],
  1770. items: {
  1771. type: 'integer'
  1772. }
  1773. };
  1774. atom.config.setSchema('foo.bar', schema);
  1775. });
  1776. it('converts an array of strings to an array of ints', () => {
  1777. atom.config.set('foo.bar', ['2', '3', '4']);
  1778. expect(atom.config.get('foo.bar')).toEqual([2, 3, 4]);
  1779. });
  1780. it('does not allow setting children of that key-path', () => {
  1781. expect(atom.config.set('foo.bar.child', 123)).toBe(false);
  1782. expect(atom.config.set('foo.bar.child.grandchild', 123)).toBe(false);
  1783. expect(atom.config.get('foo.bar')).toEqual([1, 2, 3]);
  1784. });
  1785. });
  1786. describe('when the value has a "color" type', () => {
  1787. beforeEach(() => {
  1788. const schema = {
  1789. type: 'color',
  1790. default: 'white'
  1791. };
  1792. atom.config.setSchema('foo.bar.aColor', schema);
  1793. });
  1794. it('returns a Color object', () => {
  1795. let color = atom.config.get('foo.bar.aColor');
  1796. expect(color.toHexString()).toBe('#ffffff');
  1797. expect(color.toRGBAString()).toBe('rgba(255, 255, 255, 1)');
  1798. color.red = 0;
  1799. color.green = 0;
  1800. color.blue = 0;
  1801. color.alpha = 0;
  1802. atom.config.set('foo.bar.aColor', color);
  1803. color = atom.config.get('foo.bar.aColor');
  1804. expect(color.toHexString()).toBe('#000000');
  1805. expect(color.toRGBAString()).toBe('rgba(0, 0, 0, 0)');
  1806. color.red = 300;
  1807. color.green = -200;
  1808. color.blue = -1;
  1809. color.alpha = 'not see through';
  1810. atom.config.set('foo.bar.aColor', color);
  1811. color = atom.config.get('foo.bar.aColor');
  1812. expect(color.toHexString()).toBe('#ff0000');
  1813. expect(color.toRGBAString()).toBe('rgba(255, 0, 0, 1)');
  1814. color.red = 11;
  1815. color.green = 11;
  1816. color.blue = 124;
  1817. color.alpha = 1;
  1818. atom.config.set('foo.bar.aColor', color);
  1819. color = atom.config.get('foo.bar.aColor');
  1820. expect(color.toHexString()).toBe('#0b0b7c');
  1821. expect(color.toRGBAString()).toBe('rgba(11, 11, 124, 1)');
  1822. });
  1823. it('coerces various types to a color object', () => {
  1824. atom.config.set('foo.bar.aColor', 'red');
  1825. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1826. red: 255,
  1827. green: 0,
  1828. blue: 0,
  1829. alpha: 1
  1830. });
  1831. atom.config.set('foo.bar.aColor', '#020');
  1832. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1833. red: 0,
  1834. green: 34,
  1835. blue: 0,
  1836. alpha: 1
  1837. });
  1838. atom.config.set('foo.bar.aColor', '#abcdef');
  1839. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1840. red: 171,
  1841. green: 205,
  1842. blue: 239,
  1843. alpha: 1
  1844. });
  1845. atom.config.set('foo.bar.aColor', 'rgb(1,2,3)');
  1846. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1847. red: 1,
  1848. green: 2,
  1849. blue: 3,
  1850. alpha: 1
  1851. });
  1852. atom.config.set('foo.bar.aColor', 'rgba(4,5,6,.7)');
  1853. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1854. red: 4,
  1855. green: 5,
  1856. blue: 6,
  1857. alpha: 0.7
  1858. });
  1859. atom.config.set('foo.bar.aColor', 'hsl(120,100%,50%)');
  1860. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1861. red: 0,
  1862. green: 255,
  1863. blue: 0,
  1864. alpha: 1
  1865. });
  1866. atom.config.set('foo.bar.aColor', 'hsla(120,100%,50%,0.3)');
  1867. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1868. red: 0,
  1869. green: 255,
  1870. blue: 0,
  1871. alpha: 0.3
  1872. });
  1873. atom.config.set('foo.bar.aColor', {
  1874. red: 100,
  1875. green: 255,
  1876. blue: 2,
  1877. alpha: 0.5
  1878. });
  1879. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1880. red: 100,
  1881. green: 255,
  1882. blue: 2,
  1883. alpha: 0.5
  1884. });
  1885. atom.config.set('foo.bar.aColor', { red: 255 });
  1886. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1887. red: 255,
  1888. green: 0,
  1889. blue: 0,
  1890. alpha: 1
  1891. });
  1892. atom.config.set('foo.bar.aColor', { red: 1000 });
  1893. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1894. red: 255,
  1895. green: 0,
  1896. blue: 0,
  1897. alpha: 1
  1898. });
  1899. atom.config.set('foo.bar.aColor', { red: 'dark' });
  1900. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1901. red: 0,
  1902. green: 0,
  1903. blue: 0,
  1904. alpha: 1
  1905. });
  1906. });
  1907. it('reverts back to the default value when undefined is passed to set', () => {
  1908. atom.config.set('foo.bar.aColor', undefined);
  1909. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1910. red: 255,
  1911. green: 255,
  1912. blue: 255,
  1913. alpha: 1
  1914. });
  1915. });
  1916. it('will not set non-colors', () => {
  1917. atom.config.set('foo.bar.aColor', null);
  1918. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1919. red: 255,
  1920. green: 255,
  1921. blue: 255,
  1922. alpha: 1
  1923. });
  1924. atom.config.set('foo.bar.aColor', 'nope');
  1925. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1926. red: 255,
  1927. green: 255,
  1928. blue: 255,
  1929. alpha: 1
  1930. });
  1931. atom.config.set('foo.bar.aColor', 30);
  1932. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1933. red: 255,
  1934. green: 255,
  1935. blue: 255,
  1936. alpha: 1
  1937. });
  1938. atom.config.set('foo.bar.aColor', false);
  1939. expect(atom.config.get('foo.bar.aColor')).toEqual({
  1940. red: 255,
  1941. green: 255,
  1942. blue: 255,
  1943. alpha: 1
  1944. });
  1945. });
  1946. it('returns a clone of the Color when returned in a parent object', () => {
  1947. const color1 = atom.config.get('foo.bar').aColor;
  1948. const color2 = atom.config.get('foo.bar').aColor;
  1949. expect(color1.toRGBAString()).toBe('rgba(255, 255, 255, 1)');
  1950. expect(color2.toRGBAString()).toBe('rgba(255, 255, 255, 1)');
  1951. expect(color1).not.toBe(color2);
  1952. expect(color1).toEqual(color2);
  1953. });
  1954. });
  1955. describe('when the `enum` key is used', () => {
  1956. beforeEach(() => {
  1957. const schema = {
  1958. type: 'object',
  1959. properties: {
  1960. str: {
  1961. type: 'string',
  1962. default: 'ok',
  1963. enum: ['ok', 'one', 'two']
  1964. },
  1965. int: {
  1966. type: 'integer',
  1967. default: 2,
  1968. enum: [2, 3, 5]
  1969. },
  1970. arr: {
  1971. type: 'array',
  1972. default: ['one', 'two'],
  1973. items: {
  1974. type: 'string',
  1975. enum: ['one', 'two', 'three']
  1976. }
  1977. },
  1978. str_options: {
  1979. type: 'string',
  1980. default: 'one',
  1981. enum: [
  1982. { value: 'one', description: 'One' },
  1983. 'two',
  1984. { value: 'three', description: 'Three' }
  1985. ]
  1986. }
  1987. }
  1988. };
  1989. atom.config.setSchema('foo.bar', schema);
  1990. });
  1991. it('will only set a string when the string is in the enum values', () => {
  1992. expect(atom.config.set('foo.bar.str', 'nope')).toBe(false);
  1993. expect(atom.config.get('foo.bar.str')).toBe('ok');
  1994. expect(atom.config.set('foo.bar.str', 'one')).toBe(true);
  1995. expect(atom.config.get('foo.bar.str')).toBe('one');
  1996. });
  1997. it('will only set an integer when the integer is in the enum values', () => {
  1998. expect(atom.config.set('foo.bar.int', '400')).toBe(false);
  1999. expect(atom.config.get('foo.bar.int')).toBe(2);
  2000. expect(atom.config.set('foo.bar.int', '3')).toBe(true);
  2001. expect(atom.config.get('foo.bar.int')).toBe(3);
  2002. });
  2003. it('will only set an array when the array values are in the enum values', () => {
  2004. expect(atom.config.set('foo.bar.arr', ['one', 'five'])).toBe(true);
  2005. expect(atom.config.get('foo.bar.arr')).toEqual(['one']);
  2006. expect(atom.config.set('foo.bar.arr', ['two', 'three'])).toBe(true);
  2007. expect(atom.config.get('foo.bar.arr')).toEqual(['two', 'three']);
  2008. });
  2009. it('will honor the enum when specified as an array', () => {
  2010. expect(atom.config.set('foo.bar.str_options', 'one')).toBe(true);
  2011. expect(atom.config.get('foo.bar.str_options')).toEqual('one');
  2012. expect(atom.config.set('foo.bar.str_options', 'two')).toBe(true);
  2013. expect(atom.config.get('foo.bar.str_options')).toEqual('two');
  2014. expect(atom.config.set('foo.bar.str_options', 'One')).toBe(false);
  2015. expect(atom.config.get('foo.bar.str_options')).toEqual('two');
  2016. });
  2017. });
  2018. });
  2019. describe('when .set/.unset is called prior to .resetUserSettings', () => {
  2020. beforeEach(() => {
  2021. atom.config.settingsLoaded = false;
  2022. });
  2023. it('ensures that early set and unset calls are replayed after the config is loaded from disk', () => {
  2024. atom.config.unset('foo.bar');
  2025. atom.config.set('foo.qux', 'boo');
  2026. expect(atom.config.get('foo.bar')).toBeUndefined();
  2027. expect(atom.config.get('foo.qux')).toBe('boo');
  2028. expect(atom.config.get('do.ray')).toBeUndefined();
  2029. advanceClock(100);
  2030. expect(savedSettings.length).toBe(0);
  2031. atom.config.resetUserSettings({
  2032. '*': {
  2033. foo: {
  2034. bar: 'baz'
  2035. },
  2036. do: {
  2037. ray: 'me'
  2038. }
  2039. }
  2040. });
  2041. advanceClock(100);
  2042. expect(savedSettings.length).toBe(1);
  2043. expect(atom.config.get('foo.bar')).toBeUndefined();
  2044. expect(atom.config.get('foo.qux')).toBe('boo');
  2045. expect(atom.config.get('do.ray')).toBe('me');
  2046. });
  2047. });
  2048. describe('project specific settings', () => {
  2049. describe('config.resetProjectSettings', () => {
  2050. it('gracefully handles invalid config objects', () => {
  2051. atom.config.resetProjectSettings({});
  2052. expect(atom.config.get('foo.bar')).toBeUndefined();
  2053. });
  2054. });
  2055. describe('config.get', () => {
  2056. const dummyPath = '/Users/dummy/path.json';
  2057. describe('project settings', () => {
  2058. it('returns a deep clone of the property value', () => {
  2059. atom.config.resetProjectSettings(
  2060. { '*': { value: { array: [1, { b: 2 }, 3] } } },
  2061. dummyPath
  2062. );
  2063. const retrievedValue = atom.config.get('value');
  2064. retrievedValue.array[0] = 4;
  2065. retrievedValue.array[1].b = 2.1;
  2066. expect(atom.config.get('value')).toEqual({ array: [1, { b: 2 }, 3] });
  2067. });
  2068. it('properly gets project settings', () => {
  2069. atom.config.resetProjectSettings({ '*': { foo: 'wei' } }, dummyPath);
  2070. expect(atom.config.get('foo')).toBe('wei');
  2071. atom.config.resetProjectSettings(
  2072. { '*': { foo: { bar: 'baz' } } },
  2073. dummyPath
  2074. );
  2075. expect(atom.config.get('foo.bar')).toBe('baz');
  2076. });
  2077. it('gets project settings with higher priority than regular settings', () => {
  2078. atom.config.set('foo', 'bar');
  2079. atom.config.resetProjectSettings({ '*': { foo: 'baz' } }, dummyPath);
  2080. expect(atom.config.get('foo')).toBe('baz');
  2081. });
  2082. it('correctly gets nested and scoped properties for project settings', () => {
  2083. expect(atom.config.set('foo.bar.str', 'global')).toBe(true);
  2084. expect(
  2085. atom.config.set('foo.bar.str', 'scoped', {
  2086. scopeSelector: '.source.js'
  2087. })
  2088. ).toBe(true);
  2089. expect(atom.config.get('foo.bar.str')).toBe('global');
  2090. expect(
  2091. atom.config.get('foo.bar.str', { scope: ['.source.js'] })
  2092. ).toBe('scoped');
  2093. });
  2094. it('returns a deep clone of the property value', () => {
  2095. atom.config.set('value', { array: [1, { b: 2 }, 3] });
  2096. const retrievedValue = atom.config.get('value');
  2097. retrievedValue.array[0] = 4;
  2098. retrievedValue.array[1].b = 2.1;
  2099. expect(atom.config.get('value')).toEqual({ array: [1, { b: 2 }, 3] });
  2100. });
  2101. it('gets scoped values correctly', () => {
  2102. atom.config.set('foo', 'bam', { scope: ['second'] });
  2103. expect(atom.config.get('foo', { scopeSelector: 'second' })).toBe(
  2104. 'bam'
  2105. );
  2106. atom.config.resetProjectSettings(
  2107. { '*': { foo: 'baz' }, second: { foo: 'bar' } },
  2108. dummyPath
  2109. );
  2110. expect(atom.config.get('foo', { scopeSelector: 'second' })).toBe(
  2111. 'baz'
  2112. );
  2113. atom.config.clearProjectSettings();
  2114. expect(atom.config.get('foo', { scopeSelector: 'second' })).toBe(
  2115. 'bam'
  2116. );
  2117. });
  2118. it('clears project settings correctly', () => {
  2119. atom.config.set('foo', 'bar');
  2120. expect(atom.config.get('foo')).toBe('bar');
  2121. atom.config.resetProjectSettings(
  2122. { '*': { foo: 'baz' }, second: { foo: 'bar' } },
  2123. dummyPath
  2124. );
  2125. expect(atom.config.get('foo')).toBe('baz');
  2126. expect(atom.config.getSources().length).toBe(1);
  2127. atom.config.clearProjectSettings();
  2128. expect(atom.config.get('foo')).toBe('bar');
  2129. expect(atom.config.getSources().length).toBe(0);
  2130. });
  2131. });
  2132. });
  2133. describe('config.getAll', () => {
  2134. const dummyPath = '/Users/dummy/path.json';
  2135. it('gets settings in the same way .get would return them', () => {
  2136. atom.config.resetProjectSettings({ '*': { a: 'b' } }, dummyPath);
  2137. atom.config.set('a', 'f');
  2138. expect(atom.config.getAll('a')).toEqual([
  2139. {
  2140. scopeSelector: '*',
  2141. value: 'b'
  2142. }
  2143. ]);
  2144. });
  2145. });
  2146. });
  2147. });