fix(mobile): handle file picker cancellation
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package",
|
||||
"name": "aihr-mobile-uni",
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.9",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"appid" : "__UNI__B36D8BE",
|
||||
"description" : "帮道员工端",
|
||||
"vueVersion" : "3",
|
||||
"versionName" : "0.1.8",
|
||||
"versionCode" : "108",
|
||||
"versionName" : "0.1.9",
|
||||
"versionCode" : "109",
|
||||
"uniStatistics" : {
|
||||
"enable" : false
|
||||
},
|
||||
|
||||
@@ -164,6 +164,7 @@ import {
|
||||
} from '@/services/speech';
|
||||
import { startSpeechCapture } from '@/services/speech-capture';
|
||||
import type { SpeechCapture, SpeechCaptureSession } from '@/services/speech-capture';
|
||||
import { isFileSelectionCancelled } from '@/utils/file-selection';
|
||||
|
||||
const currentPath = '/pages/candidate/interview/index';
|
||||
const records = ref<InterviewRecord[]>([]);
|
||||
@@ -342,6 +343,10 @@ const transcribeQuestionFile = async (questionId: string) => {
|
||||
applyQuestionAsrResult(questionId, result);
|
||||
} catch (error) {
|
||||
if (generation !== questionTranscriptionGeneration) return;
|
||||
if (isFileSelectionCancelled(error)) {
|
||||
message.value = '';
|
||||
return;
|
||||
}
|
||||
handleRequestError(error, '语音转写失败');
|
||||
} finally {
|
||||
if (generation === questionTranscriptionGeneration) {
|
||||
|
||||
@@ -72,6 +72,7 @@ import {
|
||||
} from '@/services/candidate-view';
|
||||
import { resetPageScroll } from '@/services/navigation';
|
||||
import StateBlock from '@/components/StateBlock.vue';
|
||||
import { isFileSelectionCancelled } from '@/utils/file-selection';
|
||||
|
||||
const currentPath = '/pages/candidate/materials/index';
|
||||
const materials = ref<CandidateMaterial[]>([]);
|
||||
@@ -131,7 +132,7 @@ const uploadMaterial = async () => {
|
||||
message.value = `资料已上传:#${saved.id}`;
|
||||
materials.value = await getCandidateMaterials();
|
||||
} catch (error) {
|
||||
handleRequestError(error, '资料上传失败');
|
||||
if (!isFileSelectionCancelled(error)) handleRequestError(error, '资料上传失败');
|
||||
} finally {
|
||||
uploading.value = false;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
import { ref } from 'vue';
|
||||
import { choosePersonalFile, createTextItem, createUrlItem, uploadPersonalFile, type PersonalSelectedFile } from '@/services/personal-assistant';
|
||||
import type { PersonalItemSourceType } from '@/types/api';
|
||||
import { isFileSelectionCancelled } from '@/utils/file-selection';
|
||||
|
||||
const typeOptions = [{ label: '文字', value: 'TEXT' }, { label: '文件', value: 'FILE' }, { label: '网页', value: 'URL' }] as const;
|
||||
const captureType = ref<PersonalItemSourceType>('TEXT');
|
||||
@@ -36,7 +37,15 @@ const url = ref('');
|
||||
const selectedFile = ref<PersonalSelectedFile>();
|
||||
const submitting = ref(false);
|
||||
const message = ref('');
|
||||
const chooseFile = async () => { try { selectedFile.value = await choosePersonalFile(); } catch (error) { message.value = error instanceof Error ? error.message : '未选择文件'; } };
|
||||
const chooseFile = async () => {
|
||||
try {
|
||||
selectedFile.value = await choosePersonalFile();
|
||||
} catch (error) {
|
||||
if (!isFileSelectionCancelled(error)) {
|
||||
message.value = error instanceof Error ? error.message : '选择文件失败';
|
||||
}
|
||||
}
|
||||
};
|
||||
const submit = async () => {
|
||||
if (submitting.value) return;
|
||||
if (captureType.value === 'TEXT' && !content.value.trim()) { message.value = '请输入需要收藏的文字'; return; }
|
||||
|
||||
@@ -148,6 +148,7 @@ import { isLoggedIn, rememberLoginRedirect } from '@/services/auth';
|
||||
import { navigateBackToToday, resetPageScroll } from '@/services/navigation';
|
||||
import { ensureEmployeePosition } from '@/services/position';
|
||||
import StateBlock from '@/components/StateBlock.vue';
|
||||
import { isFileSelectionCancelled } from '@/utils/file-selection';
|
||||
|
||||
const loggedIn = ref(false);
|
||||
const selectedFile = ref<CaseSelectedFile | null>(null);
|
||||
@@ -213,8 +214,10 @@ const selectAudio = async () => {
|
||||
selectedFile.value = await chooseCaseAudio();
|
||||
resetResult();
|
||||
} catch (error) {
|
||||
if (!isFileSelectionCancelled(error)) {
|
||||
message.value = error instanceof Error ? error.message : '选择音频失败';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadRecords = async () => {
|
||||
|
||||
@@ -532,6 +532,7 @@ import { createPageRequestScope, currentAccountKey, type RequestScopeTicket } fr
|
||||
import { createRealtimePracticeController, createRealtimeSession, fetchRealtimePersonas, invokeRealtimeTool, type RealtimePersonaOption } from '@/services/realtime-practice';
|
||||
import { stripInternalCodes } from '@/services/text';
|
||||
import { describeMicrophoneError, microphoneDeniedMessage, requestMicrophonePermission } from '@/utils/microphone';
|
||||
import { isFileSelectionCancelled } from '@/utils/file-selection';
|
||||
import { safeTopPadding } from '@/utils/safe-area';
|
||||
|
||||
type PracticeStatus = 'idle' | 'starting' | 'active' | 'submitting' | 'finished';
|
||||
@@ -913,8 +914,10 @@ const composerPickAudio = async () => {
|
||||
await submitTranscribed(text, asr.audioUrl || '', normalizeOssId(asr.ossId));
|
||||
} catch (error) {
|
||||
if (generation !== transcriptionGeneration) return;
|
||||
if (!isFileSelectionCancelled(error)) {
|
||||
const text = error instanceof Error ? error.message : '语音转写失败,请改用文字输入';
|
||||
if (text !== '未选择音频') uni.showToast({ title: text, icon: 'none' });
|
||||
uni.showToast({ title: text, icon: 'none' });
|
||||
}
|
||||
} finally {
|
||||
if (generation === transcriptionGeneration) {
|
||||
abortTranscription = null;
|
||||
@@ -1226,6 +1229,10 @@ const transcribeCapture = async (capture: SpeechCapture, assignmentId?: number)
|
||||
}
|
||||
} catch (error) {
|
||||
if (generation !== transcriptionGeneration) return;
|
||||
if (isFileSelectionCancelled(error)) {
|
||||
message.value = '';
|
||||
return;
|
||||
}
|
||||
handleError(error, '语音转写失败');
|
||||
} finally {
|
||||
if (generation === transcriptionGeneration) {
|
||||
|
||||
@@ -244,6 +244,7 @@ import {
|
||||
} from '@/services/speech';
|
||||
import type { SpeechCapture } from '@/services/speech-capture';
|
||||
import type { SpeechPlaybackStatus } from '@/types/api';
|
||||
import { isFileSelectionCancelled } from '@/utils/file-selection';
|
||||
import {
|
||||
listMyWorkReports,
|
||||
isCurrentWorkReportRequest,
|
||||
@@ -547,8 +548,10 @@ const addMediaAttachment = async () => {
|
||||
});
|
||||
} catch (error) {
|
||||
if (!requestIsCurrent(generation, mediaSelectionGeneration, accountKey)) return;
|
||||
if (!isFileSelectionCancelled(error)) {
|
||||
const text = error instanceof Error ? error.message : '附件选择失败';
|
||||
if (text !== '未选择附件') pushSystem(text);
|
||||
pushSystem(text);
|
||||
}
|
||||
} finally {
|
||||
if (requestIsCurrent(generation, mediaSelectionGeneration, accountKey)) mediaSelecting.value = false;
|
||||
}
|
||||
@@ -580,8 +583,10 @@ const pickVoiceAudio = async () => {
|
||||
});
|
||||
} catch (error) {
|
||||
if (!requestIsCurrent(generation, voiceTranscriptionGeneration, accountKey)) return;
|
||||
if (!isFileSelectionCancelled(error)) {
|
||||
const text = error instanceof Error ? error.message : '音频转写失败,请重试或改用键盘';
|
||||
if (text !== '未选择音频') pushSystem(text);
|
||||
pushSystem(text);
|
||||
}
|
||||
} finally {
|
||||
if (requestIsCurrent(generation, voiceTranscriptionGeneration, accountKey)) {
|
||||
abortVoiceTranscription = null;
|
||||
|
||||
@@ -463,6 +463,7 @@ import { isNativeAppRuntime, type SpeechCapture } from '@/services/speech-captur
|
||||
import { downloadSummaryCardImage } from '@/services/summary-card-image';
|
||||
import { getMemoryCandidates } from '@/services/personal-assistant';
|
||||
import { currentAccountKey } from '@/services/request-scope';
|
||||
import { isFileSelectionCancelled } from '@/utils/file-selection';
|
||||
import {
|
||||
clearBroadcastQuestionContext,
|
||||
getBroadcastMessage,
|
||||
@@ -1084,8 +1085,10 @@ const pickMedia = async () => {
|
||||
icon: 'none'
|
||||
});
|
||||
} catch (error) {
|
||||
if (!isFileSelectionCancelled(error)) {
|
||||
const text = error instanceof Error ? error.message : '附件选择失败';
|
||||
if (text !== '未选择附件') uni.showToast({ title: text, icon: 'none' });
|
||||
uni.showToast({ title: text, icon: 'none' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1150,8 +1153,10 @@ const pickVoiceAudio = async () => {
|
||||
void sendQuestion(text, { voice: true });
|
||||
} catch (error) {
|
||||
if (generation !== voiceTranscriptionGeneration) return;
|
||||
if (!isFileSelectionCancelled(error)) {
|
||||
const text = error instanceof Error ? error.message : '语音转写失败,请改用文字输入';
|
||||
if (text !== '未选择音频') pushSystem(text);
|
||||
pushSystem(text);
|
||||
}
|
||||
} finally {
|
||||
if (generation === voiceTranscriptionGeneration) {
|
||||
abortVoiceTranscription = null;
|
||||
|
||||
@@ -218,6 +218,7 @@ import { stripInternalCodes } from '@/services/text';
|
||||
import { ensureEmployeePosition } from '@/services/position';
|
||||
import { createPageRequestScope, currentAccountKey } from '@/services/request-scope';
|
||||
import { getBroadcastUnreadCount } from '@/services/broadcast';
|
||||
import { isFileSelectionCancelled } from '@/utils/file-selection';
|
||||
|
||||
const home = ref<RoleHome | null>(null);
|
||||
const assignments = ref<PracticeAssignment[]>([]);
|
||||
@@ -499,7 +500,7 @@ const pickAudioFallback = async () => {
|
||||
if (text) ask(text);
|
||||
else message.value = '没听清,请再说一次';
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message !== '未选择音频') {
|
||||
if (!isFileSelectionCancelled(error) && error instanceof Error) {
|
||||
message.value = error.message;
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -123,6 +123,7 @@ import { chooseKnowledgeMedia } from '@/services/knowledge';
|
||||
import { chooseSpeechAudio, createSpeechPlaybackController, MENTOR_VOICE_PROFILE, transcribeSpeechCapture, transcribeSpeechFile } from '@/services/speech';
|
||||
import type { SpeechCapture } from '@/services/speech-capture';
|
||||
import { getWebAiCapabilities, queryWebAi, queryWebAiWithImage } from '@/services/web-ai';
|
||||
import { isFileSelectionCancelled } from '@/utils/file-selection';
|
||||
import { renderMarkdown } from '@/utils/markdown';
|
||||
|
||||
const path = '/pages/user/web-ai/index';
|
||||
@@ -330,8 +331,10 @@ const handlePanelAction = async (key: string) => {
|
||||
composerRef.value?.setDraft('');
|
||||
uni.showToast({ title: '已选择图片,发送时将联网识图', icon: 'none' });
|
||||
} catch (error) {
|
||||
if (!isFileSelectionCancelled(error)) {
|
||||
const text = error instanceof Error ? error.message : '图片选择失败';
|
||||
if (text !== '未选择附件') uni.showToast({ title: text, icon: 'none' });
|
||||
uni.showToast({ title: text, icon: 'none' });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -353,8 +356,10 @@ const handlePanelAction = async (key: string) => {
|
||||
void sendQuestion(text, { voice: true });
|
||||
} catch (error) {
|
||||
if (generation !== voiceTranscriptionGeneration) return;
|
||||
if (!isFileSelectionCancelled(error)) {
|
||||
const text = error instanceof Error ? error.message : '语音转写失败,请改用文字输入';
|
||||
if (text !== '未选择音频') pushSystem(text);
|
||||
pushSystem(text);
|
||||
}
|
||||
} finally {
|
||||
if (generation === voiceTranscriptionGeneration) {
|
||||
abortVoiceTranscription = null;
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
} from '@/types/api';
|
||||
import { apiRequest, apiUrl, authHeaders, readTextPayload } from './api';
|
||||
import { getAuth } from './auth';
|
||||
import { fileSelectionFailure } from '../utils/file-selection';
|
||||
|
||||
const materialExtensions = ['.pdf', '.doc', '.docx', '.jpg', '.jpeg', '.png', '.webp'];
|
||||
|
||||
@@ -57,7 +58,7 @@ export const chooseCandidateFile = () =>
|
||||
size: Number(file.size || 0)
|
||||
});
|
||||
},
|
||||
fail: () => reject(new Error('未选择文件'))
|
||||
fail: (error) => reject(fileSelectionFailure(error, '文件'))
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
CaseUploadResponse
|
||||
} from '@/types/api';
|
||||
import { apiRequest, apiUrl, authHeaders, authenticatedFileUrl, readTextPayload } from './api';
|
||||
import { fileSelectionFailure } from '../utils/file-selection';
|
||||
|
||||
const audioExtensions = ['.mp3', '.wav', '.m4a', '.webm', '.aac', '.ogg'];
|
||||
|
||||
@@ -31,7 +32,7 @@ export const chooseCaseAudio = () =>
|
||||
size: Number(file.size || 0)
|
||||
});
|
||||
},
|
||||
fail: () => reject(new Error('未选择音频'))
|
||||
fail: (error) => reject(fileSelectionFailure(error, '音频'))
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
} from '@/types/api';
|
||||
import { apiRequest, apiUrl, authHeaders, readTextPayload } from './api';
|
||||
import { getSelectedPosition } from './auth';
|
||||
import { fileSelectionFailure } from '../utils/file-selection';
|
||||
|
||||
type RawKnowledgeSnippet = KnowledgeSnippet & {
|
||||
title?: string;
|
||||
@@ -44,7 +45,7 @@ export const chooseKnowledgeMedia = () =>
|
||||
}
|
||||
resolve({ path, name, size, kind });
|
||||
},
|
||||
fail: () => reject(new Error('未选择附件'))
|
||||
fail: (error) => reject(fileSelectionFailure(error, '附件'))
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import type {
|
||||
PersonalSpace,
|
||||
ServiceMemory
|
||||
} from '@/types/api';
|
||||
import { fileSelectionFailure } from '../utils/file-selection';
|
||||
import type { CaptureStatusHistory } from '@/types/api';
|
||||
import { apiRequest, apiUrl, authHeaders, readTextPayload } from './api';
|
||||
import { personalItemApiPath, personalSessionApiPath } from './personal-assistant-id';
|
||||
@@ -145,7 +146,7 @@ export const choosePersonalFile = () => new Promise<PersonalSelectedFile>((resol
|
||||
const path = file?.path || result.tempFilePaths?.[0] || '';
|
||||
if (!path) return reject(new Error('未获取到文件路径'));
|
||||
resolve({ path, name: file?.name || path.split('/').pop() || 'personal-file', size: Number(file?.size || 0) });
|
||||
}, fail: () => reject(new Error('未选择文件')) });
|
||||
}, fail: (error) => reject(fileSelectionFailure(error, '文件')) });
|
||||
});
|
||||
|
||||
export const uploadPersonalFile = (file: PersonalSelectedFile, title?: string, capturedAt?: string) =>
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SpeechCapture } from './speech-capture';
|
||||
import { estimateMpegAudioDuration } from './mpeg-audio-duration';
|
||||
import { dataUriToBlobUrl, decodeDataAudioUri, hasNativeAudioFileSupport, isDataAudioUri, removeNativeTempAudio, writeNativeTempAudio } from './speech-audio-source';
|
||||
import { applyTtsVoicePreference, getTtsDialectPreference, getTtsVoicePreference } from './tts-voice-preference';
|
||||
import { fileSelectionFailure } from '../utils/file-selection';
|
||||
|
||||
const audioExtensions = ['.mp3', '.wav', '.m4a', '.webm', '.aac', '.ogg'];
|
||||
|
||||
@@ -45,7 +46,7 @@ export const chooseSpeechAudio = () =>
|
||||
size: Number(file.size || 0)
|
||||
});
|
||||
},
|
||||
fail: () => reject(new Error('未选择音频'))
|
||||
fail: (error) => reject(fileSelectionFailure(error, '音频'))
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
export const FILE_SELECTION_CANCELLED = 'FILE_SELECTION_CANCELLED';
|
||||
|
||||
export class FileSelectionCancelledError extends Error {
|
||||
readonly code = FILE_SELECTION_CANCELLED;
|
||||
|
||||
constructor(label: string) {
|
||||
super(`未选择${label}`);
|
||||
this.name = 'FileSelectionCancelledError';
|
||||
}
|
||||
}
|
||||
|
||||
const failureSignal = (failure: unknown): string => {
|
||||
if (typeof failure === 'string') return failure;
|
||||
if (!failure || typeof failure !== 'object') return '';
|
||||
const value = failure as { errMsg?: unknown; message?: unknown };
|
||||
return String(value.errMsg || value.message || '');
|
||||
};
|
||||
|
||||
export const fileSelectionFailure = (failure: unknown, label: string): Error => {
|
||||
const signal = failureSignal(failure).toLowerCase();
|
||||
if (signal.includes('cancel')) return new FileSelectionCancelledError(label);
|
||||
return new Error(`无法选择${label},请重试`);
|
||||
};
|
||||
|
||||
export const isFileSelectionCancelled = (error: unknown): boolean =>
|
||||
error instanceof FileSelectionCancelledError
|
||||
|| Boolean(error && typeof error === 'object'
|
||||
&& (error as { code?: unknown }).code === FILE_SELECTION_CANCELLED);
|
||||
@@ -0,0 +1,102 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
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'
|
||||
});
|
||||
|
||||
const originalUni = globalThis.uni;
|
||||
const read = (path) => readFile(new URL(path, import.meta.url), 'utf8');
|
||||
|
||||
after(async () => {
|
||||
await vite.close();
|
||||
if (originalUni === undefined) delete globalThis.uni;
|
||||
else globalThis.uni = originalUni;
|
||||
});
|
||||
|
||||
const [
|
||||
fileSelection,
|
||||
speech,
|
||||
caseService,
|
||||
candidate,
|
||||
knowledge,
|
||||
personalAssistant
|
||||
] = await Promise.all([
|
||||
vite.ssrLoadModule('/src/utils/file-selection.ts'),
|
||||
vite.ssrLoadModule('/src/services/speech.ts'),
|
||||
vite.ssrLoadModule('/src/services/case.ts'),
|
||||
vite.ssrLoadModule('/src/services/candidate.ts'),
|
||||
vite.ssrLoadModule('/src/services/knowledge.ts'),
|
||||
vite.ssrLoadModule('/src/services/personal-assistant.ts')
|
||||
]);
|
||||
|
||||
const pickers = [
|
||||
['音频', speech.chooseSpeechAudio],
|
||||
['音频', caseService.chooseCaseAudio],
|
||||
['文件', candidate.chooseCandidateFile],
|
||||
['附件', knowledge.chooseKnowledgeMedia],
|
||||
['文件', personalAssistant.choosePersonalFile]
|
||||
];
|
||||
|
||||
test('所有文件选择器把用户取消识别为同一可静默处理的信号', async () => {
|
||||
globalThis.uni = {
|
||||
chooseFile: ({ fail }) => fail({ errMsg: 'chooseFile:fail cancel' })
|
||||
};
|
||||
|
||||
for (const [label, picker] of pickers) {
|
||||
await assert.rejects(picker(), (error) => {
|
||||
assert.equal(fileSelection.isFileSelectionCancelled(error), true);
|
||||
assert.equal(error.code, fileSelection.FILE_SELECTION_CANCELLED);
|
||||
assert.equal(error.message, `未选择${label}`);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('系统选择失败不是用户取消,并只展示稳定中文提示', async () => {
|
||||
globalThis.uni = {
|
||||
chooseFile: ({ fail }) => fail({ errMsg: 'chooseFile:fail permission denied' })
|
||||
};
|
||||
|
||||
for (const [label, picker] of pickers) {
|
||||
await assert.rejects(picker(), (error) => {
|
||||
assert.equal(fileSelection.isFileSelectionCancelled(error), false);
|
||||
assert.equal(error.message, `无法选择${label},请重试`);
|
||||
assert.doesNotMatch(error.message, /permission|denied|chooseFile/i);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('无结构异常也不会被误判为取消', () => {
|
||||
const error = fileSelection.fileSelectionFailure(undefined, '附件');
|
||||
assert.equal(fileSelection.isFileSelectionCancelled(error), false);
|
||||
assert.equal(error.message, '无法选择附件,请重试');
|
||||
});
|
||||
|
||||
test('所有使用文件选择器的页面都按取消信号恢复空闲态而不展示错误', async () => {
|
||||
const pages = await Promise.all([
|
||||
'../src/pages/candidate/interview/index.vue',
|
||||
'../src/pages/candidate/materials/index.vue',
|
||||
'../src/pages/user/web-ai/index.vue',
|
||||
'../src/pages/user/today/index.vue',
|
||||
'../src/pages/user/cases/index.vue',
|
||||
'../src/pages/user/sop/index.vue',
|
||||
'../src/pages/user/practice/index.vue',
|
||||
'../src/pages/user/report/index.vue',
|
||||
'../src/pages/user/assistant/capture.vue'
|
||||
].map(read));
|
||||
const combined = pages.join('\n');
|
||||
|
||||
for (const page of pages) assert.match(page, /isFileSelectionCancelled/);
|
||||
assert.doesNotMatch(combined, /(?:!==|===)\s*['"]未选择(?:音频|附件|文件)['"]/);
|
||||
assert.match(pages[0], /isFileSelectionCancelled\(error\)[\s\S]*?message\.value = ''[\s\S]*?return/);
|
||||
assert.match(pages[6], /isFileSelectionCancelled\(error\)[\s\S]*?message\.value = ''[\s\S]*?return/);
|
||||
});
|
||||
Reference in New Issue
Block a user