diff --git a/tests/verify_mp_kefu_navigation_spec.js b/tests/verify_mp_kefu_navigation_spec.js new file mode 100644 index 0000000..9ee6693 --- /dev/null +++ b/tests/verify_mp_kefu_navigation_spec.js @@ -0,0 +1,125 @@ +/** + * 测试用例:验证微信小程序客服跳转逻辑及组件绑定 + * 覆盖范围: + * 1. s-custom-navbar / navbar-item.vue 微信小程序 open-type="contact" 条件编译及客服判定逻辑 + * 2. detail-tabbar.vue 微信小程序客服按钮与卡片信息透传 + * 3. s-menu-tools.vue 全局快捷菜单客服按钮 open-type="contact" 适配 + * 4. feedback.vue 意见反馈页面客服按钮 open-type="contact" 适配 + * 5. aftersale/apply.vue & detail.vue 售后客服按钮适配 + * 6. chat/index.vue 微信小程序原生客服提示与快速接入组件 + */ + +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); + +console.log('=== 开始执行微信小程序客服跳转自动化测试 ===\n'); + +// 1. 验证 navbar-item.vue +const navbarItemPath = path.resolve(__dirname, '../uniapp/sheep/components/s-custom-navbar/components/navbar-item.vue'); +assert(fs.existsSync(navbarItemPath), 'navbar-item.vue 文件必须存在'); +const navbarItemContent = fs.readFileSync(navbarItemPath, 'utf-8'); + +assert(navbarItemContent.includes('#ifdef MP-WEIXIN'), 'navbar-item.vue 必须包含微信小程序条件编译'); +assert(navbarItemContent.includes('open-type="contact"'), 'navbar-item.vue 必须包含 open-type="contact" 属性'); +assert(navbarItemContent.includes('isCustomerService'), 'navbar-item.vue 必须包含 isCustomerService 计算属性'); +assert(navbarItemContent.includes("url === '/pages/chat/index'"), 'navbar-item.vue 必须精准识别 /pages/chat/index 路由'); +assert(navbarItemContent.includes('c47b048175b325b1e78f837a3b696794'), 'navbar-item.vue 必须兜底兼容生产数据库客服图片MD5特征'); +assert(navbarItemContent.includes('#ifndef MP-WEIXIN'), 'navbar-item.vue 必须保持非小程序环境原有路由兼容性'); +console.log('✅ 测试点 1 通过: 顶部导航栏 navbar-item.vue 微信小程序客服按钮及条件编译正确'); + +// 2. 模拟 isCustomerService 逻辑验证 +function testCustomerServiceMatcher(item) { + const url = item?.url || ''; + const src = item?.src || ''; + return ( + url === '/pages/chat/index' || + url.startsWith('/pages/chat') || + url.includes('chat') || + url.includes('kefu') || + url.includes('customer_service') || + src.includes('c47b048175b325b1e78f837a3b696794') + ); +} + +// 正常场景 +assert.strictEqual( + testCustomerServiceMatcher({ + type: 'image', + url: '/pages/chat/index', + src: '/storage/decorate/20221115/c47b048175b325b1e78f837a3b696794.png' + }), + true, + '生产环境会员中心导航栏客服项必须识别为客服' +); + +// 扩展场景:带参数的客服路径 +assert.strictEqual( + testCustomerServiceMatcher({ + type: 'image', + url: '/pages/chat/index?from=user_center', + src: '/storage/demo.png' + }), + true, + '带查询参数的 chat 路径必须识别为客服' +); + +// 边界场景:普通商品推广项不可误判 +assert.strictEqual( + testCustomerServiceMatcher({ + type: 'image', + url: '/pages/commission/goods', + src: '/storage/decorate/20221115/f09ef51624e48d7d8c58cd602110c46e.png' + }), + false, + '普通导航项(如推广商品)绝不可误判为客服' +); + +// 边界场景:空对象或未配置 +assert.strictEqual(testCustomerServiceMatcher({}), false, '空数据对象不可误判为客服'); +console.log('✅ 测试点 2 通过: 客服识别算法正常、边界与防误判逻辑全部通过'); + +// 3. 验证 detail-tabbar.vue +const detailTabbarPath = path.resolve(__dirname, '../uniapp/pages/goods/components/detail/detail-tabbar.vue'); +assert(fs.existsSync(detailTabbarPath), 'detail-tabbar.vue 文件必须存在'); +const detailTabbarContent = fs.readFileSync(detailTabbarPath, 'utf-8'); + +assert(detailTabbarContent.includes('#ifdef MP-WEIXIN'), 'detail-tabbar.vue 必须包含小程序条件编译'); +assert(detailTabbarContent.includes('open-type="contact"'), 'detail-tabbar.vue 客服入口必须包含 open-type="contact"'); +assert(detailTabbarContent.includes('show-message-card'), 'detail-tabbar.vue 必须支持发送商品详情卡片给客服'); +console.log('✅ 测试点 3 通过: 商品详情页底部客服按钮及商品卡片透传配置正确'); + +// 4. 验证 s-menu-tools.vue +const menuToolsPath = path.resolve(__dirname, '../uniapp/sheep/components/s-menu-tools/s-menu-tools.vue'); +assert(fs.existsSync(menuToolsPath), 's-menu-tools.vue 文件必须存在'); +const menuToolsContent = fs.readFileSync(menuToolsPath, 'utf-8'); + +assert(menuToolsContent.includes('#ifdef MP-WEIXIN'), 's-menu-tools.vue 必须包含小程序条件编译'); +assert(menuToolsContent.includes('open-type="contact"'), 's-menu-tools.vue 客服项必须支持 open-type="contact"'); +console.log('✅ 测试点 4 通过: 全局快捷工具菜单客服直达配置正确'); + +// 5. 验证 feedback.vue +const feedbackPath = path.resolve(__dirname, '../uniapp/pages/public/feedback.vue'); +assert(fs.existsSync(feedbackPath), 'feedback.vue 文件必须存在'); +const feedbackContent = fs.readFileSync(feedbackPath, 'utf-8'); + +assert(feedbackContent.includes('open-type="contact"'), 'feedback.vue 必须支持小程序原生客服'); +console.log('✅ 测试点 5 通过: 意见反馈页客服直达配置正确'); + +// 6. 验证 aftersale 页面 +const aftersaleApplyPath = path.resolve(__dirname, '../uniapp/pages/order/aftersale/apply.vue'); +const aftersaleDetailPath = path.resolve(__dirname, '../uniapp/pages/order/aftersale/detail.vue'); +assert(fs.existsSync(aftersaleApplyPath) && fs.existsSync(aftersaleDetailPath), '售后页面必须存在'); +assert(fs.readFileSync(aftersaleApplyPath, 'utf-8').includes('open-type="contact"'), '售后申请页必须支持小程序客服'); +assert(fs.readFileSync(aftersaleDetailPath, 'utf-8').includes('open-type="contact"'), '售后详情页必须支持小程序客服'); +console.log('✅ 测试点 6 通过: 售后申请与售后详情页客服直达配置正确'); + +// 7. 验证 chat/index.vue 兜底接入组件 +const chatIndexPath = path.resolve(__dirname, '../uniapp/pages/chat/index.vue'); +assert(fs.existsSync(chatIndexPath), 'chat/index.vue 文件必须存在'); +const chatIndexContent = fs.readFileSync(chatIndexPath, 'utf-8'); +assert(chatIndexContent.includes('mp-service-bar'), 'chat/index.vue 必须包含微信小程序客服直通卡片'); +assert(chatIndexContent.includes('open-type="contact"'), 'chat/index.vue 必须支持 open-type="contact"'); +console.log('✅ 测试点 7 通过: 客服主页面微信小程序客服快速接入口已就绪'); + +console.log('\n🎉 所有测试用例执行完毕,全部校验通过!'); diff --git a/uniapp/pages/chat/index.vue b/uniapp/pages/chat/index.vue index 0121df9..961768d 100644 --- a/uniapp/pages/chat/index.vue +++ b/uniapp/pages/chat/index.vue @@ -1,7 +1,16 @@