diff options
Diffstat (limited to 'examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/InteroperabilityTest.scala')
-rw-r--r-- | examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/InteroperabilityTest.scala | 528 |
1 files changed, 528 insertions, 0 deletions
diff --git a/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/InteroperabilityTest.scala b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/InteroperabilityTest.scala new file mode 100644 index 0000000..594345a --- /dev/null +++ b/examples/scala-js/test-suite/src/test/scala/scala/scalajs/testsuite/compiler/InteroperabilityTest.scala @@ -0,0 +1,528 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js Test Suite ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ +package scala.scalajs.testsuite.compiler + +import scala.scalajs.js +import org.scalajs.jasminetest.{JasmineTest, JasmineTestFramework} +import scala.scalajs.js.annotation._ + +/* + * Based on examples in: + * http://lampwww.epfl.ch/~doeraene/scala-js/doc/js-interoperability.html + */ +object InteroperabilityTest extends JasmineTest { + + describe("JavaScript interoperability") { + + it("should support backquotes to escape Scala fields") { + val obj = js.eval(""" + var interoperabilityTestFieldEscape = { + def: 0, + val: function(x) { if (x) this.def = x; return this.def; } + }; + interoperabilityTestFieldEscape; + """).asInstanceOf[InteroperabilityTestFieldEscape] + + obj.`def` = 7357 + expect(obj.`def`).toEqual(7357) + expect(obj.`val`()).toEqual(7357) + expect(obj.`val`(42)).toEqual(42) + } + + it("should support @JSName to specify the JavaScript name for fields") { + val obj = js.eval(""" + var interoperabilityTestJSName = { + def: 42, + val: function(x) { if (x) this.def = x; return this.def; } + }; + interoperabilityTestJSName; + """).asInstanceOf[InteroperabilityTestJSName] + + expect(obj.value()).toEqual(42) + expect(obj.value(7357)).toEqual(7357) + } + + it("should translate explicit getter and setter names to field access") { + val obj = js.eval(""" + var interoperabilityTestProperty = { a: 1 }; + interoperabilityTestProperty; + """).asInstanceOf[InteroperabilityTestProperty] + + expect(obj.a).toEqual(1) + obj.a = 100 + expect(obj.a).toEqual(100) + } + + it("should support @JSName together with field access") { + val obj = js.eval(""" + var interoperabilityTestProperty = { b: 1 }; + interoperabilityTestProperty; + """).asInstanceOf[InteroperabilityTestPropertyNamed] + + expect(obj.a).toEqual(1) + obj.a = 100 + expect(obj.a).toEqual(100) + expect(obj.b).toEqual(100) + } + + it("should support @JSBracketAccess to specify access using []-subscription") { + val obj = js.eval(""" + var interoperabilityTestJSBracketAccess = [ 0, 1, 7357 ]; + interoperabilityTestJSBracketAccess; + """).asInstanceOf[InteroperabilityTestJSBracketAccess] + + expect(obj(2)).toEqual(7357) + obj(2) = 42 + expect(obj(2)).toEqual(42) + } + + it("should allow instanciation of JS classes inheriting from js.Object") { + js.eval(""" + var InteroperabilityTestInherit = { + Pattern: function(x) { + this.field = 42; + this.method = function() { + return '42'; + }; + this.getConstructorParam = function() { + return x; + }; + } + } + """) + + val obj = new InteroperabilityTestPattern("Scala.js") + expect(obj.field).toEqual(42) + expect(obj.method).toEqual("42") + expect(obj.getConstructorParam).toEqual("Scala.js") + } + + it("should acces top-level JS objects via Scala objects inheriting from js.Object") { + js.eval(""" + var InteroperabilityTestTopLevelObject = function(value) { + return { + value: value, + valueAsInt: function() { + return parseInt(value); + } + }; + } + """) + + // Use alias for convenience: see end of file for definition + val TopLevel = InteroperabilityTestTopLevel + + val obj = TopLevel("7357") + expect(obj.value).toEqual("7357") + expect(obj.valueAsInt).toEqual(7357) + } + + it("should allow to call JS methods with variadic parameters") { + val obj = js.eval(""" + var obj = { + foo: function() { + var args = new Array(arguments.length); + for (var i = 0; i < arguments.length; i++) + args[i] = arguments[i]; + return args; + } + }; + obj; + """) + + val elems = Seq[js.Any]("plop", 42, 51) + + val dyn = obj.asInstanceOf[js.Dynamic] + expect(dyn.foo()).toEqual(js.Array()) + expect(dyn.foo(3, 6)).toEqual(js.Array(3, 6)) + expect(dyn.foo("hello", false)).toEqual(js.Array("hello", false)) + expect(dyn.applyDynamic("foo")(elems: _*)).toEqual(js.Array("plop", 42, 51)) + + val stat = obj.asInstanceOf[InteroperabilityTestVariadicMethod] + expect(stat.foo()).toEqual(js.Array()) + expect(stat.foo(3, 6)).toEqual(js.Array(3, 6)) + expect(stat.foo("hello", false)).toEqual(js.Array("hello", false)) + expect(stat.foo(elems: _*)).toEqual(js.Array("plop", 42, 51)) + } + + it("should allow to call JS constructors with variadic parameters") { + import js.Dynamic.{newInstance => jsnew} + + js.eval(""" + var InteroperabilityTestVariadicCtor = function() { + var args = new Array(arguments.length); + for (var i = 0; i < arguments.length; i++) + args[i] = arguments[i]; + this.args = args; + }; + """) + + val elems = Seq[js.Any]("plop", 42, 51) + + val ctor = js.Dynamic.global.InteroperabilityTestVariadicCtor + expect(jsnew(ctor)().args).toEqual(js.Array()) + expect(jsnew(ctor)(3, 6).args).toEqual(js.Array(3, 6)) + expect(jsnew(ctor)("hello", false).args).toEqual(js.Array("hello", false)) + expect(jsnew(ctor)(elems: _*).args).toEqual(js.Array("plop", 42, 51)) + + import scala.scalajs.testsuite.compiler.{InteroperabilityTestVariadicCtor => C} + expect(new C().args).toEqual(js.Array()) + expect(new C(3, 6).args).toEqual(js.Array(3, 6)) + expect(new C("hello", false).args).toEqual(js.Array("hello", false)) + expect(new C(elems: _*).args).toEqual(js.Array("plop", 42, 51)) + } + + it("should acces top-level JS objects via Scala object inheriting from js.GlobalScope") { + js.eval(""" + var interoperabilityTestGlobalScopeValue = "7357"; + var interoperabilityTestGlobalScopeValueAsInt = function() { + return parseInt(interoperabilityTestGlobalScopeValue); + }; + """) + + // Use alias for convenience: see end of file for definition + val Global = InteroperabilityTestGlobalScope + + expect(Global.interoperabilityTestGlobalScopeValue).toEqual("7357") + expect(Global.interoperabilityTestGlobalScopeValueAsInt).toEqual(7357) + + Global.interoperabilityTestGlobalScopeValue = "42" + expect(Global.interoperabilityTestGlobalScopeValueAsInt).toEqual(42) + } + + it("should protect receiver of raw JS apply if it's a select - #804") { + val rawReceiver = js.eval(""" + var interoperabilityTestRawReceiver = { + member: 0xbad, + check: function(raw) { return this.member ? this.member : raw; } + }; + interoperabilityTestRawReceiver; + """).asInstanceOf[InteroperabilityTestRawReceiver] + + expect(rawReceiver.check(7357)).toEqual(7357) + + val check = rawReceiver.check + expect(check(0x600d)).toEqual(0x600d) + + class InScalaSelect(check: js.Function1[Int, Int]) { + @JSExport + val member: Int = 0xbad2 + def test(): Unit = expect(check(5894)).toEqual(5894) + } + new InScalaSelect(check).test() + } + + it("should properly handle default parameters") { + val obj = js.eval(""" + var interoperabilityTestDefaultParam = { + fun: function() { return arguments; } + }; + interoperabilityTestDefaultParam; + """).asInstanceOf[InteroperabilityTestDefaultParam] + + // Helpers + import js.Dynamic.{literal => args} + val undef = js.undefined + + expect(obj.simple(1)).toEqual(args(`0` = 1)) + expect(obj.simple(1,5)).toEqual(args(`0` = 1, `1` = 5)) + expect(obj.named(y = 5)).toEqual(args(`0` = undef, `1` = 5)) + expect(obj.named(x = 5)).toEqual(args(`0` = 5)) + expect(obj.multi()(1,2,3,4)()).toEqual(args( + `0` = undef, + `1` = 1, + `2` = 2, + `3` = 3, + `4` = 4)) + expect(obj.multi(2)()(5)).toEqual(args( + `0` = 2, + `1` = 5)) + } + + it("should properly handle default parameters for constructors - #791") { + js.eval(""" + var InteroperabilityTestCtor = function(x,y) { + this.values = Array(x || 6, y || 8) + } + """); + + expect(new InteroperabilityTestCtor().values).toEqual(js.Array(6,8)) + expect(new InteroperabilityTestCtor(y = 7).values).toEqual(js.Array(6,7)) + expect(new InteroperabilityTestCtor(3).values).toEqual(js.Array(3,8)) + expect(new InteroperabilityTestCtor(10, 2).values).toEqual(js.Array(10,2)) + } + + it("should generate exports for methods inherited from traits - #178") { + import js.annotation.JSExport + + trait Foo { + @JSExport + def theValue = 1 + } + class Bar extends Foo + + val x = (new Bar).asInstanceOf[js.Dynamic] + + // Call the export by using js.Dynamic + expect(x.theValue).toEqual(1) + } + + it("should allow constructor params that are vals/vars in facades - #1277") { + js.eval(""" + var InteroparabilityCtorInlineValue = function(x,y) { + this.x = x; + this.y = y; + } + """) + + val obj = new InteroparabilityCtorInlineValue(10, -1) + + expect(obj.x).toEqual(10) + expect(obj.y).toEqual(-1) + + obj.y = 100 + + expect(obj.x).toEqual(10) + expect(obj.y).toEqual(100) + } + + it("should unbox Chars received from calling a JS interop method") { + val obj = js.eval(""" + var obj = { + get: function() { return JSUtils().stringToChar('e'); } + }; + obj; + """).asInstanceOf[InteroperabilityTestCharResult] + + expect(obj.get().toInt).toEqual('e'.toInt) + } + + it("should box Chars given to a JS interop method") { + val obj = js.eval(""" + var obj = { + twice: function(c) { c = JSUtils().charToString(c); return c+c; } + }; + obj; + """).asInstanceOf[InteroperabilityTestCharParam] + + expect(obj.twice('x')).toEqual("xx") + } + + it("should unbox value classes received from calling a JS interop method") { + val obj = js.eval(""" + var obj = { + test: function(vc) { return vc; } + }; + obj; + """).asInstanceOf[InteroperabilityTestValueClassResult] + + val r = obj.test(new SomeValueClass(5)) + expect(r.i).toEqual(5) + } + + it("should box value classes given to a JS interop method") { + val obj = js.eval(""" + var obj = { + stringOf: function(vc) { return vc.toString(); } + }; + obj; + """).asInstanceOf[InteroperabilityTestValueClassParam] + + val vc = new SomeValueClass(7) + expect(obj.stringOf(vc)).toEqual("SomeValueClass(7)") + } + + it("should not unbox values received from JS method in statement position") { + /* To test this, we verify that a purposefully ill-typed facade does not + * throw a ClassCastException when called in statement position. + */ + val obj = js.eval(""" + var obj = { + test: function() { return 4; } // typed as String in the trait + }; + obj; + """).asInstanceOf[InteroperabilityTestNoUnboxResultInStatement] + obj.test() // in statement position, should not throw + if (JasmineTestFramework.hasTag("compliant-asinstanceof")) + expect(() => obj.test()).toThrow // in expression position, should throw + } + + when("compliant-asinstanceof"). + it("should protect conversions from JS types to Scala types") { + class Foo + val foo: Any = new Foo + + val invalidNumber: js.prim.Number = foo.asInstanceOf[js.prim.Number] + val nullNumber: js.prim.Number = null + expect(() => invalidNumber: Double).toThrow + expect(nullNumber: Double).toEqual(0) + + val invalidBoolean: js.prim.Boolean = foo.asInstanceOf[js.prim.Boolean] + val nullBoolean: js.prim.Boolean = null + expect(() => invalidBoolean: Boolean).toThrow + expect(nullBoolean: Boolean).toEqual(false) + + val invalidString: js.prim.String = foo.asInstanceOf[js.prim.String] + val nullString: js.prim.String = null + expect(() => invalidString: String).toThrow + expect(nullString: String).toBeNull + } + + when("compliant-asinstanceof"). + it("should asInstanceOf values received from calling a JS interop method") { + val obj = js.eval(""" + var obj = { + testChar: function() { return 5; }, + testInt: function() { return 6.4; }, + testShort: function() { return 60000; }, + testDouble: function() { return JSUtils().stringToChar('e'); }, + testString: function() { return {}; }, + testValueClass: function() { return "hello"; }, + testNormalClass: function() { return 45; }, + testAny: function() { return {}; } + }; + obj; + """).asInstanceOf[InteroperabilityTestAsInstanceOfResult] + + expect(() => obj.testChar()).toThrow + expect(() => obj.testInt()).toThrow + expect(() => obj.testShort()).toThrow + expect(() => obj.testDouble()).toThrow + expect(() => obj.testString()).toThrow + expect(() => obj.testValueClass()).toThrow + expect(() => obj.testNormalClass()).toThrow + expect(() => obj.testAny()).not.toThrow + } + + } + + trait InteroperabilityTestFieldEscape extends js.Object { + var `def`: Int = js.native + def `val`(): Int = js.native + def `val`(n: Int): Int = js.native + } + + trait InteroperabilityTestJSName extends js.Object { + @JSName("val") + def value(): Int = js.native + @JSName("val") + def value(n: Int): Int = js.native + } + + trait InteroperabilityTestProperty extends js.Object { + def a_=(x: Int): Unit = js.native + def a: Int = js.native + } + + trait InteroperabilityTestPropertyNamed extends js.Object { + @JSName("b") + def a_=(x: Int): Unit = js.native + @JSName("b") + def a: Int = js.native + def b: Int = js.native + } + + trait InteroperabilityTestJSBracketAccess extends js.Object { + @JSBracketAccess + def apply(index: Int): Int = js.native + @JSBracketAccess + def update(index: Int, v: Int): Unit = js.native + } + + trait InteroperabilityTestRawReceiver extends js.Object { + val check: js.Function1[Int, Int] = js.native + } + + /** Trait with different method signatures, all forwarded to the same + * JS raw function that returns the argument list for inspection + */ + trait InteroperabilityTestDefaultParam extends js.Object { + @JSName("fun") + def simple(x: Int, y: Int = 5): js.Any = js.native + @JSName("fun") + def named(x: Int = 1, y: Int = 1, z: Int = 1): js.Any = js.native + @JSName("fun") + def multi(x: Int = 1)(ys: Int*)(z: Int = 1): js.Any = js.native + } + + trait InteroperabilityTestCharResult extends js.Object { + def get(): Char = js.native + } + + trait InteroperabilityTestCharParam extends js.Object { + def twice(c: Char): String = js.native + } + + trait InteroperabilityTestValueClassResult extends js.Object { + def test(vc: Any): SomeValueClass = js.native + } + + trait InteroperabilityTestValueClassParam extends js.Object { + def stringOf(vc: SomeValueClass): String = js.native + } + + trait InteroperabilityTestNoUnboxResultInStatement extends js.Object { + def test(): String = js.native + } + + trait InteroperabilityTestAsInstanceOfResult extends js.Object { + def testChar(): Char = js.native + def testInt(): Int = js.native + def testShort(): Short = js.native + def testDouble(): Double = js.native + def testString(): String = js.native + def testValueClass(): SomeValueClass = js.native + def testNormalClass(): List[Int] = js.native + def testAny(): Any = js.native + } +} + +/* + * Helper classes, traits and objects defined here since they cannot be nested. + */ + +@JSName("InteroperabilityTestInherit.Pattern") +class InteroperabilityTestPattern protected () extends js.Object { + def this(pattern: String) = this() + val field: Int = js.native + def method(): String = js.native + def getConstructorParam(): String = js.native +} + +trait InteroperabilityTestTopLevel extends js.Object { + val value: String = js.native + def valueAsInt(): Int = js.native +} + +@JSName("InteroperabilityTestTopLevelObject") +object InteroperabilityTestTopLevel extends js.Object { + def apply(value: String): InteroperabilityTestTopLevel = js.native +} + +trait InteroperabilityTestVariadicMethod extends js.Object { + def foo(args: Any*): js.Array[Any] = js.native +} + +class InteroperabilityTestVariadicCtor(inargs: Any*) extends js.Object { + val args: js.Array[Any] = js.native +} + +object InteroperabilityTestGlobalScope extends js.GlobalScope { + var interoperabilityTestGlobalScopeValue: String = js.native + def interoperabilityTestGlobalScopeValueAsInt(): Int = js.native +} + +class SomeValueClass(val i: Int) extends AnyVal { + override def toString(): String = s"SomeValueClass($i)" +} + +class InteroperabilityTestCtor(x: Int = 5, y: Int = ???) extends js.Object { + def values: js.Array[Int] = js.native +} + +class InteroparabilityCtorInlineValue(val x: Int, var y: Int) extends js.Object |