diff --git a/README.md b/README.md
index ad38bdcc..cd9c9c85 100644
--- a/README.md
+++ b/README.md
@@ -113,10 +113,24 @@ The following annotations are currently supported:
>**Note** Please refer to the related Javadoc to learn more about the available options.
-#### Examples
+There are two ways to set htmx response headers on controller methods.
+The first is to use annotations, e.g. `@HxTrigger`, and the second is to use the class [HtmxResponse](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxResponse.html) as the return type of the controller method.
+
+Here you can find a list of all available annotations:
+* [@HxLocation](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxLocation.html)
+* [@HxPushUrl](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxPushUrl.html)
+* [@HxRedirect](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRedirect.html)
+* [@HxRefresh](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRefresh.html)
+* [@HxReplaceUrl](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.html)
+* [@HxReselect](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReselect.html)
+* [@HxReswap](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReswap.html)
+* [@HxRetarget](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRetarget.html)
+* [@HxTrigger](https://javadoc.io/doc/io.github.wimdeblauwe/htmx-spring-boot/latest/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxTrigger.html)
-If you want htmx to trigger an event after the response is processed, you can use the annotation `@HxTrigger` which sets the necessary response header [HX-Trigger](https://htmx.org/headers/hx-trigger/).
+>**Note** Please check the corresponding Javadoc to learn about the available options.
+#### Examples
+If you want htmx to trigger an event by setting the response header [HX-Trigger](https://htmx.org/headers/hx-trigger/), you can use the annotation `@HxTrigger`.
```java
@HxRequest
@HxTrigger("userUpdated") // the event 'userUpdated' will be triggered by htmx
diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java
index 7bf23b46..dc327416 100644
--- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java
+++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptor.java
@@ -1,27 +1,41 @@
package io.github.wimdeblauwe.htmx.spring.boot.mvc;
-import static io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponseHeader.HX_REFRESH;
-import static io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponseHeader.HX_TRIGGER;
-import static io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponseHeader.HX_TRIGGER_AFTER_SETTLE;
-import static io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponseHeader.HX_TRIGGER_AFTER_SWAP;
-
-import java.lang.reflect.Method;
-
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.annotation.Nonnull;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+import java.time.Duration;
+
+import static io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponseHeader.*;
public class HtmxHandlerInterceptor implements HandlerInterceptor {
+
+ private final ObjectMapper objectMapper;
+
+ public HtmxHandlerInterceptor(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
@Override
- public boolean preHandle(HttpServletRequest request,
- HttpServletResponse response,
- Object handler) {
+ public boolean preHandle(@Nonnull HttpServletRequest request,
+ @Nonnull HttpServletResponse response,
+ @Nonnull Object handler) {
if (handler instanceof HandlerMethod) {
Method method = ((HandlerMethod) handler).getMethod();
+ setHxLocation(response, method);
+ setHxPushUrl(response, method);
+ setHxRedirect(response, method);
+ setHxReplaceUrl(response, method);
+ setHxReswap(response, method);
+ setHxRetarget(response, method);
+ setHxReselect(response, method);
setHxTrigger(response, method);
setHxRefresh(response, method);
setVary(request, response);
@@ -30,6 +44,60 @@ public boolean preHandle(HttpServletRequest request,
return true;
}
+ private void setHxLocation(HttpServletResponse response, Method method) {
+ HxLocation methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxLocation.class);
+ if (methodAnnotation != null) {
+ var location = convertToLocation(methodAnnotation);
+ if (location.hasContextData()) {
+ setHeaderJsonValue(response, HtmxResponseHeader.HX_LOCATION.getValue(), location);
+ } else {
+ response.setHeader(HtmxResponseHeader.HX_LOCATION.getValue(), location.getPath());
+ }
+ }
+ }
+
+ private void setHxPushUrl(HttpServletResponse response, Method method) {
+ HxPushUrl methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxPushUrl.class);
+ if (methodAnnotation != null) {
+ response.setHeader(HX_PUSH_URL.getValue(), methodAnnotation.value());
+ }
+ }
+
+ private void setHxRedirect(HttpServletResponse response, Method method) {
+ HxRedirect methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxRedirect.class);
+ if (methodAnnotation != null) {
+ response.setHeader(HX_REDIRECT.getValue(), methodAnnotation.value());
+ }
+ }
+
+ private void setHxReplaceUrl(HttpServletResponse response, Method method) {
+ HxReplaceUrl methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxReplaceUrl.class);
+ if (methodAnnotation != null) {
+ response.setHeader(HX_REPLACE_URL.getValue(), methodAnnotation.value());
+ }
+ }
+
+ private void setHxReswap(HttpServletResponse response, Method method) {
+ HxReswap methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxReswap.class);
+ if (methodAnnotation != null) {
+ response.setHeader(HX_RESWAP.getValue(), convertToReswap(methodAnnotation));
+ }
+ }
+
+ private void setHxRetarget(HttpServletResponse response, Method method) {
+ HxRetarget methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxRetarget.class);
+ if (methodAnnotation != null) {
+ response.setHeader(HX_RETARGET.getValue(), methodAnnotation.value());
+ }
+ }
+
+ private void setHxReselect(HttpServletResponse response, Method method) {
+ HxReselect methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxReselect.class);
+ if (methodAnnotation != null) {
+ response.setHeader(HX_RESELECT.getValue(), methodAnnotation.value());
+ }
+ }
+
private void setVary(HttpServletRequest request, HttpServletResponse response) {
if (request.getHeader(HtmxRequestHeader.HX_REQUEST.getValue()) != null) {
response.addHeader(HttpHeaders.VARY, HtmxRequestHeader.HX_REQUEST.getValue());
@@ -62,4 +130,73 @@ private String getHeaderName(HxTriggerLifecycle lifecycle) {
throw new IllegalArgumentException("Unknown lifecycle:" + lifecycle);
}
}
+
+ private void setHeaderJsonValue(HttpServletResponse response, String name, Object value) {
+ try {
+ response.setHeader(name, objectMapper.writeValueAsString(value));
+ } catch (JsonProcessingException e) {
+ throw new IllegalArgumentException("Unable to set header " + name + " to " + value, e);
+ }
+ }
+
+ private HtmxLocation convertToLocation(HxLocation annotation) {
+ var location = new HtmxLocation();
+ location.setPath(annotation.path());
+ if (!annotation.source().isEmpty()) {
+ location.setSource(annotation.source());
+ }
+ if (!annotation.event().isEmpty()) {
+ location.setEvent(annotation.event());
+ }
+ if (!annotation.handler().isEmpty()) {
+ location.setHandler(annotation.handler());
+ }
+ if (!annotation.target().isEmpty()) {
+ location.setTarget(annotation.target());
+ }
+ if (!annotation.target().isEmpty()) {
+ location.setSwap(annotation.swap());
+ }
+ return location;
+ }
+
+ private String convertToReswap(HxReswap annotation) {
+
+ var reswap = new HtmxReswap(annotation.value());
+ if (annotation.swap() != -1) {
+ reswap.swap(Duration.ofMillis(annotation.swap()));
+ }
+ if (annotation.settle() != -1) {
+ reswap.swap(Duration.ofMillis(annotation.settle()));
+ }
+ if (annotation.transition()) {
+ reswap.transition();
+ }
+ if (annotation.focusScroll() != HxReswap.FocusScroll.UNDEFINED) {
+ reswap.focusScroll(annotation.focusScroll() == HxReswap.FocusScroll.TRUE);
+ }
+ if (annotation.show() != HxReswap.Position.UNDEFINED) {
+ reswap.show(convertToPosition(annotation.show()));
+ if (!annotation.showTarget().isEmpty()) {
+ reswap.scrollTarget(annotation.showTarget());
+ }
+ }
+ if (annotation.scroll() != HxReswap.Position.UNDEFINED) {
+ reswap.scroll(convertToPosition(annotation.scroll()));
+ if (!annotation.scrollTarget().isEmpty()) {
+ reswap.scrollTarget(annotation.scrollTarget());
+ }
+ }
+
+ return reswap.toString();
+ }
+
+ private HtmxReswap.Position convertToPosition(HxReswap.Position position) {
+ return switch (position) {
+ case TOP -> HtmxReswap.Position.TOP;
+ case BOTTOM -> HtmxReswap.Position.BOTTOM;
+ default -> throw new IllegalStateException("Unexpected value: " + position);
+ };
+ }
+
}
diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxMvcAutoConfiguration.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxMvcAutoConfiguration.java
index f11347a5..3524d651 100644
--- a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxMvcAutoConfiguration.java
+++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxMvcAutoConfiguration.java
@@ -43,7 +43,7 @@ public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(new HtmxHandlerInterceptor());
+ registry.addInterceptor(new HtmxHandlerInterceptor(objectMapper));
}
@Override
diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxValue.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxValue.java
new file mode 100644
index 00000000..36356d9d
--- /dev/null
+++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxValue.java
@@ -0,0 +1,13 @@
+package io.github.wimdeblauwe.htmx.spring.boot.mvc;
+
+/**
+ * Holder for constant values.
+ */
+public class HtmxValue {
+
+ /**
+ * Constant for use in annotations that support a {@code false} value to disable functions.
+ */
+ public static final String FALSE = "false";
+
+}
diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxLocation.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxLocation.java
new file mode 100644
index 00000000..b66db6b5
--- /dev/null
+++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxLocation.java
@@ -0,0 +1,56 @@
+package io.github.wimdeblauwe.htmx.spring.boot.mvc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Map;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * Annotation to do a client side redirect that does not do a full page reload.
+ *
+ * Note that this annotation does not provide support for specifying {@code values} or {@code headers}.
+ * If you want to do this, use {@link HtmxResponse} instead.
+ *
+ * @see HX-Location Response Header
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface HxLocation {
+
+ /**
+ * The url path to make the redirect.
+ *
This is an alias for {@link #path}. For example, + * {@code @HxLocation("/foo")} is equivalent to + * {@code @HxLocation(path="/foo")}. + */ + @AliasFor("path") + String value() default ""; + /** + * The url path to make the redirect. + */ + String path() default ""; + /** + * The source element of the request + */ + String source() default ""; + /** + * An event that "triggered" the request + */ + String event() default ""; + /** + * A callback that will handle the response HTML. + */ + String handler() default ""; + /** + * The target to swap the response into. + */ + String target() default ""; + /** + * How the response will be swapped in relative to the target + */ + String swap() default ""; + +} diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxPushUrl.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxPushUrl.java new file mode 100644 index 00000000..40cbd9a9 --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxPushUrl.java @@ -0,0 +1,24 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to push a new url into the history stack. + * + * @see HX-Push-Url Response Header + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxPushUrl { + + /** + * The URL to be pushed into the location bar. This may be relative or absolute, + * as per history.pushState(). + * Or {@link HtmxValue#FALSE}, which prevents the browser’s history from being updated. + */ + String value(); + +} diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRedirect.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRedirect.java new file mode 100644 index 00000000..4edf356a --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRedirect.java @@ -0,0 +1,22 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to do a client-side redirect to a new location. + * + * @see HX-Redirect + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxRedirect { + + /** + * The URL to use to do a client-side redirect to a new location. + */ + String value(); + +} diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.java new file mode 100644 index 00000000..5085768f --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReplaceUrl.java @@ -0,0 +1,28 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +/** + * Annotation to replace the current URL in the location bar. + * + * @see HX-Replace-Url + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxReplaceUrl { + + /** + * The URL to replace the current URL in the location bar. + * This may be relative or absolute, as per + * history.replaceState(), + * but must have the same origin as the current URL. + * Or {@link HtmxValue#FALSE} which prevents the browser’s current URL from being updated. + */ + String value(); + +} diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReselect.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReselect.java new file mode 100644 index 00000000..08bc4769 --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReselect.java @@ -0,0 +1,24 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to specify a CSS selector that allows you to choose which part + * of the response is used to be swapped in. + * + * @see HX-Retarget + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxReselect { + + /** + * A CSS selector that allows you to choose which part of the response is used to be swapped in. + *
Overrides an existing hx-select on the triggering element. + */ + String value(); + +} diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReswap.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReswap.java new file mode 100644 index 00000000..ad2c1aeb --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxReswap.java @@ -0,0 +1,87 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +/** + * Annotation to specify how the response will be swapped. + * See hx-swap for possible values. + * + * @see HX-Reswap + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxReswap { + + /** + * A value to specify how the response will be swapped. + * + * @see hx-swap + */ + HxSwapType value() default HxSwapType.INNER_HTML; + + /** + * Set the time in milliseconds that should elapse after receiving a response to swap the content. + */ + long swap() default -1; + + /** + * Set the time in milliseconds that should elapse between the swap and the settle logic. + */ + long settle() default -1; + + /** + * Changes the scrolling behavior of the target element. + */ + Position scroll() default Position.UNDEFINED; + + /** + * Used to target a different element for scrolling. + */ + String scrollTarget() default ""; + + /** + * Changes the scrolling behavior of the target element. + */ + Position show() default Position.UNDEFINED; + + /** + * Used to target a different element for showing. + */ + String showTarget() default ""; + + /** + * Enables the use of the new + * View Transitions API + * when a swap occurs. + */ + boolean transition() default false; + + /** + * Enable or disable auto-scrolling to focused inputs between requests. + */ + FocusScroll focusScroll() default FocusScroll.UNDEFINED; + + /** + * Represents the values for {@link #focusScroll()} + */ + public enum FocusScroll { + TRUE, + FALSE, + UNDEFINED + } + + /** + * Represents the position values for {@link #show()} and {@link #scroll()} + */ + public enum Position { + TOP, + BOTTOM, + UNDEFINED + } + +} diff --git a/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRetarget.java b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRetarget.java new file mode 100644 index 00000000..1e85ec86 --- /dev/null +++ b/htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HxRetarget.java @@ -0,0 +1,23 @@ +package io.github.wimdeblauwe.htmx.spring.boot.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to specify a CSS selector that updates the target of + * the content update to a different element on the page. + * + * @see HX-Retarget + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HxRetarget { + + /** + * A CSS selector that updates the target of the content update to a different element on the page. + */ + String value(); + +} diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java index 74b8e34c..ef98d93a 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerInterceptorTest.java @@ -81,4 +81,61 @@ public void testHxRefresh() throws Exception { .andExpect(status().isOk()) .andExpect(header().string("HX-Refresh", "true")); } + + @Test + public void testHxLocationWithContextData() throws Exception { + mockMvc.perform(get("/hx-location-with-context-data")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Location", "{\"path\":\"/path\",\"source\":\"source\",\"event\":\"event\",\"handler\":\"handler\",\"target\":\"target\",\"swap\":\"swap\"}")); + } + + @Test + public void testHxLocationWithoutContextData() throws Exception { + mockMvc.perform(get("/hx-location-without-context-data")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Location", "/path")); + } + + @Test + public void testHxPushUrl() throws Exception { + mockMvc.perform(get("/hx-push-url")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Push-Url", "/path")); + } + + @Test + public void testHxRedirect() throws Exception { + mockMvc.perform(get("/hx-redirect")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Redirect", "/path")); + } + + @Test + public void testHxReplaceUrl() throws Exception { + mockMvc.perform(get("/hx-replace-url")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Replace-Url", "/path")); + } + + @Test + public void testHxReswap() throws Exception { + mockMvc.perform(get("/hx-reswap")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Reswap", "innerHTML swap:300ms")); + } + + @Test + public void testHxRetarget() throws Exception { + mockMvc.perform(get("/hx-retarget")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Retarget", "#target")); + } + + @Test + public void testHxReselect() throws Exception { + mockMvc.perform(get("/hx-reselect")) + .andExpect(status().isOk()) + .andExpect(header().string("HX-Reselect", "#target")); + } + } diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxResponseHandlerMethodReturnValueHandlerTest.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxResponseHandlerMethodReturnValueHandlerTest.java index 099c554c..f7a5d60e 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxResponseHandlerMethodReturnValueHandlerTest.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxResponseHandlerMethodReturnValueHandlerTest.java @@ -138,7 +138,7 @@ public void testException() throws Exception { .andExpect(status().isOk()) .andExpect(header().string("HX-Reswap", "none")) .andReturn().getResponse().getContentAsString(); - assertThat(html).contains(""" + assertThat(html).containsIgnoringWhitespaces(""" Fake exception """); diff --git a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java index f4cc67ea..e0acd385 100644 --- a/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java +++ b/htmx-spring-boot/src/test/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/TestController.java @@ -50,7 +50,6 @@ public String hxTriggerWithAliasForOverride() { return ""; } - @GetMapping("/hx-refresh") @HxRefresh @ResponseBody @@ -58,6 +57,62 @@ public String hxRefresh() { return ""; } + @GetMapping("/hx-location-without-context-data") + @HxLocation("/path") + @ResponseBody + public String hxLocationWithoutContextData() { + return ""; + } + + @GetMapping("/hx-location-with-context-data") + @HxLocation(path = "/path", source = "source", event = "event", handler = "handler", target = "target", swap = "swap") + @ResponseBody + public String hxLocationWithContextData() { + return ""; + } + + @GetMapping("/hx-push-url") + @HxPushUrl("/path") + @ResponseBody + public String hxPushUrl() { + return ""; + } + + @GetMapping("/hx-redirect") + @HxRedirect("/path") + @ResponseBody + public String hxRedirect() { + return ""; + } + + @GetMapping("/hx-replace-url") + @HxReplaceUrl("/path") + @ResponseBody + public String hxReplaceUrl() { + return ""; + } + + @GetMapping("/hx-reswap") + @HxReswap(value = HxSwapType.INNER_HTML, swap = 300) + @ResponseBody + public String hxReswap() { + return ""; + } + + @GetMapping("/hx-retarget") + @HxRetarget("#target") + @ResponseBody + public String hxRetarget() { + return ""; + } + + @GetMapping("/hx-reselect") + @HxReselect("#target") + @ResponseBody + public String hxReselect() { + return ""; + } + @GetMapping("/hx-vary") @ResponseBody public String hxVary() {