@sitecore-feaas/clientside 0.2.2 → 0.3.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 (113) hide show
  1. package/README.md +1 -1
  2. package/build/components/FEAASComponent.js +11 -5
  3. package/build/components/FEAASComponent.js.map +1 -1
  4. package/build/components/FEAASElement.js +34 -9
  5. package/build/components/FEAASElement.js.map +1 -1
  6. package/build/dom/rendering.js +1 -1
  7. package/build/dom/rendering.js.map +1 -1
  8. package/build/dom/thumbnail.js +29 -24
  9. package/build/dom/thumbnail.js.map +1 -1
  10. package/build/headless.js +5 -1
  11. package/build/headless.js.map +1 -1
  12. package/build/ui/FEAASExternal.js +15 -13
  13. package/build/ui/FEAASExternal.js.map +1 -1
  14. package/build/ui/FEAASPicker.js +20 -5
  15. package/build/ui/FEAASPicker.js.map +1 -1
  16. package/build/ui/react.js +33 -10
  17. package/build/ui/react.js.map +1 -1
  18. package/build/{dom → utils}/data.js +3 -0
  19. package/build/utils/data.js.map +1 -0
  20. package/build/utils/datapath.js +21 -2
  21. package/build/utils/datapath.js.map +1 -1
  22. package/build/utils/settings.js +162 -0
  23. package/build/utils/settings.js.map +1 -0
  24. package/dist/browser/headless.cjs +8 -6
  25. package/dist/browser/headless.cjs.map +4 -4
  26. package/dist/browser/headless.esm.js +8 -6
  27. package/dist/browser/headless.esm.js.map +1 -1
  28. package/dist/browser/index.cjs +8 -6
  29. package/dist/browser/index.cjs.map +4 -4
  30. package/dist/browser/index.esm.js +8 -6
  31. package/dist/browser/index.esm.js.map +1 -1
  32. package/dist/browser/react.cjs +8 -6
  33. package/dist/browser/react.cjs.map +4 -4
  34. package/dist/browser/react.esm.js +8 -6
  35. package/dist/browser/react.esm.js.map +1 -1
  36. package/dist/node/headless.cjs +6 -4
  37. package/dist/node/headless.cjs.map +4 -4
  38. package/dist/node/headless.esm.js +6 -4
  39. package/dist/node/headless.esm.js.map +4 -4
  40. package/dist/node/index.cjs +6 -4
  41. package/dist/node/index.cjs.map +4 -4
  42. package/dist/node/index.esm.js +6 -4
  43. package/dist/node/index.esm.js.map +4 -4
  44. package/dist/node/react.cjs +6 -4
  45. package/dist/node/react.cjs.map +4 -4
  46. package/dist/node/react.esm.js +6 -4
  47. package/dist/node/react.esm.js.map +4 -4
  48. package/headless.cjs +6 -4
  49. package/headless.cjs.map +4 -4
  50. package/headless.js +6 -4
  51. package/headless.js.map +4 -4
  52. package/package.json +1 -1
  53. package/react.cjs +8 -6
  54. package/react.cjs.map +4 -4
  55. package/react.js +8 -6
  56. package/react.js.map +4 -4
  57. package/src/components/FEAASComponent.tsx +21 -8
  58. package/src/components/FEAASElement.tsx +35 -8
  59. package/src/dom/component.ts +2 -2
  60. package/src/dom/html.ts +2 -2
  61. package/src/dom/rendering.ts +10 -7
  62. package/src/dom/thumbnail.ts +29 -24
  63. package/src/headless.ts +6 -1
  64. package/src/ui/FEAASExternal.tsx +15 -13
  65. package/src/ui/FEAASPicker.tsx +23 -6
  66. package/src/ui/react.tsx +53 -16
  67. package/src/{dom → utils}/data.ts +3 -1
  68. package/src/utils/datapath.ts +24 -2
  69. package/src/utils/settings.ts +200 -0
  70. package/test/FEAASComponent.react.test.tsx +1 -1
  71. package/test/{FEAASComponent2.test.tsx → FEAASComponentData.test.tsx} +56 -52
  72. package/test/ServerComponent.test.tsx +33 -0
  73. package/test/cdn.ts +1 -1
  74. package/test/datasettings.test.ts +248 -0
  75. package/test/{extras.ts → extras.test.ts} +1 -1
  76. package/test/fetch.ts +2 -2
  77. package/types/components/FEAASComponent.d.ts +10 -5
  78. package/types/components/FEAASComponent.d.ts.map +1 -1
  79. package/types/components/FEAASElement.d.ts +2 -0
  80. package/types/components/FEAASElement.d.ts.map +1 -1
  81. package/types/dom/component.d.ts +2 -2
  82. package/types/dom/component.d.ts.map +1 -1
  83. package/types/dom/html.d.ts +2 -2
  84. package/types/dom/html.d.ts.map +1 -1
  85. package/types/dom/rendering.d.ts +8 -7
  86. package/types/dom/rendering.d.ts.map +1 -1
  87. package/types/dom/thumbnail.d.ts +16 -16
  88. package/types/dom/thumbnail.d.ts.map +1 -1
  89. package/types/headless.d.ts +6 -1
  90. package/types/headless.d.ts.map +1 -1
  91. package/types/tsbuildinfo +1 -1
  92. package/types/ui/FEAASExternal.d.ts +15 -13
  93. package/types/ui/FEAASExternal.d.ts.map +1 -1
  94. package/types/ui/FEAASPicker.d.ts +9 -6
  95. package/types/ui/FEAASPicker.d.ts.map +1 -1
  96. package/types/ui/react.d.ts +8 -3
  97. package/types/ui/react.d.ts.map +1 -1
  98. package/types/ui/tsbuildinfo +1 -1
  99. package/types/{dom → utils}/data.d.ts +1 -1
  100. package/types/utils/data.d.ts.map +1 -0
  101. package/types/utils/datapath.d.ts +3 -0
  102. package/types/utils/datapath.d.ts.map +1 -1
  103. package/types/utils/settings.d.ts +89 -0
  104. package/types/utils/settings.d.ts.map +1 -0
  105. package/build/dom/data.js.map +0 -1
  106. package/build/utils/fetch.js +0 -26
  107. package/build/utils/fetch.js.map +0 -1
  108. package/src/utils/fetch.ts +0 -44
  109. package/test/cjs/tsconfig.json +0 -9
  110. package/test/fetch-data.ts +0 -55
  111. package/types/dom/data.d.ts.map +0 -1
  112. package/types/utils/fetch.d.ts +0 -9
  113. package/types/utils/fetch.d.ts.map +0 -1
@@ -0,0 +1,200 @@
1
+ import { queryObject } from './data.js'
2
+
3
+ export type DataSourceId = string
4
+
5
+ // Fully resolved data - object or array
6
+ export type Data = Record<string, any> | any[]
7
+
8
+ // Data keyed by datasource id
9
+ export type DataScopes = Data | Record<DataSourceId, Data>
10
+
11
+ // Data settings is json object representing fetch call
12
+ export type DataSettings = ReturnType<typeof DataSettings>
13
+
14
+ // Data options/samples indexed by id
15
+ export type DataOptionsById = Record<DataSourceId, DataSettings | Data>
16
+
17
+ // Data options combines of the above: data, settings, plain or keyed by datasource id
18
+ export type DataOptions = Data | DataSettings | DataOptionsById
19
+
20
+ /**
21
+ * Cleans the provided settings object by removing unnecessary properties that match the defaults that will be re-added
22
+ * back through normalizations. This method is used for code generated for the user, neant to make it look neater.
23
+ *
24
+ * @param {any} settings - The settings object to be cleaned.
25
+ * @returns {Object} - The cleaned settings object.
26
+ */
27
+ export function clean(settings: any) {
28
+ const { params, method, headers, body, url } = DataSettings(settings)
29
+ var cleanSettings = { url } as Partial<DataSettings>
30
+ var cleanHeaders = { ...headers }
31
+ if (headers['Content-Type'] === 'application/json') delete cleanHeaders['Content-Type']
32
+ if (headers['Accept'] === 'application/json') delete cleanHeaders['Accept']
33
+ if (Object.keys(cleanHeaders).length > 0) {
34
+ cleanSettings.headers = cleanHeaders
35
+ }
36
+ if (method !== 'GET') {
37
+ cleanSettings.method = method
38
+ }
39
+ if (!(method === 'GET' || method === 'HEAD' || Object.keys(body).length === 0)) {
40
+ cleanSettings.body = body
41
+ }
42
+ if (Object.keys(params).length > 0) {
43
+ cleanSettings = { ...cleanSettings, params }
44
+ }
45
+ return cleanSettings
46
+ }
47
+
48
+ /**
49
+ * Checks if the provided data is of type DataSettings by checking if all keys are recognized as settings keys.
50
+ *
51
+ * @param {any} data - The data to be checked.
52
+ * @returns {boolean} - True if the data is of type DataSettings, false otherwise.
53
+ */
54
+ export const isDataSettings = (data: any): data is DataSettings => {
55
+ if (typeof data.url != 'string') return false
56
+
57
+ for (var property in data) {
58
+ if (
59
+ property == 'body' ||
60
+ property == 'params' ||
61
+ property == 'headers' ||
62
+ property == 'url' ||
63
+ property === 'method' ||
64
+ (property == 'jsonpath' && typeof data[property] == 'string')
65
+ ) {
66
+ continue
67
+ }
68
+
69
+ return false
70
+ }
71
+
72
+ return true
73
+ }
74
+
75
+ /**
76
+ * Converts settings object into fetch function arguments.
77
+ *
78
+ * @param {any} settings - The settings object.
79
+ * @returns {Array} - An array containing URL and options objects as fetch function arguments.
80
+ */
81
+ export function toFetchArguments(settings?: any): Parameters<typeof fetch> {
82
+ const { url, headers, params, method, body: payload } = DataSettings(settings)
83
+
84
+ let body = undefined
85
+
86
+ const contentType = headers['Content-Type']
87
+
88
+ if (method != 'GET' && method != 'HEAD') {
89
+ if (contentType === 'application/json') body = JSON.stringify(payload)
90
+ if (contentType === 'multipart/form-data')
91
+ body = Object.keys(payload).reduce((form, key) => {
92
+ form.append(key, (payload as any)[key])
93
+ return form
94
+ }, new FormData())
95
+ if (contentType === 'application/x-www-form-urlencoded') body = new URLSearchParams(payload)
96
+ }
97
+
98
+ const query = Object.keys(params).length ? `?${new URLSearchParams(params)}` : ''
99
+
100
+ return [
101
+ `${url}${query}`,
102
+ {
103
+ headers,
104
+ body,
105
+ method
106
+ }
107
+ ]
108
+ }
109
+
110
+ /**
111
+ * Creates a DataSettings object based on the provided settings. These settings are used later to fetch data for the
112
+ * component. It's essentially fetch options with url.
113
+ *
114
+ * @param {any} settings - The settings object.
115
+ * @returns {DataSettings} - The DataSettings object.
116
+ */
117
+ export function DataSettings(settings: any) {
118
+ const headers = {} as any
119
+ for (const key in settings?.headers || {}) {
120
+ if (settings.headers.hasOwnProperty(key)) {
121
+ const headerCase = key.replace(/^[a-z]|\-[a-z]/g, (m) => m.toUpperCase())
122
+ headers[headerCase] = settings.headers[key]
123
+ }
124
+ }
125
+ if (!headers.Accept) headers['Accept'] = 'application/json'
126
+ return {
127
+ params: (settings?.params || {}) as Record<string, any>,
128
+ headers: headers as Record<string, string>,
129
+ jsonpath: String(settings?.jsonpath || '$'),
130
+ method: (settings?.method || 'GET').toUpperCase() as
131
+ | 'GET'
132
+ | 'POST'
133
+ | 'OPTIONS'
134
+ | 'PUT'
135
+ | 'DELETE'
136
+ | 'UPDATE'
137
+ | 'HEAD',
138
+ body: (settings?.body || {}) as Record<string, any>,
139
+ url: String(settings?.url || '')
140
+ }
141
+ }
142
+ /**
143
+ * Fetches data based on the provided settings. It is expected that reesponse is json (forced by Accept header)
144
+ *
145
+ * @param {DataSettings} settings - The data settings.
146
+ * @returns {Promise<Data>} - A promise that resolves to the fetched data.
147
+ */
148
+ const fetchDataSettings = async (settings: DataSettings): Promise<Data> => {
149
+ try {
150
+ const [url, options] = toFetchArguments(settings)
151
+ const response = await DataSettings.fetchImplementation(url, {
152
+ ...options,
153
+ // next.js only allows lowercase method
154
+ method: options.method.toLowerCase()
155
+ })
156
+ const json = await response.json()
157
+ if (settings.jsonpath && settings.jsonpath != '$') {
158
+ return queryObject(json, settings.jsonpath)
159
+ }
160
+ return json
161
+ } catch (e) {
162
+ return {}
163
+ }
164
+ }
165
+ /**
166
+ * Fetches data based on the provided data options. Options is a union of different types, settngs, data or
167
+ * datasource-id keyed settings/data. If the function doesnt find settings in the object, it returns it as is. It
168
+ * supports mixing of static and fetched data. If multiple data settings are found, requests are made in parallel.
169
+ *
170
+ * @param {DataOptions} data - The data options.
171
+ * @returns {Promise<DataScopes>} - A promise that resolves to the fetched data.
172
+ */
173
+ export async function fetchData(data: DataOptions): Promise<DataScopes> {
174
+ if (!data) return data
175
+
176
+ if (isDataSettings(data)) return fetchDataSettings(data)
177
+
178
+ const keys = Object.keys(data)
179
+
180
+ const allRequests = await Promise.all(
181
+ keys
182
+ .map((key: keyof typeof data) => (isDataSettings(data[key]) ? fetchDataSettings(data[key]) : null))
183
+ .filter(Boolean)
184
+ )
185
+
186
+ return keys.reduce(
187
+ (obj, key: keyof typeof data) => ({
188
+ ...obj,
189
+ [key]: isDataSettings(data[key]) ? allRequests.shift() : data[key]
190
+ }),
191
+ {}
192
+ )
193
+ }
194
+
195
+ DataSettings.clean = clean
196
+ DataSettings.toFetchArguments = toFetchArguments
197
+ DataSettings.fetchOne = fetchDataSettings
198
+ DataSettings.fetch = fetchData
199
+ DataSettings.isDataSettings = isDataSettings
200
+ DataSettings.fetchImplementation = (...args: Parameters<typeof fetch>) => fetch(...args)
@@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'
3
3
  import { describe, expect, it } from 'vitest'
4
4
  import { Component } from '../src/ui/react.js'
5
5
 
6
- async function test(react: any, html: string) {
6
+ export async function test(react: any, html: string) {
7
7
  const div = document.createElement('div')
8
8
  document.body.appendChild(div)
9
9
  const root = ReactDOM.render(react, div)
@@ -6,6 +6,7 @@ const earliest = new Date(2000, 1, 1, 0, 0, 0)
6
6
  const earlier = new Date(2000, 1, 2, 0, 0, 0)
7
7
  const now = new Date(2000, 1, 2, 12, 0, 0)
8
8
 
9
+ var delayLength = 16
9
10
  describe('FEAAS component data', () => {
10
11
  beforeAll(() => {
11
12
  vi.setSystemTime(now)
@@ -28,12 +29,12 @@ describe('FEAAS component data', () => {
28
29
  expect(resolved.map(({ name }) => name)).to.eql(promises.map(({ name }) => name))
29
30
  }
30
31
 
31
- const staleCacheSpy = (dataDelay = 100, htmlDelay = 100, stylesDelay = 100) => {
32
+ const freshRequestSpy = (dataDelay = 1, htmlDelay = 1, stylesDelay = 1) => {
32
33
  return vi
33
34
  .spyOn(global, 'fetch')
34
35
  .mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
35
36
  if (url.includes('.css')) {
36
- await new Promise((resolve) => setTimeout(resolve, stylesDelay))
37
+ await new Promise((resolve) => setTimeout(resolve, stylesDelay * delayLength))
37
38
  return new Response('YESTERDAY {}', {
38
39
  headers: {
39
40
  date: now.toUTCString(),
@@ -43,8 +44,8 @@ describe('FEAAS component data', () => {
43
44
  }
44
45
 
45
46
  if (url.includes('CDN')) {
46
- await new Promise((resolve) => setTimeout(resolve, htmlDelay))
47
- return new Response('C', {
47
+ await new Promise((resolve) => setTimeout(resolve, htmlDelay * delayLength))
48
+ return new Response('<var data-path="text">Default</var>', {
48
49
  headers: {
49
50
  date: now.toUTCString(),
50
51
  'last-modified': earlier.toUTCString()
@@ -53,36 +54,28 @@ describe('FEAAS component data', () => {
53
54
  }
54
55
 
55
56
  if (url === 'http://my-url') {
56
- await new Promise((resolve) => setTimeout(resolve, dataDelay))
57
+ await new Promise((resolve) => setTimeout(resolve, dataDelay * delayLength))
57
58
  return new Response(JSON.stringify({ text: 'My new text' }), {})
58
59
  }
59
60
 
60
61
  if (url === 'http://my-url2') {
61
- await new Promise((resolve) => setTimeout(resolve, dataDelay))
62
+ await new Promise((resolve) => setTimeout(resolve, dataDelay * delayLength))
62
63
  return new Response(JSON.stringify({ text: 'My new text2' }), {})
63
64
  }
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
65
  })
73
66
  }
74
67
 
75
68
  describe('Should render component using stale cache, no data initially, then data as object', async () => {
76
69
  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>`
70
+ const spy = freshRequestSpy(delays[0], delays[1], delays[2])
71
+ document.body.innerHTML = `<feaas-component library="L" cdn="CDN" component="C" version="V" revision="R"></feaas-component>`
79
72
  const component = document.body.firstElementChild as FEAASComponent
80
73
  const updateCall = vi.spyOn(component, 'update')
81
74
  await expect(component.whenDataReady).resolves.toEqual({})
82
75
  await expect(component.whenRendered).resolves.toEqual(component)
83
76
  await expect(component.whenLoaded).resolves.toBeTruthy()
84
77
  expect(component.readyData).toEqual({})
85
- expect(component.innerHTML).toEqual('C')
78
+ expect(component.innerHTML).toEqual('<var data-path="text">Default</var>')
86
79
  expect(spy).toHaveBeenCalledTimes(2)
87
80
  await checkRaceCondition([
88
81
  { promise: component.whenDataReady, name: 'when data ready' },
@@ -94,7 +87,7 @@ describe('FEAAS component data', () => {
94
87
  await expect(component.whenDataReady).resolves.toEqual({ text: 'this is text' })
95
88
  await expect(component.whenRendered).resolves.toEqual(component)
96
89
  expect(updateCall).toHaveBeenCalledTimes(2)
97
- expect(component.innerHTML).toEqual('this is text')
90
+ expect(component.innerHTML).toEqual('<var data-path="text">this is text</var>')
98
91
  expect(component.readyData).toEqual({ text: 'this is text' })
99
92
  expect(spy).toHaveBeenCalledTimes(2)
100
93
  await expect(component.whenLoaded).resolves.toBeTruthy()
@@ -103,27 +96,32 @@ describe('FEAAS component data', () => {
103
96
  { promise: component.whenRendered, name: 'when rendered' },
104
97
  { promise: component.whenLoaded, name: 'when loaded' }
105
98
  ])
99
+ component.set({ data: { text: 'this is updated text' } })
100
+ await expect(component.whenDataReady).resolves.toEqual({ text: 'this is updated text' })
101
+ await expect(component.whenRendered).resolves.toEqual(component)
102
+ expect(updateCall).toHaveBeenCalledTimes(3)
103
+ expect(component.innerHTML).toEqual('<var data-path="text">this is updated text</var>')
106
104
  }
107
105
 
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]))
106
+ it('DATA -> HTML -> STYLESHEET', async () => await runWithDelay([1, 2, 3]))
107
+ it('DATA -> STYLESHEET -> HTML', async () => await runWithDelay([1, 3, 2]))
108
+ it('HTML -> DATA -> STYLESHEET', async () => await runWithDelay([2, 1, 3]))
109
+ it('STYLESHEET -> DATA -> HTML', async () => await runWithDelay([2, 3, 1]))
110
+ it('HTML -> STYLESHEET -> DATA', async () => await runWithDelay([3, 1, 2]))
111
+ it('STYLESHEET -> HTML -> DATA', async () => await runWithDelay([3, 2, 1]))
114
112
  })
115
113
 
116
114
  describe('Should render component using stale cache and data as object first, then settings', () => {
117
115
  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>`
116
+ const spy = freshRequestSpy(delays[0], delays[1], delays[2])
117
+ document.body.innerHTML = `<feaas-component data='{"text": "this is text"}' library="L" cdn="CDN" component="C" version="V" revision="R"></feaas-component>`
120
118
  const component = document.body.firstElementChild as FEAASComponent
121
119
  const updateCall = vi.spyOn(component, 'update')
122
120
  await expect(component.whenDataReady).resolves.toEqual({ text: 'this is text' })
123
121
  await expect(component.whenRendered).resolves.toEqual(component)
124
122
  await expect(component.whenLoaded).resolves.toBeTruthy()
125
123
  expect(component.readyData).toEqual({ text: 'this is text' })
126
- expect(component.innerHTML).toEqual('this is text')
124
+ expect(component.innerHTML).toEqual('<var data-path="text">this is text</var>')
127
125
  expect(spy).toHaveBeenCalledTimes(2)
128
126
  await checkRaceCondition([
129
127
  { promise: component.whenDataReady, name: 'when data ready' },
@@ -135,7 +133,7 @@ describe('FEAAS component data', () => {
135
133
  await expect(component.whenDataReady).resolves.toEqual({ text: 'My new text' })
136
134
  await expect(component.whenRendered).resolves.toEqual(component)
137
135
  expect(updateCall).toHaveBeenCalledTimes(2)
138
- expect(component.innerHTML).toEqual('My new text')
136
+ expect(component.innerHTML).toEqual('<var data-path="text">My new text</var>')
139
137
  expect(component.readyData).toEqual({ text: 'My new text' })
140
138
  expect(spy).toHaveBeenCalledTimes(3)
141
139
  await expect(component.whenLoaded).resolves.toBeTruthy()
@@ -146,25 +144,25 @@ describe('FEAAS component data', () => {
146
144
  ])
147
145
  }
148
146
 
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]))
147
+ it('DATA -> HTML -> STYLESHEET', async () => await runWithDelay([1, 2, 3]))
148
+ it('DATA -> STYLESHEET -> HTML', async () => await runWithDelay([1, 3, 2]))
149
+ it('HTML -> DATA -> STYLESHEET', async () => await runWithDelay([2, 1, 3]))
150
+ it('STYLESHEET -> DATA -> HTML', async () => await runWithDelay([2, 3, 1]))
151
+ it('HTML -> STYLESHEET -> DATA', async () => await runWithDelay([3, 1, 2]))
152
+ it('STYLESHEET -> HTML -> DATA', async () => await runWithDelay([3, 2, 1]))
155
153
  })
156
154
 
157
155
  describe('Should render component using stale cache and data as settings', () => {
158
156
  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>`
157
+ const spy = freshRequestSpy(delays[0], delays[1], delays[2])
158
+ document.body.innerHTML = `<feaas-component data='{ "jsonpath": "$", "url": "http://my-url" }' library="L" cdn="CDN" component="C" version="V" revision="R"></feaas-component>`
161
159
  const component = document.body.firstElementChild as FEAASComponent
162
160
  const updateCall = vi.spyOn(component, 'update')
163
161
  await expect(component.whenDataReady).resolves.toEqual({ text: 'My new text' })
164
162
  await expect(component.whenRendered).resolves.toEqual(component)
165
163
  await expect(component.whenLoaded).resolves.toBeTruthy()
166
164
  expect(component.readyData).toEqual({ text: 'My new text' })
167
- expect(component.innerHTML).toEqual('My new text')
165
+ expect(component.innerHTML).toEqual('<var data-path="text">My new text</var>')
168
166
  expect(spy).toHaveBeenCalledTimes(3)
169
167
  await checkRaceCondition([
170
168
  { promise: component.whenDataReady, name: 'when data ready' },
@@ -174,24 +172,30 @@ describe('FEAAS component data', () => {
174
172
  expect(updateCall).toHaveBeenCalledTimes(1)
175
173
  }
176
174
 
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]))
175
+ it('DATA -> HTML -> STYLESHEET', async () => await runWithDelay([1, 2, 3]))
176
+ it('DATA -> STYLESHEET -> HTML', async () => await runWithDelay([1, 3, 2]))
177
+ it('HTML -> DATA -> STYLESHEET', async () => await runWithDelay([2, 1, 3]))
178
+ it('STYLESHEET -> DATA -> HTML', async () => await runWithDelay([2, 3, 1]))
179
+ it('HTML -> STYLESHEET -> DATA', async () => await runWithDelay([3, 1, 2]))
180
+ it('STYLESHEET -> HTML -> DATA', async () => await runWithDelay([3, 2, 1]))
183
181
  })
184
182
 
185
183
  describe('Should render component using stale cache and data as mapping', () => {
186
184
  const runWithDelay = async (delays: number[]) => {
187
- const spy = staleCacheSpy(delays[0], delays[1], delays[2])
185
+ const spy = freshRequestSpy(delays[0], delays[1], delays[2])
188
186
  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
187
  const component = document.body.firstElementChild as FEAASComponent
190
188
  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' } })
189
+ await expect(component.whenDataReady).resolves.toEqual({
190
+ 'datasource-1': { text: 'My new text' },
191
+ 'datasource-2': { text: 'My new text2' }
192
+ })
192
193
  await expect(component.whenRendered).resolves.toEqual(component)
193
194
  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.readyData).toEqual({
196
+ 'datasource-1': { text: 'My new text' },
197
+ 'datasource-2': { text: 'My new text2' }
198
+ })
195
199
  expect(component.innerHTML).toEqual('My new text2')
196
200
  expect(spy).toHaveBeenCalledTimes(4)
197
201
  await checkRaceCondition([
@@ -202,12 +206,12 @@ describe('FEAAS component data', () => {
202
206
  expect(updateCall).toHaveBeenCalledTimes(1)
203
207
  }
204
208
 
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]))
209
+ it('DATA -> HTML -> STYLESHEET', async () => await runWithDelay([1, 2, 3]))
210
+ it('DATA -> STYLESHEET -> HTML', async () => await runWithDelay([1, 3, 2]))
211
+ it('HTML -> DATA -> STYLESHEET', async () => await runWithDelay([2, 1, 3]))
212
+ it('STYLESHEET -> DATA -> HTML', async () => await runWithDelay([2, 3, 1]))
213
+ it('HTML -> STYLESHEET -> DATA', async () => await runWithDelay([3, 1, 2]))
214
+ it('STYLESHEET -> HTML -> DATA', async () => await runWithDelay([3, 2, 1]))
211
215
  })
212
216
 
213
217
  // TODO
@@ -0,0 +1,33 @@
1
+ import React from 'react'
2
+ import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
3
+ import { ServerComponent } from '../src/ui/react.js'
4
+ import { FEAASComponent } from '../src/components/FEAASComponent.js'
5
+ import { test } from './FEAASComponent.react.test.js'
6
+
7
+ const earliest = new Date(2000, 1, 1, 0, 0, 0)
8
+ const earlier = new Date(2000, 1, 2, 0, 0, 0)
9
+ const now = new Date(2000, 1, 2, 12, 0, 0)
10
+
11
+ describe('React Server', () => {
12
+ beforeAll(() => {
13
+ vi.setSystemTime(now)
14
+ FEAASComponent.register('feaas-component')
15
+ })
16
+
17
+ beforeEach(() => {
18
+ document.write()
19
+ })
20
+
21
+ it('render server component', async () => {
22
+ vi.spyOn(global, 'fetch').mockImplementation(
23
+ async (url: string, options: Parameters<typeof fetch>[1] = {}) => new Response('{"data": "1"}')
24
+ )
25
+
26
+ const Component = await ServerComponent({ src: 'library/test', data: { url: 'http://127.0.0.1:4000/api/data' } })
27
+
28
+ await test(
29
+ <>{Component}</>,
30
+ `<feaas-component library="library" component="test" src="library/test" data="{&quot;data&quot;:&quot;1&quot;}">{"data": "1"}</feaas-component>`
31
+ )
32
+ })
33
+ })
package/test/cdn.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  parseStylesheetSource,
8
8
  getStylesheetURL
9
9
  } from '../src/cdn.js'
10
- import { CH1Node, serializeCH1RichText } from '../src/dom/data.js'
10
+ import { CH1Node, serializeCH1RichText } from '../src/utils/data.js'
11
11
  describe('CDN', function () {
12
12
  it('should parse and serialize component src', function () {
13
13
  ;[