From 2e1aacaedd51f1154dc504043c274127e446e057 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 02:07:59 +0000 Subject: [PATCH] Refactor: extract SettingsTab metadata out of PreferencesView PreferencesView nested a private SettingsTab enum carrying each pane's presentation metadata (sidebar icon/tint/label + page subtitle). Lift it to a top-level value type so the mapping is plain, reusable, and testable instead of buried in view-private code. Only SettingsPage's qualified reference (PreferencesView.SettingsTab) needed updating. Behavior-preserving. Adds SettingsTabTests (4 cases): all six tabs in order, non-empty metadata for each, unique labels/icons, and spot checks. --- Sources/Stag/Views/PreferencesWindow.swift | 50 +-------------------- Sources/Stag/Views/SettingsTab.swift | 52 ++++++++++++++++++++++ Tests/StagTests/SettingsTabTests.swift | 32 +++++++++++++ 3 files changed, 85 insertions(+), 49 deletions(-) create mode 100644 Sources/Stag/Views/SettingsTab.swift create mode 100644 Tests/StagTests/SettingsTabTests.swift diff --git a/Sources/Stag/Views/PreferencesWindow.swift b/Sources/Stag/Views/PreferencesWindow.swift index c60802e..7932f7c 100644 --- a/Sources/Stag/Views/PreferencesWindow.swift +++ b/Sources/Stag/Views/PreferencesWindow.swift @@ -56,54 +56,6 @@ private struct PreferencesView: View { @ObservedObject var prefs: Preferences @State private var selectedTab: SettingsTab = .general - enum SettingsTab: String, CaseIterable { - case general, capture, recording, overlays, shortcuts, advanced - - var icon: String { - switch self { - case .general: return "gearshape.fill" - case .capture: return "camera.fill" - case .recording: return "video.fill" - case .overlays: return "square.on.square" - case .shortcuts: return "keyboard.fill" - case .advanced: return "wrench.and.screwdriver.fill" - } - } - - var label: String { - switch self { - case .general: return "General" - case .capture: return "Capture" - case .recording: return "Recording" - case .overlays: return "Overlays" - case .shortcuts: return "Shortcuts" - case .advanced: return "Advanced" - } - } - - var tint: Color { - switch self { - case .general: return .gray - case .capture: return .blue - case .recording: return .red - case .overlays: return .purple - case .shortcuts: return .orange - case .advanced: return .teal - } - } - - var subtitle: String { - switch self { - case .general: return "Output format, saving, and after-capture behavior." - case .capture: return "Timer, preparation, and selection behavior." - case .recording: return "Video quality, audio, and recording overlays." - case .overlays: return "Webcam picture-in-picture and click effects." - case .shortcuts: return "Global hotkeys and editor tool keys." - case .advanced: return "Automation URL scheme and custom upload endpoint." - } - } - } - var body: some View { HStack(spacing: 0) { sidebar @@ -663,7 +615,7 @@ private struct SidebarItem: View { /// Scrollable page with a large title header and a centered, width-capped column. private struct SettingsPage: View { - let tab: PreferencesView.SettingsTab + let tab: SettingsTab @ViewBuilder var content: Content var body: some View { diff --git a/Sources/Stag/Views/SettingsTab.swift b/Sources/Stag/Views/SettingsTab.swift new file mode 100644 index 0000000..6a066b5 --- /dev/null +++ b/Sources/Stag/Views/SettingsTab.swift @@ -0,0 +1,52 @@ +import SwiftUI + +/// The panes of the Settings window. Owns each tab's presentation metadata — +/// sidebar icon/tint/label and the page subtitle — lifted out of PreferencesView +/// so the mapping is a plain, testable value type rather than view-private code. +enum SettingsTab: String, CaseIterable { + case general, capture, recording, overlays, shortcuts, advanced + + var icon: String { + switch self { + case .general: return "gearshape.fill" + case .capture: return "camera.fill" + case .recording: return "video.fill" + case .overlays: return "square.on.square" + case .shortcuts: return "keyboard.fill" + case .advanced: return "wrench.and.screwdriver.fill" + } + } + + var label: String { + switch self { + case .general: return "General" + case .capture: return "Capture" + case .recording: return "Recording" + case .overlays: return "Overlays" + case .shortcuts: return "Shortcuts" + case .advanced: return "Advanced" + } + } + + var tint: Color { + switch self { + case .general: return .gray + case .capture: return .blue + case .recording: return .red + case .overlays: return .purple + case .shortcuts: return .orange + case .advanced: return .teal + } + } + + var subtitle: String { + switch self { + case .general: return "Output format, saving, and after-capture behavior." + case .capture: return "Timer, preparation, and selection behavior." + case .recording: return "Video quality, audio, and recording overlays." + case .overlays: return "Webcam picture-in-picture and click effects." + case .shortcuts: return "Global hotkeys and editor tool keys." + case .advanced: return "Automation URL scheme and custom upload endpoint." + } + } +} diff --git a/Tests/StagTests/SettingsTabTests.swift b/Tests/StagTests/SettingsTabTests.swift new file mode 100644 index 0000000..a6715b1 --- /dev/null +++ b/Tests/StagTests/SettingsTabTests.swift @@ -0,0 +1,32 @@ +import XCTest +@testable import Stag + +/// Tab presentation metadata extracted from PreferencesView. +final class SettingsTabTests: XCTestCase { + + func testAllSixTabsPresentInOrder() { + XCTAssertEqual(SettingsTab.allCases.map(\.rawValue), + ["general", "capture", "recording", "overlays", "shortcuts", "advanced"]) + } + + func testEveryTabHasNonEmptyMetadata() { + for tab in SettingsTab.allCases { + XCTAssertFalse(tab.icon.isEmpty, "\(tab) icon") + XCTAssertFalse(tab.label.isEmpty, "\(tab) label") + XCTAssertFalse(tab.subtitle.isEmpty, "\(tab) subtitle") + } + } + + func testLabelsAndIconsAreUnique() { + let labels = SettingsTab.allCases.map(\.label) + XCTAssertEqual(Set(labels).count, labels.count) + let icons = SettingsTab.allCases.map(\.icon) + XCTAssertEqual(Set(icons).count, icons.count) + } + + func testSpotChecks() { + XCTAssertEqual(SettingsTab.general.label, "General") + XCTAssertEqual(SettingsTab.recording.icon, "video.fill") + XCTAssertEqual(SettingsTab.shortcuts.subtitle, "Global hotkeys and editor tool keys.") + } +}