Initial commit: bface library, build fixes, and refreshed docs
- Externalize all @tamagui/* and tamagui subpaths so dist no longer vendors Tamagui. - Emit TypeScript declarations with vite-plugin-dts; fix package exports types for ui/*. - Align initEnv with profiles: displayName, brandLogo, api.baseURL, themeColor, uiShell. - Stabilize tests with Node localStorage file; env tests pass. - Update README and component docs for services, menus, API client, and development.
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 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,
|
||||
setConfig,
|
||||
getConfigDict,
|
||||
isDevelopment,
|
||||
isProduction,
|
||||
CONFIG_KEYS
|
||||
} from '../src/platform/env.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);
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDevelopment and isProduction', () => {
|
||||
test('should be functions', () => {
|
||||
assert.strictEqual(typeof isDevelopment, 'function');
|
||||
assert.strictEqual(typeof isProduction, 'function');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { describe, test } from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
import { GridDataModel } from '../src/ui/components/grid/model.js';
|
||||
|
||||
const rows = [
|
||||
{ id: 1, name: 'Northwind', status: 'open', total: 1200 },
|
||||
{ id: 2, name: 'Blue Harbor', status: 'review', total: 800 },
|
||||
{ id: 3, name: 'Summit', status: 'open', total: 2400 },
|
||||
{ id: 4, name: 'Lattice', status: 'closed', total: 400 }
|
||||
];
|
||||
|
||||
describe('GridDataModel', () => {
|
||||
test('queryStructure infers columns from row data', async () => {
|
||||
const model = new GridDataModel({ rows });
|
||||
const result = await model.queryStructure();
|
||||
|
||||
assert.ok(result.columns.name);
|
||||
assert.ok(result.columns.status);
|
||||
assert.ok(result.columns.total);
|
||||
assert.strictEqual(result.columns.total.align, 'right');
|
||||
});
|
||||
|
||||
test('queryRecords filters, sorts, and paginates', async () => {
|
||||
const model = new GridDataModel({ rows });
|
||||
const result = await model.queryRecords({
|
||||
filter_by: { status: 'open' },
|
||||
sort_by: [{ field: 'total', direction: 'desc' }],
|
||||
offset: 0,
|
||||
page_size: 1
|
||||
});
|
||||
|
||||
assert.strictEqual(result.total, 2);
|
||||
assert.strictEqual(result.rows.length, 1);
|
||||
assert.strictEqual(result.rows[0].name, 'Summit');
|
||||
});
|
||||
|
||||
test('queryRecords supports text search through search filter', async () => {
|
||||
const model = new GridDataModel({ rows });
|
||||
const result = await model.queryRecords({
|
||||
filter_by: { search: 'harbor' }
|
||||
});
|
||||
|
||||
assert.strictEqual(result.total, 1);
|
||||
assert.strictEqual(result.rows[0].name, 'Blue Harbor');
|
||||
});
|
||||
|
||||
test('queryAggregates supports count and sum metrics', async () => {
|
||||
const model = new GridDataModel({ rows });
|
||||
const result = await model.queryAggregates({
|
||||
metrics: ['count', 'sum:total'],
|
||||
filter_by: { status: 'open' }
|
||||
});
|
||||
|
||||
assert.strictEqual(result.count, 2);
|
||||
assert.strictEqual(result['sum:total'], 3600);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user