SAVE IN GUI
Provide, in the GUI, the capability to save the movie collection to the same file it was previously saved to.
We took it slowly for the last task since there were a few new ideas involved. Now we'll pick it up a bit since this task requires only a refinement of what we've done so far on this story. Specifically, we need to add the ability to resave to the same file without asking the user for a filename again.
Test 56. Telling the logical layer to "Save" causes it to save the current list to the same file as the previous "Save As" operation.
Test 57. Selecting "Save" from the "File" menu causes the list to be written into the previously selected file.
Since the required setup for testing this involves what we've already done for testing the Save As functionality, we'll extend the testSaving() tests.
Test 56: Telling the logical layer to "Save" causes it to save the current list to the same file as the previous "Save As" operation
We'll start in TestMovieListEditorFileOperations:
public void testSaving() throws Exception { mockView.setMovies(movies); control.setVoidCallable(1); mockView.getFile(); control.setReturnValue(outputFile, 1); mockView.getNameField(); control.setReturnValue(fotr.getName(), 1); mockView.getCategoryField(); control.setReturnValue(fotr.getCategory(), 1); mockView.getRatingField(); control.setReturnValue(fotr.getRating() + 1, 1); mockView.setMovies(extendedMovies); control.setVoidCallable(1); control.activate();
MovieListEditor editor = new MovieListEditor(movieList, mockView); assertTrue("Editor should have saved",editor.saveAs());
FileAssert.assertSize("Save As-ed file has wrong size.", expected.length(), outputFile);
FileAssert.assertEquals("Save As-ed file has wrong contents ", expected, outputFile);
editor.add(); assertTrue("Editor should have resaved", editor.save()); FileAssert.assertEquals("Saved file ", extendedExpected, outputFile); control.verify(); }
Here we've added to the test to add a movie to the list, resave it, and verify the result. To support this, we need to extend the fixture:
fotr = new Movie("The Fellowship of The Ring", Category.FANTASY, 5); extendedExpected = expected + "The Fellowship of The Ring|Fantasy|5\n"; extendedMovies = new Vector(movies); extendedMovies.add(fotr);
Add the required stub for MovieListEditor.save() and give it a try:
public booleansave() { return false; }
Of course it fails because save() doesn't do anything yet. Most notably, it just returns false. We need to write some code for save():
public booleansave() { if (outputFile == null) { return false; }
FileWriter writer = new FileWriter(outputFile); movies.writeTo(writer); writer.close(); return true; }
To make this compile we need to have an instance variable for outputFile. Add it, compile, and run the tests. It still fails because we don't initialize the outputFile instance variable. We'll do this in saveAs(); we need to use the new instance variable rather than the local we had before:
public boolean saveAs() throwsIOException { outputFile = view.getFile(); if (outputFile == null) { return false; }
FileWriter writer = new FileWriter(outputFile); movies.writeTo(writer); writer.close(); return true; }
Now the test fails because the resulting file is wrong. Hmm. . . that seems unlikely. Everything looks good. Run the test in the debugger (set a breakpoint on entry to MovieListEditor.save()) and we find that the Movie that got added is uncategorized and unrated. Hmm. . . a quick look at MovieListEditor.add() reveals the problem. It was creating a movie with just a name. Ponder, ponder, scratch, scratch. . . oh, yes. Our thought was to create a template movie to probe for a duplicate. We missed going back and filling in the rest of the data. A quick look at the test for adding (which was written when we had only a name) shows that the mock has no expectations regarding the category and rating being fetched during an add. Not a nice feeling, discovering thatbut it happens. Because we are practicing TDD, we have a better chance of finding that sort of thing early.
First things first, though. We'll fix add() to get our current test passing:
public void add() { String newName = view.getNameField(); Movie newMovie = new Movie(newName, view.getCategoryField(), view.getRatingField()); try { movies.add(newMovie); updateMovieList(); } catch (DuplicateMovieException e) { view.duplicateException(newName); } }
Rerun the test. Green. It's not clean yet.
Exercises
15. |
Fix the add() related tests in TestMovieListEditor.
|
Now to clean up MovieListEditor. If we compare save() and saveAs() we see that other than the initial fetch of outputFile in saveAs(), they are identical. That's an easy cleanup: we just have to have saveAs() set outputFile and call save():
public boolean saveAs() throwsIOException { outputFile = view.getFile(); return save(); }
Green and clean!
Test 57: Selecting "Save" from the "File" menu causes the list to be written into the previously selected file
The next step is to do something very similar to the GUI testSaving(). But first we'll do some damage control and fix up the add-related GUI tests which we see are also out of date. Actually, I'll make that an exercise for you.
Exercises
16. |
Fix up the add related tests in TestSwingMovieListEditorView.
|
Here's the new testSaving() GUI test:
public void testSaving() throws Exception { JMenuBarOperator menubar = new JMenuBarOperator(mainWindow); JMenuOperator fileMenu = new JMenuOperator(menubar, "File"); fileMenu.push();
JMenuItemOperator saveAsItem = new JMenuItemOperator(mainWindow, new NameBasedChooser("saveas")); saveAsItem.pushNoBlock(); JFileChooserOperator fileChooser = new JFileChooserOperator(); fileChooser.setSelectedFile(outputFile); JButtonOperator saveButton = new JButtonOperator(fileChooser, "Save"); saveButton.push(); FileAssert.assertSize("Save-Ased list has wrong size.", savedText.length(), outputFile); FileAssert.assertEquals("Save-Ased file", savedText, outputFile);
JTextFieldOperator newMovieField = new JTextFieldOperator(mainWindow, new NameBasedChooser("name")); newMovieField.enterText(theShining.getName());
JComboBoxOperator ratingCombo = new JComboBoxOperator(mainWindow, new NameBasedChooser("rating")); ratingCombo.setSelectedIndex(theShining.getRating() + 1);
JComboBoxOperator categoryCombo = new JComboBoxOperator(mainWindow, new NameBasedChooser("category")); categoryCombo.setSelectedIndex(2);
JButtonOperator addButton = new JButtonOperator(mainWindow, new NameBasedChooser("add")); addButton.doClick();
fileMenu.push(); JMenuItemOperator saveItem = new JMenuItemOperator(mainWindow, new NameBasedChooser("save")); saveItem.push();
FileAssert.assertSize("Saved list has wrong size.", extendedSavedText.length(), outputFile);
FileAssert.assertEquals("Saved file", extendedSavedText, outputFile); }
We need to also extend the fixture somewhat:
theShining = new Movie("The Shining", Category.HORROR, 2); extendedSavedText = savedText + "The Shining|Horror|2\n";
This compiles but fails because there is no Save item. Easy enough to add:
priate JMenuBar initMenuBar() { JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); menuBar.add(fileMenu);
JMenuItem saveAsItem = new JMenuItem("Save As.. ."); saveAsItem.setName("saveas"); saveAsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { myEditor.saveAs(); } catch (IOException ex) { // TODO: deal with this } }}); fileMenu.add(saveAsItem);
JMenuItem saveItem = new JMenuItem("Save"); saveItem.setName("save"); fileMenu.add(saveItem); return menuBar; }
Now the test fails because the file is not being updated. We next need to call save() in the associated MovieListEditor in response to the Save item being selected:
private JMenuBar initMenuBar() { JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); menuBar.add(fileMenu);
JMenuItem saveAsItem = new JMenuItem("Save As.. ."); saveAsItem.setName("saveas"); saveAsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { myEditor.saveAs(); } catch (IOException ex) { // TODO: deal with this } }}); fileMenu.add(saveAsItem);
JMenuItem saveItem = new JMenuItem("Save"); saveItem.setName("save"); saveItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { myEditor.save(); } catch (IOException ex) { // TODO: deal with this } }}); fileMenu.add(saveItem); return menuBar; }
The test now runs green. However, initMenuBar has grown too large and is starting to smell. A couple of Extract Method applications (one to each JMenuItem setup) will take care of it:
private JMenuBar initMenuBar() { JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); menuBar.add(fileMenu); fileMenu.add(initSaveAsItem()); fileMenu.add(initSaveItem()); return menuBar; }
private JMenuItem initSaveAsItem() { JMenuItem saveAsItem = new JMenuItem("Save As. . ."); saveAsItem.setName("saveas"); saveAsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { myEditor.saveAs(); } catch (IOException ex) { // TODO: deal with this } }}); return saveAsItem; }
private JMenuItem initSaveItem() { JMenuItem saveItem = new JMenuItem("Save"); saveItem.setName("save"); saveItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { myEditor.save(); } catch (IOException ex) { // TODO: deal with this } }}); return saveItem; }
OK. Green and clean! There's still some duplication in the menu item creation and setup code, but that's largely unavoidable in GUI construction code where you are creating multiples of the same type of component.
|
No comments:
Post a Comment