Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.wicket.core.util.string;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.apache.wicket.response.StringResponse;
import org.apache.wicket.util.value.AttributeMap;
import org.junit.jupiter.api.Test;

class JsonUtilsTest
{
@Test
public void writeInlineScript()
{
StringResponse response = new StringResponse();
AttributeMap attributes = new AttributeMap();
attributes.putAttribute(JavaScriptUtils.ATTR_TYPE, "importmap");
JsonUtils.writeInlineScript(response,
"{\"imports\":{\"<script>\":\"<!--\"}}", attributes);

assertEquals("<script type=\"importmap\">" //
+ "{\"imports\":{\"\\u003Cscript>\":\"\\u003C!--\"}}" //
+ "</script>\n", response.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.wicket.markup.head;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;

import org.apache.wicket.mock.MockWebResponse;
import org.junit.jupiter.api.Test;

public class JavaScriptContentHeaderItemTest
{
@Test
void outputsTextJavascriptAsTypeIfNoTypeSet() {
JavaScriptContentHeaderItem item = new JavaScriptContentHeaderItem("", "the id");
MockWebResponse response = new MockWebResponse();

item.render(response);

assertEquals("""
<script type="text/javascript" id="the id">
/*<![CDATA[*/

/*]]>*/
</script>
""", response.getTextResponse().toString());
}

@Test
void outputsTypeThatIsSet() {
JavaScriptContentHeaderItem item = new JavaScriptContentHeaderItem("", "the id")
.setType(JavaScriptReferenceType.MODULE);
MockWebResponse response = new MockWebResponse();

item.render(response);

assertEquals("""
<script type="module" id="the id">
/*<![CDATA[*/

/*]]>*/
</script>
""", response.getTextResponse().toString());
}

@Test
void itemsWithSameJavascriptAndDifferentTypesAreInequal() {
JavaScriptContentHeaderItem item1 = new JavaScriptContentHeaderItem("", "the id")
.setType(JavaScriptReferenceType.TEXT_JAVASCRIPT);
JavaScriptContentHeaderItem item2 = new JavaScriptContentHeaderItem("", "the id")
.setType(JavaScriptReferenceType.MODULE);

assertNotEquals(item1, item2);
}

@Test
void itemsWithSameJavascriptAndDifferentTypesHaveDifferentHashCodes() {
JavaScriptContentHeaderItem item1 = new JavaScriptContentHeaderItem("", "the id")
.setType(JavaScriptReferenceType.TEXT_JAVASCRIPT);
JavaScriptContentHeaderItem item2 = new JavaScriptContentHeaderItem("", "the id")
.setType(JavaScriptReferenceType.MODULE);

assertNotEquals(item1.hashCode(), item2.hashCode());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.wicket.markup.head;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections4.IterableUtils;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.mock.MockWebResponse;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.apache.wicket.util.tester.WicketTestCase;
import org.junit.jupiter.api.Test;

public class JavaScriptImportMapHeaderItemTest extends WicketTestCase
{
@Test
void renderTokensAreCorrectIfNoIdSpecified() {
JavaScriptHeaderItem item = new JavaScriptImportMapHeaderItem(null, null, null);

List<?> renderTokens = IterableUtils.toList(item.getRenderTokens());

assertEquals(List.of("{}"), renderTokens);
}

@Test
void renderTokensAreCorrectIfIdSpecified() {
JavaScriptHeaderItem item = new JavaScriptImportMapHeaderItem(null, null, null)
.setId("theId");

List<?> renderTokens = IterableUtils.toList(item.getRenderTokens());

assertEquals(List.of("theId", "{}"), renderTokens);
}

@Test
void doesNotAddPropertiesIfItemNotSpecified() {
JavaScriptImportMapHeaderItem item = new JavaScriptImportMapHeaderItem(null, null, null);
MockWebResponse response = new MockWebResponse();

item.render(response);

assertEquals("""
<script type="importmap">{}</script>
""", response.getTextResponse().toString());

}

@Test
void outputsBaseModuleSpecifierMapIfSpecified() {
Map<String, JavaScriptResourceReference> resourceReferencesByModuleSpecifier = new HashMap<>();
resourceReferencesByModuleSpecifier.put("themodule", new JavaScriptResourceReference(JavaScriptImportMapHeaderItemTest.class, "thescript.js"));
JavaScriptImportMapHeaderItem item = new JavaScriptImportMapHeaderItem(resourceReferencesByModuleSpecifier, null, null);
MockWebResponse response = new MockWebResponse();
tester.startComponentInPage(new WebMarkupContainer("someId"));

item.render(response);

assertEquals("""
<script type="importmap">{"imports":{"themodule":"./wicket/resource/org.apache.wicket.markup.head.JavaScriptImportMapHeaderItemTest/thescript.js"}}</script>
""", response.getTextResponse().toString());

}

@Test
void outputsScopedModuleSpecifierMapsIfSpecified() {
Map<String, JavaScriptResourceReference> resourceReferencesByModuleSpecifier = new HashMap<>();
resourceReferencesByModuleSpecifier.put("themodule", new JavaScriptResourceReference(JavaScriptImportMapHeaderItemTest.class, "thescript.js"));
Map<String, Map<String, JavaScriptResourceReference>> resourceReferencesByModuleSpecifierByScopeUrl = new HashMap<>();
resourceReferencesByModuleSpecifierByScopeUrl.put("thescope", resourceReferencesByModuleSpecifier);
JavaScriptImportMapHeaderItem item = new JavaScriptImportMapHeaderItem(null, resourceReferencesByModuleSpecifierByScopeUrl, null);
MockWebResponse response = new MockWebResponse();
tester.startComponentInPage(new WebMarkupContainer("someId"));

item.render(response);

assertEquals("""
<script type="importmap">{"scopes":{"thescope":{"themodule":"./wicket/resource/org.apache.wicket.markup.head.JavaScriptImportMapHeaderItemTest/thescript.js"}}}</script>
""", response.getTextResponse().toString());

}

@Test
void outputsHashesIfSpecified() {
Map<JavaScriptResourceReference, Collection<String>> integrityHashesByResourceReference = new HashMap<>();
integrityHashesByResourceReference.put(
new JavaScriptResourceReference(JavaScriptImportMapHeaderItemTest.class,
"thescript.js"), List.of("first-hash", "second-hash"));
JavaScriptImportMapHeaderItem item = new JavaScriptImportMapHeaderItem(null, null, integrityHashesByResourceReference);
MockWebResponse response = new MockWebResponse();
tester.startComponentInPage(new WebMarkupContainer("someId"));

item.render(response);

assertEquals("""
<script type="importmap">{"integrity":{"./wicket/resource/org.apache.wicket.markup.head.JavaScriptImportMapHeaderItemTest/thescript.js":"first-hash second-hash"}}</script>
""", response.getTextResponse().toString());

}

@Test
void outputsIdIfSpecified() {
JavaScriptHeaderItem item = new JavaScriptImportMapHeaderItem(null, null, null)
.setId("theId");
MockWebResponse response = new MockWebResponse();
tester.startComponentInPage(new WebMarkupContainer("someId"));

item.render(response);

assertEquals("""
<script type="importmap" id="theId">{}</script>
""", response.getTextResponse().toString());

}

@Test
void outputsNonceIfSpecified() {
AbstractCspHeaderItem item = new JavaScriptImportMapHeaderItem(null, null, null)
.setNonce("theNonce");
MockWebResponse response = new MockWebResponse();

item.render(response);

assertEquals("""
<script type="importmap" nonce="theNonce">{}</script>
""", response.getTextResponse().toString());

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.wicket.core.util.string;

import org.apache.wicket.request.Response;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.value.AttributeMap;

/**
* Provide some helpers to write JSON-related tags to the response object.
*
* @author Juergen Donnerstag
*/
public class JsonUtils
{
/**
* Write the simple text to the response object surrounded by a script tag. <a
* href=" https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements">Escapes
* <code>&lt;</code> to <code>\u005Cu003C</code></a>.
*
* @param response
* The HTTP: response
* @param text
* The text to added in between the script tags
* @param attributes
* Extra tag attributes. See constants prefixed with <code>ATTR_</code> in {@link JavaScriptUtils}
*/
public static void writeInlineScript(final Response response, final CharSequence text, AttributeMap attributes)
{
response.write("<script");
response.write(attributes.toCharSequence());
response.write(">");
response.write(Strings.replaceAll(text, "<", "\\u003C"));
response.write("</script>\n");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public class JavaScriptContentHeaderItem extends JavaScriptHeaderItem
{
private final CharSequence javaScript;

private JavaScriptReferenceType type = JavaScriptReferenceType.TEXT_JAVASCRIPT;

/**
* Creates a new {@code JavaScriptContentHeaderItem}.
*
Expand All @@ -57,11 +59,28 @@ public CharSequence getJavaScript()
return javaScript;
}

public JavaScriptReferenceType getType() {
return type;
}

/**
* Set the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type">type</a> of
* the script. If no type is set, it defaults to {@link JavaScriptReferenceType#TEXT_JAVASCRIPT}.
*
* @param type the new type.
*/
public JavaScriptContentHeaderItem setType(final JavaScriptReferenceType type) {
this.type = type;
return this;
}

@Override
public void render(Response response)
{
AttributeMap attributes = new AttributeMap();
attributes.putAttribute(JavaScriptUtils.ATTR_TYPE, "text/javascript");
// An empty string works the same as `text/javascript`, but use the latter for backward compatibility.
JavaScriptReferenceType actualType = type == null ? JavaScriptReferenceType.TEXT_JAVASCRIPT : type;
attributes.putAttribute(JavaScriptUtils.ATTR_TYPE, actualType.getType());
attributes.putAttribute(JavaScriptUtils.ATTR_ID, getId());
attributes.putAttribute(JavaScriptUtils.ATTR_CSP_NONCE, getNonce());
JavaScriptUtils.writeInlineScript(response, getJavaScript(), attributes);
Expand All @@ -88,7 +107,8 @@ public boolean equals(Object o)
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
JavaScriptContentHeaderItem that = (JavaScriptContentHeaderItem) o;
return Objects.equals(javaScript, that.javaScript);
return Objects.equals(javaScript, that.javaScript) &&
Objects.equals(type, that.type);
}

@Override
Expand All @@ -97,6 +117,7 @@ public int hashCode()
// Not using `Objects.hash` for performance reasons
int result = super.hashCode();
result = 31 * result + ((javaScript != null) ? javaScript.hashCode() : 0);
result = 31 * result + ((type != null) ? type.hashCode() : 0);
return result;
}
}
Loading