FileNameTemplateTests.swift 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import XCTest
  2. @testable import MixBoard
  3. /// Tests for FileNameTemplate.
  4. final class FileNameTemplateTests: XCTestCase {
  5. private func makeTrack(
  6. title: String = "Spot Rusherz",
  7. artist: String = "Raekwon",
  8. album: String = "Only Built 4 Cuban Linx",
  9. genre: String = "Hip-Hop",
  10. bpm: Double? = 95,
  11. key: String? = "Dm",
  12. format: String = "MP3",
  13. duration: TimeInterval = 193
  14. ) -> Track {
  15. let t = Track(title: title, artist: artist, album: album, genre: genre, filePath: "/fake.\(format.lowercased())", duration: duration, fileFormat: format)
  16. t.bpm = bpm
  17. t.musicalKey = key
  18. return t
  19. }
  20. // MARK: - Basic Templates
  21. func testDefaultTemplate() {
  22. let track = makeTrack()
  23. let result = FileNameTemplate.generate(template: FileNameTemplate.defaultTemplate, track: track, playlistIndex: 0, totalTracks: 10)
  24. XCTAssertTrue(result.contains("Raekwon"))
  25. XCTAssertTrue(result.contains("Only Built 4 Cuban Linx"))
  26. XCTAssertTrue(result.contains("Spot Rusherz"))
  27. }
  28. func testTrackNumberPadding() {
  29. let track = makeTrack()
  30. let r1 = FileNameTemplate.generate(template: "{track} {title}", track: track, playlistIndex: 0, totalTracks: 10)
  31. XCTAssertTrue(r1.hasPrefix("01"))
  32. let r2 = FileNameTemplate.generate(template: "{track} {title}", track: track, playlistIndex: 4, totalTracks: 150)
  33. XCTAssertTrue(r2.hasPrefix("005"))
  34. }
  35. func testAllVariables() {
  36. let track = makeTrack()
  37. let all = "{track} {artist} {album} {title} {genre} {bpm} {key} {duration} {format} {samplerate} {bitdepth}"
  38. let result = FileNameTemplate.generate(template: all, track: track, playlistIndex: 2, totalTracks: 10)
  39. XCTAssertTrue(result.contains("03"))
  40. XCTAssertTrue(result.contains("Raekwon"))
  41. XCTAssertTrue(result.contains("Only Built 4 Cuban Linx"))
  42. XCTAssertTrue(result.contains("Spot Rusherz"))
  43. XCTAssertTrue(result.contains("Hip-Hop"))
  44. XCTAssertTrue(result.contains("95"))
  45. XCTAssertTrue(result.contains("Dm"))
  46. XCTAssertTrue(result.contains("MP3"))
  47. // Duration colon gets sanitized to _ for filenames, so check for digits
  48. XCTAssertTrue(result.contains("3") && result.contains("13"), "Should contain duration digits")
  49. }
  50. func testEmptyFields() {
  51. let track = makeTrack(artist: "", album: "", bpm: nil, key: nil)
  52. let result = FileNameTemplate.generate(template: "{artist} - {album} - {title}", track: track, playlistIndex: 0, totalTracks: 1)
  53. // Should clean up double separators
  54. XCTAssertTrue(result.contains("Spot Rusherz"))
  55. XCTAssertFalse(result.contains(" - - "))
  56. }
  57. func testFilesystemSanitization() {
  58. let track = makeTrack(title: "What/Ever: The *Best?")
  59. let result = FileNameTemplate.generate(template: "{title}", track: track, playlistIndex: 0, totalTracks: 1)
  60. XCTAssertFalse(result.contains("/"))
  61. XCTAssertFalse(result.contains(":"))
  62. XCTAssertFalse(result.contains("*"))
  63. XCTAssertFalse(result.contains("?"))
  64. }
  65. func testPreview() {
  66. let preview = FileNameTemplate.preview(template: "{artist} - {title}")
  67. XCTAssertTrue(preview.contains("Raekwon"))
  68. XCTAssertTrue(preview.contains("Spot Rusherz"))
  69. }
  70. func testPresetsExist() {
  71. XCTAssertGreaterThanOrEqual(FileNameTemplate.presets.count, 4)
  72. for preset in FileNameTemplate.presets {
  73. XCTAssertFalse(preset.name.isEmpty)
  74. XCTAssertFalse(preset.template.isEmpty)
  75. // Each preset should generate a non-empty result
  76. let result = FileNameTemplate.preview(template: preset.template)
  77. XCTAssertGreaterThan(result.count, 0)
  78. }
  79. }
  80. func testAvailableVariables() {
  81. XCTAssertGreaterThanOrEqual(FileNameTemplate.availableVariables.count, 10)
  82. for v in FileNameTemplate.availableVariables {
  83. XCTAssertTrue(v.token.hasPrefix("{"))
  84. XCTAssertTrue(v.token.hasSuffix("}"))
  85. XCTAssertFalse(v.description.isEmpty)
  86. }
  87. }
  88. func testBPMOnlyWhenPresent() {
  89. let track = makeTrack(bpm: nil)
  90. let result = FileNameTemplate.generate(template: "{title} [{bpm}]", track: track, playlistIndex: 0, totalTracks: 1)
  91. // Empty BPM should result in clean output (brackets cleaned)
  92. XCTAssertFalse(result.contains("[]"))
  93. }
  94. }
  95. /// Tests for ExportOptions with fileNameTemplate.
  96. final class ExportOptionsTests: XCTestCase {
  97. func testDefaultOptionsHaveNoTemplate() {
  98. let opts = ExportOptions.default
  99. XCTAssertNil(opts.fileNameTemplate)
  100. }
  101. func testTemplateCanBeSet() {
  102. var opts = ExportOptions.default
  103. opts.fileNameTemplate = "{track} {artist} - {title}"
  104. XCTAssertEqual(opts.fileNameTemplate, "{track} {artist} - {title}")
  105. }
  106. }
  107. /// Tests for PlaylistViewModel status messages.
  108. final class StatusMessageTests: XCTestCase {
  109. @MainActor
  110. func testShowStatus() async {
  111. let vm = PlaylistViewModel()
  112. vm.showStatus("Test message", duration: 0.5)
  113. XCTAssertEqual(vm.statusMessage, "Test message")
  114. // Wait for auto-clear
  115. try? await Task.sleep(for: .seconds(0.7))
  116. XCTAssertNil(vm.statusMessage)
  117. }
  118. @MainActor
  119. func testStatusOverwrite() {
  120. let vm = PlaylistViewModel()
  121. vm.showStatus("First")
  122. XCTAssertEqual(vm.statusMessage, "First")
  123. vm.showStatus("Second")
  124. XCTAssertEqual(vm.statusMessage, "Second")
  125. }
  126. }