Skip to content

Remove custom test framework #295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 14, 2025
Merged
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
1 change: 0 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -41,7 +41,6 @@ jobs:
- name: Configure Swift SDK
run: echo "SWIFT_SDK_ID=${{ steps.setup-swiftwasm.outputs.swift-sdk-id }}" >> $GITHUB_ENV
- run: make bootstrap
- run: make test
- run: make unittest
# Skip unit tests with uwasi because its proc_exit throws
# unhandled promise rejection.
12 changes: 4 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -58,14 +58,10 @@ Thank you for considering contributing to JavaScriptKit! We welcome contribution
```

### Running Tests
- Run unit tests:
```bash
make unittest SWIFT_SDK_ID=wasm32-unknown-wasi
```
- Run integration tests:
```bash
make test SWIFT_SDK_ID=wasm32-unknown-wasi
```

```bash
make unittest SWIFT_SDK_ID=wasm32-unknown-wasi
```

### Editing `./Runtime` directory

17 changes: 0 additions & 17 deletions IntegrationTests/TestSuites/Package.swift
Original file line number Diff line number Diff line change
@@ -11,30 +11,13 @@ let package = Package(
.macOS("12.0"),
],
products: [
.executable(
name: "PrimaryTests", targets: ["PrimaryTests"]
),
.executable(
name: "ConcurrencyTests", targets: ["ConcurrencyTests"]
),
.executable(
name: "BenchmarkTests", targets: ["BenchmarkTests"]
),
],
dependencies: [.package(name: "JavaScriptKit", path: "../../")],
targets: [
.target(name: "CHelpers"),
.executableTarget(name: "PrimaryTests", dependencies: [
.product(name: "JavaScriptBigIntSupport", package: "JavaScriptKit"),
"JavaScriptKit",
"CHelpers",
]),
.executableTarget(
name: "ConcurrencyTests",
dependencies: [
.product(name: "JavaScriptEventLoop", package: "JavaScriptKit"),
]
),
.executableTarget(name: "BenchmarkTests", dependencies: ["JavaScriptKit", "CHelpers"]),
]
)

This file was deleted.

221 changes: 0 additions & 221 deletions IntegrationTests/TestSuites/Sources/ConcurrencyTests/main.swift

This file was deleted.

39 changes: 0 additions & 39 deletions IntegrationTests/TestSuites/Sources/PrimaryTests/I64.swift

This file was deleted.

161 changes: 0 additions & 161 deletions IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift

This file was deleted.

918 changes: 0 additions & 918 deletions IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift

This file was deleted.

8 changes: 0 additions & 8 deletions IntegrationTests/bin/concurrency-tests.js

This file was deleted.

110 changes: 0 additions & 110 deletions IntegrationTests/bin/primary-tests.js

This file was deleted.

114 changes: 1 addition & 113 deletions IntegrationTests/lib.js
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ import { WASI as NodeWASI } from "wasi"
import { WASI as MicroWASI, useAll } from "uwasi"
import * as fs from "fs/promises"
import path from "path";
import { Worker, parentPort } from "node:worker_threads";

const WASI = {
MicroWASI: ({ args }) => {
@@ -53,16 +52,6 @@ const selectWASIBackend = () => {
return "Node"
};

function isUsingSharedMemory(module) {
const imports = WebAssembly.Module.imports(module);
for (const entry of imports) {
if (entry.module === "env" && entry.name === "memory" && entry.kind == "memory") {
return true;
}
}
return false;
}

function constructBaseImportObject(wasi, swift) {
return {
wasi_snapshot_preview1: wasi.wasiImport,
@@ -74,79 +63,6 @@ function constructBaseImportObject(wasi, swift) {
}
}

export async function startWasiChildThread(event) {
const { module, programName, memory, tid, startArg } = event;
const swift = new SwiftRuntime({
sharedMemory: true,
threadChannel: {
postMessageToMainThread: (message, transfer) => {
parentPort.postMessage(message, transfer);
},
listenMessageFromMainThread: (listener) => {
parentPort.on("message", listener)
}
}
});
// Use uwasi for child threads because Node.js WASI cannot be used without calling
// `WASI.start` or `WASI.initialize`, which is already called in the main thread and
// will cause an error if called again.
const wasi = WASI.MicroWASI({ programName });

const importObject = constructBaseImportObject(wasi, swift);

importObject["wasi"] = {
"thread-spawn": () => {
throw new Error("Cannot spawn a new thread from a worker thread")
}
};
importObject["env"] = { memory };
importObject["JavaScriptEventLoopTestSupportTests"] = {
"isMainThread": () => false,
}

const instance = await WebAssembly.instantiate(module, importObject);
swift.setInstance(instance);
wasi.setInstance(instance);
swift.startThread(tid, startArg);
}

class ThreadRegistry {
workers = new Map();
nextTid = 1;

spawnThread(module, programName, memory, startArg) {
const tid = this.nextTid++;
const selfFilePath = new URL(import.meta.url).pathname;
const worker = new Worker(`
const { parentPort } = require('node:worker_threads');
Error.stackTraceLimit = 100;
parentPort.once("message", async (event) => {
const { selfFilePath } = event;
const { startWasiChildThread } = await import(selfFilePath);
await startWasiChildThread(event);
})
`, { type: "module", eval: true })

worker.on("error", (error) => {
console.error(`Worker thread ${tid} error:`, error);
throw error;
});
this.workers.set(tid, worker);
worker.postMessage({ selfFilePath, module, programName, memory, tid, startArg });
return tid;
}

worker(tid) {
return this.workers.get(tid);
}

wakeUpWorkerThread(tid, message, transfer) {
const worker = this.workers.get(tid);
worker.postMessage(message, transfer);
}
}

export const startWasiTask = async (wasmPath, wasiConstructorKey = selectWASIBackend()) => {
// Fetch our Wasm File
const wasmBinary = await fs.readFile(wasmPath);
@@ -157,38 +73,10 @@ export const startWasiTask = async (wasmPath, wasiConstructorKey = selectWASIBac

const module = await WebAssembly.compile(wasmBinary);

const sharedMemory = isUsingSharedMemory(module);
const threadRegistry = new ThreadRegistry();
const swift = new SwiftRuntime({
sharedMemory,
threadChannel: {
postMessageToWorkerThread: threadRegistry.wakeUpWorkerThread.bind(threadRegistry),
listenMessageFromWorkerThread: (tid, listener) => {
const worker = threadRegistry.worker(tid);
worker.on("message", listener);
}
}
});
const swift = new SwiftRuntime();

const importObject = constructBaseImportObject(wasi, swift);

importObject["JavaScriptEventLoopTestSupportTests"] = {
"isMainThread": () => true,
}

if (sharedMemory) {
// We don't have JS API to get memory descriptor of imported memory
// at this moment, so we assume 256 pages (16MB) memory is enough
// large for initial memory size.
const memory = new WebAssembly.Memory({ initial: 1024, maximum: 16384, shared: true })
importObject["env"] = { memory };
importObject["wasi"] = {
"thread-spawn": (startArg) => {
return threadRegistry.spawnThread(module, programName, memory, startArg);
}
}
}

// Instantiate the WebAssembly file
const instance = await WebAssembly.instantiate(module, importObject);

17 changes: 7 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
@@ -12,19 +12,16 @@ build:
swift build --triple wasm32-unknown-wasi
npm run build

.PHONY: test
test:
@echo Running integration tests
cd IntegrationTests && \
CONFIGURATION=debug SWIFT_BUILD_FLAGS="$(SWIFT_BUILD_FLAGS)" $(MAKE) test && \
CONFIGURATION=debug SWIFT_BUILD_FLAGS="$(SWIFT_BUILD_FLAGS) -Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS" $(MAKE) test && \
CONFIGURATION=release SWIFT_BUILD_FLAGS="$(SWIFT_BUILD_FLAGS)" $(MAKE) test && \
CONFIGURATION=release SWIFT_BUILD_FLAGS="$(SWIFT_BUILD_FLAGS) -Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS" $(MAKE) test

.PHONY: unittest
unittest:
@echo Running unit tests
swift package --swift-sdk "$(SWIFT_SDK_ID)" js test --prelude ./Tests/prelude.mjs
swift package --swift-sdk "$(SWIFT_SDK_ID)" \
--disable-sandbox \
-Xlinker --stack-first \
-Xlinker --global-base=524288 \
-Xlinker -z \
-Xlinker stack-size=524288 \
js test --prelude ./Tests/prelude.mjs

.PHONY: benchmark_setup
benchmark_setup:
9 changes: 8 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -34,14 +34,21 @@ let package = Package(
.target(name: "_CJavaScriptKit"),
.testTarget(
name: "JavaScriptKitTests",
dependencies: ["JavaScriptKit"]
dependencies: ["JavaScriptKit"],
swiftSettings: [
.enableExperimentalFeature("Extern")
]
),

.target(
name: "JavaScriptBigIntSupport",
dependencies: ["_CJavaScriptBigIntSupport", "JavaScriptKit"]
),
.target(name: "_CJavaScriptBigIntSupport", dependencies: ["_CJavaScriptKit"]),
.testTarget(
name: "JavaScriptBigIntSupportTests",
dependencies: ["JavaScriptBigIntSupport", "JavaScriptKit"]
),

.target(
name: "JavaScriptEventLoop",
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import XCTest
import JavaScriptBigIntSupport
import JavaScriptKit

class JavaScriptBigIntSupportTests: XCTestCase {
func testBigIntSupport() {
// Test signed values
func testSignedValue(_ value: Int64, file: StaticString = #filePath, line: UInt = #line) {
let bigInt = JSBigInt(value)
XCTAssertEqual(bigInt.description, value.description, file: file, line: line)
let bigInt2 = JSBigInt(_slowBridge: value)
XCTAssertEqual(bigInt2.description, value.description, file: file, line: line)
}

// Test unsigned values
func testUnsignedValue(_ value: UInt64, file: StaticString = #filePath, line: UInt = #line) {
let bigInt = JSBigInt(unsigned: value)
XCTAssertEqual(bigInt.description, value.description, file: file, line: line)
let bigInt2 = JSBigInt(_slowBridge: value)
XCTAssertEqual(bigInt2.description, value.description, file: file, line: line)
}

// Test specific signed values
testSignedValue(0)
testSignedValue(1 << 62)
testSignedValue(-2305)

// Test random signed values
for _ in 0..<100 {
testSignedValue(.random(in: .min ... .max))
}

// Test edge signed values
testSignedValue(.min)
testSignedValue(.max)

// Test specific unsigned values
testUnsignedValue(0)
testUnsignedValue(1 << 62)
testUnsignedValue(1 << 63)
testUnsignedValue(.min)
testUnsignedValue(.max)
testUnsignedValue(~0)

// Test random unsigned values
for _ in 0..<100 {
testUnsignedValue(.random(in: .min ... .max))
}
}
}
97 changes: 97 additions & 0 deletions Tests/JavaScriptEventLoopTests/JSPromiseTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import XCTest
@testable import JavaScriptKit

final class JSPromiseTests: XCTestCase {
func testPromiseThen() async throws {
var p1 = JSPromise.resolve(JSValue.null)
await withCheckedContinuation { continuation in
p1 = p1.then { value in
XCTAssertEqual(value, .null)
continuation.resume()
return JSValue.number(1.0)
}
}
await withCheckedContinuation { continuation in
p1 = p1.then { value in
XCTAssertEqual(value, .number(1.0))
continuation.resume()
return JSPromise.resolve(JSValue.boolean(true))
}
}
await withCheckedContinuation { continuation in
p1 = p1.then { value in
XCTAssertEqual(value, .boolean(true))
continuation.resume()
return JSValue.undefined
}
}
await withCheckedContinuation { continuation in
p1 = p1.catch { error in
XCTFail("Not fired due to no throw")
return JSValue.undefined
}
.finally { continuation.resume() }
}
}

func testPromiseCatch() async throws {
var p2 = JSPromise.reject(JSValue.boolean(false))
await withCheckedContinuation { continuation in
p2 = p2.catch { error in
XCTAssertEqual(error, .boolean(false))
continuation.resume()
return JSValue.boolean(true)
}
}
await withCheckedContinuation { continuation in
p2 = p2.then { value in
XCTAssertEqual(value, .boolean(true))
continuation.resume()
return JSPromise.reject(JSValue.number(2.0))
}
}
await withCheckedContinuation { continuation in
p2 = p2.catch { error in
XCTAssertEqual(error, .number(2.0))
continuation.resume()
return JSValue.undefined
}
}
await withCheckedContinuation { continuation in
p2 = p2.finally { continuation.resume() }
}
}

func testPromiseAndTimer() async throws {
let start = JSDate().valueOf()
let timeoutMilliseconds = 5.0
var timer: JSTimer?

var p3: JSPromise?
await withCheckedContinuation { continuation in
p3 = JSPromise { resolve in
timer = JSTimer(millisecondsDelay: timeoutMilliseconds) {
continuation.resume()
resolve(.success(.undefined))
}
}
}

await withCheckedContinuation { continuation in
p3?.then { _ in
XCTAssertEqual(start + timeoutMilliseconds <= JSDate().valueOf(), true)
continuation.resume()
return JSValue.undefined
}
}

// Ensure that users don't need to manage JSPromise lifetime
await withCheckedContinuation { continuation in
JSPromise.resolve(JSValue.boolean(true)).then { _ in
continuation.resume()
return JSValue.undefined
}
}
withExtendedLifetime(timer) {}
}
}
56 changes: 56 additions & 0 deletions Tests/JavaScriptEventLoopTests/JSTimerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import XCTest

@testable import JavaScriptKit

final class JSTimerTests: XCTestCase {

func testOneshotTimerCancelled() {
let timeoutMilliseconds = 5.0
var timeout: JSTimer!
timeout = JSTimer(millisecondsDelay: timeoutMilliseconds, isRepeating: false) {
XCTFail("timer should be cancelled")
}
_ = timeout
timeout = nil
}

func testRepeatingTimerCancelled() async throws {
var count = 0.0
let maxCount = 5.0
var interval: JSTimer?
let start = JSDate().valueOf()
let timeoutMilliseconds = 5.0

await withCheckedContinuation { continuation in
interval = JSTimer(millisecondsDelay: 5, isRepeating: true) {
// ensure that JSTimer is living
XCTAssertNotNil(interval)
// verify that at least `timeoutMilliseconds * count` passed since the `timeout`
// timer started
XCTAssertTrue(start + timeoutMilliseconds * count <= JSDate().valueOf())

guard count < maxCount else {
// stop the timer after `maxCount` reached
interval = nil
continuation.resume()
return
}

count += 1
}
}
withExtendedLifetime(interval) {}
}

func testTimer() async throws {
let start = JSDate().valueOf()
let timeoutMilliseconds = 5.0
var timeout: JSTimer!
await withCheckedContinuation { continuation in
timeout = JSTimer(millisecondsDelay: timeoutMilliseconds, isRepeating: false) {
continuation.resume()
}
}
withExtendedLifetime(timeout) {}
}
}
260 changes: 260 additions & 0 deletions Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
import JavaScriptEventLoop
import JavaScriptKit
import XCTest

final class JavaScriptEventLoopTests: XCTestCase {
// Helper utilities for testing
struct MessageError: Error {
let message: String
let file: StaticString
let line: UInt
let column: UInt
init(_ message: String, file: StaticString, line: UInt, column: UInt) {
self.message = message
self.file = file
self.line = line
self.column = column
}
}

func expectAsyncThrow<T>(
_ body: @autoclosure () async throws -> T, file: StaticString = #file, line: UInt = #line,
column: UInt = #column
) async throws -> Error {
do {
_ = try await body()
} catch {
return error
}
throw MessageError("Expect to throw an exception", file: file, line: line, column: column)
}

func performanceNow() -> Double {
return JSObject.global.performance.now().number!
}

func measureTime(_ block: () async throws -> Void) async rethrows -> Double {
let start = performanceNow()
try await block()
return performanceNow() - start
}

// Error type used in tests
struct E: Error, Equatable {
let value: Int
}

// MARK: - Task Tests

func testTaskInit() async throws {
// Test Task.init value
let handle = Task { 1 }
let value = await handle.value
XCTAssertEqual(value, 1)
}

func testTaskInitThrows() async throws {
// Test Task.init throws
let throwingHandle = Task {
throw E(value: 2)
}
let error = try await expectAsyncThrow(await throwingHandle.value)
let e = try XCTUnwrap(error as? E)
XCTAssertEqual(e, E(value: 2))
}

func testTaskSleep() async throws {
// Test Task.sleep(_:)
let sleepDiff = try await measureTime {
try await Task.sleep(nanoseconds: 200_000_000)
}
XCTAssertGreaterThanOrEqual(sleepDiff, 200)

// Test shorter sleep duration
let shortSleepDiff = try await measureTime {
try await Task.sleep(nanoseconds: 100_000_000)
}
XCTAssertGreaterThanOrEqual(shortSleepDiff, 100)
}

func testTaskPriority() async throws {
// Test Job reordering based on priority
class Context: @unchecked Sendable {
var completed: [String] = []
}
let context = Context()

// When no priority, they should be ordered by the enqueued order
let t1 = Task(priority: nil) {
context.completed.append("t1")
}
let t2 = Task(priority: nil) {
context.completed.append("t2")
}

_ = await (t1.value, t2.value)
XCTAssertEqual(context.completed, ["t1", "t2"])

context.completed = []
// When high priority is enqueued after a low one, they should be re-ordered
let t3 = Task(priority: .low) {
context.completed.append("t3")
}
let t4 = Task(priority: .high) {
context.completed.append("t4")
}
let t5 = Task(priority: .low) {
context.completed.append("t5")
}

_ = await (t3.value, t4.value, t5.value)
XCTAssertEqual(context.completed, ["t4", "t3", "t5"])
}

// MARK: - Promise Tests

func testPromiseResolution() async throws {
// Test await resolved Promise
let p = JSPromise(resolver: { resolve in
resolve(.success(1))
})
let resolutionValue = try await p.value
XCTAssertEqual(resolutionValue, .number(1))
let resolutionResult = await p.result
XCTAssertEqual(resolutionResult, .success(.number(1)))
}

func testPromiseRejection() async throws {
// Test await rejected Promise
let rejectedPromise = JSPromise(resolver: { resolve in
resolve(.failure(.number(3)))
})
let promiseError = try await expectAsyncThrow(try await rejectedPromise.value)
let jsValue = try XCTUnwrap(promiseError as? JSException).thrownValue
XCTAssertEqual(jsValue, .number(3))
let rejectionResult = await rejectedPromise.result
XCTAssertEqual(rejectionResult, .failure(.number(3)))
}

func testPromiseThen() async throws {
// Test Async JSPromise: then
let promise = JSPromise { resolve in
_ = JSObject.global.setTimeout!(
JSClosure { _ in
resolve(.success(JSValue.number(3)))
return .undefined
}.jsValue,
100
)
}
let promise2 = promise.then { result in
try await Task.sleep(nanoseconds: 100_000_000)
return String(result.number!)
}
let thenDiff = try await measureTime {
let result = try await promise2.value
XCTAssertEqual(result, .string("3.0"))
}
XCTAssertGreaterThanOrEqual(thenDiff, 200)
}

func testPromiseThenWithFailure() async throws {
// Test Async JSPromise: then(success:failure:)
let failingPromise = JSPromise { resolve in
_ = JSObject.global.setTimeout!(
JSClosure { _ in
resolve(.failure(JSError(message: "test").jsValue))
return .undefined
}.jsValue,
100
)
}
let failingPromise2 = failingPromise.then { _ in
throw MessageError("Should not be called", file: #file, line: #line, column: #column)
} failure: { err in
return err
}
let failingResult = try await failingPromise2.value
XCTAssertEqual(failingResult.object?.message, .string("test"))
}

func testPromiseCatch() async throws {
// Test Async JSPromise: catch
let catchPromise = JSPromise { resolve in
_ = JSObject.global.setTimeout!(
JSClosure { _ in
resolve(.failure(JSError(message: "test").jsValue))
return .undefined
}.jsValue,
100
)
}
let catchPromise2 = catchPromise.catch { err in
try await Task.sleep(nanoseconds: 100_000_000)
return err
}
let catchDiff = try await measureTime {
let result = try await catchPromise2.value
XCTAssertEqual(result.object?.message, .string("test"))
}
XCTAssertGreaterThanOrEqual(catchDiff, 200)
}

// MARK: - Continuation Tests

func testContinuation() async throws {
// Test Continuation
let continuationValue = await withUnsafeContinuation { cont in
cont.resume(returning: 1)
}
XCTAssertEqual(continuationValue, 1)

let continuationError = try await expectAsyncThrow(
try await withUnsafeThrowingContinuation { (cont: UnsafeContinuation<Never, Error>) in
cont.resume(throwing: E(value: 2))
}
)
let errorValue = try XCTUnwrap(continuationError as? E)
XCTAssertEqual(errorValue.value, 2)
}

// MARK: - JSClosure Tests

func testAsyncJSClosure() async throws {
// Test Async JSClosure
let delayClosure = JSClosure.async { _ -> JSValue in
try await Task.sleep(nanoseconds: 200_000_000)
return JSValue.number(3)
}
let delayObject = JSObject.global.Object.function!.new()
delayObject.closure = delayClosure.jsValue

let closureDiff = try await measureTime {
let promise = JSPromise(from: delayObject.closure!())
XCTAssertNotNil(promise)
let result = try await promise!.value
XCTAssertEqual(result, .number(3))
}
XCTAssertGreaterThanOrEqual(closureDiff, 200)
}

// MARK: - Clock Tests

#if compiler(>=5.7)
func testClockSleep() async throws {
// Test ContinuousClock.sleep
let continuousClockDiff = try await measureTime {
let c = ContinuousClock()
try await c.sleep(until: .now + .milliseconds(100))
}
XCTAssertGreaterThanOrEqual(continuousClockDiff, 99)

// Test SuspendingClock.sleep
let suspendingClockDiff = try await measureTime {
let c = SuspendingClock()
try await c.sleep(until: .now + .milliseconds(100))
}
XCTAssertGreaterThanOrEqual(suspendingClockDiff, 99)
}
#endif
}
84 changes: 83 additions & 1 deletion Tests/JavaScriptKitTests/JSTypedArrayTests.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import XCTest
import JavaScriptKit
import XCTest

final class JSTypedArrayTests: XCTestCase {
func testEmptyArray() {
@@ -15,4 +15,86 @@ final class JSTypedArrayTests: XCTestCase {
_ = JSTypedArray<Float32>([Float32]())
_ = JSTypedArray<Float64>([Float64]())
}

func testTypedArray() {
func checkArray<T>(_ array: [T]) where T: TypedArrayElement & Equatable {
XCTAssertEqual(toString(JSTypedArray(array).jsValue.object!), jsStringify(array))
checkArrayUnsafeBytes(array)
}

func toString<T: JSObject>(_ object: T) -> String {
return object.toString!().string!
}

func jsStringify(_ array: [Any]) -> String {
array.map({ String(describing: $0) }).joined(separator: ",")
}

func checkArrayUnsafeBytes<T>(_ array: [T]) where T: TypedArrayElement & Equatable {
let copyOfArray: [T] = JSTypedArray(array).withUnsafeBytes { buffer in
Array(buffer)
}
XCTAssertEqual(copyOfArray, array)
}

let numbers = [UInt8](0...255)
let typedArray = JSTypedArray(numbers)
XCTAssertEqual(typedArray[12], 12)
XCTAssertEqual(numbers.count, typedArray.lengthInBytes)

let numbersSet = Set(0...255)
let typedArrayFromSet = JSTypedArray(numbersSet)
XCTAssertEqual(typedArrayFromSet.jsObject.length, 256)
XCTAssertEqual(typedArrayFromSet.lengthInBytes, 256 * MemoryLayout<Int>.size)

checkArray([0, .max, 127, 1] as [UInt8])
checkArray([0, 1, .max, .min, -1] as [Int8])

checkArray([0, .max, 255, 1] as [UInt16])
checkArray([0, 1, .max, .min, -1] as [Int16])

checkArray([0, .max, 255, 1] as [UInt32])
checkArray([0, 1, .max, .min, -1] as [Int32])

checkArray([0, .max, 255, 1] as [UInt])
checkArray([0, 1, .max, .min, -1] as [Int])

let float32Array: [Float32] = [
0, 1, .pi, .greatestFiniteMagnitude, .infinity, .leastNonzeroMagnitude,
.leastNormalMagnitude, 42,
]
let jsFloat32Array = JSTypedArray(float32Array)
for (i, num) in float32Array.enumerated() {
XCTAssertEqual(num, jsFloat32Array[i])
}

let float64Array: [Float64] = [
0, 1, .pi, .greatestFiniteMagnitude, .infinity, .leastNonzeroMagnitude,
.leastNormalMagnitude, 42,
]
let jsFloat64Array = JSTypedArray(float64Array)
for (i, num) in float64Array.enumerated() {
XCTAssertEqual(num, jsFloat64Array[i])
}
}

func testTypedArrayMutation() {
let array = JSTypedArray<Int>(length: 100)
for i in 0..<100 {
array[i] = i
}
for i in 0..<100 {
XCTAssertEqual(i, array[i])
}

func toString<T: JSObject>(_ object: T) -> String {
return object.toString!().string!
}

func jsStringify(_ array: [Any]) -> String {
array.map({ String(describing: $0) }).joined(separator: ",")
}

XCTAssertEqual(toString(array.jsValue.object!), jsStringify(Array(0..<100)))
}
}
674 changes: 674 additions & 0 deletions Tests/JavaScriptKitTests/JavaScriptKitTests.swift

Large diffs are not rendered by default.

106 changes: 106 additions & 0 deletions Tests/prelude.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/** @type {import('./../.build/plugins/PackageToJS/outputs/PackageTests/test.d.ts').Prelude["setupOptions"]} */
export function setupOptions(options, context) {
Error.stackTraceLimit = 100;
setupTestGlobals(globalThis);
return {
...options,
addToCoreImports(importObject) {
@@ -10,3 +12,107 @@ export function setupOptions(options, context) {
}
}
}

function setupTestGlobals(global) {
global.globalObject1 = {
prop_1: {
nested_prop: 1,
},
prop_2: 2,
prop_3: true,
prop_4: [3, 4, "str_elm_1", null, undefined, 5],
prop_5: {
func1: function () {
return;
},
func2: function () {
return 1;
},
func3: function (n) {
return n * 2;
},
func4: function (a, b, c) {
return a + b + c;
},
func5: function (x) {
return "Hello, " + x;
},
func6: function (c, a, b) {
if (c) {
return a;
} else {
return b;
}
},
},
prop_6: {
call_host_1: () => {
return global.globalObject1.prop_6.host_func_1();
},
},
prop_7: 3.14,
prop_8: [0, , 2, 3, , , 6],
prop_9: {
func1: function () {
throw new Error();
},
func2: function () {
throw "String Error";
},
func3: function () {
throw 3.0;
},
},
eval_closure: function (fn) {
return fn(arguments[1]);
},
observable_obj: {
set_called: false,
target: new Proxy(
{
nested: {},
},
{
set(target, key, value) {
global.globalObject1.observable_obj.set_called = true;
target[key] = value;
return true;
},
}
),
},
};

global.Animal = function (name, age, isCat) {
if (age < 0) {
throw new Error("Invalid age " + age);
}
this.name = name;
this.age = age;
this.bark = () => {
return isCat ? "nyan" : "wan";
};
this.isCat = isCat;
this.getIsCat = function () {
return this.isCat;
};
this.setName = function (name) {
this.name = name;
};
};

global.callThrowingClosure = (c) => {
try {
c();
} catch (error) {
return error;
}
};

global.objectDecodingTest = {
obj: {},
fn: () => { },
sym: Symbol("s"),
bi: BigInt(3)
};
}