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
23 changes: 20 additions & 3 deletions Sources/MapConductorCore/MapViewContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,22 @@ public struct MapViewContent {
public var groundImages: [GroundImage] = []
public var rasterLayers: [RasterLayer] = []
public var views: [AnyView] = []
public var markerTilingOptions: MarkerTilingOptions = .Default
/// Marker tiling for this content. Reads ``MarkerTilingOptions/Default`` until something sets it.
///
/// Whether it was set is tracked separately from its value, so that merging the pieces a
/// ``MapViewContentBuilder`` closure produces keeps an explicit choice — including
/// ``MarkerTilingOptions/Disabled`` — while pieces that never touched it leave it alone.
public var markerTilingOptions: MarkerTilingOptions {
get { explicitMarkerTilingOptions ?? .Default }
set { explicitMarkerTilingOptions = newValue }
}
/// `nil` until ``markerTilingOptions`` is assigned.
///
/// `merge` used to carry the option over only when it was enabled, because a fresh
/// content's `.Default` and a deliberate `.Disabled` could not be told apart otherwise.
/// A single-expression builder closure always goes through `buildBlock`, which merges
/// into a fresh content, so `.Disabled` was silently replaced by the default (tiling on).
var explicitMarkerTilingOptions: MarkerTilingOptions?
/// Handlers that manage a subset of polygons imperatively (e.g. cluster hull polygons).
/// Map view coordinators call ``PolygonSyncHandler/bindPolygonSync(_:)`` on each handler
/// to provide a direct polygon sync function, ensuring those polygons are committed before
Expand All @@ -47,8 +62,10 @@ public struct MapViewContent {
rasterLayers.append(contentsOf: other.rasterLayers)
views.append(contentsOf: other.views)
polygonSyncHandlers.append(contentsOf: other.polygonSyncHandlers)
if other.markerTilingOptions.enabled {
markerTilingOptions = other.markerTilingOptions
// An explicit setting wins over an unset one, whether it enables tiling or not;
// between two explicit settings the later one wins.
if let tiling = other.explicitMarkerTilingOptions {
explicitMarkerTilingOptions = tiling
}
}
}
Expand Down
107 changes: 107 additions & 0 deletions Tests/MapConductorCoreTests/MapViewContentMergeTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import XCTest

@testable import MapConductorCore

/// `MapViewContentBuilder` が組み立てた content で、明示した設定が残ることを確かめる。
///
/// 以前の `merge` は有効なタイル設定しか引き継がず、ビルダーの中で
/// `markerTilingOptions = .Disabled` を設定しても既定(タイル有効)に戻っていた。
/// 1 式だけのビルダーも `buildBlock` で新しい content にマージされるので必ず踏む。
final class MapViewContentMergeTests: XCTestCase {
private func build(@MapViewContentBuilder _ body: () -> MapViewContent) -> MapViewContent {
body()
}

private func content(tiling: MarkerTilingOptions?) -> MapViewContent {
var content = MapViewContent()
if let tiling { content.markerTilingOptions = tiling }
return content
}

func testUnsetTilingReadsDefault() {
let content = MapViewContent()
XCTAssertTrue(content.markerTilingOptions.enabled)
XCTAssertEqual(content.markerTilingOptions.minMarkerCount, MarkerTilingOptions.Default.minMarkerCount)
XCTAssertNil(content.explicitMarkerTilingOptions)
}

/// 報告された形そのもの: ビルダーの中で即時実行クロージャが `.Disabled` を設定する。
func testDisabledSetInsideBuilderSurvives() {
let built = build {
{ () -> MapViewContent in
var content = MapViewContent()
content.markerTilingOptions = .Disabled
return content
}()
}
XCTAssertFalse(built.markerTilingOptions.enabled)
}

func testDisabledSurvivesNextToItemsThatDoNotSetTiling() {
let built = build {
content(tiling: .Disabled)
content(tiling: nil)
Marker(state: MarkerState(position: GeoPoint(latitude: 35, longitude: 139)))
}
XCTAssertFalse(built.markerTilingOptions.enabled)
XCTAssertEqual(built.markers.count, 1)
}

func testEnabledCustomOptionsSurvive() {
let built = build {
content(tiling: MarkerTilingOptions(minMarkerCount: 10, declutterPx: 4))
content(tiling: nil)
}
XCTAssertTrue(built.markerTilingOptions.enabled)
XCTAssertEqual(built.markerTilingOptions.minMarkerCount, 10)
XCTAssertEqual(built.markerTilingOptions.declutterPx, 4)
}

func testUnsetEverywhereFallsBackToDefault() {
let built = build {
content(tiling: nil)
Marker(state: MarkerState(position: GeoPoint(latitude: 35, longitude: 139)))
}
XCTAssertTrue(built.markerTilingOptions.enabled)
XCTAssertNil(built.explicitMarkerTilingOptions)
}

func testLaterExplicitSettingWins() {
let disabledLast = build {
content(tiling: MarkerTilingOptions(minMarkerCount: 10))
content(tiling: .Disabled)
}
XCTAssertFalse(disabledLast.markerTilingOptions.enabled)

let enabledLast = build {
content(tiling: .Disabled)
content(tiling: MarkerTilingOptions(minMarkerCount: 10))
}
XCTAssertTrue(enabledLast.markerTilingOptions.enabled)
XCTAssertEqual(enabledLast.markerTilingOptions.minMarkerCount, 10)
}

func testDisabledSurvivesConditionalsAndLoops() {
let flag = true
let built = build {
if flag {
content(tiling: .Disabled)
}
for _ in 0..<2 {
content(tiling: nil)
}
}
XCTAssertFalse(built.markerTilingOptions.enabled)

let nested = build {
for i in 0..<3 {
if i == 1 {
content(tiling: .Disabled)
} else {
content(tiling: nil)
}
}
}
XCTAssertFalse(nested.markerTilingOptions.enabled)
}
}
5 changes: 4 additions & 1 deletion api/MapConductorCore.api.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,10 @@ public struct MapViewContent {
public var groundImages: [MapConductorCore::GroundImage]
public var rasterLayers: [MapConductorCore::RasterLayer]
public var views: [SwiftUICore::AnyView]
public var markerTilingOptions: MapConductorCore::MarkerTilingOptions
public var markerTilingOptions: MapConductorCore::MarkerTilingOptions {
get
set
}
public var polygonSyncHandlers: [any MapConductorCore::PolygonSyncHandler]
public init()
}
Expand Down
Loading