diff --git a/src/ble/core/bleCore.js b/src/ble/core/bleCore.js index ef0b487..9a3ab04 100644 --- a/src/ble/core/bleCore.js +++ b/src/ble/core/bleCore.js @@ -291,7 +291,16 @@ export function createBleCore(options = {}) { const runConnect = async () => { setStage(BLE_STAGE.CONNECTING); - await promisifyApi(platform, 'createBLEConnection', { deviceId, timeout }); + try { + await promisifyApi(platform, 'createBLEConnection', { deviceId, timeout }); + } catch (error) { + // Android 报 already connect / iOS 报 already connected: + // 目标连接已存在(如配网页返回后再次发起),按幂等成功处理 + const msg = String(error?.errMsg || ''); + if (!/already\s*connect/i.test(msg)) { + throw error; + } + } patchState({ connected: true, deviceId }); setStage(BLE_STAGE.CONNECTED); return true; diff --git a/tests/unit/ble/connectIdempotent.spec.ts b/tests/unit/ble/connectIdempotent.spec.ts new file mode 100644 index 0000000..dcced5f --- /dev/null +++ b/tests/unit/ble/connectIdempotent.spec.ts @@ -0,0 +1,59 @@ +import { describe, it, expect, vi } from 'vitest' +// 纯 JS 资产未类型化,关闭该文件类型检查 +// @ts-ignore +import { createBleCore } from '@/ble/core/bleCore' + +// bleCore → adapter → @tarojs/taro 在 node 环境会触发 dom-external 的 +// ENABLE_INNER_HTML 编译期常量未定义;测试直接 mock 掉该模块 +vi.mock('@tarojs/taro', () => ({})) +vi.mock('@/ble/adapter', () => ({ taroPlatform: {} })) + +// platform mock:按用例切换 createBLEConnection 的 fail/success 行为 +// @ts-ignore 无类型平台对象,与 bleCore 注入签名一致 +function makePlatform({ connectFail }: { connectFail?: { errMsg?: string; errCode?: number } }) { + return { + openBluetoothAdapter: (opt: { success?: (r: unknown) => void }) => opt.success?.({}), + closeBluetoothAdapter: (opt: { success?: (r: unknown) => void }) => opt.success?.({}), + onBluetoothAdapterStateChange: vi.fn(), + offBluetoothAdapterStateChange: vi.fn(), + onBLEConnectionStateChange: vi.fn(), + offBLEConnectionStateChange: vi.fn(), + onBLECharacteristicValueChange: vi.fn(), + offBLECharacteristicValueChange: vi.fn(), + onBluetoothDeviceFound: vi.fn(), + offBluetoothDeviceFound: vi.fn(), + createBLEConnection: vi.fn((opt: { success?: (r: unknown) => void; fail?: (e: unknown) => void }) => { + if (connectFail) opt.fail?.(connectFail) + else opt.success?.({}) + }) + } +} + +describe('bleCore.connect already connect 幂等', () => { + it('Android already connect 形态:按已连接成功处理,状态进入 CONNECTED', async () => { + const platform = makePlatform({ connectFail: { errCode: 10003, errMsg: 'createBLEConnection:fail:already connect' } }) + const core = createBleCore({ platform, reconnect: { enabled: false } }) + + const ok = await core.connect('dev-1') + expect(ok).toBe(true) + expect(core.getState().connected).toBe(true) + expect(core.getState().deviceId).toBe('dev-1') + expect(core.getState().stage).toBe('CONNECTED') + }) + + it('iOS already connected 形态:同样幂等成功', async () => { + const platform = makePlatform({ connectFail: { errMsg: 'createBLEConnection:fail:already connected' } }) + const core = createBleCore({ platform, reconnect: { enabled: false } }) + + await core.connect('dev-2') + expect(core.getState().connected).toBe(true) + }) + + it('其他连接错误不吞:原样抛出且 connected 保持 false', async () => { + const platform = makePlatform({ connectFail: { errCode: 10012, errMsg: 'createBLEConnection:fail:timeout' } }) + const core = createBleCore({ platform, reconnect: { enabled: false } }) + + await expect(core.connect('dev-3')).rejects.toThrow() + expect(core.getState().connected).toBe(false) + }) +})