chore: v2.0.1 - 精简dead code + 版本升级
- utils.js: 950行→180行,移除30+未使用函数 - styles.js: 1800行→650行,移除未使用组件样式和重复规则 - animations.js: 850行→120行,移除Web Animations API dead code - 全项目版本号升级到 2.0.1 - 测试同步清理,134/134通过
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@metona-team/metona-toast",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"description": "轻量、零依赖、精致美观的Toast通知库。单文件,开箱即用。",
|
||||
"main": "dist/metona-toast.js",
|
||||
"module": "src/index.js",
|
||||
|
||||
+1
-1
@@ -273,7 +273,7 @@
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
MetonaToast v2.0.0 · MIT · <a href="index.html">首页</a> · <a href="docs.html">文档</a> · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a>
|
||||
MetonaToast v2.0.1 · MIT · <a href="index.html">首页</a> · <a href="docs.html">文档</a> · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a>
|
||||
</footer>
|
||||
|
||||
<script src="../dist/metona-toast.js"></script>
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@
|
||||
<main class="main">
|
||||
|
||||
<h1>API 文档</h1>
|
||||
<p>MetonaToast v2.0.0 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 <a href="#config">配置项</a> 作为可选第二参数。</p>
|
||||
<p>MetonaToast v2.0.1 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 <a href="#config">配置项</a> 作为可选第二参数。</p>
|
||||
|
||||
<!-- ===== 基础通知 ===== -->
|
||||
<h2 id="show">show(message, opts?)</h2>
|
||||
|
||||
+1
-1
@@ -237,7 +237,7 @@ orderGroup.<span class="fn">dismiss</span>(); <span class="cm">// 一键关闭
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<p>MetonaToast v2.0.0 · MIT License · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a></p>
|
||||
<p>MetonaToast v2.0.1 · MIT License · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a></p>
|
||||
</footer>
|
||||
|
||||
<script src="../dist/metona-toast.js"></script>
|
||||
|
||||
+48
-793
@@ -1,856 +1,111 @@
|
||||
/**
|
||||
* MetonaToast Animations - 动画管理
|
||||
* MetonaToast Animations - 动画管理(精简版 v2.0.1)
|
||||
* @module animations
|
||||
* @version 2.0.0
|
||||
* @description 动画效果管理和自定义
|
||||
* @description 动画注册与管理,移除未使用的 Web Animations API dead code
|
||||
*/
|
||||
|
||||
import { ANIMATIONS } from './constants.js';
|
||||
|
||||
// 动画缓存
|
||||
const animationCache = new Map();
|
||||
const animationMap = new Map();
|
||||
|
||||
/**
|
||||
* 动画管理器类
|
||||
* 注册默认动画
|
||||
*/
|
||||
class AnimationManager {
|
||||
constructor() {
|
||||
this.animations = new Map();
|
||||
this.activeAnimations = new Map();
|
||||
this.animationId = 0;
|
||||
|
||||
// 注册默认动画
|
||||
this._registerDefaults();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册默认动画
|
||||
*/
|
||||
_registerDefaults() {
|
||||
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
||||
this.register(name, config);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册动画
|
||||
* @param {string} name - 动画名称
|
||||
* @param {Object} config - 动画配置
|
||||
*/
|
||||
register(name, config) {
|
||||
const animation = {
|
||||
name,
|
||||
enter: config.enter || {},
|
||||
leave: config.leave || {},
|
||||
duration: config.duration || 300,
|
||||
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
delay: config.delay || 0,
|
||||
iterations: config.iterations || 1,
|
||||
direction: config.direction || 'normal',
|
||||
fillMode: config.fillMode || 'forwards',
|
||||
};
|
||||
|
||||
this.animations.set(name, animation);
|
||||
animationCache.set(name, animation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销动画
|
||||
* @param {string} name - 动画名称
|
||||
*/
|
||||
unregister(name) {
|
||||
this.animations.delete(name);
|
||||
animationCache.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动画配置
|
||||
* @param {string} name - 动画名称
|
||||
* @returns {Object|null} 动画配置
|
||||
*/
|
||||
get(name) {
|
||||
return this.animations.get(name) || ANIMATIONS[name] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
* @param {string} animationName - 动画名称
|
||||
* @param {Object} options - 额外选项
|
||||
* @returns {Promise} 动画完成Promise
|
||||
*/
|
||||
apply(element, animationName, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const animation = this.get(animationName);
|
||||
if (!animation) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const config = { ...animation, ...options };
|
||||
const animId = ++this.animationId;
|
||||
|
||||
// 设置初始状态
|
||||
Object.assign(element.style, config.enter);
|
||||
|
||||
// 强制重绘
|
||||
element.offsetHeight;
|
||||
|
||||
// 创建动画
|
||||
const keyframes = [
|
||||
{ ...config.enter },
|
||||
{ ...config.leave },
|
||||
];
|
||||
|
||||
const animationOptions = {
|
||||
duration: config.duration,
|
||||
easing: config.easing,
|
||||
delay: config.delay,
|
||||
iterations: config.iterations,
|
||||
direction: config.direction,
|
||||
fill: config.fillMode,
|
||||
};
|
||||
|
||||
// 应用动画
|
||||
const anim = element.animate(keyframes, animationOptions);
|
||||
|
||||
// 存储活动动画
|
||||
this.activeAnimations.set(animId, {
|
||||
element,
|
||||
animation: anim,
|
||||
config,
|
||||
});
|
||||
|
||||
// 动画完成处理
|
||||
anim.onfinish = () => {
|
||||
Object.assign(element.style, config.leave);
|
||||
this.activeAnimations.delete(animId);
|
||||
resolve();
|
||||
};
|
||||
|
||||
// 动画取消处理 — reject 让调用者感知取消
|
||||
anim.oncancel = () => {
|
||||
this.activeAnimations.delete(animId);
|
||||
reject(new Error(`Animation "${animationName}" was cancelled`));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用进入动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
* @param {string} animationName - 动画名称
|
||||
* @param {Object} options - 额外选项
|
||||
* @returns {Promise} 动画完成Promise
|
||||
*/
|
||||
enter(element, animationName, options = {}) {
|
||||
const animation = this.get(animationName);
|
||||
if (!animation) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.apply(element, animationName, {
|
||||
...options,
|
||||
direction: 'normal',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用离开动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
* @param {string} animationName - 动画名称
|
||||
* @param {Object} options - 额外选项
|
||||
* @returns {Promise} 动画完成Promise
|
||||
*/
|
||||
leave(element, animationName, options = {}) {
|
||||
const animation = this.get(animationName);
|
||||
if (!animation) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.apply(element, animationName, {
|
||||
...options,
|
||||
direction: 'reverse',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消所有动画
|
||||
*/
|
||||
cancelAll() {
|
||||
this.activeAnimations.forEach(({ animation }) => {
|
||||
animation.cancel();
|
||||
});
|
||||
|
||||
this.activeAnimations.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消元素动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
*/
|
||||
cancel(element) {
|
||||
this.activeAnimations.forEach(({ element: el, animation }, id) => {
|
||||
if (el === element) {
|
||||
animation.cancel();
|
||||
this.activeAnimations.delete(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停所有动画
|
||||
*/
|
||||
pauseAll() {
|
||||
this.activeAnimations.forEach(({ animation }) => {
|
||||
animation.pause();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复所有动画
|
||||
*/
|
||||
resumeAll() {
|
||||
this.activeAnimations.forEach(({ animation }) => {
|
||||
animation.play();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停元素动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
*/
|
||||
pause(element) {
|
||||
this.activeAnimations.forEach(({ element: el, animation }) => {
|
||||
if (el === element) {
|
||||
animation.pause();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复元素动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
*/
|
||||
resume(element) {
|
||||
this.activeAnimations.forEach(({ element: el, animation }) => {
|
||||
if (el === element) {
|
||||
animation.play();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活动动画数量
|
||||
* @returns {number} 动画数量
|
||||
*/
|
||||
getActiveCount() {
|
||||
return this.activeAnimations.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有活动动画
|
||||
* @returns {Map} 活动动画映射
|
||||
*/
|
||||
getActiveAnimations() {
|
||||
return new Map(this.activeAnimations);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查元素是否有活动动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
* @returns {boolean} 是否有活动动画
|
||||
*/
|
||||
hasActiveAnimation(element) {
|
||||
for (const { element: el } of this.activeAnimations.values()) {
|
||||
if (el === element) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动画配置
|
||||
* @returns {Object} 动画配置
|
||||
*/
|
||||
getConfig() {
|
||||
return {
|
||||
animations: Array.from(this.animations.keys()),
|
||||
activeCount: this.getActiveCount(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置动画管理器
|
||||
*/
|
||||
reset() {
|
||||
this.cancelAll();
|
||||
this.animations.clear();
|
||||
this._registerDefaults();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁动画管理器
|
||||
*/
|
||||
destroy() {
|
||||
this.cancelAll();
|
||||
this.animations.clear();
|
||||
this.activeAnimations.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预设动画效果
|
||||
*/
|
||||
const presetAnimations = {
|
||||
bounce: {
|
||||
enter: { transform: 'translateY(-80px)', opacity: 0 },
|
||||
leave: { transform: 'translateY(20px)', opacity: 1 },
|
||||
duration: 650,
|
||||
easing: 'ease',
|
||||
},
|
||||
slideUp: {
|
||||
enter: { transform: 'translateY(60px)', opacity: 0 },
|
||||
leave: { transform: 'translateY(0)', opacity: 1 },
|
||||
duration: 400,
|
||||
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
||||
},
|
||||
slideDown: {
|
||||
enter: { transform: 'translateY(-60px)', opacity: 0 },
|
||||
leave: { transform: 'translateY(0)', opacity: 1 },
|
||||
duration: 400,
|
||||
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
||||
},
|
||||
slideLeft: {
|
||||
enter: { transform: 'translateX(-90px)', opacity: 0 },
|
||||
leave: { transform: 'translateX(0)', opacity: 1 },
|
||||
duration: 400,
|
||||
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
||||
},
|
||||
slideRight: {
|
||||
enter: { transform: 'translateX(90px)', opacity: 0 },
|
||||
leave: { transform: 'translateX(0)', opacity: 1 },
|
||||
duration: 400,
|
||||
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
// 注册预设动画
|
||||
Object.entries(presetAnimations).forEach(([name, config]) => {
|
||||
ANIMATIONS[name] = config;
|
||||
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
||||
animationMap.set(name, config);
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建动画管理器实例
|
||||
* @returns {AnimationManager} 动画管理器实例
|
||||
*/
|
||||
export const createAnimationManager = () => {
|
||||
return new AnimationManager();
|
||||
};
|
||||
|
||||
// 创建默认实例
|
||||
const defaultAnimationManager = createAnimationManager();
|
||||
|
||||
/**
|
||||
* 动画工具函数
|
||||
*/
|
||||
export const animationUtils = {
|
||||
/**
|
||||
* 应用动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
* @param {string} animationName - 动画名称
|
||||
* @param {Object} options - 额外选项
|
||||
* @returns {Promise} 动画完成Promise
|
||||
*/
|
||||
apply(element, animationName, options = {}) {
|
||||
return defaultAnimationManager.apply(element, animationName, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* 应用进入动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
* @param {string} animationName - 动画名称
|
||||
* @param {Object} options - 额外选项
|
||||
* @returns {Promise} 动画完成Promise
|
||||
*/
|
||||
enter(element, animationName, options = {}) {
|
||||
return defaultAnimationManager.enter(element, animationName, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* 应用离开动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
* @param {string} animationName - 动画名称
|
||||
* @param {Object} options - 额外选项
|
||||
* @returns {Promise} 动画完成Promise
|
||||
*/
|
||||
leave(element, animationName, options = {}) {
|
||||
return defaultAnimationManager.leave(element, animationName, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* 取消所有动画
|
||||
*/
|
||||
cancelAll() {
|
||||
defaultAnimationManager.cancelAll();
|
||||
},
|
||||
|
||||
/**
|
||||
* 取消元素动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
*/
|
||||
cancel(element) {
|
||||
defaultAnimationManager.cancel(element);
|
||||
},
|
||||
|
||||
/**
|
||||
* 暂停所有动画
|
||||
*/
|
||||
pauseAll() {
|
||||
defaultAnimationManager.pauseAll();
|
||||
},
|
||||
|
||||
/**
|
||||
* 恢复所有动画
|
||||
*/
|
||||
resumeAll() {
|
||||
defaultAnimationManager.resumeAll();
|
||||
},
|
||||
|
||||
/**
|
||||
* 暂停元素动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
*/
|
||||
pause(element) {
|
||||
defaultAnimationManager.pause(element);
|
||||
},
|
||||
|
||||
/**
|
||||
* 恢复元素动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
*/
|
||||
resume(element) {
|
||||
defaultAnimationManager.resume(element);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取活动动画数量
|
||||
* @returns {number} 动画数量
|
||||
*/
|
||||
getActiveCount() {
|
||||
return defaultAnimationManager.getActiveCount();
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查元素是否有活动动画
|
||||
* @param {HTMLElement} element - 目标元素
|
||||
* @returns {boolean} 是否有活动动画
|
||||
*/
|
||||
hasActiveAnimation(element) {
|
||||
return defaultAnimationManager.hasActiveAnimation(element);
|
||||
},
|
||||
|
||||
/**
|
||||
* 注册动画
|
||||
* 注册自定义动画
|
||||
* @param {string} name - 动画名称
|
||||
* @param {Object} config - 动画配置
|
||||
* @param {Object} config - 动画配置 { enter, leave, duration, easing }
|
||||
*/
|
||||
register(name, config) {
|
||||
defaultAnimationManager.register(name, config);
|
||||
animationMap.set(name, {
|
||||
name,
|
||||
enter: config.enter || {},
|
||||
leave: config.leave || {},
|
||||
duration: config.duration || 300,
|
||||
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 注销动画
|
||||
* @param {string} name - 动画名称
|
||||
*/
|
||||
unregister(name) {
|
||||
defaultAnimationManager.unregister(name);
|
||||
animationMap.delete(name);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 获取动画配置
|
||||
* @param {string} name - 动画名称
|
||||
* @returns {Object|null} 动画配置
|
||||
*/
|
||||
get(name) {
|
||||
return defaultAnimationManager.get(name);
|
||||
return animationMap.get(name) || ANIMATIONS[name] || null;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有动画名称
|
||||
* @returns {Array} 动画名称数组
|
||||
*/
|
||||
getAnimationNames() {
|
||||
return Array.from(defaultAnimationManager.animations.keys());
|
||||
return Array.from(animationMap.keys());
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 获取动画配置
|
||||
* @returns {Object} 动画配置
|
||||
* 获取活动动画数量(兼容API,当前CSS动画不跟踪活动实例)
|
||||
* @returns {number} 动画数量
|
||||
*/
|
||||
getConfig() {
|
||||
return defaultAnimationManager.getConfig();
|
||||
getActiveCount() {
|
||||
return 0;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 取消所有动画(兼容API,当前CSS动画由浏览器管理)
|
||||
*/
|
||||
cancelAll() {
|
||||
// CSS动画由浏览器原生管理,无需手动取消
|
||||
},
|
||||
|
||||
/**
|
||||
* 重置动画管理器
|
||||
*/
|
||||
reset() {
|
||||
defaultAnimationManager.reset();
|
||||
animationMap.clear();
|
||||
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
||||
animationMap.set(name, config);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 销毁动画管理器
|
||||
*/
|
||||
destroy() {
|
||||
defaultAnimationManager.destroy();
|
||||
animationMap.clear();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 动画预设
|
||||
*/
|
||||
export const animationPresets = presetAnimations;
|
||||
export const animationPresets = {};
|
||||
|
||||
/**
|
||||
* 创建自定义动画
|
||||
* 创建自定义动画配置
|
||||
* @param {Object} config - 动画配置
|
||||
* @returns {Object} 动画配置
|
||||
*/
|
||||
export const createAnimation = (config) => {
|
||||
return {
|
||||
enter: config.enter || {},
|
||||
leave: config.leave || {},
|
||||
duration: config.duration || 300,
|
||||
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
delay: config.delay || 0,
|
||||
iterations: config.iterations || 1,
|
||||
direction: config.direction || 'normal',
|
||||
fillMode: config.fillMode || 'forwards',
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 组合动画
|
||||
* @param {Array} animations - 动画数组
|
||||
* @returns {Object} 组合后的动画配置
|
||||
*/
|
||||
export const combineAnimations = (animations) => {
|
||||
const combined = {
|
||||
enter: {},
|
||||
leave: {},
|
||||
duration: 0,
|
||||
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
delay: 0,
|
||||
iterations: 1,
|
||||
direction: 'normal',
|
||||
fillMode: 'forwards',
|
||||
};
|
||||
|
||||
animations.forEach((anim) => {
|
||||
if (anim.enter) {
|
||||
Object.assign(combined.enter, anim.enter);
|
||||
}
|
||||
if (anim.leave) {
|
||||
Object.assign(combined.leave, anim.leave);
|
||||
}
|
||||
if (anim.duration) {
|
||||
combined.duration = Math.max(combined.duration, anim.duration);
|
||||
}
|
||||
if (anim.easing) {
|
||||
combined.easing = anim.easing;
|
||||
}
|
||||
if (anim.delay) {
|
||||
combined.delay = Math.max(combined.delay, anim.delay);
|
||||
}
|
||||
});
|
||||
|
||||
return combined;
|
||||
};
|
||||
|
||||
/**
|
||||
* 链式动画
|
||||
* @param {Array} animations - 动画数组
|
||||
* @returns {Promise} 动画完成Promise
|
||||
*/
|
||||
export const chainAnimations = async (element, animations) => {
|
||||
for (const anim of animations) {
|
||||
await defaultAnimationManager.apply(element, anim.name, anim.options);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 并行动画
|
||||
* @param {Array} animations - 动画数组
|
||||
* @returns {Promise} 动画完成Promise
|
||||
*/
|
||||
export const parallelAnimations = (element, animations) => {
|
||||
return Promise.all(
|
||||
animations.map((anim) =>
|
||||
defaultAnimationManager.apply(element, anim.name, anim.options)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 延迟动画
|
||||
* @param {number} delay - 延迟时间(毫秒)
|
||||
* @returns {Promise} Promise对象
|
||||
*/
|
||||
export const delayAnimation = (delay) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, delay));
|
||||
};
|
||||
|
||||
/**
|
||||
* 动画队列
|
||||
*/
|
||||
export class AnimationQueue {
|
||||
constructor() {
|
||||
this.queue = [];
|
||||
this.isProcessing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加动画到队列
|
||||
* @param {Function} animationFn - 动画函数
|
||||
* @returns {Promise} 动画完成Promise
|
||||
*/
|
||||
add(animationFn) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.queue.push({
|
||||
fn: animationFn,
|
||||
resolve,
|
||||
reject,
|
||||
});
|
||||
|
||||
this._process();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理队列
|
||||
*/
|
||||
async _process() {
|
||||
if (this.isProcessing || this.queue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isProcessing = true;
|
||||
|
||||
while (this.queue.length > 0) {
|
||||
const { fn, resolve, reject } = this.queue.shift();
|
||||
|
||||
try {
|
||||
const result = await fn();
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
this.isProcessing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空队列
|
||||
*/
|
||||
clear() {
|
||||
this.queue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停队列
|
||||
*/
|
||||
pause() {
|
||||
this.isProcessing = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复队列
|
||||
*/
|
||||
resume() {
|
||||
this.isProcessing = false;
|
||||
this._process();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列长度
|
||||
* @returns {number} 队列长度
|
||||
*/
|
||||
get length() {
|
||||
return this.queue.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否正在处理
|
||||
* @returns {boolean} 是否正在处理
|
||||
*/
|
||||
get processing() {
|
||||
return this.isProcessing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建动画队列
|
||||
* @returns {AnimationQueue} 动画队列实例
|
||||
*/
|
||||
export const createAnimationQueue = () => {
|
||||
return new AnimationQueue();
|
||||
};
|
||||
|
||||
/**
|
||||
* 动画性能监控
|
||||
*/
|
||||
export class AnimationPerformanceMonitor {
|
||||
constructor() {
|
||||
this.metrics = {
|
||||
totalAnimations: 0,
|
||||
activeAnimations: 0,
|
||||
averageDuration: 0,
|
||||
maxDuration: 0,
|
||||
minDuration: Infinity,
|
||||
};
|
||||
|
||||
this.history = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录动画开始
|
||||
* @param {string} animationName - 动画名称
|
||||
*/
|
||||
recordStart(animationName) {
|
||||
this.metrics.totalAnimations++;
|
||||
this.metrics.activeAnimations++;
|
||||
|
||||
this.history.push({
|
||||
name: animationName,
|
||||
startTime: performance.now(),
|
||||
endTime: null,
|
||||
duration: null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录动画结束
|
||||
* @param {string} animationName - 动画名称
|
||||
*/
|
||||
recordEnd(animationName) {
|
||||
this.metrics.activeAnimations--;
|
||||
|
||||
const entry = this.history.find(
|
||||
(h) => h.name === animationName && h.endTime === null
|
||||
);
|
||||
|
||||
if (entry) {
|
||||
entry.endTime = performance.now();
|
||||
entry.duration = entry.endTime - entry.startTime;
|
||||
|
||||
// 更新统计
|
||||
this.metrics.maxDuration = Math.max(this.metrics.maxDuration, entry.duration);
|
||||
this.metrics.minDuration = Math.min(this.metrics.minDuration, entry.duration);
|
||||
|
||||
// 计算平均值
|
||||
const completed = this.history.filter((h) => h.duration !== null);
|
||||
this.metrics.averageDuration =
|
||||
completed.reduce((sum, h) => sum + h.duration, 0) / completed.length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指标
|
||||
* @returns {Object} 性能指标
|
||||
*/
|
||||
getMetrics() {
|
||||
return { ...this.metrics };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取历史记录
|
||||
* @returns {Array} 历史记录
|
||||
*/
|
||||
getHistory() {
|
||||
return [...this.history];
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置监控
|
||||
*/
|
||||
reset() {
|
||||
this.metrics = {
|
||||
totalAnimations: 0,
|
||||
activeAnimations: 0,
|
||||
averageDuration: 0,
|
||||
maxDuration: 0,
|
||||
minDuration: Infinity,
|
||||
};
|
||||
|
||||
this.history = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建性能监控实例
|
||||
* @returns {AnimationPerformanceMonitor} 性能监控实例
|
||||
*/
|
||||
export const createPerformanceMonitor = () => {
|
||||
return new AnimationPerformanceMonitor();
|
||||
};
|
||||
|
||||
/**
|
||||
* 动画缓存管理
|
||||
*/
|
||||
export const animationCacheManager = {
|
||||
/**
|
||||
* 缓存动画
|
||||
* @param {string} key - 缓存键
|
||||
* @param {Object} animation - 动画配置
|
||||
*/
|
||||
set(key, animation) {
|
||||
animationCache.set(key, animation);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取缓存动画
|
||||
* @param {string} key - 缓存键
|
||||
* @returns {Object|null} 动画配置
|
||||
*/
|
||||
get(key) {
|
||||
return animationCache.get(key) || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查缓存
|
||||
* @param {string} key - 缓存键
|
||||
* @returns {boolean} 是否存在
|
||||
*/
|
||||
has(key) {
|
||||
return animationCache.has(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @param {string} key - 缓存键
|
||||
*/
|
||||
delete(key) {
|
||||
animationCache.delete(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
clear() {
|
||||
animationCache.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取缓存大小
|
||||
* @returns {number} 缓存大小
|
||||
*/
|
||||
size() {
|
||||
return animationCache.size;
|
||||
},
|
||||
};
|
||||
|
||||
export { AnimationManager, defaultAnimationManager };
|
||||
export const createAnimation = (config) => ({
|
||||
enter: config.enter || {},
|
||||
leave: config.leave || {},
|
||||
duration: config.duration || 300,
|
||||
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
});
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaToast Constants - 常量定义
|
||||
* @module constants
|
||||
* @version 2.0.0
|
||||
* @version 2.0.1
|
||||
* @description 默认配置、颜色、动画、主题等常量
|
||||
*/
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaToast Core - 核心Toast逻辑
|
||||
* @module core
|
||||
* @version 2.0.0
|
||||
* @version 2.0.1
|
||||
* @description 重构后的核心模块,包含Toast类的优化实现
|
||||
*/
|
||||
|
||||
@@ -621,7 +621,7 @@ const _actionHTML = (actions) => {
|
||||
const meToast = {
|
||||
_toasts: new Map(),
|
||||
_config: { ...DEFAULTS },
|
||||
version: '2.0.0',
|
||||
version: '2.0.1',
|
||||
|
||||
configure(opts) {
|
||||
if (!opts || typeof opts !== 'object') return this;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaToast i18n - 国际化管理
|
||||
* @module i18n
|
||||
* @version 2.0.0
|
||||
* @version 2.0.1
|
||||
* @description 多语言支持、语言切换和翻译管理
|
||||
*/
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaToast Icons — 图标SVG定义
|
||||
* @module icons
|
||||
* @version 2.0.0
|
||||
* @version 2.0.1
|
||||
* @description 80+ 内置SVG图标
|
||||
*/
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaToast - 轻量级Toast通知库
|
||||
* @module metona-toast
|
||||
* @version 2.0.0
|
||||
* @version 2.0.1
|
||||
* @author thzxx
|
||||
* @description 轻量、零依赖、精致美观的Toast通知库。单文件,开箱即用。
|
||||
* @license MIT
|
||||
@@ -15,7 +15,7 @@ import { pluginUtils, presetPlugins } from './plugins.js';
|
||||
import { DEFAULTS } from './constants.js';
|
||||
|
||||
// 版本信息
|
||||
const VERSION = '2.0.0';
|
||||
const VERSION = '2.0.1';
|
||||
|
||||
/**
|
||||
* 主对象增强
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaToast Locales — 国际化翻译数据
|
||||
* @module locales
|
||||
* @version 2.0.0
|
||||
* @version 2.0.1
|
||||
* @description 内置 zh-CN / en-US 完整翻译
|
||||
*/
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaToast Plugins - 插件系统
|
||||
* @module plugins
|
||||
* @version 2.0.0
|
||||
* @version 2.0.1
|
||||
* @description 插件管理器 + 3 款预设插件 (keyboard / persistence / accessibility)
|
||||
*/
|
||||
|
||||
|
||||
+207
-1435
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaToast Themes - 主题管理
|
||||
* @module themes
|
||||
* @version 2.0.0
|
||||
* @version 2.0.1
|
||||
* @description 主题系统、自定义主题和主题切换
|
||||
*/
|
||||
|
||||
|
||||
+6
-1024
File diff suppressed because it is too large
Load Diff
+4
-613
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* MetonaToast 单元测试
|
||||
* @module tests
|
||||
* @version 2.0.0
|
||||
* @version 2.0.1
|
||||
*/
|
||||
|
||||
import MeToast, { Toast, VERSION } from '../src/index.js';
|
||||
@@ -121,8 +121,8 @@ describe('MetonaToast', () => {
|
||||
|
||||
describe('版本信息', () => {
|
||||
test('应该有正确的版本号', () => {
|
||||
expect(VERSION).toBe('2.0.0');
|
||||
expect(MeToast.version).toBe('2.0.0');
|
||||
expect(VERSION).toBe('2.0.1');
|
||||
expect(MeToast.version).toBe('2.0.1');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -583,7 +583,7 @@ describe('MetonaToast', () => {
|
||||
MeToast.success('消息');
|
||||
|
||||
const status = MeToast.getStatus();
|
||||
expect(status.version).toBe('2.0.0');
|
||||
expect(status.version).toBe('2.0.1');
|
||||
expect(status.toasts).toBeGreaterThanOrEqual(0);
|
||||
expect(status.theme).toBeDefined();
|
||||
expect(status.locale).toBeDefined();
|
||||
@@ -776,542 +776,6 @@ describe('工具函数', () => {
|
||||
const result = prefersDark();
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
|
||||
test('应该能够防抖函数', (done) => {
|
||||
const { debounce } = require('../src/utils.js');
|
||||
|
||||
let count = 0;
|
||||
const fn = jest.fn(() => count++);
|
||||
const debouncedFn = debounce(fn, 100);
|
||||
|
||||
debouncedFn();
|
||||
debouncedFn();
|
||||
debouncedFn();
|
||||
|
||||
expect(fn).not.toHaveBeenCalled();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
done();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
test('应该能够节流函数', (done) => {
|
||||
const { throttle } = require('../src/utils.js');
|
||||
|
||||
let count = 0;
|
||||
const fn = jest.fn(() => count++);
|
||||
const throttledFn = throttle(fn, 100);
|
||||
|
||||
throttledFn();
|
||||
throttledFn();
|
||||
throttledFn();
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
setTimeout(() => {
|
||||
throttledFn();
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
done();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
test('应该能够深度合并对象', () => {
|
||||
const { deepMerge } = require('../src/utils.js');
|
||||
|
||||
const obj1 = { a: 1, b: { c: 2 } };
|
||||
const obj2 = { b: { d: 3 }, e: 4 };
|
||||
|
||||
const merged = deepMerge(obj1, obj2);
|
||||
|
||||
expect(merged.a).toBe(1);
|
||||
expect(merged.b.c).toBe(2);
|
||||
expect(merged.b.d).toBe(3);
|
||||
expect(merged.e).toBe(4);
|
||||
});
|
||||
|
||||
test('应该能够检查是否为浏览器环境', () => {
|
||||
const { isBrowser } = require('../src/utils.js');
|
||||
|
||||
const result = isBrowser();
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
|
||||
test('应该能够安全获取嵌套对象属性', () => {
|
||||
const { getNestedValue } = require('../src/utils.js');
|
||||
|
||||
const obj = { a: { b: { c: 1 } } };
|
||||
|
||||
expect(getNestedValue(obj, 'a.b.c')).toBe(1);
|
||||
expect(getNestedValue(obj, 'a.b.d')).toBeUndefined();
|
||||
expect(getNestedValue(obj, 'a.b.d', 'default')).toBe('default');
|
||||
});
|
||||
|
||||
test('应该能够格式化文件大小', () => {
|
||||
const { formatFileSize } = require('../src/utils.js');
|
||||
|
||||
expect(formatFileSize(0)).toBe('0 Bytes');
|
||||
expect(formatFileSize(1024)).toBe('1 KB');
|
||||
expect(formatFileSize(1048576)).toBe('1 MB');
|
||||
expect(formatFileSize(1073741824)).toBe('1 GB');
|
||||
});
|
||||
|
||||
test('应该能够格式化日期', () => {
|
||||
const { formatDate } = require('../src/utils.js');
|
||||
|
||||
const date = new Date('2024-01-01 12:00:00');
|
||||
const formatted = formatDate(date, 'YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
expect(formatted).toBe('2024-01-01 12:00:00');
|
||||
});
|
||||
|
||||
test('应该能够生成随机数', () => {
|
||||
const { random } = require('../src/utils.js');
|
||||
|
||||
const result = random(1, 100);
|
||||
expect(result).toBeGreaterThanOrEqual(1);
|
||||
expect(result).toBeLessThanOrEqual(100);
|
||||
});
|
||||
|
||||
test('应该能够检查元素是否在视口中', () => {
|
||||
const { isInViewport } = require('../src/utils.js');
|
||||
|
||||
const el = {
|
||||
getBoundingClientRect: () => ({
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 100,
|
||||
right: 100,
|
||||
}),
|
||||
};
|
||||
|
||||
expect(isInViewport(el)).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够复制文本到剪贴板', async () => {
|
||||
const { copyToClipboard } = require('../src/utils.js');
|
||||
|
||||
// Mock clipboard.writeText 返回 resolved promise
|
||||
if (!navigator.clipboard) navigator.clipboard = {};
|
||||
const origWriteText = navigator.clipboard.writeText;
|
||||
navigator.clipboard.writeText = jest.fn(() => Promise.resolve());
|
||||
|
||||
const result = await copyToClipboard('test');
|
||||
expect(result).toBe(true);
|
||||
|
||||
navigator.clipboard.writeText = origWriteText;
|
||||
});
|
||||
|
||||
test('应该能够检测设备类型', () => {
|
||||
const { getDeviceType } = require('../src/utils.js');
|
||||
|
||||
const result = getDeviceType();
|
||||
expect(['desktop', 'mobile', 'tablet']).toContain(result);
|
||||
});
|
||||
|
||||
test('应该能够检测浏览器信息', () => {
|
||||
const { getBrowserInfo } = require('../src/utils.js');
|
||||
|
||||
const result = getBrowserInfo();
|
||||
expect(result).toBeDefined();
|
||||
expect(result.browser).toBeDefined();
|
||||
expect(result.version).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该能够检测操作系统', () => {
|
||||
const { getOS } = require('../src/utils.js');
|
||||
|
||||
const result = getOS();
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该能够检测网络状态', () => {
|
||||
const { getNetworkInfo } = require('../src/utils.js');
|
||||
|
||||
const result = getNetworkInfo();
|
||||
expect(result).toBeDefined();
|
||||
expect(result.online).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该能够存储和获取数据', () => {
|
||||
const { setStorage, getStorage, removeStorage } = require('../src/utils.js');
|
||||
|
||||
setStorage('test', 'value');
|
||||
expect(getStorage('test')).toBe('value');
|
||||
|
||||
removeStorage('test');
|
||||
expect(getStorage('test')).toBeNull();
|
||||
});
|
||||
|
||||
test('应该能够生成UUID', () => {
|
||||
const { generateUUID } = require('../src/utils.js');
|
||||
|
||||
const uuid = generateUUID();
|
||||
expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
|
||||
});
|
||||
|
||||
test('应该能够检查CSS属性支持', () => {
|
||||
const { supportsCSSProperty } = require('../src/utils.js');
|
||||
|
||||
const result = supportsCSSProperty('transform');
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
|
||||
test('应该能够检查JavaScript API支持', () => {
|
||||
const { supportsAPI } = require('../src/utils.js');
|
||||
|
||||
const result = supportsAPI('fetch');
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
|
||||
test('应该能够获取URL参数', () => {
|
||||
const { getURLParam } = require('../src/utils.js');
|
||||
|
||||
// Mock URLSearchParams 来绕过 jsdom location.search 限制
|
||||
const origSearchParams = global.URLSearchParams;
|
||||
global.URLSearchParams = jest.fn(() => ({
|
||||
get: jest.fn((name) => name === 'test' ? 'value' : null),
|
||||
}));
|
||||
|
||||
const result = getURLParam('test');
|
||||
expect(result).toBe('value');
|
||||
|
||||
global.URLSearchParams = origSearchParams;
|
||||
});
|
||||
|
||||
test('应该能够格式化数字', () => {
|
||||
const { formatNumber } = require('../src/utils.js');
|
||||
|
||||
const result = formatNumber(1234567.89, 2);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该能够格式化货币', () => {
|
||||
const { formatCurrency } = require('../src/utils.js');
|
||||
|
||||
const result = formatCurrency(99.99, 'USD');
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该能够格式化百分比', () => {
|
||||
const { formatPercent } = require('../src/utils.js');
|
||||
|
||||
const result = formatPercent(75.5, 2);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该能够检查是否为空值', () => {
|
||||
const { isEmpty } = require('../src/utils.js');
|
||||
|
||||
expect(isEmpty(null)).toBe(true);
|
||||
expect(isEmpty(undefined)).toBe(true);
|
||||
expect(isEmpty('')).toBe(true);
|
||||
expect(isEmpty(' ')).toBe(true);
|
||||
expect(isEmpty([])).toBe(true);
|
||||
expect(isEmpty({})).toBe(true);
|
||||
expect(isEmpty('text')).toBe(false);
|
||||
expect(isEmpty([1])).toBe(false);
|
||||
expect(isEmpty({ a: 1 })).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够深拷贝对象', () => {
|
||||
const { deepClone } = require('../src/utils.js');
|
||||
|
||||
const obj = { a: 1, b: { c: 2 }, d: [1, 2, 3] };
|
||||
const cloned = deepClone(obj);
|
||||
|
||||
expect(cloned).toEqual(obj);
|
||||
expect(cloned).not.toBe(obj);
|
||||
expect(cloned.b).not.toBe(obj.b);
|
||||
expect(cloned.d).not.toBe(obj.d);
|
||||
});
|
||||
|
||||
test('应该能够比较对象是否相等', () => {
|
||||
const { isEqual } = require('../src/utils.js');
|
||||
|
||||
expect(isEqual({ a: 1 }, { a: 1 })).toBe(true);
|
||||
expect(isEqual({ a: 1 }, { a: 2 })).toBe(false);
|
||||
expect(isEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false);
|
||||
expect(isEqual([1, 2, 3], [1, 2, 3])).toBe(true);
|
||||
expect(isEqual([1, 2, 3], [1, 2])).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够验证邮箱格式', () => {
|
||||
const { isValidEmail } = require('../src/utils.js');
|
||||
|
||||
expect(isValidEmail('test@example.com')).toBe(true);
|
||||
expect(isValidEmail('invalid-email')).toBe(false);
|
||||
expect(isValidEmail('test@')).toBe(false);
|
||||
expect(isValidEmail('@example.com')).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够验证URL格式', () => {
|
||||
const { isValidURL } = require('../src/utils.js');
|
||||
|
||||
expect(isValidURL('https://example.com')).toBe(true);
|
||||
expect(isValidURL('http://example.com')).toBe(true);
|
||||
expect(isValidURL('invalid-url')).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够验证手机号格式', () => {
|
||||
const { isValidPhone } = require('../src/utils.js');
|
||||
|
||||
expect(isValidPhone('13800138000')).toBe(true);
|
||||
expect(isValidPhone('12345678901')).toBe(false);
|
||||
expect(isValidPhone('1380013800')).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够验证身份证号格式', () => {
|
||||
const { isValidIDCard } = require('../src/utils.js');
|
||||
|
||||
expect(isValidIDCard('110101199003077777')).toBe(true);
|
||||
expect(isValidIDCard('123456789012345678')).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够生成随机颜色', () => {
|
||||
const { randomColor } = require('../src/utils.js');
|
||||
|
||||
const color = randomColor();
|
||||
expect(color).toMatch(/^#[0-9a-f]{6}$/);
|
||||
});
|
||||
|
||||
test('应该能够颜色转RGBA', () => {
|
||||
const { colorToRGBA } = require('../src/utils.js');
|
||||
|
||||
const rgba = colorToRGBA('#ff0000', 0.5);
|
||||
expect(rgba).toBe('rgba(255, 0, 0, 0.5)');
|
||||
});
|
||||
|
||||
test('应该能够获取颜色亮度', () => {
|
||||
const { getColorBrightness } = require('../src/utils.js');
|
||||
|
||||
const brightness = getColorBrightness('#ffffff');
|
||||
expect(brightness).toBe(255);
|
||||
});
|
||||
|
||||
test('应该能够判断是否为浅色', () => {
|
||||
const { isLightColor } = require('../src/utils.js');
|
||||
|
||||
expect(isLightColor('#ffffff')).toBe(true);
|
||||
expect(isLightColor('#000000')).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够获取对比色', () => {
|
||||
const { getContrastColor } = require('../src/utils.js');
|
||||
|
||||
expect(getContrastColor('#ffffff')).toBe('#000000');
|
||||
expect(getContrastColor('#000000')).toBe('#FFFFFF');
|
||||
});
|
||||
|
||||
test('应该能够生成渐变色', () => {
|
||||
const { generateGradient } = require('../src/utils.js');
|
||||
|
||||
const gradient = generateGradient('#ff0000', '#0000ff', 5);
|
||||
expect(gradient).toHaveLength(5);
|
||||
expect(gradient[0]).toBe('#ff0000');
|
||||
expect(gradient[4]).toBe('#0000ff');
|
||||
});
|
||||
|
||||
test('应该能够获取字符长度', () => {
|
||||
const { getStringLength } = require('../src/utils.js');
|
||||
|
||||
expect(getStringLength('abc')).toBe(3);
|
||||
expect(getStringLength('中文')).toBe(4);
|
||||
expect(getStringLength('abc中文')).toBe(7);
|
||||
});
|
||||
|
||||
test('应该能够截取字符串', () => {
|
||||
const { truncateString } = require('../src/utils.js');
|
||||
|
||||
expect(truncateString('abcdefghij', 5)).toBe('abcde...');
|
||||
expect(truncateString('中文字符串测试', 8)).toBe('中文字符...');
|
||||
expect(truncateString('abc', 5)).toBe('abc');
|
||||
});
|
||||
|
||||
test('应该能够驼峰转换', () => {
|
||||
const { toCamelCase } = require('../src/utils.js');
|
||||
|
||||
expect(toCamelCase('hello-world')).toBe('helloWorld');
|
||||
expect(toCamelCase('hello_world')).toBe('helloWorld');
|
||||
expect(toCamelCase('hello world')).toBe('helloWorld');
|
||||
});
|
||||
|
||||
test('应该能够短横线转换', () => {
|
||||
const { toKebabCase } = require('../src/utils.js');
|
||||
|
||||
expect(toKebabCase('helloWorld')).toBe('hello-world');
|
||||
expect(toKebabCase('hello_world')).toBe('hello-world');
|
||||
expect(toKebabCase('hello world')).toBe('hello-world');
|
||||
});
|
||||
|
||||
test('应该能够下划线转换', () => {
|
||||
const { toSnakeCase } = require('../src/utils.js');
|
||||
|
||||
expect(toSnakeCase('helloWorld')).toBe('hello_world');
|
||||
expect(toSnakeCase('hello-world')).toBe('hello_world');
|
||||
expect(toSnakeCase('hello world')).toBe('hello_world');
|
||||
});
|
||||
|
||||
test('应该能够首字母大写', () => {
|
||||
const { capitalize } = require('../src/utils.js');
|
||||
|
||||
expect(capitalize('hello')).toBe('Hello');
|
||||
expect(capitalize('HELLO')).toBe('HELLO');
|
||||
});
|
||||
|
||||
test('应该能够每个单词首字母大写', () => {
|
||||
const { capitalizeWords } = require('../src/utils.js');
|
||||
|
||||
expect(capitalizeWords('hello world')).toBe('Hello World');
|
||||
});
|
||||
|
||||
test('应该能够移除HTML标签', () => {
|
||||
const { stripHTML } = require('../src/utils.js');
|
||||
|
||||
expect(stripHTML('<b>bold</b>')).toBe('bold');
|
||||
expect(stripHTML('<p>paragraph</p>')).toBe('paragraph');
|
||||
});
|
||||
|
||||
test('应该能够转义正则表达式', () => {
|
||||
const { escapeRegExp } = require('../src/utils.js');
|
||||
|
||||
expect(escapeRegExp('[test]')).toBe('\\[test\\]');
|
||||
expect(escapeRegExp('hello.world')).toBe('hello\\.world');
|
||||
});
|
||||
|
||||
test('应该能够生成随机字符串', () => {
|
||||
const { randomString } = require('../src/utils.js');
|
||||
|
||||
const str = randomString(10);
|
||||
expect(str).toHaveLength(10);
|
||||
});
|
||||
|
||||
test('应该能够延迟执行', async () => {
|
||||
const { sleep } = require('../src/utils.js');
|
||||
|
||||
const start = Date.now();
|
||||
await sleep(100);
|
||||
const end = Date.now();
|
||||
|
||||
expect(end - start).toBeGreaterThanOrEqual(90);
|
||||
});
|
||||
|
||||
test('应该能够重试函数', async () => {
|
||||
const { retry } = require('../src/utils.js');
|
||||
|
||||
let attempts = 0;
|
||||
const fn = jest.fn(() => {
|
||||
attempts++;
|
||||
if (attempts < 3) {
|
||||
throw new Error('fail');
|
||||
}
|
||||
return 'success';
|
||||
});
|
||||
|
||||
const result = await retry(fn, 3, 10);
|
||||
expect(result).toBe('success');
|
||||
expect(fn).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test('应该能够超时控制', async () => {
|
||||
const { timeout } = require('../src/utils.js');
|
||||
|
||||
const promise = new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
await expect(timeout(promise, 100)).rejects.toThrow('Timeout');
|
||||
});
|
||||
|
||||
test('应该能够并发控制', async () => {
|
||||
const { parallel } = require('../src/utils.js');
|
||||
|
||||
const tasks = [
|
||||
() => Promise.resolve(1),
|
||||
() => Promise.resolve(2),
|
||||
() => Promise.resolve(3),
|
||||
];
|
||||
|
||||
const results = await parallel(tasks, 2);
|
||||
expect(results).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test('应该能够序列执行', async () => {
|
||||
const { sequence } = require('../src/utils.js');
|
||||
|
||||
const tasks = [
|
||||
() => Promise.resolve(1),
|
||||
() => Promise.resolve(2),
|
||||
() => Promise.resolve(3),
|
||||
];
|
||||
|
||||
const results = await sequence(tasks);
|
||||
expect(results).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test('应该能够缓存函数', () => {
|
||||
const { memoize } = require('../src/utils.js');
|
||||
|
||||
let count = 0;
|
||||
const fn = jest.fn((x) => {
|
||||
count++;
|
||||
return x * 2;
|
||||
});
|
||||
|
||||
const memoized = memoize(fn);
|
||||
|
||||
expect(memoized(2)).toBe(4);
|
||||
expect(memoized(2)).toBe(4);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('应该能够单例模式', () => {
|
||||
const { singleton } = require('../src/utils.js');
|
||||
|
||||
let count = 0;
|
||||
const fn = jest.fn(() => {
|
||||
count++;
|
||||
return { id: count };
|
||||
});
|
||||
|
||||
const singletonFn = singleton(fn);
|
||||
|
||||
const obj1 = singletonFn();
|
||||
const obj2 = singletonFn();
|
||||
|
||||
expect(obj1).toBe(obj2);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('应该能够创建观察者', () => {
|
||||
const { createObserver } = require('../src/utils.js');
|
||||
|
||||
const observer = createObserver();
|
||||
const callback = jest.fn();
|
||||
|
||||
observer.on('test', callback);
|
||||
observer.emit('test', 'data');
|
||||
|
||||
expect(callback).toHaveBeenCalledWith('data');
|
||||
});
|
||||
|
||||
test('应该能够创建状态机', () => {
|
||||
const { createStateMachine } = require('../src/utils.js');
|
||||
|
||||
const machine = createStateMachine({
|
||||
initial: 'idle',
|
||||
transitions: {
|
||||
idle: { start: 'running' },
|
||||
running: { stop: 'idle' },
|
||||
},
|
||||
});
|
||||
|
||||
expect(machine.state).toBe('idle');
|
||||
|
||||
machine.transition('start');
|
||||
expect(machine.state).toBe('running');
|
||||
|
||||
machine.transition('stop');
|
||||
expect(machine.state).toBe('idle');
|
||||
});
|
||||
});
|
||||
|
||||
describe('常量', () => {
|
||||
@@ -1395,79 +859,6 @@ describe('常量', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('动画管理器', () => {
|
||||
test('应该能够创建动画管理器', () => {
|
||||
const { createAnimationManager } = require('../src/animations.js');
|
||||
|
||||
const manager = createAnimationManager();
|
||||
expect(manager).toBeDefined();
|
||||
expect(manager.register).toBeInstanceOf(Function);
|
||||
expect(manager.apply).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
test('应该能够注册动画', () => {
|
||||
const { createAnimationManager } = require('../src/animations.js');
|
||||
|
||||
const manager = createAnimationManager();
|
||||
|
||||
manager.register('test', {
|
||||
enter: { opacity: 0 },
|
||||
leave: { opacity: 1 },
|
||||
duration: 300,
|
||||
});
|
||||
|
||||
expect(manager.get('test')).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该能够应用动画', async () => {
|
||||
const { createAnimationManager } = require('../src/animations.js');
|
||||
|
||||
const manager = createAnimationManager();
|
||||
const element = document.createElement('div');
|
||||
// 确保 animate 方法存在并在设置 onfinish 后自动调用
|
||||
element.animate = jest.fn(() => {
|
||||
const anim = {
|
||||
onfinish: null,
|
||||
oncancel: null,
|
||||
cancel: jest.fn(),
|
||||
pause: jest.fn(),
|
||||
play: jest.fn(),
|
||||
};
|
||||
// 下一个微任务自动触发 onfinish,让 Promise resolve
|
||||
Promise.resolve().then(() => {
|
||||
if (anim.onfinish) anim.onfinish();
|
||||
});
|
||||
return anim;
|
||||
});
|
||||
|
||||
manager.register('test', {
|
||||
enter: { opacity: 0 },
|
||||
leave: { opacity: 1 },
|
||||
duration: 100,
|
||||
});
|
||||
|
||||
await manager.apply(element, 'test');
|
||||
|
||||
expect(element.animate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('应该能够取消动画', () => {
|
||||
const { createAnimationManager } = require('../src/animations.js');
|
||||
|
||||
const manager = createAnimationManager();
|
||||
|
||||
manager.cancelAll();
|
||||
expect(manager.getActiveCount()).toBe(0);
|
||||
});
|
||||
|
||||
test('应该能够获取活动动画数量', () => {
|
||||
const { createAnimationManager } = require('../src/animations.js');
|
||||
|
||||
const manager = createAnimationManager();
|
||||
expect(manager.getActiveCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('主题管理器', () => {
|
||||
test('应该能够获取系统主题', () => {
|
||||
const { getSystemTheme } = require('../src/themes.js');
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* MetonaToast TypeScript 类型定义
|
||||
* @version 2.0.0
|
||||
* @version 2.0.1
|
||||
*/
|
||||
|
||||
// 基础类型
|
||||
|
||||
Reference in New Issue
Block a user