@furystack/shades-common-components 13.3.1 โ 13.4.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/CHANGELOG.md +33 -0
- package/esm/components/alert.d.ts.map +1 -1
- package/esm/components/alert.js +7 -6
- package/esm/components/alert.js.map +1 -1
- package/esm/components/cache-view.d.ts +2 -1
- package/esm/components/cache-view.d.ts.map +1 -1
- package/esm/components/cache-view.js +9 -7
- package/esm/components/cache-view.js.map +1 -1
- package/esm/components/cache-view.spec.js +195 -145
- package/esm/components/cache-view.spec.js.map +1 -1
- package/esm/components/command-palette/command-palette-suggestion-list.js +1 -1
- package/esm/components/command-palette/command-palette-suggestion-list.js.map +1 -1
- package/esm/components/inputs/input.d.ts.map +1 -1
- package/esm/components/inputs/input.js +2 -0
- package/esm/components/inputs/input.js.map +1 -1
- package/esm/components/inputs/select.d.ts.map +1 -1
- package/esm/components/inputs/select.js +3 -1
- package/esm/components/inputs/select.js.map +1 -1
- package/esm/components/result.d.ts +4 -2
- package/esm/components/result.d.ts.map +1 -1
- package/esm/components/result.js +11 -10
- package/esm/components/result.js.map +1 -1
- package/esm/components/suggest/index.d.ts.map +1 -1
- package/esm/components/suggest/index.js +7 -3
- package/esm/components/suggest/index.js.map +1 -1
- package/esm/components/tabs.d.ts +2 -0
- package/esm/components/tabs.d.ts.map +1 -1
- package/esm/components/tabs.js +5 -4
- package/esm/components/tabs.js.map +1 -1
- package/esm/components/tabs.spec.js +57 -0
- package/esm/components/tabs.spec.js.map +1 -1
- package/esm/components/tree/tree.d.ts.map +1 -1
- package/esm/components/tree/tree.js +1 -0
- package/esm/components/tree/tree.js.map +1 -1
- package/esm/components/wizard/index.d.ts +2 -1
- package/esm/components/wizard/index.d.ts.map +1 -1
- package/esm/components/wizard/index.js +3 -3
- package/esm/components/wizard/index.js.map +1 -1
- package/esm/components/wizard/index.spec.js +46 -1
- package/esm/components/wizard/index.spec.js.map +1 -1
- package/package.json +6 -6
- package/src/components/alert.tsx +9 -6
- package/src/components/cache-view.spec.tsx +266 -173
- package/src/components/cache-view.tsx +21 -8
- package/src/components/command-palette/command-palette-suggestion-list.tsx +1 -1
- package/src/components/inputs/input.tsx +2 -0
- package/src/components/inputs/select.tsx +3 -1
- package/src/components/result.tsx +17 -10
- package/src/components/suggest/index.tsx +18 -15
- package/src/components/tabs.spec.tsx +72 -0
- package/src/components/tabs.tsx +9 -4
- package/src/components/tree/tree.tsx +1 -0
- package/src/components/wizard/index.spec.tsx +57 -1
- package/src/components/wizard/index.tsx +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [13.4.1] - 2026-03-06
|
|
4
|
+
|
|
5
|
+
### ๐ Bug Fixes
|
|
6
|
+
|
|
7
|
+
- Fixed duplicated content on re-render in `Alert` and `Result` components caused by reusing module-level JSX element constants across renders. Default icons are now created via factory functions (`getDefaultIcon()`) that return fresh elements per render call.
|
|
8
|
+
- Updated `Result` exports: renamed `resultDefaultIcons` to `resultGetDefaultIcon` (factory function) and added `resultDefaultIconDefs` (icon definition map)
|
|
9
|
+
|
|
10
|
+
### ๐งช Tests
|
|
11
|
+
|
|
12
|
+
- Refactored `CacheView` tests to use `using()` / `usingAsync()` wrappers for `Cache` disposal, ensuring proper cleanup even if assertions fail
|
|
13
|
+
|
|
14
|
+
### ๐ง Chores
|
|
15
|
+
|
|
16
|
+
- Added `eslint-disable` comments for intentional `no-css-state-hooks` usage in `Input` (focused state) and `Select` (isFocused state) components where CSS state management via `useState` is the intended pattern
|
|
17
|
+
|
|
18
|
+
## [13.4.0] - 2026-03-05
|
|
19
|
+
|
|
20
|
+
### โจ Features
|
|
21
|
+
|
|
22
|
+
- Added `viewTransition` prop to `Tabs` โ animates tab panel switches via the View Transition API when the active tab changes
|
|
23
|
+
- Added `viewTransition` prop to `Wizard` โ animates step transitions (next/prev) via the View Transition API
|
|
24
|
+
- Added `viewTransition` prop to `CacheView` โ animates state category changes (loading โ value, value โ error, etc.) via the View Transition API
|
|
25
|
+
|
|
26
|
+
### ๐งช Tests
|
|
27
|
+
|
|
28
|
+
- Added tests for `Tabs` verifying `startViewTransition` is called on tab switch when enabled and skipped when not set
|
|
29
|
+
- Added tests for `Wizard` verifying `startViewTransition` is called on next/prev navigation when enabled and skipped when not set
|
|
30
|
+
- Added tests for `CacheView` verifying `startViewTransition` is called on state category changes when enabled and skipped when not set
|
|
31
|
+
|
|
32
|
+
### โฌ๏ธ Dependencies
|
|
33
|
+
|
|
34
|
+
- Updated `@furystack/shades` peer dependency
|
|
35
|
+
|
|
3
36
|
## [13.3.1] - 2026-03-04
|
|
4
37
|
|
|
5
38
|
### โฌ๏ธ Dependencies
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.d.ts","sourceRoot":"","sources":["../../src/components/alert.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAavD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAA;AAEpE,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC,WAAW,CAAC,GAAG;IACrD,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,OAAO,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAA;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,KAAK,IAAI,CAAA;IAClC,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,GAAG,MAAM,CAAA;CAC5B,CAAA;
|
|
1
|
+
{"version":3,"file":"alert.d.ts","sourceRoot":"","sources":["../../src/components/alert.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAavD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAA;AAEpE,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC,WAAW,CAAC,GAAG;IACrD,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,OAAO,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAA;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,KAAK,IAAI,CAAA;IAClC,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,GAAG,MAAM,CAAA;CAC5B,CAAA;AAmBD,eAAO,MAAM,KAAK;;;;;eAxBL,aAAa;cACd,QAAQ,GAAG,UAAU,GAAG,UAAU;YACpC,MAAM;cACJ,CAAC,EAAE,EAAE,UAAU,KAAK,IAAI;WAC3B,GAAG,CAAC,OAAO,GAAG,MAAM;sEAwK3B,CAAA"}
|
package/esm/components/alert.js
CHANGED
|
@@ -9,12 +9,13 @@ const severityColorMap = {
|
|
|
9
9
|
info: { ...paletteMainColors.info, light: cssVariableTheme.palette.info.light },
|
|
10
10
|
success: { ...paletteMainColors.success, light: cssVariableTheme.palette.success.light },
|
|
11
11
|
};
|
|
12
|
-
const
|
|
13
|
-
error:
|
|
14
|
-
warning:
|
|
15
|
-
info:
|
|
16
|
-
success:
|
|
12
|
+
const defaultIconDefs = {
|
|
13
|
+
error: errorCircle,
|
|
14
|
+
warning: warningIcon,
|
|
15
|
+
info: infoIcon,
|
|
16
|
+
success: checkCircle,
|
|
17
17
|
};
|
|
18
|
+
const getDefaultIcon = (severity) => (createComponent(Icon, { icon: defaultIconDefs[severity], size: "small" }));
|
|
18
19
|
export const Alert = Shade({
|
|
19
20
|
shadowDomName: 'shade-alert',
|
|
20
21
|
css: {
|
|
@@ -115,7 +116,7 @@ export const Alert = Shade({
|
|
|
115
116
|
...style,
|
|
116
117
|
},
|
|
117
118
|
});
|
|
118
|
-
const displayIcon = icon ??
|
|
119
|
+
const displayIcon = icon ?? getDefaultIcon(severity);
|
|
119
120
|
return (createComponent(createComponent, null,
|
|
120
121
|
createComponent("span", { className: "alert-icon" }, displayIcon),
|
|
121
122
|
createComponent("div", { className: "alert-content" },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.js","sourceRoot":"","sources":["../../src/components/alert.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAA;AACrF,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACtC,OAAO,EACL,WAAW,EACX,KAAK,IAAI,SAAS,EAClB,WAAW,EACX,IAAI,IAAI,QAAQ,EAChB,OAAO,IAAI,WAAW,GACvB,MAAM,6BAA6B,CAAA;AAYpC,MAAM,gBAAgB,GAAiF;IACrG,KAAK,EAAE,EAAE,GAAG,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE;IAClF,OAAO,EAAE,EAAE,GAAG,iBAAiB,CAAC,OAAO,EAAE,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE;IACxF,IAAI,EAAE,EAAE,GAAG,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE;IAC/E,OAAO,EAAE,EAAE,GAAG,iBAAiB,CAAC,OAAO,EAAE,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE;CACzF,CAAA;AAED,MAAM,
|
|
1
|
+
{"version":3,"file":"alert.js","sourceRoot":"","sources":["../../src/components/alert.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Cache, CacheWithValue } from '@furystack/cache';
|
|
2
|
-
import type { PartialElement, ShadeComponent } from '@furystack/shades';
|
|
2
|
+
import type { PartialElement, ShadeComponent, ViewTransitionConfig } from '@furystack/shades';
|
|
3
3
|
/**
|
|
4
4
|
* Props for the CacheView component.
|
|
5
5
|
* @typeParam TData - The type of data stored in the cache
|
|
@@ -25,6 +25,7 @@ export type CacheViewProps<TData, TArgs extends any[], TContentProps extends {
|
|
|
25
25
|
* If not provided, a default Result + retry Button is shown.
|
|
26
26
|
*/
|
|
27
27
|
error?: (error: unknown, retry: () => void) => JSX.Element;
|
|
28
|
+
viewTransition?: boolean | ViewTransitionConfig;
|
|
28
29
|
} & (keyof Omit<TContentProps, 'data' | keyof PartialElement<HTMLElement>> extends never ? {
|
|
29
30
|
contentProps?: never;
|
|
30
31
|
} : {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-view.d.ts","sourceRoot":"","sources":["../../src/components/cache-view.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"cache-view.d.ts","sourceRoot":"","sources":["../../src/components/cache-view.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAO7F;;;;;GAKG;AACH,MAAM,MAAM,cAAc,CACxB,KAAK,EACL,KAAK,SAAS,GAAG,EAAE,EACnB,aAAa,SAAS;IAAE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;CAAE,IACrF;IACF,gDAAgD;IAChD,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IAC1B,6DAA6D;IAC7D,IAAI,EAAE,KAAK,CAAA;IACX,gFAAgF;IAChF,OAAO,EAAE,cAAc,CAAC,aAAa,CAAC,CAAA;IACtC,kFAAkF;IAClF,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,GAAG,CAAC,OAAO,CAAA;IAC1D,cAAc,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAA;CAChD,GAAG,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC,SAAS,KAAK,GACpF;IAAE,YAAY,CAAC,EAAE,KAAK,CAAA;CAAE,GACxB;IAAE,YAAY,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC,CAAA;CAAE,CAAC,CAAA;AAiDtF,eAAO,MAAM,SAAS,EA2DL,CACf,KAAK,EACL,KAAK,SAAS,GAAG,EAAE,EACnB,aAAa,SAAS;IAAE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;CAAE,EAEvF,KAAK,EAAE,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,aAAa,CAAC,KAC/C,GAAG,CAAC,OAAO,CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hasCacheValue, isFailedCacheResult, isObsoleteCacheResult } from '@furystack/cache';
|
|
2
|
-
import { Shade, createComponent } from '@furystack/shades';
|
|
2
|
+
import { Shade, createComponent, transitionedValue } from '@furystack/shades';
|
|
3
3
|
import { cssVariableTheme } from '../services/css-variable-theme.js';
|
|
4
4
|
import { Button } from './button.js';
|
|
5
5
|
import { Result } from './result.js';
|
|
@@ -11,10 +11,12 @@ export const CacheView = Shade({
|
|
|
11
11
|
fontFamily: cssVariableTheme.typography.fontFamily,
|
|
12
12
|
},
|
|
13
13
|
render: ({ props, useObservable, useState }) => {
|
|
14
|
-
const { cache, args, content, loader, error, contentProps } = props;
|
|
14
|
+
const { cache, args, content, loader, error, contentProps, viewTransition } = props;
|
|
15
15
|
const argsKey = JSON.stringify(args);
|
|
16
16
|
const observable = cache.getObservable(...args);
|
|
17
17
|
const [result] = useObservable(`cache-${argsKey}`, observable);
|
|
18
|
+
const getCategory = (r) => isFailedCacheResult(r) ? 'error' : hasCacheValue(r) ? 'value' : 'loading';
|
|
19
|
+
const displayedResult = transitionedValue(useState, 'displayedResult', result, viewTransition, (prev, next) => getCategory(prev) !== getCategory(next));
|
|
18
20
|
const [lastReloadedArgsKey, setLastReloadedArgsKey] = useState('lastReloadedArgsKey', null);
|
|
19
21
|
const retry = () => {
|
|
20
22
|
cache.reload(...args).catch(() => {
|
|
@@ -22,13 +24,13 @@ export const CacheView = Shade({
|
|
|
22
24
|
});
|
|
23
25
|
};
|
|
24
26
|
// 1. Error first
|
|
25
|
-
if (isFailedCacheResult(
|
|
27
|
+
if (isFailedCacheResult(displayedResult)) {
|
|
26
28
|
const errorRenderer = error ?? getDefaultErrorUi;
|
|
27
|
-
return errorRenderer(
|
|
29
|
+
return errorRenderer(displayedResult.error, retry);
|
|
28
30
|
}
|
|
29
31
|
// 2. Value next
|
|
30
|
-
if (hasCacheValue(
|
|
31
|
-
if (isObsoleteCacheResult(
|
|
32
|
+
if (hasCacheValue(displayedResult)) {
|
|
33
|
+
if (isObsoleteCacheResult(displayedResult)) {
|
|
32
34
|
if (lastReloadedArgsKey !== argsKey) {
|
|
33
35
|
setLastReloadedArgsKey(argsKey);
|
|
34
36
|
cache.reload(...args).catch(() => {
|
|
@@ -40,7 +42,7 @@ export const CacheView = Shade({
|
|
|
40
42
|
setLastReloadedArgsKey(null);
|
|
41
43
|
}
|
|
42
44
|
return createComponent(content, {
|
|
43
|
-
data:
|
|
45
|
+
data: displayedResult,
|
|
44
46
|
...(contentProps ?? {}),
|
|
45
47
|
});
|
|
46
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-view.js","sourceRoot":"","sources":["../../src/components/cache-view.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AAE5F,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"cache-view.js","sourceRoot":"","sources":["../../src/components/cache-view.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Cache } from '@furystack/cache';
|
|
2
2
|
import { Shade, createComponent, flushUpdates } from '@furystack/shades';
|
|
3
|
+
import { using, usingAsync } from '@furystack/utils';
|
|
3
4
|
import { describe, expect, it, vi } from 'vitest';
|
|
4
5
|
import { CacheView } from './cache-view.js';
|
|
5
6
|
const TestContent = Shade({
|
|
@@ -27,202 +28,251 @@ describe('CacheView', () => {
|
|
|
27
28
|
expect(typeof CacheView).toBe('function');
|
|
28
29
|
});
|
|
29
30
|
it('should create a cache-view element', () => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
using(new Cache({ load: async (key) => key }), (cache) => {
|
|
32
|
+
const el = (createComponent(CacheView, { cache: cache, args: ['test'], content: TestContent }));
|
|
33
|
+
expect(el).toBeDefined();
|
|
34
|
+
expect(el.tagName?.toLowerCase()).toBe('shade-cache-view');
|
|
35
|
+
});
|
|
35
36
|
});
|
|
36
37
|
describe('loading state', () => {
|
|
37
38
|
it('should render null by default when loading', async () => {
|
|
38
|
-
|
|
39
|
+
await usingAsync(new Cache({
|
|
39
40
|
load: () => new Promise(() => { }),
|
|
41
|
+
}), async (cache) => {
|
|
42
|
+
const { cacheView } = await renderCacheView(cache, ['test']);
|
|
43
|
+
expect(cacheView.querySelector('test-cache-content')).toBeNull();
|
|
44
|
+
expect(cacheView.querySelector('shade-result')).toBeNull();
|
|
40
45
|
});
|
|
41
|
-
const { cacheView } = await renderCacheView(cache, ['test']);
|
|
42
|
-
expect(cacheView.querySelector('test-cache-content')).toBeNull();
|
|
43
|
-
expect(cacheView.querySelector('shade-result')).toBeNull();
|
|
44
|
-
cache[Symbol.dispose]();
|
|
45
46
|
});
|
|
46
47
|
it('should render custom loader when provided', async () => {
|
|
47
|
-
|
|
48
|
+
await usingAsync(new Cache({
|
|
48
49
|
load: () => new Promise(() => { }),
|
|
50
|
+
}), async (cache) => {
|
|
51
|
+
const { cacheView } = await renderCacheView(cache, ['test'], {
|
|
52
|
+
loader: (createComponent("span", { className: "custom-loader" }, "Loading...")),
|
|
53
|
+
});
|
|
54
|
+
const loader = cacheView.querySelector('.custom-loader');
|
|
55
|
+
expect(loader).not.toBeNull();
|
|
56
|
+
expect(loader?.textContent).toBe('Loading...');
|
|
49
57
|
});
|
|
50
|
-
const { cacheView } = await renderCacheView(cache, ['test'], {
|
|
51
|
-
loader: (createComponent("span", { className: "custom-loader" }, "Loading...")),
|
|
52
|
-
});
|
|
53
|
-
const loader = cacheView.querySelector('.custom-loader');
|
|
54
|
-
expect(loader).not.toBeNull();
|
|
55
|
-
expect(loader?.textContent).toBe('Loading...');
|
|
56
|
-
cache[Symbol.dispose]();
|
|
57
58
|
});
|
|
58
59
|
});
|
|
59
60
|
describe('loaded state', () => {
|
|
60
61
|
it('should render content when cache has loaded value', async () => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
await usingAsync(new Cache({ load: async (key) => `Hello ${key}` }), async (cache) => {
|
|
63
|
+
await cache.get('world');
|
|
64
|
+
const { cacheView } = await renderCacheView(cache, ['world']);
|
|
65
|
+
const contentEl = cacheView.querySelector('test-cache-content');
|
|
66
|
+
expect(contentEl).not.toBeNull();
|
|
67
|
+
const contentComponent = contentEl;
|
|
68
|
+
contentComponent.updateComponent();
|
|
69
|
+
await flushUpdates();
|
|
70
|
+
const valueEl = contentComponent.querySelector('.content-value');
|
|
71
|
+
expect(valueEl?.textContent).toBe('Hello world');
|
|
72
|
+
});
|
|
72
73
|
});
|
|
73
74
|
});
|
|
74
75
|
describe('failed state', () => {
|
|
75
76
|
it('should render default error UI when cache has failed', async () => {
|
|
76
|
-
|
|
77
|
+
await usingAsync(new Cache({
|
|
77
78
|
load: async () => {
|
|
78
79
|
throw new Error('Test failure');
|
|
79
80
|
},
|
|
81
|
+
}), async (cache) => {
|
|
82
|
+
try {
|
|
83
|
+
await cache.get('test');
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// expected
|
|
87
|
+
}
|
|
88
|
+
const { cacheView } = await renderCacheView(cache, ['test']);
|
|
89
|
+
const resultEl = cacheView.querySelector('shade-result');
|
|
90
|
+
expect(resultEl).not.toBeNull();
|
|
91
|
+
const resultComponent = resultEl;
|
|
92
|
+
resultComponent.updateComponent();
|
|
93
|
+
await flushUpdates();
|
|
94
|
+
const titleEl = resultComponent.querySelector('.result-title');
|
|
95
|
+
titleEl.updateComponent();
|
|
96
|
+
await flushUpdates();
|
|
97
|
+
expect(titleEl?.textContent).toBe('Something went wrong');
|
|
80
98
|
});
|
|
81
|
-
try {
|
|
82
|
-
await cache.get('test');
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
// expected
|
|
86
|
-
}
|
|
87
|
-
const { cacheView } = await renderCacheView(cache, ['test']);
|
|
88
|
-
const resultEl = cacheView.querySelector('shade-result');
|
|
89
|
-
expect(resultEl).not.toBeNull();
|
|
90
|
-
const resultComponent = resultEl;
|
|
91
|
-
resultComponent.updateComponent();
|
|
92
|
-
await flushUpdates();
|
|
93
|
-
const titleEl = resultComponent.querySelector('.result-title');
|
|
94
|
-
titleEl.updateComponent();
|
|
95
|
-
await flushUpdates();
|
|
96
|
-
expect(titleEl?.textContent).toBe('Something went wrong');
|
|
97
|
-
cache[Symbol.dispose]();
|
|
98
99
|
});
|
|
99
100
|
it('should render custom error UI when error prop is provided', async () => {
|
|
100
|
-
|
|
101
|
+
await usingAsync(new Cache({
|
|
101
102
|
load: async () => {
|
|
102
103
|
throw new Error('Custom failure');
|
|
103
104
|
},
|
|
105
|
+
}), async (cache) => {
|
|
106
|
+
try {
|
|
107
|
+
await cache.get('test');
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// expected
|
|
111
|
+
}
|
|
112
|
+
const customError = vi.fn((err, _retry) => (createComponent("span", { className: "custom-error" }, String(err))));
|
|
113
|
+
const { cacheView } = await renderCacheView(cache, ['test'], { error: customError });
|
|
114
|
+
expect(customError).toHaveBeenCalledOnce();
|
|
115
|
+
expect(customError.mock.calls[0][0]).toBeInstanceOf(Error);
|
|
116
|
+
const customErrorEl = cacheView.querySelector('.custom-error');
|
|
117
|
+
expect(customErrorEl).not.toBeNull();
|
|
104
118
|
});
|
|
105
|
-
try {
|
|
106
|
-
await cache.get('test');
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
// expected
|
|
110
|
-
}
|
|
111
|
-
const customError = vi.fn((err, _retry) => (createComponent("span", { className: "custom-error" }, String(err))));
|
|
112
|
-
const { cacheView } = await renderCacheView(cache, ['test'], { error: customError });
|
|
113
|
-
expect(customError).toHaveBeenCalledOnce();
|
|
114
|
-
expect(customError.mock.calls[0][0]).toBeInstanceOf(Error);
|
|
115
|
-
const customErrorEl = cacheView.querySelector('.custom-error');
|
|
116
|
-
expect(customErrorEl).not.toBeNull();
|
|
117
|
-
cache[Symbol.dispose]();
|
|
118
119
|
});
|
|
119
120
|
it('should not render content when failed even if stale value exists', async () => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
await usingAsync(new Cache({ load: async (key) => key }), async (cache) => {
|
|
122
|
+
await cache.get('test');
|
|
123
|
+
cache.setExplicitValue({
|
|
124
|
+
loadArgs: ['test'],
|
|
125
|
+
value: { status: 'failed', error: new Error('fail'), value: 'stale', updatedAt: new Date() },
|
|
126
|
+
});
|
|
127
|
+
const { cacheView } = await renderCacheView(cache, ['test']);
|
|
128
|
+
expect(cacheView.querySelector('test-cache-content')).toBeNull();
|
|
129
|
+
expect(cacheView.querySelector('shade-result')).not.toBeNull();
|
|
125
130
|
});
|
|
126
|
-
const { cacheView } = await renderCacheView(cache, ['test']);
|
|
127
|
-
expect(cacheView.querySelector('test-cache-content')).toBeNull();
|
|
128
|
-
expect(cacheView.querySelector('shade-result')).not.toBeNull();
|
|
129
|
-
cache[Symbol.dispose]();
|
|
130
131
|
});
|
|
131
132
|
it('should call cache.reload when retry is invoked', async () => {
|
|
132
133
|
const loadFn = vi.fn(async () => {
|
|
133
134
|
throw new Error('fail');
|
|
134
135
|
});
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
136
|
+
await usingAsync(new Cache({ load: loadFn }), async (cache) => {
|
|
137
|
+
try {
|
|
138
|
+
await cache.get('test');
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// expected
|
|
142
|
+
}
|
|
143
|
+
let capturedRetry;
|
|
144
|
+
const customError = (_err, retry) => {
|
|
145
|
+
capturedRetry = retry;
|
|
146
|
+
return (createComponent("span", { className: "custom-error" }, "Error"));
|
|
147
|
+
};
|
|
148
|
+
await renderCacheView(cache, ['test'], { error: customError });
|
|
149
|
+
expect(capturedRetry).toBeDefined();
|
|
150
|
+
loadFn.mockResolvedValueOnce('recovered');
|
|
151
|
+
capturedRetry();
|
|
152
|
+
await flushUpdates();
|
|
153
|
+
const observable = cache.getObservable('test');
|
|
154
|
+
const result = observable.getValue();
|
|
155
|
+
expect(result.status).toBe('loaded');
|
|
156
|
+
expect(result.value).toBe('recovered');
|
|
157
|
+
});
|
|
157
158
|
});
|
|
158
159
|
});
|
|
159
160
|
describe('obsolete state', () => {
|
|
160
161
|
it('should render content when obsolete and trigger reload', async () => {
|
|
161
162
|
const loadFn = vi.fn(async (key) => `Hello ${key}`);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
await usingAsync(new Cache({ load: loadFn }), async (cache) => {
|
|
164
|
+
await cache.get('test');
|
|
165
|
+
cache.setObsolete('test');
|
|
166
|
+
const { cacheView } = await renderCacheView(cache, ['test']);
|
|
167
|
+
const contentEl = cacheView.querySelector('test-cache-content');
|
|
168
|
+
expect(contentEl).not.toBeNull();
|
|
169
|
+
await flushUpdates();
|
|
170
|
+
// reload should have been called (initial load + obsolete reload)
|
|
171
|
+
expect(loadFn).toHaveBeenCalledTimes(2);
|
|
172
|
+
});
|
|
172
173
|
});
|
|
173
174
|
});
|
|
174
175
|
describe('error takes priority over value', () => {
|
|
175
176
|
it('should show error when failed with value, not content', async () => {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
await usingAsync(new Cache({ load: async (key) => key }), async (cache) => {
|
|
178
|
+
await cache.get('test');
|
|
179
|
+
const failedWithValue = {
|
|
180
|
+
status: 'failed',
|
|
181
|
+
error: new Error('whoops'),
|
|
182
|
+
value: 'stale-data',
|
|
183
|
+
updatedAt: new Date(),
|
|
184
|
+
};
|
|
185
|
+
cache.setExplicitValue({ loadArgs: ['test'], value: failedWithValue });
|
|
186
|
+
const { cacheView } = await renderCacheView(cache, ['test']);
|
|
187
|
+
expect(cacheView.querySelector('test-cache-content')).toBeNull();
|
|
188
|
+
expect(cacheView.querySelector('shade-result')).not.toBeNull();
|
|
189
|
+
});
|
|
189
190
|
});
|
|
190
191
|
});
|
|
191
192
|
describe('contentProps', () => {
|
|
192
193
|
it('should forward contentProps to the content component', async () => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
194
|
+
await usingAsync(new Cache({ load: async (key) => `Hello ${key}` }), async (cache) => {
|
|
195
|
+
await cache.get('world');
|
|
196
|
+
const el = (createComponent("div", null,
|
|
197
|
+
createComponent(CacheView, { cache: cache, args: ['world'], content: TestContentWithLabel, contentProps: { label: 'Greeting' } })));
|
|
198
|
+
const cacheView = el.firstElementChild;
|
|
199
|
+
cacheView.updateComponent();
|
|
200
|
+
await flushUpdates();
|
|
201
|
+
const contentEl = cacheView.querySelector('test-cache-content-with-label');
|
|
202
|
+
expect(contentEl).not.toBeNull();
|
|
203
|
+
contentEl.updateComponent();
|
|
204
|
+
await flushUpdates();
|
|
205
|
+
const valueEl = contentEl.querySelector('.content-value');
|
|
206
|
+
expect(valueEl?.textContent).toBe('Greeting: Hello world');
|
|
207
|
+
});
|
|
207
208
|
});
|
|
208
209
|
it('should forward contentProps when cache entry is obsolete', async () => {
|
|
209
210
|
const loadFn = vi.fn(async (key) => `Hello ${key}`);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
211
|
+
await usingAsync(new Cache({ load: loadFn }), async (cache) => {
|
|
212
|
+
await cache.get('world');
|
|
213
|
+
cache.setObsolete('world');
|
|
214
|
+
const el = (createComponent("div", null,
|
|
215
|
+
createComponent(CacheView, { cache: cache, args: ['world'], content: TestContentWithLabel, contentProps: { label: 'Stale' } })));
|
|
216
|
+
const cacheView = el.firstElementChild;
|
|
217
|
+
cacheView.updateComponent();
|
|
218
|
+
await flushUpdates();
|
|
219
|
+
const contentEl = cacheView.querySelector('test-cache-content-with-label');
|
|
220
|
+
expect(contentEl).not.toBeNull();
|
|
221
|
+
contentEl.updateComponent();
|
|
222
|
+
await flushUpdates();
|
|
223
|
+
const valueEl = contentEl.querySelector('.content-value');
|
|
224
|
+
expect(valueEl?.textContent).toBe('Stale: Hello world');
|
|
225
|
+
expect(loadFn).toHaveBeenCalledTimes(2);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
describe('view transitions', () => {
|
|
230
|
+
const mockStartViewTransition = () => {
|
|
231
|
+
const spy = vi.fn((optionsOrCallback) => {
|
|
232
|
+
const update = typeof optionsOrCallback === 'function' ? optionsOrCallback : optionsOrCallback.update;
|
|
233
|
+
update?.();
|
|
234
|
+
return {
|
|
235
|
+
finished: Promise.resolve(),
|
|
236
|
+
ready: Promise.resolve(),
|
|
237
|
+
updateCallbackDone: Promise.resolve(),
|
|
238
|
+
skipTransition: vi.fn(),
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
document.startViewTransition = spy;
|
|
242
|
+
return spy;
|
|
243
|
+
};
|
|
244
|
+
it('should call startViewTransition when viewTransition is enabled and cache state category changes', async () => {
|
|
245
|
+
const spy = mockStartViewTransition();
|
|
246
|
+
await usingAsync(new Cache({ load: async (key) => `loaded-${key}` }), async (cache) => {
|
|
247
|
+
const el = (createComponent("div", null,
|
|
248
|
+
createComponent(CacheView, { cache: cache, args: ['test'], content: TestContent, loader: createComponent("span", { className: "loader" }, "Loading"), viewTransition: true })));
|
|
249
|
+
const cacheView = el.firstElementChild;
|
|
250
|
+
cacheView.updateComponent();
|
|
251
|
+
await flushUpdates();
|
|
252
|
+
expect(cacheView.querySelector('.loader')).toBeTruthy();
|
|
253
|
+
spy.mockClear();
|
|
254
|
+
await cache.get('test');
|
|
255
|
+
cacheView.updateComponent();
|
|
256
|
+
await flushUpdates();
|
|
257
|
+
expect(spy).toHaveBeenCalled();
|
|
258
|
+
});
|
|
259
|
+
delete document.startViewTransition;
|
|
260
|
+
});
|
|
261
|
+
it('should not call startViewTransition when viewTransition is not set', async () => {
|
|
262
|
+
const spy = mockStartViewTransition();
|
|
263
|
+
await usingAsync(new Cache({ load: async (key) => `loaded-${key}` }), async (cache) => {
|
|
264
|
+
const el = (createComponent("div", null,
|
|
265
|
+
createComponent(CacheView, { cache: cache, args: ['test'], content: TestContent, loader: createComponent("span", { className: "loader" }, "Loading") })));
|
|
266
|
+
const cacheView = el.firstElementChild;
|
|
267
|
+
cacheView.updateComponent();
|
|
268
|
+
await flushUpdates();
|
|
269
|
+
spy.mockClear();
|
|
270
|
+
await cache.get('test');
|
|
271
|
+
cacheView.updateComponent();
|
|
272
|
+
await flushUpdates();
|
|
273
|
+
expect(spy).not.toHaveBeenCalled();
|
|
274
|
+
});
|
|
275
|
+
delete document.startViewTransition;
|
|
226
276
|
});
|
|
227
277
|
});
|
|
228
278
|
});
|