浏览代码

BetterAutoGrowPanel becomes AutoGrowPanel

Sam Jaffe 5 年之前
父节点
当前提交
fe9c99013c

+ 117 - 79
src/main/lombok/org/leumasjaffe/recipe/view/AutoGrowPanel.java

@@ -5,8 +5,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.function.IntConsumer;
-import java.util.function.IntFunction;
 import java.util.function.Supplier;
 
 import javax.swing.JPanel;
@@ -14,105 +12,145 @@ import javax.swing.event.DocumentEvent;
 import javax.swing.event.DocumentListener;
 
 import org.jdesktop.swingx.VerticalLayout;
-import org.leumasjaffe.event.AnyActionDocumentListener;
 
+import lombok.AccessLevel;
 import lombok.AllArgsConstructor;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.Delegate;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
 
-@SuppressWarnings("serial")
-public class AutoGrowPanel extends JPanel {
-	public static interface DocumentListenable {
-		void addDocumentListener(DocumentListener dl);
-		void removeDocumentListener(DocumentListener dl);
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class AutoGrowPanel<C extends Component & AutoGrowPanel.ChildComponent, T> extends JPanel {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 3815045801030954255L;
+
+	private static interface SetGap { void setGap(int gap); }
+
+	public static interface ChildComponent {
+		void addGrowShrinkListener(DocumentListener dl);
+		void removeGrowShrinkListener(DocumentListener dl);
 		default void setListPosition(int zeroIndex) {}
 	}
 	
-	@AllArgsConstructor
-	private class DeleteOnEmpty implements AnyActionDocumentListener {
-		DocumentListenable content;
-		@Override public void update(DocumentEvent e) {
-			if (e.getDocument().getLength() == 0) {
-				content.removeDocumentListener(this);
-				int index = members.indexOf(content);
-				members.remove(index);
-				onDelete.accept(index);
-				for (final int size = members.size(); index < size; ++index) {
-					members.get(index).setListPosition(index);
-				}
-				remove((Component) content);
-				validateNthParent(4);
+	@RequiredArgsConstructor
+	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+	private class GrowOnData implements DocumentListener {
+		Supplier<T> makeEmptyModel;
+		Function<T, C> makeComponent;
+		@NonFinal T model = null;
+		
+		@Override public void changedUpdate(DocumentEvent e) {}
+		@Override public void removeUpdate(DocumentEvent e) {}
+
+		@Override
+		public void insertUpdate(DocumentEvent e) {
+			if (model != null) {
+				models.add(model);
+				last().removeGrowShrinkListener(this);
+				last().addGrowShrinkListener(new ShrinkOnEmpty(last()));
 			}
+			
+			model = makeEmptyModel.get();
+			final C comp = makeComponent.apply(model);
+
+			comp.addGrowShrinkListener(this);
+			members.add(comp);
+			add(comp);
+			callback.accept(true);
 		}		
 	}
 	
 	@AllArgsConstructor
-	private class ExtendAction<T, C extends Component & DocumentListenable> implements AnyActionDocumentListener {
-		final Function<T, C> factory;
-		final Consumer<? super T> previous;
-		final Supplier<? extends T> next;
-		T current = null;
+	private class ShrinkOnEmpty implements DocumentListener {
+		ChildComponent component;
+		
+		@Override public void insertUpdate(DocumentEvent e) {}
+		@Override public void changedUpdate(DocumentEvent e) {}
 
 		@Override
-		public void update(DocumentEvent e) {
-			previous.accept(current);
-			
-			final C object = factory.apply(current = next.get());
-			final DocumentListenable back = getBack();
-			
-			back.removeDocumentListener(this);
-			back.addDocumentListener(new DeleteOnEmpty(back));			
-			object.addDocumentListener(this);
+		public void removeUpdate(DocumentEvent e) {
+			if (e.getDocument().getLength() > 0) {
+				return;
+			}
 			
-			members.add(object);
-			add(object);
-
-			validateNthParent(4);
+			component.removeGrowShrinkListener(this);
+			remove(members.indexOf(component));
+			callback.accept(false);
 		}
-		
 	}
 	
-	IntFunction<DocumentListenable> prod;
-	AnyActionDocumentListener grow;
-	IntConsumer onDelete;
-	List<DocumentListenable> members = new ArrayList<>();
+	@Delegate(types={SetGap.class})
+	VerticalLayout layout = new VerticalLayout();
+	List<ChildComponent> members = new ArrayList<>();
+	GrowOnData grow;
 	
-	@SafeVarargs
-	public <T, C extends Component & DocumentListenable> AutoGrowPanel(Function<T, C> function,
-			Supplier<T> newItem, Consumer<? super T> onData, IntConsumer onDelete, int gap, T...ts) {
-		setLayout(new VerticalLayout(gap));
-		
-		T next = newItem.get();
-		this.onDelete = onDelete;
-		this.grow = new ExtendAction<T, C>(function, onData, newItem, next);
-		
-		for (T value : ts) {
-			C listen = function.apply(value);
-			members.add(listen);
-			add(listen);
-			listen.addDocumentListener(new DeleteOnEmpty(listen));
-		}
-		
-		C empty = function.apply(next);
-		members.add(empty);
-		add(empty);
-		empty.addDocumentListener(this.grow);
-		
-		for (int i = 0; i < members.size(); ++i) {
-			members.get(i).setListPosition(i);
-		}
+	@NonFinal List<T> models = null;
+	@NonFinal Consumer<Boolean> callback = (b) -> {};
+	
+	/**
+	 * 
+	 * @param makeEmptyModel A function to produce a blank model object for display.
+	 * If the model is updated in such a way as to be non-empty, it will be inserted
+	 * into the list of models.
+	 * @param makeComponent A function to generate a UI object given a model. The
+	 * object must meet the ChildComponent interface to install the AutoGrowPanel's
+	 * growing/shrinking listener objects.
+	 */
+	public AutoGrowPanel(final @NonNull Supplier<T> makeEmptyModel,
+			final @NonNull Function<T, C> makeComponent) {
+		setLayout(layout);
+		this.grow = new GrowOnData(makeEmptyModel, makeComponent);
+		this.grow.insertUpdate(null);
 	}
-
-	private DocumentListenable getBack() {
-		return members.get(members.size() - 1);
+	
+	/**
+	 * Activate this component against the target list of children
+	 * @param models A mutable list object containing the "child models" to be
+	 * rendered in this component
+	 */
+	public void setModel(final @NonNull List<T> models) {
+		setModel(models, (b) -> {});
 	}
 	
-	private void validateNthParent(int i) {
-		Component current = getParent();
-		Component next = current;
-		while (i --> 0 && next != null) {
-			current = next;
-			next = current.getParent();
+	/**
+	 * 
+	 * @param models A mutable list object containing the "child models" to be
+	 * rendered in this component
+	 * @param callback A callback that will be invoked each time a child is
+	 * added or removed. This allows us to provide some custom interactions
+	 * with the parent's context in case other actions need to occur.
+	 */
+	public void setModel(final @NonNull List<T> models,
+			final Consumer<Boolean> callback) {
+		this.models = models;
+		this.callback = callback;
+		
+		this.members.subList(0, lastIndex()).clear();
+		models.forEach(model -> {
+			final C comp = this.grow.makeComponent.apply(model);
+			comp.addGrowShrinkListener(new ShrinkOnEmpty(comp));
+			add(comp, lastIndex());
+			this.members.add(lastIndex(), comp);
+		});
+	}
+	
+	@Override
+	public void remove(final int index) {
+		super.remove(index);
+		members.remove(index);
+		for (int size = lastIndex(); size >= index; --size) {
+			members.get(index).setListPosition(index);
 		}
-		current.validate();
+		models.remove(index);
 	}
 	
+	private int lastIndex() { return members.size() - 1; }
+	
+	private ChildComponent last() {
+		return members.get(lastIndex());
+	}
 }

+ 0 - 151
src/main/lombok/org/leumasjaffe/recipe/view/BetterAutoGrowPanel.java

@@ -1,151 +0,0 @@
-package org.leumasjaffe.recipe.view;
-
-import java.awt.Component;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Supplier;
-
-import javax.swing.JPanel;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-
-import org.jdesktop.swingx.VerticalLayout;
-
-import lombok.AccessLevel;
-import lombok.AllArgsConstructor;
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-import lombok.experimental.Delegate;
-import lombok.experimental.FieldDefaults;
-import lombok.experimental.NonFinal;
-
-@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-public class BetterAutoGrowPanel<C extends Component & BetterAutoGrowPanel.ChildComponent, T> extends JPanel {
-	private static interface SetGap { void setGap(int gap); }
-
-	public static interface ChildComponent {
-		void addGrowShrinkListener(DocumentListener dl);
-		void removeGrowShrinkListener(DocumentListener dl);
-		default void setListPosition(int zeroIndex) {}
-	}
-	
-	@RequiredArgsConstructor
-	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-	private class GrowOnData implements DocumentListener {
-		Supplier<T> makeEmptyModel;
-		Function<T, C> makeComponent;
-		@NonFinal T model = null;
-		
-		@Override public void changedUpdate(DocumentEvent e) {}
-		@Override public void removeUpdate(DocumentEvent e) {}
-
-		@Override
-		public void insertUpdate(DocumentEvent e) {
-			if (model != null) {
-				models.add(model);
-				last().removeGrowShrinkListener(this);
-				last().addGrowShrinkListener(new ShrinkOnEmpty(last()));
-			}
-			
-			model = makeEmptyModel.get();
-			final C comp = makeComponent.apply(model);
-
-			comp.addGrowShrinkListener(this);
-			members.add(comp);
-			add(comp);
-			callback.accept(true);
-		}		
-	}
-	
-	@AllArgsConstructor
-	private class ShrinkOnEmpty implements DocumentListener {
-		ChildComponent component;
-		
-		@Override public void insertUpdate(DocumentEvent e) {}
-		@Override public void changedUpdate(DocumentEvent e) {}
-
-		@Override
-		public void removeUpdate(DocumentEvent e) {
-			if (e.getDocument().getLength() > 0) {
-				return;
-			}
-			
-			component.removeGrowShrinkListener(this);
-			remove(members.indexOf(component));
-			callback.accept(false);
-		}
-	}
-	
-	@Delegate(types={SetGap.class})
-	VerticalLayout layout = new VerticalLayout();
-	List<ChildComponent> members = new ArrayList<>();
-	GrowOnData grow;
-	
-	@NonFinal List<T> models = null;
-	@NonFinal Consumer<Boolean> callback = (b) -> {};
-	
-	/**
-	 * 
-	 * @param makeEmptyModel A function to produce a blank model object for display.
-	 * If the model is updated in such a way as to be non-empty, it will be inserted
-	 * into the list of models.
-	 * @param makeComponent A function to generate a UI object given a model. The
-	 * object must meet the ChildComponent interface to install the AutoGrowPanel's
-	 * growing/shrinking listener objects.
-	 */
-	public BetterAutoGrowPanel(final @NonNull Supplier<T> makeEmptyModel,
-			final @NonNull Function<T, C> makeComponent) {
-		setLayout(layout);
-		this.grow = new GrowOnData(makeEmptyModel, makeComponent);
-		this.grow.insertUpdate(null);
-	}
-	
-	/**
-	 * Activate this component against the target list of children
-	 * @param models A mutable list object containing the "child models" to be
-	 * rendered in this component
-	 */
-	public void setModel(final @NonNull List<T> models) {
-		setModel(models, (b) -> {});
-	}
-	
-	/**
-	 * 
-	 * @param models A mutable list object containing the "child models" to be
-	 * rendered in this component
-	 * @param callback A callback that will be invoked each time a child is
-	 * added or removed. This allows us to provide some custom interactions
-	 * with the parent's context in case other actions need to occur.
-	 */
-	public void setModel(final @NonNull List<T> models,
-			final Consumer<Boolean> callback) {
-		this.models = models;
-		this.callback = callback;
-		
-		this.members.subList(0, lastIndex()).clear();
-		models.forEach(model -> {
-			final C comp = this.grow.makeComponent.apply(model);
-			comp.addGrowShrinkListener(new ShrinkOnEmpty(comp));
-			add(comp, lastIndex());
-			this.members.add(lastIndex(), comp);
-		});
-	}
-	
-	@Override
-	public void remove(final int index) {
-		super.remove(index);
-		members.remove(index);
-		for (int size = lastIndex(); size >= index; --size) {
-			members.get(index).setListPosition(index);
-		}
-		models.remove(index);
-	}
-	
-	private int lastIndex() { return members.size() - 1; }
-	
-	private ChildComponent last() {
-		return members.get(lastIndex());
-	}
-}

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

@@ -23,7 +23,7 @@ import javax.swing.JLabel;
 
 @SuppressWarnings("serial")
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-public class IngredientPanel extends JPanel implements BetterAutoGrowPanel.ChildComponent {
+public class IngredientPanel extends JPanel implements AutoGrowPanel.ChildComponent {
 	ObservableListener<JTextField, Ingredient> nameController;
 	ObservableListener<JFormattedTextField, Ingredient> amountController;
 	ObservableListener<JTextField, Ingredient> preparationController;

+ 2 - 2
src/main/lombok/org/leumasjaffe/recipe/view/PhasePanel.java

@@ -22,7 +22,7 @@ import org.jdesktop.swingx.VerticalLayout;
 public class PhasePanel extends JPanel {
 	ForwardingObservableListener<Phase> listener = new ForwardingObservableListener<>();
 
-	@Getter(AccessLevel.PACKAGE) BetterAutoGrowPanel<StepPanel, Step> panelSteps;
+	@Getter(AccessLevel.PACKAGE) AutoGrowPanel<StepPanel, Step> panelSteps;
 	
 	public PhasePanel(final Phase phase) {		
 		setLayout(new VerticalLayout(5));
@@ -31,7 +31,7 @@ public class PhasePanel extends JPanel {
 			add(new PreparationPanel(phase));
 		}
 		
-		panelSteps = new BetterAutoGrowPanel<>(Step::new, StepPanel::new);
+		panelSteps = new AutoGrowPanel<>(Step::new, StepPanel::new);
 		panelSteps.setGap(5);
 		add(panelSteps);
 		

+ 3 - 3
src/main/lombok/org/leumasjaffe/recipe/view/StepPanel.java

@@ -30,14 +30,14 @@ import java.awt.Dimension;
 
 @SuppressWarnings("serial")
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-public class StepPanel extends JPanel implements BetterAutoGrowPanel.ChildComponent {
+public class StepPanel extends JPanel implements AutoGrowPanel.ChildComponent {
 	ForwardingObservableListener<Step> listener = new ForwardingObservableListener<>();
 	ObservableListener<JTextPane, Step> intructionListener;
 	ObservableListener<JFormattedTextField, Step> durationListener;
 
 	@Getter(AccessLevel.PACKAGE) JLabel lblIndex;
 	@Getter(AccessLevel.PACKAGE) JTextPane txtpnInstructions;
-	@Getter(AccessLevel.PACKAGE) BetterAutoGrowPanel<IngredientPanel, Ingredient> panelIngredients;
+	@Getter(AccessLevel.PACKAGE) AutoGrowPanel<IngredientPanel, Ingredient> panelIngredients;
 		
 	public StepPanel(final Step step) {
 		GridBagLayout gridBagLayout = new GridBagLayout();
@@ -83,7 +83,7 @@ public class StepPanel extends JPanel implements BetterAutoGrowPanel.ChildCompon
 		panelLeft.add(panelDuration, gbc_panelDuration);
 		
 		final List<Ingredient> ingredients = step.getIngredients();
-		panelIngredients = new BetterAutoGrowPanel<>(Ingredient::new, IngredientPanel::new);
+		panelIngredients = new AutoGrowPanel<>(Ingredient::new, IngredientPanel::new);
 		GridBagConstraints gbc_panelIngredients = new GridBagConstraints();
 		gbc_panelIngredients.gridwidth = 3;
 		gbc_panelIngredients.insets = new Insets(0, 0, 0, 5);

+ 60 - 27
src/test/java/org/leumasjaffe/recipe/view/AutoGrowPanelTest.java

@@ -5,9 +5,9 @@ import static org.hamcrest.collection.IsArrayWithSize.arrayWithSize;
 import static org.mockito.Mockito.*;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
-import java.util.function.IntConsumer;
 
 import javax.swing.JTextField;
 import javax.swing.event.DocumentListener;
@@ -17,15 +17,16 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.platform.runner.JUnitPlatform;
 import org.junit.runner.RunWith;
-import org.leumasjaffe.recipe.view.AutoGrowPanel.DocumentListenable;
+import org.leumasjaffe.recipe.view.AutoGrowPanel.ChildComponent;
 import org.mockito.Mock;
+import org.mockito.Spy;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 @ExtendWith(MockitoExtension.class)
 @RunWith(JUnitPlatform.class)
 class AutoGrowPanelTest extends SwingTestCase {
 	@SuppressWarnings("serial")
-	private static class MockComponent extends JTextField implements DocumentListenable {
+	private static class MockComponent extends JTextField implements ChildComponent {
 		public MockComponent() {
 		}
 
@@ -34,92 +35,124 @@ class AutoGrowPanelTest extends SwingTestCase {
 		}
 
 		@Override
-		public void addDocumentListener(DocumentListener dl) {
+		public void addGrowShrinkListener(DocumentListener dl) {
 			super.getDocument().addDocumentListener(dl);
 		}
 
 		@Override
-		public void removeDocumentListener(DocumentListener dl) {
+		public void removeGrowShrinkListener(DocumentListener dl) {
 			super.getDocument().removeDocumentListener(dl);
 		}
 	}
 	
-	@Mock Consumer<MockComponent> add;
-	@Mock IntConsumer remove;
-	List<MockComponent> components = new ArrayList<>();
+	@Mock Consumer<Boolean> callback;
+	List<MockComponent> internal = new ArrayList<>();
+	@Spy List<MockComponent> shared = new ArrayList<>();
 	
 	private MockComponent mocked() {
 		final MockComponent mock = spy(new MockComponent());
-		components.add(mock);
+		internal.add(mock);
 		return mock;
 	}
 	
-	private AutoGrowPanel create(MockComponent... mocks) {
-		return new AutoGrowPanel(m -> m, this::mocked, add, remove, 0, mocks);
+	private AutoGrowPanel<MockComponent, MockComponent> create(MockComponent... mocks) {
+		shared.addAll(Arrays.asList(mocks));
+		final AutoGrowPanel<MockComponent, MockComponent> rval =
+				new AutoGrowPanel<>(this::mocked, m -> m);
+		
+		rval.setModel(shared);
+
+		return rval;
 	}
 	
 	@Test
 	void testAlwaysHasAtLeastOneComponent() {		
-		AutoGrowPanel panel = create();
+		AutoGrowPanel<MockComponent, MockComponent> panel = create();
 		
 		assertThat(panel.getComponents(), arrayWithSize(1));
-		verify(add, never()).accept(any());
+		verify(shared, never()).add(any());
 	}
 
 	@Test
 	void testCreatesGivenNumberOfChildrenPlusOne() {
-		AutoGrowPanel panel = create(mocked(), mocked());
+		AutoGrowPanel<MockComponent, MockComponent> panel = create(mocked(), mocked());
 
 		assertThat(panel.getComponents(), arrayWithSize(3));
-		verify(add, never()).accept(any());
+		verify(shared, never()).add(any());
 	}
 
 	@Test
 	void testEnteringContentTriggersNewRow() {
-		AutoGrowPanel panel = create();
+		AutoGrowPanel<MockComponent, MockComponent> panel = create();
 		getTestFrame().add(panel);
 
-		components.get(0).setText("A");
+		internal.get(0).setText("A");
 		assertThat(panel.getComponents(), arrayWithSize(2));
 		
-		components.get(1).setText("B");
+		internal.get(1).setText("B");
 		assertThat(panel.getComponents(), arrayWithSize(3));
-		verify(add, times(2)).accept(any());
+		verify(shared, times(2)).add(any());
 	}
 
 	@Test
 	void testEnteringEmptyContentDoesNotTrigger() {
-		AutoGrowPanel panel = create();
+		AutoGrowPanel<MockComponent, MockComponent> panel = create();
 		getTestFrame().add(panel);
-		components.get(0).setText("");
+		internal.get(0).setText("");
 		
 		assertThat(panel.getComponents(), arrayWithSize(1));
-		verify(add, never()).accept(any());
-		verify(remove, never()).accept(anyInt());
+		verify(shared, never()).add(any());
+		verify(shared, never()).remove(anyInt());
 	}
 
 	@Test
 	void testEmptyingContentClearsPanel() {
 		final MockComponent mock = spy(new MockComponent("A"));
 		
-		AutoGrowPanel panel = create(mock);
+		AutoGrowPanel<MockComponent, MockComponent> panel = create(mock);
 		getTestFrame().add(panel);
 		mock.setText("");
 		
 		assertThat(panel.getComponents(), arrayWithSize(1));
-		verify(remove, times(1)).accept(0);
+		verify(shared, times(1)).remove(0);
 	}
 
+	@Test
+	void testRemovingSomeContentDoesntClear() throws BadLocationException {
+		final MockComponent mock = spy(new MockComponent("AB"));
+		
+		AutoGrowPanel<MockComponent, MockComponent> panel = create(mock);
+		getTestFrame().add(panel);
+		mock.getDocument().remove(0, 1);
+		
+		assertThat(panel.getComponents(), arrayWithSize(2));
+		verify(shared, never()).remove(anyInt());
+	}
 
 	@Test
 	void testChangingTextDoesNotDeleteRow() throws BadLocationException {
 		final MockComponent mock = spy(new MockComponent("A"));
 		
-		AutoGrowPanel panel = create(mock);
+		AutoGrowPanel<MockComponent, MockComponent> panel = create(mock);
 		getTestFrame().add(panel);
 		mock.getDocument().insertString(0, "B", null);
 		
 		assertThat(panel.getComponents(), arrayWithSize(2));
-		verify(remove, never()).accept(anyInt());
+		verify(shared, never()).remove(anyInt());
 	}
+
+	@Test
+	void testCanInstallNotificationCallbackForAddsAndDeletes() {
+		AutoGrowPanel<MockComponent, MockComponent> panel = create();
+		panel.setModel(shared, callback);
+		getTestFrame().add(panel);
+		verify(callback, never()).accept(anyBoolean());
+
+		internal.get(0).setText("A");
+		verify(callback).accept(true);
+
+		internal.get(0).setText("");
+		verify(callback).accept(false);
+	}
+	
 }

+ 0 - 158
src/test/java/org/leumasjaffe/recipe/view/BetterAutoGrowPanelTest.java

@@ -1,158 +0,0 @@
-package org.leumasjaffe.recipe.view;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.collection.IsArrayWithSize.arrayWithSize;
-import static org.mockito.Mockito.*;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Consumer;
-
-import javax.swing.JTextField;
-import javax.swing.event.DocumentListener;
-import javax.swing.text.BadLocationException;
-
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.junit.platform.runner.JUnitPlatform;
-import org.junit.runner.RunWith;
-import org.leumasjaffe.recipe.view.BetterAutoGrowPanel.ChildComponent;
-import org.mockito.Mock;
-import org.mockito.Spy;
-import org.mockito.junit.jupiter.MockitoExtension;
-
-@ExtendWith(MockitoExtension.class)
-@RunWith(JUnitPlatform.class)
-class BetterAutoGrowPanelTest extends SwingTestCase {
-	@SuppressWarnings("serial")
-	private static class MockComponent extends JTextField implements ChildComponent {
-		public MockComponent() {
-		}
-
-		public MockComponent(String s) {
-			super(s);
-		}
-
-		@Override
-		public void addGrowShrinkListener(DocumentListener dl) {
-			super.getDocument().addDocumentListener(dl);
-		}
-
-		@Override
-		public void removeGrowShrinkListener(DocumentListener dl) {
-			super.getDocument().removeDocumentListener(dl);
-		}
-	}
-	
-	@Mock Consumer<Boolean> callback;
-	List<MockComponent> internal = new ArrayList<>();
-	@Spy List<MockComponent> shared = new ArrayList<>();
-	
-	private MockComponent mocked() {
-		final MockComponent mock = spy(new MockComponent());
-		internal.add(mock);
-		return mock;
-	}
-	
-	private BetterAutoGrowPanel<MockComponent, MockComponent> create(MockComponent... mocks) {
-		shared.addAll(Arrays.asList(mocks));
-		final BetterAutoGrowPanel<MockComponent, MockComponent> rval =
-				new BetterAutoGrowPanel<>(this::mocked, m -> m);
-		
-		rval.setModel(shared);
-
-		return rval;
-	}
-	
-	@Test
-	void testAlwaysHasAtLeastOneComponent() {		
-		BetterAutoGrowPanel<MockComponent, MockComponent> panel = create();
-		
-		assertThat(panel.getComponents(), arrayWithSize(1));
-		verify(shared, never()).add(any());
-	}
-
-	@Test
-	void testCreatesGivenNumberOfChildrenPlusOne() {
-		BetterAutoGrowPanel<MockComponent, MockComponent> panel = create(mocked(), mocked());
-
-		assertThat(panel.getComponents(), arrayWithSize(3));
-		verify(shared, never()).add(any());
-	}
-
-	@Test
-	void testEnteringContentTriggersNewRow() {
-		BetterAutoGrowPanel<MockComponent, MockComponent> panel = create();
-		getTestFrame().add(panel);
-
-		internal.get(0).setText("A");
-		assertThat(panel.getComponents(), arrayWithSize(2));
-		
-		internal.get(1).setText("B");
-		assertThat(panel.getComponents(), arrayWithSize(3));
-		verify(shared, times(2)).add(any());
-	}
-
-	@Test
-	void testEnteringEmptyContentDoesNotTrigger() {
-		BetterAutoGrowPanel<MockComponent, MockComponent> panel = create();
-		getTestFrame().add(panel);
-		internal.get(0).setText("");
-		
-		assertThat(panel.getComponents(), arrayWithSize(1));
-		verify(shared, never()).add(any());
-		verify(shared, never()).remove(anyInt());
-	}
-
-	@Test
-	void testEmptyingContentClearsPanel() {
-		final MockComponent mock = spy(new MockComponent("A"));
-		
-		BetterAutoGrowPanel<MockComponent, MockComponent> panel = create(mock);
-		getTestFrame().add(panel);
-		mock.setText("");
-		
-		assertThat(panel.getComponents(), arrayWithSize(1));
-		verify(shared, times(1)).remove(0);
-	}
-
-	@Test
-	void testRemovingSomeContentDoesntClear() throws BadLocationException {
-		final MockComponent mock = spy(new MockComponent("AB"));
-		
-		BetterAutoGrowPanel<MockComponent, MockComponent> panel = create(mock);
-		getTestFrame().add(panel);
-		mock.getDocument().remove(0, 1);
-		
-		assertThat(panel.getComponents(), arrayWithSize(2));
-		verify(shared, never()).remove(anyInt());
-	}
-
-	@Test
-	void testChangingTextDoesNotDeleteRow() throws BadLocationException {
-		final MockComponent mock = spy(new MockComponent("A"));
-		
-		BetterAutoGrowPanel<MockComponent, MockComponent> panel = create(mock);
-		getTestFrame().add(panel);
-		mock.getDocument().insertString(0, "B", null);
-		
-		assertThat(panel.getComponents(), arrayWithSize(2));
-		verify(shared, never()).remove(anyInt());
-	}
-
-	@Test
-	void testCanInstallNotificationCallbackForAddsAndDeletes() {
-		BetterAutoGrowPanel<MockComponent, MockComponent> panel = create();
-		panel.setModel(shared, callback);
-		getTestFrame().add(panel);
-		verify(callback, never()).accept(anyBoolean());
-
-		internal.get(0).setText("A");
-		verify(callback).accept(true);
-
-		internal.get(0).setText("");
-		verify(callback).accept(false);
-	}
-	
-}