Explorar o código

Start writing test cases for GUI components with IngredientPanel.
- Create a MockCallable for a simple way to check if things are working correctly without learning mockito (Internet is Out).
- Make all of the relevant text objects in the ingredient panel reachable by test code.

Sam Jaffe %!s(int64=5) %!d(string=hai) anos
pai
achega
d15c2de568

+ 12 - 4
src/main/lombok/org/leumasjaffe/recipe/view/IngredientPanel.java

@@ -15,14 +15,22 @@ import org.leumasjaffe.observer.ObservableController;
 import org.leumasjaffe.observer.ObserverDispatch;
 import org.leumasjaffe.recipe.model.Ingredient;
 
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.experimental.FieldDefaults;
+
 import javax.swing.JFormattedTextField;
 import java.awt.Font;
 import javax.swing.JLabel;
 
 @SuppressWarnings("serial")
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 public class IngredientPanel extends JPanel implements AutoGrowPanel.DocumentListenable {
 	ObservableController<JTextField, Ingredient> controller;
-	private JTextField txtName;
+	@Getter(AccessLevel.PACKAGE) JTextField txtName;
+	@Getter(AccessLevel.PACKAGE) JFormattedTextField txtAmount;
+	@Getter(AccessLevel.PACKAGE) JTextField txtUnit;
+	@Getter(AccessLevel.PACKAGE) JTextField txtPreparation;
 		
 	public IngredientPanel(final Ingredient ingredient) {
 		GridBagLayout gridBagLayout = new GridBagLayout();
@@ -53,7 +61,7 @@ public class IngredientPanel extends JPanel implements AutoGrowPanel.DocumentLis
 		NumberFormatter fmtDone = new NumberFormatter(NumberFormat.getNumberInstance(Locale.getDefault()));
 		fmtDone.setMinimum(0.0);
 		fmtDone.setCommitsOnValidEdit(true);
-		JFormattedTextField txtAmount = new JFormattedTextField(fmtDone);
+		txtAmount = new JFormattedTextField(fmtDone);
 		txtAmount.setValue(ingredient.getAmount().getValue());
 		txtAmount.setFont(new Font("Source Code Pro", Font.PLAIN, 10));
 		GridBagConstraints gbc_txtAmount = new GridBagConstraints();
@@ -64,7 +72,7 @@ public class IngredientPanel extends JPanel implements AutoGrowPanel.DocumentLis
 		add(txtAmount, gbc_txtAmount);
 		txtAmount.setColumns(4);
 		
-		JTextField txtUnit = new JTextField(ingredient.getAmount().unitName());
+		txtUnit = new JTextField(ingredient.getAmount().unitName());
 		txtUnit.setFont(new Font("Source Code Pro", Font.PLAIN, 10));
 		GridBagConstraints gbc_txtUnit = new GridBagConstraints();
 		gbc_txtUnit.insets = new Insets(0, 0, 0, 5);
@@ -75,7 +83,7 @@ public class IngredientPanel extends JPanel implements AutoGrowPanel.DocumentLis
 		add(txtUnit, gbc_txtUnit);
 		txtUnit.setColumns(6);
 		
-		JTextField txtPreparation = new JTextField(ingredient.getPreparation());
+		txtPreparation = new JTextField(ingredient.getPreparation());
 		txtPreparation.setFont(new Font("Source Code Pro", Font.PLAIN, 10));
 		GridBagConstraints gbc_txtPreparation = new GridBagConstraints();
 		gbc_txtPreparation.anchor = GridBagConstraints.ABOVE_BASELINE;

+ 9 - 0
src/test/java/org/leumasjaffe/mock/MockBiConsumer.java

@@ -0,0 +1,9 @@
+package org.leumasjaffe.mock;
+
+import java.util.function.BiConsumer;
+
+public class MockBiConsumer extends MockCallable {
+	public <T, U> BiConsumer<T, U> call() {
+		return (t, u) -> super.called();
+	}
+}

+ 23 - 0
src/test/java/org/leumasjaffe/mock/MockCallable.java

@@ -0,0 +1,23 @@
+package org.leumasjaffe.mock;
+
+public class MockCallable {
+	private boolean set = false;
+	private int calls = 0;
+	private int called = 0;
+	
+	public void expect(int calls) {
+		if (this.set) {
+			throw new IllegalStateException("Please only call expect once");
+		}
+		this.set = true;
+		this.calls = calls;
+	}
+	
+	protected void called() {
+		++called;
+	}
+	
+	public boolean satisfied() {
+		return calls == called;
+	}
+}

+ 60 - 0
src/test/java/org/leumasjaffe/recipe/view/IngredientPanelTest.java

@@ -0,0 +1,60 @@
+package org.leumasjaffe.recipe.view;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.leumasjaffe.mock.MockBiConsumer;
+import org.leumasjaffe.observer.ObservableListener;
+import org.leumasjaffe.observer.ObserverDispatch;
+import org.leumasjaffe.recipe.model.Amount;
+import org.leumasjaffe.recipe.model.Ingredient;
+
+class IngredientPanelTest extends SwingTestCase {
+	Ingredient stuff;
+	IngredientPanel panel;
+	
+	@BeforeEach
+	void setUp() {
+		stuff = new Ingredient("Onions", "Sliced", new Amount("100 g"));
+		panel = new IngredientPanel(stuff);
+	}
+
+	@Test
+	void testFilledOutWithContent() {
+		assertEquals("Onions", panel.getTxtName().getText());
+		assertEquals("Sliced", panel.getTxtPreparation().getText());
+		assertEquals("100", panel.getTxtAmount().getText());
+		assertEquals("g", panel.getTxtUnit().getText());
+	}
+
+	@Test
+	void testIsSubscribedToUpdates() {
+		stuff.setName("Bacon");
+		assertEquals("Onions", panel.getTxtName().getText());
+		ObserverDispatch.notifySubscribers(stuff);
+		assertEquals("Bacon", panel.getTxtName().getText());
+	}
+
+	@Test
+	void testViewUpdateAltersModel() {
+		panel.getTxtName().setText("Bacon");
+		waitForSwing();
+		assertEquals("Bacon", stuff.getName());
+	}
+
+	// TODO: I need to add hook-ups for the rest of the fields, too
+	@Test
+	void testViewUpdateSendsNotify() {
+		final MockBiConsumer mock = new MockBiConsumer();
+		mock.expect(2);
+		final ObservableListener<Void, Ingredient> listener =
+				new ObservableListener<>(null, mock.call());
+		listener.setObserved(stuff);
+		assertFalse(mock.satisfied());
+		panel.getTxtName().setText("Bacon");
+		waitForSwing();
+		assertTrue(mock.satisfied());
+	}
+
+}

+ 40 - 0
src/test/java/org/leumasjaffe/recipe/view/SwingTestCase.java

@@ -0,0 +1,40 @@
+package org.leumasjaffe.recipe.view;
+
+import java.lang.reflect.InvocationTargetException;
+
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+
+import org.junit.jupiter.api.AfterEach;
+
+public class SwingTestCase {
+    private JFrame testFrame;
+
+    @AfterEach
+    protected void tearDown() {
+        if (this.testFrame != null) {
+            this.testFrame.dispose();
+            this.testFrame = null;
+        }
+    }
+
+    public JFrame getTestFrame() {
+        if (this.testFrame == null) {
+            this.testFrame = new JFrame("Test");
+        }
+        return this.testFrame;
+    }
+    
+    public void waitForSwing() {
+        if (!SwingUtilities.isEventDispatchThread(  )) {
+            try {
+                SwingUtilities.invokeAndWait(new Runnable(  ) {
+                    public void run(  ) {
+                    }
+                });
+            } catch (InterruptedException e) {
+            } catch (InvocationTargetException e) {
+            }
+        }
+    }
+}