@antelopejs/dms-mailing 0.9.1 → 0.10.0

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 (83) hide show
  1. package/README.md +11 -11
  2. package/dist/data/read-only-routes.d.ts +14 -1
  3. package/dist/data/sends-table.d.ts +14 -1
  4. package/dist/data/suppressions-table.d.ts +14 -1
  5. package/dist/data/templates-table.d.ts +15 -2
  6. package/dist/db/models/send-events.model.d.ts +3 -3
  7. package/dist/db/models/sends.model.d.ts +6 -4
  8. package/dist/db/models/sends.model.js +26 -2
  9. package/dist/db/models/sends.model.js.map +1 -1
  10. package/dist/db/models/settings.model.d.ts +3 -3
  11. package/dist/db/models/suppressions.model.d.ts +3 -3
  12. package/dist/db/models/template-versions.model.d.ts +3 -3
  13. package/dist/db/models/template-versions.model.js +2 -3
  14. package/dist/db/models/template-versions.model.js.map +1 -1
  15. package/dist/db/models/templates.model.d.ts +4 -3
  16. package/dist/db/models/templates.model.js +14 -6
  17. package/dist/db/models/templates.model.js.map +1 -1
  18. package/dist/db/models/webhook-status.model.d.ts +3 -3
  19. package/dist/db/tables/templates.table.d.ts +4 -1
  20. package/dist/hooks/tenant-export.js +5 -1
  21. package/dist/hooks/tenant-export.js.map +1 -1
  22. package/dist/pages/editor.js +1 -1
  23. package/dist/pages/editor.js.map +1 -1
  24. package/dist/routes/metrics.js +1 -2
  25. package/dist/routes/metrics.js.map +1 -1
  26. package/dist/routes/sends.js +2 -1
  27. package/dist/routes/sends.js.map +1 -1
  28. package/dist/routes/templates.d.ts +2 -2
  29. package/dist/routes/templates.js +4 -4
  30. package/dist/routes/templates.js.map +1 -1
  31. package/dist/services/attention.d.ts +1 -1
  32. package/dist/services/attention.js +3 -3
  33. package/dist/services/attention.js.map +1 -1
  34. package/dist/services/metrics.js +5 -1
  35. package/dist/services/metrics.js.map +1 -1
  36. package/dist/services/settings.js +2 -1
  37. package/dist/services/settings.js.map +1 -1
  38. package/dist/services/template-attention.d.ts +1 -1
  39. package/dist/services/template-attention.js +2 -4
  40. package/dist/services/template-attention.js.map +1 -1
  41. package/dist/services/templates.js +10 -8
  42. package/dist/services/templates.js.map +1 -1
  43. package/dist/validation/declarations.schema.d.ts +1 -1
  44. package/dist/validation/stored.schema.d.ts +12 -0
  45. package/dist/validation/stored.schema.js +198 -0
  46. package/dist/validation/stored.schema.js.map +1 -0
  47. package/frontend-vue/app/composables/useEditorContext.ts +1 -1
  48. package/frontend-vue/build/backend-boundary.ts +7 -2
  49. package/frontend-vue/dms.frontend.ts +1 -4
  50. package/frontend-vue/eslint.config.mjs +9 -1
  51. package/frontend-vue/package.json +1 -1
  52. package/frontend-vue/pnpm-lock.yaml +24 -7
  53. package/frontend-vue/tests/backend-boundary.test.ts +4 -4
  54. package/frontend-vue/tests/column-displays.test.ts +13 -12
  55. package/frontend-vue/tests/footer-block.test.ts +1 -1
  56. package/frontend-vue/tests/template-gallery-display.test.ts +6 -5
  57. package/frontend-vue/tests/template-overlays.test.ts +4 -12
  58. package/frontend-vue/tsconfig.typecheck.json +1 -2
  59. package/package.json +16 -16
  60. package/src/db/models/sends.model.ts +34 -4
  61. package/src/db/models/template-versions.model.ts +8 -6
  62. package/src/db/models/templates.model.ts +25 -9
  63. package/src/hooks/tenant-export.ts +5 -2
  64. package/src/pages/editor.ts +1 -1
  65. package/src/routes/metrics.ts +1 -2
  66. package/src/routes/sends.ts +2 -3
  67. package/src/routes/templates.ts +9 -9
  68. package/src/services/attention.ts +6 -6
  69. package/src/services/metrics.ts +3 -1
  70. package/src/services/settings.ts +9 -3
  71. package/src/services/template-attention.ts +4 -5
  72. package/src/services/templates.ts +35 -16
  73. package/src/validation/stored.schema.ts +257 -0
  74. /package/frontend-vue/app/components/{Editor.vue → TemplateEditor.vue} +0 -0
  75. /package/frontend-vue/app/components/blocks/{Button.vue → BlockButton.vue} +0 -0
  76. /package/frontend-vue/app/components/blocks/{Code.vue → BlockCode.vue} +0 -0
  77. /package/frontend-vue/app/components/blocks/{Divider.vue → BlockDivider.vue} +0 -0
  78. /package/frontend-vue/app/components/blocks/{Footer.vue → BlockFooter.vue} +0 -0
  79. /package/frontend-vue/app/components/blocks/{Heading.vue → BlockHeading.vue} +0 -0
  80. /package/frontend-vue/app/components/blocks/{Hero.vue → BlockHero.vue} +0 -0
  81. /package/frontend-vue/app/components/blocks/{List.vue → BlockList.vue} +0 -0
  82. /package/frontend-vue/app/components/blocks/{Paragraph.vue → BlockParagraph.vue} +0 -0
  83. /package/frontend-vue/app/components/blocks/{Total.vue → BlockTotal.vue} +0 -0
@@ -111,8 +111,8 @@ export declare const senderDeclarationSchema: z.ZodObject<{
111
111
  name: z.ZodDefault<z.ZodEffects<z.ZodString, string, string>>;
112
112
  replyTo: z.ZodDefault<z.ZodPipeline<z.ZodEffects<z.ZodString, string, string>, z.ZodUnion<[z.ZodLiteral<"">, z.ZodString]>>>;
113
113
  }, "strip", z.ZodTypeAny, {
114
- email: string;
115
114
  name: string;
115
+ email: string;
116
116
  tenantId: string;
117
117
  replyTo: string;
118
118
  }, {
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ import type { MailingTemplateVersion } from "../db/tables";
3
+ import type { TemplateChange } from "../engine/changes";
4
+ import type { TemplateCategory, TemplateContent, VariableDefinition } from "../types";
5
+ type StoredSchema<T> = z.ZodType<T, z.ZodTypeDef, unknown>;
6
+ export declare function parseStored<T>(text: string, schema: StoredSchema<T>, what: string): T;
7
+ export declare const storedContentSchema: StoredSchema<TemplateContent>;
8
+ export declare const storedVariablesSchema: StoredSchema<VariableDefinition[]>;
9
+ export declare const storedChangesSchema: StoredSchema<TemplateChange[]>;
10
+ export declare const storedVersionRowSchema: StoredSchema<MailingTemplateVersion>;
11
+ export declare const storedCategoriesSchema: StoredSchema<TemplateCategory[]>;
12
+ export {};
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.storedCategoriesSchema = exports.storedVersionRowSchema = exports.storedChangesSchema = exports.storedVariablesSchema = exports.storedContentSchema = void 0;
4
+ exports.parseStored = parseStored;
5
+ const zod_1 = require("zod");
6
+ function parseStored(text, schema, what) {
7
+ const value = JSON.parse(text);
8
+ const read = schema.safeParse(value);
9
+ if (!read.success)
10
+ throw new TypeError(`expected ${what}`);
11
+ return read.data;
12
+ }
13
+ function oneOf(members) {
14
+ return zod_1.z
15
+ .string()
16
+ .refine((value) => Object.hasOwn(members, value));
17
+ }
18
+ const conditionOperators = {
19
+ truthy: true,
20
+ falsy: true,
21
+ eq: true,
22
+ ne: true,
23
+ gt: true,
24
+ lt: true,
25
+ };
26
+ const textAligns = {
27
+ left: true,
28
+ center: true,
29
+ right: true,
30
+ };
31
+ const variableTypes = {
32
+ string: true,
33
+ text: true,
34
+ number: true,
35
+ money: true,
36
+ date: true,
37
+ url: true,
38
+ boolean: true,
39
+ array: true,
40
+ };
41
+ const blockTypes = {
42
+ hero: true,
43
+ heading: true,
44
+ paragraph: true,
45
+ code: true,
46
+ list: true,
47
+ total: true,
48
+ button: true,
49
+ if: true,
50
+ divider: true,
51
+ footer: true,
52
+ };
53
+ const changeKinds = {
54
+ locale_added: true,
55
+ locale_removed: true,
56
+ subject: true,
57
+ preheader: true,
58
+ block_added: true,
59
+ block_removed: true,
60
+ block_changed: true,
61
+ blocks_reordered: true,
62
+ variable_added: true,
63
+ variable_removed: true,
64
+ variable_changed: true,
65
+ };
66
+ const storedConditionSchema = zod_1.z
67
+ .object({
68
+ path: zod_1.z.string(),
69
+ operator: oneOf(conditionOperators),
70
+ value: zod_1.z.string(),
71
+ })
72
+ .passthrough();
73
+ const align = oneOf(textAligns);
74
+ const base = { id: zod_1.z.string(), visibleIf: storedConditionSchema.nullable() };
75
+ const storedBlockSchema = zod_1.z.lazy(() => storedBlockUnion);
76
+ const storedBlockUnion = zod_1.z.discriminatedUnion("type", [
77
+ zod_1.z
78
+ .object({
79
+ ...base,
80
+ type: zod_1.z.literal("hero"),
81
+ imageUrl: zod_1.z.string(),
82
+ alt: zod_1.z.string(),
83
+ })
84
+ .passthrough(),
85
+ zod_1.z
86
+ .object({
87
+ ...base,
88
+ type: zod_1.z.literal("heading"),
89
+ text: zod_1.z.string(),
90
+ align,
91
+ size: zod_1.z.number(),
92
+ })
93
+ .passthrough(),
94
+ zod_1.z
95
+ .object({
96
+ ...base,
97
+ type: zod_1.z.literal("paragraph"),
98
+ text: zod_1.z.string(),
99
+ align,
100
+ })
101
+ .passthrough(),
102
+ zod_1.z
103
+ .object({ ...base, type: zod_1.z.literal("code"), text: zod_1.z.string() })
104
+ .passthrough(),
105
+ zod_1.z
106
+ .object({
107
+ ...base,
108
+ type: zod_1.z.literal("list"),
109
+ source: zod_1.z.string(),
110
+ labelPath: zod_1.z.string(),
111
+ valuePath: zod_1.z.string(),
112
+ })
113
+ .passthrough(),
114
+ zod_1.z
115
+ .object({
116
+ ...base,
117
+ type: zod_1.z.literal("total"),
118
+ label: zod_1.z.string(),
119
+ value: zod_1.z.string(),
120
+ })
121
+ .passthrough(),
122
+ zod_1.z
123
+ .object({
124
+ ...base,
125
+ type: zod_1.z.literal("button"),
126
+ text: zod_1.z.string(),
127
+ href: zod_1.z.string(),
128
+ align,
129
+ })
130
+ .passthrough(),
131
+ zod_1.z
132
+ .object({
133
+ ...base,
134
+ type: zod_1.z.literal("if"),
135
+ condition: storedConditionSchema,
136
+ children: zod_1.z.array(storedBlockSchema),
137
+ elseChildren: zod_1.z.array(storedBlockSchema).nullable(),
138
+ })
139
+ .passthrough(),
140
+ zod_1.z.object({ ...base, type: zod_1.z.literal("divider") }).passthrough(),
141
+ zod_1.z
142
+ .object({
143
+ ...base,
144
+ type: zod_1.z.literal("footer"),
145
+ text: zod_1.z.string(),
146
+ unsubscribeLabel: zod_1.z.string(),
147
+ preferencesLabel: zod_1.z.string(),
148
+ })
149
+ .passthrough(),
150
+ ]);
151
+ exports.storedContentSchema = zod_1.z
152
+ .object({
153
+ locales: zod_1.z.record(zod_1.z
154
+ .object({
155
+ subject: zod_1.z.string(),
156
+ preheader: zod_1.z.string(),
157
+ blocks: zod_1.z.array(storedBlockSchema),
158
+ })
159
+ .passthrough()),
160
+ })
161
+ .passthrough();
162
+ exports.storedVariablesSchema = zod_1.z.array(zod_1.z
163
+ .object({
164
+ path: zod_1.z.string(),
165
+ type: oneOf(variableTypes),
166
+ required: zod_1.z.boolean(),
167
+ })
168
+ .passthrough());
169
+ exports.storedChangesSchema = zod_1.z.array(zod_1.z
170
+ .object({
171
+ locale: zod_1.z.string(),
172
+ kind: oneOf(changeKinds),
173
+ blockId: zod_1.z.string().optional(),
174
+ blockType: oneOf(blockTypes).optional(),
175
+ variable: zod_1.z.string().optional(),
176
+ })
177
+ .passthrough());
178
+ exports.storedVersionRowSchema = zod_1.z
179
+ .object({
180
+ _id: zod_1.z.string(),
181
+ templateId: zod_1.z.string(),
182
+ version: zod_1.z.number(),
183
+ json_content: zod_1.z.string(),
184
+ json_variables: zod_1.z.string(),
185
+ locales: zod_1.z.string(),
186
+ publishedAt: zod_1.z.string().transform((at) => new Date(at)),
187
+ publishedBy: zod_1.z.string(),
188
+ json_changes: zod_1.z.string(),
189
+ })
190
+ .passthrough();
191
+ exports.storedCategoriesSchema = zod_1.z.array(zod_1.z
192
+ .object({
193
+ id: zod_1.z.string(),
194
+ label: zod_1.z.record(zod_1.z.string()),
195
+ icon: zod_1.z.string(),
196
+ })
197
+ .passthrough());
198
+ //# sourceMappingURL=stored.schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stored.schema.js","sourceRoot":"","sources":["../../src/validation/stored.schema.ts"],"names":[],"mappings":"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"}
@@ -21,7 +21,7 @@ export interface EditorContext {
21
21
  }
22
22
 
23
23
  const EDITOR_KEY: InjectionKey<EditorContext> = Symbol('dms-mailing-editor')
24
- const MISSING_CONTEXT = 'useEditorContext() must be called inside MailingEditor'
24
+ const MISSING_CONTEXT = 'useEditorContext() must be called inside MailingTemplateEditor'
25
25
 
26
26
  export function provideEditorContext(context: EditorContext): void {
27
27
  provide(EDITOR_KEY, context)
@@ -74,6 +74,11 @@ function backendSourceFile(id: string): string | undefined {
74
74
  : undefined
75
75
  }
76
76
 
77
+ /** `path` with `/` separators, the form Vite gives an importer, so a boundary error names both paths alike on every OS. */
78
+ function forwardSlashes(path: string): string {
79
+ return path.split(sep).join('/')
80
+ }
81
+
77
82
  /**
78
83
  * Fails the build and the dev server when a module of the frontend resolves to
79
84
  * a file under the `src` of the backend package: the browser reaches the
@@ -113,9 +118,9 @@ function boundaryCheck(): VitePlugin {
113
118
  const from =
114
119
  importer === undefined
115
120
  ? 'an entry point'
116
- : importer.replace(QUERY_OR_HASH, '')
121
+ : forwardSlashes(importer.replace(QUERY_OR_HASH, ''))
117
122
  return this.error(
118
- `Backend boundary: ${from} imports ${file}, which is under the src of ${BACKEND_PACKAGE}. The frontend reaches the backend only through its HTTP interface.`,
123
+ `Backend boundary: ${from} imports ${forwardSlashes(file)}, which is under the src of ${BACKEND_PACKAGE}. The frontend reaches the backend only through its HTTP interface.`,
119
124
  )
120
125
  },
121
126
  }
@@ -8,17 +8,14 @@ interface VueModule {
8
8
  }
9
9
 
10
10
  const COMPONENT_PREFIX = 'Mailing'
11
- const BLOCKS_DIRECTORY = '/components/blocks/'
12
- const BLOCK_NAME_PREFIX = 'Block'
13
11
 
14
12
  const components = import.meta.glob<VueModule>('./app/components/**/*.vue')
15
13
 
16
14
  function componentName(path: string): string {
17
- const base = path
15
+ return path
18
16
  .split('/')
19
17
  .at(-1)!
20
18
  .replace(/\.vue$/, '')
21
- return path.includes(BLOCKS_DIRECTORY) ? `${BLOCK_NAME_PREFIX}${base}` : base
22
19
  }
23
20
 
24
21
  const frontendModule: DmsFrontendModule = {
@@ -9,7 +9,6 @@ export default [
9
9
  {
10
10
  files: ['**/*.ts', '**/*.vue'],
11
11
  languageOptions: { parserOptions: { parser: ts.parser } },
12
- rules: { 'vue/multi-word-component-names': 'off' },
13
12
  },
14
13
  { files: ['**/*.ts'], languageOptions: { parser: ts.parser } },
15
14
  {
@@ -21,6 +20,15 @@ export default [
21
20
  'antelopejs/no-typeof-function-only-test': 'error',
22
21
  'antelopejs/no-self-import-in-tests': 'error',
23
22
  'anti-slop/no-module-mocking': 'error',
23
+ 'no-restricted-syntax': [
24
+ 'error',
25
+ {
26
+ selector:
27
+ 'CallExpression:matches([callee.name=/^(it|test)$/], [callee.object.name=/^(it|test)$/], [callee.callee.object.name=/^(it|test)$/]) ImportExpression',
28
+ message:
29
+ 'Import the module under test statically at the top of the file: a dynamic import inside a test body counts the module load in the test timeout.',
30
+ },
31
+ ],
24
32
  },
25
33
  },
26
34
  ]
@@ -21,7 +21,7 @@
21
21
  "vue": "^3.5.18"
22
22
  },
23
23
  "devDependencies": {
24
- "@antelopejs/tooling-configs": "^0.3.3",
24
+ "@antelopejs/tooling-configs": ">=0.7.1 <0.8.0",
25
25
  "@nuxt/ui": "4.10.0",
26
26
  "@types/sortablejs": "^1.15.9",
27
27
  "@vitejs/plugin-vue": "^6.0.0",
@@ -22,8 +22,8 @@ importers:
22
22
  version: 3.5.42(typescript@5.9.3)
23
23
  devDependencies:
24
24
  '@antelopejs/tooling-configs':
25
- specifier: ^0.3.3
26
- version: 0.3.3
25
+ specifier: '>=0.7.1 <0.8.0'
26
+ version: 0.7.1
27
27
  '@nuxt/ui':
28
28
  specifier: 4.10.0
29
29
  version: 4.10.0(9bdbb06d8a0b4accef61070adc217622)
@@ -70,8 +70,8 @@ packages:
70
70
  resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==}
71
71
  engines: {node: '>=10'}
72
72
 
73
- '@antelopejs/tooling-configs@0.3.3':
74
- resolution: {integrity: sha512-jQ09zavCF5MMQtfyd8YOqpoxV6l6bp3m9tA5iN0z0XhQpVHM5VWr53yEuQ6Mc+1tseBW85N94ruP4TEfiGg8iQ==}
73
+ '@antelopejs/tooling-configs@0.7.1':
74
+ resolution: {integrity: sha512-X/mr/nQEgmFI2dMr3ThMS7HcPaU1amsTU/RNfEnQv46+l7au8cv7unpypCZ0BggnfJHjpk2QG2CTOOllpwj3cw==}
75
75
  engines: {node: '>=22.18.0'}
76
76
  hasBin: true
77
77
  peerDependencies:
@@ -79,7 +79,7 @@ packages:
79
79
  eslint-plugin-perfectionist: '>=5.0.0'
80
80
  knip: '>=6.0.0'
81
81
  mongodb-memory-server: '>=10.0.0'
82
- oxfmt: ^0.66.0
82
+ oxfmt: ^0.66.0 || ^0.77.0
83
83
  oxlint: 1.81.0
84
84
  pixelmatch: '>=7.0.0'
85
85
  playwright-core: '>=1.56.0'
@@ -1496,6 +1496,10 @@ packages:
1496
1496
  resolution: {integrity: sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==}
1497
1497
  engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1498
1498
 
1499
+ '@typescript/typescript6@6.0.2':
1500
+ resolution: {integrity: sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==}
1501
+ hasBin: true
1502
+
1499
1503
  '@unhead/vue@2.1.17':
1500
1504
  resolution: {integrity: sha512-pnC8x9HLV3qQXdvWfylUEU25uhfCAy3ly9nmpQz84j9py818DRfU8jOsQ5wjdWtxyU1vX/fW2udfm4jtxUK8Bg==}
1501
1505
  peerDependencies:
@@ -2967,6 +2971,11 @@ packages:
2967
2971
  engines: {node: '>=14.17'}
2968
2972
  hasBin: true
2969
2973
 
2974
+ typescript@6.0.3:
2975
+ resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
2976
+ engines: {node: '>=14.17'}
2977
+ hasBin: true
2978
+
2970
2979
  ufo@1.6.4:
2971
2980
  resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
2972
2981
 
@@ -3390,11 +3399,13 @@ snapshots:
3390
3399
 
3391
3400
  '@alloc/quick-lru@5.3.0': {}
3392
3401
 
3393
- '@antelopejs/tooling-configs@0.3.3':
3402
+ '@antelopejs/tooling-configs@0.7.1':
3394
3403
  dependencies:
3395
3404
  '@oxlint/plugins': 1.81.0
3405
+ '@typescript/typescript6': 6.0.2
3396
3406
  dts-bundle-generator: 9.5.1
3397
- typescript: 5.9.3
3407
+ minimatch: 10.2.6
3408
+ semver: 7.8.5
3398
3409
  yaml: 2.9.1
3399
3410
 
3400
3411
  '@antfu/install-pkg@2.0.1':
@@ -4667,6 +4678,10 @@ snapshots:
4667
4678
  '@typescript-eslint/types': 8.70.0
4668
4679
  eslint-visitor-keys: 5.0.1
4669
4680
 
4681
+ '@typescript/typescript6@6.0.2':
4682
+ dependencies:
4683
+ '@typescript/old': typescript@6.0.3
4684
+
4670
4685
  '@unhead/vue@2.1.17(vue@3.5.42(typescript@5.9.3))':
4671
4686
  dependencies:
4672
4687
  hookable: 6.1.1
@@ -6170,6 +6185,8 @@ snapshots:
6170
6185
 
6171
6186
  typescript@5.9.3: {}
6172
6187
 
6188
+ typescript@6.0.3: {}
6189
+
6173
6190
  ufo@1.6.4: {}
6174
6191
 
6175
6192
  uncrypto@0.1.3: {}
@@ -7,8 +7,8 @@ import {
7
7
  writeFileSync,
8
8
  } from 'node:fs'
9
9
  import { tmpdir } from 'node:os'
10
- import { dirname, join } from 'node:path'
11
- import { build as viteBuild, type Plugin } from 'vite'
10
+ import { dirname, join } from 'node:path/posix'
11
+ import { normalizePath, build as viteBuild, type Plugin } from 'vite'
12
12
  import { afterAll, beforeAll, describe, expect, it } from 'vitest'
13
13
  import { backendBoundary } from '../build/backend-boundary'
14
14
 
@@ -22,8 +22,8 @@ function write(file: string, text: string): void {
22
22
  }
23
23
 
24
24
  beforeAll(() => {
25
- repository = realpathSync(
26
- mkdtempSync(join(tmpdir(), 'dms-mailing-boundary-')),
25
+ repository = normalizePath(
26
+ realpathSync(mkdtempSync(`${tmpdir()}/dms-mailing-boundary-`)),
27
27
  )
28
28
  const backend = join(repository, 'packages/dms-mailing')
29
29
  frontend = join(backend, 'frontend-vue')
@@ -1,6 +1,11 @@
1
1
  // @vitest-environment node
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3
3
  import type { VNode } from 'vue'
4
+ import CategoryLabel from '../app/components/CategoryLabel.vue'
5
+ import setup, {
6
+ renderCategory,
7
+ renderLocaleChips,
8
+ } from '../app/plugins/column-displays'
4
9
 
5
10
  interface RegisteredType {
6
11
  id: string
@@ -11,9 +16,12 @@ type PluginSetup = () => void
11
16
 
12
17
  const registered: RegisteredType[] = []
13
18
 
19
+ vi.hoisted(() => {
20
+ vi.stubGlobal('defineDmsPlugin', (pluginSetup: PluginSetup) => pluginSetup)
21
+ })
22
+
14
23
  beforeEach(() => {
15
24
  registered.length = 0
16
- vi.stubGlobal('defineDmsPlugin', (setup: PluginSetup) => setup)
17
25
  vi.stubGlobal('useDataTypes', () => ({
18
26
  registerDataType: (type: RegisteredType) => registered.push(type),
19
27
  }))
@@ -24,7 +32,6 @@ beforeEach(() => {
24
32
 
25
33
  afterEach(() => {
26
34
  vi.unstubAllGlobals()
27
- vi.resetModules()
28
35
  })
29
36
 
30
37
  function texts(node: VNode): string[] {
@@ -36,8 +43,7 @@ function texts(node: VNode): string[] {
36
43
  }
37
44
 
38
45
  describe('column displays plugin', () => {
39
- it('registers the locale chips and category displays without a DOM', async () => {
40
- const { default: setup } = await import('../app/plugins/column-displays')
46
+ it('registers the locale chips and category displays without a DOM', () => {
41
47
  ;(setup as unknown as PluginSetup)()
42
48
  expect(registered.map((type) => type.id)).toEqual([
43
49
  'mailing:locale-chips',
@@ -45,18 +51,14 @@ describe('column displays plugin', () => {
45
51
  ])
46
52
  })
47
53
 
48
- it('draws a category through its label component', async () => {
49
- const { renderCategory } = await import('../app/plugins/column-displays')
50
- const { default: CategoryLabel } =
51
- await import('../app/components/CategoryLabel.vue')
54
+ it('draws a category through its label component', () => {
52
55
  const cell = renderCategory('orders')
53
56
  expect(cell.type).toBe(CategoryLabel)
54
57
  expect(cell.props).toEqual({ value: 'orders' })
55
58
  expect(renderCategory(undefined).props).toEqual({ value: null })
56
59
  })
57
60
 
58
- it('draws the locale chips with the missing workspace ones dashed', async () => {
59
- const { default: setup } = await import('../app/plugins/column-displays')
61
+ it('draws the locale chips with the missing workspace ones dashed', () => {
60
62
  ;(setup as unknown as PluginSetup)()
61
63
  const cell = registered[0]!.formatter.default('en,fr', 'en')
62
64
  const chips = cell.children as VNode[]
@@ -65,8 +67,7 @@ describe('column displays plugin', () => {
65
67
  expect(String(chips[0]!.props?.class)).not.toContain('border-dashed')
66
68
  })
67
69
 
68
- it('draws a dash for a row without locale information', async () => {
69
- const { renderLocaleChips } = await import('../app/plugins/column-displays')
70
+ it('draws a dash for a row without locale information', () => {
70
71
  expect(texts(renderLocaleChips('', ['en']))).toEqual(['—'])
71
72
  expect(texts(renderLocaleChips(undefined, ['en']))).toEqual(['—'])
72
73
  })
@@ -1,7 +1,7 @@
1
1
  // @vitest-environment jsdom
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3
3
  import { defineComponent, h, provide, ref } from 'vue'
4
- import FooterBlockView from '../app/components/blocks/Footer.vue'
4
+ import FooterBlockView from '../app/components/blocks/BlockFooter.vue'
5
5
  import type { FooterBlock } from '../app/types/mailing'
6
6
  import { TOKEN_DATA_KEY } from '../app/utils/tokens'
7
7
  import type { TokenData } from '../app/utils/tokens'
@@ -1,6 +1,7 @@
1
1
  // @vitest-environment node
2
2
  import { afterEach, describe, expect, it, vi } from 'vitest'
3
3
  import type { Component } from 'vue'
4
+ import setup from '../app/plugins/template-gallery-display'
4
5
 
5
6
  interface DisplayRegistration {
6
7
  id: string
@@ -9,21 +10,21 @@ interface DisplayRegistration {
9
10
 
10
11
  type PluginSetup = () => void
11
12
 
13
+ vi.hoisted(() => {
14
+ vi.stubGlobal('defineDmsPlugin', (pluginSetup: PluginSetup) => pluginSetup)
15
+ })
16
+
12
17
  afterEach(() => {
13
18
  vi.unstubAllGlobals()
14
- vi.resetModules()
15
19
  })
16
20
 
17
21
  describe('template gallery display plugin', () => {
18
- it('registers the gallery under the mailing namespace without a DOM', async () => {
22
+ it('registers the gallery under the mailing namespace without a DOM', () => {
19
23
  const registrations: DisplayRegistration[] = []
20
- vi.stubGlobal('defineDmsPlugin', (setup: PluginSetup) => setup)
21
24
  vi.stubGlobal(
22
25
  'registerTableViewDisplay',
23
26
  (registration: DisplayRegistration) => registrations.push(registration),
24
27
  )
25
- const { default: setup } =
26
- await import('../app/plugins/template-gallery-display')
27
28
  setup()
28
29
  expect(registrations.map((registration) => registration.id)).toEqual([
29
30
  'mailing:gallery',
@@ -1,5 +1,6 @@
1
1
  // @vitest-environment node
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3
+ import { useTemplateDrawer } from '../app/composables/useTemplateDrawer'
3
4
  import type { TemplateRow } from '../app/types/mailing'
4
5
 
5
6
  interface OpenCall {
@@ -53,7 +54,6 @@ beforeEach(() => {
53
54
 
54
55
  afterEach(() => {
55
56
  vi.unstubAllGlobals()
56
- vi.resetModules()
57
57
  })
58
58
 
59
59
  function row(id: string): TemplateRow {
@@ -68,9 +68,7 @@ function row(id: string): TemplateRow {
68
68
  }
69
69
 
70
70
  describe('useTemplateDrawer', () => {
71
- it('opens the test send with the editor draft and locale', async () => {
72
- const { useTemplateDrawer } =
73
- await import('../app/composables/useTemplateDrawer')
71
+ it('opens the test send with the editor draft and locale', () => {
74
72
  const content = { locales: {} }
75
73
  useTemplateDrawer().openTestSend('t1', { locale: 'fr', content })
76
74
  expect(modalCalls[0]?.component).toEqual({ name: 'MailingTestSendModal' })
@@ -82,8 +80,6 @@ describe('useTemplateDrawer', () => {
82
80
  })
83
81
 
84
82
  it('calls back only when the dialog closes after a change', async () => {
85
- const { useTemplateDrawer } =
86
- await import('../app/composables/useTemplateDrawer')
87
83
  const onDone = vi.fn()
88
84
  useTemplateDrawer().openRealSend('t1', { onDone })
89
85
  await Promise.resolve()
@@ -97,9 +93,7 @@ describe('useTemplateDrawer', () => {
97
93
  expect(modalCalls[1]?.componentOptions).toEqual({ initialName: 'Refund' })
98
94
  })
99
95
 
100
- it('opens the details from the right and steps through the rows shown', async () => {
101
- const { useTemplateDrawer } =
102
- await import('../app/composables/useTemplateDrawer')
96
+ it('opens the details from the right and steps through the rows shown', () => {
103
97
  const rows = [row('a'), row('b'), row('c')]
104
98
  useTemplateDrawer().openDetails(rows[1]!, { rows })
105
99
  const call = drawerCalls[0]!
@@ -115,9 +109,7 @@ describe('useTemplateDrawer', () => {
115
109
  expect((options.navigation as Navigation).hasNext).toBe(false)
116
110
  })
117
111
 
118
- it('opens the details of a bare id without navigation', async () => {
119
- const { useTemplateDrawer } =
120
- await import('../app/composables/useTemplateDrawer')
112
+ it('opens the details of a bare id without navigation', () => {
121
113
  useTemplateDrawer().openDetails('x')
122
114
  expect(drawerCalls[0]?.componentOptions.templateId).toBe('x')
123
115
  expect(drawerCalls[0]?.componentOptions.navigation).toBeUndefined()
@@ -5,8 +5,7 @@
5
5
  "moduleResolution": "bundler",
6
6
  "lib": ["esnext", "dom"],
7
7
  "strict": true,
8
- "noEmit": true,
9
- "skipLibCheck": true
8
+ "noEmit": true
10
9
  },
11
10
  "include": ["tests/**/*.test-d.ts"]
12
11
  }