Laget to varianter til og la til flere tester
This commit is contained in:
parent
ccc6a98379
commit
b643a36ab5
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"java.configuration.updateBuildConfiguration": "automatic"
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ public class Calc {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pushes a new operand onto top of the stack.
|
* Pushes a new operand onto top of the stack.
|
||||||
|
*
|
||||||
* @param d the new operand
|
* @param d the new operand
|
||||||
*/
|
*/
|
||||||
public void pushOperand(double d) {
|
public void pushOperand(double d) {
|
||||||
|
@ -52,6 +53,7 @@ public class Calc {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes and returns the top operand.
|
* Removes and returns the top operand.
|
||||||
|
*
|
||||||
* @return the top operand
|
* @return the top operand
|
||||||
* @throws IllegalStateException if the stack is empty
|
* @throws IllegalStateException if the stack is empty
|
||||||
*/
|
*/
|
||||||
|
@ -65,6 +67,7 @@ public class Calc {
|
||||||
/**
|
/**
|
||||||
* Performs the provided operation in the top operand, and
|
* Performs the provided operation in the top operand, and
|
||||||
* replaces it with the result.
|
* replaces it with the result.
|
||||||
|
*
|
||||||
* @param op the operation to perform
|
* @param op the operation to perform
|
||||||
* @return the result of performing the operation
|
* @return the result of performing the operation
|
||||||
* @throws IllegalStateException if the operand stack is empty
|
* @throws IllegalStateException if the operand stack is empty
|
||||||
|
@ -77,6 +80,7 @@ public class Calc {
|
||||||
/**
|
/**
|
||||||
* Performs the provided operation in the two topmost operands, and
|
* Performs the provided operation in the two topmost operands, and
|
||||||
* replaces them with the result.
|
* replaces them with the result.
|
||||||
|
*
|
||||||
* @param op the operation to perform
|
* @param op the operation to perform
|
||||||
* @return the result of performing the operation
|
* @return the result of performing the operation
|
||||||
* @throws IllegalStateException if the operand count is less than two
|
* @throws IllegalStateException if the operand count is less than two
|
||||||
|
@ -94,6 +98,8 @@ public class Calc {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Swaps the two topmost operands.
|
* Swaps the two topmost operands.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the operand count is less than two
|
||||||
*/
|
*/
|
||||||
public void swap() {
|
public void swap() {
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -101,6 +107,8 @@ public class Calc {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duplicates the top operand.
|
* Duplicates the top operand.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the operand stack is empty
|
||||||
*/
|
*/
|
||||||
public void dup() {
|
public void dup() {
|
||||||
// TODO
|
// TODO
|
||||||
|
|
|
@ -28,6 +28,21 @@ public class CalcTest {
|
||||||
checkCalc(calc, 3.14, 1.0);
|
checkCalc(calc, 3.14, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPeekOperand() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
Assertions.assertEquals(3.14, calc.peekOperand());
|
||||||
|
Assertions.assertThrows(IllegalArgumentException.class, () -> new Calc().peekOperand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPeekOperandN() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
Assertions.assertEquals(3.14, calc.peekOperand(0));
|
||||||
|
Assertions.assertEquals(1.0, calc.peekOperand(1));
|
||||||
|
Assertions.assertThrows(IllegalArgumentException.class, () -> calc.peekOperand(2));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPopOperand() {
|
public void testPopOperand() {
|
||||||
Calc calc = new Calc(1.0, 3.14);
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
@ -36,4 +51,64 @@ public class CalcTest {
|
||||||
Assertions.assertEquals(1.0, calc.popOperand());
|
Assertions.assertEquals(1.0, calc.popOperand());
|
||||||
checkCalc(calc);
|
checkCalc(calc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPopOperand_emptyStack() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().popOperand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOperation1() {
|
||||||
|
Calc calc = new Calc(1.0);
|
||||||
|
Assertions.assertEquals(-1.0, calc.performOperation(n -> -n));
|
||||||
|
checkCalc(calc, -1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOperation1_emptyOperandStack() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().performOperation(n -> -n));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOperation2() {
|
||||||
|
Calc calc = new Calc(1.0, 3.0);
|
||||||
|
Assertions.assertEquals(-2.0, calc.performOperation((n1, n2) -> n1 - n2));
|
||||||
|
checkCalc(calc, -2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOperation2_lessThanTwoOperands() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc(1.0).performOperation((n1, n2) -> n1 - n2));
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().performOperation((n1, n2) -> n1 - n2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSwap() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
calc.swap();
|
||||||
|
checkCalc(calc, 3.14, 1.0);
|
||||||
|
calc.swap();
|
||||||
|
checkCalc(calc, 1.0, 3.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSwap_lessThanTwoOperands() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc(1.0).swap());
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().swap());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDup() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
Assertions.assertEquals(3.14, calc.popOperand());
|
||||||
|
checkCalc(calc, 1.0);
|
||||||
|
Assertions.assertEquals(1.0, calc.popOperand());
|
||||||
|
checkCalc(calc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDup_emptyOperandStack() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().dup());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
# ignore maven build folder
|
||||||
|
target/
|
|
@ -0,0 +1,43 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>it1901</groupId>
|
||||||
|
<artifactId>modules-core</artifactId>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>it1901</groupId>
|
||||||
|
<artifactId>modules-template</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>..</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- junit testing with jupiter -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-params</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -0,0 +1,108 @@
|
||||||
|
package core;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
public class Calc {
|
||||||
|
|
||||||
|
private final List<Double> operandStack;
|
||||||
|
|
||||||
|
public Calc(double... operands) {
|
||||||
|
operandStack = new ArrayList<>(operands.length + 2);
|
||||||
|
for (var d : operands) {
|
||||||
|
operandStack.add(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the number of operands on the stack
|
||||||
|
*/
|
||||||
|
public int getOperandCount() {
|
||||||
|
return operandStack.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a new operand onto top of the stack.
|
||||||
|
* @param d the new operand
|
||||||
|
*/
|
||||||
|
public void pushOperand(double d) {
|
||||||
|
operandStack.add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param n the place (from the top) to peek
|
||||||
|
* @return the n'th operand from the top
|
||||||
|
* @throws IllegalArgumentException if n is larger than the operand count
|
||||||
|
*/
|
||||||
|
public double peekOperand(int n) {
|
||||||
|
if (n > getOperandCount()) {
|
||||||
|
throw new IllegalArgumentException("Cannot peek at position " + n + " when the operand count is " + getOperandCount());
|
||||||
|
}
|
||||||
|
return operandStack.get(getOperandCount() - n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the top operand
|
||||||
|
*/
|
||||||
|
public double peekOperand() {
|
||||||
|
return peekOperand(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes and returns the top operand.
|
||||||
|
* @return the top operand
|
||||||
|
* @throws IllegalStateException if the stack is empty
|
||||||
|
*/
|
||||||
|
public double popOperand() {
|
||||||
|
if (getOperandCount() == 0) {
|
||||||
|
throw new IllegalStateException("Cannot pop from an empty stack");
|
||||||
|
}
|
||||||
|
return operandStack.remove(operandStack.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the provided operation in the top operand, and
|
||||||
|
* replaces it with the result.
|
||||||
|
* @param op the operation to perform
|
||||||
|
* @return the result of performing the operation
|
||||||
|
* @throws IllegalStateException if the operand stack is empty
|
||||||
|
*/
|
||||||
|
public double performOperation(UnaryOperator<Double> op) throws IllegalStateException {
|
||||||
|
// TODO
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the provided operation in the two topmost operands, and
|
||||||
|
* replaces them with the result.
|
||||||
|
* @param op the operation to perform
|
||||||
|
* @return the result of performing the operation
|
||||||
|
* @throws IllegalStateException if the operand count is less than two
|
||||||
|
*/
|
||||||
|
public double performOperation(BinaryOperator<Double> op) throws IllegalStateException {
|
||||||
|
if (getOperandCount() < 2) {
|
||||||
|
throw new IllegalStateException("Too few operands (" + getOperandCount() + ") on the stack");
|
||||||
|
}
|
||||||
|
var op1 = popOperand();
|
||||||
|
var op2 = popOperand();
|
||||||
|
var result = op.apply(op1, op2);
|
||||||
|
pushOperand(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swaps the two topmost operands.
|
||||||
|
*/
|
||||||
|
public void swap() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicates the top operand.
|
||||||
|
*/
|
||||||
|
public void dup() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
package core;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class CalcTest {
|
||||||
|
|
||||||
|
private static void checkCalc(Calc calc, double... operands) {
|
||||||
|
Assertions.assertEquals(operands.length, calc.getOperandCount(), "Wrong operand count");
|
||||||
|
for (int i = 0; i < operands.length; i++) {
|
||||||
|
Assertions.assertEquals(operands[i], calc.peekOperand(i), "Wrong value at #" + i + " of operand stack");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalc() {
|
||||||
|
checkCalc(new Calc());
|
||||||
|
checkCalc(new Calc(1.0), 1.0);
|
||||||
|
checkCalc(new Calc(3.14, 1.0), 1.0, 3.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPushOperand() {
|
||||||
|
Calc calc = new Calc();
|
||||||
|
calc.pushOperand(1.0);
|
||||||
|
checkCalc(calc, 1.0);
|
||||||
|
calc.pushOperand(3.14);
|
||||||
|
checkCalc(calc, 3.14, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPeekOperand() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
Assertions.assertEquals(3.14, calc.peekOperand());
|
||||||
|
Assertions.assertThrows(IllegalArgumentException.class, () -> new Calc().peekOperand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPeekOperandN() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
Assertions.assertEquals(3.14, calc.peekOperand(0));
|
||||||
|
Assertions.assertEquals(1.0, calc.peekOperand(1));
|
||||||
|
Assertions.assertThrows(IllegalArgumentException.class, () -> calc.peekOperand(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPopOperand() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
Assertions.assertEquals(3.14, calc.popOperand());
|
||||||
|
checkCalc(calc, 1.0);
|
||||||
|
Assertions.assertEquals(1.0, calc.popOperand());
|
||||||
|
checkCalc(calc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPopOperand_emptyStack() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().popOperand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOperation1() {
|
||||||
|
Calc calc = new Calc(1.0);
|
||||||
|
Assertions.assertEquals(-1.0, calc.performOperation(n -> -n));
|
||||||
|
checkCalc(calc, -1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOperation1_emptyOperandStack() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().performOperation(n -> -n));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOperation2() {
|
||||||
|
Calc calc = new Calc(1.0, 3.0);
|
||||||
|
Assertions.assertEquals(-2.0, calc.performOperation((n1, n2) -> n1 - n2));
|
||||||
|
checkCalc(calc, -2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOperation2_lessThanTwoOperands() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc(1.0).performOperation((n1, n2) -> n1 - n2));
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().performOperation((n1, n2) -> n1 - n2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSwap() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
calc.swap();
|
||||||
|
checkCalc(calc, 3.14, 1.0);
|
||||||
|
calc.swap();
|
||||||
|
checkCalc(calc, 1.0, 3.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSwap_lessThanTwoOperands() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc(1.0).swap());
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().swap());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDup() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
Assertions.assertEquals(3.14, calc.popOperand());
|
||||||
|
checkCalc(calc, 1.0);
|
||||||
|
Assertions.assertEquals(1.0, calc.popOperand());
|
||||||
|
checkCalc(calc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDup_emptyOperandStack() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().dup());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>it1901</groupId>
|
||||||
|
<artifactId>modules-template</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.compiler.source>16</maven.compiler.source>
|
||||||
|
<maven.compiler.target>16</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<version>5.7.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>5.7.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-params</artifactId>
|
||||||
|
<version>5.7.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<release>16</release>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.0.0-M5</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>core</module>
|
||||||
|
<module>ui</module>
|
||||||
|
</modules>
|
||||||
|
</project>
|
|
@ -0,0 +1,108 @@
|
||||||
|
package core;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
public class Calc {
|
||||||
|
|
||||||
|
private final List<Double> operandStack;
|
||||||
|
|
||||||
|
public Calc(double... operands) {
|
||||||
|
operandStack = new ArrayList<>(operands.length + 2);
|
||||||
|
for (var d : operands) {
|
||||||
|
operandStack.add(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the number of operands on the stack
|
||||||
|
*/
|
||||||
|
public int getOperandCount() {
|
||||||
|
return operandStack.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a new operand onto top of the stack.
|
||||||
|
* @param d the new operand
|
||||||
|
*/
|
||||||
|
public void pushOperand(double d) {
|
||||||
|
operandStack.add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param n the place (from the top) to peek
|
||||||
|
* @return the n'th operand from the top
|
||||||
|
* @throws IllegalArgumentException if n is larger than the operand count
|
||||||
|
*/
|
||||||
|
public double peekOperand(int n) {
|
||||||
|
if (n > getOperandCount()) {
|
||||||
|
throw new IllegalArgumentException("Cannot peek at position " + n + " when the operand count is " + getOperandCount());
|
||||||
|
}
|
||||||
|
return operandStack.get(getOperandCount() - n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the top operand
|
||||||
|
*/
|
||||||
|
public double peekOperand() {
|
||||||
|
return peekOperand(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes and returns the top operand.
|
||||||
|
* @return the top operand
|
||||||
|
* @throws IllegalStateException if the stack is empty
|
||||||
|
*/
|
||||||
|
public double popOperand() {
|
||||||
|
if (getOperandCount() == 0) {
|
||||||
|
throw new IllegalStateException("Cannot pop from an empty stack");
|
||||||
|
}
|
||||||
|
return operandStack.remove(operandStack.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the provided operation in the top operand, and
|
||||||
|
* replaces it with the result.
|
||||||
|
* @param op the operation to perform
|
||||||
|
* @return the result of performing the operation
|
||||||
|
* @throws IllegalStateException if the operand stack is empty
|
||||||
|
*/
|
||||||
|
public double performOperation(UnaryOperator<Double> op) throws IllegalStateException {
|
||||||
|
// TODO
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the provided operation in the two topmost operands, and
|
||||||
|
* replaces them with the result.
|
||||||
|
* @param op the operation to perform
|
||||||
|
* @return the result of performing the operation
|
||||||
|
* @throws IllegalStateException if the operand count is less than two
|
||||||
|
*/
|
||||||
|
public double performOperation(BinaryOperator<Double> op) throws IllegalStateException {
|
||||||
|
if (getOperandCount() < 2) {
|
||||||
|
throw new IllegalStateException("Too few operands (" + getOperandCount() + ") on the stack");
|
||||||
|
}
|
||||||
|
var op1 = popOperand();
|
||||||
|
var op2 = popOperand();
|
||||||
|
var result = op.apply(op1, op2);
|
||||||
|
pushOperand(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swaps the two topmost operands.
|
||||||
|
*/
|
||||||
|
public void swap() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicates the top operand.
|
||||||
|
*/
|
||||||
|
public void dup() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package core;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class CalcTest {
|
||||||
|
|
||||||
|
private static void checkCalc(Calc calc, double... operands) {
|
||||||
|
Assertions.assertEquals(operands.length, calc.getOperandCount(), "Wrong operand count");
|
||||||
|
for (int i = 0; i < operands.length; i++) {
|
||||||
|
Assertions.assertEquals(operands[i], calc.peekOperand(i), "Wrong value at #" + i + " of operand stack");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalc() {
|
||||||
|
checkCalc(new Calc());
|
||||||
|
checkCalc(new Calc(1.0), 1.0);
|
||||||
|
checkCalc(new Calc(3.14, 1.0), 1.0, 3.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPushOperand() {
|
||||||
|
Calc calc = new Calc();
|
||||||
|
calc.pushOperand(1.0);
|
||||||
|
checkCalc(calc, 1.0);
|
||||||
|
calc.pushOperand(3.14);
|
||||||
|
checkCalc(calc, 3.14, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPopOperand() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
Assertions.assertEquals(3.14, calc.popOperand());
|
||||||
|
checkCalc(calc, 1.0);
|
||||||
|
Assertions.assertEquals(1.0, calc.popOperand());
|
||||||
|
checkCalc(calc);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>it1901</groupId>
|
||||||
|
<artifactId>modules-ui</artifactId>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>it1901</groupId>
|
||||||
|
<artifactId>modules-template</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>..</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>it1901</groupId>
|
||||||
|
<artifactId>modules-core</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- javafx -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-controls</artifactId>
|
||||||
|
<version>16</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-fxml</artifactId>
|
||||||
|
<version>16</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- junit testing with jupiter -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-params</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- test javafx with TextFX -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testfx</groupId>
|
||||||
|
<artifactId>testfx-core</artifactId>
|
||||||
|
<version>4.0.16-alpha</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testfx</groupId>
|
||||||
|
<artifactId>testfx-junit5</artifactId>
|
||||||
|
<version>4.0.16-alpha</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hamcrest</groupId>
|
||||||
|
<artifactId>hamcrest</artifactId>
|
||||||
|
<version>2.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-maven-plugin</artifactId>
|
||||||
|
<version>0.0.6</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<!-- Default configuration for running -->
|
||||||
|
<!-- Usage: mvn clean javafx:run -->
|
||||||
|
<id>default-cli</id>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>ui.App</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -0,0 +1,27 @@
|
||||||
|
package ui;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaFX App
|
||||||
|
*/
|
||||||
|
public class App extends Application {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Stage stage) throws IOException {
|
||||||
|
FXMLLoader fxmlLoader = new FXMLLoader(this.getClass().getResource("App.fxml"));
|
||||||
|
Parent parent = fxmlLoader.load();
|
||||||
|
stage.setScene(new Scene(parent));
|
||||||
|
stage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
launch();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package ui;
|
||||||
|
|
||||||
|
import core.Calc;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Labeled;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
|
||||||
|
public class AppController {
|
||||||
|
|
||||||
|
private Calc calc;
|
||||||
|
|
||||||
|
public AppController() {
|
||||||
|
calc = new Calc(0.0, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Calc getCalc() {
|
||||||
|
return calc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCalc(Calc calc) {
|
||||||
|
this.calc = calc;
|
||||||
|
updateOperandsView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ListView<Double> operandsView;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label operandView;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void initialize() {
|
||||||
|
updateOperandsView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOperandsView() {
|
||||||
|
List<Double> operands = operandsView.getItems();
|
||||||
|
operands.clear();
|
||||||
|
int elementCount = Math.min(calc.getOperandCount(), 3);
|
||||||
|
for (int i = 0; i < elementCount; i++) {
|
||||||
|
operands.add(calc.peekOperand(elementCount - i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getOperandString() {
|
||||||
|
return operandView.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasOperand() {
|
||||||
|
return ! getOperandString().isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getOperand() {
|
||||||
|
return Double.valueOf(operandView.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOperand(String operandString) {
|
||||||
|
operandView.setText(operandString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleEnter() {
|
||||||
|
if (hasOperand()) {
|
||||||
|
calc.pushOperand(getOperand());
|
||||||
|
} else {
|
||||||
|
calc.dup();
|
||||||
|
}
|
||||||
|
setOperand("");
|
||||||
|
updateOperandsView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendToOperand(String s) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleDigit(ActionEvent ae) {
|
||||||
|
if (ae.getSource() instanceof Labeled l) {
|
||||||
|
// TODO append button label to operand
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handlePoint() {
|
||||||
|
var operandString = getOperandString();
|
||||||
|
if (operandString.contains(".")) {
|
||||||
|
// TODO remove characters after point
|
||||||
|
} else {
|
||||||
|
// TODO append point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleClear() {
|
||||||
|
// TODO clear operand
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleSwap() {
|
||||||
|
// TODO clear operand
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performOperation(UnaryOperator<Double> op) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performOperation(boolean swap, BinaryOperator<Double> op) {
|
||||||
|
if (hasOperand()) {
|
||||||
|
// TODO push operand first
|
||||||
|
}
|
||||||
|
// TODO perform operation, but swap first if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleOpAdd() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleOpSub() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleOpMult() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ListView?>
|
||||||
|
|
||||||
|
<GridPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.AppController"
|
||||||
|
alignment="CENTER" hgap="10.0" vgap="10.0" >
|
||||||
|
|
||||||
|
<ListView fx:id="operandsView" prefHeight="80.0"
|
||||||
|
GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="4"/>
|
||||||
|
<Label text="" fx:id="operandView"
|
||||||
|
GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="4"/>
|
||||||
|
|
||||||
|
<!-- multi-line button label with XML entity for newline -->
|
||||||
|
<Button text="E n t e r" onAction="#handleEnter"
|
||||||
|
GridPane.rowIndex="2" GridPane.columnIndex="3" GridPane.rowSpan="3"/>
|
||||||
|
|
||||||
|
<Button text="7" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="2" GridPane.columnIndex="0"/>
|
||||||
|
<Button text="8" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="2" GridPane.columnIndex="1"/>
|
||||||
|
<Button text="9" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="2" GridPane.columnIndex="2"/>
|
||||||
|
|
||||||
|
<Button text="4" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="3" GridPane.columnIndex="0"/>
|
||||||
|
<Button text="5" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="3" GridPane.columnIndex="1"/>
|
||||||
|
<Button text="6" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="3" GridPane.columnIndex="2"/>
|
||||||
|
|
||||||
|
<Button text="1" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="4" GridPane.columnIndex="0"/>
|
||||||
|
<Button text="2" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="4" GridPane.columnIndex="1"/>
|
||||||
|
<Button text="3" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="4" GridPane.columnIndex="2"/>
|
||||||
|
|
||||||
|
<Button text="0" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="5" GridPane.columnIndex="0"/>
|
||||||
|
<Button text="." onAction="#handlePoint"
|
||||||
|
GridPane.rowIndex="5" GridPane.columnIndex="1"/>
|
||||||
|
<Button text="C" onAction="#handleClear"
|
||||||
|
GridPane.rowIndex="5" GridPane.columnIndex="2"/>
|
||||||
|
<Button text="~" onAction="#handleSwap"
|
||||||
|
GridPane.rowIndex="5" GridPane.columnIndex="3"/>
|
||||||
|
|
||||||
|
<Button text="+" onAction="#handleOpAdd"
|
||||||
|
GridPane.rowIndex="6" GridPane.columnIndex="0"/>
|
||||||
|
<Button text="-" onAction="#handleOpSub"
|
||||||
|
GridPane.rowIndex="6" GridPane.columnIndex="1"/>
|
||||||
|
<Button text="*" onAction="#handleOpMult"
|
||||||
|
GridPane.rowIndex="6" GridPane.columnIndex="2"/>
|
||||||
|
|
||||||
|
<!-- TODO -->
|
||||||
|
<Button text="/"
|
||||||
|
GridPane.rowIndex="6" GridPane.columnIndex="3"/>
|
||||||
|
<Button text="√"
|
||||||
|
GridPane.rowIndex="7" GridPane.columnIndex="0"/>
|
||||||
|
<Button text="π"
|
||||||
|
GridPane.rowIndex="7" GridPane.columnIndex="1"/>
|
||||||
|
</GridPane>
|
|
@ -0,0 +1,130 @@
|
||||||
|
package ui;
|
||||||
|
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.testfx.framework.junit5.ApplicationTest;
|
||||||
|
import org.testfx.matcher.control.LabeledMatchers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TestFX App test
|
||||||
|
*/
|
||||||
|
public class AppTest extends ApplicationTest {
|
||||||
|
|
||||||
|
private AppController controller;
|
||||||
|
private Parent root;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Stage stage) throws IOException {
|
||||||
|
FXMLLoader fxmlLoader = new FXMLLoader(this.getClass().getResource("App.fxml"));
|
||||||
|
root = fxmlLoader.load();
|
||||||
|
controller = fxmlLoader.getController();
|
||||||
|
stage.setScene(new Scene(root));
|
||||||
|
stage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Parent getRootNode() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String enterLabel = """
|
||||||
|
E
|
||||||
|
n
|
||||||
|
t
|
||||||
|
e
|
||||||
|
r
|
||||||
|
""".stripTrailing();
|
||||||
|
|
||||||
|
private void click(String... labels) {
|
||||||
|
for (var label : labels) {
|
||||||
|
clickOn(LabeledMatchers.hasText(label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getOperandString() {
|
||||||
|
return ((Label) getRootNode().lookup("#operandView")).getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListView<Double> getOperandsView() {
|
||||||
|
return (ListView<Double>) getRootNode().lookup("#operandsView");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkView(double... operands) {
|
||||||
|
for (int i = 0; i < operands.length; i++) {
|
||||||
|
Assertions.assertEquals(operands[i], controller.getCalc().peekOperand(i), "Wrong value at #" + i + " of operand stack");
|
||||||
|
}
|
||||||
|
List<Double> viewItems = getOperandsView().getItems();
|
||||||
|
for (int i = 0; i < operands.length; i++) {
|
||||||
|
Assertions.assertEquals(operands[i], viewItems.get(viewItems.size() - i - 1), "Wrong value at #" + i + " of operands view");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkView(String operandString, double... operands) {
|
||||||
|
Assertions.assertEquals(operandString, getOperandString());
|
||||||
|
checkView(operands);
|
||||||
|
}
|
||||||
|
|
||||||
|
// see https://www.baeldung.com/parameterized-tests-junit-5
|
||||||
|
// about @ParameterizedTest
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource
|
||||||
|
public void testClicksOperand(String labels, String operandString) {
|
||||||
|
for (var label : labels.split(" ")) {
|
||||||
|
click(label);
|
||||||
|
}
|
||||||
|
checkView(operandString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> testClicksOperand() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("2 7", "27"),
|
||||||
|
Arguments.of("2 7 .", "27."),
|
||||||
|
Arguments.of("2 7 . 5", "27.5"),
|
||||||
|
Arguments.of("2 7 . 5 .", "27.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource
|
||||||
|
public void testClicksOperands(String labels, String operandsString) {
|
||||||
|
for (var label : labels.split(" ")) {
|
||||||
|
click(label.equals("\n") ? enterLabel : label);
|
||||||
|
}
|
||||||
|
checkView("", Stream.of(operandsString.split(" ")).mapToDouble(Double::valueOf).toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> testClicksOperands() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("2 7 . 5 \n", "27.5"),
|
||||||
|
Arguments.of("2 7 \n", "27.0"),
|
||||||
|
Arguments.of("2 \n 7 \n 5 \n", "5.0", "7.0", "2.0"),
|
||||||
|
Arguments.of("2 7 . \n", "27.0"),
|
||||||
|
Arguments.of("2 7 . 5 \n", "27.5"),
|
||||||
|
Arguments.of("2 \n 7 +", "9.0"),
|
||||||
|
Arguments.of("2 \n 7 -", "-5.0"),
|
||||||
|
Arguments.of("2 \n 7 *", "14.0"),
|
||||||
|
Arguments.of("6 \n 3 /", "2.0"),
|
||||||
|
Arguments.of("2 5 \n √", "5.0")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPi() {
|
||||||
|
click("π");
|
||||||
|
checkView("", Math.PI);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Tests for the RPN calculator
|
||||||
|
|
||||||
|
This folder/package contains tests based on TestFX for the RPN Calculator (currently only one test class).
|
||||||
|
|
||||||
|
As can be seen when launching, the app contains a list (top) showing the operands
|
||||||
|
(topmost operand at the bottom), a text field (below list, initially empty) for a new operand and
|
||||||
|
the buttons for digits, enter, decimal point, operations etc.
|
||||||
|
|
||||||
|
## What is tested
|
||||||
|
|
||||||
|
The tests simulate clicks on the buttons and checks that the underlying Calc object,
|
||||||
|
the list (a view of the Calc object's operand stack) and the text field are updated as expected.
|
||||||
|
E.g. if you click buttons `2 3 . 5` the string `23.5` should be shown,
|
||||||
|
while the list is not affected. If you then click `enter`, the text field should be emptied, the operand stack should have `23.5` at the top and the list should have `23.5` at the bottom
|
||||||
|
(logically the top of the operand stack).
|
||||||
|
|
||||||
|
Below are the specific cases that are tested.
|
||||||
|
|
||||||
|
buttons to click `=>` text field content:
|
||||||
|
|
||||||
|
- `2 7` => `27`
|
||||||
|
- `2 7 .` => `27.`
|
||||||
|
- `2 7 . 5` => `27.5`
|
||||||
|
- `2 7 . 5 .` => `27.` (cut at decimal point)
|
||||||
|
|
||||||
|
buttons to click `=>` operand stack/list content (from the bottom):
|
||||||
|
|
||||||
|
- `2 7 . 5 enter"` => `27.5`
|
||||||
|
- `2 7 enter` => `27.0"`
|
||||||
|
- `2 enter 7 enter 5 enter` => `5.0 7.0 2.0`
|
||||||
|
- `2 7 . enter` => `27.0`
|
||||||
|
- `2 7 . 5 enter` => `27.5`
|
||||||
|
- `2 enter 7 +` => `9.0`
|
||||||
|
- `2 enter 7 -` => `-5.0`
|
||||||
|
- `2 enter 7 *` => `14.0`
|
||||||
|
- `6 enter 3 /` => `2.0`
|
||||||
|
- `2 5 enter √` => `5.0`
|
||||||
|
- `π` => `3.1415...` (the value of the `Math.PI` constant)
|
|
@ -0,0 +1,2 @@
|
||||||
|
# ignore maven build folder
|
||||||
|
target/
|
|
@ -0,0 +1,103 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>it1901</groupId>
|
||||||
|
<artifactId>packages-template</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.compiler.source>16</maven.compiler.source>
|
||||||
|
<maven.compiler.target>16</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- javafx -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-controls</artifactId>
|
||||||
|
<version>16</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-fxml</artifactId>
|
||||||
|
<version>16</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- junit testing with jupiter -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<version>5.7.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>5.7.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-params</artifactId>
|
||||||
|
<version>5.7.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- test javafx with TextFX -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testfx</groupId>
|
||||||
|
<artifactId>testfx-core</artifactId>
|
||||||
|
<version>4.0.16-alpha</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testfx</groupId>
|
||||||
|
<artifactId>testfx-junit5</artifactId>
|
||||||
|
<version>4.0.16-alpha</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hamcrest</groupId>
|
||||||
|
<artifactId>hamcrest</artifactId>
|
||||||
|
<version>2.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<release>16</release>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.0.0-M5</version>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-maven-plugin</artifactId>
|
||||||
|
<version>0.0.6</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<!-- Default configuration for running -->
|
||||||
|
<!-- Usage: mvn clean javafx:run -->
|
||||||
|
<id>default-cli</id>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>ui.App</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -0,0 +1,108 @@
|
||||||
|
package core;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
public class Calc {
|
||||||
|
|
||||||
|
private final List<Double> operandStack;
|
||||||
|
|
||||||
|
public Calc(double... operands) {
|
||||||
|
operandStack = new ArrayList<>(operands.length + 2);
|
||||||
|
for (var d : operands) {
|
||||||
|
operandStack.add(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the number of operands on the stack
|
||||||
|
*/
|
||||||
|
public int getOperandCount() {
|
||||||
|
return operandStack.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a new operand onto top of the stack.
|
||||||
|
* @param d the new operand
|
||||||
|
*/
|
||||||
|
public void pushOperand(double d) {
|
||||||
|
operandStack.add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param n the place (from the top) to peek
|
||||||
|
* @return the n'th operand from the top
|
||||||
|
* @throws IllegalArgumentException if n is larger than the operand count
|
||||||
|
*/
|
||||||
|
public double peekOperand(int n) {
|
||||||
|
if (n > getOperandCount()) {
|
||||||
|
throw new IllegalArgumentException("Cannot peek at position " + n + " when the operand count is " + getOperandCount());
|
||||||
|
}
|
||||||
|
return operandStack.get(getOperandCount() - n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the top operand
|
||||||
|
*/
|
||||||
|
public double peekOperand() {
|
||||||
|
return peekOperand(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes and returns the top operand.
|
||||||
|
* @return the top operand
|
||||||
|
* @throws IllegalStateException if the stack is empty
|
||||||
|
*/
|
||||||
|
public double popOperand() {
|
||||||
|
if (getOperandCount() == 0) {
|
||||||
|
throw new IllegalStateException("Cannot pop from an empty stack");
|
||||||
|
}
|
||||||
|
return operandStack.remove(operandStack.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the provided operation in the top operand, and
|
||||||
|
* replaces it with the result.
|
||||||
|
* @param op the operation to perform
|
||||||
|
* @return the result of performing the operation
|
||||||
|
* @throws IllegalStateException if the operand stack is empty
|
||||||
|
*/
|
||||||
|
public double performOperation(UnaryOperator<Double> op) throws IllegalStateException {
|
||||||
|
// TODO
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the provided operation in the two topmost operands, and
|
||||||
|
* replaces them with the result.
|
||||||
|
* @param op the operation to perform
|
||||||
|
* @return the result of performing the operation
|
||||||
|
* @throws IllegalStateException if the operand count is less than two
|
||||||
|
*/
|
||||||
|
public double performOperation(BinaryOperator<Double> op) throws IllegalStateException {
|
||||||
|
if (getOperandCount() < 2) {
|
||||||
|
throw new IllegalStateException("Too few operands (" + getOperandCount() + ") on the stack");
|
||||||
|
}
|
||||||
|
var op1 = popOperand();
|
||||||
|
var op2 = popOperand();
|
||||||
|
var result = op.apply(op1, op2);
|
||||||
|
pushOperand(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swaps the two topmost operands.
|
||||||
|
*/
|
||||||
|
public void swap() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicates the top operand.
|
||||||
|
*/
|
||||||
|
public void dup() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package ui;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaFX App
|
||||||
|
*/
|
||||||
|
public class App extends Application {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Stage stage) throws IOException {
|
||||||
|
FXMLLoader fxmlLoader = new FXMLLoader(this.getClass().getResource("App.fxml"));
|
||||||
|
Parent parent = fxmlLoader.load();
|
||||||
|
stage.setScene(new Scene(parent));
|
||||||
|
stage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
launch();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package ui;
|
||||||
|
|
||||||
|
import core.Calc;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Labeled;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
|
||||||
|
public class AppController {
|
||||||
|
|
||||||
|
private Calc calc;
|
||||||
|
|
||||||
|
public AppController() {
|
||||||
|
calc = new Calc(0.0, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Calc getCalc() {
|
||||||
|
return calc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCalc(Calc calc) {
|
||||||
|
this.calc = calc;
|
||||||
|
updateOperandsView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ListView<Double> operandsView;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label operandView;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void initialize() {
|
||||||
|
updateOperandsView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOperandsView() {
|
||||||
|
List<Double> operands = operandsView.getItems();
|
||||||
|
operands.clear();
|
||||||
|
int elementCount = Math.min(calc.getOperandCount(), 3);
|
||||||
|
for (int i = 0; i < elementCount; i++) {
|
||||||
|
operands.add(calc.peekOperand(elementCount - i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getOperandString() {
|
||||||
|
return operandView.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasOperand() {
|
||||||
|
return ! getOperandString().isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getOperand() {
|
||||||
|
return Double.valueOf(operandView.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOperand(String operandString) {
|
||||||
|
operandView.setText(operandString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleEnter() {
|
||||||
|
if (hasOperand()) {
|
||||||
|
calc.pushOperand(getOperand());
|
||||||
|
} else {
|
||||||
|
calc.dup();
|
||||||
|
}
|
||||||
|
setOperand("");
|
||||||
|
updateOperandsView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendToOperand(String s) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleDigit(ActionEvent ae) {
|
||||||
|
if (ae.getSource() instanceof Labeled l) {
|
||||||
|
// TODO append button label to operand
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handlePoint() {
|
||||||
|
var operandString = getOperandString();
|
||||||
|
if (operandString.contains(".")) {
|
||||||
|
// TODO remove characters after point
|
||||||
|
} else {
|
||||||
|
// TODO append point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleClear() {
|
||||||
|
// TODO clear operand
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleSwap() {
|
||||||
|
// TODO clear operand
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performOperation(UnaryOperator<Double> op) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performOperation(boolean swap, BinaryOperator<Double> op) {
|
||||||
|
if (hasOperand()) {
|
||||||
|
// TODO push operand first
|
||||||
|
}
|
||||||
|
// TODO perform operation, but swap first if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleOpAdd() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleOpSub() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleOpMult() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ListView?>
|
||||||
|
|
||||||
|
<GridPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.AppController"
|
||||||
|
alignment="CENTER" hgap="10.0" vgap="10.0" >
|
||||||
|
|
||||||
|
<ListView fx:id="operandsView" prefHeight="80.0"
|
||||||
|
GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="4"/>
|
||||||
|
<Label text="" fx:id="operandView"
|
||||||
|
GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="4"/>
|
||||||
|
|
||||||
|
<!-- multi-line button label with XML entity for newline -->
|
||||||
|
<Button text="E n t e r" onAction="#handleEnter"
|
||||||
|
GridPane.rowIndex="2" GridPane.columnIndex="3" GridPane.rowSpan="3"/>
|
||||||
|
|
||||||
|
<Button text="7" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="2" GridPane.columnIndex="0"/>
|
||||||
|
<Button text="8" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="2" GridPane.columnIndex="1"/>
|
||||||
|
<Button text="9" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="2" GridPane.columnIndex="2"/>
|
||||||
|
|
||||||
|
<Button text="4" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="3" GridPane.columnIndex="0"/>
|
||||||
|
<Button text="5" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="3" GridPane.columnIndex="1"/>
|
||||||
|
<Button text="6" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="3" GridPane.columnIndex="2"/>
|
||||||
|
|
||||||
|
<Button text="1" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="4" GridPane.columnIndex="0"/>
|
||||||
|
<Button text="2" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="4" GridPane.columnIndex="1"/>
|
||||||
|
<Button text="3" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="4" GridPane.columnIndex="2"/>
|
||||||
|
|
||||||
|
<Button text="0" onAction="#handleDigit"
|
||||||
|
GridPane.rowIndex="5" GridPane.columnIndex="0"/>
|
||||||
|
<Button text="." onAction="#handlePoint"
|
||||||
|
GridPane.rowIndex="5" GridPane.columnIndex="1"/>
|
||||||
|
<Button text="C" onAction="#handleClear"
|
||||||
|
GridPane.rowIndex="5" GridPane.columnIndex="2"/>
|
||||||
|
<Button text="~" onAction="#handleSwap"
|
||||||
|
GridPane.rowIndex="5" GridPane.columnIndex="3"/>
|
||||||
|
|
||||||
|
<Button text="+" onAction="#handleOpAdd"
|
||||||
|
GridPane.rowIndex="6" GridPane.columnIndex="0"/>
|
||||||
|
<Button text="-" onAction="#handleOpSub"
|
||||||
|
GridPane.rowIndex="6" GridPane.columnIndex="1"/>
|
||||||
|
<Button text="*" onAction="#handleOpMult"
|
||||||
|
GridPane.rowIndex="6" GridPane.columnIndex="2"/>
|
||||||
|
|
||||||
|
<!-- TODO -->
|
||||||
|
<Button text="/"
|
||||||
|
GridPane.rowIndex="6" GridPane.columnIndex="3"/>
|
||||||
|
<Button text="√"
|
||||||
|
GridPane.rowIndex="7" GridPane.columnIndex="0"/>
|
||||||
|
<Button text="π"
|
||||||
|
GridPane.rowIndex="7" GridPane.columnIndex="1"/>
|
||||||
|
</GridPane>
|
|
@ -0,0 +1,114 @@
|
||||||
|
package core;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class CalcTest {
|
||||||
|
|
||||||
|
private static void checkCalc(Calc calc, double... operands) {
|
||||||
|
Assertions.assertEquals(operands.length, calc.getOperandCount(), "Wrong operand count");
|
||||||
|
for (int i = 0; i < operands.length; i++) {
|
||||||
|
Assertions.assertEquals(operands[i], calc.peekOperand(i), "Wrong value at #" + i + " of operand stack");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalc() {
|
||||||
|
checkCalc(new Calc());
|
||||||
|
checkCalc(new Calc(1.0), 1.0);
|
||||||
|
checkCalc(new Calc(3.14, 1.0), 1.0, 3.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPushOperand() {
|
||||||
|
Calc calc = new Calc();
|
||||||
|
calc.pushOperand(1.0);
|
||||||
|
checkCalc(calc, 1.0);
|
||||||
|
calc.pushOperand(3.14);
|
||||||
|
checkCalc(calc, 3.14, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPeekOperand() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
Assertions.assertEquals(3.14, calc.peekOperand());
|
||||||
|
Assertions.assertThrows(IllegalArgumentException.class, () -> new Calc().peekOperand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPeekOperandN() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
Assertions.assertEquals(3.14, calc.peekOperand(0));
|
||||||
|
Assertions.assertEquals(1.0, calc.peekOperand(1));
|
||||||
|
Assertions.assertThrows(IllegalArgumentException.class, () -> calc.peekOperand(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPopOperand() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
Assertions.assertEquals(3.14, calc.popOperand());
|
||||||
|
checkCalc(calc, 1.0);
|
||||||
|
Assertions.assertEquals(1.0, calc.popOperand());
|
||||||
|
checkCalc(calc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPopOperand_emptyStack() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().popOperand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOperation1() {
|
||||||
|
Calc calc = new Calc(1.0);
|
||||||
|
Assertions.assertEquals(-1.0, calc.performOperation(n -> -n));
|
||||||
|
checkCalc(calc, -1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOperation1_emptyOperandStack() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().performOperation(n -> -n));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOperation2() {
|
||||||
|
Calc calc = new Calc(1.0, 3.0);
|
||||||
|
Assertions.assertEquals(-2.0, calc.performOperation((n1, n2) -> n1 - n2));
|
||||||
|
checkCalc(calc, -2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOperation2_lessThanTwoOperands() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc(1.0).performOperation((n1, n2) -> n1 - n2));
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().performOperation((n1, n2) -> n1 - n2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSwap() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
calc.swap();
|
||||||
|
checkCalc(calc, 3.14, 1.0);
|
||||||
|
calc.swap();
|
||||||
|
checkCalc(calc, 1.0, 3.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSwap_lessThanTwoOperands() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc(1.0).swap());
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().swap());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDup() {
|
||||||
|
Calc calc = new Calc(1.0, 3.14);
|
||||||
|
Assertions.assertEquals(3.14, calc.popOperand());
|
||||||
|
checkCalc(calc, 1.0);
|
||||||
|
Assertions.assertEquals(1.0, calc.popOperand());
|
||||||
|
checkCalc(calc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDup_emptyOperandStack() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> new Calc().dup());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package ui;
|
||||||
|
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.testfx.framework.junit5.ApplicationTest;
|
||||||
|
import org.testfx.matcher.control.LabeledMatchers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TestFX App test
|
||||||
|
*/
|
||||||
|
public class AppTest extends ApplicationTest {
|
||||||
|
|
||||||
|
private AppController controller;
|
||||||
|
private Parent root;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Stage stage) throws IOException {
|
||||||
|
FXMLLoader fxmlLoader = new FXMLLoader(this.getClass().getResource("App.fxml"));
|
||||||
|
root = fxmlLoader.load();
|
||||||
|
controller = fxmlLoader.getController();
|
||||||
|
stage.setScene(new Scene(root));
|
||||||
|
stage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Parent getRootNode() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String enterLabel = """
|
||||||
|
E
|
||||||
|
n
|
||||||
|
t
|
||||||
|
e
|
||||||
|
r
|
||||||
|
""".stripTrailing();
|
||||||
|
|
||||||
|
private void click(String... labels) {
|
||||||
|
for (var label : labels) {
|
||||||
|
clickOn(LabeledMatchers.hasText(label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getOperandString() {
|
||||||
|
return ((Label) getRootNode().lookup("#operandView")).getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListView<Double> getOperandsView() {
|
||||||
|
return (ListView<Double>) getRootNode().lookup("#operandsView");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkView(double... operands) {
|
||||||
|
for (int i = 0; i < operands.length; i++) {
|
||||||
|
Assertions.assertEquals(operands[i], controller.getCalc().peekOperand(i), "Wrong value at #" + i + " of operand stack");
|
||||||
|
}
|
||||||
|
List<Double> viewItems = getOperandsView().getItems();
|
||||||
|
for (int i = 0; i < operands.length; i++) {
|
||||||
|
Assertions.assertEquals(operands[i], viewItems.get(viewItems.size() - i - 1), "Wrong value at #" + i + " of operands view");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkView(String operandString, double... operands) {
|
||||||
|
Assertions.assertEquals(operandString, getOperandString());
|
||||||
|
checkView(operands);
|
||||||
|
}
|
||||||
|
|
||||||
|
// see https://www.baeldung.com/parameterized-tests-junit-5
|
||||||
|
// about @ParameterizedTest
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource
|
||||||
|
public void testClicksOperand(String labels, String operandString) {
|
||||||
|
for (var label : labels.split(" ")) {
|
||||||
|
click(label);
|
||||||
|
}
|
||||||
|
checkView(operandString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> testClicksOperand() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("2 7", "27"),
|
||||||
|
Arguments.of("2 7 .", "27."),
|
||||||
|
Arguments.of("2 7 . 5", "27.5"),
|
||||||
|
Arguments.of("2 7 . 5 .", "27.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource
|
||||||
|
public void testClicksOperands(String labels, String operandsString) {
|
||||||
|
for (var label : labels.split(" ")) {
|
||||||
|
click(label.equals("\n") ? enterLabel : label);
|
||||||
|
}
|
||||||
|
checkView("", Stream.of(operandsString.split(" ")).mapToDouble(Double::valueOf).toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> testClicksOperands() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("2 7 . 5 \n", "27.5"),
|
||||||
|
Arguments.of("2 7 \n", "27.0"),
|
||||||
|
Arguments.of("2 \n 7 \n 5 \n", "5.0", "7.0", "2.0"),
|
||||||
|
Arguments.of("2 7 . \n", "27.0"),
|
||||||
|
Arguments.of("2 7 . 5 \n", "27.5"),
|
||||||
|
Arguments.of("2 \n 7 +", "9.0"),
|
||||||
|
Arguments.of("2 \n 7 -", "-5.0"),
|
||||||
|
Arguments.of("2 \n 7 *", "14.0"),
|
||||||
|
Arguments.of("6 \n 3 /", "2.0"),
|
||||||
|
Arguments.of("2 5 \n √", "5.0")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPi() {
|
||||||
|
click("π");
|
||||||
|
checkView("", Math.PI);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Tests for the RPN calculator
|
||||||
|
|
||||||
|
This folder/package contains tests based on TestFX for the RPN Calculator (currently only one test class).
|
||||||
|
|
||||||
|
As can be seen when launching, the app contains a list (top) showing the operands
|
||||||
|
(topmost operand at the bottom), a text field (below list, initially empty) for a new operand and
|
||||||
|
the buttons for digits, enter, decimal point, operations etc.
|
||||||
|
|
||||||
|
## What is tested
|
||||||
|
|
||||||
|
The tests simulate clicks on the buttons and checks that the underlying Calc object,
|
||||||
|
the list (a view of the Calc object's operand stack) and the text field are updated as expected.
|
||||||
|
E.g. if you click buttons `2 3 . 5` the string `23.5` should be shown,
|
||||||
|
while the list is not affected. If you then click `enter`, the text field should be emptied, the operand stack should have `23.5` at the top and the list should have `23.5` at the bottom
|
||||||
|
(logically the top of the operand stack).
|
||||||
|
|
||||||
|
Below are the specific cases that are tested.
|
||||||
|
|
||||||
|
buttons to click `=>` text field content:
|
||||||
|
|
||||||
|
- `2 7` => `27`
|
||||||
|
- `2 7 .` => `27.`
|
||||||
|
- `2 7 . 5` => `27.5`
|
||||||
|
- `2 7 . 5 .` => `27.` (cut at decimal point)
|
||||||
|
|
||||||
|
buttons to click `=>` operand stack/list content (from the bottom):
|
||||||
|
|
||||||
|
- `2 7 . 5 enter"` => `27.5`
|
||||||
|
- `2 7 enter` => `27.0"`
|
||||||
|
- `2 enter 7 enter 5 enter` => `5.0 7.0 2.0`
|
||||||
|
- `2 7 . enter` => `27.0`
|
||||||
|
- `2 7 . 5 enter` => `27.5`
|
||||||
|
- `2 enter 7 +` => `9.0`
|
||||||
|
- `2 enter 7 -` => `-5.0`
|
||||||
|
- `2 enter 7 *` => `14.0`
|
||||||
|
- `6 enter 3 /` => `2.0`
|
||||||
|
- `2 5 enter √` => `5.0`
|
||||||
|
- `π` => `3.1415...` (the value of the `Math.PI` constant)
|
Loading…
Reference in New Issue