Defaults/Tests/DefaultsMacrosDeclarationsT.../ObservableDefaultMacroTests...

111 lines
3.0 KiB
Swift

import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import XCTest
// Macro implementations build for the host, so the corresponding module is not available when cross-compiling.
// Cross-compiled tests may still make use of the macro itself in end-to-end tests.
#if canImport(DefaultsMacrosDeclarations)
@testable import DefaultsMacros
@testable import DefaultsMacrosDeclarations
let testMacros: [String: Macro.Type] = [
"ObservableDefault": ObservableDefaultMacro.self
]
#else
let testMacros: [String: Macro.Type] = [:]
#endif
final class ObservableDefaultMacroTests: XCTestCase {
func testExpansionWithMemberSyntax() throws {
#if canImport(DefaultsMacrosDeclarations)
assertMacroExpansion(
declaration(for: "Defaults.Keys.name"),
expandedSource: expectedExpansion(for: "Defaults.Keys.name"),
macros: testMacros,
indentationWidth: .tabs(1)
)
#else
throw XCTSkip("Macros are only supported when running tests for the host platform")
#endif
}
func testExpansionWithDotSyntax() throws {
#if canImport(DefaultsMacrosDeclarations)
assertMacroExpansion(
declaration(for: ".name"),
expandedSource: expectedExpansion(for: ".name"),
macros: testMacros,
indentationWidth: .tabs(1)
)
#else
throw XCTSkip("Macros are only supported when running tests for the host platform")
#endif
}
func testExpansionWithFunctionCall() throws {
#if canImport(DefaultsMacrosDeclarations)
assertMacroExpansion(
declaration(for: "getName()"),
expandedSource: expectedExpansion(for: "getName()"),
macros: testMacros,
indentationWidth: .tabs(1)
)
#else
throw XCTSkip("Macros are only supported when running tests for the host platform")
#endif
}
func testExpansionWithProperty() throws {
#if canImport(DefaultsMacrosDeclarations)
assertMacroExpansion(
declaration(for: "propertyName"),
expandedSource: expectedExpansion(for: "propertyName"),
macros: testMacros,
indentationWidth: .tabs(1)
)
#else
throw XCTSkip("Macros are only supported when running tests for the host platform")
#endif
}
private func declaration(for keyExpression: String) -> String {
#"""
@Observable
class ObservableClass {
@ObservableDefault(\#(keyExpression))
@ObservationIgnored
var name: String
}
"""#
}
private func expectedExpansion(for keyExpression: String) -> String {
#"""
@Observable
class ObservableClass {
@ObservationIgnored
var name: String {
get {
if objc_getAssociatedObject(self, &Self._objcAssociatedKey_name) == nil {
let cancellable = Defaults.publisher(\#(keyExpression))
.sink { [weak self] in
self?.name = $0.newValue
}
objc_setAssociatedObject(self, &Self._objcAssociatedKey_name, cancellable, .OBJC_ASSOCIATION_RETAIN)
}
access(keyPath: \.name)
return Defaults[\#(keyExpression)]
}
set {
withMutation(keyPath: \.name) {
Defaults[\#(keyExpression)] = newValue
}
}
}
private nonisolated(unsafe) static var _objcAssociatedKey_name: Void?
}
"""#
}
}