From d067490f71348f9af213f11dc87744362cbd2e4d Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:46:52 +0200 Subject: [PATCH] ui(deploy): add deriveAppName pure function + tests Co-Authored-By: Claude Sonnet 4.6 --- .../utils/deriveAppName.test.ts | 40 +++++++++++++++++++ .../AppDeploymentPage/utils/deriveAppName.ts | 38 ++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 ui/src/pages/AppsTab/AppDeploymentPage/utils/deriveAppName.test.ts create mode 100644 ui/src/pages/AppsTab/AppDeploymentPage/utils/deriveAppName.ts diff --git a/ui/src/pages/AppsTab/AppDeploymentPage/utils/deriveAppName.test.ts b/ui/src/pages/AppsTab/AppDeploymentPage/utils/deriveAppName.test.ts new file mode 100644 index 00000000..fc307036 --- /dev/null +++ b/ui/src/pages/AppsTab/AppDeploymentPage/utils/deriveAppName.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from 'vitest'; +import { deriveAppName } from './deriveAppName'; + +describe('deriveAppName', () => { + it('truncates at first digit', () => { + expect(deriveAppName('payment-gateway-1.2.0.jar')).toBe('Payment Gateway'); + }); + + it('returns clean title-cased name without digits', () => { + expect(deriveAppName('order-service.jar')).toBe('Order Service'); + }); + + it('strips orphan 1-char token after truncation (v from my-app-v2)', () => { + expect(deriveAppName('my-app-v2.jar')).toBe('My App'); + }); + + it('treats underscore like dash', () => { + expect(deriveAppName('acme_billing-3.jar')).toBe('Acme Billing'); + }); + + it('strips the .jar extension when no digits present', () => { + expect(deriveAppName('acme-billing.jar')).toBe('Acme Billing'); + }); + + it('returns empty string for empty input', () => { + expect(deriveAppName('')).toBe(''); + }); + + it('returns empty string when filename starts with a digit', () => { + expect(deriveAppName('1-my-thing.jar')).toBe(''); + }); + + it('mixed separators are both collapsed to spaces', () => { + expect(deriveAppName('foo_bar-baz.jar')).toBe('Foo Bar Baz'); + }); + + it('strips trailing orphan regardless of letter identity', () => { + expect(deriveAppName('release-x9.jar')).toBe('Release'); + }); +}); diff --git a/ui/src/pages/AppsTab/AppDeploymentPage/utils/deriveAppName.ts b/ui/src/pages/AppsTab/AppDeploymentPage/utils/deriveAppName.ts new file mode 100644 index 00000000..e172bbaf --- /dev/null +++ b/ui/src/pages/AppsTab/AppDeploymentPage/utils/deriveAppName.ts @@ -0,0 +1,38 @@ +/** + * Derive a human-readable app name from a JAR filename. + * + * Rule: + * 1. Strip the `.jar` extension. + * 2. Truncate at the first digit (0-9) or `.`. + * 3. Replace `-` and `_` with spaces. + * 4. Collapse multiple spaces and trim. + * 5. Drop 1-char orphan tokens (e.g. the trailing `v` in `my-app-v2`). + * 6. Title-case each remaining word. + * + * The result is a suggestion — the caller is expected to let the user override. + */ +export function deriveAppName(filename: string): string { + if (!filename) return ''; + + let stem = filename.replace(/\.jar$/i, ''); + + // Truncate at first digit or dot + const match = stem.match(/[0-9.]/); + if (match && match.index !== undefined) { + stem = stem.slice(0, match.index); + } + + // Separators → space + stem = stem.replace(/[-_]+/g, ' '); + + // Collapse whitespace + trim + stem = stem.replace(/\s+/g, ' ').trim(); + if (!stem) return ''; + + // Drop 1-char orphan tokens + const tokens = stem.split(' ').filter((t) => t.length > 1); + if (tokens.length === 0) return ''; + + // Title-case + return tokens.map((t) => t.charAt(0).toUpperCase() + t.slice(1).toLowerCase()).join(' '); +}