Skip to content

Commit dd0d2f5

Browse files
Add test cases
1 parent 9db9885 commit dd0d2f5

File tree

7 files changed

+159
-5
lines changed

7 files changed

+159
-5
lines changed

IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,20 @@ func expectString(_ value: JSValue, file: StaticString = #file, line: UInt = #li
8484
throw MessageError("Type of \(value) should be \"string\"", file: file, line: line, column: column)
8585
}
8686
}
87+
88+
func expectThrow<T>(_ body: @autoclosure () throws -> T, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Error {
89+
do {
90+
_ = try body()
91+
} catch {
92+
return error
93+
}
94+
throw MessageError("Expect to throw an exception", file: file, line: line, column: column)
95+
}
96+
97+
func expectNotNil<T>(_ value: T?, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws {
98+
switch value {
99+
case .some: return
100+
case .none:
101+
throw MessageError("Expect a non-nil value", file: file, line: line, column: column)
102+
}
103+
}

IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,3 +517,57 @@ try test("JSValue accessor") {
517517
try expectEqual(globalObject1.prop_4[0], .number(3))
518518
try expectEqual(globalObject1.prop_4[1], .number(4))
519519
}
520+
521+
try test("Exception") {
522+
// ```js
523+
// global.globalObject1 = {
524+
// ...
525+
// prop_9: {
526+
// func1: function () {
527+
// throw new Error();
528+
// },
529+
// func2: function () {
530+
// throw "String Error";
531+
// },
532+
// func3: function () {
533+
// throw 3.0
534+
// },
535+
// },
536+
// ...
537+
// }
538+
// ```
539+
//
540+
541+
let globalObject1 = JSObject.global.globalObject1
542+
let prop_9: JSValue = globalObject1.prop_9
543+
544+
// MARK: Throwing method calls
545+
let error1 = try expectThrow(try prop_9.object!.throwing.func1!())
546+
try expectEqual(error1 is JSValue, true)
547+
let errorObject = JSError(from: error1 as! JSValue)
548+
try expectNotNil(errorObject)
549+
550+
let error2 = try expectThrow(try prop_9.object!.throwing.func2!())
551+
try expectEqual(error2 is JSValue, true)
552+
let errorString = try expectString(error2 as! JSValue)
553+
try expectEqual(errorString, "String Error")
554+
555+
let error3 = try expectThrow(try prop_9.object!.throwing.func3!())
556+
try expectEqual(error3 is JSValue, true)
557+
let errorNumber = try expectNumber(error3 as! JSValue)
558+
try expectEqual(errorNumber, 3.0)
559+
560+
// MARK: Simple function calls
561+
let error4 = try expectThrow(try prop_9.func1.function!.throws())
562+
try expectEqual(error4 is JSValue, true)
563+
let errorObject2 = JSError(from: error4 as! JSValue)
564+
try expectNotNil(errorObject2)
565+
566+
// MARK: Throwing constructor call
567+
let Animal = JSObject.global.Animal.function!
568+
_ = try Animal.throws.new("Tama", 3, true)
569+
let ageError = try expectThrow(try Animal.throws.new("Tama", -3, true))
570+
try expectEqual(ageError is JSValue, true)
571+
let errorObject3 = JSError(from: ageError as! JSValue)
572+
try expectNotNil(errorObject3)
573+
}

IntegrationTests/bin/primary-tests.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,23 @@ global.globalObject1 = {
3636
},
3737
prop_7: 3.14,
3838
prop_8: [0, , 2, 3, , , 6],
39+
prop_9: {
40+
func1: function () {
41+
throw new Error();
42+
},
43+
func2: function () {
44+
throw "String Error";
45+
},
46+
func3: function () {
47+
throw 3.0
48+
},
49+
},
3950
};
4051

4152
global.Animal = function (name, age, isCat) {
53+
if (age < 0) {
54+
throw new Error("Invalid age " + age);
55+
}
4256
this.name = name;
4357
this.age = age;
4458
this.bark = () => {

Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,25 @@ public class JSFunction: JSObject {
5050
}
5151
}
5252

53+
/// A modifier to call this function as a throwing function
54+
///
55+
///
56+
/// ```javascript
57+
/// function validateAge(age) {
58+
/// if (age < 0) {
59+
/// throw new Error("Invalid age");
60+
/// }
61+
/// }
62+
/// ```
63+
///
64+
/// ```swift
65+
/// let validateAge = JSObject.global.validateAge.function!
66+
/// try validateAge.throws(20)
67+
/// ```
68+
public var `throws`: JSThrowingFunction {
69+
JSThrowingFunction(self)
70+
}
71+
5372
/// A variadic arguments version of `new`.
5473
public func new(_ arguments: ConvertibleToJSValue...) -> JSObject {
5574
new(arguments: arguments)
@@ -109,16 +128,16 @@ public class JSThrowingFunction {
109128
let argv = bufferPointer.baseAddress
110129
let argc = bufferPointer.count
111130

112-
var exceptionKind: JavaScriptValueKind = .invalid
131+
var exceptionKind = JavaScriptValueKindAndFlags()
113132
var exceptionPayload1 = JavaScriptPayload1()
114133
var exceptionPayload2 = JavaScriptPayload2()
115134
var resultObj = JavaScriptObjectRef()
116135
_call_throwing_new(
117136
self.base.id, argv, Int32(argc),
118137
&resultObj, &exceptionKind, &exceptionPayload1, &exceptionPayload2
119138
)
120-
if exceptionKind != .invalid {
121-
let exception = RawJSValue(kind: exceptionKind, payload1: exceptionPayload1, payload2: exceptionPayload2)
139+
if exceptionKind.isException {
140+
let exception = RawJSValue(kind: exceptionKind.kind, payload1: exceptionPayload1, payload2: exceptionPayload2)
122141
return .failure(exception.jsValue())
123142
}
124143
return .success(JSObject(id: resultObj))

Sources/JavaScriptKit/FundamentalObjects/JSObject.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,27 @@ public class JSObject: Equatable {
7676
get { getJSValue(this: self, index: Int32(index)) }
7777
set { setJSValue(this: self, index: Int32(index), value: newValue) }
7878
}
79+
80+
/// A modifier to call methods as throwing methods capturing `this`
81+
///
82+
///
83+
/// ```javascript
84+
/// const animal = {
85+
/// validateAge: function() {
86+
/// if (this.age < 0) {
87+
/// throw new Error("Invalid age");
88+
/// }
89+
/// }
90+
/// }
91+
/// ```
92+
///
93+
/// ```swift
94+
/// let animal = JSObject.global.animal.object!
95+
/// try animal.throwing.validateAge!()
96+
/// ```
97+
public var `throwing`: JSThrowingObject {
98+
JSThrowingObject(self)
99+
}
79100

80101
/// Return `true` if this value is an instance of the passed `constructor` function.
81102
/// - Parameter constructor: The constructor function to check.
@@ -113,3 +134,32 @@ public class JSObject: Equatable {
113134
extension JSObject: CustomStringConvertible {
114135
public var description: String { self.toString!().string! }
115136
}
137+
138+
139+
/// A `JSObject` wrapper that enables throwing method calls capturing `this`.
140+
/// Exceptions produced by JavaScript functions will be thrown as `JSValue`.
141+
@dynamicMemberLookup
142+
public class JSThrowingObject {
143+
private let base: JSObject
144+
public init(_ base: JSObject) {
145+
self.base = base
146+
}
147+
148+
/// Returns the `name` member method binding this object as `this` context.
149+
/// - Parameter name: The name of this object's member to access.
150+
/// - Returns: The `name` member method binding this object as `this` context.
151+
@_disfavoredOverload
152+
public subscript(_ name: String) -> ((ConvertibleToJSValue...) throws -> JSValue)? {
153+
guard let function = base[name].function?.throws else { return nil }
154+
return { [base] (arguments: ConvertibleToJSValue...) in
155+
try function(this: base, arguments: arguments)
156+
}
157+
}
158+
159+
/// A convenience method of `subscript(_ name: String) -> ((ConvertibleToJSValue...) throws -> JSValue)?`
160+
/// to access the member through Dynamic Member Lookup.
161+
@_disfavoredOverload
162+
public subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) throws -> JSValue)? {
163+
self[name]
164+
}
165+
}

Sources/JavaScriptKit/XcodeSupport.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ import _CJavaScriptKit
7070
_: JavaScriptObjectRef,
7171
_: UnsafePointer<RawJSValue>!, _: Int32,
7272
_: UnsafeMutablePointer<JavaScriptObjectRef>!,
73-
_: UnsafeMutablePointer<JavaScriptValueKind>!,
73+
_: UnsafeMutablePointer<JavaScriptValueKindAndFlags>!,
7474
_: UnsafeMutablePointer<JavaScriptPayload1>!,
7575
_: UnsafeMutablePointer<JavaScriptPayload2>!
7676
) { fatalError() }

Sources/_CJavaScriptKit/include/_CJavaScriptKit.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ __attribute__((__import_module__("javascript_kit"),
214214
extern void _call_throwing_new(const JavaScriptObjectRef ref,
215215
const RawJSValue *argv, const int argc,
216216
JavaScriptObjectRef *result_obj,
217-
JavaScriptValueKind *exception_kind,
217+
JavaScriptValueKindAndFlags *exception_kind,
218218
JavaScriptPayload1 *exception_payload1,
219219
JavaScriptPayload2 *exception_payload2);
220220

0 commit comments

Comments
 (0)