feat(uba): UBA-008/009 panel registration + health indicator

UBA-008: Register UBAPanel in editor sidebar (router.setup.ts)
UBA-009: Health indicator widget in Configure tab
  - useUBAHealth hook polling UBAClient.health() every 30s
  - HealthBadge component: 4 states (unknown/checking/healthy/unhealthy)
  - Pulsing animation on checking; design token colours with fallbacks
  - Shown only when schema.backend.endpoints.health is present

Test fix: UBASchemaParser.test.ts
  - isFailure<T>() type guard for webpack ts-loader friendly narrowing
  - eslint-disable for destructuring discard patterns
This commit is contained in:
Richard Osborne
2026-02-18 20:43:03 +01:00
parent 6403341bcc
commit ade2afee85
5 changed files with 234 additions and 18 deletions

View File

@@ -9,6 +9,12 @@
import { describe, it, expect, beforeEach } from '@jest/globals';
import { SchemaParser } from '../../src/editor/src/models/UBA/SchemaParser';
import type { ParseResult } from '../../src/editor/src/models/UBA/types';
/** Type guard: narrows ParseResult to the failure branch (webpack ts-loader friendly) */
function isFailure<T>(result: ParseResult<T>): result is Extract<ParseResult<T>, { success: false }> {
return !result.success;
}
// ─── Fixtures ─────────────────────────────────────────────────────────────────
@@ -44,7 +50,7 @@ describe('SchemaParser', () => {
it('rejects null input', () => {
const result = parser.parse(null);
expect(result.success).toBe(false);
if (!result.success) {
if (isFailure(result)) {
expect(result.errors[0].path).toBe('');
}
});
@@ -55,10 +61,11 @@ describe('SchemaParser', () => {
});
it('rejects missing schema_version', () => {
const { schema_version: _sv, ...noVersion } = minimalValid;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { schema_version: _, ...noVersion } = minimalValid;
const result = parser.parse(noVersion);
expect(result.success).toBe(false);
if (!result.success) {
if (isFailure(result)) {
expect(result.errors.some((e) => e.path === 'schema_version')).toBe(true);
}
});
@@ -87,10 +94,11 @@ describe('SchemaParser', () => {
describe('backend validation', () => {
it('errors when backend is missing', () => {
const { backend: _b, ...noBackend } = minimalValid;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { backend: _, ...noBackend } = minimalValid;
const result = parser.parse(noBackend);
expect(result.success).toBe(false);
if (!result.success) {
if (isFailure(result)) {
expect(result.errors.some((e) => e.path === 'backend')).toBe(true);
}
});
@@ -107,7 +115,7 @@ describe('SchemaParser', () => {
});
const result = parser.parse(data);
expect(result.success).toBe(false);
if (!result.success) {
if (isFailure(result)) {
expect(result.errors.some((e) => e.path === 'backend.endpoints.config')).toBe(true);
}
});
@@ -138,7 +146,7 @@ describe('SchemaParser', () => {
});
const result = parser.parse(data);
expect(result.success).toBe(false);
if (!result.success) {
if (isFailure(result)) {
expect(result.errors.some((e) => e.path === 'backend.auth.type')).toBe(true);
}
});
@@ -171,7 +179,7 @@ describe('SchemaParser', () => {
});
const result = parser.parse(data);
expect(result.success).toBe(false);
if (!result.success) {
if (isFailure(result)) {
expect(result.errors.some((e) => e.path.includes('id'))).toBe(true);
}
});