Laget to varianter til og la til flere tester

This commit is contained in:
Hallvard Trætteberg 2021-08-18 14:53:22 +00:00
parent ccc6a98379
commit b643a36ab5
25 changed files with 1775 additions and 0 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
}

View File

@ -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

View File

@ -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());
}
} }

2
modules-template/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# ignore maven build folder
target/

View File

@ -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>

View File

@ -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
}
}

View File

@ -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());
}
}

62
modules-template/pom.xml Normal file
View File

@ -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>

View File

@ -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
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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
}
}

View File

@ -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&#10;n&#10;t&#10;e&#10;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>

View File

@ -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);
}
}

View File

@ -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)

2
packages-template/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# ignore maven build folder
target/

103
packages-template/pom.xml Normal file
View File

@ -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>

View File

@ -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
}
}

View File

@ -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();
}
}

View File

@ -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
}
}

View File

@ -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&#10;n&#10;t&#10;e&#10;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>

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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)