From dbe04f1b1b10b5b61fc9460fa3d5d1e65823390d Mon Sep 17 00:00:00 2001 From: svenwiegand Date: Thu, 21 Apr 2016 20:45:36 +0200 Subject: [PATCH 01/11] ScopeOps are useful -- especially when writing unit tests it is helpful to add the ScopeOps trait to the test suite to be able to dynamically add stuff to the scope. --- src/main/scala/com/greencatsoft/angularjs/core/Scope.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/com/greencatsoft/angularjs/core/Scope.scala b/src/main/scala/com/greencatsoft/angularjs/core/Scope.scala index 70be12d..a699ae1 100755 --- a/src/main/scala/com/greencatsoft/angularjs/core/Scope.scala +++ b/src/main/scala/com/greencatsoft/angularjs/core/Scope.scala @@ -38,7 +38,6 @@ trait Scope extends js.Object { trait RootScope extends Scope trait ScopeOps { - this: Service => implicit class DynamicScope(scope: Scope) { From 4d6807888cac872c6e3bf0d3cacdebabe32c7383 Mon Sep 17 00:00:00 2001 From: svenwiegand Date: Thu, 21 Apr 2016 20:54:06 +0200 Subject: [PATCH 02/11] Now Angular.injector() is working (angular.injector expects a list of modules to respect for the injector). --- .../scala/com/greencatsoft/angularjs/internal/Angular.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/com/greencatsoft/angularjs/internal/Angular.scala b/src/main/scala/com/greencatsoft/angularjs/internal/Angular.scala index 513d108..bdfe283 100644 --- a/src/main/scala/com/greencatsoft/angularjs/internal/Angular.scala +++ b/src/main/scala/com/greencatsoft/angularjs/internal/Angular.scala @@ -15,7 +15,7 @@ import scala.scalajs.js.{ UndefOr, | } @js.native private[angularjs] trait Angular extends js.Object { - def injector(): Injector = js.native + def injector(modules: js.Array[String]): Injector = js.native def module(name: String): UndefOr[Module] = js.native From 25686538ec345d7226725981c0395e9a8748e743 Mon Sep 17 00:00:00 2001 From: svenwiegand Date: Thu, 21 Apr 2016 21:03:27 +0200 Subject: [PATCH 03/11] Now Angular.injector() is working (angular.injector expects a list of modules to respect for the injector). --- src/main/scala/com/greencatsoft/angularjs/Angular.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/scala/com/greencatsoft/angularjs/Angular.scala b/src/main/scala/com/greencatsoft/angularjs/Angular.scala index c420f06..9c7b246 100644 --- a/src/main/scala/com/greencatsoft/angularjs/Angular.scala +++ b/src/main/scala/com/greencatsoft/angularjs/Angular.scala @@ -2,9 +2,12 @@ package com.greencatsoft.angularjs import com.greencatsoft.angularjs.core.Injector import com.greencatsoft.angularjs.internal.GlobalDefinitions.angular +import com.greencatsoft.angularjs.internal.ServiceProxy import org.scalajs.dom.html.Element +import scala.language.experimental.macros import scala.language.implicitConversions +import scala.reflect.macros.blackbox.Context import scala.scalajs.js import scala.scalajs.js.JSConverters.genTravConvertible2JSRichGenTrav import scala.scalajs.js.| @@ -14,7 +17,7 @@ object Angular { def apply(name: String): Option[Module] = angular.module(name).toOption.map(new Module(_)) - def injector: Injector = angular.injector + def injector(modules: String*): Injector = angular.injector(modules.toJSArray) def module(name: String, dependencies: Seq[String] = Nil): Module = new Module(angular.module(name, dependencies.toJSArray)) From ce360dd6f15eef4e4d6f29c0b0f4a4ed91bc7198 Mon Sep 17 00:00:00 2001 From: svenwiegand Date: Thu, 21 Apr 2016 21:01:34 +0200 Subject: [PATCH 04/11] Typesafe and comfort injector. --- .../angularjs/core/Injector.scala | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/main/scala/com/greencatsoft/angularjs/core/Injector.scala b/src/main/scala/com/greencatsoft/angularjs/core/Injector.scala index 6a986f5..54bae00 100644 --- a/src/main/scala/com/greencatsoft/angularjs/core/Injector.scala +++ b/src/main/scala/com/greencatsoft/angularjs/core/Injector.scala @@ -1,11 +1,45 @@ package com.greencatsoft.angularjs.core import com.greencatsoft.angularjs.injectable +import com.greencatsoft.angularjs.internal.ServiceProxy +import scala.language.experimental.macros +import scala.reflect.macros.blackbox.Context import scala.scalajs.js @injectable("$injector") @js.native trait Injector extends js.Object { - def get(name: String): js.Object = js.native + def get[A](name: String): A = js.native } + +/** Supports easy, type safe injection of types annotated with `@injectable`. + * + * See example: + * {{{ + * implicit val injector = Angular.injector("ng", "myModule") + * val compile = Injector.get[Compile] + * val rootScope = Injector.get[RootScope] + * }}} + */ +object Injector { + /** Gets an instance of the specified type from the provided injector. + * + * Fails at compile time, if the type isn't annotated with `@injectable`. + * + * @param injector the injector to retrieve the type from + * @tparam A the type to return an instance of + * @return instance of the specified type provided by the injector + */ + def get[A](implicit injector: Injector): A = macro get_impl[A] + + def get_impl[A](c: Context)(injector: c.Expr[Injector])(implicit tag: c.WeakTypeTag[A]): c.Expr[A] = { + import c.universe._ + + val name = ServiceProxy.identifierFromType(c)(tag.tpe) getOrElse { + c.abort(c.enclosingPosition, s"The specified type '${tag.tpe}' does not have @injectable annotation.") + } + val nameExpr = c.Expr[String](q"$name") + reify { injector.splice.get[A](nameExpr.splice) } + } +} \ No newline at end of file From b927db46e87066a5fcdda46e35b8390dbaf0e280 Mon Sep 17 00:00:00 2001 From: svenwiegand Date: Thu, 21 Apr 2016 21:59:52 +0200 Subject: [PATCH 05/11] Merge branch 'master' of https://github.com/svenwiegand/scalajs-angular --- src/main/scala/com/greencatsoft/angularjs/Angular.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/scala/com/greencatsoft/angularjs/Angular.scala b/src/main/scala/com/greencatsoft/angularjs/Angular.scala index 9c7b246..132c73a 100644 --- a/src/main/scala/com/greencatsoft/angularjs/Angular.scala +++ b/src/main/scala/com/greencatsoft/angularjs/Angular.scala @@ -2,12 +2,9 @@ package com.greencatsoft.angularjs import com.greencatsoft.angularjs.core.Injector import com.greencatsoft.angularjs.internal.GlobalDefinitions.angular -import com.greencatsoft.angularjs.internal.ServiceProxy import org.scalajs.dom.html.Element -import scala.language.experimental.macros import scala.language.implicitConversions -import scala.reflect.macros.blackbox.Context import scala.scalajs.js import scala.scalajs.js.JSConverters.genTravConvertible2JSRichGenTrav import scala.scalajs.js.| From 4b03e70b75f833b8813395d6da799ab5c80bc18d Mon Sep 17 00:00:00 2001 From: svenwiegand Date: Sun, 24 Apr 2016 14:22:42 +0200 Subject: [PATCH 06/11] Helper to embed templates from HTML source files as `String`. For example load a template from a file relative to the current source file: {{{ val templateUrl = Template.relativeTemplate("./my-template.html") }}} If the following code is placed in `MyDirective.scala` the content of `MyDirective.html` from the same source directory will be embedded: {{{ val templateUrl = Template.companionTemplate }}} --- .../com/greencatsoft/angularjs/Template.scala | 34 ++++++++++++++ .../angularjs/internal/Template.scala | 46 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/main/scala/com/greencatsoft/angularjs/internal/Template.scala diff --git a/src/main/scala/com/greencatsoft/angularjs/Template.scala b/src/main/scala/com/greencatsoft/angularjs/Template.scala index 22118e2..82029e7 100644 --- a/src/main/scala/com/greencatsoft/angularjs/Template.scala +++ b/src/main/scala/com/greencatsoft/angularjs/Template.scala @@ -1,5 +1,7 @@ package com.greencatsoft.angularjs +import scala.language.experimental.macros + trait Templated { val templateUrl: String @@ -9,3 +11,35 @@ trait TemplateSource { val template: String } + +/** Helper to embed templates from HTML source files as `String`. + * + * For example load a template from a file relative to the current source file: {{{ + * val templateUrl = Template.relativeTemplate("./my-template.html") + * }}} + * + * If the following code is placed in `MyDirective.scala` the content of `MyDirective.html` from the same source + * directory will be embedded: {{{ + * val templateUrl = Template.companionTemplate + * }}} + */ +object Template { + /** Provides the content from the specified source file as `String` constant. + * + * Fails at compile time, if the specified file does not exist. + * + * @param relativePath path relative to the source file, this method is called from. + * @return the content of the specified file. + */ + def relativeTemplate(relativePath: String): String = macro internal.Template.relativeTemplate + + /** Provides the content from the companion template as `String` constant. + * The companion template is a file which is named exactly as the Scala source file, this method is called from, + * but with a `.html` extension instead of the `.scala` extension. + * + * Fails at compile time, if the specified file does not exist. + * + * @return the content of the companion template file. + */ + def companionTemplate: String = macro internal.Template.companionTemplate +} \ No newline at end of file diff --git a/src/main/scala/com/greencatsoft/angularjs/internal/Template.scala b/src/main/scala/com/greencatsoft/angularjs/internal/Template.scala new file mode 100644 index 0000000..d110d80 --- /dev/null +++ b/src/main/scala/com/greencatsoft/angularjs/internal/Template.scala @@ -0,0 +1,46 @@ +package com.greencatsoft.angularjs.internal + +import java.io.File + +import scala.io.Source +import scala.reflect.macros.blackbox.Context + +/** Macro implementation for [[com.greencatsoft.angularjs.Template]]. */ +private[angularjs] object Template { + def relativeTemplate(c: Context)(relativePath: c.Expr[String]): c.Expr[String] = { + import c.universe._ + + val Literal(Constant(relativePathConstant: String)) = relativePath.tree + val relativeTemplateFile = new File(sourceFile(c), relativePathConstant) + val content = fileAsString(c)(relativeTemplateFile) + c.Expr[String](q"$content") + } + + def companionTemplate(c: Context): c.Expr[String] = { + import c.universe._ + + val companionTemplateFile = { + val sourcePath = sourceFile(c).getAbsolutePath + val templatePath = if (sourcePath.endsWith(".scala")) + sourcePath.dropRight(".scala".length) + ".html" + else + c.abort(c.enclosingPosition, s"Companion templates are only supported for scala files. Current contest is $sourcePath.") + new File(templatePath) + } + val content = fileAsString(c)(companionTemplateFile) + c.Expr[String](q"$content") + } + + private def sourceFile(c: Context): File = + c.enclosingPosition.source.file.file + + private def fileAsString(c: Context)(file: File): String = { + if (!file.exists) + c.abort(c.enclosingPosition, s"No template found at ${file.getAbsolutePath}") + + val source = Source.fromFile(file) + val content = source.mkString + source.close() + content + } +} \ No newline at end of file From a31ace6cad179b47f7cec3dcaca6fec5bcfbaa7c Mon Sep 17 00:00:00 2001 From: svenwiegand Date: Tue, 17 May 2016 09:26:29 +0200 Subject: [PATCH 07/11] Make unit testing easier. --- .../com/greencatsoft/angularjs/Angular.scala | 4 ++ .../angularjs/internal/Angular.scala | 2 + .../angularjs/test/AngularMocks.scala | 17 ++++++ .../test/AngularTestEnvironment.scala | 55 +++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 src/main/scala/com/greencatsoft/angularjs/test/AngularMocks.scala create mode 100644 src/main/scala/com/greencatsoft/angularjs/test/AngularTestEnvironment.scala diff --git a/src/main/scala/com/greencatsoft/angularjs/Angular.scala b/src/main/scala/com/greencatsoft/angularjs/Angular.scala index 7c51961..213926f 100644 --- a/src/main/scala/com/greencatsoft/angularjs/Angular.scala +++ b/src/main/scala/com/greencatsoft/angularjs/Angular.scala @@ -17,10 +17,14 @@ object Angular { def apply(name: String): Option[Module] = angular.module(name).toOption.map(new Module(_)) + def bootstrap(element: Element, modules: String*): Injector = angular.bootstrap(element, modules.toJSArray) + def injector: Injector = angular.injector() def injector(modules: String*): Injector = angular.injector(modules.toJSArray) + def injector: Injector = angular.injector + def module(name: String, dependencies: Seq[String] = Nil): Module = new Module(angular.module(name, dependencies.toJSArray)) diff --git a/src/main/scala/com/greencatsoft/angularjs/internal/Angular.scala b/src/main/scala/com/greencatsoft/angularjs/internal/Angular.scala index 5a30f56..8728d9d 100644 --- a/src/main/scala/com/greencatsoft/angularjs/internal/Angular.scala +++ b/src/main/scala/com/greencatsoft/angularjs/internal/Angular.scala @@ -15,6 +15,8 @@ import scala.scalajs.js.{ UndefOr, | } @js.native private[angularjs] trait Angular extends js.Object { + def bootstrap(element: Element, modules: js.Array[String]): Injector = js.native + def injector(): Injector = js.native def injector(modules: js.Array[String]): Injector = js.native diff --git a/src/main/scala/com/greencatsoft/angularjs/test/AngularMocks.scala b/src/main/scala/com/greencatsoft/angularjs/test/AngularMocks.scala new file mode 100644 index 0000000..4caf714 --- /dev/null +++ b/src/main/scala/com/greencatsoft/angularjs/test/AngularMocks.scala @@ -0,0 +1,17 @@ +package com.greencatsoft.angularjs.test + +import com.greencatsoft.angularjs.core.Timeout +import com.greencatsoft.angularjs.injectable + +import scala.scalajs.js + +object AngularMocks { + val ModuleName = "ngMock" + + @js.native + @injectable("$timeout") + trait TimeoutMock extends Timeout { + + def flush(): Unit = js.native + } +} \ No newline at end of file diff --git a/src/main/scala/com/greencatsoft/angularjs/test/AngularTestEnvironment.scala b/src/main/scala/com/greencatsoft/angularjs/test/AngularTestEnvironment.scala new file mode 100644 index 0000000..9dc8b5b --- /dev/null +++ b/src/main/scala/com/greencatsoft/angularjs/test/AngularTestEnvironment.scala @@ -0,0 +1,55 @@ +package com.greencatsoft.angularjs.test + +import com.greencatsoft.angularjs.core.Injector +import com.greencatsoft.angularjs.{Angular, Module, internal} + +import scala.language.experimental.macros + +/** Provides an injector for your test suites. + * + * Setup for example like this: + * {{{ + * class MyDirectiveSpec extends FunSpec with AngularTestEnvironment with ScopeOps with MustMatchers { + * override val app = Angular.module("app", Seq("ngAnimate", "ngMaterial")).directive[MyDirective] + * override val appModuleName = "app" + * + * describe("MyDirective") { + * it("must render") { + * val scope = inject[RootScope].$new(true) + * scope.dynamic.greeting = "Hello World!" + * + * val tag = """""" + * val element = inject[Compile](tag)(scope, null) + * scope.$digest() + * + * element.textContent must be ("Hello World!") + * } + * } + * } + * }}} + */ +trait AngularTestEnvironment { + /** Your angular module to be used during the test. + * + * For example {{{Angular.module("app", Seq("ngAnimate", "ngMaterial")).directive[MyDirective]}}} + */ + val app: Module + + /** The name of your application module */ + val appModuleName: String + + /** Injector you can use in your tests to access services. + * + * You may want to use the `inject[A]` method for more readable code. + */ + implicit lazy val injector: Injector = { + val rootElement = org.scalajs.dom.document.documentElement + Angular.bootstrap(rootElement, appModuleName) + } + + /** Provides readable access to angular services. + * + * Example: {{{inject[RootScope].$new(true)}}} + */ + def inject[A](implicit injector: Injector): A = macro internal.Injector.get[A] +} From 0b64248457390d97e59761b25b37d32c373fffd5 Mon Sep 17 00:00:00 2001 From: svenwiegand Date: Tue, 24 May 2016 08:23:09 +0200 Subject: [PATCH 08/11] Make unit testing easier. --- src/main/scala/com/greencatsoft/angularjs/Angular.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/scala/com/greencatsoft/angularjs/Angular.scala b/src/main/scala/com/greencatsoft/angularjs/Angular.scala index 213926f..1c4c051 100644 --- a/src/main/scala/com/greencatsoft/angularjs/Angular.scala +++ b/src/main/scala/com/greencatsoft/angularjs/Angular.scala @@ -23,8 +23,6 @@ object Angular { def injector(modules: String*): Injector = angular.injector(modules.toJSArray) - def injector: Injector = angular.injector - def module(name: String, dependencies: Seq[String] = Nil): Module = new Module(angular.module(name, dependencies.toJSArray)) From 7614eaae109872ac52b14846e52f75b07173ba1b Mon Sep 17 00:00:00 2001 From: svenwiegand Date: Wed, 25 May 2016 08:09:23 +0200 Subject: [PATCH 09/11] Rolling back macro based templates (cherry picked from commit 930118e) --- .../com/greencatsoft/angularjs/Template.scala | 34 -------------- .../angularjs/internal/Template.scala | 46 ------------------- 2 files changed, 80 deletions(-) delete mode 100644 src/main/scala/com/greencatsoft/angularjs/internal/Template.scala diff --git a/src/main/scala/com/greencatsoft/angularjs/Template.scala b/src/main/scala/com/greencatsoft/angularjs/Template.scala index 82029e7..22118e2 100644 --- a/src/main/scala/com/greencatsoft/angularjs/Template.scala +++ b/src/main/scala/com/greencatsoft/angularjs/Template.scala @@ -1,7 +1,5 @@ package com.greencatsoft.angularjs -import scala.language.experimental.macros - trait Templated { val templateUrl: String @@ -11,35 +9,3 @@ trait TemplateSource { val template: String } - -/** Helper to embed templates from HTML source files as `String`. - * - * For example load a template from a file relative to the current source file: {{{ - * val templateUrl = Template.relativeTemplate("./my-template.html") - * }}} - * - * If the following code is placed in `MyDirective.scala` the content of `MyDirective.html` from the same source - * directory will be embedded: {{{ - * val templateUrl = Template.companionTemplate - * }}} - */ -object Template { - /** Provides the content from the specified source file as `String` constant. - * - * Fails at compile time, if the specified file does not exist. - * - * @param relativePath path relative to the source file, this method is called from. - * @return the content of the specified file. - */ - def relativeTemplate(relativePath: String): String = macro internal.Template.relativeTemplate - - /** Provides the content from the companion template as `String` constant. - * The companion template is a file which is named exactly as the Scala source file, this method is called from, - * but with a `.html` extension instead of the `.scala` extension. - * - * Fails at compile time, if the specified file does not exist. - * - * @return the content of the companion template file. - */ - def companionTemplate: String = macro internal.Template.companionTemplate -} \ No newline at end of file diff --git a/src/main/scala/com/greencatsoft/angularjs/internal/Template.scala b/src/main/scala/com/greencatsoft/angularjs/internal/Template.scala deleted file mode 100644 index d110d80..0000000 --- a/src/main/scala/com/greencatsoft/angularjs/internal/Template.scala +++ /dev/null @@ -1,46 +0,0 @@ -package com.greencatsoft.angularjs.internal - -import java.io.File - -import scala.io.Source -import scala.reflect.macros.blackbox.Context - -/** Macro implementation for [[com.greencatsoft.angularjs.Template]]. */ -private[angularjs] object Template { - def relativeTemplate(c: Context)(relativePath: c.Expr[String]): c.Expr[String] = { - import c.universe._ - - val Literal(Constant(relativePathConstant: String)) = relativePath.tree - val relativeTemplateFile = new File(sourceFile(c), relativePathConstant) - val content = fileAsString(c)(relativeTemplateFile) - c.Expr[String](q"$content") - } - - def companionTemplate(c: Context): c.Expr[String] = { - import c.universe._ - - val companionTemplateFile = { - val sourcePath = sourceFile(c).getAbsolutePath - val templatePath = if (sourcePath.endsWith(".scala")) - sourcePath.dropRight(".scala".length) + ".html" - else - c.abort(c.enclosingPosition, s"Companion templates are only supported for scala files. Current contest is $sourcePath.") - new File(templatePath) - } - val content = fileAsString(c)(companionTemplateFile) - c.Expr[String](q"$content") - } - - private def sourceFile(c: Context): File = - c.enclosingPosition.source.file.file - - private def fileAsString(c: Context)(file: File): String = { - if (!file.exists) - c.abort(c.enclosingPosition, s"No template found at ${file.getAbsolutePath}") - - val source = Source.fromFile(file) - val content = source.mkString - source.close() - content - } -} \ No newline at end of file From 4822e31d14d598001e4c477d15f6d5d39d7562c3 Mon Sep 17 00:00:00 2001 From: svenwiegand Date: Sun, 29 May 2016 14:26:50 +0200 Subject: [PATCH 10/11] Code style --- .../angularjs/test/AngularTestEnvironment.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/scala/com/greencatsoft/angularjs/test/AngularTestEnvironment.scala b/src/main/scala/com/greencatsoft/angularjs/test/AngularTestEnvironment.scala index 9dc8b5b..d2066b4 100644 --- a/src/main/scala/com/greencatsoft/angularjs/test/AngularTestEnvironment.scala +++ b/src/main/scala/com/greencatsoft/angularjs/test/AngularTestEnvironment.scala @@ -2,6 +2,7 @@ package com.greencatsoft.angularjs.test import com.greencatsoft.angularjs.core.Injector import com.greencatsoft.angularjs.{Angular, Module, internal} +import org.scalajs.dom.document import scala.language.experimental.macros @@ -33,18 +34,18 @@ trait AngularTestEnvironment { * * For example {{{Angular.module("app", Seq("ngAnimate", "ngMaterial")).directive[MyDirective]}}} */ - val app: Module + val module: Module /** The name of your application module */ - val appModuleName: String + val moduleName: String /** Injector you can use in your tests to access services. * * You may want to use the `inject[A]` method for more readable code. */ implicit lazy val injector: Injector = { - val rootElement = org.scalajs.dom.document.documentElement - Angular.bootstrap(rootElement, appModuleName) + val rootElement = document.documentElement + Angular.bootstrap(rootElement, moduleName) } /** Provides readable access to angular services. From 2650fa32fa51c601c38617f8d58b1990f58bf9dc Mon Sep 17 00:00:00 2001 From: svenwiegand Date: Sun, 29 May 2016 14:44:33 +0200 Subject: [PATCH 11/11] Fixed ScalaDoc --- .../greencatsoft/angularjs/test/AngularTestEnvironment.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/com/greencatsoft/angularjs/test/AngularTestEnvironment.scala b/src/main/scala/com/greencatsoft/angularjs/test/AngularTestEnvironment.scala index d2066b4..89dcda8 100644 --- a/src/main/scala/com/greencatsoft/angularjs/test/AngularTestEnvironment.scala +++ b/src/main/scala/com/greencatsoft/angularjs/test/AngularTestEnvironment.scala @@ -11,8 +11,8 @@ import scala.language.experimental.macros * Setup for example like this: * {{{ * class MyDirectiveSpec extends FunSpec with AngularTestEnvironment with ScopeOps with MustMatchers { - * override val app = Angular.module("app", Seq("ngAnimate", "ngMaterial")).directive[MyDirective] - * override val appModuleName = "app" + * override val module = Angular.module("app", Seq("ngAnimate", "ngMaterial")).directive[MyDirective] + * override val moduleName = "app" * * describe("MyDirective") { * it("must render") {