This commit is contained in:
2025-12-24 14:42:57 +08:00
parent 08e79c60e7
commit 7743cdc29a
49 changed files with 2179 additions and 4575 deletions

View File

@@ -1,130 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { StorageManager } from '../storage-manager';
describe('storageManager', () => {
let storageManager: StorageManager;
beforeEach(() => {
vi.useFakeTimers();
localStorage.clear();
storageManager = new StorageManager({
prefix: 'test_',
});
});
it('should set and get an item', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' });
const user = storageManager.getItem('user');
expect(user).toEqual({ age: 30, name: 'John Doe' });
});
it('should return default value if item does not exist', () => {
const user = storageManager.getItem('nonexistent', {
age: 0,
name: 'Default User',
});
expect(user).toEqual({ age: 0, name: 'Default User' });
});
it('should remove an item', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' });
storageManager.removeItem('user');
const user = storageManager.getItem('user');
expect(user).toBeNull();
});
it('should clear all items with the prefix', () => {
storageManager.setItem('user1', { age: 30, name: 'John Doe' });
storageManager.setItem('user2', { age: 25, name: 'Jane Doe' });
storageManager.clear();
expect(storageManager.getItem('user1')).toBeNull();
expect(storageManager.getItem('user2')).toBeNull();
});
it('should clear expired items', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' }, 1000); // 1秒过期
vi.advanceTimersByTime(1001); // 快进时间
storageManager.clearExpiredItems();
const user = storageManager.getItem('user');
expect(user).toBeNull();
});
it('should not clear non-expired items', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' }, 10_000); // 10秒过期
vi.advanceTimersByTime(5000); // 快进时间
storageManager.clearExpiredItems();
const user = storageManager.getItem('user');
expect(user).toEqual({ age: 30, name: 'John Doe' });
});
it('should handle JSON parse errors gracefully', () => {
localStorage.setItem('test_user', '{ invalid JSON }');
const user = storageManager.getItem('user', {
age: 0,
name: 'Default User',
});
expect(user).toEqual({ age: 0, name: 'Default User' });
});
it('should return null for non-existent items without default value', () => {
const user = storageManager.getItem('nonexistent');
expect(user).toBeNull();
});
it('should overwrite existing items', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' });
storageManager.setItem('user', { age: 25, name: 'Jane Doe' });
const user = storageManager.getItem('user');
expect(user).toEqual({ age: 25, name: 'Jane Doe' });
});
it('should handle items without expiry correctly', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' });
vi.advanceTimersByTime(5000);
const user = storageManager.getItem('user');
expect(user).toEqual({ age: 30, name: 'John Doe' });
});
it('should remove expired items when accessed', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' }, 1000); // 1秒过期
vi.advanceTimersByTime(1001); // 快进时间
const user = storageManager.getItem('user');
expect(user).toBeNull();
});
it('should not remove non-expired items when accessed', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' }, 10_000); // 10秒过期
vi.advanceTimersByTime(5000); // 快进时间
const user = storageManager.getItem('user');
expect(user).toEqual({ age: 30, name: 'John Doe' });
});
it('should handle multiple items with different expiry times', () => {
storageManager.setItem('user1', { age: 30, name: 'John Doe' }, 1000); // 1秒过期
storageManager.setItem('user2', { age: 25, name: 'Jane Doe' }, 2000); // 2秒过期
vi.advanceTimersByTime(1500); // 快进时间
storageManager.clearExpiredItems();
const user1 = storageManager.getItem('user1');
const user2 = storageManager.getItem('user2');
expect(user1).toBeNull();
expect(user2).toEqual({ age: 25, name: 'Jane Doe' });
});
it('should handle items with no expiry', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' });
vi.advanceTimersByTime(10_000); // 快进时间
storageManager.clearExpiredItems();
const user = storageManager.getItem('user');
expect(user).toEqual({ age: 30, name: 'John Doe' });
});
it('should clear all items correctly', () => {
storageManager.setItem('user1', { age: 30, name: 'John Doe' });
storageManager.setItem('user2', { age: 25, name: 'Jane Doe' });
storageManager.clear();
const user1 = storageManager.getItem('user1');
const user2 = storageManager.getItem('user2');
expect(user1).toBeNull();
expect(user2).toBeNull();
});
});

View File

@@ -1,58 +0,0 @@
import { describe, expect, it } from 'vitest';
import {
convertToHsl,
convertToHslCssVar,
convertToRgb,
isValidColor,
} from '../convert';
describe('color conversion functions', () => {
it('should correctly convert color to HSL format', () => {
const color = '#ff0000';
const expectedHsl = 'hsl(0 100% 50%)';
expect(convertToHsl(color)).toEqual(expectedHsl);
});
it('should correctly convert color with alpha to HSL format', () => {
const color = 'rgba(255, 0, 0, 0.5)';
const expectedHsl = 'hsl(0 100% 50%) 0.5';
expect(convertToHsl(color)).toEqual(expectedHsl);
});
it('should correctly convert color to HSL CSS variable format', () => {
const color = '#ff0000';
const expectedHsl = '0 100% 50%';
expect(convertToHslCssVar(color)).toEqual(expectedHsl);
});
it('should correctly convert color with alpha to HSL CSS variable format', () => {
const color = 'rgba(255, 0, 0, 0.5)';
const expectedHsl = '0 100% 50% / 0.5';
expect(convertToHslCssVar(color)).toEqual(expectedHsl);
});
it('should correctly convert color to RGB CSS variable format', () => {
const color = 'hsl(284, 100%, 50%)';
const expectedRgb = 'rgb(187, 0, 255)';
expect(convertToRgb(color)).toEqual(expectedRgb);
});
it('should correctly convert color with alpha to RGBA CSS variable format', () => {
const color = 'hsla(284, 100%, 50%, 0.92)';
const expectedRgba = 'rgba(187, 0, 255, 0.92)';
expect(convertToRgb(color)).toEqual(expectedRgba);
});
});
describe('isValidColor', () => {
it('isValidColor function', () => {
// 测试有效颜色
expect(isValidColor('blue')).toBe(true);
expect(isValidColor('#000000')).toBe(true);
// 测试无效颜色
expect(isValidColor('invalid color')).toBe(false);
expect(isValidColor()).toBe(false);
});
});

View File

@@ -1,53 +0,0 @@
import { describe, expect, it } from 'vitest';
import { diff } from '../diff';
describe('diff function', () => {
it('should return an empty object when comparing identical objects', () => {
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };
expect(diff(obj1, obj2)).toEqual(undefined);
});
it('should detect simple changes in primitive values', () => {
const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 3 };
expect(diff(obj1, obj2)).toEqual({ b: 3 });
});
it('should detect nested object changes', () => {
const obj1 = { a: 1, b: { c: 2, d: 4 } };
const obj2 = { a: 1, b: { c: 3, d: 4 } };
expect(diff(obj1, obj2)).toEqual({ b: { c: 3 } });
});
it('should handle array changes', () => {
const obj1 = { a: [1, 2, 3], b: 2 };
const obj2 = { a: [1, 2, 4], b: 2 };
expect(diff(obj1, obj2)).toEqual({ a: [1, 2, 4] });
});
it('should handle added keys', () => {
const obj1 = { a: 1 };
const obj2 = { a: 1, b: 2 };
expect(diff(obj1, obj2)).toEqual({ b: 2 });
});
it('should handle removed keys', () => {
const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1 };
expect(diff(obj1, obj2)).toEqual(undefined);
});
it('should handle boolean value changes', () => {
const obj1 = { a: true, b: false };
const obj2 = { a: true, b: true };
expect(diff(obj1, obj2)).toEqual({ b: true });
});
it('should handle null and undefined values', () => {
const obj1 = { a: null, b: undefined };
const obj2: any = { a: 1, b: undefined };
expect(diff(obj1, obj2)).toEqual({ a: 1 });
});
});

View File

@@ -1,127 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getElementVisibleRect } from '../dom';
describe('getElementVisibleRect', () => {
// 设置浏览器视口尺寸的 mock
beforeEach(() => {
vi.spyOn(document.documentElement, 'clientHeight', 'get').mockReturnValue(
800,
);
vi.spyOn(window, 'innerHeight', 'get').mockReturnValue(800);
vi.spyOn(document.documentElement, 'clientWidth', 'get').mockReturnValue(
1000,
);
vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(1000);
});
it('should return default rect if element is undefined', () => {
expect(getElementVisibleRect()).toEqual({
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0,
});
});
it('should return default rect if element is null', () => {
expect(getElementVisibleRect(null)).toEqual({
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0,
});
});
it('should return correct visible rect when element is fully visible', () => {
const element = {
getBoundingClientRect: () => ({
bottom: 400,
height: 300,
left: 200,
right: 600,
top: 100,
width: 400,
}),
} as HTMLElement;
expect(getElementVisibleRect(element)).toEqual({
bottom: 400,
height: 300,
left: 200,
right: 600,
top: 100,
width: 400,
});
});
it('should return correct visible rect when element is partially off-screen at the top', () => {
const element = {
getBoundingClientRect: () => ({
bottom: 200,
height: 250,
left: 100,
right: 500,
top: -50,
width: 400,
}),
} as HTMLElement;
expect(getElementVisibleRect(element)).toEqual({
bottom: 200,
height: 200,
left: 100,
right: 500,
top: 0,
width: 400,
});
});
it('should return correct visible rect when element is partially off-screen at the right', () => {
const element = {
getBoundingClientRect: () => ({
bottom: 400,
height: 300,
left: 800,
right: 1200,
top: 100,
width: 400,
}),
} as HTMLElement;
expect(getElementVisibleRect(element)).toEqual({
bottom: 400,
height: 300,
left: 800,
right: 1000,
top: 100,
width: 200,
});
});
it('should return all zeros when element is completely off-screen', () => {
const element = {
getBoundingClientRect: () => ({
bottom: 1200,
height: 300,
left: 1100,
right: 1400,
top: 900,
width: 300,
}),
} as HTMLElement;
expect(getElementVisibleRect(element)).toEqual({
bottom: 800,
height: 0,
left: 1100,
right: 1000,
top: 900,
width: 0,
});
});
});

View File

@@ -1,183 +0,0 @@
import { describe, expect, it } from 'vitest';
import {
getFirstNonNullOrUndefined,
isBoolean,
isEmpty,
isHttpUrl,
isObject,
isUndefined,
isWindow,
} from '../inference';
describe('isHttpUrl', () => {
it("should return true when given 'http://example.com'", () => {
expect(isHttpUrl('http://example.com')).toBe(true);
});
it("should return true when given 'https://example.com'", () => {
expect(isHttpUrl('https://example.com')).toBe(true);
});
it("should return false when given 'ftp://example.com'", () => {
expect(isHttpUrl('ftp://example.com')).toBe(false);
});
it("should return false when given 'example.com'", () => {
expect(isHttpUrl('example.com')).toBe(false);
});
});
describe('isUndefined', () => {
it('isUndefined should return true for undefined values', () => {
expect(isUndefined()).toBe(true);
});
it('isUndefined should return false for null values', () => {
expect(isUndefined(null)).toBe(false);
});
it('isUndefined should return false for defined values', () => {
expect(isUndefined(0)).toBe(false);
expect(isUndefined('')).toBe(false);
expect(isUndefined(false)).toBe(false);
});
it('isUndefined should return false for objects and arrays', () => {
expect(isUndefined({})).toBe(false);
expect(isUndefined([])).toBe(false);
});
});
describe('isEmpty', () => {
it('should return true for empty string', () => {
expect(isEmpty('')).toBe(true);
});
it('should return true for empty array', () => {
expect(isEmpty([])).toBe(true);
});
it('should return true for empty object', () => {
expect(isEmpty({})).toBe(true);
});
it('should return false for non-empty string', () => {
expect(isEmpty('hello')).toBe(false);
});
it('should return false for non-empty array', () => {
expect(isEmpty([1, 2, 3])).toBe(false);
});
it('should return false for non-empty object', () => {
expect(isEmpty({ a: 1 })).toBe(false);
});
it('should return true for null or undefined', () => {
expect(isEmpty(null)).toBe(true);
expect(isEmpty()).toBe(true);
});
it('should return false for number or boolean', () => {
expect(isEmpty(0)).toBe(false);
expect(isEmpty(true)).toBe(false);
});
});
describe('isWindow', () => {
it('should return true for the window object', () => {
expect(isWindow(window)).toBe(true);
});
it('should return false for other objects', () => {
expect(isWindow({})).toBe(false);
expect(isWindow([])).toBe(false);
expect(isWindow(null)).toBe(false);
});
});
describe('isBoolean', () => {
it('should return true for boolean values', () => {
expect(isBoolean(true)).toBe(true);
expect(isBoolean(false)).toBe(true);
});
it('should return false for non-boolean values', () => {
expect(isBoolean(null)).toBe(false);
expect(isBoolean(42)).toBe(false);
expect(isBoolean('string')).toBe(false);
expect(isBoolean({})).toBe(false);
expect(isBoolean([])).toBe(false);
});
});
describe('isObject', () => {
it('should return true for objects', () => {
expect(isObject({})).toBe(true);
expect(isObject({ a: 1 })).toBe(true);
});
it('should return false for non-objects', () => {
expect(isObject(null)).toBe(false);
expect(isObject(42)).toBe(false);
expect(isObject('string')).toBe(false);
expect(isObject(true)).toBe(false);
expect(isObject([1, 2, 3])).toBe(true);
expect(isObject(new Date())).toBe(true);
expect(isObject(/regex/)).toBe(true);
});
});
describe('getFirstNonNullOrUndefined', () => {
describe('getFirstNonNullOrUndefined', () => {
it('should return the first non-null and non-undefined value for a number array', () => {
expect(getFirstNonNullOrUndefined<number>(undefined, null, 0, 42)).toBe(
0,
);
expect(getFirstNonNullOrUndefined<number>(null, undefined, 42, 123)).toBe(
42,
);
});
it('should return the first non-null and non-undefined value for a string array', () => {
expect(
getFirstNonNullOrUndefined<string>(undefined, null, '', 'hello'),
).toBe('');
expect(
getFirstNonNullOrUndefined<string>(null, undefined, 'test', 'world'),
).toBe('test');
});
it('should return undefined if all values are null or undefined', () => {
expect(getFirstNonNullOrUndefined(undefined, null)).toBeUndefined();
expect(getFirstNonNullOrUndefined(null)).toBeUndefined();
});
it('should work with a single value', () => {
expect(getFirstNonNullOrUndefined(42)).toBe(42);
expect(getFirstNonNullOrUndefined()).toBeUndefined();
expect(getFirstNonNullOrUndefined(null)).toBeUndefined();
});
it('should handle mixed types correctly', () => {
expect(
getFirstNonNullOrUndefined<number | object | string>(
undefined,
null,
'test',
123,
{ key: 'value' },
),
).toBe('test');
expect(
getFirstNonNullOrUndefined<number | object | string>(
null,
undefined,
[1, 2, 3],
'string',
),
).toEqual([1, 2, 3]);
});
});
});

View File

@@ -1,116 +0,0 @@
import { describe, expect, it } from 'vitest';
import {
capitalizeFirstLetter,
kebabToCamelCase,
toCamelCase,
toLowerCaseFirstLetter,
} from '../letter';
describe('capitalizeFirstLetter', () => {
it('should capitalize the first letter of a string', () => {
expect(capitalizeFirstLetter('hello')).toBe('Hello');
expect(capitalizeFirstLetter('world')).toBe('World');
});
it('should handle empty strings', () => {
expect(capitalizeFirstLetter('')).toBe('');
});
it('should handle single character strings', () => {
expect(capitalizeFirstLetter('a')).toBe('A');
expect(capitalizeFirstLetter('b')).toBe('B');
});
it('should not change the case of other characters', () => {
expect(capitalizeFirstLetter('hElLo')).toBe('HElLo');
});
});
describe('toLowerCaseFirstLetter', () => {
it('should convert the first letter to lowercase', () => {
expect(toLowerCaseFirstLetter('CommonAppName')).toBe('commonAppName');
expect(toLowerCaseFirstLetter('AnotherKeyExample')).toBe(
'anotherKeyExample',
);
});
it('should return the same string if the first letter is already lowercase', () => {
expect(toLowerCaseFirstLetter('alreadyLowerCase')).toBe('alreadyLowerCase');
});
it('should handle empty strings', () => {
expect(toLowerCaseFirstLetter('')).toBe('');
});
it('should handle single character strings', () => {
expect(toLowerCaseFirstLetter('A')).toBe('a');
expect(toLowerCaseFirstLetter('a')).toBe('a');
});
it('should handle strings with only one uppercase letter', () => {
expect(toLowerCaseFirstLetter('A')).toBe('a');
});
it('should handle strings with special characters', () => {
expect(toLowerCaseFirstLetter('!Special')).toBe('!Special');
expect(toLowerCaseFirstLetter('123Number')).toBe('123Number');
});
});
describe('toCamelCase', () => {
it('should return the key if parentKey is empty', () => {
expect(toCamelCase('child', '')).toBe('child');
});
it('should combine parentKey and key in camel case', () => {
expect(toCamelCase('child', 'parent')).toBe('parentChild');
});
it('should handle empty key and parentKey', () => {
expect(toCamelCase('', '')).toBe('');
});
it('should handle key with capital letters', () => {
expect(toCamelCase('Child', 'parent')).toBe('parentChild');
expect(toCamelCase('Child', 'Parent')).toBe('ParentChild');
});
});
describe('kebabToCamelCase', () => {
it('should convert kebab-case to camelCase correctly', () => {
expect(kebabToCamelCase('my-component-name')).toBe('myComponentName');
});
it('should handle multiple consecutive hyphens', () => {
expect(kebabToCamelCase('my--component--name')).toBe('myComponentName');
});
it('should trim leading and trailing hyphens', () => {
expect(kebabToCamelCase('-my-component-name-')).toBe('myComponentName');
});
it('should preserve the case of the first word', () => {
expect(kebabToCamelCase('My-component-name')).toBe('MyComponentName');
});
it('should convert a single word correctly', () => {
expect(kebabToCamelCase('component')).toBe('component');
});
it('should return an empty string if input is empty', () => {
expect(kebabToCamelCase('')).toBe('');
});
it('should handle strings with no hyphens', () => {
expect(kebabToCamelCase('mycomponentname')).toBe('mycomponentname');
});
it('should handle strings with only hyphens', () => {
expect(kebabToCamelCase('---')).toBe('');
});
it('should handle mixed case inputs', () => {
expect(kebabToCamelCase('my-Component-Name')).toBe('myComponentName');
});
});

View File

@@ -1,60 +0,0 @@
import { describe, expect, it } from 'vitest';
import { StateHandler } from '../state-handler';
describe('stateHandler', () => {
it('should resolve when condition is set to true', async () => {
const handler = new StateHandler();
// 模拟异步设置 condition 为 true
setTimeout(() => {
handler.setConditionTrue(); // 明确触发 condition 为 true
}, 10);
// 等待条件被设置为 true
await handler.waitForCondition();
expect(handler.isConditionTrue()).toBe(true);
});
it('should resolve immediately if condition is already true', async () => {
const handler = new StateHandler();
handler.setConditionTrue(); // 提前设置为 true
// 立即 resolve因为 condition 已经是 true
await handler.waitForCondition();
expect(handler.isConditionTrue()).toBe(true);
});
it('should reject when condition is set to false after waiting', async () => {
const handler = new StateHandler();
// 模拟异步设置 condition 为 false
setTimeout(() => {
handler.setConditionFalse(); // 明确触发 condition 为 false
}, 10);
// 等待过程中,期望 Promise 被 reject
await expect(handler.waitForCondition()).rejects.toThrow();
expect(handler.isConditionTrue()).toBe(false);
});
it('should reset condition to false', () => {
const handler = new StateHandler();
handler.setConditionTrue(); // 设置为 true
handler.reset(); // 重置为 false
expect(handler.isConditionTrue()).toBe(false);
});
it('should resolve when condition is set to true after reset', async () => {
const handler = new StateHandler();
handler.reset(); // 确保初始为 false
setTimeout(() => {
handler.setConditionTrue(); // 重置后设置为 true
}, 10);
await handler.waitForCondition();
expect(handler.isConditionTrue()).toBe(true);
});
});

View File

@@ -1,196 +0,0 @@
import { describe, expect, it } from 'vitest';
import { filterTree, mapTree, traverseTreeValues } from '../tree';
describe('traverseTreeValues', () => {
interface Node {
children?: Node[];
name: string;
}
type NodeValue = string;
const sampleTree: Node[] = [
{
name: 'A',
children: [
{ name: 'B' },
{
name: 'C',
children: [{ name: 'D' }, { name: 'E' }],
},
],
},
{
name: 'F',
children: [
{ name: 'G' },
{
name: 'H',
children: [{ name: 'I' }],
},
],
},
];
it('traverses tree and returns all node values', () => {
const values = traverseTreeValues<Node, NodeValue>(
sampleTree,
(node) => node.name,
{
childProps: 'children',
},
);
expect(values).toEqual(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']);
});
it('handles empty tree', () => {
const values = traverseTreeValues<Node, NodeValue>([], (node) => node.name);
expect(values).toEqual([]);
});
it('handles tree with only root node', () => {
const rootNode = { name: 'A' };
const values = traverseTreeValues<Node, NodeValue>(
[rootNode],
(node) => node.name,
);
expect(values).toEqual(['A']);
});
it('handles tree with only leaf nodes', () => {
const leafNodes = [{ name: 'A' }, { name: 'B' }, { name: 'C' }];
const values = traverseTreeValues<Node, NodeValue>(
leafNodes,
(node) => node.name,
);
expect(values).toEqual(['A', 'B', 'C']);
});
});
describe('filterTree', () => {
const tree = [
{
id: 1,
children: [
{ id: 2 },
{ id: 3, children: [{ id: 4 }, { id: 5 }, { id: 6 }] },
{ id: 7 },
],
},
{ id: 8, children: [{ id: 9 }, { id: 10 }] },
{ id: 11 },
];
it('should return all nodes when condition is always true', () => {
const result = filterTree(tree, () => true, { childProps: 'children' });
expect(result).toEqual(tree);
});
it('should return only root nodes when condition is always false', () => {
const result = filterTree(tree, () => false);
expect(result).toEqual([]);
});
it('should return nodes with even id values', () => {
const result = filterTree(tree, (node) => node.id % 2 === 0);
expect(result).toEqual([{ id: 8, children: [{ id: 10 }] }]);
});
it('should return nodes with odd id values and their ancestors', () => {
const result = filterTree(tree, (node) => node.id % 2 === 1);
expect(result).toEqual([
{
id: 1,
children: [{ id: 3, children: [{ id: 5 }] }, { id: 7 }],
},
{ id: 11 },
]);
});
it('should return nodes with "leaf" in their name', () => {
const tree = [
{
name: 'root',
children: [
{ name: 'leaf 1' },
{
name: 'branch',
children: [{ name: 'leaf 2' }, { name: 'leaf 3' }],
},
{ name: 'leaf 4' },
],
},
];
const result = filterTree(
tree,
(node) => node.name.includes('leaf') || node.name === 'root',
);
expect(result).toEqual([
{
name: 'root',
children: [{ name: 'leaf 1' }, { name: 'leaf 4' }],
},
]);
});
});
describe('mapTree', () => {
it('map infinite depth tree using mapTree', () => {
const tree = [
{
id: 1,
name: 'node1',
children: [
{ id: 2, name: 'node2' },
{ id: 3, name: 'node3' },
{
id: 4,
name: 'node4',
children: [
{
id: 5,
name: 'node5',
children: [
{ id: 6, name: 'node6' },
{ id: 7, name: 'node7' },
],
},
{ id: 8, name: 'node8' },
],
},
],
},
];
const newTree = mapTree(tree, (node) => ({
...node,
name: `${node.name}-new`,
}));
expect(newTree).toEqual([
{
id: 1,
name: 'node1-new',
children: [
{ id: 2, name: 'node2-new' },
{ id: 3, name: 'node3-new' },
{
id: 4,
name: 'node4-new',
children: [
{
id: 5,
name: 'node5-new',
children: [
{ id: 6, name: 'node6-new' },
{ id: 7, name: 'node7-new' },
],
},
{ id: 8, name: 'node8-new' },
],
},
],
},
]);
});
});

View File

@@ -1,60 +0,0 @@
import { describe, expect, it } from 'vitest';
import { uniqueByField } from '../unique';
describe('uniqueByField', () => {
it('should return an array with unique items based on id field', () => {
const items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
{ id: 1, name: 'Duplicate Item' },
];
const uniqueItems = uniqueByField(items, 'id');
expect(uniqueItems).toHaveLength(3);
expect(uniqueItems).toEqual([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
]);
});
it('should return an empty array when input array is empty', () => {
const items: any[] = []; // Empty array
const uniqueItems = uniqueByField(items, 'id');
// Assert expected results
expect(uniqueItems).toEqual([]);
});
it('should handle arrays with only one item correctly', () => {
const items = [{ id: 1, name: 'Item 1' }];
const uniqueItems = uniqueByField(items, 'id');
// Assert expected results
expect(uniqueItems).toHaveLength(1);
expect(uniqueItems).toEqual([{ id: 1, name: 'Item 1' }]);
});
it('should preserve the order of the first occurrence of each item', () => {
const items = [
{ id: 2, name: 'Item 2' },
{ id: 1, name: 'Item 1' },
{ id: 3, name: 'Item 3' },
{ id: 1, name: 'Duplicate Item' },
];
const uniqueItems = uniqueByField(items, 'id');
// Assert expected results (order of first occurrences preserved)
expect(uniqueItems).toEqual([
{ id: 2, name: 'Item 2' },
{ id: 1, name: 'Item 1' },
{ id: 3, name: 'Item 3' },
]);
});
});

View File

@@ -1,30 +0,0 @@
import { expect, it } from 'vitest';
import { updateCSSVariables } from '../update-css-variables';
it('updateCSSVariables should update CSS variables in :root selector', () => {
// 模拟初始的内联样式表内容
const initialStyleContent = ':root { --primaryColor: red; }';
document.head.innerHTML = `<style id="custom-styles">${initialStyleContent}</style>`;
// 要更新的CSS变量和它们的新值
const updatedVariables = {
fontSize: '16px',
primaryColor: 'blue',
secondaryColor: 'green',
};
// 调用函数来更新CSS变量
updateCSSVariables(updatedVariables, 'custom-styles');
// 获取更新后的样式内容
const styleElement = document.querySelector('#custom-styles');
const updatedStyleContent = styleElement ? styleElement.textContent : '';
// 检查更新后的样式内容是否包含正确的更新值
expect(
updatedStyleContent?.includes('primaryColor: blue;') &&
updatedStyleContent?.includes('secondaryColor: green;') &&
updatedStyleContent?.includes('fontSize: 16px;'),
).toBe(true);
});

View File

@@ -1,156 +0,0 @@
import { describe, expect, it } from 'vitest';
import { bindMethods, getNestedValue } from '../util';
class TestClass {
public value: string;
constructor(value: string) {
this.value = value;
bindMethods(this); // 调用通用方法
}
getValue() {
return this.value;
}
setValue(newValue: string) {
this.value = newValue;
}
}
describe('bindMethods', () => {
it('should bind methods to the instance correctly', () => {
const instance = new TestClass('initial');
// 解构方法
const { getValue } = instance;
// 检查 getValue 是否能正确调用,并且 this 绑定了 instance
expect(getValue()).toBe('initial');
});
it('should bind multiple methods', () => {
const instance = new TestClass('initial');
const { getValue, setValue } = instance;
// 检查 getValue 和 setValue 方法是否正确绑定了 this
setValue('newValue');
expect(getValue()).toBe('newValue');
});
it('should not bind non-function properties', () => {
const instance = new TestClass('initial');
// 检查普通属性是否保持原样
expect(instance.value).toBe('initial');
});
it('should not bind constructor method', () => {
const instance = new TestClass('test');
// 检查 constructor 是否没有被绑定
expect(instance.constructor.name).toBe('TestClass');
});
it('should not bind getter/setter properties', () => {
class TestWithGetterSetter {
get value() {
return this._value;
}
set value(newValue: string) {
this._value = newValue;
}
private _value: string = 'test';
constructor() {
bindMethods(this);
}
}
const instance = new TestWithGetterSetter();
const { value } = instance;
// Getter 和 setter 不应被绑定
expect(value).toBe('test');
});
});
describe('getNestedValue', () => {
interface UserProfile {
age: number;
name: string;
}
interface UserSettings {
theme: string;
}
interface Data {
user: {
profile: UserProfile;
settings: UserSettings;
};
}
const data: Data = {
user: {
profile: {
age: 25,
name: 'Alice',
},
settings: {
theme: 'dark',
},
},
};
it('should get a nested value when the path is valid', () => {
const result = getNestedValue(data, 'user.profile.name');
expect(result).toBe('Alice');
});
it('should return undefined for non-existent property', () => {
const result = getNestedValue(data, 'user.profile.gender');
expect(result).toBeUndefined();
});
it('should return undefined when accessing a non-existent deep path', () => {
const result = getNestedValue(data, 'user.nonexistent.field');
expect(result).toBeUndefined();
});
it('should return undefined if a middle level is undefined', () => {
const result = getNestedValue({ user: undefined }, 'user.profile.name');
expect(result).toBeUndefined();
});
it('should return the correct value for a nested setting', () => {
const result = getNestedValue(data, 'user.settings.theme');
expect(result).toBe('dark');
});
it('should work for a single-level path', () => {
const result = getNestedValue({ a: 1, b: 2 }, 'b');
expect(result).toBe(2);
});
it('should return the entire object if path is empty', () => {
expect(() => getNestedValue(data, '')()).toThrow();
});
it('should handle paths with array indexes', () => {
const complexData = { list: [{ name: 'Item1' }, { name: 'Item2' }] };
const result = getNestedValue(complexData, 'list.1.name');
expect(result).toBe('Item2');
});
it('should return undefined when accessing an out-of-bounds array index', () => {
const complexData = { list: [{ name: 'Item1' }] };
const result = getNestedValue(complexData, 'list.2.name');
expect(result).toBeUndefined();
});
});

View File

@@ -1,33 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { openWindow } from '../window';
describe('openWindow', () => {
// 保存原始的 window.open 函数
let originalOpen: typeof window.open;
beforeEach(() => {
originalOpen = window.open;
});
afterEach(() => {
window.open = originalOpen;
});
it('should call window.open with correct arguments', () => {
const url = 'https://example.com';
const options = { noopener: true, noreferrer: true, target: '_blank' };
window.open = vi.fn();
// 调用函数
openWindow(url, options);
// 验证 window.open 是否被正确地调用
expect(window.open).toHaveBeenCalledWith(
url,
options.target,
'noopener=yes,noreferrer=yes',
);
});
});

View File

@@ -1,48 +0,0 @@
import type { SortableOptions } from 'sortablejs';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useSortable } from '../use-sortable';
describe('useSortable', () => {
beforeEach(() => {
vi.mock('sortablejs/modular/sortable.complete.esm.js', () => ({
default: {
create: vi.fn(),
},
}));
});
it('should call Sortable.create with the correct options', async () => {
// Create a mock element
const mockElement = document.createElement('div') as HTMLDivElement;
// Define custom options
const customOptions: SortableOptions = {
group: 'test-group',
sort: false,
};
// Use the useSortable function
const { initializeSortable } = useSortable(mockElement, customOptions);
// Initialize sortable
await initializeSortable();
// Import sortablejs to access the mocked create function
const Sortable = await import(
'sortablejs/modular/sortable.complete.esm.js'
);
// Verify that Sortable.create was called with the correct parameters
expect(Sortable.default.create).toHaveBeenCalledTimes(1);
expect(Sortable.default.create).toHaveBeenCalledWith(
mockElement,
expect.objectContaining({
animation: 300,
delay: 400,
delayOnTouchOnly: true,
...customOptions,
}),
);
});
});

View File

@@ -1,122 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`defaultPreferences immutability test > should not modify the config object 1`] = `
{
"app": {
"accessMode": "frontend",
"authPageLayout": "panel-right",
"checkUpdatesInterval": 1,
"colorGrayMode": false,
"colorWeakMode": false,
"compact": false,
"contentCompact": "wide",
"defaultAvatar": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp",
"dynamicTitle": true,
"enableCheckUpdates": true,
"enablePreferences": true,
"enableRefreshToken": false,
"isMobile": false,
"layout": "sidebar-nav",
"locale": "zh-CN",
"loginExpiredMode": "page",
"name": "Vben Admin",
"preferencesButtonPosition": "auto",
"watermark": false,
},
"breadcrumb": {
"enable": true,
"hideOnlyOne": false,
"showHome": false,
"showIcon": true,
"styleType": "normal",
},
"copyright": {
"companyName": "Vben",
"companySiteLink": "https://www.vben.pro",
"date": "2024",
"enable": true,
"icp": "",
"icpLink": "",
"settingShow": true,
},
"footer": {
"enable": false,
"fixed": false,
},
"header": {
"enable": true,
"hidden": false,
"menuAlign": "start",
"mode": "fixed",
},
"logo": {
"enable": true,
"source": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp",
},
"navigation": {
"accordion": true,
"split": true,
"styleType": "rounded",
},
"shortcutKeys": {
"enable": true,
"globalLockScreen": true,
"globalLogout": true,
"globalPreferences": true,
"globalSearch": true,
},
"sidebar": {
"autoActivateChild": false,
"collapsed": false,
"collapsedButton": true,
"collapsedShowTitle": false,
"enable": true,
"expandOnHover": true,
"extraCollapse": false,
"fixedButton": true,
"hidden": false,
"width": 224,
},
"tabbar": {
"draggable": true,
"enable": true,
"height": 38,
"keepAlive": true,
"maxCount": 0,
"middleClickToClose": false,
"persist": true,
"showIcon": true,
"showMaximize": true,
"showMore": true,
"styleType": "chrome",
"wheelable": true,
},
"theme": {
"builtinType": "default",
"colorDestructive": "hsl(348 100% 61%)",
"colorPrimary": "hsl(212 100% 45%)",
"colorSuccess": "hsl(144 57% 58%)",
"colorWarning": "hsl(42 84% 61%)",
"mode": "dark",
"radius": "0.5",
"semiDarkHeader": false,
"semiDarkSidebar": false,
},
"transition": {
"enable": true,
"loading": true,
"name": "fade-slide",
"progress": true,
},
"widget": {
"fullscreen": true,
"globalSearch": true,
"languageToggle": true,
"lockScreen": true,
"notification": true,
"refresh": true,
"sidebarToggle": true,
"themeToggle": true,
},
}
`;

View File

@@ -1,10 +0,0 @@
import { describe, expect, it } from 'vitest';
import { defaultPreferences } from '../src/config';
describe('defaultPreferences immutability test', () => {
// 创建快照,确保默认配置对象不被修改
it('should not modify the config object', () => {
expect(defaultPreferences).toMatchSnapshot();
});
});

View File

@@ -1,253 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { defaultPreferences } from '../src/config';
import { PreferenceManager } from '../src/preferences';
import { isDarkTheme } from '../src/update-css-variables';
describe('preferences', () => {
let preferenceManager: PreferenceManager;
// 模拟 window.matchMedia 方法
vi.stubGlobal(
'matchMedia',
vi.fn().mockImplementation((query) => ({
addEventListener: vi.fn(),
addListener: vi.fn(), // Deprecated
dispatchEvent: vi.fn(),
matches: query === '(prefers-color-scheme: dark)',
media: query,
onchange: null,
removeEventListener: vi.fn(),
removeListener: vi.fn(), // Deprecated
})),
);
beforeEach(() => {
preferenceManager = new PreferenceManager();
});
it('loads default preferences if no saved preferences found', () => {
const preferences = preferenceManager.getPreferences();
expect(preferences).toEqual(defaultPreferences);
});
it('initializes preferences with overrides', async () => {
const overrides: any = {
app: {
locale: 'en-US',
},
};
await preferenceManager.initPreferences({
namespace: 'testNamespace',
overrides,
});
// 等待防抖动操作完成
// await new Promise((resolve) => setTimeout(resolve, 300)); // 等待100毫秒
const expected = {
...defaultPreferences,
app: {
...defaultPreferences.app,
...overrides.app,
},
};
expect(preferenceManager.getPreferences()).toEqual(expected);
});
it('updates theme mode correctly', () => {
preferenceManager.updatePreferences({
theme: {
mode: 'light',
},
});
expect(preferenceManager.getPreferences().theme.mode).toBe('light');
});
it('updates color modes correctly', () => {
preferenceManager.updatePreferences({
app: { colorGrayMode: true, colorWeakMode: true },
});
expect(preferenceManager.getPreferences().app.colorGrayMode).toBe(true);
expect(preferenceManager.getPreferences().app.colorWeakMode).toBe(true);
});
it('resets preferences to default', () => {
// 先更新一些偏好设置
preferenceManager.updatePreferences({
theme: {
mode: 'light',
},
});
// 然后重置偏好设置
preferenceManager.resetPreferences();
expect(preferenceManager.getPreferences()).toEqual(defaultPreferences);
});
it('updates isMobile correctly', () => {
// 模拟移动端状态
vi.stubGlobal(
'matchMedia',
vi.fn().mockImplementation((query) => ({
addEventListener: vi.fn(),
addListener: vi.fn(),
dispatchEvent: vi.fn(),
matches: query === '(max-width: 768px)',
media: query,
onchange: null,
removeEventListener: vi.fn(),
removeListener: vi.fn(),
})),
);
preferenceManager.updatePreferences({
app: { isMobile: true },
});
expect(preferenceManager.getPreferences().app.isMobile).toBe(true);
});
it('updates the locale preference correctly', () => {
preferenceManager.updatePreferences({
app: { locale: 'en-US' },
});
expect(preferenceManager.getPreferences().app.locale).toBe('en-US');
});
it('updates the sidebar width correctly', () => {
preferenceManager.updatePreferences({
sidebar: { width: 200 },
});
expect(preferenceManager.getPreferences().sidebar.width).toBe(200);
});
it('updates the sidebar collapse state correctly', () => {
preferenceManager.updatePreferences({
sidebar: { collapsed: true },
});
expect(preferenceManager.getPreferences().sidebar.collapsed).toBe(true);
});
it('updates the navigation style type correctly', () => {
preferenceManager.updatePreferences({
navigation: { styleType: 'flat' },
} as any);
expect(preferenceManager.getPreferences().navigation.styleType).toBe(
'flat',
);
});
it('resets preferences to default correctly', () => {
// 先更新一些偏好设置
preferenceManager.updatePreferences({
app: { locale: 'en-US' },
sidebar: { collapsed: true, width: 200 },
theme: {
mode: 'light',
},
});
// 然后重置偏好设置
preferenceManager.resetPreferences();
expect(preferenceManager.getPreferences()).toEqual(defaultPreferences);
});
it('does not update undefined preferences', () => {
const originalPreferences = preferenceManager.getPreferences();
preferenceManager.updatePreferences({
app: { nonexistentField: 'value' },
} as any);
expect(preferenceManager.getPreferences()).toEqual(originalPreferences);
});
it('reverts to default when a preference field is deleted', () => {
preferenceManager.updatePreferences({
app: { locale: 'en-US' },
});
preferenceManager.updatePreferences({
app: { locale: undefined },
});
expect(preferenceManager.getPreferences().app.locale).toBe('en-US');
});
it('ignores updates with invalid preference value types', () => {
const originalPreferences = preferenceManager.getPreferences();
preferenceManager.updatePreferences({
app: { isMobile: 'true' as unknown as boolean }, // 错误类型
});
expect(preferenceManager.getPreferences()).toEqual(originalPreferences);
});
it('merges nested preference objects correctly', () => {
preferenceManager.updatePreferences({
app: { name: 'New App Name' },
});
const expected = {
...defaultPreferences,
app: {
...defaultPreferences.app,
name: 'New App Name',
},
};
expect(preferenceManager.getPreferences()).toEqual(expected);
});
it('applies updates immediately after initialization', async () => {
const overrides: any = {
app: {
locale: 'en-US',
},
};
await preferenceManager.initPreferences(overrides);
preferenceManager.updatePreferences({
theme: { mode: 'light' },
});
expect(preferenceManager.getPreferences().theme.mode).toBe('light');
});
});
describe('isDarkTheme', () => {
it('should return true for dark theme', () => {
expect(isDarkTheme('dark')).toBe(true);
});
it('should return false for light theme', () => {
expect(isDarkTheme('light')).toBe(false);
});
it('should return system preference for auto theme', () => {
vi.spyOn(window, 'matchMedia').mockImplementation((query) => ({
addEventListener: vi.fn(),
addListener: vi.fn(), // Deprecated
dispatchEvent: vi.fn(),
matches: query === '(prefers-color-scheme: dark)',
media: query,
onchange: null,
removeEventListener: vi.fn(),
removeListener: vi.fn(), // Deprecated
}));
expect(isDarkTheme('auto')).toBe(true);
expect(window.matchMedia).toHaveBeenCalledWith(
'(prefers-color-scheme: dark)',
);
});
});

View File

@@ -1,189 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { FormApi } from '../src/form-api';
describe('formApi', () => {
let formApi: FormApi;
beforeEach(() => {
formApi = new FormApi();
});
it('should initialize with default state', () => {
expect(formApi.state).toEqual(
expect.objectContaining({
actionWrapperClass: '',
collapsed: false,
collapsedRows: 1,
commonConfig: {},
handleReset: undefined,
handleSubmit: undefined,
layout: 'horizontal',
resetButtonOptions: {},
schema: [],
showCollapseButton: false,
showDefaultActions: true,
submitButtonOptions: {},
wrapperClass: 'grid-cols-1',
}),
);
expect(formApi.isMounted).toBe(false);
});
it('should mount form actions', async () => {
const formActions: any = {
meta: {},
resetForm: vi.fn(),
setFieldValue: vi.fn(),
setValues: vi.fn(),
submitForm: vi.fn(),
validate: vi.fn(),
values: { name: 'test' },
};
await formApi.mount(formActions);
expect(formApi.isMounted).toBe(true);
expect(formApi.form).toEqual(formActions);
});
it('should get values from form', async () => {
const formActions: any = {
meta: {},
values: { name: 'test' },
};
await formApi.mount(formActions);
const values = await formApi.getValues();
expect(values).toEqual({ name: 'test' });
});
it('should set field value', async () => {
const setFieldValueMock = vi.fn();
const formActions: any = {
meta: {},
setFieldValue: setFieldValueMock,
values: { name: 'test' },
};
await formApi.mount(formActions);
await formApi.setFieldValue('name', 'new value');
expect(setFieldValueMock).toHaveBeenCalledWith(
'name',
'new value',
undefined,
);
});
it('should reset form', async () => {
const resetFormMock = vi.fn();
const formActions: any = {
meta: {},
resetForm: resetFormMock,
values: { name: 'test' },
};
await formApi.mount(formActions);
await formApi.resetForm();
expect(resetFormMock).toHaveBeenCalled();
});
it('should call handleSubmit on submit', async () => {
const handleSubmitMock = vi.fn();
const formActions: any = {
meta: {},
submitForm: vi.fn().mockResolvedValue(true),
values: { name: 'test' },
};
const state = {
handleSubmit: handleSubmitMock,
};
formApi.setState(state);
await formApi.mount(formActions);
const result = await formApi.submitForm();
expect(formActions.submitForm).toHaveBeenCalled();
expect(handleSubmitMock).toHaveBeenCalledWith({ name: 'test' });
expect(result).toEqual({ name: 'test' });
});
it('should unmount form and reset state', () => {
formApi.unmount();
expect(formApi.isMounted).toBe(false);
});
it('should validate form', async () => {
const validateMock = vi.fn().mockResolvedValue(true);
const formActions: any = {
meta: {},
validate: validateMock,
};
await formApi.mount(formActions);
const isValid = await formApi.validate();
expect(validateMock).toHaveBeenCalled();
expect(isValid).toBe(true);
});
});
describe('updateSchema', () => {
let instance: FormApi;
beforeEach(() => {
instance = new FormApi();
instance.state = {
schema: [
{ component: 'text', fieldName: 'name' },
{ component: 'number', fieldName: 'age', label: 'Age' },
],
};
});
it('should update the schema correctly when fieldName matches', () => {
const newSchema = [
{ component: 'text', fieldName: 'name' },
{ component: 'number', fieldName: 'age', label: 'Age' },
];
instance.updateSchema(newSchema);
expect(instance.state?.schema?.[0]?.component).toBe('text');
expect(instance.state?.schema?.[1]?.label).toBe('Age');
});
it('should log an error if fieldName is missing in some items', () => {
const newSchema: any[] = [
{ component: 'textarea', fieldName: 'name' },
{ component: 'number' },
];
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
instance.updateSchema(newSchema);
expect(consoleErrorSpy).toHaveBeenCalledWith(
'All items in the schema array must have a valid `fieldName` property to be updated',
);
});
it('should not update schema if fieldName does not match', () => {
const newSchema = [{ component: 'textarea', fieldName: 'unknown' }];
instance.updateSchema(newSchema);
expect(instance.state?.schema?.[0]?.component).toBe('text');
expect(instance.state?.schema?.[1]?.component).toBe('number');
});
it('should not update schema if updatedMap is empty', () => {
const newSchema: any[] = [{ component: 'textarea' }];
instance.updateSchema(newSchema);
expect(instance.state?.schema?.[0]?.component).toBe('text');
expect(instance.state?.schema?.[1]?.component).toBe('number');
});
});

View File

@@ -1,116 +0,0 @@
import type { DrawerState } from '../drawer';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { DrawerApi } from '../drawer-api';
// 模拟 Store 类
vi.mock('@vben-core/shared/store', () => {
return {
isFunction: (fn: any) => typeof fn === 'function',
Store: class {
get state() {
return this._state;
}
private _state: DrawerState;
private options: any;
constructor(initialState: DrawerState, options: any) {
this._state = initialState;
this.options = options;
}
batch(cb: () => void) {
cb();
}
setState(fn: (prev: DrawerState) => DrawerState) {
this._state = fn(this._state);
this.options.onUpdate();
}
},
};
});
describe('drawerApi', () => {
let drawerApi: DrawerApi;
let drawerState: DrawerState;
beforeEach(() => {
drawerApi = new DrawerApi();
drawerState = drawerApi.store.state;
});
it('should initialize with default state', () => {
expect(drawerState.isOpen).toBe(false);
expect(drawerState.cancelText).toBe(undefined);
expect(drawerState.confirmText).toBe(undefined);
});
it('should open the drawer', () => {
drawerApi.open();
expect(drawerApi.store.state.isOpen).toBe(true);
});
it('should close the drawer if onBeforeClose allows it', () => {
drawerApi.close();
expect(drawerApi.store.state.isOpen).toBe(false);
});
it('should not close the drawer if onBeforeClose returns false', () => {
const onBeforeClose = vi.fn(() => false);
const drawerApiWithHook = new DrawerApi({ onBeforeClose });
drawerApiWithHook.open();
drawerApiWithHook.close();
expect(drawerApiWithHook.store.state.isOpen).toBe(true);
expect(onBeforeClose).toHaveBeenCalled();
});
it('should trigger onCancel and keep drawer open if onCancel is provided', () => {
const onCancel = vi.fn();
const drawerApiWithHook = new DrawerApi({ onCancel });
drawerApiWithHook.open();
drawerApiWithHook.onCancel();
expect(onCancel).toHaveBeenCalled();
expect(drawerApiWithHook.store.state.isOpen).toBe(true); // 关闭逻辑不在 onCancel 内
});
it('should update shared data correctly', () => {
const testData = { key: 'value' };
drawerApi.setData(testData);
expect(drawerApi.getData()).toEqual(testData);
});
it('should set state correctly using an object', () => {
drawerApi.setState({ title: 'New Title' });
expect(drawerApi.store.state.title).toBe('New Title');
});
it('should set state correctly using a function', () => {
drawerApi.setState((prev) => ({ ...prev, confirmText: 'Yes' }));
expect(drawerApi.store.state.confirmText).toBe('Yes');
});
it('should call onOpenChange when state changes', () => {
const onOpenChange = vi.fn();
const drawerApiWithHook = new DrawerApi({ onOpenChange });
drawerApiWithHook.open();
expect(onOpenChange).toHaveBeenCalledWith(true);
});
it('should call onClosed callback when provided', () => {
const onClosed = vi.fn();
const drawerApiWithHook = new DrawerApi({ onClosed });
drawerApiWithHook.onClosed();
expect(onClosed).toHaveBeenCalled();
});
it('should call onOpened callback when provided', () => {
const onOpened = vi.fn();
const drawerApiWithHook = new DrawerApi({ onOpened });
drawerApiWithHook.open();
drawerApiWithHook.onOpened();
expect(onOpened).toHaveBeenCalled();
});
});

View File

@@ -1,117 +0,0 @@
import type { ModalState } from '../modal';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ModalApi } from '../modal-api';
vi.mock('@vben-core/shared/store', () => {
return {
isFunction: (fn: any) => typeof fn === 'function',
Store: class {
get state() {
return this._state;
}
private _state: ModalState;
private options: any;
constructor(initialState: ModalState, options: any) {
this._state = initialState;
this.options = options;
}
batch(cb: () => void) {
cb();
}
setState(fn: (prev: ModalState) => ModalState) {
this._state = fn(this._state);
this.options.onUpdate();
}
},
};
});
describe('modalApi', () => {
let modalApi: ModalApi;
// 使用 modalState 而不是 state
let modalState: ModalState;
beforeEach(() => {
modalApi = new ModalApi();
// 获取 modalApi 内的 state
modalState = modalApi.store.state;
});
it('should initialize with default state', () => {
expect(modalState.isOpen).toBe(false);
expect(modalState.cancelText).toBe(undefined);
expect(modalState.confirmText).toBe(undefined);
});
it('should open the modal', () => {
modalApi.open();
expect(modalApi.store.state.isOpen).toBe(true);
});
it('should close the modal if onBeforeClose allows it', () => {
modalApi.close();
expect(modalApi.store.state.isOpen).toBe(false);
});
it('should not close the modal if onBeforeClose returns false', () => {
const onBeforeClose = vi.fn(() => false);
const modalApiWithHook = new ModalApi({ onBeforeClose });
modalApiWithHook.open();
modalApiWithHook.close();
expect(modalApiWithHook.store.state.isOpen).toBe(true);
expect(onBeforeClose).toHaveBeenCalled();
});
it('should trigger onCancel and close the modal if no onCancel hook is provided', () => {
const onCancel = vi.fn();
const modalApiWithHook = new ModalApi({ onCancel });
modalApiWithHook.open();
modalApiWithHook.onCancel();
expect(onCancel).toHaveBeenCalled();
expect(modalApiWithHook.store.state.isOpen).toBe(true);
});
it('should update shared data correctly', () => {
const testData = { key: 'value' };
modalApi.setData(testData);
expect(modalApi.getData()).toEqual(testData);
});
it('should set state correctly using an object', () => {
modalApi.setState({ title: 'New Title' });
expect(modalApi.store.state.title).toBe('New Title');
});
it('should set state correctly using a function', () => {
modalApi.setState((prev) => ({ ...prev, confirmText: 'Yes' }));
expect(modalApi.store.state.confirmText).toBe('Yes');
});
it('should call onOpenChange when state changes', () => {
const onOpenChange = vi.fn();
const modalApiWithHook = new ModalApi({ onOpenChange });
modalApiWithHook.open();
expect(onOpenChange).toHaveBeenCalledWith(true);
});
it('should call onClosed callback when provided', () => {
const onClosed = vi.fn();
const modalApiWithHook = new ModalApi({ onClosed });
modalApiWithHook.onClosed();
expect(onClosed).toHaveBeenCalled();
});
it('should call onOpened callback when provided', () => {
const onOpened = vi.fn();
const modalApiWithHook = new ModalApi({ onOpened });
modalApiWithHook.open();
modalApiWithHook.onOpened();
expect(onOpened).toHaveBeenCalled();
});
});