@teleporthq/teleport-plugin-next-workflows 0.43.65 → 0.43.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/__tests__/_helpers/bind-workflow-utils.ts +30 -0
  2. package/__tests__/_helpers/run-generated-middleware.ts +11 -2
  3. package/__tests__/admin-form-row-hydration.test.ts +55 -0
  4. package/__tests__/cart-clear-db-mirror.test.ts +12 -0
  5. package/__tests__/cart-get-total-regional-pricing.test.ts +166 -0
  6. package/__tests__/client-config-redaction.test.ts +53 -0
  7. package/__tests__/continue-on-error.test.ts +119 -0
  8. package/__tests__/email-locale-module.test.ts +323 -0
  9. package/__tests__/email-locale-senders.test.ts +268 -0
  10. package/__tests__/invoice-delivery-fee.test.ts +104 -0
  11. package/__tests__/invoice-regional-tax.test.ts +126 -0
  12. package/__tests__/locale-aware-redirects.test.ts +355 -0
  13. package/__tests__/navigation-go-to-page.test.ts +43 -0
  14. package/__tests__/node-handler-file-inline-map.test.ts +29 -3
  15. package/__tests__/payment-redirect-and-order-notification.test.ts +6 -0
  16. package/__tests__/paypal-webhook-order-notification.test.ts +41 -0
  17. package/__tests__/resolve-config-localized-templates.test.ts +262 -0
  18. package/__tests__/sent-email-log-module.test.ts +381 -0
  19. package/__tests__/sent-email-log-wiring.test.ts +267 -0
  20. package/dist/cjs/account-delete-route-generator.d.ts +20 -0
  21. package/dist/cjs/account-delete-route-generator.d.ts.map +1 -1
  22. package/dist/cjs/account-delete-route-generator.js +29 -26
  23. package/dist/cjs/account-delete-route-generator.js.map +1 -1
  24. package/dist/cjs/api-route-generator.d.ts.map +1 -1
  25. package/dist/cjs/api-route-generator.js +57 -11
  26. package/dist/cjs/api-route-generator.js.map +1 -1
  27. package/dist/cjs/auth-generator.d.ts +2 -1
  28. package/dist/cjs/auth-generator.d.ts.map +1 -1
  29. package/dist/cjs/auth-generator.js +55 -18
  30. package/dist/cjs/auth-generator.js.map +1 -1
  31. package/dist/cjs/data-api-route-generator.d.ts.map +1 -1
  32. package/dist/cjs/data-api-route-generator.js +19 -0
  33. package/dist/cjs/data-api-route-generator.js.map +1 -1
  34. package/dist/cjs/email-locale/email-locale-code.d.ts +25 -0
  35. package/dist/cjs/email-locale/email-locale-code.d.ts.map +1 -0
  36. package/dist/cjs/email-locale/email-locale-code.js +228 -0
  37. package/dist/cjs/email-locale/email-locale-code.js.map +1 -0
  38. package/dist/cjs/email-locale/email-locale-scope.d.ts +46 -0
  39. package/dist/cjs/email-locale/email-locale-scope.d.ts.map +1 -0
  40. package/dist/cjs/email-locale/email-locale-scope.js +51 -0
  41. package/dist/cjs/email-locale/email-locale-scope.js.map +1 -0
  42. package/dist/cjs/email-locale/index.d.ts +11 -0
  43. package/dist/cjs/email-locale/index.d.ts.map +1 -0
  44. package/dist/cjs/email-locale/index.js +40 -0
  45. package/dist/cjs/email-locale/index.js.map +1 -0
  46. package/dist/cjs/executor-generator.d.ts +11 -1
  47. package/dist/cjs/executor-generator.d.ts.map +1 -1
  48. package/dist/cjs/executor-generator.js +49 -7
  49. package/dist/cjs/executor-generator.js.map +1 -1
  50. package/dist/cjs/index.d.ts +2 -0
  51. package/dist/cjs/index.d.ts.map +1 -1
  52. package/dist/cjs/index.js +9 -1
  53. package/dist/cjs/index.js.map +1 -1
  54. package/dist/cjs/invoice/api-routes-code.d.ts.map +1 -1
  55. package/dist/cjs/invoice/api-routes-code.js +55 -6
  56. package/dist/cjs/invoice/api-routes-code.js.map +1 -1
  57. package/dist/cjs/invoice/email-sender-code.d.ts.map +1 -1
  58. package/dist/cjs/invoice/email-sender-code.js +72 -31
  59. package/dist/cjs/invoice/email-sender-code.js.map +1 -1
  60. package/dist/cjs/invoice/index.d.ts +1 -4
  61. package/dist/cjs/invoice/index.d.ts.map +1 -1
  62. package/dist/cjs/invoice/index.js +8 -19
  63. package/dist/cjs/invoice/index.js.map +1 -1
  64. package/dist/cjs/invoice/invoice-html-code.d.ts.map +1 -1
  65. package/dist/cjs/invoice/invoice-html-code.js +31 -8
  66. package/dist/cjs/invoice/invoice-html-code.js.map +1 -1
  67. package/dist/cjs/invoice/regional-invoice-tax-code.d.ts +25 -0
  68. package/dist/cjs/invoice/regional-invoice-tax-code.d.ts.map +1 -0
  69. package/dist/cjs/invoice/regional-invoice-tax-code.js +116 -0
  70. package/dist/cjs/invoice/regional-invoice-tax-code.js.map +1 -0
  71. package/dist/cjs/invoice/resolve-data-source.d.ts +6 -0
  72. package/dist/cjs/invoice/resolve-data-source.d.ts.map +1 -0
  73. package/dist/cjs/invoice/resolve-data-source.js +23 -0
  74. package/dist/cjs/invoice/resolve-data-source.js.map +1 -0
  75. package/dist/cjs/nodes/account/account-delete-current.d.ts.map +1 -1
  76. package/dist/cjs/nodes/account/account-delete-current.js +10 -3
  77. package/dist/cjs/nodes/account/account-delete-current.js.map +1 -1
  78. package/dist/cjs/nodes/account/account-login.d.ts.map +1 -1
  79. package/dist/cjs/nodes/account/account-login.js +3 -1
  80. package/dist/cjs/nodes/account/account-login.js.map +1 -1
  81. package/dist/cjs/nodes/account/account-logout.d.ts.map +1 -1
  82. package/dist/cjs/nodes/account/account-logout.js +4 -2
  83. package/dist/cjs/nodes/account/account-logout.js.map +1 -1
  84. package/dist/cjs/nodes/account/account-signup.d.ts.map +1 -1
  85. package/dist/cjs/nodes/account/account-signup.js +12 -2
  86. package/dist/cjs/nodes/account/account-signup.js.map +1 -1
  87. package/dist/cjs/nodes/account/account-social-login.d.ts.map +1 -1
  88. package/dist/cjs/nodes/account/account-social-login.js +4 -2
  89. package/dist/cjs/nodes/account/account-social-login.js.map +1 -1
  90. package/dist/cjs/nodes/cart/cart-clear.d.ts.map +1 -1
  91. package/dist/cjs/nodes/cart/cart-clear.js +6 -0
  92. package/dist/cjs/nodes/cart/cart-clear.js.map +1 -1
  93. package/dist/cjs/nodes/cart/cart-get-total.d.ts.map +1 -1
  94. package/dist/cjs/nodes/cart/cart-get-total.js +81 -5
  95. package/dist/cjs/nodes/cart/cart-get-total.js.map +1 -1
  96. package/dist/cjs/nodes/data/data-create-item.d.ts.map +1 -1
  97. package/dist/cjs/nodes/data/data-create-item.js +16 -0
  98. package/dist/cjs/nodes/data/data-create-item.js.map +1 -1
  99. package/dist/cjs/nodes/ecommerce/ecommerce-generate-invoice.d.ts.map +1 -1
  100. package/dist/cjs/nodes/ecommerce/ecommerce-generate-invoice.js +6 -0
  101. package/dist/cjs/nodes/ecommerce/ecommerce-generate-invoice.js.map +1 -1
  102. package/dist/cjs/nodes/navigation/navigation-go-to-page.d.ts.map +1 -1
  103. package/dist/cjs/nodes/navigation/navigation-go-to-page.js +4 -1
  104. package/dist/cjs/nodes/navigation/navigation-go-to-page.js.map +1 -1
  105. package/dist/cjs/nodes/navigation/navigation-navigate-to-url.d.ts.map +1 -1
  106. package/dist/cjs/nodes/navigation/navigation-navigate-to-url.js +5 -2
  107. package/dist/cjs/nodes/navigation/navigation-navigate-to-url.js.map +1 -1
  108. package/dist/cjs/nodes/payment/payment-charge-user.d.ts.map +1 -1
  109. package/dist/cjs/nodes/payment/payment-charge-user.js +7 -2
  110. package/dist/cjs/nodes/payment/payment-charge-user.js.map +1 -1
  111. package/dist/cjs/segment-splitter.d.ts +1 -1
  112. package/dist/cjs/segment-splitter.d.ts.map +1 -1
  113. package/dist/cjs/segment-splitter.js +29 -2
  114. package/dist/cjs/segment-splitter.js.map +1 -1
  115. package/dist/cjs/sent-email-log/index.d.ts +16 -0
  116. package/dist/cjs/sent-email-log/index.d.ts.map +1 -0
  117. package/dist/cjs/sent-email-log/index.js +53 -0
  118. package/dist/cjs/sent-email-log/index.js.map +1 -0
  119. package/dist/cjs/sent-email-log/sent-email-log-code.d.ts +4 -0
  120. package/dist/cjs/sent-email-log/sent-email-log-code.d.ts.map +1 -0
  121. package/dist/cjs/sent-email-log/sent-email-log-code.js +415 -0
  122. package/dist/cjs/sent-email-log/sent-email-log-code.js.map +1 -0
  123. package/dist/cjs/sent-email-log/sent-email-log-scope.d.ts +21 -0
  124. package/dist/cjs/sent-email-log/sent-email-log-scope.d.ts.map +1 -0
  125. package/dist/cjs/sent-email-log/sent-email-log-scope.js +36 -0
  126. package/dist/cjs/sent-email-log/sent-email-log-scope.js.map +1 -0
  127. package/dist/cjs/transactional-email-code.d.ts +39 -1
  128. package/dist/cjs/transactional-email-code.d.ts.map +1 -1
  129. package/dist/cjs/transactional-email-code.js +86 -12
  130. package/dist/cjs/transactional-email-code.js.map +1 -1
  131. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  132. package/dist/cjs/webhook-generator.d.ts.map +1 -1
  133. package/dist/cjs/webhook-generator.js +9 -2
  134. package/dist/cjs/webhook-generator.js.map +1 -1
  135. package/dist/cjs/workflow-component-plugin.js +4 -3
  136. package/dist/cjs/workflow-component-plugin.js.map +1 -1
  137. package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
  138. package/dist/cjs/workflow-project-plugin.js +31 -3
  139. package/dist/cjs/workflow-project-plugin.js.map +1 -1
  140. package/dist/cjs/workflow-utils-alias.d.ts +21 -0
  141. package/dist/cjs/workflow-utils-alias.d.ts.map +1 -0
  142. package/dist/cjs/workflow-utils-alias.js +27 -0
  143. package/dist/cjs/workflow-utils-alias.js.map +1 -0
  144. package/dist/esm/account-delete-route-generator.d.ts +20 -0
  145. package/dist/esm/account-delete-route-generator.d.ts.map +1 -1
  146. package/dist/esm/account-delete-route-generator.js +30 -27
  147. package/dist/esm/account-delete-route-generator.js.map +1 -1
  148. package/dist/esm/api-route-generator.d.ts.map +1 -1
  149. package/dist/esm/api-route-generator.js +57 -11
  150. package/dist/esm/api-route-generator.js.map +1 -1
  151. package/dist/esm/auth-generator.d.ts +2 -1
  152. package/dist/esm/auth-generator.d.ts.map +1 -1
  153. package/dist/esm/auth-generator.js +56 -19
  154. package/dist/esm/auth-generator.js.map +1 -1
  155. package/dist/esm/data-api-route-generator.d.ts.map +1 -1
  156. package/dist/esm/data-api-route-generator.js +19 -0
  157. package/dist/esm/data-api-route-generator.js.map +1 -1
  158. package/dist/esm/email-locale/email-locale-code.d.ts +25 -0
  159. package/dist/esm/email-locale/email-locale-code.d.ts.map +1 -0
  160. package/dist/esm/email-locale/email-locale-code.js +222 -0
  161. package/dist/esm/email-locale/email-locale-code.js.map +1 -0
  162. package/dist/esm/email-locale/email-locale-scope.d.ts +46 -0
  163. package/dist/esm/email-locale/email-locale-scope.d.ts.map +1 -0
  164. package/dist/esm/email-locale/email-locale-scope.js +47 -0
  165. package/dist/esm/email-locale/email-locale-scope.js.map +1 -0
  166. package/dist/esm/email-locale/index.d.ts +11 -0
  167. package/dist/esm/email-locale/index.d.ts.map +1 -0
  168. package/dist/esm/email-locale/index.js +28 -0
  169. package/dist/esm/email-locale/index.js.map +1 -0
  170. package/dist/esm/executor-generator.d.ts +11 -1
  171. package/dist/esm/executor-generator.d.ts.map +1 -1
  172. package/dist/esm/executor-generator.js +49 -7
  173. package/dist/esm/executor-generator.js.map +1 -1
  174. package/dist/esm/index.d.ts +2 -0
  175. package/dist/esm/index.d.ts.map +1 -1
  176. package/dist/esm/index.js +2 -0
  177. package/dist/esm/index.js.map +1 -1
  178. package/dist/esm/invoice/api-routes-code.d.ts.map +1 -1
  179. package/dist/esm/invoice/api-routes-code.js +55 -6
  180. package/dist/esm/invoice/api-routes-code.js.map +1 -1
  181. package/dist/esm/invoice/email-sender-code.d.ts.map +1 -1
  182. package/dist/esm/invoice/email-sender-code.js +72 -31
  183. package/dist/esm/invoice/email-sender-code.js.map +1 -1
  184. package/dist/esm/invoice/index.d.ts +1 -4
  185. package/dist/esm/invoice/index.d.ts.map +1 -1
  186. package/dist/esm/invoice/index.js +7 -18
  187. package/dist/esm/invoice/index.js.map +1 -1
  188. package/dist/esm/invoice/invoice-html-code.d.ts.map +1 -1
  189. package/dist/esm/invoice/invoice-html-code.js +31 -8
  190. package/dist/esm/invoice/invoice-html-code.js.map +1 -1
  191. package/dist/esm/invoice/regional-invoice-tax-code.d.ts +25 -0
  192. package/dist/esm/invoice/regional-invoice-tax-code.d.ts.map +1 -0
  193. package/dist/esm/invoice/regional-invoice-tax-code.js +113 -0
  194. package/dist/esm/invoice/regional-invoice-tax-code.js.map +1 -0
  195. package/dist/esm/invoice/resolve-data-source.d.ts +6 -0
  196. package/dist/esm/invoice/resolve-data-source.d.ts.map +1 -0
  197. package/dist/esm/invoice/resolve-data-source.js +19 -0
  198. package/dist/esm/invoice/resolve-data-source.js.map +1 -0
  199. package/dist/esm/nodes/account/account-delete-current.d.ts.map +1 -1
  200. package/dist/esm/nodes/account/account-delete-current.js +10 -3
  201. package/dist/esm/nodes/account/account-delete-current.js.map +1 -1
  202. package/dist/esm/nodes/account/account-login.d.ts.map +1 -1
  203. package/dist/esm/nodes/account/account-login.js +3 -1
  204. package/dist/esm/nodes/account/account-login.js.map +1 -1
  205. package/dist/esm/nodes/account/account-logout.d.ts.map +1 -1
  206. package/dist/esm/nodes/account/account-logout.js +4 -2
  207. package/dist/esm/nodes/account/account-logout.js.map +1 -1
  208. package/dist/esm/nodes/account/account-signup.d.ts.map +1 -1
  209. package/dist/esm/nodes/account/account-signup.js +12 -2
  210. package/dist/esm/nodes/account/account-signup.js.map +1 -1
  211. package/dist/esm/nodes/account/account-social-login.d.ts.map +1 -1
  212. package/dist/esm/nodes/account/account-social-login.js +4 -2
  213. package/dist/esm/nodes/account/account-social-login.js.map +1 -1
  214. package/dist/esm/nodes/cart/cart-clear.d.ts.map +1 -1
  215. package/dist/esm/nodes/cart/cart-clear.js +6 -0
  216. package/dist/esm/nodes/cart/cart-clear.js.map +1 -1
  217. package/dist/esm/nodes/cart/cart-get-total.d.ts.map +1 -1
  218. package/dist/esm/nodes/cart/cart-get-total.js +81 -5
  219. package/dist/esm/nodes/cart/cart-get-total.js.map +1 -1
  220. package/dist/esm/nodes/data/data-create-item.d.ts.map +1 -1
  221. package/dist/esm/nodes/data/data-create-item.js +16 -0
  222. package/dist/esm/nodes/data/data-create-item.js.map +1 -1
  223. package/dist/esm/nodes/ecommerce/ecommerce-generate-invoice.d.ts.map +1 -1
  224. package/dist/esm/nodes/ecommerce/ecommerce-generate-invoice.js +6 -0
  225. package/dist/esm/nodes/ecommerce/ecommerce-generate-invoice.js.map +1 -1
  226. package/dist/esm/nodes/navigation/navigation-go-to-page.d.ts.map +1 -1
  227. package/dist/esm/nodes/navigation/navigation-go-to-page.js +4 -1
  228. package/dist/esm/nodes/navigation/navigation-go-to-page.js.map +1 -1
  229. package/dist/esm/nodes/navigation/navigation-navigate-to-url.d.ts.map +1 -1
  230. package/dist/esm/nodes/navigation/navigation-navigate-to-url.js +5 -2
  231. package/dist/esm/nodes/navigation/navigation-navigate-to-url.js.map +1 -1
  232. package/dist/esm/nodes/payment/payment-charge-user.d.ts.map +1 -1
  233. package/dist/esm/nodes/payment/payment-charge-user.js +7 -2
  234. package/dist/esm/nodes/payment/payment-charge-user.js.map +1 -1
  235. package/dist/esm/segment-splitter.d.ts +1 -1
  236. package/dist/esm/segment-splitter.d.ts.map +1 -1
  237. package/dist/esm/segment-splitter.js +29 -2
  238. package/dist/esm/segment-splitter.js.map +1 -1
  239. package/dist/esm/sent-email-log/index.d.ts +16 -0
  240. package/dist/esm/sent-email-log/index.d.ts.map +1 -0
  241. package/dist/esm/sent-email-log/index.js +45 -0
  242. package/dist/esm/sent-email-log/index.js.map +1 -0
  243. package/dist/esm/sent-email-log/sent-email-log-code.d.ts +4 -0
  244. package/dist/esm/sent-email-log/sent-email-log-code.d.ts.map +1 -0
  245. package/dist/esm/sent-email-log/sent-email-log-code.js +411 -0
  246. package/dist/esm/sent-email-log/sent-email-log-code.js.map +1 -0
  247. package/dist/esm/sent-email-log/sent-email-log-scope.d.ts +21 -0
  248. package/dist/esm/sent-email-log/sent-email-log-scope.d.ts.map +1 -0
  249. package/dist/esm/sent-email-log/sent-email-log-scope.js +31 -0
  250. package/dist/esm/sent-email-log/sent-email-log-scope.js.map +1 -0
  251. package/dist/esm/transactional-email-code.d.ts +39 -1
  252. package/dist/esm/transactional-email-code.d.ts.map +1 -1
  253. package/dist/esm/transactional-email-code.js +81 -10
  254. package/dist/esm/transactional-email-code.js.map +1 -1
  255. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  256. package/dist/esm/webhook-generator.d.ts.map +1 -1
  257. package/dist/esm/webhook-generator.js +9 -2
  258. package/dist/esm/webhook-generator.js.map +1 -1
  259. package/dist/esm/workflow-component-plugin.js +4 -3
  260. package/dist/esm/workflow-component-plugin.js.map +1 -1
  261. package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
  262. package/dist/esm/workflow-project-plugin.js +32 -4
  263. package/dist/esm/workflow-project-plugin.js.map +1 -1
  264. package/dist/esm/workflow-utils-alias.d.ts +21 -0
  265. package/dist/esm/workflow-utils-alias.d.ts.map +1 -0
  266. package/dist/esm/workflow-utils-alias.js +21 -0
  267. package/dist/esm/workflow-utils-alias.js.map +1 -0
  268. package/package.json +4 -4
  269. package/src/account-delete-route-generator.ts +36 -7
  270. package/src/api-route-generator.ts +64 -11
  271. package/src/auth-generator.ts +63 -18
  272. package/src/data-api-route-generator.ts +19 -0
  273. package/src/email-locale/email-locale-code.ts +239 -0
  274. package/src/email-locale/email-locale-scope.ts +65 -0
  275. package/src/email-locale/index.ts +46 -0
  276. package/src/executor-generator.ts +65 -7
  277. package/src/index.ts +8 -0
  278. package/src/invoice/api-routes-code.ts +65 -6
  279. package/src/invoice/email-sender-code.ts +72 -31
  280. package/src/invoice/index.ts +7 -23
  281. package/src/invoice/invoice-html-code.ts +31 -8
  282. package/src/invoice/regional-invoice-tax-code.ts +112 -0
  283. package/src/invoice/resolve-data-source.ts +25 -0
  284. package/src/nodes/account/account-delete-current.ts +10 -3
  285. package/src/nodes/account/account-login.ts +3 -1
  286. package/src/nodes/account/account-logout.ts +4 -2
  287. package/src/nodes/account/account-signup.ts +12 -2
  288. package/src/nodes/account/account-social-login.ts +7 -2
  289. package/src/nodes/cart/cart-clear.ts +6 -0
  290. package/src/nodes/cart/cart-get-total.ts +94 -5
  291. package/src/nodes/data/data-create-item.ts +16 -0
  292. package/src/nodes/ecommerce/ecommerce-generate-invoice.ts +6 -0
  293. package/src/nodes/navigation/navigation-go-to-page.ts +5 -1
  294. package/src/nodes/navigation/navigation-navigate-to-url.ts +6 -2
  295. package/src/nodes/payment/payment-charge-user.ts +13 -2
  296. package/src/nodes/workflow-utils-global.d.ts +12 -0
  297. package/src/segment-splitter.ts +33 -2
  298. package/src/sent-email-log/index.ts +57 -0
  299. package/src/sent-email-log/sent-email-log-code.ts +419 -0
  300. package/src/sent-email-log/sent-email-log-scope.ts +36 -0
  301. package/src/transactional-email-code.ts +112 -4
  302. package/src/webhook-generator.ts +11 -2
  303. package/src/workflow-component-plugin.ts +4 -3
  304. package/src/workflow-project-plugin.ts +36 -4
  305. package/src/workflow-utils-alias.ts +26 -0
@@ -140,6 +140,66 @@ describe('invoice PDF renderer — delivery fee', () => {
140
140
  expect(withoutFee).not.toContain('<span>Delivery</span>')
141
141
  })
142
142
 
143
+ it('renders the template Delivery row gated on Invoice.hasShipping only when there is a fee', () => {
144
+ // The GUI emits the row's rendering condition as an `expr` reference
145
+ // (`invoiceData?.Invoice?.hasShipping`) compared with the STRING operand
146
+ // 'true'. The renderer resolves that expr to boolean true, so a strict
147
+ // `===` against the operand can never pass — the row was hidden on every
148
+ // invoice that had a fee.
149
+ const textNode = (children: unknown[]) => ({
150
+ type: 'element',
151
+ content: { elementType: 'text', semanticType: 'p', children },
152
+ })
153
+ const templateUidl = {
154
+ name: 'invoice-template',
155
+ node: {
156
+ type: 'element',
157
+ content: {
158
+ elementType: 'container',
159
+ children: [
160
+ {
161
+ type: 'conditional',
162
+ content: {
163
+ reference: { type: 'expr', content: 'invoiceData?.Invoice?.hasShipping' },
164
+ condition: { conditions: [{ operation: '=', operand: 'true' }] },
165
+ node: {
166
+ type: 'element',
167
+ content: {
168
+ elementType: 'container',
169
+ name: 'Delivery Row',
170
+ children: [
171
+ textNode([{ type: 'static', content: 'Delivery:' }]),
172
+ textNode([{ type: 'expr', content: 'invoiceData?.Invoice?.shippingAmount' }]),
173
+ ],
174
+ },
175
+ },
176
+ },
177
+ },
178
+ ],
179
+ },
180
+ },
181
+ }
182
+ const code = generatePdfGeneratorCode(FAKE_SETTINGS, templateUidl, {})
183
+ const moduleObject = { exports: {} as Record<string, unknown> }
184
+ new Function('require', 'module', 'exports', `${code}; return module.exports;`)(
185
+ require,
186
+ moduleObject,
187
+ moduleObject.exports
188
+ )
189
+ const templated = moduleObject.exports as unknown as PdfGeneratorModule
190
+
191
+ const withFee = templated.buildInvoiceHtml({
192
+ ...baseInvoice,
193
+ shippingAmount: 9.99,
194
+ total: 209.99,
195
+ })
196
+ expect(withFee).toContain('Delivery:')
197
+ expect(withFee).toContain('$9.99')
198
+
199
+ const withoutFee = templated.buildInvoiceHtml({ ...baseInvoice, total: 200 })
200
+ expect(withoutFee).not.toContain('Delivery:')
201
+ })
202
+
143
203
  it('fills the {{shippingAmount}} merge token in the invoice email', () => {
144
204
  const data = pdfGenerator.buildDataContext({
145
205
  ...baseInvoice,
@@ -153,3 +213,47 @@ describe('invoice PDF renderer — delivery fee', () => {
153
213
  expect(pdfGenerator.replacePlaceholders('Due: {{totalAmount}}', data)).toBe('Due: $209.99')
154
214
  })
155
215
  })
216
+
217
+ describe('invoice PDF renderer — each line says which VAT rate it was taxed at', () => {
218
+ const pdfGenerator = loadPdfGenerator()
219
+ const regionalInvoice = {
220
+ invoiceNumber: 'INV-0001',
221
+ taxRate: 20,
222
+ taxIncludedInPrice: false,
223
+ subtotal: 247,
224
+ taxAmount: 40,
225
+ shippingAmount: 0,
226
+ total: 287,
227
+ currency: 'RON',
228
+ items: [
229
+ {
230
+ name: 'Kettle',
231
+ quantity: 2,
232
+ unitPrice: 100,
233
+ totalPrice: 200,
234
+ taxRate: 20,
235
+ taxIncluded: false,
236
+ },
237
+ { name: 'Book', quantity: 1, unitPrice: 47, totalPrice: 47, taxRate: 9, taxIncluded: true },
238
+ ],
239
+ }
240
+
241
+ it('binds the per-line rate and VAT beside the net price', () => {
242
+ const products = (
243
+ pdfGenerator.buildInvoiceDataScope(regionalInvoice).invoiceData as unknown as {
244
+ Products: Array<Record<string, string>>
245
+ }
246
+ ).Products
247
+ expect(products.map((product) => product.lineVatRate)).toEqual(['20%', '9%'])
248
+ expect(products[0].lineVatAmount).toContain('40.00')
249
+ expect(products[1].lineVatAmount).toContain('3.88')
250
+ })
251
+
252
+ it('prints the VAT % and VAT columns and a VAT total on the fallback invoice', () => {
253
+ const html = pdfGenerator.buildInvoiceHtml(regionalInvoice)
254
+ expect(html).toContain('>VAT %</th>')
255
+ expect(html).toContain('>20%</td>')
256
+ expect(html).toContain('>9%</td>')
257
+ expect(html).toContain('<span>VAT</span>')
258
+ })
259
+ })
@@ -0,0 +1,126 @@
1
+ /* tslint:disable:function-constructor */
2
+ import { generateInvoiceGenerateRouteCode } from '../src/invoice/api-routes-code'
3
+ import { REGIONAL_INVOICE_TAX_CODE } from '../src/invoice/regional-invoice-tax-code'
4
+ import type { UIDLInvoiceSettings } from '@teleporthq/teleport-types'
5
+
6
+ /**
7
+ * The invoice of an order priced by region (shipping zones + tax
8
+ * jurisdictions) is computed from the rates the order was CHARGED at —
9
+ * `teleport_orders.tax_breakdown` — not from the store's single default rate,
10
+ * and its total must equal `goods + shipping_amount - discount_amount`, the
11
+ * amount the buyer was debited.
12
+ */
13
+
14
+ interface RegionalInvoiceTax {
15
+ taxRate: number
16
+ taxIncluded: boolean
17
+ subtotal: number
18
+ taxAmount: number
19
+ shippingNet: number
20
+ total: number
21
+ itemTax: Array<{ rate: number; included: boolean; amount: number }>
22
+ }
23
+
24
+ const api = new Function(
25
+ REGIONAL_INVOICE_TAX_CODE +
26
+ '\nreturn { parse: parseOrderTaxBreakdown, resolve: resolveRegionalInvoiceTax };'
27
+ )() as {
28
+ parse: (raw: unknown) => Record<string, unknown> | null
29
+ resolve: (
30
+ breakdown: Record<string, unknown>,
31
+ items: Array<Record<string, unknown>>,
32
+ discountAmount: number,
33
+ shippingAmount: number
34
+ ) => RegionalInvoiceTax
35
+ }
36
+
37
+ const cents = (n: number) => Math.round(n * 100) / 100
38
+
39
+ // Germany: food at 7% and tools at 19%, both added on top; 6.00 shipping taxed
40
+ // at 19% → shipping_amount 7.14 on the order.
41
+ const GERMAN_ORDER = {
42
+ taxRate: 19,
43
+ taxIncluded: false,
44
+ taxDecimals: 2,
45
+ shippingTax: 1.14,
46
+ lines: [
47
+ { productId: 'p1', variantId: '', taxRate: 7, taxIncluded: false },
48
+ { productId: 'p2', variantId: 'v2', taxRate: 19, taxIncluded: false },
49
+ ],
50
+ }
51
+ const GERMAN_ITEMS = [
52
+ { productId: 'p1', variantId: null, quantity: 2, unitPrice: 10, totalPrice: 20 },
53
+ { productId: 'p2', variantId: 'v2', quantity: 1, unitPrice: 25.99, totalPrice: 25.99 },
54
+ ]
55
+
56
+ describe('invoice — regional tax breakdown', () => {
57
+ it('reads the breakdown from the TEXT column, and nothing from a legacy order', () => {
58
+ expect(api.parse(JSON.stringify(GERMAN_ORDER))).toMatchObject({ taxRate: 19 })
59
+ expect(api.parse(null)).toBeNull()
60
+ expect(api.parse('not json')).toBeNull()
61
+ expect(api.parse('{"taxRate":19}')).toBeNull()
62
+ })
63
+
64
+ it('taxes each line at its own rate and reconciles with the order total', () => {
65
+ const result = api.resolve(GERMAN_ORDER, GERMAN_ITEMS, 0, 7.14)
66
+ // goods: 21.40 + 30.93 = 52.33, tax 1.40 + 4.94 = 6.34; shipping tax 1.14.
67
+ expect(cents(result.total)).toBe(59.47)
68
+ expect(cents(result.taxAmount)).toBe(7.48)
69
+ expect(cents(result.subtotal)).toBe(45.99)
70
+ // Delivery prints net, so Subtotal + Tax + Delivery is the total.
71
+ expect(cents(result.shippingNet)).toBe(6)
72
+ expect(cents(result.subtotal + result.taxAmount + result.shippingNet)).toBe(59.47)
73
+ expect(result.itemTax).toEqual([
74
+ { rate: 7, included: false, amount: 1.4 },
75
+ { rate: 19, included: false, amount: 4.94 },
76
+ ])
77
+ expect(result.taxRate).toBe(19)
78
+ })
79
+
80
+ it('takes a discount off the goods first, then the shipping, shrinking each tax in proportion', () => {
81
+ const goodsOnly = api.resolve(GERMAN_ORDER, GERMAN_ITEMS, 10.466, 7.14)
82
+ // 52.33 - 10.466 leaves 80% of the goods: 6.34 * 0.8 = 5.072, shipping untouched.
83
+ expect(cents(goodsOnly.total)).toBe(cents(52.33 - 10.466 + 7.14))
84
+ expect(cents(goodsOnly.taxAmount)).toBe(cents(6.34 * 0.8 + 1.14))
85
+
86
+ const intoShipping = api.resolve(GERMAN_ORDER, GERMAN_ITEMS, 55.9, 7.14)
87
+ // The goods are fully discounted and 3.57 of 7.14 shipping is left: half its tax.
88
+ expect(cents(intoShipping.total)).toBe(3.57)
89
+ expect(cents(intoShipping.taxAmount)).toBe(0.57)
90
+ expect(intoShipping.subtotal).toBe(0)
91
+ expect(cents(intoShipping.shippingNet)).toBe(3)
92
+ })
93
+
94
+ it('extracts included tax without adding it, and falls back to the standard rate for an unmatched line', () => {
95
+ const french = api.resolve(
96
+ { taxRate: 20, taxIncluded: true, taxDecimals: 2, shippingTax: 1, lines: [] },
97
+ [{ productId: 'p9', quantity: 1, unitPrice: 25.99, totalPrice: 25.99 }],
98
+ 0,
99
+ 6
100
+ )
101
+ expect(cents(french.total)).toBe(31.99)
102
+ expect(cents(french.taxAmount)).toBe(5.33)
103
+ expect(french.itemTax).toEqual([{ rate: 20, included: true, amount: 4.33 }])
104
+ })
105
+ })
106
+
107
+ describe('/api/invoices/generate — regional tax wiring', () => {
108
+ const route = generateInvoiceGenerateRouteCode({
109
+ invoicePrefix: 'INV-',
110
+ defaultTaxRate: 20,
111
+ showDiscount: false,
112
+ taxIncludedInPrice: false,
113
+ companyDetails: {},
114
+ template: { document: null },
115
+ tables: { invoicesTable: 'teleport_invoices', invoiceItemsTable: 'teleport_invoice_items' },
116
+ emailDelivery: { enabled: false },
117
+ } as unknown as UIDLInvoiceSettings)
118
+
119
+ it('prices from the order breakdown unless the caller names its own rate', () => {
120
+ expect(route).toContain('function resolveRegionalInvoiceTax(')
121
+ expect(route).toContain('var regionalTaxBreakdown = body.taxRate == null')
122
+ expect(route).toContain('parseOrderTaxBreakdown(orderShippingSource.tax_breakdown)')
123
+ expect(route).toContain('taxIncludedInPrice: taxIncludedInPrice,')
124
+ expect(route).toContain('variantId: row.variant_id || row.variantId || null,')
125
+ })
126
+ })
@@ -0,0 +1,355 @@
1
+ import { NextWorkflowProjectPlugin } from '../src/workflow-project-plugin'
2
+ import {
3
+ generateCronAPIRoute,
4
+ generateServerSegmentAPIRoute,
5
+ generateStreamingServerSegmentAPIRoute,
6
+ generateWebhookWorkflowAPIRoute,
7
+ } from '../src/api-route-generator'
8
+ import { generateClientRuntimeCode } from '../src/executor-generator'
9
+ import { paymentChargeUser } from '../src/nodes/payment/payment-charge-user'
10
+ import {
11
+ WORKFLOW_UTILS_ALIAS,
12
+ workflowUtilsAliasLine,
13
+ workflowUtilsImportLine,
14
+ } from '../src/workflow-utils-alias'
15
+ import { WorkflowSegment } from '../src/types'
16
+ import { loadHandler, HandlerFn } from './_helpers/load-handler'
17
+ import { bindWorkflowUtils } from './_helpers/bind-workflow-utils'
18
+ import { compileGeneratedMiddleware } from './_helpers/run-generated-middleware'
19
+
20
+ /**
21
+ * A visitor who switched the site to Spanish must stay on the Spanish site
22
+ * through every redirect the generated app performs: the account handlers'
23
+ * "back to the home page", the navigation nodes, the payment provider's return
24
+ * URLs and the middleware's sign-in redirect. Next.js serves the non-default
25
+ * languages under a "/<locale>" prefix, so each of those has to put the run's
26
+ * (or the request's) locale back on the path it builds. Executed, not grepped:
27
+ * what matters is where the browser is sent.
28
+ */
29
+
30
+ interface BrowserStub {
31
+ location: { href: string }
32
+ open: jest.Mock
33
+ localStorage: { setItem: jest.Mock; removeItem: jest.Mock }
34
+ dispatchEvent: jest.Mock
35
+ setTimeout: (fn: () => void, ms: number) => void
36
+ document: { createElement: () => Record<string, unknown>; body: { appendChild: jest.Mock } }
37
+ __teleportNextAuth: Record<string, jest.Mock>
38
+ }
39
+
40
+ const installBrowser = (): BrowserStub => {
41
+ const element = { textContent: '', setAttribute: jest.fn() }
42
+ const browser: BrowserStub = {
43
+ location: { href: '' },
44
+ open: jest.fn(),
45
+ localStorage: { setItem: jest.fn(), removeItem: jest.fn() },
46
+ dispatchEvent: jest.fn(),
47
+ // The farewell redirect is delayed so its toast is readable; run it now.
48
+ setTimeout: (fn) => fn(),
49
+ document: { createElement: () => element, body: { appendChild: jest.fn() } },
50
+ __teleportNextAuth: {
51
+ signIn: jest.fn(async () => ({})),
52
+ signOut: jest.fn(async () => undefined),
53
+ },
54
+ }
55
+ ;(globalThis as any).window = browser
56
+ ;(globalThis as any).CustomEvent = class {
57
+ public type: string
58
+ public detail: unknown
59
+ constructor(type: string, init?: { detail?: unknown }) {
60
+ this.type = type
61
+ this.detail = init ? init.detail : undefined
62
+ }
63
+ }
64
+ return browser
65
+ }
66
+
67
+ const okJson = (payload: unknown) => async () => ({ ok: true, json: async () => payload })
68
+
69
+ describe('client handlers keep the language of the run', () => {
70
+ let unbindWorkflowUtils: () => void
71
+ let browser: BrowserStub
72
+
73
+ beforeAll(() => {
74
+ unbindWorkflowUtils = bindWorkflowUtils()
75
+ })
76
+
77
+ afterAll(() => {
78
+ unbindWorkflowUtils()
79
+ })
80
+
81
+ beforeEach(() => {
82
+ browser = installBrowser()
83
+ })
84
+
85
+ afterEach(() => {
86
+ delete (globalThis as any).window
87
+ delete (globalThis as any).CustomEvent
88
+ delete (globalThis as any).fetch
89
+ })
90
+
91
+ it('account-login lands on the home page of the language signed in from', async () => {
92
+ const handler: HandlerFn = loadHandler('account-login')
93
+ ;(globalThis as any).fetch = okJson({ user: { id: 'u-1' } })
94
+ await handler({ email: 'a@b.c', password: 'secret' }, { __locale: 'es' })
95
+ expect(browser.location.href).toBe('/es')
96
+ await handler({ email: 'a@b.c', password: 'secret' }, { __locale: 'en' })
97
+ expect(browser.location.href).toBe('/')
98
+ })
99
+
100
+ it('account-signup lands on the home page of the language signed up from', async () => {
101
+ const handler: HandlerFn = loadHandler('account-signup')
102
+ ;(globalThis as any).fetch = okJson({ user: { id: 'u-1' } })
103
+ await handler({ email: 'a@b.c', password: 'secret' }, { __locale: 'es' })
104
+ expect(browser.location.href).toBe('/es')
105
+ await handler({ email: 'a@b.c', password: 'secret' }, {})
106
+ expect(browser.location.href).toBe('/')
107
+ })
108
+
109
+ it('account-logout lands on the home page of the language signed out from', async () => {
110
+ const handler: HandlerFn = loadHandler('account-logout')
111
+ await handler({}, { __locale: 'es' })
112
+ expect(browser.location.href).toBe('/es')
113
+ })
114
+
115
+ it('account-delete-current lands on the configured page in the language deleted from', async () => {
116
+ const handler: HandlerFn = loadHandler('account-delete-current')
117
+ ;(globalThis as any).fetch = okJson({ success: true })
118
+ await handler({ redirectTo: '/goodbye' }, { __locale: 'es' })
119
+ expect(browser.location.href).toBe('/es/goodbye')
120
+ await handler({}, { __locale: 'es' })
121
+ expect(browser.location.href).toBe('/es')
122
+ })
123
+
124
+ it('account-social-login asks the provider to return to the language started from', async () => {
125
+ const handler: HandlerFn = loadHandler('account-social-login')
126
+ await handler({ provider: 'google' }, { __locale: 'es' })
127
+ expect(browser.__teleportNextAuth.signIn).toHaveBeenCalledWith('google', {
128
+ callbackUrl: '/es',
129
+ })
130
+ await handler({ provider: 'google', callbackUrl: '/my-profile' }, { __locale: 'es' })
131
+ expect(browser.__teleportNextAuth.signIn).toHaveBeenLastCalledWith('google', {
132
+ callbackUrl: '/es/my-profile',
133
+ })
134
+ })
135
+
136
+ it('navigation-navigate-to-url localizes a site path and leaves everything else alone', async () => {
137
+ const handler: HandlerFn = loadHandler('navigation-navigate-to-url')
138
+ await handler({ url: '/sign-in' }, { __locale: 'es' })
139
+ expect(browser.location.href).toBe('/es/sign-in')
140
+ await handler({ url: '/sign-in', openInNewTab: true }, { __locale: 'es' })
141
+ expect(browser.open).toHaveBeenCalledWith('/es/sign-in', '_blank')
142
+ await handler({ url: 'https://example.com/x' }, { __locale: 'es' })
143
+ expect(browser.location.href).toBe('https://example.com/x')
144
+ await handler({ url: '/api/invoices/download?id=1' }, { __locale: 'es' })
145
+ expect(browser.location.href).toBe('/api/invoices/download?id=1')
146
+ await handler({ url: '/sign-in' }, {})
147
+ expect(browser.location.href).toBe('/sign-in')
148
+ })
149
+ })
150
+
151
+ describe('payment-charge-user returns the buyer to the language of the checkout', () => {
152
+ let unbindWorkflowUtils: () => void
153
+
154
+ beforeAll(() => {
155
+ unbindWorkflowUtils = bindWorkflowUtils()
156
+ })
157
+
158
+ afterAll(() => {
159
+ unbindWorkflowUtils()
160
+ })
161
+
162
+ const source = paymentChargeUser.generateHandler()
163
+ // The entry and the helpers up to the real absolutizer; the provider calls
164
+ // are stubbed to capture the URLs they were handed.
165
+ const entrySource = source.slice(
166
+ source.indexOf('async function payment_charge_user'),
167
+ source.indexOf('function toStripeMinorUnits')
168
+ )
169
+
170
+ const charge = async (
171
+ config: Record<string, unknown>,
172
+ context: Record<string, unknown>
173
+ ): Promise<Record<string, unknown>> => {
174
+ const capture = async (
175
+ _config: unknown,
176
+ _currency: unknown,
177
+ _amount: unknown,
178
+ successUrl: string,
179
+ cancelUrl: string
180
+ ) => ({ successUrl, cancelUrl })
181
+ // eslint-disable-next-line no-new-func
182
+ const factory = new Function(
183
+ 'chargeWithStripe',
184
+ 'chargeWithPaypal',
185
+ `${entrySource}\nreturn payment_charge_user;`
186
+ )
187
+ return factory(capture, capture)(config, context)
188
+ }
189
+
190
+ it('prefixes site-relative return paths with the run locale before absolutizing', async () => {
191
+ const result = await charge(
192
+ { successUrl: '/orders/ORD-42?payment=success', cancelUrl: '/checkout', amount: 10 },
193
+ { __baseUrl: 'https://shop.test', __locale: 'es' }
194
+ )
195
+ expect(result).toEqual({
196
+ successUrl: 'https://shop.test/es/orders/ORD-42?payment=success',
197
+ cancelUrl: 'https://shop.test/es/checkout',
198
+ })
199
+ })
200
+
201
+ it('keeps the bare paths for the default language and an external override untouched', async () => {
202
+ expect(
203
+ await charge(
204
+ { successUrl: '/orders/ORD-42', cancelUrl: 'https://pay.example/back', amount: 10 },
205
+ { __baseUrl: 'https://shop.test', __locale: 'en' }
206
+ )
207
+ ).toEqual({
208
+ successUrl: 'https://shop.test/orders/ORD-42',
209
+ cancelUrl: 'https://pay.example/back',
210
+ })
211
+ expect(
212
+ await charge({ successUrl: '/orders/ORD-42', amount: 10 }, { __baseUrl: 'https://shop.test' })
213
+ ).toMatchObject({ successUrl: 'https://shop.test/orders/ORD-42' })
214
+ })
215
+ })
216
+
217
+ describe('every file that inlines handlers binds the shared runtime alias', () => {
218
+ const segment: WorkflowSegment = {
219
+ id: 'seg-1',
220
+ env: 'server',
221
+ nodeIds: ['n1'],
222
+ nodes: [
223
+ {
224
+ id: 'n1',
225
+ type: 'payment-charge-user',
226
+ config: { amount: 10 },
227
+ executionEnv: 'server',
228
+ stepNumber: 1,
229
+ label: 'Charge',
230
+ } as any,
231
+ ],
232
+ edges: [],
233
+ }
234
+ const workflow = {
235
+ id: 'wf-1',
236
+ name: 'WF',
237
+ nodes: [segment.nodes[0]],
238
+ edges: [],
239
+ }
240
+
241
+ it('api routes alias the runtime they already require', () => {
242
+ const routes = [
243
+ generateServerSegmentAPIRoute(segment, 'wf'),
244
+ generateStreamingServerSegmentAPIRoute(segment, 'wf'),
245
+ generateCronAPIRoute({
246
+ ...workflow,
247
+ trigger: {
248
+ type: 'event-cron-triggered',
249
+ nodeId: 't',
250
+ scope: 'global',
251
+ config: { schedule: '0 * * * *', urlPath: 'cron_x' },
252
+ },
253
+ } as any),
254
+ generateWebhookWorkflowAPIRoute({
255
+ ...workflow,
256
+ trigger: { type: 'event-webhook-triggered', nodeId: 't', scope: 'global', config: {} },
257
+ webhookConfig: { urlPath: 'hooks/x', httpMethod: 'POST', signatureSecret: '' },
258
+ } as any),
259
+ ]
260
+ for (const route of routes) {
261
+ const aliasAt = route.indexOf(workflowUtilsAliasLine('utils'))
262
+ expect(aliasAt).toBeGreaterThan(route.indexOf('server-runtime'))
263
+ expect(aliasAt).toBeLessThan(route.indexOf('const nodeHandlers = {'))
264
+ }
265
+ })
266
+
267
+ it('the global workflows hook imports the runtime as an ES module', () => {
268
+ const plugin = new NextWorkflowProjectPlugin()
269
+ const hook: string = (plugin as any).generateGlobalWorkflowsHook([
270
+ {
271
+ id: 'global-wf',
272
+ trigger: { type: 'event-custom-triggered', config: { eventName: 'x' } },
273
+ nodes: [
274
+ {
275
+ id: 'n1',
276
+ type: 'navigation-navigate-to-url',
277
+ config: { url: '/sign-in' },
278
+ stepNumber: 1,
279
+ },
280
+ ],
281
+ edges: [],
282
+ },
283
+ ])
284
+ expect(hook).toContain(workflowUtilsImportLine('./runtime-utils'))
285
+ // Pure ESM, like the runtime import beside it (see the hook's emitter).
286
+ expect(hook).not.toContain(`${WORKFLOW_UTILS_ALIAS} = require(`)
287
+ expect(hook.indexOf(workflowUtilsImportLine('./runtime-utils'))).toBeLessThan(
288
+ hook.indexOf('export function useGlobalWorkflows')
289
+ )
290
+ })
291
+
292
+ it('the client runtime localizes a terminal redirect it performs itself', () => {
293
+ expect(generateClientRuntimeCode()).toContain(
294
+ 'window.location.href = utils.localizeHref(__redirectUrl, context.__locale);'
295
+ )
296
+ })
297
+ })
298
+
299
+ describe('middleware redirects keep the request locale', () => {
300
+ const auth: any = {
301
+ enabled: true,
302
+ dataSourceId: 'ds-1',
303
+ dataSourceType: 'postgres',
304
+ passwordAuthEnabled: true,
305
+ providers: [],
306
+ roles: ['admin'],
307
+ tables: {},
308
+ folderProtection: {},
309
+ authPages: {
310
+ signIn: { pageId: 'sign-in', pageName: 'sign-in', route: '/sign-in' },
311
+ signUp: { pageId: 'sign-up', pageName: 'sign-up', route: '/sign-up' },
312
+ },
313
+ pageProtection: {
314
+ ORDERS: { requiresAuth: true, allowedRoles: [], pageName: 'Orders', route: '/orders' },
315
+ ADMIN: {
316
+ requiresAuth: true,
317
+ allowedRoles: ['admin'],
318
+ pageName: 'Admin',
319
+ route: '/admin',
320
+ },
321
+ },
322
+ callbackBaseUrl: '',
323
+ envKeys: {},
324
+ customUserProperties: [],
325
+ }
326
+ const run = compileGeneratedMiddleware(auth)
327
+
328
+ it('sends a guest to the sign-in page of the language they were browsing in', async () => {
329
+ const result = await run('/orders', { locale: 'es', defaultLocale: 'en' })
330
+ expect(result).toEqual({
331
+ kind: 'redirect',
332
+ location: 'https://shop.test/es/sign-in?callbackUrl=%2Fes%2Forders',
333
+ })
334
+ })
335
+
336
+ it('keeps the bare routes for the default language and for a project without languages', async () => {
337
+ expect(await run('/orders', { locale: 'en', defaultLocale: 'en' })).toEqual({
338
+ kind: 'redirect',
339
+ location: 'https://shop.test/sign-in?callbackUrl=%2Forders',
340
+ })
341
+ expect(await run('/orders')).toEqual({
342
+ kind: 'redirect',
343
+ location: 'https://shop.test/sign-in?callbackUrl=%2Forders',
344
+ })
345
+ })
346
+
347
+ it('sends a signed-in visitor without the role to the home page of their language', async () => {
348
+ const result = await run('/admin', {
349
+ locale: 'es',
350
+ defaultLocale: 'en',
351
+ sessionUser: { id: 'u-1', role: 'user' },
352
+ })
353
+ expect(result).toEqual({ kind: 'redirect', location: 'https://shop.test/es' })
354
+ })
355
+ })
@@ -1,5 +1,6 @@
1
1
  /* tslint:disable:no-eval */
2
2
  import { navigationGoToPage } from '../src/nodes/navigation/navigation-go-to-page'
3
+ import { bindWorkflowUtils } from './_helpers/bind-workflow-utils'
3
4
 
4
5
  function createHandler() {
5
6
  const fnStr = navigationGoToPage.generateHandler()
@@ -23,11 +24,17 @@ function withWindow(): FakeWindow {
23
24
  describe('navigation-go-to-page', () => {
24
25
  let handler: any
25
26
  let win: FakeWindow
27
+ let unbindWorkflowUtils: () => void
26
28
 
27
29
  beforeAll(() => {
30
+ unbindWorkflowUtils = bindWorkflowUtils()
28
31
  handler = createHandler()
29
32
  })
30
33
 
34
+ afterAll(() => {
35
+ unbindWorkflowUtils()
36
+ })
37
+
31
38
  beforeEach(() => {
32
39
  win = withWindow()
33
40
  })
@@ -36,6 +43,42 @@ describe('navigation-go-to-page', () => {
36
43
  delete (global as any).window
37
44
  })
38
45
 
46
+ describe('language of the run', () => {
47
+ // The visitor stays in the language they are browsing in: the run's locale
48
+ // (put on the context by the runtime) prefixes every site-relative target.
49
+ it('prefixes the target with a non-default locale', async () => {
50
+ await handler({ pageId: '/about', openInNewTab: false }, { __locale: 'es' })
51
+ expect(win.location.href).toBe('/es/about')
52
+ })
53
+
54
+ it('prefixes the home page, a details page and its query params alike', async () => {
55
+ await handler({ pageId: '/', openInNewTab: false }, { __locale: 'es' })
56
+ expect(win.location.href).toBe('/es')
57
+ await handler(
58
+ {
59
+ openInNewTab: false,
60
+ differentiator: 'ORD-42',
61
+ targetPage: { pageId: 'p', staticUrl: '/orders', isDetailsPage: true },
62
+ queryParams: [{ key: 'payment', value: 'success' }],
63
+ },
64
+ { __locale: 'es' }
65
+ )
66
+ expect(win.location.href).toBe('/es/orders/ORD-42?payment=success')
67
+ })
68
+
69
+ it('opens the localized target in a new tab when requested', async () => {
70
+ await handler({ pageId: '/about', openInNewTab: true }, { __locale: 'es' })
71
+ expect(win.open).toHaveBeenCalledWith('/es/about', '_blank')
72
+ })
73
+
74
+ it('keeps the bare route for the default language and for a run without a locale', async () => {
75
+ await handler({ pageId: '/about', openInNewTab: false }, { __locale: 'en' })
76
+ expect(win.location.href).toBe('/about')
77
+ await handler({ pageId: '/about', openInNewTab: false }, {})
78
+ expect(win.location.href).toBe('/about')
79
+ })
80
+ })
81
+
39
82
  describe('legacy UIDL (no targetPage)', () => {
40
83
  it('navigates to the rewritten pageId', async () => {
41
84
  const result = await handler({ pageId: '/about', openInNewTab: false }, {})