@hexclave/shared 1.0.2 → 1.0.5
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/dist/ai/unified-prompts/reminders.js +4 -3
- package/dist/ai/unified-prompts/reminders.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts +3 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +217 -30
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.d.ts.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.js +1 -0
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +18 -6
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +23 -8
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js.map +1 -1
- package/dist/config/schema.d.ts +63 -63
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +5 -5
- package/dist/config/schema.js.map +1 -1
- package/dist/config-authoring.d.ts +4 -4
- package/dist/config-authoring.d.ts.map +1 -1
- package/dist/config-authoring.js +2 -2
- package/dist/config-authoring.js.map +1 -1
- package/dist/config-rendering.d.ts +4 -4
- package/dist/config-rendering.d.ts.map +1 -1
- package/dist/config-rendering.js +19 -18
- package/dist/config-rendering.js.map +1 -1
- package/dist/esm/ai/unified-prompts/reminders.js +4 -3
- package/dist/esm/ai/unified-prompts/reminders.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts +3 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +216 -31
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.d.ts.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.js +1 -0
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +18 -6
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +23 -8
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js.map +1 -1
- package/dist/esm/config/schema.d.ts +63 -63
- package/dist/esm/config/schema.d.ts.map +1 -1
- package/dist/esm/config/schema.js +5 -5
- package/dist/esm/config/schema.js.map +1 -1
- package/dist/esm/config-authoring.d.ts +4 -4
- package/dist/esm/config-authoring.d.ts.map +1 -1
- package/dist/esm/config-authoring.js +2 -2
- package/dist/esm/config-authoring.js.map +1 -1
- package/dist/esm/config-rendering.d.ts +4 -4
- package/dist/esm/config-rendering.d.ts.map +1 -1
- package/dist/esm/config-rendering.js +15 -14
- package/dist/esm/config-rendering.js.map +1 -1
- package/dist/esm/helpers/vault/client-side.d.ts.map +1 -1
- package/dist/esm/helpers/vault/client-side.js.map +1 -1
- package/dist/esm/helpers/vault/server-side.d.ts.map +1 -1
- package/dist/esm/helpers/vault/server-side.js.map +1 -1
- package/dist/esm/hexclave-config-file.d.ts +15 -0
- package/dist/esm/hexclave-config-file.d.ts.map +1 -0
- package/dist/esm/{stack-config-file.js → hexclave-config-file.js} +7 -7
- package/dist/esm/hexclave-config-file.js.map +1 -0
- package/dist/esm/hooks/use-async-callback.js +1 -1
- package/dist/esm/hooks/use-async-external-store.js +1 -1
- package/dist/esm/hooks/use-strict-memo.js +1 -1
- package/dist/esm/interface/admin-metrics.d.ts +8 -8
- package/dist/esm/interface/admin-metrics.js.map +1 -1
- package/dist/esm/interface/client-interface.js +2 -2
- package/dist/esm/interface/client-interface.js.map +1 -1
- package/dist/esm/interface/conversations.d.ts +32 -32
- package/dist/esm/interface/crud/current-user.d.ts +9 -9
- package/dist/esm/interface/crud/email-outbox.d.ts +200 -200
- package/dist/esm/interface/crud/products.d.ts +13 -13
- package/dist/esm/interface/crud/products.d.ts.map +1 -1
- package/dist/esm/interface/crud/project-api-keys.d.ts +4 -4
- package/dist/esm/interface/crud/project-permissions.js +5 -5
- package/dist/esm/interface/crud/project-permissions.js.map +1 -1
- package/dist/esm/interface/crud/projects.d.ts +36 -36
- package/dist/esm/interface/crud/team-member-profiles.d.ts +18 -18
- package/dist/esm/interface/crud/team-permissions.js +5 -5
- package/dist/esm/interface/crud/team-permissions.js.map +1 -1
- package/dist/esm/interface/crud/transactions.d.ts +6 -6
- package/dist/esm/interface/crud/transactions.d.ts.map +1 -1
- package/dist/esm/interface/crud/users.d.ts +12 -12
- package/dist/esm/interface/page-component-versions.js +107 -107
- package/dist/esm/interface/page-component-versions.js.map +1 -1
- package/dist/esm/interface/webhooks.d.ts +2 -2
- package/dist/esm/known-errors.d.ts +2 -2
- package/dist/esm/known-errors.js +4 -4
- package/dist/esm/known-errors.js.map +1 -1
- package/dist/esm/schema-fields.d.ts +17 -7
- package/dist/esm/schema-fields.d.ts.map +1 -1
- package/dist/esm/schema-fields.js +26 -16
- package/dist/esm/schema-fields.js.map +1 -1
- package/dist/esm/sessions.d.ts +7 -7
- package/dist/esm/utils/crypto.d.ts.map +1 -1
- package/dist/esm/utils/crypto.js.map +1 -1
- package/dist/esm/utils/currency-constants.d.ts +6 -0
- package/dist/esm/utils/currency-constants.d.ts.map +1 -1
- package/dist/esm/utils/currency-constants.js.map +1 -1
- package/dist/esm/utils/dev-env-state-path.d.ts +3 -3
- package/dist/esm/utils/dev-env-state-path.d.ts.map +1 -1
- package/dist/esm/utils/dev-env-state-path.js +4 -4
- package/dist/esm/utils/dev-env-state-path.js.map +1 -1
- package/dist/esm/utils/errors.js +2 -2
- package/dist/esm/utils/errors.js.map +1 -1
- package/dist/esm/utils/esbuild.d.ts.map +1 -1
- package/dist/esm/utils/esbuild.js +0 -1
- package/dist/esm/utils/esbuild.js.map +1 -1
- package/dist/esm/utils/featurebase.d.ts +3 -3
- package/dist/esm/utils/featurebase.d.ts.map +1 -1
- package/dist/esm/utils/featurebase.js +15 -15
- package/dist/esm/utils/featurebase.js.map +1 -1
- package/dist/esm/utils/fs.js +6 -6
- package/dist/esm/utils/fs.js.map +1 -1
- package/dist/esm/utils/globals.d.ts.map +1 -1
- package/dist/esm/utils/globals.js +10 -10
- package/dist/esm/utils/globals.js.map +1 -1
- package/dist/esm/utils/jwt.d.ts.map +1 -1
- package/dist/esm/utils/jwt.js +3 -3
- package/dist/esm/utils/jwt.js.map +1 -1
- package/dist/esm/utils/redirect-urls.d.ts +3 -3
- package/dist/esm/utils/redirect-urls.d.ts.map +1 -1
- package/dist/esm/utils/redirect-urls.js +5 -5
- package/dist/esm/utils/redirect-urls.js.map +1 -1
- package/dist/esm/utils/urls.js.map +1 -1
- package/dist/helpers/vault/client-side.d.ts.map +1 -1
- package/dist/helpers/vault/client-side.js.map +1 -1
- package/dist/helpers/vault/server-side.d.ts.map +1 -1
- package/dist/helpers/vault/server-side.js.map +1 -1
- package/dist/hexclave-config-file.d.ts +15 -0
- package/dist/hexclave-config-file.d.ts.map +1 -0
- package/dist/{stack-config-file.js → hexclave-config-file.js} +8 -8
- package/dist/hexclave-config-file.js.map +1 -0
- package/dist/hooks/use-async-callback.js +1 -1
- package/dist/hooks/use-async-external-store.js +1 -1
- package/dist/hooks/use-strict-memo.js +1 -1
- package/dist/interface/admin-metrics.d.ts +8 -8
- package/dist/interface/admin-metrics.js.map +1 -1
- package/dist/interface/client-interface.js +2 -2
- package/dist/interface/client-interface.js.map +1 -1
- package/dist/interface/conversations.d.ts +32 -32
- package/dist/interface/crud/current-user.d.ts +9 -9
- package/dist/interface/crud/email-outbox.d.ts +200 -200
- package/dist/interface/crud/products.d.ts +13 -13
- package/dist/interface/crud/products.d.ts.map +1 -1
- package/dist/interface/crud/project-api-keys.d.ts +4 -4
- package/dist/interface/crud/project-permissions.js +5 -5
- package/dist/interface/crud/project-permissions.js.map +1 -1
- package/dist/interface/crud/projects.d.ts +36 -36
- package/dist/interface/crud/team-member-profiles.d.ts +18 -18
- package/dist/interface/crud/team-permissions.js +5 -5
- package/dist/interface/crud/team-permissions.js.map +1 -1
- package/dist/interface/crud/transactions.d.ts +6 -6
- package/dist/interface/crud/transactions.d.ts.map +1 -1
- package/dist/interface/crud/users.d.ts +12 -12
- package/dist/interface/page-component-versions.js +107 -107
- package/dist/interface/page-component-versions.js.map +1 -1
- package/dist/interface/webhooks.d.ts +2 -2
- package/dist/known-errors.d.ts +2 -2
- package/dist/known-errors.js +4 -4
- package/dist/known-errors.js.map +1 -1
- package/dist/schema-fields.d.ts +17 -7
- package/dist/schema-fields.d.ts.map +1 -1
- package/dist/schema-fields.js +26 -16
- package/dist/schema-fields.js.map +1 -1
- package/dist/sessions.d.ts +7 -7
- package/dist/utils/crypto.d.ts.map +1 -1
- package/dist/utils/crypto.js.map +1 -1
- package/dist/utils/currency-constants.d.ts +6 -0
- package/dist/utils/currency-constants.d.ts.map +1 -1
- package/dist/utils/currency-constants.js.map +1 -1
- package/dist/utils/dev-env-state-path.d.ts +3 -3
- package/dist/utils/dev-env-state-path.d.ts.map +1 -1
- package/dist/utils/dev-env-state-path.js +5 -5
- package/dist/utils/dev-env-state-path.js.map +1 -1
- package/dist/utils/errors.js +2 -2
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/esbuild.d.ts.map +1 -1
- package/dist/utils/esbuild.js +0 -1
- package/dist/utils/esbuild.js.map +1 -1
- package/dist/utils/featurebase.d.ts +3 -3
- package/dist/utils/featurebase.d.ts.map +1 -1
- package/dist/utils/featurebase.js +15 -15
- package/dist/utils/featurebase.js.map +1 -1
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/globals.d.ts.map +1 -1
- package/dist/utils/globals.js +10 -10
- package/dist/utils/globals.js.map +1 -1
- package/dist/utils/jwt.d.ts.map +1 -1
- package/dist/utils/jwt.js +3 -3
- package/dist/utils/jwt.js.map +1 -1
- package/dist/utils/redirect-urls.d.ts +3 -3
- package/dist/utils/redirect-urls.d.ts.map +1 -1
- package/dist/utils/redirect-urls.js +5 -5
- package/dist/utils/redirect-urls.js.map +1 -1
- package/dist/utils/urls.js.map +1 -1
- package/package.json +2 -1
- package/src/ai/image-limits.ts +62 -0
- package/src/ai/llms/llms.ts +36 -0
- package/src/ai/unified-prompts/reminders.ts +27 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts +1047 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/brain.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/cli-help.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/config-docs.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/custom-components-instructions.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/dashboard-instructions.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/docs-index.ts +80 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.ts +322 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/sdk-interface-source.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt.ts +72 -0
- package/src/apps/apps-config.ts +177 -0
- package/src/apps/apps-ui.tsx +112 -0
- package/src/config/README.md +154 -0
- package/src/config/db-sync-mappings.ts +2293 -0
- package/src/config/format.ts +388 -0
- package/src/config/migrate-catalogs-to-product-lines.ts +475 -0
- package/src/config/schema-fuzzer.test.ts +377 -0
- package/src/config/schema.ts +1507 -0
- package/src/config-authoring.test.ts +52 -0
- package/src/config-authoring.ts +37 -0
- package/src/config-rendering.ts +142 -0
- package/src/crud.tsx +275 -0
- package/src/global.d.ts +1 -0
- package/src/helpers/emails.ts +250 -0
- package/src/helpers/init-prompt.ts +39 -0
- package/src/helpers/password.ts +16 -0
- package/src/helpers/production-mode.ts +58 -0
- package/src/helpers/support-sla.test.ts +124 -0
- package/src/helpers/support-sla.ts +93 -0
- package/src/helpers/vault/client-side.ts +92 -0
- package/src/helpers/vault/server-side.ts +153 -0
- package/src/hexclave-config-file.ts +120 -0
- package/src/hooks/use-async-callback.tsx +43 -0
- package/src/hooks/use-async-external-store.tsx +24 -0
- package/src/hooks/use-hash.tsx +14 -0
- package/src/hooks/use-hover.tsx +88 -0
- package/src/hooks/use-strict-memo.tsx +157 -0
- package/src/index.ts +17 -0
- package/src/interface/admin-interface.ts +1043 -0
- package/src/interface/admin-metrics.ts +214 -0
- package/src/interface/client-interface.test.ts +765 -0
- package/src/interface/client-interface.ts +2586 -0
- package/src/interface/conversations.ts +109 -0
- package/src/interface/crud/analytics.ts +11 -0
- package/src/interface/crud/connected-accounts.ts +52 -0
- package/src/interface/crud/contact-channels.ts +74 -0
- package/src/interface/crud/current-user.ts +72 -0
- package/src/interface/crud/email-outbox.ts +263 -0
- package/src/interface/crud/email-templates.ts +50 -0
- package/src/interface/crud/emails.ts +22 -0
- package/src/interface/crud/internal-api-keys.ts +69 -0
- package/src/interface/crud/invoices.ts +36 -0
- package/src/interface/crud/items.ts +24 -0
- package/src/interface/crud/notification-preferences.ts +33 -0
- package/src/interface/crud/oauth-providers.ts +91 -0
- package/src/interface/crud/products.ts +42 -0
- package/src/interface/crud/project-api-keys.ts +100 -0
- package/src/interface/crud/project-permissions.ts +118 -0
- package/src/interface/crud/projects.ts +229 -0
- package/src/interface/crud/session-replays.ts +85 -0
- package/src/interface/crud/sessions.ts +66 -0
- package/src/interface/crud/sign-up-rules.ts +12 -0
- package/src/interface/crud/svix-token.ts +20 -0
- package/src/interface/crud/team-invitation-details.ts +22 -0
- package/src/interface/crud/team-invitation.ts +35 -0
- package/src/interface/crud/team-member-profiles.ts +61 -0
- package/src/interface/crud/team-memberships.ts +56 -0
- package/src/interface/crud/team-permissions.ts +119 -0
- package/src/interface/crud/teams.ts +142 -0
- package/src/interface/crud/transactions.ts +131 -0
- package/src/interface/crud/users.ts +185 -0
- package/src/interface/handler-urls.ts +74 -0
- package/src/interface/page-component-versions.ts +1702 -0
- package/src/interface/server-interface.ts +1146 -0
- package/src/interface/webhooks.ts +28 -0
- package/src/known-errors.tsx +2031 -0
- package/src/local-emulator.ts +5 -0
- package/src/payments/payment-methods.ts +167 -0
- package/src/payments/stripe-limits.ts +18 -0
- package/src/plans.ts +107 -0
- package/src/schema-fields.ts +944 -0
- package/src/sessions.ts +310 -0
- package/src/utils/api-keys.tsx +122 -0
- package/src/utils/arrays.tsx +248 -0
- package/src/utils/auth-methods.ts +8 -0
- package/src/utils/base64.tsx +24 -0
- package/src/utils/booleans.tsx +34 -0
- package/src/utils/browser-compat.tsx +17 -0
- package/src/utils/bytes.tsx +280 -0
- package/src/utils/caches.tsx +280 -0
- package/src/utils/cel-fields.ts +92 -0
- package/src/utils/cloud-hosts.tsx +92 -0
- package/src/utils/compile-time.tsx +11 -0
- package/src/utils/country-codes.ts +39 -0
- package/src/utils/crypto.tsx +174 -0
- package/src/utils/currencies.tsx +53 -0
- package/src/utils/currency-constants.tsx +51 -0
- package/src/utils/dates.tsx +246 -0
- package/src/utils/dev-env-state-path.ts +14 -0
- package/src/utils/dom.tsx +7 -0
- package/src/utils/env.tsx +117 -0
- package/src/utils/errors.tsx +284 -0
- package/src/utils/esbuild.tsx +235 -0
- package/src/utils/featurebase.tsx +247 -0
- package/src/utils/fs.tsx +33 -0
- package/src/utils/functions.tsx +22 -0
- package/src/utils/geo.tsx +16 -0
- package/src/utils/globals.tsx +48 -0
- package/src/utils/hashes.tsx +52 -0
- package/src/utils/html.tsx +60 -0
- package/src/utils/http.tsx +91 -0
- package/src/utils/ips.tsx +47 -0
- package/src/utils/json.tsx +200 -0
- package/src/utils/jsx-editable-transpiler.test.ts +156 -0
- package/src/utils/jsx-editable-transpiler.tsx +401 -0
- package/src/utils/jwt.tsx +170 -0
- package/src/utils/locks.tsx +71 -0
- package/src/utils/maps.tsx +407 -0
- package/src/utils/math.tsx +18 -0
- package/src/utils/monkey-patch.tsx +29 -0
- package/src/utils/node-http.tsx +47 -0
- package/src/utils/numbers.tsx +82 -0
- package/src/utils/oauth.tsx +9 -0
- package/src/utils/objects.tsx +581 -0
- package/src/utils/paginated-lists.test.ts +1039 -0
- package/src/utils/paginated-lists.tsx +479 -0
- package/src/utils/passkey.tsx +1 -0
- package/src/utils/promises.tsx +540 -0
- package/src/utils/proxies.tsx +212 -0
- package/src/utils/react.tsx +308 -0
- package/src/utils/redirect-urls.tsx +299 -0
- package/src/utils/regex.tsx +10 -0
- package/src/utils/results.tsx +411 -0
- package/src/utils/sentry.tsx +20 -0
- package/src/utils/stores.tsx +238 -0
- package/src/utils/strings.nicify.test.ts +253 -0
- package/src/utils/strings.tsx +723 -0
- package/src/utils/telemetry.tsx +37 -0
- package/src/utils/turnstile-browser.ts +106 -0
- package/src/utils/turnstile-flow.test.ts +101 -0
- package/src/utils/turnstile-flow.ts +266 -0
- package/src/utils/turnstile.ts +34 -0
- package/src/utils/typed-arrays.tsx +17 -0
- package/src/utils/types.tsx +208 -0
- package/src/utils/unicode.tsx +24 -0
- package/src/utils/urls.tsx +411 -0
- package/src/utils/uuids.tsx +47 -0
- package/dist/esm/stack-config-file.d.ts +0 -15
- package/dist/esm/stack-config-file.d.ts.map +0 -1
- package/dist/esm/stack-config-file.js.map +0 -1
- package/dist/stack-config-file.d.ts +0 -15
- package/dist/stack-config-file.d.ts.map +0 -1
- package/dist/stack-config-file.js.map +0 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { templateIdentity } from "./strings";
|
|
2
|
+
|
|
3
|
+
export function escapeHtml(unsafe: string): string {
|
|
4
|
+
return `${unsafe}`
|
|
5
|
+
.replace(/&/g, '&')
|
|
6
|
+
.replace(/</g, '<')
|
|
7
|
+
.replace(/>/g, '>')
|
|
8
|
+
.replace(/"/g, """)
|
|
9
|
+
.replace(/'/g, "'");
|
|
10
|
+
}
|
|
11
|
+
import.meta.vitest?.test("escapeHtml", ({ expect }) => {
|
|
12
|
+
// Test with empty string
|
|
13
|
+
expect(escapeHtml("")).toBe("");
|
|
14
|
+
|
|
15
|
+
// Test with string without special characters
|
|
16
|
+
expect(escapeHtml("hello world")).toBe("hello world");
|
|
17
|
+
|
|
18
|
+
// Test with special characters
|
|
19
|
+
expect(escapeHtml("<div>")).toBe("<div>");
|
|
20
|
+
expect(escapeHtml("a & b")).toBe("a & b");
|
|
21
|
+
expect(escapeHtml('a "quoted" string')).toBe("a "quoted" string");
|
|
22
|
+
expect(escapeHtml("it's a test")).toBe("it's a test");
|
|
23
|
+
|
|
24
|
+
// Test with multiple special characters
|
|
25
|
+
expect(escapeHtml("<a href=\"test\">It's a link</a>")).toBe(
|
|
26
|
+
"<a href="test">It's a link</a>"
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export function html(strings: TemplateStringsArray, ...values: any[]): string {
|
|
31
|
+
return templateIdentity(strings, ...values.map(v => escapeHtml(`${v}`)));
|
|
32
|
+
}
|
|
33
|
+
import.meta.vitest?.test("html", ({ expect }) => {
|
|
34
|
+
// Test with no interpolation
|
|
35
|
+
expect(html`simple string`).toBe("simple string");
|
|
36
|
+
|
|
37
|
+
// Test with string interpolation
|
|
38
|
+
expect(html`Hello, ${"world"}!`).toBe("Hello, world!");
|
|
39
|
+
|
|
40
|
+
// Test with number interpolation
|
|
41
|
+
expect(html`Count: ${42}`).toBe("Count: 42");
|
|
42
|
+
|
|
43
|
+
// Test with HTML special characters in interpolated values
|
|
44
|
+
expect(html`<div>${"<script>"}</div>`).toBe("<div><script></div>");
|
|
45
|
+
|
|
46
|
+
// Test with multiple interpolations
|
|
47
|
+
expect(html`${1} + ${2} = ${"<3"}`).toBe("1 + 2 = <3");
|
|
48
|
+
|
|
49
|
+
// Test with object interpolation
|
|
50
|
+
const obj = { toString: () => "<object>" };
|
|
51
|
+
expect(html`Object: ${obj}`).toBe("Object: <object>");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export function htmlToText(untrustedHtml: string): string {
|
|
55
|
+
|
|
56
|
+
const doc = new DOMParser().parseFromString(untrustedHtml, 'text/html');
|
|
57
|
+
|
|
58
|
+
return doc.body.textContent;
|
|
59
|
+
|
|
60
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { decodeBase64, encodeBase64, isBase64 } from "./bytes";
|
|
2
|
+
|
|
3
|
+
export const HTTP_METHODS = {
|
|
4
|
+
"GET": {
|
|
5
|
+
safe: true,
|
|
6
|
+
idempotent: true,
|
|
7
|
+
},
|
|
8
|
+
"POST": {
|
|
9
|
+
safe: false,
|
|
10
|
+
idempotent: false,
|
|
11
|
+
},
|
|
12
|
+
"PUT": {
|
|
13
|
+
safe: false,
|
|
14
|
+
idempotent: true,
|
|
15
|
+
},
|
|
16
|
+
"DELETE": {
|
|
17
|
+
safe: false,
|
|
18
|
+
idempotent: true,
|
|
19
|
+
},
|
|
20
|
+
"PATCH": {
|
|
21
|
+
safe: false,
|
|
22
|
+
idempotent: false,
|
|
23
|
+
},
|
|
24
|
+
"OPTIONS": {
|
|
25
|
+
safe: true,
|
|
26
|
+
idempotent: true,
|
|
27
|
+
},
|
|
28
|
+
"HEAD": {
|
|
29
|
+
safe: true,
|
|
30
|
+
idempotent: true,
|
|
31
|
+
},
|
|
32
|
+
"TRACE": {
|
|
33
|
+
safe: true,
|
|
34
|
+
idempotent: true,
|
|
35
|
+
},
|
|
36
|
+
"CONNECT": {
|
|
37
|
+
safe: false,
|
|
38
|
+
idempotent: false,
|
|
39
|
+
},
|
|
40
|
+
} as const;
|
|
41
|
+
export type HttpMethod = keyof typeof HTTP_METHODS;
|
|
42
|
+
|
|
43
|
+
export function decodeBasicAuthorizationHeader(value: string): [string, string] | null {
|
|
44
|
+
const [type, encoded, ...rest] = value.split(' ');
|
|
45
|
+
if (rest.length > 0) return null;
|
|
46
|
+
if (!encoded) return null;
|
|
47
|
+
if (type !== 'Basic') return null;
|
|
48
|
+
if (!isBase64(encoded)) return null;
|
|
49
|
+
const decoded = new TextDecoder().decode(decodeBase64(encoded));
|
|
50
|
+
const split = decoded.split(':');
|
|
51
|
+
return [split[0], split.slice(1).join(':')];
|
|
52
|
+
}
|
|
53
|
+
import.meta.vitest?.test("decodeBasicAuthorizationHeader", ({ expect }) => {
|
|
54
|
+
// Test with valid Basic Authorization header
|
|
55
|
+
const username = "user";
|
|
56
|
+
const password = "pass";
|
|
57
|
+
const encoded = encodeBasicAuthorizationHeader(username, password);
|
|
58
|
+
expect(decodeBasicAuthorizationHeader(encoded)).toEqual([username, password]);
|
|
59
|
+
|
|
60
|
+
// Test with password containing colons
|
|
61
|
+
const complexPassword = "pass:with:colons";
|
|
62
|
+
const encodedComplex = encodeBasicAuthorizationHeader(username, complexPassword);
|
|
63
|
+
expect(decodeBasicAuthorizationHeader(encodedComplex)).toEqual([username, complexPassword]);
|
|
64
|
+
|
|
65
|
+
// Test with invalid headers
|
|
66
|
+
expect(decodeBasicAuthorizationHeader("NotBasic dXNlcjpwYXNz")).toBe(null); // Wrong type
|
|
67
|
+
expect(decodeBasicAuthorizationHeader("Basic")).toBe(null); // Missing encoded part
|
|
68
|
+
expect(decodeBasicAuthorizationHeader("Basic not-base64")).toBe(null); // Not base64
|
|
69
|
+
expect(decodeBasicAuthorizationHeader("Basic dXNlcjpwYXNz extra")).toBe(null); // Extra parts
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export function encodeBasicAuthorizationHeader(id: string, password: string): string {
|
|
73
|
+
if (id.includes(':')) throw new Error("Basic authorization header id cannot contain ':'");
|
|
74
|
+
return `Basic ${encodeBase64(new TextEncoder().encode(`${id}:${password}`))}`;
|
|
75
|
+
}
|
|
76
|
+
import.meta.vitest?.test("encodeBasicAuthorizationHeader", ({ expect }) => {
|
|
77
|
+
// Test with simple username and password
|
|
78
|
+
const encoded = encodeBasicAuthorizationHeader("user", "pass");
|
|
79
|
+
expect(encoded).toMatch(/^Basic [A-Za-z0-9+/=]+$/); // Should start with "Basic " followed by base64
|
|
80
|
+
|
|
81
|
+
// Test with empty password
|
|
82
|
+
const encodedEmptyPass = encodeBasicAuthorizationHeader("user", "");
|
|
83
|
+
expect(encodedEmptyPass).toMatch(/^Basic [A-Za-z0-9+/=]+$/);
|
|
84
|
+
|
|
85
|
+
// Test with password containing special characters
|
|
86
|
+
const encodedSpecialChars = encodeBasicAuthorizationHeader("user", "p@ss!w0rd");
|
|
87
|
+
expect(encodedSpecialChars).toMatch(/^Basic [A-Za-z0-9+/=]+$/);
|
|
88
|
+
|
|
89
|
+
// Test with username containing colon should throw
|
|
90
|
+
expect(() => encodeBasicAuthorizationHeader("user:name", "pass")).toThrow();
|
|
91
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import ipRegex from "ip-regex";
|
|
2
|
+
|
|
3
|
+
export type Ipv4Address = `${number}.${number}.${number}.${number}`;
|
|
4
|
+
export type Ipv6Address = string;
|
|
5
|
+
|
|
6
|
+
export function isIpAddress(ip: string) {
|
|
7
|
+
return ipRegex({ exact: true }).test(ip);
|
|
8
|
+
}
|
|
9
|
+
import.meta.vitest?.test("isIpAddress", ({ expect }) => {
|
|
10
|
+
// Test valid IPv4 addresses
|
|
11
|
+
expect(isIpAddress("192.168.1.1")).toBe(true);
|
|
12
|
+
expect(isIpAddress("127.0.0.1")).toBe(true);
|
|
13
|
+
expect(isIpAddress("0.0.0.0")).toBe(true);
|
|
14
|
+
expect(isIpAddress("255.255.255.255")).toBe(true);
|
|
15
|
+
|
|
16
|
+
// Test valid IPv6 addresses
|
|
17
|
+
expect(isIpAddress("::1")).toBe(true);
|
|
18
|
+
expect(isIpAddress("2001:db8::")).toBe(true);
|
|
19
|
+
expect(isIpAddress("2001:db8:85a3:8d3:1319:8a2e:370:7348")).toBe(true);
|
|
20
|
+
|
|
21
|
+
// Test invalid IP addresses
|
|
22
|
+
expect(isIpAddress("")).toBe(false);
|
|
23
|
+
expect(isIpAddress("not an ip")).toBe(false);
|
|
24
|
+
expect(isIpAddress("256.256.256.256")).toBe(false);
|
|
25
|
+
expect(isIpAddress("192.168.1")).toBe(false);
|
|
26
|
+
expect(isIpAddress("192.168.1.1.1")).toBe(false);
|
|
27
|
+
expect(isIpAddress("2001:db8::xyz")).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export function assertIpAddress(ip: string): asserts ip is Ipv4Address | Ipv6Address {
|
|
31
|
+
if (!isIpAddress(ip)) {
|
|
32
|
+
throw new Error(`Invalid IP address: ${ip}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
import.meta.vitest?.test("assertIpAddress", ({ expect }) => {
|
|
36
|
+
// Test with valid IPv4 address
|
|
37
|
+
expect(() => assertIpAddress("192.168.1.1")).not.toThrow();
|
|
38
|
+
|
|
39
|
+
// Test with valid IPv6 address
|
|
40
|
+
expect(() => assertIpAddress("::1")).not.toThrow();
|
|
41
|
+
|
|
42
|
+
// Test with invalid IP addresses
|
|
43
|
+
expect(() => assertIpAddress("")).toThrow("Invalid IP address: ");
|
|
44
|
+
expect(() => assertIpAddress("not an ip")).toThrow("Invalid IP address: not an ip");
|
|
45
|
+
expect(() => assertIpAddress("256.256.256.256")).toThrow("Invalid IP address: 256.256.256.256");
|
|
46
|
+
expect(() => assertIpAddress("192.168.1")).toThrow("Invalid IP address: 192.168.1");
|
|
47
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { Result } from "./results";
|
|
2
|
+
|
|
3
|
+
export type Json =
|
|
4
|
+
| null
|
|
5
|
+
| boolean
|
|
6
|
+
| number
|
|
7
|
+
| string
|
|
8
|
+
| Json[]
|
|
9
|
+
| { [key: string]: Json };
|
|
10
|
+
|
|
11
|
+
export type ReadonlyJson =
|
|
12
|
+
| null
|
|
13
|
+
| boolean
|
|
14
|
+
| number
|
|
15
|
+
| string
|
|
16
|
+
| readonly ReadonlyJson[]
|
|
17
|
+
| { readonly [key: string]: ReadonlyJson };
|
|
18
|
+
|
|
19
|
+
export function isJsonSerializable(value: unknown): value is Json {
|
|
20
|
+
switch (typeof value) {
|
|
21
|
+
case "object": {
|
|
22
|
+
if (value === null) return true;
|
|
23
|
+
if (Array.isArray(value)) return value.every(isJsonSerializable);
|
|
24
|
+
return Object.keys(value).every(k => typeof k === "string") && Object.values(value).every(isJsonSerializable);
|
|
25
|
+
}
|
|
26
|
+
case "string":
|
|
27
|
+
case "number":
|
|
28
|
+
case "boolean": {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
default: {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
import.meta.vitest?.test("isJsonSerializable", ({ expect }) => {
|
|
37
|
+
// Test primitive values
|
|
38
|
+
expect(isJsonSerializable(null)).toBe(true);
|
|
39
|
+
expect(isJsonSerializable(true)).toBe(true);
|
|
40
|
+
expect(isJsonSerializable(false)).toBe(true);
|
|
41
|
+
expect(isJsonSerializable(123)).toBe(true);
|
|
42
|
+
expect(isJsonSerializable("string")).toBe(true);
|
|
43
|
+
|
|
44
|
+
// Test arrays
|
|
45
|
+
expect(isJsonSerializable([])).toBe(true);
|
|
46
|
+
expect(isJsonSerializable([1, 2, 3])).toBe(true);
|
|
47
|
+
expect(isJsonSerializable(["a", "b", "c"])).toBe(true);
|
|
48
|
+
expect(isJsonSerializable([1, "a", true, null])).toBe(true);
|
|
49
|
+
expect(isJsonSerializable([1, [2, 3], { a: "b" }])).toBe(true);
|
|
50
|
+
|
|
51
|
+
// Test objects
|
|
52
|
+
expect(isJsonSerializable({})).toBe(true);
|
|
53
|
+
expect(isJsonSerializable({ a: 1, b: 2 })).toBe(true);
|
|
54
|
+
expect(isJsonSerializable({ a: "string", b: true, c: null })).toBe(true);
|
|
55
|
+
expect(isJsonSerializable({ a: [1, 2, 3], b: { c: "d" } })).toBe(true);
|
|
56
|
+
|
|
57
|
+
// Test invalid JSON values
|
|
58
|
+
expect(isJsonSerializable(undefined)).toBe(false);
|
|
59
|
+
expect(isJsonSerializable(() => {})).toBe(false);
|
|
60
|
+
expect(isJsonSerializable(Symbol())).toBe(false);
|
|
61
|
+
expect(isJsonSerializable(BigInt(123))).toBe(false);
|
|
62
|
+
|
|
63
|
+
// Test arrays with invalid JSON values
|
|
64
|
+
expect(isJsonSerializable([1, undefined, 3])).toBe(false);
|
|
65
|
+
expect(isJsonSerializable([1, () => {}, 3])).toBe(false);
|
|
66
|
+
|
|
67
|
+
// Test objects with invalid JSON values
|
|
68
|
+
expect(isJsonSerializable({ a: 1, b: undefined })).toBe(false);
|
|
69
|
+
expect(isJsonSerializable({ a: 1, b: () => {} })).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export function parseJson(json: string): Result<Json> {
|
|
73
|
+
return Result.fromThrowing(() => JSON.parse(json));
|
|
74
|
+
}
|
|
75
|
+
import.meta.vitest?.test("parseJson", ({ expect }) => {
|
|
76
|
+
// Test valid JSON strings
|
|
77
|
+
const nullResult = parseJson("null");
|
|
78
|
+
expect(nullResult.status).toBe("ok");
|
|
79
|
+
if (nullResult.status === "ok") {
|
|
80
|
+
expect(nullResult.data).toBe(null);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const trueResult = parseJson("true");
|
|
84
|
+
expect(trueResult.status).toBe("ok");
|
|
85
|
+
if (trueResult.status === "ok") {
|
|
86
|
+
expect(trueResult.data).toBe(true);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const numberResult = parseJson("123");
|
|
90
|
+
expect(numberResult.status).toBe("ok");
|
|
91
|
+
if (numberResult.status === "ok") {
|
|
92
|
+
expect(numberResult.data).toBe(123);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const stringResult = parseJson('"string"');
|
|
96
|
+
expect(stringResult.status).toBe("ok");
|
|
97
|
+
if (stringResult.status === "ok") {
|
|
98
|
+
expect(stringResult.data).toBe("string");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const emptyArrayResult = parseJson("[]");
|
|
102
|
+
expect(emptyArrayResult.status).toBe("ok");
|
|
103
|
+
if (emptyArrayResult.status === "ok") {
|
|
104
|
+
expect(emptyArrayResult.data).toEqual([]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const arrayResult = parseJson("[1,2,3]");
|
|
108
|
+
expect(arrayResult.status).toBe("ok");
|
|
109
|
+
if (arrayResult.status === "ok") {
|
|
110
|
+
expect(arrayResult.data).toEqual([1, 2, 3]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const emptyObjectResult = parseJson("{}");
|
|
114
|
+
expect(emptyObjectResult.status).toBe("ok");
|
|
115
|
+
if (emptyObjectResult.status === "ok") {
|
|
116
|
+
expect(emptyObjectResult.data).toEqual({});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const objectResult = parseJson('{"a":1,"b":"string"}');
|
|
120
|
+
expect(objectResult.status).toBe("ok");
|
|
121
|
+
if (objectResult.status === "ok") {
|
|
122
|
+
expect(objectResult.data).toEqual({ a: 1, b: "string" });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Test invalid JSON strings
|
|
126
|
+
expect(parseJson("").status).toBe("error");
|
|
127
|
+
expect(parseJson("undefined").status).toBe("error");
|
|
128
|
+
expect(parseJson("{").status).toBe("error");
|
|
129
|
+
expect(parseJson('{"a":1,}').status).toBe("error");
|
|
130
|
+
expect(parseJson("function(){}").status).toBe("error");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export function stringifyJson(json: Json): Result<string> {
|
|
134
|
+
return Result.fromThrowing(() => JSON.stringify(json));
|
|
135
|
+
}
|
|
136
|
+
import.meta.vitest?.test("stringifyJson", ({ expect }) => {
|
|
137
|
+
// Test primitive values
|
|
138
|
+
const nullResult = stringifyJson(null);
|
|
139
|
+
expect(nullResult.status).toBe("ok");
|
|
140
|
+
if (nullResult.status === "ok") {
|
|
141
|
+
expect(nullResult.data).toBe("null");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const trueResult = stringifyJson(true);
|
|
145
|
+
expect(trueResult.status).toBe("ok");
|
|
146
|
+
if (trueResult.status === "ok") {
|
|
147
|
+
expect(trueResult.data).toBe("true");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const numberResult = stringifyJson(123);
|
|
151
|
+
expect(numberResult.status).toBe("ok");
|
|
152
|
+
if (numberResult.status === "ok") {
|
|
153
|
+
expect(numberResult.data).toBe("123");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const stringResult = stringifyJson("string");
|
|
157
|
+
expect(stringResult.status).toBe("ok");
|
|
158
|
+
if (stringResult.status === "ok") {
|
|
159
|
+
expect(stringResult.data).toBe('"string"');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Test arrays
|
|
163
|
+
const emptyArrayResult = stringifyJson([]);
|
|
164
|
+
expect(emptyArrayResult.status).toBe("ok");
|
|
165
|
+
if (emptyArrayResult.status === "ok") {
|
|
166
|
+
expect(emptyArrayResult.data).toBe("[]");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const arrayResult = stringifyJson([1, 2, 3]);
|
|
170
|
+
expect(arrayResult.status).toBe("ok");
|
|
171
|
+
if (arrayResult.status === "ok") {
|
|
172
|
+
expect(arrayResult.data).toBe("[1,2,3]");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Test objects
|
|
176
|
+
const emptyObjectResult = stringifyJson({});
|
|
177
|
+
expect(emptyObjectResult.status).toBe("ok");
|
|
178
|
+
if (emptyObjectResult.status === "ok") {
|
|
179
|
+
expect(emptyObjectResult.data).toBe("{}");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const objectResult = stringifyJson({ a: 1, b: "string" });
|
|
183
|
+
expect(objectResult.status).toBe("ok");
|
|
184
|
+
if (objectResult.status === "ok") {
|
|
185
|
+
expect(objectResult.data).toBe('{"a":1,"b":"string"}');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Test nested structures
|
|
189
|
+
const nested = { a: [1, 2, 3], b: { c: "d" } };
|
|
190
|
+
const nestedResult = stringifyJson(nested);
|
|
191
|
+
expect(nestedResult.status).toBe("ok");
|
|
192
|
+
if (nestedResult.status === "ok") {
|
|
193
|
+
expect(nestedResult.data).toBe('{"a":[1,2,3],"b":{"c":"d"}}');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Test circular references (should error)
|
|
197
|
+
const circular: any = { a: 1 };
|
|
198
|
+
circular.self = circular;
|
|
199
|
+
expect(stringifyJson(circular).status).toBe("error");
|
|
200
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { transpileJsxForEditing, convertSentinelTokensToComments } from './jsx-editable-transpiler';
|
|
3
|
+
|
|
4
|
+
describe('transpileJsxForEditing', () => {
|
|
5
|
+
it('should wrap simple JSX text nodes with __Editable', () => {
|
|
6
|
+
const source = `
|
|
7
|
+
export function EmailTemplate() {
|
|
8
|
+
return <div>Hello World!</div>;
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
11
|
+
const result = transpileJsxForEditing(source, { sourceFile: 'template' });
|
|
12
|
+
|
|
13
|
+
expect(result.code).toContain('__Editable');
|
|
14
|
+
expect(result.code).toContain('Hello World!');
|
|
15
|
+
expect(Object.keys(result.editableRegions).length).toBeGreaterThan(0);
|
|
16
|
+
|
|
17
|
+
// Check that the editable region has the expected metadata
|
|
18
|
+
const region = Object.values(result.editableRegions)[0];
|
|
19
|
+
expect(region.originalText).toBe('Hello World!');
|
|
20
|
+
expect(region.sourceFile).toBe('template');
|
|
21
|
+
expect(region.parentElement.tagName).toBe('div');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should handle multiple text nodes in the same element', () => {
|
|
25
|
+
const source = `
|
|
26
|
+
export function EmailTemplate({ name }) {
|
|
27
|
+
return <div>Hello, {name}! Welcome to our platform.</div>;
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
const result = transpileJsxForEditing(source, { sourceFile: 'template' });
|
|
31
|
+
|
|
32
|
+
// Should have 2 editable regions (text before and after the expression)
|
|
33
|
+
const regions = Object.values(result.editableRegions);
|
|
34
|
+
expect(regions.length).toBe(2);
|
|
35
|
+
|
|
36
|
+
const texts = regions.map(r => r.originalText);
|
|
37
|
+
expect(texts).toContain('Hello, ');
|
|
38
|
+
expect(texts).toContain('! Welcome to our platform.');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should skip whitespace-only text nodes', () => {
|
|
42
|
+
const source = `
|
|
43
|
+
export function EmailTemplate() {
|
|
44
|
+
return (
|
|
45
|
+
<div>
|
|
46
|
+
<span>Text</span>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
const result = transpileJsxForEditing(source, { sourceFile: 'template' });
|
|
52
|
+
|
|
53
|
+
// Should only have 1 editable region (the "Text" in span)
|
|
54
|
+
const regions = Object.values(result.editableRegions);
|
|
55
|
+
expect(regions.length).toBe(1);
|
|
56
|
+
expect(regions[0].originalText).toBe('Text');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should track occurrence count for duplicate text', () => {
|
|
60
|
+
const source = `
|
|
61
|
+
export function EmailTemplate() {
|
|
62
|
+
return (
|
|
63
|
+
<div>
|
|
64
|
+
<p>Click here</p>
|
|
65
|
+
<p>Click here</p>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
const result = transpileJsxForEditing(source, { sourceFile: 'template' });
|
|
71
|
+
|
|
72
|
+
const regions = Object.values(result.editableRegions);
|
|
73
|
+
expect(regions.length).toBe(2);
|
|
74
|
+
|
|
75
|
+
// Both should have occurrenceCount of 2
|
|
76
|
+
expect(regions[0].occurrenceCount).toBe(2);
|
|
77
|
+
expect(regions[1].occurrenceCount).toBe(2);
|
|
78
|
+
|
|
79
|
+
// They should have different occurrence indices
|
|
80
|
+
expect(regions[0].occurrenceIndex).toBe(1);
|
|
81
|
+
expect(regions[1].occurrenceIndex).toBe(2);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should capture JSX path correctly', () => {
|
|
85
|
+
const source = `
|
|
86
|
+
export function EmailTemplate() {
|
|
87
|
+
return (
|
|
88
|
+
<Container>
|
|
89
|
+
<Section>
|
|
90
|
+
<Text>Hello</Text>
|
|
91
|
+
</Section>
|
|
92
|
+
</Container>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
const result = transpileJsxForEditing(source, { sourceFile: 'template' });
|
|
97
|
+
|
|
98
|
+
const region = Object.values(result.editableRegions)[0];
|
|
99
|
+
expect(region.jsxPath).toContain('EmailTemplate');
|
|
100
|
+
expect(region.jsxPath).toContain('Container');
|
|
101
|
+
expect(region.jsxPath).toContain('Section');
|
|
102
|
+
expect(region.jsxPath).toContain('Text');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should capture parent element props', () => {
|
|
106
|
+
const source = `
|
|
107
|
+
export function EmailTemplate() {
|
|
108
|
+
return <Text className="heading" style={{ color: 'red' }}>Title</Text>;
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
const result = transpileJsxForEditing(source, { sourceFile: 'template' });
|
|
112
|
+
|
|
113
|
+
const region = Object.values(result.editableRegions)[0];
|
|
114
|
+
expect(region.parentElement.tagName).toBe('Text');
|
|
115
|
+
expect(region.parentElement.props.className).toBe('heading');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should inject __Editable component definition', () => {
|
|
119
|
+
const source = `
|
|
120
|
+
export function EmailTemplate() {
|
|
121
|
+
return <div>Test</div>;
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
const result = transpileJsxForEditing(source, { sourceFile: 'template' });
|
|
125
|
+
|
|
126
|
+
expect(result.code).toContain('function __Editable');
|
|
127
|
+
expect(result.code).toContain('STACK_EDITABLE_START');
|
|
128
|
+
expect(result.code).toContain('STACK_EDITABLE_END');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('convertSentinelTokensToComments', () => {
|
|
133
|
+
it('should convert start tokens to HTML comments', () => {
|
|
134
|
+
const html = '<div>⟦STACK_EDITABLE_START:e0⟧Hello⟦STACK_EDITABLE_END:e0⟧</div>';
|
|
135
|
+
const result = convertSentinelTokensToComments(html);
|
|
136
|
+
|
|
137
|
+
expect(result).toBe('<div><!-- STACK_EDITABLE_START e0 -->Hello<!-- STACK_EDITABLE_END e0 --></div>');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should handle multiple editable regions', () => {
|
|
141
|
+
const html = '<div>⟦STACK_EDITABLE_START:e0⟧Hello⟦STACK_EDITABLE_END:e0⟧ ⟦STACK_EDITABLE_START:e1⟧World⟦STACK_EDITABLE_END:e1⟧</div>';
|
|
142
|
+
const result = convertSentinelTokensToComments(html);
|
|
143
|
+
|
|
144
|
+
expect(result).toContain('<!-- STACK_EDITABLE_START e0 -->');
|
|
145
|
+
expect(result).toContain('<!-- STACK_EDITABLE_END e0 -->');
|
|
146
|
+
expect(result).toContain('<!-- STACK_EDITABLE_START e1 -->');
|
|
147
|
+
expect(result).toContain('<!-- STACK_EDITABLE_END e1 -->');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should leave regular HTML unchanged', () => {
|
|
151
|
+
const html = '<div><p>Hello World</p></div>';
|
|
152
|
+
const result = convertSentinelTokensToComments(html);
|
|
153
|
+
|
|
154
|
+
expect(result).toBe(html);
|
|
155
|
+
});
|
|
156
|
+
});
|