Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest'
import { frameworkToCfPackage } from './toCasePackage'
import type { Framework } from '@/domain/framework/model/types'
import type { FrameworkId } from '@/domain/shared/types'

function makeFramework(version?: string): Framework {
return {
id: 'fw-test' as FrameworkId,
metadata: { title: 'Test Framework', version },
items: new Map(),
associations: new Map(),
}
}

describe('frameworkToCfPackage — version', () => {
it('passes through an author-assigned version unchanged', () => {
const cfPackage = frameworkToCfPackage({ framework: makeFramework('1.4'), caseVersion: '1.1' })
expect(cfPackage.CFDocument.version).toBe('1.4')
})

it('does not invent a default version when none is set', () => {
const cfPackage = frameworkToCfPackage({ framework: makeFramework(undefined), caseVersion: '1.1' })
expect(cfPackage.CFDocument.version).toBeUndefined()
})

it('leaves version untouched across repeated saves (no auto-increment)', () => {
const framework = makeFramework('2.0')
const first = frameworkToCfPackage({ framework, caseVersion: '1.1' })
const second = frameworkToCfPackage({ framework, caseVersion: '1.1' })
expect(first.CFDocument.version).toBe('2.0')
expect(second.CFDocument.version).toBe('2.0')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ const nowIso = () => new Date().toISOString()
/** OpenCASE extension namespace for editor-specific data */
const OPENCASE_EXT_KEY = 'ext:opencase'

/** Default version format: major.minor.build */
const DEFAULT_VERSION = '1.0.0'

/** UUID v4 pattern */
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i

Expand Down Expand Up @@ -82,30 +79,6 @@ function makePackageUri(uuid: string): string {
return `/ims/case/v1p1/CFPackages/${uuid}`
}

/**
* Increment the build number of a version string (format: major.minor.build).
* If the version doesn't match the expected format, returns the default version.
*/
function incrementVersion(currentVersion?: string): string {
if (!currentVersion) return DEFAULT_VERSION

const parts = currentVersion.split('.')
if (parts.length !== 3) {
// If current version exists but doesn't match format, try to preserve major.minor
if (parts.length === 2) {
return `${parts[0]}.${parts[1]}.0`
}
return DEFAULT_VERSION
}

const build = Number.parseInt(parts[2], 10)
if (Number.isNaN(build)) {
return `${parts[0]}.${parts[1]}.0`
}

return `${parts[0]}.${parts[1]}.${build + 1}`
}

type OpencaseExtension = {
layout?: NodeLayout
notes?: string
Expand Down Expand Up @@ -148,16 +121,11 @@ function frameworkToCfDocument(
framework: Framework,
caseVersion: CaseVersion,
layout?: NodeLayout,
options?: { incrementVersion?: boolean; edgeType?: string }
options?: { edgeType?: string }
): CFDocument {
const meta = framework.metadata
const fwId = String(framework.id)
const effectiveVersion = caseVersion === 'unknown' ? '1.1' : caseVersion

// Handle version: either use current, increment, or set default
const documentVersion = options?.incrementVersion
? incrementVersion(meta.version)
: (meta.version ?? DEFAULT_VERSION)

const docTitle = meta.title ?? 'Untitled Framework'

Expand All @@ -171,7 +139,7 @@ function frameworkToCfDocument(
publisher: meta.publisher,
notes: meta.notes,
language: meta.language,
version: documentVersion,
version: meta.version,
adoptionStatus: meta.adoptionStatus,
frameworkType: meta.frameworkType,
officialSourceURL: meta.officialSourceURL,
Expand Down Expand Up @@ -349,13 +317,11 @@ function associationToCfAssociation(
* @param framework - The domain Framework (source of truth)
* @param caseVersion - Target CASE version for serialization ('1.0' or '1.1')
* @param layout - Optional layout state to store in extensions
* @param incrementVersion - If true, increment the build number of the version (for saves)
*/
export function frameworkToCfPackage(params: {
framework: Framework
caseVersion: CaseVersion
layout?: LayoutState
incrementVersion?: boolean
/** Edge rendering style to persist with this framework */
edgeType?: string
/** CFItemType definitions to include in CFDefinitions (from editor state) */
Expand All @@ -369,12 +335,12 @@ export function frameworkToCfPackage(params: {
/** CFLicense definitions to include in CFDefinitions (from editor state) */
cfLicenses?: CFLicense[]
}): CFPackage {
const { framework, caseVersion, layout, incrementVersion, edgeType, cfItemTypes, cfSubjects, cfConcepts, cfAssociationGroupings, cfLicenses } = params
const { framework, caseVersion, layout, edgeType, cfItemTypes, cfSubjects, cfConcepts, cfAssociationGroupings, cfLicenses } = params
const fwId = String(framework.id)

// Build CFDocument
const documentLayout = layout?.byNodeId?.[fwId]
const document = frameworkToCfDocument(framework, caseVersion, documentLayout, { incrementVersion, edgeType })
const document = frameworkToCfDocument(framework, caseVersion, documentLayout, { edgeType })

// Build CFItems
const itemIds = Array.from(framework.items.keys()).map(String)
Expand Down Expand Up @@ -783,5 +749,4 @@ export type FrameworkExportParams = {
framework: Framework
caseVersion: CaseVersion
layout?: LayoutState
incrementVersion?: boolean
}
6 changes: 3 additions & 3 deletions apps/editor/src/ui/editor/EditorCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export default function EditorCanvas({ onBack, onSaveToServer, isPublishedToOpen
const ctx = saveCtxRef.current
const { framework, layout } = fromEditorGraph({ graph: { nodes: n, edges: e } })
const cfPackage = frameworkToCfPackage({
framework, layout, incrementVersion: false,
framework, layout,
caseVersion: ctx.caseVersion, edgeType: ctx.edgeType,
cfItemTypes: ctx.cfItemTypes, cfSubjects: ctx.cfSubjects,
cfConcepts: ctx.cfConcepts, cfLicenses: ctx.cfLicenses, cfAssociationGroupings: ctx.cfAssociationGroupings,
Expand All @@ -170,13 +170,13 @@ export default function EditorCanvas({ onBack, onSaveToServer, isPublishedToOpen
}
}, [isPublishedToOpenCase, onFetchCfPackage])

// Save: Generate CFPackage with version increment and POST to server
// Save: Generate CFPackage and POST to server
const handleSave = useCallback(async () => {
const { nodes: n, edges: e } = graphRef.current
const ctx = saveCtxRef.current
const { framework, layout } = fromEditorGraph({ graph: { nodes: n, edges: e } })
const cfPackage = frameworkToCfPackage({
framework, layout, incrementVersion: true,
framework, layout,
caseVersion: ctx.caseVersion, edgeType: ctx.edgeType,
cfItemTypes: ctx.cfItemTypes, cfSubjects: ctx.cfSubjects,
cfConcepts: ctx.cfConcepts, cfLicenses: ctx.cfLicenses, cfAssociationGroupings: ctx.cfAssociationGroupings,
Expand Down
10 changes: 4 additions & 6 deletions apps/editor/src/ui/editor/components/NodePropertiesPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -312,12 +312,10 @@ export default memo(function NodePropertiesPanel({
<input id="node-fw-end-date" type="date" className={INPUT_CLS} value={cfDocument?.statusEndDate ?? ''} onChange={(e) => updateDocument({ statusEndDate: e.target.value || undefined })} />
</div>
</div>
{cfDocument?.version ? (
<div>
<div className="text-sm font-medium text-slate-700">Version</div>
<div className="mt-1 text-base text-slate-600">{cfDocument.version}</div>
</div>
) : null}
<div>
<label className={LABEL_CLS} htmlFor="node-fw-version">Version</label>
<input id="node-fw-version" className={INPUT_CLS} value={cfDocument?.version ?? ''} onChange={(e) => updateDocument({ version: e.target.value || undefined })} placeholder="e.g., 1.0" />
</div>
</div>
) : (
/* ── Item fields — single column ── */
Expand Down
Loading