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",
|
"name": "@metona-team/metona-toast",
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"description": "轻量、零依赖、精致美观的Toast通知库。单文件,开箱即用。",
|
"description": "轻量、零依赖、精致美观的Toast通知库。单文件,开箱即用。",
|
||||||
"main": "dist/metona-toast.js",
|
"main": "dist/metona-toast.js",
|
||||||
"module": "src/index.js",
|
"module": "src/index.js",
|
||||||
|
|||||||
+1
-1
@@ -273,7 +273,7 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<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>
|
</footer>
|
||||||
|
|
||||||
<script src="../dist/metona-toast.js"></script>
|
<script src="../dist/metona-toast.js"></script>
|
||||||
|
|||||||
+1
-1
@@ -87,7 +87,7 @@
|
|||||||
<main class="main">
|
<main class="main">
|
||||||
|
|
||||||
<h1>API 文档</h1>
|
<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>
|
<h2 id="show">show(message, opts?)</h2>
|
||||||
|
|||||||
+1
-1
@@ -237,7 +237,7 @@ orderGroup.<span class="fn">dismiss</span>(); <span class="cm">// 一键关闭
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer>
|
<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>
|
</footer>
|
||||||
|
|
||||||
<script src="../dist/metona-toast.js"></script>
|
<script src="../dist/metona-toast.js"></script>
|
||||||
|
|||||||
+48
-793
@@ -1,856 +1,111 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Animations - 动画管理
|
* MetonaToast Animations - 动画管理(精简版 v2.0.1)
|
||||||
* @module animations
|
* @module animations
|
||||||
* @version 2.0.0
|
* @description 动画注册与管理,移除未使用的 Web Animations API dead code
|
||||||
* @description 动画效果管理和自定义
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ANIMATIONS } from './constants.js';
|
import { ANIMATIONS } from './constants.js';
|
||||||
|
|
||||||
// 动画缓存
|
// 动画缓存
|
||||||
const animationCache = new Map();
|
const animationMap = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 动画管理器类
|
* 注册默认动画
|
||||||
*/
|
*/
|
||||||
class AnimationManager {
|
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
||||||
constructor() {
|
animationMap.set(name, config);
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建动画管理器实例
|
|
||||||
* @returns {AnimationManager} 动画管理器实例
|
|
||||||
*/
|
|
||||||
export const createAnimationManager = () => {
|
|
||||||
return new AnimationManager();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建默认实例
|
|
||||||
const defaultAnimationManager = createAnimationManager();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 动画工具函数
|
* 动画工具函数
|
||||||
*/
|
*/
|
||||||
export const animationUtils = {
|
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 {string} name - 动画名称
|
||||||
* @param {Object} config - 动画配置
|
* @param {Object} config - 动画配置 { enter, leave, duration, easing }
|
||||||
*/
|
*/
|
||||||
register(name, config) {
|
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 - 动画名称
|
* @param {string} name - 动画名称
|
||||||
*/
|
*/
|
||||||
unregister(name) {
|
unregister(name) {
|
||||||
defaultAnimationManager.unregister(name);
|
animationMap.delete(name);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取动画配置
|
* 获取动画配置
|
||||||
* @param {string} name - 动画名称
|
* @param {string} name - 动画名称
|
||||||
* @returns {Object|null} 动画配置
|
* @returns {Object|null} 动画配置
|
||||||
*/
|
*/
|
||||||
get(name) {
|
get(name) {
|
||||||
return defaultAnimationManager.get(name);
|
return animationMap.get(name) || ANIMATIONS[name] || null;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有动画名称
|
* 获取所有动画名称
|
||||||
* @returns {Array} 动画名称数组
|
* @returns {Array} 动画名称数组
|
||||||
*/
|
*/
|
||||||
getAnimationNames() {
|
getAnimationNames() {
|
||||||
return Array.from(defaultAnimationManager.animations.keys());
|
return Array.from(animationMap.keys());
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取动画配置
|
* 获取活动动画数量(兼容API,当前CSS动画不跟踪活动实例)
|
||||||
* @returns {Object} 动画配置
|
* @returns {number} 动画数量
|
||||||
*/
|
*/
|
||||||
getConfig() {
|
getActiveCount() {
|
||||||
return defaultAnimationManager.getConfig();
|
return 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消所有动画(兼容API,当前CSS动画由浏览器管理)
|
||||||
|
*/
|
||||||
|
cancelAll() {
|
||||||
|
// CSS动画由浏览器原生管理,无需手动取消
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置动画管理器
|
* 重置动画管理器
|
||||||
*/
|
*/
|
||||||
reset() {
|
reset() {
|
||||||
defaultAnimationManager.reset();
|
animationMap.clear();
|
||||||
|
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
||||||
|
animationMap.set(name, config);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 销毁动画管理器
|
* 销毁动画管理器
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
defaultAnimationManager.destroy();
|
animationMap.clear();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 动画预设
|
* 动画预设
|
||||||
*/
|
*/
|
||||||
export const animationPresets = presetAnimations;
|
export const animationPresets = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建自定义动画
|
* 创建自定义动画配置
|
||||||
* @param {Object} config - 动画配置
|
* @param {Object} config - 动画配置
|
||||||
* @returns {Object} 动画配置
|
* @returns {Object} 动画配置
|
||||||
*/
|
*/
|
||||||
export const createAnimation = (config) => {
|
export const createAnimation = (config) => ({
|
||||||
return {
|
enter: config.enter || {},
|
||||||
enter: config.enter || {},
|
leave: config.leave || {},
|
||||||
leave: config.leave || {},
|
duration: config.duration || 300,
|
||||||
duration: config.duration || 300,
|
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
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 };
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Constants - 常量定义
|
* MetonaToast Constants - 常量定义
|
||||||
* @module constants
|
* @module constants
|
||||||
* @version 2.0.0
|
* @version 2.0.1
|
||||||
* @description 默认配置、颜色、动画、主题等常量
|
* @description 默认配置、颜色、动画、主题等常量
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Core - 核心Toast逻辑
|
* MetonaToast Core - 核心Toast逻辑
|
||||||
* @module core
|
* @module core
|
||||||
* @version 2.0.0
|
* @version 2.0.1
|
||||||
* @description 重构后的核心模块,包含Toast类的优化实现
|
* @description 重构后的核心模块,包含Toast类的优化实现
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -621,7 +621,7 @@ const _actionHTML = (actions) => {
|
|||||||
const meToast = {
|
const meToast = {
|
||||||
_toasts: new Map(),
|
_toasts: new Map(),
|
||||||
_config: { ...DEFAULTS },
|
_config: { ...DEFAULTS },
|
||||||
version: '2.0.0',
|
version: '2.0.1',
|
||||||
|
|
||||||
configure(opts) {
|
configure(opts) {
|
||||||
if (!opts || typeof opts !== 'object') return this;
|
if (!opts || typeof opts !== 'object') return this;
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast i18n - 国际化管理
|
* MetonaToast i18n - 国际化管理
|
||||||
* @module i18n
|
* @module i18n
|
||||||
* @version 2.0.0
|
* @version 2.0.1
|
||||||
* @description 多语言支持、语言切换和翻译管理
|
* @description 多语言支持、语言切换和翻译管理
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Icons — 图标SVG定义
|
* MetonaToast Icons — 图标SVG定义
|
||||||
* @module icons
|
* @module icons
|
||||||
* @version 2.0.0
|
* @version 2.0.1
|
||||||
* @description 80+ 内置SVG图标
|
* @description 80+ 内置SVG图标
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast - 轻量级Toast通知库
|
* MetonaToast - 轻量级Toast通知库
|
||||||
* @module metona-toast
|
* @module metona-toast
|
||||||
* @version 2.0.0
|
* @version 2.0.1
|
||||||
* @author thzxx
|
* @author thzxx
|
||||||
* @description 轻量、零依赖、精致美观的Toast通知库。单文件,开箱即用。
|
* @description 轻量、零依赖、精致美观的Toast通知库。单文件,开箱即用。
|
||||||
* @license MIT
|
* @license MIT
|
||||||
@@ -15,7 +15,7 @@ import { pluginUtils, presetPlugins } from './plugins.js';
|
|||||||
import { DEFAULTS } from './constants.js';
|
import { DEFAULTS } from './constants.js';
|
||||||
|
|
||||||
// 版本信息
|
// 版本信息
|
||||||
const VERSION = '2.0.0';
|
const VERSION = '2.0.1';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主对象增强
|
* 主对象增强
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Locales — 国际化翻译数据
|
* MetonaToast Locales — 国际化翻译数据
|
||||||
* @module locales
|
* @module locales
|
||||||
* @version 2.0.0
|
* @version 2.0.1
|
||||||
* @description 内置 zh-CN / en-US 完整翻译
|
* @description 内置 zh-CN / en-US 完整翻译
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Plugins - 插件系统
|
* MetonaToast Plugins - 插件系统
|
||||||
* @module plugins
|
* @module plugins
|
||||||
* @version 2.0.0
|
* @version 2.0.1
|
||||||
* @description 插件管理器 + 3 款预设插件 (keyboard / persistence / accessibility)
|
* @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 - 主题管理
|
* MetonaToast Themes - 主题管理
|
||||||
* @module themes
|
* @module themes
|
||||||
* @version 2.0.0
|
* @version 2.0.1
|
||||||
* @description 主题系统、自定义主题和主题切换
|
* @description 主题系统、自定义主题和主题切换
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+6
-1024
File diff suppressed because it is too large
Load Diff
+4
-613
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast 单元测试
|
* MetonaToast 单元测试
|
||||||
* @module tests
|
* @module tests
|
||||||
* @version 2.0.0
|
* @version 2.0.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import MeToast, { Toast, VERSION } from '../src/index.js';
|
import MeToast, { Toast, VERSION } from '../src/index.js';
|
||||||
@@ -121,8 +121,8 @@ describe('MetonaToast', () => {
|
|||||||
|
|
||||||
describe('版本信息', () => {
|
describe('版本信息', () => {
|
||||||
test('应该有正确的版本号', () => {
|
test('应该有正确的版本号', () => {
|
||||||
expect(VERSION).toBe('2.0.0');
|
expect(VERSION).toBe('2.0.1');
|
||||||
expect(MeToast.version).toBe('2.0.0');
|
expect(MeToast.version).toBe('2.0.1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -583,7 +583,7 @@ describe('MetonaToast', () => {
|
|||||||
MeToast.success('消息');
|
MeToast.success('消息');
|
||||||
|
|
||||||
const status = MeToast.getStatus();
|
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.toasts).toBeGreaterThanOrEqual(0);
|
||||||
expect(status.theme).toBeDefined();
|
expect(status.theme).toBeDefined();
|
||||||
expect(status.locale).toBeDefined();
|
expect(status.locale).toBeDefined();
|
||||||
@@ -776,542 +776,6 @@ describe('工具函数', () => {
|
|||||||
const result = prefersDark();
|
const result = prefersDark();
|
||||||
expect(typeof result).toBe('boolean');
|
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('常量', () => {
|
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('主题管理器', () => {
|
describe('主题管理器', () => {
|
||||||
test('应该能够获取系统主题', () => {
|
test('应该能够获取系统主题', () => {
|
||||||
const { getSystemTheme } = require('../src/themes.js');
|
const { getSystemTheme } = require('../src/themes.js');
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast TypeScript 类型定义
|
* MetonaToast TypeScript 类型定义
|
||||||
* @version 2.0.0
|
* @version 2.0.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 基础类型
|
// 基础类型
|
||||||
|
|||||||
Reference in New Issue
Block a user