@gem-sdk/system 1.58.0-dev.142 → 1.58.0-dev.143
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +0 -6
- package/dist/esm/index.js +0 -1
- package/dist/types/index.d.ts +1 -8
- package/package.json +6 -15
- package/src/component/__tests__/ template.test.tsx +76 -0
- package/src/component/__tests__/createAttr.test.ts +62 -0
- package/src/component/__tests__/createClass.test.ts +68 -0
- package/src/component/__tests__/createContent.test.ts +52 -0
- package/src/component/__tests__/createStateOrContext.test.ts +129 -0
- package/src/component/__tests__/createStyle.test.ts +63 -0
- package/src/component/createAttr.ts +44 -0
- package/src/component/createClass.ts +48 -0
- package/src/component/createContent.ts +20 -0
- package/src/component/createStateOrContext.ts +70 -0
- package/src/component/createStyle.ts +53 -0
- package/src/component/template.ts +119 -0
- package/src/component/types.ts +9 -0
- package/src/component/utils/__tests__/toCamelCaseKeys.test.ts +79 -0
- package/src/component/utils/toCamelCaseKeys.ts +20 -0
- package/src/e2e-tests/README.md +1 -0
- package/src/examples/components/text/DemoText.liquid.ts +49 -0
- package/src/examples/components/text/DemoText.tsx +50 -0
- package/src/examples/components/text/common/__tests__/globalTypoClasses.test.ts +11 -0
- package/src/examples/components/text/common/getAttr.ts +7 -0
- package/src/examples/components/text/common/getStyle.ts +5 -0
- package/src/examples/components/text/common/globalTypoClasses.ts +5 -0
- package/src/examples/components/text/e2e-tests/DemoText.spec.tsx +23 -0
- package/src/examples/components/text/e2e-tests/DemoText.tsx +23 -0
- package/src/index.ts +34 -0
- package/src/validator/README.md +1 -0
- package/dist/cjs/component/template.js +0 -89
- package/dist/esm/component/template.js +0 -83
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export const createStateOrContext = (obj: { [key: string]: any }) => {
|
|
2
|
+
const isDevOrStaging =
|
|
3
|
+
!process.env.APP_ENV ||
|
|
4
|
+
process.env.APP_ENV === 'development' ||
|
|
5
|
+
process.env.APP_ENV === 'staging';
|
|
6
|
+
|
|
7
|
+
const isValid = (value: any) => {
|
|
8
|
+
// Ensure value is neither undefined, null, empty string, nor false (except 0)
|
|
9
|
+
return value !== undefined && value !== null && value !== '' && value !== false;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const validateKey = (key: string) => {
|
|
13
|
+
// Check key length
|
|
14
|
+
if (key.length > 20) {
|
|
15
|
+
console.error(`Invalid key "${key}": Key length must not exceed 20 characters.`);
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Ensure no special characters or numbers
|
|
20
|
+
const validKeyRegex = /^[a-zA-Z]+$/;
|
|
21
|
+
if (!validKeyRegex.test(key)) {
|
|
22
|
+
console.error(
|
|
23
|
+
`Invalid key "${key}": Key must contain only alphabetic characters (no numbers or special characters).`,
|
|
24
|
+
);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check camelCase format (should start with lowercase)
|
|
29
|
+
const camelCaseRegex = /^[a-z][a-zA-Z]*$/;
|
|
30
|
+
if (!camelCaseRegex.test(key)) {
|
|
31
|
+
console.error(`Invalid key "${key}": Key must be in camelCase format.`);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return true;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const validateObject = (data: any, depth: number) => {
|
|
39
|
+
if (depth > 3) {
|
|
40
|
+
console.error('Invalid structure: Data must not be nested deeper than 3 levels.');
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const key in data) {
|
|
45
|
+
const value = data[key];
|
|
46
|
+
|
|
47
|
+
// Key validation
|
|
48
|
+
if (!validateKey(key)) continue;
|
|
49
|
+
|
|
50
|
+
// Value validation
|
|
51
|
+
if (!isValid(value)) {
|
|
52
|
+
console.error(
|
|
53
|
+
`Invalid value for key "${key}": Value must not be undefined, null, blank, or false.`,
|
|
54
|
+
);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Recursive check if the value is an object
|
|
59
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
60
|
+
validateObject(value, depth + 1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (isDevOrStaging) {
|
|
66
|
+
validateObject(obj, 1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return obj;
|
|
70
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { toCamelCaseKeys } from './utils/toCamelCaseKeys';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Properties to ignore with explanations for each
|
|
5
|
+
*/
|
|
6
|
+
const ignoredProperties: { [key: string]: string } = {
|
|
7
|
+
ignore: 'This property is not supported in the current styling setup.',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const createStyle = (obj: { [key: string]: string | number }) => {
|
|
11
|
+
const isDevOrStaging =
|
|
12
|
+
!process.env.APP_ENV ||
|
|
13
|
+
process.env.APP_ENV === 'development' ||
|
|
14
|
+
process.env.APP_ENV === 'staging';
|
|
15
|
+
|
|
16
|
+
if (isDevOrStaging) {
|
|
17
|
+
for (const key in obj) {
|
|
18
|
+
const value = obj[key];
|
|
19
|
+
|
|
20
|
+
// Check if the property is in the ignored list and log the explanation
|
|
21
|
+
if (Object.prototype.hasOwnProperty.call(ignoredProperties, key)) {
|
|
22
|
+
console.error(`Ignored property detected: "${key}". ${ignoredProperties[key]}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check for uppercase letters, numbers, and special characters (only allow lowercase letters and "-"
|
|
26
|
+
const isValidKey = /^[a-z-]+$/.test(key);
|
|
27
|
+
if (!isValidKey) {
|
|
28
|
+
console.error(
|
|
29
|
+
`Invalid key "${key}": Keys must be lowercase letters and may only contain "-".`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check for nested objects (only support single-level properties)
|
|
34
|
+
if (typeof value === 'object' && value !== null) {
|
|
35
|
+
console.error(
|
|
36
|
+
`Invalid nested object for property "${key}". Nested objects are not supported.`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if the value is a valid type
|
|
41
|
+
const isValidType = typeof value === 'string' || typeof value === 'number';
|
|
42
|
+
if (!isValidType) {
|
|
43
|
+
console.error(`Invalid style value for "${key}": ${value}. Must be a string or number.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return obj;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const createStyleReact = (obj: { [key: string]: string | number }) => {
|
|
52
|
+
return toCamelCaseKeys(createStyle(obj));
|
|
53
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
Liquid in liquid.ts
|
|
4
|
+
<div>
|
|
5
|
+
Liquid(`
|
|
6
|
+
{%-if productSelectedVariant == empty or productSelectedVariant == null -%}
|
|
7
|
+
{%- assign productSelectedVariant = product.selected_or_first_available_variant -%}
|
|
8
|
+
{%- endif -%}
|
|
9
|
+
{%-if variant == empty or variant == null -%}
|
|
10
|
+
{%- assign variant = product.selected_or_first_available_variant -%}
|
|
11
|
+
{%- endif -%}
|
|
12
|
+
`)
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
IF in tsx & liquid.ts
|
|
16
|
+
<div {...attrs}>
|
|
17
|
+
{
|
|
18
|
+
If(product.id != "", (
|
|
19
|
+
<label className={classes} style={styles}>
|
|
20
|
+
{content}
|
|
21
|
+
</label>
|
|
22
|
+
), (
|
|
23
|
+
<label className={classes} style={styles}>
|
|
24
|
+
{content}
|
|
25
|
+
</label>
|
|
26
|
+
))
|
|
27
|
+
}
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
LiquidIF in liquid.ts
|
|
31
|
+
<div {...attrs}>
|
|
32
|
+
{
|
|
33
|
+
LiquidIf("product.quanity > 0", `
|
|
34
|
+
<label className={classes} style={styles}>
|
|
35
|
+
{content}
|
|
36
|
+
</label>
|
|
37
|
+
`, `
|
|
38
|
+
<label className={classes} style={styles}>
|
|
39
|
+
{content}
|
|
40
|
+
</label>
|
|
41
|
+
`)
|
|
42
|
+
}
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
For in tsx & liquid.ts
|
|
46
|
+
{
|
|
47
|
+
For(numbers, (item, index) => (
|
|
48
|
+
<div key={index}>
|
|
49
|
+
{index + 1}: Số {item}
|
|
50
|
+
</div>
|
|
51
|
+
))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
LiquidFor in tsx & liquid.ts
|
|
55
|
+
{
|
|
56
|
+
LiquidFor('(item, index) in items', `
|
|
57
|
+
<div key="{{ forloop.index }}">
|
|
58
|
+
{{ forloop.index + 1}}: Số {{item}}
|
|
59
|
+
</div>
|
|
60
|
+
`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
*/
|
|
64
|
+
type CallbackCondition = () => JSX.Element | string;
|
|
65
|
+
|
|
66
|
+
export const Liquid = (code: string)=> {
|
|
67
|
+
return code
|
|
68
|
+
}
|
|
69
|
+
export const For = <T,>(
|
|
70
|
+
items: T[],
|
|
71
|
+
renderFn: (item: T, index: number) => JSX.Element
|
|
72
|
+
): JSX.Element[] => {
|
|
73
|
+
return items.map((item, index) => renderFn(item, index));
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const LiquidFor = (
|
|
77
|
+
c: string,
|
|
78
|
+
t: string | CallbackCondition
|
|
79
|
+
): string => {
|
|
80
|
+
return `{% for ${c} %}${(typeof t === 'string' ? t : t())}{% endfor %}`
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const If = (
|
|
84
|
+
condition: boolean | null | undefined,
|
|
85
|
+
trueResult: string | JSX.Element | CallbackCondition,
|
|
86
|
+
falseResult?: string | JSX.Element | CallbackCondition
|
|
87
|
+
): JSX.Element | string | null => {
|
|
88
|
+
if (condition) {
|
|
89
|
+
// Trả về kết quả đúng nếu điều kiện là true
|
|
90
|
+
return typeof trueResult === 'function' ? trueResult() : trueResult;
|
|
91
|
+
} else {
|
|
92
|
+
// Trả về kết quả sai nếu điều kiện là false
|
|
93
|
+
return falseResult
|
|
94
|
+
? typeof falseResult === 'function'
|
|
95
|
+
? falseResult()
|
|
96
|
+
: falseResult
|
|
97
|
+
: null; // Nếu không có falseResult, trả về null
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
export const LiquidIf = (
|
|
103
|
+
c: string,
|
|
104
|
+
t: string | CallbackCondition,
|
|
105
|
+
f?: string | CallbackCondition,
|
|
106
|
+
) => `{% if ${c} %}${(typeof t === 'string' ? t : t())}${f ? `{% else %}${(typeof f === 'string' ? f : f?.())}` : ''}{% endif %}`;
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
export const Unless = (
|
|
110
|
+
condition: boolean | null | undefined,
|
|
111
|
+
trueResult: string | JSX.Element | CallbackCondition,
|
|
112
|
+
falseResult?: string | JSX.Element | CallbackCondition
|
|
113
|
+
) => If(!condition, trueResult, falseResult);
|
|
114
|
+
|
|
115
|
+
export const LiquidUnless = (
|
|
116
|
+
c: string,
|
|
117
|
+
t: string | CallbackCondition,
|
|
118
|
+
f?: string | CallbackCondition,
|
|
119
|
+
) => `{% unless ${c} %}${(typeof t === 'string' ? t : t())}${f ? `{% else %}${(typeof f === 'string' ? f : f?.())}` : ''}{% endunless %}`;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, test, expect } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
import { toCamelCaseKeys } from '../toCamelCaseKeys';
|
|
4
|
+
|
|
5
|
+
describe('toCamelCaseKeys', () => {
|
|
6
|
+
test('should convert hyphenated keys to camelCase', () => {
|
|
7
|
+
const input = {
|
|
8
|
+
'background-color': 'blue',
|
|
9
|
+
'font-size': '12px',
|
|
10
|
+
padding: 10,
|
|
11
|
+
};
|
|
12
|
+
const expectedOutput = {
|
|
13
|
+
backgroundColor: 'blue',
|
|
14
|
+
fontSize: '12px',
|
|
15
|
+
padding: 10,
|
|
16
|
+
};
|
|
17
|
+
expect(toCamelCaseKeys(input)).toEqual(expectedOutput);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('should keep keys with "--" prefix as-is', () => {
|
|
21
|
+
const input = {
|
|
22
|
+
'--custom-var': '20px',
|
|
23
|
+
'--another-var': '15px',
|
|
24
|
+
};
|
|
25
|
+
const expectedOutput = {
|
|
26
|
+
'--custom-var': '20px',
|
|
27
|
+
'--another-var': '15px',
|
|
28
|
+
};
|
|
29
|
+
expect(toCamelCaseKeys(input)).toEqual(expectedOutput);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should convert hyphenated keys in nested objects to camelCase', () => {
|
|
33
|
+
const input = {
|
|
34
|
+
nested: {
|
|
35
|
+
'border-radius': '5px',
|
|
36
|
+
'text-align': 'center',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
const expectedOutput = {
|
|
40
|
+
nested: {
|
|
41
|
+
borderRadius: '5px',
|
|
42
|
+
textAlign: 'center',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
expect(toCamelCaseKeys(input)).toEqual(expectedOutput);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('should handle mixed keys with hyphens and "--" prefix correctly', () => {
|
|
49
|
+
const input = {
|
|
50
|
+
'background-color': 'blue',
|
|
51
|
+
'--custom-var': '20px',
|
|
52
|
+
nested: {
|
|
53
|
+
'font-size': '12px',
|
|
54
|
+
'--nested-var': '5px',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
const expectedOutput = {
|
|
58
|
+
backgroundColor: 'blue',
|
|
59
|
+
'--custom-var': '20px',
|
|
60
|
+
nested: {
|
|
61
|
+
fontSize: '12px',
|
|
62
|
+
'--nested-var': '5px',
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
expect(toCamelCaseKeys(input)).toEqual(expectedOutput);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('should leave keys without hyphens or "--" prefix unchanged', () => {
|
|
69
|
+
const input = {
|
|
70
|
+
padding: 10,
|
|
71
|
+
margin: '5px',
|
|
72
|
+
};
|
|
73
|
+
const expectedOutput = {
|
|
74
|
+
padding: 10,
|
|
75
|
+
margin: '5px',
|
|
76
|
+
};
|
|
77
|
+
expect(toCamelCaseKeys(input)).toEqual(expectedOutput);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const toCamelCaseKeys = (obj: { [key: string]: any }): { [key: string]: any } => {
|
|
2
|
+
const newObj: { [key: string]: any } = {};
|
|
3
|
+
|
|
4
|
+
for (const key in obj) {
|
|
5
|
+
const value = obj[key];
|
|
6
|
+
|
|
7
|
+
// If the key starts with "--", keep it as is
|
|
8
|
+
const newKey = key.startsWith('--')
|
|
9
|
+
? key
|
|
10
|
+
: key.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
11
|
+
|
|
12
|
+
// Recursively apply to nested objects
|
|
13
|
+
newObj[newKey] =
|
|
14
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
15
|
+
? toCamelCaseKeys(value)
|
|
16
|
+
: value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return newObj;
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Đây là folder chưa các e2e-tests compare react vs liquid
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAttr,
|
|
3
|
+
createClass,
|
|
4
|
+
createContent,
|
|
5
|
+
createStateOrContext,
|
|
6
|
+
createStyle,
|
|
7
|
+
If,
|
|
8
|
+
} from '../../../index';
|
|
9
|
+
import { getAttr } from './common/getAttr';
|
|
10
|
+
import { getStyle } from './common/getStyle';
|
|
11
|
+
import { globalTypoClasses } from './common/globalTypoClasses';
|
|
12
|
+
import type { TextProps } from './DemoText';
|
|
13
|
+
import { template } from '@gem-sdk/core';
|
|
14
|
+
|
|
15
|
+
const Text = ({ product, text }: TextProps) => {
|
|
16
|
+
const state = createStateOrContext({
|
|
17
|
+
productId: product.id,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const attrs = createAttr({
|
|
21
|
+
...getAttr({ product }),
|
|
22
|
+
});
|
|
23
|
+
const styles = createStyle({
|
|
24
|
+
...getStyle(),
|
|
25
|
+
});
|
|
26
|
+
const classes = createClass({
|
|
27
|
+
...globalTypoClasses(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const content = createContent(text);
|
|
31
|
+
|
|
32
|
+
return template`
|
|
33
|
+
<div ${attrs} gp-data="${state}">
|
|
34
|
+
${
|
|
35
|
+
If(product.id != "", `
|
|
36
|
+
<label class="${{ classes }}" style="${styles}">
|
|
37
|
+
${content}
|
|
38
|
+
</label>
|
|
39
|
+
`, `
|
|
40
|
+
<label class="${{ classes }}" style="${styles}">
|
|
41
|
+
${content}
|
|
42
|
+
</label>
|
|
43
|
+
`)
|
|
44
|
+
}
|
|
45
|
+
</div>
|
|
46
|
+
`;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default Text;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAttrReact,
|
|
3
|
+
createClassReact,
|
|
4
|
+
createContentReact,
|
|
5
|
+
createStyleReact,
|
|
6
|
+
If,
|
|
7
|
+
} from '../../../index';
|
|
8
|
+
import { globalTypoClasses } from './common/globalTypoClasses';
|
|
9
|
+
|
|
10
|
+
export type TextProps = {
|
|
11
|
+
product: {
|
|
12
|
+
id: string;
|
|
13
|
+
};
|
|
14
|
+
text: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const Text: React.FC<TextProps> = ({ product, text }) => {
|
|
18
|
+
const attrs = createAttrReact({
|
|
19
|
+
'data-gp-product-id': product.id,
|
|
20
|
+
});
|
|
21
|
+
const styles = createStyleReact({
|
|
22
|
+
'font-size': '16px',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const typoClasses = globalTypoClasses();
|
|
26
|
+
const classes = createClassReact({
|
|
27
|
+
'gp-text': true,
|
|
28
|
+
...typoClasses,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const content = createContentReact(text);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div {...attrs}>
|
|
35
|
+
{
|
|
36
|
+
If(product.id != "", (
|
|
37
|
+
<label className={classes} style={styles}>
|
|
38
|
+
{content}
|
|
39
|
+
</label>
|
|
40
|
+
), (
|
|
41
|
+
<label className={classes} style={styles}>
|
|
42
|
+
{content}
|
|
43
|
+
</label>
|
|
44
|
+
))
|
|
45
|
+
}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default Text;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, test, expect } from '@jest/globals';
|
|
2
|
+
import { globalTypoClasses } from '../globalTypoClasses';
|
|
3
|
+
|
|
4
|
+
describe('globalTypoClasses', () => {
|
|
5
|
+
test('Test case 1', () => {
|
|
6
|
+
const classes = globalTypoClasses();
|
|
7
|
+
expect(classes).toEqual({
|
|
8
|
+
'gp-global-h1': true,
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/experimental-ct-react';
|
|
2
|
+
import type { TextProps } from "../DemoText";
|
|
3
|
+
import DemoText from "../DemoText"
|
|
4
|
+
import DemoTextLiquid from "../DemoText.liquid"
|
|
5
|
+
|
|
6
|
+
test('should work', async ({ mount }, testInfo) => {
|
|
7
|
+
const props: TextProps = {
|
|
8
|
+
product: {
|
|
9
|
+
id: "1"
|
|
10
|
+
},
|
|
11
|
+
text: "Demo test"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const component = await mount((
|
|
15
|
+
<div>
|
|
16
|
+
<div className='react-el'><DemoText {...props}/></div>
|
|
17
|
+
<div className='liquid-el'><div dangerouslySetInnerHTML={{__html: DemoTextLiquid(props)}}></div></div>
|
|
18
|
+
</div>
|
|
19
|
+
));
|
|
20
|
+
|
|
21
|
+
await expect(component.locator('.react-el')).toHaveScreenshot(`${testInfo.title}.png`);
|
|
22
|
+
await expect(component.locator('.liquid-el')).toHaveScreenshot(`${testInfo.title}.png`);
|
|
23
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { E2ETests } from '../../../../component/types';
|
|
2
|
+
import type { TextProps } from '../DemoText';
|
|
3
|
+
import DemoTextReact from '../DemoText';
|
|
4
|
+
import DemoTextLiquid from '../DemoText.liquid';
|
|
5
|
+
|
|
6
|
+
export const e2eDemoText = (): E2ETests => {
|
|
7
|
+
return {
|
|
8
|
+
'DemoText basic': async () => {
|
|
9
|
+
const props: TextProps = {
|
|
10
|
+
product: {
|
|
11
|
+
id: '1',
|
|
12
|
+
},
|
|
13
|
+
text: 'Demo test',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
props,
|
|
18
|
+
reactComponent: DemoTextReact,
|
|
19
|
+
liquidComponent: DemoTextLiquid,
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createAttr } from './component/createAttr';
|
|
2
|
+
const createAttrReact = createAttr;
|
|
3
|
+
|
|
4
|
+
import { createStyle, createStyleReact } from './component/createStyle';
|
|
5
|
+
|
|
6
|
+
import { createContent } from './component/createContent';
|
|
7
|
+
const createContentReact = createContent;
|
|
8
|
+
|
|
9
|
+
import { createClass } from './component/createClass';
|
|
10
|
+
const createClassReact = createClass;
|
|
11
|
+
|
|
12
|
+
import { createStateOrContext } from './component/createStateOrContext';
|
|
13
|
+
|
|
14
|
+
import { Liquid, If, LiquidIf, For, LiquidFor } from './component/template';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
// Attr
|
|
18
|
+
createAttr,
|
|
19
|
+
createAttrReact,
|
|
20
|
+
createStyle,
|
|
21
|
+
createStyleReact,
|
|
22
|
+
createContent,
|
|
23
|
+
createContentReact,
|
|
24
|
+
createClass,
|
|
25
|
+
createClassReact,
|
|
26
|
+
createStateOrContext,
|
|
27
|
+
|
|
28
|
+
// Template
|
|
29
|
+
Liquid,
|
|
30
|
+
If,
|
|
31
|
+
LiquidIf,
|
|
32
|
+
For,
|
|
33
|
+
LiquidFor,
|
|
34
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Đây là folder chưa các func validate component setting
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
|
|
5
|
-
Liquid in liquid.ts
|
|
6
|
-
<div>
|
|
7
|
-
Liquid(`
|
|
8
|
-
{%-if productSelectedVariant == empty or productSelectedVariant == null -%}
|
|
9
|
-
{%- assign productSelectedVariant = product.selected_or_first_available_variant -%}
|
|
10
|
-
{%- endif -%}
|
|
11
|
-
{%-if variant == empty or variant == null -%}
|
|
12
|
-
{%- assign variant = product.selected_or_first_available_variant -%}
|
|
13
|
-
{%- endif -%}
|
|
14
|
-
`)
|
|
15
|
-
</div>
|
|
16
|
-
|
|
17
|
-
IF in tsx & liquid.ts
|
|
18
|
-
<div {...attrs}>
|
|
19
|
-
{
|
|
20
|
-
If(product.id != "", (
|
|
21
|
-
<label className={classes} style={styles}>
|
|
22
|
-
{content}
|
|
23
|
-
</label>
|
|
24
|
-
), (
|
|
25
|
-
<label className={classes} style={styles}>
|
|
26
|
-
{content}
|
|
27
|
-
</label>
|
|
28
|
-
))
|
|
29
|
-
}
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
LiquidIF in liquid.ts
|
|
33
|
-
<div {...attrs}>
|
|
34
|
-
{
|
|
35
|
-
LiquidIf("product.quanity > 0", `
|
|
36
|
-
<label className={classes} style={styles}>
|
|
37
|
-
{content}
|
|
38
|
-
</label>
|
|
39
|
-
`, `
|
|
40
|
-
<label className={classes} style={styles}>
|
|
41
|
-
{content}
|
|
42
|
-
</label>
|
|
43
|
-
`)
|
|
44
|
-
}
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
For in tsx & liquid.ts
|
|
48
|
-
{
|
|
49
|
-
For(numbers, (item, index) => (
|
|
50
|
-
<div key={index}>
|
|
51
|
-
{index + 1}: Số {item}
|
|
52
|
-
</div>
|
|
53
|
-
))
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
LiquidFor in tsx & liquid.ts
|
|
57
|
-
{
|
|
58
|
-
LiquidFor('(item, index) in items', `
|
|
59
|
-
<div key="{{ forloop.index }}">
|
|
60
|
-
{{ forloop.index + 1}}: Số {{item}}
|
|
61
|
-
</div>
|
|
62
|
-
`)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
*/ const Liquid = (code)=>{
|
|
66
|
-
return code;
|
|
67
|
-
};
|
|
68
|
-
const For = (items, renderFn)=>{
|
|
69
|
-
return items.map((item, index)=>renderFn(item, index));
|
|
70
|
-
};
|
|
71
|
-
const LiquidFor = (c, t)=>{
|
|
72
|
-
return `{% for ${c} %}${typeof t === 'string' ? t : t()}{% endfor %}`;
|
|
73
|
-
};
|
|
74
|
-
const If = (condition, trueResult, falseResult)=>{
|
|
75
|
-
if (condition) {
|
|
76
|
-
// Trả về kết quả đúng nếu điều kiện là true
|
|
77
|
-
return typeof trueResult === 'function' ? trueResult() : trueResult;
|
|
78
|
-
} else {
|
|
79
|
-
// Trả về kết quả sai nếu điều kiện là false
|
|
80
|
-
return falseResult ? typeof falseResult === 'function' ? falseResult() : falseResult : null; // Nếu không có falseResult, trả về null
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
const LiquidIf = (c, t, f)=>`{% if ${c} %}${typeof t === 'string' ? t : t()}${f ? `{% else %}${typeof f === 'string' ? f : f?.()}` : ''}{% endif %}`;
|
|
84
|
-
|
|
85
|
-
exports.For = For;
|
|
86
|
-
exports.If = If;
|
|
87
|
-
exports.Liquid = Liquid;
|
|
88
|
-
exports.LiquidFor = LiquidFor;
|
|
89
|
-
exports.LiquidIf = LiquidIf;
|