feat: 完善淘宝小程序拍照、报告与登录流程

接入本地实时识别和自动三连拍,调整报告裁切、商品跳转、分享标题及居中页眉;完善游客会话、后台渠道隔离、部署脚本与回归测试。
This commit is contained in:
xsl
2026-11-04 15:07:51 +08:00
parent 381da60ae4
commit 774376fa16
147 changed files with 17420 additions and 597 deletions
+82
View File
@@ -23,6 +23,59 @@ function page(file, deps = {}, extra = {}) {
return { instance, timers, context };
}
const flush = () => new Promise(resolve => setImmediate(resolve));
test('cold launch resets session without loading or creating a native audio context', () => {
let app, resets = 0, restores = 0;
vm.runInNewContext(fs.readFileSync(path.join(base, 'app.js'), 'utf8'), {
App(value) { app = value; },
require(name) {
assert.equal(name, './utils/api.js', 'startup must not load the voice module');
return { resetForLaunch() { resets++; }, hasSession: () => false, restore() { restores++; } };
}
});
app.onLaunch(); app.onShow();
assert.equal(resets, 1); assert.equal(restores, 0);
});
test('global app share uses the login header title and a public entry without report or session parameters', () => {
let app;
vm.runInNewContext(fs.readFileSync(path.join(base, 'app.js'), 'utf8'), {
App(value) { app = value; }, require(name) { return require(path.resolve(base, name)); }
});
const shared = app.onShareAppMessage();
const title = require('../utils/page-header').layout().title;
assert.equal(shared.title, title);
assert.equal(shared.desc, title);
assert.equal(shared.path, 'pages/login/login');
assert.deepEqual(Object.keys(shared).sort(), ['desc', 'path', 'title']);
});
test('every registered business page explicitly shares the apostrophe title and public login entry', () => {
const routes = JSON.parse(fs.readFileSync(path.join(base, 'app.json'), 'utf8')).pages;
const title = require('../utils/page-header').layout().title;
for (const route of routes) {
const p = page(route + '.js', { '../../utils/api': { assetUrl: value => value } }).instance;
p.data.profile = { name: 'private-profile' }; p.data.payload = { report: 'private-report' };
const result = p.onShareAppMessage({ from: 'menu' });
assert.equal(result.title, title, route); assert.equal(result.desc, title, route);
assert.equal(result.path, 'pages/login/login', route);
assert.deepEqual(Object.keys(result).sort(), ['desc', 'path', 'title']);
result.title = 'changed'; assert.equal(p.onShareAppMessage().title, title, 'each share returns a fresh object');
}
});
test('login first-screen images are packaged original H5 assets with no remote URL dependency', () => {
const p = page('pages/login/login.js', { '../../utils/api': {} }).instance;
let total = 0;
for (const key of ['bgUrl', 'brandUrl', 'guestIconUrl']) {
assert.match(p.data[key], /^\.\.\/\.\.\/assets\/startup\//);
const local = path.resolve(base, 'pages/login', p.data[key]);
const original = path.join(base, '../h5/assets/img/ui-20261009', path.basename(local));
const bytes = fs.readFileSync(local); total += bytes.length;
assert.deepEqual(bytes, fs.readFileSync(original), 'visual assets remain byte-for-byte identical');
}
assert(total < 140 * 1024);
});
test('offline camera debug skips identity/profile, never uploads or plays remote voice, and clears previews on exit', async () => {
let backendCalls = 0, voiceCalls = 0, exited = 0, authReads = 0;
const voice = { play() { voiceCalls++; }, stop() {}, VOICE: { step: [] } };
@@ -133,6 +186,35 @@ test('province changes reset cities, canceled choices do not leak into reopening
p.setData({ birth: '1996-01', age: 30, birthText: '1996年1月', genderText: '女' });
p.onStartCamera(); assert.equal(saved.key, 'juzi.taobao.profile.account-a'); assert.equal(app.globalData.profile.real_age, 30);
});
test('gender wheel saves only on confirmation and canceled drafts never replace the committed value', () => {
const api = { assetUrl: v => v, user: () => ({ id: 'guest-a', kind: 'taobao_guest' }) };
const p = page('pages/profile/profile.js', { '../../utils/api.js': api }).instance;
p.onLoad();
p.onOpenGender();
assert.equal(p.data.pickerTitle, '性别'); assert.equal(p.data.picker, 'gender');
assert.deepEqual(Array.from(p.data.pickerValue), [0]);
p.onPickerClose(); assert.equal(p.data.genderText, '', 'cancel does not implicitly choose the first option');
p.onOpenGender(); p.onPickerChange({ detail: { value: [1] } });
assert.equal(p.data.genderText, '', 'scrolling changes only the draft');
p.onPickerConfirm(); assert.equal(p.data.genderText, '男'); assert.equal(p.data.picker, '');
p.onOpenGender(); assert.deepEqual(Array.from(p.data.pickerValue), [1]);
p.onPickerChange({ detail: { value: [0] } }); p.onPickerClose();
assert.equal(p.data.genderText, '男');
p.onOpenGender(); assert.deepEqual(Array.from(p.data.pickerValue), [1]);
p.onPickerChange({ detail: { value: [0] } }); p.onPickerConfirm();
assert.equal(p.data.genderText, '女');
});
test('restored gender initializes the wheel and confirmation leaves birth and region intact', () => {
const api = { assetUrl: v => v, user: () => ({ id: 'account-a' }) };
const p = page('pages/profile/profile.js', { '../../utils/api.js': api }, {
my: { getStorageSync: () => ({ data: { birth: '1996-01', genderText: '男', regionText: '北京' } }) }
}).instance;
p.onLoad(); p.onOpenGender(); assert.deepEqual(Array.from(p.data.pickerValue), [1]);
p.onPickerChange({ detail: { value: [0] } }); p.onPickerConfirm();
assert.equal(p.data.genderText, '女'); assert.equal(p.data.birth, '1996-01'); assert.equal(p.data.regionText, '北京');
});
test('native AXML event handlers resolve and all routes have JS/AXML/ACSS/JSON files', () => {
const pages = require('../app.json').pages;
for (const route of pages) {