Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing implementations for RadioNodeList and HTMLFormControlsCollection #838

Merged
merged 4 commits into from
Aug 1, 2024
Merged
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
84 changes: 84 additions & 0 deletions src/main/java/org/htmlunit/javascript/host/dom/RadioNodeList.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,63 @@
*/
package org.htmlunit.javascript.host.dom;

import org.htmlunit.html.DomNode;
import org.htmlunit.html.HtmlRadioButtonInput;
import org.htmlunit.javascript.configuration.JsxClass;
import org.htmlunit.javascript.configuration.JsxConstructor;
import org.htmlunit.javascript.configuration.JsxGetter;
import org.htmlunit.javascript.configuration.JsxSetter;

import java.util.List;

import static org.htmlunit.html.DomElement.ATTRIBUTE_NOT_DEFINED;

/**
* A JavaScript object for {@code RadioNodeList}.
*
* @author Ahmed Ashour
* @author Ronald Brill
* @author Lai Quang Duong
*/
@JsxClass
public class RadioNodeList extends NodeList {

/**
* Creates an instance.
*/
public RadioNodeList() {
super();
}

/**
* Creates an instance.
*
* @param domNode the {@link DomNode}
*/
public RadioNodeList(final DomNode domNode) {
super(domNode, true);
}

/**
* Creates an instance.
*
* @param domNode the {@link DomNode}
* @param attributeChangeSensitive indicates if the content of the collection may change when an attribute
* of a descendant node of parentScope changes (attribute added, modified or removed)
*/
public RadioNodeList(final DomNode domNode, final boolean attributeChangeSensitive) {
super(domNode, attributeChangeSensitive);
}

/**
* Constructs an instance with an initial cache value.
* @param domNode the parent scope, on which we listen for changes
* @param initialElements the initial content for the cache
*/
public RadioNodeList(final DomNode domNode, final List<DomNode> initialElements) {
super(domNode, initialElements);
}

/**
* JavaScript constructor.
*/
Expand All @@ -34,4 +79,43 @@
public void jsConstructor() {
super.jsConstructor();
}

/**
* Returns the value of the first checked radio button represented by radioNodeList.
* @return the value of the first checked radio button represented by radioNodeList ("on" if value attribute
* is not defined) or an empty string if no radio button is checked.
* @see <a href="https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#the-htmlformcontrolscollection-interface">HTML Standard</a>

Check failure on line 87 in src/main/java/org/htmlunit/javascript/host/dom/RadioNodeList.java

View workflow job for this annotation

GitHub Actions / CheckStyle

[checkstyle] reported by reviewdog 🐶 Line is longer than 120 characters (found 146). Raw Output: /home/runner/work/htmlunit/htmlunit/src/main/java/org/htmlunit/javascript/host/dom/RadioNodeList.java:87:0: error: Line is longer than 120 characters (found 146). (com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck)
*/
@JsxGetter
public String getValue() {
for (final DomNode node : getElements()) {
if (node instanceof HtmlRadioButtonInput && ((HtmlRadioButtonInput) node).isChecked()) {
final String value = ((HtmlRadioButtonInput) node).getValueAttribute();
return value == ATTRIBUTE_NOT_DEFINED ? "on" : value;
}
}

return "";
}

/**
* Checks the first radio button represented by radioNodeList that has value equal the specified value.
* @param newValue the value of the radio button to be checked.
* @see <a href="https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#the-htmlformcontrolscollection-interface">HTML Standard</a>

Check failure on line 104 in src/main/java/org/htmlunit/javascript/host/dom/RadioNodeList.java

View workflow job for this annotation

GitHub Actions / CheckStyle

[checkstyle] reported by reviewdog 🐶 Line is longer than 120 characters (found 146). Raw Output: /home/runner/work/htmlunit/htmlunit/src/main/java/org/htmlunit/javascript/host/dom/RadioNodeList.java:104:0: error: Line is longer than 120 characters (found 146). (com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck)
*/
@JsxSetter
public void setValue(final String newValue) {
for (final DomNode node : getElements()) {
if (node instanceof HtmlRadioButtonInput) {
String value = ((HtmlRadioButtonInput) node).getValueAttribute();
if (value == ATTRIBUTE_NOT_DEFINED) {
value = "on";
}
if (newValue.equals(value)) {
((HtmlRadioButtonInput) node).setChecked(true);
break;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,52 @@
*/
package org.htmlunit.javascript.host.html;

import org.htmlunit.html.DomElement;
import org.htmlunit.html.DomNode;
import org.htmlunit.javascript.configuration.JsxClass;
import org.htmlunit.javascript.configuration.JsxConstructor;
import org.htmlunit.javascript.configuration.JsxFunction;
import org.htmlunit.javascript.host.dom.RadioNodeList;

import java.util.ArrayList;
import java.util.List;

/**
* A JavaScript object for {@code HTMLFormControlsCollection}.
*
* @author Ahmed Ashour
* @author Ronald Brill
* @author Lai Quang Duong
*/
@JsxClass
public class HTMLFormControlsCollection extends HTMLCollection {

/**
* Creates an instance.
*/
public HTMLFormControlsCollection() {
super();
}

/**
* Creates an instance.
* @param domNode parent scope
* @param attributeChangeSensitive indicates if the content of the collection may change when an attribute
* of a descendant node of parentScope changes (attribute added, modified or removed)
*/
public HTMLFormControlsCollection(final DomNode domNode, final boolean attributeChangeSensitive) {
super(domNode, attributeChangeSensitive);
}

/**
* Constructs an instance with an initial cache value.
* @param domNode the parent scope, on which we listen for changes
* @param initialElements the initial content for the cache
*/
HTMLFormControlsCollection(final DomNode domNode, final List<DomNode> initialElements) {
super(domNode, initialElements);
}

/**
* JavaScript constructor.
*/
Expand All @@ -34,4 +68,47 @@
public void jsConstructor() {
super.jsConstructor();
}

/**
* Returns the element with ID or name match the specified value from the collection.
* If there are multiple matching elements, then a RadioNodeList object containing all those elements is returned.
* @param name the name or id the element or elements to return
* @return the element or elements corresponding to the specified name or id
* @see <a href="https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#the-htmlformcontrolscollection-interface">HTML Standard</a>

Check failure on line 77 in src/main/java/org/htmlunit/javascript/host/html/HTMLFormControlsCollection.java

View workflow job for this annotation

GitHub Actions / CheckStyle

[checkstyle] reported by reviewdog 🐶 Line is longer than 120 characters (found 146). Raw Output: /home/runner/work/htmlunit/htmlunit/src/main/java/org/htmlunit/javascript/host/html/HTMLFormControlsCollection.java:77:0: error: Line is longer than 120 characters (found 146). (com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck)
*/
@Override
@JsxFunction
public Object namedItem(final String name) {
if (name.isEmpty()) {
return null;
}

final List<DomNode> elements = new ArrayList<>();
for (final Object next : getElements()) {
if (next instanceof DomElement) {
final DomElement elem = (DomElement) next;
final String nodeName = elem.getAttributeDirect(DomElement.NAME_ATTRIBUTE);
if (name.equals(nodeName)) {
elements.add(elem);
continue;
}

final String id = elem.getId();
if (name.equals(id)) {
elements.add(elem);
}
}
}

if (elements.isEmpty()) {
return null;
}
if (elements.size() == 1) {
return getScriptableForElement(elements.get(0));
}

final RadioNodeList nodeList = new RadioNodeList(getDomNodeOrDie(), elements);
nodeList.setElementsSupplier(getElementSupplier());
return nodeList;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.htmlunit.javascript.configuration.JsxSymbol;
import org.htmlunit.javascript.host.dom.AbstractList.EffectOnCache;
import org.htmlunit.javascript.host.dom.DOMTokenList;
import org.htmlunit.javascript.host.dom.RadioNodeList;
import org.htmlunit.javascript.host.event.Event;
import org.htmlunit.util.MimeType;

Expand All @@ -61,6 +62,7 @@
* @author Sudhan Moghe
* @author Ronald Brill
* @author Frank Danek
* @author Lai Quang Duong
*
* @see <a href="http://msdn.microsoft.com/en-us/library/ms535249.aspx">MSDN documentation</a>
*/
Expand Down Expand Up @@ -101,10 +103,11 @@ public void setName(final String name) {
* @return the value of this property
*/
@JsxGetter
public HTMLCollection getElements() {
public HTMLFormControlsCollection getElements() {
final HtmlForm htmlForm = getHtmlForm();

final HTMLCollection elements = new HTMLCollection(htmlForm, false) {
final HTMLFormControlsCollection elements = new HTMLFormControlsCollection(htmlForm,
false) {
@Override
protected Object getWithPreemption(final String name) {
return HTMLFormElement.this.getWithPreemption(name);
Expand Down Expand Up @@ -382,9 +385,11 @@ protected Object getWithPreemption(final String name) {
}
final List<DomNode> nodes = new ArrayList<>(elements);

final HTMLCollection coll = new HTMLCollection(getHtmlForm(), nodes);
coll.setElementsSupplier((Supplier<List<DomNode>> & Serializable) () -> new ArrayList<>(findElements(name)));
return coll;
final RadioNodeList nodeList = new RadioNodeList(getHtmlForm(), nodes);
nodeList.setElementsSupplier(
(Supplier<List<DomNode>> & Serializable)
() -> new ArrayList<>(findElements(name)));
return nodeList;
}

/**
Expand Down
116 changes: 116 additions & 0 deletions src/test/java/org/htmlunit/javascript/host/dom/RadioNodeListTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright (c) 2002-2024 Gargoyle Software Inc.
*
* 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
* https://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.htmlunit.javascript.host.dom;

import org.htmlunit.WebDriverTestCase;
import org.htmlunit.junit.BrowserRunner;
import org.htmlunit.junit.BrowserRunner.Alerts;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
* Tests for {@link RadioNodeList}.
*
* @author Lai Quang Duong
*/
@RunWith(BrowserRunner.class)
public class RadioNodeListTest extends WebDriverTestCase {

private static final String FORM_HTML = "<form name='form'>\n"
+ "<input type='text' name='first' value='0'/>\n"
+ "<input type='radio' name='first'/>\n"
+ "<input type='radio' name='first' value='2' checked/>\n"
+ "<input type='radio' name='first' value='3'/>\n"
+ "\n"
+ "<input type='radio' name='second' value='1'/>\n"
+ "\n"
+ "<input type='radio' name='third' value='1' checked/>\n"
+ "\n"
+ "<input type='radio' name='fourth' value='1'/>\n"
+ "<input type='radio' name='fourth' value='2'/>\n"
+ "\n"
+ "<input type='radio' name='fifth' value='1'/>\n"
+ "<input type='radio' name='fifth' checked/>\n"
+ "</form>";

/**
* @throws Exception on test failure
*/
@Test
@Alerts({"true", "true", "true"})
public void instanceOf() throws Exception {
final String html = "<html><head>\n"
+ "<script>\n"
+ LOG_TITLE_FUNCTION
+ " function test() {\n"
+ " log(document.form.first instanceof RadioNodeList);\n"
+ " log(document.form.fourth instanceof RadioNodeList);\n"
+ " log(document.form.fifth instanceof RadioNodeList);\n"
+ " }\n"
+ "</script>\n"
+ "</head><body onload='test()'>\n"
+ FORM_HTML
+ "</body></html>\n";

loadPageVerifyTitle2(html);
}

/**
* @throws Exception on test failure
*/
@Test
@Alerts({"2", "1", "1", "", "on"})
public void getValue() throws Exception {
final String html = "<html><head>\n"
+ "<script>\n"
+ LOG_TITLE_FUNCTION
+ " function test() {\n"
+ " log(document.form.first.value);\n"
+ " log(document.form.second.value);\n"
+ " log(document.form.third.value);\n"
+ " log(document.form.fourth.value);\n"
+ " log(document.form.fifth.value);\n"
+ " }\n"
+ "</script>\n"
+ "</head><body onload='test()'>\n"
+ FORM_HTML
+ "</body></html>\n";

loadPageVerifyTitle2(html);
}

/**
* @throws Exception on test failure
*/
@Test
@Alerts({"2", "on", "true"})
public void setValue() throws Exception {
final String html = "<html><head>\n"
+ "<script>\n"
+ LOG_TITLE_FUNCTION
+ " function test() {\n"
+ " log(document.form.first.value);\n"
+ " document.form.first.value = 'on';\n"
+ " log(document.form.first[1].value);\n"
+ " log(document.form.first[1].checked);\n"
+ " }\n"
+ "</script>\n"
+ "</head><body onload='test()'>\n"
+ FORM_HTML
+ "</body></html>\n";

loadPageVerifyTitle2(html);
}
}
Loading
Loading