/**
* Copyright (C) 2022 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package sevenUnitsGUI;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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 sevenUnits.unit.BaseDimension;
import sevenUnits.unit.BritishImperial;
import sevenUnits.unit.LinearUnit;
import sevenUnits.unit.Metric;
import sevenUnits.unit.Unit;
import sevenUnits.unit.UnitDatabase;
import sevenUnits.unit.UnitType;
import sevenUnits.utils.NameSymbol;
import sevenUnits.utils.Nameable;
import sevenUnits.utils.ObjectProduct;
import sevenUnits.utils.UncertainDouble;
/**
* Various tests for the {@link Presenter}.
*
* Note: this test outputs a lot to the standard output, because creating a
* {@link UnitDatabase} and converting with a {@link ViewBot} both trigger
* println statements.
*
* @author Adrien Hopkins
*
* @since v0.4.0
* @since 2022-02-10
*/
public final class PresenterTest {
private static final Path TEST_SETTINGS = Path.of("src", "test", "resources",
"test-settings.txt");
static final Set testUnits = Set.of(Metric.METRE, Metric.KILOMETRE,
Metric.METRE_PER_SECOND, Metric.KILOMETRE_PER_HOUR);
static final Set> testDimensions = Set
.of(Metric.Dimensions.LENGTH, Metric.Dimensions.VELOCITY);
private static final Stream, Map>> SEARCH_RULES = Stream
.of(PrefixSearchRule.NO_PREFIXES, PrefixSearchRule.COMMON_PREFIXES,
PrefixSearchRule.ALL_METRIC_PREFIXES);
/**
* @return rounding rules used by {@link #testRoundingRules}
* @since v0.4.0
* @since 2022-04-16
*/
private static Stream> getRoundingRules() {
final var SCIENTIFIC_ROUNDING = StandardDisplayRules.uncertaintyBased();
final var INTEGER_ROUNDING = StandardDisplayRules.fixedDecimals(0);
final var SIG_FIG_ROUNDING = StandardDisplayRules.fixedPrecision(4);
return Stream.of(SCIENTIFIC_ROUNDING, INTEGER_ROUNDING, SIG_FIG_ROUNDING);
}
private static Stream, Map>> getSearchRules() {
return SEARCH_RULES;
}
private static Set names(Set extends Nameable> units) {
return units.stream().map(Nameable::getName).collect(Collectors.toSet());
}
private static Stream testConvertExpressions() {
return Stream.of(
Arguments.of("10000.0 m", "km", "10000.0 m = 10.00000 km"),
Arguments.of("1.0 m", "ft; in", "1.0 m = 3 ft + 3 in"),
Arguments.of("1.0 m", "ftin", "1.0 m = 3 ft + 3 in"));
}
private static Stream testConvertUnits() {
return Stream.of(
Arguments.of("m", "km", 10000.0, "10000.0 m = 10.00000 km"),
Arguments.of("m", "ftin", 1.0, "1.0 m = 3 ft + 3 in"));
}
/**
* Test method for {@link Presenter#convertExpressions}
*
* @since v0.4.0
* @since 2022-02-12
*/
@ParameterizedTest
@MethodSource
void testConvertExpressions(String from, String to, String expectedOutput) {
// setup
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
viewBot.setFromExpression(from);
viewBot.setToExpression(to);
// convert expression
presenter.convertExpressions();
// test result
final var outputs = viewBot.expressionConversionList();
assertEquals(expectedOutput, outputs.get(outputs.size() - 1).toString());
}
/**
* Test method for {@link Presenter#convertUnits}
*
* @since v0.4.0
* @since 2022-02-12
*/
@ParameterizedTest
@MethodSource
void testConvertUnits(String from, String to, double value,
String expectedOutput) {
// setup
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
viewBot.setFromSelection(from);
viewBot.setToSelection(to);
viewBot.setInputValue(Double.toString(value));
// convert expression
presenter.convertUnits();
// test result
final var outputs = viewBot.unitConversionList();
assertEquals(expectedOutput, outputs.get(outputs.size() - 1).toString());
}
/**
* Ensures that the default unitfile can be disabled.
*
* @since v1.0.0
* @since 2025-02-23
*/
@Test
void testDisableDefault() {
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
assumeTrue(presenter.database.containsUnitName("joule"),
"Attempted to test disabling default on unit not in default file.");
presenter.setUseDefaultDatafiles(false);
assertFalse(presenter.database.containsUnitName("joule"),
"Presenter disabled default datafiles, but still contains the joule.");
}
/**
* Tests that duplicate units are successfully removed, if that is asked for
*
* @since v0.4.0
* @since 2022-04-16
*/
@Test
void testDuplicateUnits() {
final var metre = Metric.METRE;
final var meter = Metric.METRE.withName(NameSymbol.of("meter", "m"));
// load 2 duplicate units
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
presenter.database.clear();
presenter.database.addUnit("metre", metre);
presenter.database.addUnit("meter", meter);
presenter.setOneWayConversionEnabled(false);
presenter.setSearchRule(PrefixSearchRule.NO_PREFIXES);
// test that only one of them is included if duplicate units disabled
presenter.setShowDuplicates(false);
presenter.updateView();
assertEquals(1, viewBot.getFromUnitNames().size());
assertEquals(1, viewBot.getToUnitNames().size());
// test that both of them is included if duplicate units enabled
presenter.setShowDuplicates(true);
presenter.updateView();
assertEquals(2, viewBot.getFromUnitNames().size());
assertEquals(2, viewBot.getToUnitNames().size());
}
/**
* Tests that one-way conversion correctly filters From and To units
*
* @since v0.4.0
* @since 2022-04-16
*/
@Test
void testOneWayConversion() {
// metre is metric, inch is non-metric, tempC is semi-metric
final var allNames = Set.of("metre", "inch", "tempC");
final var metricNames = Set.of("metre", "tempC");
final var nonMetricNames = Set.of("inch", "tempC");
// load view with one metric and one non-metric unit
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
presenter.database.clear();
presenter.database.addUnit("metre", Metric.METRE);
presenter.database.addUnit("inch", BritishImperial.Length.INCH);
presenter.database.addUnit("tempC", Metric.CELSIUS);
presenter.setSearchRule(PrefixSearchRule.NO_PREFIXES);
// test that units are removed from each side when one-way conversion is
// enabled
presenter.setOneWayConversionEnabled(true);
assertEquals(nonMetricNames, viewBot.getFromUnitNames());
assertEquals(metricNames, viewBot.getToUnitNames());
// test that units are kept when one-way conversion is disabled
presenter.setOneWayConversionEnabled(false);
assertEquals(allNames, viewBot.getFromUnitNames());
assertEquals(allNames, viewBot.getToUnitNames());
}
/**
* Tests the prefix-viewing functionality.
*
* @since v0.4.0
* @since 2022-04-16
*/
@Test
void testPrefixViewing() {
// setup
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
viewBot.setViewablePrefixNames(Set.of("kilo", "milli"));
presenter.setNumberDisplayRule(UncertainDouble::toString);
// view prefix
viewBot.setViewedPrefixName("kilo");
presenter.prefixSelected(); // just in case
// get correct values
final var expectedNameSymbol = presenter.database.getPrefix("kilo")
.getNameSymbol();
final var expectedMultiplierString = String
.valueOf(Metric.KILO.getMultiplier());
// test that presenter's values are correct
final var prefixRecord = viewBot.prefixViewList().get(0);
assertEquals(expectedNameSymbol, prefixRecord.getNameSymbol());
assertEquals(expectedMultiplierString, prefixRecord.multiplierString());
}
/**
* Tests that rounding rules are used correctly.
*
* @since v0.4.0
* @since 2022-04-16
*/
@ParameterizedTest
@MethodSource("getRoundingRules")
void testRoundingRules(Function roundingRule) {
// setup
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
presenter.setNumberDisplayRule(roundingRule);
// convert and round
viewBot.setInputValue("12345.6789");
viewBot.setFromSelection("metre");
viewBot.setToSelection("kilometre");
presenter.convertUnits();
// test the result of the rounding
final var expectedOutputString = roundingRule
.apply(UncertainDouble.fromRoundedString("12.3456789"));
final var actualOutputString = viewBot.unitConversionList().get(0)
.outputValueString();
assertEquals(expectedOutputString, actualOutputString);
}
/**
* Tests that the Presenter correctly applies search rules.
*
* @param searchRule search rule to test
* @since v0.4.0
* @since 2022-07-08
*/
@ParameterizedTest
@MethodSource("getSearchRules")
void testSearchRules(
Function, Map> searchRule) {
// setup
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
presenter.setSearchRule(searchRule);
presenter.setOneWayConversionEnabled(false);
presenter.database.clear();
presenter.database.addUnit("metre", Metric.METRE);
presenter.database.addUnit("inch", BritishImperial.Length.INCH);
presenter.updateView();
// create expected output based on rule
final Set expectedOutput = new HashSet<>();
expectedOutput.addAll(searchRule
.apply(Map.entry("inch", BritishImperial.Length.INCH)).keySet());
expectedOutput.addAll(
searchRule.apply(Map.entry("metre", Metric.METRE)).keySet());
final var actualOutput = viewBot.getFromUnitNames();
// test output
assertEquals(expectedOutput, actualOutput);
}
/**
* Tests that settings can be saved to and loaded from a file.
*
* @since v0.4.0
* @since 2022-04-16
*/
@Test
void testSettingsSaving() {
// setup
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
// set and save custom settings
presenter.setOneWayConversionEnabled(true);
presenter.setShowDuplicates(true);
presenter.setNumberDisplayRule(StandardDisplayRules.fixedPrecision(11));
presenter.setPrefixRepetitionRule(
DefaultPrefixRepetitionRule.COMPLEX_REPETITION);
assumeTrue(presenter.writeSettings(TEST_SETTINGS),
"Could not write to settings file.");
// overwrite custom settings
presenter.setOneWayConversionEnabled(false);
presenter.setShowDuplicates(false);
presenter.setNumberDisplayRule(StandardDisplayRules.uncertaintyBased());
// load settings & test that they're the same
presenter.loadSettings(TEST_SETTINGS);
assertTrue(presenter.oneWayConversionEnabled());
assertTrue(presenter.duplicatesShown());
assertEquals(StandardDisplayRules.fixedPrecision(11),
presenter.getNumberDisplayRule());
}
/**
* Ensures the Presenter generates the correct data upon a unit-viewing.
*
* @since v0.4.0
* @since 2022-04-16
*/
@Test
void testUnitViewing() {
// setup
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
viewBot.setViewableUnitNames(names(testUnits));
// view unit
viewBot.setViewedUnitName("metre");
presenter.unitNameSelected(); // just in case this isn't triggered
// automatically
// get correct values
final var expectedNameSymbol = presenter.database.getUnit("metre")
.getNameSymbol();
final var expectedDefinition = "(Base unit)";
final var expectedDimensionName = presenter
.getDimensionName(Metric.METRE.getDimension());
final var expectedUnitType = UnitType.METRIC;
// test for correctness
final var viewRecord = viewBot.unitViewList().get(0);
assertEquals(expectedNameSymbol, viewRecord.getNameSymbol());
assertEquals(expectedDefinition, viewRecord.definition());
assertEquals(expectedDimensionName, viewRecord.dimensionName());
assertEquals(expectedUnitType, viewRecord.unitType());
}
/**
* Test for {@link Presenter#updateView()}
*
* @since v0.4.0
* @since 2022-02-12
*/
@Test
void testUpdateView() {
// setup
final var viewBot = new ViewBot();
final var presenter = new Presenter(viewBot);
presenter.setOneWayConversionEnabled(false);
presenter.setSearchRule(PrefixSearchRule.NO_PREFIXES);
// override default database units
presenter.database.clear();
for (final Unit unit : testUnits) {
presenter.database.addUnit(unit.getPrimaryName().orElseThrow(), unit);
}
for (final var dimension : testDimensions) {
presenter.database.addDimension(
dimension.getPrimaryName().orElseThrow(), dimension);
}
// set from and to units
viewBot.setFromUnitNames(names(testUnits));
viewBot.setToUnitNames(names(testUnits));
viewBot.setDimensionNames(names(testDimensions));
viewBot.setSelectedDimensionName(Metric.Dimensions.LENGTH.getName());
// filter to length units only, then get the filtered sets of units
presenter.updateView();
final var fromUnits = viewBot.getFromUnitNames();
final var toUnits = viewBot.getToUnitNames();
// test that fromUnits/toUnits is [METRE, KILOMETRE]
assertEquals(Set.of("metre", "kilometre"), fromUnits);
assertEquals(Set.of("metre", "kilometre"), toUnits);
}
}