diff --git a/Package.resolved b/Package.resolved index af8c37e7b..c2b034761 100644 --- a/Package.resolved +++ b/Package.resolved @@ -7,7 +7,7 @@ "location" : "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", "state" : { "branch" : "main", - "revision" : "f97c5e4c96d3bf66b5161a0635638360b386c6e8" + "revision" : "ad86657d9bdd98f7c40c977b34ff0c3de3e2fcee" } }, { @@ -43,7 +43,7 @@ "location" : "https://github.com/OpenSwiftUIProject/OpenRenderBox", "state" : { "branch" : "main", - "revision" : "ba06b06d03c0e25922ebc2fee183f62a39fdd8d4" + "revision" : "5ae2d6708298f64ca4fcbe5977fb7627ad243f65" } }, { diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift index ca06e9c7f..f8038976e 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift @@ -6,9 +6,10 @@ // Status: WIP // ID: F37E3733E490AA5E3BDC045E3D34D9F8 (SwiftUICore) -package import OpenCoreGraphicsShims package import Foundation package import OpenAttributeGraphShims +package import OpenCoreGraphicsShims +package import OpenRenderBoxShims // MARK: - _DisplayList_Identity @@ -561,4 +562,3 @@ extension DisplayList.Item { package class ORBDisplayListContents {} package class ORBDisplayListInterpolator {} package struct ORBTransition {} -package class ORBAnimation {} diff --git a/Sources/OpenSwiftUICore/Render/SymbolEffect.swift b/Sources/OpenSwiftUICore/Render/SymbolEffect.swift index 5518017af..3f70711c4 100644 --- a/Sources/OpenSwiftUICore/Render/SymbolEffect.swift +++ b/Sources/OpenSwiftUICore/Render/SymbolEffect.swift @@ -5,6 +5,7 @@ // Status: Empty import OpenAttributeGraphShims +package import OpenRenderBoxShims package struct _SymbolEffect: Equatable { diff --git a/Sources/OpenSwiftUICore/Util/CoreUIShims.swift b/Sources/OpenSwiftUICore/Util/CoreUIShims.swift new file mode 100644 index 000000000..be85c7f67 --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/CoreUIShims.swift @@ -0,0 +1,9 @@ +// +// CoreUIShims.swift +// OpenSwiftUICore + +#if !OPENSWIFTUI_LINK_COREUI +package import Foundation +package class CUICatalog: NSObject {} +package class CUINamedVectorGlyph: NSObject {} +#endif diff --git a/Sources/OpenSwiftUICore/Util/RenderBoxShims.swift b/Sources/OpenSwiftUICore/Util/RenderBoxShims.swift index 4ac01ec6e..476b13e77 100644 --- a/Sources/OpenSwiftUICore/Util/RenderBoxShims.swift +++ b/Sources/OpenSwiftUICore/Util/RenderBoxShims.swift @@ -1,6 +1,6 @@ // // RenderBoxShims.swift -// OpenSwiftUI +// OpenSwiftUICore package protocol RBDisplayListContents {} // RenderBox.RBDisplayListContents diff --git a/Sources/OpenSwiftUICore/View/Image/GraphicsImage.swift b/Sources/OpenSwiftUICore/View/Image/GraphicsImage.swift index 3febbcc7d..09a5c330a 100644 --- a/Sources/OpenSwiftUICore/View/Image/GraphicsImage.swift +++ b/Sources/OpenSwiftUICore/View/Image/GraphicsImage.swift @@ -3,10 +3,11 @@ // OpenSwiftUICore // // Audited for 6.5.4 -// Status: Complete with WIP implementation +// Status: Complete // ID: B4F00EDEBAA4ECDCB2CAB650A00E4160 (SwiftUICore) package import OpenCoreGraphicsShims +package import OpenRenderBoxShims #if canImport(CoreGraphics) import CoreGraphics_Private #endif @@ -160,7 +161,7 @@ extension GraphicsImage.Contents { } } -// TODO: ResolvedVectorGlyph +// MARK: - ResolvedVectorGlyph package struct ResolvedVectorGlyph: Equatable { package let animator: ORBSymbolAnimator @@ -169,9 +170,7 @@ package struct ResolvedVectorGlyph: Equatable { package var animatorVersion: UInt32 package var allowsContentTransitions: Bool package var preservesVectorRepresentation: Bool - #if OPENSWIFTUI_LINK_COREUI package let catalog: CUICatalog - #endif package init( glyph: CUINamedVectorGlyph, @@ -181,48 +180,117 @@ package struct ResolvedVectorGlyph: Equatable { at location: Image.Location, catalog: CUICatalog ) { - _openSwiftUIUnimplementedFailure() + let variableValue: CGFloat + let animator: ORBSymbolAnimator + let allowsContentTransitions: Bool + if let existingAnimator = context.symbolAnimator { + variableValue = value.map { CGFloat($0) } ?? .infinity + animator = existingAnimator + allowsContentTransitions = context.willUpdateVectorGlyph( + to: glyph, + variableValue: variableValue + ) + } else { + animator = ORBSymbolAnimator() + animator.anchorPoint = .zero + allowsContentTransitions = false + variableValue = value.map { CGFloat($0) } ?? .infinity + } + animator.glyph = glyph + animator.variableValue = variableValue + animator.flipsRightToLeft = flipsRightToLeft + animator.renderingMode = context.effectiveSymbolRenderingMode?.rbRenderingMode ?? 255 + let direction = context.environment.layoutDirection + let version = animator.version + let options = context.options + self.animator = animator + self.layoutDirection = direction + self.location = location + self.animatorVersion = version + self.allowsContentTransitions = allowsContentTransitions + self.preservesVectorRepresentation = options.contains(.preservesVectors) + self.catalog = catalog } + // MARK: - Computed properties + package var flipsRightToLeft: Bool { animator.flipsRightToLeft } - package func isClear(styles: ShapeStyle.Pack) -> Bool { - _openSwiftUIUnimplementedWarning() - return false + #if OPENSWIFTUI_LINK_COREUI + package var glyph: CUINamedVectorGlyph? { + animator.glyph } -} + #endif -extension GraphicsImage { - package var bitmapOrientation: Image.Orientation { - guard case let .vectorGlyph(vectorGlyph) = contents else { - return orientation - } - return vectorGlyph.flipsRightToLeft ? orientation.mirrored : orientation + package var value: Float? { + let v = animator.variableValue + guard v.isFinite else { return nil } + return Float(v) } - package func render(at targetSize: CGSize, prefersMask: Bool = false) -> CGImage? { + package var renderingMode: SymbolRenderingMode.Storage? { + SymbolRenderingMode(rbRenderingMode: animator.renderingMode)?.storage + } + + package var resolvedRenderingMode: SymbolRenderingMode.Storage? { + #if OPENSWIFTUI_LINK_COREUI && OPENSWIFTUI_RENDERBOX + let rbMode = animator.renderingMode + guard rbMode == 0 else { + return SymbolRenderingMode(rbRenderingMode: rbMode)?.storage + } + guard let glyph = animator.glyph else { + return .preferred + } + switch glyph.preferredRenderingMode { + case 2: return .multicolor + case 3: return .hierarchical + default: return nil + } + #else _openSwiftUIUnimplementedFailure() + #endif } -} -// FIXME + package var alignmentRect: CGRect { + animator.alignmentRect + } -package class ORBSymbolAnimator: Hashable { - var styleMask: UInt32 { - .zero + package var styleResolverMode: ShapeStyle.ResolverMode { + .init(rbSymbolStyleMask: animator.styleMask, location: location) } - var flipsRightToLeft: Bool { - false + // MARK: - Methods + + package func isClear(styles: ShapeStyle.Pack) -> Bool { + guard animator.styleMask & 0x1200 == 0 else { + return false + } + return styles.isClear(name: .foreground) } - package func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) + // MARK: - ResolvedVectorGlyph + Equatable + + package static func == (lhs: ResolvedVectorGlyph, rhs: ResolvedVectorGlyph) -> Bool { + lhs.animator === rhs.animator + && lhs.animatorVersion == rhs.animatorVersion + && lhs.layoutDirection == rhs.layoutDirection + && lhs.location == rhs.location } +} + +// MARK: - GraphicsImage + Extension [WIP] - package static func == (lhs: ORBSymbolAnimator, rhs: ORBSymbolAnimator) -> Bool { - lhs === rhs +extension GraphicsImage { + package var bitmapOrientation: Image.Orientation { + guard case let .vectorGlyph(vectorGlyph) = contents else { + return orientation + } + return vectorGlyph.flipsRightToLeft ? orientation.mirrored : orientation + } + + package func render(at targetSize: CGSize, prefersMask: Bool = false) -> CGImage? { + _openSwiftUIUnimplementedFailure() } } diff --git a/Sources/OpenSwiftUICore/View/Image/Image.swift b/Sources/OpenSwiftUICore/View/Image/Image.swift index a788af595..6f2ded46f 100644 --- a/Sources/OpenSwiftUICore/View/Image/Image.swift +++ b/Sources/OpenSwiftUICore/View/Image/Image.swift @@ -8,6 +8,10 @@ package import OpenAttributeGraphShims package import OpenCoreGraphicsShims +package import OpenRenderBoxShims +#if OPENSWIFTUI_LINK_COREUI +package import CoreUI +#endif // MARK: - Image @@ -83,7 +87,7 @@ public struct Image: Equatable, Sendable { } } -// MARK: - ImageResolutionContext +// MARK: - ImageResolutionContext [WIP] [willUpdateVectorGlyph] package struct ImageResolutionContext { package struct Options: OptionSet { @@ -128,7 +132,32 @@ package struct ImageResolutionContext { self.transaction = transaction } - // TODO: willUpdateVectorGlyph + // TODO: Full implementation of willUpdateVectorGlyph + // This is a complex method that handles animator state transitions + // when an existing symbolAnimator is being reused for a new glyph. + package func willUpdateVectorGlyph( + to glyph: CUINamedVectorGlyph, + variableValue: CGFloat + ) -> Bool { + _openSwiftUIUnimplementedWarning() + return false + } + + package var effectiveSymbolRenderingMode: SymbolRenderingMode? { + if let mode = symbolRenderingMode { + return mode + } + if let envMode = environment.symbolRenderingMode { + return envMode + } + if ForegroundStyle().isMultiLevel(in: environment) { + return .palette + } + guard options.contains(.inferSymbolRenderingMode) else { + return nil + } + return SymbolRenderingMode.preferredIfEnabled ?? .monochrome + } package func effectiveAllowedDynamicRange(for image: GraphicsImage) -> Image.DynamicRange? { #if canImport(CoreGraphics) diff --git a/Sources/OpenSwiftUICore/View/Image/NamedImage.swift b/Sources/OpenSwiftUICore/View/Image/NamedImage.swift index 68eb97b4a..7c5d87392 100644 --- a/Sources/OpenSwiftUICore/View/Image/NamedImage.swift +++ b/Sources/OpenSwiftUICore/View/Image/NamedImage.swift @@ -8,6 +8,7 @@ public import Foundation package import OpenCoreGraphicsShims +package import OpenRenderBoxShims #if canImport(CoreGraphics) import CoreGraphics_Private #endif @@ -594,8 +595,13 @@ package enum NamedImage { } let resolved = try delegate.resolveImage(uuid: uuid) let cgImage = resolved.cgImage + #if canImport(CoreGraphics) let width = CGFloat(cgImage.width) let height = CGFloat(cgImage.height) + #else + let width = CGFloat.zero + let height = CGFloat.zero + #endif let decoded = DecodedInfo( contents: .cgImage(cgImage), scale: resolved.scale, @@ -721,14 +727,13 @@ extension Image { package static let privateSystemAssetManager = SystemAssetManager(internalUse: true) package struct SystemAssetManager { - #if OPENSWIFTUI_LINK_COREUI let catalog: CUICatalog - #endif let fillMapping: [String: String] let nameAliases: [String: String] let symbols: [String] package init(internalUse: Bool) { + #if canImport(Darwin) let bundlePath: String if internalUse { fillMapping = SFSymbols.private_nofill_to_fill @@ -745,6 +750,11 @@ extension Image { let fullPath = _SimulatorSystemRootDirectory() + bundlePath let bundle = Bundle(path: fullPath)! catalog = try! CUICatalog(name: "Assets", from: bundle, error: ()) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + #else + _openSwiftUIPlatformUnimplementedFailure() #endif } } diff --git a/Sources/OpenSwiftUICore/View/Image/SymbolRenderingMode.swift b/Sources/OpenSwiftUICore/View/Image/SymbolRenderingMode.swift index 9acd6361f..6b97e0029 100644 --- a/Sources/OpenSwiftUICore/View/Image/SymbolRenderingMode.swift +++ b/Sources/OpenSwiftUICore/View/Image/SymbolRenderingMode.swift @@ -98,6 +98,37 @@ public struct SymbolRenderingMode: Sendable { }() } +// MARK: - SymbolRenderingMode + RB Rendering Mode + +extension SymbolRenderingMode { + package var rbRenderingMode: UInt32 { + switch storage { + case .monochrome: 1 + case .multicolor: 2 + case .hierarchical: 3 + case .palette: 4 + case .preferred: 0 + case .hierarchicalUnlessSlashed: 128 + case .hierarchicalSlashBadge: 129 + case .paletteSlashBadge: 130 + } + } + + package init?(rbRenderingMode mode: UInt32) { + switch mode { + case 0: self = .preferred + case 1: self = .monochrome + case 2: self = .multicolor + case 3: self = .hierarchical + case 4: self = .palette + case 128: self = .hierarchicalUnlessSlashed + case 129: self = .hierarchicalSlashBadge + case 130: self = .paletteSlashBadge + default: return nil + } + } +} + @_spi(Private) @available(OpenSwiftUI_v6_0, *) extension SymbolRenderingMode { diff --git a/Sources/OpenSwiftUICore/View/Text/Font/ModifiedFont.swift b/Sources/OpenSwiftUICore/View/Text/Font/ModifiedFont.swift index 2ebc2059e..11047c4fb 100644 --- a/Sources/OpenSwiftUICore/View/Text/Font/ModifiedFont.swift +++ b/Sources/OpenSwiftUICore/View/Text/Font/ModifiedFont.swift @@ -467,7 +467,7 @@ extension Font { } package func modify(traits: inout Font.ResolvedTraits) { - _openSwiftUIUnimplementedFailure() + traits.weight = weight.value } } @@ -479,7 +479,7 @@ extension Font { } package func modify(traits: inout Font.ResolvedTraits) { - _openSwiftUIUnimplementedFailure() + traits.width = width } } diff --git a/Tests/OpenSwiftUICoreTests/View/Image/NamedImageTests.swift b/Tests/OpenSwiftUICoreTests/View/Image/NamedImageTests.swift index 27b997e96..0a11568ac 100644 --- a/Tests/OpenSwiftUICoreTests/View/Image/NamedImageTests.swift +++ b/Tests/OpenSwiftUICoreTests/View/Image/NamedImageTests.swift @@ -15,23 +15,6 @@ import Testing // MARK: - NamedImage.Errors Tests struct NamedImageErrorsTests { - @Test - func equality() { - let a = NamedImage.Errors.missingCatalogImage - let b = NamedImage.Errors.missingUUIDImage - #expect(a == a) - #expect(b == b) - #expect(a != b) - } - - @Test - func hashing() { - let a = NamedImage.Errors.missingCatalogImage - let b = NamedImage.Errors.missingUUIDImage - #expect(a.hashValue != b.hashValue) - #expect(a.hashValue == NamedImage.Errors.missingCatalogImage.hashValue) - } - @Test func conformsToError() { let error: any Error = NamedImage.Errors.missingCatalogImage @@ -43,179 +26,39 @@ struct NamedImageErrorsTests { struct NamedImageKeyTests { @Test - func bitmapKeyEquality() { - let key1 = NamedImage.BitmapKey( - catalogKey: CatalogKey(colorScheme: .light, contrast: .standard), - name: "test", - scale: 2.0, - location: .system, - layoutDirection: .leftToRight, - locale: .autoupdatingCurrent, - gamut: .sRGB, - idiom: 0, - subtype: 0 - ) - let key2 = NamedImage.BitmapKey( - catalogKey: CatalogKey(colorScheme: .light, contrast: .standard), - name: "test", - scale: 2.0, - location: .system, - layoutDirection: .leftToRight, - locale: .autoupdatingCurrent, - gamut: .sRGB, - idiom: 0, - subtype: 0 - ) - #expect(key1 == key2) - } - - @Test - func bitmapKeyInequality() { - let key1 = NamedImage.BitmapKey( - catalogKey: CatalogKey(colorScheme: .light, contrast: .standard), - name: "image_a", - scale: 2.0, - location: .system, - layoutDirection: .leftToRight, - locale: .autoupdatingCurrent, - gamut: .sRGB, - idiom: 0, - subtype: 0 - ) - let key2 = NamedImage.BitmapKey( - catalogKey: CatalogKey(colorScheme: .light, contrast: .standard), - name: "image_b", - scale: 2.0, - location: .system, - layoutDirection: .leftToRight, - locale: .autoupdatingCurrent, - gamut: .sRGB, - idiom: 0, - subtype: 0 - ) - #expect(key1 != key2) - } - - @Test - func bitmapKeyHashing() { - let key1 = NamedImage.BitmapKey( - catalogKey: CatalogKey(colorScheme: .light, contrast: .standard), - name: "test", - scale: 2.0, - location: .system, - layoutDirection: .leftToRight, - locale: .autoupdatingCurrent, - gamut: .sRGB, - idiom: 0, - subtype: 0 - ) - let key2 = NamedImage.BitmapKey( - catalogKey: CatalogKey(colorScheme: .light, contrast: .standard), - name: "test", - scale: 2.0, - location: .system, - layoutDirection: .leftToRight, - locale: .autoupdatingCurrent, - gamut: .sRGB, - idiom: 0, - subtype: 0 - ) - #expect(key1.hashValue == key2.hashValue) - } - - @Test - func bitmapKeyDifferentNamesProduceDifferentHashes() { - let key1 = NamedImage.BitmapKey( - catalogKey: CatalogKey(colorScheme: .light, contrast: .standard), - name: "alpha", - scale: 1.0, - location: .system, - layoutDirection: .leftToRight, - locale: .autoupdatingCurrent, - gamut: .sRGB, - idiom: 0, - subtype: 0 - ) - let key2 = NamedImage.BitmapKey( - catalogKey: CatalogKey(colorScheme: .light, contrast: .standard), - name: "beta", - scale: 1.0, - location: .system, - layoutDirection: .leftToRight, - locale: .autoupdatingCurrent, - gamut: .sRGB, - idiom: 0, - subtype: 0 - ) - #expect(key1.hashValue != key2.hashValue) - } - - @Test - func bitmapKeySizeClassDefaults() { + func bitmapKeyInitFromEnvironment() { + var env = EnvironmentValues() + env.displayScale = 3.0 + env.layoutDirection = .rightToLeft + env.displayGamut = .displayP3 let key = NamedImage.BitmapKey( - catalogKey: CatalogKey(colorScheme: .light, contrast: .standard), - name: "test", - scale: 1.0, - location: .system, - layoutDirection: .leftToRight, - locale: .autoupdatingCurrent, - gamut: .sRGB, - idiom: 0, - subtype: 0 - ) - #expect(key.horizontalSizeClass == 0) - #expect(key.verticalSizeClass == 0) - } - - @Test - func keyEquality() { - let bitmapKey = NamedImage.BitmapKey( - catalogKey: CatalogKey(colorScheme: .light, contrast: .standard), - name: "test", - scale: 1.0, - location: .system, - layoutDirection: .leftToRight, - locale: .autoupdatingCurrent, - gamut: .sRGB, - idiom: 0, - subtype: 0 + name: "icon", + location: .bundle(.main), + in: env ) - let key1 = NamedImage.Key.bitmap(bitmapKey) - let key2 = NamedImage.Key.bitmap(bitmapKey) - #expect(key1 == key2) + #expect(key.name == "icon") + #expect(key.scale == 3.0) + #expect(key.layoutDirection == .rightToLeft) + #expect(key.gamut == .displayP3) } @Test - func keyInequalityDifferentCases() { - let bitmapKey = NamedImage.BitmapKey( - catalogKey: CatalogKey(colorScheme: .light, contrast: .standard), - name: "test", - scale: 1.0, - location: .system, - layoutDirection: .leftToRight, - locale: .autoupdatingCurrent, - gamut: .sRGB, - idiom: 0, - subtype: 0 - ) - let key1 = NamedImage.Key.bitmap(bitmapKey) - let key2 = NamedImage.Key.uuid(UUID()) - #expect(key1 != key2) - } + func bitmapKeySizeClassConversion() { + var env = EnvironmentValues() - @Test - func keyUUIDEquality() { - let uuid = UUID() - let key1 = NamedImage.Key.uuid(uuid) - let key2 = NamedImage.Key.uuid(uuid) - #expect(key1 == key2) - } + // .compact -> 1 + env.horizontalSizeClass = .compact + env.verticalSizeClass = .compact + let key1 = NamedImage.BitmapKey(name: "a", location: .system, in: env) + #expect(key1.horizontalSizeClass == 1) + #expect(key1.verticalSizeClass == 1) - @Test - func keyUUIDInequality() { - let key1 = NamedImage.Key.uuid(UUID()) - let key2 = NamedImage.Key.uuid(UUID()) - #expect(key1 != key2) + // .regular -> 2 + env.horizontalSizeClass = .regular + env.verticalSizeClass = .regular + let key2 = NamedImage.BitmapKey(name: "a", location: .system, in: env) + #expect(key2.horizontalSizeClass == 2) + #expect(key2.verticalSizeClass == 2) } } @@ -223,58 +66,75 @@ struct NamedImageKeyTests { struct ImageLocationTests { @Test - func systemEquality() { - #expect(Image.Location.system == Image.Location.system) + func supportsNonVectorImages() { + #expect(Image.Location.bundle(.main).supportsNonVectorImages == true) + #expect(Image.Location.system.supportsNonVectorImages == false) + #expect(Image.Location.privateSystem.supportsNonVectorImages == false) } @Test - func privateSystemEquality() { - #expect(Image.Location.privateSystem == Image.Location.privateSystem) + func bundleAccessor() { + let bundle = Bundle.main + #expect(Image.Location.bundle(bundle).bundle === bundle) + #expect(Image.Location.system.bundle == nil) + #expect(Image.Location.privateSystem.bundle == nil) } @Test - func bundleEquality() { - let bundle = Bundle.main - #expect(Image.Location.bundle(bundle) == Image.Location.bundle(bundle)) + func fillVariantBundleAppendsFill() { + let location = Image.Location.bundle(.main) + let result = location.fillVariant(.fill, name: "star") + #expect(result == "star.fill") } @Test - func differentCasesNotEqual() { - #expect(Image.Location.system != Image.Location.privateSystem) - #expect(Image.Location.system != Image.Location.bundle(.main)) - #expect(Image.Location.privateSystem != Image.Location.bundle(.main)) + func fillVariantReturnsNilWithoutFill() { + let location = Image.Location.bundle(.main) + let result = location.fillVariant(.none, name: "star") + #expect(result == nil) } @Test - func supportsNonVectorImages() { - #expect(Image.Location.bundle(.main).supportsNonVectorImages == true) - #expect(Image.Location.system.supportsNonVectorImages == false) - #expect(Image.Location.privateSystem.supportsNonVectorImages == false) + func mayContainSymbolBundleAlwaysTrue() { + let location = Image.Location.bundle(.main) + #expect(location.mayContainSymbol("anything") == true) + #expect(location.mayContainSymbol("") == true) } @Test - func bundleAccessor() { - let bundle = Bundle.main - #expect(Image.Location.bundle(bundle).bundle === bundle) - #expect(Image.Location.system.bundle == nil) - #expect(Image.Location.privateSystem.bundle == nil) + func findNamePassesCorrectCandidates() { + let location = Image.Location.bundle(.main) + var candidates: [String] = [] + + // With no variants, should just pass the base name + let _: String? = location.findName(.none, base: "star") { name in + candidates.append(name) + return nil + } + #expect(candidates == ["star"]) } @Test - func hashConsistency() { - let loc1 = Image.Location.system - let loc2 = Image.Location.system - #expect(loc1.hashValue == loc2.hashValue) + func findNameWithFillVariant() { + let location = Image.Location.bundle(.main) + var candidates: [String] = [] + + // With .fill variant, should try "star.fill" before "star" + let _: String? = location.findName(.fill, base: "star") { name in + candidates.append(name) + return nil + } + #expect(candidates == ["star.fill", "star"]) } @Test - func hashDifferentCases() { - let systemHash = Image.Location.system.hashValue - let privateHash = Image.Location.privateSystem.hashValue - let bundleHash = Image.Location.bundle(.main).hashValue - // All three should be different (technically not guaranteed, but highly likely) - #expect(systemHash != privateHash) - #expect(systemHash != bundleHash) + func findNameReturnsFirstMatch() { + let location = Image.Location.bundle(.main) + + let result: String? = location.findName(.fill, base: "star") { name in + name.hasSuffix(".fill") ? name : nil + } + #expect(result == "star.fill") } } @@ -331,74 +191,6 @@ struct NamedImageCacheTests { // MARK: - NamedImageProvider Tests struct NamedImageProviderTests { - @Test - func equality() { - let provider1 = Image.NamedImageProvider( - name: "star", - location: .system, - label: nil, - decorative: false - ) - let provider2 = Image.NamedImageProvider( - name: "star", - location: .system, - label: nil, - decorative: false - ) - #expect(provider1 == provider2) - } - - @Test - func inequalityDifferentName() { - let provider1 = Image.NamedImageProvider( - name: "star", - location: .system, - label: nil, - decorative: false - ) - let provider2 = Image.NamedImageProvider( - name: "heart", - location: .system, - label: nil, - decorative: false - ) - #expect(provider1 != provider2) - } - - @Test - func inequalityDifferentLocation() { - let provider1 = Image.NamedImageProvider( - name: "star", - location: .system, - label: nil, - decorative: false - ) - let provider2 = Image.NamedImageProvider( - name: "star", - location: .bundle(.main), - label: nil, - decorative: false - ) - #expect(provider1 != provider2) - } - - @Test - func inequalityDifferentDecorative() { - let provider1 = Image.NamedImageProvider( - name: "star", - location: .system, - label: nil, - decorative: false - ) - let provider2 = Image.NamedImageProvider( - name: "star", - location: .system, - label: nil, - decorative: true - ) - #expect(provider1 != provider2) - } - @Test func valueProperty() { let provider = Image.NamedImageProvider( @@ -421,7 +213,7 @@ struct NamedImageProviderTests { ) let environment = EnvironmentValues() let resolved = provider.resolveError(in: environment) - #expect(resolved.image.scale == 0) + #expect(resolved.image.scale == 1.0) #expect(resolved.image.contents == nil) #expect(resolved.decorative == true) } @@ -439,46 +231,4 @@ struct NamedImageProviderTests { #expect(result == nil) } - @Test - func equalityFieldOrder() { - let provider1 = Image.NamedImageProvider( - name: "star", - value: 0.5, - location: .system, - label: nil, - decorative: false, - backupLocation: nil - ) - let provider2 = Image.NamedImageProvider( - name: "star", - value: 0.5, - location: .system, - label: nil, - decorative: false, - backupLocation: nil - ) - #expect(provider1 == provider2) - - // Different backupLocation should cause inequality - let provider3 = Image.NamedImageProvider( - name: "star", - value: 0.5, - location: .system, - label: nil, - decorative: false, - backupLocation: .privateSystem - ) - #expect(provider1 != provider3) - - // Different value should cause inequality - let provider4 = Image.NamedImageProvider( - name: "star", - value: 0.75, - location: .system, - label: nil, - decorative: false, - backupLocation: nil - ) - #expect(provider1 != provider4) - } }