Files
Amer Agovic aa872bdd6b Release 1.0.10 with subpath deployment and modal menu invokes.
Add app_base/router_base config, compat path helpers, and scoped service worker
registration so apps can mount under a URL prefix. Wire invoke_type modal through
a handler registry and open the notification center from the standard menu flow.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:44:32 -05:00

241 lines
8.0 KiB
JavaScript

/**
* Tests for platform/env.js
* Uses Node.js built-in test runner (node --test)
*/
import { test, describe, beforeEach } from 'node:test';
import assert from 'node:assert';
import {
initEnv,
getConfig,
getConfigSync,
setConfig,
getConfigDict,
isDevelopment,
isProduction,
isServiceWorkerEnabledSync,
resolveServiceWorkerEnabled,
resolveProfileBases,
normalizeRouterBase,
CONFIG_KEYS
} from '../src/platform/env.js';
import { getProvider } from '../src/platform/storage.js';
describe('env.js', () => {
beforeEach(() => {
// Reset config before each test
initEnv({});
});
describe('initEnv', () => {
test('should initialize config with provided values', () => {
const appConfig = {
name: 'TestApp',
displayName: 'Test Application',
brandLogo: '/logo.png',
ui_shell: 'DashboardShell',
storage: { backend: 'indexedDB' },
api: { baseURL: '/api/v1' },
modules: ['core', 'dummy']
};
initEnv(appConfig);
const config = getConfigDict();
assert.strictEqual(config.APP_NAME, 'TestApp');
assert.strictEqual(config.APP_DISPLAY_NAME, 'Test Application');
assert.strictEqual(config.BRAND_LOGO, '/logo.png');
assert.strictEqual(config.UI_SHELL, 'DashboardShell');
assert.strictEqual(config.STORAGE_BACKEND, 'indexedDB');
assert.strictEqual(config.API_BASE_URL, '/api/v1');
assert.deepStrictEqual(config.MODULES, ['core', 'dummy']);
});
test('should use defaults when values are missing', () => {
const appConfig = {
name: 'TestApp'
};
initEnv(appConfig);
const config = getConfigDict();
assert.strictEqual(config.APP_NAME, 'TestApp');
assert.strictEqual(config.APP_DISPLAY_NAME, 'TestApp'); // Falls back to name
assert.strictEqual(config.BRAND_LOGO, '/favicon.svg'); // Default
assert.strictEqual(config.UI_SHELL, 'EmptyShell'); // Default
assert.strictEqual(config.STORAGE_BACKEND, 'localStorage'); // Default
assert.strictEqual(config.API_BASE_URL, '/api'); // Default
assert.deepStrictEqual(config.MODULES, []); // Default
});
});
describe('getConfig', () => {
beforeEach(() => {
initEnv({
name: 'TestApp',
displayName: 'Test Display Name'
});
});
test('should return config value from dictionary', async () => {
const value = await getConfig(CONFIG_KEYS.APP_NAME);
assert.strictEqual(value, 'TestApp');
});
test('should return altValue when key not found', async () => {
// Note: getConfig checks import.meta.env which may not be available in Node
// This test verifies the fallback behavior
const value = await getConfig('NON_EXISTENT_KEY', 'default');
assert.strictEqual(value, 'default');
});
test('should return null when key not found and no altValue', async () => {
// Note: getConfig checks import.meta.env which may not be available in Node
// This test verifies the fallback behavior
const value = await getConfig('NON_EXISTENT_KEY');
// May return null or undefined depending on import.meta.env availability
assert.ok(value === null || value === undefined);
});
test('locked keys ignore persisted storage overrides', async () => {
initEnv({
name: 'TestApp',
api: { base_url: '/api/profile' },
modules: ['rt', 'game']
});
const configStorage = getProvider('kv', 'config');
await configStorage.set(CONFIG_KEYS.API_BASE_URL, '/api/stale');
await configStorage.set(CONFIG_KEYS.MODULES, ['stale']);
assert.strictEqual(await getConfig(CONFIG_KEYS.API_BASE_URL), '/api/profile');
assert.deepStrictEqual(await getConfig(CONFIG_KEYS.MODULES), ['rt', 'game']);
});
});
describe('getConfigSync', () => {
test('should return in-memory config without storage reads', () => {
initEnv({
name: 'SyncApp',
api: { base_url: '/api/sync' }
});
assert.strictEqual(getConfigSync(CONFIG_KEYS.APP_NAME), 'SyncApp');
assert.strictEqual(getConfigSync(CONFIG_KEYS.API_BASE_URL), '/api/sync');
assert.strictEqual(getConfigSync('MISSING', 'fallback'), 'fallback');
});
});
describe('setConfig', () => {
beforeEach(() => {
initEnv({
name: 'TestApp'
});
});
test('should update existing config key in dictionary', async () => {
await setConfig(CONFIG_KEYS.APP_NAME, 'UpdatedApp');
const value = await getConfig(CONFIG_KEYS.APP_NAME);
assert.strictEqual(value, 'UpdatedApp');
});
test('should handle new keys (may attempt storage)', async () => {
// Since it's a new key, it should attempt to store in storage
// We just verify it doesn't throw
await assert.doesNotReject(async () => {
await setConfig('custom.key', 'customValue');
});
});
});
describe('getConfigDict', () => {
test('should return a copy of config dictionary', () => {
initEnv({
name: 'TestApp',
displayName: 'Test Display'
});
const config1 = getConfigDict();
const config2 = getConfigDict();
// Should be equal but not the same object (copy)
assert.deepStrictEqual(config1, config2);
assert.notStrictEqual(config1, config2);
});
test('should return config with defaults when initialized with empty object', () => {
initEnv({});
const config = getConfigDict();
// initEnv sets defaults even with empty object
assert.ok('APP_NAME' in config);
assert.ok('BRAND_LOGO' in config);
assert.strictEqual(config.BRAND_LOGO, '/favicon.svg');
});
});
describe('CONFIG_KEYS', () => {
test('should export all expected config keys', () => {
assert.ok('APP_NAME' in CONFIG_KEYS);
assert.ok('APP_DISPLAY_NAME' in CONFIG_KEYS);
assert.ok('BRAND_LOGO' in CONFIG_KEYS);
assert.ok('UI_SHELL' in CONFIG_KEYS);
assert.ok('STORAGE_BACKEND' in CONFIG_KEYS);
assert.ok('API_BASE_URL' in CONFIG_KEYS);
assert.ok('MODULES' in CONFIG_KEYS);
assert.ok('SERVICE_WORKER_ENABLED' in CONFIG_KEYS);
});
});
describe('service worker profile flag', () => {
test('resolveServiceWorkerEnabled defaults to true', () => {
assert.strictEqual(resolveServiceWorkerEnabled({}), true);
});
test('resolveServiceWorkerEnabled reads service_worker.enabled', () => {
assert.strictEqual(resolveServiceWorkerEnabled({ service_worker: { enabled: false } }), false);
assert.strictEqual(resolveServiceWorkerEnabled({ pwa: { service_worker: { enabled: false } } }), false);
});
test('initEnv seeds SERVICE_WORKER_ENABLED into config', () => {
initEnv({ service_worker: { enabled: false } });
assert.strictEqual(isServiceWorkerEnabledSync(), false);
assert.strictEqual(getConfigDict()[CONFIG_KEYS.SERVICE_WORKER_ENABLED], false);
});
});
describe('profile base paths', () => {
test('resolveProfileBases defaults to root', () => {
assert.deepStrictEqual(resolveProfileBases({}), { app_base: '/', router_base: '/' });
});
test('resolveProfileBases mirrors a single configured base', () => {
assert.deepStrictEqual(resolveProfileBases({ app_base: '/admin/' }), {
app_base: '/admin',
router_base: '/admin'
});
});
test('resolveProfileBases allows separate router base', () => {
assert.deepStrictEqual(resolveProfileBases({ app_base: '/admin', router_base: '/console' }), {
app_base: '/admin',
router_base: '/console'
});
});
test('initEnv seeds APP_BASE and ROUTER_BASE', () => {
initEnv({ id: 'demo', router_base: 'ops' });
assert.strictEqual(getConfigSync(CONFIG_KEYS.ROUTER_BASE), '/ops');
assert.strictEqual(getConfigSync(CONFIG_KEYS.APP_BASE), '/ops');
assert.strictEqual(normalizeRouterBase('/ops/'), '/ops');
});
});
describe('isDevelopment and isProduction', () => {
test('should be functions', () => {
assert.strictEqual(typeof isDevelopment, 'function');
assert.strictEqual(typeof isProduction, 'function');
});
});
});