|
|
@@ -1,49 +1,165 @@
|
|
|
-const assert = require('assert');
|
|
|
const fs = require('fs');
|
|
|
const path = require('path');
|
|
|
|
|
|
const root = path.join(__dirname, '..');
|
|
|
+const applyEntryDir = path.join(root, 'packageMRC', 'pages', 'apply-entry');
|
|
|
+const applyEntryScriptPath = path.join(applyEntryDir, 'index.js');
|
|
|
+const applyEntryTemplatePath = path.join(applyEntryDir, 'index.wxml');
|
|
|
const patientCreateDir = path.join(root, 'packageMRC', 'pages', 'patient-create');
|
|
|
const patientCreateScriptPath = path.join(patientCreateDir, 'index.js');
|
|
|
const patientCreateTemplatePath = path.join(patientCreateDir, 'index.wxml');
|
|
|
const adapterPath = path.join(root, 'packageMRC', 'common', 'mrcDemoState.js');
|
|
|
const demoState = require(adapterPath);
|
|
|
+const failures = [];
|
|
|
|
|
|
-function readRequired(filePath, intent) {
|
|
|
- assert(fs.existsSync(filePath), intent);
|
|
|
- return fs.readFileSync(filePath, 'utf8');
|
|
|
+function expect(condition, message) {
|
|
|
+ if (!condition) failures.push(message);
|
|
|
+}
|
|
|
+
|
|
|
+function readOptional(filePath) {
|
|
|
+ return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
|
|
|
}
|
|
|
|
|
|
function assertNoForbiddenCapability(source, scope) {
|
|
|
- assert(!/\bwx\.(?:request|upload\w*|choose(?:Image|Media|MessageFile)|getStorage(?:Sync)?|setStorage(?:Sync)?|removeStorage(?:Sync)?|clearStorage(?:Sync)?)\b/.test(source), `${scope}不得使用 wx.request、上传能力或 Storage`);
|
|
|
- assert(!/(?:utils\/(?:patientMaintenance|patientSelector|homePatient)|pages\/mine\/patientEdit)/.test(source), `${scope}不得引用既有就诊人服务或页面`);
|
|
|
+ expect(!/\bwx\.(?:request|upload\w*|choose(?:Image|Media|MessageFile)|getStorage(?:Sync)?|setStorage(?:Sync)?|removeStorage(?:Sync)?|clearStorage(?:Sync)?)\b/.test(source), `${scope}不得使用 wx.request、上传能力或 Storage`);
|
|
|
+ expect(!/(?:utils\/(?:patientMaintenance|patientSelector|homePatient)|pages\/mine\/patientEdit)/.test(source), `${scope}不得引用既有就诊人服务或页面`);
|
|
|
+}
|
|
|
+
|
|
|
+function handlerForClass(template, className, interactionName) {
|
|
|
+ const tag = new RegExp(`<[^>]*class=["'][^"']*\\b${className}\\b[^"']*["'][^>]*>`, 'g').exec(template);
|
|
|
+ expect(Boolean(tag), `${interactionName}必须保留 .${className} 可点击节点`);
|
|
|
+ if (!tag) return '';
|
|
|
+ const binding = /bindtap=["']([\w$]+)["']/.exec(tag[0]);
|
|
|
+ expect(Boolean(binding), `${interactionName}必须绑定 tap 处理函数`);
|
|
|
+ return binding ? binding[1] : '';
|
|
|
+}
|
|
|
+
|
|
|
+function loadApplyEntryPage() {
|
|
|
+ let definition;
|
|
|
+ const originalPage = global.Page;
|
|
|
+ global.Page = (config) => { definition = config; };
|
|
|
+ delete require.cache[require.resolve(applyEntryScriptPath)];
|
|
|
+ try {
|
|
|
+ require(applyEntryScriptPath);
|
|
|
+ } catch (error) {
|
|
|
+ failures.push(`申请入口 JS 必须可由 Page mock 加载:${error.message}`);
|
|
|
+ } finally {
|
|
|
+ global.Page = originalPage;
|
|
|
+ }
|
|
|
+ expect(Boolean(definition), '申请入口必须通过 Page 注册,供真实小程序事件分发');
|
|
|
+ return definition;
|
|
|
}
|
|
|
|
|
|
-const patientCreateScript = readRequired(patientCreateScriptPath, '本地 Mock 添加就诊人页必须存在,当前交互契约应保持 RED 直至该页实现');
|
|
|
-const patientCreateTemplate = readRequired(patientCreateTemplatePath, '本地 Mock 添加就诊人页必须提供可操作表单');
|
|
|
-const adapterSource = fs.readFileSync(adapterPath, 'utf8');
|
|
|
+function mountPage(definition) {
|
|
|
+ const page = {
|
|
|
+ data: Object.assign({}, definition.data || {}),
|
|
|
+ setData(nextData) { Object.assign(this.data, nextData || {}); }
|
|
|
+ };
|
|
|
+ Object.keys(definition).forEach((key) => {
|
|
|
+ if (typeof definition[key] === 'function') page[key] = definition[key];
|
|
|
+ });
|
|
|
+ return page;
|
|
|
+}
|
|
|
+
|
|
|
+function invoke(page, handlerName, event, interactionName) {
|
|
|
+ if (!handlerName || typeof page[handlerName] !== 'function') {
|
|
|
+ failures.push(`${interactionName}的 ${handlerName || 'tap'} 处理函数必须在申请入口 Page 中实现`);
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return page[handlerName].call(page, event || {});
|
|
|
+ } catch (error) {
|
|
|
+ failures.push(`${interactionName}必须可在 Page mock 中执行:${error.message}`);
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const applyEntryTemplate = readOptional(applyEntryTemplatePath);
|
|
|
+expect(Boolean(applyEntryTemplate), '申请入口 WXML 必须存在');
|
|
|
+const serviceHandler = handlerForClass(applyEntryTemplate, 'service-btn', '服务 CTA');
|
|
|
+const applicationHandler = handlerForClass(applyEntryTemplate, 'my-application-card', '我的申请');
|
|
|
+const patientSelectorHandler = handlerForClass(applyEntryTemplate, 'patient-selector', '就诊人选择');
|
|
|
+const patientSheetHandler = handlerForClass(applyEntryTemplate, 'sheet-item', '就诊人抽屉选项');
|
|
|
+const demoEntryHandler = handlerForClass(applyEntryTemplate, 'demo-mode-entry', 'Demo 模式入口');
|
|
|
+const demoCloseHandler = handlerForClass(applyEntryTemplate, 'demo-mode-close', 'Demo 模式关闭');
|
|
|
+const patientAddHandler = handlerForClass(applyEntryTemplate, 'patient-add-card', '无就诊人添加');
|
|
|
+
|
|
|
+const originalWx = global.wx;
|
|
|
+const navigations = [];
|
|
|
+global.wx = {
|
|
|
+ navigateTo(options) { navigations.push(options || {}); },
|
|
|
+ showToast() {}
|
|
|
+};
|
|
|
|
|
|
-assert(/bindinput=/.test(patientCreateTemplate), '添加就诊人页必须保留原型表单的真实输入交互');
|
|
|
-assert(/createAnonymousPatient/.test(patientCreateScript), '添加就诊人页必须仅通过 Demo Adapter 创建匿名患者');
|
|
|
-assert.strictEqual(typeof demoState.createAnonymousPatient, 'function', 'Demo Adapter 必须提供 createAnonymousPatient,且不能写入原始表单值');
|
|
|
+try {
|
|
|
+ const definition = loadApplyEntryPage();
|
|
|
+ if (definition) {
|
|
|
+ const page = mountPage(definition);
|
|
|
+ invoke(page, patientAddHandler, {}, '无就诊人添加');
|
|
|
+ expect(navigations.some((item) => item.url === '/packageMRC/pages/patient-create/index'), '无就诊人添加必须实际导航到本地 patient-create 页面');
|
|
|
+
|
|
|
+ invoke(page, demoEntryHandler, {}, 'Demo 模式入口');
|
|
|
+ expect(page.data.showDemoMode === true, '点击 Demo 模式入口必须打开辅助面板');
|
|
|
+ invoke(page, demoCloseHandler, {}, 'Demo 模式关闭');
|
|
|
+ expect(page.data.showDemoMode === false, '关闭 Demo 模式必须恢复患者首屏');
|
|
|
+
|
|
|
+ if (typeof demoState.createAnonymousPatient === 'function') {
|
|
|
+ const created = demoState.createAnonymousPatient({
|
|
|
+ name: 'PATIENT_INPUT_NAME_MUST_NOT_PERSIST',
|
|
|
+ idCard: 'PATIENT_INPUT_ID_MUST_NOT_PERSIST',
|
|
|
+ mobile: 'PATIENT_INPUT_MOBILE_MUST_NOT_PERSIST'
|
|
|
+ });
|
|
|
+ expect(created && created.ok, 'Demo Adapter 必须能生成匿名演示就诊人');
|
|
|
+ invoke(page, 'onShow', {}, 'patient-create 返回后的入口刷新');
|
|
|
+ expect(page.data.currentPatient && /演示就诊人/.test(page.data.currentPatient.displayName || ''), 'patient-create 返回后 onShow 必须回填当前匿名就诊人');
|
|
|
+
|
|
|
+ invoke(page, patientSelectorHandler, {}, '就诊人选择');
|
|
|
+ expect(page.data.showPatientSheet === true, '点击当前就诊人必须打开底部选择抽屉');
|
|
|
+ const candidate = (page.data.patients || []).find((patient) => patient.id !== (page.data.currentPatient || {}).id);
|
|
|
+ expect(Boolean(candidate), '本地演示必须提供至少两名匿名就诊人供抽屉选择');
|
|
|
+ if (candidate) {
|
|
|
+ invoke(page, patientSheetHandler, { currentTarget: { dataset: { id: candidate.id } } }, '就诊人抽屉选项');
|
|
|
+ expect(page.data.showPatientSheet === false, '选择就诊人后必须关闭底部抽屉');
|
|
|
+ expect((page.data.currentPatient || {}).id === candidate.id, '选择就诊人后必须回填当前就诊人卡片');
|
|
|
+ }
|
|
|
+
|
|
|
+ invoke(page, serviceHandler, {}, '服务 CTA');
|
|
|
+ expect(navigations.some((item) => /^\/packageMRC\/pages\/identity\/index(?:\?|$)/.test(item.url || '')), '有匿名就诊人时“我要申请复印”必须进入身份验证页');
|
|
|
+ } else {
|
|
|
+ failures.push('Demo Adapter 必须提供 createAnonymousPatient,且不能写入原始表单值');
|
|
|
+ }
|
|
|
+
|
|
|
+ invoke(page, applicationHandler, {}, '我的申请');
|
|
|
+ expect(navigations.some((item) => item.url === '/packageMRC/pages/my-applications/index'), '点击“我的申请”必须进入申请列表页');
|
|
|
+ }
|
|
|
+} finally {
|
|
|
+ global.wx = originalWx;
|
|
|
+}
|
|
|
+
|
|
|
+const patientCreateScript = readOptional(patientCreateScriptPath);
|
|
|
+const patientCreateTemplate = readOptional(patientCreateTemplatePath);
|
|
|
+const adapterSource = readOptional(adapterPath);
|
|
|
+expect(Boolean(patientCreateScript), '本地 Mock 添加就诊人页必须存在,当前交互契约应保持 RED 直至该页实现');
|
|
|
+expect(Boolean(patientCreateTemplate), '本地 Mock 添加就诊人页必须提供可操作表单');
|
|
|
+expect(/bindinput=/.test(patientCreateTemplate), '添加就诊人页必须保留原型表单的真实输入交互');
|
|
|
+expect(/createAnonymousPatient/.test(patientCreateScript), '添加就诊人页必须仅通过 Demo Adapter 创建匿名患者');
|
|
|
+expect(typeof demoState.createAnonymousPatient === 'function', 'Demo Adapter 必须提供 createAnonymousPatient,且不能写入原始表单值');
|
|
|
|
|
|
assertNoForbiddenCapability(patientCreateScript, '本地 Mock 添加就诊人页');
|
|
|
assertNoForbiddenCapability(adapterSource, 'MRC Demo Adapter');
|
|
|
|
|
|
-const rawInput = {
|
|
|
- name: 'PATIENT_INPUT_NAME_MUST_NOT_PERSIST',
|
|
|
- idCard: 'PATIENT_INPUT_ID_MUST_NOT_PERSIST',
|
|
|
- mobile: 'PATIENT_INPUT_MOBILE_MUST_NOT_PERSIST'
|
|
|
-};
|
|
|
-const createResult = demoState.createAnonymousPatient(rawInput);
|
|
|
-assert(createResult && createResult.ok, 'Demo Adapter 必须能生成匿名演示就诊人');
|
|
|
-
|
|
|
-const snapshotResult = demoState.getSnapshot();
|
|
|
-assert(snapshotResult && snapshotResult.ok, '匿名患者创建后必须能读取 Demo Adapter 快照');
|
|
|
-const snapshotText = JSON.stringify(snapshotResult.data);
|
|
|
-Object.keys(rawInput).forEach((field) => {
|
|
|
- assert(!snapshotText.includes(rawInput[field]), `Adapter 快照不得包含 patient-create 的原始 ${field} 输入`);
|
|
|
-});
|
|
|
-assert(/演示就诊人/.test(snapshotText), 'Adapter 快照只能暴露生成后的匿名演示就诊人');
|
|
|
+if (typeof demoState.createAnonymousPatient === 'function') {
|
|
|
+ const snapshotResult = demoState.getSnapshot();
|
|
|
+ expect(snapshotResult && snapshotResult.ok, '匿名患者创建后必须能读取 Demo Adapter 快照');
|
|
|
+ const snapshotText = JSON.stringify(snapshotResult && snapshotResult.data);
|
|
|
+ ['PATIENT_INPUT_NAME_MUST_NOT_PERSIST', 'PATIENT_INPUT_ID_MUST_NOT_PERSIST', 'PATIENT_INPUT_MOBILE_MUST_NOT_PERSIST'].forEach((input) => {
|
|
|
+ expect(!snapshotText.includes(input), `Adapter 快照不得包含 patient-create 的原始输入:${input}`);
|
|
|
+ });
|
|
|
+ expect(/演示就诊人/.test(snapshotText), 'Adapter 快照只能暴露生成后的匿名演示就诊人');
|
|
|
+}
|
|
|
+
|
|
|
+if (failures.length) {
|
|
|
+ throw new Error(`mrc-apply-entry interaction contract: FAIL\n- ${failures.join('\n- ')}`);
|
|
|
+}
|
|
|
|
|
|
process.stdout.write('mrc-apply-entry interaction contract: PASS\n');
|