propertyreplacer_test.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.testing.PropertyReplacerTest');
  15. goog.setTestOnly('goog.testing.PropertyReplacerTest');
  16. goog.require('goog.dom');
  17. goog.require('goog.dom.TagName');
  18. goog.require('goog.testing.PropertyReplacer');
  19. goog.require('goog.testing.asserts');
  20. goog.require('goog.testing.jsunit');
  21. goog.require('goog.userAgent.product');
  22. goog.require('goog.userAgent.product.isVersion');
  23. function isSafari8() {
  24. return goog.userAgent.product.SAFARI &&
  25. goog.userAgent.product.isVersion('8.0');
  26. }
  27. // Test PropertyReplacer with JavaScript objects.
  28. function testSetJsProperties() {
  29. var stubs = new goog.testing.PropertyReplacer();
  30. var x = {a: 1, b: undefined};
  31. // Setting simple values.
  32. stubs.set(x, 'num', 1);
  33. assertEquals('x.num = 1', 1, x.num);
  34. stubs.set(x, 'undef', undefined);
  35. assertTrue('x.undef = undefined', 'undef' in x && x.undef === undefined);
  36. stubs.set(x, 'null', null);
  37. assertTrue('x["null"] = null', x['null'] === null);
  38. // Setting a simple value that existed originally.
  39. stubs.set(x, 'b', null);
  40. assertTrue('x.b = null', x.b === null);
  41. // Setting a complex value.
  42. stubs.set(x, 'obj', {});
  43. assertEquals('x.obj = {}', 'object', typeof x.obj);
  44. stubs.set(x.obj, 'num', 2);
  45. assertEquals('x.obj.num = 2', 2, x.obj.num);
  46. // Overwriting a leaf.
  47. stubs.set(x.obj, 'num', 3);
  48. assertEquals('x.obj.num = 3', 3, x.obj.num);
  49. // Overwriting a non-leaf.
  50. stubs.set(x, 'obj', {});
  51. assertFalse('x.obj = {} again', 'num' in x.obj);
  52. // Setting a function.
  53. stubs.set(x, 'func', function(n) { return n + 1; });
  54. assertEquals('x.func = lambda n: n+1', 11, x.func(10));
  55. // Setting a constructor and a prototype method.
  56. stubs.set(x, 'Class', function(num) { this.num = num; });
  57. stubs.set(x.Class.prototype, 'triple', function() { return this.num * 3; });
  58. assertEquals('prototype method', 12, (new x.Class(4)).triple());
  59. // Cleaning up with UnsetAll() twice. The second run should have no effect.
  60. for (var i = 0; i < 2; i++) {
  61. stubs.reset();
  62. assertEquals('x.a preserved', 1, x.a);
  63. assertTrue('x.b reset', 'b' in x && x.b === undefined);
  64. assertFalse('x.num removed', 'num' in x);
  65. assertFalse('x.undef removed', 'undef' in x);
  66. assertFalse('x["null"] removed', 'null' in x);
  67. assertFalse('x.obj removed', 'obj' in x);
  68. assertFalse('x.func removed', 'func' in x);
  69. assertFalse('x.Class removed', 'Class' in x);
  70. }
  71. }
  72. // Test removing JavaScript object properties.
  73. function testRemoveJsProperties() {
  74. var stubs = new goog.testing.PropertyReplacer();
  75. var orig = {'a': 1, 'b': undefined};
  76. var x = {'a': 1, 'b': undefined};
  77. stubs.remove(x, 'a');
  78. assertFalse('x.a removed', 'a' in x);
  79. assertTrue('x.b not removed', 'b' in x);
  80. stubs.reset();
  81. assertObjectEquals('x.a reset', x, orig);
  82. stubs.remove(x, 'b');
  83. assertFalse('x.b removed', 'b' in x);
  84. stubs.reset();
  85. assertObjectEquals('x.b reset', x, orig);
  86. stubs.set(x, 'c', 2);
  87. stubs.remove(x, 'c');
  88. assertObjectEquals('x.c added then removed', x, orig);
  89. stubs.reset();
  90. assertObjectEquals('x.c reset', x, orig);
  91. stubs.remove(x, 'b');
  92. stubs.set(x, 'b', undefined);
  93. assertObjectEquals('x.b removed then added', x, orig);
  94. stubs.reset();
  95. assertObjectEquals('x.b reset', x, orig);
  96. stubs.remove(x, 'd');
  97. assertObjectEquals('removing non-existing key', x, orig);
  98. stubs.reset();
  99. assertObjectEquals('reset removing non-existing key', x, orig);
  100. }
  101. // Test PropertyReplacer with prototype chain.
  102. function testPrototype() {
  103. var stubs = new goog.testing.PropertyReplacer();
  104. // Simple inheritance.
  105. var a = {a: 0};
  106. function B() {}
  107. B.prototype = a;
  108. var b = new B();
  109. stubs.set(a, 0, 1);
  110. stubs.set(b, 0, 2);
  111. stubs.reset();
  112. assertEquals('a.a == 0', 0, a['a']);
  113. assertEquals('b.a == 0', 0, b['a']);
  114. // Inheritance with goog.inherits.
  115. var c = {a: 0};
  116. function C() {}
  117. C.prototype = c;
  118. function D() {}
  119. goog.inherits(D, C);
  120. var d = new D();
  121. stubs = new goog.testing.PropertyReplacer();
  122. stubs.set(c, 'a', 1);
  123. stubs.set(d, 'a', 2);
  124. stubs.reset();
  125. assertEquals('c.a == 0', 0, c['a']);
  126. assertEquals('d.a == 0', 0, d['a']);
  127. // Setting prototype fields.
  128. stubs.set(B.prototype, 'c', 'z');
  129. assertEquals('b.c=="z"', 'z', b.c);
  130. stubs.reset();
  131. assertFalse('b.c deleted', 'c' in b);
  132. // Setting Object.prototype's fields.
  133. stubs.set(Object.prototype, 'one', 1);
  134. assertEquals('b.one==1', 1, b.one);
  135. stubs.reset();
  136. assertFalse('b.one deleted', 'one' in b);
  137. stubs.set(Object.prototype, 'two', 2);
  138. stubs.remove(b, 'two');
  139. assertEquals('b.two==2', 2, b.two);
  140. stubs.remove(Object.prototype, 'two');
  141. assertFalse('b.two deleted', 'two' in b);
  142. stubs.reset();
  143. assertFalse('Object prototype reset', 'two' in b);
  144. }
  145. // Test replacing function properties.
  146. function testFunctionProperties() {
  147. var stubs = new goog.testing.PropertyReplacer();
  148. stubs.set(Array, 'x', 1);
  149. assertEquals('Array.x==1', 1, Array.x);
  150. stubs.reset();
  151. assertFalse('Array.x deleted', 'x' in Array);
  152. stubs.set(Math.random, 'x', 1);
  153. assertEquals('Math.random.x==1', 1, Math.random.x);
  154. stubs.reset();
  155. assertFalse('Math.random.x deleted', 'x' in Math.random);
  156. }
  157. // Test the hasKey_ private method.
  158. function testHasKey() {
  159. f = goog.testing.PropertyReplacer.hasKey_;
  160. assertFalse('{}.a', f({}, 'a'));
  161. assertTrue('{a:0}.a', f({a: 0}, 'a'));
  162. function C() {}
  163. C.prototype.a = 0;
  164. assertFalse('C.prototype.a set, is C.a own?', f(C, 'a'));
  165. assertTrue('C.prototype.a', f(C.prototype, 'a'));
  166. assertFalse('C.a not set', f(C, 'a'));
  167. C.a = 0;
  168. assertTrue('C.a set', f(C, 'a'));
  169. var c = new C();
  170. assertFalse('C().a, inherited', f(c, 'a'));
  171. c.a = 0;
  172. assertTrue('C().a, own', f(c, 'a'));
  173. assertFalse('window, invalid key', f(window, 'no such key'));
  174. assertTrue('window, global variable', f(window, 'goog'));
  175. assertTrue('window, build-in key', f(window, 'location'));
  176. assertFalse('document, invalid key', f(document, 'no such key'));
  177. var div = goog.dom.createElement(goog.dom.TagName.DIV);
  178. assertFalse('div, invalid key', f(div, 'no such key'));
  179. div['a'] = 0;
  180. assertTrue('div, key added by JS', f(div, 'a'));
  181. // hasKey_ returns false for these DOM properties on Safari 8. See b/22044928.
  182. if (!isSafari8()) {
  183. assertTrue('div.className', f(div, 'className'));
  184. assertTrue('document.body', f(document, 'body'));
  185. }
  186. assertFalse('Date().getTime', f(new Date(), 'getTime'));
  187. assertTrue('Date.prototype.getTime', f(Date.prototype, 'getTime'));
  188. assertFalse('Math, invalid key', f(Math, 'no such key'));
  189. assertTrue('Math.random', f(Math, 'random'));
  190. function Parent() {}
  191. Parent.prototype.a = 0;
  192. function Child() {}
  193. goog.inherits(Child, Parent);
  194. assertFalse('goog.inherits, parent prototype', f(new Child, 'a'));
  195. Child.prototype.a = 1;
  196. assertFalse('goog.inherits, child prototype', f(new Child, 'a'));
  197. function OverwrittenProto() {}
  198. OverwrittenProto.prototype = {a: 0};
  199. assertFalse(f(new OverwrittenProto, 'a'));
  200. }
  201. // Test PropertyReplacer with DOM objects' built in attributes.
  202. function testDomBuiltInAttributes() {
  203. var div = goog.dom.createElement(goog.dom.TagName.DIV);
  204. div.id = 'old-id';
  205. var stubs = new goog.testing.PropertyReplacer();
  206. stubs.set(div, 'id', 'new-id');
  207. stubs.set(div, 'className', 'test-class');
  208. assertEquals('div.id == "new-id"', 'new-id', div.id);
  209. assertEquals('div.className == "test-class"', 'test-class', div.className);
  210. stubs.remove(div, 'className');
  211. // Removal of these DOM properties is not supported in Safari 8. See
  212. // b/22044928.
  213. if (!isSafari8()) {
  214. // '' in Firefox, undefined in Chrome.
  215. assertEvaluatesToFalse('div.className is empty', div.className);
  216. stubs.reset();
  217. assertEquals('div.id == "old-id"', 'old-id', div.id);
  218. assertEquals('div.name == ""', '', div.className);
  219. }
  220. }
  221. // Test PropertyReplacer with DOM objects' custom attributes.
  222. function testDomCustomAttributes() {
  223. var div = goog.dom.createElement(goog.dom.TagName.DIV);
  224. div.attr1 = 'old';
  225. var stubs = new goog.testing.PropertyReplacer();
  226. stubs.set(div, 'attr1', 'new');
  227. stubs.set(div, 'attr2', 'new');
  228. assertEquals('div.attr1 == "new"', 'new', div.attr1);
  229. assertEquals('div.attr2 == "new"', 'new', div.attr2);
  230. stubs.set(div, 'attr3', 'new');
  231. stubs.remove(div, 'attr3');
  232. assertEquals('div.attr3 == undefined', undefined, div.attr3);
  233. stubs.reset();
  234. assertEquals('div.attr1 == "old"', 'old', div.attr1);
  235. assertEquals('div.attr2 == undefined', undefined, div.attr2);
  236. }
  237. // Test PropertyReplacer trying to override a read-only property.
  238. function testReadOnlyProperties() {
  239. var stubs = new goog.testing.PropertyReplacer();
  240. // Function.prototype.length should be read-only.
  241. var foo = function(_) {};
  242. assertThrows(
  243. 'Trying to set a read-only property fails silently.',
  244. goog.bind(stubs.set, stubs, foo, 'length', 10));
  245. assertThrows(
  246. 'Trying to replace a read-only property fails silently.',
  247. goog.bind(stubs.replace, stubs, foo, 'length', 10));
  248. // Array length should be undeletable.
  249. var a = [1, 2, 3];
  250. assertThrows(
  251. 'Trying to remove a read-only property fails silently.',
  252. goog.bind(stubs.remove, stubs, a, 'length'));
  253. window.foo = foo;
  254. assertThrows(
  255. 'Trying to set a read-only property by path fails silently.',
  256. goog.bind(stubs.setPath, stubs, 'window.foo.length', 10));
  257. window.foo = undefined;
  258. }
  259. // Test PropertyReplacer trying to override a style property doesn't trigger
  260. // read-only exception.
  261. function testSettingStyleProperties() {
  262. var stubs = new goog.testing.PropertyReplacer();
  263. var div = document.createElement('div');
  264. // Ensures setting a pixel style value doesn't trigger the read-only property
  265. // exception, considering div.style.margin will return "0px" instead of just
  266. // "0".
  267. assertNotThrows(
  268. 'Trying to set a read-only property fails silently.',
  269. goog.bind(stubs.set, stubs, div.style, 'margin', '0'));
  270. }
  271. // Test PropertyReplacer trying to override a sealed property.
  272. function testSealedProperties() {
  273. if (!goog.isFunction(Object.seal)) {
  274. return;
  275. }
  276. var stubs = new goog.testing.PropertyReplacer();
  277. var sealed = Object.seal({a: 1});
  278. assertThrows(
  279. 'Trying to set a new sealed property fails silently.',
  280. goog.bind(stubs.set, stubs, sealed, 'b', 2));
  281. assertNotThrows(
  282. 'Trying to remove a new sealed property fails.',
  283. goog.bind(stubs.remove, stubs, sealed, 'b'));
  284. assertNotThrows(
  285. 'Trying to remove a sealed property fails.',
  286. goog.bind(stubs.remove, stubs, sealed, 'a'));
  287. window.sealed = sealed;
  288. assertThrows(
  289. 'Trying to set a new sealed property by path fails silently in strict ' +
  290. 'mode.',
  291. goog.bind(stubs.setPath, stubs, 'window.sealed.b', 2));
  292. (function() {
  293. // Test Object.seal() in strict mode, where the assignment itself throws the
  294. // error rather than our explicit consistency check.
  295. 'use strict';
  296. var sealed = Object.seal({a: 1});
  297. assertThrows(
  298. 'Trying to set a new sealed property fails silently in strict mode.',
  299. goog.bind(stubs.set, stubs, sealed, 'b', 2));
  300. assertNotThrows(
  301. 'Trying to remove a new sealed property fails in strict mode.',
  302. goog.bind(stubs.remove, stubs, sealed, 'b'));
  303. assertNotThrows(
  304. 'Trying to remove a sealed property fails in strict mode.',
  305. goog.bind(stubs.remove, stubs, sealed, 'a'));
  306. window.sealed = sealed;
  307. assertThrows(
  308. 'Trying to set a new sealed property by path fails silently in ' +
  309. 'strict mode.',
  310. goog.bind(stubs.setPath, stubs, 'window.sealed.b', 2));
  311. })();
  312. delete window.sealed;
  313. }
  314. // Test PropertyReplacer trying to override a frozen property.
  315. function testFrozenProperty() {
  316. if (!goog.isFunction(Object.freeze)) {
  317. return;
  318. }
  319. var stubs = new goog.testing.PropertyReplacer();
  320. var frozen = Object.freeze({a: 1});
  321. assertThrows(
  322. 'Trying to set a new frozen property fails silently.',
  323. goog.bind(stubs.set, stubs, frozen, 'b', 2));
  324. assertThrows(
  325. 'Trying to set a frozen property fails silently.',
  326. goog.bind(stubs.set, stubs, frozen, 'a', 2));
  327. assertThrows(
  328. 'Trying to replace a frozen property fails silently.',
  329. goog.bind(stubs.replace, stubs, frozen, 'a', 2));
  330. assertNotThrows(
  331. 'Trying to remove a new frozen property fails.',
  332. goog.bind(stubs.remove, stubs, frozen, 'b'));
  333. assertThrows(
  334. 'Trying to remove a frozen property fails silently.',
  335. goog.bind(stubs.remove, stubs, frozen, 'a'));
  336. window.frozen = frozen;
  337. assertThrows(
  338. 'Trying to set a frozen property by path fails silently.',
  339. goog.bind(stubs.setPath, stubs, 'window.frozen.a', 2));
  340. (function() {
  341. // Test Object.freeze() in strict mode, where the assignment itself throws
  342. // the error rather than our explicit consistency check.
  343. 'use strict';
  344. var frozen = Object.freeze({a: 1});
  345. assertThrows(
  346. 'Trying to set a new frozen property fails silently in strict mode.',
  347. goog.bind(stubs.set, stubs, frozen, 'b', 2));
  348. assertThrows(
  349. 'Trying to ovewrite a frozen property fails silently in strict mode.',
  350. goog.bind(stubs.set, stubs, frozen, 'a', 2));
  351. assertThrows(
  352. 'Trying to replace a frozen property fails silently in strict mode.',
  353. goog.bind(stubs.replace, stubs, frozen, 'a', 2));
  354. assertNotThrows(
  355. 'Trying to remove a new frozen property fails in strict mode.',
  356. goog.bind(stubs.remove, stubs, frozen, 'b'));
  357. assertThrows(
  358. 'Trying to remove a frozen property fails silently in strict mode.',
  359. goog.bind(stubs.remove, stubs, frozen, 'a'));
  360. window.frozen = frozen;
  361. assertThrows(
  362. 'Trying to set a new frozen property by path fails silently in ' +
  363. 'strict mode.',
  364. goog.bind(stubs.setPath, stubs, 'window.frozen.b', 2));
  365. assertThrows(
  366. 'Trying to set a frozen property by path fails silently in strict ' +
  367. 'mode.',
  368. goog.bind(stubs.setPath, stubs, 'window.frozen.a', 2));
  369. })();
  370. delete window.frozen;
  371. }
  372. // Test PropertyReplacer overriding Math.random.
  373. function testMathRandom() {
  374. var stubs = new goog.testing.PropertyReplacer();
  375. stubs.set(Math, 'random', function() { return -1; });
  376. assertEquals('mocked Math.random', -1, Math.random());
  377. stubs.reset();
  378. assertNotEquals('original Math.random', -1, Math.random());
  379. }
  380. // Tests the replace method of PropertyReplacer.
  381. function testReplace() {
  382. var stubs = new goog.testing.PropertyReplacer();
  383. function C() {
  384. this.a = 1;
  385. }
  386. C.prototype.b = 1;
  387. C.prototype.toString = function() { return 'obj'; };
  388. var obj = new C();
  389. stubs.replace(obj, 'a', 2);
  390. assertEquals('successfully replaced the own property of an object', 2, obj.a);
  391. stubs.replace(obj, 'b', 2);
  392. assertEquals('successfully replaced the property in the prototype', 2, obj.b);
  393. var error = assertThrows(
  394. 'cannot replace missing key',
  395. goog.bind(stubs.replace, stubs, obj, 'unknown', 1));
  396. // Using assertContains instead of assertEquals because Opera 10.0 adds
  397. // the stack trace to the error message.
  398. assertEquals(
  399. 'error message for missing key',
  400. 'Cannot replace missing property "unknown" in obj', error.message);
  401. assertFalse('object not touched', 'unknown' in obj);
  402. error = assertThrows(
  403. 'cannot change value type',
  404. goog.bind(stubs.replace, stubs, obj, 'a', '3'));
  405. assertContains(
  406. 'error message for type mismatch',
  407. 'Cannot replace property "a" in obj with a value of different type',
  408. error.message);
  409. assertThrows(
  410. 'cannot change value type to null',
  411. goog.bind(stubs.replace, stubs, obj, 'a', null));
  412. assertThrows(
  413. 'cannot change value type to undefined',
  414. goog.bind(stubs.replace, stubs, obj, 'a', undefined));
  415. }
  416. function testReplaceAllowNullOrUndefined() {
  417. var stubs = new goog.testing.PropertyReplacer();
  418. var obj = {value: 1, zero: 0, emptyString: ''};
  419. stubs.replace(obj, 'value', undefined, true);
  420. assertUndefined(
  421. 'Expected int value to be replaced with undefined', obj.value);
  422. stubs.replace(obj, 'value', 'b', true);
  423. assertEquals(
  424. 'Expected undefined value to be replace with string', 'b', obj.value);
  425. stubs.replace(obj, 'value', null, true);
  426. assertNull('Expected string value to be replaced with null', obj.value);
  427. stubs.replace(obj, 'value', 1, true);
  428. assertEquals(
  429. 'Expected null value to be replaced with non-null', 1, obj.value);
  430. // Replacing 0 with a string or empty string with a number is not allowed.
  431. assertThrows(
  432. 'Cannot change value type',
  433. goog.bind(stubs.replace, stubs, obj, 'zero', 'a', true));
  434. assertThrows(
  435. 'Cannot change value type',
  436. goog.bind(stubs.replace, stubs, obj, 'emptyString', 1, true));
  437. }
  438. // Tests altering complete namespace paths.
  439. function testSetPath() {
  440. goog.global.a = {b: {}};
  441. var stubs = new goog.testing.PropertyReplacer();
  442. stubs.setPath('a.b.c.d', 1);
  443. assertObjectEquals('a.b.c.d=1', {b: {c: {d: 1}}}, goog.global.a);
  444. stubs.setPath('a.b.e', 2);
  445. assertObjectEquals('a.b.e=2', {b: {c: {d: 1}, e: 2}}, goog.global.a);
  446. stubs.setPath('a.f', 3);
  447. assertObjectEquals('a.f=3', {b: {c: {d: 1}, e: 2}, f: 3}, goog.global.a);
  448. stubs.setPath('a.f.g', 4);
  449. assertObjectEquals(
  450. 'a.f.g=4', {b: {c: {d: 1}, e: 2}, f: {g: 4}}, goog.global.a);
  451. stubs.setPath('a', 5);
  452. assertEquals('a=5', 5, goog.global.a);
  453. stubs.setPath('x.y.z', 5);
  454. assertObjectEquals('x.y.z=5', {y: {z: 5}}, goog.global.x);
  455. stubs.reset();
  456. assertObjectEquals('a.* reset', {b: {}}, goog.global.a);
  457. // NOTE: it's impossible to delete global variables in Internet Explorer,
  458. // so ('x' in goog.global) would be true.
  459. assertUndefined('x.* reset', goog.global.x);
  460. }
  461. // Tests altering paths with functions in them.
  462. function testSetPathWithFunction() {
  463. var f = function() {};
  464. goog.global.a = {b: f};
  465. var stubs = new goog.testing.PropertyReplacer();
  466. stubs.setPath('a.b.c', 1);
  467. assertEquals('a.b.c=1, f kept', f, goog.global.a.b);
  468. assertEquals('a.b.c=1, c set', 1, goog.global.a.b.c);
  469. stubs.setPath('a.b.prototype.d', 2);
  470. assertEquals('a.b.prototype.d=2, b kept', f, goog.global.a.b);
  471. assertEquals('a.b.prototype.d=2, c kept', 1, goog.global.a.b.c);
  472. assertFalse('a.b.prototype.d=2, a.b.d not set', 'd' in goog.global.a.b);
  473. assertTrue('a.b.prototype.d=2, proto set', 'd' in goog.global.a.b.prototype);
  474. assertEquals('a.b.prototype.d=2, d set', 2, new goog.global.a.b().d);
  475. var invalidSetPath = function() { stubs.setPath('a.prototype.e', 3); };
  476. assertThrows('setting the prototype of a non-function', invalidSetPath);
  477. stubs.reset();
  478. assertObjectEquals('a.b.c reset', {b: f}, goog.global.a);
  479. assertObjectEquals('a.b.prototype reset', {}, goog.global.a.b.prototype);
  480. }
  481. // Tests restoring original attribute values with restore() rather than reset().
  482. function testRestore() {
  483. var stubs = new goog.testing.PropertyReplacer();
  484. var x = {a: 1, b: undefined};
  485. // Setting simple value.
  486. stubs.set(x, 'num', 1);
  487. assertEquals('x.num = 1', 1, x.num);
  488. stubs.restore(x, 'num');
  489. assertFalse('x.num removed', 'num' in x);
  490. // Setting undefined value.
  491. stubs.set(x, 'undef', undefined);
  492. assertTrue('x.undef = undefined', 'undef' in x && x.undef === undefined);
  493. stubs.restore(x, 'undef');
  494. assertFalse('x.undef removed', 'undef' in x);
  495. // Setting null value.
  496. stubs.set(x, 'null', null);
  497. assertTrue('x["null"] = null', x['null'] === null);
  498. stubs.restore(x, 'null');
  499. assertFalse('x["null"] removed', 'null' in x);
  500. // Setting a simple value that existed originally.
  501. stubs.set(x, 'b', null);
  502. assertTrue('x.b = null', x.b === null);
  503. // Setting a complex value.
  504. stubs.set(x, 'obj', {});
  505. assertEquals('x.obj = {}', 'object', typeof x.obj);
  506. stubs.set(x.obj, 'num', 2);
  507. assertEquals('x.obj.num = 2', 2, x.obj.num);
  508. stubs.restore(x.obj, 'num');
  509. assertFalse('x.obj.num removed', 'num' in x.obj);
  510. stubs.restore(x, 'obj');
  511. assertFalse('x.obj removed', 'obj' in x);
  512. // Setting a function.
  513. stubs.set(x, 'func', function(n) { return n + 1; });
  514. assertEquals('x.func = lambda n: n+1', 11, x.func(10));
  515. stubs.restore(x, 'func');
  516. assertFalse('x.func removed', 'func' in x);
  517. // Setting a constructor and a prototype method.
  518. stubs.set(x, 'Class', function(num) { this.num = num; });
  519. stubs.set(x.Class.prototype, 'triple', function() { return this.num * 3; });
  520. assertEquals('prototype method', 12, (new x.Class(4)).triple());
  521. stubs.restore(x, 'Class');
  522. assertFalse('x.Class removed', 'Class' in x);
  523. // Final cleanup with reset(). This should have no effect:
  524. // all assertions about the original state shall still hold.
  525. stubs.reset();
  526. assertEquals('x.a preserved', 1, x.a);
  527. assertTrue('x.b reset', 'b' in x && x.b === undefined);
  528. assertFalse('x.num removed', 'num' in x);
  529. assertFalse('x.undef removed', 'undef' in x);
  530. assertFalse('x["null"] removed', 'null' in x);
  531. assertFalse('x.obj removed', 'obj' in x);
  532. assertFalse('x.func removed', 'func' in x);
  533. assertFalse('x.Class removed', 'Class' in x);
  534. }
  535. // Tests restore() with invalid arguments.
  536. function testRestoreWithInvalidArguments() {
  537. var stubs = new goog.testing.PropertyReplacer();
  538. var x = {a: 1, b: undefined};
  539. var y = {a: 1};
  540. stubs.set(x, 'a', 42);
  541. assertThrows(
  542. 'Trying to restore state of an unmodified property',
  543. goog.bind(stubs.restore, stubs, x, 'b'));
  544. assertThrows(
  545. 'Trying to restore state of a non-existing property',
  546. goog.bind(stubs.restore, stubs, x, 'not_here'));
  547. assertThrows(
  548. 'Trying to restore state of an unmodified object',
  549. goog.bind(stubs.restore, stubs, y, 'a'));
  550. }