compositeFrom.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. import t from 'tap';
  2. import {compositeFrom, continuationSymbol, input} from '#composite';
  3. import {isString} from '#validators';
  4. t.test(`compositeFrom: basic behavior`, t => {
  5. t.plan(2);
  6. const composite = compositeFrom({
  7. annotation: `myComposite`,
  8. compose: false,
  9. steps: [
  10. {
  11. dependencies: ['foo'],
  12. compute: (continuation, {foo}) =>
  13. continuation({'#bar': foo * 2}),
  14. },
  15. {
  16. dependencies: ['#bar', 'baz', 'suffix'],
  17. compute: ({'#bar': bar, baz, suffix}) =>
  18. baz.repeat(bar) + suffix,
  19. },
  20. ],
  21. });
  22. t.match(composite, {
  23. annotation: `myComposite`,
  24. flags: {expose: true, compose: false, update: false},
  25. expose: {
  26. dependencies: ['foo', 'baz', 'suffix'],
  27. compute: Function,
  28. transform: null,
  29. },
  30. update: null,
  31. });
  32. t.equal(
  33. composite.expose.compute({
  34. foo: 3,
  35. baz: 'ba',
  36. suffix: 'BOOM',
  37. }),
  38. 'babababababaBOOM');
  39. });
  40. t.test(`compositeFrom: input-shaped step dependencies`, t => {
  41. t.plan(2);
  42. const composite = compositeFrom({
  43. compose: false,
  44. steps: [
  45. {
  46. dependencies: [
  47. input.myself(),
  48. input.updateValue(),
  49. ],
  50. transform: (updateValue1, {
  51. [input.myself()]: me,
  52. [input.updateValue()]: updateValue2,
  53. }) => ({me, updateValue1, updateValue2}),
  54. },
  55. ],
  56. });
  57. t.match(composite, {
  58. expose: {
  59. dependencies: ['this'],
  60. transform: Function,
  61. compute: null,
  62. },
  63. });
  64. const myself = {foo: 'bar'};
  65. t.same(
  66. composite.expose.transform('banana', {
  67. this: myself,
  68. pomelo: 'delicious',
  69. }),
  70. {
  71. me: myself,
  72. updateValue1: 'banana',
  73. updateValue2: 'banana',
  74. });
  75. });
  76. t.test(`compositeFrom: dependencies from inputs`, t => {
  77. t.plan(3);
  78. const composite = compositeFrom({
  79. annotation: `myComposite`,
  80. compose: true,
  81. inputMapping: {
  82. foo: input('bar'),
  83. pomelo: input.value('delicious'),
  84. humorous: input.dependency('#mammal'),
  85. data: input.dependency('albumData'),
  86. ref: input.updateValue(),
  87. },
  88. inputDescriptions: {
  89. foo: input(),
  90. pomelo: input(),
  91. humorous: input(),
  92. data: input(),
  93. ref: input(),
  94. },
  95. steps: [
  96. {
  97. dependencies: [
  98. input('foo'),
  99. input('pomelo'),
  100. input('humorous'),
  101. input('data'),
  102. input('ref'),
  103. ],
  104. compute: (continuation, {
  105. [input('foo')]: foo,
  106. [input('pomelo')]: pomelo,
  107. [input('humorous')]: humorous,
  108. [input('data')]: data,
  109. [input('ref')]: ref,
  110. }) => continuation.exit({foo, pomelo, humorous, data, ref}),
  111. },
  112. ],
  113. });
  114. t.match(composite, {
  115. expose: {
  116. dependencies: [
  117. input('bar'),
  118. '#mammal',
  119. 'albumData',
  120. ],
  121. transform: Function,
  122. compute: null,
  123. },
  124. });
  125. const exitData = {};
  126. const continuation = {
  127. exit(value) {
  128. Object.assign(exitData, value);
  129. return continuationSymbol;
  130. },
  131. };
  132. t.equal(
  133. composite.expose.transform('album:bepis', continuation, {
  134. [input('bar')]: 'squid time',
  135. '#mammal': 'fox',
  136. 'albumData': ['album1', 'album2'],
  137. }),
  138. continuationSymbol);
  139. t.same(exitData, {
  140. foo: 'squid time',
  141. pomelo: 'delicious',
  142. humorous: 'fox',
  143. data: ['album1', 'album2'],
  144. ref: 'album:bepis',
  145. });
  146. });
  147. t.test(`compositeFrom: update from various sources`, t => {
  148. t.plan(3);
  149. const match = {
  150. flags: {update: true, expose: true, compose: false},
  151. update: {
  152. validate: isString,
  153. default: 'foo',
  154. },
  155. expose: {
  156. transform: Function,
  157. compute: null,
  158. },
  159. };
  160. t.test(`compositeFrom: update from composition description`, t => {
  161. t.plan(2);
  162. const composite = compositeFrom({
  163. compose: false,
  164. update: {
  165. validate: isString,
  166. default: 'foo',
  167. },
  168. steps: [
  169. {transform: (value, continuation) => continuation(value.repeat(2))},
  170. {transform: (value) => `Xx_${value}_xX`},
  171. ],
  172. });
  173. t.match(composite, match);
  174. t.equal(composite.expose.transform('foo'), `Xx_foofoo_xX`);
  175. });
  176. t.test(`compositeFrom: update from step dependencies`, t => {
  177. t.plan(2);
  178. const composite = compositeFrom({
  179. compose: false,
  180. steps: [
  181. {
  182. dependencies: [
  183. input.updateValue({
  184. validate: isString,
  185. default: 'foo',
  186. }),
  187. ],
  188. compute: ({
  189. [input.updateValue()]: value,
  190. }) => `Xx_${value.repeat(2)}_xX`,
  191. },
  192. ],
  193. });
  194. t.match(composite, match);
  195. t.equal(composite.expose.transform('foo'), 'Xx_foofoo_xX');
  196. });
  197. t.test(`compositeFrom: update from inputs`, t => {
  198. t.plan(3);
  199. const composite = compositeFrom({
  200. inputMapping: {
  201. myInput: input.updateValue({
  202. validate: isString,
  203. default: 'foo',
  204. }),
  205. },
  206. inputDescriptions: {
  207. myInput: input(),
  208. },
  209. steps: [
  210. {
  211. dependencies: [input('myInput')],
  212. compute: (continuation, {
  213. [input('myInput')]: value,
  214. }) => continuation({
  215. '#value': `Xx_${value.repeat(2)}_xX`,
  216. }),
  217. },
  218. {
  219. dependencies: ['#value'],
  220. transform: (_value, continuation, {'#value': value}) =>
  221. continuation(value),
  222. },
  223. ],
  224. });
  225. let continuationValue = null;
  226. const continuation = value => {
  227. continuationValue = value;
  228. return continuationSymbol;
  229. };
  230. t.match(composite, {
  231. ...match,
  232. flags: {update: true, expose: true, compose: true},
  233. });
  234. t.equal(
  235. composite.expose.transform('foo', continuation),
  236. continuationSymbol);
  237. t.equal(continuationValue, 'Xx_foofoo_xX');
  238. });
  239. });
  240. t.test(`compositeFrom: dynamic input validation from type`, t => {
  241. t.plan(2);
  242. const composite = compositeFrom({
  243. inputMapping: {
  244. string: input('string'),
  245. number: input('number'),
  246. boolean: input('boolean'),
  247. function: input('function'),
  248. object: input('object'),
  249. array: input('array'),
  250. },
  251. inputDescriptions: {
  252. string: input({null: true, type: 'string'}),
  253. number: input({null: true, type: 'number'}),
  254. boolean: input({null: true, type: 'boolean'}),
  255. function: input({null: true, type: 'function'}),
  256. object: input({null: true, type: 'object'}),
  257. array: input({null: true, type: 'array'}),
  258. },
  259. outputs: {'#result': '#result'},
  260. steps: [
  261. {compute: continuation => continuation({'#result': 'OK'})},
  262. ],
  263. });
  264. const notCalledSymbol = Symbol('continuation not called');
  265. let continuationValue;
  266. const continuation = value => {
  267. continuationValue = value;
  268. return continuationSymbol;
  269. };
  270. let thrownError;
  271. try {
  272. continuationValue = notCalledSymbol;
  273. thrownError = null;
  274. composite.expose.compute(continuation, {
  275. [input('string')]: 123,
  276. });
  277. } catch (error) {
  278. thrownError = error;
  279. }
  280. t.equal(continuationValue, notCalledSymbol);
  281. t.match(thrownError, {
  282. });
  283. });