接入反馈凭证原生图片导入
反馈页上传凭证在原生壳内优先调用 HostBridge 图片导入 宿主图片结果转换为 File 后复用现有凭证预览和提交校验 补充反馈凭证原生导入测试和宿主壳文档边界
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import * as hostBridgeServices from '../../services/host-bridge/hostBridge';
|
||||
import { PlatformFeedbackView } from './PlatformFeedbackView';
|
||||
|
||||
class MockFileReader {
|
||||
@@ -138,6 +139,56 @@ test('PlatformFeedbackView previews image data urls and submits evidence items',
|
||||
});
|
||||
});
|
||||
|
||||
test('PlatformFeedbackView imports evidence image through native HostBridge', async () => {
|
||||
const inputClickSpy = vi
|
||||
.spyOn(HTMLInputElement.prototype, 'click')
|
||||
.mockImplementation(() => undefined);
|
||||
vi.spyOn(hostBridgeServices, 'canUseNativeHostCapability').mockImplementation(
|
||||
(capability) => capability === 'file.importImage',
|
||||
);
|
||||
vi.spyOn(hostBridgeServices, 'importHostImageFile').mockResolvedValue({
|
||||
action: 'selected',
|
||||
fileName: 'feedback.png',
|
||||
base64Data: 'ZmVlZGJhY2s=',
|
||||
mimeType: 'image/png',
|
||||
bytes: 8,
|
||||
});
|
||||
const onSubmit = vi.fn();
|
||||
|
||||
try {
|
||||
render(<PlatformFeedbackView onBack={vi.fn()} onSubmit={onSubmit} />);
|
||||
|
||||
fireEvent.click(screen.getByText('上传凭证'));
|
||||
|
||||
const preview = await screen.findByAltText('反馈凭证预览');
|
||||
expect(preview.getAttribute('src')).toBe(
|
||||
'data:image/png;base64,ZmVlZGJhY2s=',
|
||||
);
|
||||
expect(inputClickSpy).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.change(screen.getByLabelText('问题描述'), {
|
||||
target: { value: '原生壳导入截图后应该可以提交' },
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '提交' }));
|
||||
|
||||
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1));
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
description: '原生壳导入截图后应该可以提交',
|
||||
contactPhone: null,
|
||||
evidenceItems: [
|
||||
{
|
||||
fileName: 'feedback.png',
|
||||
contentType: 'image/png',
|
||||
sizeBytes: 8,
|
||||
dataUrl: 'data:image/png;base64,ZmVlZGJhY2s=',
|
||||
},
|
||||
],
|
||||
});
|
||||
} finally {
|
||||
inputClickSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
test('PlatformFeedbackView calls back from header home button', () => {
|
||||
const onBack = vi.fn();
|
||||
render(<PlatformFeedbackView onBack={onBack} />);
|
||||
|
||||
@@ -2,6 +2,11 @@ import { ArrowLeft, CheckCircle2, Send } from 'lucide-react';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import type { ProfileFeedbackEvidenceItemInput } from '../../../packages/shared/src/contracts/runtime';
|
||||
import {
|
||||
canUseNativeHostCapability,
|
||||
type HostFileImportImageResult,
|
||||
importHostImageFile,
|
||||
} from '../../services/host-bridge/hostBridge';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
|
||||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
@@ -55,6 +60,16 @@ function readFileAsDataUrl(file: File) {
|
||||
});
|
||||
}
|
||||
|
||||
function hostEvidenceImageResultToFile(result: HostFileImportImageResult) {
|
||||
const binary = atob(result.base64Data);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let index = 0; index < binary.length; index += 1) {
|
||||
bytes[index] = binary.charCodeAt(index);
|
||||
}
|
||||
|
||||
return new File([bytes], result.fileName, { type: result.mimeType });
|
||||
}
|
||||
|
||||
export function PlatformFeedbackView({
|
||||
onBack,
|
||||
onSubmit,
|
||||
@@ -90,10 +105,22 @@ export function PlatformFeedbackView({
|
||||
};
|
||||
|
||||
const openEvidencePicker = () => {
|
||||
if (canUseNativeHostCapability('file.importImage')) {
|
||||
void (async () => {
|
||||
const importedImageFile = await importHostImageFile();
|
||||
if (!importedImageFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
addEvidenceFiles([hostEvidenceImageResultToFile(importedImageFile)]);
|
||||
})();
|
||||
return;
|
||||
}
|
||||
|
||||
evidenceInputRef.current?.click();
|
||||
};
|
||||
|
||||
const addEvidenceFiles = (files: FileList | null) => {
|
||||
const addEvidenceFiles = (files: FileList | File[] | null) => {
|
||||
const selectedFiles = files ? Array.from(files) : [];
|
||||
if (evidenceInputRef.current) {
|
||||
evidenceInputRef.current.value = '';
|
||||
|
||||
Reference in New Issue
Block a user