S.spec.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /* global S, describe, it, expect, beforeEach, jasmine */
  2. describe("S()", function () {
  3. describe("creation", function () {
  4. it("throws if no function passed in", function () {
  5. S.root(function () {
  6. expect(function() { S(); }).toThrow();
  7. });
  8. });
  9. it("throws if arg is not a function", function () {
  10. S.root(function () {
  11. expect(function() { S(1); }).toThrow();
  12. });
  13. });
  14. it("generates a function", function () {
  15. S.root(function () {
  16. var f = S(function () { return 1; });
  17. expect(f).toEqual(jasmine.any(Function));
  18. });
  19. });
  20. it("returns initial value of wrapped function", function () {
  21. S.root(function () {
  22. var f = S(function () { return 1; });
  23. expect(f()).toBe(1);
  24. });
  25. });
  26. });
  27. describe("evaluation", function () {
  28. it("occurs once intitially", function () {
  29. S.root(function () {
  30. var spy = jasmine.createSpy(),
  31. f = S(spy);
  32. expect(spy.calls.count()).toBe(1);
  33. });
  34. });
  35. it("does not re-occur when read", function () {
  36. S.root(function () {
  37. var spy = jasmine.createSpy(),
  38. f = S(spy);
  39. f(); f(); f();
  40. expect(spy.calls.count()).toBe(1);
  41. });
  42. });
  43. });
  44. describe("with a dependency on an S.data", function () {
  45. it("updates when S.data is set", function () {
  46. S.root(function () {
  47. var d = S.data(1),
  48. fevals = 0,
  49. f = S(function () { fevals++; return d(); });
  50. fevals = 0;
  51. d(1);
  52. expect(fevals).toBe(1);
  53. });
  54. });
  55. it("does not update when S.data is read", function () {
  56. S.root(function () {
  57. var d = S.data(1),
  58. fevals = 0,
  59. f = S(function () { fevals++; return d(); });
  60. fevals = 0;
  61. d();
  62. expect(fevals).toBe(0);
  63. });
  64. });
  65. it("updates return value", function () {
  66. S.root(function () {
  67. var d = S.data(1),
  68. fevals = 0,
  69. f = S(function () { fevals++; return d(); });
  70. fevals = 0;
  71. d(2);
  72. expect(f()).toBe(2);
  73. });
  74. });
  75. });
  76. describe("with changing dependencies", function () {
  77. var i, t, e, fevals, f;
  78. function init() {
  79. i = S.data(true);
  80. t = S.data(1);
  81. e = S.data(2);
  82. fevals = 0;
  83. f = S(function () { fevals++; return i() ? t() : e(); });
  84. fevals = 0;
  85. }
  86. it("updates on active dependencies", function () {
  87. S.root(function () {
  88. init();
  89. t(5);
  90. expect(fevals).toBe(1);
  91. expect(f()).toBe(5);
  92. });
  93. });
  94. it("does not update on inactive dependencies", function () {
  95. S.root(function () {
  96. init();
  97. e(5);
  98. expect(fevals).toBe(0);
  99. expect(f()).toBe(1);
  100. });
  101. });
  102. it("deactivates obsolete dependencies", function () {
  103. S.root(function () {
  104. init();
  105. i(false);
  106. fevals = 0;
  107. t(5);
  108. expect(fevals).toBe(0);
  109. });
  110. });
  111. it("activates new dependencies", function () {
  112. S.root(function () {
  113. init();
  114. i(false);
  115. fevals = 0;
  116. e(5);
  117. expect(fevals).toBe(1);
  118. });
  119. });
  120. it("insures that new dependencies are updated before dependee", function () {
  121. S.root(function () {
  122. var order = "",
  123. a = S.data(0),
  124. b = S(function () { order += "b"; return a() + 1; }),
  125. c = S(function () { order += "c"; return b() || d(); }),
  126. d = S(function () { order += "d"; return a() + 10; });
  127. expect(order).toBe("bcd");
  128. order = "";
  129. a(-1);
  130. expect(order).toBe("bcd");
  131. expect(c()).toBe(9);
  132. order = "";
  133. a(0);
  134. expect(order).toBe("bcd");
  135. expect(c()).toBe(1);
  136. });
  137. });
  138. });
  139. describe("that creates an S.data", function () {
  140. it("does not register a dependency", function () {
  141. S.root(function () {
  142. var fevals = 0,
  143. f = S(function () { fevals++; d = S.data(1); });
  144. fevals = 0;
  145. d(2);
  146. expect(fevals).toBe(0);
  147. });
  148. });
  149. });
  150. describe("from a function with no return value", function () {
  151. it("reads as undefined", function () {
  152. S.root(function () {
  153. var f = S(function () { });
  154. expect(f()).not.toBeDefined();
  155. });
  156. });
  157. });
  158. describe("with a seed", function () {
  159. it("reduces seed value", function () {
  160. S.root(function () {
  161. var a = S.data(5),
  162. f = S(function (v) { return v + a(); }, 5);
  163. expect(f()).toBe(10);
  164. a(6);
  165. expect(f()).toBe(16);
  166. });
  167. });
  168. });
  169. describe("with a dependency on a computation", function () {
  170. var d, fcount, f, gcount, g;
  171. function init() {
  172. d = S.data(1),
  173. fcount = 0,
  174. f = S(function () { fcount++; return d(); }),
  175. gcount = 0,
  176. g = S(function () { gcount++; return f(); });
  177. }
  178. it("does not cause re-evaluation", function () {
  179. S.root(function () {
  180. init();
  181. expect(fcount).toBe(1);
  182. });
  183. });
  184. it("does not occur from a read", function () {
  185. S.root(function () {
  186. init();
  187. f();
  188. expect(gcount).toBe(1);
  189. });
  190. });
  191. it("does not occur from a read of the watcher", function () {
  192. S.root(function () {
  193. init();
  194. g();
  195. expect(gcount).toBe(1);
  196. });
  197. });
  198. it("occurs when computation updates", function () {
  199. S.root(function () {
  200. init();
  201. d(2);
  202. expect(fcount).toBe(2);
  203. expect(gcount).toBe(2);
  204. expect(g()).toBe(2);
  205. });
  206. });
  207. });
  208. describe("with unending changes", function () {
  209. it("throws when continually setting a direct dependency", function () {
  210. S.root(function () {
  211. var d = S.data(1);
  212. expect(function () {
  213. S(function () { d(); d(2); });
  214. }).toThrow();
  215. });
  216. });
  217. it("throws when continually setting an indirect dependency", function () {
  218. S.root(function () {
  219. var d = S.data(1),
  220. f1 = S(function () { return d(); }),
  221. f2 = S(function () { return f1(); }),
  222. f3 = S(function () { return f2(); });
  223. expect(function () {
  224. S(function () { f3(); d(2); });
  225. }).toThrow();
  226. });
  227. });
  228. });
  229. describe("with circular dependencies", function () {
  230. it("throws when cycle created by modifying a branch", function () {
  231. S.root(function () {
  232. var d = S.data(1),
  233. f = S(function () { return f ? f() : d(); });
  234. expect(function () { d(0); }).toThrow();
  235. });
  236. });
  237. });
  238. describe("with converging dependencies", function () {
  239. it("propagates in topological order", function () {
  240. S.root(function () {
  241. //
  242. // c1
  243. // / \
  244. // / \
  245. // b1 b2
  246. // \ /
  247. // \ /
  248. // a1
  249. //
  250. var seq = "",
  251. a1 = S.data(true),
  252. b1 = S(function () { a1(); seq += "b1"; }),
  253. b2 = S(function () { a1(); seq += "b2"; }),
  254. c1 = S(function () { b1(), b2(); seq += "c1"; });
  255. seq = "";
  256. a1(true);
  257. expect(seq).toBe("b1b2c1");
  258. });
  259. });
  260. it("only propagates once with linear convergences", function () {
  261. S.root(function () {
  262. // d
  263. // |
  264. // +---+---+---+---+
  265. // v v v v v
  266. // f1 f2 f3 f4 f5
  267. // | | | | |
  268. // +---+---+---+---+
  269. // v
  270. // g
  271. var d = S.data(0),
  272. f1 = S(function () { return d(); }),
  273. f2 = S(function () { return d(); }),
  274. f3 = S(function () { return d(); }),
  275. f4 = S(function () { return d(); }),
  276. f5 = S(function () { return d(); }),
  277. gcount = 0,
  278. g = S(function () { gcount++; return f1() + f2() + f3() + f4() + f5(); });
  279. gcount = 0;
  280. d(0);
  281. expect(gcount).toBe(1);
  282. });
  283. });
  284. it("only propagates once with exponential convergence", function () {
  285. S.root(function () {
  286. // d
  287. // |
  288. // +---+---+
  289. // v v v
  290. // f1 f2 f3
  291. // \ | /
  292. // O
  293. // / | \
  294. // v v v
  295. // g1 g2 g3
  296. // +---+---+
  297. // v
  298. // h
  299. var d = S.data(0),
  300. f1 = S(function () { return d(); }),
  301. f2 = S(function () { return d(); }),
  302. f3 = S(function () { return d(); }),
  303. g1 = S(function () { return f1() + f2() + f3(); }),
  304. g2 = S(function () { return f1() + f2() + f3(); }),
  305. g3 = S(function () { return f1() + f2() + f3(); }),
  306. hcount = 0,
  307. h = S(function () { hcount++; return g1() + g2() + g3(); });
  308. hcount = 0;
  309. d(0);
  310. expect(hcount).toBe(1);
  311. });
  312. });
  313. });
  314. });