Skip to content
Open
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
153 changes: 153 additions & 0 deletions jme3-core/src/test/java/com/jme3/input/DefaultJoystickTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package com.jme3.input;

import com.jme3.input.controls.JoyAxisTrigger;
import com.jme3.input.controls.JoyButtonTrigger;
import com.jme3.input.controls.Trigger;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

class DefaultJoystickTest {

@Test
void storesAxesAndButtonsAsReadOnlyLogicalCollections() {
TestJoystick joystick = new TestJoystick(mock(InputManager.class), mock(JoyInput.class), 3, "Gamepad");
DefaultJoystickAxis xAxis = new DefaultJoystickAxis(null, joystick, 0, "X Axis",
JoystickAxis.X_AXIS, true, false, 0.1f);
DefaultJoystickButton fire = new DefaultJoystickButton(null, joystick, 1, "Fire",
JoystickButton.BUTTON_1);

joystick.addTestAxis(xAxis);
joystick.addTestButton(fire);

assertSame(xAxis, joystick.getAxis(JoystickAxis.X_AXIS));
assertNull(joystick.getAxis(JoystickAxis.Y_AXIS));
assertSame(fire, joystick.getButton(JoystickButton.BUTTON_1));
assertNull(joystick.getButton(JoystickButton.BUTTON_2));
assertEquals(1, joystick.getAxisCount());
assertEquals(1, joystick.getButtonCount());
assertThrows(UnsupportedOperationException.class, () -> joystick.getAxes().clear());
assertThrows(UnsupportedOperationException.class, () -> joystick.getButtons().clear());
assertEquals("Joystick[name=Gamepad, id=3, buttons=1, axes=1]", joystick.toString());
}

@Test
void rumbleDelegatesToJoyInput() {
JoyInput joyInput = mock(JoyInput.class);
TestJoystick joystick = new TestJoystick(mock(InputManager.class), joyInput, 7, "Wheel");

joystick.rumble(0.75f);

verify(joyInput).setJoyRumble(7, 0.75f);
}

@Test
void defaultAxisExposesStateAndAssignsPositiveAndNegativeTriggers() {
InputManager inputManager = mock(InputManager.class);
TestJoystick joystick = new TestJoystick(inputManager, mock(JoyInput.class), 2, "Arcade Stick");
DefaultJoystickAxis axis = new DefaultJoystickAxis(inputManager, joystick, 4, "Throttle",
"throttle", true, false, 0.2f);

axis.setDeadZone(0.35f);
axis.assignAxis("Throttle+", "Throttle-");

assertSame(joystick, axis.getJoystick());
assertEquals("Throttle", axis.getName());
assertEquals("throttle", axis.getLogicalId());
assertEquals(4, axis.getAxisId());
assertTrue(axis.isAnalog());
assertFalse(axis.isRelative());
assertEquals(0.35f, axis.getDeadZone());
assertEquals(0f, axis.getJitterThreshold());
assertEquals("JoystickAxis[name=Throttle, parent=Arcade Stick, id=4, logicalId=throttle, "
+ "isAnalog=true, isRelative=false, deadZone=0.35, jitterThreshold=0.0]", axis.toString());

ArgumentCaptor<Trigger[]> positive = ArgumentCaptor.forClass(Trigger[].class);
ArgumentCaptor<Trigger[]> negative = ArgumentCaptor.forClass(Trigger[].class);
verify(inputManager).addMapping(eq("Throttle+"), positive.capture());
verify(inputManager).addMapping(eq("Throttle-"), negative.capture());
JoyAxisTrigger positiveTrigger = (JoyAxisTrigger) positive.getValue()[0];
JoyAxisTrigger negativeTrigger = (JoyAxisTrigger) negative.getValue()[0];
assertEquals(2, positiveTrigger.getJoyId());
assertEquals(4, positiveTrigger.getAxisId());
assertFalse(positiveTrigger.isNegative());
assertTrue(negativeTrigger.isNegative());
}

@Test
void defaultAxisWithUnknownIndexDoesNotAssignMappings() {
InputManager inputManager = mock(InputManager.class);
TestJoystick joystick = new TestJoystick(inputManager, mock(JoyInput.class), 2, "Unknown");
DefaultJoystickAxis axis = new DefaultJoystickAxis(inputManager, joystick, -1, "Unknown",
"unknown", false, true, 0f);

axis.assignAxis("Positive", "Negative");

org.mockito.Mockito.verifyNoInteractions(inputManager);
}

@Test
void defaultButtonExposesStateAndAssignsTrigger() {
InputManager inputManager = mock(InputManager.class);
TestJoystick joystick = new TestJoystick(inputManager, mock(JoyInput.class), 5, "Pad");
DefaultJoystickButton button = new DefaultJoystickButton(inputManager, joystick, 6, "Start",
JoystickButton.BUTTON_XBOX_START);

button.assignButton("Pause");

assertSame(joystick, button.getJoystick());
assertEquals("Start", button.getName());
assertEquals(JoystickButton.BUTTON_XBOX_START, button.getLogicalId());
assertEquals(6, button.getButtonId());
assertEquals("JoystickButton[name=Start, parent=Pad, id=6, logicalId=9]", button.toString());

ArgumentCaptor<Trigger[]> triggers = ArgumentCaptor.forClass(Trigger[].class);
verify(inputManager).addMapping(eq("Pause"), triggers.capture());
JoyButtonTrigger trigger = (JoyButtonTrigger) triggers.getValue()[0];
assertEquals(5, trigger.getJoyId());
assertEquals(6, trigger.getAxisId());
}

private static final class TestJoystick extends AbstractJoystick {
private TestJoystick(InputManager inputManager, JoyInput joyInput, int joyId, String name) {
super(inputManager, joyInput, joyId, name);
}

void addTestAxis(JoystickAxis axis) {
addAxis(axis);
}

void addTestButton(JoystickButton button) {
addButton(button);
}

@Override
public JoystickAxis getXAxis() {
return getAxis(JoystickAxis.X_AXIS);
}

@Override
public JoystickAxis getYAxis() {
return getAxis(JoystickAxis.Y_AXIS);
}

@Override
public JoystickAxis getPovXAxis() {
return getAxis(JoystickAxis.POV_X);
}

@Override
public JoystickAxis getPovYAxis() {
return getAxis(JoystickAxis.POV_Y);
}
}
}
46 changes: 46 additions & 0 deletions jme3-core/src/test/java/com/jme3/input/InputUtilityTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.jme3.input;

import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import org.junit.jupiter.api.Test;

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

class InputUtilityTest {

@Test
void keyNamesReturnStableNamesForRepresentativeKeyGroups() {
new KeyNames();

assertEquals("Unknown", KeyNames.getName(KeyInput.KEY_UNKNOWN));
assertEquals("0", KeyNames.getName(KeyInput.KEY_0));
assertEquals("A", KeyNames.getName(KeyInput.KEY_A));
assertEquals("F12", KeyNames.getName(KeyInput.KEY_F12));
assertEquals("Numpad Enter", KeyNames.getName(KeyInput.KEY_NUMPADENTER));
assertEquals("Left Ctrl", KeyNames.getName(KeyInput.KEY_LCONTROL));
assertEquals("Esc", KeyNames.getName(KeyInput.KEY_ESCAPE));
assertEquals("Page Down", KeyNames.getName(KeyInput.KEY_PGDN));
assertEquals("Kana", KeyNames.getName(KeyInput.KEY_KANA));
assertNull(KeyNames.getName(KeyInput.KEY_LAST));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This assertion will likely cause an ArrayIndexOutOfBoundsException. In KeyInput, KEY_LAST is defined as 0xFF (255). However, the KEY_NAMES array in KeyNames.java (line 47) is initialized with new String[0xFF], which has a size of 255 and valid indices from 0 to 254. Accessing index 255 will throw an exception. The production code in KeyNames.java should be updated to new String[0x100] to correctly support all key codes including KEY_LAST.

References
  1. Issues found in test code should be reported with a reduced priority, at most medium.

}

@Test
void rawInputListenerAdapterAcceptsAllCallbacksAsNoOps() {
RawInputListener listener = new RawInputListenerAdapter() {
};

listener.beginInput();
listener.onJoyAxisEvent((JoyAxisEvent) null);
listener.onJoyButtonEvent((JoyButtonEvent) null);
listener.onMouseMotionEvent((MouseMotionEvent) null);
listener.onMouseButtonEvent((MouseButtonEvent) null);
listener.onKeyEvent((KeyInputEvent) null);
listener.onTouchEvent((TouchEvent) null);
listener.endInput();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.jme3.input;

import org.junit.jupiter.api.Test;

import java.util.Map;
import java.util.Properties;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class JoystickCompatibilityMappingsTest {

@Test
void remapsSpecificAxisButtonAndGenericComponents() {
String stick = "Codex Test Stick";
JoystickCompatibilityMappings.addAxisMapping(stick, "x_raw", JoystickAxis.X_AXIS);
JoystickCompatibilityMappings.addButtonMapping(stick, "0_raw", JoystickButton.BUTTON_XBOX_A);
JoystickCompatibilityMappings.addMapping(stick, "misc_raw", "misc");

assertEquals(JoystickAxis.X_AXIS, JoystickCompatibilityMappings.remapAxis(" " + stick + " ", "x_raw"));
assertEquals(JoystickButton.BUTTON_XBOX_A, JoystickCompatibilityMappings.remapButton(stick, "0_raw"));
assertEquals("misc", JoystickCompatibilityMappings.remapComponent(stick, "misc_raw"));
assertEquals("unmapped", JoystickCompatibilityMappings.remapAxis(stick, "unmapped"));
assertEquals("unmapped", JoystickCompatibilityMappings.remapButton(stick, "unmapped"));
assertEquals("unmapped", JoystickCompatibilityMappings.remapComponent(stick, "unmapped"));
}

@Test
void returnsUnmodifiableMappingViews() {
String stick = "Codex Test Mapping View";
JoystickCompatibilityMappings.addMapping(stick, "raw", "logical");
JoystickCompatibilityMappings.addButtonMapping(stick, "buttonRaw", "buttonLogical");

Map<String, String> componentMappings = JoystickCompatibilityMappings.getJoystickMappings(stick);
Map<String, String> buttonMappings = JoystickCompatibilityMappings.getJoystickButtonMappings(stick);

assertEquals("logical", componentMappings.get("raw"));
assertEquals("buttonLogical", buttonMappings.get("buttonRaw"));
assertThrows(UnsupportedOperationException.class, () -> componentMappings.put("other", "value"));
assertThrows(UnsupportedOperationException.class, () -> buttonMappings.put("other", "value"));
assertEquals(0, JoystickCompatibilityMappings.getJoystickMappings("No Such Stick").size());
}

@Test
void addMappingsParsesTypedEntriesRangesAndNameRegex() {
Properties properties = new Properties();
properties.setProperty("axis.Codex Regex Stick.x", "left_x [-1.0, 1.0]");
properties.setProperty("button.Codex Regex Stick.trigger", "fire");
properties.setProperty("Codex Regex Stick.misc", "menu");
properties.setProperty("Codex Regex Stick.regex", "Codex Regex Stick \\(rev \\d+\\)");

JoystickCompatibilityMappings.addMappings(properties);

String physicalName = "Codex Regex Stick (rev 42)";
assertEquals("left_x", JoystickCompatibilityMappings.remapAxis(physicalName, "x"));
assertEquals("fire", JoystickCompatibilityMappings.remapButton(physicalName, "trigger"));
assertEquals("menu", JoystickCompatibilityMappings.remapComponent(physicalName, "misc"));
}

@Test
void remapAxisRangeUsesConfiguredRangeAndCachesMissingMappings() {
String stick = "Codex Test Axis Range";
JoystickCompatibilityMappings.addAxisMapping(stick, "slider", "slider", new float[]{0f, 1f});
JoystickAxis mappedAxis = axis(stick, "slider");
JoystickAxis unmappedAxis = axis(stick, "unknown");

assertEquals(0.25f, JoystickCompatibilityMappings.remapAxisRange(mappedAxis, -0.5f));
assertEquals(0.75f, JoystickCompatibilityMappings.remapAxisRange(mappedAxis, 0.5f));
assertEquals(0.4f, JoystickCompatibilityMappings.remapAxisRange(unmappedAxis, 0.4f));
assertEquals(-0.6f, JoystickCompatibilityMappings.remapAxisRange(unmappedAxis, -0.6f));
}
Comment on lines +63 to +73
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current test only covers the range [0, 1], which happens to work with the current (but mathematically incorrect) implementation of JoystickCompatibilityMappings.remapAxisRange. The formula in the production code: (currentValue + range[1] + range[0]) * ((range[1] - range[0]) / 2) is incorrect for ranges that are not centered at 0.5 or have a width other than 1. For example, with a range of [0, 2], a currentValue of 1.0 would result in 3.0 instead of 2.0. It is recommended to add a test case with a different range (e.g., [0, 2] or [0.5, 1.0]) to ensure the remapping logic is robust and to reveal the underlying bug in the production code.

References
  1. Issues found in test code should be reported with a reduced priority, at most medium.


@Test
void rejectsInvalidAxisRange() {
assertThrows(IllegalArgumentException.class,
() -> JoystickCompatibilityMappings.addAxisMapping("Bad Range", "axis", "axis", new float[]{0f}));
}

private static JoystickAxis axis(String joystickName, String axisName) {
Joystick joystick = mock(Joystick.class);
when(joystick.getName()).thenReturn(joystickName);

JoystickAxis axis = mock(JoystickAxis.class);
when(axis.getJoystick()).thenReturn(joystick);
when(axis.getName()).thenReturn(axisName);
return axis;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.jme3.input.controls;

import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

class InputTriggerTest {

@Test
void keyTriggerUsesLowByteHashAndReadableName() {
KeyTrigger trigger = new KeyTrigger(KeyInput.KEY_SPACE);

assertEquals(KeyInput.KEY_SPACE, trigger.getKeyCode());
assertEquals("KeyCode " + KeyInput.KEY_SPACE, trigger.getName());
assertEquals(KeyInput.KEY_SPACE & 0xff, trigger.triggerHashCode());
}

@Test
void mouseAxisTriggerNamesKnownAxesAndEncodesDirection() {
MouseAxisTrigger xPositive = new MouseAxisTrigger(MouseInput.AXIS_X, false);
MouseAxisTrigger wheelNegative = new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true);

assertEquals(MouseInput.AXIS_X, xPositive.getMouseAxis());
assertFalse(xPositive.isNegative());
assertEquals("Mouse X Axis Positive", xPositive.getName());
assertEquals("Mouse Wheel Negative", wheelNegative.getName());
assertEquals(512 | MouseInput.AXIS_X, xPositive.triggerHashCode());
assertEquals(768 | MouseInput.AXIS_WHEEL, wheelNegative.triggerHashCode());
assertThrows(IllegalArgumentException.class, () -> new MouseAxisTrigger(9, false));
}

@Test
void mouseButtonTriggerUsesButtonHashAndName() {
MouseButtonTrigger trigger = new MouseButtonTrigger(MouseInput.BUTTON_RIGHT);

assertEquals(MouseInput.BUTTON_RIGHT, trigger.getMouseButton());
assertEquals("Mouse Button " + MouseInput.BUTTON_RIGHT, trigger.getName());
assertEquals(256 | MouseInput.BUTTON_RIGHT, trigger.triggerHashCode());
}

@Test
void joystickAxisTriggerEncodesJoystickAxisAndSign() {
JoyAxisTrigger positive = new JoyAxisTrigger(2, 3, false);
JoyAxisTrigger negative = new JoyAxisTrigger(2, 3, true);

assertEquals(2, positive.getJoyId());
assertEquals(3, positive.getAxisId());
assertFalse(positive.isNegative());
assertTrue(negative.isNegative());
assertEquals("JoyAxis[joyId=2, axisId=3, neg=false]", positive.getName());
assertEquals((2048 * 2) | 1024 | 3, positive.triggerHashCode());
assertEquals((2048 * 2) | 1280 | 3, negative.triggerHashCode());
}

@Test
void joystickButtonTriggerEncodesJoystickAndButton() {
JoyButtonTrigger trigger = new JoyButtonTrigger(4, 7);

assertEquals(4, trigger.getJoyId());
assertEquals(7, trigger.getAxisId());
assertEquals("JoyButton[joyId=4, axisId=7]", trigger.getName());
assertEquals((2048 * 4) | 1536 | 7, trigger.triggerHashCode());
}

@Test
void touchTriggerDistinguishesZeroFromNonZeroKeyCodes() {
TouchTrigger zero = new TouchTrigger(0);
TouchTrigger key = new TouchTrigger(KeyInput.KEY_RETURN);

assertEquals("TouchInput KeyCode 0", zero.getName());
assertEquals("TouchInput", key.getName());
assertEquals(0, zero.getKeyCode());
assertEquals(KeyInput.KEY_RETURN, key.getKeyCode());
assertEquals(TouchTrigger.touchHash(0), zero.triggerHashCode());
assertEquals(TouchTrigger.touchHash(KeyInput.KEY_RETURN), key.triggerHashCode());
}
}
Loading
Loading