Files
swift-mirror/test/stdlib/Observation/Observable.swift
Doug Gregor 6265f0c542 [SE-0395] Rename _Observation module to Observation
The review of SE-0395 is down to small details at this point that won't
affect the overall shape of the API much. Rename the model in
anticipation of that.
2023-06-30 11:01:02 -07:00

390 lines
9.0 KiB
Swift

// REQUIRES: swift_swift_parser, executable_test
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -parse-as-library -enable-experimental-feature InitAccessors -enable-experimental-feature Macros -Xfrontend -plugin-path -Xfrontend %swift-host-lib-dir/plugins)
// Run this test via the swift-plugin-server
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -parse-as-library -enable-experimental-feature InitAccessors -enable-experimental-feature Macros -Xfrontend -external-plugin-path -Xfrontend %swift-host-lib-dir/plugins#%swift-plugin-server)
// Asserts is required for '-enable-experimental-feature InitAccessors'.
// REQUIRES: asserts
// REQUIRES: observation
// REQUIRES: concurrency
// REQUIRES: objc_interop
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime
import StdlibUnittest
import Observation
@usableFromInline
@inline(never)
func _blackHole<T>(_ value: T) { }
@Observable
class ContainsNothing { }
@Observable
struct Structure {
var field: Int = 0
}
@Observable
struct MemberwiseInitializers {
var field: Int
}
func validateMemberwiseInitializers() {
_ = MemberwiseInitializers(field: 3)
}
@Observable
struct DefiniteInitialization {
var field: Int
init(field: Int) {
self.field = field
}
}
@Observable
class ContainsWeak {
weak var obj: AnyObject? = nil
}
@Observable
public class PublicContainsWeak {
public weak var obj: AnyObject? = nil
}
@Observable
class ContainsUnowned {
unowned var obj: AnyObject? = nil
}
@Observable
class ContainsIUO {
var obj: Int! = nil
}
class NonObservable {
}
@Observable
class InheritsFromNonObservable: NonObservable {
}
protocol NonObservableProtocol {
}
@Observable
class ConformsToNonObservableProtocol: NonObservableProtocol {
}
struct NonObservableContainer {
@Observable
class ObservableContents {
var field: Int = 3
}
}
@Observable
class ImplementsAccessAndMutation {
var field = 3
let accessCalled: (PartialKeyPath<ImplementsAccessAndMutation>) -> Void
let withMutationCalled: (PartialKeyPath<ImplementsAccessAndMutation>) -> Void
init(accessCalled: @escaping (PartialKeyPath<ImplementsAccessAndMutation>) -> Void, withMutationCalled: @escaping (PartialKeyPath<ImplementsAccessAndMutation>) -> Void) {
self.accessCalled = accessCalled
self.withMutationCalled = withMutationCalled
}
internal func access<Member>(
keyPath: KeyPath<ImplementsAccessAndMutation , Member>
) {
accessCalled(keyPath)
_$observationRegistrar.access(self, keyPath: keyPath)
}
internal func withMutation<Member, T>(
keyPath: KeyPath<ImplementsAccessAndMutation , Member>,
_ mutation: () throws -> T
) rethrows -> T {
withMutationCalled(keyPath)
return try _$observationRegistrar.withMutation(of: self, keyPath: keyPath, mutation)
}
}
@Observable
class HasIgnoredProperty {
var field = 3
@ObservationIgnored var ignored = 4
}
@Observable
class Entity {
var age: Int = 0
}
@Observable
class Person : Entity {
var firstName = ""
var lastName = ""
var friends = [Person]()
var fullName: String { firstName + " " + lastName }
}
@Observable
class MiddleNamePerson: Person {
var middleName = ""
override var fullName: String { firstName + " " + middleName + " " + lastName }
}
@Observable
class IsolatedClass {
@MainActor var test = "hello"
}
@MainActor
@Observable
class IsolatedInstance {
var test = "hello"
}
@Observable
struct StructHasExistingConformance: Observable { }
@Observable
class ClassHasExistingConformance: Observable { }
protocol Intermediary: Observable { }
@Observable
class HasIntermediaryConformance: Intermediary { }
class CapturedState<State>: @unchecked Sendable {
var state: State
init(state: State) {
self.state = state
}
}
@main
struct Validator {
@MainActor
static func main() {
let suite = TestSuite("Observable")
suite.test("unobserved value changes") {
let test = MiddleNamePerson()
for i in 0..<100 {
test.firstName = "\(i)"
}
}
suite.test("tracking changes") {
let changed = CapturedState(state: false)
let test = MiddleNamePerson()
withObservationTracking {
_blackHole(test.firstName)
} onChange: {
changed.state = true
}
test.firstName = "c"
expectEqual(changed.state, true)
changed.state = false
test.firstName = "c"
expectEqual(changed.state, false)
}
suite.test("tracking structure changes") {
let changed = CapturedState(state: false)
var test = Structure()
withObservationTracking {
_blackHole(test.field)
} onChange: {
changed.state = true
}
test.field = 4
expectEqual(changed.state, true)
changed.state = false
test.field = 5
expectEqual(changed.state, false)
}
suite.test("conformance") {
func testConformance<O: Observable>(_ o: O) -> Bool {
return true
}
func testConformance<O>(_ o: O) -> Bool {
return false
}
let test = Person()
expectEqual(testConformance(test), true)
}
suite.test("tracking nonchanged") {
let changed = CapturedState(state: false)
let test = MiddleNamePerson()
withObservationTracking {
_blackHole(test.lastName)
} onChange: {
changed.state = true
}
test.firstName = "c"
expectEqual(changed.state, false)
}
suite.test("tracking computed") {
let changed = CapturedState(state: false)
let test = MiddleNamePerson()
withObservationTracking {
_blackHole(test.fullName)
} onChange: {
changed.state = true
}
test.middleName = "c"
expectEqual(changed.state, true)
changed.state = false
test.middleName = "c"
expectEqual(changed.state, false)
}
suite.test("graph changes") {
let changed = CapturedState(state: false)
let test = MiddleNamePerson()
let friend = MiddleNamePerson()
test.friends.append(friend)
withObservationTracking {
_blackHole(test.friends.first?.fullName)
} onChange: {
changed.state = true
}
test.middleName = "c"
expectEqual(changed.state, false)
friend.middleName = "c"
expectEqual(changed.state, true)
}
suite.test("nesting") {
let changedOuter = CapturedState(state: false)
let changedInner = CapturedState(state: false)
let test = MiddleNamePerson()
withObservationTracking {
withObservationTracking {
_blackHole(test.firstName)
} onChange: {
changedInner.state = true
}
} onChange: {
changedOuter.state = true
}
test.firstName = "c"
expectEqual(changedInner.state, true)
expectEqual(changedOuter.state, true)
changedOuter.state = false
test.firstName = "c"
expectEqual(changedOuter.state, false)
}
suite.test("access and mutation") {
let accessKeyPath = CapturedState<PartialKeyPath<ImplementsAccessAndMutation>?>(state: nil)
let mutationKeyPath = CapturedState<PartialKeyPath<ImplementsAccessAndMutation>?>(state: nil)
let test = ImplementsAccessAndMutation { keyPath in
accessKeyPath.state = keyPath
} withMutationCalled: { keyPath in
mutationKeyPath.state = keyPath
}
expectEqual(accessKeyPath.state, nil)
_blackHole(test.field)
expectEqual(accessKeyPath.state, \.field)
expectEqual(mutationKeyPath.state, nil)
accessKeyPath.state = nil
test.field = 123
expectEqual(accessKeyPath.state, nil)
expectEqual(mutationKeyPath.state, \.field)
}
suite.test("ignores no change") {
let changed = CapturedState(state: false)
let test = HasIgnoredProperty()
withObservationTracking {
_blackHole(test.ignored)
} onChange: {
changed.state = true
}
test.ignored = 122112
expectEqual(changed.state, false)
changed.state = false
test.field = 3429
expectEqual(changed.state, false)
}
suite.test("ignores change") {
let changed = CapturedState(state: false)
let test = HasIgnoredProperty()
withObservationTracking {
_blackHole(test.ignored)
_blackHole(test.field)
} onChange: {
changed.state = true
}
test.ignored = 122112
expectEqual(changed.state, false)
changed.state = false
test.field = 3429
expectEqual(changed.state, true)
}
suite.test("isolated class") { @MainActor in
let changed = CapturedState(state: false)
let test = IsolatedClass()
withObservationTracking {
_blackHole(test.test)
} onChange: {
changed.state = true
}
test.test = "c"
expectEqual(changed.state, true)
changed.state = false
test.test = "c"
expectEqual(changed.state, false)
}
runAllTests()
}
}