From 83aec9e27ea686cc00ee37df88aa0168cafb8137 Mon Sep 17 00:00:00 2001 From: guqing Date: Mon, 18 Sep 2023 11:43:32 +0800 Subject: [PATCH 1/4] fix: not using the default template when the custom template does not exist --- .../app/theme/router/ViewNameResolver.java | 34 ++++++------ .../theme/router/ViewNameResolverTest.java | 54 ++++++++++++------- 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/application/src/main/java/run/halo/app/theme/router/ViewNameResolver.java b/application/src/main/java/run/halo/app/theme/router/ViewNameResolver.java index 38e7bdf15b..42ce51fafb 100644 --- a/application/src/main/java/run/halo/app/theme/router/ViewNameResolver.java +++ b/application/src/main/java/run/halo/app/theme/router/ViewNameResolver.java @@ -1,14 +1,13 @@ package run.halo.app.theme.router; -import java.util.Locale; +import java.nio.file.Files; import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties; -import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.server.ServerRequest; import reactor.core.publisher.Mono; -import run.halo.app.theme.HaloViewResolver; +import run.halo.app.theme.ThemeResolver; /** * The {@link ViewNameResolver} is used to resolve view name. @@ -19,7 +18,8 @@ @Component @AllArgsConstructor public class ViewNameResolver { - private final HaloViewResolver haloViewResolver; + private static final String TEMPLATES = "templates"; + private final ThemeResolver themeResolver; private final ThymeleafProperties thymeleafProperties; /** @@ -29,20 +29,24 @@ public class ViewNameResolver { public Mono resolveViewNameOrDefault(ServerRequest request, String name, String defaultName) { if (StringUtils.isBlank(name)) { - return Mono.just(defaultName); + return Mono.justOrEmpty(defaultName); } - final String nameToUse = processName(name); - Locale locale = LocaleContextHolder.getLocale(request.exchange().getLocaleContext()); - return haloViewResolver.resolveViewName(nameToUse, locale) - .map(view -> nameToUse) - .switchIfEmpty(Mono.just(defaultName)); + return themeResolver.getTheme(request.exchange()) + .mapNotNull(themeContext -> { + String templateResourceName = computeResourceName(name); + var resourcePath = themeContext.getPath() + .resolve(TEMPLATES) + .resolve(templateResourceName); + return Files.exists(resourcePath) ? name : defaultName; + }) + .switchIfEmpty(Mono.justOrEmpty(defaultName)); } - String processName(String name) { - String nameToLookup = name; - if (StringUtils.endsWith(name, thymeleafProperties.getSuffix())) { - nameToLookup = StringUtils.substringBeforeLast(name, thymeleafProperties.getSuffix()); + String computeResourceName(String name) { + if (StringUtils.isBlank(name)) { + return name; } - return nameToLookup; + return StringUtils.endsWith(name, thymeleafProperties.getSuffix()) + ? name : name + thymeleafProperties.getSuffix(); } } diff --git a/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java b/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java index f069e7dfbe..a294a1e669 100644 --- a/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java +++ b/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java @@ -2,14 +2,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; +import java.io.File; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.nio.file.Files; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; @@ -17,11 +20,11 @@ import org.springframework.http.HttpMethod; import org.springframework.mock.web.reactive.function.server.MockServerRequest; import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.web.reactive.result.view.View; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import run.halo.app.theme.HaloViewResolver; +import run.halo.app.theme.ThemeContext; +import run.halo.app.theme.ThemeResolver; /** * Tests for {@link ViewNameResolver}. @@ -33,7 +36,7 @@ class ViewNameResolverTest { @Mock - private HaloViewResolver haloViewResolver; + private ThemeResolver themeResolver; @Mock private ThymeleafProperties thymeleafProperties; @@ -41,17 +44,27 @@ class ViewNameResolverTest { @InjectMocks private ViewNameResolver viewNameResolver; + @TempDir + private File themePath; + @BeforeEach - void setUp() { + void setUp() throws IOException { when(thymeleafProperties.getSuffix()).thenReturn(ThymeleafProperties.DEFAULT_SUFFIX); - when(haloViewResolver.resolveViewName(eq("post_news"), any())) - .thenReturn(Mono.just(Mockito.mock(View.class))); - when(haloViewResolver.resolveViewName(eq("post_docs"), any())) - .thenReturn(Mono.just(new EmptyView())); + var templatesPath = themePath.toPath().resolve("templates"); + if (!Files.exists(templatesPath)) { + Files.createDirectory(templatesPath); + } + Files.createFile(templatesPath.resolve("post_news.html")); + Files.createFile(templatesPath.resolve("post_docs.html")); - when(haloViewResolver.resolveViewName(eq("post_nothing"), any())) - .thenReturn(Mono.empty()); + when(themeResolver.getTheme(any())) + .thenReturn(Mono.fromSupplier(() -> ThemeContext.builder() + .name("fake-theme") + .path(themePath.toPath()) + .active(true) + .build()) + ); } @Test @@ -71,7 +84,7 @@ void resolveViewNameOrDefault() throws URISyntaxException { String viewName = "post_docs" + thymeleafProperties.getSuffix(); viewNameResolver.resolveViewNameOrDefault(request, viewName, "post") .as(StepVerifier::create) - .expectNext("post_docs") + .expectNext(viewName) .verifyComplete(); viewNameResolver.resolveViewNameOrDefault(request, "post_nothing", "post") @@ -82,11 +95,14 @@ void resolveViewNameOrDefault() throws URISyntaxException { @Test void processName() { - assertThat(viewNameResolver.processName("post_news")).isEqualTo("post_news"); - assertThat(viewNameResolver.processName("post_news" + thymeleafProperties.getSuffix())) - .isEqualTo("post_news"); - assertThat(viewNameResolver.processName("post_news.test")) - .isEqualTo("post_news.test"); - assertThat(viewNameResolver.processName(null)).isNull(); + var suffix = thymeleafProperties.getSuffix(); + assertThat(viewNameResolver.computeResourceName("post_news")) + .isEqualTo("post_news" + suffix); + assertThat( + viewNameResolver.computeResourceName("post_news" + suffix)) + .isEqualTo("post_news" + suffix); + assertThat(viewNameResolver.computeResourceName("post_news.test")) + .isEqualTo("post_news.test" + suffix); + assertThat(viewNameResolver.computeResourceName(null)).isNull(); } -} \ No newline at end of file +} From ec27d31012cd58a20adb8b6c050ee3afab933803 Mon Sep 17 00:00:00 2001 From: guqing Date: Fri, 22 Sep 2023 16:28:14 +0800 Subject: [PATCH 2/4] chore: remove if case --- .../java/run/halo/app/theme/router/ViewNameResolver.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/run/halo/app/theme/router/ViewNameResolver.java b/application/src/main/java/run/halo/app/theme/router/ViewNameResolver.java index 42ce51fafb..de53492ca3 100644 --- a/application/src/main/java/run/halo/app/theme/router/ViewNameResolver.java +++ b/application/src/main/java/run/halo/app/theme/router/ViewNameResolver.java @@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties; import org.springframework.stereotype.Component; +import org.springframework.util.Assert; import org.springframework.web.reactive.function.server.ServerRequest; import reactor.core.publisher.Mono; import run.halo.app.theme.ThemeResolver; @@ -43,9 +44,7 @@ public Mono resolveViewNameOrDefault(ServerRequest request, String name, } String computeResourceName(String name) { - if (StringUtils.isBlank(name)) { - return name; - } + Assert.notNull(name, "Name must not be null"); return StringUtils.endsWith(name, thymeleafProperties.getSuffix()) ? name : name + thymeleafProperties.getSuffix(); } From 3d87e74af180d826dc5dbb746344ce92b4fb6010 Mon Sep 17 00:00:00 2001 From: guqing Date: Fri, 22 Sep 2023 17:07:10 +0800 Subject: [PATCH 3/4] fix: unit test --- .../java/run/halo/app/theme/router/ViewNameResolverTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java b/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java index a294a1e669..bf243bd20c 100644 --- a/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java +++ b/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java @@ -103,6 +103,5 @@ void processName() { .isEqualTo("post_news" + suffix); assertThat(viewNameResolver.computeResourceName("post_news.test")) .isEqualTo("post_news.test" + suffix); - assertThat(viewNameResolver.computeResourceName(null)).isNull(); } } From ea4d725cd6423ec8a03f55b0138b870d61c2e55b Mon Sep 17 00:00:00 2001 From: guqing Date: Fri, 22 Sep 2023 17:23:44 +0800 Subject: [PATCH 4/4] chore: unit test case --- .../java/run/halo/app/theme/router/ViewNameResolverTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java b/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java index bf243bd20c..8ec9e863c5 100644 --- a/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java +++ b/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java @@ -1,6 +1,7 @@ package run.halo.app.theme.router; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -103,5 +104,9 @@ void processName() { .isEqualTo("post_news" + suffix); assertThat(viewNameResolver.computeResourceName("post_news.test")) .isEqualTo("post_news.test" + suffix); + + assertThatThrownBy(() -> viewNameResolver.computeResourceName(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Name must not be null"); } }