@sitecore-feaas/clientside 0.5.3 → 0.5.5
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/build/components/FEAASComponent.js +17 -6
- package/build/components/FEAASComponent.js.map +1 -1
- package/build/dom/stylesheet.js +11 -12
- package/build/dom/stylesheet.js.map +1 -1
- package/build/fetch.js +25 -1
- package/build/fetch.js.map +1 -1
- package/build/utils/settings.js +2 -2
- package/build/utils/settings.js.map +1 -1
- package/dist/browser/headless.cjs +4 -4
- package/dist/browser/headless.cjs.map +3 -3
- package/dist/browser/headless.esm.js +5 -5
- package/dist/browser/headless.esm.js.map +1 -1
- package/dist/browser/index.cjs +4 -4
- package/dist/browser/index.cjs.map +3 -3
- package/dist/browser/index.esm.js +5 -5
- package/dist/browser/index.esm.js.map +1 -1
- package/dist/browser/react.cjs +5 -5
- package/dist/browser/react.cjs.map +3 -3
- package/dist/browser/react.esm.js +5 -5
- package/dist/browser/react.esm.js.map +1 -1
- package/dist/node/headless.cjs +4 -4
- package/dist/node/headless.cjs.map +3 -3
- package/dist/node/headless.esm.js +4 -4
- package/dist/node/headless.esm.js.map +3 -3
- package/dist/node/index.cjs +4 -4
- package/dist/node/index.cjs.map +3 -3
- package/dist/node/index.esm.js +4 -4
- package/dist/node/index.esm.js.map +3 -3
- package/dist/node/react.cjs +4 -4
- package/dist/node/react.cjs.map +3 -3
- package/dist/node/react.esm.js +3 -3
- package/dist/node/react.esm.js.map +3 -3
- package/headless.cjs +4 -4
- package/headless.cjs.map +3 -3
- package/headless.js +4 -4
- package/headless.js.map +3 -3
- package/package.json +1 -1
- package/react.cjs +5 -5
- package/react.cjs.map +3 -3
- package/react.js +5 -5
- package/react.js.map +3 -3
- package/src/components/FEAASComponent.tsx +21 -7
- package/src/dom/stylesheet.ts +17 -15
- package/src/fetch.ts +25 -1
- package/src/utils/settings.ts +2 -2
- package/test/FEAASComponent.test.tsx +144 -47
- package/test/FEAASStylesheet.test.tsx +13 -11
- package/test/datasettings.test.ts +92 -0
- package/types/components/FEAASComponent.d.ts +1 -0
- package/types/components/FEAASComponent.d.ts.map +1 -1
- package/types/dom/stylesheet.d.ts +1 -1
- package/types/dom/stylesheet.d.ts.map +1 -1
- package/types/fetch.d.ts +24 -0
- package/types/fetch.d.ts.map +1 -1
- package/types/ui/FEAASPicker.d.ts +3 -3
- package/types/utils/settings.d.ts +6 -6
- package/types/utils/settings.d.ts.map +1 -1
|
@@ -104,6 +104,7 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
104
104
|
} as Partial<FEAASComponentsProps>
|
|
105
105
|
|
|
106
106
|
needsRefresh: boolean
|
|
107
|
+
needsToFetchTemplate: boolean
|
|
107
108
|
|
|
108
109
|
setData(data: DataOptions) {
|
|
109
110
|
this.set({ data })
|
|
@@ -123,14 +124,19 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
123
124
|
// force component to be refreshed
|
|
124
125
|
if (name == 'library' || name == 'component' || name == 'revision' || name == 'version') {
|
|
125
126
|
this.payload = undefined
|
|
127
|
+
if (this.alreadyRendered) {
|
|
128
|
+
this.needsRefresh = true
|
|
129
|
+
this.needsToFetchTemplate = true
|
|
130
|
+
}
|
|
126
131
|
}
|
|
127
132
|
|
|
128
|
-
this.needsRefresh = true
|
|
129
|
-
|
|
130
133
|
super.attributeChangedCallback(name, oldValue, newValue)
|
|
131
134
|
|
|
132
|
-
if (name
|
|
133
|
-
this.
|
|
135
|
+
if (name == 'data') {
|
|
136
|
+
this.needsRefresh = true
|
|
137
|
+
if (this.props.fetch.includes('data') || this.alreadyRendered || !DataSettings.hasDataSettings(this.props.data)) {
|
|
138
|
+
this.fetchData()
|
|
139
|
+
}
|
|
134
140
|
}
|
|
135
141
|
}
|
|
136
142
|
|
|
@@ -157,6 +163,7 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
157
163
|
constructor() {
|
|
158
164
|
super()
|
|
159
165
|
|
|
166
|
+
const data = this.getAttribute('data')
|
|
160
167
|
if (!this.getAttribute('data')) {
|
|
161
168
|
this.whenDataReady.resolve({})
|
|
162
169
|
}
|
|
@@ -225,7 +232,8 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
225
232
|
})
|
|
226
233
|
}
|
|
227
234
|
|
|
228
|
-
if (this.props.fetch.includes('template')) {
|
|
235
|
+
if (this.props.fetch.includes('template') || this.needsToFetchTemplate) {
|
|
236
|
+
this.needsToFetchTemplate = false
|
|
229
237
|
var templatePromise = fetchAndRevalidateComponent(this.props, (html) => {
|
|
230
238
|
this.payload = html
|
|
231
239
|
advanceIfEverythingIsReady()
|
|
@@ -252,11 +260,17 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
252
260
|
update() {
|
|
253
261
|
this.alreadyRendered = true
|
|
254
262
|
// avoid unnecessary rendering if hydration was disabled
|
|
255
|
-
if (
|
|
263
|
+
if (
|
|
264
|
+
!this.props.fetch.includes('data') &&
|
|
265
|
+
!this.props.fetch.includes('template') &&
|
|
266
|
+
this.firstElementChild &&
|
|
267
|
+
!this.needsRefresh
|
|
268
|
+
)
|
|
269
|
+
return
|
|
256
270
|
if (Array.from(this.children).filter((c) => c.tagName != 'BR').length > 0 && !this.needsRefresh) {
|
|
257
271
|
renderDOMElement(this, this.readyData)
|
|
258
272
|
} else {
|
|
259
|
-
this.needsRefresh =
|
|
273
|
+
this.needsRefresh = false
|
|
260
274
|
// defer rendering to original element
|
|
261
275
|
if (this.getOriginal()) {
|
|
262
276
|
return
|
package/src/dom/stylesheet.ts
CHANGED
|
@@ -17,23 +17,25 @@ export function renderStylesheetPromise(props: FEAASCDNStylesheetProps, styleshe
|
|
|
17
17
|
}).then(() => stylesheet)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const validatedStylesheets = new WeakSet<HTMLStyleElement | HTMLLinkElement>()
|
|
20
21
|
export function loadStylesheet(props: FEAASCDNStylesheetProps, callback?: ValidatableStylesheetCallback) {
|
|
21
|
-
var foundStylesheet
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
callback(null, 'initial')
|
|
27
|
-
return Promise.resolve(foundStylesheet)
|
|
28
|
-
}
|
|
22
|
+
var foundStylesheet: HTMLStyleElement | HTMLLinkElement
|
|
23
|
+
var writableStylesheet: HTMLStyleElement
|
|
24
|
+
|
|
25
|
+
if (foundStylesheet && validatedStylesheets.has(foundStylesheet)) {
|
|
26
|
+
return Promise.resolve(foundStylesheet)
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
return fetchAndRevalidateStylesheet(props, (cssText, phase) => {
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
foundStylesheet ||= findStylesheet(props)
|
|
31
|
+
// dont need to write stylesheet for cached state if we already found a stylesheet
|
|
32
|
+
if (!(phase == 'initial' && foundStylesheet)) {
|
|
33
|
+
writableStylesheet ||=
|
|
34
|
+
!foundStylesheet || foundStylesheet.tagName == 'LINK' ? createStylesheet(props) : foundStylesheet
|
|
35
|
+
writableStylesheet.textContent = cssText
|
|
34
36
|
}
|
|
35
37
|
callback?.(cssText, phase)
|
|
36
|
-
}).then(() => foundStylesheet ||
|
|
38
|
+
}).then(() => foundStylesheet || writableStylesheet)
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
export function loadStylesheetAllowStale(props: FEAASCDNStylesheetProps) {
|
|
@@ -43,10 +45,10 @@ export function loadStylesheetAllowStale(props: FEAASCDNStylesheetProps) {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export function findStylesheet(props: FEAASCDNStylesheetProps) {
|
|
46
|
-
return (
|
|
47
|
-
document.
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
return (document.head.querySelector(`style[data-href="${getStylesheetURL(props)}"]`) ||
|
|
49
|
+
document.querySelector(`link[rel="stylesheet"][href="${getStylesheetURL(props)}"]`)) as
|
|
50
|
+
| HTMLStyleElement
|
|
51
|
+
| HTMLLinkElement
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export function createStylesheet(props: FEAASCDNStylesheetProps) {
|
package/src/fetch.ts
CHANGED
|
@@ -68,6 +68,22 @@ export function getFetchCache(url: Parameters<typeof fetch>[0], dontCreate = fal
|
|
|
68
68
|
|
|
69
69
|
export class InvalidResponse extends Error {}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Executes a fetch request optionally validating and handling the response. Using callback function it's possible to
|
|
73
|
+
* observe successful response twice: Cached version first, and up-to-date later.
|
|
74
|
+
*
|
|
75
|
+
* @param {Object} request - An object containing request parameters.
|
|
76
|
+
* @param {string} request.url - The URL to fetch.
|
|
77
|
+
* @param {Object} request.options - Fetch request options.
|
|
78
|
+
* @param {Function} [request.validator] - Function to validate the response. Should return `false` if
|
|
79
|
+
* validation fails.
|
|
80
|
+
* @param {Function} [request.callback] - Function to execute after fetching. Receives the response,
|
|
81
|
+
* phase, and cache status.
|
|
82
|
+
* @param {Object} [request.cache] - Cache object to store or retrieve cached data.
|
|
83
|
+
* @param {ValidatableRequestPhase} [phase='initial'] - The phase of the request, defaults to 'initial'.
|
|
84
|
+
* @returns {Promise<Object>} A promise that resolves to an object containing request details and the response.
|
|
85
|
+
* @throws {Error} Throws an error if the fetch fails or if the response is not valid according to the validator.
|
|
86
|
+
*/
|
|
71
87
|
export async function fetchWithCacheAwareness(
|
|
72
88
|
{ url, options, validator, callback, cache }: ValidatableRequest,
|
|
73
89
|
phase: ValidatableRequestPhase = 'initial'
|
|
@@ -107,6 +123,14 @@ export async function fetchWithCacheAwareness(
|
|
|
107
123
|
}
|
|
108
124
|
}
|
|
109
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Validates cache of previousl fetch If the resource is cached and there is a newer version available, it will be
|
|
128
|
+
* re-fetched.
|
|
129
|
+
*
|
|
130
|
+
* @param request - The request object containing the URL, cache information, options, validator, and response of
|
|
131
|
+
* initial request.
|
|
132
|
+
* @returns A promise that resolves to the request object.
|
|
133
|
+
*/
|
|
110
134
|
export async function fetchToValidateCache(
|
|
111
135
|
request: ValidatableRequest & Required<Pick<ValidatableRequest, 'response'>>
|
|
112
136
|
) {
|
|
@@ -129,7 +153,7 @@ export async function fetchToValidateCache(
|
|
|
129
153
|
|
|
130
154
|
// there's a newer version of the file that we may fetch
|
|
131
155
|
if (head.headers.get('last-modified') != response.headers.get('last-modified')) {
|
|
132
|
-
//
|
|
156
|
+
// the new resource has to be re-fetched
|
|
133
157
|
return fetchBypassingCache(request)
|
|
134
158
|
}
|
|
135
159
|
}
|
package/src/utils/settings.ts
CHANGED
|
@@ -52,7 +52,7 @@ export function clean(settings: any) {
|
|
|
52
52
|
* @returns {boolean} - True if the data is of type DataSettings, false otherwise.
|
|
53
53
|
*/
|
|
54
54
|
export const isDataSettings = (data: any): data is DataSettings => {
|
|
55
|
-
if (typeof data.url != 'string') return false
|
|
55
|
+
if (!data || typeof data.url != 'string') return false
|
|
56
56
|
|
|
57
57
|
for (var property in data) {
|
|
58
58
|
if (
|
|
@@ -192,7 +192,7 @@ export async function fetchData(data: DataOptions): Promise<DataScopes> {
|
|
|
192
192
|
)
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
export
|
|
195
|
+
export function hasDataSettings(data: DataOptions) {
|
|
196
196
|
if (!data) return false
|
|
197
197
|
return isDataSettings(data) || Object.values(data).some(isDataSettings)
|
|
198
198
|
}
|
|
@@ -25,7 +25,7 @@ describe('Components', () => {
|
|
|
25
25
|
const spy = vi.spyOn(global, 'fetch')
|
|
26
26
|
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
27
27
|
if (url.match('CDN/components/L/C/V/R')) {
|
|
28
|
-
return new Response('
|
|
28
|
+
return new Response('FRESH', {
|
|
29
29
|
headers: {
|
|
30
30
|
date: now.toUTCString(),
|
|
31
31
|
'last-modified': earlier.toUTCString()
|
|
@@ -43,14 +43,21 @@ describe('Components', () => {
|
|
|
43
43
|
await expect(component.whenRendered).resolves.toEqual(component)
|
|
44
44
|
expect(spy).toHaveBeenCalledTimes(1)
|
|
45
45
|
|
|
46
|
-
expect(component.innerHTML).toContain('
|
|
46
|
+
expect(component.innerHTML).toContain('FRESH')
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
describe('fetch control', () => {
|
|
50
|
-
it('should not make requests when hydration is turned off', async () => {
|
|
50
|
+
it('should not make requests when hydration is turned off, unless attribute changed after initial render', async () => {
|
|
51
51
|
const spy = vi.spyOn(global, 'fetch')
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
53
|
+
return new Response(url, {
|
|
54
|
+
headers: {
|
|
55
|
+
date: now.toUTCString(),
|
|
56
|
+
'last-modified': earlier.toUTCString()
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
// Fixme change to production component
|
|
54
61
|
document.body.innerHTML =
|
|
55
62
|
'<feaas-component fetch="" library="L" cdn="CDN" component="C" version="V" revision="R"></feaas-component>'
|
|
56
63
|
|
|
@@ -59,7 +66,11 @@ describe('Components', () => {
|
|
|
59
66
|
await expect(component.whenRendered).resolves.toEqual(component)
|
|
60
67
|
expect(spy).toHaveBeenCalledTimes(0)
|
|
61
68
|
|
|
62
|
-
|
|
69
|
+
component.setAttribute('library', 'Z')
|
|
70
|
+
await expect(component.whenRendered).resolves.toEqual(component)
|
|
71
|
+
expect(spy).toHaveBeenCalledTimes(1)
|
|
72
|
+
|
|
73
|
+
expect(component.innerHTML).toBe('CDN/components/Z/C/V/R.html')
|
|
63
74
|
})
|
|
64
75
|
|
|
65
76
|
it('should not make requests when hydration is turned off, but should render template', async () => {
|
|
@@ -81,7 +92,7 @@ describe('Components', () => {
|
|
|
81
92
|
const spy = vi.spyOn(global, 'fetch')
|
|
82
93
|
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
83
94
|
if (url.match('.css')) {
|
|
84
|
-
return new Response('{}', {
|
|
95
|
+
return new Response('.a{}', {
|
|
85
96
|
headers: {
|
|
86
97
|
date: now.toUTCString(),
|
|
87
98
|
'last-modified': earlier.toUTCString()
|
|
@@ -105,7 +116,7 @@ describe('Components', () => {
|
|
|
105
116
|
const spy = vi.spyOn(global, 'fetch')
|
|
106
117
|
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
107
118
|
if (url.match('CDN/components/L/C/V/R')) {
|
|
108
|
-
return new Response('
|
|
119
|
+
return new Response('FRESH', {
|
|
109
120
|
headers: {
|
|
110
121
|
date: now.toUTCString(),
|
|
111
122
|
'last-modified': earlier.toUTCString()
|
|
@@ -113,7 +124,7 @@ describe('Components', () => {
|
|
|
113
124
|
})
|
|
114
125
|
}
|
|
115
126
|
if (url.match('.css')) {
|
|
116
|
-
return new Response('{}', {
|
|
127
|
+
return new Response('.a{}', {
|
|
117
128
|
headers: {
|
|
118
129
|
date: now.toUTCString(),
|
|
119
130
|
'last-modified': earlier.toUTCString()
|
|
@@ -128,7 +139,7 @@ describe('Components', () => {
|
|
|
128
139
|
|
|
129
140
|
await expect(component.whenRendered).resolves.toEqual(component)
|
|
130
141
|
expect(spy).toHaveBeenCalledTimes(2)
|
|
131
|
-
expect(component.innerHTML).toBe('
|
|
142
|
+
expect(component.innerHTML).toBe('FRESH')
|
|
132
143
|
expect(document.head.querySelectorAll('style').length).toEqual(1)
|
|
133
144
|
})
|
|
134
145
|
it('should make data/css requests when fetch="stylesheet data"', async () => {
|
|
@@ -143,7 +154,7 @@ describe('Components', () => {
|
|
|
143
154
|
})
|
|
144
155
|
}
|
|
145
156
|
if (url.match('.css')) {
|
|
146
|
-
return new Response('{}', {
|
|
157
|
+
return new Response('.a{}', {
|
|
147
158
|
headers: {
|
|
148
159
|
date: now.toUTCString(),
|
|
149
160
|
'last-modified': earlier.toUTCString()
|
|
@@ -163,17 +174,9 @@ describe('Components', () => {
|
|
|
163
174
|
expect(document.head.querySelectorAll('style').length).toEqual(1)
|
|
164
175
|
})
|
|
165
176
|
|
|
166
|
-
it('should
|
|
177
|
+
it('should fetch data even if fetch control doesnt allow it, once data attribute was updated', async () => {
|
|
167
178
|
const spy = vi.spyOn(global, 'fetch')
|
|
168
179
|
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
169
|
-
if (url.match('.html')) {
|
|
170
|
-
return new Response('<var data-path="source.path"></var>', {
|
|
171
|
-
headers: {
|
|
172
|
-
date: now.toUTCString(),
|
|
173
|
-
'last-modified': earlier.toUTCString()
|
|
174
|
-
}
|
|
175
|
-
})
|
|
176
|
-
}
|
|
177
180
|
if (url.match('my.json')) {
|
|
178
181
|
return new Response('{"path": "value"}', {
|
|
179
182
|
headers: {
|
|
@@ -181,26 +184,23 @@ describe('Components', () => {
|
|
|
181
184
|
'last-modified': earlier.toUTCString()
|
|
182
185
|
}
|
|
183
186
|
})
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return new Response('{}', {
|
|
187
|
-
headers: {
|
|
188
|
-
date: now.toUTCString(),
|
|
189
|
-
'last-modified': earlier.toUTCString()
|
|
190
|
-
}
|
|
191
|
-
})
|
|
187
|
+
} else {
|
|
188
|
+
throw new Error('Unknown fetch url' + url)
|
|
192
189
|
}
|
|
193
190
|
})
|
|
194
191
|
|
|
195
192
|
document.body.innerHTML =
|
|
196
|
-
'<feaas-component fetch="
|
|
193
|
+
'<feaas-component fetch="" data=\'{"source": {"path": "original"}}\' library="L" cdn="CDN" component="C" version="V" revision="R"><var data-path="source.path">original</var></feaas-component>'
|
|
197
194
|
|
|
198
195
|
const component = document.body.firstElementChild as FEAASComponent
|
|
199
196
|
|
|
200
197
|
await expect(component.whenRendered).resolves.toEqual(component)
|
|
201
|
-
expect(spy).toHaveBeenCalledTimes(
|
|
198
|
+
expect(spy).toHaveBeenCalledTimes(0)
|
|
199
|
+
expect(component.innerHTML).toBe('<var data-path="source.path">original</var>')
|
|
200
|
+
component.setAttribute('data', '{"source": {"url": "my.json"}}')
|
|
201
|
+
await expect(component.whenRendered).resolves.toEqual(component)
|
|
202
|
+
expect(spy).toHaveBeenCalledTimes(1)
|
|
202
203
|
expect(component.innerHTML).toBe('<var data-path="source.path">value</var>')
|
|
203
|
-
expect(document.head.querySelectorAll('style').length).toEqual(1)
|
|
204
204
|
})
|
|
205
205
|
})
|
|
206
206
|
|
|
@@ -208,7 +208,7 @@ describe('Components', () => {
|
|
|
208
208
|
const spy = vi.spyOn(global, 'fetch')
|
|
209
209
|
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
210
210
|
if (url.match('http://a/components/b/c/d/1.html')) {
|
|
211
|
-
return new Response('
|
|
211
|
+
return new Response('FRESH', {
|
|
212
212
|
headers: {
|
|
213
213
|
date: now.toUTCString(),
|
|
214
214
|
'last-modified': earlier.toUTCString()
|
|
@@ -226,7 +226,7 @@ describe('Components', () => {
|
|
|
226
226
|
expect(spy).toHaveBeenCalledTimes(1)
|
|
227
227
|
|
|
228
228
|
expect(component.outerHTML).toBe(
|
|
229
|
-
`<feaas-component fetch="template data" src="http://a/components/b/c/d/1.html" cdn="http://a" library="b" component="c" version="d" revision="1" class="-feaas">
|
|
229
|
+
`<feaas-component fetch="template data" src="http://a/components/b/c/d/1.html" cdn="http://a" library="b" component="c" version="d" revision="1" class="-feaas">FRESH</feaas-component>`
|
|
230
230
|
)
|
|
231
231
|
})
|
|
232
232
|
|
|
@@ -234,7 +234,7 @@ describe('Components', () => {
|
|
|
234
234
|
const spy = vi.spyOn(global, 'fetch')
|
|
235
235
|
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
236
236
|
if (url.match('http://a/components/b/c/d/1.html')) {
|
|
237
|
-
return new Response('
|
|
237
|
+
return new Response('FRESH', {
|
|
238
238
|
headers: {
|
|
239
239
|
date: now.toUTCString(),
|
|
240
240
|
'last-modified': earlier.toUTCString()
|
|
@@ -260,16 +260,108 @@ describe('Components', () => {
|
|
|
260
260
|
expect(document.head.querySelectorAll('style')[1].getAttribute('data-href')).toBe('http://a/styles/b/staged.css')
|
|
261
261
|
})
|
|
262
262
|
|
|
263
|
-
it('should
|
|
263
|
+
it('should validate stylesheet when included as link before rendering component', async () => {
|
|
264
|
+
const spy = vi.spyOn(global, 'fetch')
|
|
265
|
+
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
266
|
+
if (url.match('http://a/components/b/c/d/1.html')) {
|
|
267
|
+
return new Response('FRESH', {
|
|
268
|
+
headers: {
|
|
269
|
+
date: now.toUTCString(),
|
|
270
|
+
'last-modified': earlier.toUTCString()
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
} else if (url.match('.css')) {
|
|
274
|
+
return new Response('FRESH', {
|
|
275
|
+
headers: {
|
|
276
|
+
date: /*String(options?.method).toLowerCase() == 'head' ? */ now.toUTCString(),
|
|
277
|
+
'last-modified': earlier.toUTCString()
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
document.body.innerHTML =
|
|
284
|
+
'<feaas-component src="http://a/components/b/c/d/1.html"></feaas-component><link rel="stylesheet" href="http://a/styles/b/published.css" />'
|
|
285
|
+
|
|
286
|
+
const component = document.body.firstElementChild as FEAASComponent
|
|
287
|
+
await expect(component.whenRendered).resolves.toEqual(component)
|
|
288
|
+
expect(spy).toHaveBeenCalledTimes(2)
|
|
289
|
+
expect(document.head.querySelectorAll('style').length).toBe(0)
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('should validate and re-write stylesheet when included as link before rendering component', async () => {
|
|
293
|
+
const spy = vi.spyOn(global, 'fetch')
|
|
294
|
+
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
295
|
+
if (url.match('http://a/components/b/c/d/1.html')) {
|
|
296
|
+
return new Response('FRESH', {
|
|
297
|
+
headers: {
|
|
298
|
+
date: now.toUTCString(),
|
|
299
|
+
'last-modified': earlier.toUTCString()
|
|
300
|
+
}
|
|
301
|
+
})
|
|
302
|
+
} else if (url.match('.css')) {
|
|
303
|
+
if (!options?.cache) {
|
|
304
|
+
// respond with cached css
|
|
305
|
+
return new Response('CACHED', {
|
|
306
|
+
headers: {
|
|
307
|
+
date: earliest.toUTCString(),
|
|
308
|
+
'last-modified': earliest.toUTCString()
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
} else {
|
|
312
|
+
// respond with up to date
|
|
313
|
+
return new Response('CACHED', {
|
|
314
|
+
headers: {
|
|
315
|
+
date: now.toUTCString(),
|
|
316
|
+
'last-modified': earliest.toUTCString()
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
document.body.innerHTML =
|
|
324
|
+
'<feaas-component src="http://a/components/b/c/d/1.html"></feaas-component><link rel="stylesheet" href="http://a/styles/b/published.css" />'
|
|
325
|
+
|
|
326
|
+
const component = document.body.firstElementChild as FEAASComponent
|
|
327
|
+
await expect(component.whenRendered).resolves.toEqual(component)
|
|
328
|
+
expect(spy).toHaveBeenCalledTimes(3)
|
|
329
|
+
// render with stale stylesheet
|
|
330
|
+
expect(document.head.querySelectorAll('style').length).toBe(0)
|
|
331
|
+
await new Promise((r) => setTimeout(r, 50))
|
|
332
|
+
expect(spy).toHaveBeenCalledTimes(3)
|
|
333
|
+
expect(document.head.querySelectorAll('style').length).toBe(0)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('should validate and re-write stylesheet when included as link before rendering component', async () => {
|
|
264
337
|
const spy = vi.spyOn(global, 'fetch')
|
|
265
338
|
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
266
339
|
if (url.match('http://a/components/b/c/d/1.html')) {
|
|
267
|
-
return new Response('
|
|
340
|
+
return new Response('FRESH', {
|
|
268
341
|
headers: {
|
|
269
342
|
date: now.toUTCString(),
|
|
270
343
|
'last-modified': earlier.toUTCString()
|
|
271
344
|
}
|
|
272
345
|
})
|
|
346
|
+
} else if (url.match('.css')) {
|
|
347
|
+
if (!options?.cache) {
|
|
348
|
+
// respond with cached css
|
|
349
|
+
return new Response('CACHED', {
|
|
350
|
+
headers: {
|
|
351
|
+
date: earliest.toUTCString(),
|
|
352
|
+
'last-modified': earliest.toUTCString()
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
} else {
|
|
356
|
+
if (String(options?.method).toLowerCase() == 'head') await new Promise((r) => setTimeout(r, 50))
|
|
357
|
+
// respond with up to date
|
|
358
|
+
return new Response('FRESH', {
|
|
359
|
+
headers: {
|
|
360
|
+
date: now.toUTCString(),
|
|
361
|
+
'last-modified': now.toUTCString()
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
}
|
|
273
365
|
}
|
|
274
366
|
})
|
|
275
367
|
|
|
@@ -278,7 +370,12 @@ describe('Components', () => {
|
|
|
278
370
|
|
|
279
371
|
const component = document.body.firstElementChild as FEAASComponent
|
|
280
372
|
await expect(component.whenRendered).resolves.toEqual(component)
|
|
373
|
+
expect(spy).toHaveBeenCalledTimes(3)
|
|
374
|
+
// render with stale stylesheet
|
|
281
375
|
expect(document.head.querySelectorAll('style').length).toBe(0)
|
|
376
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
377
|
+
expect(spy).toHaveBeenCalledTimes(4)
|
|
378
|
+
expect(document.head.querySelectorAll('style').length).toBe(1)
|
|
282
379
|
})
|
|
283
380
|
|
|
284
381
|
it('should revalidate cached component response', async () => {
|
|
@@ -286,7 +383,7 @@ describe('Components', () => {
|
|
|
286
383
|
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
287
384
|
if (url.match('CDN/components/L/C/V/R') && options.method != 'HEAD') {
|
|
288
385
|
// 1. Here's cached component
|
|
289
|
-
return new Response('
|
|
386
|
+
return new Response('CACHED', {
|
|
290
387
|
headers: {
|
|
291
388
|
date: earlier.toUTCString(),
|
|
292
389
|
'last-modified': earlier.toUTCString()
|
|
@@ -311,7 +408,7 @@ describe('Components', () => {
|
|
|
311
408
|
await expect(component.whenRendered).resolves.toEqual(component)
|
|
312
409
|
expect(spy).toHaveBeenCalledTimes(2)
|
|
313
410
|
|
|
314
|
-
expect(component.innerHTML).toContain('
|
|
411
|
+
expect(component.innerHTML).toContain('CACHED')
|
|
315
412
|
})
|
|
316
413
|
|
|
317
414
|
it('should reload component while using stale cache', async () => {
|
|
@@ -335,7 +432,7 @@ describe('Components', () => {
|
|
|
335
432
|
})
|
|
336
433
|
} else {
|
|
337
434
|
// Up to date version
|
|
338
|
-
return new Response('
|
|
435
|
+
return new Response('FRESH', {
|
|
339
436
|
headers: {
|
|
340
437
|
date: now.toUTCString(),
|
|
341
438
|
'last-modified': earlier.toUTCString()
|
|
@@ -356,7 +453,7 @@ describe('Components', () => {
|
|
|
356
453
|
// whenLoaded only fires when ensures that up-to-date version is loaded
|
|
357
454
|
await expect(component.whenLoaded).resolves.toBeTruthy()
|
|
358
455
|
expect(spy).toHaveBeenCalledTimes(3)
|
|
359
|
-
expect(component.innerHTML).toContain('
|
|
456
|
+
expect(component.innerHTML).toContain('FRESH')
|
|
360
457
|
})
|
|
361
458
|
|
|
362
459
|
it('should re-fetch component when attributes change', async () => {
|
|
@@ -418,7 +515,7 @@ describe('Components', () => {
|
|
|
418
515
|
})
|
|
419
516
|
} else {
|
|
420
517
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
421
|
-
return new Response('
|
|
518
|
+
return new Response('FRESH {}', {
|
|
422
519
|
headers: {
|
|
423
520
|
date: now.toUTCString(),
|
|
424
521
|
'last-modified': earlier.toUTCString()
|
|
@@ -445,7 +542,7 @@ describe('Components', () => {
|
|
|
445
542
|
} else {
|
|
446
543
|
// Latest
|
|
447
544
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
448
|
-
return new Response('
|
|
545
|
+
return new Response('FRESH', {
|
|
449
546
|
headers: {
|
|
450
547
|
date: now.toUTCString(),
|
|
451
548
|
'last-modified': earlier.toUTCString()
|
|
@@ -459,20 +556,20 @@ describe('Components', () => {
|
|
|
459
556
|
const component = document.body.firstElementChild as FEAASComponent
|
|
460
557
|
expect(document.head.querySelector('style')).toBeNull()
|
|
461
558
|
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
462
|
-
|
|
463
|
-
expect(stylesheet.innerHTML).toEqual('')
|
|
559
|
+
expect(document.head.querySelector('style')).toBeNull()
|
|
464
560
|
expect(component.innerHTML).toEqual('')
|
|
465
561
|
// Component will rendered with stale cached stylesheet
|
|
466
562
|
await expect(component.whenRendered).resolves.toEqual(component)
|
|
563
|
+
const stylesheet = document.head.querySelector('style') as HTMLStyleElement
|
|
467
564
|
expect(stylesheet.innerHTML).toEqual('YESTERDAY {}')
|
|
468
565
|
expect(component.innerHTML).toEqual('YESTERDAY')
|
|
469
566
|
// Component will update itself with stale cached stylesheet
|
|
470
|
-
await expect(component.whenLoaded).resolves.toEqual('
|
|
567
|
+
await expect(component.whenLoaded).resolves.toEqual('FRESH')
|
|
471
568
|
expect(stylesheet.innerHTML).toEqual('YESTERDAY {}')
|
|
472
|
-
expect(component.innerHTML).toEqual('
|
|
569
|
+
expect(component.innerHTML).toEqual('FRESH')
|
|
473
570
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
474
571
|
// Stylesheet will update later
|
|
475
|
-
expect(stylesheet.innerHTML).toEqual('
|
|
572
|
+
expect(stylesheet.innerHTML).toEqual('FRESH {}')
|
|
476
573
|
})
|
|
477
574
|
|
|
478
575
|
it('render without data', () => {
|
|
@@ -29,7 +29,7 @@ describe('Stylesheet', () => {
|
|
|
29
29
|
it('should load stylesheet (cold cache)', async () => {
|
|
30
30
|
const spy = vi.spyOn(global, 'fetch')
|
|
31
31
|
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
32
|
-
return new Response('
|
|
32
|
+
return new Response('FRESH {}', {
|
|
33
33
|
headers: {
|
|
34
34
|
date: now.toUTCString(),
|
|
35
35
|
'last-modified': earlier.toUTCString()
|
|
@@ -44,13 +44,13 @@ describe('Stylesheet', () => {
|
|
|
44
44
|
await expect(stylesheet.whenRendered).resolves.toEqual(stylesheet)
|
|
45
45
|
expect(spy).toHaveBeenCalledTimes(1)
|
|
46
46
|
|
|
47
|
-
expect(document.head.querySelector('style').textContent).toContain('
|
|
47
|
+
expect(document.head.querySelector('style').textContent).toContain('FRESH')
|
|
48
48
|
})
|
|
49
49
|
it('should load stylesheet with minimum parameters(cold cache)', async () => {
|
|
50
50
|
const spy = vi.spyOn(global, 'fetch')
|
|
51
51
|
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
52
52
|
if (url.match(DEFAULT_CDN_URL + '/styles/L/published.css'))
|
|
53
|
-
return new Response('
|
|
53
|
+
return new Response('FRESH {}', {
|
|
54
54
|
headers: {
|
|
55
55
|
date: now.toUTCString(),
|
|
56
56
|
'last-modified': earlier.toUTCString()
|
|
@@ -65,14 +65,14 @@ describe('Stylesheet', () => {
|
|
|
65
65
|
await expect(stylesheet.whenRendered).resolves.toEqual(stylesheet)
|
|
66
66
|
expect(spy).toHaveBeenCalledTimes(1)
|
|
67
67
|
|
|
68
|
-
expect(document.head.querySelector('style').textContent).toContain('
|
|
68
|
+
expect(document.head.querySelector('style').textContent).toContain('FRESH')
|
|
69
69
|
})
|
|
70
70
|
|
|
71
71
|
it('should invalidate stylesheet cache', async () => {
|
|
72
72
|
const spy = vi.spyOn(global, 'fetch')
|
|
73
73
|
spy.mockImplementation(async (url: string, options: Parameters<typeof fetch>[1] = {}) => {
|
|
74
74
|
if (url.match('CDN/styles/L/published.css') && options?.method != 'HEAD') {
|
|
75
|
-
return new Response('
|
|
75
|
+
return new Response('FRESH {}', {
|
|
76
76
|
headers: {
|
|
77
77
|
date: earlier.toUTCString(),
|
|
78
78
|
'last-modified': earlier.toUTCString()
|
|
@@ -95,7 +95,7 @@ describe('Stylesheet', () => {
|
|
|
95
95
|
await expect(stylesheet.whenRendered).resolves.toEqual(stylesheet)
|
|
96
96
|
expect(spy).toHaveBeenCalledTimes(2)
|
|
97
97
|
|
|
98
|
-
expect(document.head.querySelector('style').textContent).toContain('
|
|
98
|
+
expect(document.head.querySelector('style').textContent).toContain('FRESH')
|
|
99
99
|
})
|
|
100
100
|
it('should update stylesheet while using stale cache', async () => {
|
|
101
101
|
const spy = vi.spyOn(global, 'fetch')
|
|
@@ -117,7 +117,7 @@ describe('Stylesheet', () => {
|
|
|
117
117
|
}
|
|
118
118
|
})
|
|
119
119
|
} else {
|
|
120
|
-
return new Response('
|
|
120
|
+
return new Response('FRESH {}', {
|
|
121
121
|
headers: {
|
|
122
122
|
date: now.toUTCString(),
|
|
123
123
|
'last-modified': earlier.toUTCString()
|
|
@@ -137,7 +137,7 @@ describe('Stylesheet', () => {
|
|
|
137
137
|
// whenLoaded only fires when ensures that up-to-date version is loaded
|
|
138
138
|
await expect(stylesheet.whenLoaded).resolves.toBeTruthy()
|
|
139
139
|
expect(spy).toHaveBeenCalledTimes(3)
|
|
140
|
-
expect(document.head.querySelector('style').textContent).toContain('
|
|
140
|
+
expect(document.head.querySelector('style').textContent).toContain('FRESH {}')
|
|
141
141
|
})
|
|
142
142
|
|
|
143
143
|
it('should load stylesheet statically', async () => {
|
|
@@ -160,7 +160,7 @@ describe('Stylesheet', () => {
|
|
|
160
160
|
}
|
|
161
161
|
})
|
|
162
162
|
} else {
|
|
163
|
-
return new Response('
|
|
163
|
+
return new Response('FRESH {}', {
|
|
164
164
|
headers: {
|
|
165
165
|
date: now.toUTCString(),
|
|
166
166
|
'last-modified': earlier.toUTCString()
|
|
@@ -175,7 +175,9 @@ describe('Stylesheet', () => {
|
|
|
175
175
|
library: 'L',
|
|
176
176
|
revision: 'published'
|
|
177
177
|
}).then((r) => r.textContent)
|
|
178
|
-
).resolves.toEqual('
|
|
179
|
-
await expect(loadStylesheet(`http://CDN/L/published.css`).then((r) => r.textContent)).resolves.toEqual('
|
|
178
|
+
).resolves.toEqual('FRESH {}')
|
|
179
|
+
await expect(loadStylesheet(`http://CDN/L/published.css`).then((r) => r.textContent)).resolves.toEqual('FRESH {}')
|
|
180
180
|
})
|
|
181
|
+
|
|
182
|
+
it('should ')
|
|
181
183
|
})
|