@sitecore-feaas/clientside 0.3.9 → 0.3.11-alpha.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.
Files changed (67) hide show
  1. package/README.md +43 -0
  2. package/build/components/FEAASComponent.js.map +1 -1
  3. package/build/components/FEAASElement.js +12 -8
  4. package/build/components/FEAASElement.js.map +1 -1
  5. package/build/dom/rendering.js +16 -13
  6. package/build/dom/rendering.js.map +1 -1
  7. package/build/ui/FEAASExternal.js +29 -14
  8. package/build/ui/FEAASExternal.js.map +1 -1
  9. package/build/ui/react.js +73 -15
  10. package/build/ui/react.js.map +1 -1
  11. package/build/utils/data.js +9 -1
  12. package/build/utils/data.js.map +1 -1
  13. package/dist/browser/headless.cjs +7 -7
  14. package/dist/browser/headless.cjs.map +3 -3
  15. package/dist/browser/headless.esm.js +7 -7
  16. package/dist/browser/headless.esm.js.map +1 -1
  17. package/dist/browser/index.cjs +6 -6
  18. package/dist/browser/index.cjs.map +3 -3
  19. package/dist/browser/index.esm.js +6 -6
  20. package/dist/browser/index.esm.js.map +1 -1
  21. package/dist/browser/react.cjs +7 -7
  22. package/dist/browser/react.cjs.map +3 -3
  23. package/dist/browser/react.esm.js +7 -7
  24. package/dist/browser/react.esm.js.map +1 -1
  25. package/dist/node/headless.cjs +4 -4
  26. package/dist/node/headless.cjs.map +3 -3
  27. package/dist/node/headless.esm.js +4 -4
  28. package/dist/node/headless.esm.js.map +3 -3
  29. package/dist/node/index.cjs +4 -4
  30. package/dist/node/index.cjs.map +3 -3
  31. package/dist/node/index.esm.js +5 -5
  32. package/dist/node/index.esm.js.map +3 -3
  33. package/dist/node/react.cjs +6 -6
  34. package/dist/node/react.cjs.map +3 -3
  35. package/dist/node/react.esm.js +6 -6
  36. package/dist/node/react.esm.js.map +3 -3
  37. package/headless.cjs +4 -4
  38. package/headless.cjs.map +3 -3
  39. package/headless.js +4 -4
  40. package/headless.js.map +3 -3
  41. package/package.json +5 -1
  42. package/react.cjs +7 -7
  43. package/react.cjs.map +3 -3
  44. package/react.js +7 -7
  45. package/react.js.map +3 -3
  46. package/src/components/FEAASComponent.tsx +1 -0
  47. package/src/components/FEAASElement.tsx +15 -10
  48. package/src/dom/rendering.ts +16 -14
  49. package/src/ui/FEAASExternal.tsx +35 -15
  50. package/src/ui/react.tsx +88 -20
  51. package/src/utils/data.ts +8 -1
  52. package/test/FEAASComponent.react.test.tsx +97 -25
  53. package/test/FEAASExternal.test.tsx +2 -2
  54. package/test/ServerComponent.test.tsx +43 -13
  55. package/test/dom.test.ts +70 -0
  56. package/types/components/FEAASComponent.d.ts +1 -0
  57. package/types/components/FEAASComponent.d.ts.map +1 -1
  58. package/types/components/FEAASElement.d.ts.map +1 -1
  59. package/types/dom/rendering.d.ts.map +1 -1
  60. package/types/tsbuildinfo +1 -1
  61. package/types/ui/FEAASExternal.d.ts +9 -2
  62. package/types/ui/FEAASExternal.d.ts.map +1 -1
  63. package/types/ui/react.d.ts +17 -8
  64. package/types/ui/react.d.ts.map +1 -1
  65. package/types/ui/tsbuildinfo +1 -1
  66. package/types/utils/data.d.ts +1 -0
  67. package/types/utils/data.d.ts.map +1 -1
@@ -265,6 +265,7 @@ declare global {
265
265
  children?: any
266
266
  dangerouslySetInnerHTML?: { __html: string }
267
267
  'last-modified'?: string
268
+ hydrate?: 'false' | 'true'
268
269
  'with-stylesheet'?: 'false' | 'true' | ''
269
270
  } & (
270
271
  | {
@@ -145,17 +145,21 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
145
145
  if (typeof process != 'undefined' && process.env.TESTING) return
146
146
  const format = args[0]
147
147
 
148
- console.log(
149
- format +
150
- ' ' +
151
- args
152
- .slice(1)
153
- .map((arg: any) => {
154
- return arg && typeof arg == 'object' ? '%O' : '%s'
155
- })
156
- .join(' '),
157
- ...args.slice(1)
148
+ if (
149
+ (typeof location != 'undefined' && location?.hostname.startsWith('components-')) ||
150
+ localStorage['Sitecore.Components.Debug']
158
151
  )
152
+ console.log(
153
+ format +
154
+ ' ' +
155
+ args
156
+ .slice(1)
157
+ .map((arg: any) => {
158
+ return arg && typeof arg == 'object' ? '%O' : '%s'
159
+ })
160
+ .join(' '),
161
+ ...args.slice(1)
162
+ )
159
163
  }
160
164
 
161
165
  /** Nothing special happens on unmount, except setting a flag */
@@ -325,6 +329,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
325
329
  const { React, ReactDOM, ReactDOMClient } = (this.payload as ReactPayload) || {}
326
330
  if (!React) throw new Error(`${this.tagName}: React is not present in payload`)
327
331
  if (!ReactDOM) throw new Error(`${this.tagName}: ReactDOM is not present in payload`)
332
+ if (this.getAttribute('hydrate') == 'false') return
328
333
  if (!this.refs.topReact && !this.forceUpdateReact) {
329
334
  if (ReactDOMClient) {
330
335
  this.reactRoot ||= ReactDOMClient.createRoot(this.getReactRootElement())
@@ -84,14 +84,11 @@ export function setDOMAttribute(element: HTMLElement, attribute: string, value:
84
84
  value = config?.processValue(element, attribute, value)
85
85
  }
86
86
  var prop = attribute.replace('data-path-', '')
87
- if (attribute == 'data-path-src') {
87
+ if (attribute == 'data-path-src' || attribute == 'data-path-alt') {
88
88
  const picture = Array.from(element.children).find((n) => n.localName == 'picture') || element
89
89
  target = element.localName == 'img' ? element : <HTMLElement>picture.querySelector('img, video')
90
90
  } else if (attribute == 'data-path-href') {
91
91
  target = element.querySelector('a') || element.closest('a')
92
- } else if (attribute == 'data-path-alt') {
93
- const picture = Array.from(element.children).find((n) => n.localName == 'picture') || element
94
- target = picture.querySelector('img, picture')
95
92
  } else if (attribute == 'data-path-hidden') {
96
93
  value = !value ? true : null
97
94
  } else if (attribute == 'data-path' || attribute == 'data-embed-html' || attribute == 'html') {
@@ -121,13 +118,22 @@ export function setDOMAttribute(element: HTMLElement, attribute: string, value:
121
118
  if (typeof value == 'boolean') {
122
119
  value = value ? '' : null
123
120
  }
124
- if (!target) return
125
121
 
126
- var placeholder = String(
127
- (element.getAttribute('data-path-placeholder') || target[prop as keyof HTMLElement]) ?? target.getAttribute(prop)
128
- )
122
+ if (!target) return
129
123
 
130
- if (value != null) {
124
+ const usePlaceholder = prop == 'innerHTML' || prop == 'src'
125
+ if (usePlaceholder && (value == null || value === '')) {
126
+ var placeholder = String(
127
+ (element.getAttribute('data-path-placeholder') || target[prop as keyof HTMLElement]) ?? target.getAttribute(prop)
128
+ )
129
+ if (prop == 'innerHTML') {
130
+ target.innerHTML = placeholder
131
+ } else if (placeholder) {
132
+ target.setAttribute(prop, placeholder)
133
+ } else {
134
+ target.removeAttribute(prop)
135
+ }
136
+ } else if (value != null) {
131
137
  if (prop == 'innerHTML') {
132
138
  var str = String(value)
133
139
  // sanitize html of structural text elements
@@ -154,11 +160,7 @@ export function setDOMAttribute(element: HTMLElement, attribute: string, value:
154
160
  }
155
161
  }
156
162
  } else {
157
- if (prop == 'innerHTML') {
158
- target.innerHTML = placeholder
159
- } else {
160
- target.removeAttribute(prop)
161
- }
163
+ target.removeAttribute(prop)
162
164
  }
163
165
  }
164
166
 
@@ -22,7 +22,7 @@ const addTitles = (properties: { [key: string]: any }) =>
22
22
  }, {})
23
23
 
24
24
  /** Converts the keys of an object to kebab case. */
25
- const objectKeysToKebabCase = (object: { [key: string]: any } = {}) =>
25
+ export const objectKeysToKebabCase = (object: { [key: string]: any } = {}) =>
26
26
  Object.keys(object).reduce((acc: { [key: string]: any }, key) => {
27
27
  acc[kebabCase(key)] = object[key]
28
28
  return acc
@@ -61,38 +61,52 @@ const transformUiSchema = (uiSchema: UISchema, properties: { [key: string]: any
61
61
 
62
62
  return transformed
63
63
  }
64
+
64
65
  /** Parses a property value based on its property name and type defined in the JSON properties object. */
65
- export const parseProps = (propName: string, propValue: string, properties: JSONSchema['properties']) => {
66
- //@ts-ignore
66
+ export const parseProps = (propName: string, value: string, properties: JSONSchema['properties']) => {
67
67
  switch (properties?.[propName]?.type) {
68
68
  case 'string':
69
- return propValue
69
+ return value
70
70
  case 'object':
71
- return JSON.parse(propValue)
71
+ try {
72
+ return typeof value == 'object' && value != null ? value : JSON.parse(value)
73
+ } catch (e) {
74
+ return null
75
+ }
72
76
  case 'array':
73
- return JSON.parse(propValue)
77
+ try {
78
+ return Array.isArray(value) ? value : JSON.parse(value)
79
+ } catch (e) {
80
+ return null
81
+ }
74
82
  case 'number':
75
- return +propValue
83
+ return parseFloat(value)
76
84
  case 'integer':
77
- return +propValue
85
+ return parseInt(value)
78
86
  case 'boolean':
79
- return propValue === 'true'
87
+ return value == 'true' || value == '1'
80
88
  default:
81
- return propValue
89
+ return value
82
90
  }
83
91
  }
84
92
 
85
93
  export function FEAASExternalProps(element: FEAASElement): any {
86
- const id = element.getAttribute('data-external-id')
87
- const schema: JSONSchema = FEAASExternal.registered[id]?.schema
88
94
  const props = Array.from(element.attributes).reduce((prev, { name, value }) => {
95
+ return Object.assign(prev, { [name]: value })
96
+ }, {} as any)
97
+ return FEAASExternalPropsObject(props['data-external-id'], props)
98
+ }
99
+
100
+ export function FEAASExternalPropsObject(id: string, props: Record<string, string>): any {
101
+ const schema: JSONSchema = FEAASExternal.registered[id]?.schema
102
+ return Object.keys(props).reduce((prev, name) => {
103
+ const value = props[name]
89
104
  if (!name.startsWith('data-attribute') && !['class', 'id', 'contenteditable'].includes(name)) {
90
105
  return { ...prev, [reverseKebabCase(name)]: parseProps(reverseKebabCase(name), value, schema?.properties) }
91
106
  } else {
92
107
  return prev
93
108
  }
94
109
  }, {})
95
- return props
96
110
  }
97
111
 
98
112
  export type RegisteredComponents = {
@@ -178,9 +192,15 @@ export default class FEAASExternal extends FEAASElement<FEAASExternalProps, Reac
178
192
  * })
179
193
  *
180
194
  */
181
- static registerComponent(component: React.ComponentType, options: ExternalComponentOptions) {
195
+ static registerComponent(
196
+ component: React.ComponentType | ((...args: any[]) => Promise<React.ReactElement>),
197
+ options: ExternalComponentOptions
198
+ ) {
182
199
  if (!options.name)
183
- throw new Error('Could not register external component. Please make sure you provide a name in the options')
200
+ throw new Error(
201
+ 'Could not register external component. Please make sure you provide a name in the options' +
202
+ JSON.stringify(options)
203
+ )
184
204
  const id = options.name
185
205
  const { thumbnail, group, ui: uiSchema, ...schemaOptions } = options
186
206
  const schema = { ...schemaOptions, type: 'object' } as JSONSchema
package/src/ui/react.tsx CHANGED
@@ -4,23 +4,31 @@ import {
4
4
  fetchComponent,
5
5
  getComponentURL,
6
6
  getStylesheetURL,
7
- parseComponentSource
7
+ parseComponentSource,
8
+ FEAASStylesheetProps
8
9
  } from './index.js'
9
10
  import React from 'react'
10
11
  import { renderHTMLContent, HTMLNode, parseHTMLContent } from '../dom/html.js'
11
12
  import { DataSettings } from '../utils/settings.js'
13
+ import { FEAASExternalPropsObject, getComponent, objectKeysToKebabCase } from './FEAASExternal.js'
14
+ import { reverseKebabCase } from '../utils/string.js'
12
15
  export { renderHTMLContent }
13
- import { FEAASExternalProps, getComponent } from './FEAASExternal.js'
14
- export { External, linkComponents, registerComponent, getComponent } from './FEAASExternal.js'
16
+ export { External, linkComponents, registerComponent, getComponent, FEAASExternalPropsObject } from './FEAASExternal.js'
15
17
  export * from './index.js'
16
18
  export * from '../headless.js'
17
19
 
20
+ export type FEAASReactExternalComponentProps = { componentName: string; fallback?: any; [prop: string]: any }
21
+
18
22
  export type FEAASReactComponentProps = FEAASComponentProps & {
19
- src?: string
20
23
  preload?: boolean
21
24
  lastModified?: Date | string
22
25
  }
23
- export const ServerComponent = async (props: FEAASComponentProps) => {
26
+
27
+ export const FEAASReactStylesheet = (props: FEAASStylesheetProps) => {
28
+ return <feaas-stylesheet {...props} />
29
+ }
30
+
31
+ export const FEAASReactServerComponent = async ({ ...props }: FEAASReactComponentProps) => {
24
32
  const fetchedData = await DataSettings.fetch(props?.data || {})
25
33
 
26
34
  const p: FEAASReactComponentProps = { ...props }
@@ -36,7 +44,6 @@ export const ServerComponent = async (props: FEAASComponentProps) => {
36
44
  }
37
45
 
38
46
  export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps) {
39
- let html = ''
40
47
  let preload = null
41
48
  let children: null | React.ReactNode
42
49
 
@@ -44,10 +51,14 @@ export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps
44
51
  children = parseHTMLContent(props.template, data || {}).map((node, index) =>
45
52
  HTMLNodeToReact(
46
53
  node,
47
- (node) => {
48
- if (node.localName == 'feaas-external' && getComponent(node.getAttribute('data-external-id'))) {
49
- const { dataExternalId, ...props } = FEAASExternalProps(node as any)
50
- return [FEAASReactExternal, props]
54
+ (node, attributes) => {
55
+ if (node.localName == 'feaas-external') {
56
+ const camelized = {} as any
57
+ for (var prop in attributes) {
58
+ const key = prop == 'data-external-id' ? 'componentName' : reverseKebabCase(prop)
59
+ camelized[key] = attributes[prop]
60
+ }
61
+ return [NextDynamicFunction ? FEAASNextExternalComponent : FEAASReactExternalComponent, camelized]
51
62
  }
52
63
  },
53
64
  index
@@ -72,6 +83,7 @@ export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps
72
83
  suppressHydrationWarning
73
84
  template={null}
74
85
  data={typeof data == 'string' ? data : data ? JSON.stringify(data) : null}
86
+ hydrate={'false'}
75
87
  >
76
88
  {children}
77
89
  </feaas-component>
@@ -79,20 +91,31 @@ export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps
79
91
  )
80
92
  }
81
93
 
82
- export function FEAASReactExternal(props: { 'data-external-id': string }) {
83
- const Component = getComponent(props['data-external-id'])?.component
94
+ export function FEAASReactExternalComponent(props: FEAASReactExternalComponentProps) {
95
+ const { componentName, fallback, ...attributes } = props
96
+ const Component = getComponent(componentName)?.component
97
+ if (!Component && fallback) return fallback
98
+ const parsed = FEAASExternalPropsObject(componentName, attributes)
99
+ delete parsed['suppressHydrationWarning']
84
100
  // execute component as a function to supress hydration errors inside
85
101
  return (
86
- <feaas-external {...props} suppressHydrationWarning>
87
- {typeof Component == 'function' ? Component({ ...props }) : <Component {...props} />}
88
- </feaas-external>
102
+ <>
103
+ <feaas-external
104
+ {...objectKeysToKebabCase(parsed)}
105
+ data-external-id={componentName}
106
+ suppressHydrationWarning
107
+ hydrate={Component ? 'false' : null}
108
+ >
109
+ {Component == null ? null : <Component {...parsed} />}
110
+ </feaas-external>
111
+ </>
89
112
  )
90
113
  }
91
114
 
92
115
  /** Turn HTMLNode into React tree. It accepts callback to customize the behavior */
93
116
  export function HTMLNodeToReact(
94
117
  node: HTMLNode,
95
- callback?: (node: HTMLNode) => [tagName: any, attributes: Record<string, any>],
118
+ callback?: (node: HTMLNode, attributes: Record<string, any>) => [tagName: any, attributes: Record<string, any>],
96
119
  key?: any
97
120
  ): React.ReactNode {
98
121
  if (node.nodeType === 3) {
@@ -100,16 +123,61 @@ export function HTMLNodeToReact(
100
123
  }
101
124
  const children = Array.from(node.childNodes).map((child, index) => HTMLNodeToReact(child, callback, index))
102
125
 
103
- const attributes: { [key: string]: string } = {}
126
+ const attributes: { [key: string]: string | Record<string, string> } = {}
104
127
  for (let i = 0; i < node.attributes.length; i++) {
105
128
  const { name, value } = node.attributes[i]
106
- attributes[name] = value
129
+ if (name == 'style') {
130
+ attributes[name] = String(value || '')
131
+ .trim()
132
+ .split(/\s*;\s*/)
133
+ .reduce((object, pair) => {
134
+ const [k, v] = pair.split(':')
135
+ return {
136
+ ...object,
137
+ [k]: v
138
+ }
139
+ }, {})
140
+ } else if (name == 'class') {
141
+ attributes.className = value
142
+ } else {
143
+ attributes[name] = value
144
+ }
107
145
  }
108
146
 
109
- const reactNode = callback?.(node)
147
+ const reactNode = callback?.(node, attributes)
110
148
  const reactName = reactNode?.[0] || node.nodeName.toLowerCase()
111
- const reactAttributes = Object.assign({ key, suppressHydrationWarning: true }, attributes, reactNode?.[1] || {})
149
+ const reactAttributes = Object.assign({ key, suppressHydrationWarning: true }, reactNode?.[1] || attributes)
112
150
  return React.createElement(reactName, reactAttributes, ['img', 'br', 'input'].includes(reactName) ? null : children)
113
151
  }
114
152
 
153
+ const NextComponents = new Map()
154
+ var NextClientsideRenderer: any = null
155
+ var NextDynamicFunction: any = null
156
+ export function FEAASNextExternalComponent(props: FEAASReactExternalComponentProps) {
157
+ // See if component is registered in current context
158
+ const { componentName, ...attributes } = props
159
+ const Component = getComponent(componentName)?.component
160
+ if (!NextComponents.get(componentName)) {
161
+ // Designate component for rendering on clientside
162
+ const Dynamic = NextDynamicFunction(() => Promise.resolve(NextClientsideRenderer), {
163
+ ssr: false,
164
+ loading: () => <FEAASReactExternalComponent {...attributes} componentName={componentName} />
165
+ })
166
+
167
+ NextComponents.set(componentName, Dynamic)
168
+ }
169
+
170
+ const External = NextComponents.get(componentName)
171
+ return <External {...props} fallback={Component ? <Component {...props} /> : null}></External>
172
+ }
173
+ export function enableNextClientsideComponents(dynamic: any, component: any) {
174
+ NextDynamicFunction = dynamic
175
+ NextClientsideRenderer = component
176
+ return component
177
+ }
178
+
179
+ export const ExternalComponent = FEAASReactExternalComponent
180
+ export const NextExternalComponent = FEAASNextExternalComponent
115
181
  export const Component = FEAASReactComponent
182
+ export const ServerComponent = FEAASReactServerComponent
183
+ export const Stylesheet = FEAASReactStylesheet
package/src/utils/data.ts CHANGED
@@ -1,7 +1,14 @@
1
1
  import { JSONPath } from 'jsonpath-plus'
2
2
 
3
+ export function queryObjectRaw(json: any, path: string) {
4
+ try {
5
+ return JSONPath<any[]>({ path, json })
6
+ } catch (e) {
7
+ return []
8
+ }
9
+ }
3
10
  export function queryObject(json: any, path: string) {
4
- return JSONPath<any[]>({ path, json })?.flat(Infinity) || []
11
+ return queryObjectRaw(json, path)?.flat(Infinity) || []
5
12
  }
6
13
 
7
14
  export function queryScopes(scopes: any, datapath: string, singleValue = false, json: any = scopes) {
@@ -1,9 +1,32 @@
1
- import React from 'react'
1
+ import React, { useContext } from 'react'
2
2
  import ReactDOM from 'react-dom'
3
3
  import { describe, expect, it } from 'vitest'
4
- import { Component } from '../src/ui/react.js'
4
+ import { Component, registerComponent, ExternalComponent } from '../src/ui/react.js'
5
5
 
6
- export async function test(react: any, html: string) {
6
+ function Hello(props: { firstName: string; telephone: string }) {
7
+ const context = useContext(DataContext)
8
+
9
+ return React.createElement(
10
+ 'div',
11
+ {},
12
+ `Hello my name is ${props.firstName} (${typeof props.firstName}) and my tel is ${
13
+ props.telephone
14
+ } (${typeof props.telephone}). And context value is ${context}`
15
+ )
16
+ }
17
+ const helloSchema = {
18
+ name: 'MyTestComponent',
19
+ properties: {
20
+ firstName: {
21
+ type: 'string'
22
+ },
23
+ telephone: {
24
+ type: 'number'
25
+ }
26
+ }
27
+ } as const
28
+
29
+ export async function validate(react: any, html: string) {
7
30
  const div = document.createElement('div')
8
31
  document.body.appendChild(div)
9
32
  const root = ReactDOM.render(react, div)
@@ -22,57 +45,59 @@ export async function test(react: any, html: string) {
22
45
  expect(div2.innerHTML).toEqual(html)
23
46
  }
24
47
 
48
+ const DataContext = React.createContext('DEFAULT')
49
+
25
50
  describe('React', () => {
26
51
  it('render react component', async () => {
27
- await test(
52
+ await validate(
28
53
  <section>
29
54
  <Component src='library/test'></Component>
30
55
  </section>,
31
- '<section><feaas-component library="library" component="test" src="library/test"></feaas-component></section>'
56
+ '<section><feaas-component library="library" component="test" src="library/test" hydrate="false"></feaas-component></section>'
32
57
  )
33
58
  })
34
59
  it('render react with empty template', async () => {
35
- await test(
60
+ await validate(
36
61
  <section>
37
62
  <Component src='library/test' template=''></Component>
38
63
  </section>,
39
- '<section><feaas-component library="library" component="test" src="library/test"></feaas-component></section>'
64
+ '<section><feaas-component library="library" component="test" src="library/test" hydrate="false"></feaas-component></section>'
40
65
  )
41
66
  })
42
67
  it('render react component with src and overrides', async () => {
43
- await test(
68
+ await validate(
44
69
  <section>
45
70
  <Component src='library/test' component='test2'></Component>
46
71
  </section>,
47
- '<section><feaas-component library="library" component="test2" src="library/test"></feaas-component></section>'
72
+ '<section><feaas-component library="library" component="test2" src="library/test" hydrate="false"></feaas-component></section>'
48
73
  )
49
74
  })
50
75
  it('render react component with extras', async () => {
51
- await test(
76
+ await validate(
52
77
  <section>
53
78
  <Component src='library/test' version='v1'></Component>
54
79
  </section>,
55
- '<section><feaas-component library="library" component="test" version="v1" src="library/test"></feaas-component></section>'
80
+ '<section><feaas-component library="library" component="test" version="v1" src="library/test" hydrate="false"></feaas-component></section>'
56
81
  )
57
82
  })
58
83
  it('render react template', async () => {
59
- await test(
84
+ await validate(
60
85
  <section>
61
86
  <Component src='library/test' template={'<div data-path-src="abc">'}></Component>
62
87
  </section>,
63
- '<section><feaas-component library="library" component="test" src="library/test"><div data-path-src="abc"></div></feaas-component></section>'
88
+ '<section><feaas-component library="library" component="test" src="library/test" hydrate="false"><div data-path-src="abc"></div></feaas-component></section>'
64
89
  )
65
90
  })
66
91
  it('render template with data and src', async () => {
67
- await test(
92
+ await validate(
68
93
  <section>
69
94
  <Component src='library/test' template={'<div data-path-src="abc">'} data={{ abc: 321 }}></Component>
70
95
  </section>,
71
- '<section><feaas-component library="library" component="test" src="library/test" data="{&quot;abc&quot;:321}"><div data-path-src="abc"></div></feaas-component></section>'
96
+ '<section><feaas-component library="library" component="test" src="library/test" data="{&quot;abc&quot;:321}" hydrate="false"><div data-path-src="abc"></div></feaas-component></section>'
72
97
  )
73
98
  })
74
99
  it('render template with data but without src', async () => {
75
- test(
100
+ validate(
76
101
  <section>
77
102
  <Component
78
103
  library='library'
@@ -81,23 +106,70 @@ describe('React', () => {
81
106
  data={{ abc: 321 }}
82
107
  ></Component>
83
108
  </section>,
84
- '<section><feaas-component library="library" component="test" data="{&quot;abc&quot;:321}"><div data-path-src="abc"></div></feaas-component></section>'
109
+ '<section><feaas-component library="library" component="test" data="{&quot;abc&quot;:321}" hydrate="false"><div data-path-src="abc"></div></feaas-component></section>'
85
110
  )
86
111
  })
87
- it('render template with data with src and stringy data', async () => {
88
- test(
112
+ it('render template with style attribute', async () => {
113
+ validate(
89
114
  <section>
90
- <Component src='library/test' template={'<div data-path-src="abc">'} data={'{"abc":321}'}></Component>
115
+ <Component template={'<div data-path-src="abc">'} data={{ abc: 321 }}></Component>
91
116
  </section>,
92
- '<section><feaas-component library="library" component="test" src="library/test" data="{&quot;abc&quot;:321}"><div data-path-src="abc"></div></feaas-component></section>'
117
+ '<section><feaas-component data="{&quot;abc&quot;:321}" hydrate="false"><div data-path-src="abc"></div></feaas-component></section>'
93
118
  )
94
119
  })
95
- it('render template with data and template', async () => {
96
- test(
120
+ it('render unwrapped external component with stringified values', async () => {
121
+ registerComponent(Hello, helloSchema)
122
+ validate(
97
123
  <section>
98
- <Component template={'<div data-path-src="abc">'} data={'{"abc":321}'}></Component>
124
+ <ExternalComponent componentName='MyTestComponent' telephone={'12345'} firstName='Yarik' />
99
125
  </section>,
100
- '<section><feaas-component data="{&quot;abc&quot;:321}"><div data-path-src="abc"></div></feaas-component></section>'
126
+ '<section><feaas-external telephone="12345" first-name="Yarik" data-external-id="MyTestComponent" hydrate="false"><div>Hello my name is Yarik (string) and my tel is 12345 (number). And context value is DEFAULT</div></feaas-external></section>'
127
+ )
128
+ })
129
+ it('render wrapped external component with parsed values', async () => {
130
+ registerComponent(Hello, helloSchema)
131
+ validate(
132
+ <section>
133
+ <Component
134
+ template={
135
+ '<feaas-external telephone="12345" first-name="Yarik" data-external-id="MyTestComponent"></feaas-external>'
136
+ }
137
+ data={{ abc: 321 }}
138
+ ></Component>
139
+ </section>,
140
+ '<section><feaas-component data="{&quot;abc&quot;:321}" hydrate="false"><feaas-external telephone="12345" first-name="Yarik" data-external-id="MyTestComponent" hydrate="false"><div>Hello my name is Yarik (string) and my tel is 12345 (number). And context value is DEFAULT</div></feaas-external></feaas-component></section>'
141
+ )
142
+ })
143
+ it('render wrapped external component with context provider', async () => {
144
+ registerComponent(Hello, helloSchema)
145
+ validate(
146
+ <DataContext.Provider value='INHERITED'>
147
+ <section>
148
+ <Component
149
+ template={
150
+ '<feaas-external telephone="12345" first-name="Yarik" data-external-id="MyTestComponent"></feaas-external>'
151
+ }
152
+ data={{ abc: 321 }}
153
+ ></Component>
154
+ </section>
155
+ </DataContext.Provider>,
156
+ '<section><feaas-component data="{&quot;abc&quot;:321}" hydrate="false"><feaas-external telephone="12345" first-name="Yarik" data-external-id="MyTestComponent" hydrate="false"><div>Hello my name is Yarik (string) and my tel is 12345 (number). And context value is INHERITED</div></feaas-external></feaas-component></section>'
157
+ )
158
+ })
159
+ it('render wrapped external component with context provider and data mapping', async () => {
160
+ registerComponent(Hello, helloSchema)
161
+ validate(
162
+ <DataContext.Provider value='INHERITED'>
163
+ <section>
164
+ <Component
165
+ template={
166
+ '<feaas-external data-path-attributes="$" first-name="Yarik" data-path-attributes="$" data-external-id="MyTestComponent"></feaas-external>'
167
+ }
168
+ data={{ telephone: 321 }}
169
+ ></Component>
170
+ </section>
171
+ </DataContext.Provider>,
172
+ '<section><feaas-component data="{&quot;telephone&quot;:321}" hydrate="false"><feaas-external data-path-attributes="$" first-name="Yarik" telephone="321" data-attributes-keys="telephone" data-external-id="MyTestComponent" hydrate="false"><div>Hello my name is Yarik (string) and my tel is 321 (number). And context value is INHERITED</div></feaas-external></feaas-component></section>'
101
173
  )
102
174
  })
103
175
  })
@@ -17,7 +17,7 @@ describe('External', () => {
17
17
  type: 'number'
18
18
  }
19
19
  }
20
- }
20
+ } as const
21
21
  function Hello2(props: { firstName: string; address: string }) {
22
22
  return React.createElement('div', {}, `Hello my name is ${props.firstName} and my address is ${props.address}`)
23
23
  }
@@ -58,7 +58,7 @@ describe('External', () => {
58
58
  type: 'object'
59
59
  }
60
60
  }
61
- }
61
+ } as const
62
62
  it('can register and render component', async () => {
63
63
  Clientside.External.registerComponent(Hello, helloSchema)
64
64
  document.body.innerHTML =