@teleporthq/teleport-plugin-next-workflows 0.43.66 → 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.
- package/__tests__/_helpers/bind-workflow-utils.ts +30 -0
- package/__tests__/_helpers/run-generated-middleware.ts +11 -2
- package/__tests__/admin-form-row-hydration.test.ts +55 -0
- package/__tests__/client-config-redaction.test.ts +53 -0
- package/__tests__/email-locale-module.test.ts +323 -0
- package/__tests__/email-locale-senders.test.ts +268 -0
- package/__tests__/locale-aware-redirects.test.ts +355 -0
- package/__tests__/navigation-go-to-page.test.ts +43 -0
- package/__tests__/node-handler-file-inline-map.test.ts +29 -3
- package/__tests__/resolve-config-localized-templates.test.ts +262 -0
- package/__tests__/sent-email-log-wiring.test.ts +2 -2
- package/dist/cjs/account-delete-route-generator.d.ts +20 -0
- package/dist/cjs/account-delete-route-generator.d.ts.map +1 -1
- package/dist/cjs/account-delete-route-generator.js +16 -22
- package/dist/cjs/account-delete-route-generator.js.map +1 -1
- package/dist/cjs/api-route-generator.d.ts.map +1 -1
- package/dist/cjs/api-route-generator.js +13 -2
- package/dist/cjs/api-route-generator.js.map +1 -1
- package/dist/cjs/auth-generator.d.ts +2 -1
- package/dist/cjs/auth-generator.d.ts.map +1 -1
- package/dist/cjs/auth-generator.js +42 -14
- package/dist/cjs/auth-generator.js.map +1 -1
- package/dist/cjs/data-api-route-generator.d.ts.map +1 -1
- package/dist/cjs/data-api-route-generator.js +19 -0
- package/dist/cjs/data-api-route-generator.js.map +1 -1
- package/dist/cjs/email-locale/email-locale-code.d.ts +25 -0
- package/dist/cjs/email-locale/email-locale-code.d.ts.map +1 -0
- package/dist/cjs/email-locale/email-locale-code.js +228 -0
- package/dist/cjs/email-locale/email-locale-code.js.map +1 -0
- package/dist/cjs/email-locale/email-locale-scope.d.ts +46 -0
- package/dist/cjs/email-locale/email-locale-scope.d.ts.map +1 -0
- package/dist/cjs/email-locale/email-locale-scope.js +51 -0
- package/dist/cjs/email-locale/email-locale-scope.js.map +1 -0
- package/dist/cjs/email-locale/index.d.ts +11 -0
- package/dist/cjs/email-locale/index.d.ts.map +1 -0
- package/dist/cjs/email-locale/index.js +40 -0
- package/dist/cjs/email-locale/index.js.map +1 -0
- package/dist/cjs/executor-generator.d.ts +11 -1
- package/dist/cjs/executor-generator.d.ts.map +1 -1
- package/dist/cjs/executor-generator.js +32 -3
- package/dist/cjs/executor-generator.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +7 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/invoice/api-routes-code.d.ts.map +1 -1
- package/dist/cjs/invoice/api-routes-code.js +14 -3
- package/dist/cjs/invoice/api-routes-code.js.map +1 -1
- package/dist/cjs/invoice/email-sender-code.d.ts.map +1 -1
- package/dist/cjs/invoice/email-sender-code.js +14 -2
- package/dist/cjs/invoice/email-sender-code.js.map +1 -1
- package/dist/cjs/invoice/index.d.ts.map +1 -1
- package/dist/cjs/invoice/index.js +4 -1
- package/dist/cjs/invoice/index.js.map +1 -1
- package/dist/cjs/nodes/account/account-delete-current.d.ts.map +1 -1
- package/dist/cjs/nodes/account/account-delete-current.js +10 -3
- package/dist/cjs/nodes/account/account-delete-current.js.map +1 -1
- package/dist/cjs/nodes/account/account-login.d.ts.map +1 -1
- package/dist/cjs/nodes/account/account-login.js +3 -1
- package/dist/cjs/nodes/account/account-login.js.map +1 -1
- package/dist/cjs/nodes/account/account-logout.d.ts.map +1 -1
- package/dist/cjs/nodes/account/account-logout.js +4 -2
- package/dist/cjs/nodes/account/account-logout.js.map +1 -1
- package/dist/cjs/nodes/account/account-signup.d.ts.map +1 -1
- package/dist/cjs/nodes/account/account-signup.js +12 -2
- package/dist/cjs/nodes/account/account-signup.js.map +1 -1
- package/dist/cjs/nodes/account/account-social-login.d.ts.map +1 -1
- package/dist/cjs/nodes/account/account-social-login.js +4 -2
- package/dist/cjs/nodes/account/account-social-login.js.map +1 -1
- package/dist/cjs/nodes/data/data-create-item.d.ts.map +1 -1
- package/dist/cjs/nodes/data/data-create-item.js +8 -0
- package/dist/cjs/nodes/data/data-create-item.js.map +1 -1
- package/dist/cjs/nodes/ecommerce/ecommerce-generate-invoice.d.ts.map +1 -1
- package/dist/cjs/nodes/ecommerce/ecommerce-generate-invoice.js +6 -0
- package/dist/cjs/nodes/ecommerce/ecommerce-generate-invoice.js.map +1 -1
- package/dist/cjs/nodes/navigation/navigation-go-to-page.d.ts.map +1 -1
- package/dist/cjs/nodes/navigation/navigation-go-to-page.js +4 -1
- package/dist/cjs/nodes/navigation/navigation-go-to-page.js.map +1 -1
- package/dist/cjs/nodes/navigation/navigation-navigate-to-url.d.ts.map +1 -1
- package/dist/cjs/nodes/navigation/navigation-navigate-to-url.js +5 -2
- package/dist/cjs/nodes/navigation/navigation-navigate-to-url.js.map +1 -1
- package/dist/cjs/nodes/payment/payment-charge-user.d.ts.map +1 -1
- package/dist/cjs/nodes/payment/payment-charge-user.js +7 -2
- package/dist/cjs/nodes/payment/payment-charge-user.js.map +1 -1
- package/dist/cjs/segment-splitter.d.ts +1 -1
- package/dist/cjs/segment-splitter.d.ts.map +1 -1
- package/dist/cjs/segment-splitter.js +29 -2
- package/dist/cjs/segment-splitter.js.map +1 -1
- package/dist/cjs/transactional-email-code.d.ts +27 -0
- package/dist/cjs/transactional-email-code.d.ts.map +1 -1
- package/dist/cjs/transactional-email-code.js +43 -7
- package/dist/cjs/transactional-email-code.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/workflow-component-plugin.js +4 -3
- package/dist/cjs/workflow-component-plugin.js.map +1 -1
- package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
- package/dist/cjs/workflow-project-plugin.js +17 -3
- package/dist/cjs/workflow-project-plugin.js.map +1 -1
- package/dist/cjs/workflow-utils-alias.d.ts +21 -0
- package/dist/cjs/workflow-utils-alias.d.ts.map +1 -0
- package/dist/cjs/workflow-utils-alias.js +27 -0
- package/dist/cjs/workflow-utils-alias.js.map +1 -0
- package/dist/esm/account-delete-route-generator.d.ts +20 -0
- package/dist/esm/account-delete-route-generator.d.ts.map +1 -1
- package/dist/esm/account-delete-route-generator.js +17 -23
- package/dist/esm/account-delete-route-generator.js.map +1 -1
- package/dist/esm/api-route-generator.d.ts.map +1 -1
- package/dist/esm/api-route-generator.js +13 -2
- package/dist/esm/api-route-generator.js.map +1 -1
- package/dist/esm/auth-generator.d.ts +2 -1
- package/dist/esm/auth-generator.d.ts.map +1 -1
- package/dist/esm/auth-generator.js +43 -15
- package/dist/esm/auth-generator.js.map +1 -1
- package/dist/esm/data-api-route-generator.d.ts.map +1 -1
- package/dist/esm/data-api-route-generator.js +19 -0
- package/dist/esm/data-api-route-generator.js.map +1 -1
- package/dist/esm/email-locale/email-locale-code.d.ts +25 -0
- package/dist/esm/email-locale/email-locale-code.d.ts.map +1 -0
- package/dist/esm/email-locale/email-locale-code.js +222 -0
- package/dist/esm/email-locale/email-locale-code.js.map +1 -0
- package/dist/esm/email-locale/email-locale-scope.d.ts +46 -0
- package/dist/esm/email-locale/email-locale-scope.d.ts.map +1 -0
- package/dist/esm/email-locale/email-locale-scope.js +47 -0
- package/dist/esm/email-locale/email-locale-scope.js.map +1 -0
- package/dist/esm/email-locale/index.d.ts +11 -0
- package/dist/esm/email-locale/index.d.ts.map +1 -0
- package/dist/esm/email-locale/index.js +28 -0
- package/dist/esm/email-locale/index.js.map +1 -0
- package/dist/esm/executor-generator.d.ts +11 -1
- package/dist/esm/executor-generator.d.ts.map +1 -1
- package/dist/esm/executor-generator.js +32 -3
- package/dist/esm/executor-generator.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/invoice/api-routes-code.d.ts.map +1 -1
- package/dist/esm/invoice/api-routes-code.js +14 -3
- package/dist/esm/invoice/api-routes-code.js.map +1 -1
- package/dist/esm/invoice/email-sender-code.d.ts.map +1 -1
- package/dist/esm/invoice/email-sender-code.js +14 -2
- package/dist/esm/invoice/email-sender-code.js.map +1 -1
- package/dist/esm/invoice/index.d.ts.map +1 -1
- package/dist/esm/invoice/index.js +4 -1
- package/dist/esm/invoice/index.js.map +1 -1
- package/dist/esm/nodes/account/account-delete-current.d.ts.map +1 -1
- package/dist/esm/nodes/account/account-delete-current.js +10 -3
- package/dist/esm/nodes/account/account-delete-current.js.map +1 -1
- package/dist/esm/nodes/account/account-login.d.ts.map +1 -1
- package/dist/esm/nodes/account/account-login.js +3 -1
- package/dist/esm/nodes/account/account-login.js.map +1 -1
- package/dist/esm/nodes/account/account-logout.d.ts.map +1 -1
- package/dist/esm/nodes/account/account-logout.js +4 -2
- package/dist/esm/nodes/account/account-logout.js.map +1 -1
- package/dist/esm/nodes/account/account-signup.d.ts.map +1 -1
- package/dist/esm/nodes/account/account-signup.js +12 -2
- package/dist/esm/nodes/account/account-signup.js.map +1 -1
- package/dist/esm/nodes/account/account-social-login.d.ts.map +1 -1
- package/dist/esm/nodes/account/account-social-login.js +4 -2
- package/dist/esm/nodes/account/account-social-login.js.map +1 -1
- package/dist/esm/nodes/data/data-create-item.d.ts.map +1 -1
- package/dist/esm/nodes/data/data-create-item.js +8 -0
- package/dist/esm/nodes/data/data-create-item.js.map +1 -1
- package/dist/esm/nodes/ecommerce/ecommerce-generate-invoice.d.ts.map +1 -1
- package/dist/esm/nodes/ecommerce/ecommerce-generate-invoice.js +6 -0
- package/dist/esm/nodes/ecommerce/ecommerce-generate-invoice.js.map +1 -1
- package/dist/esm/nodes/navigation/navigation-go-to-page.d.ts.map +1 -1
- package/dist/esm/nodes/navigation/navigation-go-to-page.js +4 -1
- package/dist/esm/nodes/navigation/navigation-go-to-page.js.map +1 -1
- package/dist/esm/nodes/navigation/navigation-navigate-to-url.d.ts.map +1 -1
- package/dist/esm/nodes/navigation/navigation-navigate-to-url.js +5 -2
- package/dist/esm/nodes/navigation/navigation-navigate-to-url.js.map +1 -1
- package/dist/esm/nodes/payment/payment-charge-user.d.ts.map +1 -1
- package/dist/esm/nodes/payment/payment-charge-user.js +7 -2
- package/dist/esm/nodes/payment/payment-charge-user.js.map +1 -1
- package/dist/esm/segment-splitter.d.ts +1 -1
- package/dist/esm/segment-splitter.d.ts.map +1 -1
- package/dist/esm/segment-splitter.js +29 -2
- package/dist/esm/segment-splitter.js.map +1 -1
- package/dist/esm/transactional-email-code.d.ts +27 -0
- package/dist/esm/transactional-email-code.d.ts.map +1 -1
- package/dist/esm/transactional-email-code.js +39 -6
- package/dist/esm/transactional-email-code.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/workflow-component-plugin.js +4 -3
- package/dist/esm/workflow-component-plugin.js.map +1 -1
- package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
- package/dist/esm/workflow-project-plugin.js +18 -4
- package/dist/esm/workflow-project-plugin.js.map +1 -1
- package/dist/esm/workflow-utils-alias.d.ts +21 -0
- package/dist/esm/workflow-utils-alias.d.ts.map +1 -0
- package/dist/esm/workflow-utils-alias.js +21 -0
- package/dist/esm/workflow-utils-alias.js.map +1 -0
- package/package.json +4 -4
- package/src/account-delete-route-generator.ts +22 -4
- package/src/api-route-generator.ts +13 -2
- package/src/auth-generator.ts +48 -14
- package/src/data-api-route-generator.ts +19 -0
- package/src/email-locale/email-locale-code.ts +239 -0
- package/src/email-locale/email-locale-scope.ts +65 -0
- package/src/email-locale/index.ts +46 -0
- package/src/executor-generator.ts +48 -3
- package/src/index.ts +7 -0
- package/src/invoice/api-routes-code.ts +18 -3
- package/src/invoice/email-sender-code.ts +14 -2
- package/src/invoice/index.ts +4 -1
- package/src/nodes/account/account-delete-current.ts +10 -3
- package/src/nodes/account/account-login.ts +3 -1
- package/src/nodes/account/account-logout.ts +4 -2
- package/src/nodes/account/account-signup.ts +12 -2
- package/src/nodes/account/account-social-login.ts +7 -2
- package/src/nodes/data/data-create-item.ts +8 -0
- package/src/nodes/ecommerce/ecommerce-generate-invoice.ts +6 -0
- package/src/nodes/navigation/navigation-go-to-page.ts +5 -1
- package/src/nodes/navigation/navigation-navigate-to-url.ts +6 -2
- package/src/nodes/payment/payment-charge-user.ts +13 -2
- package/src/nodes/workflow-utils-global.d.ts +12 -0
- package/src/segment-splitter.ts +33 -2
- package/src/transactional-email-code.ts +51 -0
- package/src/workflow-component-plugin.ts +4 -3
- package/src/workflow-project-plugin.ts +21 -4
- package/src/workflow-utils-alias.ts +26 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import type { UIDLInvoiceEmailDelivery, UIDLInvoiceSettings } from '@teleporthq/teleport-types'
|
|
2
|
+
import { generateSignupRouteFile } from '../src/auth-generator'
|
|
3
|
+
import { generateAccountDeleteRoute } from '../src/account-delete-route-generator'
|
|
4
|
+
import { generateEmailSenderCode } from '../src/invoice/email-sender-code'
|
|
5
|
+
import { generateInvoiceGenerateRouteCode } from '../src/invoice/api-routes-code'
|
|
6
|
+
import { generateDataAPIRoute } from '../src/data-api-route-generator'
|
|
7
|
+
import { generateEmailLocaleModuleCode } from '../src/email-locale'
|
|
8
|
+
import { readLocalizedEmailTemplates } from '../src/transactional-email-code'
|
|
9
|
+
import { nodeRegistry } from '../src'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The email senders that live OUTSIDE the workflow runtime — the welcome and
|
|
13
|
+
* farewell routes, the invoice sender, the data-api that stamps an order's
|
|
14
|
+
* language — each read the shared locale module. These tests pin that every
|
|
15
|
+
* one of them selects the copy of the language it resolved, and that the
|
|
16
|
+
* order row keeps the storefront language it was written from.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const LOCALIZED = {
|
|
20
|
+
es: { subject: 'Bienvenido {{userName}}', body: '<p>Hola {{userName}}</p>' },
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const extractFunctionSource = (haystack: string, funcDecl: string): string => {
|
|
24
|
+
const startIdx = haystack.indexOf(funcDecl)
|
|
25
|
+
if (startIdx === -1) {
|
|
26
|
+
throw new Error('Helper not found: ' + funcDecl)
|
|
27
|
+
}
|
|
28
|
+
let depth = 0
|
|
29
|
+
let i = haystack.indexOf('{', startIdx)
|
|
30
|
+
for (; i < haystack.length; i++) {
|
|
31
|
+
const ch = haystack.charAt(i)
|
|
32
|
+
if (ch === '{') {
|
|
33
|
+
depth++
|
|
34
|
+
} else if (ch === '}') {
|
|
35
|
+
depth--
|
|
36
|
+
if (depth === 0) {
|
|
37
|
+
return haystack.slice(startIdx, i + 1)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
throw new Error('Unbalanced braces for ' + funcDecl)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const loadLocaleModule = () => {
|
|
45
|
+
const moduleShim = { exports: {} as Record<string, unknown> }
|
|
46
|
+
// eslint-disable-next-line no-new-func
|
|
47
|
+
new Function(
|
|
48
|
+
'module',
|
|
49
|
+
'exports',
|
|
50
|
+
generateEmailLocaleModuleCode({ locales: ['en', 'es'], defaultLocale: 'en' })
|
|
51
|
+
)(moduleShim, moduleShim.exports)
|
|
52
|
+
return moduleShim.exports
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('readLocalizedEmailTemplates', () => {
|
|
56
|
+
it('keeps only well-formed copies', () => {
|
|
57
|
+
expect(
|
|
58
|
+
readLocalizedEmailTemplates({
|
|
59
|
+
es: { subject: 'S', body: 'B' },
|
|
60
|
+
fr: { body: 'B' },
|
|
61
|
+
de: { subject: 'only a subject' },
|
|
62
|
+
it: { subject: '', body: '' },
|
|
63
|
+
pt: 'B',
|
|
64
|
+
})
|
|
65
|
+
).toEqual({ es: { subject: 'S', body: 'B' }, fr: { body: 'B' } })
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('answers no copies for anything that is not a map', () => {
|
|
69
|
+
expect(readLocalizedEmailTemplates(undefined)).toEqual({})
|
|
70
|
+
expect(readLocalizedEmailTemplates([{ body: 'B' }])).toEqual({})
|
|
71
|
+
expect(readLocalizedEmailTemplates('es')).toEqual({})
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('welcome and farewell routes', () => {
|
|
76
|
+
const auth = { enabled: true, dataSourceType: 'postgresql' } as any
|
|
77
|
+
const signup = generateSignupRouteFile(auth, {
|
|
78
|
+
emailProvider: 'resend',
|
|
79
|
+
emailSubject: 'Welcome {{userName}}',
|
|
80
|
+
emailBodyHtml: '<p>Hi {{userName}}</p>',
|
|
81
|
+
localizedTemplates: LOCALIZED,
|
|
82
|
+
})
|
|
83
|
+
const del = generateAccountDeleteRoute({
|
|
84
|
+
emailProvider: 'resend',
|
|
85
|
+
emailSubject: 'Goodbye {{userName}}',
|
|
86
|
+
emailBodyHtml: '<p>Bye {{userName}}</p>',
|
|
87
|
+
localizedTemplates: LOCALIZED,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('bake the per-language copies and require the locale module', () => {
|
|
91
|
+
expect(signup).toContain(`const WELCOME_EMAIL_LOCALIZED = ${JSON.stringify(LOCALIZED)};`)
|
|
92
|
+
expect(signup).toContain("var __emailLocale = require('../../../utils/email/email-locale');")
|
|
93
|
+
expect(del).toContain(`const EMAIL_LOCALIZED = ${JSON.stringify(LOCALIZED)};`)
|
|
94
|
+
expect(del).toContain("var __emailLocale = require('../../../utils/email/email-locale');")
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('send the copy of the request language', () => {
|
|
98
|
+
expect(signup).toContain('var copy = resolveWelcomeEmailCopy(locale);')
|
|
99
|
+
expect(signup).toContain('var subject = fillTemplate(copy.subject, tokenValues);')
|
|
100
|
+
expect(signup).toContain('var html = fillTemplate(copy.body, tokenValues);')
|
|
101
|
+
expect(del).toContain('var copy = resolveFarewellEmailCopy(locale);')
|
|
102
|
+
expect(del).toContain('var subject = fillTemplate(copy.subject, tokenValues);')
|
|
103
|
+
expect(del).toContain('var html = fillTemplate(copy.body, tokenValues);')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('resolve the language off the request at the call site', () => {
|
|
107
|
+
expect(signup).toContain('newUser && newUser.id, __emailLocale.resolveRequestLocale(req));')
|
|
108
|
+
expect(del).toContain('}, userId, __emailLocale.resolveRequestLocale(req));')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('the copy selector honours the locale module rules', () => {
|
|
112
|
+
const locale = loadLocaleModule()
|
|
113
|
+
const selector = extractFunctionSource(signup, 'function resolveWelcomeEmailCopy(')
|
|
114
|
+
// eslint-disable-next-line no-new-func
|
|
115
|
+
const resolveWelcomeEmailCopy = new Function(
|
|
116
|
+
'__emailLocale',
|
|
117
|
+
'WELCOME_EMAIL_SUBJECT',
|
|
118
|
+
'WELCOME_EMAIL_BODY_HTML',
|
|
119
|
+
'WELCOME_EMAIL_LOCALIZED',
|
|
120
|
+
`${selector}; return resolveWelcomeEmailCopy;`
|
|
121
|
+
)(locale, 'Welcome {{userName}}', '<p>Hi {{userName}}</p>', LOCALIZED)
|
|
122
|
+
expect(resolveWelcomeEmailCopy('es')).toEqual({
|
|
123
|
+
subject: 'Bienvenido {{userName}}',
|
|
124
|
+
body: '<p>Hola {{userName}}</p>',
|
|
125
|
+
locale: 'es',
|
|
126
|
+
})
|
|
127
|
+
expect(resolveWelcomeEmailCopy('en')).toEqual({
|
|
128
|
+
subject: 'Welcome {{userName}}',
|
|
129
|
+
body: '<p>Hi {{userName}}</p>',
|
|
130
|
+
locale: 'en',
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('the signup route never inserts the copies as a user column', () => {
|
|
135
|
+
expect(signup).toContain('"localizedTemplates":1')
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
describe('invoice email', () => {
|
|
140
|
+
const delivery: UIDLInvoiceEmailDelivery = {
|
|
141
|
+
enabled: true,
|
|
142
|
+
provider: 'resend',
|
|
143
|
+
fromEmail: 'billing@example.com',
|
|
144
|
+
fromName: 'Billing',
|
|
145
|
+
subject: 'Invoice {{invoiceNumber}}',
|
|
146
|
+
body: '<p>Invoice {{invoiceNumber}}</p>',
|
|
147
|
+
secretKeys: {},
|
|
148
|
+
localizedTemplates: {
|
|
149
|
+
es: { subject: 'Factura {{invoiceNumber}}', body: '<p>Factura {{invoiceNumber}}</p>' },
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
it('the sender picks the copy of the invoice locale', () => {
|
|
154
|
+
const sender = generateEmailSenderCode(delivery)
|
|
155
|
+
expect(sender).toContain("var emailLocale = require('../email/email-locale');")
|
|
156
|
+
expect(sender).toContain(
|
|
157
|
+
`var LOCALIZED_TEMPLATES = ${JSON.stringify(delivery.localizedTemplates)};`
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const locale = loadLocaleModule()
|
|
161
|
+
const buildEmailData = extractFunctionSource(sender, 'function buildEmailData(')
|
|
162
|
+
const pdfGenerator = {
|
|
163
|
+
buildDataContext: (data: { invoiceNumber: string }) => ({
|
|
164
|
+
invoiceNumber: data.invoiceNumber,
|
|
165
|
+
}),
|
|
166
|
+
}
|
|
167
|
+
const replacePlaceholders = (template: string, data: Record<string, string>) =>
|
|
168
|
+
template.replace(/\{\{(\w+)\}\}/g, (_m, key) => data[key] || '')
|
|
169
|
+
// eslint-disable-next-line no-new-func
|
|
170
|
+
const build = new Function(
|
|
171
|
+
'emailLocale',
|
|
172
|
+
'pdfGenerator',
|
|
173
|
+
'replacePlaceholders',
|
|
174
|
+
'SUBJECT_TEMPLATE',
|
|
175
|
+
'BODY_TEMPLATE',
|
|
176
|
+
'LOCALIZED_TEMPLATES',
|
|
177
|
+
`${buildEmailData}; return buildEmailData;`
|
|
178
|
+
)(
|
|
179
|
+
locale,
|
|
180
|
+
pdfGenerator,
|
|
181
|
+
replacePlaceholders,
|
|
182
|
+
delivery.subject,
|
|
183
|
+
delivery.body,
|
|
184
|
+
delivery.localizedTemplates
|
|
185
|
+
)
|
|
186
|
+
expect(build({ invoiceNumber: 'INV-7', locale: 'es', customerEmail: 'a@b.c' })).toMatchObject({
|
|
187
|
+
subject: 'Factura INV-7',
|
|
188
|
+
body: '<p>Factura INV-7</p>',
|
|
189
|
+
})
|
|
190
|
+
expect(build({ invoiceNumber: 'INV-7', customerEmail: 'a@b.c' })).toMatchObject({
|
|
191
|
+
subject: 'Invoice INV-7',
|
|
192
|
+
body: '<p>Invoice INV-7</p>',
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('the generate route resolves the locale: order row, then caller, then request', () => {
|
|
197
|
+
const settings = {
|
|
198
|
+
invoicePrefix: 'INV-',
|
|
199
|
+
defaultTaxRate: 0,
|
|
200
|
+
showDiscount: false,
|
|
201
|
+
template: { document: null as unknown },
|
|
202
|
+
tables: { invoicesTable: 'teleport_invoices', invoiceItemsTable: 'teleport_invoice_items' },
|
|
203
|
+
emailDelivery: delivery,
|
|
204
|
+
} as unknown as UIDLInvoiceSettings
|
|
205
|
+
const route = generateInvoiceGenerateRouteCode(settings)
|
|
206
|
+
expect(route).toContain("var emailLocale = require('../../../utils/email/email-locale');")
|
|
207
|
+
expect(route).toContain('var invoiceLocale = emailLocale.normalizeEmailLocale(orderRow.locale)')
|
|
208
|
+
expect(route).toContain('|| emailLocale.normalizeEmailLocale(body.locale)')
|
|
209
|
+
expect(route).toContain('|| emailLocale.resolveRequestLocale(req);')
|
|
210
|
+
expect(route).toContain('locale: invoiceLocale,')
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('a project without email delivery needs no locale module', () => {
|
|
214
|
+
const route = generateInvoiceGenerateRouteCode({
|
|
215
|
+
invoicePrefix: 'INV-',
|
|
216
|
+
emailDelivery: { enabled: false },
|
|
217
|
+
} as unknown as UIDLInvoiceSettings)
|
|
218
|
+
expect(route).not.toContain('email-locale')
|
|
219
|
+
expect(route).not.toContain('invoiceLocale')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('the generate-invoice node forwards the run locale as a hint', () => {
|
|
223
|
+
const source = nodeRegistry['ecommerce-generate-invoice'].generateHandler()
|
|
224
|
+
expect(source).toContain('payload.locale = runLocale')
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
describe('data-api stamps the storefront language on an order', () => {
|
|
229
|
+
const code = generateDataAPIRoute()
|
|
230
|
+
const stamp = extractFunctionSource(code, 'function stampRequestLocale(')
|
|
231
|
+
// eslint-disable-next-line no-new-func
|
|
232
|
+
const stampRequestLocale = new Function(
|
|
233
|
+
'LOCALE_STAMPED_TABLES',
|
|
234
|
+
'LOCALE_COLUMN',
|
|
235
|
+
`${stamp}; return stampRequestLocale;`
|
|
236
|
+
)({ teleport_orders: 1 }, 'locale')
|
|
237
|
+
|
|
238
|
+
it('adds the locale to an orders insert when the table has the column', () => {
|
|
239
|
+
const entries = [['status', 'pending']]
|
|
240
|
+
expect(
|
|
241
|
+
stampRequestLocale('teleport_orders', entries, { locale: 'character varying' }, 'es')
|
|
242
|
+
).toEqual([
|
|
243
|
+
['status', 'pending'],
|
|
244
|
+
['locale', 'es'],
|
|
245
|
+
])
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('leaves the insert alone when the column, the locale or the table does not apply', () => {
|
|
249
|
+
const entries = [['status', 'pending']]
|
|
250
|
+
expect(stampRequestLocale('teleport_orders', entries, {}, 'es')).toBe(entries)
|
|
251
|
+
expect(stampRequestLocale('teleport_orders', entries, { locale: 'text' }, '')).toBe(entries)
|
|
252
|
+
expect(stampRequestLocale('teleport_products', entries, { locale: 'text' }, 'es')).toBe(entries)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('never overrides a locale the workflow mapped itself', () => {
|
|
256
|
+
const entries = [['locale', 'fr']]
|
|
257
|
+
expect(stampRequestLocale('teleport_orders', entries, { locale: 'text' }, 'es')).toBe(entries)
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('is wired into handleCreate from the request body', () => {
|
|
261
|
+
expect(code).toContain(
|
|
262
|
+
'entries = stampRequestLocale(tableName, entries, colTypes, body.__requestLocale);'
|
|
263
|
+
)
|
|
264
|
+
expect(nodeRegistry['data-create-item'].generateHandler()).toContain(
|
|
265
|
+
'reqBody.__requestLocale = __runLocale'
|
|
266
|
+
)
|
|
267
|
+
})
|
|
268
|
+
})
|
|
@@ -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 }, {})
|