Files
BetterDesk/web-nodejs/tests/mobile-shell.test.js
UNITRONIX abb19c7c44 fix: improve mobile device detection for RdClient Web phone gate
Enhanced mobile device detection logic to prevent false positives that blocked access on PCs, tablets, and unfolded foldables. The phone gate now respects the `[hidden]` attribute, skips zero-width false positives, excludes fine-pointer desktops, and re-evaluates on viewport resize. Updated related CSS and JavaScript to support these changes.
2026-06-22 18:40:01 +02:00

111 lines
3.9 KiB
JavaScript

'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
describe('mobile i18n keys', () => {
const langDir = path.join(__dirname, '../lang');
const locales = fs.readdirSync(langDir).filter(f => f.endsWith('.json'));
const requiredMobileNav = ['label', 'home', 'devices', 'remote', 'chat', 'more'];
const requiredRemote = [
'input_mode_touch',
'input_mode_touchpad',
'show_keyboard',
'special_keys',
'phone_unsupported_title',
'phone_unsupported_body',
'phone_unsupported_back_devices'
];
for (const file of locales) {
it(`${file} has mobile_nav and remote mobile keys`, () => {
const data = JSON.parse(fs.readFileSync(path.join(langDir, file), 'utf8'));
assert.ok(data.mobile_nav, `${file} missing mobile_nav`);
for (const key of requiredMobileNav) {
assert.ok(data.mobile_nav[key], `${file} mobile_nav.${key}`);
}
assert.ok(data.remote, `${file} missing remote`);
for (const key of requiredRemote) {
assert.ok(data.remote[key], `${file} remote.${key}`);
}
});
}
});
function loadDeviceCapabilities(opts) {
opts = opts || {};
const width = opts.width !== undefined ? opts.width : 400;
const window = {
innerWidth: width,
innerHeight: opts.height || 800,
navigator: { maxTouchPoints: opts.touch ? 5 : 0 },
matchMedia: opts.matchMedia || (() => ({ matches: false, media: 'not all', addEventListener: () => {} })),
visualViewport: opts.visualViewport || null,
addEventListener: () => {},
dispatchEvent: () => true
};
const sandbox = {
window,
document: {
readyState: 'complete',
body: { classList: { toggle: () => {}, contains: () => false } },
documentElement: { clientWidth: width },
addEventListener: () => {}
},
DeviceCapabilities: null
};
sandbox.global = window;
const code = fs.readFileSync(path.join(__dirname, '../public/js/device-capabilities.js'), 'utf8');
vm.runInNewContext(code, sandbox);
return sandbox.window.DeviceCapabilities;
}
describe('DeviceCapabilities', () => {
it('exposes breakpoint constants and helpers', () => {
const DC = loadDeviceCapabilities({ width: 400, touch: true });
assert.strictEqual(DC.BP_PHONE, 767);
assert.strictEqual(DC.isPhone(), true);
assert.strictEqual(DC.isTablet(), false);
});
it('isPhone() is false when width is 0 (pre-layout / WebView)', () => {
const DC = loadDeviceCapabilities({ width: 0 });
assert.strictEqual(DC.isPhone(), false);
});
it('isPhone() is false on desktop with fine pointer and hover', () => {
const DC = loadDeviceCapabilities({
width: 1920,
matchMedia: (q) => ({
matches: q === '(hover: hover) and (pointer: fine)',
media: q,
addEventListener: () => {}
})
});
assert.strictEqual(DC.isPhone(), false);
});
it('isPhone() is true on small touch viewport without hover', () => {
const DC = loadDeviceCapabilities({ width: 400, touch: true });
assert.strictEqual(DC.isPhone(), true);
});
it('isPhone() is false on tablet width', () => {
const DC = loadDeviceCapabilities({ width: 900, touch: true });
assert.strictEqual(DC.isPhone(), false);
assert.strictEqual(DC.isTablet(), true);
});
it('getWidth uses visualViewport when innerWidth is 0', () => {
const DC = loadDeviceCapabilities({
width: 0,
visualViewport: { width: 820, height: 600, addEventListener: () => {} }
});
assert.strictEqual(DC.isPhone(), false);
assert.strictEqual(DC.isTablet(), true);
});
});