@cloudcome/utils-browser 0.0.0 → 1.1.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 +27 -0
- package/LICENSE +21 -0
- package/package.json +120 -6
- package/src/base64.ts +25 -0
- package/src/cache.ts +115 -0
- package/src/canvas.ts +147 -0
- package/src/clipboard.ts +29 -0
- package/src/cookie.ts +118 -0
- package/src/dom.ts +48 -0
- package/src/download.ts +22 -0
- package/src/dts/global.d.ts +27 -0
- package/src/image.ts +45 -0
- package/src/index.ts +1 -0
- package/src/timer.ts +56 -0
- package/src/video.ts +19 -0
- package/test/base64.test.ts +47 -0
- package/test/cache.test.ts +199 -0
- package/test/canvas.test.ts +124 -0
- package/test/clipboard.test.ts +18 -0
- package/test/cookie.test.ts +52 -0
- package/test/dom.test.ts +67 -0
- package/test/download.test.ts +15 -0
- package/test/image.test.ts +75 -0
- package/test/index.test.ts +6 -0
- package/test/timer.test.ts +109 -0
- package/test/video.test.ts +36 -0
- package/tsconfig.json +31 -0
- package/vite.config.mts +100 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { imageLoad } from '../src/image';
|
|
3
|
+
|
|
4
|
+
describe('图片工具函数', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.restoreAllMocks();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('应该能够成功加载图片', async () => {
|
|
10
|
+
const mockImage = new Image();
|
|
11
|
+
vi.spyOn(window, 'Image').mockReturnValue(mockImage);
|
|
12
|
+
vi.spyOn(document.body, 'appendChild');
|
|
13
|
+
vi.spyOn(document.body, 'removeChild');
|
|
14
|
+
|
|
15
|
+
const url = 'https://example.com/image.png';
|
|
16
|
+
const promise = imageLoad(url);
|
|
17
|
+
|
|
18
|
+
mockImage.onload?.({} as Event);
|
|
19
|
+
const result = await promise;
|
|
20
|
+
|
|
21
|
+
expect(result).toBe(mockImage);
|
|
22
|
+
expect(mockImage.src).toBe(url);
|
|
23
|
+
expect(mockImage.crossOrigin).toBe('anonymous');
|
|
24
|
+
expect(document.body.appendChild).toHaveBeenCalledWith(mockImage);
|
|
25
|
+
expect(document.body.removeChild).toHaveBeenCalledWith(mockImage);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('图片加载失败时应抛出错误', async () => {
|
|
29
|
+
const mockImage = new Image();
|
|
30
|
+
vi.spyOn(window, 'Image').mockReturnValue(mockImage);
|
|
31
|
+
|
|
32
|
+
const url = 'https://example.com/invalid.png';
|
|
33
|
+
const promise = imageLoad(url);
|
|
34
|
+
|
|
35
|
+
mockImage.onerror?.({} as Event);
|
|
36
|
+
|
|
37
|
+
await expect(promise).rejects.toThrow('图片加载失败');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('如果图片已经加载完成应立即返回', async () => {
|
|
41
|
+
const mockImage = new Image();
|
|
42
|
+
vi.spyOn(window, 'Image').mockReturnValue(mockImage);
|
|
43
|
+
Object.defineProperty(mockImage, 'complete', { value: true });
|
|
44
|
+
Object.defineProperty(mockImage, 'width', { value: 100 });
|
|
45
|
+
|
|
46
|
+
const url = 'https://example.com/image.png';
|
|
47
|
+
const result = await imageLoad(url);
|
|
48
|
+
|
|
49
|
+
expect(result).toBe(mockImage);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('应该为图片元素设置正确的样式', async () => {
|
|
53
|
+
const mockImage = new Image();
|
|
54
|
+
vi.spyOn(window, 'Image').mockReturnValue(mockImage);
|
|
55
|
+
|
|
56
|
+
const url = 'https://example.com/image.png';
|
|
57
|
+
const promise = imageLoad(url);
|
|
58
|
+
|
|
59
|
+
mockImage.onload?.({} as Event);
|
|
60
|
+
await promise;
|
|
61
|
+
|
|
62
|
+
expect(mockImage.style.visibility).toBe('hidden');
|
|
63
|
+
expect(mockImage.style.position).toBe('absolute');
|
|
64
|
+
expect(mockImage.style.top).toBe('-99999%');
|
|
65
|
+
expect(mockImage.style.left).toBe('-99999%');
|
|
66
|
+
expect(mockImage.style.maxWidth).toBe('');
|
|
67
|
+
expect(mockImage.style.maxHeight).toBe('');
|
|
68
|
+
expect(mockImage.style.border).toBe('0px');
|
|
69
|
+
expect(mockImage.style.width).toBe('auto');
|
|
70
|
+
expect(mockImage.style.height).toBe('auto');
|
|
71
|
+
expect(mockImage.style.margin).toBe('0px');
|
|
72
|
+
expect(mockImage.style.padding).toBe('0px');
|
|
73
|
+
expect(mockImage.style.transform).toBe('');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { frameInterval } from '@/timer';
|
|
2
|
+
import { type Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('帧间隔计时器', () => {
|
|
5
|
+
let mockRAF: number;
|
|
6
|
+
let mockCAF: Mock;
|
|
7
|
+
let callbacks: FrameRequestCallback[] = [];
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
mockRAF = 0;
|
|
11
|
+
callbacks = [];
|
|
12
|
+
mockCAF = vi.fn();
|
|
13
|
+
|
|
14
|
+
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
|
|
15
|
+
callbacks.push(cb);
|
|
16
|
+
return ++mockRAF;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
vi.stubGlobal('cancelAnimationFrame', mockCAF);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
vi.unstubAllGlobals();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('启动时应在每一帧调用回调函数', () => {
|
|
27
|
+
const callback = vi.fn();
|
|
28
|
+
const timer = frameInterval(callback);
|
|
29
|
+
|
|
30
|
+
timer.start();
|
|
31
|
+
callbacks[0]?.(0);
|
|
32
|
+
const arg = callback.mock.calls[0][0];
|
|
33
|
+
expect(Object.keys(arg)).toEqual(
|
|
34
|
+
expect.arrayContaining([
|
|
35
|
+
'times',
|
|
36
|
+
'startAt',
|
|
37
|
+
'stopAt',
|
|
38
|
+
'pauseAt',
|
|
39
|
+
'resumeAt',
|
|
40
|
+
'currentAt',
|
|
41
|
+
'elapsedTime',
|
|
42
|
+
'runningTime',
|
|
43
|
+
'intervalTime',
|
|
44
|
+
]),
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('应该支持 leading 选项', () => {
|
|
49
|
+
const callback = vi.fn();
|
|
50
|
+
const timer = frameInterval(callback, { leading: true });
|
|
51
|
+
|
|
52
|
+
timer.start();
|
|
53
|
+
expect(callback).toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('停止时应支持 trailing 选项', () => {
|
|
57
|
+
const callback = vi.fn();
|
|
58
|
+
const timer = frameInterval(callback, { trailing: true });
|
|
59
|
+
|
|
60
|
+
timer.start();
|
|
61
|
+
callbacks[0]?.(0);
|
|
62
|
+
|
|
63
|
+
timer.stop();
|
|
64
|
+
expect(callback).toHaveBeenCalledTimes(2);
|
|
65
|
+
expect(mockCAF).toHaveBeenCalledWith(mockRAF);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('暂停时应支持 trailing 选项', () => {
|
|
69
|
+
const callback = vi.fn();
|
|
70
|
+
const timer = frameInterval(callback, { trailing: true });
|
|
71
|
+
|
|
72
|
+
timer.start();
|
|
73
|
+
callbacks[0]?.(0);
|
|
74
|
+
|
|
75
|
+
timer.pause();
|
|
76
|
+
expect(callback).toHaveBeenCalledTimes(2);
|
|
77
|
+
expect(mockCAF).toHaveBeenCalledWith(mockRAF);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('使用 immediate 标志时应立即恢复', () => {
|
|
81
|
+
const callback = vi.fn();
|
|
82
|
+
const timer = frameInterval(callback);
|
|
83
|
+
|
|
84
|
+
timer.start();
|
|
85
|
+
callbacks[0]?.(0);
|
|
86
|
+
timer.pause();
|
|
87
|
+
|
|
88
|
+
timer.resume(true);
|
|
89
|
+
callbacks[1]?.(16);
|
|
90
|
+
|
|
91
|
+
expect(callback).toHaveBeenCalledTimes(3);
|
|
92
|
+
expect(callback.mock.calls[1][0].times).toBe(2);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('不使用 immediate 标志时应下一帧恢复', () => {
|
|
96
|
+
const callback = vi.fn();
|
|
97
|
+
const timer = frameInterval(callback);
|
|
98
|
+
|
|
99
|
+
timer.start();
|
|
100
|
+
callbacks[0]?.(0);
|
|
101
|
+
timer.pause();
|
|
102
|
+
|
|
103
|
+
timer.resume();
|
|
104
|
+
callbacks[1]?.(16);
|
|
105
|
+
|
|
106
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
107
|
+
expect(callback.mock.calls[0][0].times).toBe(1);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { videoLoad } from '../src/video';
|
|
3
|
+
|
|
4
|
+
describe('视频工具函数', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.restoreAllMocks();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('应该能够成功加载视频', async () => {
|
|
10
|
+
const mockVideo = document.createElement('video');
|
|
11
|
+
vi.spyOn(document, 'createElement').mockReturnValue(mockVideo);
|
|
12
|
+
|
|
13
|
+
const url = 'https://example.com/video.mp4';
|
|
14
|
+
const promise = videoLoad(url);
|
|
15
|
+
|
|
16
|
+
mockVideo.onloadedmetadata?.({} as Event);
|
|
17
|
+
const result = await promise;
|
|
18
|
+
|
|
19
|
+
expect(result).toBe(mockVideo);
|
|
20
|
+
expect(mockVideo.src).toBe(url);
|
|
21
|
+
expect(mockVideo.crossOrigin).toBe('anonymous');
|
|
22
|
+
expect(mockVideo.currentTime).toBe(1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('视频加载失败时应抛出错误', async () => {
|
|
26
|
+
const mockVideo = document.createElement('video');
|
|
27
|
+
vi.spyOn(document, 'createElement').mockReturnValue(mockVideo);
|
|
28
|
+
|
|
29
|
+
const url = 'https://example.com/invalid.mp4';
|
|
30
|
+
const promise = videoLoad(url);
|
|
31
|
+
|
|
32
|
+
mockVideo.onerror?.({} as Event);
|
|
33
|
+
|
|
34
|
+
await expect(promise).rejects.toThrow('视频加载失败');
|
|
35
|
+
});
|
|
36
|
+
});
|
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", "DOM"],
|
|
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,100 @@
|
|
|
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
|
+
base64: './src/base64.ts',
|
|
41
|
+
cache: './src/cache.ts',
|
|
42
|
+
canvas: './src/canvas.ts',
|
|
43
|
+
clipboard: './src/clipboard.ts',
|
|
44
|
+
cookie: './src/cookie.ts',
|
|
45
|
+
dom: './src/dom.ts',
|
|
46
|
+
download: './src/download.ts',
|
|
47
|
+
image: './src/image.ts',
|
|
48
|
+
timer: './src/timer.ts',
|
|
49
|
+
video: './src/video.ts',
|
|
50
|
+
},
|
|
51
|
+
// expose-end
|
|
52
|
+
},
|
|
53
|
+
rollupOptions: {
|
|
54
|
+
output: [
|
|
55
|
+
{
|
|
56
|
+
format: 'esm',
|
|
57
|
+
entryFileNames: '[name].mjs',
|
|
58
|
+
chunkFileNames: '[name].mjs',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
format: 'cjs',
|
|
62
|
+
entryFileNames: '[name].cjs',
|
|
63
|
+
chunkFileNames: '[name].cjs',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
test: {
|
|
69
|
+
globals: true,
|
|
70
|
+
environment: 'jsdom',
|
|
71
|
+
environmentOptions: {
|
|
72
|
+
jsdom: {
|
|
73
|
+
// 加载外部资源
|
|
74
|
+
resources: 'usable',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
coverage: {
|
|
78
|
+
all: true,
|
|
79
|
+
include: ['src/**/*.ts'],
|
|
80
|
+
reporter: ['lcov', 'text'],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
// esbuild: {
|
|
84
|
+
// drop: isProd ? ['console', 'debugger'] : [],
|
|
85
|
+
// },
|
|
86
|
+
plugins: [
|
|
87
|
+
tsconfigPaths(),
|
|
88
|
+
externalizeDeps({
|
|
89
|
+
deps: true,
|
|
90
|
+
devDeps: true,
|
|
91
|
+
peerDeps: true,
|
|
92
|
+
optionalDeps: true,
|
|
93
|
+
nodeBuiltins: true,
|
|
94
|
+
}),
|
|
95
|
+
dts({
|
|
96
|
+
include: 'src',
|
|
97
|
+
}),
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
});
|