config-spec.js 79 KB

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