diff --git a/Sources/MusicAssistantCore/Domain/SongProject.swift b/Sources/MusicAssistantCore/Domain/SongProject.swift index fffb11e..28b1da6 100644 --- a/Sources/MusicAssistantCore/Domain/SongProject.swift +++ b/Sources/MusicAssistantCore/Domain/SongProject.swift @@ -324,6 +324,25 @@ public extension SongProject { vocalists[index].assignedSections.removeAll { $0 == sectionID } } + mutating func createVersion( + id: String = UUID().uuidString, + label: String, + createdAt: Date = Date() + ) -> SongVersion { + let version = SongVersion( + id: id, + label: label, + snapshot: SongProjectSnapshot(project: self), + createdAt: createdAt + ) + versions.append(version) + return version + } + + func version(id versionID: String) -> SongVersion? { + versions.first { $0.id == versionID } + } + mutating func appendSection(_ section: SongSection) { var orderedSection = section orderedSection.order = nextSectionOrder diff --git a/Tests/MusicAssistantCoreTests/SongProjectModelTests.swift b/Tests/MusicAssistantCoreTests/SongProjectModelTests.swift index 5dd15c8..d9effaa 100644 --- a/Tests/MusicAssistantCoreTests/SongProjectModelTests.swift +++ b/Tests/MusicAssistantCoreTests/SongProjectModelTests.swift @@ -89,6 +89,36 @@ final class SongProjectModelTests: XCTestCase { XCTAssertEqual(version.snapshot.instruments, project.instruments) } + func testVersionHistoryStoresIndependentSnapshots() { + var project = SongProject( + id: "versioned-project", + title: "First Title", + idea: "Original idea", + genres: [GenreStyle(id: "folk", name: "Folk")] + ) + let createdAt = Date(timeIntervalSince1970: 500) + + let version = project.createVersion( + id: "version-1", + label: "Initial snapshot", + createdAt: createdAt + ) + + project.title = "Changed Title" + project.idea = "Changed idea" + project.selectGenreStyle(GenreStyle(id: "electronic", name: "Electronic")) + + XCTAssertEqual(project.versions.count, 1) + XCTAssertEqual(project.version(id: "version-1"), version) + XCTAssertEqual(project.versions.first?.label, "Initial snapshot") + XCTAssertEqual(project.versions.first?.createdAt, createdAt) + XCTAssertEqual(project.versions.first?.snapshot.title, "First Title") + XCTAssertEqual(project.versions.first?.snapshot.idea, "Original idea") + XCTAssertEqual(project.versions.first?.snapshot.genres, [GenreStyle(id: "folk", name: "Folk")]) + XCTAssertEqual(project.title, "Changed Title") + XCTAssertEqual(project.genres.map(\.id), ["folk", "electronic"]) + } + func testGenreStylesAreModeledAsMultiSelect() { let arabicPop = GenreStyle(id: "arabic-pop", name: "Arabic Pop") let synthwave = GenreStyle(id: "synthwave", name: "Synthwave") diff --git a/docs/TASKS.md b/docs/TASKS.md index 2e2a60d..7db8dab 100644 --- a/docs/TASKS.md +++ b/docs/TASKS.md @@ -34,7 +34,7 @@ requirement is missing and blocks implementation, record it in - [x] Create new/open/save song projects locally. - [x] Implement project list. -- [ ] Implement immutable or snapshot-based version history. +- [x] Implement immutable or snapshot-based version history. - [ ] Restore a previous version without destroying later versions. - [ ] Add autosave strategy that does not create excessive versions.