cacheable-object.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import test from 'tape';
  2. import CacheableObject from '../src/data/cacheable-object.js';
  3. // Utility
  4. function newCacheableObject(PD) {
  5. return new (class extends CacheableObject {
  6. static propertyDescriptors = PD;
  7. });
  8. }
  9. // Tests
  10. test(`CacheableObject simple separate update & expose`, t => {
  11. const obj = newCacheableObject({
  12. number: {
  13. flags: {
  14. update: true
  15. }
  16. },
  17. timesTwo: {
  18. flags: {
  19. expose: true
  20. },
  21. expose: {
  22. dependencies: ['number'],
  23. compute: ({ number }) => number * 2
  24. }
  25. }
  26. });
  27. t.plan(1);
  28. obj.number = 5;
  29. t.equal(obj.timesTwo, 10);
  30. });
  31. test(`CacheableObject basic cache behavior`, t => {
  32. let computeCount = 0;
  33. const obj = newCacheableObject({
  34. string: {
  35. flags: {
  36. update: true
  37. }
  38. },
  39. karkat: {
  40. flags: {
  41. expose: true
  42. },
  43. expose: {
  44. dependencies: ['string'],
  45. compute: ({ string }) => {
  46. computeCount++;
  47. return string.toUpperCase();
  48. }
  49. }
  50. }
  51. });
  52. t.plan(8);
  53. t.is(computeCount, 0);
  54. obj.string = 'hello world';
  55. t.is(computeCount, 0);
  56. obj.karkat;
  57. t.is(computeCount, 1);
  58. obj.karkat;
  59. t.is(computeCount, 1);
  60. obj.string = 'testing once again';
  61. t.is(computeCount, 1);
  62. obj.karkat;
  63. t.is(computeCount, 2);
  64. obj.string = 'testing once again';
  65. t.is(computeCount, 2);
  66. obj.karkat;
  67. t.is(computeCount, 2);
  68. });
  69. test(`CacheableObject combined update & expose (no transform)`, t => {
  70. const obj = newCacheableObject({
  71. directory: {
  72. flags: {
  73. update: true,
  74. expose: true
  75. }
  76. }
  77. });
  78. t.plan(2);
  79. t.directory = 'the-world-revolving';
  80. t.is(t.directory, 'the-world-revolving');
  81. t.directory = 'chaos-king';
  82. t.is(t.directory, 'chaos-king');
  83. });
  84. test(`CacheableObject combined update & expose (basic transform)`, t => {
  85. const obj = newCacheableObject({
  86. getsRepeated: {
  87. flags: {
  88. update: true,
  89. expose: true
  90. },
  91. expose: {
  92. transform: value => value.repeat(2)
  93. }
  94. }
  95. });
  96. t.plan(1);
  97. obj.getsRepeated = 'dog';
  98. t.is(obj.getsRepeated, 'dogdog');
  99. });
  100. test(`CacheableObject combined update & expose (transform with dependency)`, t => {
  101. const obj = newCacheableObject({
  102. customRepeat: {
  103. flags: {
  104. update: true,
  105. expose: true
  106. },
  107. expose: {
  108. dependencies: ['times'],
  109. transform: (value, { times }) => value.repeat(times)
  110. }
  111. },
  112. times: {
  113. flags: {
  114. update: true
  115. }
  116. }
  117. });
  118. t.plan(3);
  119. obj.customRepeat = 'dog';
  120. obj.times = 1;
  121. t.is(obj.customRepeat, 'dog');
  122. obj.times = 5;
  123. t.is(obj.customRepeat, 'dogdogdogdogdog');
  124. obj.customRepeat = 'cat';
  125. t.is(obj.customRepeat, 'catcatcatcatcat');
  126. });
  127. test(`CacheableObject validate on update`, t => {
  128. const mockError = new TypeError(`Expected a string, not ${typeof value}`);
  129. const obj = newCacheableObject({
  130. directory: {
  131. flags: {
  132. update: true,
  133. expose: true
  134. },
  135. update: {
  136. validate: value => {
  137. if (typeof value !== 'string') {
  138. throw mockError;
  139. }
  140. return true;
  141. }
  142. }
  143. },
  144. date: {
  145. flags: {
  146. update: true,
  147. expose: true
  148. },
  149. update: {
  150. validate: value => (value instanceof Date)
  151. }
  152. }
  153. });
  154. let thrownError;
  155. t.plan(6);
  156. obj.directory = 'megalovania';
  157. t.is(obj.directory, 'megalovania');
  158. try {
  159. obj.directory = 25;
  160. } catch (err) {
  161. thrownError = err;
  162. }
  163. t.is(thrownError, mockError);
  164. t.is(obj.directory, 'megalovania');
  165. const date = new Date(`25 December 2009`);
  166. obj.date = date;
  167. t.is(obj.date, date);
  168. try {
  169. obj.date = `TWELFTH PERIGEE'S EVE`;
  170. } catch (err) {
  171. thrownError = err;
  172. }
  173. t.is(thrownError?.constructor, TypeError);
  174. t.is(obj.date, date);
  175. });
  176. test(`CacheableObject default update property value`, t => {
  177. const obj = newCacheableObject({
  178. fruit: {
  179. flags: {
  180. update: true,
  181. expose: true
  182. },
  183. update: {
  184. default: 'potassium'
  185. }
  186. }
  187. });
  188. t.plan(1);
  189. t.is(obj.fruit, 'potassium');
  190. });
  191. test(`CacheableObject default property throws if invalid`, t => {
  192. const mockError = new TypeError(`Expected a string, not ${typeof value}`);
  193. t.plan(1);
  194. let thrownError;
  195. try {
  196. newCacheableObject({
  197. string: {
  198. flags: {
  199. update: true
  200. },
  201. update: {
  202. default: 123,
  203. validate: value => {
  204. if (typeof value !== 'string') {
  205. throw mockError;
  206. }
  207. return true;
  208. }
  209. }
  210. }
  211. });
  212. } catch (err) {
  213. thrownError = err;
  214. }
  215. t.is(thrownError, mockError);
  216. });