Files
prop-ai-hr/mobile-uni/tests/microphone-permission.test.mjs

126 lines
4.7 KiB
JavaScript

import assert from 'node:assert/strict';
import { after, test } from 'node:test';
import { fileURLToPath } from 'node:url';
import { createServer } from 'vite';
const vite = await createServer({
root: fileURLToPath(new URL('..', import.meta.url)),
configFile: false,
optimizeDeps: { noDiscovery: true },
server: { middlewareMode: true, hmr: false },
appType: 'custom'
});
after(async () => {
await vite.close();
delete globalThis.plus;
});
const load = () => vite.ssrLoadModule('/src/utils/microphone.ts');
test('底层音频错误一律翻译为中文,英文原文不得透给用户', async () => {
const { describeMicrophoneError } = await load();
const fallback = '实时陪练连接失败,请使用录音对练';
// 这些是 Chromium / WebRTC 的真实原文,真机上曾直接展示给用户。
const rawErrors = [
new Error('Could not start audio source'),
Object.assign(new Error('Permission denied'), { name: 'NotAllowedError' }),
Object.assign(new Error('Requested device not found'), { name: 'NotFoundError' }),
Object.assign(new Error('Could not start audio source'), { name: 'NotReadableError' }),
'getUserMedia requires a secure context'
];
for (const error of rawErrors) {
const text = describeMicrophoneError(error, fallback);
assert.match(text, /[一-龥]/, `应翻译为中文: ${error}`);
assert.ok(!/[A-Za-z]{6,}/.test(text), `不应残留英文原文: ${text}`);
}
});
test('业务层自抛的中文错误原样透出,不被兜底文案吞掉', async () => {
const { describeMicrophoneError } = await load();
const own = '实时语音模块尚未就绪,请稍后重试';
assert.equal(describeMicrophoneError(new Error(own), '兜底文案'), own);
});
test('无法识别的错误回落到兜底文案', async () => {
const { describeMicrophoneError } = await load();
assert.equal(describeMicrophoneError(new Error('zzz unknown'), '兜底文案'), '兜底文案');
assert.equal(describeMicrophoneError(undefined, '兜底文案'), '兜底文案');
});
test('Android 运行时主动申请 RECORD_AUDIO 并区分拒绝与永久拒绝', async () => {
const requested = [];
globalThis.plus = {
android: {
runtimeMainActivity: () => ({ hasWindowFocus: () => true }),
requestPermissions: (permissions, success) => {
requested.push(...permissions);
success(pendingResult);
}
}
};
let pendingResult = { granted: ['android.permission.RECORD_AUDIO'] };
const { requestMicrophonePermission } = await load();
assert.equal(await requestMicrophonePermission(), 'granted');
assert.deepEqual(requested, ['android.permission.RECORD_AUDIO']);
pendingResult = { deniedPresent: ['android.permission.RECORD_AUDIO'] };
assert.equal(await requestMicrophonePermission(), 'denied');
pendingResult = { deniedAlways: ['android.permission.RECORD_AUDIO'] };
assert.equal(await requestMicrophonePermission(), 'denied-always');
delete globalThis.plus;
});
test('权限 Activity 未归还窗口焦点前不结束授权流程,避免文件选择器被系统吞掉', async () => {
let focused = false;
let importedActivity = false;
const activity = { hasWindowFocus: () => focused };
globalThis.plus = {
android: {
importClass: (target) => {
assert.equal(target, activity);
importedActivity = true;
},
runtimeMainActivity: () => activity,
requestPermissions: (_permissions, success) => {
success({ deniedAlways: ['android.permission.RECORD_AUDIO'] });
}
}
};
const { requestMicrophonePermission } = await load();
const lifecycle = await vite.ssrLoadModule('/src/services/file-selection-lifecycle.ts');
let resolved = false;
const permission = requestMicrophonePermission().then((result) => {
resolved = true;
return result;
});
await new Promise((resolve) => setTimeout(resolve, 20));
assert.equal(importedActivity, true);
assert.equal(lifecycle.isPlatformNativeActivityActive(), true);
assert.equal(lifecycle.isPlatformFileSelectionActive(), false);
assert.equal(resolved, false);
focused = true;
assert.equal(await permission, 'denied-always');
assert.equal(resolved, true);
assert.equal(lifecycle.isPlatformNativeActivityActive(), false);
});
test('非 App 运行时返回 unsupported,交由平台原生授权流程处理', async () => {
delete globalThis.plus;
const { requestMicrophonePermission } = await load();
assert.equal(await requestMicrophonePermission(), 'unsupported');
});
test('永久拒绝的引导文案指向系统设置,普通拒绝不误导', async () => {
const { microphoneDeniedMessage } = await load();
assert.match(microphoneDeniedMessage('denied-always'), /系统设置/);
assert.ok(!/系统设置/.test(microphoneDeniedMessage('denied')));
});