@@ -363,7 +363,7 @@ test('反例自测: 新形态标记不得出现在未改造组件中', () => {
* 2.19 快捷操作: 显示控制 + 自定义操作 + 素材图标库 + 图层式管理
* 2.19 快捷操作: 显示控制 + 自定义操作 + 素材图标库 + 图层式管理
* ------------------------------------------------------------------ */
* ------------------------------------------------------------------ */
const V19 _FIELDS = { actVis : 'object' , actLabels : 'object' , customActs : 'array' , actOrder : 'array' } ;
const V19 _FIELDS = { actVis : 'object' , actDeleted : 'object' , actLabels : 'object' , customActs : 'array' , actOrder : 'array' } ;
const V19 _ACT _LABELS = { tel : '打电话' , wx : '微信号' , mail : '发邮件' , web : '网站' , save : '存通讯录' } ;
const V19 _ACT _LABELS = { tel : '打电话' , wx : '微信号' , mail : '发邮件' , web : '网站' , save : '存通讯录' } ;
test ( 'diy_view.php: Show/Self value 均含 2.19 字段(默认值=全显/默认文案/空数组/历史顺序=零变化)' , ( ) => {
test ( 'diy_view.php: Show/Self value 均含 2.19 字段(默认值=全显/默认文案/空数组/历史顺序=零变化)' , ( ) => {
@@ -379,6 +379,8 @@ test('diy_view.php: Show/Self value 均含 2.19 字段(默认值=全显/默认
assert . deepEqual ( v . customActs , [ ] , comp + '.customActs 默认空数组' ) ;
assert . deepEqual ( v . customActs , [ ] , comp + '.customActs 默认空数组' ) ;
// actOrder(2.19.1): 历史渲染顺序
// actOrder(2.19.1): 历史渲染顺序
assert . deepEqual ( v . actOrder , [ 'tel' , 'wx' , 'mail' , 'web' , 'save' ] , comp + '.actOrder 默认=历史顺序' ) ;
assert . deepEqual ( v . actOrder , [ 'tel' , 'wx' , 'mail' , 'web' , 'save' ] , comp + '.actOrder 默认=历史顺序' ) ;
// actDeleted(2.19.2): 全 0=无删除(历史零变化)
assert . deepEqual ( v . actDeleted , { tel : 0 , wx : 0 , mail : 0 , web : 0 , save : 0 } , comp + '.actDeleted 默认全 0' ) ;
}
}
} ) ;
} ) ;
@@ -432,13 +434,16 @@ test('common.js: normalizeActVis / normalizeActLabels / normalizeCustomActs / no
js . match ( /export const ACT_ORDER_DEFAULT = [^;]+;/ ) [ 0 ] . replace ( 'export const' , 'const' ) ,
js . match ( /export const ACT_ORDER_DEFAULT = [^;]+;/ ) [ 0 ] . replace ( 'export const' , 'const' ) ,
] . join ( '\n' ) ;
] . join ( '\n' ) ;
const grab = ( name ) => js . match ( new RegExp ( 'export function ' + name + '[\\s\\S]*?\\n}' ) ) [ 0 ] . replace ( 'export function ' , 'function ' ) ;
const grab = ( name ) => js . match ( new RegExp ( 'export function ' + name + '[\\s\\S]*?\\n}' ) ) [ 0 ] . replace ( 'export function ' , 'function ' ) ;
const body = consts + '\n' + [ 'normalizeActVis' , 'normalizeActLabels' , 'normalizeCustomActs' , 'normalizeActOrder' ] . map ( grab ) . join ( '\n' ) + '\n'
const body = consts + '\n' + [ 'normalizeActVis' , 'normalizeActDeleted' , 'normalizeActLabels' , 'normalizeCustomActs' , 'normalizeActOrder' ] . map ( grab ) . join ( '\n' ) + '\n'
+ 'return [normalizeActVis, normalizeActLabels, normalizeCustomActs, normalizeActOrder];' ;
+ 'return [normalizeActVis, normalizeActDeleted, normalizeActLabels, normalizeCustomActs, normalizeActOrder];' ;
const [ visFn , labelFn , customFn , orderFn ] = new Function ( body ) ( ) ;
const [ visFn , delFn , labelFn , customFn , orderFn ] = new Function ( body ) ( ) ;
// actVis: 缺省全显(历史零变化); '0' → 0; 未知键忽略
// actVis: 缺省全显(历史零变化); '0' → 0; 未知键忽略
assert . deepEqual ( visFn ( undefined ) , { tel : 1 , wx : 1 , mail : 1 , web : 1 , save : 1 } , 'actVis 缺省全显' ) ;
assert . deepEqual ( visFn ( undefined ) , { tel : 1 , wx : 1 , mail : 1 , web : 1 , save : 1 } , 'actVis 缺省全显' ) ;
assert . deepEqual ( visFn ( { tel : '0' , wx : 0 } ) , { tel : 0 , wx : 0 , mail : 1 , web : 1 , save : 1 } , 'actVis 0=隐藏/缺键=显' ) ;
assert . deepEqual ( visFn ( { tel : '0' , wx : 0 } ) , { tel : 0 , wx : 0 , mail : 1 , web : 1 , save : 1 } , 'actVis 0=隐藏/缺键=显' ) ;
// actDeleted(2.19.2): 缺省全 0(历史零变化); '1' → 1
assert . deepEqual ( delFn ( undefined ) , { tel : 0 , wx : 0 , mail : 0 , web : 0 , save : 0 } , 'actDeleted 缺省全 0' ) ;
assert . deepEqual ( delFn ( { tel : '1' , save : 1 } ) , { tel : 1 , wx : 0 , mail : 0 , web : 0 , save : 1 } , 'actDeleted 1=已删/缺键=0' ) ;
// actLabels: 缺省=内置文案; 空串回落默认
// actLabels: 缺省=内置文案; 空串回落默认
assert . deepEqual ( labelFn ( undefined ) , V19 _ACT _LABELS , 'actLabels 缺省=内置默认' ) ;
assert . deepEqual ( labelFn ( undefined ) , V19 _ACT _LABELS , 'actLabels 缺省=内置默认' ) ;
assert . deepEqual ( labelFn ( { tel : '' , wx : '加微信' } ) . wx , '加微信' , 'actLabels 改名生效' ) ;
assert . deepEqual ( labelFn ( { tel : '' , wx : '加微信' } ) . wx , '加微信' , 'actLabels 改名生效' ) ;
@@ -492,8 +497,11 @@ test('mini.vue: acts 合成(显隐过滤 + 次序 + 文案覆盖 + 自定义动
assert . match ( src , /acts\(\)\s*\{[\s\S]*?normalizeCustomActs\(this\.c\.customActs\)/ , 'acts 消费 normalizeCustomActs' ) ;
assert . match ( src , /acts\(\)\s*\{[\s\S]*?normalizeCustomActs\(this\.c\.customActs\)/ , 'acts 消费 normalizeCustomActs' ) ;
// 2.19.1 次序: 按 normalizeActOrder 输出默认动作 + order 外扩展类型尾接兜底
// 2.19.1 次序: 按 normalizeActOrder 输出默认动作 + order 外扩展类型尾接兜底
assert . match ( src , /acts\(\)\s*\{[\s\S]*?normalizeActOrder\(this\.c\.actOrder\)/ , 'acts 消费 normalizeActOrder' ) ;
assert . match ( src , /acts\(\)\s*\{[\s\S]*?normalizeActOrder\(this\.c\.actOrder\)/ , 'acts 消费 normalizeActOrder' ) ;
assert . match ( src , /acts\(\)\s*\{[\s\S]*?const byType = \{\};[\s\S]*?order\.forEach\(t => \{[\s\S]*?if \(byType\[t\] && vis\[t\] !== 0\) out\.push\(merge\(byType\[t\]\)\); / , '按 order 次序取默认动作(vis=0 过滤) ' ) ;
assert . match ( src , /acts\(\)\s*\{[\s\S]*?const byType = \{\};[\s\S]*?order\.forEach\(t => \{/ , 'acts 按 order 次序取默认动作' ) ;
assert . match ( src , /acts\(\)\s*\{[\s\S]*?if \(order\.indexOf\(t\) >= 0 \|\| vis\[t\] === 0\) return;/ , 'order 外扩展类型尾接且隐藏不渲染' ) ;
assert . match ( src , /acts\(\)\s*\{[\s\S]*?if \(order\.indexOf\(t\) >= 0 \|\| vis\[t\] === 0 \|\| del\[t\] === 1 \) return;/ , 'order 外扩展类型尾接且隐藏/已删 不渲染' ) ;
// 2.19.2 删除: 按 order 取默认动作时 deleted=1 的不渲染
assert . match ( src , /acts\(\)\s*\{[\s\S]*?normalizeActDeleted\(this\.c\.actDeleted\)/ , 'acts 消费 normalizeActDeleted' ) ;
assert . match ( src , /acts\(\)\s*\{[\s\S]*?if \(byType\[t\] && vis\[t\] !== 0 && del\[t\] !== 1\) out\.push\(merge\(byType\[t\]\)\);/ , 'order 取默认动作时 vis=0/deleted=1 均过滤' ) ;
assert . match ( src , /labels\[t\] \|\| a\.label/ , 'actLabels 覆盖文案(空回落服务端)' ) ;
assert . match ( src , /labels\[t\] \|\| a\.label/ , 'actLabels 覆盖文案(空回落服务端)' ) ;
assert . match ( src , /cfgImg: String\(cfg\.img \|\| ''\)\.trim\(\)/ , '默认动作合并 cfgImg' ) ;
assert . match ( src , /cfgImg: String\(cfg\.img \|\| ''\)\.trim\(\)/ , '默认动作合并 cfgImg' ) ;
assert . match ( src , /cfgKey: String\(cfg\.iconKey \|\| ''\)\.trim\(\)/ , '默认动作合并 cfgKey(iconKey)' ) ;
assert . match ( src , /cfgKey: String\(cfg\.iconKey \|\| ''\)\.trim\(\)/ , '默认动作合并 cfgKey(iconKey)' ) ;
@@ -503,11 +511,11 @@ test('mini.vue: acts 合成(显隐过滤 + 次序 + 文案覆盖 + 自定义动
assert . match ( src , /actLibStyle\(act\)\s*\{[\s\S]*?libIconBg\(this\.actLibOf\(act\), this\.actIconColor\(act\)\)/ , 'actLibStyle 消费 libIconBg + 四级色口径' ) ;
assert . match ( src , /actLibStyle\(act\)\s*\{[\s\S]*?libIconBg\(this\.actLibOf\(act\), this\.actIconColor\(act\)\)/ , 'actLibStyle 消费 libIconBg + 四级色口径' ) ;
assert . equal ( ( src . match ( /v-else-if="actLibOf\(act\)" class="act-grid-svg" :style="actLibStyle\(act\)"/g ) || [ ] ) . length , 2 , '上/下两份宫格副本均插入素材库分支' ) ;
assert . equal ( ( src . match ( /v-else-if="actLibOf\(act\)" class="act-grid-svg" :style="actLibStyle\(act\)"/g ) || [ ] ) . length , 2 , '上/下两份宫格副本均插入素材库分支' ) ;
assert . match ( src , /actSvgOf\(act\)\s*\{[\s\S]*?!this\.actImgOf\(act\) && !this\.actLibOf\(act\) && !!WAP_LINE_ICONS/ , '线稿档被 img/素材库优先占位后不再进入' ) ;
assert . match ( src , /actSvgOf\(act\)\s*\{[\s\S]*?!this\.actImgOf\(act\) && !this\.actLibOf\(act\) && !!WAP_LINE_ICONS/ , '线稿档被 img/素材库优先占位后不再进入' ) ;
assert . match ( src , /import \{[\s\S]*?normalizeActVis, normalizeActLabels, normalizeCustomActs, normalizeActOrder, libIconBg/ , 'mini.vue 引入 2.19+2.19.1 依赖' ) ;
assert . match ( src , /import \{[\s\S]*?normalizeActVis, normalizeActDeleted, normalizeActLabels, normalizeCustomActs, normalizeActOrder, libIconBg/ , 'mini.vue 引入 2.19+2.19.1+2.19.2 依赖' ) ;
// 透传
// 透传
for ( const file of [ 'diy-card-show.vue' , 'diy-card-self.vue' ] ) {
for ( const file of [ 'diy-card-show.vue' , 'diy-card-self.vue' ] ) {
const vue = readFileSync ( resolve ( FE , file ) , 'utf8' ) ;
const vue = readFileSync ( resolve ( FE , file ) , 'utf8' ) ;
for ( const f of [ 'actVis' , 'actLabels' , 'actOrder' , 'customActs' ] ) {
for ( const f of [ 'actVis' , 'actDeleted' , 'actLabels' , 'actOrder' , 'customActs' ] ) {
assert . match ( vue , new RegExp ( f + ': v\\.' + f + ',' ) , file + ' 透传 ' + f ) ;
assert . match ( vue , new RegExp ( f + ': v\\.' + f + ',' ) , file + ' 透传 ' + f ) ;
}
}
}
}
@@ -540,13 +548,18 @@ test('design.html × 2: 图层式管理面板 + 素材图标库 + 预览显隐/
assert . match ( html , /nc\.customActs\.splice\(ci, 1\)/ , comp + ' 删除自定义动作' ) ;
assert . match ( html , /nc\.customActs\.splice\(ci, 1\)/ , comp + ' 删除自定义动作' ) ;
// 2.19.1 默认动作次序: 面板行按 actOrder 渲染 + ↑↓ 委托调整
// 2.19.1 默认动作次序: 面板行按 actOrder 渲染 + ↑↓ 委托调整
assert . equal ( ( html . match ( /<template v-for="k in \(nc\.actOrder && nc\.actOrder\.length \? nc\.actOrder : \['tel','wx','mail','web','save'\]\)"><div class="diy-card-layer"/g ) || [ ] ) . length , 1 , comp + ' 面板默认行按 actOrder 次序渲染(嵌套 v-for + v-if 匹配)' ) ;
assert . equal ( ( html . match ( /<template v-for="k in \(nc\.actOrder && nc\.actOrder\.length \? nc\.actOrder : \['tel','wx','mail','web','save'\]\)"><div class="diy-card-layer"/g ) || [ ] ) . length , 1 , comp + ' 面板默认行按 actOrder 次序渲染(嵌套 v-for + v-if 匹配)' ) ;
// 2.19.2 删除: 行过滤 + 删除按钮 + 恢复 chips + 预览过滤
assert . match ( html , /v-if="a\.k === k && !\(nc\.actDeleted && nc\.actDeleted\[a\.k\] === 1\)"/ , comp + ' 面板行过滤已删除动作' ) ;
assert . match ( html , /diy-card-layer__delact" title="删除此默认操作\(可在下方恢复\)" @click="\$set\(nc, 'actDeleted', nc\.actDeleted \|\| \{ tel: 0, wx: 0, mail: 0, web: 0, save: 0 \}\); \$set\(nc\.actDeleted, a\.k, 1\);"/ , comp + ' 删除按钮防御式写回 actDeleted' ) ;
assert . match ( html , /diy-card-layer__restore" v-if="nc\.actDeleted && \(nc\.actDeleted\.tel === 1/ , comp + ' 恢复 chips 行(有删除项才显示)' ) ;
assert . match ( html , /v-if="nc\.actDeleted\[t\.k\] === 1" :key="t\.k" @click="\$set\(nc\.actDeleted, t\.k, 0\);"/ , comp + ' 恢复 chip 点击写回 0' ) ;
assert . equal ( ( html . match ( /v-if="a\.k === k && !\(nc\.actVis && nc\.actVis\[a\.k\] === 0\) && !\(nc\.actDeleted && nc\.actDeleted\[a\.k\] === 1\)"/g ) || [ ] ) . length , 2 , comp + ' 预览显隐+删除双重过滤(上/下两副本)' ) ;
assert . equal ( ( html . match ( /js-act-move" title="上移" :data-key="a\.k" data-dir="up"/g ) || [ ] ) . length , 1 , comp + ' 默认行上移按钮(委托)' ) ;
assert . equal ( ( html . match ( /js-act-move" title="上移" :data-key="a\.k" data-dir="up"/g ) || [ ] ) . length , 1 , comp + ' 默认行上移按钮(委托)' ) ;
assert . equal ( ( html . match ( /js-act-move" title="下移" :data-key="a\.k" data-dir="down"/g ) || [ ] ) . length , 1 , comp + ' 默认行下移按钮(委托)' ) ;
assert . equal ( ( html . match ( /js-act-move" title="下移" :data-key="a\.k" data-dir="down"/g ) || [ ] ) . length , 1 , comp + ' 默认行下移按钮(委托)' ) ;
assert . match ( html , /nc\.customActs\.push\(\{ label: '自定义', type: 'copy'[\s\S]*?svg: ''[\s\S]*?vis: 1 \}\)/ , comp + ' 添加自定义操作(全键补齐含 svg)' ) ;
assert . match ( html , /nc\.customActs\.push\(\{ label: '自定义', type: 'copy'[\s\S]*?svg: ''[\s\S]*?vis: 1 \}\)/ , comp + ' 添加自定义操作(全键补齐含 svg)' ) ;
assert . match ( html , /c\.vis = c\.vis === 0 \? 1 : 0;/ , comp + ' 自定义动作眼睛开关' ) ;
assert . match ( html , /c\.vis = c\.vis === 0 \? 1 : 0;/ , comp + ' 自定义动作眼睛开关' ) ;
// 预览消费: 次序 + 显隐过滤 + 文案覆盖 + 自定义动作单元格
// 预览消费: 次序 + 显隐过滤 + 文案覆盖 + 自定义动作单元格
assert . equal ( ( html . match ( /<template v-for="k in \(nc\.actOrder && nc\.actOrder\.length \? nc\.actOrder : \['tel','wx','mail','web','save'\]\)"><template v-for="a in \[\{k:'tel',ch:'打',lb:'打电话'/g ) || [ ] ) . length , 2 , comp + ' 预览默认 cell 按 actOrder 次序渲染(上/下两副本)' ) ;
assert . equal ( ( html . match ( /<template v-for="k in \(nc\.actOrder && nc\.actOrder\.length \? nc\.actOrder : \['tel','wx','mail','web','save'\]\)"><template v-for="a in \[\{k:'tel',ch:'打',lb:'打电话'/g ) || [ ] ) . length , 2 , comp + ' 预览默认 cell 按 actOrder 次序渲染(上/下两副本)' ) ;
assert . equal ( ( html . match ( /v-if="a\.k === k && !\(nc\.actVis && nc\.actVis\[a\.k\] === 0\)"/g ) || [ ] ) . length , 2 , comp + ' 预览显隐过滤(嵌套 key 匹配)' ) ;
assert . match ( html , /\{\{ \(nc\.actLabels && nc\.actLabels\[a\.k\]\) \|\| a\.lb \}\}/ , comp + ' 预览文案覆盖' ) ;
assert . match ( html , /\{\{ \(nc\.actLabels && nc\.actLabels\[a\.k\]\) \|\| a\.lb \}\}/ , comp + ' 预览文案覆盖' ) ;
// svg 快照口径: 预览只读纯数据字段(委托 JS 选图/改色时预计算 data URI 写入),
// svg 快照口径: 预览只读纯数据字段(委托 JS 选图/改色时预计算 data URI 写入),
// 模板内严禁调用全局函数(模板作用域函数解析不可靠, 实测 typeof 恒 false)
// 模板内严禁调用全局函数(模板作用域函数解析不可靠, 实测 typeof 恒 false)