@capillarytech/creatives-library 8.0.302 → 8.0.303
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/package.json +1 -1
- package/v2Components/FormBuilder/index.js +1 -1
- package/v2Components/HtmlEditor/HTMLEditor.js +1 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +132 -3
- package/v2Components/HtmlEditor/hooks/useValidation.js +12 -9
- package/v2Components/HtmlEditor/utils/htmlValidator.js +4 -2
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +2 -2
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +110 -18
package/package.json
CHANGED
|
@@ -1331,7 +1331,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1331
1331
|
}
|
|
1332
1332
|
onSubmitWrapper = (args) => {
|
|
1333
1333
|
const {singleTab = null} = args || {};
|
|
1334
|
-
if (this.liquidFlow()) {
|
|
1334
|
+
if (this.liquidFlow() && !this.props?.isFullMode) {
|
|
1335
1335
|
// For MPUSH, we need to validate both Android and iOS content separately
|
|
1336
1336
|
if (this.props.channel === MOBILE_PUSH || this.props?.schema?.channel?.toUpperCase() === MOBILE_PUSH) {
|
|
1337
1337
|
this.validateFormBuilderMPush(this.state.formData, singleTab);
|
|
@@ -294,6 +294,7 @@ const HTMLEditor = forwardRef(({
|
|
|
294
294
|
enableSanitization: true,
|
|
295
295
|
securityLevel: 'standard',
|
|
296
296
|
apiValidationErrors, // Pass API validation errors to merge with client-side validation
|
|
297
|
+
isFullMode, // Skip liquid validation in standalone/full mode
|
|
297
298
|
}, formatSanitizerMessage, formatValidatorMessage);
|
|
298
299
|
|
|
299
300
|
// Expose validation and content state via ref
|
|
@@ -155,7 +155,7 @@ describe('useValidation', () => {
|
|
|
155
155
|
await Promise.resolve();
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null);
|
|
158
|
+
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null, { skipLiquidValidation: false });
|
|
159
159
|
});
|
|
160
160
|
|
|
161
161
|
it('updates validation when content changes', async () => {
|
|
@@ -472,7 +472,7 @@ describe('useValidation', () => {
|
|
|
472
472
|
await Promise.resolve();
|
|
473
473
|
});
|
|
474
474
|
|
|
475
|
-
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'inapp', null);
|
|
475
|
+
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'inapp', null, { skipLiquidValidation: false });
|
|
476
476
|
});
|
|
477
477
|
|
|
478
478
|
it('defaults to email variant', async () => {
|
|
@@ -487,7 +487,7 @@ describe('useValidation', () => {
|
|
|
487
487
|
await Promise.resolve();
|
|
488
488
|
});
|
|
489
489
|
|
|
490
|
-
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null);
|
|
490
|
+
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null, { skipLiquidValidation: false });
|
|
491
491
|
});
|
|
492
492
|
});
|
|
493
493
|
|
|
@@ -1242,4 +1242,133 @@ describe('useValidation', () => {
|
|
|
1242
1242
|
});
|
|
1243
1243
|
});
|
|
1244
1244
|
});
|
|
1245
|
+
|
|
1246
|
+
describe('isFullMode - skip liquid validation', () => {
|
|
1247
|
+
it('passes skipLiquidValidation: true to validateHTML when isFullMode is true', async () => {
|
|
1248
|
+
const { validateHTML } = require('../../utils/htmlValidator');
|
|
1249
|
+
|
|
1250
|
+
render(<TestComponent content="<p>Test</p>" options={{ isFullMode: true }} />);
|
|
1251
|
+
|
|
1252
|
+
await act(async () => {
|
|
1253
|
+
jest.advanceTimersByTime(500);
|
|
1254
|
+
await Promise.resolve();
|
|
1255
|
+
await Promise.resolve();
|
|
1256
|
+
await Promise.resolve();
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null, { skipLiquidValidation: true });
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
it('excludes API liquid errors from getAllIssues when isFullMode is true', async () => {
|
|
1263
|
+
let validationState;
|
|
1264
|
+
const onStateChange = (state) => { validationState = state; };
|
|
1265
|
+
|
|
1266
|
+
const apiValidationErrors = {
|
|
1267
|
+
liquidErrors: ['Unsupported tag: points_balance'],
|
|
1268
|
+
standardErrors: [],
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
render(
|
|
1272
|
+
<TestComponent
|
|
1273
|
+
content="<p>Test</p>"
|
|
1274
|
+
options={{ apiValidationErrors, isFullMode: true }}
|
|
1275
|
+
onStateChange={onStateChange}
|
|
1276
|
+
/>
|
|
1277
|
+
);
|
|
1278
|
+
|
|
1279
|
+
await act(async () => {
|
|
1280
|
+
jest.advanceTimersByTime(500);
|
|
1281
|
+
await Promise.resolve();
|
|
1282
|
+
await Promise.resolve();
|
|
1283
|
+
await Promise.resolve();
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
await waitFor(() => {
|
|
1287
|
+
expect(validationState).toBeDefined();
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
const issues = validationState.getAllIssues();
|
|
1291
|
+
const liquidIssues = issues.filter((i) => i.source === 'liquid-validator');
|
|
1292
|
+
expect(liquidIssues).toHaveLength(0);
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
it('returns isClean true when only liquid errors exist and isFullMode is true', () => {
|
|
1296
|
+
const apiValidationErrors = {
|
|
1297
|
+
liquidErrors: ['Unsupported tag: points_balance'],
|
|
1298
|
+
standardErrors: [],
|
|
1299
|
+
};
|
|
1300
|
+
|
|
1301
|
+
render(
|
|
1302
|
+
<TestComponent
|
|
1303
|
+
content=""
|
|
1304
|
+
options={{ apiValidationErrors, isFullMode: true }}
|
|
1305
|
+
/>
|
|
1306
|
+
);
|
|
1307
|
+
|
|
1308
|
+
// Before validation runs, isClean should be true because liquid errors are ignored in full mode
|
|
1309
|
+
expect(screen.getByTestId('is-clean')).toHaveTextContent('true');
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
it('does not treat liquid errors as blocking when isFullMode is true', async () => {
|
|
1313
|
+
let validationState;
|
|
1314
|
+
const onStateChange = (state) => { validationState = state; };
|
|
1315
|
+
|
|
1316
|
+
const apiValidationErrors = {
|
|
1317
|
+
liquidErrors: ['Unsupported tag: points_balance'],
|
|
1318
|
+
standardErrors: [],
|
|
1319
|
+
};
|
|
1320
|
+
|
|
1321
|
+
render(
|
|
1322
|
+
<TestComponent
|
|
1323
|
+
content="<p>Valid</p>"
|
|
1324
|
+
options={{ apiValidationErrors, isFullMode: true }}
|
|
1325
|
+
onStateChange={onStateChange}
|
|
1326
|
+
/>
|
|
1327
|
+
);
|
|
1328
|
+
|
|
1329
|
+
await act(async () => {
|
|
1330
|
+
jest.advanceTimersByTime(500);
|
|
1331
|
+
await Promise.resolve();
|
|
1332
|
+
await Promise.resolve();
|
|
1333
|
+
await Promise.resolve();
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
await waitFor(() => {
|
|
1337
|
+
expect(validationState).toBeDefined();
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
expect(validationState.hasBlockingErrors).toBe(false);
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
it('still treats standard API errors as blocking when isFullMode is true', async () => {
|
|
1344
|
+
let validationState;
|
|
1345
|
+
const onStateChange = (state) => { validationState = state; };
|
|
1346
|
+
|
|
1347
|
+
const apiValidationErrors = {
|
|
1348
|
+
liquidErrors: ['Liquid error'],
|
|
1349
|
+
standardErrors: ['Standard error'],
|
|
1350
|
+
};
|
|
1351
|
+
|
|
1352
|
+
render(
|
|
1353
|
+
<TestComponent
|
|
1354
|
+
content="<p>Valid</p>"
|
|
1355
|
+
options={{ apiValidationErrors, isFullMode: true }}
|
|
1356
|
+
onStateChange={onStateChange}
|
|
1357
|
+
/>
|
|
1358
|
+
);
|
|
1359
|
+
|
|
1360
|
+
await act(async () => {
|
|
1361
|
+
jest.advanceTimersByTime(500);
|
|
1362
|
+
await Promise.resolve();
|
|
1363
|
+
await Promise.resolve();
|
|
1364
|
+
await Promise.resolve();
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
await waitFor(() => {
|
|
1368
|
+
expect(validationState).toBeDefined();
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
expect(validationState.hasBlockingErrors).toBe(true);
|
|
1372
|
+
});
|
|
1373
|
+
});
|
|
1245
1374
|
});
|
|
@@ -77,6 +77,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
77
77
|
enableSanitization = true,
|
|
78
78
|
securityLevel = 'standard',
|
|
79
79
|
apiValidationErrors = null, // API validation errors from validateLiquidTemplateContent
|
|
80
|
+
isFullMode = false, // When true, skip liquid validation (standalone/full mode)
|
|
80
81
|
} = options;
|
|
81
82
|
|
|
82
83
|
// Validation state
|
|
@@ -137,7 +138,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
137
138
|
|
|
138
139
|
try {
|
|
139
140
|
// 1. HTML Validation
|
|
140
|
-
const htmlValidation = validateHTML(htmlContent, variant, formatValidatorMessage);
|
|
141
|
+
const htmlValidation = validateHTML(htmlContent, variant, formatValidatorMessage, { skipLiquidValidation: isFullMode });
|
|
141
142
|
|
|
142
143
|
// 2. CSS Validation (extract from HTML)
|
|
143
144
|
const cssValidation = extractAndValidateCSS(htmlContent, formatValidatorMessage);
|
|
@@ -206,7 +207,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
206
207
|
},
|
|
207
208
|
}));
|
|
208
209
|
}
|
|
209
|
-
}, [variant, enableSanitization, securityLevel, formatSanitizerMessage, formatValidatorMessage]);
|
|
210
|
+
}, [variant, enableSanitization, securityLevel, formatSanitizerMessage, formatValidatorMessage, isFullMode]);
|
|
210
211
|
|
|
211
212
|
/**
|
|
212
213
|
* Validates content with debouncing
|
|
@@ -339,7 +340,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
339
340
|
*/
|
|
340
341
|
const getAllIssues = useCallback(() => {
|
|
341
342
|
// API errors (liquid + standard) are blocking – they block Save/Update/Preview/Test
|
|
342
|
-
const apiLiquidErrors = (apiValidationErrors?.liquidErrors || []).map((errorMessage) => {
|
|
343
|
+
const apiLiquidErrors = (isFullMode ? [] : (apiValidationErrors?.liquidErrors || [])).map((errorMessage) => {
|
|
343
344
|
const extractedLine = extractLineNumberFromMessage(errorMessage);
|
|
344
345
|
return {
|
|
345
346
|
type: VALIDATION_SEVERITY.ERROR,
|
|
@@ -420,19 +421,20 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
420
421
|
|
|
421
422
|
// Ensure we always return an array
|
|
422
423
|
return Array.isArray(allIssues) ? allIssues : [];
|
|
423
|
-
}, [validationState, apiValidationErrors, extractLineNumberFromMessage, content]);
|
|
424
|
+
}, [validationState, apiValidationErrors, extractLineNumberFromMessage, content, isFullMode]);
|
|
424
425
|
|
|
425
426
|
/**
|
|
426
427
|
* Check if validation is clean (no errors or warnings)
|
|
427
428
|
* Includes API validation errors in the check
|
|
428
429
|
*/
|
|
429
430
|
const isClean = useCallback(() => {
|
|
430
|
-
const
|
|
431
|
+
const liquidErrorCount = isFullMode ? 0 : (apiValidationErrors?.liquidErrors?.length || 0);
|
|
432
|
+
const hasApiErrors = liquidErrorCount + (apiValidationErrors?.standardErrors?.length || 0) > 0;
|
|
431
433
|
return validationState.summary.totalErrors === 0
|
|
432
434
|
&& validationState.summary.totalWarnings === 0
|
|
433
435
|
&& !validationState.summary.hasSecurityIssues
|
|
434
436
|
&& !hasApiErrors;
|
|
435
|
-
}, [validationState.summary, apiValidationErrors]);
|
|
437
|
+
}, [validationState.summary, apiValidationErrors, isFullMode]);
|
|
436
438
|
|
|
437
439
|
// Effect to validate content when it changes
|
|
438
440
|
useEffect(() => {
|
|
@@ -448,11 +450,12 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
448
450
|
}
|
|
449
451
|
}, []);
|
|
450
452
|
|
|
451
|
-
const
|
|
453
|
+
const hasApiLiquidErrors = isFullMode ? false : (apiValidationErrors?.liquidErrors?.length || 0) > 0;
|
|
454
|
+
const hasApiErrors = hasApiLiquidErrors || (apiValidationErrors?.standardErrors?.length || 0) > 0;
|
|
452
455
|
|
|
453
456
|
const protocolTypes = ['JavaScript Protocol', 'Data URL', 'VBScript Protocol'];
|
|
454
|
-
// Client-side Liquid validation errors are blocking (genuine syntax errors)
|
|
455
|
-
const hasClientSideLiquidErrors = (validationState.htmlErrors || []).some((e) => e.source === ISSUE_SOURCES.LIQUID && e.severity === VALIDATION_SEVERITY.ERROR);
|
|
457
|
+
// Client-side Liquid validation errors are blocking (genuine syntax errors) - skip in full mode
|
|
458
|
+
const hasClientSideLiquidErrors = isFullMode ? false : (validationState.htmlErrors || []).some((e) => e.source === ISSUE_SOURCES.LIQUID && e.severity === VALIDATION_SEVERITY.ERROR);
|
|
456
459
|
const hasBlockingErrors = (validationState.sanitizationWarnings || []).some((w) => BLOCKING_ERROR_RULE_IDS.includes(w.rule)) || (validationState.securityIssues || []).some((s) => protocolTypes.includes(s?.type)) || hasApiErrors || hasClientSideLiquidErrors;
|
|
457
460
|
|
|
458
461
|
return {
|
|
@@ -76,7 +76,7 @@ const CUSTOM_VALIDATIONS = {
|
|
|
76
76
|
* @param {Function} formatMessage - Message formatter function for internationalization
|
|
77
77
|
* @returns {Object} Validation result with errors and warnings
|
|
78
78
|
*/
|
|
79
|
-
export const validateHTML = (html, variant = 'email', formatMessage = defaultMessageFormatter) => {
|
|
79
|
+
export const validateHTML = (html, variant = 'email', formatMessage = defaultMessageFormatter, options = {}) => {
|
|
80
80
|
if (!html || typeof html !== 'string') {
|
|
81
81
|
return {
|
|
82
82
|
isValid: true,
|
|
@@ -133,7 +133,9 @@ export const validateHTML = (html, variant = 'email', formatMessage = defaultMes
|
|
|
133
133
|
// Always run custom validations and Liquid validation, even if HTMLHint failed
|
|
134
134
|
// This ensures unsafe protocol detection and other critical validations still run
|
|
135
135
|
runCustomValidations(html, variant, results, formatMessage);
|
|
136
|
-
|
|
136
|
+
if (!options.skipLiquidValidation) {
|
|
137
|
+
runLiquidValidation(html, variant, results, formatMessage);
|
|
138
|
+
}
|
|
137
139
|
|
|
138
140
|
return results;
|
|
139
141
|
};
|
|
@@ -956,8 +956,8 @@ const EmailHTMLEditor = (props) => {
|
|
|
956
956
|
}
|
|
957
957
|
};
|
|
958
958
|
|
|
959
|
-
// If liquid enabled, validate first using extractTags API
|
|
960
|
-
if (isLiquidEnabled && getLiquidTags) {
|
|
959
|
+
// If liquid enabled, validate first using extractTags API (skip in full/standalone mode)
|
|
960
|
+
if (isLiquidEnabled && getLiquidTags && !isFullMode) {
|
|
961
961
|
// Note: API validation errors are already cleared at the start of handleSave
|
|
962
962
|
// This ensures fresh validation on every save attempt
|
|
963
963
|
|
|
@@ -1176,9 +1176,10 @@ describe('EmailHTMLEditor', () => {
|
|
|
1176
1176
|
mockGetAllIssues.mockReturnValue([]);
|
|
1177
1177
|
|
|
1178
1178
|
// Set subject and content via component interactions
|
|
1179
|
+
// Use isFullMode: false (library mode) to test liquid validation path
|
|
1179
1180
|
const { rerender } = renderWithIntl({
|
|
1180
1181
|
isGetFormData: false,
|
|
1181
|
-
isFullMode:
|
|
1182
|
+
isFullMode: false,
|
|
1182
1183
|
metaEntities: {
|
|
1183
1184
|
tags: {
|
|
1184
1185
|
standard: [{ name: 'customer.name' }],
|
|
@@ -1206,7 +1207,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1206
1207
|
<EmailHTMLEditor
|
|
1207
1208
|
{...defaultProps}
|
|
1208
1209
|
isGetFormData
|
|
1209
|
-
isFullMode
|
|
1210
|
+
isFullMode={false}
|
|
1210
1211
|
metaEntities={{
|
|
1211
1212
|
tags: {
|
|
1212
1213
|
standard: [{ name: 'customer.name' }],
|
|
@@ -1231,9 +1232,10 @@ describe('EmailHTMLEditor', () => {
|
|
|
1231
1232
|
mockGetAllIssues.mockReturnValue([]);
|
|
1232
1233
|
|
|
1233
1234
|
// Set subject and content via component interactions
|
|
1235
|
+
// Use isFullMode: false (library mode) to test liquid validation path
|
|
1234
1236
|
const { rerender } = renderWithIntl({
|
|
1235
1237
|
isGetFormData: false,
|
|
1236
|
-
isFullMode:
|
|
1238
|
+
isFullMode: false,
|
|
1237
1239
|
isLiquidEnabled: true,
|
|
1238
1240
|
getLiquidTags,
|
|
1239
1241
|
});
|
|
@@ -1253,7 +1255,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1253
1255
|
await act(async () => {
|
|
1254
1256
|
rerender(
|
|
1255
1257
|
<IntlProvider locale="en" messages={{}}>
|
|
1256
|
-
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} />
|
|
1258
|
+
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode={false} getLiquidTags={getLiquidTags} />
|
|
1257
1259
|
</IntlProvider>
|
|
1258
1260
|
);
|
|
1259
1261
|
});
|
|
@@ -1281,9 +1283,10 @@ describe('EmailHTMLEditor', () => {
|
|
|
1281
1283
|
mockGetAllIssues.mockReturnValue([]);
|
|
1282
1284
|
|
|
1283
1285
|
// Set subject and content via component interactions
|
|
1286
|
+
// Use isFullMode: false (library mode) to test liquid validation path
|
|
1284
1287
|
const { rerender } = renderWithIntl({
|
|
1285
1288
|
isGetFormData: false,
|
|
1286
|
-
isFullMode:
|
|
1289
|
+
isFullMode: false,
|
|
1287
1290
|
isLiquidEnabled: true,
|
|
1288
1291
|
getLiquidTags,
|
|
1289
1292
|
showLiquidErrorInFooter,
|
|
@@ -1305,7 +1308,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1305
1308
|
await act(async () => {
|
|
1306
1309
|
rerender(
|
|
1307
1310
|
<IntlProvider locale="en" messages={{}}>
|
|
1308
|
-
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} showLiquidErrorInFooter={showLiquidErrorInFooter} onValidationFail={onValidationFail} />
|
|
1311
|
+
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode={false} getLiquidTags={getLiquidTags} showLiquidErrorInFooter={showLiquidErrorInFooter} onValidationFail={onValidationFail} />
|
|
1309
1312
|
</IntlProvider>
|
|
1310
1313
|
);
|
|
1311
1314
|
});
|
|
@@ -1325,13 +1328,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1325
1328
|
return Promise.resolve(true);
|
|
1326
1329
|
});
|
|
1327
1330
|
|
|
1328
|
-
const
|
|
1329
|
-
...defaultProps.emailActions,
|
|
1330
|
-
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
1331
|
-
createTemplate: jest.fn((obj, callback) => {
|
|
1332
|
-
callback({ templateId: { _id: '123', versions: {} } });
|
|
1333
|
-
}),
|
|
1334
|
-
};
|
|
1331
|
+
const getFormdata = jest.fn();
|
|
1335
1332
|
const getLiquidTags = jest.fn((content, callback) => {
|
|
1336
1333
|
callback({ askAiraResponse: { data: [] }, isError: false });
|
|
1337
1334
|
});
|
|
@@ -1339,12 +1336,12 @@ describe('EmailHTMLEditor', () => {
|
|
|
1339
1336
|
mockGetAllIssues.mockReturnValue([]);
|
|
1340
1337
|
|
|
1341
1338
|
// Set subject and content via component interactions
|
|
1339
|
+
// Use isFullMode: false (library mode) to test liquid validation path
|
|
1342
1340
|
const { rerender } = renderWithIntl({
|
|
1343
1341
|
isGetFormData: false,
|
|
1344
|
-
isFullMode:
|
|
1345
|
-
isLiquidEnabled: true,
|
|
1342
|
+
isFullMode: false,
|
|
1346
1343
|
getLiquidTags,
|
|
1347
|
-
|
|
1344
|
+
getFormdata,
|
|
1348
1345
|
templateName: 'New Template',
|
|
1349
1346
|
});
|
|
1350
1347
|
const input = screen.getByTestId('subject-input');
|
|
@@ -1363,7 +1360,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1363
1360
|
await act(async () => {
|
|
1364
1361
|
rerender(
|
|
1365
1362
|
<IntlProvider locale="en" messages={{}}>
|
|
1366
|
-
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags}
|
|
1363
|
+
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode={false} getLiquidTags={getLiquidTags} getFormdata={getFormdata} templateName="New Template" />
|
|
1367
1364
|
</IntlProvider>
|
|
1368
1365
|
);
|
|
1369
1366
|
});
|
|
@@ -1372,8 +1369,9 @@ describe('EmailHTMLEditor', () => {
|
|
|
1372
1369
|
expect(validateLiquidTemplateContent).toHaveBeenCalled();
|
|
1373
1370
|
}, { timeout: 5000 });
|
|
1374
1371
|
|
|
1372
|
+
// In library mode (isFullMode=false), save proceeds via getFormdata
|
|
1375
1373
|
await waitFor(() => {
|
|
1376
|
-
expect(
|
|
1374
|
+
expect(getFormdata).toHaveBeenCalled();
|
|
1377
1375
|
}, { timeout: 5000 });
|
|
1378
1376
|
});
|
|
1379
1377
|
|
|
@@ -2711,4 +2709,98 @@ describe('EmailHTMLEditor', () => {
|
|
|
2711
2709
|
expect(screen.getByTestId('html-editor')).toBeInTheDocument();
|
|
2712
2710
|
});
|
|
2713
2711
|
});
|
|
2712
|
+
|
|
2713
|
+
describe('isFullMode - skip liquid validation on save', () => {
|
|
2714
|
+
it('skips liquid validation when isFullMode is true', async () => {
|
|
2715
|
+
validateLiquidTemplateContent.mockClear();
|
|
2716
|
+
const getLiquidTags = jest.fn((content, callback) => {
|
|
2717
|
+
callback({ askAiraResponse: { data: [] }, isError: false });
|
|
2718
|
+
});
|
|
2719
|
+
const getFormdata = jest.fn();
|
|
2720
|
+
// Ensure no HTML/Label/Liquid errors from HtmlEditor
|
|
2721
|
+
mockGetAllIssues.mockReturnValue([]);
|
|
2722
|
+
|
|
2723
|
+
const { rerender } = renderWithIntl({
|
|
2724
|
+
isGetFormData: false,
|
|
2725
|
+
isFullMode: true,
|
|
2726
|
+
isLiquidEnabled: true,
|
|
2727
|
+
getLiquidTags,
|
|
2728
|
+
getFormdata,
|
|
2729
|
+
});
|
|
2730
|
+
const input = screen.getByTestId('subject-input');
|
|
2731
|
+
await act(async () => {
|
|
2732
|
+
fireEvent.change(input, { target: { value: 'Valid Subject' } });
|
|
2733
|
+
});
|
|
2734
|
+
const changeButton = screen.getByTestId('trigger-content-change');
|
|
2735
|
+
await act(async () => {
|
|
2736
|
+
fireEvent.click(changeButton);
|
|
2737
|
+
});
|
|
2738
|
+
await act(async () => {
|
|
2739
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2740
|
+
});
|
|
2741
|
+
// Trigger save
|
|
2742
|
+
await act(async () => {
|
|
2743
|
+
rerender(
|
|
2744
|
+
<IntlProvider locale="en" messages={{}}>
|
|
2745
|
+
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} getFormdata={getFormdata} />
|
|
2746
|
+
</IntlProvider>
|
|
2747
|
+
);
|
|
2748
|
+
});
|
|
2749
|
+
|
|
2750
|
+
// In full mode, liquid validation should be skipped entirely
|
|
2751
|
+
await act(async () => {
|
|
2752
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
2753
|
+
});
|
|
2754
|
+
expect(validateLiquidTemplateContent).not.toHaveBeenCalled();
|
|
2755
|
+
});
|
|
2756
|
+
|
|
2757
|
+
it('proceeds directly to save without liquid validation in full mode', async () => {
|
|
2758
|
+
validateLiquidTemplateContent.mockClear();
|
|
2759
|
+
const getLiquidTags = jest.fn((content, callback) => {
|
|
2760
|
+
callback({ askAiraResponse: { data: [] }, isError: false });
|
|
2761
|
+
});
|
|
2762
|
+
const emailActions = {
|
|
2763
|
+
...defaultProps.emailActions,
|
|
2764
|
+
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
2765
|
+
createTemplate: jest.fn((obj, callback) => {
|
|
2766
|
+
callback({ templateId: { _id: '123', versions: {} } });
|
|
2767
|
+
}),
|
|
2768
|
+
};
|
|
2769
|
+
// Ensure no HTML/Label/Liquid errors from HtmlEditor
|
|
2770
|
+
mockGetAllIssues.mockReturnValue([]);
|
|
2771
|
+
|
|
2772
|
+
const { rerender } = renderWithIntl({
|
|
2773
|
+
isGetFormData: false,
|
|
2774
|
+
isFullMode: true,
|
|
2775
|
+
getLiquidTags,
|
|
2776
|
+
emailActions,
|
|
2777
|
+
templateName: 'New Template',
|
|
2778
|
+
});
|
|
2779
|
+
const input = screen.getByTestId('subject-input');
|
|
2780
|
+
await act(async () => {
|
|
2781
|
+
fireEvent.change(input, { target: { value: 'Valid Subject' } });
|
|
2782
|
+
});
|
|
2783
|
+
const changeButton = screen.getByTestId('trigger-content-change');
|
|
2784
|
+
await act(async () => {
|
|
2785
|
+
fireEvent.click(changeButton);
|
|
2786
|
+
});
|
|
2787
|
+
await act(async () => {
|
|
2788
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2789
|
+
});
|
|
2790
|
+
// Trigger save
|
|
2791
|
+
await act(async () => {
|
|
2792
|
+
rerender(
|
|
2793
|
+
<IntlProvider locale="en" messages={{}}>
|
|
2794
|
+
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} emailActions={emailActions} templateName="New Template" />
|
|
2795
|
+
</IntlProvider>
|
|
2796
|
+
);
|
|
2797
|
+
});
|
|
2798
|
+
|
|
2799
|
+
// Should skip liquid validation and proceed to save directly
|
|
2800
|
+
await waitFor(() => {
|
|
2801
|
+
expect(emailActions.createTemplate).toHaveBeenCalled();
|
|
2802
|
+
}, { timeout: 5000 });
|
|
2803
|
+
expect(validateLiquidTemplateContent).not.toHaveBeenCalled();
|
|
2804
|
+
});
|
|
2805
|
+
});
|
|
2714
2806
|
});
|