package gov.nasa.arc.mct.defaults.view; import gov.nasa.arc.mct.components.AbstractComponent; import gov.nasa.arc.mct.components.PropertyDescriptor; import gov.nasa.arc.mct.components.PropertyDescriptor.VisualControlDescriptor; import gov.nasa.arc.mct.components.PropertyEditor; import gov.nasa.arc.mct.context.GlobalContext; import gov.nasa.arc.mct.gui.CustomVisualControl; import gov.nasa.arc.mct.platform.spi.Platform; import gov.nasa.arc.mct.platform.spi.PlatformAccess; import gov.nasa.arc.mct.platform.spi.RoleAccess; import gov.nasa.arc.mct.platform.spi.RoleService; import gov.nasa.arc.mct.services.component.ViewInfo; import gov.nasa.arc.mct.services.component.ViewType; import gov.nasa.arc.mct.services.internal.component.User; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JTextArea; import javax.swing.JTextField; import org.mockito.Mockito; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class TestInfoView { // This is just elaborate mocking to avoid NPEs User mockUser = Mockito.mock(User.class); RoleService mockRoleService = Mockito.mock(RoleService.class); AbstractComponent comp = Mockito.mock(AbstractComponent.class); Platform mockPlatform = Mockito.mock(Platform.class); Platform oldPlatform; ViewInfo info = new ViewInfo(InfoView.class, "Info", ViewType.CENTER); @BeforeMethod public void setup() { // Setup minimum expected environment for info view oldPlatform = PlatformAccess.getPlatform(); Mockito.when(comp.getOwner()).thenReturn("*"); Mockito.when(comp.getComponentTypeID()).thenReturn(""); Mockito.when(mockUser.getUserId()).thenReturn(""); Mockito.when(mockRoleService.getAllUsers()).thenReturn(Collections.singleton("")); Mockito.when(mockPlatform.getBootstrapComponents()).thenReturn(Collections.<AbstractComponent>emptyList()); GlobalContext.getGlobalContext().switchUser(mockUser, Mockito.mock(Runnable.class)); new RoleAccess().addRoleService(mockRoleService); new PlatformAccess().setPlatform(mockPlatform); } @AfterMethod public void teardown() { new PlatformAccess().setPlatform(oldPlatform); } @Test public void testUpdateMonitoredGUI() { /* This test ensures that updateMonitoredGUI triggers revalidate/repaint */ final AtomicInteger repaintCalls = new AtomicInteger(0); final AtomicInteger revalidateCalls = new AtomicInteger(0); // We can't mock InfoView because we want to test its code, but we // do want to verify that methods of its Swing superclass are invoked. InfoView infoView = new InfoView(comp, info) { private static final long serialVersionUID = 1L; public void repaint() { super.repaint(); repaintCalls.incrementAndGet(); } public void revalidate() { super.revalidate(); revalidateCalls.incrementAndGet(); } }; // The real part of the test: Check that repaint and revalidate really do get called. repaintCalls.set(0); revalidateCalls.set(0); infoView.updateMonitoredGUI(); Assert.assertEquals(repaintCalls.get(), 1); Assert.assertEquals(revalidateCalls.get(), 1); } @Test (dataProvider = "getExtendedFieldTestCases") public void testExtendedFieldRefresh(VisualControlDescriptor a, VisualControlDescriptor b) { // Verify that making a change to the second extended property // triggers an update in the first extended property. List<PropertyDescriptor> mockDescriptors = Arrays.asList(buildMockDescriptor(a), buildMockDescriptor(b)); Mockito.when(comp.getFieldDescriptors()).thenReturn(mockDescriptors); // Mocking Swing components tends to fail on different platforms, so stub CustomVisualControl mockControl = new CustomVisualControl() { private static final long serialVersionUID = -7026155085259171397L; private Object object = null; @Override public void setValue(Object value) { this.object = value; } @Override public Object getValue() { return object; } @Override public void setMutable(boolean mutable) {} }; Mockito.when(comp.getAsset(CustomVisualControl.class)).thenReturn(mockControl); PropertyDescriptor lastPropertyDescriptor = mockDescriptors.get(mockDescriptors.size() - 1); // Build a new info view InfoView infoView = new InfoView(comp, info); // First, verify that the appropriate get method has been called // (this implies that info view was populated with extended descriptors upon instantiation) for (PropertyDescriptor mockDescriptor : mockDescriptors) { switch (mockDescriptor.getVisualControlDescriptor()) { case CheckBox: case ComboBox: Mockito.verify(mockDescriptor.getPropertyEditor(), Mockito.times(1)).getValue(); break; case Label: case TextField: Mockito.verify(mockDescriptor.getPropertyEditor(), Mockito.times(1)).getAsText(); break; } } ActionEvent mockEvent = Mockito.mock(ActionEvent.class); // Act as if user changed a property switch (lastPropertyDescriptor.getVisualControlDescriptor()) { case CheckBox: { JCheckBox checkBox = findLastComponentOfType(infoView, JCheckBox.class); checkBox.doClick(); break; } case ComboBox: { @SuppressWarnings("rawtypes") JComboBox comboBox = findLastComponentOfType(infoView, JComboBox.class); comboBox.setSelectedItem("other"); break; } case Label: { // Non-editable break; } case TextField: { JTextField textField = findLastComponentOfType(infoView, JTextField.class); textField.setText("changed"); for (ActionListener listener : textField.getActionListeners()) { // Don't invoke listeners related to platform-specific Swing implementation // (may lead to inconsistent behavior when running tests on other platforms) if (listener.getClass().getName().startsWith("gov.nasa.arc.mct")) { listener.actionPerformed(mockEvent); } } break; } case TextArea: { JTextArea textArea = findLastComponentOfType(infoView, JTextArea.class); textArea.setText("changed"); for (FocusListener listener : textArea.getFocusListeners()) { // Don't invoke listeners related to platform-specific Swing implementation // (may lead to inconsistent behavior when running tests on other platforms) if (listener.getClass().getName().startsWith("gov.nasa.arc.mct")) { listener.focusLost(Mockito.mock(FocusEvent.class)); } } break; } case Custom: { CustomVisualControl control = findLastComponentOfType(infoView, CustomVisualControl.class); control.setValue("changed"); try { Method m = CustomVisualControl.class.getDeclaredMethod("fireChange"); m.setAccessible(true); m.invoke(control); } catch (Exception e) { Assert.fail("Could not fire property change", e); } break; } } // Verify that some get method has been called on the OTHER mock // (this implies that changes to one field cause others to be refreshed) // Since labels cannot be edited, we don't expect this to occur // if the property in the last position was a label. In that case, // it is expected that get methods have not been called again. int expectedTimes = (lastPropertyDescriptor.getVisualControlDescriptor() == VisualControlDescriptor.Label) ? 1 : 2; PropertyDescriptor mockDescriptor = mockDescriptors.get(0); switch (mockDescriptor.getVisualControlDescriptor()) { case CheckBox: Mockito.verify(mockDescriptor.getPropertyEditor(), Mockito.times(expectedTimes)).getValue(); break; case ComboBox: Mockito.verify(mockDescriptor.getPropertyEditor(), Mockito.times(expectedTimes)).getValue(); break; case Label: Mockito.verify(mockDescriptor.getPropertyEditor(), Mockito.times(expectedTimes)).getAsText(); break; case TextField: Mockito.verify(mockDescriptor.getPropertyEditor(), Mockito.times(expectedTimes)).getAsText(); break; case TextArea: Mockito.verify(mockDescriptor.getPropertyEditor(), Mockito.times(expectedTimes)).getAsText(); break; } } /* * Find the last, deepest component of a given type in the Swing hierarchy. * Used to identify a property field (which are placed after the default fields * in info view.) * If InfoView layout changes, this will need to change too. */ private <T extends JComponent> T findLastComponentOfType(JComponent container, Class<T> componentClass) { T result = componentClass.isAssignableFrom(container.getClass()) ? componentClass.cast(container) : null; for (Component c : container.getComponents()) { if (c instanceof JComponent) { T candidate = findLastComponentOfType((JComponent) c, componentClass); result = candidate != null ? candidate : result; } } return result; } @DataProvider public Object[][] getExtendedFieldTestCases() { // Test all combinations of two VCDs Object[][] testCases = new Object[VisualControlDescriptor.values().length * VisualControlDescriptor.values().length][2]; int i = 0; for (VisualControlDescriptor a : VisualControlDescriptor.values()) { for (VisualControlDescriptor b : VisualControlDescriptor.values()) { testCases[i ][0] = a; testCases[i++][1] = b; } } return testCases; } @SuppressWarnings({ "rawtypes", "unchecked" }) private PropertyDescriptor buildMockDescriptor(VisualControlDescriptor vcd) { PropertyDescriptor mockDescriptor = Mockito.mock(PropertyDescriptor.class); PropertyEditor mockEditor = Mockito.mock(PropertyEditor.class); Mockito.when(mockDescriptor.getShortDescription()).thenReturn("mock"); Mockito.when(mockDescriptor.getVisualControlDescriptor()).thenReturn(vcd); Mockito.when(mockDescriptor.getPropertyEditor()).thenReturn(mockEditor); Mockito.when(mockDescriptor.isFieldMutable()).thenReturn(true); // Change the result of get() method, to trigger refresh Mockito.when(mockEditor.getTags()).thenReturn(Arrays.asList("mock", "other")); Mockito.when(mockEditor.getAsText()).thenReturn("mock"); Mockito.when(mockEditor.getValue()).thenReturn(vcd != VisualControlDescriptor.CheckBox ? "mock" : Boolean.TRUE); return mockDescriptor; } }