Files
readest/apps/readest-app/src/__tests__/services/environment.test.ts
T
Huang Xin 0e516f6e56 chore(test): add unit tests and enforce dash-case naming for test files (#3715)
Add ~290 new unit tests covering utils (lru, diff, css, a11y, walk,
usage, txt-worker), services (EdgeTTSClient, transformers), and
suppress noisy console output across all test files. Rename 27
camelCase test files to dash-case for consistency.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 09:14:00 +02:00

251 lines
11 KiB
TypeScript

import { describe, test, expect, beforeEach, vi } from 'vitest';
// ── Mocks for constants ──────────────────────────────────────────
vi.mock('@/services/constants', () => ({
READEST_WEB_BASE_URL: 'https://web.readest.com',
READEST_NODE_BASE_URL: 'https://node.readest.com',
}));
// We need to reset modules between tests to pick up env var changes,
// so we import dynamically in each test or test group.
// Cast process.env to a mutable record for test manipulation
const env = process.env as Record<string, string | undefined>;
const originalEnv = { ...env };
beforeEach(() => {
vi.resetModules();
Object.keys(env).forEach((key) => delete env[key]);
Object.assign(env, originalEnv);
// Clean up any window globals we set
delete (window as unknown as Record<string, unknown>)['__READEST_CLI_ACCESS'];
});
describe('environment', () => {
// ── isTauriAppPlatform ─────────────────────────────────────────
describe('isTauriAppPlatform', () => {
test('returns true when NEXT_PUBLIC_APP_PLATFORM is tauri', async () => {
env['NEXT_PUBLIC_APP_PLATFORM'] = 'tauri';
const { isTauriAppPlatform } = await import('@/services/environment');
expect(isTauriAppPlatform()).toBe(true);
});
test('returns false when NEXT_PUBLIC_APP_PLATFORM is web', async () => {
env['NEXT_PUBLIC_APP_PLATFORM'] = 'web';
const { isTauriAppPlatform } = await import('@/services/environment');
expect(isTauriAppPlatform()).toBe(false);
});
test('returns false when NEXT_PUBLIC_APP_PLATFORM is not set', async () => {
delete env['NEXT_PUBLIC_APP_PLATFORM'];
const { isTauriAppPlatform } = await import('@/services/environment');
expect(isTauriAppPlatform()).toBe(false);
});
});
// ── isWebAppPlatform ───────────────────────────────────────────
describe('isWebAppPlatform', () => {
test('returns true when NEXT_PUBLIC_APP_PLATFORM is web', async () => {
env['NEXT_PUBLIC_APP_PLATFORM'] = 'web';
const { isWebAppPlatform } = await import('@/services/environment');
expect(isWebAppPlatform()).toBe(true);
});
test('returns false when NEXT_PUBLIC_APP_PLATFORM is tauri', async () => {
env['NEXT_PUBLIC_APP_PLATFORM'] = 'tauri';
const { isWebAppPlatform } = await import('@/services/environment');
expect(isWebAppPlatform()).toBe(false);
});
test('returns false when NEXT_PUBLIC_APP_PLATFORM is not set', async () => {
delete env['NEXT_PUBLIC_APP_PLATFORM'];
const { isWebAppPlatform } = await import('@/services/environment');
expect(isWebAppPlatform()).toBe(false);
});
});
// ── hasCli ─────────────────────────────────────────────────────
describe('hasCli', () => {
test('returns true when __READEST_CLI_ACCESS is true', async () => {
window.__READEST_CLI_ACCESS = true;
const { hasCli } = await import('@/services/environment');
expect(hasCli()).toBe(true);
});
test('returns false when __READEST_CLI_ACCESS is not set', async () => {
const { hasCli } = await import('@/services/environment');
expect(hasCli()).toBe(false);
});
test('returns false when __READEST_CLI_ACCESS is explicitly false', async () => {
window.__READEST_CLI_ACCESS = false;
const { hasCli } = await import('@/services/environment');
expect(hasCli()).toBe(false);
});
});
// ── isPWA ──────────────────────────────────────────────────────
describe('isPWA', () => {
test('returns false by default (jsdom matchMedia mock returns false)', async () => {
const { isPWA } = await import('@/services/environment');
expect(isPWA()).toBe(false);
});
test('returns true when display-mode is standalone', async () => {
const originalMatchMedia = window.matchMedia;
window.matchMedia = vi
.fn()
.mockReturnValue({ matches: true }) as unknown as typeof window.matchMedia;
const { isPWA } = await import('@/services/environment');
expect(isPWA()).toBe(true);
window.matchMedia = originalMatchMedia;
});
});
// ── getBaseUrl ─────────────────────────────────────────────────
describe('getBaseUrl', () => {
test('returns NEXT_PUBLIC_API_BASE_URL when set', async () => {
env['NEXT_PUBLIC_API_BASE_URL'] = 'https://custom-api.example.com';
const { getBaseUrl } = await import('@/services/environment');
expect(getBaseUrl()).toBe('https://custom-api.example.com');
});
test('falls back to READEST_WEB_BASE_URL when env var not set', async () => {
delete env['NEXT_PUBLIC_API_BASE_URL'];
const { getBaseUrl } = await import('@/services/environment');
expect(getBaseUrl()).toBe('https://web.readest.com');
});
});
// ── getNodeBaseUrl ─────────────────────────────────────────────
describe('getNodeBaseUrl', () => {
test('returns NEXT_PUBLIC_NODE_BASE_URL when set', async () => {
env['NEXT_PUBLIC_NODE_BASE_URL'] = 'https://custom-node.example.com';
const { getNodeBaseUrl } = await import('@/services/environment');
expect(getNodeBaseUrl()).toBe('https://custom-node.example.com');
});
test('falls back to READEST_NODE_BASE_URL when env var not set', async () => {
delete env['NEXT_PUBLIC_NODE_BASE_URL'];
const { getNodeBaseUrl } = await import('@/services/environment');
expect(getNodeBaseUrl()).toBe('https://node.readest.com');
});
});
// ── isMacPlatform ──────────────────────────────────────────────
describe('isMacPlatform', () => {
test('returns true when navigator.platform contains Mac', async () => {
Object.defineProperty(navigator, 'platform', { value: 'MacIntel', configurable: true });
const { isMacPlatform } = await import('@/services/environment');
expect(isMacPlatform()).toBe(true);
});
test('returns true when navigator.platform is iPhone', async () => {
Object.defineProperty(navigator, 'platform', { value: 'iPhone', configurable: true });
const { isMacPlatform } = await import('@/services/environment');
expect(isMacPlatform()).toBe(true);
});
test('returns true when navigator.platform is iPad', async () => {
Object.defineProperty(navigator, 'platform', { value: 'iPad', configurable: true });
const { isMacPlatform } = await import('@/services/environment');
expect(isMacPlatform()).toBe(true);
});
test('returns true when navigator.platform is iPod', async () => {
Object.defineProperty(navigator, 'platform', { value: 'iPod', configurable: true });
const { isMacPlatform } = await import('@/services/environment');
expect(isMacPlatform()).toBe(true);
});
test('returns false when navigator.platform is Win32', async () => {
Object.defineProperty(navigator, 'platform', { value: 'Win32', configurable: true });
const { isMacPlatform } = await import('@/services/environment');
expect(isMacPlatform()).toBe(false);
});
test('returns false when navigator.platform is Linux', async () => {
Object.defineProperty(navigator, 'platform', { value: 'Linux x86_64', configurable: true });
const { isMacPlatform } = await import('@/services/environment');
expect(isMacPlatform()).toBe(false);
});
});
// ── getCommandPaletteShortcut ──────────────────────────────────
describe('getCommandPaletteShortcut', () => {
test('returns Mac shortcut on Mac platforms', async () => {
Object.defineProperty(navigator, 'platform', { value: 'MacIntel', configurable: true });
const { getCommandPaletteShortcut } = await import('@/services/environment');
expect(getCommandPaletteShortcut()).toContain('P');
});
test('returns Ctrl shortcut on non-Mac platforms', async () => {
Object.defineProperty(navigator, 'platform', { value: 'Win32', configurable: true });
const { getCommandPaletteShortcut } = await import('@/services/environment');
expect(getCommandPaletteShortcut()).toBe('Ctrl+Shift+P');
});
});
// ── getAPIBaseUrl ──────────────────────────────────────────────
describe('getAPIBaseUrl', () => {
test('returns /api in web development mode', async () => {
env['NODE_ENV'] = 'development';
env['NEXT_PUBLIC_APP_PLATFORM'] = 'web';
const { getAPIBaseUrl } = await import('@/services/environment');
expect(getAPIBaseUrl()).toBe('/api');
});
test('returns full URL in production', async () => {
env['NODE_ENV'] = 'production';
env['NEXT_PUBLIC_APP_PLATFORM'] = 'web';
delete env['NEXT_PUBLIC_API_BASE_URL'];
const { getAPIBaseUrl } = await import('@/services/environment');
expect(getAPIBaseUrl()).toBe('https://web.readest.com/api');
});
test('returns full URL for tauri platform even in development', async () => {
env['NODE_ENV'] = 'development';
env['NEXT_PUBLIC_APP_PLATFORM'] = 'tauri';
delete env['NEXT_PUBLIC_API_BASE_URL'];
const { getAPIBaseUrl } = await import('@/services/environment');
expect(getAPIBaseUrl()).toBe('https://web.readest.com/api');
});
});
// ── getNodeAPIBaseUrl ──────────────────────────────────────────
describe('getNodeAPIBaseUrl', () => {
test('returns /api in web development mode', async () => {
env['NODE_ENV'] = 'development';
env['NEXT_PUBLIC_APP_PLATFORM'] = 'web';
const { getNodeAPIBaseUrl } = await import('@/services/environment');
expect(getNodeAPIBaseUrl()).toBe('/api');
});
test('returns full node URL in production', async () => {
env['NODE_ENV'] = 'production';
env['NEXT_PUBLIC_APP_PLATFORM'] = 'web';
delete env['NEXT_PUBLIC_NODE_BASE_URL'];
const { getNodeAPIBaseUrl } = await import('@/services/environment');
expect(getNodeAPIBaseUrl()).toBe('https://node.readest.com/api');
});
test('returns full node URL for tauri platform even in development', async () => {
env['NODE_ENV'] = 'development';
env['NEXT_PUBLIC_APP_PLATFORM'] = 'tauri';
delete env['NEXT_PUBLIC_NODE_BASE_URL'];
const { getNodeAPIBaseUrl } = await import('@/services/environment');
expect(getNodeAPIBaseUrl()).toBe('https://node.readest.com/api');
});
});
// ── environmentConfig default export ───────────────────────────
describe('environmentConfig', () => {
test('exports an object with getAppService function', async () => {
const envConfig = await import('@/services/environment');
expect(typeof envConfig.default.getAppService).toBe('function');
});
});
});