longMessage(@PathParam("locale") String tag) {
+ return engine
+ .parse(String.format("{greeting:long_hello('%s')}", "Dr. Livingstone"))
+ .instance()
+ .setAttribute("locale", Locale.forLanguageTag(tag))
+ .createUni();
+ }
+
+ @GET
+ @Path("/message/")
+ @Produces(MediaType.TEXT_PLAIN)
+ public String injectedLocalization() {
+ return hebrew.hello("אדם");
+ }
+
+ private static URI getUri(String path) {
+ final Config system = ConfigProvider.getConfig();
+ final String host = system.getValue("quarkus.http.host", String.class);
+ final Integer port = system.getValue("quarkus.http.port", Integer.class);
+ try {
+ return new URL("http", host, port, path).toURI();
+ } catch (URISyntaxException | MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/qute/reactive/src/main/java/io/quarkus/ts/qute/Book.java b/qute/reactive/src/main/java/io/quarkus/ts/qute/Book.java
new file mode 100644
index 0000000000..cbf8df59d0
--- /dev/null
+++ b/qute/reactive/src/main/java/io/quarkus/ts/qute/Book.java
@@ -0,0 +1,16 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.TemplateData;
+
+@TemplateData
+public class Book {
+ public final String title;
+ public final String author;
+ public final String[] characters;
+
+ public Book(String title, String author, String... characters) {
+ this.title = title;
+ this.author = author;
+ this.characters = characters;
+ }
+}
diff --git a/qute/reactive/src/main/java/io/quarkus/ts/qute/City.java b/qute/reactive/src/main/java/io/quarkus/ts/qute/City.java
new file mode 100644
index 0000000000..10495db47d
--- /dev/null
+++ b/qute/reactive/src/main/java/io/quarkus/ts/qute/City.java
@@ -0,0 +1,17 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.TemplateEnum;
+
+@TemplateEnum
+public enum City {
+ BRUGES,
+ DUBLIN,
+ MOMBASA;
+
+ public String naturalName() {
+ String full = this.name();
+ String firstLetter = full.substring(0, 1);
+ String rest = full.substring(1).toLowerCase();
+ return firstLetter + rest;
+ }
+}
diff --git a/qute/reactive/src/main/java/io/quarkus/ts/qute/Fish.java b/qute/reactive/src/main/java/io/quarkus/ts/qute/Fish.java
new file mode 100644
index 0000000000..0ce7edf1fa
--- /dev/null
+++ b/qute/reactive/src/main/java/io/quarkus/ts/qute/Fish.java
@@ -0,0 +1,20 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.TemplateData;
+
+@TemplateData
+public class Fish {
+ private final String name;
+
+ public Fish(String name) {
+ this.name = name;
+ }
+
+ public String saySomething() {
+ return String.format("This %s stays silent", this.name);
+ }
+
+ public String slap(String user, String target) {
+ return String.format("%s slaps %s around a bit with a large %s", user, target, this.name);
+ }
+}
diff --git a/qute/reactive/src/main/java/io/quarkus/ts/qute/Messages.java b/qute/reactive/src/main/java/io/quarkus/ts/qute/Messages.java
new file mode 100644
index 0000000000..c47ca810dc
--- /dev/null
+++ b/qute/reactive/src/main/java/io/quarkus/ts/qute/Messages.java
@@ -0,0 +1,14 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.i18n.Message;
+import io.quarkus.qute.i18n.MessageBundle;
+
+@MessageBundle("greeting")
+public interface Messages {
+ @Message("Hello, {name}!")
+ String hello(String name);
+
+ @Message("Hello, {name}! \n How are you, {name}?")
+ String long_hello(String name);
+
+}
diff --git a/qute/reactive/src/main/java/io/quarkus/ts/qute/SpanishMessages.java b/qute/reactive/src/main/java/io/quarkus/ts/qute/SpanishMessages.java
new file mode 100644
index 0000000000..002d919165
--- /dev/null
+++ b/qute/reactive/src/main/java/io/quarkus/ts/qute/SpanishMessages.java
@@ -0,0 +1,12 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.i18n.Localized;
+import io.quarkus.qute.i18n.Message;
+
+@Localized("es")
+public interface SpanishMessages extends Messages {
+
+ @Override
+ @Message("Hola, {name}!")
+ String hello(String name);
+}
diff --git a/qute/reactive/src/main/java/io/quarkus/ts/qute/StringWrapper.java b/qute/reactive/src/main/java/io/quarkus/ts/qute/StringWrapper.java
new file mode 100644
index 0000000000..641cc5b74f
--- /dev/null
+++ b/qute/reactive/src/main/java/io/quarkus/ts/qute/StringWrapper.java
@@ -0,0 +1,13 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.TemplateData;
+
+@TemplateData(target = String.class)
+@TemplateData //required for native
+public class StringWrapper {
+ public final String content;
+
+ public StringWrapper(String content) {
+ this.content = content;
+ }
+}
diff --git a/qute/reactive/src/main/java/io/quarkus/ts/qute/Variables.java b/qute/reactive/src/main/java/io/quarkus/ts/qute/Variables.java
new file mode 100644
index 0000000000..3776afb106
--- /dev/null
+++ b/qute/reactive/src/main/java/io/quarkus/ts/qute/Variables.java
@@ -0,0 +1,13 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.TemplateGlobal;
+
+@TemplateGlobal
+public class Variables {
+ static final int airspeedVelocityOfAnUnladenSwallow = 11;
+
+ @TemplateGlobal(name = "random")
+ static int getRandomNumber() {
+ return 5;
+ }
+}
diff --git a/qute/reactive/src/main/resources/messages/greeting_cs.properties b/qute/reactive/src/main/resources/messages/greeting_cs.properties
new file mode 100644
index 0000000000..21b0dd4383
--- /dev/null
+++ b/qute/reactive/src/main/resources/messages/greeting_cs.properties
@@ -0,0 +1 @@
+hello=Ahoj, {name}!
diff --git a/qute/reactive/src/main/resources/messages/greeting_he.properties b/qute/reactive/src/main/resources/messages/greeting_he.properties
new file mode 100644
index 0000000000..270f414488
--- /dev/null
+++ b/qute/reactive/src/main/resources/messages/greeting_he.properties
@@ -0,0 +1,3 @@
+hello=שלם, {name}!
+long_hello=שלם, {name}, \
+ מה נשמע?
diff --git a/qute/reactive/src/main/resources/templates/1.i18n.html b/qute/reactive/src/main/resources/templates/1.i18n.html
new file mode 100644
index 0000000000..1ee7cb132e
--- /dev/null
+++ b/qute/reactive/src/main/resources/templates/1.i18n.html
@@ -0,0 +1,3 @@
+
+ This page is fetched and rendered by {server}
+
diff --git a/qute/reactive/src/main/resources/templates/annotations.html b/qute/reactive/src/main/resources/templates/annotations.html
new file mode 100644
index 0000000000..6877d0805b
--- /dev/null
+++ b/qute/reactive/src/main/resources/templates/annotations.html
@@ -0,0 +1,5 @@
+{fish.slap("Joe", "Seligman")}
+{fish.saySomething}
+{wrapper.content.replace("o","()")}
+Airspeed velocity of an unladen swallow is {airspeedVelocityOfAnUnladenSwallow} m/s
+This random number was chosen by a roll of dice: {random}
diff --git a/qute/reactive/src/main/resources/templates/base.html b/qute/reactive/src/main/resources/templates/base.html
new file mode 100644
index 0000000000..b50383829a
--- /dev/null
+++ b/qute/reactive/src/main/resources/templates/base.html
@@ -0,0 +1,10 @@
+
+
+{#insert title}A poem{/}
+
+{#insert first}{/}
+{#insert}{/}
+{#insert}Empty body!{/}
+{#insert last}{/}
+
+
diff --git a/qute/reactive/src/main/resources/templates/basic.html b/qute/reactive/src/main/resources/templates/basic.html
new file mode 100644
index 0000000000..a4a04a3305
--- /dev/null
+++ b/qute/reactive/src/main/resources/templates/basic.html
@@ -0,0 +1,3 @@
+
+ This page is rendered by {server}
+
diff --git a/qute/reactive/src/main/resources/templates/detail.html b/qute/reactive/src/main/resources/templates/detail.html
new file mode 100644
index 0000000000..853386b025
--- /dev/null
+++ b/qute/reactive/src/main/resources/templates/detail.html
@@ -0,0 +1,6 @@
+{#include base}
+{#title}Auguries of Innocence{/title}
+{#first}Every Morn and every Night
{/first}
+{#last}Some are Born to Endless Night
{/last}
+Some are Born to sweet delight
+{/include}
diff --git a/qute/reactive/src/main/resources/templates/expressions.html b/qute/reactive/src/main/resources/templates/expressions.html
new file mode 100644
index 0000000000..eaef33c4e3
--- /dev/null
+++ b/qute/reactive/src/main/resources/templates/expressions.html
@@ -0,0 +1,17 @@
+
+
+This page is rendered by {server}
+The book is called {book.title} and is written by {book.['author']}
+
+It has {book.characters.length} characters:
+
+{#for character in book.characters}
+{character_count}. {character}{#if character_hasNext} {#if character_odd }as well as{/if}{#if character_even }and also{/if}{/if}
+{/for}
+
+At first, {book.characters.take(1).0} tries to fight on a duel with {book.characters.takeLast(3)[0]}, {book.characters.takeLast(3).get(1)} and {book.characters.takeLast(3).2}, but later they become friends.
+Here are some numbers:{#for i in 5} {i_index}{/for}
+{#for i in 5}
+{#if i_isFirst}I have to say, that{#else}and{/if} this is {i_indexParity}{#if i_isLast}.{/if}
+{/for}
+
diff --git a/qute/reactive/src/main/resources/templates/maps.html b/qute/reactive/src/main/resources/templates/maps.html
new file mode 100644
index 0000000000..c597d533f7
--- /dev/null
+++ b/qute/reactive/src/main/resources/templates/maps.html
@@ -0,0 +1,16 @@
+
+{#if !map.isEmpty}
+The capital of Tasmania is {map.Tasmania}, {map.['Java']} is a capital of Java and {map.get('The Great Britain')} is a capital of the UK.
+{/if}
+
+
+{#for country in map.keySet}
+ {country_count}. {country}
+{/for}
+I am a Java programmer and there are {#if map.isEmpty}zero{#else}{map.size}{/if} cities I know:
+{#for capital in map.values}
+ {capital_index}. {capital}
+{/for}
+
diff --git "a/qute/reactive/src/main/resources/templates/\320\275\320\265\320\273\320\260\321\202\321\213\320\275\321\214.html" "b/qute/reactive/src/main/resources/templates/\320\275\320\265\320\273\320\260\321\202\321\213\320\275\321\214.html"
new file mode 100644
index 0000000000..0a0f2024b7
--- /dev/null
+++ "b/qute/reactive/src/main/resources/templates/\320\275\320\265\320\273\320\260\321\202\321\213\320\275\321\214.html"
@@ -0,0 +1,9 @@
+
+
+ Englishmen say {English}
+ Češi říkají {česky}
+ Русские говорят {по-русски}
+ יהודים היגדים {בעברית}
+{|{česky}
+{English}|}
+
diff --git a/qute/reactive/src/test/java/io/quarkus/ts/qute/DevModeQuteReactiveIT.java b/qute/reactive/src/test/java/io/quarkus/ts/qute/DevModeQuteReactiveIT.java
new file mode 100644
index 0000000000..8e74422b6d
--- /dev/null
+++ b/qute/reactive/src/test/java/io/quarkus/ts/qute/DevModeQuteReactiveIT.java
@@ -0,0 +1,48 @@
+package io.quarkus.ts.qute;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.bootstrap.DevModeQuarkusService;
+import io.quarkus.test.scenarios.QuarkusScenario;
+import io.quarkus.test.services.DevModeQuarkusApplication;
+import io.quarkus.test.utils.AwaitilityUtils;
+import io.restassured.response.Response;
+
+@QuarkusScenario
+public class DevModeQuteReactiveIT {
+ private static final String FILE = "src/main/resources/templates/basic.html";
+ private static final String RESERVE = "src/main/resources/templates/basic.html.bck";
+
+ @DevModeQuarkusApplication
+ static DevModeQuarkusService app = new DevModeQuarkusService();
+
+ @BeforeAll
+ static void beforeAll() {
+ app.copyFile(FILE, RESERVE);
+ }
+
+ @Test
+ void changeTemplate() {
+ Response was = app.given().get("/basic");
+ assertEquals("\n This page is rendered by Quarkus
\n\n", was.body().asString());
+ app.modifyFile(FILE, string -> string.replace("This page is rendered", "This page is being edited"));
+ AwaitilityUtils.untilAsserted(() -> {
+ Response changed = app.given().get("/basic");
+ assertEquals("\n This page is being edited by Quarkus
\n\n", changed.body().asString());
+ });
+ app.logs().assertContains("File change detected");
+ app.logs().assertContains("Restarting quarkus due to changes in basic.html");
+
+ assertEquals("\n This page is being edited by basic engine
\n\n",
+ app.given().get("/engine/basic").body().asString());
+ }
+
+ @AfterAll
+ static void afterAll() {
+ app.copyFile(RESERVE, FILE);
+ }
+}
diff --git a/qute/reactive/src/test/java/io/quarkus/ts/qute/MessageBundlesReactiveIT.java b/qute/reactive/src/test/java/io/quarkus/ts/qute/MessageBundlesReactiveIT.java
new file mode 100644
index 0000000000..04626c6d76
--- /dev/null
+++ b/qute/reactive/src/test/java/io/quarkus/ts/qute/MessageBundlesReactiveIT.java
@@ -0,0 +1,75 @@
+package io.quarkus.ts.qute;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.bootstrap.RestService;
+import io.quarkus.test.scenarios.QuarkusScenario;
+import io.quarkus.test.services.QuarkusApplication;
+import io.restassured.response.Response;
+
+@QuarkusScenario
+public class MessageBundlesReactiveIT {
+ @QuarkusApplication
+ static RestService app = new RestService();
+
+ @Test
+ void smoke() {
+ Response response = app.given().get("message/en");
+ assertEquals(200, response.statusCode());
+ assertEquals("Hello, Dr. Livingstone!", response.body().asString());
+ }
+
+ @Test
+ void fromClass() {
+ Response response = app.given().get("message/es");
+ assertEquals(200, response.statusCode());
+ assertEquals("Hola, Dr. Livingstone!", response.body().asString());
+ }
+
+ @Test
+ void fromFile() {
+ Response response = app.given().get("message/cs");
+ assertEquals(200, response.statusCode());
+ assertEquals("Ahoj, Dr. Livingstone!", response.body().asString());
+ }
+
+ @Test
+ void rtl() {
+ Response response = app.given().get("message/he");
+ assertEquals(200, response.statusCode());
+ assertEquals("שלם, Dr. Livingstone!", response.body().asString());
+ }
+
+ @Test
+ void multiline() {
+ Response response = app.given().get("message/long/en");
+ assertEquals(200, response.statusCode());
+ assertEquals("Hello, Dr. Livingstone! \n How are you, Dr. Livingstone?", response.body().asString());
+ }
+
+ @Test
+ void defaultMessage() {
+ Response response = app.given().get("message/long/es");
+ assertEquals(200, response.statusCode());
+ assertEquals("Hello, Dr. Livingstone! \n How are you, Dr. Livingstone?", response.body().asString());
+ }
+
+ @Test
+ void multilineRTL() {
+ Response response = app.given().get("message/long/he");
+ assertEquals(200, response.statusCode());
+ String content = response.body().asString();
+ assertEquals("שלם, Dr. Livingstone, מה נשמע?", content);
+ }
+
+ @Test
+ void injected() {
+ Response response = app.given().get("message/");
+ assertEquals(200, response.statusCode());
+ String content = response.body().asString();
+ assertEquals("שלם, אדם!", content);
+ }
+
+}
diff --git a/qute/reactive/src/test/java/io/quarkus/ts/qute/OpenShiftMessageBundlesReactiveIT.java b/qute/reactive/src/test/java/io/quarkus/ts/qute/OpenShiftMessageBundlesReactiveIT.java
new file mode 100644
index 0000000000..79bed2f217
--- /dev/null
+++ b/qute/reactive/src/test/java/io/quarkus/ts/qute/OpenShiftMessageBundlesReactiveIT.java
@@ -0,0 +1,8 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.test.scenarios.OpenShiftScenario;
+
+@OpenShiftScenario
+public class OpenShiftMessageBundlesReactiveIT extends QuteReactiveIT {
+
+}
diff --git a/qute/reactive/src/test/java/io/quarkus/ts/qute/OpenShiftQuteReactiveIT.java b/qute/reactive/src/test/java/io/quarkus/ts/qute/OpenShiftQuteReactiveIT.java
new file mode 100644
index 0000000000..ff92486f78
--- /dev/null
+++ b/qute/reactive/src/test/java/io/quarkus/ts/qute/OpenShiftQuteReactiveIT.java
@@ -0,0 +1,8 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.test.scenarios.OpenShiftScenario;
+
+@OpenShiftScenario
+public class OpenShiftQuteReactiveIT extends QuteReactiveIT {
+
+}
diff --git a/qute/reactive/src/test/java/io/quarkus/ts/qute/QuteReactiveIT.java b/qute/reactive/src/test/java/io/quarkus/ts/qute/QuteReactiveIT.java
new file mode 100644
index 0000000000..cab782fcf1
--- /dev/null
+++ b/qute/reactive/src/test/java/io/quarkus/ts/qute/QuteReactiveIT.java
@@ -0,0 +1,255 @@
+package io.quarkus.ts.qute;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.ws.rs.core.MediaType;
+
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.bootstrap.RestService;
+import io.quarkus.test.scenarios.QuarkusScenario;
+import io.quarkus.test.services.QuarkusApplication;
+import io.restassured.response.Response;
+
+@QuarkusScenario
+public class QuteReactiveIT {
+
+ private static final String UTF8_HTML = MediaType.TEXT_HTML + ";charset=UTF-8";
+
+ @QuarkusApplication
+ static RestService app = new RestService();
+
+ @Test
+ void smoke() {
+ Response response = app.given().get("/basic");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+
+ final String result = response.body().asString();
+ assertEquals(59, result.length());
+ assertEquals("\n This page is rendered by Quarkus
\n\n", result);
+ }
+
+ @Test
+ void location() {
+ Response response = app.given().get("/location");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+ assertTrue(response.body().asString().contains("This page is fetched and rendered by Quarkus
"),
+ response.body().asString());
+ }
+
+ @Test
+ void engine() {
+ Response response = app.given().get("/engine/basic");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+ assertTrue(response.body().asString().contains("This page is rendered by basic engine
"),
+ response.body().asString());
+ }
+
+ @Test
+ void register() {
+ Response check = app.given().get("/engine/another");
+ assertEquals(HttpStatus.SC_NOT_FOUND, check.statusCode());
+
+ Response create = app.given().body("This page is rendered by {server} from HTTP request").put("/engine/another");
+ assertEquals(HttpStatus.SC_CREATED, create.statusCode());
+
+ Response fetch = app.given().get("/engine/another");
+ assertEquals(UTF8_HTML, fetch.contentType());
+ assertEquals("This page is rendered by another engine from HTTP request", fetch.body().asString());
+
+ Response change = app.given().body("This page is rendered by {server} from HTTP request. Again")
+ .put("/engine/another");
+ assertEquals(HttpStatus.SC_NO_CONTENT, change.statusCode());
+
+ Response fetchAgain = app.given().get("/engine/another");
+ assertEquals("This page is rendered by another engine from HTTP request. Again", fetchAgain.body().asString());
+ }
+
+ @Test
+ void searchAndDelete() {
+ Response check = app.given().get("/engine/temporary");
+ assertEquals(HttpStatus.SC_NOT_FOUND, check.statusCode());
+
+ Response create = app.given().body("This page is rendered by {server} from POST HTTP request")
+ .post("/engine/temporary");
+ assertEquals(HttpStatus.SC_CREATED, create.statusCode());
+
+ final String same = "This page is rendered by temporary engine from POST HTTP request";
+
+ Response fetch = app.given().get("/engine/temporary");
+ assertEquals(same, fetch.body().asString());
+
+ Response createAgain = app.given().body("This page is rendered by {server} from HTTP request. Again")
+ .post("/engine/temporary");
+ assertEquals(HttpStatus.SC_CONFLICT, createAgain.statusCode());
+
+ Response fetchAgain = app.given().get("/engine/temporary");
+ assertEquals(same, fetchAgain.body().asString());
+
+ assertEquals(HttpStatus.SC_OK, app.given().delete("/engine/temporary").statusCode());
+ Response checkDeletion = app.given().get("/engine/temporary");
+ assertEquals(HttpStatus.SC_NOT_FOUND, checkDeletion.statusCode());
+ }
+
+ @Test
+ void format() {
+ Response defaultType = app.given().get("/format?name=remote client");
+ assertEquals(200, defaultType.statusCode());
+ assertEquals(UTF8_HTML, defaultType.contentType());
+ assertEquals("This page is rendered for \"remote client\" by Qute", defaultType.body().asString());
+
+ Response html = app.given().accept("text/html").get("/format?name=html client");
+ assertEquals(200, html.statusCode());
+ assertEquals(UTF8_HTML, html.contentType());
+ assertEquals("This page is rendered for \"html client\" by Qute", html.body().asString());
+
+ Response plain = app.given().accept("text/plain").get("/format?name=plaintext client");
+ assertEquals(200, plain.statusCode());
+ assertEquals(MediaType.TEXT_PLAIN + ";charset=UTF-8", plain.contentType());
+ assertEquals("This page is rendered for \"plaintext client\" by Qute", plain.body().asString());
+ }
+
+ @Test
+ void advancedFormat() {
+ Response response = app.given().get("/format-advanced?name=remote client");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+ assertEquals("This text is fluently rendered for \"remote client\" by Qute", response.body().asString());
+ }
+
+ @Test
+ void expressions() {
+ Response response = app.given().get("/book");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+
+ String[] content = response.body().asString().replace("
", "").split("\n");
+ assertEquals(20, content.length);
+
+ assertEquals("", content[1]);
+ assertEquals("This page is rendered by engine
", content[2]);
+ assertEquals("The book is called The Three Musketeers and is written by Alexandre Dumas",
+ content[3]);
+
+ final int loopStartLine = 5;
+ assertEquals("It has 4 characters:", content[loopStartLine]);
+ assertEquals("1. d'Artagnan as well as", content[loopStartLine + 2].replace("'", "'"));
+ assertEquals("2. Athos and also", content[loopStartLine + 3]);
+ assertEquals("3. Porthos as well as", content[loopStartLine + 4]);
+ assertEquals("4. Aramis", content[loopStartLine + 5]);
+
+ assertEquals(
+ "At first, d'Artagnan tries to fight on a duel with Athos, Porthos and Aramis, but later they become friends.",
+ content[12].replace("'", "'"));
+ final int arrayStartLine = 13;
+ assertEquals("Here are some numbers: 0 1 2 3 4", content[arrayStartLine]);
+ assertEquals("I have to say, that this is odd", content[arrayStartLine + 1]);
+ assertEquals("and this is even", content[arrayStartLine + 2]);
+ assertEquals("and this is odd", content[arrayStartLine + 3]);
+ assertEquals("and this is even", content[arrayStartLine + 4]);
+ assertEquals("and this is odd.", content[arrayStartLine + 5]);
+ }
+
+ @Test
+ void encoding() {
+ Response response = app.given().get("/encoding");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+ final String[] content = response.body().asString().split("\n");
+ assertEquals("Englishmen say hello
", content[2].stripLeading());
+ assertEquals("Češi říkají čau
", content[3].stripLeading());
+ assertEquals("Русские говорят привет
", content[4].stripLeading());
+ assertEquals("יהודים היגדים שלום
", content[5].stripLeading());
+ assertEquals("{česky}", content[6].stripLeading());
+ assertEquals("{English}", content[7].stripLeading());
+ }
+
+ @Test
+ void maps() {
+ Response response = app.given().get("/map");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+ final String[] content = response.body().asString().split("\n");
+ assertEquals("The capital of Tasmania is Hobart, Jakarta is a capital of Java and London is a capital of the UK.",
+ content[1].stripLeading());
+ int keysLine = 6;
+ assertEquals("1. Java
", content[keysLine].stripLeading());
+ assertEquals("2. Tasmania
", content[keysLine + 1].stripLeading());
+ assertEquals("3. The Great Britain
", content[keysLine + 2].stripLeading());
+ assertEquals("I am a Java programmer and there are 3 cities I know:
", content[9].stripLeading());
+ int valuesLine = 10;
+ assertEquals("0. Jakarta
", content[valuesLine].stripLeading());
+ assertEquals("1. Hobart
", content[valuesLine + 1].stripLeading());
+ assertEquals("2. London
", content[valuesLine + 2].stripLeading());
+ }
+
+ @Test
+ void emptyMaps() {
+ Response response = app.given().get("/map?name=europe");
+ assertEquals(200, response.statusCode());
+ final String[] content = response.body().asString().split("\n");
+ assertEquals("I am a Java programmer and there are zero cities I know:
", content[5].stripLeading());
+ }
+
+ @Test
+ void inheritance() {
+ Response base = app.given().get("/inheritance?name=base");
+ assertEquals(200, base.statusCode());
+ assertEquals(UTF8_HTML, base.contentType());
+ final String[] baseContent = base.body().asString().split("\n");
+
+ assertEquals(7, baseContent.length);
+ assertEquals("A poem", baseContent[2].stripLeading());
+ assertEquals("Empty body!", baseContent[4].stripLeading());
+
+ Response inherited = app.given().get("/inheritance?name=detail");
+ assertEquals(200, inherited.statusCode());
+ assertEquals(UTF8_HTML, inherited.contentType());
+
+ List updatedContent = Stream.of(inherited.body().asString())
+ .map(string -> string.split("\n"))
+ .flatMap(Arrays::stream)
+ .filter(string -> !string.isEmpty())
+ .filter(string -> !string.isEmpty())
+ .collect(Collectors.toList());
+ assertEquals(9, updatedContent.size());
+ assertEquals("Auguries of Innocence", updatedContent.get(2).stripLeading());
+ assertEquals("Every Morn and every Night
", updatedContent.get(4).stripLeading());
+ assertEquals("Some are Born to sweet delight
", updatedContent.get(5).stripLeading());
+ assertEquals("Some are Born to sweet delight
", updatedContent.get(6).stripLeading());
+ assertEquals("Some are Born to Endless Night
", updatedContent.get(7).stripLeading());
+ }
+
+ @Test
+ void annotations() {
+ Response response = app.given().get("/annotated");
+ assertEquals(200, response.statusCode());
+ final String[] content = response.body().asString().split("\n");
+ assertEquals("Joe slaps Seligman around a bit with a large trout
", content[0].stripLeading());
+ assertEquals("This trout stays silent", content[1].stripLeading());
+ assertEquals("A quick br()wn f()x jumps ()ver the lazy d()g", content[2].stripLeading());
+ assertEquals("Airspeed velocity of an unladen swallow is 11 m/s", content[3].stripLeading());
+ assertEquals("This random number was chosen by a roll of dice: 5", content[4].stripLeading());
+ }
+
+ @Test
+ void enums() {
+ Response dublin = app.given().get("/enums/dublin");
+ assertEquals(200, dublin.statusCode());
+ assertEquals("Good news, we will spend a week in Dublin!", dublin.body().asString());
+
+ Response bruges = app.given().get("/enums/bruges");
+ assertEquals(200, bruges.statusCode());
+ assertEquals("Good news, we will not spend a week in Bruges!", bruges.body().asString());
+ }
+}
diff --git a/qute/synchronous/pom.xml b/qute/synchronous/pom.xml
new file mode 100644
index 0000000000..b926425b3b
--- /dev/null
+++ b/qute/synchronous/pom.xml
@@ -0,0 +1,19 @@
+
+
+
+ io.quarkus.ts.qe
+ parent
+ 1.0.0-SNAPSHOT
+ ../../pom.xml
+
+ 4.0.0
+ qute-synchronous
+ jar
+ Quarkus QE TS: Synchronous Qute
+
+
+ io.quarkus
+ quarkus-resteasy-qute
+
+
+
diff --git a/qute/synchronous/src/main/java/io/quarkus/ts/qute/Application.java b/qute/synchronous/src/main/java/io/quarkus/ts/qute/Application.java
new file mode 100644
index 0000000000..96ec09f79f
--- /dev/null
+++ b/qute/synchronous/src/main/java/io/quarkus/ts/qute/Application.java
@@ -0,0 +1,224 @@
+package io.quarkus.ts.qute;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+
+import io.quarkus.qute.Engine;
+import io.quarkus.qute.Location;
+import io.quarkus.qute.Qute;
+import io.quarkus.qute.Template;
+import io.quarkus.qute.TemplateInstance;
+import io.quarkus.qute.i18n.Localized;
+
+@Path("")
+public class Application {
+
+ @Inject
+ Template basic;
+
+ @Location("1.i18n.html")
+ Template multiLanguage;
+
+ @Inject
+ Engine engine;
+
+ @Localized("he")
+ Messages hebrew;
+
+ @GET
+ @Path("/basic")
+ @Produces(MediaType.TEXT_HTML)
+ public TemplateInstance base() {
+ return basic.data("server", "Quarkus");
+ }
+
+ @GET
+ @Path("/location")
+ @Produces(MediaType.TEXT_HTML)
+ public TemplateInstance located() {
+ return multiLanguage.data("server", "Quarkus");
+ }
+
+ @GET
+ @Path("/engine/{name}")
+ @Produces(MediaType.TEXT_HTML)
+ public Response engine(@PathParam("name") String name) {
+ final Template template = engine.getTemplate(name);
+ if (template == null) {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ return Response.ok(template.data("server", name + " engine")).build();
+ }
+
+ @POST
+ @Path("/engine/{name}")
+ @Produces(MediaType.TEXT_HTML)
+ public Response registerNew(@PathParam("name") String name, String body) {
+ Response.ResponseBuilder result;
+ if (engine.getTemplate(name) == null) {
+ engine.putTemplate(name, engine.parse(body));
+ result = Response.created(getUri("/engine/" + name));
+ } else {
+ result = Response.status(Response.Status.CONFLICT);
+ }
+ return result.build();
+ }
+
+ @PUT
+ @Path("/engine/{name}")
+ @Produces(MediaType.TEXT_HTML)
+ public Response register(@PathParam("name") String name, String body) {
+ Template existing = engine.putTemplate(name, engine.parse(body));
+ if (existing == null) {
+ return Response.created(getUri("/engine/" + name)).build();
+ } else {
+ return Response.noContent().build();
+ }
+ }
+
+ @DELETE
+ @Path("/engine/{name}")
+ @Produces(MediaType.TEXT_HTML)
+ public Response delete(@PathParam("name") String name) {
+ engine.removeTemplates(templateName -> templateName.equals(name));
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/format")
+ @Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN })
+ public String format(@QueryParam("name") String name) {
+ return Qute.fmt("This page is rendered for \"{}\" by Qute", name);
+ }
+
+ @GET
+ @Path("/format-advanced")
+ @Produces(MediaType.TEXT_HTML)
+ public String formatHtml(@QueryParam("name") String name) {
+ return Qute.fmt("This text is fluently rendered for \"{name}\" by Qute")
+ .data("name", name)
+ .render();
+ }
+
+ @GET
+ @Path("/book")
+ @Produces(MediaType.TEXT_HTML)
+ public TemplateInstance book() {
+ Book musketeers = new Book("The Three Musketeers", "Alexandre Dumas", "d'Artagnan", "Athos", "Porthos", "Aramis");
+ return engine.getTemplate("expressions")
+ .data("server", "engine")
+ .data("book", musketeers);
+ }
+
+ @GET
+ @Path("/encoding")
+ @Produces(MediaType.TEXT_HTML)
+ public TemplateInstance encoding() {
+ return engine.getTemplate("нелатынь")
+ .data("English", "hello")
+ .data("česky", "čau")
+ .data("по-русски", "привет")
+ .data("בעברית", "שלום");
+ }
+
+ @GET
+ @Path("/map")
+ @Produces(MediaType.TEXT_HTML)
+ public TemplateInstance map(@QueryParam("name") @DefaultValue("islands") String region) {
+ Map map = new HashMap<>();
+ if (region.equals("islands")) {
+ map.put("Tasmania", "Hobart");
+ map.put("Java", "Jakarta");
+ map.put("The Great Britain", "London");
+ }
+ return engine
+ .getTemplate("maps")
+ .data("map", map);
+ }
+
+ @GET
+ @Path("/inheritance")
+ @Produces(MediaType.TEXT_HTML)
+ public TemplateInstance inheritance(@QueryParam("name") @DefaultValue("detail") String template) {
+ return engine.getTemplate(template).instance();
+ }
+
+ @GET
+ @Path("/annotated")
+ public String annotated() {
+ final Fish trout = new Fish("trout");
+ return engine
+ .getTemplate("annotations")
+ .data("fish", trout)
+ .data("wrapper", new StringWrapper("A quick brown fox jumps over the lazy dog"))
+ .render();
+ }
+
+ @GET
+ @Path("/enums/{city}")
+ @Produces(MediaType.TEXT_HTML)
+ public TemplateInstance enums(@PathParam("city") String city) {
+ final City destination = City.valueOf(city.toUpperCase());
+ return engine.parse("Good news, we will{#if city == City:BRUGES} not{/if} spend a week in {city.naturalName}!")
+ .data("city", destination);
+ }
+
+ @GET
+ @Path("/message/{locale}")
+ @Produces(MediaType.TEXT_PLAIN)
+ public TemplateInstance message(@PathParam("locale") String tag) {
+ return engine
+ .parse(String.format("{greeting:hello('%s')}", "Dr. Livingstone"))
+ .instance()
+ .setAttribute("locale", Locale.forLanguageTag(tag));
+ }
+
+ @GET
+ @Path("/message/long/{locale}")
+ @Produces(MediaType.TEXT_PLAIN)
+ public TemplateInstance longMessage(@PathParam("locale") String tag) {
+ return engine
+ .parse(String.format("{greeting:long_hello('%s')}", "Dr. Livingstone"))
+ .instance()
+ .setAttribute("locale", Locale.forLanguageTag(tag));
+ }
+
+ @GET
+ @Path("/message/")
+ @Produces(MediaType.TEXT_PLAIN)
+ public String injectedLocalization() {
+ return hebrew.hello("אדם");
+ }
+
+ private static URI getUri(String path) {
+ final Config system = ConfigProvider.getConfig();
+ final String host = system.getValue("quarkus.http.host", String.class);
+ final Integer port = system.getValue("quarkus.http.port", Integer.class);
+ try {
+ return new URL("http", host, port, path).toURI();
+ } catch (URISyntaxException | MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/qute/synchronous/src/main/java/io/quarkus/ts/qute/Book.java b/qute/synchronous/src/main/java/io/quarkus/ts/qute/Book.java
new file mode 100644
index 0000000000..cbf8df59d0
--- /dev/null
+++ b/qute/synchronous/src/main/java/io/quarkus/ts/qute/Book.java
@@ -0,0 +1,16 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.TemplateData;
+
+@TemplateData
+public class Book {
+ public final String title;
+ public final String author;
+ public final String[] characters;
+
+ public Book(String title, String author, String... characters) {
+ this.title = title;
+ this.author = author;
+ this.characters = characters;
+ }
+}
diff --git a/qute/synchronous/src/main/java/io/quarkus/ts/qute/City.java b/qute/synchronous/src/main/java/io/quarkus/ts/qute/City.java
new file mode 100644
index 0000000000..10495db47d
--- /dev/null
+++ b/qute/synchronous/src/main/java/io/quarkus/ts/qute/City.java
@@ -0,0 +1,17 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.TemplateEnum;
+
+@TemplateEnum
+public enum City {
+ BRUGES,
+ DUBLIN,
+ MOMBASA;
+
+ public String naturalName() {
+ String full = this.name();
+ String firstLetter = full.substring(0, 1);
+ String rest = full.substring(1).toLowerCase();
+ return firstLetter + rest;
+ }
+}
diff --git a/qute/synchronous/src/main/java/io/quarkus/ts/qute/Fish.java b/qute/synchronous/src/main/java/io/quarkus/ts/qute/Fish.java
new file mode 100644
index 0000000000..0ce7edf1fa
--- /dev/null
+++ b/qute/synchronous/src/main/java/io/quarkus/ts/qute/Fish.java
@@ -0,0 +1,20 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.TemplateData;
+
+@TemplateData
+public class Fish {
+ private final String name;
+
+ public Fish(String name) {
+ this.name = name;
+ }
+
+ public String saySomething() {
+ return String.format("This %s stays silent", this.name);
+ }
+
+ public String slap(String user, String target) {
+ return String.format("%s slaps %s around a bit with a large %s", user, target, this.name);
+ }
+}
diff --git a/qute/synchronous/src/main/java/io/quarkus/ts/qute/Messages.java b/qute/synchronous/src/main/java/io/quarkus/ts/qute/Messages.java
new file mode 100644
index 0000000000..c47ca810dc
--- /dev/null
+++ b/qute/synchronous/src/main/java/io/quarkus/ts/qute/Messages.java
@@ -0,0 +1,14 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.i18n.Message;
+import io.quarkus.qute.i18n.MessageBundle;
+
+@MessageBundle("greeting")
+public interface Messages {
+ @Message("Hello, {name}!")
+ String hello(String name);
+
+ @Message("Hello, {name}! \n How are you, {name}?")
+ String long_hello(String name);
+
+}
diff --git a/qute/synchronous/src/main/java/io/quarkus/ts/qute/SpanishMessages.java b/qute/synchronous/src/main/java/io/quarkus/ts/qute/SpanishMessages.java
new file mode 100644
index 0000000000..002d919165
--- /dev/null
+++ b/qute/synchronous/src/main/java/io/quarkus/ts/qute/SpanishMessages.java
@@ -0,0 +1,12 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.i18n.Localized;
+import io.quarkus.qute.i18n.Message;
+
+@Localized("es")
+public interface SpanishMessages extends Messages {
+
+ @Override
+ @Message("Hola, {name}!")
+ String hello(String name);
+}
diff --git a/qute/synchronous/src/main/java/io/quarkus/ts/qute/StringWrapper.java b/qute/synchronous/src/main/java/io/quarkus/ts/qute/StringWrapper.java
new file mode 100644
index 0000000000..641cc5b74f
--- /dev/null
+++ b/qute/synchronous/src/main/java/io/quarkus/ts/qute/StringWrapper.java
@@ -0,0 +1,13 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.TemplateData;
+
+@TemplateData(target = String.class)
+@TemplateData //required for native
+public class StringWrapper {
+ public final String content;
+
+ public StringWrapper(String content) {
+ this.content = content;
+ }
+}
diff --git a/qute/synchronous/src/main/java/io/quarkus/ts/qute/Variables.java b/qute/synchronous/src/main/java/io/quarkus/ts/qute/Variables.java
new file mode 100644
index 0000000000..3776afb106
--- /dev/null
+++ b/qute/synchronous/src/main/java/io/quarkus/ts/qute/Variables.java
@@ -0,0 +1,13 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.qute.TemplateGlobal;
+
+@TemplateGlobal
+public class Variables {
+ static final int airspeedVelocityOfAnUnladenSwallow = 11;
+
+ @TemplateGlobal(name = "random")
+ static int getRandomNumber() {
+ return 5;
+ }
+}
diff --git a/qute/synchronous/src/main/resources/messages/greeting_cs.properties b/qute/synchronous/src/main/resources/messages/greeting_cs.properties
new file mode 100644
index 0000000000..21b0dd4383
--- /dev/null
+++ b/qute/synchronous/src/main/resources/messages/greeting_cs.properties
@@ -0,0 +1 @@
+hello=Ahoj, {name}!
diff --git a/qute/synchronous/src/main/resources/messages/greeting_he.properties b/qute/synchronous/src/main/resources/messages/greeting_he.properties
new file mode 100644
index 0000000000..270f414488
--- /dev/null
+++ b/qute/synchronous/src/main/resources/messages/greeting_he.properties
@@ -0,0 +1,3 @@
+hello=שלם, {name}!
+long_hello=שלם, {name}, \
+ מה נשמע?
diff --git a/qute/synchronous/src/main/resources/templates/1.i18n.html b/qute/synchronous/src/main/resources/templates/1.i18n.html
new file mode 100644
index 0000000000..1ee7cb132e
--- /dev/null
+++ b/qute/synchronous/src/main/resources/templates/1.i18n.html
@@ -0,0 +1,3 @@
+
+ This page is fetched and rendered by {server}
+
diff --git a/qute/synchronous/src/main/resources/templates/annotations.html b/qute/synchronous/src/main/resources/templates/annotations.html
new file mode 100644
index 0000000000..6877d0805b
--- /dev/null
+++ b/qute/synchronous/src/main/resources/templates/annotations.html
@@ -0,0 +1,5 @@
+{fish.slap("Joe", "Seligman")}
+{fish.saySomething}
+{wrapper.content.replace("o","()")}
+Airspeed velocity of an unladen swallow is {airspeedVelocityOfAnUnladenSwallow} m/s
+This random number was chosen by a roll of dice: {random}
diff --git a/qute/synchronous/src/main/resources/templates/base.html b/qute/synchronous/src/main/resources/templates/base.html
new file mode 100644
index 0000000000..b50383829a
--- /dev/null
+++ b/qute/synchronous/src/main/resources/templates/base.html
@@ -0,0 +1,10 @@
+
+
+{#insert title}A poem{/}
+
+{#insert first}{/}
+{#insert}{/}
+{#insert}Empty body!{/}
+{#insert last}{/}
+
+
diff --git a/qute/synchronous/src/main/resources/templates/basic.html b/qute/synchronous/src/main/resources/templates/basic.html
new file mode 100644
index 0000000000..a4a04a3305
--- /dev/null
+++ b/qute/synchronous/src/main/resources/templates/basic.html
@@ -0,0 +1,3 @@
+
+ This page is rendered by {server}
+
diff --git a/qute/synchronous/src/main/resources/templates/detail.html b/qute/synchronous/src/main/resources/templates/detail.html
new file mode 100644
index 0000000000..853386b025
--- /dev/null
+++ b/qute/synchronous/src/main/resources/templates/detail.html
@@ -0,0 +1,6 @@
+{#include base}
+{#title}Auguries of Innocence{/title}
+{#first}Every Morn and every Night
{/first}
+{#last}Some are Born to Endless Night
{/last}
+Some are Born to sweet delight
+{/include}
diff --git a/qute/synchronous/src/main/resources/templates/expressions.html b/qute/synchronous/src/main/resources/templates/expressions.html
new file mode 100644
index 0000000000..eaef33c4e3
--- /dev/null
+++ b/qute/synchronous/src/main/resources/templates/expressions.html
@@ -0,0 +1,17 @@
+
+
+This page is rendered by {server}
+The book is called {book.title} and is written by {book.['author']}
+
+It has {book.characters.length} characters:
+
+{#for character in book.characters}
+{character_count}. {character}{#if character_hasNext} {#if character_odd }as well as{/if}{#if character_even }and also{/if}{/if}
+{/for}
+
+At first, {book.characters.take(1).0} tries to fight on a duel with {book.characters.takeLast(3)[0]}, {book.characters.takeLast(3).get(1)} and {book.characters.takeLast(3).2}, but later they become friends.
+Here are some numbers:{#for i in 5} {i_index}{/for}
+{#for i in 5}
+{#if i_isFirst}I have to say, that{#else}and{/if} this is {i_indexParity}{#if i_isLast}.{/if}
+{/for}
+
diff --git a/qute/synchronous/src/main/resources/templates/maps.html b/qute/synchronous/src/main/resources/templates/maps.html
new file mode 100644
index 0000000000..c597d533f7
--- /dev/null
+++ b/qute/synchronous/src/main/resources/templates/maps.html
@@ -0,0 +1,16 @@
+
+{#if !map.isEmpty}
+The capital of Tasmania is {map.Tasmania}, {map.['Java']} is a capital of Java and {map.get('The Great Britain')} is a capital of the UK.
+{/if}
+
+
+{#for country in map.keySet}
+ {country_count}. {country}
+{/for}
+I am a Java programmer and there are {#if map.isEmpty}zero{#else}{map.size}{/if} cities I know:
+{#for capital in map.values}
+ {capital_index}. {capital}
+{/for}
+
diff --git "a/qute/synchronous/src/main/resources/templates/\320\275\320\265\320\273\320\260\321\202\321\213\320\275\321\214.html" "b/qute/synchronous/src/main/resources/templates/\320\275\320\265\320\273\320\260\321\202\321\213\320\275\321\214.html"
new file mode 100644
index 0000000000..0a0f2024b7
--- /dev/null
+++ "b/qute/synchronous/src/main/resources/templates/\320\275\320\265\320\273\320\260\321\202\321\213\320\275\321\214.html"
@@ -0,0 +1,9 @@
+
+
+ Englishmen say {English}
+ Češi říkají {česky}
+ Русские говорят {по-русски}
+ יהודים היגדים {בעברית}
+{|{česky}
+{English}|}
+
diff --git a/qute/synchronous/src/test/java/io/quarkus/ts/qute/DevModeQuteIT.java b/qute/synchronous/src/test/java/io/quarkus/ts/qute/DevModeQuteIT.java
new file mode 100644
index 0000000000..8b767fb4f9
--- /dev/null
+++ b/qute/synchronous/src/test/java/io/quarkus/ts/qute/DevModeQuteIT.java
@@ -0,0 +1,48 @@
+package io.quarkus.ts.qute;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.bootstrap.DevModeQuarkusService;
+import io.quarkus.test.scenarios.QuarkusScenario;
+import io.quarkus.test.services.DevModeQuarkusApplication;
+import io.quarkus.test.utils.AwaitilityUtils;
+import io.restassured.response.Response;
+
+@QuarkusScenario
+public class DevModeQuteIT {
+ private static final String FILE = "src/main/resources/templates/basic.html";
+ private static final String RESERVE = "src/main/resources/templates/basic.html.bck";
+
+ @DevModeQuarkusApplication
+ static DevModeQuarkusService app = new DevModeQuarkusService();
+
+ @BeforeAll
+ static void beforeAll() {
+ app.copyFile(FILE, RESERVE);
+ }
+
+ @Test
+ void changeTemplate() {
+ Response was = app.given().get("/basic");
+ assertEquals("\n This page is rendered by Quarkus
\n\n", was.body().asString());
+ app.modifyFile(FILE, string -> string.replace("This page is rendered", "This page is being edited"));
+ AwaitilityUtils.untilAsserted(() -> {
+ Response changed = app.given().get("/basic");
+ assertEquals("\n This page is being edited by Quarkus
\n\n", changed.body().asString());
+ });
+ app.logs().assertContains("File change detected");
+ app.logs().assertContains("Restarting quarkus due to changes in basic.html");
+
+ assertEquals("\n This page is being edited by basic engine
\n\n",
+ app.given().get("/engine/basic").body().asString());
+ }
+
+ @AfterAll
+ static void afterAll() {
+ app.copyFile(RESERVE, FILE);
+ }
+}
diff --git a/qute/synchronous/src/test/java/io/quarkus/ts/qute/MessageBundlesIT.java b/qute/synchronous/src/test/java/io/quarkus/ts/qute/MessageBundlesIT.java
new file mode 100644
index 0000000000..b968bcdcc7
--- /dev/null
+++ b/qute/synchronous/src/test/java/io/quarkus/ts/qute/MessageBundlesIT.java
@@ -0,0 +1,75 @@
+package io.quarkus.ts.qute;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.bootstrap.RestService;
+import io.quarkus.test.scenarios.QuarkusScenario;
+import io.quarkus.test.services.QuarkusApplication;
+import io.restassured.response.Response;
+
+@QuarkusScenario
+public class MessageBundlesIT {
+ @QuarkusApplication
+ static RestService app = new RestService();
+
+ @Test
+ void smoke() {
+ Response response = app.given().get("message/en");
+ assertEquals(200, response.statusCode());
+ assertEquals("Hello, Dr. Livingstone!", response.body().asString());
+ }
+
+ @Test
+ void fromClass() {
+ Response response = app.given().get("message/es");
+ assertEquals(200, response.statusCode());
+ assertEquals("Hola, Dr. Livingstone!", response.body().asString());
+ }
+
+ @Test
+ void fromFile() {
+ Response response = app.given().get("message/cs");
+ assertEquals(200, response.statusCode());
+ assertEquals("Ahoj, Dr. Livingstone!", response.body().asString());
+ }
+
+ @Test
+ void rtl() {
+ Response response = app.given().get("message/he");
+ assertEquals(200, response.statusCode());
+ assertEquals("שלם, Dr. Livingstone!", response.body().asString());
+ }
+
+ @Test
+ void multiline() {
+ Response response = app.given().get("message/long/en");
+ assertEquals(200, response.statusCode());
+ assertEquals("Hello, Dr. Livingstone! \n How are you, Dr. Livingstone?", response.body().asString());
+ }
+
+ @Test
+ void defaultMessage() {
+ Response response = app.given().get("message/long/es");
+ assertEquals(200, response.statusCode());
+ assertEquals("Hello, Dr. Livingstone! \n How are you, Dr. Livingstone?", response.body().asString());
+ }
+
+ @Test
+ void multilineRTL() {
+ Response response = app.given().get("message/long/he");
+ assertEquals(200, response.statusCode());
+ String content = response.body().asString();
+ assertEquals("שלם, Dr. Livingstone, מה נשמע?", content);
+ }
+
+ @Test
+ void injected() {
+ Response response = app.given().get("message/");
+ assertEquals(200, response.statusCode());
+ String content = response.body().asString();
+ assertEquals("שלם, אדם!", content);
+ }
+
+}
diff --git a/qute/synchronous/src/test/java/io/quarkus/ts/qute/OpenShiftMessageBundlesIT.java b/qute/synchronous/src/test/java/io/quarkus/ts/qute/OpenShiftMessageBundlesIT.java
new file mode 100644
index 0000000000..f7e28d8aa5
--- /dev/null
+++ b/qute/synchronous/src/test/java/io/quarkus/ts/qute/OpenShiftMessageBundlesIT.java
@@ -0,0 +1,8 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.test.scenarios.OpenShiftScenario;
+
+@OpenShiftScenario
+public class OpenShiftMessageBundlesIT extends QuteIT {
+
+}
diff --git a/qute/synchronous/src/test/java/io/quarkus/ts/qute/OpenShiftQuteIT.java b/qute/synchronous/src/test/java/io/quarkus/ts/qute/OpenShiftQuteIT.java
new file mode 100644
index 0000000000..a32523063f
--- /dev/null
+++ b/qute/synchronous/src/test/java/io/quarkus/ts/qute/OpenShiftQuteIT.java
@@ -0,0 +1,8 @@
+package io.quarkus.ts.qute;
+
+import io.quarkus.test.scenarios.OpenShiftScenario;
+
+@OpenShiftScenario
+public class OpenShiftQuteIT extends QuteIT {
+
+}
diff --git a/qute/synchronous/src/test/java/io/quarkus/ts/qute/QuteIT.java b/qute/synchronous/src/test/java/io/quarkus/ts/qute/QuteIT.java
new file mode 100644
index 0000000000..c92aea3dd3
--- /dev/null
+++ b/qute/synchronous/src/test/java/io/quarkus/ts/qute/QuteIT.java
@@ -0,0 +1,255 @@
+package io.quarkus.ts.qute;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.ws.rs.core.MediaType;
+
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.bootstrap.RestService;
+import io.quarkus.test.scenarios.QuarkusScenario;
+import io.quarkus.test.services.QuarkusApplication;
+import io.restassured.response.Response;
+
+@QuarkusScenario
+public class QuteIT {
+
+ private static final String UTF8_HTML = MediaType.TEXT_HTML + ";charset=UTF-8";
+
+ @QuarkusApplication
+ static RestService app = new RestService();
+
+ @Test
+ void smoke() {
+ Response response = app.given().get("/basic");
+ assertEquals(200, response.statusCode());
+ assertEquals(MediaType.TEXT_HTML, response.contentType()); //https://github.com/quarkusio/quarkus/issues/27872
+
+ final String result = response.body().asString();
+ assertEquals(59, result.length());
+ assertEquals("\n This page is rendered by Quarkus
\n\n", result);
+ }
+
+ @Test
+ void location() {
+ Response response = app.given().get("/location");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+ assertTrue(response.body().asString().contains("This page is fetched and rendered by Quarkus
"),
+ response.body().asString());
+ }
+
+ @Test
+ void engine() {
+ Response response = app.given().get("/engine/basic");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+ assertTrue(response.body().asString().contains("This page is rendered by basic engine
"),
+ response.body().asString());
+ }
+
+ @Test
+ void register() {
+ Response check = app.given().get("/engine/another");
+ assertEquals(HttpStatus.SC_NOT_FOUND, check.statusCode());
+
+ Response create = app.given().body("This page is rendered by {server} from HTTP request").put("/engine/another");
+ assertEquals(HttpStatus.SC_CREATED, create.statusCode());
+
+ Response fetch = app.given().get("/engine/another");
+ assertEquals(UTF8_HTML, fetch.contentType());
+ assertEquals("This page is rendered by another engine from HTTP request", fetch.body().asString());
+
+ Response change = app.given().body("This page is rendered by {server} from HTTP request. Again")
+ .put("/engine/another");
+ assertEquals(HttpStatus.SC_NO_CONTENT, change.statusCode());
+
+ Response fetchAgain = app.given().get("/engine/another");
+ assertEquals("This page is rendered by another engine from HTTP request. Again", fetchAgain.body().asString());
+ }
+
+ @Test
+ void searchAndDelete() {
+ Response check = app.given().get("/engine/temporary");
+ assertEquals(HttpStatus.SC_NOT_FOUND, check.statusCode());
+
+ Response create = app.given().body("This page is rendered by {server} from POST HTTP request")
+ .post("/engine/temporary");
+ assertEquals(HttpStatus.SC_CREATED, create.statusCode());
+
+ final String same = "This page is rendered by temporary engine from POST HTTP request";
+
+ Response fetch = app.given().get("/engine/temporary");
+ assertEquals(same, fetch.body().asString());
+
+ Response createAgain = app.given().body("This page is rendered by {server} from HTTP request. Again")
+ .post("/engine/temporary");
+ assertEquals(HttpStatus.SC_CONFLICT, createAgain.statusCode());
+
+ Response fetchAgain = app.given().get("/engine/temporary");
+ assertEquals(same, fetchAgain.body().asString());
+
+ assertEquals(HttpStatus.SC_OK, app.given().delete("/engine/temporary").statusCode());
+ Response checkDeletion = app.given().get("/engine/temporary");
+ assertEquals(HttpStatus.SC_NOT_FOUND, checkDeletion.statusCode());
+ }
+
+ @Test
+ void format() {
+ Response defaultType = app.given().get("/format?name=remote client");
+ assertEquals(200, defaultType.statusCode());
+ assertEquals(UTF8_HTML, defaultType.contentType());
+ assertEquals("This page is rendered for \"remote client\" by Qute", defaultType.body().asString());
+
+ Response html = app.given().accept("text/html").get("/format?name=html client");
+ assertEquals(200, html.statusCode());
+ assertEquals(UTF8_HTML, html.contentType());
+ assertEquals("This page is rendered for \"html client\" by Qute", html.body().asString());
+
+ Response plain = app.given().accept("text/plain").get("/format?name=plaintext client");
+ assertEquals(200, plain.statusCode());
+ assertEquals(MediaType.TEXT_PLAIN + ";charset=UTF-8", plain.contentType());
+ assertEquals("This page is rendered for \"plaintext client\" by Qute", plain.body().asString());
+ }
+
+ @Test
+ void advancedFormat() {
+ Response response = app.given().get("/format-advanced?name=remote client");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+ assertEquals("This text is fluently rendered for \"remote client\" by Qute", response.body().asString());
+ }
+
+ @Test
+ void expressions() {
+ Response response = app.given().get("/book");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+
+ String[] content = response.body().asString().replace("
", "").split("\n");
+ assertEquals(20, content.length);
+
+ assertEquals("", content[1]);
+ assertEquals("This page is rendered by engine
", content[2]);
+ assertEquals("The book is called The Three Musketeers and is written by Alexandre Dumas",
+ content[3]);
+
+ final int loopStartLine = 5;
+ assertEquals("It has 4 characters:", content[loopStartLine]);
+ assertEquals("1. d'Artagnan as well as", content[loopStartLine + 2].replace("'", "'"));
+ assertEquals("2. Athos and also", content[loopStartLine + 3]);
+ assertEquals("3. Porthos as well as", content[loopStartLine + 4]);
+ assertEquals("4. Aramis", content[loopStartLine + 5]);
+
+ assertEquals(
+ "At first, d'Artagnan tries to fight on a duel with Athos, Porthos and Aramis, but later they become friends.",
+ content[12].replace("'", "'"));
+ final int arrayStartLine = 13;
+ assertEquals("Here are some numbers: 0 1 2 3 4", content[arrayStartLine]);
+ assertEquals("I have to say, that this is odd", content[arrayStartLine + 1]);
+ assertEquals("and this is even", content[arrayStartLine + 2]);
+ assertEquals("and this is odd", content[arrayStartLine + 3]);
+ assertEquals("and this is even", content[arrayStartLine + 4]);
+ assertEquals("and this is odd.", content[arrayStartLine + 5]);
+ }
+
+ @Test
+ void encoding() {
+ Response response = app.given().get("/encoding");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+ final String[] content = response.body().asString().split("\n");
+ assertEquals("Englishmen say hello
", content[2].stripLeading());
+ assertEquals("Češi říkají čau
", content[3].stripLeading());
+ assertEquals("Русские говорят привет
", content[4].stripLeading());
+ assertEquals("יהודים היגדים שלום
", content[5].stripLeading());
+ assertEquals("{česky}", content[6].stripLeading());
+ assertEquals("{English}", content[7].stripLeading());
+ }
+
+ @Test
+ void maps() {
+ Response response = app.given().get("/map");
+ assertEquals(200, response.statusCode());
+ assertEquals(UTF8_HTML, response.contentType());
+ final String[] content = response.body().asString().split("\n");
+ assertEquals("The capital of Tasmania is Hobart, Jakarta is a capital of Java and London is a capital of the UK.",
+ content[1].stripLeading());
+ int keysLine = 6;
+ assertEquals("1. Java
", content[keysLine].stripLeading());
+ assertEquals("2. Tasmania
", content[keysLine + 1].stripLeading());
+ assertEquals("3. The Great Britain
", content[keysLine + 2].stripLeading());
+ assertEquals("I am a Java programmer and there are 3 cities I know:
", content[9].stripLeading());
+ int valuesLine = 10;
+ assertEquals("0. Jakarta
", content[valuesLine].stripLeading());
+ assertEquals("1. Hobart
", content[valuesLine + 1].stripLeading());
+ assertEquals("2. London
", content[valuesLine + 2].stripLeading());
+ }
+
+ @Test
+ void emptyMaps() {
+ Response response = app.given().get("/map?name=europe");
+ assertEquals(200, response.statusCode());
+ final String[] content = response.body().asString().split("\n");
+ assertEquals("I am a Java programmer and there are zero cities I know:
", content[5].stripLeading());
+ }
+
+ @Test
+ void inheritance() {
+ Response base = app.given().get("/inheritance?name=base");
+ assertEquals(200, base.statusCode());
+ assertEquals(UTF8_HTML, base.contentType());
+ final String[] baseContent = base.body().asString().split("\n");
+
+ assertEquals(7, baseContent.length);
+ assertEquals("A poem", baseContent[2].stripLeading());
+ assertEquals("Empty body!", baseContent[4].stripLeading());
+
+ Response inherited = app.given().get("/inheritance?name=detail");
+ assertEquals(200, inherited.statusCode());
+ assertEquals(UTF8_HTML, inherited.contentType());
+
+ List updatedContent = Stream.of(inherited.body().asString())
+ .map(string -> string.split("\n"))
+ .flatMap(Arrays::stream)
+ .filter(string -> !string.isEmpty())
+ .filter(string -> !string.isEmpty())
+ .collect(Collectors.toList());
+ assertEquals(9, updatedContent.size());
+ assertEquals("Auguries of Innocence", updatedContent.get(2).stripLeading());
+ assertEquals("Every Morn and every Night
", updatedContent.get(4).stripLeading());
+ assertEquals("Some are Born to sweet delight
", updatedContent.get(5).stripLeading());
+ assertEquals("Some are Born to sweet delight
", updatedContent.get(6).stripLeading());
+ assertEquals("Some are Born to Endless Night
", updatedContent.get(7).stripLeading());
+ }
+
+ @Test
+ void annotations() {
+ Response response = app.given().get("/annotated");
+ assertEquals(200, response.statusCode());
+ final String[] content = response.body().asString().split("\n");
+ assertEquals("Joe slaps Seligman around a bit with a large trout
", content[0].stripLeading());
+ assertEquals("This trout stays silent", content[1].stripLeading());
+ assertEquals("A quick br()wn f()x jumps ()ver the lazy d()g", content[2].stripLeading());
+ assertEquals("Airspeed velocity of an unladen swallow is 11 m/s", content[3].stripLeading());
+ assertEquals("This random number was chosen by a roll of dice: 5", content[4].stripLeading());
+ }
+
+ @Test
+ void enums() {
+ Response dublin = app.given().get("/enums/dublin");
+ assertEquals(200, dublin.statusCode());
+ assertEquals("Good news, we will spend a week in Dublin!", dublin.body().asString());
+
+ Response bruges = app.given().get("/enums/bruges");
+ assertEquals(200, bruges.statusCode());
+ assertEquals("Good news, we will not spend a week in Bruges!", bruges.body().asString());
+ }
+}