Added // TODO and updated READMEs

This commit is contained in:
Hallvard Trætteberg 2021-08-13 13:46:07 +00:00
parent 8075ed5f4b
commit cf0d1f6e87
6 changed files with 126 additions and 79 deletions

View File

@ -1,3 +1,13 @@
## Javafx template
# Javafx template
Template for single-module javafx projects, with maven setup for latest java and javafx, and jupiter and testfx for testing.
Template for single-module javafx project, with maven setup for Java 16 and JavaFX 16, and JUnit 5 (Jupiter) and TestFX for testing.
To make the project more interesting, it is the start of an [RPN](https://en.wikipedia.org/wiki/Reverse_Polish_notation) calculator (look for `// TODO`) markers). The core logic is almost implemented (in [Calc.java](src/main/java/app/Calc.java)), the fxml file (in [App.fxml](src/main/resources/app/App.fxml) is almost complete, but the controller class (in [AppController.java](src/main/java/app/AppController.java) is pretty limited. And last, but not least, there is a TestFX-based test (in [AppTest.java](src/test/java/app/AppTest.java), see the [README](src/test/java/app/README.md) for details about what it tests.
## Trying it out
The project in javafx-template can be tried out in various ways:
- compile with `mvn compile` (after `cd javafx-template` of course)
- test with `mvn test` (it should fail until you complete the RPN calculator)
- run with `mvn javafx:run` (it should open, but not work properly)

View File

@ -2,7 +2,6 @@ package app;
import java.util.List;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import javafx.event.ActionEvent;
@ -39,16 +38,12 @@ public class AppController {
updateOperandsView();
}
private int minOperandCount = 2;
private void updateOperandsView() {
while (calc.getOperandCount() < minOperandCount) {
calc.pushOperand(calc.getOperandCount(), 0.0);
}
List<Double> operands = operandsView.getItems();
operands.clear();
for (int i = 0; i < minOperandCount; i++) {
operands.add(calc.peekOperand(minOperandCount - i - 1));
int elementCount = Math.min(calc.getOperandCount(), 3);
for (int i = 0; i < elementCount; i++) {
operands.add(calc.peekOperand(elementCount - i - 1));
}
}
@ -68,33 +63,25 @@ public class AppController {
operandView.setText(operandString);
}
private void setOperand(double d) {
setOperand(Double.toString(d));
}
private void clearOperand() {
setOperand("");
}
@FXML
void handleEnter() {
if (hasOperand()) {
calc.pushOperand(getOperand());
} else {
calc.dup();
}
clearOperand();
}
setOperand("");
updateOperandsView();
}
}
private void appendToOperand(String s) {
setOperand(operandView.getText() + s);
// TODO
}
@FXML
void handleDigit(ActionEvent ae) {
if (ae.getSource() instanceof Labeled l) {
appendToOperand(l.getText());
// TODO append button label to operand
}
}
@ -102,70 +89,40 @@ public class AppController {
void handlePoint() {
var operandString = getOperandString();
if (operandString.contains(".")) {
setOperand(operandString.substring(0, operandString.indexOf(".") + 1));
// TODO remove characters after point
} else {
appendToOperand(".");
// TODO append point
}
}
@FXML
void handleClear() {
clearOperand();
}
private void withOperand(Runnable proc) {
if (hasOperand()) {
calc.pushOperand(getOperand());
clearOperand();
}
proc.run();
updateOperandsView();
// TODO clear operand
}
private void performOperation(UnaryOperator<Double> op) {
withOperand(() -> calc.performOperation(op));
// TODO
}
private void performOperation(boolean swap, BinaryOperator<Double> op) {
withOperand(() -> {
if (swap) {
calc.swap();
}
calc.performOperation(op);
});
if (hasOperand()) {
// TODO push operand first
}
// TODO perform operation, but swap first if needed
}
@FXML
void handleOpAdd() {
performOperation(false, (op1, op2) -> op1 + op2);
// TODO
}
@FXML
void handleOpSub() {
performOperation(true, (op1, op2) -> op1 - op2);
// TODO
}
@FXML
void handleOpMult() {
performOperation(false, (op1, op2) -> op1 * op2);
}
@FXML
void handleOpDiv() {
performOperation(true, (op1, op2) -> op1 / op2);
}
@FXML
void handleOpSquareRoot() {
performOperation(op1 -> Math.sqrt(op1));
}
@FXML
void handlePi() {
withOperand(() -> calc.pushOperand(Math.PI));
}
public static void main(String[] args) {
System.out.println("\u221A");
// TODO
}
}

View File

@ -13,41 +13,79 @@ public class Calc {
operandStack.addAll(List.of(operands));
}
/**
* @return the number of operands on the stack
*/
public int getOperandCount() {
return operandStack.size();
}
/**
* Pushes a new operand into the n'th place from the top.
* @param n the place to push
* @param d the new operand
*/
public void pushOperand(int n, double d) {
operandStack.add(operandStack.size() - n, d);
}
/**
* Pushes a new operand onto top of the stack.
* @param d the new operand
*/
public void pushOperand(double d) {
pushOperand(0, d);
}
/**
* @param n the place (from the top) to peek
* @return the n'th operand from the top
*/
public double peekOperand(int n) {
return operandStack.get(operandStack.size() - n - 1);
}
/**
* @return the top operand
*/
public double peekOperand() {
return peekOperand(0);
}
/**
* Removes and returns the n'th operand from the top.
* @param n the place from the top to remove
* @return the n'th operand from the top
*/
public double popOperand(int n) {
return operandStack.remove(operandStack.size() - n - 1);
}
/**
* Removes and returns the top operand.
* @return the top operand
*/
public double popOperand() {
return popOperand(0);
}
/**
* 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
*/
public double performOperation(UnaryOperator<Double> op) {
var op1 = popOperand();
var result = op.apply(op1);
pushOperand(result);
return result;
// 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
*/
public double performOperation(BinaryOperator<Double> op) {
var op1 = popOperand();
var op2 = popOperand();
@ -56,14 +94,17 @@ public class Calc {
return result;
}
/**
* Swaps the two topmost operands.
*/
public void swap() {
var op1 = popOperand();
var op2 = popOperand();
pushOperand(op1);
pushOperand(op2);
// TODO
}
/**
* Duplicates the top operand.
*/
public void dup() {
pushOperand(peekOperand());
// TODO
}
}

View File

@ -13,6 +13,7 @@
<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="4"/>
@ -47,10 +48,12 @@
GridPane.rowIndex="6" GridPane.columnIndex="1"/>
<Button text="*" onAction="#handleOpMult"
GridPane.rowIndex="6" GridPane.columnIndex="2"/>
<Button text="/" onAction="#handleOpDiv"
<!-- TODO -->
<Button text="/"
GridPane.rowIndex="6" GridPane.columnIndex="3"/>
<Button text="√" onAction="#handleOpSquareRoot"
<Button text="√"
GridPane.rowIndex="7" GridPane.columnIndex="0"/>
<Button text="π" onAction="#handlePi"
<Button text="π"
GridPane.rowIndex="7" GridPane.columnIndex="1"/>
</GridPane>

View File

@ -9,7 +9,6 @@ import javafx.stage.Stage;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
@ -17,7 +16,6 @@ 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.opentest4j.AssertionFailedError;
import org.testfx.framework.junit5.ApplicationTest;
import org.testfx.matcher.control.LabeledMatchers;
@ -106,7 +104,7 @@ public class AppTest extends ApplicationTest {
for (var label : labels.split(" ")) {
click(label.equals("\n") ? enterLabel : label);
}
checkView(Stream.of(operandsString.split(" ")).mapToDouble(Double::valueOf).toArray());
checkView("", Stream.of(operandsString.split(" ")).mapToDouble(Double::valueOf).toArray());
}
private static Stream<Arguments> testClicksOperands() {

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)