diff --git a/Sources/PokeTokenBar/UI/CompanionView.swift b/Sources/PokeTokenBar/UI/CompanionView.swift index aeaf2fbbe..3dae3cfe7 100644 --- a/Sources/PokeTokenBar/UI/CompanionView.swift +++ b/Sources/PokeTokenBar/UI/CompanionView.swift @@ -1562,8 +1562,8 @@ struct PokemonDetailView: View { if species.id == UnownForm.speciesID { unownFormPicker } if displayedSpecies.hasNormal && displayedSpecies.isShiny { appearancePicker } identityHeader - if !individuals.isEmpty { individualPicker } - else { + if individuals.count > 1 { individualPicker } + if individuals.isEmpty { Text(store.l.dexAppearancePreview).font(.callout).foregroundStyle(.secondary) } if let details = store.pokemonDetailsByID[species.id] { diff --git a/Tests/PokeTokenBarTests/IndividualPickerRenderingTests.swift b/Tests/PokeTokenBarTests/IndividualPickerRenderingTests.swift new file mode 100644 index 000000000..8100ad68e --- /dev/null +++ b/Tests/PokeTokenBarTests/IndividualPickerRenderingTests.swift @@ -0,0 +1,66 @@ +import AppKit +import SwiftUI +import XCTest +@testable import PokeTokenBar + +private struct PickerRenderProvider: PokeProviding, PokemonDetailProviding { + func line(baseSpeciesID: Int) async throws -> EvoLine { throw URLError(.notConnectedToInternet) } + func baseSpeciesIndex() async throws -> [BaseSpecies] { [] } + func baseSpecies(id: Int) async throws -> BaseSpecies? { nil } + func pokemonDetails(speciesID: Int) async throws -> PokemonDetails { + PokemonDetails(speciesID: speciesID, name: "parasect", height: 10, weight: 295, + baseExperience: 142, genderRate: 4, types: ["bug", "grass"], + baseStats: ["hp": 60, "attack": 95, "defense": 80, "special-attack": 60, "special-defense": 80, "speed": 30], + abilities: [PokemonAbilityOption(name: "effect-spore", slot: 1, isHidden: false)], moves: []) + } +} +@MainActor +final class IndividualPickerRenderingTests: XCTestCase { + func testMountedSingleAndMultipleIndividuals() async throws { + for count in [1, 2] { + let dir = FileManager.default.temporaryDirectory.appendingPathComponent("individual-picker-\(UUID())") + try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: dir) } + let file = dir.appendingPathComponent("state.json") + var state = CompanionState() + state.language = .en + state.dex = (0.. [NSView] { [v] + v.subviews.flatMap(descendants) } + let menus = descendants(host).compactMap { $0 as? NSPopUpButton } + XCTAssertEqual(menus.count, count == 1 ? 0 : 1) + if count == 2 { XCTAssertEqual(menus.first?.numberOfItems, 2) } + if let output = ProcessInfo.processInfo.environment["PTB_PICKER_RENDER_DIR"] { + // Capture only the selector/profile area; no sprite or user save data is included. + let captureBounds = NSRect(x: 0, y: 140, width: PopoverMetrics.contentWidth, height: 370) + let bitmap = try XCTUnwrap(host.bitmapImageRepForCachingDisplay(in: captureBounds)) + host.cacheDisplay(in: captureBounds, to: bitmap) + try XCTUnwrap(bitmap.representation(using: .png, properties: [:])).write(to: + URL(fileURLWithPath: output).appendingPathComponent("individual-\(count).png")) + } + } + } +} diff --git a/docs/pr/individual-picker/after-multiple.png b/docs/pr/individual-picker/after-multiple.png new file mode 100644 index 000000000..2e6848127 Binary files /dev/null and b/docs/pr/individual-picker/after-multiple.png differ diff --git a/docs/pr/individual-picker/after-single.png b/docs/pr/individual-picker/after-single.png new file mode 100644 index 000000000..95791735c Binary files /dev/null and b/docs/pr/individual-picker/after-single.png differ diff --git a/docs/pr/individual-picker/before-single.png b/docs/pr/individual-picker/before-single.png new file mode 100644 index 000000000..d59d39551 Binary files /dev/null and b/docs/pr/individual-picker/before-single.png differ