001: package abbot.editor.recorder;
002:
003: import java.awt.*;
004: import java.awt.event.*;
005:
006: import javax.swing.*;
007: import javax.swing.table.JTableHeader;
008: import junit.extensions.abbot.*;
009: import junit.extensions.abbot.Timer;
010: import abbot.Log;
011: import abbot.Platform;
012: import abbot.util.AWT;
013: import abbot.util.WeakAWTEventListener;
014: import abbot.script.*;
015: import abbot.tester.*;
016:
017: /**
018: * Unit test to verify proper capture of basic user semantic events. A
019: * ComponentTester is used to generate the appropriate events, which is
020: * significantly easier than reproducing the steps manually. These tests
021: * should be run after the ComponentTester tests.
022: */
023: public class ComponentRecorderTest extends
024: AbstractSemanticRecorderFixture {
025:
026: private ComponentTester tester;
027:
028: protected void setUp() {
029: tester = new ComponentTester();
030: }
031:
032: protected void tearDown() {
033: tester = null;
034: }
035:
036: protected SemanticRecorder createSemanticRecorder(Resolver r) {
037: return new ComponentRecorder(r);
038: }
039:
040: // Open events to capture (those known to generate unique sequences of
041: // events):
042: // First window (open)
043: // Subsequent window (open')
044: // Window (close)
045:
046: public void testNamedWindowShow() {
047: Frame frame = showFrame(new JLabel(getName()));
048: JWindow win = new JWindow(frame);
049: win.getContentPane().add(new JLabel(getName()));
050: String NAME = "MyWindow";
051: win.setName(NAME);
052: startRecording();
053: showWindow(win);
054: assertStep("Wait for ComponentShowing\\(" + NAME + "\\)");
055: assertNoStep();
056:
057: win.dispose();
058: assertStep("Wait for !ComponentShowing\\(" + NAME + "\\)");
059: }
060:
061: public void testFrameShowDispose() {
062: startRecording();
063: Frame frame = showFrame(new JLabel(getName()));
064: // FIXME 1.4 produces additional RESIZE and SHOWING events prior to
065: // WINDOW_OPEN -- need to catch these
066:
067: // Must catch an initial window open. First open produces
068: // WINDOW_OPENED.
069: assertStep("Wait for ComponentShowing\\(" + getName() + "\\)");
070:
071: // Dispose produces WINDOW_CLOSED, without a COMPONENT_HIDDEN
072: // 1.4.1 adds a HIERARCHY_CHANGED !SHOWING prior to CLOSED
073: frame.dispose();
074: assertStep("Wait for !ComponentShowing\\(" + getName() + "\\)");
075:
076: // Shouldn't record multiple WINDOW_CLOSED events (pre-1.4)
077: frame.dispose();
078: assertTrue("Multiple CLOSE events generated", !hasRecorder());
079: }
080:
081: public void testFrameShowHideShow() {
082: Frame frame = showFrame(new JLabel(getName()));
083: hideWindow(frame);
084: startRecording();
085: showWindow(frame);
086: assertStep("Wait for ComponentShowing\\(" + getName());
087:
088: hideWindow(frame);
089: assertStep("Wait for !ComponentShowing\\(" + getName());
090:
091: showWindow(frame);
092: assertStep("Wait for ComponentShowing\\(" + getName());
093: }
094:
095: public void testDialogShowDispose() {
096: // Showing a dialog produces a WINDOW_OPEN, but no SHOWING
097: Frame frame = showFrame(new JLabel(getName()));
098: startRecording();
099: final JDialog dialog = new JDialog(frame, "Dialog");
100: showWindow(dialog);
101: assertStep("Wait for ComponentShowing");
102:
103: // avoid extraneous INPUT_METHOD_TEXT_CHANGED events on linux
104: startRecording();
105: dialog.dispose();
106: assertStep("Wait for !ComponentShowing");
107:
108: // Shouldn't record multiple WINDOW_CLOSED events (pre-1.4)
109: dialog.dispose();
110: assertTrue("Multiple CLOSE events generated", !hasRecorder());
111: }
112:
113: public void testDialogShowHideShow() {
114: // Showing a dialog produces a WINDOW_OPEN, but no SHOWING
115: Frame frame = showFrame(new JLabel(getName()));
116: startRecording();
117: JDialog dialog = new JDialog(frame, "Dialog");
118: dialog.getContentPane().add(new JLabel(getName() + " Dialog"));
119: showWindow(dialog);
120: assertStep("Wait for ComponentShowing");
121:
122: // avoid extraneous INPUT_METHOD_TEXT_CHANGED events on linux
123: startRecording();
124: hideWindow(dialog);
125: assertStep("Wait for !ComponentShowing");
126:
127: // avoid extraneous INPUT_METHOD_TEXT_CHANGED events on linux
128: startRecording();
129: showWindow(dialog);
130: assertStep("Wait for ComponentShowing");
131: }
132:
133: public void testShowDialogOnSameNamedFrame() {
134: Frame frame = showFrame(new JLabel(getName()));
135: startRecording();
136: JDialog dialog = new JDialog(frame, getName());
137: dialog.getContentPane().add(new JLabel(getName() + " Dialog"));
138: showWindow(dialog);
139: assertStep("Wait for ComponentShowing");
140: }
141:
142: private boolean itemOne = false;
143: private boolean itemTwo = false;
144: private boolean subItemOne = false;
145: private boolean subItemTwo = false;
146:
147: private JPanel initPopup() {
148: final JPopupMenu popup = new JPopupMenu();
149: popup.add(new JMenuItem(new AbstractAction("Item One") {
150: public void actionPerformed(ActionEvent ev) {
151: itemOne = true;
152: }
153: }));
154: popup.add(new JMenuItem(new AbstractAction("Item Two") {
155: public void actionPerformed(ActionEvent ev) {
156: itemTwo = true;
157: }
158: }));
159: JMenu submenu = new JMenu("Submenu");
160: submenu.add(new JMenuItem(new AbstractAction("Subitem One") {
161: public void actionPerformed(ActionEvent ev) {
162: subItemOne = true;
163: }
164: }));
165: submenu.add(new JMenuItem(new AbstractAction("Subitem Two") {
166: public void actionPerformed(ActionEvent ev) {
167: subItemTwo = true;
168: }
169: }));
170: popup.add(submenu);
171: JPanel pane = new JPanel() {
172: public Dimension getPreferredSize() {
173: return new Dimension(100, 100);
174: }
175: };
176: addPopup(pane, popup);
177: return pane;
178: }
179:
180: private void triggerPopup(Component pane) {
181: Point where = new Point(pane.getWidth() / 2,
182: pane.getHeight() / 2);
183: if (Platform.isLinux()) {
184: tester.mousePress(pane, where.x, where.y,
185: AWTConstants.POPUP_MASK);
186: } else {
187: // Use click instead of actionClick to avoid event queue blockage
188: // on w32 when popup is visible
189: tester.click(pane, where.x, where.y,
190: AWTConstants.POPUP_MASK);
191: }
192: // OSX needs this
193: tester.delay(200);
194: where.translate(10, 10);
195: if (Platform.isLinux()) {
196: tester.mousePress(pane, where.x, where.y,
197: InputEvent.BUTTON1_MASK);
198: tester.mouseRelease(InputEvent.BUTTON1_MASK
199: | AWTConstants.POPUP_MASK);
200: tester.waitForIdle();
201: } else {
202: tester.actionClick(pane, where.x, where.y);
203: }
204: // OSX needs this
205: tester.delay(200);
206: }
207:
208: // Care must be taken when activating an AWT PopupMenu, since it holds the
209: // AWTTreeLock while the popup is showing. Since the normal
210: // abbot.tester.Robot.mouseMove(Component,int,int) method calls
211: // Component.getLocationOnScreen, it can't be used to position the pointer
212: // for a click on the popup.
213: public void testCaptureAWTPopupMenuSelection() throws Exception {
214: Panel pane = new Panel();
215: final PopupMenu popup = new PopupMenu();
216: popup.add(new MenuItem("popup action"));
217: popup.add(new MenuItem("one"));
218: popup.add(new MenuItem("two"));
219: popup.add(new CheckboxMenuItem("three"));
220: addPopup(pane, popup);
221: showFrame(pane, new Dimension(400, 400));
222: startRecording();
223:
224: triggerPopup(pane);
225:
226: assertStep("SelectAWTPopupMenuItem\\(.*,popup action\\)");
227: }
228:
229: public void testCapturePopup() {
230: JPanel pane = initPopup();
231: showFrame(pane);
232:
233: startRecording();
234: tester.actionSelectPopupMenuItem(pane, "Item One");
235: assertStep("SelectPopupMenuItem");
236: }
237:
238: public void testCapturePopupSubmenu() {
239: JPanel pane = initPopup();
240: showFrame(pane);
241:
242: startRecording();
243: tester.actionSelectPopupMenuItem(pane, "Subitem Two");
244: assertStep("SelectPopupMenuItem");
245: }
246:
247: public void testCapturePopupSubmenuPath() {
248: JPanel pane = initPopup();
249: showFrame(pane);
250:
251: startRecording();
252: tester.actionSelectPopupMenuItem(pane, "Submenu|Subitem Two");
253: assertStep("SelectPopupMenuItem");
254: }
255:
256: /** If no popup exists, a regular click should be recorded instead, using
257: * the appropriate mask.
258: */
259: public void testCaptureNoPopup() {
260: JLabel label = new JLabel("No Popup Here");
261: showFrame(label);
262: startRecording();
263: tester.actionClick(label, label.getWidth() / 2, label
264: .getHeight() / 2, AWTConstants.POPUP_MASK);
265: assertStep("Click\\(.*,POPUP_MASK\\)");
266: }
267:
268: // See comments for testCaptureAWTPopupMenuSelection
269: public void testCaptureAWTMenuSelection() throws Exception {
270: MenuBar mb = new MenuBar();
271: Menu menu1 = new Menu("File");
272: mb.add(menu1);
273: Menu menu2 = new Menu("Edit");
274: mb.add(menu2);
275: MenuItem mi = new MenuItem("Open");
276: class Flag {
277: volatile boolean flag;
278: }
279: menu1.add(mi);
280: Frame frame = new Frame(getName());
281: Label label = new Label(getName());
282: frame.add(label);
283: frame.setMenuBar(mb);
284: showWindow(frame);
285:
286: startRecording();
287: if (Platform.isOSX()) {
288: // might have useScreenMenuBar enabled, so punt
289: tester.actionSelectAWTMenuItem(frame, mi.getLabel());
290: } else {
291: // Guess the positioning for the menu bar and its popup
292: tester.actionClick(label, 10, -10);
293: tester.actionClick(label, 10, 10);
294: }
295: assertStep("SelectAWTMenuItem\\(.*," + "File|" + mi.getLabel()
296: + "\\)");
297: }
298:
299: public void testCaptureMenuBarMenuSelection() {
300: JMenuBar menubar = new JMenuBar();
301: JMenu menu1 = new JMenu("File");
302: menubar.add(menu1);
303: JMenu menu2 = new JMenu("Edit");
304: menubar.add(menu2);
305: JMenuItem mi = new JMenuItem("Copy");
306: menu2.add(mi);
307: JFrame frame = new JFrame(getName());
308: frame.setJMenuBar(menubar);
309: showWindow(frame);
310:
311: startRecording();
312: tester.actionSelectMenuItem(mi);
313: assertStep("MenuItem");
314: }
315:
316: public void testCaptureClick() {
317: Frame frame = showFrame(new JLabel(getName()));
318: startRecording();
319: tester.actionClick(frame);
320: assertStep("Click");
321: }
322:
323: /** If the step we're recording results in the target component being
324: * hidden (or in general if the component gets hidden before the step is
325: * finished recording), the step should still be recorded properly.
326: */
327: // NOTE: this doesn't test the most general case of an arbitrary component
328: // being hidden.
329: // FIXME consistent linux failure here
330: public void testCaptureWithAHiddenComponent() {
331: JButton close = new JButton("close");
332:
333: JButton push = new JButton("push");
334: Frame frame = showFrame(push);
335:
336: final JDialog dialog = new JDialog(frame, getName() + " Dialog");
337: dialog.getContentPane().add(close);
338: close.addActionListener(new ActionListener() {
339: public void actionPerformed(ActionEvent ev) {
340: dialog.dispose();
341: }
342: });
343: push.addActionListener(new ActionListener() {
344: public void actionPerformed(ActionEvent ev) {
345: dialog.pack();
346: dialog.setVisible(true);
347: }
348: });
349:
350: tester.actionClick(push);
351: startRecording();
352: assertEquals("Dialog prematurely gone", 1, frame
353: .getOwnedWindows().length);
354: assertEquals("Frame should own dialog", dialog, frame
355: .getOwnedWindows()[0]);
356: assertEquals("Wrong dialog parent", frame, dialog.getParent());
357: tester.actionClick(close);
358: assertStep("Click");
359: }
360:
361: private void showTextField() {
362: JTextField tf = new JTextField();
363: tf.setColumns(10);
364: showFrame(tf);
365: // make sure text field has focus
366: tester.actionFocus(tf);
367: }
368:
369: public void testCaptureKeyStroke() {
370: showTextField();
371: startRecording();
372: tester.actionKeyString("a");
373: assertStep("KeyString\\(.*,a\\)");
374: }
375:
376: public void testCaptureShiftedKeyStroke() {
377: showTextField();
378: startRecording();
379: tester.actionKeyString("A");
380: assertStep("KeyString\\(.*,A\\)");
381: }
382:
383: public void testCaptureSpecialKeys() {
384: showTextField();
385: startRecording();
386: int[] keys = { KeyEvent.VK_ENTER, KeyEvent.VK_TAB,
387: KeyEvent.VK_BACK_SPACE, KeyEvent.VK_DELETE,
388: KeyEvent.VK_ESCAPE };
389: for (int i = 0; i < keys.length; i++) {
390: tester.actionKeyStroke(keys[i]);
391: // NOTE: not every platform generates KEY_TYPED events; if not,
392: // then there is no problem. (w2k 1.3.1 gets no DELETE)
393: if (hasRecorder())
394: assertStep("KeyStroke\\(.*," + AWT.getKeyCode(keys[i])
395: + "\\)");
396: }
397: }
398:
399: public void testCaptureMacInputMethod() {
400: if (Platform.isMacintosh()) {
401: showTextField();
402: tester.actionKeyStroke(KeyEvent.VK_E, KeyEvent.ALT_MASK);
403: startRecording();
404: tester.actionKeyStroke(KeyEvent.VK_E);
405: assertStep("KeyString\\(.*,\u00e9\\)");
406: }
407: }
408:
409: // FIXME only do this where InputMethod is supported -- how do we detect
410: // that?
411: public void xtestCaptureInputMethod() {
412: showTextField();
413: startRecording();
414: try {
415: tester.actionKeyStroke(KeyEvent.VK_HALF_WIDTH);
416: } catch (ArrayIndexOutOfBoundsException aio) {
417: if (Platform.isMacintosh())
418: return;
419: throw aio;
420: } catch (IllegalArgumentException iae) {
421: if (Platform.isLinux())
422: return;
423: throw iae;
424: }
425: tester.actionKeyStroke(KeyEvent.VK_K);
426: tester.actionKeyStroke(KeyEvent.VK_A);
427: tester.actionKeyStroke(KeyEvent.VK_N);
428: tester.actionKeyStroke(KeyEvent.VK_J);
429: tester.actionKeyStroke(KeyEvent.VK_I);
430: tester.actionKeyStroke(KeyEvent.VK_FULL_WIDTH);
431: assertStep("Some text"); // FIXME
432: Step step = getStep();
433: assertTrue("Should have recorded a sequence",
434: step instanceof Sequence);
435: assertEquals("Wrong number of keystrokes", 7, ((Sequence) step)
436: .size());
437: }
438:
439: // sporadic failures on Linux 2.4.20/1.4.1_02
440: public void testCaptureMultipleClick() {
441: JLabel label = new JLabel(getName());
442: showFrame(label);
443: startRecording();
444: tester.actionClick(label, label.getWidth() / 2, label
445: .getHeight() / 2, InputEvent.BUTTON1_MASK, 2);
446: assertStep("Click\\(.*,2\\)");
447: }
448:
449: public void testCaptureClickWithComponentDisposal() {
450: JButton button = new JButton(getName());
451: final Frame frame = showFrame(button);
452: button.addActionListener(new ActionListener() {
453: public void actionPerformed(ActionEvent e) {
454: frame.dispose();
455: }
456: });
457: startRecording();
458: tester.actionClick(button);
459: assertStep("Click");
460: }
461:
462: public void testCaptureMultiClickWithComponentDisposal() {
463: // switch panes in response to a double click
464: // the original bug was found when changing to a new applet page via
465: // javascript in response to a double click (the double click was not
466: // recorded).
467: final JLabel label = new JLabel("Next Page");
468: final Frame frame = showFrame(label);
469: final JPanel replacement = new JPanel();
470: replacement.add(new JButton(getName() + " replacement"));
471: label.addMouseListener(new MouseAdapter() {
472: public void mouseReleased(MouseEvent e) {
473: if (e.getClickCount() == 2) {
474: // Force a mouse exit event
475: getRobot().mouseMove(frame, 1, 1);
476: ((JFrame) frame).setContentPane(replacement);
477: frame.invalidate();
478: }
479: }
480: });
481: startRecording();
482: tester.actionClick(label, label.getWidth() / 2, label
483: .getHeight() / 2, InputEvent.BUTTON1_MASK, 2);
484: assertStep("Click\\(.*,2\\)");
485: }
486:
487: private class ToolTipWatcher implements AWTEventListener {
488: public volatile boolean opened = false;
489:
490: public void eventDispatched(AWTEvent ev) {
491: if (ev.getID() == WindowEvent.WINDOW_OPENED) {
492: opened = true;
493: Toolkit.getDefaultToolkit().removeAWTEventListener(
494: ToolTipWatcher.this );
495: }
496: }
497: }
498:
499: /** Tooltip displays should not be recorded. */
500: public void testCaptureNoToolTips() {
501: JLabel label = new JLabel(getName());
502: ToolTipWatcher tw = new ToolTipWatcher();
503: label
504: .setToolTipText("A really long label that is sure to create a window that exceeds the original frame bounds");
505: showFrame(label);
506: new WeakAWTEventListener(tw, WindowEvent.WINDOW_EVENT_MASK);
507: startRecording();
508: tester.mouseMove(label, 0, 0);
509: tester.mouseMove(label);
510: Timer timer = new Timer();
511: while (!tw.opened) {
512: if (timer.elapsed() > 5000)
513: throw new RuntimeException("No tooltip appeared");
514: }
515: assertNoStep();
516: }
517:
518: public void testCaptureTableHeaderClick() {
519: int MAX_ENTRIES = 4;
520: String[][] cells = new String[MAX_ENTRIES][MAX_ENTRIES];
521: for (int i = 0; i < MAX_ENTRIES; i++) {
522: for (int j = 0; j < MAX_ENTRIES; j++) {
523: cells[i][j] = "cell " + i + "," + j;
524: }
525: }
526: String[] names = new String[MAX_ENTRIES];
527: for (int i = 0; i < MAX_ENTRIES; i++) {
528: names[i] = "col " + i;
529: }
530: JTable table = new JTable(cells, names);
531: showFrame(new JScrollPane(table));
532:
533: startRecording();
534: JTableHeader header = table.getTableHeader();
535: tester.actionClick(header, new JTableHeaderLocation(1));
536: assertStep("Click\\(.*,\"col 1\"\\)");
537: }
538:
539: // If duplicate labels are found among available popup menus, ensure that
540: // additional information is provided to the invocation.
541: public void testCaptureAWTPopupMenuSelectionWithTwoSimilarPopups() {
542: Frame frame = new Frame("frame");
543: Panel pane = new Panel();
544: Label label1 = new Label("left");
545: Label label2 = new Label("right");
546: pane.add(label1);
547: pane.add(label2);
548: frame.add(pane);
549: PopupMenu p1 = new PopupMenu();
550: MenuItem m1 = new MenuItem("item");
551: p1.add(m1);
552: PopupMenu p2 = new PopupMenu();
553: MenuItem m2 = new MenuItem("item");
554: p2.add(m2);
555:
556: addPopup(label1, p1);
557: addPopup(label2, p2);
558: // Make both popups have the same invoker
559: frame.add(p1);
560: frame.add(p2);
561:
562: showWindow(frame);
563:
564: startRecording(false);
565:
566: triggerPopup(label1);
567:
568: String id = frame.getTitle(); // should be cref id
569: assertStep("SelectAWTPopupMenuItem\\(" + id + ","
570: + AWT.getPath(m1) + "\\)");
571: stopRecording();
572:
573: startRecording(false);
574:
575: triggerPopup(label2);
576: assertStep("SelectAWTPopupMenuItem\\(" + id + ","
577: + AWT.getPath(m2) + "\\)");
578: }
579:
580: /** Focus accelerators should not be recorded as typed events, but rather
581: * as basic keystrokes.
582: */
583: // FIXME sporadic linux failure here
584: public void testCaptureFocusAccelerator() {
585: // Mac doesn't support focus accelerators
586: if (Platform.isOSX())
587: return;
588:
589: JTextField tf1 = new JTextField("1");
590: tf1.setName("TF1");
591: JTextField tf2 = new JTextField("2");
592: tf2.setName("TF2");
593: tf1.setFocusAccelerator('a');
594: tf2.setFocusAccelerator('b');
595: JPanel p = new JPanel();
596: p.add(tf1);
597: p.add(tf2);
598: showFrame(p);
599: assertTrue("First text field should have focus", tf1.hasFocus());
600:
601: startRecording();
602: tester.actionKeyStroke(KeyEvent.VK_B, KeyEvent.ALT_MASK);
603: assertTrue("Focus accelerator didn't work", tf2.hasFocus());
604: assertNoStep();
605:
606: startRecording();
607: tester.actionKeyStroke(KeyEvent.VK_A, KeyEvent.ALT_MASK);
608: assertTrue("Focus accelerator didn't work", tf1.hasFocus());
609: assertNoStep();
610: }
611:
612: // This would be a nice test, but it'd have to be run on every
613: // keyboard hardware to actually test it. For now, ad hoc discard what we
614: // know to be irrelevant
615: //public void testWhichKeyTypedEventsAreIgnored() { }
616:
617: /** Create a new test case with the given name. */
618: public ComponentRecorderTest(String name) {
619: super (name);
620: }
621:
622: public static void main(String[] args) {
623: RepeatHelper.runTests(args, ComponentRecorderTest.class);
624: }
625: }
|