@capillarytech/creatives-library 8.0.262-alpha.1 → 8.0.263
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/ErrorInfoNote/index.js +58 -113
- package/v2Components/ErrorInfoNote/messages.js +8 -12
- package/v2Components/HtmlEditor/HTMLEditor.js +48 -182
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +11 -15
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +7 -8
- package/v2Components/HtmlEditor/_htmlEditor.scss +6 -6
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +1 -1
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +0 -1
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +27 -2
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +4 -4
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +17 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +15 -28
- package/v2Components/HtmlEditor/components/ValidationPanel/index.js +10 -33
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +48 -13
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +77 -146
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +14 -14
- package/v2Components/HtmlEditor/constants.js +3 -0
- package/v2Components/HtmlEditor/hooks/useValidation.js +34 -13
- package/v2Components/HtmlEditor/messages.js +10 -0
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +38 -36
- package/v2Components/HtmlEditor/utils/validationConstants.js +3 -4
- package/v2Components/MobilePushPreviewV2/constants.js +6 -0
- package/v2Components/MobilePushPreviewV2/index.js +4 -3
- package/v2Containers/CreativesContainer/index.js +1 -3
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +6 -9
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +5 -49
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +23 -38
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +4 -2
|
@@ -7,6 +7,8 @@ import { html } from '@codemirror/lang-html';
|
|
|
7
7
|
import { syntaxHighlighting, HighlightStyle } from '@codemirror/language';
|
|
8
8
|
import { tags } from '@lezer/highlight';
|
|
9
9
|
import { EditorView } from '@codemirror/view';
|
|
10
|
+
import { VALIDATION_SEVERITY } from '../constants';
|
|
11
|
+
import { ISSUE_SOURCES } from './validationConstants';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Liquid Template Syntax Patterns
|
|
@@ -145,13 +147,13 @@ export class LiquidValidator {
|
|
|
145
147
|
} else {
|
|
146
148
|
// Stray closing brace - no matching opening brace
|
|
147
149
|
this.errors.push({
|
|
148
|
-
type:
|
|
150
|
+
type: VALIDATION_SEVERITY.ERROR,
|
|
149
151
|
message: 'Stray closing }} without matching opening {{',
|
|
150
152
|
line: this.getLineNumber(html, position),
|
|
151
153
|
column: 1,
|
|
152
154
|
rule: 'liquid-stray-closing-output',
|
|
153
|
-
severity:
|
|
154
|
-
source:
|
|
155
|
+
severity: VALIDATION_SEVERITY.ERROR,
|
|
156
|
+
source: ISSUE_SOURCES.LIQUID,
|
|
155
157
|
});
|
|
156
158
|
}
|
|
157
159
|
}
|
|
@@ -162,13 +164,13 @@ export class LiquidValidator {
|
|
|
162
164
|
// Report each unclosed opening brace
|
|
163
165
|
stack.forEach((position) => {
|
|
164
166
|
this.errors.push({
|
|
165
|
-
type:
|
|
167
|
+
type: VALIDATION_SEVERITY.ERROR,
|
|
166
168
|
message: 'unclosed Liquid output tag - missing }}',
|
|
167
169
|
line: this.getLineNumber(html, position),
|
|
168
170
|
column: 1,
|
|
169
171
|
rule: 'liquid-unclosed-output',
|
|
170
|
-
severity:
|
|
171
|
-
source:
|
|
172
|
+
severity: VALIDATION_SEVERITY.ERROR,
|
|
173
|
+
source: ISSUE_SOURCES.LIQUID,
|
|
172
174
|
});
|
|
173
175
|
});
|
|
174
176
|
}
|
|
@@ -197,13 +199,13 @@ export class LiquidValidator {
|
|
|
197
199
|
if (openTags.length > closeTags.length) {
|
|
198
200
|
const unmatchedCount = openTags.length - closeTags.length;
|
|
199
201
|
this.errors.push({
|
|
200
|
-
type:
|
|
202
|
+
type: VALIDATION_SEVERITY.ERROR,
|
|
201
203
|
message: `${unmatchedCount} unclosed Liquid logic tag(s) - missing %}`,
|
|
202
204
|
line: this.getLineNumber(html, openTags[openTags.length - 1]),
|
|
203
205
|
column: 1,
|
|
204
206
|
rule: 'liquid-unclosed-logic',
|
|
205
|
-
severity:
|
|
206
|
-
source:
|
|
207
|
+
severity: VALIDATION_SEVERITY.ERROR,
|
|
208
|
+
source: ISSUE_SOURCES.LIQUID,
|
|
207
209
|
});
|
|
208
210
|
}
|
|
209
211
|
}
|
|
@@ -218,13 +220,13 @@ export class LiquidValidator {
|
|
|
218
220
|
if (nestedOutput) {
|
|
219
221
|
nestedOutput.forEach((match) => {
|
|
220
222
|
this.errors.push({
|
|
221
|
-
type:
|
|
223
|
+
type: VALIDATION_SEVERITY.ERROR,
|
|
222
224
|
message: `Nested braces in Liquid output tag: ${match}`,
|
|
223
225
|
line: this.getLineNumber(html, html.indexOf(match)),
|
|
224
226
|
column: 1,
|
|
225
227
|
rule: 'liquid-nested-braces',
|
|
226
|
-
severity:
|
|
227
|
-
source:
|
|
228
|
+
severity: VALIDATION_SEVERITY.ERROR,
|
|
229
|
+
source: ISSUE_SOURCES.LIQUID,
|
|
228
230
|
});
|
|
229
231
|
});
|
|
230
232
|
}
|
|
@@ -235,13 +237,13 @@ export class LiquidValidator {
|
|
|
235
237
|
if (nestedLogic) {
|
|
236
238
|
nestedLogic.forEach((match) => {
|
|
237
239
|
this.errors.push({
|
|
238
|
-
type:
|
|
240
|
+
type: VALIDATION_SEVERITY.ERROR,
|
|
239
241
|
message: `Nested braces in Liquid logic tag: ${match}`,
|
|
240
242
|
line: this.getLineNumber(html, html.indexOf(match)),
|
|
241
243
|
column: 1,
|
|
242
244
|
rule: 'liquid-nested-braces',
|
|
243
|
-
severity:
|
|
244
|
-
source:
|
|
245
|
+
severity: VALIDATION_SEVERITY.ERROR,
|
|
246
|
+
source: ISSUE_SOURCES.LIQUID,
|
|
245
247
|
});
|
|
246
248
|
});
|
|
247
249
|
}
|
|
@@ -302,23 +304,23 @@ export class LiquidValidator {
|
|
|
302
304
|
|
|
303
305
|
if (!lastOpening) {
|
|
304
306
|
this.errors.push({
|
|
305
|
-
type:
|
|
307
|
+
type: VALIDATION_SEVERITY.ERROR,
|
|
306
308
|
message: `Unexpected closing tag: {% ${keyword} %}`,
|
|
307
309
|
line: tag.line,
|
|
308
310
|
column: 1,
|
|
309
311
|
rule: 'liquid-unexpected-closing',
|
|
310
|
-
severity:
|
|
311
|
-
source:
|
|
312
|
+
severity: VALIDATION_SEVERITY.ERROR,
|
|
313
|
+
source: ISSUE_SOURCES.LIQUID,
|
|
312
314
|
});
|
|
313
315
|
} else if (lastOpening.keyword !== expectedOpening) {
|
|
314
316
|
this.errors.push({
|
|
315
|
-
type:
|
|
317
|
+
type: VALIDATION_SEVERITY.ERROR,
|
|
316
318
|
message: `Mismatched Liquid tags: {% ${lastOpening.keyword} %} ... {% ${keyword} %}`,
|
|
317
319
|
line: tag.line,
|
|
318
320
|
column: 1,
|
|
319
321
|
rule: 'liquid-mismatched-tags',
|
|
320
|
-
severity:
|
|
321
|
-
source:
|
|
322
|
+
severity: VALIDATION_SEVERITY.ERROR,
|
|
323
|
+
source: ISSUE_SOURCES.LIQUID,
|
|
322
324
|
});
|
|
323
325
|
}
|
|
324
326
|
}
|
|
@@ -329,13 +331,13 @@ export class LiquidValidator {
|
|
|
329
331
|
stack.forEach(({ keyword, tag }) => {
|
|
330
332
|
const expectedClosing = pairs[keyword];
|
|
331
333
|
const error = {
|
|
332
|
-
type:
|
|
334
|
+
type: VALIDATION_SEVERITY.ERROR,
|
|
333
335
|
message: `Unclosed Liquid tag: {% ${keyword} %} - missing {% ${expectedClosing} %}`,
|
|
334
336
|
line: tag.line,
|
|
335
337
|
column: 1,
|
|
336
338
|
rule: 'liquid-unclosed-tag',
|
|
337
|
-
severity:
|
|
338
|
-
source:
|
|
339
|
+
severity: VALIDATION_SEVERITY.ERROR,
|
|
340
|
+
source: ISSUE_SOURCES.LIQUID,
|
|
339
341
|
};
|
|
340
342
|
this.errors.push(error);
|
|
341
343
|
});
|
|
@@ -344,13 +346,13 @@ export class LiquidValidator {
|
|
|
344
346
|
// Check for unclosed opening tags
|
|
345
347
|
stack.forEach((unclosed) => {
|
|
346
348
|
this.errors.push({
|
|
347
|
-
type:
|
|
349
|
+
type: VALIDATION_SEVERITY.ERROR,
|
|
348
350
|
message: `Unclosed Liquid tag: {% ${unclosed.keyword} %}`,
|
|
349
351
|
line: unclosed.tag.line,
|
|
350
352
|
column: 1,
|
|
351
353
|
rule: 'liquid-unclosed-tag',
|
|
352
|
-
severity:
|
|
353
|
-
source:
|
|
354
|
+
severity: VALIDATION_SEVERITY.ERROR,
|
|
355
|
+
source: ISSUE_SOURCES.LIQUID,
|
|
354
356
|
});
|
|
355
357
|
});
|
|
356
358
|
}
|
|
@@ -364,13 +366,13 @@ export class LiquidValidator {
|
|
|
364
366
|
if (malformedFilters) {
|
|
365
367
|
malformedFilters.forEach((match) => {
|
|
366
368
|
this.warnings.push({
|
|
367
|
-
type:
|
|
369
|
+
type: VALIDATION_SEVERITY.WARNING,
|
|
368
370
|
message: `Malformed Liquid filter: ${match.trim()}`,
|
|
369
371
|
line: this.getLineNumber(html, html.indexOf(match)),
|
|
370
372
|
column: 1,
|
|
371
373
|
rule: 'liquid-malformed-filter',
|
|
372
|
-
severity:
|
|
373
|
-
source:
|
|
374
|
+
severity: VALIDATION_SEVERITY.WARNING,
|
|
375
|
+
source: ISSUE_SOURCES.LIQUID,
|
|
374
376
|
});
|
|
375
377
|
});
|
|
376
378
|
}
|
|
@@ -401,13 +403,13 @@ export class LiquidValidator {
|
|
|
401
403
|
// Only show info for truly custom/unknown filters
|
|
402
404
|
// Standard Liquid filters are now included in commonFilters list
|
|
403
405
|
this.info.push({
|
|
404
|
-
type:
|
|
406
|
+
type: VALIDATION_SEVERITY.INFO,
|
|
405
407
|
message: `Using filter: ${filterName}`,
|
|
406
408
|
line: this.getLineNumber(html, filterMatch.index),
|
|
407
409
|
column: 1,
|
|
408
410
|
rule: 'liquid-filter-usage',
|
|
409
|
-
severity:
|
|
410
|
-
source:
|
|
411
|
+
severity: VALIDATION_SEVERITY.INFO,
|
|
412
|
+
source: ISSUE_SOURCES.LIQUID,
|
|
411
413
|
});
|
|
412
414
|
}
|
|
413
415
|
}
|
|
@@ -422,13 +424,13 @@ export class LiquidValidator {
|
|
|
422
424
|
if (suspiciousVariables) {
|
|
423
425
|
suspiciousVariables.forEach((match) => {
|
|
424
426
|
this.warnings.push({
|
|
425
|
-
type:
|
|
427
|
+
type: VALIDATION_SEVERITY.WARNING,
|
|
426
428
|
message: `Potentially undefined variable: ${match}`,
|
|
427
429
|
line: this.getLineNumber(html, html.indexOf(match)),
|
|
428
430
|
column: 1,
|
|
429
431
|
rule: 'liquid-undefined-variable',
|
|
430
|
-
severity:
|
|
431
|
-
source:
|
|
432
|
+
severity: VALIDATION_SEVERITY.WARNING,
|
|
433
|
+
source: ISSUE_SOURCES.LIQUID,
|
|
432
434
|
});
|
|
433
435
|
});
|
|
434
436
|
}
|
|
@@ -31,9 +31,8 @@ export const LABEL_ISSUE_PATTERNS = [
|
|
|
31
31
|
'alt-require',
|
|
32
32
|
];
|
|
33
33
|
|
|
34
|
-
// Tab keys
|
|
34
|
+
// Tab keys: Errors = blocking, Warnings = non-blocking
|
|
35
35
|
export const ERROR_TAB_KEYS = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
LIQUID: 'liquid',
|
|
36
|
+
ERRORS: 'errors',
|
|
37
|
+
WARNINGS: 'warnings',
|
|
39
38
|
};
|
|
@@ -16,6 +16,7 @@ import { INAPP } from '../../v2Containers/App/constants';
|
|
|
16
16
|
import { ANDROID, IOS } from '../../v2Containers/InApp/constants';
|
|
17
17
|
import { getCtaObject } from '../../v2Containers/InApp/utils';
|
|
18
18
|
import { CAROUSEL, VIDEO } from '../../v2Containers/MobilePushNew/constants';
|
|
19
|
+
import { DEVICE_TYPES, IPHONE } from './constants';
|
|
19
20
|
|
|
20
21
|
class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
|
21
22
|
constructor(props) {
|
|
@@ -57,7 +58,7 @@ class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react
|
|
|
57
58
|
const isBeeFreeTemplate = get(androidContent, 'isBEEeditor') || get(iosContent, 'isBEEeditor');
|
|
58
59
|
if (isBeeFreeTemplate) {
|
|
59
60
|
// Normalize device to 'android' or 'ios' for comparison
|
|
60
|
-
const normalizedDevice = device ===
|
|
61
|
+
const normalizedDevice = device === IPHONE ? DEVICE_TYPES.IOS : device?.toLowerCase();
|
|
61
62
|
const isAndroid = normalizedDevice === ANDROID.toLowerCase();
|
|
62
63
|
content = {
|
|
63
64
|
inAppPreviewContent: isAndroid ? androidContent?.beeHtml : iosContent?.beeHtml,
|
|
@@ -78,7 +79,7 @@ class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react
|
|
|
78
79
|
ctaData: getCtaObject(iosContent?.expandableDetails?.ctas),
|
|
79
80
|
};
|
|
80
81
|
// Normalize device to 'android' or 'ios' for comparison
|
|
81
|
-
const normalizedDevice = device ===
|
|
82
|
+
const normalizedDevice = device === IPHONE ? DEVICE_TYPES.IOS : device?.toLowerCase();
|
|
82
83
|
const isAndroid = normalizedDevice === ANDROID.toLowerCase();
|
|
83
84
|
content = {
|
|
84
85
|
inAppPreviewContent: isAndroid ? androidPreviewContent : iosPreviewContent,
|
|
@@ -149,7 +150,7 @@ class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react
|
|
|
149
150
|
|
|
150
151
|
getPreview(device) {
|
|
151
152
|
// Normalize device to 'android' or 'ios' for comparison
|
|
152
|
-
const normalizedDevice = device ===
|
|
153
|
+
const normalizedDevice = device === IPHONE ? DEVICE_TYPES.IOS : device?.toLowerCase();
|
|
153
154
|
const deviceParam = normalizedDevice === ANDROID.toLowerCase() ? ANDROID : IOS;
|
|
154
155
|
return (
|
|
155
156
|
<TemplatePreview
|
|
@@ -126,9 +126,7 @@ export class Creatives extends React.Component {
|
|
|
126
126
|
// HTML Editor validation state (for email channel)
|
|
127
127
|
htmlEditorValidationState: {
|
|
128
128
|
isContentEmpty: true,
|
|
129
|
-
issueCounts: {
|
|
130
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
131
|
-
},
|
|
129
|
+
issueCounts: { errors: 0, warnings: 0, total: 0 },
|
|
132
130
|
validationComplete: false, // Flag to track if validation has completed
|
|
133
131
|
errorsAcknowledged: false, // Flag to track if user has acknowledged errors by clicking redirection icon
|
|
134
132
|
},
|
|
@@ -262,10 +262,9 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit all data
|
|
|
262
262
|
"errorsAcknowledged": false,
|
|
263
263
|
"isContentEmpty": true,
|
|
264
264
|
"issueCounts": Object {
|
|
265
|
-
"
|
|
266
|
-
"label": 0,
|
|
267
|
-
"liquid": 0,
|
|
265
|
+
"errors": 0,
|
|
268
266
|
"total": 0,
|
|
267
|
+
"warnings": 0,
|
|
269
268
|
},
|
|
270
269
|
"validationComplete": false,
|
|
271
270
|
}
|
|
@@ -392,10 +391,9 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit min data
|
|
|
392
391
|
"errorsAcknowledged": false,
|
|
393
392
|
"isContentEmpty": true,
|
|
394
393
|
"issueCounts": Object {
|
|
395
|
-
"
|
|
396
|
-
"label": 0,
|
|
397
|
-
"liquid": 0,
|
|
394
|
+
"errors": 0,
|
|
398
395
|
"total": 0,
|
|
396
|
+
"warnings": 0,
|
|
399
397
|
},
|
|
400
398
|
"validationComplete": false,
|
|
401
399
|
}
|
|
@@ -522,10 +520,9 @@ exports[`Test SlideBoxContent container it should clear the url, on channel chan
|
|
|
522
520
|
"errorsAcknowledged": false,
|
|
523
521
|
"isContentEmpty": true,
|
|
524
522
|
"issueCounts": Object {
|
|
525
|
-
"
|
|
526
|
-
"label": 0,
|
|
527
|
-
"liquid": 0,
|
|
523
|
+
"errors": 0,
|
|
528
524
|
"total": 0,
|
|
525
|
+
"warnings": 0,
|
|
529
526
|
},
|
|
530
527
|
"validationComplete": false,
|
|
531
528
|
}
|
|
@@ -651,55 +651,11 @@ const EmailHTMLEditor = (props) => {
|
|
|
651
651
|
setSubjectError('');
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
-
// 1.5. Check for
|
|
655
|
-
//
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
};
|
|
660
|
-
let allIssues = [];
|
|
661
|
-
if (htmlEditorRef.current && typeof htmlEditorRef.current.getAllIssues === 'function') {
|
|
662
|
-
allIssues = htmlEditorRef.current.getAllIssues();
|
|
663
|
-
// Filter out API validation errors - they're validated separately via API call
|
|
664
|
-
const clientSideIssues = allIssues.filter((issue) => issue.rule !== 'liquid-api-validation' && issue.rule !== 'standard-api-validation');
|
|
665
|
-
|
|
666
|
-
// Count only client-side errors
|
|
667
|
-
issueCounts = {
|
|
668
|
-
html: 0,
|
|
669
|
-
label: 0,
|
|
670
|
-
liquid: 0,
|
|
671
|
-
total: clientSideIssues.length,
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
clientSideIssues.forEach((issue) => {
|
|
675
|
-
const { source, rule, message } = issue;
|
|
676
|
-
const messageLower = (message || '').toLowerCase();
|
|
677
|
-
const ruleLower = (rule || '').toLowerCase();
|
|
678
|
-
|
|
679
|
-
// Check if it's a liquid error (client-side only, not API)
|
|
680
|
-
if (source === 'liquid-validator' && rule !== 'liquid-api-validation') {
|
|
681
|
-
issueCounts.liquid++;
|
|
682
|
-
} else if (
|
|
683
|
-
messageLower.includes('tag must be paired')
|
|
684
|
-
|| messageLower.includes('open tag match failed')
|
|
685
|
-
|| messageLower.includes('closed tag match failed')
|
|
686
|
-
|| messageLower.includes('unclosed')
|
|
687
|
-
|| messageLower.includes('missing required')
|
|
688
|
-
|| ruleLower.includes('tag-pair')
|
|
689
|
-
|| ruleLower.includes('attr-value-not-empty')
|
|
690
|
-
|| ruleLower.includes('attr-no-duplication')
|
|
691
|
-
|| ruleLower.includes('tag-self-close')
|
|
692
|
-
|| ruleLower.includes('spec-char-escape')
|
|
693
|
-
|| ruleLower.includes('tagname-lowercase')
|
|
694
|
-
|| ruleLower.includes('attr-lowercase')
|
|
695
|
-
|| ruleLower.includes('src-not-empty')
|
|
696
|
-
|| ruleLower.includes('alt-require')
|
|
697
|
-
) {
|
|
698
|
-
issueCounts.label++;
|
|
699
|
-
} else {
|
|
700
|
-
issueCounts.html++;
|
|
701
|
-
}
|
|
702
|
-
});
|
|
654
|
+
// 1.5. Check for validation errors (Errors = blocking, Warnings = non-blocking)
|
|
655
|
+
// Get issue counts from ref or stored state
|
|
656
|
+
let issueCounts = { errors: 0, warnings: 0, total: 0 };
|
|
657
|
+
if (htmlEditorRef.current && typeof htmlEditorRef.current.getIssueCounts === 'function') {
|
|
658
|
+
issueCounts = htmlEditorRef.current.getIssueCounts();
|
|
703
659
|
} else if (lastValidationStateRef.current?.issueCounts) {
|
|
704
660
|
issueCounts = lastValidationStateRef.current.issueCounts;
|
|
705
661
|
}
|
|
@@ -46,9 +46,7 @@ const mockGetAllIssues = jest.fn(() => []);
|
|
|
46
46
|
const mockGetValidationState = jest.fn(() => ({
|
|
47
47
|
isValidating: false,
|
|
48
48
|
hasErrors: false,
|
|
49
|
-
issueCounts: {
|
|
50
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
51
|
-
},
|
|
49
|
+
issueCounts: { errors: 0, warnings: 0, total: 0 },
|
|
52
50
|
}));
|
|
53
51
|
|
|
54
52
|
// Mock HtmlEditor - it exports a lazy-loaded component by default
|
|
@@ -62,12 +60,10 @@ jest.mock('../../../../v2Components/HtmlEditor/index.lazy', () => {
|
|
|
62
60
|
isContentEmpty: () => !props.initialContent || (typeof props.initialContent === 'string' && props.initialContent.trim() === ''),
|
|
63
61
|
getIssueCounts: () => {
|
|
64
62
|
const issues = mockGetAllIssues();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
total: issues.length,
|
|
70
|
-
};
|
|
63
|
+
const total = issues.length;
|
|
64
|
+
const errors = issues.filter((i) => (i.source === 'liquid-validator' && i.severity === 'error')
|
|
65
|
+
|| ['liquid-api-validation', 'standard-api-validation'].includes(i.rule)).length;
|
|
66
|
+
return { errors, warnings: total - errors, total };
|
|
71
67
|
},
|
|
72
68
|
}));
|
|
73
69
|
|
|
@@ -94,9 +90,7 @@ jest.mock('../../../../v2Components/HtmlEditor/index.lazy', () => {
|
|
|
94
90
|
<button
|
|
95
91
|
onClick={() => props.onValidationChange && props.onValidationChange({
|
|
96
92
|
isContentEmpty: false,
|
|
97
|
-
issueCounts: {
|
|
98
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
99
|
-
},
|
|
93
|
+
issueCounts: { errors: 0, warnings: 0, total: 0 },
|
|
100
94
|
validationComplete: true,
|
|
101
95
|
hasErrors: false,
|
|
102
96
|
})}
|
|
@@ -122,12 +116,10 @@ jest.mock('../../../../v2Components/HtmlEditor', () => {
|
|
|
122
116
|
isContentEmpty: () => !props.initialContent || (typeof props.initialContent === 'string' && props.initialContent.trim() === ''),
|
|
123
117
|
getIssueCounts: () => {
|
|
124
118
|
const issues = mockGetAllIssues();
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
total: issues.length,
|
|
130
|
-
};
|
|
119
|
+
const total = issues.length;
|
|
120
|
+
const errors = issues.filter((i) => (i.source === 'liquid-validator' && i.severity === 'error')
|
|
121
|
+
|| ['liquid-api-validation', 'standard-api-validation'].includes(i.rule)).length;
|
|
122
|
+
return { errors, warnings: total - errors, total };
|
|
131
123
|
},
|
|
132
124
|
}));
|
|
133
125
|
|
|
@@ -154,9 +146,7 @@ jest.mock('../../../../v2Components/HtmlEditor', () => {
|
|
|
154
146
|
<button
|
|
155
147
|
onClick={() => props.onValidationChange && props.onValidationChange({
|
|
156
148
|
isContentEmpty: false,
|
|
157
|
-
issueCounts: {
|
|
158
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
159
|
-
},
|
|
149
|
+
issueCounts: { errors: 0, warnings: 0, total: 0 },
|
|
160
150
|
validationComplete: true,
|
|
161
151
|
hasErrors: false,
|
|
162
152
|
})}
|
|
@@ -426,9 +416,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
426
416
|
mockGetValidationState.mockReturnValue({
|
|
427
417
|
isValidating: false,
|
|
428
418
|
hasErrors: false,
|
|
429
|
-
issueCounts: {
|
|
430
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
431
|
-
},
|
|
419
|
+
issueCounts: { errors: 0, warnings: 0, total: 0 },
|
|
432
420
|
});
|
|
433
421
|
// Reset hasLiquidSupportFeature mock to return true by default
|
|
434
422
|
mockHasLiquidSupportFeature.mockReturnValue(true);
|
|
@@ -878,7 +866,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
878
866
|
isValidating: false,
|
|
879
867
|
hasErrors: true,
|
|
880
868
|
issueCounts: {
|
|
881
|
-
|
|
869
|
+
errors: 0, warnings: 1, total: 1,
|
|
882
870
|
},
|
|
883
871
|
});
|
|
884
872
|
|
|
@@ -921,7 +909,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
921
909
|
isValidating: false,
|
|
922
910
|
hasErrors: true,
|
|
923
911
|
issueCounts: {
|
|
924
|
-
|
|
912
|
+
errors: 0, warnings: 1, total: 1,
|
|
925
913
|
},
|
|
926
914
|
});
|
|
927
915
|
|
|
@@ -963,7 +951,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
963
951
|
isValidating: false,
|
|
964
952
|
hasErrors: true,
|
|
965
953
|
issueCounts: {
|
|
966
|
-
|
|
954
|
+
errors: 1, warnings: 0, total: 1,
|
|
967
955
|
},
|
|
968
956
|
});
|
|
969
957
|
|
|
@@ -2149,9 +2137,8 @@ describe('EmailHTMLEditor', () => {
|
|
|
2149
2137
|
|
|
2150
2138
|
const issueCounts = ref.current.getIssueCounts();
|
|
2151
2139
|
expect(issueCounts).toEqual({
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
liquid: expect.any(Number),
|
|
2140
|
+
errors: expect.any(Number),
|
|
2141
|
+
warnings: expect.any(Number),
|
|
2155
2142
|
total: expect.any(Number),
|
|
2156
2143
|
});
|
|
2157
2144
|
});
|
|
@@ -2201,7 +2188,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
2201
2188
|
});
|
|
2202
2189
|
|
|
2203
2190
|
it('getIssueCounts returns default object with zeros (lines 168-170)', async () => {
|
|
2204
|
-
// This covers
|
|
2191
|
+
// This covers default return: { errors: 0, warnings: 0, total: 0 };
|
|
2205
2192
|
const ref = React.createRef();
|
|
2206
2193
|
|
|
2207
2194
|
render(
|
|
@@ -2218,18 +2205,16 @@ describe('EmailHTMLEditor', () => {
|
|
|
2218
2205
|
}, { timeout: 3000 });
|
|
2219
2206
|
|
|
2220
2207
|
// When htmlEditorRef.current?.getIssueCounts is not available,
|
|
2221
|
-
// the default return value should be {
|
|
2208
|
+
// the default return value should be { errors: 0, warnings: 0, total: 0 }
|
|
2222
2209
|
const issueCounts = ref.current.getIssueCounts();
|
|
2223
2210
|
expect(issueCounts).toEqual({
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
liquid: expect.any(Number),
|
|
2211
|
+
errors: expect.any(Number),
|
|
2212
|
+
warnings: expect.any(Number),
|
|
2227
2213
|
total: expect.any(Number),
|
|
2228
2214
|
});
|
|
2229
2215
|
// Verify all keys exist
|
|
2230
|
-
expect(issueCounts.
|
|
2231
|
-
expect(issueCounts.
|
|
2232
|
-
expect(issueCounts.liquid).toBeDefined();
|
|
2216
|
+
expect(issueCounts.errors).toBeDefined();
|
|
2217
|
+
expect(issueCounts.warnings).toBeDefined();
|
|
2233
2218
|
expect(issueCounts.total).toBeDefined();
|
|
2234
2219
|
});
|
|
2235
2220
|
|
|
@@ -14,8 +14,10 @@ export default css`
|
|
|
14
14
|
margin: 0 auto;
|
|
15
15
|
width: 100%;
|
|
16
16
|
padding: ${CAP_SPACE_24} 0;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/* Only main channel tabs content, not HTML Editor validation panel tabs */
|
|
18
|
+
> .cap-tab-v2 > .ant-tabs-content-holder > .ant-tabs-content,
|
|
19
|
+
> .cap-tab-v2 > .ant-tabs-content {
|
|
20
|
+
margin-top: ${CAP_SPACE_16};
|
|
19
21
|
}
|
|
20
22
|
` : `.cap-tab-v2 {
|
|
21
23
|
height: calc(100vh - 11.25rem);
|