Browse Source
Thymeleaf 3.0 implements the Spring 5.0 view infrastructure for WebMVC and the new WebFlux framework. This commit adds auto-configuration for the WebFlux support. In that process, the configuration property for `spring.thymeleaf` has been changed to add `spring.thymeleaf.servlet` and `spring.thymeleaf.reactive` for MVC/WebFlux specific properties. Now that the `spring-boot-starter-thymeleaf` does not only support Spring MVC, the transitive dependency on `spring-boot-starter-web` is removed from it. Fixes gh-8124pull/9032/head
12 changed files with 435 additions and 134 deletions
@ -0,0 +1,186 @@
@@ -0,0 +1,186 @@
|
||||
/* |
||||
* Copyright 2012-2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.autoconfigure.thymeleaf; |
||||
|
||||
import java.io.File; |
||||
import java.util.Collections; |
||||
import java.util.Locale; |
||||
|
||||
import nz.net.ultraq.thymeleaf.LayoutDialect; |
||||
import nz.net.ultraq.thymeleaf.decorators.strategies.GroupingStrategy; |
||||
import org.junit.After; |
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.thymeleaf.TemplateEngine; |
||||
import org.thymeleaf.context.Context; |
||||
import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine; |
||||
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; |
||||
import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver; |
||||
import org.thymeleaf.templateresolver.ITemplateResolver; |
||||
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration; |
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; |
||||
import org.springframework.boot.test.rule.OutputCapture; |
||||
import org.springframework.boot.test.util.EnvironmentTestUtils; |
||||
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.context.annotation.Import; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.test.util.ReflectionTestUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hamcrest.Matchers.containsString; |
||||
import static org.hamcrest.Matchers.not; |
||||
|
||||
/** |
||||
* Tests for {@link ThymeleafAutoConfiguration} in Reactive applications. |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class ThymeleafReactiveAutoConfigurationTests { |
||||
|
||||
@Rule |
||||
public OutputCapture output = new OutputCapture(); |
||||
|
||||
private GenericReactiveWebApplicationContext context; |
||||
|
||||
@After |
||||
public void close() { |
||||
if (this.context != null) { |
||||
this.context.close(); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void createFromConfigClass() throws Exception { |
||||
load(BaseConfiguration.class, "spring.thymeleaf.suffix:.txt"); |
||||
TemplateEngine engine = this.context.getBean(TemplateEngine.class); |
||||
Context attrs = new Context(Locale.UK, Collections.singletonMap("foo", "bar")); |
||||
String result = engine.process("template", attrs); |
||||
assertThat(result).isEqualTo("<html>bar</html>"); |
||||
} |
||||
|
||||
@Test |
||||
public void overrideCharacterEncoding() throws Exception { |
||||
load(BaseConfiguration.class, "spring.thymeleaf.encoding:UTF-16"); |
||||
ITemplateResolver resolver = this.context.getBean(ITemplateResolver.class); |
||||
assertThat(resolver instanceof SpringResourceTemplateResolver).isTrue(); |
||||
assertThat(((SpringResourceTemplateResolver) resolver).getCharacterEncoding()) |
||||
.isEqualTo("UTF-16"); |
||||
ThymeleafReactiveViewResolver views = this.context.getBean(ThymeleafReactiveViewResolver.class); |
||||
assertThat(views.getDefaultCharset().name()).isEqualTo("UTF-16"); |
||||
} |
||||
|
||||
@Test |
||||
public void overrideMediaTypes() throws Exception { |
||||
load(BaseConfiguration.class, |
||||
"spring.thymeleaf.reactive.media-types:text/html,text/plain"); |
||||
ThymeleafReactiveViewResolver views = this.context.getBean(ThymeleafReactiveViewResolver.class); |
||||
assertThat(views.getSupportedMediaTypes()).contains(MediaType.TEXT_HTML, MediaType.TEXT_PLAIN); |
||||
} |
||||
|
||||
@Test |
||||
public void overrideTemplateResolverOrder() throws Exception { |
||||
load(BaseConfiguration.class, "spring.thymeleaf.templateResolverOrder:25"); |
||||
ITemplateResolver resolver = this.context.getBean(ITemplateResolver.class); |
||||
assertThat(resolver.getOrder()).isEqualTo(Integer.valueOf(25)); |
||||
} |
||||
|
||||
@Test |
||||
public void overrideViewNames() throws Exception { |
||||
load(BaseConfiguration.class, "spring.thymeleaf.viewNames:foo,bar"); |
||||
ThymeleafReactiveViewResolver views = this.context.getBean(ThymeleafReactiveViewResolver.class); |
||||
assertThat(views.getViewNames()).isEqualTo(new String[] {"foo", "bar"}); |
||||
} |
||||
|
||||
@Test |
||||
public void templateLocationDoesNotExist() throws Exception { |
||||
load(BaseConfiguration.class, "spring.thymeleaf.prefix:classpath:/no-such-directory/"); |
||||
this.output.expect(containsString("Cannot find template location")); |
||||
} |
||||
|
||||
@Test |
||||
public void templateLocationEmpty() throws Exception { |
||||
new File("target/test-classes/templates/empty-directory").mkdir(); |
||||
load(BaseConfiguration.class, "spring.thymeleaf.prefix:classpath:/templates/empty-directory/"); |
||||
this.output.expect(not(containsString("Cannot find template location"))); |
||||
} |
||||
|
||||
@Test |
||||
public void useDataDialect() throws Exception { |
||||
load(BaseConfiguration.class); |
||||
ISpringWebFluxTemplateEngine engine = this.context.getBean(ISpringWebFluxTemplateEngine.class); |
||||
Context attrs = new Context(Locale.UK, Collections.singletonMap("foo", "bar")); |
||||
String result = engine.process("data-dialect", attrs); |
||||
assertThat(result).isEqualTo("<html><body data-foo=\"bar\"></body></html>"); |
||||
} |
||||
|
||||
@Test |
||||
public void useJava8TimeDialect() throws Exception { |
||||
load(BaseConfiguration.class); |
||||
ISpringWebFluxTemplateEngine engine = this.context.getBean(ISpringWebFluxTemplateEngine.class); |
||||
Context attrs = new Context(Locale.UK); |
||||
String result = engine.process("java8time-dialect", attrs); |
||||
assertThat(result).isEqualTo("<html><body>2015-11-24</body></html>"); |
||||
} |
||||
|
||||
@Test |
||||
public void renderTemplate() throws Exception { |
||||
load(BaseConfiguration.class); |
||||
ISpringWebFluxTemplateEngine engine = this.context.getBean(ISpringWebFluxTemplateEngine.class); |
||||
Context attrs = new Context(Locale.UK, Collections.singletonMap("foo", "bar")); |
||||
String result = engine.process("home", attrs); |
||||
assertThat(result).isEqualTo("<html><body>bar</body></html>"); |
||||
} |
||||
|
||||
@Test |
||||
public void layoutDialectCanBeCustomized() throws Exception { |
||||
load(LayoutDialectConfiguration.class); |
||||
LayoutDialect layoutDialect = this.context.getBean(LayoutDialect.class); |
||||
assertThat(ReflectionTestUtils.getField(layoutDialect, "sortingStrategy")) |
||||
.isInstanceOf(GroupingStrategy.class); |
||||
} |
||||
|
||||
private void load(Class<?> config, String... envVariables) { |
||||
this.context = new GenericReactiveWebApplicationContext(); |
||||
EnvironmentTestUtils.addEnvironment(this.context, envVariables); |
||||
if (config != null) { |
||||
this.context.register(config); |
||||
} |
||||
this.context.register(config); |
||||
this.context.refresh(); |
||||
} |
||||
|
||||
@Configuration |
||||
@ImportAutoConfiguration({ThymeleafAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class}) |
||||
protected static class BaseConfiguration { |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
@Import(BaseConfiguration.class) |
||||
static class LayoutDialectConfiguration { |
||||
|
||||
@Bean |
||||
public LayoutDialect layoutDialect() { |
||||
return new LayoutDialect(new GroupingStrategy()); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue