From 2f3fff0afa75f8615cfaa2dd9a0ac49176ce8bf4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 17 Jun 2026 19:26:35 +0000 Subject: [PATCH] Refactor: extract recent-color MRU logic into RecentColors helper The de-dup/insert/cap-at-8 sequence for the editor's recent-color swatches lived inline in EditorView. Hoist it into a pure RecentColors.recording(_:into:limit:) function (behavior-preserving) and have the view delegate to it. Adds RecentColorsTests (6 cases). --- Sources/Stag/Utils/RecentColors.swift | 21 ++++++++++ Sources/Stag/Views/Editor/EditorView.swift | 9 +---- Tests/StagTests/RecentColorsTests.swift | 47 ++++++++++++++++++++++ 3 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 Sources/Stag/Utils/RecentColors.swift create mode 100644 Tests/StagTests/RecentColorsTests.swift diff --git a/Sources/Stag/Utils/RecentColors.swift b/Sources/Stag/Utils/RecentColors.swift new file mode 100644 index 0000000..5e34b8d --- /dev/null +++ b/Sources/Stag/Utils/RecentColors.swift @@ -0,0 +1,21 @@ +import SwiftUI + +/// Most-recently-used color list logic for the editor's swatch row. Previously +/// this de-dup/insert/cap sequence lived inline in `EditorView`; extracting it +/// makes the rule a pure, unit-testable function and trims the view's surface. +enum RecentColors { + /// Default number of swatches retained. + static let limit = 8 + + /// Returns `history` with `color` moved to the front (de-duplicated) and the + /// list capped at `limit` — most-recently-used first. + static func recording(_ color: Color, into history: [Color], limit: Int = limit) -> [Color] { + var out = history + out.removeAll { $0 == color } + out.insert(color, at: 0) + if out.count > limit { + out.removeLast(out.count - limit) + } + return out + } +} diff --git a/Sources/Stag/Views/Editor/EditorView.swift b/Sources/Stag/Views/Editor/EditorView.swift index 246317d..52f1fbb 100644 --- a/Sources/Stag/Views/Editor/EditorView.swift +++ b/Sources/Stag/Views/Editor/EditorView.swift @@ -1743,14 +1743,7 @@ struct EditorView: View { // MARK: - Color History private func recordColorUsage(_ color: Color) { - // Remove if already in history - colorHistory.removeAll { $0 == color } - // Add to beginning - colorHistory.insert(color, at: 0) - // Keep only last 8 - if colorHistory.count > 8 { - colorHistory.removeLast() - } + colorHistory = RecentColors.recording(color, into: colorHistory) } // MARK: - Arrow Head Drawing diff --git a/Tests/StagTests/RecentColorsTests.swift b/Tests/StagTests/RecentColorsTests.swift new file mode 100644 index 0000000..8a5fcc7 --- /dev/null +++ b/Tests/StagTests/RecentColorsTests.swift @@ -0,0 +1,47 @@ +import XCTest +import SwiftUI +@testable import Stag + +/// MRU/dedup/cap logic for the editor's recent-color swatches. +final class RecentColorsTests: XCTestCase { + + private let a = Color(red: 1, green: 0, blue: 0) + private let b = Color(red: 0, green: 1, blue: 0) + private let c = Color(red: 0, green: 0, blue: 1) + + func testRecordingIntoEmpty() { + XCTAssertEqual(RecentColors.recording(a, into: []), [a]) + } + + func testNewColorGoesToFront() { + XCTAssertEqual(RecentColors.recording(c, into: [a, b]), [c, a, b]) + } + + func testExistingColorMovesToFrontWithoutDuplicating() { + XCTAssertEqual(RecentColors.recording(b, into: [a, b, c]), [b, a, c]) + } + + func testCapsAtLimitDroppingOldest() { + // Fill beyond the limit with distinct grays, newest recorded last. + var history: [Color] = [] + for i in 0..<12 { + history = RecentColors.recording(Color(white: Double(i) / 12.0), into: history) + } + XCTAssertEqual(history.count, RecentColors.limit) + // The most recent (i = 11) is first; the oldest survivors dropped off the end. + XCTAssertEqual(history.first, Color(white: 11.0 / 12.0)) + } + + func testRecordingExistingAtCapacityKeepsCount() { + var history: [Color] = (0..