WellNuo/services/ble/__tests__/webBluetooth.test.ts
Sergei c2064a76eb Add Web Bluetooth support for browser-based sensor setup
- Add webBluetooth.ts with browser detection and compatibility checks
- Add WebBLEManager implementing IBLEManager for Web Bluetooth API
- Add BrowserNotSupported component showing clear error for Safari/Firefox
- Update services/ble/index.ts to use WebBLEManager on web platform
- Add comprehensive tests for browser detection and WebBLEManager

Works in Chrome/Edge/Opera, shows user-friendly error in unsupported browsers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-01 10:48:01 -08:00

338 lines
10 KiB
TypeScript

// Tests for Web Bluetooth browser compatibility utilities
import {
detectBrowser,
checkWebBluetoothSupport,
getUnsupportedBrowserMessage,
isWebPlatform,
hasWebBluetooth,
SUPPORTED_BROWSERS,
BROWSER_HELP_URLS,
} from '../webBluetooth';
describe('webBluetooth utilities', () => {
// Store original navigator and window
const originalNavigator = global.navigator;
const originalWindow = global.window;
afterEach(() => {
// Restore originals
Object.defineProperty(global, 'navigator', {
value: originalNavigator,
writable: true,
configurable: true,
});
Object.defineProperty(global, 'window', {
value: originalWindow,
writable: true,
configurable: true,
});
});
describe('detectBrowser', () => {
it('should detect Chrome', () => {
Object.defineProperty(global, 'navigator', {
value: {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
},
writable: true,
configurable: true,
});
const browser = detectBrowser();
expect(browser.name).toBe('Chrome');
expect(browser.version).toBe('120');
});
it('should detect Safari', () => {
Object.defineProperty(global, 'navigator', {
value: {
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
},
writable: true,
configurable: true,
});
const browser = detectBrowser();
expect(browser.name).toBe('Safari');
expect(browser.version).toBe('17');
});
it('should detect Firefox', () => {
Object.defineProperty(global, 'navigator', {
value: {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0',
},
writable: true,
configurable: true,
});
const browser = detectBrowser();
expect(browser.name).toBe('Firefox');
expect(browser.version).toBe('122');
});
it('should detect Edge', () => {
Object.defineProperty(global, 'navigator', {
value: {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0',
},
writable: true,
configurable: true,
});
const browser = detectBrowser();
expect(browser.name).toBe('Edge');
expect(browser.version).toBe('120');
});
it('should detect Opera', () => {
Object.defineProperty(global, 'navigator', {
value: {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 OPR/106.0.0.0',
},
writable: true,
configurable: true,
});
const browser = detectBrowser();
expect(browser.name).toBe('Opera');
expect(browser.version).toBe('106');
});
it('should detect Samsung Internet', () => {
Object.defineProperty(global, 'navigator', {
value: {
userAgent: 'Mozilla/5.0 (Linux; Android 13; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/23.0 Chrome/115.0.0.0 Mobile Safari/537.36',
},
writable: true,
configurable: true,
});
const browser = detectBrowser();
expect(browser.name).toBe('Samsung Internet');
expect(browser.version).toBe('23');
});
it('should return Unknown for undefined navigator', () => {
Object.defineProperty(global, 'navigator', {
value: undefined,
writable: true,
configurable: true,
});
const browser = detectBrowser();
expect(browser.name).toBe('Unknown');
expect(browser.version).toBe('0');
});
});
describe('checkWebBluetoothSupport', () => {
it('should return supported for Chrome with Web Bluetooth', () => {
Object.defineProperty(global, 'navigator', {
value: {
userAgent: 'Mozilla/5.0 Chrome/120.0.0.0',
bluetooth: {},
},
writable: true,
configurable: true,
});
Object.defineProperty(global, 'window', {
value: { isSecureContext: true },
writable: true,
configurable: true,
});
const support = checkWebBluetoothSupport();
expect(support.supported).toBe(true);
expect(support.browserName).toBe('Chrome');
});
it('should return unsupported for Safari', () => {
Object.defineProperty(global, 'navigator', {
value: {
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X) Safari/605.1.15 Version/17.0',
},
writable: true,
configurable: true,
});
Object.defineProperty(global, 'window', {
value: { isSecureContext: true },
writable: true,
configurable: true,
});
const support = checkWebBluetoothSupport();
expect(support.supported).toBe(false);
expect(support.browserName).toBe('Safari');
expect(support.reason).toBe('unsupported_browser');
});
it('should return unsupported for Firefox', () => {
Object.defineProperty(global, 'navigator', {
value: {
userAgent: 'Mozilla/5.0 Firefox/122.0',
},
writable: true,
configurable: true,
});
Object.defineProperty(global, 'window', {
value: { isSecureContext: true },
writable: true,
configurable: true,
});
const support = checkWebBluetoothSupport();
expect(support.supported).toBe(false);
expect(support.browserName).toBe('Firefox');
expect(support.reason).toBe('unsupported_browser');
});
it('should return unsupported for insecure context', () => {
Object.defineProperty(global, 'navigator', {
value: {
userAgent: 'Mozilla/5.0 Chrome/120.0.0.0',
bluetooth: {},
},
writable: true,
configurable: true,
});
Object.defineProperty(global, 'window', {
value: { isSecureContext: false },
writable: true,
configurable: true,
});
const support = checkWebBluetoothSupport();
expect(support.supported).toBe(false);
expect(support.reason).toBe('insecure_context');
});
it('should return api_unavailable for Chrome without bluetooth API', () => {
Object.defineProperty(global, 'navigator', {
value: {
userAgent: 'Mozilla/5.0 Chrome/120.0.0.0',
// No bluetooth property
},
writable: true,
configurable: true,
});
Object.defineProperty(global, 'window', {
value: { isSecureContext: true },
writable: true,
configurable: true,
});
const support = checkWebBluetoothSupport();
expect(support.supported).toBe(false);
expect(support.reason).toBe('api_unavailable');
});
});
describe('getUnsupportedBrowserMessage', () => {
it('should return correct message for unsupported browser', () => {
const support = {
supported: false,
browserName: 'Safari',
browserVersion: '17',
reason: 'unsupported_browser' as const,
};
const message = getUnsupportedBrowserMessage(support);
expect(message.title).toBe('Browser Not Supported');
expect(message.message).toContain('Safari');
expect(message.suggestion).toContain('Chrome');
});
it('should return correct message for insecure context', () => {
const support = {
supported: false,
browserName: 'Chrome',
browserVersion: '120',
reason: 'insecure_context' as const,
};
const message = getUnsupportedBrowserMessage(support);
expect(message.title).toBe('Secure Connection Required');
expect(message.message).toContain('HTTPS');
});
it('should return correct message for api unavailable', () => {
const support = {
supported: false,
browserName: 'Chrome',
browserVersion: '120',
reason: 'api_unavailable' as const,
};
const message = getUnsupportedBrowserMessage(support);
expect(message.title).toBe('Bluetooth Not Available');
});
});
describe('isWebPlatform', () => {
it('should return true when window and document exist', () => {
Object.defineProperty(global, 'window', {
value: {},
writable: true,
configurable: true,
});
Object.defineProperty(global, 'document', {
value: {},
writable: true,
configurable: true,
});
expect(isWebPlatform()).toBe(true);
});
it('should return false when window is undefined', () => {
Object.defineProperty(global, 'window', {
value: undefined,
writable: true,
configurable: true,
});
expect(isWebPlatform()).toBe(false);
});
});
describe('hasWebBluetooth', () => {
it('should return true when navigator.bluetooth exists', () => {
Object.defineProperty(global, 'navigator', {
value: { bluetooth: {} },
writable: true,
configurable: true,
});
expect(hasWebBluetooth()).toBe(true);
});
it('should return false when navigator.bluetooth does not exist', () => {
Object.defineProperty(global, 'navigator', {
value: {},
writable: true,
configurable: true,
});
expect(hasWebBluetooth()).toBe(false);
});
});
describe('constants', () => {
it('should have supported browsers list', () => {
expect(SUPPORTED_BROWSERS).toContain('Chrome');
expect(SUPPORTED_BROWSERS).toContain('Edge');
expect(SUPPORTED_BROWSERS).toContain('Opera');
expect(SUPPORTED_BROWSERS).not.toContain('Safari');
expect(SUPPORTED_BROWSERS).not.toContain('Firefox');
});
it('should have browser help URLs', () => {
expect(BROWSER_HELP_URLS.Chrome).toContain('google.com/chrome');
expect(BROWSER_HELP_URLS.Edge).toContain('microsoft.com');
expect(BROWSER_HELP_URLS.Opera).toContain('opera.com');
});
});
});