PageRenderTime 46ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/subprojects/platform-play/src/integTest/groovy/org/gradle/play/tasks/TwirlCompileIntegrationTest.groovy

http://github.com/gradle/gradle
Groovy | 594 lines | 517 code | 60 blank | 17 comment | 2 complexity | 75c3e095b96ba05317537afd590f1864 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, LGPL-2.1
  1. /*
  2. * Copyright 2014 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.gradle.play.tasks
  17. import org.gradle.integtests.fixtures.ToBeFixedForInstantExecution
  18. import org.gradle.play.integtest.fixtures.PlayMultiVersionIntegrationTest
  19. import org.gradle.test.fixtures.archive.JarTestFixture
  20. import org.gradle.util.VersionNumber
  21. import spock.lang.Unroll
  22. import static org.gradle.play.integtest.fixtures.Repositories.PLAY_REPOSITORIES
  23. import static org.hamcrest.CoreMatchers.containsString
  24. class TwirlCompileIntegrationTest extends PlayMultiVersionIntegrationTest {
  25. def destinationDir = file("build/src/play/binary/twirlTemplatesScalaSources/views")
  26. def setup() {
  27. settingsFile << """ rootProject.name = 'twirl-play-app' """
  28. buildFile << """
  29. plugins {
  30. id 'play-application'
  31. }
  32. ${PLAY_REPOSITORIES}
  33. model {
  34. components {
  35. play {
  36. targetPlatform "play-${version}"
  37. }
  38. }
  39. }
  40. """
  41. }
  42. @Unroll
  43. def "can run TwirlCompile with #format template"() {
  44. given:
  45. twirlTemplate("test.scala.${format}") << template
  46. when:
  47. succeeds("compilePlayBinaryScala")
  48. then:
  49. def generatedFile = destinationDir.file("${format}/test.template.scala")
  50. generatedFile.assertIsFile()
  51. generatedFile.assertContents(containsString("import views.${format}._"))
  52. generatedFile.assertContents(containsString(templateFormat))
  53. where:
  54. format | templateFormat | template
  55. "js" | 'JavaScriptFormat' | '@(username: String) alert(@helper.json(username));'
  56. "xml" | 'XmlFormat' | '@(username: String) <xml> <username> @username </username>'
  57. "txt" | 'TxtFormat' | '@(username: String) @username'
  58. "html" | 'HtmlFormat' | '@(username: String) <html> <body> <h1>Hello @username</h1> </body> </html>'
  59. }
  60. def "can compile custom Twirl templates"() {
  61. given:
  62. twirlTemplate("test.scala.csv") << """
  63. @(username: String)(content: Csv)
  64. # generated by @username
  65. @content
  66. """
  67. addCsvFormat()
  68. when:
  69. succeeds("compilePlayBinaryScala")
  70. then:
  71. def generatedFile = destinationDir.file("csv/test.template.scala")
  72. generatedFile.assertIsFile()
  73. generatedFile.assertContents(containsString("import views.formats.csv._"))
  74. generatedFile.assertContents(containsString("CsvFormat"))
  75. // Modifying user templates causes TwirlCompile to be out-of-date
  76. when:
  77. executer.noDeprecationChecks()
  78. buildFile << """
  79. model {
  80. components {
  81. play {
  82. sources {
  83. withType(TwirlSourceSet) {
  84. addUserTemplateFormat("unused", "views.formats.unused.UnusedFormat")
  85. }
  86. }
  87. }
  88. }
  89. }
  90. """
  91. and:
  92. succeeds("compilePlayBinaryScala")
  93. then:
  94. result.assertTasksNotSkipped(":compilePlayBinaryPlayTwirlTemplates", ":compilePlayBinaryScala")
  95. }
  96. def "can specify additional imports for a Twirl template"() {
  97. given:
  98. withTwirlTemplate()
  99. buildFile << """
  100. model {
  101. components {
  102. play {
  103. sources {
  104. twirlTemplates {
  105. additionalImports = [ 'my.pkg._' ]
  106. }
  107. }
  108. }
  109. }
  110. }
  111. """
  112. file("app/my/pkg/MyClass.scala") << """
  113. package my.pkg
  114. object MyClass;
  115. """
  116. when:
  117. succeeds("compilePlayBinaryScala")
  118. then:
  119. def generatedFile = destinationDir.file("html/index.template.scala")
  120. generatedFile.assertIsFile()
  121. generatedFile.assertContents(containsString("import my.pkg._"))
  122. // Changing the imports causes TwirlCompile to be out-of-date
  123. when:
  124. executer.noDeprecationChecks()
  125. buildFile << """
  126. model {
  127. components {
  128. play {
  129. sources {
  130. twirlTemplates {
  131. additionalImports = [ 'my.pkg._', 'my.pkg.MyClass' ]
  132. }
  133. }
  134. }
  135. }
  136. }
  137. """
  138. and:
  139. succeeds("compilePlayBinaryScala")
  140. then:
  141. result.assertTasksNotSkipped(":compilePlayBinaryPlayTwirlTemplates", ":compilePlayBinaryScala")
  142. generatedFile.assertContents(containsString("import my.pkg._"))
  143. generatedFile.assertContents(containsString("import my.pkg.MyClass"))
  144. }
  145. @ToBeFixedForInstantExecution
  146. def "runs compiler incrementally"() {
  147. when:
  148. withTwirlTemplate("input1.scala.html")
  149. then:
  150. succeeds("compilePlayBinaryPlayTwirlTemplates")
  151. and:
  152. destinationDir.assertHasDescendants("html/input1.template.scala")
  153. def input1FirstCompileSnapshot = destinationDir.file("html/input1.template.scala").snapshot()
  154. when:
  155. executer.noDeprecationChecks()
  156. succeeds("compilePlayBinaryPlayTwirlTemplates")
  157. then:
  158. skipped(":compilePlayBinaryPlayTwirlTemplates")
  159. when:
  160. executer.noDeprecationChecks()
  161. withTwirlTemplate("input2.scala.html")
  162. and:
  163. succeeds("compilePlayBinaryPlayTwirlTemplates")
  164. then:
  165. destinationDir.assertHasDescendants("html/input1.template.scala", "html/input2.template.scala")
  166. and:
  167. destinationDir.file("html/input1.template.scala").assertHasNotChangedSince(input1FirstCompileSnapshot)
  168. when:
  169. executer.noDeprecationChecks()
  170. file("app/views/input2.scala.html").delete()
  171. then:
  172. succeeds("compilePlayBinaryPlayTwirlTemplates")
  173. and:
  174. destinationDir.assertHasDescendants("html/input1.template.scala")
  175. }
  176. @ToBeFixedForInstantExecution
  177. def "removes stale output files in incremental compile"(){
  178. given:
  179. withTwirlTemplate("input1.scala.html")
  180. withTwirlTemplate("input2.scala.html")
  181. succeeds("compilePlayBinaryPlayTwirlTemplates")
  182. and:
  183. destinationDir.assertHasDescendants("html/input1.template.scala", "html/input2.template.scala")
  184. def input1FirstCompileSnapshot = destinationDir.file("html/input1.template.scala").snapshot()
  185. when:
  186. executer.noDeprecationChecks()
  187. file("app/views/input2.scala.html").delete()
  188. then:
  189. succeeds("compilePlayBinaryPlayTwirlTemplates")
  190. and:
  191. destinationDir.assertHasDescendants("html/input1.template.scala")
  192. destinationDir.file("html/input1.template.scala").assertHasNotChangedSince(input1FirstCompileSnapshot)
  193. destinationDir.file("html/input2.template.scala").assertDoesNotExist()
  194. }
  195. def "builds multiple twirl source sets as part of play build" () {
  196. withExtraSourceSets()
  197. withTemplateSource(file("app", "views", "index.scala.html"))
  198. withTemplateSource(file("otherSources", "templates", "other.scala.html"))
  199. withTemplateSource(file("extraSources", "extra.scala.html"))
  200. when:
  201. succeeds "assemble"
  202. then:
  203. executedAndNotSkipped(
  204. ":compilePlayBinaryPlayTwirlTemplates",
  205. ":compilePlayBinaryPlayExtraTwirl",
  206. ":compilePlayBinaryPlayOtherTwirl"
  207. )
  208. and:
  209. destinationDir.assertHasDescendants("html/index.template.scala")
  210. file("build/src/play/binary/otherTwirlScalaSources").assertHasDescendants("templates/html/other.template.scala")
  211. file("build/src/play/binary/extraTwirlScalaSources").assertHasDescendants("html/extra.template.scala")
  212. and:
  213. jar("build/playBinary/lib/twirl-play-app.jar")
  214. .containsDescendants("views/html/index.class", "templates/html/other.class", "html/extra.class")
  215. }
  216. @ToBeFixedForInstantExecution
  217. def "can build twirl source set with default Java imports" () {
  218. withTwirlJavaSourceSets()
  219. withTemplateSourceExpectingJavaImports(file("twirlJava", "javaTemplate.scala.html"))
  220. validateThatPlayJavaDependencyIsAdded()
  221. when:
  222. succeeds "assemble"
  223. then:
  224. executedAndNotSkipped ":compilePlayBinaryPlayTwirlJava"
  225. and:
  226. jar("build/playBinary/lib/twirl-play-app.jar")
  227. .containsDescendants("html/javaTemplate.class")
  228. }
  229. def "can build twirl source sets both with and without default Java imports" () {
  230. withTwirlJavaSourceSets()
  231. withTemplateSource(file("app", "views", "index.scala.html"))
  232. withTemplateSourceExpectingJavaImports(file("twirlJava", "javaTemplate.scala.html"))
  233. when:
  234. succeeds "assemble"
  235. then:
  236. executedAndNotSkipped(
  237. ":compilePlayBinaryPlayTwirlTemplates",
  238. ":compilePlayBinaryPlayTwirlJava"
  239. )
  240. and:
  241. jar("build/playBinary/lib/twirl-play-app.jar")
  242. .containsDescendants("html/javaTemplate.class", "views/html/index.class")
  243. }
  244. @ToBeFixedForInstantExecution
  245. def "twirl source sets default to Scala imports" () {
  246. withTemplateSource(file("app", "views", "index.scala.html"))
  247. validateThatPlayJavaDependencyIsNotAdded()
  248. validateThatSourceSetsDefaultToScalaImports()
  249. when:
  250. succeeds "assemble"
  251. then:
  252. executedAndNotSkipped ":compilePlayBinaryPlayTwirlTemplates"
  253. }
  254. @ToBeFixedForInstantExecution(because = ":components")
  255. def "extra sources appear in the component report"() {
  256. withExtraSourceSets()
  257. when:
  258. succeeds "components"
  259. then:
  260. output.contains """
  261. Play Application 'play'
  262. -----------------------
  263. Source sets
  264. Java source 'play:java'
  265. srcDir: app
  266. includes: **/*.java
  267. JVM resources 'play:resources'
  268. srcDir: conf
  269. Routes source 'play:routes'
  270. srcDir: conf
  271. includes: routes, *.routes
  272. Scala source 'play:scala'
  273. srcDir: app
  274. includes: **/*.scala
  275. Twirl template source 'play:extraTwirl'
  276. srcDir: extraSources
  277. Twirl template source 'play:otherTwirl'
  278. srcDir: otherSources
  279. Twirl template source 'play:twirlTemplates'
  280. srcDir: app
  281. includes: **/*.scala.*
  282. Binaries
  283. """
  284. }
  285. @Unroll
  286. def "has reasonable error if Twirl template is configured incorrectly with (#template)"() {
  287. given:
  288. executer.noDeprecationChecks()
  289. buildFile << """
  290. model {
  291. components {
  292. play {
  293. sources {
  294. withType(TwirlSourceSet) {
  295. addUserTemplateFormat($template)
  296. }
  297. }
  298. }
  299. }
  300. }
  301. """
  302. when:
  303. result = executer.withTasks('components').runWithFailure()
  304. then:
  305. result.assertHasCause(errorMessage)
  306. where:
  307. template | errorMessage
  308. "null, 'CustomFormat'" | "Custom template extension cannot be null."
  309. "'.ext', 'CustomFormat'" | "Custom template extension should not start with a dot."
  310. "'ext', null" | "Custom template format type cannot be null."
  311. }
  312. def "has reasonable error if Twirl template cannot be found"() {
  313. twirlTemplate("test.scala.custom") << "@(username: String) Custom template, @username!"
  314. when:
  315. fails("compilePlayBinaryScala")
  316. then:
  317. failure.assertHasCause("Twirl compiler could not find a matching template for 'test.scala.custom'.")
  318. }
  319. def withTemplateSource(File templateFile) {
  320. templateFile << """@(message: String)
  321. <h1>@message</h1>
  322. """
  323. }
  324. def twirlTemplate(String fileName) {
  325. file("app", "views", fileName)
  326. }
  327. def withTwirlTemplate(String fileName = "index.scala.html") {
  328. def templateFile = file("app", "views", fileName)
  329. templateFile.createFile()
  330. withTemplateSource(templateFile)
  331. }
  332. def withTemplateSourceExpectingJavaImports(File templateFile) {
  333. templateFile << """
  334. <!DOCTYPE html>
  335. <html>
  336. <body>
  337. <p>@UUID.randomUUID().toString()</p>
  338. </body>
  339. </html>
  340. """
  341. }
  342. def withExtraSourceSets() {
  343. buildFile << """
  344. model {
  345. components {
  346. play {
  347. sources {
  348. extraTwirl(TwirlSourceSet) {
  349. source.srcDir "extraSources"
  350. }
  351. otherTwirl(TwirlSourceSet) {
  352. source.srcDir "otherSources"
  353. }
  354. }
  355. }
  356. }
  357. }
  358. """
  359. }
  360. def withTwirlJavaSourceSets() {
  361. buildFile << """
  362. model {
  363. components {
  364. play {
  365. sources {
  366. twirlJava(TwirlSourceSet) {
  367. defaultImports = TwirlImports.JAVA
  368. source.srcDir "twirlJava"
  369. }
  370. }
  371. }
  372. }
  373. }
  374. """
  375. }
  376. def validateThatPlayJavaDependencyIsAdded() {
  377. validateThatPlayJavaDependency(true)
  378. }
  379. def validateThatPlayJavaDependencyIsNotAdded() {
  380. validateThatPlayJavaDependency(false)
  381. }
  382. def validateThatPlayJavaDependency(boolean shouldBePresent) {
  383. buildFile << """
  384. model {
  385. components {
  386. play {
  387. binaries.all { binary ->
  388. tasks.withType(TwirlCompile) {
  389. doFirst {
  390. assert ${shouldBePresent ? "" : "!"} configurations.play.dependencies.any {
  391. it.group == "com.typesafe.play" &&
  392. it.name == "play-java_\${binary.targetPlatform.scalaPlatform.scalaCompatibilityVersion}" &&
  393. it.version == binary.targetPlatform.playVersion
  394. }
  395. }
  396. }
  397. }
  398. }
  399. }
  400. }
  401. """
  402. }
  403. def validateThatSourceSetsDefaultToScalaImports() {
  404. buildFile << """
  405. model {
  406. components {
  407. play {
  408. binaries.all { binary ->
  409. tasks.withType(TwirlCompile) {
  410. doFirst {
  411. assert defaultImports == TwirlImports.SCALA
  412. assert binary.inputs.withType(TwirlSourceSet).every {
  413. it.defaultImports == TwirlImports.SCALA
  414. }
  415. }
  416. }
  417. }
  418. }
  419. }
  420. }
  421. """
  422. }
  423. JarTestFixture jar(String fileName) {
  424. new JarTestFixture(file(fileName))
  425. }
  426. private void addCsvFormat() {
  427. buildFile << """
  428. model {
  429. components {
  430. play {
  431. sources {
  432. withType(TwirlSourceSet) {
  433. addUserTemplateFormat("csv", "views.formats.csv.CsvFormat", "views.formats.csv._")
  434. }
  435. }
  436. }
  437. }
  438. }
  439. """
  440. if (versionNumber < VersionNumber.parse("2.3.0")) {
  441. file("app/views/formats/csv/Csv.scala") << """
  442. package views.formats.csv
  443. import play.api.http.ContentTypeOf
  444. import play.api.mvc.Codec
  445. import play.api.templates.BufferedContent
  446. import play.templates.Format
  447. class Csv(buffer: StringBuilder) extends BufferedContent[Csv](buffer) {
  448. val contentType = Csv.contentType
  449. }
  450. object Csv {
  451. val contentType = "text/csv"
  452. implicit def contentTypeCsv(implicit codec: Codec): ContentTypeOf[Csv] = ContentTypeOf[Csv](Some(Csv.contentType))
  453. def apply(text: String): Csv = new Csv(new StringBuilder(text))
  454. def empty: Csv = new Csv(new StringBuilder)
  455. }
  456. object CsvFormat extends Format[Csv] {
  457. def raw(text: String): Csv = Csv(text)
  458. def escape(text: String): Csv = Csv(text)
  459. }
  460. """
  461. } else {
  462. file("app/views/formats/csv/Csv.scala") << """
  463. package views.formats.csv
  464. import scala.collection.immutable
  465. import play.twirl.api.BufferedContent
  466. import play.twirl.api.Format
  467. class Csv private (elements: immutable.Seq[Csv], text: String) extends BufferedContent[Csv](elements, text) {
  468. def this(text: String) = this(Nil, if (text eq null) "" else text)
  469. def this(elements: immutable.Seq[Csv]) = this(elements, "")
  470. /**
  471. * Content type of CSV.
  472. */
  473. def contentType = "text/csv"
  474. }
  475. /**
  476. * Helper for CSV utility methods.
  477. */
  478. object Csv {
  479. /**
  480. * Creates an CSV fragment with initial content specified.
  481. */
  482. def apply(text: String): Csv = {
  483. new Csv(text)
  484. }
  485. }
  486. /**
  487. * Formatter for CSV content.
  488. */
  489. object CsvFormat extends Format[Csv] {
  490. /**
  491. * Creates a CSV fragment.
  492. */
  493. def raw(text: String) = Csv(text)
  494. /**
  495. * Creates an escaped CSV fragment.
  496. */
  497. def escape(text: String) = Csv(text)
  498. /**
  499. * Generate an empty CSV fragment
  500. */
  501. val empty: Csv = new Csv("")
  502. /**
  503. * Create a CSV Fragment that holds other fragments.
  504. */
  505. def fill(elements: immutable.Seq[Csv]): Csv = new Csv(elements)
  506. }
  507. """
  508. }
  509. }
  510. }