@teleporthq/teleport-plugin-common 0.44.0-alpha.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/builders/html-builders.ts +9 -2
- package/__tests__/builders/style-builders.ts +23 -0
- package/__tests__/node-handlers/node-to-html/index.ts +2 -0
- package/__tests__/node-handlers/node-to-jsx/attribute-value-mapper.ts +77 -0
- package/__tests__/node-handlers/node-to-jsx/calendar-events-reviver.ts +176 -0
- package/__tests__/node-handlers/node-to-jsx/cms-list-repeater-ecommerce-source.ts +79 -0
- package/__tests__/node-handlers/node-to-jsx/cms-list-repeater-loading.ts +122 -0
- package/__tests__/node-handlers/node-to-jsx/index.ts +719 -3
- package/__tests__/node-handlers/node-to-jsx/url-search-params-expression.ts +59 -0
- package/__tests__/node-handlers/node-to-jsx/utils.ts +602 -3
- package/__tests__/utils/ast-utils.ts +188 -0
- package/__tests__/utils/content-property-sanitiser.ts +156 -0
- package/__tests__/utils/page-seeds-state-from-data-source.ts +70 -0
- package/__tests__/utils/parse-string-with-template-expressions.ts +82 -0
- package/__tests__/utils/resource-auth-scheme.ts +55 -0
- package/__tests__/utils/route-utils.ts +118 -0
- package/__tests__/utils/style-utils.ts +59 -0
- package/__tests__/utils/template-expression-balance.ts +83 -0
- package/__tests__/utils/url-query-writer.ts +239 -0
- package/__tests__/utils/url-search-param-sync.ts +138 -0
- package/__tests__/utils/url-search-params.ts +48 -0
- package/dist/cjs/builders/ast-builders.d.ts +2 -0
- package/dist/cjs/builders/ast-builders.d.ts.map +1 -1
- package/dist/cjs/builders/ast-builders.js +41 -3
- package/dist/cjs/builders/ast-builders.js.map +1 -1
- package/dist/cjs/builders/hast-builders.d.ts +1 -0
- package/dist/cjs/builders/hast-builders.d.ts.map +1 -1
- package/dist/cjs/builders/hast-builders.js +11 -1
- package/dist/cjs/builders/hast-builders.js.map +1 -1
- package/dist/cjs/builders/style-builders.d.ts +8 -2
- package/dist/cjs/builders/style-builders.d.ts.map +1 -1
- package/dist/cjs/builders/style-builders.js +65 -19
- package/dist/cjs/builders/style-builders.js.map +1 -1
- package/dist/cjs/index.d.ts +5 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +9 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/node-handlers/node-to-html/constants.d.ts.map +1 -1
- package/dist/cjs/node-handlers/node-to-html/constants.js +2 -0
- package/dist/cjs/node-handlers/node-to-html/constants.js.map +1 -1
- package/dist/cjs/node-handlers/node-to-html/index.d.ts.map +1 -1
- package/dist/cjs/node-handlers/node-to-html/index.js +69 -1
- package/dist/cjs/node-handlers/node-to-html/index.js.map +1 -1
- package/dist/cjs/node-handlers/node-to-html/types.d.ts +5 -1
- package/dist/cjs/node-handlers/node-to-html/types.d.ts.map +1 -1
- package/dist/cjs/node-handlers/node-to-html/utils.d.ts +2 -1
- package/dist/cjs/node-handlers/node-to-html/utils.d.ts.map +1 -1
- package/dist/cjs/node-handlers/node-to-html/utils.js +174 -13
- package/dist/cjs/node-handlers/node-to-html/utils.js.map +1 -1
- package/dist/cjs/node-handlers/node-to-jsx/index.d.ts.map +1 -1
- package/dist/cjs/node-handlers/node-to-jsx/index.js +1461 -66
- package/dist/cjs/node-handlers/node-to-jsx/index.js.map +1 -1
- package/dist/cjs/node-handlers/node-to-jsx/types.d.ts +60 -6
- package/dist/cjs/node-handlers/node-to-jsx/types.d.ts.map +1 -1
- package/dist/cjs/node-handlers/node-to-jsx/utils.d.ts +73 -7
- package/dist/cjs/node-handlers/node-to-jsx/utils.d.ts.map +1 -1
- package/dist/cjs/node-handlers/node-to-jsx/utils.js +864 -71
- package/dist/cjs/node-handlers/node-to-jsx/utils.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils/ast-utils.d.ts +142 -11
- package/dist/cjs/utils/ast-utils.d.ts.map +1 -1
- package/dist/cjs/utils/ast-utils.js +931 -75
- package/dist/cjs/utils/ast-utils.js.map +1 -1
- package/dist/cjs/utils/hast-utils.d.ts +8 -6
- package/dist/cjs/utils/hast-utils.d.ts.map +1 -1
- package/dist/cjs/utils/hast-utils.js +28 -4
- package/dist/cjs/utils/hast-utils.js.map +1 -1
- package/dist/cjs/utils/route-utils.d.ts +90 -0
- package/dist/cjs/utils/route-utils.d.ts.map +1 -0
- package/dist/cjs/utils/route-utils.js +152 -0
- package/dist/cjs/utils/route-utils.js.map +1 -0
- package/dist/cjs/utils/style-utils.d.ts +1 -0
- package/dist/cjs/utils/style-utils.d.ts.map +1 -1
- package/dist/cjs/utils/style-utils.js +181 -2
- package/dist/cjs/utils/style-utils.js.map +1 -1
- package/dist/cjs/utils/template-expression-balance.d.ts +58 -0
- package/dist/cjs/utils/template-expression-balance.d.ts.map +1 -0
- package/dist/cjs/utils/template-expression-balance.js +181 -0
- package/dist/cjs/utils/template-expression-balance.js.map +1 -0
- package/dist/cjs/utils/types.d.ts +3 -0
- package/dist/cjs/utils/types.d.ts.map +1 -0
- package/dist/cjs/utils/types.js +3 -0
- package/dist/cjs/utils/types.js.map +1 -0
- package/dist/cjs/utils/url-query-writer.d.ts +123 -0
- package/dist/cjs/utils/url-query-writer.d.ts.map +1 -0
- package/dist/cjs/utils/url-query-writer.js +260 -0
- package/dist/cjs/utils/url-query-writer.js.map +1 -0
- package/dist/cjs/utils/url-search-param-sync.d.ts +4 -0
- package/dist/cjs/utils/url-search-param-sync.d.ts.map +1 -0
- package/dist/cjs/utils/url-search-param-sync.js +241 -0
- package/dist/cjs/utils/url-search-param-sync.js.map +1 -0
- package/dist/cjs/utils/url-search-params.d.ts +48 -0
- package/dist/cjs/utils/url-search-params.d.ts.map +1 -0
- package/dist/cjs/utils/url-search-params.js +109 -0
- package/dist/cjs/utils/url-search-params.js.map +1 -0
- package/dist/esm/builders/ast-builders.d.ts +2 -0
- package/dist/esm/builders/ast-builders.d.ts.map +1 -1
- package/dist/esm/builders/ast-builders.js +40 -4
- package/dist/esm/builders/ast-builders.js.map +1 -1
- package/dist/esm/builders/hast-builders.d.ts +1 -0
- package/dist/esm/builders/hast-builders.d.ts.map +1 -1
- package/dist/esm/builders/hast-builders.js +9 -0
- package/dist/esm/builders/hast-builders.js.map +1 -1
- package/dist/esm/builders/style-builders.d.ts +8 -2
- package/dist/esm/builders/style-builders.d.ts.map +1 -1
- package/dist/esm/builders/style-builders.js +63 -18
- package/dist/esm/builders/style-builders.js.map +1 -1
- package/dist/esm/index.d.ts +5 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +5 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/node-handlers/node-to-html/constants.d.ts.map +1 -1
- package/dist/esm/node-handlers/node-to-html/constants.js +2 -0
- package/dist/esm/node-handlers/node-to-html/constants.js.map +1 -1
- package/dist/esm/node-handlers/node-to-html/index.d.ts.map +1 -1
- package/dist/esm/node-handlers/node-to-html/index.js +71 -3
- package/dist/esm/node-handlers/node-to-html/index.js.map +1 -1
- package/dist/esm/node-handlers/node-to-html/types.d.ts +5 -1
- package/dist/esm/node-handlers/node-to-html/types.d.ts.map +1 -1
- package/dist/esm/node-handlers/node-to-html/utils.d.ts +2 -1
- package/dist/esm/node-handlers/node-to-html/utils.d.ts.map +1 -1
- package/dist/esm/node-handlers/node-to-html/utils.js +170 -13
- package/dist/esm/node-handlers/node-to-html/utils.js.map +1 -1
- package/dist/esm/node-handlers/node-to-jsx/index.d.ts.map +1 -1
- package/dist/esm/node-handlers/node-to-jsx/index.js +1460 -68
- package/dist/esm/node-handlers/node-to-jsx/index.js.map +1 -1
- package/dist/esm/node-handlers/node-to-jsx/types.d.ts +60 -6
- package/dist/esm/node-handlers/node-to-jsx/types.d.ts.map +1 -1
- package/dist/esm/node-handlers/node-to-jsx/utils.d.ts +73 -7
- package/dist/esm/node-handlers/node-to-jsx/utils.d.ts.map +1 -1
- package/dist/esm/node-handlers/node-to-jsx/utils.js +857 -69
- package/dist/esm/node-handlers/node-to-jsx/utils.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils/ast-utils.d.ts +142 -11
- package/dist/esm/utils/ast-utils.d.ts.map +1 -1
- package/dist/esm/utils/ast-utils.js +911 -75
- package/dist/esm/utils/ast-utils.js.map +1 -1
- package/dist/esm/utils/hast-utils.d.ts +8 -6
- package/dist/esm/utils/hast-utils.d.ts.map +1 -1
- package/dist/esm/utils/hast-utils.js +26 -3
- package/dist/esm/utils/hast-utils.js.map +1 -1
- package/dist/esm/utils/route-utils.d.ts +90 -0
- package/dist/esm/utils/route-utils.d.ts.map +1 -0
- package/dist/esm/utils/route-utils.js +144 -0
- package/dist/esm/utils/route-utils.js.map +1 -0
- package/dist/esm/utils/style-utils.d.ts +1 -0
- package/dist/esm/utils/style-utils.d.ts.map +1 -1
- package/dist/esm/utils/style-utils.js +179 -1
- package/dist/esm/utils/style-utils.js.map +1 -1
- package/dist/esm/utils/template-expression-balance.d.ts +58 -0
- package/dist/esm/utils/template-expression-balance.d.ts.map +1 -0
- package/dist/esm/utils/template-expression-balance.js +174 -0
- package/dist/esm/utils/template-expression-balance.js.map +1 -0
- package/dist/esm/utils/types.d.ts +3 -0
- package/dist/esm/utils/types.d.ts.map +1 -0
- package/dist/esm/utils/types.js +2 -0
- package/dist/esm/utils/types.js.map +1 -0
- package/dist/esm/utils/url-query-writer.d.ts +123 -0
- package/dist/esm/utils/url-query-writer.d.ts.map +1 -0
- package/dist/esm/utils/url-query-writer.js +229 -0
- package/dist/esm/utils/url-query-writer.js.map +1 -0
- package/dist/esm/utils/url-search-param-sync.d.ts +4 -0
- package/dist/esm/utils/url-search-param-sync.d.ts.map +1 -0
- package/dist/esm/utils/url-search-param-sync.js +213 -0
- package/dist/esm/utils/url-search-param-sync.js.map +1 -0
- package/dist/esm/utils/url-search-params.d.ts +48 -0
- package/dist/esm/utils/url-search-params.d.ts.map +1 -0
- package/dist/esm/utils/url-search-params.js +81 -0
- package/dist/esm/utils/url-search-params.js.map +1 -0
- package/package.json +4 -4
- package/src/builders/ast-builders.ts +80 -3
- package/src/builders/hast-builders.ts +11 -0
- package/src/builders/style-builders.ts +107 -27
- package/src/index.ts +18 -1
- package/src/node-handlers/node-to-html/constants.ts +2 -0
- package/src/node-handlers/node-to-html/index.ts +126 -6
- package/src/node-handlers/node-to-html/types.ts +6 -0
- package/src/node-handlers/node-to-html/utils.ts +228 -14
- package/src/node-handlers/node-to-jsx/index.ts +2219 -111
- package/src/node-handlers/node-to-jsx/types.ts +75 -32
- package/src/node-handlers/node-to-jsx/utils.ts +1142 -84
- package/src/utils/ast-utils.ts +1223 -127
- package/src/utils/hast-utils.ts +37 -9
- package/src/utils/route-utils.ts +168 -0
- package/src/utils/style-utils.ts +183 -1
- package/src/utils/template-expression-balance.ts +196 -0
- package/src/utils/types.ts +25 -0
- package/src/utils/url-query-writer.ts +318 -0
- package/src/utils/url-search-param-sync.ts +303 -0
- package/src/utils/url-search-params.ts +122 -0
|
@@ -3,7 +3,7 @@ import { createHTMLNode, createTextNode } from '../../src/builders/hast-builders
|
|
|
3
3
|
describe('HTML Builders', () => {
|
|
4
4
|
describe('createHTMLNode', () => {
|
|
5
5
|
it('should create html node', () => {
|
|
6
|
-
const htmlNode = createHTMLNode('span', ['span'])
|
|
6
|
+
const htmlNode = createHTMLNode('span', [createHTMLNode('span', [])])
|
|
7
7
|
|
|
8
8
|
expect(htmlNode).toHaveProperty('type')
|
|
9
9
|
expect(htmlNode).toHaveProperty('tagName')
|
|
@@ -12,7 +12,14 @@ describe('HTML Builders', () => {
|
|
|
12
12
|
expect(htmlNode.type).toBe('element')
|
|
13
13
|
expect(htmlNode.tagName).toBe('span')
|
|
14
14
|
expect(htmlNode.properties).toEqual({})
|
|
15
|
-
expect(htmlNode.children).toEqual([
|
|
15
|
+
expect(htmlNode.children).toEqual([
|
|
16
|
+
{
|
|
17
|
+
children: [],
|
|
18
|
+
tagName: 'span',
|
|
19
|
+
type: 'element',
|
|
20
|
+
properties: {},
|
|
21
|
+
},
|
|
22
|
+
])
|
|
16
23
|
})
|
|
17
24
|
})
|
|
18
25
|
describe('createTextNode', () => {
|
|
@@ -24,6 +24,29 @@ describe('CSS Class Generation', () => {
|
|
|
24
24
|
expect(result).toEqual(`.name h1 > h2 .ab.cd #id {
|
|
25
25
|
some-key: value;
|
|
26
26
|
other-key: otherValue;
|
|
27
|
+
}`)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('attaches an ATTRIBUTE subselector with no separating space', () => {
|
|
31
|
+
// ⛔ THE SELECTED-PAGE CONTRACT. A numbered pagination strip repeats one
|
|
32
|
+
// authored node per visible page, so the copies cannot differ in the
|
|
33
|
+
// document. Both runtimes instead mark the showing page with
|
|
34
|
+
// `aria-current="page"` (`numbered-pagination.ts` here, `element-node.tsx`
|
|
35
|
+
// in the canvas editor), and the LOOK is a compound project class whose
|
|
36
|
+
// subselector is that attribute.
|
|
37
|
+
//
|
|
38
|
+
// A space before the bracket would turn this into a DESCENDANT selector —
|
|
39
|
+
// `.thq-page-number [aria-current='page']` matches something INSIDE the
|
|
40
|
+
// button, never the button — and the selected page would silently render
|
|
41
|
+
// identically to every other one. Which is exactly the bug the marker was
|
|
42
|
+
// added to fix.
|
|
43
|
+
const result = createCSSClassWithSelector('thq-page-number', "&[aria-current='page']", {
|
|
44
|
+
background: '#2563eb',
|
|
45
|
+
fontWeight: '600',
|
|
46
|
+
})
|
|
47
|
+
expect(result).toEqual(`.thq-page-number[aria-current='page'] {
|
|
48
|
+
background: #2563eb;
|
|
49
|
+
font-weight: 600;
|
|
27
50
|
}`)
|
|
28
51
|
})
|
|
29
52
|
})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import generator from '@babel/generator'
|
|
2
|
+
import * as types from '@babel/types'
|
|
3
|
+
import generateJSXSyntax from '../../../src/node-handlers/node-to-jsx'
|
|
4
|
+
import {
|
|
5
|
+
JSXGenerationParams,
|
|
6
|
+
JSXGenerationOptions,
|
|
7
|
+
} from '../../../src/node-handlers/node-to-jsx/types'
|
|
8
|
+
import { UIDLElementNode } from '@teleporthq/teleport-types'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* An attribute bound through a value mapper renders through it, as a text
|
|
12
|
+
* binding does: a filter chip's `aria-pressed` reads the filter's state as
|
|
13
|
+
* "true" or "false", never as the filter's value.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const MAPPER = [
|
|
17
|
+
'function mapValue(value) {',
|
|
18
|
+
" return String(value) === 'On track' ? 'true' : 'false';",
|
|
19
|
+
'}',
|
|
20
|
+
].join('\n')
|
|
21
|
+
|
|
22
|
+
const chip = (valueMapper?: string): UIDLElementNode => ({
|
|
23
|
+
type: 'element',
|
|
24
|
+
content: {
|
|
25
|
+
key: 'chip',
|
|
26
|
+
elementType: 'button',
|
|
27
|
+
attrs: {
|
|
28
|
+
'aria-pressed': {
|
|
29
|
+
type: 'dynamic',
|
|
30
|
+
content: {
|
|
31
|
+
referenceType: 'state',
|
|
32
|
+
id: 'jobFilterStatus',
|
|
33
|
+
refPath: [],
|
|
34
|
+
...(valueMapper ? { valueMapper } : {}),
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
children: [],
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const params = (): JSXGenerationParams => ({
|
|
43
|
+
dependencies: {},
|
|
44
|
+
propDefinitions: {},
|
|
45
|
+
stateDefinitions: {
|
|
46
|
+
jobFilterStatus: { type: 'string', defaultValue: '' },
|
|
47
|
+
},
|
|
48
|
+
globalStateDefinitions: {},
|
|
49
|
+
nodesLookup: {},
|
|
50
|
+
windowImports: {},
|
|
51
|
+
localeReferences: [],
|
|
52
|
+
localeAttributeReferences: [],
|
|
53
|
+
globalReferences: [],
|
|
54
|
+
globalStateReferences: [],
|
|
55
|
+
hoistedConstants: [],
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const options: JSXGenerationOptions = {
|
|
59
|
+
dynamicReferencePrefixMap: { prop: 'props', state: '', local: '' },
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const render = (node: UIDLElementNode): string =>
|
|
63
|
+
generator(generateJSXSyntax(node, params(), options) as types.Node).code.replace(/\s+/g, ' ')
|
|
64
|
+
|
|
65
|
+
describe('a dynamic attribute with a value mapper', () => {
|
|
66
|
+
it('renders the mapped value', () => {
|
|
67
|
+
const code = render(chip(MAPPER))
|
|
68
|
+
expect(code).toContain('aria-pressed={')
|
|
69
|
+
expect(code).toContain('mapValue(value)')
|
|
70
|
+
expect(code).toContain('jobFilterStatus')
|
|
71
|
+
expect(code).not.toContain('aria-pressed={jobFilterStatus}')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('keeps a bare reference without one', () => {
|
|
75
|
+
expect(render(chip())).toContain('aria-pressed={jobFilterStatus}')
|
|
76
|
+
})
|
|
77
|
+
})
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import * as types from '@babel/types'
|
|
2
|
+
import generateJSXSyntax from '../../../src/node-handlers/node-to-jsx'
|
|
3
|
+
import {
|
|
4
|
+
JSXGenerationParams,
|
|
5
|
+
JSXGenerationOptions,
|
|
6
|
+
} from '../../../src/node-handlers/node-to-jsx/types'
|
|
7
|
+
import { UIDLAttributeValue, UIDLDependency, UIDLElementNode } from '@teleporthq/teleport-types'
|
|
8
|
+
|
|
9
|
+
const CALENDARKIT_DEPENDENCY: UIDLDependency = {
|
|
10
|
+
type: 'package',
|
|
11
|
+
path: 'calendarkit-basic',
|
|
12
|
+
version: '1.1.0',
|
|
13
|
+
meta: {
|
|
14
|
+
namedImport: true,
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Mirrors the post-resolver shape produced by the GUI's calendar-to-uidl
|
|
19
|
+
// mapper: elementType is the React component name, dependency is embedded.
|
|
20
|
+
const calendarNode = (
|
|
21
|
+
attrs: Record<string, UIDLAttributeValue>,
|
|
22
|
+
dependency: UIDLDependency = CALENDARKIT_DEPENDENCY,
|
|
23
|
+
elementType = 'BasicScheduler'
|
|
24
|
+
): UIDLElementNode => ({
|
|
25
|
+
type: 'element',
|
|
26
|
+
content: {
|
|
27
|
+
key: 'calendar',
|
|
28
|
+
elementType,
|
|
29
|
+
dependency,
|
|
30
|
+
attrs,
|
|
31
|
+
children: [],
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const buildParams = (): JSXGenerationParams => ({
|
|
36
|
+
dependencies: {},
|
|
37
|
+
propDefinitions: {},
|
|
38
|
+
stateDefinitions: {},
|
|
39
|
+
globalStateDefinitions: {},
|
|
40
|
+
nodesLookup: {},
|
|
41
|
+
windowImports: {},
|
|
42
|
+
localeReferences: [],
|
|
43
|
+
localeAttributeReferences: [],
|
|
44
|
+
globalReferences: [],
|
|
45
|
+
globalStateReferences: [],
|
|
46
|
+
hoistedConstants: [],
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const options: JSXGenerationOptions = {
|
|
50
|
+
dynamicReferencePrefixMap: {
|
|
51
|
+
prop: 'props',
|
|
52
|
+
state: '',
|
|
53
|
+
local: '',
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const getEventsAttr = (element: types.JSXElement): types.JSXAttribute | undefined =>
|
|
58
|
+
element.openingElement.attributes.find(
|
|
59
|
+
(attr): attr is types.JSXAttribute =>
|
|
60
|
+
attr.type === 'JSXAttribute' &&
|
|
61
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
62
|
+
attr.name.name === 'events'
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
// Asserts the `.map((e) => ({ ...e, start: new Date(e.start), end: new Date(e.end) }))`
|
|
66
|
+
// wrapper and returns the expression being mapped over (the guard or the array).
|
|
67
|
+
const expectEventsReviver = (eventsAttr: types.JSXAttribute): types.Expression => {
|
|
68
|
+
expect(eventsAttr.value?.type).toBe('JSXExpressionContainer')
|
|
69
|
+
const expression = (eventsAttr.value as types.JSXExpressionContainer).expression
|
|
70
|
+
|
|
71
|
+
expect(expression.type).toBe('CallExpression')
|
|
72
|
+
const mapCall = expression as types.CallExpression
|
|
73
|
+
expect(mapCall.callee.type).toBe('MemberExpression')
|
|
74
|
+
const mapCallee = mapCall.callee as types.MemberExpression
|
|
75
|
+
expect((mapCallee.property as types.Identifier).name).toBe('map')
|
|
76
|
+
|
|
77
|
+
const arrow = mapCall.arguments[0] as types.ArrowFunctionExpression
|
|
78
|
+
expect(arrow.type).toBe('ArrowFunctionExpression')
|
|
79
|
+
const body = arrow.body as types.ObjectExpression
|
|
80
|
+
expect(body.type).toBe('ObjectExpression')
|
|
81
|
+
expect(body.properties[0].type).toBe('SpreadElement')
|
|
82
|
+
|
|
83
|
+
const dateProps = body.properties.slice(1) as types.ObjectProperty[]
|
|
84
|
+
const propNames = dateProps.map((prop) => (prop.key as types.Identifier).name)
|
|
85
|
+
expect(propNames).toEqual(['start', 'end'])
|
|
86
|
+
dateProps.forEach((prop) => {
|
|
87
|
+
expect(prop.value.type).toBe('NewExpression')
|
|
88
|
+
expect(((prop.value as types.NewExpression).callee as types.Identifier).name).toBe('Date')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
return mapCallee.object as types.Expression
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const expectArrayIsArrayGuard = (mapSource: types.Expression) => {
|
|
95
|
+
expect(mapSource.type).toBe('ConditionalExpression')
|
|
96
|
+
const guard = mapSource as types.ConditionalExpression
|
|
97
|
+
expect(guard.test.type).toBe('CallExpression')
|
|
98
|
+
const isArrayCall = guard.test as types.CallExpression
|
|
99
|
+
const isArrayCallee = isArrayCall.callee as types.MemberExpression
|
|
100
|
+
expect((isArrayCallee.object as types.Identifier).name).toBe('Array')
|
|
101
|
+
expect((isArrayCallee.property as types.Identifier).name).toBe('isArray')
|
|
102
|
+
expect(guard.alternate.type).toBe('ArrayExpression')
|
|
103
|
+
expect((guard.alternate as types.ArrayExpression).elements.length).toBe(0)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
describe('calendarkit-basic BasicScheduler events revival', () => {
|
|
107
|
+
it('wraps a static events array with the Date revival map (no redundant guard)', () => {
|
|
108
|
+
const node = calendarNode({
|
|
109
|
+
view: { type: 'static', content: 'month' },
|
|
110
|
+
events: {
|
|
111
|
+
type: 'static',
|
|
112
|
+
content: [
|
|
113
|
+
{ id: '1', title: 'Standup', start: '2026-06-10T09:00:00', end: '2026-06-10T09:30:00' },
|
|
114
|
+
],
|
|
115
|
+
} as unknown as UIDLAttributeValue,
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const result = generateJSXSyntax(node, buildParams(), options) as types.JSXElement
|
|
119
|
+
const eventsAttr = getEventsAttr(result)
|
|
120
|
+
|
|
121
|
+
expect(eventsAttr).toBeDefined()
|
|
122
|
+
const mapSource = expectEventsReviver(eventsAttr as types.JSXAttribute)
|
|
123
|
+
expect(mapSource.type).toBe('ArrayExpression')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('wraps a dynamic state-bound events reference with the Array.isArray guard', () => {
|
|
127
|
+
const node = calendarNode({
|
|
128
|
+
events: {
|
|
129
|
+
type: 'dynamic',
|
|
130
|
+
content: { referenceType: 'state', id: 'calendarEvents' },
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const result = generateJSXSyntax(node, buildParams(), options) as types.JSXElement
|
|
135
|
+
const eventsAttr = getEventsAttr(result)
|
|
136
|
+
|
|
137
|
+
expect(eventsAttr).toBeDefined()
|
|
138
|
+
const mapSource = expectEventsReviver(eventsAttr as types.JSXAttribute)
|
|
139
|
+
expectArrayIsArrayGuard(mapSource)
|
|
140
|
+
|
|
141
|
+
const guard = mapSource as types.ConditionalExpression
|
|
142
|
+
const isArrayArg = (guard.test as types.CallExpression).arguments[0] as types.Identifier
|
|
143
|
+
expect(isArrayArg.name).toBe('calendarEvents')
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('leaves the element untouched when events is missing — the library defaults to []', () => {
|
|
147
|
+
const node = calendarNode({
|
|
148
|
+
view: { type: 'static', content: 'week' },
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
const result = generateJSXSyntax(node, buildParams(), options) as types.JSXElement
|
|
152
|
+
|
|
153
|
+
expect(getEventsAttr(result)).toBeUndefined()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('does not touch events attrs on elements from other packages', () => {
|
|
157
|
+
const node = calendarNode(
|
|
158
|
+
{
|
|
159
|
+
events: {
|
|
160
|
+
type: 'dynamic',
|
|
161
|
+
content: { referenceType: 'state', id: 'calendarEvents' },
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{ type: 'package', path: 'some-other-lib', version: '1.0.0' },
|
|
165
|
+
'OtherScheduler'
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const result = generateJSXSyntax(node, buildParams(), options) as types.JSXElement
|
|
169
|
+
const eventsAttr = getEventsAttr(result)
|
|
170
|
+
|
|
171
|
+
expect(eventsAttr).toBeDefined()
|
|
172
|
+
const expression = ((eventsAttr as types.JSXAttribute).value as types.JSXExpressionContainer)
|
|
173
|
+
.expression
|
|
174
|
+
expect(expression.type).not.toBe('CallExpression')
|
|
175
|
+
})
|
|
176
|
+
})
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as types from '@babel/types'
|
|
2
|
+
import generator from '@babel/generator'
|
|
3
|
+
import { elementNode, staticNode } from '@teleporthq/teleport-uidl-builders'
|
|
4
|
+
import { UIDLNode } from '@teleporthq/teleport-types'
|
|
5
|
+
import generateJSXSyntax from '../../../src/node-handlers/node-to-jsx'
|
|
6
|
+
import {
|
|
7
|
+
JSXGenerationParams,
|
|
8
|
+
JSXGenerationOptions,
|
|
9
|
+
} from '../../../src/node-handlers/node-to-jsx/types'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A repeater over the `ecommerce` global carries no array path — the editor's
|
|
13
|
+
* mapper keeps only the global's id — so the generator picks the array from the
|
|
14
|
+
* repeater's render-prop identifier. These pin that table: each e-commerce list
|
|
15
|
+
* the editor builds must land on the context array it was bound to, and an
|
|
16
|
+
* identifier the table does not know still means the cart's items.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const params = (): JSXGenerationParams => ({
|
|
20
|
+
dependencies: {},
|
|
21
|
+
propDefinitions: {},
|
|
22
|
+
stateDefinitions: {},
|
|
23
|
+
globalStateDefinitions: {},
|
|
24
|
+
nodesLookup: {},
|
|
25
|
+
windowImports: {},
|
|
26
|
+
localeReferences: [],
|
|
27
|
+
localeAttributeReferences: [],
|
|
28
|
+
globalReferences: [],
|
|
29
|
+
globalStateReferences: [],
|
|
30
|
+
hoistedConstants: [],
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const options: JSXGenerationOptions = {
|
|
34
|
+
dynamicReferencePrefixMap: {
|
|
35
|
+
prop: 'props',
|
|
36
|
+
state: '',
|
|
37
|
+
local: '',
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const generate = (renderPropIdentifier: string): string => {
|
|
42
|
+
const repeater = {
|
|
43
|
+
type: 'cms-list-repeater',
|
|
44
|
+
content: {
|
|
45
|
+
elementType: 'Repeater',
|
|
46
|
+
name: 'repeater',
|
|
47
|
+
key: 'repeater',
|
|
48
|
+
renderPropIdentifier,
|
|
49
|
+
source: 'ecommerce',
|
|
50
|
+
nodes: {
|
|
51
|
+
list: elementNode('container', {}, [staticNode('row')]),
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
} as unknown as UIDLNode
|
|
55
|
+
const tree = elementNode('container', {}, [repeater])
|
|
56
|
+
return generator(generateJSXSyntax(tree, params(), options) as types.JSXElement).code
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('cms-list-repeater over the ecommerce global', () => {
|
|
60
|
+
it('lists the shipping methods a checkout binds, not the cart items', () => {
|
|
61
|
+
expect(generate('shippingMethod')).toContain('items={ecommerce?.Cart?.shippingOptions || []}')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('lists the discount sets a checkout offers, in both fulfilment shapes', () => {
|
|
65
|
+
expect(generate('discountOption')).toContain('items={ecommerce?.Cart?.discountOptions || []}')
|
|
66
|
+
expect(generate('discountOptionPickup')).toContain(
|
|
67
|
+
'items={ecommerce?.Cart?.discountOptionsPickup || []}'
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('keeps the payment-provider and store-location lists on their arrays', () => {
|
|
72
|
+
expect(generate('paymentProvider')).toContain('items={ecommerce?.paymentProviders || []}')
|
|
73
|
+
expect(generate('storeLocation')).toContain('items={ecommerce?.storeLocations || []}')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('reads an identifier it does not know as the cart items', () => {
|
|
77
|
+
expect(generate('orderItem')).toContain('items={ecommerce?.Cart?.items || []}')
|
|
78
|
+
})
|
|
79
|
+
})
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as types from '@babel/types'
|
|
2
|
+
import generator from '@babel/generator'
|
|
3
|
+
import { elementNode, staticNode } from '@teleporthq/teleport-uidl-builders'
|
|
4
|
+
import { UIDLNode } from '@teleporthq/teleport-types'
|
|
5
|
+
import generateJSXSyntax from '../../../src/node-handlers/node-to-jsx'
|
|
6
|
+
import {
|
|
7
|
+
JSXGenerationParams,
|
|
8
|
+
JSXGenerationOptions,
|
|
9
|
+
} from '../../../src/node-handlers/node-to-jsx/types'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A repeater fed by a plain STATE has no `DataProvider` — nothing hoists its
|
|
13
|
+
* loading branch onto a `renderLoading` slot, so before `isLoading` existed the
|
|
14
|
+
* branch was generated and then thrown away, and a page that was still fetching
|
|
15
|
+
* showed its EMPTY branch ("no results") for the whole request.
|
|
16
|
+
*
|
|
17
|
+
* These tests pin both halves of the contract: the branch renders when the node
|
|
18
|
+
* carries the signal, and the emitted code is UNCHANGED when it does not — which
|
|
19
|
+
* is what keeps every data-source-backed mapper (products list, blog) on exactly
|
|
20
|
+
* the path it was on.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const params = (): JSXGenerationParams => ({
|
|
24
|
+
dependencies: {},
|
|
25
|
+
propDefinitions: {},
|
|
26
|
+
stateDefinitions: {},
|
|
27
|
+
globalStateDefinitions: {},
|
|
28
|
+
nodesLookup: {},
|
|
29
|
+
windowImports: {},
|
|
30
|
+
localeReferences: [],
|
|
31
|
+
localeAttributeReferences: [],
|
|
32
|
+
globalReferences: [],
|
|
33
|
+
globalStateReferences: [],
|
|
34
|
+
hoistedConstants: [],
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const options: JSXGenerationOptions = {
|
|
38
|
+
dynamicReferencePrefixMap: {
|
|
39
|
+
prop: 'props',
|
|
40
|
+
state: '',
|
|
41
|
+
local: '',
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Two children, so `unwrapTransparentRepeaterRoot` leaves the fragment in place —
|
|
46
|
+
// the shape a listing's skeleton branch actually has.
|
|
47
|
+
const loadingBranch = () =>
|
|
48
|
+
elementNode('fragment', {}, [
|
|
49
|
+
elementNode('container', {}, [staticNode('skeleton one')]),
|
|
50
|
+
elementNode('container', {}, [staticNode('skeleton two')]),
|
|
51
|
+
])
|
|
52
|
+
|
|
53
|
+
const repeaterNode = (isLoading?: { type: 'expr'; content: string }): UIDLNode =>
|
|
54
|
+
({
|
|
55
|
+
type: 'cms-list-repeater',
|
|
56
|
+
content: {
|
|
57
|
+
elementType: 'Repeater',
|
|
58
|
+
name: 'repeater',
|
|
59
|
+
key: 'repeater',
|
|
60
|
+
renderPropIdentifier: 'favouriteProduct',
|
|
61
|
+
source: 'favouriteProducts',
|
|
62
|
+
nodes: {
|
|
63
|
+
list: elementNode('container', {}, [staticNode('row')]),
|
|
64
|
+
empty: elementNode('container', {}, [staticNode('No products available')]),
|
|
65
|
+
loading: loadingBranch(),
|
|
66
|
+
},
|
|
67
|
+
...(isLoading ? { isLoading } : {}),
|
|
68
|
+
},
|
|
69
|
+
} as unknown as UIDLNode)
|
|
70
|
+
|
|
71
|
+
// The repeater is generated through its PARENT, which is where a non-JSX result
|
|
72
|
+
// gets wrapped in a `{ … }` expression container. Generating it standalone would
|
|
73
|
+
// not exercise that.
|
|
74
|
+
const generate = (isLoading?: { type: 'expr'; content: string }): string => {
|
|
75
|
+
const tree = elementNode('container', {}, [repeaterNode(isLoading)])
|
|
76
|
+
const ast = generateJSXSyntax(tree, params(), options) as types.JSXElement
|
|
77
|
+
return generator(ast).code
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
describe('cms-list-repeater loading branch', () => {
|
|
81
|
+
it('renders the loading branch instead of the repeater when isLoading is set', () => {
|
|
82
|
+
const code = generate({ type: 'expr', content: "isLoadingFavourites || ''" })
|
|
83
|
+
|
|
84
|
+
// `String(x) === "true"`, never a bare truthiness test: the flag is a string
|
|
85
|
+
// state, and the STRING `'false'` is truthy.
|
|
86
|
+
expect(code).toContain(`String(isLoadingFavourites || '') === "true"`)
|
|
87
|
+
expect(code).toContain('skeleton one')
|
|
88
|
+
expect(code).toContain('skeleton two')
|
|
89
|
+
// Still a ternary, not a replacement — the repeater is the other branch.
|
|
90
|
+
expect(code).toContain('<Repeater')
|
|
91
|
+
expect(code.indexOf('skeleton one')).toBeLessThan(code.indexOf('<Repeater'))
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('accepts a static flag', () => {
|
|
95
|
+
expect(generate()).not.toContain('String(')
|
|
96
|
+
|
|
97
|
+
const tree = elementNode('container', {}, [
|
|
98
|
+
{
|
|
99
|
+
...(repeaterNode() as { content: unknown }),
|
|
100
|
+
content: {
|
|
101
|
+
...(repeaterNode() as unknown as { content: Record<string, unknown> }).content,
|
|
102
|
+
isLoading: { type: 'static', content: 'true' },
|
|
103
|
+
},
|
|
104
|
+
} as unknown as UIDLNode,
|
|
105
|
+
])
|
|
106
|
+
const code = generator(generateJSXSyntax(tree, params(), options) as types.JSXElement).code
|
|
107
|
+
|
|
108
|
+
expect(code).toContain(`String("true") === "true"`)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('leaves a repeater WITHOUT isLoading exactly as it was', () => {
|
|
112
|
+
const code = generate()
|
|
113
|
+
|
|
114
|
+
// ⛔ The regression guard for every data-source-backed mapper: its loading
|
|
115
|
+
// branch is hoisted onto the `DataProvider`, so emitting it here as well
|
|
116
|
+
// would draw the same branch twice.
|
|
117
|
+
expect(code).not.toContain('String(')
|
|
118
|
+
expect(code).not.toContain('skeleton one')
|
|
119
|
+
expect(code).toContain('<Repeater')
|
|
120
|
+
expect(code).toContain('renderEmpty')
|
|
121
|
+
})
|
|
122
|
+
})
|