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') const parsed = ts.createSourceFile('profile-edit.tsx', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX) const stateNames = [] const collectStateNames = node => { if (ts.isVariableDeclaration(node) && ts.isArrayBindingPattern(node.name) && node.initializer && ts.isCallExpression(node.initializer) && node.initializer.expression.getText(parsed) === 'useState') { stateNames.push(node.name.elements[0].name.getText(parsed)) } ts.forEachChild(node, collectStateNames) } collectStateNames(parsed) const avatarSheetIndex = stateNames.indexOf('avatarSheetOpen') assert.notEqual(avatarSheetIndex, -1) 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 === avatarSheetIndex) value = sheet return [value, next => updates.push([current, next])] } } if (id === '@tarojs/taro') return { useRouter: () => ({ params: {} }), default: { getWindowInfo: () => ({ statusBarHeight: 44, safeArea: { top: 44 } }), 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, avatarSheetUpdates: () => updates.filter(([index]) => index === avatarSheetIndex).map(([, value]) => value) } } 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.avatarSheetUpdates().includes(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.avatarSheetUpdates().includes(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') })