Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4

- name: Install SwiftFormat
run: brew install swiftformat
run: |
brew update
brew install swiftformat
swiftformat --version
- name: Check formatting
run: |
Expand Down
38 changes: 4 additions & 34 deletions App/KasetApp.swift
Original file line number Diff line number Diff line change
@@ -1,46 +1,16 @@
import AppKit
import SwiftUI

// MARK: - SearchFocusTriggerKey

/// Environment key for triggering search focus from keyboard shortcut.
struct SearchFocusTriggerKey: EnvironmentKey {
static let defaultValue: Binding<Bool> = .constant(false)
}

extension EnvironmentValues {
var searchFocusTrigger: Binding<Bool> {
get { self[SearchFocusTriggerKey.self] }
set { self[SearchFocusTriggerKey.self] = newValue }
}
}

// MARK: - NavigationSelectionKey

/// Environment key for navigation selection.
struct NavigationSelectionKey: EnvironmentKey {
static let defaultValue: Binding<NavigationItem?> = .constant(nil)
@Entry var searchFocusTrigger: Binding<Bool> = .constant(false)
}

extension EnvironmentValues {
var navigationSelection: Binding<NavigationItem?> {
get { self[NavigationSelectionKey.self] }
set { self[NavigationSelectionKey.self] = newValue }
}
}

// MARK: - CommandBarVisibilityKey

/// Environment key for command bar visibility.
struct CommandBarVisibilityKey: EnvironmentKey {
static let defaultValue: Binding<Bool> = .constant(false)
@Entry var navigationSelection: Binding<NavigationItem?> = .constant(nil)
}

extension EnvironmentValues {
var showCommandBar: Binding<Bool> {
get { self[CommandBarVisibilityKey.self] }
set { self[CommandBarVisibilityKey.self] = newValue }
}
@Entry var showCommandBar: Binding<Bool> = .constant(false)
}

// MARK: - KasetApp
Expand Down Expand Up @@ -112,7 +82,7 @@ struct KasetApp: App {
}

var body: some Scene {
WindowGroup {
Window("Kaset", id: "main") {
// Skip UI during unit tests to prevent window spam
if UITestConfig.isRunningUnitTests, !UITestConfig.isUITestMode {
Color.clear
Expand Down
2 changes: 1 addition & 1 deletion App/VideoWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final class VideoWindowController {
/// Flag to prevent re-entrant close handling
private var isClosing = false

// Corner snapping
/// Corner snapping
enum Corner: Int {
case topLeft, topRight, bottomLeft, bottomRight
}
Expand Down
4 changes: 3 additions & 1 deletion Core/Models/AI/MusicIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -329,5 +329,7 @@ enum ContentSource: String, Sendable, CustomStringConvertible {
/// Use Charts for popularity-based requests
case charts

var description: String { rawValue }
var description: String {
rawValue
}
}
9 changes: 7 additions & 2 deletions Core/Models/ArtistDetail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@ struct ArtistDetail: Sendable {
/// Starting video ID for Mix.
let mixVideoId: String?

var id: String { self.artist.id }
var name: String { self.artist.name }
var id: String {
self.artist.id
}

var name: String {
self.artist.name
}

init(
artist: Artist,
Expand Down
4 changes: 3 additions & 1 deletion Core/Models/Lyrics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ struct Lyrics: Sendable, Equatable {
let source: String?

/// Whether the song has lyrics available.
var isAvailable: Bool { !self.text.isEmpty }
var isAvailable: Bool {
!self.text.isEmpty
}

/// Lyrics split into individual lines for display.
var lines: [String] {
Expand Down
2 changes: 1 addition & 1 deletion Core/Models/YTMusicError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ enum YTMusicError: LocalizedError, Sendable {

// MARK: CustomDebugStringConvertible

// Make the underlying error's description accessible for logging
/// Make the underlying error's description accessible for logging
extension YTMusicError: CustomDebugStringConvertible {
var debugDescription: String {
switch self {
Expand Down
39 changes: 31 additions & 8 deletions Core/Services/API/MockUITestYTMusicClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,37 @@ import Foundation
final class MockUITestYTMusicClient: YTMusicClientProtocol {
// MARK: - Continuation State

var hasMoreHomeSections: Bool { false }
var hasMoreExploreSections: Bool { false }
var hasMoreChartsSections: Bool { false }
var hasMoreMoodsAndGenresSections: Bool { false }
var hasMoreNewReleasesSections: Bool { false }
var hasMorePodcastsSections: Bool { false }
var hasMoreLikedSongs: Bool { false }
var hasMorePlaylistTracks: Bool { false }
var hasMoreHomeSections: Bool {
false
}

var hasMoreExploreSections: Bool {
false
}

var hasMoreChartsSections: Bool {
false
}

var hasMoreMoodsAndGenresSections: Bool {
false
}

var hasMoreNewReleasesSections: Bool {
false
}

var hasMorePodcastsSections: Bool {
false
}

var hasMoreLikedSongs: Bool {
false
}

var hasMorePlaylistTracks: Bool {
false
}

// MARK: - Mock Data

Expand Down
9 changes: 6 additions & 3 deletions Core/Services/API/YTMusicClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1218,12 +1218,14 @@ final class YTMusicClient: YTMusicClientProtocol {
let brandId = self.brandIdProvider?() ?? ""
let cacheKey = APICache.stableCacheKey(endpoint: endpoint, body: fullBody, brandId: brandId)
self.logger.debug(
"Request \(endpoint): brandId=\(brandId.isEmpty ? "primary" : brandId), cacheKey=\(cacheKey)")
"Request \(endpoint): brandId=\(brandId.isEmpty ? "primary" : brandId), cacheKey=\(cacheKey)"
)

// Check cache first
if ttl != nil, let cached = APICache.shared.get(key: cacheKey) {
self.logger.debug(
"Cache hit for \(endpoint) (brandId=\(brandId.isEmpty ? "primary" : brandId))")
"Cache hit for \(endpoint) (brandId=\(brandId.isEmpty ? "primary" : brandId))"
)
return cached
}

Expand Down Expand Up @@ -1265,7 +1267,8 @@ final class YTMusicClient: YTMusicClientProtocol {
{
let onBehalfOfUser = user["onBehalfOfUser"] as? String
self.logger.debug(
"Making request to \(endpoint) (onBehalfOfUser=\(onBehalfOfUser ?? "primary"))")
"Making request to \(endpoint) (onBehalfOfUser=\(onBehalfOfUser ?? "primary"))"
)
} else {
self.logger.debug("Making request to \(endpoint) (missing context)")
}
Expand Down
4 changes: 3 additions & 1 deletion Core/Services/FavoritesManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ final class FavoritesManager {
private(set) var items: [FavoriteItem] = []

/// Whether Favorites section should be visible.
var isVisible: Bool { !self.items.isEmpty }
var isVisible: Bool {
!self.items.isEmpty
}

/// Whether this instance should skip persistence (for testing).
private let skipPersistence: Bool
Expand Down
8 changes: 6 additions & 2 deletions Core/Services/Player/PlayerService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ final class PlayerService: NSObject, PlayerServiceProtocol {
var currentTrack: Song?

/// Whether playback is active.
var isPlaying: Bool { self.state.isPlaying }
var isPlaying: Bool {
self.state.isPlaying
}

/// Current playback position in seconds.
private(set) var progress: TimeInterval = 0
Expand All @@ -64,7 +66,9 @@ final class PlayerService: NSObject, PlayerServiceProtocol {
private var volumeBeforeMute: Double = 1.0

/// Whether audio is currently muted.
var isMuted: Bool { self.volume == 0 }
var isMuted: Bool {
self.volume == 0
}

/// Whether shuffle mode is enabled.
private(set) var shuffleEnabled: Bool = false
Expand Down
4 changes: 3 additions & 1 deletion Core/Services/SettingsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ final class SettingsManager {
case playlists
case lastUsed

var id: String { rawValue }
var id: String {
rawValue
}

var displayName: String {
switch self {
Expand Down
4 changes: 3 additions & 1 deletion Core/ViewModels/SearchViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ final class SearchViewModel {
case communityPlaylists = "Community playlists"
case podcasts = "Podcasts"

var id: String { rawValue }
var id: String {
rawValue
}
}

/// Filtered results based on selected filter.
Expand Down
1 change: 0 additions & 1 deletion Tests/KasetTests/AccountServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// Tests for AccountService using Swift Testing framework.

import Testing

@testable import Kaset

// MARK: - AccountServiceTests
Expand Down
1 change: 0 additions & 1 deletion Tests/KasetTests/AccountsListParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// Tests for AccountsListParser using Swift Testing framework.

import Testing

@testable import Kaset

// MARK: - AccountsListParserTests
Expand Down
20 changes: 10 additions & 10 deletions Tests/KasetTests/ExtensionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,31 +87,31 @@ struct ExtensionsTests {
// MARK: - URL High Quality Thumbnail Tests

@Test("Upgrades ytimg URL to high quality")
func highQualityThumbnailYtimg() {
let url = URL(string: "https://i.ytimg.com/vi/abc/w60-h60-l90-rj")!
func highQualityThumbnailYtimg() throws {
let url = try #require(URL(string: "https://i.ytimg.com/vi/abc/w60-h60-l90-rj"))
let highQuality = url.highQualityThumbnailURL
#expect(highQuality != nil)
#expect(highQuality!.absoluteString.contains("w226-h226"))
#expect(try #require(highQuality?.absoluteString.contains("w226-h226")))
}

@Test("Upgrades googleusercontent URL to high quality")
func highQualityThumbnailGoogleusercontent() {
let url = URL(string: "https://lh3.googleusercontent.com/abc=w120-h120-l90-rj")!
func highQualityThumbnailGoogleusercontent() throws {
let url = try #require(URL(string: "https://lh3.googleusercontent.com/abc=w120-h120-l90-rj"))
let highQuality = url.highQualityThumbnailURL
#expect(highQuality != nil)
#expect(highQuality!.absoluteString.contains("w226-h226"))
#expect(try #require(highQuality?.absoluteString.contains("w226-h226")))
}

@Test("Returns original URL for non-YouTube URLs")
func highQualityThumbnailNonYouTubeURL() {
let url = URL(string: "https://example.com/image.jpg")!
func highQualityThumbnailNonYouTubeURL() throws {
let url = try #require(URL(string: "https://example.com/image.jpg"))
let highQuality = url.highQualityThumbnailURL
#expect(highQuality == url)
}

@Test("Returns same URL for already high quality thumbnails")
func highQualityThumbnailAlreadyHighQuality() {
let url = URL(string: "https://i.ytimg.com/vi/abc/w400-h400-l90-rj")!
func highQualityThumbnailAlreadyHighQuality() throws {
let url = try #require(URL(string: "https://i.ytimg.com/vi/abc/w400-h400-l90-rj"))
let highQuality = url.highQualityThumbnailURL
#expect(highQuality?.absoluteString == "https://i.ytimg.com/vi/abc/w400-h400-l90-rj")
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/KasetTests/FavoritesManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Testing
@Suite("FavoritesManager", .serialized, .tags(.service), .timeLimit(.minutes(1)))
@MainActor
struct FavoritesManagerTests {
// Use a fresh manager for each test (skipLoad to avoid disk state)
/// Use a fresh manager for each test (skipLoad to avoid disk state)
var manager: FavoritesManager

init() {
Expand Down
1 change: 0 additions & 1 deletion Tests/KasetTests/MoodCategoryViewModelTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Foundation
import Testing

@testable import Kaset

@Suite("MoodCategoryViewModel", .serialized, .tags(.viewModel), .timeLimit(.minutes(1)))
Expand Down
1 change: 0 additions & 1 deletion Tests/KasetTests/MoodsAndGenresViewModelTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Foundation
import Testing

@testable import Kaset

@Suite("MoodsAndGenresViewModel", .serialized, .tags(.viewModel), .timeLimit(.minutes(1)))
Expand Down
1 change: 0 additions & 1 deletion Tests/KasetTests/NewReleasesViewModelTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Foundation
import Testing

@testable import Kaset

@Suite("NewReleasesViewModel", .serialized, .tags(.viewModel), .timeLimit(.minutes(1)))
Expand Down
1 change: 0 additions & 1 deletion Tests/KasetTests/PodcastModelTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Foundation
import Testing

@testable import Kaset

/// Tests for Podcast model types.
Expand Down
1 change: 0 additions & 1 deletion Tests/KasetTests/PodcastsViewModelTests.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Testing

@testable import Kaset

@Suite(.serialized)
Expand Down
6 changes: 3 additions & 3 deletions Tests/KasetTests/SearchSuggestionsParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct SearchSuggestionsParserTests {
// MARK: - Valid Response Tests

@Test("Parse single suggestion")
func parseSingleSuggestion() throws {
func parseSingleSuggestion() {
let data = self.makeSuggestionsResponse(queries: ["test query"])
let suggestions = SearchSuggestionsParser.parse(data)

Expand All @@ -48,7 +48,7 @@ struct SearchSuggestionsParserTests {
}

@Test("Parse suggestion with multiple runs joins text")
func parseSuggestionWithMultipleRuns() throws {
func parseSuggestionWithMultipleRuns() {
let data: [String: Any] = [
"contents": [
[
Expand Down Expand Up @@ -77,7 +77,7 @@ struct SearchSuggestionsParserTests {
}

@Test("Parse history suggestion")
func parseHistorySuggestion() throws {
func parseHistorySuggestion() {
let data: [String: Any] = [
"contents": [
[
Expand Down
1 change: 0 additions & 1 deletion Tests/KasetTests/TopSongsViewModelTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Foundation
import Testing

@testable import Kaset

@Suite("TopSongsViewModel", .serialized, .tags(.viewModel), .timeLimit(.minutes(1)))
Expand Down
Loading