@sitecore-feaas/clientside 0.2.0 → 0.2.2

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 (120) hide show
  1. package/README.md +56 -36
  2. package/build/components/FEAASComponent.js +71 -11
  3. package/build/components/FEAASComponent.js.map +1 -1
  4. package/build/components/FEAASElement.js +411 -37
  5. package/build/components/FEAASElement.js.map +1 -1
  6. package/build/components/FEAASLoader.js +2 -2
  7. package/build/components/FEAASLoader.js.map +1 -1
  8. package/build/components/FEAASReactElement.js +47 -0
  9. package/build/components/FEAASReactElement.js.map +1 -0
  10. package/build/components/FEAASStylesheet.js +2 -2
  11. package/build/components/FEAASStylesheet.js.map +1 -1
  12. package/build/headless.js +4 -3
  13. package/build/headless.js.map +1 -1
  14. package/build/ui/FEAAS.js +5 -0
  15. package/build/ui/FEAAS.js.map +1 -0
  16. package/build/ui/FEAASContext.js +20 -8
  17. package/build/ui/FEAASContext.js.map +1 -1
  18. package/build/ui/FEAASEditor.js +2 -2
  19. package/build/ui/FEAASEditor.js.map +1 -1
  20. package/build/ui/FEAASExternal.js +128 -24
  21. package/build/ui/FEAASExternal.js.map +1 -1
  22. package/build/ui/FEAASPicker.js +7 -2
  23. package/build/ui/FEAASPicker.js.map +1 -1
  24. package/build/ui/index.js +3 -4
  25. package/build/ui/index.js.map +1 -1
  26. package/build/ui/react.js.map +1 -1
  27. package/build/utils/fetch.js +26 -0
  28. package/build/utils/fetch.js.map +1 -0
  29. package/dist/browser/headless.cjs +6 -6
  30. package/dist/browser/headless.cjs.map +4 -4
  31. package/dist/browser/headless.esm.js +6 -6
  32. package/dist/browser/headless.esm.js.map +1 -1
  33. package/dist/browser/index.cjs +7 -7
  34. package/dist/browser/index.cjs.map +4 -4
  35. package/dist/browser/index.esm.js +7 -7
  36. package/dist/browser/index.esm.js.map +1 -1
  37. package/dist/browser/react.cjs +6 -6
  38. package/dist/browser/react.cjs.map +4 -4
  39. package/dist/browser/react.esm.js +6 -6
  40. package/dist/browser/react.esm.js.map +1 -1
  41. package/dist/node/headless.cjs +4 -4
  42. package/dist/node/headless.cjs.map +4 -4
  43. package/dist/node/headless.esm.js +4 -4
  44. package/dist/node/headless.esm.js.map +4 -4
  45. package/dist/node/index.cjs +4 -4
  46. package/dist/node/index.cjs.map +4 -4
  47. package/dist/node/index.esm.js +4 -4
  48. package/dist/node/index.esm.js.map +4 -4
  49. package/dist/node/react.cjs +4 -4
  50. package/dist/node/react.cjs.map +4 -4
  51. package/dist/node/react.esm.js +4 -4
  52. package/dist/node/react.esm.js.map +4 -4
  53. package/headless.cjs +4 -4
  54. package/headless.cjs.map +4 -4
  55. package/headless.js +4 -4
  56. package/headless.js.map +4 -4
  57. package/package.json +11 -3
  58. package/react.cjs +6 -6
  59. package/react.cjs.map +4 -4
  60. package/react.js +6 -6
  61. package/react.js.map +4 -4
  62. package/scripts/build.js +1 -1
  63. package/scripts/test-setup.ts +1 -0
  64. package/src/components/FEAASComponent.tsx +96 -12
  65. package/src/components/FEAASElement.tsx +479 -41
  66. package/src/components/FEAASLoader.tsx +2 -2
  67. package/src/components/FEAASReactElement.tsx +61 -0
  68. package/src/components/FEAASStylesheet.tsx +3 -3
  69. package/src/headless.ts +5 -4
  70. package/src/ui/FEAAS.tsx +4 -0
  71. package/src/ui/FEAASContext.tsx +25 -7
  72. package/src/ui/FEAASEditor.tsx +7 -7
  73. package/src/ui/FEAASExternal.tsx +134 -24
  74. package/src/ui/FEAASPicker.tsx +9 -5
  75. package/src/ui/index.ts +4 -4
  76. package/src/ui/react.tsx +5 -0
  77. package/src/ui/tsconfig.json +1 -1
  78. package/src/utils/fetch.ts +44 -0
  79. package/test/FEAASBase.test.tsx +2 -2
  80. package/test/FEAASComponent.test.tsx +4 -1
  81. package/test/FEAASComponent2.test.tsx +215 -0
  82. package/test/FEAASContext.test.tsx +206 -29
  83. package/test/FEAASExternal.test.tsx +1 -8
  84. package/test/FEAASPicker.test.tsx +4 -2
  85. package/test/fetch-data.ts +55 -0
  86. package/types/components/FEAASComponent.d.ts +33 -3
  87. package/types/components/FEAASComponent.d.ts.map +1 -1
  88. package/types/components/FEAASElement.d.ts +299 -22
  89. package/types/components/FEAASElement.d.ts.map +1 -1
  90. package/types/components/FEAASLoader.d.ts +2 -2
  91. package/types/components/FEAASLoader.d.ts.map +1 -1
  92. package/types/components/FEAASReactElement.d.ts +30 -0
  93. package/types/components/FEAASReactElement.d.ts.map +1 -0
  94. package/types/components/FEAASStylesheet.d.ts +3 -3
  95. package/types/components/FEAASStylesheet.d.ts.map +1 -1
  96. package/types/headless.d.ts +4 -3
  97. package/types/headless.d.ts.map +1 -1
  98. package/types/tsbuildinfo +1 -1
  99. package/types/ui/FEAAS.d.ts +5 -0
  100. package/types/ui/FEAAS.d.ts.map +1 -0
  101. package/types/ui/FEAASContext.d.ts +16 -7
  102. package/types/ui/FEAASContext.d.ts.map +1 -1
  103. package/types/ui/FEAASEditor.d.ts +6 -6
  104. package/types/ui/FEAASEditor.d.ts.map +1 -1
  105. package/types/ui/FEAASExternal.d.ts +59 -10
  106. package/types/ui/FEAASExternal.d.ts.map +1 -1
  107. package/types/ui/FEAASPicker.d.ts +11 -9
  108. package/types/ui/FEAASPicker.d.ts.map +1 -1
  109. package/types/ui/index.d.ts +1 -4
  110. package/types/ui/index.d.ts.map +1 -1
  111. package/types/ui/react.d.ts.map +1 -1
  112. package/types/ui/tsbuildinfo +1 -0
  113. package/types/utils/fetch.d.ts +9 -0
  114. package/types/utils/fetch.d.ts.map +1 -0
  115. package/build/components/FEAASBase.js +0 -416
  116. package/build/components/FEAASBase.js.map +0 -1
  117. package/src/components/FEAASBase.tsx +0 -485
  118. package/types/components/FEAASBase.d.ts +0 -299
  119. package/types/components/FEAASBase.d.ts.map +0 -1
  120. package/types/tsbuildinfo_ui +0 -1
@@ -0,0 +1,215 @@
1
+ import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { FEAASComponent } from '../src/components/FEAASComponent.js'
3
+ import { clearFetchCache } from '../src/fetch.js'
4
+
5
+ const earliest = new Date(2000, 1, 1, 0, 0, 0)
6
+ const earlier = new Date(2000, 1, 2, 0, 0, 0)
7
+ const now = new Date(2000, 1, 2, 12, 0, 0)
8
+
9
+ describe('FEAAS component data', () => {
10
+ beforeAll(() => {
11
+ vi.setSystemTime(now)
12
+ FEAASComponent.register('feaas-component')
13
+ })
14
+
15
+ beforeEach(() => {
16
+ document.write()
17
+ clearFetchCache()
18
+ })
19
+
20
+ const checkRaceCondition = async (promises: { promise: Promise<any>; name: string }[]) => {
21
+ const resolved: { promise: Promise<any>; name: string }[] = []
22
+ await Promise.all(
23
+ promises.map(async (p) => {
24
+ await p.promise
25
+ resolved.push(p)
26
+ })
27
+ )
28
+ expect(resolved.map(({ name }) => name)).to.eql(promises.map(({ name }) => name))
29
+ }
30
+
31
+ const staleCacheSpy = (dataDelay = 100, htmlDelay = 100, stylesDelay = 100) => {
32
+ return vi
33
+ .spyOn(global, 'fetch')
34
+ .mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
35
+ if (url.includes('.css')) {
36
+ await new Promise((resolve) => setTimeout(resolve, stylesDelay))
37
+ return new Response('YESTERDAY {}', {
38
+ headers: {
39
+ date: now.toUTCString(),
40
+ 'last-modified': earliest.toUTCString()
41
+ }
42
+ })
43
+ }
44
+
45
+ if (url.includes('CDN')) {
46
+ await new Promise((resolve) => setTimeout(resolve, htmlDelay))
47
+ return new Response('C', {
48
+ headers: {
49
+ date: now.toUTCString(),
50
+ 'last-modified': earlier.toUTCString()
51
+ }
52
+ })
53
+ }
54
+
55
+ if (url === 'http://my-url') {
56
+ await new Promise((resolve) => setTimeout(resolve, dataDelay))
57
+ return new Response(JSON.stringify({ text: 'My new text' }), {})
58
+ }
59
+
60
+ if (url === 'http://my-url2') {
61
+ await new Promise((resolve) => setTimeout(resolve, dataDelay))
62
+ return new Response(JSON.stringify({ text: 'My new text2' }), {})
63
+ }
64
+
65
+ await new Promise((resolve) => setTimeout(resolve, htmlDelay))
66
+ return new Response('C', {
67
+ headers: {
68
+ date: now.toUTCString(),
69
+ 'last-modified': earlier.toUTCString()
70
+ }
71
+ })
72
+ })
73
+ }
74
+
75
+ describe('Should render component using stale cache, no data initially, then data as object', async () => {
76
+ const runWithDelay = async (delays: number[]) => {
77
+ const spy = staleCacheSpy(delays[0], delays[1], delays[2])
78
+ document.body.innerHTML = `<feaas-component data-path="text" library="L" cdn="CDN" component="C" version="V" revision="R"></feaas-component>`
79
+ const component = document.body.firstElementChild as FEAASComponent
80
+ const updateCall = vi.spyOn(component, 'update')
81
+ await expect(component.whenDataReady).resolves.toEqual({})
82
+ await expect(component.whenRendered).resolves.toEqual(component)
83
+ await expect(component.whenLoaded).resolves.toBeTruthy()
84
+ expect(component.readyData).toEqual({})
85
+ expect(component.innerHTML).toEqual('C')
86
+ expect(spy).toHaveBeenCalledTimes(2)
87
+ await checkRaceCondition([
88
+ { promise: component.whenDataReady, name: 'when data ready' },
89
+ { promise: component.whenRendered, name: 'when rendered' },
90
+ { promise: component.whenLoaded, name: 'when loaded' }
91
+ ])
92
+ expect(updateCall).toHaveBeenCalledTimes(1)
93
+ component.setAttribute('data', '{"text": "this is text"}')
94
+ await expect(component.whenDataReady).resolves.toEqual({ text: 'this is text' })
95
+ await expect(component.whenRendered).resolves.toEqual(component)
96
+ expect(updateCall).toHaveBeenCalledTimes(2)
97
+ expect(component.innerHTML).toEqual('this is text')
98
+ expect(component.readyData).toEqual({ text: 'this is text' })
99
+ expect(spy).toHaveBeenCalledTimes(2)
100
+ await expect(component.whenLoaded).resolves.toBeTruthy()
101
+ await checkRaceCondition([
102
+ { promise: component.whenDataReady, name: 'when data ready' },
103
+ { promise: component.whenRendered, name: 'when rendered' },
104
+ { promise: component.whenLoaded, name: 'when loaded' }
105
+ ])
106
+ }
107
+
108
+ it('DATA -> HTML -> STYLESHEET', async () => await runWithDelay([100, 200, 300]))
109
+ it('DATA -> STYLESHEET -> HTML', async () => await runWithDelay([100, 300, 200]))
110
+ it('HTML -> DATA -> STYLESHEET', async () => await runWithDelay([200, 100, 300]))
111
+ it('STYLESHEET -> DATA -> HTML', async () => await runWithDelay([200, 300, 100]))
112
+ it('HTML -> STYLESHEET -> DATA', async () => await runWithDelay([300, 100, 200]))
113
+ it('STYLESHEET -> HTML -> DATA', async () => await runWithDelay([300, 200, 100]))
114
+ })
115
+
116
+ describe('Should render component using stale cache and data as object first, then settings', () => {
117
+ const runWithDelay = async (delays: number[]) => {
118
+ const spy = staleCacheSpy(delays[0], delays[1], delays[2])
119
+ document.body.innerHTML = `<feaas-component data='{"text": "this is text"}' data-path="text" library="L" cdn="CDN" component="C" version="V" revision="R"></feaas-component>`
120
+ const component = document.body.firstElementChild as FEAASComponent
121
+ const updateCall = vi.spyOn(component, 'update')
122
+ await expect(component.whenDataReady).resolves.toEqual({ text: 'this is text' })
123
+ await expect(component.whenRendered).resolves.toEqual(component)
124
+ await expect(component.whenLoaded).resolves.toBeTruthy()
125
+ expect(component.readyData).toEqual({ text: 'this is text' })
126
+ expect(component.innerHTML).toEqual('this is text')
127
+ expect(spy).toHaveBeenCalledTimes(2)
128
+ await checkRaceCondition([
129
+ { promise: component.whenDataReady, name: 'when data ready' },
130
+ { promise: component.whenRendered, name: 'when rendered' },
131
+ { promise: component.whenLoaded, name: 'when loaded' }
132
+ ])
133
+ expect(updateCall).toHaveBeenCalledTimes(1)
134
+ component.setAttribute('data', '{"jsonpath": "$", "url": "http://my-url"}')
135
+ await expect(component.whenDataReady).resolves.toEqual({ text: 'My new text' })
136
+ await expect(component.whenRendered).resolves.toEqual(component)
137
+ expect(updateCall).toHaveBeenCalledTimes(2)
138
+ expect(component.innerHTML).toEqual('My new text')
139
+ expect(component.readyData).toEqual({ text: 'My new text' })
140
+ expect(spy).toHaveBeenCalledTimes(3)
141
+ await expect(component.whenLoaded).resolves.toBeTruthy()
142
+ await checkRaceCondition([
143
+ { promise: component.whenDataReady, name: 'when data ready' },
144
+ { promise: component.whenRendered, name: 'when rendered' },
145
+ { promise: component.whenLoaded, name: 'when loaded' }
146
+ ])
147
+ }
148
+
149
+ it('DATA -> HTML -> STYLESHEET', async () => await runWithDelay([100, 200, 300]))
150
+ it('DATA -> STYLESHEET -> HTML', async () => await runWithDelay([100, 300, 200]))
151
+ it('HTML -> DATA -> STYLESHEET', async () => await runWithDelay([200, 100, 300]))
152
+ it('STYLESHEET -> DATA -> HTML', async () => await runWithDelay([200, 300, 100]))
153
+ it('HTML -> STYLESHEET -> DATA', async () => await runWithDelay([300, 100, 200]))
154
+ it('STYLESHEET -> HTML -> DATA', async () => await runWithDelay([300, 200, 100]))
155
+ })
156
+
157
+ describe('Should render component using stale cache and data as settings', () => {
158
+ const runWithDelay = async (delays: number[]) => {
159
+ const spy = staleCacheSpy(delays[0], delays[1], delays[2])
160
+ document.body.innerHTML = `<feaas-component data='{ "jsonpath": "$", "url": "http://my-url" }' data-path="text" library="L" cdn="CDN" component="C" version="V" revision="R"></feaas-component>`
161
+ const component = document.body.firstElementChild as FEAASComponent
162
+ const updateCall = vi.spyOn(component, 'update')
163
+ await expect(component.whenDataReady).resolves.toEqual({ text: 'My new text' })
164
+ await expect(component.whenRendered).resolves.toEqual(component)
165
+ await expect(component.whenLoaded).resolves.toBeTruthy()
166
+ expect(component.readyData).toEqual({ text: 'My new text' })
167
+ expect(component.innerHTML).toEqual('My new text')
168
+ expect(spy).toHaveBeenCalledTimes(3)
169
+ await checkRaceCondition([
170
+ { promise: component.whenDataReady, name: 'when data ready' },
171
+ { promise: component.whenRendered, name: 'when rendered' },
172
+ { promise: component.whenLoaded, name: 'when loaded' }
173
+ ])
174
+ expect(updateCall).toHaveBeenCalledTimes(1)
175
+ }
176
+
177
+ it('DATA -> HTML -> STYLESHEET', async () => await runWithDelay([100, 200, 300]))
178
+ it('DATA -> STYLESHEET -> HTML', async () => await runWithDelay([100, 300, 200]))
179
+ it('HTML -> DATA -> STYLESHEET', async () => await runWithDelay([200, 100, 300]))
180
+ it('STYLESHEET -> DATA -> HTML', async () => await runWithDelay([200, 300, 100]))
181
+ it('HTML -> STYLESHEET -> DATA', async () => await runWithDelay([300, 100, 200]))
182
+ it('STYLESHEET -> HTML -> DATA', async () => await runWithDelay([300, 200, 100]))
183
+ })
184
+
185
+ describe('Should render component using stale cache and data as mapping', () => {
186
+ const runWithDelay = async (delays: number[]) => {
187
+ const spy = staleCacheSpy(delays[0], delays[1], delays[2])
188
+ document.body.innerHTML = `<feaas-component data='{ "datasource-1": {"jsonpath": "$", "url": "http://my-url"}, "datasource-2": {"jsonpath": "$", "url": "http://my-url2"} }' data-path="datasource-2.text" library="L" cdn="CDN" component="C" version="V" revision="R"></feaas-component>`
189
+ const component = document.body.firstElementChild as FEAASComponent
190
+ const updateCall = vi.spyOn(component, 'update')
191
+ await expect(component.whenDataReady).resolves.toEqual({ 'datasource-1': { text: 'My new text' }, 'datasource-2': { text: 'My new text2' } })
192
+ await expect(component.whenRendered).resolves.toEqual(component)
193
+ await expect(component.whenLoaded).resolves.toBeTruthy()
194
+ expect(component.readyData).toEqual({ 'datasource-1': { text: 'My new text' }, 'datasource-2': { text: 'My new text2' } })
195
+ expect(component.innerHTML).toEqual('My new text2')
196
+ expect(spy).toHaveBeenCalledTimes(4)
197
+ await checkRaceCondition([
198
+ { promise: component.whenDataReady, name: 'when data ready' },
199
+ { promise: component.whenRendered, name: 'when rendered' },
200
+ { promise: component.whenLoaded, name: 'when loaded' }
201
+ ])
202
+ expect(updateCall).toHaveBeenCalledTimes(1)
203
+ }
204
+
205
+ it('DATA -> HTML -> STYLESHEET', async () => await runWithDelay([100, 200, 300]))
206
+ it('DATA -> STYLESHEET -> HTML', async () => await runWithDelay([100, 300, 200]))
207
+ it('HTML -> DATA -> STYLESHEET', async () => await runWithDelay([200, 100, 300]))
208
+ it('STYLESHEET -> DATA -> HTML', async () => await runWithDelay([200, 300, 100]))
209
+ it('HTML -> STYLESHEET -> DATA', async () => await runWithDelay([300, 100, 200]))
210
+ it('STYLESHEET -> HTML -> DATA', async () => await runWithDelay([300, 200, 100]))
211
+ })
212
+
213
+ // TODO
214
+ it.skip('Should render component using non-stale cache and data as settings', async () => {})
215
+ })
@@ -1,13 +1,16 @@
1
1
  import { beforeEach, describe, it, assert, beforeAll, expect } from 'vitest'
2
- import { FEAASBase, ReactPayload } from '../src/headless.js'
2
+ import { FEAASElement, ReactPayload } from '../src/headless.js'
3
3
  import { FEAASContextProps, FEAASContext } from '../src/ui/FEAASContext.js'
4
4
  import { JSDOM } from 'jsdom'
5
5
  import { vi } from 'vitest'
6
+ import { ReactNode, ReactElement, JSXElementConstructor } from 'react'
6
7
 
7
8
  describe('React', function () {
8
9
  describe('FEAASContext', () => {
9
10
  beforeEach(() => {
10
- document.write()
11
+ document.open()
12
+ document.write('<html><head></head><body></body></html>')
13
+ document.close()
11
14
  })
12
15
  it('should initiate loading with isolation', async () => {
13
16
  // @ts-ignore
@@ -37,40 +40,214 @@ describe('React', function () {
37
40
  expect(spyLoad).toHaveBeenCalledTimes(1)
38
41
 
39
42
  expect(component.outerHTML).toBe(
40
- '<feaas-context src="test.js" library="abc" isolate="false" frontend="https://components.sitecoreclud.io" backend="https://components.sitecoreclud.io/api" cdn="https://feaas.blob.core.windows.net"><div><p>Context abc</p></div></feaas-context>'
43
+ '<feaas-context src="test.js" library="abc" isolate="false" frontend="https://components.sitecorecloud.io" backend="https://components.sitecorecloud.io/api" cdn="https://feaas.blob.core.windows.net" feaas-role="ui"><div><p>Context abc</p></div></feaas-context>'
41
44
  )
42
45
  })
43
- })
44
- it('should initiate loading without isolation', async () => {
45
- // @ts-ignore
46
- const spyLoad = vi.spyOn(FEAASContext.prototype, 'import')
47
- // @ts-ignore
48
- spyLoad.mockImplementation(async (url: string) => {
49
- if (url.match('test.js')) {
50
- return Promise.all([import('react'), import('react-dom'), import('react-dom/client')]).then(
51
- ([React, ReactDOM, ReactDOMClient]) => {
52
- return {
53
- React,
54
- ReactDOM,
55
- ReactDOMClient,
56
- Chakra: { Tooltip: {} },
57
- AppContext: ({ libraryId }: any) => <p>Context {libraryId}</p>
46
+ it('should initiate loading without isolation', async () => {
47
+ // @ts-ignore
48
+ const spyLoad = vi.spyOn(FEAASContext.prototype, 'import')
49
+ // @ts-ignore
50
+ spyLoad.mockImplementation(async (url: string) => {
51
+ if (url.match('test.js')) {
52
+ return Promise.all([import('react'), import('react-dom'), import('react-dom/client')]).then(
53
+ ([React, ReactDOM, ReactDOMClient]) => {
54
+ return {
55
+ React,
56
+ ReactDOM,
57
+ ReactDOMClient,
58
+ Chakra: { Tooltip: {} },
59
+ AppContext: ({ libraryId }: any) => <p>Context {libraryId}</p>
60
+ }
58
61
  }
59
- }
60
- )
62
+ )
63
+ }
64
+ })
65
+ // Fixme change to production component
66
+ document.body.innerHTML = '<feaas-context src="test.js" library="abc" isolate="false"></feaas-context>'
67
+
68
+ const component = document.body.firstElementChild as FEAASContext
69
+
70
+ await expect(component.whenRendered).resolves.toEqual(component)
71
+ expect(spyLoad).toHaveBeenCalledTimes(1)
72
+
73
+ expect(component.outerHTML).toBe(
74
+ '<feaas-context src="test.js" library="abc" isolate="false" frontend="https://components.sitecorecloud.io" backend="https://components.sitecorecloud.io/api" cdn="https://feaas.blob.core.windows.net" feaas-role="ui"><div><p>Context abc</p></div></feaas-context>'
75
+ )
76
+ })
77
+ it('should initiate loading with isolation', async () => {
78
+ // @ts-ignore
79
+ const spyLoad = vi.spyOn(FEAASContext.prototype, 'import')
80
+ // @ts-ignore
81
+ spyLoad.mockImplementation(async (url: string) => {
82
+ if (url.match('test.js')) {
83
+ return Promise.all([import('react'), import('react-dom'), import('react-dom/client')]).then(
84
+ ([React, ReactDOM, ReactDOMClient]) => {
85
+ return {
86
+ React,
87
+ ReactDOM,
88
+ ReactDOMClient,
89
+ Chakra: { Tooltip: {} },
90
+ AppContext: ({ libraryId }: any) => <p>Context {libraryId}</p>
91
+ }
92
+ }
93
+ )
94
+ }
95
+ })
96
+ // Fixme change to production component
97
+ document.body.innerHTML = '<feaas-context src="test.js" library="abc"></feaas-context>'
98
+
99
+ const component = document.body.firstElementChild as FEAASContext
100
+
101
+ await expect(component.whenRendered).resolves.toEqual(component)
102
+ expect(spyLoad).toHaveBeenCalledTimes(1)
103
+
104
+ expect(component.outerHTML).toBe(
105
+ '<feaas-context src="test.js" library="abc" frontend="https://components.sitecorecloud.io" backend="https://components.sitecorecloud.io/api" cdn="https://feaas.blob.core.windows.net" feaas-role="ui"></feaas-context>'
106
+ )
107
+ expect(component.shadowRoot.innerHTML).toBe('<div><p>Context abc</p></div>')
108
+ })
109
+ it('should nest components and render them in order', async () => {
110
+ const renders = [] as FEAASElement[]
111
+ class FEAASElementTestRenderOrder extends FEAASElement {
112
+ update() {
113
+ renders.push(this)
114
+ }
61
115
  }
116
+ FEAASElementTestRenderOrder.register('feaas-element-test-order')
117
+
118
+ const top = document.createElement('feaas-element-test-order') as FEAASElement
119
+ top.innerHTML = `
120
+ <feaas-element-test-order>
121
+ <feaas-element-test-order></feaas-element-test-order>
122
+ <feaas-element-test-order></feaas-element-test-order>
123
+ </feaas-element-test-order>
124
+ <feaas-element-test-order></feaas-element-test-order>
125
+ <feaas-element-test-order>
126
+ <feaas-element-test-order></feaas-element-test-order>
127
+ </feaas-element-test-order>
128
+ `
129
+ document.body.appendChild(top)
130
+ await expect(top.whenRendered).resolves.toEqual(top)
131
+ expect(top.descendants).toEqual([
132
+ top.children[0],
133
+ top.children[0].children[0],
134
+
135
+ top.children[0].children[1],
136
+
137
+ top.children[1],
138
+
139
+ top.children[2],
140
+
141
+ top.children[2].children[0]
142
+ ])
143
+ expect(renders).toEqual([
144
+ top.children[0].children[0],
145
+ top.children[0].children[1],
146
+ top.children[0],
147
+ top.children[1],
148
+ top.children[2].children[0],
149
+ top.children[2],
150
+ top
151
+ ])
62
152
  })
63
- // Fixme change to production component
64
- document.body.innerHTML = '<feaas-context src="test.js" library="abc"></feaas-context>'
65
153
 
66
- const component = document.body.firstElementChild as FEAASContext
154
+ it('should nest components into context', async () => {
155
+ const renders = [] as FEAASElement[]
156
+ class FEAASElementTestRenderOrder extends FEAASElement {
157
+ render(props: any, payload: any): ReactNode | ReactElement<any, string | JSXElementConstructor<any>> {
158
+ return null
159
+ }
160
+ load() {
161
+ return this.import('context.js')
162
+ }
163
+ }
164
+ ;[FEAASElementTestRenderOrder, FEAASContext].forEach((klass) => {
165
+ // @ts-ignore
166
+ const spyLoad = vi.spyOn(klass.prototype, 'import')
167
+ // @ts-ignore
168
+ spyLoad.mockImplementation(async (url: string) => {
169
+ if (url.match('context.js')) {
170
+ return Promise.all([import('react'), import('react-dom'), import('react-dom/client')]).then(
171
+ ([React, ReactDOM, ReactDOMClient]) => {
172
+ return {
173
+ React,
174
+ ReactDOM,
175
+ ReactDOMClient,
176
+ Chakra: { Tooltip: {} },
177
+ AppContext: ({ libraryId }: any) => <p>Context {libraryId}</p>
178
+ }
179
+ }
180
+ )
181
+ }
182
+ })
183
+ const spyRender = vi.spyOn(klass.prototype, 'render')
184
+ spyRender.mockImplementation(function (this: FEAASContext, { children }, { React }) {
185
+ renders.push(this)
186
+ return <>{children}</>
187
+ })
188
+ })
189
+
190
+ FEAASElementTestRenderOrder.register('feaas-element-test-nesting')
191
+
192
+ const context = document.createElement('feaas-context') as FEAASElement
193
+ context.setAttribute('library', 'library1')
194
+ const top = document.createElement('feaas-element-test-nesting') as FEAASElement
195
+ top.innerHTML = `
196
+ <feaas-element-test-nesting>
197
+ <feaas-element-test-nesting></feaas-element-test-nesting>
198
+ <feaas-element-test-nesting></feaas-element-test-nesting>
199
+ </feaas-element-test-nesting>
200
+ <feaas-element-test-nesting></feaas-element-test-nesting>
201
+ <feaas-element-test-nesting>
202
+ <feaas-element-test-nesting></feaas-element-test-nesting>
203
+ </feaas-element-test-nesting>
204
+ `
205
+ document.body.appendChild(context)
206
+ context.render = function () {
207
+ renders.push(this)
208
+ return FEAASContext.prototype.render.apply(this, arguments)
209
+ }
210
+ document.body.appendChild(top)
211
+ await expect(top.whenRendered).resolves.toEqual(top)
212
+ await expect(context.whenRendered).resolves.toEqual(context)
213
+ expect(top.refs.top).toEqual(context)
214
+ expect(top.refs.topReact).toEqual(context)
215
+ expect(top.refs.parentReact).toEqual(context)
216
+ expect(context.descendants).toEqual([
217
+ top,
218
+ top.children[0],
219
+ top.children[0].children[0],
67
220
 
68
- await expect(component.whenRendered).resolves.toEqual(component)
69
- expect(spyLoad).toHaveBeenCalledTimes(1)
221
+ top.children[0].children[1],
70
222
 
71
- expect(component.outerHTML).toBe(
72
- '<feaas-context src="test.js" library="abc" frontend="https://components.sitecoreclud.io" backend="https://components.sitecoreclud.io/api" cdn="https://feaas.blob.core.windows.net"></feaas-context>'
73
- )
74
- expect(component.shadowRoot.innerHTML).toBe('<div><p>Context abc</p></div>')
223
+ top.children[1],
224
+
225
+ top.children[2],
226
+
227
+ top.children[2].children[0]
228
+ ])
229
+ // FIXME: Currently re-renders happen on each element addition
230
+ //expect(renders).toEqual([
231
+ // context,
232
+ // top.children[0].children[0],
233
+ // top.children[0].children[1],
234
+ // top.children[0],
235
+ // top.children[1],
236
+ // top.children[2].children[0],
237
+ // top.children[2],
238
+ // top
239
+ //])
240
+
241
+ const els = [
242
+ context,
243
+ top.children[0].children[0],
244
+ top.children[0].children[1],
245
+ top.children[0],
246
+ top.children[1],
247
+ top.children[2].children[0],
248
+ top.children[2],
249
+ top
250
+ ].forEach((el) => expect(renders).toContain(el))
251
+ })
75
252
  })
76
253
  })
@@ -37,6 +37,7 @@ describe('External', () => {
37
37
  )
38
38
  }
39
39
  const typesComponentSchema = {
40
+ name: 'TypesComponent',
40
41
  properties: {
41
42
  stringProp: {
42
43
  type: 'string'
@@ -66,14 +67,6 @@ describe('External', () => {
66
67
  await expect(component.whenRendered).resolves.toEqual(component)
67
68
  expect(component.innerHTML).toEqual('<div>Hello my name is Chuck and my tel is 45657890875643</div>')
68
69
  })
69
- it('can take functional component name as id', async () => {
70
- Clientside.External.registerComponent(Hello)
71
- document.body.innerHTML =
72
- '<feaas-external id="external-component" class="-component" data-attributes-keys="first-name,telephone" first-name="Chuck" telephone="45657890875643" data-attributes="{&quot;first-name&quot;:&quot;Chuck&quot;,&quot;telephone&quot;:&quot;45657890875643&quot;}" data-external-id="Hello" contenteditable="false"></feaas-external>'
73
- const component = document.getElementById('external-component') as FEAASExternal
74
- await expect(component.whenRendered).resolves.toEqual(component)
75
- expect(component.innerHTML).toEqual('<div>Hello my name is Chuck and my tel is 45657890875643</div>')
76
- })
77
70
  it('should return empty if component is not registered', async () => {
78
71
  document.body.innerHTML =
79
72
  '<feaas-external id="external-component" class="-component" data-external-id="NotRegisteredComponent" contenteditable="false"></feaas-external>'
@@ -1,5 +1,5 @@
1
1
  import { beforeEach, describe, it, assert, beforeAll, expect } from 'vitest'
2
- import { FEAASBase, ReactPayload } from '../src/headless.js'
2
+ import { FEAASElement, ReactPayload } from '../src/headless.js'
3
3
  import { FEAASPickerProps, FEAASPicker } from '../src/ui/FEAASPicker.js'
4
4
  import { JSDOM } from 'jsdom'
5
5
  import { vi } from 'vitest'
@@ -7,7 +7,9 @@ import { vi } from 'vitest'
7
7
  describe('React', function () {
8
8
  describe('FEAASPicker', () => {
9
9
  beforeEach(() => {
10
- document.write()
10
+ document.open()
11
+ document.write('<html><head></head><body></body></html>')
12
+ document.close()
11
13
  })
12
14
  it('should initiate loading', async () => {
13
15
  // @ts-ignore
@@ -0,0 +1,55 @@
1
+ import { vi, describe, it, expect } from 'vitest'
2
+ import { fetchData } from '../src/utils/fetch.js'
3
+
4
+ describe('Fetches data', () => {
5
+ it('fetches when passed data object', async () => {
6
+ const result = await fetchData({ hello: 'world' })
7
+
8
+ expect(result).toEqual({ hello: 'world' })
9
+ })
10
+
11
+ it('fetches when passed settings object', async () => {
12
+ const spy = vi.spyOn(global, 'fetch')
13
+ spy.mockImplementation(
14
+ async (url: string, options?: Parameters<typeof fetch>[1]) =>
15
+ new Response(JSON.stringify({ data: 'My data' }), {})
16
+ )
17
+
18
+ const result = await fetchData({ jsonpath: '$', url: 'http://my-url' })
19
+
20
+ expect(result).toEqual({ data: 'My data' })
21
+
22
+ expect(spy).toHaveBeenCalledOnce()
23
+ })
24
+
25
+ it('fetches when passed mapping object', async () => {
26
+ const spy = vi.spyOn(global, 'fetch')
27
+ spy.mockImplementation(async (url: string, options?: Parameters<typeof fetch>[1]) => {
28
+ if (url === 'http://my-url1') return new Response(JSON.stringify({ data: 'My data1' }), {})
29
+ if (url === 'http://my-url2') return new Response(JSON.stringify({ data: 'My data2' }), {})
30
+ })
31
+
32
+ const inputData = {
33
+ datasourceId1: { jsonpath: '$', url: 'http://my-url1' },
34
+ datasourceId2: { data: 'This is plain data' },
35
+ datasourceId3: { jsonpath: '$', url: 'http://my-url2' },
36
+ datasourceId4: { jsonpath: '$', url: 'http://bad-url' }
37
+ }
38
+
39
+ const result = await fetchData(inputData)
40
+
41
+ expect(result).to.have.property('datasourceId1')
42
+ expect((result as Record<keyof typeof inputData, any>).datasourceId1).toEqual({ data: 'My data1' })
43
+
44
+ expect(result).to.have.property('datasourceId2')
45
+ expect((result as Record<keyof typeof inputData, any>).datasourceId2).toEqual({ data: 'This is plain data' })
46
+
47
+ expect(result).to.have.property('datasourceId3')
48
+ expect((result as Record<keyof typeof inputData, any>).datasourceId3).toEqual({ data: 'My data2' })
49
+
50
+ expect(result).to.have.property('datasourceId4')
51
+ expect((result as Record<keyof typeof inputData, any>).datasourceId4).toEqual({})
52
+
53
+ expect(spy).toHaveBeenCalledTimes(3)
54
+ })
55
+ })
@@ -1,5 +1,6 @@
1
- import { FEAASBase } from './FEAASBase.js';
2
- export declare function FEAASComponentsProps(element: FEAASBase): {
1
+ import { ControlledPromise } from '../utils/promise.js';
2
+ import { FEAASElement } from './FEAASElement.js';
3
+ export declare function FEAASComponentsProps(element: FEAASElement): {
3
4
  data: any;
4
5
  cdn: string;
5
6
  library: string;
@@ -15,7 +16,33 @@ export declare function FEAASComponentsProps(element: FEAASBase): {
15
16
  withStylesheet: boolean;
16
17
  };
17
18
  export type FEAASComponentsProps = ReturnType<typeof FEAASComponentsProps>;
18
- export declare class FEAASComponent extends FEAASBase<FEAASComponentsProps, string> {
19
+ /**
20
+ * - Initialization lifecycle:
21
+ * - - `set(props)`
22
+ * - - `scheduleRender()` - any attribute change causes render to schedule to next frame
23
+ * - - `whenRendered.restart()` - rendering promise is reset and will resolve after render
24
+ * - - `advance()` - called on next frame
25
+ * - - `if (isReadyToLoad()) => boolean` - check if component has all necessary properties to load
26
+ * - - `load() => payload` => component is loaded
27
+ * - - `onLoad?(payload)` => invoke loaded callback for cached version of a component
28
+ * - - `advance()` => going further in state machine
29
+ * - - `flush()` => flushes all react changes if needed
30
+ * - - `update()` => component-specific method (or react) to render changes to dom
31
+ * - - `render(payload, props)` => render means react
32
+ * - - `whenRendered.resolve()` - rendering promise is resolved after rendering
33
+ * - - IF CACHE WAS STALE - FETCH AND RENDER AGAIN
34
+ * - - `fetchByPassingCache()` - load up-to-date version of a component
35
+ * - - `onLoad(newPayload) `
36
+ * - - `advance()` => going further in state machine
37
+ * - - `flush()` => flushes all react changes if needed
38
+ * - - `update()` => component-specific method (or react) to render changes to dom
39
+ * - - `render(payload, props)` => render means react
40
+ * - - `whenRendered.resolve()` - rendering promise is resolved after rendering
41
+ * - - `whenLoaded.resolved()` - resolved after loading of up to date component
42
+ */
43
+ export declare class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
44
+ readyData: any;
45
+ whenDataReady: ControlledPromise<any>;
19
46
  static observedAttributes: string[];
20
47
  defaultProps: Partial<{
21
48
  data: any;
@@ -50,6 +77,7 @@ export declare class FEAASComponent extends FEAASBase<FEAASComponentsProps, stri
50
77
  withStylesheet: boolean;
51
78
  };
52
79
  attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
80
+ fetchData(): Promise<void>;
53
81
  getEditor(): any;
54
82
  constructor();
55
83
  edit(): boolean;
@@ -57,6 +85,8 @@ export declare class FEAASComponent extends FEAASBase<FEAASComponentsProps, stri
57
85
  isReadyToLoad(): boolean;
58
86
  onLoad: (html: string) => void;
59
87
  load(): Promise<string>;
88
+ alreadyRendered: boolean;
89
+ flush(): void;
60
90
  update(): void;
61
91
  }
62
92
  declare global {
@@ -1 +1 @@
1
- {"version":3,"file":"FEAASComponent.d.ts","sourceRoot":"","sources":["../../src/components/FEAASComponent.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS;;;;;;;;;;;;;;EA6BtD;AACD,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAA;AAC1E,qBAAa,cAAe,SAAQ,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACzE,MAAM,CAAC,kBAAkB,WAYxB;IAED,YAAY;;;;;;;;;;;;;;OAOsB;IAElC,YAAY,EAAE,OAAO,CAAA;IACrB,OAAO,CAAC,IAAI,EAAE,GAAG;IAIjB,QAAQ;;;;;;;;;;;;;;;IAIR,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAUhF,SAAS,IAAI,GAAG;;IAahB,IAAI;IASJ,WAAW;IAaX,aAAa;IAMb,MAAM,SAAU,MAAM,UAGrB;IAEK,IAAI;IAsBV,MAAM;CAYP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,GAAG,CAAC;QACZ,UAAU,iBAAiB;YACzB,iBAAiB,EAAE;gBACjB,IAAI,CAAC,EAAE,GAAG,CAAA;gBACV,QAAQ,CAAC,EAAE,MAAM,CAAA;gBACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;gBACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;gBAClB,QAAQ,CAAC,EAAE,GAAG,CAAA;gBACd,uBAAuB,CAAC,EAAE;oBAAE,MAAM,EAAE,MAAM,CAAA;iBAAE,CAAA;gBAC5C,eAAe,CAAC,EAAE,MAAM,CAAA;gBACxB,iBAAiB,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,EAAE,CAAA;aAC1C,GAAG,CACA;gBACE,kCAAkC;gBAClC,QAAQ,CAAC,EAAE,MAAM,CAAA;gBACjB,GAAG,CAAC,EAAE,MAAM,CAAA;gBACZ,OAAO,EAAE,MAAM,CAAA;gBACf,SAAS,EAAE,MAAM,CAAA;gBACjB,OAAO,CAAC,EAAE,MAAM,CAAA;gBAChB,QAAQ,CAAC,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAA;aAC5C,GACD;gBAAE,GAAG,EAAE,MAAM,CAAA;aAAE,GACf;gBAAE,QAAQ,EAAE,MAAM,CAAA;aAAE,CACvB,CAAA;SACF;KACF;CACF;AAGD,OAAO,EAAE,cAAc,IAAI,SAAS,EAAE,oBAAoB,IAAI,cAAc,EAAE,CAAA"}
1
+ {"version":3,"file":"FEAASComponent.d.ts","sourceRoot":"","sources":["../../src/components/FEAASComponent.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY;;;;;;;;;;;;;;EA6BzD;AAED,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAA;AAE1E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,cAAe,SAAQ,YAAY,CAAC,oBAAoB,EAAE,MAAM,CAAC;IAC5E,SAAS,EAAE,GAAG,CAAK;IACnB,aAAa,yBAGZ;IAED,MAAM,CAAC,kBAAkB,WAYxB;IAED,YAAY;;;;;;;;;;;;;;OAOsB;IAElC,YAAY,EAAE,OAAO,CAAA;IAErB,OAAO,CAAC,IAAI,EAAE,GAAG;IAIjB,QAAQ;;;;;;;;;;;;;;;IAIR,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAkB1E,SAAS;IAcf,SAAS,IAAI,GAAG;;IAkBhB,IAAI;IASJ,WAAW;IAaX,aAAa;IAMb,MAAM,SAAU,MAAM,UAGrB;IAEK,IAAI;IAkCV,eAAe,EAAE,OAAO,CAAA;IAExB,KAAK;IAML,MAAM;CAcP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,GAAG,CAAC;QACZ,UAAU,iBAAiB;YACzB,iBAAiB,EAAE;gBACjB,IAAI,CAAC,EAAE,GAAG,CAAA;gBACV,QAAQ,CAAC,EAAE,MAAM,CAAA;gBACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;gBACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;gBAClB,QAAQ,CAAC,EAAE,GAAG,CAAA;gBACd,uBAAuB,CAAC,EAAE;oBAAE,MAAM,EAAE,MAAM,CAAA;iBAAE,CAAA;gBAC5C,eAAe,CAAC,EAAE,MAAM,CAAA;gBACxB,iBAAiB,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,EAAE,CAAA;aAC1C,GAAG,CACA;gBACE,kCAAkC;gBAClC,QAAQ,CAAC,EAAE,MAAM,CAAA;gBACjB,GAAG,CAAC,EAAE,MAAM,CAAA;gBACZ,OAAO,EAAE,MAAM,CAAA;gBACf,SAAS,EAAE,MAAM,CAAA;gBACjB,OAAO,CAAC,EAAE,MAAM,CAAA;gBAChB,QAAQ,CAAC,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAA;aAC5C,GACD;gBAAE,GAAG,EAAE,MAAM,CAAA;aAAE,GACf;gBAAE,QAAQ,EAAE,MAAM,CAAA;aAAE,CACvB,CAAA;SACF;KACF;CACF;AAGD,OAAO,EAAE,cAAc,IAAI,SAAS,EAAE,oBAAoB,IAAI,cAAc,EAAE,CAAA"}