diff --git a/src/app/Main.java b/src/app/Main.java index 6d4e9f45..a757b691 100644 --- a/src/app/Main.java +++ b/src/app/Main.java @@ -2,6 +2,7 @@ import data_access.FileUserDataAccessObject; import entity.CommonUserFactory; +import interface_adapter.clear_users.ClearViewModel; import interface_adapter.login.LoginViewModel; import interface_adapter.logged_in.LoggedInViewModel; import interface_adapter.signup.SignupViewModel; @@ -43,6 +44,9 @@ public static void main(String[] args) { LoggedInViewModel loggedInViewModel = new LoggedInViewModel(); SignupViewModel signupViewModel = new SignupViewModel(); + // add clearViewModel + ClearViewModel clearViewModel = new ClearViewModel(); + FileUserDataAccessObject userDataAccessObject; try { userDataAccessObject = new FileUserDataAccessObject("./users.csv", new CommonUserFactory()); @@ -50,7 +54,8 @@ public static void main(String[] args) { throw new RuntimeException(e); } - SignupView signupView = SignupUseCaseFactory.create(viewManagerModel, loginViewModel, signupViewModel, userDataAccessObject); + // add clearViewModel and userDataAccessObject + SignupView signupView = SignupUseCaseFactory.create(viewManagerModel, loginViewModel, signupViewModel, userDataAccessObject, clearViewModel, userDataAccessObject); views.add(signupView, signupView.viewName); LoginView loginView = LoginUseCaseFactory.create(viewManagerModel, loginViewModel, loggedInViewModel, userDataAccessObject); diff --git a/src/app/SignupUseCaseFactory.java b/src/app/SignupUseCaseFactory.java index abdb9456..4cdd9b7a 100644 --- a/src/app/SignupUseCaseFactory.java +++ b/src/app/SignupUseCaseFactory.java @@ -1,9 +1,16 @@ package app; +import interface_adapter.clear_users.ClearController; +import interface_adapter.clear_users.ClearPresenter; +import interface_adapter.clear_users.ClearViewModel; import interface_adapter.login.LoginViewModel; import interface_adapter.signup.SignupController; import interface_adapter.signup.SignupPresenter; import interface_adapter.signup.SignupViewModel; +import use_case.clear_users.ClearInputBoundary; +import use_case.clear_users.ClearInteractor; +import use_case.clear_users.ClearOutputBoundary; +import use_case.clear_users.ClearUserDataAccessInterface; import use_case.signup.SignupUserDataAccessInterface; import entity.CommonUserFactory; import entity.UserFactory; @@ -22,15 +29,17 @@ public class SignupUseCaseFactory { private SignupUseCaseFactory() {} public static SignupView create( - ViewManagerModel viewManagerModel, LoginViewModel loginViewModel, SignupViewModel signupViewModel, SignupUserDataAccessInterface userDataAccessObject) { + ViewManagerModel viewManagerModel, LoginViewModel loginViewModel, SignupViewModel signupViewModel, SignupUserDataAccessInterface userDataAccessObject, ClearViewModel clearViewModel, ClearUserDataAccessInterface clearUserDataAccessObject) { try { SignupController signupController = createUserSignupUseCase(viewManagerModel, signupViewModel, loginViewModel, userDataAccessObject); - return new SignupView(signupController, signupViewModel); + ClearController clearController = createUserClearUseCase(viewManagerModel, clearViewModel, clearUserDataAccessObject); + return new SignupView(signupController, signupViewModel, clearController, clearViewModel); } catch (IOException e) { JOptionPane.showMessageDialog(null, "Could not open user data file."); } + return null; } @@ -46,4 +55,16 @@ private static SignupController createUserSignupUseCase(ViewManagerModel viewMan return new SignupController(userSignupInteractor); } + + private static ClearController createUserClearUseCase(ViewManagerModel viewManagerModel, ClearViewModel clearViewModel, ClearUserDataAccessInterface userDataAccessObject) throws IOException { + + // Notice how we pass this method's parameters to the Presenter. + ClearOutputBoundary clearOutputBoundary = new ClearPresenter(viewManagerModel, clearViewModel); + + // no UserFactorty + ClearInputBoundary clearInteractor = new ClearInteractor( + userDataAccessObject, clearOutputBoundary); + + return new ClearController(clearInteractor); + } } diff --git a/src/data_access/FileUserDataAccessObject.java b/src/data_access/FileUserDataAccessObject.java index 11ebc4f5..79b137ad 100644 --- a/src/data_access/FileUserDataAccessObject.java +++ b/src/data_access/FileUserDataAccessObject.java @@ -2,16 +2,18 @@ import entity.User; import entity.UserFactory; +import use_case.clear_users.ClearUserDataAccessInterface; import use_case.login.LoginUserDataAccessInterface; import use_case.signup.SignupUserDataAccessInterface; import java.io.*; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -public class FileUserDataAccessObject implements SignupUserDataAccessInterface, LoginUserDataAccessInterface { +public class FileUserDataAccessObject implements SignupUserDataAccessInterface, LoginUserDataAccessInterface, ClearUserDataAccessInterface { private final File csvFile; @@ -85,6 +87,30 @@ private void save() { } } + @Override + public String delete() { + StringBuilder deleted = new StringBuilder(); + ArrayList accountsToDelete = new ArrayList<>(accounts.keySet()); + + for (String account : accountsToDelete) { + accounts.remove(account); + // start a new line after each user + deleted.append(account).append("\n"); + } + + BufferedWriter writer; + try { + writer = new BufferedWriter(new FileWriter(csvFile)); + writer.flush(); + + writer.close(); + + } catch (IOException e) { + throw new RuntimeException(e); + } + return deleted.toString(); + } + /** * Return whether a user exists with username identifier. diff --git a/src/interface_adapter/clear_users/ClearController.java b/src/interface_adapter/clear_users/ClearController.java index 218de853..65774af3 100644 --- a/src/interface_adapter/clear_users/ClearController.java +++ b/src/interface_adapter/clear_users/ClearController.java @@ -1,5 +1,15 @@ package interface_adapter.clear_users; -// TODO Complete me +import use_case.clear_users.ClearInputBoundary; + public class ClearController { + final ClearInputBoundary clearUseCaseInteractor; + + public ClearController(ClearInputBoundary clearUseCaseInteractor) { + this.clearUseCaseInteractor = clearUseCaseInteractor; + } + + public void execute() { + clearUseCaseInteractor.execute(); + } } diff --git a/src/interface_adapter/clear_users/ClearPresenter.java b/src/interface_adapter/clear_users/ClearPresenter.java index dbe0c5c7..1c8cde39 100644 --- a/src/interface_adapter/clear_users/ClearPresenter.java +++ b/src/interface_adapter/clear_users/ClearPresenter.java @@ -1,6 +1,35 @@ package interface_adapter.clear_users; -// TODO Complete me -public class ClearPresenter { +import interface_adapter.ViewManagerModel; +import use_case.clear_users.ClearOutputBoundary; +import use_case.clear_users.ClearOutputData; + +public class ClearPresenter implements ClearOutputBoundary { + private final ClearViewModel clearViewModel; + private ViewManagerModel viewManagerModel; + + public ClearPresenter(ViewManagerModel viewManagerModel, + ClearViewModel clearViewModel) { + this.viewManagerModel = viewManagerModel; + this.clearViewModel = clearViewModel; + } + + @Override + public void prepareSuccessView(ClearOutputData users) { + ClearState clearState = clearViewModel.getState(); + clearState.setClear(users.getUsernames()); + this.clearViewModel.setState(clearState); + this.clearViewModel.firePropertyChanged(); + + this.viewManagerModel.setActiveView(clearViewModel.getViewName()); + this.viewManagerModel.firePropertyChanged(); + } + + @Override + public void prepareFailView(String error) { + ClearState clearState = clearViewModel.getState(); + clearState.setClearError(error); + clearViewModel.firePropertyChanged(); + } } diff --git a/src/interface_adapter/clear_users/ClearState.java b/src/interface_adapter/clear_users/ClearState.java index e168aeb8..2d161e18 100644 --- a/src/interface_adapter/clear_users/ClearState.java +++ b/src/interface_adapter/clear_users/ClearState.java @@ -1,6 +1,30 @@ package interface_adapter.clear_users; -// TODO Complete me public class ClearState { + private String clear = null; + private String clearError = null; + + public ClearState(ClearState copy) { + clear = copy.clear; + clearError = copy.clearError; + } + + public ClearState() {} + + public String getClear() { + return clear; + } + + public String getClearError() { + return clearError; + } + + public void setClear(String clear) { + this.clear = clear; + } + + public void setClearError(String clearError) { + this.clearError = clearError; + } } diff --git a/src/interface_adapter/clear_users/ClearViewModel.java b/src/interface_adapter/clear_users/ClearViewModel.java index 208b082f..c3d1fb05 100644 --- a/src/interface_adapter/clear_users/ClearViewModel.java +++ b/src/interface_adapter/clear_users/ClearViewModel.java @@ -1,6 +1,30 @@ package interface_adapter.clear_users; -// TODO Complete me +import interface_adapter.ViewModel; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; -public class ClearViewModel { +public class ClearViewModel extends ViewModel{ + private ClearState state = new ClearState(); + public ClearViewModel() { + super("clear"); + } + + public void setState(ClearState state) { + this.state = state; + } + private final PropertyChangeSupport support = new PropertyChangeSupport(this); + + + public void firePropertyChanged() { + support.firePropertyChange("clear", null, this.state); + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + support.addPropertyChangeListener(listener); + } + + public ClearState getState() { + return state; + } } diff --git a/src/use_case/clear_users/ClearInputBoundary.java b/src/use_case/clear_users/ClearInputBoundary.java index 3d7f9b87..a42a2368 100644 --- a/src/use_case/clear_users/ClearInputBoundary.java +++ b/src/use_case/clear_users/ClearInputBoundary.java @@ -1,6 +1,7 @@ package use_case.clear_users; -// TODO Complete me +// Complete me public interface ClearInputBoundary { + void execute(); } diff --git a/src/use_case/clear_users/ClearInputData.java b/src/use_case/clear_users/ClearInputData.java index 9aec462e..16fb2020 100644 --- a/src/use_case/clear_users/ClearInputData.java +++ b/src/use_case/clear_users/ClearInputData.java @@ -1,6 +1,6 @@ package use_case.clear_users; -// TODO Complete me public class ClearInputData { + // no input } diff --git a/src/use_case/clear_users/ClearInteractor.java b/src/use_case/clear_users/ClearInteractor.java index df0ffa34..a1ce6057 100644 --- a/src/use_case/clear_users/ClearInteractor.java +++ b/src/use_case/clear_users/ClearInteractor.java @@ -1,6 +1,19 @@ package use_case.clear_users; -// TODO Complete me -public class ClearInteractor { +public class ClearInteractor implements ClearInputBoundary{ + final ClearUserDataAccessInterface userDataAccessObject; + final ClearOutputBoundary clearPresenter; + + public ClearInteractor(ClearUserDataAccessInterface userDataAccessInterface, ClearOutputBoundary clearOutputBoundary) { + this.userDataAccessObject = userDataAccessInterface; + this.clearPresenter = clearOutputBoundary; + } + + @Override + public void execute() { + String usernames = userDataAccessObject.delete(); + ClearOutputData clearOutputData = new ClearOutputData(usernames, false); + clearPresenter.prepareSuccessView(clearOutputData); + } } diff --git a/src/use_case/clear_users/ClearOutputBoundary.java b/src/use_case/clear_users/ClearOutputBoundary.java index 5972535c..d9894395 100644 --- a/src/use_case/clear_users/ClearOutputBoundary.java +++ b/src/use_case/clear_users/ClearOutputBoundary.java @@ -1,6 +1,7 @@ package use_case.clear_users; -// TODO Complete me - public interface ClearOutputBoundary { + + void prepareSuccessView(ClearOutputData user); + void prepareFailView(String error); } diff --git a/src/use_case/clear_users/ClearOutputData.java b/src/use_case/clear_users/ClearOutputData.java index e06df88f..da9ca2b4 100644 --- a/src/use_case/clear_users/ClearOutputData.java +++ b/src/use_case/clear_users/ClearOutputData.java @@ -1,6 +1,12 @@ package use_case.clear_users; +public class ClearOutputData { + private final String usernames; + private boolean useCaseFailed; -// TODO Complete me + public ClearOutputData(String usernames, boolean useCaseFailed) { + this.usernames = usernames; + this.useCaseFailed = useCaseFailed; + } -public class ClearOutputData { + public String getUsernames() { return usernames; } } diff --git a/src/use_case/clear_users/ClearUserDataAccessInterface.java b/src/use_case/clear_users/ClearUserDataAccessInterface.java index 4035e814..cca851b8 100644 --- a/src/use_case/clear_users/ClearUserDataAccessInterface.java +++ b/src/use_case/clear_users/ClearUserDataAccessInterface.java @@ -1,6 +1,5 @@ package use_case.clear_users; -// TODO Complete me - public interface ClearUserDataAccessInterface { + String delete(); } diff --git a/src/view/SignupView.java b/src/view/SignupView.java index 3c54132d..9cc38ab7 100644 --- a/src/view/SignupView.java +++ b/src/view/SignupView.java @@ -1,5 +1,8 @@ package view; +import interface_adapter.clear_users.ClearController; +import interface_adapter.clear_users.ClearState; +import interface_adapter.clear_users.ClearViewModel; import interface_adapter.signup.SignupController; import interface_adapter.signup.SignupState; import interface_adapter.signup.SignupViewModel; @@ -21,18 +24,21 @@ public class SignupView extends JPanel implements ActionListener, PropertyChange private final JPasswordField passwordInputField = new JPasswordField(15); private final JPasswordField repeatPasswordInputField = new JPasswordField(15); private final SignupController signupController; - + private final ClearController clearController; private final JButton signUp; private final JButton cancel; - // TODO Note: this is the new JButton for clearing the users file + // Note: this is the new JButton for clearing the users file private final JButton clear; - public SignupView(SignupController controller, SignupViewModel signupViewModel) { + public SignupView(SignupController controller, SignupViewModel signupViewModel, ClearController clearController, ClearViewModel clearViewModel) { this.signupController = controller; this.signupViewModel = signupViewModel; + this.clearController = clearController; + signupViewModel.addPropertyChangeListener(this); + clearViewModel.addPropertyChangeListener(this); JLabel title = new JLabel(SignupViewModel.TITLE_LABEL); title.setAlignmentX(Component.CENTER_ALIGNMENT); @@ -50,10 +56,11 @@ public SignupView(SignupController controller, SignupViewModel signupViewModel) cancel = new JButton(SignupViewModel.CANCEL_BUTTON_LABEL); buttons.add(cancel); - // TODO Note: the following line instantiates the "clear" button; it uses + // Note: the following line instantiates the "clear" button; it uses // a CLEAR_BUTTON_LABEL constant which is defined in the SignupViewModel class. // You need to add this "clear" button to the "buttons" panel. clear = new JButton(SignupViewModel.CLEAR_BUTTON_LABEL); + buttons.add(clear); signUp.addActionListener( // This creates an anonymous subclass of ActionListener and instantiates it. @@ -72,13 +79,16 @@ public void actionPerformed(ActionEvent evt) { } ); - // TODO Add the body to the actionPerformed method of the action listener below + // Add the body to the actionPerformed method of the action listener below // for the "clear" button. You'll need to write the controller before // you can complete this. clear.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { + if (e.getSource().equals(clear)) + //SignupState currentState = signupViewModel.getState(); + clearController.execute(); } } @@ -169,9 +179,17 @@ public void actionPerformed(ActionEvent evt) { @Override public void propertyChange(PropertyChangeEvent evt) { - SignupState state = (SignupState) evt.getNewValue(); - if (state.getUsernameError() != null) { - JOptionPane.showMessageDialog(this, state.getUsernameError()); + if (evt.getPropertyName().equals("state")) { + SignupState state = (SignupState) evt.getNewValue(); + if (state.getUsernameError() != null) { + JOptionPane.showMessageDialog(this, state.getUsernameError()); + } + } + else if (evt.getPropertyName().equals("clear")) { + ClearState state = (ClearState) evt.getNewValue(); + if (state.getClear() != null) { + JOptionPane.showMessageDialog(this, state.getClear()); + } } } } \ No newline at end of file diff --git a/test/view/ClearUsersTest.java b/test/view/ClearUsersTest.java new file mode 100644 index 00000000..a19244b5 --- /dev/null +++ b/test/view/ClearUsersTest.java @@ -0,0 +1,219 @@ +package view; + +import app.Main; +import data_access.FileUserDataAccessObject; +import entity.CommonUserFactory; +import entity.UserFactory; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.time.LocalDateTime; + +import static org.junit.Assert.*; + + +public class ClearUsersTest { + + + static String message = ""; + static boolean popUpDiscovered = false; + + /** + * ensures there are at least 2 users in the CSV file for testing purposes + */ + public void addTwoUsers() { + UserFactory uf = new CommonUserFactory(); + FileUserDataAccessObject fudao; + try { + fudao = new FileUserDataAccessObject("./users.csv", uf); + } catch (IOException e) { + throw new RuntimeException(e); + } + fudao.save(uf.create("user1", "password1", LocalDateTime.now())); + fudao.save(uf.create("user2", "password2", LocalDateTime.now())); + } + + + public JButton getButton() { + JFrame app = null; + Window[] windows = Window.getWindows(); + for (Window window : windows) { + if (window instanceof JFrame) { + app = (JFrame) window; + } + } + + assertNotNull(app); // found the window? + + Component root = app.getComponent(0); + + Component cp = ((JRootPane) root).getContentPane(); + + JPanel jp = (JPanel) cp; + + JPanel jp2 = (JPanel) jp.getComponent(0); + + SignupView sv = (SignupView) jp2.getComponent(0); + + JPanel buttons = (JPanel) sv.getComponent(4); + + return (JButton) buttons.getComponent(2); // this should be the clear button + } + + /** + * + * Test that the Clear button is present and where it is expected to be + */ + @org.junit.Test + public void testClearButtonPresent() { + Main.main(null); + JButton button = getButton(); + assert(button.getText().equals("Clear")); + } + + /** + * + * Test that pressing the Clear button deletes all users. This test first + * adds two users to the CSV file, then starts the program, then clicks the Clear button, + * and then checks that the file's length has decreased. + */ + @org.junit.Test + public void testClearUsersDeletedUsersFromFile() { + + addTwoUsers(); + Main.main(null); + JButton button = getButton(); + + // since clicking the button should end up displaying a JDialog to the user to report the + // result, we set a timer, which will execute code necessary to complete the testing. + createCloseTimer().start(); + + button.doClick(); + + // will continue execution here after the JDialog is closed + + // users.csv format + //username, password,timestamp (in format returned by a call like LocalDateTime.now()) + //example + //user1,pass1,2023-10-12T14:46:26.733518 + + //check the users.csv file to ensure the users are gone + try { + int lines = countLines(); + System.out.println("lines left = " + lines); + assert(lines <= 1); + + } catch (IOException e) { + throw new RuntimeException(e); + } + + + } + + /** + * + * This test is the same as above, but it additionally checks that the JDialog contains the names of + * all users deleted from the file. + */ + @org.junit.Test + public void testClearUsersPopUpShown() { + + addTwoUsers(); + popUpDiscovered = false; + + Main.main(null); + JFrame app = null; + + JButton button = getButton(); + + + // since clicking the button should end up displaying a JDialog to the user to report the + // result, we set a timer, which will execute code necessary to complete the testing. + createCloseTimer().start(); + + //click the button + button.doClick(); + + // will continue execution here after the JDialog is closed + + // confirm a popUp was discovered + assert(popUpDiscovered); + System.out.println("popup was detected successfully."); + + } + + @org.junit.Test + public void testClearUsersReturnedUsersDeleted() throws InterruptedException { + + addTwoUsers(); + message = ""; + + Main.main(null); + + JButton button = getButton(); + + // since clicking the button should end up displaying a JDialog to the user to report the + // result, we set a timer, which will execute code necessary to complete the testing. + createCloseTimer().start(); + + //click the button + button.doClick(); + + // will continue execution here after the JDialog is closed + + // check the message + assert(message.contains("user1") && message.contains("user2")); + } + + private Timer createCloseTimer() { + ActionListener close = new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + + Window[] windows = Window.getWindows(); + for (Window window : windows) { + + if (window instanceof JDialog) { + + JDialog dialog = (JDialog)window; + + // this ignores old dialogs + if (dialog.isVisible()) { + String s = ((JOptionPane) ((BorderLayout) dialog.getRootPane() + .getContentPane().getLayout()).getLayoutComponent(BorderLayout.CENTER)).getMessage().toString(); + System.out.println("message = " + s); + + // store the information we got from the JDialog + ClearUsersTest.message = s; + ClearUsersTest.popUpDiscovered = true; + + System.out.println("disposing of..." + window.getClass()); + window.dispose(); + } + } + } + } + + }; + + Timer t = new Timer(1000, close); + t.setRepeats(false); + return t; + } + + private static int countLines() throws IOException { + BufferedReader reader = new BufferedReader(new FileReader("users.csv")); + int lineCount = 0; + while (reader.readLine() != null) { + lineCount++; + } + return lineCount; + } + +}