@cloudcome/utils-vue 1.1.0 → 1.3.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.
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/package.json +84 -1
- package/src/async.ts +153 -0
- package/src/component.ts +60 -0
- package/src/dts/global.d.ts +27 -0
- package/src/event.ts +51 -0
- package/src/index.ts +1 -0
- package/src/page.ts +29 -0
- package/src/request.ts +200 -0
- package/test/async.test.ts +104 -0
- package/test/component.test.ts +61 -0
- package/test/event.test.ts +109 -0
- package/test/index.test.ts +6 -0
- package/test/page.test.ts +63 -0
- package/test/request.test.ts +123 -0
- package/tsconfig.json +31 -0
- package/vite.config.mts +95 -0
- package/dist/async.cjs +0 -57
- package/dist/async.cjs.map +0 -1
- package/dist/async.d.ts +0 -63
- package/dist/async.mjs +0 -57
- package/dist/async.mjs.map +0 -1
- package/dist/component.cjs +0 -12
- package/dist/component.cjs.map +0 -1
- package/dist/component.mjs +0 -12
- package/dist/component.mjs.map +0 -1
- package/dist/event.cjs +0 -36
- package/dist/event.cjs.map +0 -1
- package/dist/event.d.ts +0 -16
- package/dist/event.mjs +0 -36
- package/dist/event.mjs.map +0 -1
- package/dist/index.cjs +0 -5
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.mjs +0 -5
- package/dist/index.mjs.map +0 -1
- package/dist/page.cjs +0 -24
- package/dist/page.cjs.map +0 -1
- package/dist/page.d.ts +0 -5
- package/dist/page.mjs +0 -24
- package/dist/page.mjs.map +0 -1
- package/dist/request.cjs +0 -59
- package/dist/request.cjs.map +0 -1
- package/dist/request.d.ts +0 -101
- package/dist/request.mjs +0 -59
- package/dist/request.mjs.map +0 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { promiseDelay } from '@cloudcome/utils-core/promise';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { ref } from 'vue';
|
|
4
|
+
import { useAsync } from '../src/async';
|
|
5
|
+
|
|
6
|
+
describe('useAsync 组合式函数', () => {
|
|
7
|
+
const mockAsyncFn = vi.fn();
|
|
8
|
+
const mockOptions = {
|
|
9
|
+
onBefore: vi.fn(),
|
|
10
|
+
onSuccess: vi.fn(),
|
|
11
|
+
onError: vi.fn(),
|
|
12
|
+
onAfter: vi.fn(),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.resetAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('应该正确处理异步操作', async () => {
|
|
20
|
+
const mockData = { id: 1 };
|
|
21
|
+
mockAsyncFn.mockResolvedValue(mockData);
|
|
22
|
+
const { loading, data, error, runAsync } = useAsync(mockAsyncFn, mockOptions);
|
|
23
|
+
|
|
24
|
+
const promise = runAsync('test');
|
|
25
|
+
expect(loading.value).toBe(true);
|
|
26
|
+
expect(mockOptions.onBefore).toHaveBeenCalled();
|
|
27
|
+
|
|
28
|
+
await promise;
|
|
29
|
+
expect(loading.value).toBe(false);
|
|
30
|
+
expect(data.value).toEqual(mockData);
|
|
31
|
+
expect(error.value).toBeNull();
|
|
32
|
+
expect(mockOptions.onSuccess).toHaveBeenCalledWith(mockData, 'test');
|
|
33
|
+
expect(mockOptions.onAfter).toHaveBeenCalled();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('应该处理异步操作失败', async () => {
|
|
37
|
+
const mockError = new Error('test error');
|
|
38
|
+
mockAsyncFn.mockRejectedValue(mockError);
|
|
39
|
+
const { loading, error, runAsync } = useAsync(mockAsyncFn, mockOptions);
|
|
40
|
+
|
|
41
|
+
await expect(runAsync('test1', 'test2')).rejects.toThrow(mockError);
|
|
42
|
+
expect(loading.value).toBe(false);
|
|
43
|
+
expect(error.value).toEqual(mockError);
|
|
44
|
+
expect(mockOptions.onError).toHaveBeenCalledWith(mockError, 'test1', 'test2');
|
|
45
|
+
expect(mockOptions.onAfter).toHaveBeenCalled();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('应该支持 run 方法', async () => {
|
|
49
|
+
const mockData = { id: 1 };
|
|
50
|
+
mockAsyncFn.mockResolvedValue(mockData);
|
|
51
|
+
const { run } = useAsync(mockAsyncFn);
|
|
52
|
+
|
|
53
|
+
run('test');
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
55
|
+
|
|
56
|
+
expect(mockAsyncFn).toHaveBeenCalledWith('test');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('应该正确处理run方法的返回值', async () => {
|
|
60
|
+
const mockData = { id: 1 };
|
|
61
|
+
mockAsyncFn.mockResolvedValue(mockData);
|
|
62
|
+
const { run } = useAsync(mockAsyncFn);
|
|
63
|
+
|
|
64
|
+
const result = await new Promise((resolve) => {
|
|
65
|
+
run('test');
|
|
66
|
+
setTimeout(() => resolve(mockAsyncFn.mock.results[0].value), 0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(result).toEqual(mockData);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('应该正确处理空options的情况', async () => {
|
|
73
|
+
const mockData = { id: 1 };
|
|
74
|
+
mockAsyncFn.mockResolvedValue(mockData);
|
|
75
|
+
const { loading, data, runAsync } = useAsync(mockAsyncFn);
|
|
76
|
+
|
|
77
|
+
await runAsync('test');
|
|
78
|
+
expect(loading.value).toBe(false);
|
|
79
|
+
expect(data.value).toEqual(mockData);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('应该正确处理参数传递', async () => {
|
|
83
|
+
const mockData = { id: 1 };
|
|
84
|
+
mockAsyncFn.mockResolvedValue(mockData);
|
|
85
|
+
const { runAsync } = useAsync(mockAsyncFn);
|
|
86
|
+
|
|
87
|
+
const args = ['arg1', 2, { key: 'value' }];
|
|
88
|
+
await runAsync(...args);
|
|
89
|
+
expect(mockAsyncFn).toHaveBeenCalledWith(...args);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('应该正确处理多次调用', async () => {
|
|
93
|
+
const mockData1 = { id: 1 };
|
|
94
|
+
const mockData2 = { id: 2 };
|
|
95
|
+
mockAsyncFn.mockResolvedValueOnce(mockData1).mockResolvedValueOnce(mockData2);
|
|
96
|
+
const { data, runAsync } = useAsync(mockAsyncFn);
|
|
97
|
+
|
|
98
|
+
await runAsync('first');
|
|
99
|
+
expect(data.value).toEqual(mockData1);
|
|
100
|
+
|
|
101
|
+
await runAsync('second');
|
|
102
|
+
expect(data.value).toEqual(mockData2);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ref } from 'vue';
|
|
3
|
+
import { useEmit, useExpose } from '../src/component';
|
|
4
|
+
|
|
5
|
+
describe('组件工具函数', () => {
|
|
6
|
+
describe('useExpose', () => {
|
|
7
|
+
it('应该创建一个可响应式访问的组件实例引用', () => {
|
|
8
|
+
const TestComponent = {
|
|
9
|
+
setup() {
|
|
10
|
+
defineExpose({
|
|
11
|
+
testMethod() {
|
|
12
|
+
return 'test';
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const compRef = useExpose(TestComponent);
|
|
19
|
+
expect(compRef.value).toBeNull();
|
|
20
|
+
|
|
21
|
+
compRef.value = { testMethod: () => 'mocked' };
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
expect(compRef.value?.testMethod()).toBe('mocked');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('useEmit', () => {
|
|
28
|
+
it('应该正确返回事件监听函数', () => {
|
|
29
|
+
const TestComponent = {
|
|
30
|
+
props: {
|
|
31
|
+
onClick: Function,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const mockListener = vi.fn();
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
const result = useEmit(TestComponent, 'click', mockListener);
|
|
38
|
+
|
|
39
|
+
expect(result).toBe(mockListener);
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
result();
|
|
42
|
+
expect(mockListener).toHaveBeenCalledTimes(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('应该正确处理带参数的emit事件', () => {
|
|
46
|
+
const TestComponent = {
|
|
47
|
+
props: {
|
|
48
|
+
onChange: Function,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const mockListener = vi.fn();
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
const result = useEmit(TestComponent, 'change', mockListener);
|
|
55
|
+
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
result(1, 'test');
|
|
58
|
+
expect(mockListener).toHaveBeenCalledWith(1, 'test');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { createEventHook } from '../src/event';
|
|
4
|
+
|
|
5
|
+
describe('createEventCenter 事件中心', () => {
|
|
6
|
+
// 定义测试事件类型
|
|
7
|
+
type TestEvents = {
|
|
8
|
+
'test-event': [string, number];
|
|
9
|
+
'another-event': [boolean];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
it('应该正确创建事件中心实例', () => {
|
|
13
|
+
const eventCenter = createEventHook<TestEvents>();
|
|
14
|
+
|
|
15
|
+
expect(eventCenter).toBeDefined();
|
|
16
|
+
expect(typeof eventCenter.on).toBe('function');
|
|
17
|
+
expect(typeof eventCenter.off).toBe('function');
|
|
18
|
+
expect(typeof eventCenter.emit).toBe('function');
|
|
19
|
+
expect(typeof eventCenter.useEvent).toBe('function');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('应该正确注册和触发事件', () => {
|
|
23
|
+
const eventCenter = createEventHook<TestEvents>();
|
|
24
|
+
const mockListener = vi.fn();
|
|
25
|
+
|
|
26
|
+
eventCenter.on('test-event', mockListener);
|
|
27
|
+
eventCenter.emit('test-event', 'hello', 123);
|
|
28
|
+
|
|
29
|
+
expect(mockListener).toHaveBeenCalledTimes(1);
|
|
30
|
+
expect(mockListener).toHaveBeenCalledWith('hello', 123);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('应该正确取消事件监听', () => {
|
|
34
|
+
const eventCenter = createEventHook<TestEvents>();
|
|
35
|
+
const mockListener = vi.fn();
|
|
36
|
+
|
|
37
|
+
eventCenter.on('test-event', mockListener);
|
|
38
|
+
eventCenter.off('test-event', mockListener);
|
|
39
|
+
eventCenter.emit('test-event', 'hello', 123);
|
|
40
|
+
|
|
41
|
+
expect(mockListener).not.toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('应该正确处理 useEventCenter 在 mount 阶段', async () => {
|
|
45
|
+
const eventCenter = createEventHook<TestEvents>({ stage: 'mount' });
|
|
46
|
+
const mockListener = vi.fn();
|
|
47
|
+
const wrapper = mount({
|
|
48
|
+
template: '<div>test</div>',
|
|
49
|
+
setup() {
|
|
50
|
+
eventCenter.useEvent('another-event', mockListener);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await wrapper.vm.$nextTick();
|
|
55
|
+
|
|
56
|
+
eventCenter.emit('another-event', true);
|
|
57
|
+
|
|
58
|
+
expect(mockListener).toHaveBeenCalledTimes(1);
|
|
59
|
+
expect(mockListener).toHaveBeenCalledWith(true);
|
|
60
|
+
|
|
61
|
+
wrapper.unmount();
|
|
62
|
+
|
|
63
|
+
eventCenter.emit('another-event', false);
|
|
64
|
+
expect(mockListener).toHaveBeenCalledTimes(1);
|
|
65
|
+
expect(mockListener).toHaveBeenCalledWith(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('应该正确处理 useEventCenter 在 mounted 阶段', async () => {
|
|
69
|
+
const eventCenter = createEventHook<TestEvents>({ stage: 'mounted' });
|
|
70
|
+
const mockListener = vi.fn();
|
|
71
|
+
const wrapper = mount({
|
|
72
|
+
template: '<div>test</div>',
|
|
73
|
+
setup() {
|
|
74
|
+
eventCenter.useEvent('another-event', mockListener);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await wrapper.vm.$nextTick();
|
|
79
|
+
|
|
80
|
+
eventCenter.emit('another-event', true);
|
|
81
|
+
|
|
82
|
+
expect(mockListener).toHaveBeenCalledTimes(1);
|
|
83
|
+
expect(mockListener).toHaveBeenCalledWith(true);
|
|
84
|
+
|
|
85
|
+
wrapper.unmount();
|
|
86
|
+
|
|
87
|
+
eventCenter.emit('another-event', false);
|
|
88
|
+
expect(mockListener).toHaveBeenCalledTimes(1);
|
|
89
|
+
expect(mockListener).toHaveBeenCalledWith(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('应该支持自定义事件发射器', () => {
|
|
93
|
+
const customEmitter = {
|
|
94
|
+
on: vi.fn(),
|
|
95
|
+
off: vi.fn(),
|
|
96
|
+
emit: vi.fn(),
|
|
97
|
+
};
|
|
98
|
+
const eventCenter = createEventHook<TestEvents>({ emitter: customEmitter });
|
|
99
|
+
|
|
100
|
+
const mockListener = vi.fn();
|
|
101
|
+
eventCenter.on('test-event', mockListener);
|
|
102
|
+
eventCenter.emit('test-event', 'hello', 123);
|
|
103
|
+
eventCenter.off('test-event', mockListener);
|
|
104
|
+
|
|
105
|
+
expect(customEmitter.on).toHaveBeenCalled();
|
|
106
|
+
expect(customEmitter.emit).toHaveBeenCalled();
|
|
107
|
+
expect(customEmitter.off).toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { onBeforeMount, onBeforeUnmount, onMounted } from 'vue';
|
|
4
|
+
import { usePageMount, usePageMounted } from '../src/page';
|
|
5
|
+
|
|
6
|
+
describe('hook-page', () => {
|
|
7
|
+
it('应该正确触发 usePageMount 生命周期', async () => {
|
|
8
|
+
const fn = vi.fn();
|
|
9
|
+
const cleanup = vi.fn();
|
|
10
|
+
const beforeMountFn = vi.fn(() => cleanup);
|
|
11
|
+
const wrapper = mount({
|
|
12
|
+
template: '<div>test</div>',
|
|
13
|
+
setup() {
|
|
14
|
+
onBeforeMount(fn);
|
|
15
|
+
onBeforeUnmount(fn);
|
|
16
|
+
usePageMount(beforeMountFn);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
await wrapper.vm.$nextTick();
|
|
21
|
+
expect(beforeMountFn).toHaveBeenCalledTimes(1);
|
|
22
|
+
wrapper.unmount();
|
|
23
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
24
|
+
expect(cleanup).toHaveBeenCalledTimes(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('应该正确触发 usePageMounted 生命周期', async () => {
|
|
28
|
+
const fn = vi.fn();
|
|
29
|
+
const cleanup = vi.fn();
|
|
30
|
+
const mountedFn = vi.fn(() => cleanup);
|
|
31
|
+
const wrapper = mount({
|
|
32
|
+
template: '<div>test</div>',
|
|
33
|
+
setup() {
|
|
34
|
+
onMounted(fn);
|
|
35
|
+
onBeforeUnmount(fn);
|
|
36
|
+
usePageMounted(mountedFn);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await wrapper.vm.$nextTick();
|
|
41
|
+
expect(mountedFn).toHaveBeenCalledTimes(1);
|
|
42
|
+
wrapper.unmount();
|
|
43
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
44
|
+
expect(cleanup).toHaveBeenCalledTimes(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('应该支持异步回调函数', async () => {
|
|
48
|
+
const cleanup = vi.fn();
|
|
49
|
+
const asyncFn = vi.fn(async () => cleanup);
|
|
50
|
+
|
|
51
|
+
const wrapper = mount({
|
|
52
|
+
template: '<div>test</div>',
|
|
53
|
+
setup() {
|
|
54
|
+
usePageMount(asyncFn);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await wrapper.vm.$nextTick();
|
|
59
|
+
expect(asyncFn).toHaveBeenCalledTimes(1);
|
|
60
|
+
wrapper.unmount();
|
|
61
|
+
expect(cleanup).toHaveBeenCalledTimes(1);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useRequest } from '@/request';
|
|
2
|
+
import { MemoryCache } from '@cloudcome/utils-core/cache';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
describe('useRequest 组合式函数', () => {
|
|
6
|
+
const mockRequestFn = vi.fn();
|
|
7
|
+
const mockOptions = {
|
|
8
|
+
onSuccess: vi.fn(),
|
|
9
|
+
onCacheHit: vi.fn(),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.resetAllMocks();
|
|
14
|
+
(new MemoryCache() as MemoryCache<unknown>).clear();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('应该正确处理基本请求', async () => {
|
|
18
|
+
const mockData = { id: 1 };
|
|
19
|
+
mockRequestFn.mockResolvedValue(mockData);
|
|
20
|
+
const { loading, data, error, sendAsync } = useRequest(mockRequestFn, mockOptions);
|
|
21
|
+
|
|
22
|
+
const promise = sendAsync('test');
|
|
23
|
+
expect(loading.value).toBe(true);
|
|
24
|
+
|
|
25
|
+
await promise;
|
|
26
|
+
expect(loading.value).toBe(false);
|
|
27
|
+
expect(data.value).toEqual(mockData);
|
|
28
|
+
expect(error.value).toBeNull();
|
|
29
|
+
expect(mockOptions.onSuccess).toHaveBeenCalledWith(mockData, 'test');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('应该处理请求失败', async () => {
|
|
33
|
+
const mockError = new Error('test error');
|
|
34
|
+
mockRequestFn.mockRejectedValue(mockError);
|
|
35
|
+
const { loading, error, sendAsync } = useRequest(mockRequestFn);
|
|
36
|
+
|
|
37
|
+
await expect(sendAsync('test')).rejects.toThrow(mockError);
|
|
38
|
+
expect(loading.value).toBe(false);
|
|
39
|
+
expect(error.value).toEqual(mockError);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('应该支持缓存功能', async () => {
|
|
43
|
+
const mockData = { id: 1 };
|
|
44
|
+
mockRequestFn.mockResolvedValue(mockData);
|
|
45
|
+
const { hitCache, sendAsync } = useRequest(mockRequestFn, {
|
|
46
|
+
id: 'test-cache',
|
|
47
|
+
cache: true,
|
|
48
|
+
onCacheHit: mockOptions.onCacheHit,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await sendAsync('test');
|
|
52
|
+
expect(hitCache.value).toBe(false);
|
|
53
|
+
expect(mockOptions.onCacheHit).not.toHaveBeenCalled();
|
|
54
|
+
|
|
55
|
+
await sendAsync('test');
|
|
56
|
+
expect(hitCache.value).toBe(true);
|
|
57
|
+
expect(mockOptions.onCacheHit).toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('应该支持共享请求功能', async () => {
|
|
61
|
+
const mockData = { id: 1 };
|
|
62
|
+
const fn = vi.fn().mockImplementation(async () => mockData);
|
|
63
|
+
|
|
64
|
+
const id = 'test-share';
|
|
65
|
+
const {
|
|
66
|
+
hitShare: hs1,
|
|
67
|
+
sendAsync: sendAsync1,
|
|
68
|
+
data: data1,
|
|
69
|
+
} = useRequest(fn, {
|
|
70
|
+
id,
|
|
71
|
+
share: true,
|
|
72
|
+
});
|
|
73
|
+
const {
|
|
74
|
+
hitShare: hs2,
|
|
75
|
+
sendAsync: sendAsync2,
|
|
76
|
+
data: data2,
|
|
77
|
+
} = useRequest(fn, {
|
|
78
|
+
id,
|
|
79
|
+
share: true,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await sendAsync1(1);
|
|
83
|
+
await sendAsync2(1);
|
|
84
|
+
|
|
85
|
+
expect(hs1.value).toBe(false);
|
|
86
|
+
expect(hs2.value).toBe(true);
|
|
87
|
+
|
|
88
|
+
expect(data1.value).toEqual(mockData);
|
|
89
|
+
expect(data2.value).toEqual(mockData);
|
|
90
|
+
|
|
91
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('应该支持禁用缓存', async () => {
|
|
95
|
+
const mockData = { id: 1 };
|
|
96
|
+
mockRequestFn.mockResolvedValue(mockData);
|
|
97
|
+
const { hitCache, sendAsync } = useRequest(mockRequestFn, {
|
|
98
|
+
id: 'test-cache',
|
|
99
|
+
cache: { disabled: true },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await sendAsync('test');
|
|
103
|
+
await sendAsync('test');
|
|
104
|
+
expect(hitCache.value).toBe(false);
|
|
105
|
+
expect(mockRequestFn).toHaveBeenCalledTimes(2);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('应该支持禁用共享请求', async () => {
|
|
109
|
+
const mockData = { id: 1 };
|
|
110
|
+
mockRequestFn.mockResolvedValue(mockData);
|
|
111
|
+
const { hitShare, sendAsync } = useRequest(mockRequestFn, {
|
|
112
|
+
id: 'test-share',
|
|
113
|
+
share: { disabled: true },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const promise1 = sendAsync('test');
|
|
117
|
+
const promise2 = sendAsync('test');
|
|
118
|
+
await Promise.all([promise1, promise2]);
|
|
119
|
+
|
|
120
|
+
expect(hitShare.value).toBe(false);
|
|
121
|
+
expect(mockRequestFn).toHaveBeenCalledTimes(2);
|
|
122
|
+
});
|
|
123
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"jsx": "preserve",
|
|
4
|
+
"jsxImportSource": "vue",
|
|
5
|
+
"target": "ES2024",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"allowJs": true,
|
|
12
|
+
"sourceMap": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"verbatimModuleSyntax": true,
|
|
17
|
+
"allowImportingTsExtensions": false,
|
|
18
|
+
"allowSyntheticDefaultImports": true,
|
|
19
|
+
"forceConsistentCasingInFileNames": true,
|
|
20
|
+
|
|
21
|
+
"lib": ["ES2024"],
|
|
22
|
+
"types": ["node", "vite/client", "vitest/globals"],
|
|
23
|
+
"plugins": [],
|
|
24
|
+
"paths": {
|
|
25
|
+
"@/*": ["./src/*"]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
"include": ["**/*.ts", "**/.*.ts", "**/*.mts", "**/*.tsx", "**/*.vue"],
|
|
30
|
+
"exclude": []
|
|
31
|
+
}
|
package/vite.config.mts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file vite.config.mts
|
|
3
|
+
* @ref https://vitejs.dev/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import dts from 'vite-plugin-dts';
|
|
7
|
+
import { externalizeDeps } from 'vite-plugin-externalize-deps';
|
|
8
|
+
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
9
|
+
import { defineConfig } from 'vitest/config';
|
|
10
|
+
import pkg from './package.json';
|
|
11
|
+
|
|
12
|
+
export default defineConfig((env) => {
|
|
13
|
+
const isProd = env.mode === 'production';
|
|
14
|
+
const isTest = env.mode === 'test';
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
base: '/',
|
|
18
|
+
server: {
|
|
19
|
+
port: 15170,
|
|
20
|
+
},
|
|
21
|
+
preview: {
|
|
22
|
+
port: 15171,
|
|
23
|
+
},
|
|
24
|
+
define: {
|
|
25
|
+
PKG_NAME: JSON.stringify(isTest ? 'pkg-name-for-test' : pkg.name),
|
|
26
|
+
PKG_VERSION: JSON.stringify(isTest ? 'pkg-version-for-test' : pkg.version),
|
|
27
|
+
PKG_DESCRIPTION: JSON.stringify(isTest ? 'pkg-description-for-test' : pkg.description),
|
|
28
|
+
IS_TEST: JSON.stringify(isTest),
|
|
29
|
+
},
|
|
30
|
+
build: {
|
|
31
|
+
minify: false,
|
|
32
|
+
sourcemap: true,
|
|
33
|
+
copyPublicDir: false,
|
|
34
|
+
reportCompressedSize: false,
|
|
35
|
+
lib: {
|
|
36
|
+
entry:
|
|
37
|
+
// expose-start
|
|
38
|
+
{
|
|
39
|
+
index: 'src/index.ts',
|
|
40
|
+
async: './src/async.ts',
|
|
41
|
+
component: './src/component.ts',
|
|
42
|
+
event: './src/event.ts',
|
|
43
|
+
page: './src/page.ts',
|
|
44
|
+
request: './src/request.ts',
|
|
45
|
+
},
|
|
46
|
+
// expose-end
|
|
47
|
+
},
|
|
48
|
+
rollupOptions: {
|
|
49
|
+
output: [
|
|
50
|
+
{
|
|
51
|
+
format: 'esm',
|
|
52
|
+
entryFileNames: '[name].mjs',
|
|
53
|
+
chunkFileNames: '[name].mjs',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
format: 'cjs',
|
|
57
|
+
entryFileNames: '[name].cjs',
|
|
58
|
+
chunkFileNames: '[name].cjs',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
test: {
|
|
64
|
+
globals: true,
|
|
65
|
+
environment: 'jsdom',
|
|
66
|
+
environmentOptions: {
|
|
67
|
+
jsdom: {
|
|
68
|
+
// 加载外部资源
|
|
69
|
+
resources: 'usable',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
coverage: {
|
|
73
|
+
all: true,
|
|
74
|
+
include: ['src/**/*.ts'],
|
|
75
|
+
reporter: ['lcov', 'text'],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
// esbuild: {
|
|
79
|
+
// drop: isProd ? ['console', 'debugger'] : [],
|
|
80
|
+
// },
|
|
81
|
+
plugins: [
|
|
82
|
+
tsconfigPaths(),
|
|
83
|
+
externalizeDeps({
|
|
84
|
+
deps: true,
|
|
85
|
+
devDeps: true,
|
|
86
|
+
peerDeps: true,
|
|
87
|
+
optionalDeps: true,
|
|
88
|
+
nodeBuiltins: true,
|
|
89
|
+
}),
|
|
90
|
+
dts({
|
|
91
|
+
include: 'src',
|
|
92
|
+
}),
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
});
|
package/dist/async.cjs
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
require("@cloudcome/utils-core/type");
|
|
4
|
-
const vue = require("vue");
|
|
5
|
-
function useAsync(fn, options) {
|
|
6
|
-
const loading = vue.ref(false);
|
|
7
|
-
const data = vue.ref(null);
|
|
8
|
-
const error = vue.ref(null);
|
|
9
|
-
const runAsync = async (...inputs) => {
|
|
10
|
-
var _a, _b, _c, _d;
|
|
11
|
-
loading.value = true;
|
|
12
|
-
error.value = null;
|
|
13
|
-
try {
|
|
14
|
-
(_a = options == null ? void 0 : options.onBefore) == null ? void 0 : _a.call(options, ...inputs);
|
|
15
|
-
data.value = await fn(...inputs);
|
|
16
|
-
(_b = options == null ? void 0 : options.onSuccess) == null ? void 0 : _b.call(options, data.value);
|
|
17
|
-
return data.value;
|
|
18
|
-
} catch (err) {
|
|
19
|
-
error.value = err;
|
|
20
|
-
(_c = options == null ? void 0 : options.onError) == null ? void 0 : _c.call(options, err);
|
|
21
|
-
throw err;
|
|
22
|
-
} finally {
|
|
23
|
-
loading.value = false;
|
|
24
|
-
(_d = options == null ? void 0 : options.onAfter) == null ? void 0 : _d.call(options, ...inputs);
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
const run = (...inputs) => {
|
|
28
|
-
runAsync(...inputs).then();
|
|
29
|
-
};
|
|
30
|
-
return {
|
|
31
|
-
/**
|
|
32
|
-
* 是否正在加载。
|
|
33
|
-
*/
|
|
34
|
-
loading,
|
|
35
|
-
/**
|
|
36
|
-
* 异步操作返回的数据。
|
|
37
|
-
*/
|
|
38
|
-
data,
|
|
39
|
-
/**
|
|
40
|
-
* 异步操作抛出的错误。
|
|
41
|
-
*/
|
|
42
|
-
error,
|
|
43
|
-
/**
|
|
44
|
-
* 执行异步操作并返回 Promise。
|
|
45
|
-
* @param inputs 异步函数的参数。
|
|
46
|
-
* @returns 异步操作的结果。
|
|
47
|
-
*/
|
|
48
|
-
runAsync,
|
|
49
|
-
/**
|
|
50
|
-
* 执行异步操作但不返回 Promise。
|
|
51
|
-
* @param inputs 异步函数的参数。
|
|
52
|
-
*/
|
|
53
|
-
run
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
exports.useAsync = useAsync;
|
|
57
|
-
//# sourceMappingURL=async.cjs.map
|
package/dist/async.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"async.cjs","sources":["../src/async.ts"],"sourcesContent":["import { isFunction, isNullish } from '@cloudcome/utils-core/type';\nimport type { AnyArray, MaybeCallable } from '@cloudcome/utils-core/types';\nimport { type Ref, nextTick } from 'vue';\nimport { onMounted, ref } from 'vue';\n\n/**\n * 异步操作的配置选项\n * @template T 异步操作返回的数据类型\n * @template P 异步操作的参数类型\n */\nexport type TUseAsyncOptions<I extends AnyArray, O> = {\n /**\n * 异步操作开始前的回调函数。\n * 可用于执行初始化逻辑或显示加载状态。\n */\n onBefore?: (...inputs: I) => unknown;\n\n /**\n * 异步操作成功后的回调函数。\n * @param data 异步操作返回的数据。\n * 可用于处理成功后的数据更新或通知。\n */\n onSuccess?: (data: O) => unknown;\n\n /**\n * 异步操作失败后的回调函数。\n * @param err 异步操作抛出的错误。\n * 可用于记录错误日志或显示错误提示。\n */\n onError?: (err: unknown) => unknown;\n\n /**\n * 异步操作结束后的回调函数(无论成功或失败)。\n * 可用于清理操作或触发后续逻辑。\n */\n onAfter?: (...inputs: I) => unknown;\n};\n\nexport type TUseAsyncReturns<I extends AnyArray, O> = {\n loading: Ref<boolean>;\n data: Ref<O | null>;\n error: Ref<unknown>;\n run: (...inputs: I) => void;\n runAsync: (...inputs: I) => Promise<O>;\n};\n\n/**\n * 用于处理异步操作的组合式函数。\n * 提供加载状态、数据、错误信息以及执行方法。\n * @template O 异步函数返回的数据类型\n * @template I 异步函数的入参类型\n * @param fn 异步函数,接收参数并返回 Promise。\n * @param options 异步操作的配置选项。\n * @returns 包含状态和操作方法的对象:\n * - loading: 是否正在加载。\n * - data: 异步操作返回的数据。\n * - error: 异步操作抛出的错误。\n * - runAsync: 执行异步操作并返回 Promise。\n * - run: 执行异步操作但不返回 Promise。\n * @example\n * const { loading, data, error, runAsync, run } = useAsync(async (id: number) => {\n * const response = await fetch(`/api/user/${id}`);\n * return response.json();\n * }, {\n * onBefore: () => console.log('Fetching user data...'),\n * onSuccess: (data) => console.log('User data fetched:', data),\n * onError: (err) => console.error('Failed to fetch user data:', err),\n * onFinally: () => console.log('Fetch operation completed.'),\n * });\n */\nexport function useAsync<I extends AnyArray, O>(\n fn: (...inputs: I) => Promise<O>,\n options?: TUseAsyncOptions<I, O>,\n): TUseAsyncReturns<I, O> {\n const loading = ref(false);\n const data = ref<O | null>(null) as Ref<O | null>;\n const error = ref<unknown>(null);\n\n const runAsync = async (...inputs: I): Promise<O> => {\n loading.value = true;\n error.value = null;\n\n try {\n options?.onBefore?.(...inputs);\n data.value = await fn(...inputs);\n options?.onSuccess?.(data.value);\n return data.value;\n } catch (err) {\n error.value = err;\n options?.onError?.(err);\n throw err;\n } finally {\n loading.value = false;\n options?.onAfter?.(...inputs);\n }\n };\n\n const run = (...inputs: I) => {\n runAsync(...inputs).then();\n };\n\n return {\n /**\n * 是否正在加载。\n */\n loading,\n\n /**\n * 异步操作返回的数据。\n */\n data,\n\n /**\n * 异步操作抛出的错误。\n */\n error,\n\n /**\n * 执行异步操作并返回 Promise。\n * @param inputs 异步函数的参数。\n * @returns 异步操作的结果。\n */\n runAsync,\n\n /**\n * 执行异步操作但不返回 Promise。\n * @param inputs 异步函数的参数。\n */\n run,\n };\n}\n\n// const { run: run1 } = useAsync(() => Promise.resolve(1));\n// run1();\n\n// const { run: run2 } = useAsync((a: number) => Promise.resolve(1));\n// run2(2);\n\n// const { run: run3 } = useAsync((a: number, b: string) => Promise.resolve(1));\n// run3(2, '2');\n"],"names":["ref"],"mappings":";;;;AAsEgB,SAAA,SACd,IACA,SACwB;AAClB,QAAA,UAAUA,QAAI,KAAK;AACnB,QAAA,OAAOA,QAAc,IAAI;AACzB,QAAA,QAAQA,QAAa,IAAI;AAEzB,QAAA,WAAW,UAAU,WAA0B;;AACnD,YAAQ,QAAQ;AAChB,UAAM,QAAQ;AAEV,QAAA;AACO,+CAAA,aAAA,iCAAW,GAAG;AACvB,WAAK,QAAQ,MAAM,GAAG,GAAG,MAAM;AACtB,+CAAA,cAAA,iCAAY,KAAK;AAC1B,aAAO,KAAK;AAAA,aACL,KAAK;AACZ,YAAM,QAAQ;AACd,+CAAS,YAAT,iCAAmB;AACb,YAAA;AAAA,IAAA,UACN;AACA,cAAQ,QAAQ;AACP,+CAAA,YAAA,iCAAU,GAAG;AAAA,IAAM;AAAA,EAEhC;AAEM,QAAA,MAAM,IAAI,WAAc;AACnB,aAAA,GAAG,MAAM,EAAE,KAAK;AAAA,EAC3B;AAEO,SAAA;AAAA;AAAA;AAAA;AAAA,IAIL;AAAA;AAAA;AAAA;AAAA,IAKA;AAAA;AAAA;AAAA;AAAA,IAKA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA;AAAA,EACF;AACF;;"}
|