001: package abbot.editor.recorder;
002:
003: import java.awt.*;
004: import java.awt.event.*;
005:
006: import javax.swing.*;
007:
008: import junit.extensions.abbot.RepeatHelper;
009: import abbot.*;
010: import abbot.script.*;
011: import abbot.tester.*;
012: import abbot.tester.Robot;
013:
014: /**
015: * Unit test to verify proper capture of multiple user semantic events when
016: * parsed from a continuous event stream.
017: * TODO: feed the event recorder one or more canned event sequences with the
018: * expected results. Input streams may vary by platform/VM version.
019: * Need a separate test of the canned stream itself against robot-generated
020: * events to ensure things don't change. Currently the input event stream is
021: * not explicit in the tests and is thus resistant to change.
022: * <p>
023: * Feeding specific event sequences avoids having to throw up an
024: * IF during testing.
025: * <p>
026: * Needs much more testing on multi-step recording.
027: * All recording should be tested here since EventRecorder is the only context
028: * in which the others are used, and individual tests are prone to missing
029: * things at the start or end of a recording.
030: */
031: public class EventRecorderTest extends AbstractRecorderFixture {
032:
033: private EventRecorder recorder;
034: private JTextComponentTester tester;
035: private Step step;
036:
037: // Provide this so we can test as if we were a platform that doesn't
038: // generate them
039: private boolean suppressKeyTypedEvents = false;
040:
041: protected Recorder getRecorder() {
042: step = null;
043: // Add a little extra to optionally suppress KEY_TYPED events
044: recorder = new EventRecorder(getResolver(), false) {
045: protected void recordEvent(AWTEvent event)
046: throws RecordingFailedException {
047: if (!suppressKeyTypedEvents
048: || event.getID() != KeyEvent.KEY_TYPED) {
049: Log.debug("ERT: " + Robot.toString(event));
050: super .recordEvent(event);
051: }
052: }
053: };
054: recorder.addActionListener(new ActionListener() {
055: /** Will be invoked when the recorder posts status messages. */
056: public void actionPerformed(ActionEvent ev) {
057: Log.debug(ev.getActionCommand());
058: }
059: });
060: return recorder;
061: }
062:
063: protected void setUp() {
064: tester = new JTextComponentTester();
065: }
066:
067: protected void tearDown() {
068: recorder = null;
069: tester = null;
070: step = null;
071: }
072:
073: private void waitForStep() {
074: tester.waitForIdle();
075: step = recorder.getStep();
076: }
077:
078: private Step getStep() {
079: if (step == null)
080: step = recorder.getStep();
081: return step;
082: }
083:
084: public void testCaptureMnemonic() {
085: JButton button = new JButton("Push Me");
086: button.setMnemonic(KeyEvent.VK_P);
087: class Flag {
088: volatile boolean flag;
089: }
090: final Flag flag = new Flag();
091: button.addActionListener(new ActionListener() {
092: public void actionPerformed(ActionEvent e) {
093: flag.flag = true;
094: }
095: });
096: showFrame(button);
097: startRecording();
098: tester.actionKeyStroke(KeyEvent.VK_P,
099: Robot.MOUSELESS_MODIFIER_MASK);
100: assertTrue("Mnemonic had no effect, check mask ("
101: + Robot.MOUSELESS_MODIFIER + ")", flag.flag);
102: Sequence seq = (Sequence) getStep();
103: assertStepCount(1, seq);
104: assertStep("KeyStroke\\(.*,VK_P,", seq.getStep(0));
105: }
106:
107: // FIXME NPE on X11, OSX, as if tracking a drag
108: public void testCaptureTextSelection() {
109: String text = "Select some text";
110: JTextField tf = new JTextField(text);
111: JPanel p = new JPanel();
112: p.add(tf);
113: p.add(new JLabel(getName()));
114: showFrame(p);
115:
116: // Ensure the drag exits the component
117: startRecording();
118: tester.actionDrag(tf, 1, tf.getHeight() / 2);
119: tester.mouseMove(tf, tf.getWidth() / 2, tf.getHeight() / 2);
120: tester.mouseMove(tf, tf.getWidth() / 2, tf.getHeight() / 2 + 1);
121: tester.actionDrop(tf, tf.getWidth() + 100, tf.getHeight() / 2);
122: Sequence seq = (Sequence) getStep();
123: assertStep("SelectText\\(.*,0," + text.length() + "\\)", seq
124: .getStep(0));
125: }
126:
127: public void testCaptureActivateDialogFromButton() {
128: // clicking a button that results in a change of focus to that button
129: // shouldn't leave a trailing MOUSE_RELEASED after the Click is parsed
130: // are there other possible intervening events?
131: //
132: // In addition, make sure the resulting dialog only results in a
133: // single "wait for frame showing"
134: JButton button = new JButton("Push me");
135: final Frame frame = showFrame(button);
136:
137: button.addActionListener(new ActionListener() {
138: public void actionPerformed(ActionEvent ev) {
139: JDialog dialog = new JDialog(frame, "Dialog", true);
140: dialog.getContentPane().add(new JButton("OK"));
141: dialog.pack();
142: dialog.setVisible(true);
143: }
144: });
145: startRecording();
146: tester.actionClick(button);
147: stopRecording();
148:
149: assertTrue("Event recorder should return a Sequence",
150: getStep() instanceof Sequence);
151: Sequence seq = (Sequence) getStep();
152: assertStepCount(2, seq);
153: assertStep("Click", seq.getStep(0));
154: assertStep("Wait for ComponentShowing", seq.getStep(1));
155: }
156:
157: /** Display a text field, already focused. */
158: private JTextField showTextField() {
159: JTextField tf = new JTextField();
160: tf.setColumns(10);
161: showFrame(tf);
162: tester.actionFocus(tf);
163: return tf;
164: }
165:
166: /** Ensure that a single key press results in a single keystroke event. */
167: // Sometimes fails on linux with pointer focus
168: // w32 sporadic failure
169: public void testCaptureSingleKey() {
170: // Some VM versions produce dual key_typed events for non-text
171: // components; make sure we save only one. If a text field is used,
172: // only one key_typed event is received.
173: JButton button = new JButton("Focus here");
174: JTextField tf = new JTextField("Focus here next");
175: JPanel pane = new JPanel();
176: pane.add(button);
177: pane.add(tf);
178: showFrame(pane);
179:
180: // Don't want to capture any actions related to focus.
181: tester.actionFocus(button);
182: startRecording();
183: tester.actionKeyStroke(KeyEvent.VK_A);
184: stopRecording();
185: waitForStep();
186: assertTrue("Event recorder should return a Sequence",
187: getStep() instanceof Sequence);
188: Sequence seq = (Sequence) getStep();
189: assertStepCount(1, seq);
190: assertStep("KeyString\\(.*,a\\)", seq.getStep(0));
191:
192: // Now send to a text field
193: tester.actionFocus(tf);
194: startRecording();
195: tester.actionKeyStroke(KeyEvent.VK_B);
196: stopRecording();
197: waitForStep();
198: assertTrue("Event recorder should return a Sequence",
199: getStep() instanceof Sequence);
200: seq = (Sequence) getStep();
201: assertStepCount(1, seq);
202: assertStep("KeyString\\(.*,b\\)", seq.getStep(0));
203: }
204:
205: /** Prefer capturing numeric keypad keystrokes to the characters that are
206: * produced, since keypad operation may produce different behavior than
207: * entering numeric characters.
208: */
209: // FIXME linux only types first 6 keys
210: public void testCaptureNumPadKeys() {
211: JTextField tf = showTextField();
212:
213: final int NUMKEYS = 10;
214: boolean needLock = false;
215: try {
216: needLock = !Toolkit.getDefaultToolkit().getLockingKeyState(
217: KeyEvent.VK_NUM_LOCK);
218: } catch (UnsupportedOperationException e) {
219: // Type a numpad key and see what we get
220: tester.actionKeyStroke(tf, KeyEvent.VK_NUMPAD4);
221: needLock = !tf.getText().equals("4");
222: }
223: if (needLock) {
224: tester.actionKeyStroke(KeyEvent.VK_NUM_LOCK);
225: tester.actionActionMap(tf, "select-all");
226: tester.actionKeyStroke(KeyEvent.VK_NUMPAD4);
227: assertEquals("NumLock not set, can't generate numpad keys",
228: "4", tf.getText());
229: }
230: // Don't want any special key mappings; this prevents the keys from
231: // appearing in the text field, but still allows the recorder to run
232: // as expected.
233: //tf.setInputMap(JComponent.WHEN_FOCUSED, new InputMap());
234:
235: startRecording();
236: try {
237: for (int i = 0; i < NUMKEYS; i++) {
238: tester.actionKeyStroke(tf, KeyEvent.VK_NUMPAD0 + i);
239: }
240: stopRecording();
241: waitForStep();
242: assertTrue("Event recorder should return a Sequence",
243: getStep() instanceof Sequence);
244: Sequence seq = (Sequence) getStep();
245: assertTrue("No steps recorded", seq.size() > 0);
246: assertStepCount(NUMKEYS, seq);
247: for (int i = 0; i < NUMKEYS; i++) {
248: String expected = "VK_NUMPAD" + i;
249: assertStep("KeyStroke\\(.*," + expected + "\\)", seq
250: .getStep(i));
251: }
252: } finally {
253: if (needLock)
254: tester.actionKeyStroke(KeyEvent.VK_NUM_LOCK);
255: }
256: }
257:
258: /** Should save no individual key press/release events. */
259: public void testCaptureKeyString() {
260: showTextField();
261: startRecording();
262: tester.actionKeyStroke(KeyEvent.VK_T);
263: tester.actionKeyStroke(KeyEvent.VK_E);
264: tester.actionKeyStroke(KeyEvent.VK_X);
265: tester.actionKeyStroke(KeyEvent.VK_T);
266: stopRecording();
267: waitForStep();
268: assertTrue("Event recorder should return a Sequence",
269: step instanceof Sequence);
270: Sequence seq = (Sequence) getStep();
271: assertStepCount(1, seq);
272: assertStep("KeyString\\(.*,text\\)", seq.getStep(0));
273: }
274:
275: /** Should save no modifiers or individual key press/release. */
276: public void testCaptureKeyStringShifted() {
277: showTextField();
278: startRecording();
279: tester.actionKeyPress(KeyEvent.VK_SHIFT);
280: tester.actionKeyStroke(KeyEvent.VK_T);
281: tester.actionKeyStroke(KeyEvent.VK_E);
282: tester.actionKeyStroke(KeyEvent.VK_X);
283: tester.actionKeyStroke(KeyEvent.VK_T);
284: tester.actionKeyRelease(KeyEvent.VK_SHIFT);
285: stopRecording();
286: waitForStep();
287: assertTrue("Event recorder should return a Sequence",
288: step instanceof Sequence);
289: Sequence seq = (Sequence) step;
290: assertStepCount(1, seq);
291: assertStep("KeyString\\(.*,TEXT\\)", seq.getStep(0));
292: }
293:
294: /** Should save no modifiers, since the modifier gets incorporated into
295: * each key stroke.
296: */
297: public void testCaptureKeyStringPartiallyShifted() {
298: showTextField();
299: startRecording();
300: tester.actionKeyPress(KeyEvent.VK_SHIFT);
301: tester.actionKeyStroke(KeyEvent.VK_T);
302: tester.actionKeyRelease(KeyEvent.VK_SHIFT);
303: tester.actionKeyStroke(KeyEvent.VK_E);
304: tester.actionKeyStroke(KeyEvent.VK_X);
305: tester.actionKeyStroke(KeyEvent.VK_T);
306: stopRecording();
307: waitForStep();
308: assertTrue("Event recorder should return a Sequence",
309: step instanceof Sequence);
310: Sequence seq = (Sequence) step;
311: assertStepCount(1, seq);
312: assertStep("KeyString\\(.*,Text\\)", seq.getStep(0));
313: }
314:
315: /** A modifier down/up should be captured that way. */
316: public void testCaptureModifier() {
317: showTextField();
318: startRecording();
319: tester.actionKeyPress(KeyEvent.VK_SHIFT);
320: tester.actionKeyRelease(KeyEvent.VK_SHIFT);
321: stopRecording();
322: waitForStep();
323: assertTrue("Event recorder should return a Sequence",
324: step instanceof Sequence);
325: Sequence seq = (Sequence) step;
326: assertStepCount(2, seq);
327: assertStep(
328: "KeyEvent.KEY_PRESSED \\(VK_SHIFT\\) on \\$\\{JTextField Instance\\}",
329: seq.getStep(0));
330: assertStep(
331: "KeyEvent.KEY_RELEASED \\(VK_SHIFT\\) on \\$\\{JTextField Instance\\}",
332: seq.getStep(1));
333: }
334:
335: public void testCaptureWithNoKeyTypedEvents() {
336: suppressKeyTypedEvents = true;
337: showTextField();
338: startRecording();
339: tester.actionKeyStroke(KeyEvent.VK_T);
340: tester.actionKeyStroke(KeyEvent.VK_E);
341: tester.actionKeyStroke(KeyEvent.VK_X);
342: tester.actionKeyStroke(KeyEvent.VK_T);
343: stopRecording();
344: waitForStep();
345: assertTrue("Event recorder should return a Sequence",
346: step instanceof Sequence);
347: Sequence seq = (Sequence) step;
348: assertStepCount(4, seq);
349: assertStep("KeyStroke\\(.*,VK_T\\)", seq.getStep(0));
350: assertStep("KeyStroke\\(.*,VK_E\\)", seq.getStep(1));
351: assertStep("KeyStroke\\(.*,VK_X\\)", seq.getStep(2));
352: assertStep("KeyStroke\\(.*,VK_T\\)", seq.getStep(3));
353: suppressKeyTypedEvents = false;
354: }
355:
356: /** Test an event stream from rapidly typed keys. */
357: public void testCaptureJumbledKeyEvents() {
358: showTextField();
359: startRecording();
360: // Actual event sequence seen when typing "SHIFt" rapidly
361: tester.actionKeyPress(KeyEvent.VK_SHIFT);
362: tester.actionKeyPress(KeyEvent.VK_S);
363: tester.actionKeyRelease(KeyEvent.VK_S);
364: tester.actionKeyPress(KeyEvent.VK_H);
365: tester.actionKeyPress(KeyEvent.VK_I);
366: tester.actionKeyRelease(KeyEvent.VK_H);
367: tester.actionKeyPress(KeyEvent.VK_F);
368: tester.actionKeyRelease(KeyEvent.VK_I);
369: tester.actionKeyRelease(KeyEvent.VK_F);
370: tester.actionKeyRelease(KeyEvent.VK_SHIFT);
371: tester.actionKeyStroke(KeyEvent.VK_T);
372: stopRecording();
373: waitForStep();
374: // Should capture exactly 5 steps, one for each letter typed
375: assertTrue("Event recorder should return a Sequence",
376: step instanceof Sequence);
377: Sequence seq = (Sequence) step;
378: assertStepCount(1, seq);
379: assertStep("KeyString\\(.*,SHIFt\\)", seq.getStep(0));
380: }
381:
382: /** Tabs are used for focus traversal. */
383: public void testCaptureFocusTraversal() {
384: JTextField comp1, comp2, comp3, comp4;
385: JPanel pane = new JPanel();
386: pane.add(comp1 = new JTextField("Field 1"));
387: pane.add(comp2 = new JTextField("Field 2"));
388: pane.add(comp3 = new JTextField("Field 3"));
389: pane.add(comp4 = new JTextField("Field 4"));
390: showFrame(pane);
391: tester.actionFocus(comp1);
392: startRecording();
393: tester.actionKeyStroke(KeyEvent.VK_TAB);
394: tester.actionKeyStroke(KeyEvent.VK_TAB);
395: tester.actionKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_MASK);
396: long start = System.currentTimeMillis();
397: while (!comp2.hasFocus()) {
398: if (System.currentTimeMillis() - start > 5000) {
399: throw new Error(
400: "Timed out waiting for proper focus on "
401: + Robot.toString(comp2));
402: }
403: }
404: stopRecording();
405: waitForStep();
406: assertTrue("Event recorder should return a sequence",
407: step instanceof Sequence);
408: Sequence seq = (Sequence) step;
409: assertStepCount(3, seq);
410: toString(seq);
411: assertStep("KeyStroke\\(.*,VK_TAB\\)1*", seq.getStep(0));
412: assertStep("KeyStroke\\(.*,VK_TAB\\)2*", seq.getStep(1));
413: assertStep("KeyStroke\\(.*,VK_TAB,SHIFT_MASK\\)", seq
414: .getStep(2));
415: assertEquals("No text expected", "Field 1", comp1.getText());
416: assertEquals("No text expected", "Field 2", comp2.getText());
417: assertEquals("No text expected", "Field 3", comp3.getText());
418: assertEquals("No text expected", "Field 4", comp4.getText());
419: }
420:
421: public void testMacModifierStripping() {
422: // Ensure the input method modifiers are stripped
423: if (!Platform.isMacintosh()) {
424: return;
425: }
426: showTextField();
427: startRecording();
428: tester.keyPress(KeyEvent.VK_ALT);
429: tester.keyPress(KeyEvent.VK_E);
430: tester.keyRelease(KeyEvent.VK_E);
431: tester.keyRelease(KeyEvent.VK_ALT);
432: tester.keyPress(KeyEvent.VK_E);
433: tester.keyRelease(KeyEvent.VK_E);
434: tester.waitForIdle();
435: stopRecording();
436: waitForStep();
437: assertTrue("Event recorder should return a sequence",
438: step instanceof Sequence);
439: Sequence seq = (Sequence) step;
440: assertStepCount(1, seq);
441: assertStep("KeyString\\(.*,\u00e9\\)", seq.getStep(0));
442: }
443:
444: public void testMouseThenKey() {
445: JButton button = new JButton("Press");
446: showFrame(button);
447: startRecording();
448: tester.actionClick(button);
449: tester.actionKeyStroke(KeyEvent.VK_A);
450: stopRecording();
451: waitForStep();
452: assertTrue("Event recorder should return a sequence",
453: step instanceof Sequence);
454: Sequence seq = (Sequence) step;
455: assertStepCount(2, seq);
456: assertStep("Click\\(.*\\)", seq.getStep(0));
457: assertStep("KeyString\\(.*,a\\)", seq.getStep(1));
458: }
459:
460: public void testStripShortcutKey() {
461: int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
462: JTextField tf = new JTextField(getName());
463: showFrame(tf);
464: tester.actionFocus(tf);
465: startRecording();
466: tester.actionKeyStroke(KeyEvent.VK_A, mask);
467: tester.actionKeyStroke(KeyEvent.VK_X, mask);
468: tester.actionKeyStroke(KeyEvent.VK_V, mask);
469: tester.actionKeyPress(KeyEvent.VK_CONTROL);
470: tester.actionKeyStroke(KeyEvent.VK_A);
471: tester.actionKeyStroke(KeyEvent.VK_X);
472: tester.actionKeyStroke(KeyEvent.VK_V);
473: tester.actionKeyRelease(KeyEvent.VK_CONTROL);
474: stopRecording();
475: waitForStep();
476: assertTrue("Expected a sequence", step instanceof Sequence);
477: Sequence seq = (Sequence) step;
478: assertStepCount(6, seq);
479: assertStep("ActionMap\\(.*,select-all\\)", seq.getStep(0));
480: assertStep("ActionMap\\(.*,cut-to-clipboard\\)", seq.getStep(1));
481: assertStep("ActionMap\\(.*,paste-from-clipboard\\)", seq
482: .getStep(2));
483: assertStep("ActionMap\\(.*,select-all\\)", seq.getStep(0));
484: assertStep("ActionMap\\(.*,cut-to-clipboard\\)", seq.getStep(1));
485: assertStep("ActionMap\\(.*,paste-from-clipboard\\)", seq
486: .getStep(2));
487: }
488:
489: // Popups may be displayed in response to any stimulus. */
490: public void testCapturePopupMenuFromButton() {
491: final JButton button = new JButton(getName());
492: final JPopupMenu popup = new JPopupMenu();
493: JMenuItem mi = new JMenuItem("File");
494: popup.add(mi);
495: JMenuItem mi2 = new JMenuItem("Edit");
496: popup.add(mi2);
497: button.addActionListener(new ActionListener() {
498: public void actionPerformed(ActionEvent e) {
499: popup.show(button, 0, button.getHeight());
500: }
501: });
502: showFrame(button);
503:
504: startRecording();
505: tester.actionClick(button);
506: tester.actionClick(mi2);
507:
508: waitForStep();
509: assertTrue("Expected a sequence: " + step,
510: step instanceof Sequence);
511: Sequence seq = (Sequence) step;
512: assertStepCount(2, seq);
513: assertStep("Click\\(.*\\)", seq.getStep(0));
514: assertStep("SelectMenuItem\\(" + mi2.getText() + "\\)", seq
515: .getStep(1));
516: }
517:
518: // AWT popups are a bit more tricky
519: public void testCaptureAWTPopupMenuFromButton() throws Exception {
520: final JButton button = new JButton(getName());
521: final PopupMenu popup = new PopupMenu();
522: MenuItem mi = new MenuItem("File");
523: popup.add(mi);
524: MenuItem mi2 = new MenuItem("Edit");
525: popup.add(mi2);
526: button.addActionListener(new ActionListener() {
527: public void actionPerformed(ActionEvent e) {
528: popup.show(button, 0, button.getHeight());
529: }
530: });
531: button.add(popup);
532: showFrame(button);
533:
534: startRecording();
535:
536: // waitForIdle times out if a w32 AWT popup is showing,
537: // so use plain old click
538: ComponentTester tester = new ComponentTester();
539: tester.click(button);
540: tester.delay(100);
541: tester.click(button, 10, button.getHeight() + 10);
542: // FIXME figure out why this delay is necessary
543: // Note: the framework doesn't use events to trigger AWT menu
544: // selections, so this is not normally an issue.
545: if (Platform.isOSX()) // Consider Bugs.XXX
546: tester.delay(500);
547: waitForStep();
548: assertTrue("Expected a sequence: " + step,
549: step instanceof Sequence);
550: Sequence seq = (Sequence) step;
551: assertStepCount(2, seq);
552: assertStep("Click\\(.*\\)", seq.getStep(0));
553: assertStep("SelectAWTPopupMenuItem\\(.*," + mi.getLabel()
554: + "\\)", seq.getStep(1));
555: }
556:
557: /** Create a new test case with the given name. */
558: public EventRecorderTest(String name) {
559: super (name);
560: }
561:
562: public static void main(String[] args) {
563: RepeatHelper.runTests(args, EventRecorderTest.class);
564: }
565: }
|