const test = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const path = require('node:path') const vm = require('node:vm') const ts = require('typescript') const profile = { nickname: '测试', gender: '男', birthday: '1998-06-18', birthdayCalendar: 'solar', birthHour: '辰时', province: '北京市', city: '北京市', district: '朝阳区', remainingEdits: 5 } function render(env, sheet = false) { const exports = {}, updates = [], media = [] let index = 0 const source = fs.readFileSync(path.join(__dirname, '../src/pages/profile-edit/index.tsx'), 'utf8') vm.runInNewContext(ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2019, jsx: ts.JsxEmit.ReactJSX } }).outputText, { exports, process: { env: { TARO_ENV: env } }, require: id => { if (id === 'react/jsx-runtime') return require(id) if (id === 'react') return { useEffect: () => {}, useRef: initial => ({ current: initial }), useState: initial => { const current = index++ let value = typeof initial === 'function' ? initial() : initial if (value === null || (value && typeof value === 'object' && 'nickname' in value)) value = profile if (initial === true) value = false if (current === 5) value = sheet // avatarSheetOpen return [value, next => updates.push([current, next])] } } if (id === '@tarojs/taro') return { useRouter: () => ({ params: {} }), default: { chooseMedia: async options => { media.push(options); return { tempFiles: [{ tempFilePath: 'temp:album', size: 10 }] } }, showToast: () => {} } } if (id === '@tarojs/components') return { View: 'View', Text: 'Text', Button: 'Button', Input: 'Input', Picker: 'Picker' } if (id === 'china-area-data') return { default: {} } if (id.endsWith('/NavBar')) return { NavBar: 'NavBar' } if (id.endsWith('/UserAvatar')) return { UserAvatar: 'UserAvatar' } if (id.endsWith('/BirthdayPicker')) return { BirthdayPicker: 'BirthdayPicker' } if (id.endsWith('/frontendPreview')) return { isFrontendPreview: () => false } if (id.endsWith('/avatarDemo')) return { isAvatarDemo: () => false } return {} } }) const collect = node => { if (!node || typeof node !== 'object') return [] if (Array.isArray(node)) return node.flatMap(collect) return [node, ...collect(node.props?.children)] } return { nodes: collect(exports.default()), updates, media } } test('微信端头像本身直接绑定官方chooseAvatar,不再弹自定义菜单', () => { const h = render('weapp', true) const button = h.nodes.find(node => node.props?.className === 'avatar-button') assert.equal(button.props.openType, 'chooseAvatar') assert.equal(button.props.onClick, undefined) assert.equal(typeof button.props.onChooseAvatar, 'function') assert.ok(!h.nodes.some(node => node.props?.className === 'avatar-mask')) button.props.onChooseAvatar({ detail: { avatarUrl: 'temp:wechat-avatar' } }) assert.equal(h.media.length, 0) assert.ok(h.updates.some(([, value]) => value === 'temp:wechat-avatar')) assert.ok(h.updates.some(([index, value]) => index === 5 && value === false)) }) test('H5保留相册拍照菜单,不伪造微信官方头像选择能力', async () => { const h = render('h5') const button = h.nodes.find(node => node.props?.className === 'avatar-button') assert.equal(button.props.openType, undefined) assert.equal(button.props.onChooseAvatar, undefined) button.props.onClick() assert.ok(h.updates.some(([index, value]) => index === 5 && value === true)) const opened = render('h5', true) const album = opened.nodes.find(node => node.props?.children === '从相册选择') await album.props.onClick() assert.equal(opened.media.length, 1) assert.equal(opened.media[0].sourceType[0], 'album') })