@furystack/shades-common-components 12.0.0 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +26 -0
  3. package/esm/components/cache-view.d.ts +46 -0
  4. package/esm/components/cache-view.d.ts.map +1 -0
  5. package/esm/components/cache-view.js +65 -0
  6. package/esm/components/cache-view.js.map +1 -0
  7. package/esm/components/cache-view.spec.d.ts +2 -0
  8. package/esm/components/cache-view.spec.d.ts.map +1 -0
  9. package/esm/components/cache-view.spec.js +183 -0
  10. package/esm/components/cache-view.spec.js.map +1 -0
  11. package/esm/components/command-palette/command-palette-input.spec.js +148 -148
  12. package/esm/components/command-palette/command-palette-input.spec.js.map +1 -1
  13. package/esm/components/command-palette/command-palette-suggestion-list.spec.js +258 -258
  14. package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -1
  15. package/esm/components/context-menu/context-menu-manager.spec.js +211 -217
  16. package/esm/components/context-menu/context-menu-manager.spec.js.map +1 -1
  17. package/esm/components/data-grid/body.spec.js +173 -173
  18. package/esm/components/data-grid/body.spec.js.map +1 -1
  19. package/esm/components/data-grid/data-grid.spec.js +39 -130
  20. package/esm/components/data-grid/data-grid.spec.js.map +1 -1
  21. package/esm/components/index.d.ts +1 -0
  22. package/esm/components/index.d.ts.map +1 -1
  23. package/esm/components/index.js +1 -0
  24. package/esm/components/index.js.map +1 -1
  25. package/esm/components/skeleton.d.ts.map +1 -1
  26. package/esm/components/skeleton.js +2 -11
  27. package/esm/components/skeleton.js.map +1 -1
  28. package/esm/components/skeleton.spec.js +6 -55
  29. package/esm/components/skeleton.spec.js.map +1 -1
  30. package/esm/services/click-away-service.spec.js +14 -12
  31. package/esm/services/click-away-service.spec.js.map +1 -1
  32. package/esm/services/list-service.spec.js +170 -141
  33. package/esm/services/list-service.spec.js.map +1 -1
  34. package/esm/services/tree-service.spec.js +190 -159
  35. package/esm/services/tree-service.spec.js.map +1 -1
  36. package/package.json +8 -8
  37. package/src/components/cache-view.spec.tsx +210 -0
  38. package/src/components/cache-view.tsx +103 -0
  39. package/src/components/command-palette/command-palette-input.spec.tsx +183 -194
  40. package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +303 -321
  41. package/src/components/context-menu/context-menu-manager.spec.ts +213 -258
  42. package/src/components/data-grid/body.spec.tsx +266 -276
  43. package/src/components/data-grid/data-grid.spec.tsx +137 -232
  44. package/src/components/index.ts +1 -0
  45. package/src/components/skeleton.spec.tsx +6 -73
  46. package/src/components/skeleton.tsx +2 -11
  47. package/src/services/click-away-service.spec.ts +14 -16
  48. package/src/services/list-service.spec.ts +170 -172
  49. package/src/services/tree-service.spec.ts +191 -207
package/CHANGELOG.md CHANGED
@@ -1,5 +1,61 @@
1
1
  # Changelog
2
2
 
3
+ ## [12.1.0] - 2026-02-11
4
+
5
+ ### ✨ Features
6
+
7
+ ### New `CacheView` component
8
+
9
+ Added a new `CacheView` component that renders the state of a cache entry. It subscribes to a `Cache` instance observable and handles all states automatically:
10
+
11
+ 1. **Error first** — shows error UI with a retry button
12
+ 2. **Value next** — renders the content component (triggers reload when obsolete)
13
+ 3. **Loading last** — shows a custom loader or nothing by default
14
+
15
+ ```tsx
16
+ import { CacheView } from '@furystack/shades-common-components'
17
+
18
+ <CacheView cache={userCache} args={[userId]} content={UserContent} />
19
+
20
+ // With custom loader and error UI
21
+ <CacheView
22
+ cache={userCache}
23
+ args={[userId]}
24
+ content={UserContent}
25
+ loader={<Skeleton />}
26
+ error={(err, retry) => (
27
+ <Alert severity="error">
28
+ <Button onclick={retry}>Retry</Button>
29
+ </Alert>
30
+ )}
31
+ />
32
+ ```
33
+
34
+ ### 🐛 Bug Fixes
35
+
36
+ - Fixed `Skeleton` component background styles not rendering correctly when used inside Shadow DOM — moved gradient styles from host CSS to inline styles on the inner element
37
+
38
+ ### 📚 Documentation
39
+
40
+ - Added `CacheView` usage examples to the package README
41
+
42
+ ### ⬆️ Dependencies
43
+
44
+ - Added `@furystack/cache` (workspace:^) as a new dependency
45
+
46
+ ## [12.0.1] - 2026-02-11
47
+
48
+ ### 🧪 Tests
49
+
50
+ - Wrapped all disposable resources in `using()` / `usingAsync()` across command palette, context menu, data grid, click-away service, list service, and tree service tests to ensure cleanup runs even when assertions fail
51
+
52
+ ### ⬆️ Dependencies
53
+
54
+ - Bump `vitest` from `^4.0.17` to `^4.0.18`
55
+ - Updated `@furystack/shades` dependency
56
+ - Removed `semaphore-async-await` dependency
57
+ - Updated `@furystack/shades` with fix for `useState` setter disposal error
58
+
3
59
  ## [12.0.0] - 2026-02-09
4
60
 
5
61
  ### 💥 Breaking Changes
package/README.md CHANGED
@@ -150,6 +150,32 @@ import { Tabs } from '@furystack/shades-common-components'
150
150
  />
151
151
  ```
152
152
 
153
+ ### CacheView
154
+
155
+ Renders the state of a cache entry. Takes a `Cache` instance and `args`, subscribes to the observable, and handles loading, error (with retry), and loaded/obsolete states.
156
+
157
+ ```tsx
158
+ import { CacheView } from '@furystack/shades-common-components'
159
+ import type { CacheWithValue } from '@furystack/cache'
160
+
161
+ const UserContent = Shade<{ data: CacheWithValue<User> }>({
162
+ shadowDomName: 'user-content',
163
+ render: ({ props }) => <div>{props.data.value.name}</div>,
164
+ })
165
+
166
+ // Basic usage
167
+ <CacheView cache={userCache} args={[userId]} content={UserContent} />
168
+
169
+ // With custom loader and error
170
+ <CacheView
171
+ cache={userCache}
172
+ args={[userId]}
173
+ content={UserContent}
174
+ loader={<Skeleton />}
175
+ error={(err, retry) => <Alert severity="error"><Button onclick={retry}>Retry</Button></Alert>}
176
+ />
177
+ ```
178
+
153
179
  ### Loader
154
180
 
155
181
  A loading spinner component.
@@ -0,0 +1,46 @@
1
+ import type { Cache, CacheWithValue } from '@furystack/cache';
2
+ import type { ShadeComponent } from '@furystack/shades';
3
+ /**
4
+ * Props for the CacheView component.
5
+ * @typeParam TData - The type of data stored in the cache
6
+ * @typeParam TArgs - The tuple type of arguments used to identify the cache entry
7
+ */
8
+ export type CacheViewProps<TData, TArgs extends any[]> = {
9
+ /** The cache instance to observe and control */
10
+ cache: Cache<TData, TArgs>;
11
+ /** The arguments identifying which cache entry to display */
12
+ args: TArgs;
13
+ /** Shades component rendered when a value is available (loaded or obsolete). Receives CacheWithValue<TData>. */
14
+ content: ShadeComponent<{
15
+ data: CacheWithValue<TData>;
16
+ }>;
17
+ /** Optional custom loader element. Default: null (nothing shown when loading). */
18
+ loader?: JSX.Element;
19
+ /**
20
+ * Optional custom error UI. Receives the error and a retry callback.
21
+ * The retry callback calls cache.reload(...args).
22
+ * If not provided, a default Result + retry Button is shown.
23
+ */
24
+ error?: (error: unknown, retry: () => void) => JSX.Element;
25
+ };
26
+ /**
27
+ * CacheView renders the state of a cache entry for the given cache + args.
28
+ *
29
+ * It subscribes to the cache observable and handles all states:
30
+ * 1. **Error first** - If the cache entry has failed, shows the error UI with a retry button.
31
+ * 2. **Value next** - If the entry has a value (loaded or obsolete), renders the content component.
32
+ * When obsolete, it also triggers a reload automatically.
33
+ * 3. **Loading last** - If there is no value and no error, shows the loader (or null by default).
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * const MyContent = Shade<{ data: CacheWithValue<User> }>({
38
+ * shadowDomName: 'my-content',
39
+ * render: ({ props }) => <div>{props.data.value.name}</div>,
40
+ * })
41
+ *
42
+ * <CacheView cache={userCache} args={[userId]} content={MyContent} />
43
+ * ```
44
+ */
45
+ export declare const CacheView: <TData, TArgs extends any[]>(props: CacheViewProps<TData, TArgs>) => JSX.Element;
46
+ //# sourceMappingURL=cache-view.d.ts.map
@@ -0,0 +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,MAAM,mBAAmB,CAAA;AAMvD;;;;GAIG;AACH,MAAM,MAAM,cAAc,CAAC,KAAK,EAAE,KAAK,SAAS,GAAG,EAAE,IAAI;IACvD,gDAAgD;IAChD,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IAC1B,6DAA6D;IAC7D,IAAI,EAAE,KAAK,CAAA;IACX,gHAAgH;IAChH,OAAO,EAAE,cAAc,CAAC;QAAE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;KAAE,CAAC,CAAA;IACxD,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;CAC3D,CAAA;AAWD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,SAAS,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC,OA4C/F,CAAA"}
@@ -0,0 +1,65 @@
1
+ import { hasCacheValue, isFailedCacheResult, isObsoleteCacheResult } from '@furystack/cache';
2
+ import { Shade, createComponent } from '@furystack/shades';
3
+ import { Button } from './button.js';
4
+ import { Result } from './result.js';
5
+ const getDefaultErrorUi = (error, retry) => (createComponent(Result, { status: "error", title: "Something went wrong", subtitle: String(error) },
6
+ createComponent(Button, { variant: "outlined", onclick: retry }, "Retry")));
7
+ /**
8
+ * CacheView renders the state of a cache entry for the given cache + args.
9
+ *
10
+ * It subscribes to the cache observable and handles all states:
11
+ * 1. **Error first** - If the cache entry has failed, shows the error UI with a retry button.
12
+ * 2. **Value next** - If the entry has a value (loaded or obsolete), renders the content component.
13
+ * When obsolete, it also triggers a reload automatically.
14
+ * 3. **Loading last** - If there is no value and no error, shows the loader (or null by default).
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * const MyContent = Shade<{ data: CacheWithValue<User> }>({
19
+ * shadowDomName: 'my-content',
20
+ * render: ({ props }) => <div>{props.data.value.name}</div>,
21
+ * })
22
+ *
23
+ * <CacheView cache={userCache} args={[userId]} content={MyContent} />
24
+ * ```
25
+ */
26
+ export const CacheView = Shade({
27
+ shadowDomName: 'shade-cache-view',
28
+ render: ({ props, useObservable, useState }) => {
29
+ const { cache, args, content, loader, error } = props;
30
+ const argsKey = JSON.stringify(args);
31
+ const observable = cache.getObservable(...args);
32
+ const [result] = useObservable(`cache-${argsKey}`, observable);
33
+ const [lastReloadedArgsKey, setLastReloadedArgsKey] = useState('lastReloadedArgsKey', null);
34
+ const retry = () => {
35
+ cache.reload(...args).catch(() => {
36
+ /* error state will be set by cache */
37
+ });
38
+ };
39
+ // 1. Error first
40
+ if (isFailedCacheResult(result)) {
41
+ const errorRenderer = error ?? getDefaultErrorUi;
42
+ return errorRenderer(result.error, retry);
43
+ }
44
+ // 2. Value next
45
+ if (hasCacheValue(result)) {
46
+ if (isObsoleteCacheResult(result)) {
47
+ if (lastReloadedArgsKey !== argsKey) {
48
+ setLastReloadedArgsKey(argsKey);
49
+ cache.reload(...args).catch(() => {
50
+ /* error state will be set by cache */
51
+ });
52
+ }
53
+ }
54
+ else if (lastReloadedArgsKey !== null) {
55
+ setLastReloadedArgsKey(null);
56
+ }
57
+ return createComponent(content, {
58
+ data: result,
59
+ });
60
+ }
61
+ // 3. Loading last
62
+ return loader ?? null;
63
+ },
64
+ });
65
+ //# sourceMappingURL=cache-view.js.map
@@ -0,0 +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;AAE1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAwBpC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAAE,KAAiB,EAAe,EAAE,CAC3E,CACE,gBAAC,MAAM,IAAC,MAAM,EAAC,OAAO,EAAC,KAAK,EAAC,sBAAsB,EAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC;IACzE,gBAAC,MAAM,IAAC,OAAO,EAAC,UAAU,EAAC,OAAO,EAAE,KAAK,YAEhC,CACF,CACgB,CAAA;AAE7B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAqF,KAAK,CAAC;IAC/G,aAAa,EAAE,kBAAkB;IACjC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAsB,EAAE;QACjE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAA;QAErD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;QACpC,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC,CAAA;QAE/C,MAAM,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,SAAS,OAAO,EAAE,EAAE,UAAU,CAAC,CAAA;QAE9D,MAAM,CAAC,mBAAmB,EAAE,sBAAsB,CAAC,GAAG,QAAQ,CAAgB,qBAAqB,EAAE,IAAI,CAAC,CAAA;QAE1G,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;gBAC/B,sCAAsC;YACxC,CAAC,CAAC,CAAA;QACJ,CAAC,CAAA;QAED,iBAAiB;QACjB,IAAI,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC;YAChC,MAAM,aAAa,GAAG,KAAK,IAAI,iBAAiB,CAAA;YAChD,OAAO,aAAa,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QAC3C,CAAC;QAED,gBAAgB;QAChB,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,IAAI,qBAAqB,CAAC,MAAM,CAAC,EAAE,CAAC;gBAClC,IAAI,mBAAmB,KAAK,OAAO,EAAE,CAAC;oBACpC,sBAAsB,CAAC,OAAO,CAAC,CAAA;oBAC/B,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;wBAC/B,sCAAsC;oBACxC,CAAC,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC;iBAAM,IAAI,mBAAmB,KAAK,IAAI,EAAE,CAAC;gBACxC,sBAAsB,CAAC,IAAI,CAAC,CAAA;YAC9B,CAAC;YACD,OAAO,eAAe,CAAC,OAA4D,EAAE;gBACnF,IAAI,EAAE,MAAM;aACb,CAA2B,CAAA;QAC9B,CAAC;QAED,kBAAkB;QAClB,OAAO,MAAM,IAAI,IAAI,CAAA;IACvB,CAAC;CACF,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cache-view.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-view.spec.d.ts","sourceRoot":"","sources":["../../src/components/cache-view.spec.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,183 @@
1
+ import { Cache } from '@furystack/cache';
2
+ import { Shade, createComponent, flushUpdates } from '@furystack/shades';
3
+ import { sleepAsync } from '@furystack/utils';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { CacheView } from './cache-view.js';
6
+ const TestContent = Shade({
7
+ shadowDomName: 'test-cache-content',
8
+ render: ({ props }) => createComponent("span", { className: "content-value" }, props.data.value),
9
+ });
10
+ const renderCacheView = async (cache, args, options) => {
11
+ const el = (createComponent("div", null,
12
+ createComponent(CacheView, { cache: cache, args: args, content: TestContent, loader: options?.loader, error: options?.error })));
13
+ const cacheView = el.firstElementChild;
14
+ cacheView.updateComponent();
15
+ await flushUpdates();
16
+ return { container: el, cacheView };
17
+ };
18
+ describe('CacheView', () => {
19
+ it('should be defined', () => {
20
+ expect(CacheView).toBeDefined();
21
+ expect(typeof CacheView).toBe('function');
22
+ });
23
+ it('should create a cache-view element', () => {
24
+ const cache = new Cache({ load: async (key) => key });
25
+ const el = (createComponent(CacheView, { cache: cache, args: ['test'], content: TestContent }));
26
+ expect(el).toBeDefined();
27
+ expect(el.tagName?.toLowerCase()).toBe('shade-cache-view');
28
+ cache[Symbol.dispose]();
29
+ });
30
+ describe('loading state', () => {
31
+ it('should render null by default when loading', async () => {
32
+ const cache = new Cache({
33
+ load: () => new Promise(() => { }),
34
+ });
35
+ const { cacheView } = await renderCacheView(cache, ['test']);
36
+ expect(cacheView.querySelector('test-cache-content')).toBeNull();
37
+ expect(cacheView.querySelector('shade-result')).toBeNull();
38
+ cache[Symbol.dispose]();
39
+ });
40
+ it('should render custom loader when provided', async () => {
41
+ const cache = new Cache({
42
+ load: () => new Promise(() => { }),
43
+ });
44
+ const { cacheView } = await renderCacheView(cache, ['test'], {
45
+ loader: (createComponent("span", { className: "custom-loader" }, "Loading...")),
46
+ });
47
+ const loader = cacheView.querySelector('.custom-loader');
48
+ expect(loader).not.toBeNull();
49
+ expect(loader?.textContent).toBe('Loading...');
50
+ cache[Symbol.dispose]();
51
+ });
52
+ });
53
+ describe('loaded state', () => {
54
+ it('should render content when cache has loaded value', async () => {
55
+ const cache = new Cache({ load: async (key) => `Hello ${key}` });
56
+ await cache.get('world');
57
+ const { cacheView } = await renderCacheView(cache, ['world']);
58
+ const contentEl = cacheView.querySelector('test-cache-content');
59
+ expect(contentEl).not.toBeNull();
60
+ const contentComponent = contentEl;
61
+ contentComponent.updateComponent();
62
+ await flushUpdates();
63
+ const valueEl = contentComponent.querySelector('.content-value');
64
+ expect(valueEl?.textContent).toBe('Hello world');
65
+ cache[Symbol.dispose]();
66
+ });
67
+ });
68
+ describe('failed state', () => {
69
+ it('should render default error UI when cache has failed', async () => {
70
+ const cache = new Cache({
71
+ load: async () => {
72
+ throw new Error('Test failure');
73
+ },
74
+ });
75
+ try {
76
+ await cache.get('test');
77
+ }
78
+ catch {
79
+ // expected
80
+ }
81
+ const { cacheView } = await renderCacheView(cache, ['test']);
82
+ const resultEl = cacheView.querySelector('shade-result');
83
+ expect(resultEl).not.toBeNull();
84
+ const resultComponent = resultEl;
85
+ resultComponent.updateComponent();
86
+ await flushUpdates();
87
+ expect(resultComponent.querySelector('.result-title')?.textContent).toBe('Something went wrong');
88
+ cache[Symbol.dispose]();
89
+ });
90
+ it('should render custom error UI when error prop is provided', async () => {
91
+ const cache = new Cache({
92
+ load: async () => {
93
+ throw new Error('Custom failure');
94
+ },
95
+ });
96
+ try {
97
+ await cache.get('test');
98
+ }
99
+ catch {
100
+ // expected
101
+ }
102
+ const customError = vi.fn((err, _retry) => (createComponent("span", { className: "custom-error" }, String(err))));
103
+ const { cacheView } = await renderCacheView(cache, ['test'], { error: customError });
104
+ expect(customError).toHaveBeenCalledOnce();
105
+ expect(customError.mock.calls[0][0]).toBeInstanceOf(Error);
106
+ const customErrorEl = cacheView.querySelector('.custom-error');
107
+ expect(customErrorEl).not.toBeNull();
108
+ cache[Symbol.dispose]();
109
+ });
110
+ it('should not render content when failed even if stale value exists', async () => {
111
+ const cache = new Cache({ load: async (key) => key });
112
+ await cache.get('test');
113
+ cache.setExplicitValue({
114
+ loadArgs: ['test'],
115
+ value: { status: 'failed', error: new Error('fail'), value: 'stale', updatedAt: new Date() },
116
+ });
117
+ const { cacheView } = await renderCacheView(cache, ['test']);
118
+ expect(cacheView.querySelector('test-cache-content')).toBeNull();
119
+ expect(cacheView.querySelector('shade-result')).not.toBeNull();
120
+ cache[Symbol.dispose]();
121
+ });
122
+ it('should call cache.reload when retry is invoked', async () => {
123
+ const loadFn = vi.fn(async () => {
124
+ throw new Error('fail');
125
+ });
126
+ const cache = new Cache({ load: loadFn });
127
+ try {
128
+ await cache.get('test');
129
+ }
130
+ catch {
131
+ // expected
132
+ }
133
+ let capturedRetry;
134
+ const customError = (_err, retry) => {
135
+ capturedRetry = retry;
136
+ return (createComponent("span", { className: "custom-error" }, "Error"));
137
+ };
138
+ await renderCacheView(cache, ['test'], { error: customError });
139
+ expect(capturedRetry).toBeDefined();
140
+ loadFn.mockResolvedValueOnce('recovered');
141
+ capturedRetry();
142
+ await sleepAsync(50);
143
+ const observable = cache.getObservable('test');
144
+ const result = observable.getValue();
145
+ expect(result.status).toBe('loaded');
146
+ expect(result.value).toBe('recovered');
147
+ cache[Symbol.dispose]();
148
+ });
149
+ });
150
+ describe('obsolete state', () => {
151
+ it('should render content when obsolete and trigger reload', async () => {
152
+ const loadFn = vi.fn(async (key) => `Hello ${key}`);
153
+ const cache = new Cache({ load: loadFn });
154
+ await cache.get('test');
155
+ cache.setObsolete('test');
156
+ const { cacheView } = await renderCacheView(cache, ['test']);
157
+ const contentEl = cacheView.querySelector('test-cache-content');
158
+ expect(contentEl).not.toBeNull();
159
+ await sleepAsync(50);
160
+ // reload should have been called (initial load + obsolete reload)
161
+ expect(loadFn).toHaveBeenCalledTimes(2);
162
+ cache[Symbol.dispose]();
163
+ });
164
+ });
165
+ describe('error takes priority over value', () => {
166
+ it('should show error when failed with value, not content', async () => {
167
+ const cache = new Cache({ load: async (key) => key });
168
+ await cache.get('test');
169
+ const failedWithValue = {
170
+ status: 'failed',
171
+ error: new Error('whoops'),
172
+ value: 'stale-data',
173
+ updatedAt: new Date(),
174
+ };
175
+ cache.setExplicitValue({ loadArgs: ['test'], value: failedWithValue });
176
+ const { cacheView } = await renderCacheView(cache, ['test']);
177
+ expect(cacheView.querySelector('test-cache-content')).toBeNull();
178
+ expect(cacheView.querySelector('shade-result')).not.toBeNull();
179
+ cache[Symbol.dispose]();
180
+ });
181
+ });
182
+ });
183
+ //# sourceMappingURL=cache-view.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-view.spec.js","sourceRoot":"","sources":["../../src/components/cache-view.spec.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAExC,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,MAAM,WAAW,GAAG,KAAK,CAAmC;IAC1D,aAAa,EAAE,oBAAoB;IACnC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,0BAAM,SAAS,EAAC,eAAe,IAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAQ;CACjF,CAAC,CAAA;AAEF,MAAM,eAAe,GAAG,KAAK,EAC3B,KAA8B,EAC9B,IAAc,EACd,OAGC,EACD,EAAE;IACF,MAAM,EAAE,GAAG,CACT;QACE,gBAAC,SAAS,IAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,GAAI,CACzG,CACP,CAAA;IACD,MAAM,SAAS,GAAG,EAAE,CAAC,iBAAgC,CAAA;IACrD,SAAS,CAAC,eAAe,EAAE,CAAA;IAC3B,MAAM,YAAY,EAAE,CAAA;IACpB,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,CAAA;AACrC,CAAC,CAAA;AAED,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC3B,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAA;QAC/B,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,KAAK,GAAG,IAAI,KAAK,CAAmB,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,CAAA;QACvE,MAAM,EAAE,GAAG,CAAC,gBAAC,SAAS,IAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,WAAW,GAAI,CAA2B,CAAA;QACxG,MAAM,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;QACxB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;QAC1D,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;IACzB,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;YAC1D,MAAM,KAAK,GAAG,IAAI,KAAK,CAAmB;gBACxC,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;aAClC,CAAC,CAAA;YACF,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;YAC5D,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;YAChE,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;YAC1D,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;QACzB,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,KAAK,GAAG,IAAI,KAAK,CAAmB;gBACxC,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;aAClC,CAAC,CAAA;YACF,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE;gBAC3D,MAAM,EAAE,CAAC,0BAAM,SAAS,EAAC,eAAe,iBAAkB,CAA2B;aACtF,CAAC,CAAA;YACF,MAAM,MAAM,GAAG,SAAS,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAA;YACxD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;YAC7B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;YAC9C,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;QACzB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;YACjE,MAAM,KAAK,GAAG,IAAI,KAAK,CAAmB,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,SAAS,GAAG,EAAE,EAAE,CAAC,CAAA;YAClF,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YACxB,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;YAC7D,MAAM,SAAS,GAAG,SAAS,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAA;YAC/D,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;YAChC,MAAM,gBAAgB,GAAG,SAAwB,CAAA;YACjD,gBAAgB,CAAC,eAAe,EAAE,CAAA;YAClC,MAAM,YAAY,EAAE,CAAA;YACpB,MAAM,OAAO,GAAG,gBAAgB,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAA;YAChE,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;YAChD,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;QACzB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;YACpE,MAAM,KAAK,GAAG,IAAI,KAAK,CAAmB;gBACxC,IAAI,EAAE,KAAK,IAAI,EAAE;oBACf,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAA;gBACjC,CAAC;aACF,CAAC,CAAA;YACF,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YACzB,CAAC;YAAC,MAAM,CAAC;gBACP,WAAW;YACb,CAAC;YACD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;YAC5D,MAAM,QAAQ,GAAG,SAAS,CAAC,aAAa,CAAC,cAAc,CAAC,CAAA;YACxD,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;YAC/B,MAAM,eAAe,GAAG,QAAuB,CAAA;YAC/C,eAAe,CAAC,eAAe,EAAE,CAAA;YACjC,MAAM,YAAY,EAAE,CAAA;YACpB,MAAM,CAAC,eAAe,CAAC,aAAa,CAAC,eAAe,CAAC,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAA;YAChG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;QACzB,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;YACzE,MAAM,KAAK,GAAG,IAAI,KAAK,CAAmB;gBACxC,IAAI,EAAE,KAAK,IAAI,EAAE;oBACf,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAA;gBACnC,CAAC;aACF,CAAC,CAAA;YACF,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YACzB,CAAC;YAAC,MAAM,CAAC;gBACP,WAAW;YACb,CAAC;YACD,MAAM,WAAW,GAAG,EAAE,CAAC,EAAE,CACvB,CAAC,GAAY,EAAE,MAAkB,EAAE,EAAE,CACnC,CAAC,0BAAM,SAAS,EAAC,cAAc,IAAE,MAAM,CAAC,GAAG,CAAC,CAAQ,CAA2B,CAClF,CAAA;YACD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;YACpF,MAAM,CAAC,WAAW,CAAC,CAAC,oBAAoB,EAAE,CAAA;YAC1C,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;YAC1D,MAAM,aAAa,GAAG,SAAS,CAAC,aAAa,CAAC,eAAe,CAAC,CAAA;YAC9D,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;YACpC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;QACzB,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,kEAAkE,EAAE,KAAK,IAAI,EAAE;YAChF,MAAM,KAAK,GAAG,IAAI,KAAK,CAAmB,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,CAAA;YACvE,MAAM,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YACvB,KAAK,CAAC,gBAAgB,CAAC;gBACrB,QAAQ,EAAE,CAAC,MAAM,CAAC;gBAClB,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE;aAC7F,CAAC,CAAA;YACF,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;YAC5D,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;YAChE,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;YAC9D,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;QACzB,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;YAC9D,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAAmC,KAAK,IAAI,EAAE;gBAChE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAA;YACzB,CAAC,CAAC,CAAA;YACF,MAAM,KAAK,GAAG,IAAI,KAAK,CAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;YAC3D,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YACzB,CAAC;YAAC,MAAM,CAAC;gBACP,WAAW;YACb,CAAC;YACD,IAAI,aAAuC,CAAA;YAC3C,MAAM,WAAW,GAAG,CAAC,IAAa,EAAE,KAAiB,EAAE,EAAE;gBACvD,aAAa,GAAG,KAAK,CAAA;gBACrB,OAAO,CAAC,0BAAM,SAAS,EAAC,cAAc,YAAa,CAA2B,CAAA;YAChF,CAAC,CAAA;YACD,MAAM,eAAe,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;YAC9D,MAAM,CAAC,aAAa,CAAC,CAAC,WAAW,EAAE,CAAA;YAEnC,MAAM,CAAC,qBAAqB,CAAC,WAAW,CAAC,CAAA;YACzC,aAAc,EAAE,CAAA;YAChB,MAAM,UAAU,CAAC,EAAE,CAAC,CAAA;YAEpB,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;YAC9C,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAA;YACpC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACpC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YACtC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;QACzB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;QAC9B,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;YACtE,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,GAAW,EAAE,EAAE,CAAC,SAAS,GAAG,EAAE,CAAC,CAAA;YAC3D,MAAM,KAAK,GAAG,IAAI,KAAK,CAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;YAC3D,MAAM,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YACvB,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;YAEzB,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;YAC5D,MAAM,SAAS,GAAG,SAAS,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAA;YAC/D,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;YAEhC,MAAM,UAAU,CAAC,EAAE,CAAC,CAAA;YACpB,kEAAkE;YAClE,MAAM,CAAC,MAAM,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAA;YACvC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;QACzB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;QAC/C,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;YACrE,MAAM,KAAK,GAAG,IAAI,KAAK,CAAmB,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,CAAA;YACvE,MAAM,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YACvB,MAAM,eAAe,GAAwB;gBAC3C,MAAM,EAAE,QAAQ;gBAChB,KAAK,EAAE,IAAI,KAAK,CAAC,QAAQ,CAAC;gBAC1B,KAAK,EAAE,YAAY;gBACnB,SAAS,EAAE,IAAI,IAAI,EAAE;aACtB,CAAA;YACD,KAAK,CAAC,gBAAgB,CAAC,EAAE,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAA;YACtE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;YAC5D,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;YAChE,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;YAC9D,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;QACzB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}