接入个人中心原生扫码优先路径
让个人中心扫码弹层优先调用 scanner.scanQrCode HostBridge 能力 保留宿主不支持或异常时的浏览器摄像头扫码回退 取消原生扫码时关闭弹层且不连带打开 H5 摄像头 增加扫码弹层 HostBridge 优先测试并纳入原生壳总门禁
This commit is contained in:
@@ -77,6 +77,7 @@ const h5HostBridgeRequiredCallChainFiles = [
|
||||
'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx',
|
||||
'src/components/platform-entry/PlatformFeedbackView.tsx',
|
||||
'src/components/platform-entry/PlatformProfilePrimitives.tsx',
|
||||
'src/components/platform-entry/PlatformProfileQrScannerModal.tsx',
|
||||
'src/components/platform-entry/PlatformProfileReferralModal.tsx',
|
||||
'src/components/platform-entry/PlatformProfileRewardCodeRedeemModal.tsx',
|
||||
'src/components/platform-entry/usePlatformProfileCenterController.ts',
|
||||
@@ -430,6 +431,7 @@ const h5HostBridgeTests = [
|
||||
'src/components/bark-battle-creation/BarkBattleResultView.test.tsx',
|
||||
'src/components/common/CreativeAudioInputPanel.test.tsx',
|
||||
'src/components/common/PublishShareModal.test.tsx',
|
||||
'src/components/platform-entry/PlatformProfileQrScannerModal.test.tsx',
|
||||
'src/components/creation-agent/CreationAgentWorkspace.test.tsx',
|
||||
'src/components/visual-novel-result/VisualNovelResultView.test.tsx',
|
||||
'src/components/platform-entry/platformDraftGenerationShelfModel.test.ts',
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import * as hostBridgeServices from '../../services/host-bridge/hostBridge';
|
||||
import { PlatformProfileQrScannerModal } from './PlatformProfileQrScannerModal';
|
||||
|
||||
type MockTrack = {
|
||||
@@ -49,6 +50,7 @@ describe('PlatformProfileQrScannerModal', () => {
|
||||
});
|
||||
|
||||
test('detects qr result and stops camera tracks', async () => {
|
||||
vi.spyOn(hostBridgeServices, 'scanHostQrCode').mockResolvedValue(false);
|
||||
const stop = vi.fn();
|
||||
const stream = buildStream([{ stop }]);
|
||||
const getUserMedia = vi.fn().mockResolvedValue(stream);
|
||||
@@ -84,6 +86,7 @@ describe('PlatformProfileQrScannerModal', () => {
|
||||
});
|
||||
|
||||
test('releases camera resource when modal unmounts before recognition', async () => {
|
||||
vi.spyOn(hostBridgeServices, 'scanHostQrCode').mockResolvedValue(false);
|
||||
const stop = vi.fn();
|
||||
const stream = buildStream([{ stop }]);
|
||||
const getUserMedia = vi.fn().mockResolvedValue(stream);
|
||||
@@ -112,6 +115,59 @@ describe('PlatformProfileQrScannerModal', () => {
|
||||
expect(stop).toHaveBeenCalledTimes(1);
|
||||
expect(screen.queryByRole('dialog', { name: '扫码' })).toBeNull();
|
||||
});
|
||||
|
||||
test('uses native QR scanner before browser camera', async () => {
|
||||
const getUserMedia = vi.fn();
|
||||
const onResult = vi.fn();
|
||||
vi.spyOn(hostBridgeServices, 'scanHostQrCode').mockResolvedValue({
|
||||
value: 'https://app.genarrative.world/works/detail?work=PZ-1',
|
||||
format: 'qr_code',
|
||||
});
|
||||
installMediaDevices(getUserMedia);
|
||||
|
||||
render(
|
||||
<PlatformProfileQrScannerModal
|
||||
error={null}
|
||||
result={null}
|
||||
onClose={vi.fn()}
|
||||
onError={vi.fn()}
|
||||
onResult={onResult}
|
||||
/>,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
expect(onResult).toHaveBeenCalledWith(
|
||||
'https://app.genarrative.world/works/detail?work=PZ-1',
|
||||
);
|
||||
expect(getUserMedia).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('closes scanner without browser fallback when native scan is cancelled', async () => {
|
||||
const getUserMedia = vi.fn();
|
||||
const onClose = vi.fn();
|
||||
vi.spyOn(hostBridgeServices, 'scanHostQrCode').mockResolvedValue(null);
|
||||
installMediaDevices(getUserMedia);
|
||||
|
||||
render(
|
||||
<PlatformProfileQrScannerModal
|
||||
error={null}
|
||||
result={null}
|
||||
onClose={onClose}
|
||||
onError={vi.fn()}
|
||||
onResult={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
expect(getUserMedia).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
function buildStream(tracks: MockTrack[]): MockStream {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useRef } from 'react';
|
||||
|
||||
import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton';
|
||||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
import { scanHostQrCode } from '../../services/host-bridge/hostBridge';
|
||||
import { PlatformProfileModalShell } from './PlatformProfileModalShell';
|
||||
|
||||
const PROFILE_QR_SCAN_INTERVAL_MS = 360;
|
||||
@@ -102,7 +103,7 @@ export function PlatformProfileQrScannerModal({
|
||||
}
|
||||
};
|
||||
|
||||
const startCamera = async () => {
|
||||
const startBrowserCamera = async () => {
|
||||
if (
|
||||
typeof navigator === 'undefined' ||
|
||||
!navigator.mediaDevices?.getUserMedia
|
||||
@@ -136,7 +137,28 @@ export function PlatformProfileQrScannerModal({
|
||||
}
|
||||
};
|
||||
|
||||
void startCamera();
|
||||
const startScanner = async () => {
|
||||
try {
|
||||
const nativeResult = await scanHostQrCode();
|
||||
if (!isMounted) {
|
||||
return;
|
||||
}
|
||||
if (nativeResult) {
|
||||
onResult(nativeResult.value);
|
||||
return;
|
||||
}
|
||||
if (nativeResult === null) {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// 浏览器摄像头路径继续承接不支持或异常宿主。
|
||||
}
|
||||
|
||||
await startBrowserCamera();
|
||||
};
|
||||
|
||||
void startScanner();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
|
||||
Reference in New Issue
Block a user