feat(release): close handler and microphone gaps

This commit is contained in:
key
2026-07-30 12:01:45 +08:00
parent 8934f13811
commit c16a3e83d7
30 changed files with 830 additions and 49 deletions
+24 -7
View File
@@ -195,6 +195,19 @@ test('Android 文件选择期间保持页面请求有效,并在完成后恢复
assert.equal(fileSelectionLifecycle.isPlatformFileSelectionActive(), false);
});
test('Android 权限 Activity 期间保持原生交互状态,但不冒充文件选择', async () => {
let finishPermission;
const permission = fileSelectionLifecycle.withPlatformPermissionActivity(() =>
new Promise((resolve) => { finishPermission = resolve; })
);
assert.equal(fileSelectionLifecycle.isPlatformNativeActivityActive(), true);
assert.equal(fileSelectionLifecycle.isPlatformFileSelectionActive(), false);
finishPermission('denied');
assert.equal(await permission, 'denied');
assert.equal(fileSelectionLifecycle.isPlatformNativeActivityActive(), false);
});
test('Android App 图片和视频使用系统媒体选择器', async () => {
globalThis.plus = androidPickerRuntime();
globalThis.uni = {
@@ -251,19 +264,23 @@ test('所有使用文件选择器的页面都按取消信号恢复空闲态而
assert.match(pages[6], /isFileSelectionCancelled\(error\)[\s\S]*?message\.value = ''[\s\S]*?return/);
});
test('会作废转写请求的页面在 Android 系统选择器打开时保留选择任务', async () => {
test('会作废转写请求的页面在 Android 系统权限框或选择器打开时保留选择任务', async () => {
const pages = await Promise.all([
'../src/pages/candidate/interview/index.vue',
'../src/pages/user/practice/index.vue',
'../src/pages/user/sop/index.vue',
'../src/pages/user/report/index.vue'
'../src/pages/user/report/index.vue',
'../src/pages/user/today/index.vue',
'../src/pages/user/direct/index.vue'
].map(read));
for (const page of pages) assert.match(page, /isPlatformFileSelectionActive\(\)/);
assert.match(pages[0], /if \(!isPlatformFileSelectionActive\(\)\) cancelQuestionRecording\(\)/);
assert.match(pages[1], /if \(!isPlatformFileSelectionActive\(\)\) cancelRecording\(\)/);
assert.match(pages[2], /if \(!isPlatformFileSelectionActive\(\)\) cancelVoiceTranscription\(\)/);
assert.match(pages[3], /teardown\(isPlatformFileSelectionActive\(\)\)/);
for (const page of pages) assert.match(page, /isPlatformNativeActivityActive\(\)/);
assert.match(pages[0], /if \(!isPlatformNativeActivityActive\(\)\) cancelQuestionRecording\(\)/);
assert.match(pages[1], /if \(!isPlatformNativeActivityActive\(\)\) cancelRecording\(\)/);
assert.match(pages[2], /if \(!isPlatformNativeActivityActive\(\)\)[\s\S]*?cancelVoiceTranscription\(\)/);
assert.match(pages[3], /teardown\(isPlatformNativeActivityActive\(\)\)/);
assert.match(pages[4], /if \(!isPlatformNativeActivityActive\(\)\)[\s\S]*?holdGeneration \+= 1/);
assert.match(pages[5], /if \(!isPlatformNativeActivityActive\(\)\) teardownVoice\(\)/);
});
test('Android SAF 复制完全在 Java FileChannel 内完成,不回退 Native.js byte 数组', async () => {
@@ -54,6 +54,7 @@ test('Android 运行时主动申请 RECORD_AUDIO 并区分拒绝与永久拒绝'
const requested = [];
globalThis.plus = {
android: {
runtimeMainActivity: () => ({ hasWindowFocus: () => true }),
requestPermissions: (permissions, success) => {
requested.push(...permissions);
success(pendingResult);
@@ -75,6 +76,42 @@ test('Android 运行时主动申请 RECORD_AUDIO 并区分拒绝与永久拒绝'
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();
+1 -1
View File
@@ -115,7 +115,7 @@ test('异步请求具备 generation、上传取消和离页 teardown', async ()
assert.match(page, /:disabled="draftLocked \|\| submitActionDisabled"/);
assert.match(page, /composerRef\.value\?\.teardown\(\)/);
assert.match(page, /speechPlayback\.stop\(\)/);
assert.match(page, /onHide\(\(\) => teardown\(isPlatformFileSelectionActive\(\)\)\)/);
assert.match(page, /onHide\(\(\) => teardown\(isPlatformNativeActivityActive\(\)\)\)/);
assert.match(page, /onUnload/);
});