From ccb088ba9c3871c7f71afafc5420d26a155fa2e0 Mon Sep 17 00:00:00 2001 From: ozh Date: Thu, 15 Oct 2026 10:27:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(ble):=20createBLEConnection=20=E5=B7=B2?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=8A=A5=E9=94=99=E6=8C=89=E5=B9=82=E7=AD=89?= =?UTF-8?q?=E6=88=90=E5=8A=9F=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Android 报 createBLEConnection:fail:already connect(iOS 为 already connected)出现在从配网页返回后再次点击网络配置等重复连接场景, 连接实际已存在,不应按失败弹「设备连接失败」。bleCore.connect 捕获 该 errMsg 视为成功并进入 CONNECTED;其他错误原样抛出。新增单测 覆盖 Android/iOS 两种文案与普通失败不吞三条分支。 --- src/ble/core/bleCore.js | 11 ++++- tests/unit/ble/connectIdempotent.spec.ts | 59 ++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 tests/unit/ble/connectIdempotent.spec.ts 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) + }) +})