@sitecore-feaas/clientside 0.3.10 → 0.3.11-alpha.1

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 +31 -16
  8. package/build/ui/FEAASExternal.js.map +1 -1
  9. package/build/ui/react.js +68 -13
  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 +41 -17
  50. package/src/ui/react.tsx +90 -16
  51. package/src/utils/data.ts +8 -1
  52. package/test/FEAASComponent.react.test.tsx +99 -19
  53. package/test/FEAASExternal.test.tsx +2 -2
  54. package/test/ServerComponent.test.tsx +31 -15
  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 +13 -2
  62. package/types/ui/FEAASExternal.d.ts.map +1 -1
  63. package/types/ui/react.d.ts +19 -7
  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 = {
@@ -110,7 +124,7 @@ export default class FEAASExternal extends FEAASElement<FEAASExternalProps, Reac
110
124
  update() {
111
125
  const { dataExternalId: id } = this.props
112
126
  const Component = FEAASExternal.registered[id]?.component
113
- if (!Component && this.childNodes.length > 0) return
127
+ if (!Component && this.getAttribute('hydrate') == 'false') return
114
128
  return super.update()
115
129
  }
116
130
 
@@ -118,7 +132,7 @@ export default class FEAASExternal extends FEAASElement<FEAASExternalProps, Reac
118
132
  const { dataExternalId: id } = props
119
133
  const Component = FEAASExternal.registered[id]?.component
120
134
  if (!Component) {
121
- console.warn('FEAASExternal: The component you requested is not registered')
135
+ console.warn(`FEAASExternal: \`${id}\` is not registered`)
122
136
  }
123
137
  return <>{Component && <Component {...props} />}</>
124
138
  }
@@ -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
@@ -258,6 +278,10 @@ declare global {
258
278
  dangerouslySetInnerHTML?: { __html: string }
259
279
  [key: string]: any
260
280
  }
281
+ 'feaas-registration': {
282
+ suppressHydrationWarning?: boolean
283
+ components: string
284
+ }
261
285
  }
262
286
  }
263
287
  }
package/src/ui/react.tsx CHANGED
@@ -4,22 +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 { External, FEAASExternalPropsObject, getComponent, objectKeysToKebabCase } from './FEAASExternal.js'
14
+ import { reverseKebabCase } from '../utils/string.js'
12
15
  export { renderHTMLContent }
13
- import { 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
23
  preload?: boolean
20
24
  lastModified?: Date | string
21
25
  }
22
- export const ServerComponent = async (props: FEAASReactComponentProps) => {
26
+
27
+ export const FEAASReactStylesheet = (props: FEAASStylesheetProps) => {
28
+ return <feaas-stylesheet {...props} />
29
+ }
30
+
31
+ export const FEAASReactServerComponent = async ({ ...props }: FEAASReactComponentProps) => {
23
32
  const fetchedData = await DataSettings.fetch(props?.data || {})
24
33
 
25
34
  const p: FEAASReactComponentProps = { ...props }
@@ -35,7 +44,6 @@ export const ServerComponent = async (props: FEAASReactComponentProps) => {
35
44
  }
36
45
 
37
46
  export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps) {
38
- let html = ''
39
47
  let preload = null
40
48
  let children: null | React.ReactNode
41
49
 
@@ -43,9 +51,14 @@ export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps
43
51
  children = parseHTMLContent(props.template, data || {}).map((node, index) =>
44
52
  HTMLNodeToReact(
45
53
  node,
46
- (node) => {
47
- if (node.localName == 'feaas-external' && getComponent(node.getAttribute('data-external-id'))) {
48
- return [FEAASReactExternal, {}]
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 [FEAASExternalComponent, camelized]
49
62
  }
50
63
  },
51
64
  index
@@ -70,6 +83,7 @@ export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps
70
83
  suppressHydrationWarning
71
84
  template={null}
72
85
  data={typeof data == 'string' ? data : data ? JSON.stringify(data) : null}
86
+ hydrate={'false'}
73
87
  >
74
88
  {children}
75
89
  </feaas-component>
@@ -77,20 +91,36 @@ export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps
77
91
  )
78
92
  }
79
93
 
80
- export function FEAASReactExternal(props: { 'data-external-id': string }) {
81
- 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) || !componentName)
98
+ return (
99
+ <feaas-external data-external-id={componentName} suppressHydrationWarning hydrate={'false'}>
100
+ {fallback}
101
+ </feaas-external>
102
+ )
103
+ const parsed = FEAASExternalPropsObject(componentName, attributes)
104
+ delete parsed['suppressHydrationWarning']
82
105
  // execute component as a function to supress hydration errors inside
83
106
  return (
84
- <feaas-external {...props} suppressHydrationWarning>
85
- {typeof Component == 'function' ? Component({ ...props }) : <Component {...props} />}
86
- </feaas-external>
107
+ <>
108
+ <feaas-external
109
+ {...objectKeysToKebabCase(parsed)}
110
+ data-external-id={componentName}
111
+ suppressHydrationWarning
112
+ hydrate={'false'}
113
+ >
114
+ {Component == null ? null : <Component {...parsed} />}
115
+ </feaas-external>
116
+ </>
87
117
  )
88
118
  }
89
119
 
90
120
  /** Turn HTMLNode into React tree. It accepts callback to customize the behavior */
91
121
  export function HTMLNodeToReact(
92
122
  node: HTMLNode,
93
- callback?: (node: HTMLNode) => [tagName: any, attributes: Record<string, any>],
123
+ callback?: (node: HTMLNode, attributes: Record<string, any>) => [tagName: any, attributes: Record<string, any>],
94
124
  key?: any
95
125
  ): React.ReactNode {
96
126
  if (node.nodeType === 3) {
@@ -112,15 +142,59 @@ export function HTMLNodeToReact(
112
142
  [k]: v
113
143
  }
114
144
  }, {})
145
+ } else if (name == 'class') {
146
+ attributes.className = value
115
147
  } else {
116
148
  attributes[name] = value
117
149
  }
118
150
  }
119
151
 
120
- const reactNode = callback?.(node)
152
+ const reactNode = callback?.(node, attributes)
121
153
  const reactName = reactNode?.[0] || node.nodeName.toLowerCase()
122
- const reactAttributes = Object.assign({ key, suppressHydrationWarning: true }, attributes, reactNode?.[1] || {})
154
+ const reactAttributes = Object.assign({ key, suppressHydrationWarning: true }, reactNode?.[1] || attributes)
123
155
  return React.createElement(reactName, reactAttributes, ['img', 'br', 'input'].includes(reactName) ? null : children)
124
156
  }
125
157
 
158
+ const NextComponents = new Map()
159
+ var NextClientsideRenderer: any = null
160
+ var NextDynamicFunction: any = null
161
+ export function FEAASNextExternalComponent(props: FEAASReactExternalComponentProps) {
162
+ // See if component is registered in current context
163
+ const { componentName, ...attributes } = props
164
+ const Component = getComponent(componentName)?.component
165
+ if (!NextComponents.get(componentName)) {
166
+ // Designate component for rendering on clientside
167
+ const Dynamic = NextDynamicFunction(() => Promise.resolve(NextClientsideRenderer), {
168
+ ssr: false,
169
+ loading: () => <FEAASReactExternalComponent {...attributes} componentName={componentName} />
170
+ })
171
+
172
+ NextComponents.set(componentName, Dynamic)
173
+ }
174
+
175
+ const External = NextComponents.get(componentName)
176
+ return <External {...props} fallback={Component ? <Component {...props} /> : null}></External>
177
+ }
178
+ export function enableNextClientsideComponents(dynamic: any, component: any) {
179
+ NextDynamicFunction = dynamic
180
+ NextClientsideRenderer = component
181
+ return component
182
+ }
183
+
184
+ export function FEAASExternalComponent(props: FEAASReactExternalComponentProps) {
185
+ if (Object.keys(props).length == 0) {
186
+ // Serializing server-side registrations to clientside
187
+ return (
188
+ <feaas-registration components={JSON.stringify(Object.values(External.registered))} suppressHydrationWarning />
189
+ )
190
+ }
191
+ if (NextDynamicFunction) return FEAASNextExternalComponent(props)
192
+ return FEAASReactExternalComponent(props)
193
+ }
194
+
195
+ export const ExternalComponent = FEAASExternalComponent
196
+ export const ReactExternalComponent = FEAASReactExternalComponent
197
+ export const NextExternalComponent = FEAASNextExternalComponent
126
198
  export const Component = FEAASReactComponent
199
+ export const ServerComponent = FEAASReactServerComponent
200
+ 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,15 +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
112
  it('render template with style attribute', async () => {
88
- test(
113
+ validate(
89
114
  <section>
90
115
  <Component template={'<div data-path-src="abc">'} data={{ abc: 321 }}></Component>
91
116
  </section>,
92
- '<section><feaas-component 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>'
118
+ )
119
+ })
120
+ it('render unwrapped external component with stringified values', async () => {
121
+ registerComponent(Hello, helloSchema)
122
+ validate(
123
+ <section>
124
+ <ExternalComponent componentName='MyTestComponent' telephone={'12345'} firstName='Yarik' />
125
+ </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>'
93
173
  )
94
174
  })
95
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 =