Add Modular and Manager, merge Logger to core

This commit is contained in:
2021-12-01 10:52:38 +08:00
parent cea68f5145
commit 7fa3a215cf
18 changed files with 875 additions and 535 deletions
+31
View File
@@ -0,0 +1,31 @@
// APP 全局配置文件
/**
* 是否在控制台输出调试信息
* 生产模式下一定要关闭此选项
*
* 注意:
* 1、Release 时必须置为 false
* 2、调试模式将造成性能损失
*/
export const LOGGER_CONSOLE:boolean = true;
/**
* 调试过滤器
* 按照 LogLabel 进行过滤
*
* 注意:
* 1、行与行是之间是 || 关系
* 2、每行的元素之间是 && 关系
* 3、支持正则表达式
* 4、字符串使用 === 严格匹配
* 5、尽量将范围大的 Filter 写在前面以提高性能
*/
export const LOGGER_FILTER:Array<RegExp | string>[] = [
// 调试输出全部内容
[/.*/],
// 输出警告和错误
// ["WARN", "ERROR", "FATAL"],
];
+227
View File
@@ -0,0 +1,227 @@
/**
* 调试输出样式
*/
class LogStyle {
/**
* 日志文字颜色
*/
private color:string | undefined;
/**
* 日志背景颜色
*/
private backgroundColor:string | undefined;
/**
* 日志文字粗细
*/
private weight:string | undefined;
/**
* 日志文字大小
*/
private size:string | undefined;
/**
* 日志文字字体
*/
private family:string | undefined;
/**
* 日志文字圆角
*/
private borderRadius:string | undefined;
/**
* 日志文字边框
*/
private border:string | undefined;
/**
* 日志文字外边距
*/
private margin:string | undefined;
/**
* 日志文字内边距
*/
private padding:string | undefined;
/**
* 设置颜色
* @param color 日志文字颜色
* @param backgroundColor 日志背景颜色
*/
public setColor(color?:string, backgroundColor?:string):LogStyle {
this.color = color ?? this.color;
this.backgroundColor = backgroundColor ?? this.backgroundColor;
return this;
}
/**
* 设置边框
* @param borderRadius 日志文字圆角
* @param border 日志文字边框
*/
public setBorder(borderRadius?:string, border?:string):LogStyle {
this.borderRadius = borderRadius ?? this.borderRadius;
this.border = border ?? this.border;
return this;
}
/**
* 设置文字
* @param weight 日志文字粗细
* @param family 日志文字字体
*/
public setFont(weight?:string, family?:string):LogStyle {
this.weight = weight ?? this.weight;
this.family = family ?? this.family;
return this;
}
/**
* 设置文字大小
* @param size 日志文字大小
*/
public setSize(size?:string):LogStyle {
this.size = size ?? this.size;
return this;
}
/**
* 设置内边距外边距
* @param padding 内边距
* @param margin 外边距
*/
public setBlank(padding?:string, margin?:string):LogStyle {
this.padding = padding ?? this.padding;
this.margin = margin ?? this.margin;
return this;
}
/**
* 字符化转义样式
*/
public stringify():string {
let stringArr:string[] = [];
this.color && stringArr.push(`color:${ this.color }`);
this.backgroundColor && stringArr.push(`background-color:${ this.backgroundColor }`);
this.weight && stringArr.push(`font-weight:${ this.weight }`);
this.family && stringArr.push(`font-family:${ this.family }`);
this.borderRadius && stringArr.push(`border-radius:${ this.borderRadius }`);
this.border && stringArr.push(`border:${ this.border }`);
this.size && stringArr.push(`font-size:${ this.size }`);
this.padding && stringArr.push(`padding:${ this.padding }`);
this.margin && stringArr.push(`margin:${ this.margin }`);
return stringArr.join(";");
}
/**
* 克隆一个新的 LogStyle
*/
public clone():LogStyle {
return new LogStyle()
.setColor(this.color, this.backgroundColor)
.setBorder(this.borderRadius, this.border)
.setFont(this.weight, this.family)
.setBlank(this.padding, this.margin)
.setSize(this.size)
}
}
/**
* 日志标签
*/
class LogLabel {
/**
* 关键字
* 用于标识这个类别
*/
public key:string;
/**
* 文字样式
*/
public style:LogStyle;
/**
* 是否受到过滤器影响
*/
public checked:boolean;
/**
* 是否输出
*/
public display:boolean;
/**
* 是否为附件标签
* 例如回车、时间、代码位置
*/
public attach:boolean;
/**
* @param key 关键字
* @param style 文字样式
*/
constructor(key:string, style:LogStyle,
checked?:boolean, display?:boolean, attach?:boolean) {
this.key = key;
this.style = style;
this.checked = checked ?? true;
this.display = display ?? true;
this.attach = attach ?? false;
}
/**
* 获得 Logger 输出使用的内容
*/
public getLoggerOutput():string {
if(!this.display) return "";
return `%c${ this.key }`;
}
/**
* 获得 Text 输出内容
*/
public getTextOutput():string {
if(!this.display) return "";
return `[${ this.key }]`;
}
/**
* 获得 style 格式化
*/
public getStyleOutput():string {
if(!this.display) return "";
return this.style.stringify();
}
/**
* 校验
*/
public checking(src:RegExp | string):boolean {
let pass = false;
// 关闭校验
if(!this.checked) return pass;
if(src instanceof RegExp) {
pass = (src as RegExp).test(this.key)
} else {
pass = (src as string) === this.key;
}
return pass;
}
}
export default LogLabel;
export {LogLabel, LogStyle}
+205
View File
@@ -0,0 +1,205 @@
import { LOGGER_FILTER, LOGGER_CONSOLE } from "./Config";
import { StackLogLabel } from "./PresetLogLabel";
import { LogLabel } from "./LogLabel";
/**
* 多重内容捆绑
* 用于 log 输出
*/
class MultipleLogContent<T extends Array<any>> {
/**
* 输出内容
*/
private readonly content:T;
/**
* @param content 输出内容
*/
public constructor(...content:T) {
this.content = content;
}
/**
* 获取内容
*/
public getContent():T {
return this.content;
}
}
/**
* 格式化日志输出
*/
class Logger {
/**
* 标签过滤
*/
public static filterLog(filter:Array<RegExp | string>, labels:LogLabel[]):boolean {
let passNum:number = 0;
for(let i = 0; i < filter.length; i++) {
let pass:boolean = false;
for(let j = 0; j < labels.length; j++) {
pass = labels[j].checking(filter[i]);
if(pass) break;
}
if(pass) passNum ++;
}
return passNum === filter.length;
}
/**
* 检测是否应该输出
* @param labels 使用标签
*/
public static testLog(...labels:LogLabel[]):boolean {
if(!LOGGER_CONSOLE) return false;
let isLogging = false;
for(let i = 0; i < LOGGER_FILTER.length; i++) {
// 判断是否进行输出
isLogging = Logger.filterLog(LOGGER_FILTER[i], labels);
if(isLogging) break;
}
return isLogging;
}
/**
* 收集计算样式
* @param labels 使用标签
*/
public static calcStyle(...labels:LogLabel[]):[string[], string[]] {
// 过滤出需要显示的 Labels
let labelsNeedRender:LogLabel[] = labels.filter((label:LogLabel)=>{
return label.display
});
let consoleLabels:string[] = [];
let consoleStyles:string[] = [];
// 放置标签
for(let i = 0; i < labelsNeedRender.length; i++) {
consoleLabels.push(labels[i].getLoggerOutput());
if (i !== ( labelsNeedRender.length - 1))
consoleLabels.push("%c ");
consoleStyles.push(labelsNeedRender[i].getStyleOutput());
if (i !== ( labelsNeedRender.length - 1))
consoleStyles.push("");
}
return [consoleLabels, consoleStyles];
}
/**
* 基础调试输出
* 其他的 Log 函数都是基于此封装
* @param content 输出内容
* @param label 使用标签
* @param attachLabel 附加标签
*/
public static logBase<T extends Array<any>>
(content:MultipleLogContent<T>, labels:LogLabel[], attachLabel:LogLabel[] = []):T {
// TODO: 这里可以添加一些钩子作为中间件处理日志输出
// 测试是否输出内容
if(!Logger.testLog(...labels, ...attachLabel, StackLogLabel.filterUrlLabel))
return content.getContent();
// 计算收集样式
let [consoleLabels, consoleStyles]= Logger.calcStyle(...labels, ...attachLabel);
// 调试输出
console.log(consoleLabels.join(""), ...consoleStyles, ...content.getContent());
return content.getContent();
}
/**
* 调试输出
* @param content 输出内容
* @param label 使用标签
*/
public static log<T>(content:T, ...labels:LogLabel[]):T {
return Logger.logBase<Array<T>>(
new MultipleLogContent<Array<T>>(content), labels,
[StackLogLabel.fileNameLabel]
)[0];
}
/**
* 函数 Logger.log 的别名
*/
public static l:typeof Logger.log = Logger.log;
/**
* 多重调试输出
* @param labels 输出内容
* @param content 使用标签
*/
public static logMultiple<T extends Array<any>>(labels:LogLabel[], ...content:T):T {
return Logger.logBase<T>(
new MultipleLogContent<T>(...content), labels,
[StackLogLabel.fileNameLabel]
);
}
/**
* 函数 Logger.logMultiple 的别名
*/
public static m:typeof Logger.logMultiple = Logger.logMultiple;
/**
* 在下一行调试输出
* @param content 输出内容
* @param label 使用标签
*/
public static logLine<T>(content:T, ...labels:LogLabel[]):T {
return Logger.logBase<Array<T>>(
new MultipleLogContent<Array<T>>(content), labels,
[StackLogLabel.fileNameLabel, StackLogLabel.blankLabel]
)[0];
}
/**
* 函数 Logger.logLine 的别名
*/
public static ll:typeof Logger.logLine = Logger.logLine;
/**
* 在下一行多重调试输出
* @param labels 输出内容
* @param content 使用标签
*/
public static logLineMultiple<T extends Array<any>>(labels:LogLabel[], ...content:T):T {
return Logger.logBase<T>(
new MultipleLogContent<T>(...content), labels,
[StackLogLabel.fileNameLabel, StackLogLabel.blankLabel]
);
}
/**
* 函数 Logger.logLineMultiple 的别名
*/
public static lm:typeof Logger.logLineMultiple = Logger.logLineMultiple;
}
export default Logger;
export { Logger };
+356
View File
@@ -0,0 +1,356 @@
/**
* 自定义对象类型
*/
type IAnyTypeObject<T = any> = {
[x:string]: T;
};
// 微信 Data 类型
type Data<D> = WechatMiniprogram.Page.Data<D>;
// 微信生命周期类型
type ILifetime = WechatMiniprogram.Page.ILifetime;
// 继承的方法
type InstanceMethods<D> = WechatMiniprogram.Page.InstanceMethods<D>;
// 继承的属性
type InstanceProperties = WechatMiniprogram.Page.InstanceProperties;
/**
* 定义微信微信上下文类型
*
* @template TD data 结构类型
* @template TC 用户 MinIn 类型
*
* Partial<ILifetime> 可选的微信生命周期
* Partial<Data<TD>> 可选的 data 结构
* InstanceMethods<TD> 必选的微信继承方法
* InstanceProperties 必选的微信继承属性
*/
type WXContext<TD, TC> = Partial<ILifetime> & Partial<Data<TD>>
& InstanceMethods<TD> & InstanceProperties & TC;
/**
* 任意的微信上下文
* 当我们不关心具体是哪个上下文时
* 使用这个类型
*/
type AnyWXContext = WXContext<IAnyTypeObject, IAnyTypeObject>;
/**
* 依赖关系图谱
* 注意: 这是一个递给类型
* @template M 限制了所有依赖必须使用同一个 manager
*/
type Depends<M extends Manager<AnyWXContext>> = {
[x:string]: Modular<M, Depends<M>>;
};
/**
* 页面模组
* @template M 所属 Manager
* @template DEP 模组依赖
* @template TD 模组 Data 类型
*/
class Modular<
M extends Manager<AnyWXContext> = Manager<AnyWXContext>,
DEP extends Depends<M> = Depends<M>,
TD extends IAnyTypeObject = IAnyTypeObject>
implements WXContext<TD, IAnyTypeObject> {
// [x:string]: any;
/**
* 保存页面上下文
*/
private manager:M;
/**
* 映射 Manager 上下文
*/
private get context():M["context"] {
return this.manager.context;
}
/**
* 模组依赖
*/
protected depends?:DEP
/**
* 模组数据
*/
public data?:TD;
/**
* 模组使用的函数列表
*/
private functionList:Set<string>;
/**
* 函数使用的参数列表
*/
private paramList:Set<string>;
/**
* 命名空间
*/
private nameSpace:string;
// 映射主上下文属性
public get is():string { return this.context.is };
public get route():string { return this.context.route };
public get options():Record<string, string | undefined> {
return this.context.options;
};
/**
* 一旦被类被构造,整个页面的全部申明周期将
* 由该类实例控制,此后请勿修改任何生命周期函数
* @param manager 页面上下文
* @param nameSpace 模组命名空间
* @param depend 模组依赖
*/
public constructor(manager:M, nameSpace:string, depend?: DEP) {
// 保存微信上下文
this.manager = manager;
// 保存模块依赖
this.depends = depend;
// 初始化内部属性
this.functionList = new Set<string>();
this.paramList = new Set<string>();
this.nameSpace = nameSpace;
}
public setData(data:Partial<TD>, callback?: () => void):void {
if(this.data === void 0) {
this.data = {} as TD;
}
let reportData:IAnyTypeObject = {};
for(let key in data) {
(this.data as IAnyTypeObject)[key] = data[key];
reportData[`${ this.nameSpace }$${ key }`] = data[key];
this.paramList.add(key);
}
return this.context.setData(reportData, callback);
}
/**
* 向上层上下文传递函数
* @param fn 传递的函数
* @param name 函数名字
*/
public setFunc(fn:Function, name:string):void {
this.functionList.add(name);
(this.context as IAnyTypeObject)
[`${ this.nameSpace }$${ name }`] = fn.bind(this);
}
//#region 映射微信的继承函数
public hasBehavior(behavior: string): void {
return this.context.hasBehavior(behavior);
}
public triggerEvent<DetailType>(name: string, detail?: DetailType,
options?: WechatMiniprogram.Component.TriggerEventOption): void {
return this.context.triggerEvent<DetailType>(name, detail, options);
}
public createSelectorQuery(): WechatMiniprogram.SelectorQuery {
return this.context.createSelectorQuery();
}
public createIntersectionObserver(options: WechatMiniprogram.CreateIntersectionObserverOption):
WechatMiniprogram.IntersectionObserver {
return this.context.createIntersectionObserver(options);
}
public selectComponent(selector: string): WechatMiniprogram.Component.TrivialInstance {
return this.context.selectComponent(selector);
}
public selectAllComponents(selector: string): WechatMiniprogram.Component.TrivialInstance[] {
return this.context.selectAllComponents(selector);
}
public selectOwnerComponent(): WechatMiniprogram.Component.TrivialInstance {
return this.context.selectOwnerComponent();
}
public getRelationNodes(relationKey: string): WechatMiniprogram.Component.TrivialInstance[] {
return this.context.getRelationNodes(relationKey);
}
public groupSetData(callback?: () => void): void {
return this.context.groupSetData(callback);
}
public getTabBar(): WechatMiniprogram.Component.TrivialInstance {
return this.context.getTabBar();
}
public getPageId(): string {
return this.context.getPageId();
}
public animate(selector: string, keyFrames: WechatMiniprogram.Component.KeyFrame[],
duration: number, callback?: () => void): void;
public animate(selector: string, keyFrames: WechatMiniprogram.Component.ScrollTimelineKeyframe[],
duration: number, scrollTimeline: WechatMiniprogram.Component.ScrollTimelineOption): void;
public animate(selector: any, keyFrames: any, duration: any, scrollTimeline?: any): void {
return this.context.animate(selector, keyFrames, duration, scrollTimeline);
}
public clearAnimation(selector: string, callback: () => void): void;
public clearAnimation(selector: string, options?: WechatMiniprogram.Component.ClearAnimationOptions,
callback?: () => void): void;
public clearAnimation(selector: any, options?: any, callback?: any): void {
return this.context.clearAnimation(selector, options, callback);
}
public getOpenerEventChannel(): WechatMiniprogram.EventChannel {
return this.context.getOpenerEventChannel();
}
//#endregion
}
/**
* 页面控件
* 用于管理页面的所有模组
*/
class Manager<WXC extends AnyWXContext = AnyWXContext> {
/**
* 微信生命周期
*/
static readonly WxLifeCycle:(keyof ILifetime)[] = [
"onShow",
"onReady",
"onHide",
"onUnload",
"onPullDownRefresh",
"onReachBottom",
"onShareAppMessage",
"onShareTimeline",
"onAddToFavorites",
"onPageScroll",
"onResize",
"onTabItemTap"
]
/**
* 保存页面上下文
*/
public context:WXC;
/**
* 一旦被类被构造,整个页面的全部申明周期将
* 由该类实例控制,此后请勿修改任何生命周期函数
*/
public constructor(context:WXC) {
// 保存微信上下文
this.context = context;
// 初始化模组列表
this.modules = [];
}
/**
* 模组列表
*/
public modules:Modular[];
/**
* 添加一个模块
* @param mode 模块类
* @param nameSpace 命名空间
* @param depend 模块依赖
* @returns 模块实例
*/
public addModule<DEP extends Depends<this>, M extends Modular<this, DEP>>
(mode: new (manager:Manager<WXC>, nameSpace:string, depend?:DEP) => M,
nameSpace:string, depend?:DEP):M {
let mod = new mode(this, nameSpace, depend);
this.modules.push(mod);
return mod;
}
/**
* 创建指定生命周期的钩子
* @param key 生命周期键值
*/
public creatHooks(key:keyof ILifetime):ILifetime[keyof ILifetime] {
return (...arg: any[]) => {
let hooks:Promise<any>[] = [];
let simple:any;
for(let i = 0; i < this.modules.length; i++) {
let res: Promise<any> | any =
(this.modules[i] as IAnyTypeObject)[key](...arg);
if (res instanceof Promise) {
hooks.push(res);
} else {
hooks.push(Promise.resolve(res));
}
if (
key === "onShareAppMessage" ||
key === "onShareTimeline" ||
key === "onAddToFavorites"
) {
// 如果返回值有特殊含义在处理时进行 MinIn
simple = Object.assign({}, simple, res);
} else {
simple = res;
}
}
if(hooks.length === 0) return;
if(hooks.length === 1) return simple;
return Promise.all(hooks);
}
}
/**
* 创建全部生命周期钩子
*/
public creatAllHooks() {
for(let i = 0; i < Manager.WxLifeCycle.length; i++) {
(this.context as IAnyTypeObject)[Manager.WxLifeCycle[i]] =
this.creatHooks(Manager.WxLifeCycle[i]);
}
}
/**
* 加载全部的模块
*/
public loadAllModule(query:Record<string, string | undefined>) {
this.creatAllHooks();
let res = this.creatHooks("onLoad")(query as any);
return res;
}
}
export { Manager, Modular, AnyWXContext, WXContext, ILifetime}
+313
View File
@@ -0,0 +1,313 @@
import { LogLabel, LogStyle } from "./LogLabel";
/**
* 栈信息
*/
class StackInfo {
/**
* 函数名
*/
public functionName:string | undefined;
/**
* 文件名
*/
public fileName:string | undefined;
/**
* 文件路径
*/
public url:string | undefined;
/**
* 文件名和行号
*/
public fileNameLine: string | undefined;
/**
* 设置信息
* @param functionName 函数名
* @param fileName 文件名
* @param url 文件路径
*/
public setInfo(functionName:string, fileNameLine:string, url:string):StackInfo {
this.functionName = functionName;
this.fileNameLine = fileNameLine;
this.url = url;
return this;
}
/**
* 计算文件名
*/
public calcFileName():string | undefined {
let replaceToTs = this.fileNameLine?.replace(".js", ".ts");
let matched = replaceToTs?.match(/^(.+\.(js|ts)):\d+:\d+$/);
return matched ? matched[1] : undefined;
}
/**
* 计算路径名
*/
public calcPathName():string | undefined {
let replaceToTs = this.url?.replace(".js", ".ts");
let matched = replaceToTs?.match(/^https?:\/\/(\d+\.){3}\d+:\d+\/(.+):\d+:\d+$/);
return matched ? matched[2] : undefined;
}
/**
* 获取函数调用栈列表
*/
public static getCallStack():StackInfo[] {
// 获取堆栈信息
let stack:string | undefined = new Error().stack;
if (stack === void 0) return [];
// 去除 Error
stack = stack.replace(/^(Error)\s/, "");
// 获取堆栈信息
let stackList:string[] = stack.split(/\n/);
let callStack:StackInfo[] = [];
for(let i = 0; i < stackList.length; i++) {
let matcher = stackList[i].match(/^\s+at\s+(.+)\s(\(.+\))/);
if (matcher === null || matcher.length < 3) continue;
let fileName = matcher[2].match(/.+\/(.+\..+:\d+:\d+)\)/);
if (fileName === null || matcher.length < 2) continue;
callStack.push(new StackInfo().setInfo(
matcher[1], fileName[1], matcher[2]?.replace(/(\(|\))/g, "")
))
}
// console.log(callStack);
return callStack;
}
/**
* 排除的
*/
public static readonly excludeFile:RegExp = /^.*(\\|\/)logger(\\|\/).+\.js:\d+:\d+/;
/**
* 获取第一个调用栈
*/
public static getFirstStack():StackInfo | undefined {
let callStack = this.getCallStack();
for(let i = 0; i < callStack.length; i++) {
if(!callStack[i].url) continue;
if(!StackInfo.excludeFile.test(callStack[i].url ?? "")) {
return callStack[i];
}
}
return;
}
}
/**
* 内部预定义的 LogLabel
*/
class StackLogLabel {
/**
* 堆栈路径样式
*/
public static readonly normalStyle:LogStyle = new LogStyle()
.setColor("#979797").setBorder("4px", "1px solid #979797").setBlank("0 5px");
/**
* 一个回车
*/
public static readonly blankLabel = new LogLabel("\n\r",
new LogStyle(), false, true, true);
/**
* 包含文件名和行号的 label
*/
public static get fileNameLabel():LogLabel {
// 获得调用堆栈
let stack = StackInfo.getFirstStack();
return new LogLabel(stack?.calcFileName() ?? "Unknown file name",
StackLogLabel.normalStyle, false, true, true);
}
/**
* 包含 URL 链接的 label
*/
public static get urlLabel():LogLabel {
// 获得调用堆栈
let stack = StackInfo.getFirstStack();
return new LogLabel(stack?.calcPathName() ?? "Unknown url",
StackLogLabel.normalStyle, false, true, true);
}
/**
* 仅仅用来 filter 的 URL 链接的 label
*/
public static get filterUrlLabel():LogLabel {
// 获得调用堆栈
let stack = StackInfo.getFirstStack();
return new LogLabel(stack?.calcPathName() ?? "Unknown url",
StackLogLabel.normalStyle, true, false, true);
}
}
/**
* 生成圆角颜色标签样式
*/
const normalLevelStyleGen = (color:string):LogStyle => {
return new LogStyle().setBorder("4px", `1px solid ${color}`)
.setColor(color).setBlank("0 5px");
}
/**
* 调试等级标签
*/
class LevelLogLabel {
/**
* 致命
*/
static readonly FatalLabel = new LogLabel(
"FATAL", normalLevelStyleGen("#FF00CC")
);
/**
* 错误
*/
static readonly ErrorLabel = new LogLabel(
"ERROR", normalLevelStyleGen("#FF0000")
);
/**
* 警告
*/
static readonly WarnLabel = new LogLabel(
"WARN", normalLevelStyleGen("#FF9900")
);
/**
* 消息
*/
static readonly InfoLabel = new LogLabel(
"INFO", normalLevelStyleGen("#99FF00")
);
/**
* 调试
*/
static readonly DebugLabel = new LogLabel(
"DEBUG", normalLevelStyleGen("#00FF99")
);
/**
* 追踪
*/
static readonly TraceLabel = new LogLabel(
"TRACE", normalLevelStyleGen("#00CCFF")
);
}
/**
* 生成圆角颜色标签样式
*/
const normalLifeStyleGen = (r:number, g:number, b:number):LogStyle => {
return new LogStyle().setBorder("4px", `1px solid rgb(${ r }, ${ g }, ${ b })`)
.setColor(`rgb(${ r }, ${ g }, ${ b })`, `rgba(${ r }, ${ g }, ${ b }, .1)`)
.setBlank("0 5px");
}
/**
* 生命周期标签
*/
class LifeCycleLogLabel {
/**
* 小程序加载时
*/
static readonly OnLaunchLabel = new LogLabel(
"onLaunch", normalLifeStyleGen(160, 32, 240)
);
/**
* 生命周期函数--监听页面加载
*/
static readonly OnLoadLabel = new LogLabel(
"onLoad", normalLifeStyleGen(255, 140, 105)
);
/**
* 生命周期函数--监听页面初次渲染完成
*/
static readonly OnReadyLabel = new LogLabel(
"onReady", normalLifeStyleGen(255, 127, 36)
);
/**
* 生命周期函数--监听页面显示
*/
static readonly OnShowLabel = new LogLabel(
"onShow", normalLifeStyleGen(255, 215, 0)
)
/**
* 生命周期函数--监听页面隐藏
*/
static readonly OnHideLabel = new LogLabel(
"onHide", normalLifeStyleGen(173, 255, 47)
);
/**
* 生命周期函数--监听页面卸载
*/
static readonly OnUnloadLabel = new LogLabel(
"onUnload", normalLifeStyleGen(127, 255, 212)
);
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
static readonly OnPullDownRefreshLabel = new LogLabel(
"onPullDownRefresh", normalLifeStyleGen(0, 191, 255)
);
/**
* 页面上拉触底事件的处理函数
*/
static readonly OnReachBottomLabel = new LogLabel(
"onReachBottom", normalLifeStyleGen(84, 255, 159)
);
/**
* 用户点击右上角分享
*/
static readonly OnShareAppMessageLabel = new LogLabel(
"onShareAppMessage", normalLifeStyleGen(147, 112, 219)
);
}
export { StackInfo, StackLogLabel, LevelLogLabel, LifeCycleLogLabel };
+205
View File
@@ -0,0 +1,205 @@
// import { Logger } from "../logger/Logger";
import { LogStyle, LogLabel } from "./LogLabel";
/**
* 测试结果
*/
class TestResult {
/**
* 用例名称
*/
public caseName:string;
/**
* 测试结果
*/
public result:boolean;
/**
* 消息
*/
public message:string;
/**
* 附加消息
*/
public attach:string;
/**
* 初始化
* @param caseName 用例名称
*/
constructor(caseName:string) {
this.caseName = caseName;
this.result = false;
this.message = "";
this.attach = "";
}
/**
* 设置结果
*/
public setResult(result:boolean, message?:string, attach?:string) {
this.result = result;
this.message = message ?? (result ? "success!" : "failed!");
this.attach = attach ?? this.attach;
return this;
}
}
/**
* 测试函数结构
*/
type TestFunction = () => TestResult | Promise<TestResult>;
/**
* 收集测试函数结构
*/
class CaseCollect {
/**
* 用例键名
*/
public key:string;
/**
* 用例测试函数
*/
public caseFunction:TestFunction;
/**
* 测试结果
*/
result: Promise<TestResult> | undefined;
/**
* @param key 测试用例键名
* @param caseFunction 测试函数
*/
public constructor(key:string, caseFunction:TestFunction) {
this.key = key;
this.caseFunction = caseFunction;
}
/**
* 运行测试用例
*/
public async runTestCase():Promise<CaseCollect> {
let result = this.caseFunction();
if(result instanceof Promise) {
this.result = result;
} else {
this.result = Promise.resolve(result);
}
return this;
}
public static readonly baseStyle = new LogStyle().setBlank();
public static readonly successLabel:LogLabel = new LogLabel("√",
new LogStyle().setBlank("0 4px").setBorder("1000px", "1px solid green")
);
/**
* 打印结果
* @param current 当前进度
* @param total 总进度
*/
public printResult(current?:number, total?:number) {
// 如果测试没有运行,先运行它
if(this.result === void 0) this.runTestCase();
this.result?.then((res) => {
if(res.result) {
console.log(
`%c√%c %c1/1%c %c${ this.key }%c ` + res.message,
"padding:0 4px; border-radius:1000px; border:1px solid green; color:green",
"", "padding:0 4px; border-radius:4px; border:1px solid green; color:green",
"", "padding:0 4px; border-radius:4px; border:1px solid #979797; color:#979797",
""
)
} else {
console.log(
`%c√%c %c1/1%c %c${ this.key }%c ` + res.message,
"padding:0 4px; border-radius:1000px; border:1px solid red; color:red",
"", "padding:0 4px; border-radius:4px; border:1px solid red; color:red",
"", "padding:0 4px; border-radius:4px; border:1px solid #979797; color:#979797",
""
)
}
console.log(res)
})
}
/**
* 收集测试用例
* @param testCaseClass 测试用例表
*/
public static collectCase(testCaseClass:ITestCase):CaseCollect[] {
// 获取静态方法 key
let key = Object.getOwnPropertyNames(testCaseClass);
// 过滤掉通用的方法和属性
key = key.filter((key) => !/(length|name|prototype)/.test(key) );
// 生成 CaseCollect
let caseCollect = [];
for (let i = 0; i < key.length; i++) {
caseCollect.push(new CaseCollect(key[i], testCaseClass[key[i]]))
}
return caseCollect;
}
/**
* 运行测试样例
*/
public static async runCollectCase(cases:CaseCollect[]):Promise<CaseCollect[]> {
let running:Promise<CaseCollect>[] = [];
for(let i = 0; i < cases.length; i++) {
running.push(cases[i].runTestCase());
}
return Promise.all(running);
}
/**
* 启动单元测试
*/
public static runUnitTest(testCaseClass:ITestCase) {
let caseCollect = this.collectCase(testCaseClass);
CaseCollect.runCollectCase(caseCollect).then((caseCollect:CaseCollect[]) => {
for(let i = 0; i < caseCollect.length; i++) {
caseCollect[i].printResult()
}
})
}
}
/**
* 测试用例接口
*/
interface ITestCase {
/**
* 测试用例函数
*/
[key:string]:TestFunction;
}
export default ITestCase;
export { ITestCase, TestResult, TestFunction, CaseCollect };