Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

/// Swift subclasses become Kotlin-visible subclasses.
open class Animal {
public let name: String

public init(name: String) {
self.name = name
}

open func sound() -> String { "..." }

public func describe() -> String { "\(name) says \(sound())" }
}

public final class Dog: Animal {
public override init(name: String) {
super.init(name: name)
}

public override func sound() -> String { "woof" }

public func fetch() -> String { "stick" }
}

public enum Zoo {
public static func describe(_ animal: Animal) -> String { animal.describe() }

/// Returned as `Animal`; Kotlin receives a `Dog`.
public static func makeDog(name: String) -> Animal { Dog(name: name) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation
import Observation

/// The shape of a Skip Fuse view model: a main-actor, observable open base class; a subclass with
/// callbacks taking structs, an input enum with payloads, and async loading.
@MainActor
@Observable
open class BaseViewModel {
public var isLoading = false

public init() {}

open func setup() {}
}

@MainActor
public final class MarketViewModel: BaseViewModel {
public struct Callbacks: Sendable {
let onOpen: @MainActor @Sendable (Market) -> Void
let onClose: @MainActor @Sendable () -> Void

public init(
onOpen: @escaping @MainActor @Sendable (Market) -> Void,
onClose: @escaping @MainActor @Sendable () -> Void
) {
self.onOpen = onOpen
self.onClose = onClose
}
}

public enum Input: Sendable {
case tapped(Market)
case closeTapped
}

public private(set) var markets: [Market] = []
private let callbacks: Callbacks

public init(callbacks: Callbacks) {
self.callbacks = callbacks
super.init()
}

public override func setup() {
markets = [Market(id: "1")]
}

public func send(_ input: Input) {
switch input {
case .tapped(let market): callbacks.onOpen(market)
case .closeTapped: callbacks.onClose()
}
}

public func load() async {
isLoading = true
markets = [Market(id: "1"), Market(id: "2")]
isLoading = false
}
}

/// For the tests: runs the main run loop briefly, which drains the main queue as Android's main
/// looper does, so main-actor work can run while the JVM main thread waits.
public enum MainLoop {
@MainActor
public static func run(seconds: Double) {
_ = RunLoop.main.run(mode: .default, before: Date(timeIntervalSinceNow: seconds))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

package com.example.kotlin

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs

class ClassHierarchyTest {
@Test
fun subclassIsASuperclass() {
val dog = Dog.init("Rex")
val animal: Animal = dog
assertEquals("Rex says woof", animal.describe())
assertEquals("Rex", animal.name)
assertEquals("stick", dog.fetch())
}

@Test
fun subclassPassedAsSuperclass() {
assertEquals("Rex says woof", Zoo.describe(Dog.init("Rex")))
}

@Test
fun returnedInstanceKeepsItsDynamicClass() {
val animal = Zoo.makeDog("Rex")
assertIs<Dog>(animal)
assertEquals("stick", animal.fetch())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

package com.example.kotlin

import org.junit.jupiter.api.condition.EnabledOnOs
import org.junit.jupiter.api.condition.OS
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse

// A view model as Compose code uses it. Main-actor isolated, so macOS only (see MainActorTest).
@EnabledOnOs(OS.MAC)
class ViewModelTest {
@Test
fun viewModelLifecycle() {
val opened = mutableListOf<String>()
var closed = false
val vm = MarketViewModel.init(MarketViewModel.Callbacks.init({ market -> opened += market.id }, { closed = true }))
val base: BaseViewModel = vm
base.setup()
assertEquals(1, vm.markets.size)

// `load()` is main-actor isolated: pump the main loop instead of blocking the main thread on
// the future (on Android the main looper runs it while the caller suspends).
val loaded = vm.load()
val deadline = System.currentTimeMillis() + 10_000
while (!loaded.isDone && System.currentTimeMillis() < deadline) MainLoop.run(0.01)
loaded.get()
assertFalse(vm.isLoading)
assertEquals(2, vm.markets.size)

vm.send(MarketViewModel.Input.tapped(vm.markets[1]))
vm.send(MarketViewModel.Input.closeTapped())
assertEquals(listOf("2"), opened)
assertEquals(true, closed)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//


/// Swift subclasses become Java subclasses.
open class Shape {
public let name: String

public init(name: String) {
self.name = name
}

open func area() -> Double { 0 }

public func describe() -> String { "\(name): \(area())" }
}

public final class Square: Shape {
public let side: Double

public init(side: Double) {
self.side = side
super.init(name: "square")
}

public override func area() -> Double { side * side }
}

public func totalArea(_ shapes: [Shape]) -> Double {
shapes.reduce(0) { $0 + $1.area() }
}

/// Returned as `Shape`; Java receives a `Square`.
public func makeSquareShape(side: Double) -> Shape {
Square(side: side)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

package com.example.swift;

import org.junit.jupiter.api.Test;
import org.swift.swiftkit.core.SwiftArena;

import static org.junit.jupiter.api.Assertions.*;

public class ClassHierarchyTest {
@Test
void subclassIsASuperclass() {
try (var arena = SwiftArena.ofConfined()) {
Square square = Square.init(3, arena);
Shape shape = square;
assertEquals("square: 9.0", shape.describe());
assertEquals(3, square.getSide());
assertEquals(13, MySwiftLibrary.totalArea(new Shape[] { square, Square.init(2, arena) }));
}
}

@Test
void returnedInstanceHasItsDynamicClass() {
try (var arena = SwiftArena.ofConfined()) {
Shape shape = MySwiftLibrary.makeSquareShape(4, arena);
assertInstanceOf(Square.class, shape);
assertEquals(16, shape.area());
}
}
}
Loading
Loading