vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

471 lines
15 KiB

  1. var assert = require('assert');
  2. var util = require('../lib/util');
  3. describe('util', function () {
  4. /**
  5. * Tests for copy and extend methods.
  6. *
  7. * Goal: to cover all possible paths within the tested method(s)
  8. *
  9. *
  10. * **NOTES**
  11. *
  12. * - All these methods have the inherent flaw that it's possible to define properties
  13. * on an object with value 'undefined'. e.g. in `node`:
  14. *
  15. * > a = { b:undefined }
  16. * > a.hasOwnProperty('b')
  17. * true
  18. *
  19. * The logic for handling this in the code is minimal and accidental. For the time being,
  20. * this flaw is ignored.
  21. */
  22. describe('extend routines', function () {
  23. /**
  24. * Check if values have been copied over from b to a as intended
  25. */
  26. function checkExtended(a, b, checkCopyTarget = false) {
  27. var result = {
  28. color: 'green',
  29. sub: {
  30. enabled: false,
  31. sub2: {
  32. font: 'awesome'
  33. }
  34. }
  35. };
  36. assert(a.color !== undefined && a.color === result.color);
  37. assert(a.notInSource === true);
  38. if (checkCopyTarget) {
  39. assert(a.notInTarget === true);
  40. } else {
  41. assert(a.notInTarget === undefined);
  42. }
  43. var sub = a.sub;
  44. assert(sub !== undefined);
  45. assert(sub.enabled !== undefined && sub.enabled === result.sub.enabled);
  46. assert(sub.notInSource === true);
  47. if (checkCopyTarget) {
  48. assert(sub.notInTarget === true);
  49. } else {
  50. assert(sub.notInTarget === undefined);
  51. }
  52. sub = a.sub.sub2;
  53. assert(sub !== undefined);
  54. assert(sub !== undefined && sub.font !== undefined && sub.font === result.sub.sub2.font);
  55. assert(sub.notInSource === true);
  56. assert(a.subNotInSource !== undefined);
  57. if (checkCopyTarget) {
  58. assert(a.subNotInTarget.enabled === true);
  59. assert(sub.notInTarget === true);
  60. } else {
  61. assert(a.subNotInTarget === undefined);
  62. assert(sub.notInTarget === undefined);
  63. }
  64. }
  65. /**
  66. * Spot check on values of a unchanged as intended
  67. */
  68. function testAUnchanged(a) {
  69. var sub = a.sub;
  70. assert(sub !== undefined);
  71. assert(sub.enabled !== undefined && sub.enabled === true);
  72. assert(sub.notInSource === true);
  73. assert(sub.notInTarget === undefined);
  74. assert(sub.deleteThis === true);
  75. sub = a.sub.sub2;
  76. assert(sub !== undefined);
  77. assert(sub !== undefined && sub.font !== undefined && sub.font === 'arial');
  78. assert(sub.notInSource === true);
  79. assert(sub.notInTarget === undefined);
  80. assert(a.subNotInSource !== undefined);
  81. assert(a.subNotInTarget === undefined);
  82. }
  83. function initA() {
  84. return {
  85. color: 'red',
  86. notInSource: true,
  87. sub: {
  88. enabled: true,
  89. notInSource: true,
  90. sub2: {
  91. font: 'arial',
  92. notInSource: true,
  93. },
  94. deleteThis: true,
  95. },
  96. subNotInSource: {
  97. enabled: true,
  98. },
  99. deleteThis: true,
  100. subDeleteThis: {
  101. enabled: true,
  102. },
  103. };
  104. }
  105. beforeEach(function() {
  106. this.a = initA();
  107. this.b = {
  108. color: 'green',
  109. notInTarget: true,
  110. sub: {
  111. enabled: false,
  112. notInTarget: true,
  113. sub2: {
  114. font: 'awesome',
  115. notInTarget: true,
  116. },
  117. deleteThis: null,
  118. },
  119. subNotInTarget: {
  120. enabled: true,
  121. },
  122. deleteThis: null,
  123. subDeleteThis: null
  124. };
  125. });
  126. it('performs fillIfDefined() as advertized', function () {
  127. var a = this.a;
  128. var b = this.b;
  129. util.fillIfDefined(a, b);
  130. checkExtended(a, b);
  131. // NOTE: if allowDeletion === false, null values are copied over!
  132. // This is due to existing logic; it might not be the intention and hence a bug
  133. assert(a.sub.deleteThis === null);
  134. assert(a.deleteThis === null);
  135. assert(a.subDeleteThis === null);
  136. });
  137. it('performs fillIfDefined() as advertized with deletion', function () {
  138. var a = this.a;
  139. var b = this.b;
  140. util.fillIfDefined(a, b, true); // thrid param: allowDeletion
  141. checkExtended(a, b);
  142. // Following should be removed now
  143. assert(a.sub.deleteThis === undefined);
  144. assert(a.deleteThis === undefined);
  145. assert(a.subDeleteThis === undefined);
  146. });
  147. it('performs selectiveDeepExtend() as advertized', function () {
  148. var a = this.a;
  149. var b = this.b;
  150. // pedantic: copy nothing
  151. util.selectiveDeepExtend([], a, b);
  152. assert(a.color !== undefined && a.color === 'red');
  153. assert(a.notInSource === true);
  154. assert(a.notInTarget === undefined);
  155. // pedantic: copy nonexistent property (nothing happens)
  156. assert(b.iDontExist === undefined);
  157. util.selectiveDeepExtend(['iDontExist'], a, b, true);
  158. assert(a.iDontExist === undefined);
  159. // At this point nothing should have changed yet.
  160. testAUnchanged(a);
  161. // Copy one property
  162. util.selectiveDeepExtend(['color'], a, b);
  163. assert(a.color !== undefined && a.color === 'green');
  164. // Copy property Object
  165. var sub = a.sub;
  166. assert(sub.deleteThis === true); // pre
  167. util.selectiveDeepExtend(['sub'], a, b);
  168. assert(sub !== undefined);
  169. assert(sub.enabled !== undefined && sub.enabled === false);
  170. assert(sub.notInSource === true);
  171. assert(sub.notInTarget === true);
  172. assert(sub.deleteThis === null);
  173. // Copy new Objects
  174. assert(a.notInTarget === undefined); // pre
  175. assert(a.subNotInTarget === undefined); // pre
  176. util.selectiveDeepExtend(['notInTarget', 'subNotInTarget'], a, b);
  177. assert(a.notInTarget === true);
  178. assert(a.subNotInTarget.enabled === true);
  179. // Copy null objects
  180. assert(a.deleteThis !== null); // pre
  181. assert(a.subDeleteThis !== null); // pre
  182. util.selectiveDeepExtend(['deleteThis', 'subDeleteThis'], a, b);
  183. // NOTE: if allowDeletion === false, null values are copied over!
  184. // This is due to existing logic; it might not be the intention and hence a bug
  185. assert(a.deleteThis === null);
  186. assert(a.subDeleteThis === null);
  187. });
  188. it('performs selectiveDeepExtend() as advertized with deletion', function () {
  189. var a = this.a;
  190. var b = this.b;
  191. // Only test expected differences here with test allowDeletion === false
  192. // Copy object property with properties to be deleted
  193. var sub = a.sub;
  194. assert(sub.deleteThis === true); // pre
  195. util.selectiveDeepExtend(['sub'], a, b, true);
  196. assert(sub.deleteThis === undefined); // should be deleted
  197. // Spot check on rest of properties in `a.sub` - there should have been copied
  198. sub = a.sub;
  199. assert(sub !== undefined);
  200. assert(sub.enabled !== undefined && sub.enabled === false);
  201. assert(sub.notInSource === true);
  202. assert(sub.notInTarget === true);
  203. // Copy null objects
  204. assert(a.deleteThis === true); // pre
  205. assert(a.subDeleteThis !== undefined); // pre
  206. assert(a.subDeleteThis.enabled === true); // pre
  207. util.selectiveDeepExtend(['deleteThis', 'subDeleteThis'], a, b, true);
  208. assert(a.deleteThis === undefined); // should be deleted
  209. assert(a.subDeleteThis === undefined); // should be deleted
  210. });
  211. it('performs selectiveNotDeepExtend() as advertized', function () {
  212. var a = this.a;
  213. var b = this.b;
  214. // Exclude all properties, nothing copied
  215. util.selectiveNotDeepExtend(Object.keys(b), a, b);
  216. testAUnchanged(a);
  217. // Exclude nothing, everything copied
  218. util.selectiveNotDeepExtend([], a, b);
  219. checkExtended(a, b, true);
  220. // Exclude some
  221. a = initA();
  222. assert(a.notInTarget === undefined); // pre
  223. assert(a.subNotInTarget === undefined); // pre
  224. util.selectiveNotDeepExtend(['notInTarget', 'subNotInTarget'], a, b);
  225. assert(a.notInTarget === undefined); // not copied
  226. assert(a.subNotInTarget === undefined); // not copied
  227. assert(a.sub.notInTarget === true); // copied!
  228. });
  229. it('performs selectiveNotDeepExtend() as advertized with deletion', function () {
  230. var a = this.a;
  231. var b = this.b;
  232. // Exclude all properties, nothing copied
  233. util.selectiveNotDeepExtend(Object.keys(b), a, b, true);
  234. testAUnchanged(a);
  235. // Exclude nothing, everything copied and some deleted
  236. util.selectiveNotDeepExtend([], a, b, true);
  237. checkExtended(a, b, true);
  238. // Exclude some
  239. a = initA();
  240. assert(a.notInTarget === undefined); // pre
  241. assert(a.subNotInTarget === undefined); // pre
  242. assert(a.deleteThis === true); // pre
  243. assert(a.subDeleteThis !== undefined); // pre
  244. assert(a.sub.deleteThis === true); // pre
  245. assert(a.subDeleteThis.enabled === true); // pre
  246. util.selectiveNotDeepExtend(['notInTarget', 'subNotInTarget'], a, b, true);
  247. assert(a.deleteThis === undefined); // should be deleted
  248. assert(a.sub.deleteThis !== undefined); // not deleted! Original logic, could be a bug
  249. assert(a.subDeleteThis === undefined); // should be deleted
  250. // Spot check: following should be same as allowDeletion === false
  251. assert(a.notInTarget === undefined); // not copied
  252. assert(a.subNotInTarget === undefined); // not copied
  253. assert(a.sub.notInTarget === true); // copied!
  254. });
  255. /**
  256. * NOTE: parameter `protoExtend` not tested here!
  257. */
  258. it('performs deepExtend() as advertized', function () {
  259. var a = this.a;
  260. var b = this.b;
  261. util.deepExtend(a, b);
  262. checkExtended(a, b, true);
  263. });
  264. /**
  265. * NOTE: parameter `protoExtend` not tested here!
  266. */
  267. it('performs deepExtend() as advertized with delete', function () {
  268. var a = this.a;
  269. var b = this.b;
  270. // Copy null objects
  271. assert(a.deleteThis === true); // pre
  272. assert(a.subDeleteThis !== undefined); // pre
  273. assert(a.subDeleteThis.enabled === true); // pre
  274. util.deepExtend(a, b, false, true);
  275. checkExtended(a, b, true); // Normal copy should be good
  276. assert(a.deleteThis === undefined); // should be deleted
  277. assert(a.subDeleteThis === undefined); // should be deleted
  278. assert(a.sub.deleteThis !== undefined); // not deleted!!! Original logic, could be a bug
  279. });
  280. }); // extend routines
  281. //
  282. // The important thing with mergeOptions() is that 'enabled' is always set in target option.
  283. //
  284. describe('mergeOptions', function () {
  285. it('handles good input without global options', function () {
  286. var options = {
  287. someValue: "silly value",
  288. aBoolOption: false,
  289. anObject: {
  290. answer:42
  291. },
  292. anotherObject: {
  293. enabled: false,
  294. },
  295. merge: null
  296. };
  297. // Case with empty target
  298. var mergeTarget = {};
  299. util.mergeOptions(mergeTarget, options, 'someValue');
  300. assert(mergeTarget.someValue === undefined, 'Non-object option should not be copied');
  301. assert(mergeTarget.anObject === undefined);
  302. util.mergeOptions(mergeTarget, options, 'aBoolOption');
  303. assert(mergeTarget.aBoolOption !== undefined, 'option aBoolOption should now be an object');
  304. assert(mergeTarget.aBoolOption.enabled === false, 'enabled value option aBoolOption should have been copied into object');
  305. util.mergeOptions(mergeTarget, options, 'anObject');
  306. assert(mergeTarget.anObject !== undefined, 'Option object is not copied');
  307. assert(mergeTarget.anObject.answer === 42);
  308. assert(mergeTarget.anObject.enabled === true);
  309. util.mergeOptions(mergeTarget, options, 'anotherObject');
  310. assert(mergeTarget.anotherObject.enabled === false, 'enabled value from options must have priority');
  311. util.mergeOptions(mergeTarget, options, 'merge');
  312. assert(mergeTarget.merge === undefined, 'Explicit null option should not be copied, there is no global option for it');
  313. // Case with non-empty target
  314. mergeTarget = {
  315. someValue: false,
  316. aBoolOption: true,
  317. anObject: {
  318. answer: 49
  319. },
  320. anotherObject: {
  321. enabled: true,
  322. },
  323. merge: 'hello'
  324. };
  325. util.mergeOptions(mergeTarget, options, 'someValue');
  326. assert(mergeTarget.someValue === false, 'Non-object option should not be copied');
  327. assert(mergeTarget.anObject.answer === 49, 'Sibling option should not be changed');
  328. util.mergeOptions(mergeTarget, options, 'aBoolOption');
  329. assert(mergeTarget.aBoolOption !== true, 'option enabled should have been overwritten');
  330. assert(mergeTarget.aBoolOption.enabled === false, 'enabled value option aBoolOption should have been copied into object');
  331. util.mergeOptions(mergeTarget, options, 'anObject');
  332. assert(mergeTarget.anObject.answer === 42);
  333. assert(mergeTarget.anObject.enabled === true);
  334. util.mergeOptions(mergeTarget, options, 'anotherObject');
  335. assert(mergeTarget.anotherObject !== undefined, 'Option object is not copied');
  336. assert(mergeTarget.anotherObject.enabled === false, 'enabled value from options must have priority');
  337. util.mergeOptions(mergeTarget, options, 'merge');
  338. assert(mergeTarget.merge === 'hello', 'Explicit null-option should not be copied, already present in target');
  339. });
  340. it('gracefully handles bad input', function () {
  341. var mergeTarget = {};
  342. var options = {
  343. merge: null
  344. };
  345. var errMsg = 'Non-object parameters should not be accepted';
  346. assert.throws(() => util.mergeOptions(null, options, 'anything'), Error, errMsg);
  347. assert.throws(() => util.mergeOptions(undefined, options, 'anything'), Error, errMsg);
  348. assert.throws(() => util.mergeOptions(42, options, 'anything'), Error, errMsg);
  349. assert.throws(() => util.mergeOptions(mergeTarget, null, 'anything'), Error, errMsg);
  350. assert.throws(() => util.mergeOptions(mergeTarget, undefined, 'anything'), Error, errMsg);
  351. assert.throws(() => util.mergeOptions(mergeTarget, 42, 'anything'), Error, errMsg);
  352. assert.throws(() => util.mergeOptions(mergeTarget, options, null), Error, errMsg);
  353. assert.throws(() => util.mergeOptions(mergeTarget, options, undefined), Error, errMsg);
  354. assert.throws(() => util.mergeOptions(mergeTarget, options, 'anything', null), Error, errMsg);
  355. assert.throws(() => util.mergeOptions(mergeTarget, options, 'anything', 'not an object'), Error, errMsg);
  356. util.mergeOptions(mergeTarget, options, 'iDontExist');
  357. assert(mergeTarget.iDontExist === undefined);
  358. });
  359. it('handles good input with global options', function () {
  360. var mergeTarget = {
  361. };
  362. var options = {
  363. merge: null,
  364. missingEnabled: {
  365. answer: 42
  366. },
  367. alsoMissingEnabled: { // has no enabled in globals
  368. answer: 42
  369. }
  370. };
  371. var globalOptions = {
  372. merge: {
  373. enabled: false
  374. },
  375. missingEnabled: {
  376. enabled: false
  377. }
  378. };
  379. util.mergeOptions(mergeTarget, options, 'merge', globalOptions);
  380. assert(mergeTarget.merge.enabled === false, "null-option should create an empty target object");
  381. util.mergeOptions(mergeTarget, options, 'missingEnabled', globalOptions);
  382. assert(mergeTarget.missingEnabled.enabled === false);
  383. util.mergeOptions(mergeTarget, options, 'alsoMissingEnabled', globalOptions);
  384. assert(mergeTarget.alsoMissingEnabled.enabled === true);
  385. });
  386. }); // mergeOptions
  387. }); // util