@taicode/common-web 1.1.1 → 1.1.3

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 (37) hide show
  1. package/output/cache-api/cache-api.d.ts +19 -0
  2. package/output/cache-api/cache-api.d.ts.map +1 -0
  3. package/output/cache-api/cache-api.js +134 -0
  4. package/output/cache-api/cache-api.test.d.ts +2 -0
  5. package/output/cache-api/cache-api.test.d.ts.map +1 -0
  6. package/output/cache-api/cache-api.test.js +449 -0
  7. package/output/cache-api/index.d.ts +2 -0
  8. package/output/cache-api/index.d.ts.map +1 -0
  9. package/output/cache-api/index.js +1 -0
  10. package/output/service/index.d.ts +2 -0
  11. package/output/service/index.d.ts.map +1 -0
  12. package/output/service/index.js +1 -0
  13. package/output/service/service.d.ts +114 -0
  14. package/output/service/service.d.ts.map +1 -0
  15. package/output/service/service.js +189 -0
  16. package/output/service/service.test.d.ts +2 -0
  17. package/output/service/service.test.d.ts.map +1 -0
  18. package/output/service/service.test.jsx +367 -0
  19. package/output/side-cache/index.d.ts +2 -0
  20. package/output/side-cache/index.d.ts.map +1 -0
  21. package/output/side-cache/index.js +1 -0
  22. package/output/side-cache/side-cache.d.ts +11 -0
  23. package/output/side-cache/side-cache.d.ts.map +1 -0
  24. package/output/side-cache/side-cache.js +161 -0
  25. package/output/side-cache/side-cache.test.d.ts +2 -0
  26. package/output/side-cache/side-cache.test.d.ts.map +1 -0
  27. package/output/side-cache/side-cache.test.js +271 -0
  28. package/output/use-observer/index.d.ts +2 -0
  29. package/output/use-observer/index.d.ts.map +1 -0
  30. package/output/use-observer/index.js +1 -0
  31. package/output/use-observer/use-observer.d.ts +3 -0
  32. package/output/use-observer/use-observer.d.ts.map +1 -0
  33. package/output/use-observer/use-observer.js +16 -0
  34. package/output/use-observer/use-observer.test.d.ts +2 -0
  35. package/output/use-observer/use-observer.test.d.ts.map +1 -0
  36. package/output/use-observer/use-observer.test.jsx +134 -0
  37. package/package.json +1 -1
@@ -0,0 +1,271 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { runInAction } from 'mobx';
3
+ import { SideCache } from './side-cache';
4
+ describe('SideCache', () => {
5
+ let cache;
6
+ beforeEach(() => {
7
+ cache = new SideCache();
8
+ });
9
+ describe('初始状态', () => {
10
+ it('应该初始化为空状态', () => {
11
+ expect(cache.empty).toBe(true);
12
+ expect(cache.value).toBe(undefined);
13
+ });
14
+ });
15
+ describe('同步缓存', () => {
16
+ it('应该能够缓存同步函数的结果', () => {
17
+ const mockFunc = vi.fn(() => 'test-result');
18
+ const key = 'test-key';
19
+ const result = cache.handle(key, mockFunc);
20
+ expect(result).toBe('test-result');
21
+ expect(mockFunc).toHaveBeenCalledTimes(1);
22
+ expect(cache.empty).toBe(false);
23
+ expect(cache.value).toBe('test-result');
24
+ });
25
+ it('应该对相同 key 返回缓存的结果', () => {
26
+ const mockFunc = vi.fn(() => 'cached-result');
27
+ const key = 'same-key';
28
+ // 第一次调用
29
+ const result1 = cache.handle(key, mockFunc);
30
+ expect(result1).toBe('cached-result');
31
+ expect(mockFunc).toHaveBeenCalledTimes(1);
32
+ // 第二次调用相同 key,应该直接返回缓存
33
+ const result2 = cache.handle(key, mockFunc);
34
+ expect(result2).toBe('cached-result');
35
+ expect(mockFunc).toHaveBeenCalledTimes(2); // 函数仍会被调用,但会更新缓存
36
+ expect(cache.value).toBe('cached-result');
37
+ });
38
+ it('应该对不同 key 分别缓存', () => {
39
+ const mockFunc1 = vi.fn(() => 'result1');
40
+ const mockFunc2 = vi.fn(() => 'result2');
41
+ cache.handle('key1', mockFunc1);
42
+ expect(cache.value).toBe('result1');
43
+ cache.handle('key2', mockFunc2);
44
+ expect(cache.value).toBe('result2');
45
+ // 切换回 key1
46
+ cache.handle('key1', mockFunc1);
47
+ expect(cache.value).toBe('result1');
48
+ });
49
+ });
50
+ describe('异步缓存', () => {
51
+ it('应该能够缓存异步函数的结果', async () => {
52
+ const mockAsyncFunc = vi.fn(async () => 'async-result');
53
+ const key = 'async-key';
54
+ const promise = cache.handle(key, mockAsyncFunc);
55
+ expect(promise).toBeInstanceOf(Promise);
56
+ const result = await promise;
57
+ expect(result).toBe('async-result');
58
+ expect(mockAsyncFunc).toHaveBeenCalledTimes(1);
59
+ expect(cache.empty).toBe(false);
60
+ expect(cache.value).toBe('async-result');
61
+ });
62
+ it('应该在异步函数执行期间立即设置 currentKey', async () => {
63
+ const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
64
+ const mockAsyncFunc = vi.fn(async () => {
65
+ await delay(10);
66
+ return 'delayed-result';
67
+ });
68
+ const key = 'delayed-key';
69
+ // 在异步函数开始执行时,currentKey 应该已经设置
70
+ const promise = cache.handle(key, mockAsyncFunc);
71
+ // 此时异步函数还在执行,但 currentKey 已设置
72
+ // 如果之前有缓存,value 应该能立即获取到
73
+ const result = await promise;
74
+ expect(result).toBe('delayed-result');
75
+ expect(cache.value).toBe('delayed-result');
76
+ });
77
+ it('应该处理异步函数的错误', async () => {
78
+ const error = new Error('Async error');
79
+ const mockAsyncFunc = vi.fn(async () => {
80
+ throw error;
81
+ });
82
+ const key = 'error-key';
83
+ await expect(cache.handle(key, mockAsyncFunc)).rejects.toThrow('Async error');
84
+ expect(mockAsyncFunc).toHaveBeenCalledTimes(1);
85
+ });
86
+ });
87
+ describe('key 序列化', () => {
88
+ it('应该正确处理不同类型的 key', () => {
89
+ const mockFunc = vi.fn(() => 'result');
90
+ // 字符串 key
91
+ cache.handle('string-key', mockFunc);
92
+ expect(cache.value).toBe('result');
93
+ // 数字 key
94
+ cache.handle(123, mockFunc);
95
+ expect(cache.value).toBe('result');
96
+ // 对象 key
97
+ cache.handle({ id: 1, name: 'test' }, mockFunc);
98
+ expect(cache.value).toBe('result');
99
+ // 数组 key
100
+ cache.handle([1, 2, 3], mockFunc);
101
+ expect(cache.value).toBe('result');
102
+ });
103
+ it('相同内容的对象 key 应该被视为相同', () => {
104
+ const mockFunc = vi.fn(() => 'object-result');
105
+ const key1 = { id: 1, name: 'test' };
106
+ const key2 = { id: 1, name: 'test' };
107
+ cache.handle(key1, mockFunc);
108
+ const result1 = cache.value;
109
+ cache.handle(key2, mockFunc);
110
+ const result2 = cache.value;
111
+ expect(result1).toBe(result2);
112
+ expect(result1).toBe('object-result');
113
+ });
114
+ });
115
+ describe('缓存时间戳', () => {
116
+ it('应该为缓存项添加创建时间', () => {
117
+ const mockFunc = vi.fn(() => 'timestamped-result');
118
+ const key = 'timestamp-key';
119
+ const beforeTime = new Date().toISOString();
120
+ cache.handle(key, mockFunc);
121
+ const afterTime = new Date().toISOString();
122
+ // 访问私有属性进行测试(仅用于测试目的)
123
+ const cacheData = cache.cache;
124
+ const cachedItem = cacheData[JSON.stringify(key)];
125
+ expect(cachedItem).toBeDefined();
126
+ expect(cachedItem.data).toBe('timestamped-result');
127
+ expect(cachedItem.createTime).toBeDefined();
128
+ expect(typeof cachedItem.createTime).toBe('string');
129
+ // 验证时间戳在合理范围内
130
+ expect(cachedItem.createTime >= beforeTime).toBe(true);
131
+ expect(cachedItem.createTime <= afterTime).toBe(true);
132
+ });
133
+ });
134
+ describe('边界情况', () => {
135
+ it('应该处理 null 和 undefined key', () => {
136
+ const mockFunc1 = vi.fn(() => 'null-key-result');
137
+ const mockFunc2 = vi.fn(() => 'undefined-key-result');
138
+ cache.handle(null, mockFunc1);
139
+ expect(cache.value).toBe('null-key-result');
140
+ cache.handle(undefined, mockFunc2);
141
+ expect(cache.value).toBe('undefined-key-result');
142
+ });
143
+ it('应该处理返回 null 或 undefined 的函数', () => {
144
+ const nullFunc = vi.fn(() => null);
145
+ const undefinedFunc = vi.fn(() => undefined);
146
+ cache.handle('null-key', nullFunc);
147
+ expect(cache.value).toBe(null);
148
+ expect(cache.empty).toBe(false);
149
+ cache.handle('undefined-key', undefinedFunc);
150
+ expect(cache.value).toBe(undefined);
151
+ expect(cache.empty).toBe(false);
152
+ });
153
+ it('应该处理返回 Promise.resolve(null) 的异步函数', async () => {
154
+ const nullAsyncFunc = vi.fn(async () => null);
155
+ const result = await cache.handle('async-null', nullAsyncFunc);
156
+ expect(result).toBe(null);
157
+ expect(cache.value).toBe(null);
158
+ expect(cache.empty).toBe(false);
159
+ });
160
+ });
161
+ describe('类型检查', () => {
162
+ it('应该正确识别 Promise-like 对象', async () => {
163
+ let thenCallback;
164
+ const promiseLike = {
165
+ then: vi.fn((callback) => {
166
+ thenCallback = callback;
167
+ return promiseLike;
168
+ })
169
+ };
170
+ const mockFunc = vi.fn(() => promiseLike);
171
+ const result = cache.handle('promise-like', mockFunc);
172
+ expect(result).toBe(promiseLike);
173
+ expect(promiseLike.then).toHaveBeenCalled();
174
+ // 模拟 Promise 完成
175
+ if (thenCallback) {
176
+ thenCallback('resolved-value');
177
+ }
178
+ });
179
+ });
180
+ describe('clear 方法', () => {
181
+ it('应该能够清除指定 key 的缓存', () => {
182
+ const mockFunc = vi.fn(() => 'test-result');
183
+ const key = 'test-key';
184
+ // 先添加缓存
185
+ cache.handle(key, mockFunc);
186
+ expect(cache.empty).toBe(false);
187
+ expect(cache.value).toBe('test-result');
188
+ // 清除指定 key 的缓存
189
+ cache.clear(key);
190
+ expect(cache.empty).toBe(true);
191
+ expect(cache.value).toBe(undefined);
192
+ });
193
+ it('应该能够清除当前激活的缓存', () => {
194
+ const mockFunc = vi.fn(() => 'active-result');
195
+ const key = 'active-key';
196
+ cache.handle(key, mockFunc);
197
+ expect(cache.value).toBe('active-result');
198
+ // 清除当前激活的 key
199
+ cache.clear(key);
200
+ expect(cache.value).toBe(undefined);
201
+ expect(cache.empty).toBe(true);
202
+ });
203
+ it('应该能够清除所有缓存', () => {
204
+ const mockFunc1 = vi.fn(() => 'result1');
205
+ const mockFunc2 = vi.fn(() => 'result2');
206
+ // 添加多个缓存
207
+ cache.handle('key1', mockFunc1);
208
+ cache.handle('key2', mockFunc2);
209
+ expect(cache.value).toBe('result2');
210
+ expect(cache.empty).toBe(false);
211
+ // 清除所有缓存
212
+ cache.clear();
213
+ expect(cache.value).toBe(undefined);
214
+ expect(cache.empty).toBe(true);
215
+ });
216
+ it('应该能够清除非当前激活的缓存而不影响当前状态', () => {
217
+ const mockFunc1 = vi.fn(() => 'result1');
218
+ const mockFunc2 = vi.fn(() => 'result2');
219
+ // 添加两个缓存
220
+ cache.handle('key1', mockFunc1);
221
+ cache.handle('key2', mockFunc2);
222
+ expect(cache.value).toBe('result2'); // 当前激活的是 key2
223
+ // 清除非当前激活的 key1
224
+ cache.clear('key1');
225
+ expect(cache.value).toBe('result2'); // 当前值不变
226
+ expect(cache.empty).toBe(false); // 仍有缓存存在
227
+ });
228
+ it('应该正确处理清除不存在的 key', () => {
229
+ const mockFunc = vi.fn(() => 'test-result');
230
+ cache.handle('existing-key', mockFunc);
231
+ // 清除不存在的 key 不应该影响现有缓存
232
+ cache.clear('non-existing-key');
233
+ expect(cache.value).toBe('test-result');
234
+ expect(cache.empty).toBe(false);
235
+ });
236
+ it('应该正确处理复杂对象作为 key', () => {
237
+ const complexKey = { id: 1, data: { nested: 'value' } };
238
+ const mockFunc = vi.fn(() => 'complex-result');
239
+ // 使用复杂对象作为 key
240
+ cache.handle(complexKey, mockFunc);
241
+ expect(cache.value).toBe('complex-result');
242
+ // 清除复杂对象 key
243
+ cache.clear(complexKey);
244
+ expect(cache.value).toBe(undefined);
245
+ expect(cache.empty).toBe(true);
246
+ });
247
+ it('应该正确处理 undefined 作为 key', () => {
248
+ const mockFunc = vi.fn(() => 'undefined-key-result');
249
+ // 使用 undefined 作为 key
250
+ cache.handle(undefined, mockFunc);
251
+ expect(cache.value).toBe('undefined-key-result');
252
+ // 清除 undefined key
253
+ cache.clear(undefined);
254
+ expect(cache.value).toBe(undefined);
255
+ expect(cache.empty).toBe(true);
256
+ });
257
+ it('应该在 MobX action 中正确更新状态', () => {
258
+ const mockFunc = vi.fn(() => 'action-result');
259
+ const key = 'action-key';
260
+ runInAction(() => {
261
+ cache.handle(key, mockFunc);
262
+ });
263
+ expect(cache.value).toBe('action-result');
264
+ runInAction(() => {
265
+ cache.clear(key);
266
+ });
267
+ expect(cache.value).toBe(undefined);
268
+ expect(cache.empty).toBe(true);
269
+ });
270
+ });
271
+ });
@@ -0,0 +1,2 @@
1
+ export { useObserver } from './use-observer';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/use-observer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1 @@
1
+ export { useObserver } from './use-observer';
@@ -0,0 +1,3 @@
1
+ export declare function useObserver<T>(initialValue: T): T;
2
+ export declare function useObserver<T, U>(initialValue: T, selector: (v: T) => U): U;
3
+ //# sourceMappingURL=use-observer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-observer.d.ts","sourceRoot":"","sources":["../../source/use-observer/use-observer.ts"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC,CAAA;AAClD,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA"}
@@ -0,0 +1,16 @@
1
+ import { reaction } from 'mobx';
2
+ import React, { useState } from 'react';
3
+ export function useObserver(initialValue, selector) {
4
+ const [, refresh] = useState({});
5
+ React.useEffect(() => {
6
+ if (initialValue == null)
7
+ return;
8
+ if (selector == null)
9
+ return;
10
+ return reaction(() => selector(initialValue), () => refresh({}));
11
+ }, [selector, initialValue]);
12
+ if (selector == null) {
13
+ return initialValue;
14
+ }
15
+ return selector(initialValue);
16
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-observer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-observer.test.d.ts","sourceRoot":"","sources":["../../source/use-observer/use-observer.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,134 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { renderHook, act } from '@testing-library/react';
3
+ import { observable, action } from 'mobx';
4
+ import { useObserver } from './use-observer';
5
+ describe('useObserver', () => {
6
+ it('应该返回初始值当没有selector时', () => {
7
+ const initialValue = '初始值';
8
+ const { result } = renderHook(() => useObserver(initialValue));
9
+ expect(result.current).toBe(initialValue);
10
+ });
11
+ it('应该使用selector转换值', () => {
12
+ const initialValue = { count: 5 };
13
+ const selector = (value) => value.count * 2;
14
+ const { result } = renderHook(() => useObserver(initialValue, selector));
15
+ expect(result.current).toBe(10);
16
+ });
17
+ it('应该在观察对象变化时重新渲染', async () => {
18
+ const observableStore = observable({
19
+ count: 0,
20
+ increment: action(() => {
21
+ observableStore.count++;
22
+ })
23
+ });
24
+ const { result } = renderHook(() => useObserver(observableStore, (store) => store.count));
25
+ expect(result.current).toBe(0);
26
+ // 改变observable的值
27
+ act(() => {
28
+ observableStore.increment();
29
+ });
30
+ // 等待React更新
31
+ await new Promise(resolve => setTimeout(resolve, 0));
32
+ expect(result.current).toBe(1);
33
+ });
34
+ it('应该处理复杂的selector函数', () => {
35
+ const store = observable({
36
+ users: [
37
+ { id: 1, name: '张三', active: true },
38
+ { id: 2, name: '李四', active: false },
39
+ { id: 3, name: '王五', active: true }
40
+ ]
41
+ });
42
+ const selector = (store) => store.users.filter((user) => user.active).map((user) => user.name);
43
+ const { result } = renderHook(() => useObserver(store, selector));
44
+ expect(result.current).toEqual(['张三', '王五']);
45
+ });
46
+ it('应该在observable数组变化时重新渲染', async () => {
47
+ const store = observable({
48
+ items: ['项目1', '项目2'],
49
+ addItem: action((item) => {
50
+ store.items.push(item);
51
+ })
52
+ });
53
+ const { result } = renderHook(() => useObserver(store, (store) => store.items.length));
54
+ expect(result.current).toBe(2);
55
+ act(() => {
56
+ store.addItem('项目3');
57
+ });
58
+ await new Promise(resolve => setTimeout(resolve, 0));
59
+ expect(result.current).toBe(3);
60
+ });
61
+ it('应该处理null值的初始值', () => {
62
+ const { result } = renderHook(() => useObserver(null));
63
+ expect(result.current).toBeNull();
64
+ });
65
+ it('应该处理undefined值的初始值', () => {
66
+ const { result } = renderHook(() => useObserver(undefined));
67
+ expect(result.current).toBeUndefined();
68
+ });
69
+ it('应该在selector为null时返回原值', () => {
70
+ const initialValue = { data: '测试数据' };
71
+ const { result } = renderHook(() => useObserver(initialValue, null));
72
+ expect(result.current).toBe(initialValue);
73
+ });
74
+ it('应该处理嵌套observable对象', async () => {
75
+ const store = observable({
76
+ user: {
77
+ profile: {
78
+ name: '测试用户',
79
+ email: 'test@example.com'
80
+ }
81
+ },
82
+ updateName: action((name) => {
83
+ store.user.profile.name = name;
84
+ })
85
+ });
86
+ const { result } = renderHook(() => useObserver(store, (store) => store.user.profile.name));
87
+ expect(result.current).toBe('测试用户');
88
+ act(() => {
89
+ store.updateName('新用户名');
90
+ });
91
+ await new Promise(resolve => setTimeout(resolve, 0));
92
+ expect(result.current).toBe('新用户名');
93
+ });
94
+ it('应该处理多个observable属性的变化', async () => {
95
+ const store = observable({
96
+ firstName: '张',
97
+ lastName: '三',
98
+ updateFirstName: action((name) => {
99
+ store.firstName = name;
100
+ }),
101
+ updateLastName: action((name) => {
102
+ store.lastName = name;
103
+ })
104
+ });
105
+ const { result } = renderHook(() => useObserver(store, (store) => `${store.firstName}${store.lastName}`));
106
+ expect(result.current).toBe('张三');
107
+ act(() => {
108
+ store.updateFirstName('李');
109
+ });
110
+ await new Promise(resolve => setTimeout(resolve, 0));
111
+ expect(result.current).toBe('李三');
112
+ act(() => {
113
+ store.updateLastName('四');
114
+ });
115
+ await new Promise(resolve => setTimeout(resolve, 0));
116
+ expect(result.current).toBe('李四');
117
+ });
118
+ it('应该在组件卸载时清理reaction', () => {
119
+ const store = observable({ count: 0 });
120
+ const { unmount } = renderHook(() => useObserver(store, (store) => store.count));
121
+ // 卸载组件应该不会抛出错误
122
+ expect(() => unmount()).not.toThrow();
123
+ });
124
+ it('应该处理boolean类型的返回值', () => {
125
+ const store = observable({ isActive: true });
126
+ const { result } = renderHook(() => useObserver(store, (store) => store.isActive));
127
+ expect(result.current).toBe(true);
128
+ });
129
+ it('应该处理数字类型的返回值', () => {
130
+ const store = observable({ count: 42 });
131
+ const { result } = renderHook(() => useObserver(store, (store) => store.count));
132
+ expect(result.current).toBe(42);
133
+ });
134
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taicode/common-web",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "author": "Alain",
5
5
  "license": "ISC",
6
6
  "description": "",