@capillarytech/creatives-library 8.0.285-alpha.0 → 8.0.285
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/constants/unified.js +1 -0
- package/package.json +1 -1
- package/utils/common.js +7 -0
- package/utils/commonUtils.js +79 -2
- package/utils/tagValidations.js +71 -92
- package/utils/tests/commonUtil.test.js +32 -79
- package/utils/tests/tagValidations.test.js +37 -18
- package/v2Components/FormBuilder/index.js +126 -47
- package/v2Containers/CreativesContainer/index.js +1 -0
- package/v2Containers/Email/index.js +5 -1
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +18 -6
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +106 -8
- package/v2Containers/FTP/index.js +51 -2
- package/v2Containers/InApp/index.js +23 -2
- package/v2Containers/InApp/tests/index.test.js +6 -17
- package/v2Containers/InappAdvance/index.js +24 -3
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +24 -3
- package/v2Containers/Line/Container/Text/index.js +1 -0
- package/v2Containers/MobilePushNew/index.js +24 -4
- package/v2Containers/Rcs/index.js +37 -12
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +18 -4
- package/v2Containers/SmsTrai/Create/index.scss +1 -1
- package/v2Containers/SmsTrai/Edit/index.js +17 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +0 -9
- package/v2Containers/Viber/index.js +1 -0
- package/v2Containers/Viber/index.scss +1 -1
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
- package/v2Containers/WebPush/Create/index.js +2 -2
- package/v2Containers/WebPush/Create/utils/validation.js +9 -18
- package/v2Containers/WebPush/Create/utils/validation.test.js +24 -0
- package/v2Containers/Whatsapp/index.js +17 -6
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +624 -248
- package/v2Containers/Zalo/index.js +11 -3
package/constants/unified.js
CHANGED
|
@@ -43,6 +43,7 @@ export const HOSPITALITY_BASED_SCOPE = 'HOSPITALITY_BASED_SCOPE';
|
|
|
43
43
|
export const REGISTRATION_CUSTOM_FIELD = 'Registration custom fields';
|
|
44
44
|
export const GIFT_CARDS = 'GIFT_CARDS';
|
|
45
45
|
export const PROMO_ENGINE = 'PROMO_ENGINE';
|
|
46
|
+
export const LIQUID_SUPPORT = 'ENABLE_LIQUID_SUPPORT';
|
|
46
47
|
export const ENABLE_NEW_MPUSH = 'ENABLE_NEW_MPUSH';
|
|
47
48
|
export const SUPPORT_CK_EDITOR = 'SUPPORT_CK_EDITOR';
|
|
48
49
|
export const CUSTOM_TAG = 'CustomTagMessage';
|
package/package.json
CHANGED
package/utils/common.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
BADGES_ISSUE,
|
|
23
23
|
ENABLE_WECHAT,
|
|
24
24
|
ENABLE_WEBPUSH,
|
|
25
|
+
LIQUID_SUPPORT,
|
|
25
26
|
SUPPORT_CK_EDITOR,
|
|
26
27
|
ENABLE_NEW_MPUSH
|
|
27
28
|
} from '../constants/unified';
|
|
@@ -90,6 +91,12 @@ export const hasPromoFeature = Auth.hasFeatureAccess.bind(
|
|
|
90
91
|
null,
|
|
91
92
|
PROMO_ENGINE,
|
|
92
93
|
);
|
|
94
|
+
|
|
95
|
+
export const hasLiquidSupportFeature = Auth.hasFeatureAccess.bind(
|
|
96
|
+
null,
|
|
97
|
+
LIQUID_SUPPORT,
|
|
98
|
+
);
|
|
99
|
+
|
|
93
100
|
export const hasSupportCKEditor = Auth.hasFeatureAccess.bind(
|
|
94
101
|
null,
|
|
95
102
|
SUPPORT_CK_EDITOR,
|
package/utils/commonUtils.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
IOS,
|
|
17
17
|
} from "../v2Containers/CreativesContainer/constants";
|
|
18
18
|
import { GLOBAL_CONVERT_OPTIONS } from "../v2Components/FormBuilder/constants";
|
|
19
|
+
import { checkSupport, extractNames, skipTags as defaultSkipTags, isInsideLiquidBlock } from "./tagValidations";
|
|
19
20
|
import { SMS_TRAI_VAR } from '../v2Containers/SmsTrai/Edit/constants';
|
|
20
21
|
export const apiMessageFormatHandler = (id, fallback) => (
|
|
21
22
|
<FormattedMessage id={id} defaultMessage={fallback} />
|
|
@@ -145,6 +146,7 @@ export const validateLiquidTemplateContent = async (
|
|
|
145
146
|
isLiquidFlow,
|
|
146
147
|
forwardedTags = {},
|
|
147
148
|
tabType,
|
|
149
|
+
skipTags = defaultSkipTags,
|
|
148
150
|
} = options;
|
|
149
151
|
const emptyBodyError = formatMessage(messages?.emailBodyEmptyError);
|
|
150
152
|
const somethingWrongMsg = formatMessage(messages?.somethingWentWrong);
|
|
@@ -202,6 +204,81 @@ export const validateLiquidTemplateContent = async (
|
|
|
202
204
|
});
|
|
203
205
|
return false;
|
|
204
206
|
}
|
|
207
|
+
// Extract and validate tags
|
|
208
|
+
const extractedLiquidTags = extractNames(result?.data || []);
|
|
209
|
+
// Get supported tags
|
|
210
|
+
const supportedLiquidTags = checkSupport(
|
|
211
|
+
result,
|
|
212
|
+
tagLookupMap,
|
|
213
|
+
eventContextTags,
|
|
214
|
+
isLiquidFlow,
|
|
215
|
+
forwardedTags
|
|
216
|
+
);
|
|
217
|
+
// Helper function to check if a tag appears only inside {% %} blocks
|
|
218
|
+
const isTagOnlyInsideLiquidBlocks = (tagName) => {
|
|
219
|
+
// Escape special regex characters in tag name, including dots
|
|
220
|
+
// Dots need to be escaped to match literally (item.name should match item.name, not item or name)
|
|
221
|
+
const escapedTagName = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
222
|
+
|
|
223
|
+
// First, check if tag appears in {% %} syntax itself (like "order.items" in "{% for item in order.items %}")
|
|
224
|
+
// This is a tag used in Liquid logic, not output, so it should always be skipped
|
|
225
|
+
// Match the tag name as a whole (with dots escaped), optionally surrounded by word boundaries or non-word chars
|
|
226
|
+
// For tags with dots, we match them directly; for simple tags, we use word boundaries
|
|
227
|
+
const hasDot = tagName.includes('.');
|
|
228
|
+
const liquidSyntaxPattern = hasDot
|
|
229
|
+
? `{%[^%]*${escapedTagName}[^%]*%}`
|
|
230
|
+
: `{%[^%]*\\b${escapedTagName}\\b[^%]*%}`;
|
|
231
|
+
const liquidSyntaxRegex = new RegExp(liquidSyntaxPattern, 'g');
|
|
232
|
+
const liquidSyntaxMatches = Array.from(content.matchAll(liquidSyntaxRegex));
|
|
233
|
+
|
|
234
|
+
// Find all occurrences of {{tagName}} in the content (output tags)
|
|
235
|
+
// Match patterns like: {{tagName}}, {{ tagName }}, {{tagName }}, {{ tagName}}
|
|
236
|
+
// Use non-word-boundary approach for tags with dots (item.name should match item.name, not item or name separately)
|
|
237
|
+
const outputTagRegex = new RegExp(`\\{\\{\\s*${escapedTagName}\\s*\\}\\}`, 'g');
|
|
238
|
+
const outputTagMatches = Array.from(content.matchAll(outputTagRegex));
|
|
239
|
+
const outputTagPositions = outputTagMatches.map((match) => match.index);
|
|
240
|
+
|
|
241
|
+
// If tag appears in {% %} syntax, skip validation
|
|
242
|
+
if (liquidSyntaxMatches.length > 0) {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// If no output tag matches found, don't skip validation
|
|
247
|
+
// The tag was extracted by the API, so it exists somewhere and should be validated
|
|
248
|
+
if (outputTagPositions.length === 0) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Check if all output tag occurrences are inside {% %} blocks
|
|
253
|
+
// We check the position of {{ to see if it's inside a block
|
|
254
|
+
// Only skip validation if ALL occurrences are inside blocks
|
|
255
|
+
return outputTagPositions.every((tagIndex) => {
|
|
256
|
+
if (tagIndex === undefined || tagIndex === null) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
return isInsideLiquidBlock(content, tagIndex);
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Find unsupported tags, excluding those that are only inside {% %} blocks
|
|
264
|
+
const unsupportedLiquidTags = extractedLiquidTags?.filter(
|
|
265
|
+
(tag) => !supportedLiquidTags?.includes(tag)
|
|
266
|
+
&& !skipTags(tag)
|
|
267
|
+
&& !isTagOnlyInsideLiquidBlocks(tag)
|
|
268
|
+
);
|
|
269
|
+
// Handle unsupported tags
|
|
270
|
+
if (unsupportedLiquidTags?.length > 0) {
|
|
271
|
+
const errorMsg = formatMessage(messages.unsupportedTagsValidationError, {
|
|
272
|
+
unsupportedTags: unsupportedLiquidTags.join(", "),
|
|
273
|
+
});
|
|
274
|
+
onError({
|
|
275
|
+
standardErrors: [],
|
|
276
|
+
liquidErrors: [errorMsg],
|
|
277
|
+
tabType,
|
|
278
|
+
});
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
// All validations passed
|
|
205
282
|
onSuccess(content, tabType);
|
|
206
283
|
return true;
|
|
207
284
|
};
|
|
@@ -288,8 +365,8 @@ export const _validatePlatformSpecificContent = async (
|
|
|
288
365
|
/**
|
|
289
366
|
* Validate Mobile Push content for both Android and iOS tabs
|
|
290
367
|
* @param {object} formData - Form data containing Android and iOS content
|
|
291
|
-
* @param {object} options - Options for validation
|
|
292
|
-
* @returns {Promise
|
|
368
|
+
* @param {object} options - Options for validation
|
|
369
|
+
* @returns {Promise} - Promise that resolves when validation completes
|
|
293
370
|
*/
|
|
294
371
|
export const validateMobilePushContent = async (formData, options) => {
|
|
295
372
|
const {
|
package/utils/tagValidations.js
CHANGED
|
@@ -177,118 +177,97 @@ export const isInsideLiquidBlock = (content, tagIndex) => {
|
|
|
177
177
|
};
|
|
178
178
|
|
|
179
179
|
/**
|
|
180
|
-
*
|
|
181
|
-
* @param {Object} params
|
|
182
|
-
* @param {string} params.contentForBraceCheck - String to run validateIfTagClosed on.
|
|
183
|
-
* @param {string} params.contentForUnsubscribeScan - String to scan for {{...}} unsubscribe variants.
|
|
184
|
-
* @param {Array} [params.tags] - Tag definitions (for definition-based missing tags).
|
|
185
|
-
* @param {string} [params.currentModule] - Module context (e.g. 'default', 'outbound').
|
|
186
|
-
* @param {boolean} [params.isFullMode] - If true, skip unsubscribe checks.
|
|
187
|
-
* @param {Array} [params.initialMissingTags=null] - If set, use instead of computing from definitions.
|
|
188
|
-
* @param {function} [params.skipTagsFn=skipTags] - skipTags implementation.
|
|
189
|
-
* @param {boolean} [params.includeIsContentEmpty=false] - If true, response includes isContentEmpty: false.
|
|
190
|
-
* @returns {{ valid: boolean, missingTags: string[], unsupportedTags: string[], isBraceError: boolean }}
|
|
180
|
+
* Validates the tags based on the provided parameters.
|
|
181
|
+
* @param {Object} params - The parameters for tag validation.
|
|
191
182
|
*/
|
|
192
|
-
export const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
183
|
+
export const validateTags = ({
|
|
184
|
+
content,
|
|
185
|
+
tagsParam,
|
|
186
|
+
injectedTagsParams,
|
|
187
|
+
location,
|
|
188
|
+
tagModule,
|
|
189
|
+
eventContextTags,
|
|
197
190
|
isFullMode,
|
|
198
|
-
initialMissingTags = null,
|
|
199
|
-
skipTagsFn = skipTags,
|
|
200
|
-
includeIsContentEmpty = false,
|
|
201
191
|
}) => {
|
|
192
|
+
const tags = tagsParam;
|
|
193
|
+
const injectedTags = transformInjectedTags(injectedTagsParams);
|
|
194
|
+
let currentModule = location?.query?.module ? location?.query?.module : DEFAULT;
|
|
195
|
+
if (tagModule) {
|
|
196
|
+
currentModule = tagModule;
|
|
197
|
+
}
|
|
202
198
|
const response = {
|
|
203
199
|
valid: true,
|
|
204
200
|
missingTags: [],
|
|
205
201
|
unsupportedTags: [],
|
|
206
202
|
isBraceError: false,
|
|
207
|
-
...(includeIsContentEmpty && { isContentEmpty: false }),
|
|
208
203
|
};
|
|
209
|
-
|
|
204
|
+
// Mandatory-tags check: only when we have a tags list and are in library mode
|
|
210
205
|
if (tags && tags.length && !isFullMode) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
response.missingTags.push(value);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
});
|
|
206
|
+
lodashForEach(tags, ({
|
|
207
|
+
definition: {
|
|
208
|
+
supportedModules,
|
|
209
|
+
value,
|
|
210
|
+
},
|
|
211
|
+
}) => {
|
|
212
|
+
lodashForEach(supportedModules, (module) => {
|
|
213
|
+
if (module.mandatory && (currentModule === module.context)) {
|
|
214
|
+
if (content.indexOf(`{{${value}}}`) === -1) {
|
|
215
|
+
response.valid = false;
|
|
216
|
+
response.missingTags.push(value);
|
|
217
|
+
}
|
|
227
218
|
}
|
|
228
219
|
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const regex = /{{(
|
|
234
|
-
let match = regex.exec(
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
// In library mode, always scan content for {{...}} and flag unsupported tags (even when tags list is empty)
|
|
223
|
+
if (!isFullMode && content) {
|
|
224
|
+
const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
|
|
225
|
+
let match = regex.exec(content);
|
|
235
226
|
while (match !== null) {
|
|
236
|
-
const tagValue = match[
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
227
|
+
const tagValue = match[0].substring(indexOfEnd(match[0], '{{'), match[0].indexOf('}}'));
|
|
228
|
+
const tagIndex = match?.index;
|
|
229
|
+
match = regex.exec(content);
|
|
230
|
+
let ifSupported = false;
|
|
231
|
+
lodashForEach(tags || [], (tag) => {
|
|
232
|
+
if (tag?.definition?.value === tagValue) {
|
|
233
|
+
ifSupported = true;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
const ifSkipped = skipTags(tagValue);
|
|
237
|
+
if (ifSkipped) {
|
|
238
|
+
ifSupported = true;
|
|
239
|
+
const isUnsubscribeSkipped = tagValue.indexOf("unsubscribe") !== -1;
|
|
240
|
+
if (isUnsubscribeSkipped) {
|
|
241
|
+
const missingTagIndex = response.missingTags.indexOf("unsubscribe");
|
|
242
|
+
if (missingTagIndex !== -1) {
|
|
243
|
+
response.missingTags.splice(missingTagIndex, 1);
|
|
244
|
+
}
|
|
242
245
|
}
|
|
243
246
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
// Journey Event Context Tags support
|
|
248
|
+
eventContextTags?.forEach((tag) => {
|
|
249
|
+
if (tagValue === tag?.tagName) {
|
|
250
|
+
ifSupported = true;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
ifSupported = ifSupported || checkIfSupportedTag(tagValue, injectedTags);
|
|
254
|
+
// Only add to unsupportedTags if not inside a {% ... %} block and does not contain a dot
|
|
255
|
+
if (!ifSupported && !isInsideLiquidBlock(content, tagIndex) && tagValue?.indexOf('.') === -1) {
|
|
256
|
+
response.unsupportedTags.push(tagValue);
|
|
257
|
+
response.valid = false;
|
|
258
|
+
}
|
|
259
|
+
if (response.unsupportedTags.length === 0 && response.missingTags.length === 0) {
|
|
260
|
+
response.valid = true;
|
|
261
|
+
}
|
|
249
262
|
}
|
|
250
263
|
}
|
|
251
|
-
|
|
252
|
-
response
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
} else if (response.missingTags.length > 0) {
|
|
256
|
-
response.valid = false;
|
|
257
|
-
}
|
|
264
|
+
response.isBraceError = !validateIfTagClosed(content);
|
|
265
|
+
// response should not be valid if there is unbalanced bracket error, as
|
|
266
|
+
// validations (eg button) are handled on valid property coming from the response.
|
|
267
|
+
response.isBraceError ? response.valid = false : response.valid = true;
|
|
258
268
|
return response;
|
|
259
269
|
};
|
|
260
270
|
|
|
261
|
-
/**
|
|
262
|
-
* Validates the tags based on the provided parameters.
|
|
263
|
-
* @param {Object} params - The parameters for tag validation.
|
|
264
|
-
* @param {string} params.content - Content to validate.
|
|
265
|
-
* @param {Array} [params.tagsParam] - Tag definitions.
|
|
266
|
-
* @param {Object} [params.location] - Location with query.module.
|
|
267
|
-
* @param {string} [params.tagModule] - Override for current module context.
|
|
268
|
-
* @param {boolean} [params.isFullMode] - If true, skip unsubscribe checks.
|
|
269
|
-
* @returns {{ valid: boolean, missingTags: string[], unsupportedTags: string[], isBraceError: boolean }}
|
|
270
|
-
*/
|
|
271
|
-
export const validateTags = ({
|
|
272
|
-
content,
|
|
273
|
-
tagsParam,
|
|
274
|
-
location,
|
|
275
|
-
tagModule,
|
|
276
|
-
isFullMode,
|
|
277
|
-
}) => {
|
|
278
|
-
const tags = tagsParam;
|
|
279
|
-
let currentModule = location?.query?.module ? location?.query?.module : DEFAULT;
|
|
280
|
-
if (tagModule) {
|
|
281
|
-
currentModule = tagModule;
|
|
282
|
-
}
|
|
283
|
-
return validateTagsCore({
|
|
284
|
-
contentForBraceCheck: content,
|
|
285
|
-
contentForUnsubscribeScan: content,
|
|
286
|
-
tags,
|
|
287
|
-
currentModule,
|
|
288
|
-
isFullMode,
|
|
289
|
-
});
|
|
290
|
-
};
|
|
291
|
-
|
|
292
271
|
/**
|
|
293
272
|
* Checks if the given tag is supported based on the injected tags.
|
|
294
273
|
* @param {string} checkingTag - The tag to check.
|
|
@@ -27,11 +27,6 @@ describe("validateLiquidTemplateContent", () => {
|
|
|
27
27
|
|
|
28
28
|
beforeEach(() => {
|
|
29
29
|
jest.clearAllMocks();
|
|
30
|
-
formatMessage.mockImplementation((msg, vars) =>
|
|
31
|
-
vars && vars.unsupportedTags != null
|
|
32
|
-
? `${msg?.id}:${vars.unsupportedTags}`
|
|
33
|
-
: (msg?.id ?? "unsupported")
|
|
34
|
-
);
|
|
35
30
|
});
|
|
36
31
|
|
|
37
32
|
it("calls onError for empty content", async () => {
|
|
@@ -48,7 +43,7 @@ describe("validateLiquidTemplateContent", () => {
|
|
|
48
43
|
eventContextTags
|
|
49
44
|
});
|
|
50
45
|
expect(onError).toHaveBeenCalledWith({
|
|
51
|
-
standardErrors: [
|
|
46
|
+
standardErrors: [undefined],
|
|
52
47
|
liquidErrors: [],
|
|
53
48
|
tabType: undefined
|
|
54
49
|
});
|
|
@@ -91,9 +86,9 @@ describe("validateLiquidTemplateContent", () => {
|
|
|
91
86
|
expect(onSuccess).not.toHaveBeenCalled();
|
|
92
87
|
});
|
|
93
88
|
|
|
94
|
-
it("calls
|
|
89
|
+
it("calls onError for unsupported tags", async () => {
|
|
95
90
|
const getLiquidTags = jest.fn((content, cb) =>
|
|
96
|
-
cb({ askAiraResponse: { errors: [], data: [{ name: "
|
|
91
|
+
cb({ askAiraResponse: { errors: [], data: [{ name: "baz" }] }, isError: false })
|
|
97
92
|
);
|
|
98
93
|
await validateLiquidTemplateContent("foo", {
|
|
99
94
|
getLiquidTags,
|
|
@@ -104,11 +99,15 @@ describe("validateLiquidTemplateContent", () => {
|
|
|
104
99
|
tagLookupMap,
|
|
105
100
|
eventContextTags
|
|
106
101
|
});
|
|
107
|
-
expect(
|
|
108
|
-
|
|
102
|
+
expect(onError).toHaveBeenCalledWith({
|
|
103
|
+
standardErrors: [],
|
|
104
|
+
liquidErrors: [undefined],
|
|
105
|
+
tabType: undefined
|
|
106
|
+
});
|
|
107
|
+
expect(onSuccess).not.toHaveBeenCalled();
|
|
109
108
|
});
|
|
110
109
|
|
|
111
|
-
it("calls onSuccess for valid content
|
|
110
|
+
it("calls onSuccess for valid content", async () => {
|
|
112
111
|
const getLiquidTags = jest.fn((content, cb) =>
|
|
113
112
|
cb({ askAiraResponse: { errors: [], data: [{ name: "foo" }, { name: "bar" }] }, isError: false })
|
|
114
113
|
);
|
|
@@ -298,7 +297,7 @@ describe("validateLiquidTemplateContent", () => {
|
|
|
298
297
|
expect(onSuccess).not.toHaveBeenCalled();
|
|
299
298
|
});
|
|
300
299
|
|
|
301
|
-
it("
|
|
300
|
+
it("should skip tags that appear in {% %} syntax (like order.items in for loop)", async () => {
|
|
302
301
|
const content = '{% for item in order.items %} Hello {% endfor %}';
|
|
303
302
|
const getLiquidTags = jest.fn((content, cb) =>
|
|
304
303
|
cb({ askAiraResponse: { errors: [], data: [{ name: "order.items" }] }, isError: false })
|
|
@@ -312,11 +311,12 @@ describe("validateLiquidTemplateContent", () => {
|
|
|
312
311
|
tagLookupMap,
|
|
313
312
|
eventContextTags
|
|
314
313
|
});
|
|
314
|
+
// order.items appears in {% %} syntax, so it should be skipped and validation should pass
|
|
315
315
|
expect(onSuccess).toHaveBeenCalledWith(content, undefined);
|
|
316
316
|
expect(onError).not.toHaveBeenCalled();
|
|
317
317
|
});
|
|
318
318
|
|
|
319
|
-
it("
|
|
319
|
+
it("should skip tags that appear only inside {% %} blocks (like item.name in for loop)", async () => {
|
|
320
320
|
const content = '{% for item in order.items %} {{ item.name }} - {{ item.quantity }} {% endfor %}';
|
|
321
321
|
const getLiquidTags = jest.fn((content, cb) =>
|
|
322
322
|
cb({ askAiraResponse: { errors: [], data: [{ name: "item.name" }, { name: "item.quantity" }] }, isError: false })
|
|
@@ -330,11 +330,12 @@ describe("validateLiquidTemplateContent", () => {
|
|
|
330
330
|
tagLookupMap,
|
|
331
331
|
eventContextTags
|
|
332
332
|
});
|
|
333
|
+
// item.name and item.quantity appear inside {% for %} block, so they should be skipped
|
|
333
334
|
expect(onSuccess).toHaveBeenCalledWith(content, undefined);
|
|
334
335
|
expect(onError).not.toHaveBeenCalled();
|
|
335
336
|
});
|
|
336
337
|
|
|
337
|
-
it("
|
|
338
|
+
it("should validate tags that don't appear in content but are extracted by API", async () => {
|
|
338
339
|
const content = 'Some content without the tag';
|
|
339
340
|
const getLiquidTags = jest.fn((content, cb) =>
|
|
340
341
|
cb({ askAiraResponse: { errors: [], data: [{ name: "extractedTag" }] }, isError: false })
|
|
@@ -348,11 +349,16 @@ describe("validateLiquidTemplateContent", () => {
|
|
|
348
349
|
tagLookupMap,
|
|
349
350
|
eventContextTags
|
|
350
351
|
});
|
|
351
|
-
|
|
352
|
-
expect(onError).
|
|
352
|
+
// extractedTag doesn't appear in content but was extracted by API, should be validated
|
|
353
|
+
expect(onError).toHaveBeenCalledWith({
|
|
354
|
+
standardErrors: [],
|
|
355
|
+
liquidErrors: [undefined],
|
|
356
|
+
tabType: undefined
|
|
357
|
+
});
|
|
358
|
+
expect(onSuccess).not.toHaveBeenCalled();
|
|
353
359
|
});
|
|
354
360
|
|
|
355
|
-
it("
|
|
361
|
+
it("should validate tags that appear outside {% %} blocks", async () => {
|
|
356
362
|
const content = '{{ outsideTag }} {% for item in order.items %} {{ item.name }} {% endfor %}';
|
|
357
363
|
const getLiquidTags = jest.fn((content, cb) =>
|
|
358
364
|
cb({ askAiraResponse: { errors: [], data: [{ name: "outsideTag" }, { name: "item.name" }] }, isError: false })
|
|
@@ -366,71 +372,17 @@ describe("validateLiquidTemplateContent", () => {
|
|
|
366
372
|
tagLookupMap,
|
|
367
373
|
eventContextTags
|
|
368
374
|
});
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const getLiquidTags = jest.fn((content, cb) =>
|
|
376
|
-
cb({ askAiraResponse: { errors: [], data: [{ name: "unsupportedTag" }] }, isError: false })
|
|
377
|
-
);
|
|
378
|
-
await validateLiquidTemplateContent(content, {
|
|
379
|
-
getLiquidTags,
|
|
380
|
-
formatMessage,
|
|
381
|
-
messages,
|
|
382
|
-
onError,
|
|
383
|
-
onSuccess,
|
|
384
|
-
tagLookupMap,
|
|
385
|
-
eventContextTags
|
|
386
|
-
});
|
|
387
|
-
expect(onSuccess).toHaveBeenCalledWith(content, undefined);
|
|
388
|
-
expect(onError).not.toHaveBeenCalled();
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
it("calls onSuccess when API returns tag with spacing variants and no errors", async () => {
|
|
392
|
-
const content = '{{ tag}} and {{tag }} and {{ tag }}';
|
|
393
|
-
const getLiquidTags = jest.fn((content, cb) =>
|
|
394
|
-
cb({
|
|
395
|
-
askAiraResponse: {
|
|
396
|
-
errors: [],
|
|
397
|
-
data: [{ name: "tag" }]
|
|
398
|
-
},
|
|
399
|
-
isError: false
|
|
400
|
-
})
|
|
401
|
-
);
|
|
402
|
-
await validateLiquidTemplateContent(content, {
|
|
403
|
-
getLiquidTags,
|
|
404
|
-
formatMessage,
|
|
405
|
-
messages,
|
|
406
|
-
onError,
|
|
407
|
-
onSuccess,
|
|
408
|
-
tagLookupMap,
|
|
409
|
-
eventContextTags
|
|
410
|
-
});
|
|
411
|
-
expect(onSuccess).toHaveBeenCalledWith(content, undefined);
|
|
412
|
-
expect(onError).not.toHaveBeenCalled();
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
it("calls onSuccess when API returns tag inside {% %} blocks but no errors", async () => {
|
|
416
|
-
const content = '{% for x in some.unsupported %} {{ x }} {% endfor %}';
|
|
417
|
-
const getLiquidTags = jest.fn((content, cb) =>
|
|
418
|
-
cb({ askAiraResponse: { errors: [], data: [{ name: "some.unsupported" }] }, isError: false })
|
|
419
|
-
);
|
|
420
|
-
await validateLiquidTemplateContent(content, {
|
|
421
|
-
getLiquidTags,
|
|
422
|
-
formatMessage,
|
|
423
|
-
messages,
|
|
424
|
-
onError,
|
|
425
|
-
onSuccess,
|
|
426
|
-
tagLookupMap,
|
|
427
|
-
eventContextTags
|
|
375
|
+
// outsideTag appears outside {% %} block, so it should be validated
|
|
376
|
+
// item.name appears inside block, so it should be skipped
|
|
377
|
+
expect(onError).toHaveBeenCalledWith({
|
|
378
|
+
standardErrors: [],
|
|
379
|
+
liquidErrors: [undefined],
|
|
380
|
+
tabType: undefined
|
|
428
381
|
});
|
|
429
|
-
expect(onSuccess).
|
|
430
|
-
expect(onError).not.toHaveBeenCalled();
|
|
382
|
+
expect(onSuccess).not.toHaveBeenCalled();
|
|
431
383
|
});
|
|
432
384
|
|
|
433
|
-
it("
|
|
385
|
+
it("should skip tags with dots that appear in {% %} syntax", async () => {
|
|
434
386
|
const content = '{% assign myVar = order.items %} Some text';
|
|
435
387
|
const getLiquidTags = jest.fn((content, cb) =>
|
|
436
388
|
cb({ askAiraResponse: { errors: [], data: [{ name: "order.items" }] }, isError: false })
|
|
@@ -444,6 +396,7 @@ describe("validateLiquidTemplateContent", () => {
|
|
|
444
396
|
tagLookupMap,
|
|
445
397
|
eventContextTags
|
|
446
398
|
});
|
|
399
|
+
// order.items appears in {% %} syntax, so it should be skipped
|
|
447
400
|
expect(onSuccess).toHaveBeenCalledWith(content, undefined);
|
|
448
401
|
expect(onError).not.toHaveBeenCalled();
|
|
449
402
|
});
|