@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.
- package/README.md +1 -1
- package/build/components/FEAASComponent.js +11 -5
- package/build/components/FEAASComponent.js.map +1 -1
- package/build/components/FEAASElement.js +34 -9
- package/build/components/FEAASElement.js.map +1 -1
- package/build/dom/rendering.js +1 -1
- package/build/dom/rendering.js.map +1 -1
- package/build/dom/thumbnail.js +29 -24
- package/build/dom/thumbnail.js.map +1 -1
- package/build/headless.js +5 -1
- package/build/headless.js.map +1 -1
- package/build/ui/FEAASExternal.js +15 -13
- package/build/ui/FEAASExternal.js.map +1 -1
- package/build/ui/FEAASPicker.js +20 -5
- package/build/ui/FEAASPicker.js.map +1 -1
- package/build/ui/react.js +33 -10
- package/build/ui/react.js.map +1 -1
- package/build/{dom → utils}/data.js +3 -0
- package/build/utils/data.js.map +1 -0
- package/build/utils/datapath.js +21 -2
- package/build/utils/datapath.js.map +1 -1
- package/build/utils/settings.js +162 -0
- package/build/utils/settings.js.map +1 -0
- package/dist/browser/headless.cjs +8 -6
- package/dist/browser/headless.cjs.map +4 -4
- package/dist/browser/headless.esm.js +8 -6
- package/dist/browser/headless.esm.js.map +1 -1
- package/dist/browser/index.cjs +8 -6
- package/dist/browser/index.cjs.map +4 -4
- package/dist/browser/index.esm.js +8 -6
- package/dist/browser/index.esm.js.map +1 -1
- package/dist/browser/react.cjs +8 -6
- package/dist/browser/react.cjs.map +4 -4
- package/dist/browser/react.esm.js +8 -6
- package/dist/browser/react.esm.js.map +1 -1
- package/dist/node/headless.cjs +6 -4
- package/dist/node/headless.cjs.map +4 -4
- package/dist/node/headless.esm.js +6 -4
- package/dist/node/headless.esm.js.map +4 -4
- package/dist/node/index.cjs +6 -4
- package/dist/node/index.cjs.map +4 -4
- package/dist/node/index.esm.js +6 -4
- package/dist/node/index.esm.js.map +4 -4
- package/dist/node/react.cjs +6 -4
- package/dist/node/react.cjs.map +4 -4
- package/dist/node/react.esm.js +6 -4
- package/dist/node/react.esm.js.map +4 -4
- package/headless.cjs +6 -4
- package/headless.cjs.map +4 -4
- package/headless.js +6 -4
- package/headless.js.map +4 -4
- package/package.json +1 -1
- package/react.cjs +8 -6
- package/react.cjs.map +4 -4
- package/react.js +8 -6
- package/react.js.map +4 -4
- package/src/components/FEAASComponent.tsx +21 -8
- package/src/components/FEAASElement.tsx +35 -8
- package/src/dom/component.ts +2 -2
- package/src/dom/html.ts +2 -2
- package/src/dom/rendering.ts +10 -7
- package/src/dom/thumbnail.ts +29 -24
- package/src/headless.ts +6 -1
- package/src/ui/FEAASExternal.tsx +15 -13
- package/src/ui/FEAASPicker.tsx +23 -6
- package/src/ui/react.tsx +53 -16
- package/src/{dom → utils}/data.ts +3 -1
- package/src/utils/datapath.ts +24 -2
- package/src/utils/settings.ts +200 -0
- package/test/FEAASComponent.react.test.tsx +1 -1
- package/test/{FEAASComponent2.test.tsx → FEAASComponentData.test.tsx} +56 -52
- package/test/ServerComponent.test.tsx +33 -0
- package/test/cdn.ts +1 -1
- package/test/datasettings.test.ts +248 -0
- package/test/{extras.ts → extras.test.ts} +1 -1
- package/test/fetch.ts +2 -2
- package/types/components/FEAASComponent.d.ts +10 -5
- package/types/components/FEAASComponent.d.ts.map +1 -1
- package/types/components/FEAASElement.d.ts +2 -0
- package/types/components/FEAASElement.d.ts.map +1 -1
- package/types/dom/component.d.ts +2 -2
- package/types/dom/component.d.ts.map +1 -1
- package/types/dom/html.d.ts +2 -2
- package/types/dom/html.d.ts.map +1 -1
- package/types/dom/rendering.d.ts +8 -7
- package/types/dom/rendering.d.ts.map +1 -1
- package/types/dom/thumbnail.d.ts +16 -16
- package/types/dom/thumbnail.d.ts.map +1 -1
- package/types/headless.d.ts +6 -1
- package/types/headless.d.ts.map +1 -1
- package/types/tsbuildinfo +1 -1
- package/types/ui/FEAASExternal.d.ts +15 -13
- package/types/ui/FEAASExternal.d.ts.map +1 -1
- package/types/ui/FEAASPicker.d.ts +9 -6
- package/types/ui/FEAASPicker.d.ts.map +1 -1
- package/types/ui/react.d.ts +8 -3
- package/types/ui/react.d.ts.map +1 -1
- package/types/ui/tsbuildinfo +1 -1
- package/types/{dom → utils}/data.d.ts +1 -1
- package/types/utils/data.d.ts.map +1 -0
- package/types/utils/datapath.d.ts +3 -0
- package/types/utils/datapath.d.ts.map +1 -1
- package/types/utils/settings.d.ts +89 -0
- package/types/utils/settings.d.ts.map +1 -0
- package/build/dom/data.js.map +0 -1
- package/build/utils/fetch.js +0 -26
- package/build/utils/fetch.js.map +0 -1
- package/src/utils/fetch.ts +0 -44
- package/test/cjs/tsconfig.json +0 -9
- package/test/fetch-data.ts +0 -55
- package/types/dom/data.d.ts.map +0 -1
- package/types/utils/fetch.d.ts +0 -9
- 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
|
|
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('
|
|
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 =
|
|
78
|
-
document.body.innerHTML = `<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('
|
|
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([
|
|
109
|
-
it('DATA -> STYLESHEET -> HTML', async () => await runWithDelay([
|
|
110
|
-
it('HTML -> DATA -> STYLESHEET', async () => await runWithDelay([
|
|
111
|
-
it('STYLESHEET -> DATA -> HTML', async () => await runWithDelay([
|
|
112
|
-
it('HTML -> STYLESHEET -> DATA', async () => await runWithDelay([
|
|
113
|
-
it('STYLESHEET -> HTML -> DATA', async () => await runWithDelay([
|
|
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 =
|
|
119
|
-
document.body.innerHTML = `<feaas-component data='{"text": "this is text"}'
|
|
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([
|
|
150
|
-
it('DATA -> STYLESHEET -> HTML', async () => await runWithDelay([
|
|
151
|
-
it('HTML -> DATA -> STYLESHEET', async () => await runWithDelay([
|
|
152
|
-
it('STYLESHEET -> DATA -> HTML', async () => await runWithDelay([
|
|
153
|
-
it('HTML -> STYLESHEET -> DATA', async () => await runWithDelay([
|
|
154
|
-
it('STYLESHEET -> HTML -> DATA', async () => await runWithDelay([
|
|
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 =
|
|
160
|
-
document.body.innerHTML = `<feaas-component data='{ "jsonpath": "$", "url": "http://my-url" }'
|
|
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([
|
|
178
|
-
it('DATA -> STYLESHEET -> HTML', async () => await runWithDelay([
|
|
179
|
-
it('HTML -> DATA -> STYLESHEET', async () => await runWithDelay([
|
|
180
|
-
it('STYLESHEET -> DATA -> HTML', async () => await runWithDelay([
|
|
181
|
-
it('HTML -> STYLESHEET -> DATA', async () => await runWithDelay([
|
|
182
|
-
it('STYLESHEET -> HTML -> DATA', async () => await runWithDelay([
|
|
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 =
|
|
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({
|
|
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({
|
|
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([
|
|
206
|
-
it('DATA -> STYLESHEET -> HTML', async () => await runWithDelay([
|
|
207
|
-
it('HTML -> DATA -> STYLESHEET', async () => await runWithDelay([
|
|
208
|
-
it('STYLESHEET -> DATA -> HTML', async () => await runWithDelay([
|
|
209
|
-
it('HTML -> STYLESHEET -> DATA', async () => await runWithDelay([
|
|
210
|
-
it('STYLESHEET -> HTML -> DATA', async () => await runWithDelay([
|
|
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="{"data":"1"}">{"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/
|
|
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
|
;[
|