001package edu.pdx.cs.joy.grader.poa;
002
003import com.google.common.eventbus.Subscribe;
004import edu.pdx.cs.joy.grader.gradebook.Assignment;
005import edu.pdx.cs.joy.grader.gradebook.Grade;
006import edu.pdx.cs.joy.grader.gradebook.GradeBook;
007import edu.pdx.cs.joy.grader.gradebook.Student;
008import org.junit.jupiter.api.BeforeEach;
009import org.junit.jupiter.api.Test;
010import org.mockito.ArgumentCaptor;
011import org.mockito.InOrder;
012
013import java.time.LocalDateTime;
014
015import static org.hamcrest.MatcherAssert.assertThat;
016import static org.hamcrest.Matchers.equalTo;
017import static org.hamcrest.Matchers.nullValue;
018import static org.mockito.ArgumentMatchers.eq;
019import static org.mockito.Mockito.*;
020
021public class POAGradePresenterTest extends POASubmissionTestCase {
022
023  private final double scoreForPOAWithGrade = 1.5;
024  private POAGradeView view;
025  private POASubmission submission;
026  private Student student;
027  private Assignment assignment;
028  private GradeBook book;
029  private POAGradePresenter presenter;
030  private POASubmission submissionForPOAWithGrade;
031  private Assignment assignmentWithPOAGrade;
032
033  @BeforeEach
034  @Override
035  public void setUp() {
036    super.setUp();
037
038    this.view = mock(POAGradeView.class);
039
040    presenter = new POAGradePresenter(this.bus, this.view);
041    submission = createPOASubmission("Subject", "Submitter", LocalDateTime.now());
042    student = new Student("id");
043    assignment = new Assignment("assignment", 1.0);
044
045    book = new GradeBook("POAGradePresenterTest");
046    book.addAssignment(assignment);
047    book.addStudent(student);
048
049    submissionForPOAWithGrade = createPOASubmission("POA for Project with Grade", "Submitter", LocalDateTime.now());
050    assignmentWithPOAGrade = new Assignment("assignmentWithPOAGrade", 2.0);
051    book.addAssignment(assignmentWithPOAGrade);
052    student.setGrade(assignmentWithPOAGrade, scoreForPOAWithGrade);
053  }
054
055  @Test
056  public void onlySubmissionSelectedDisablesView() {
057    this.bus.post(new POASubmissionSelected(submission));
058
059    verifyIsLate(false);
060    verify(this.view).setIsEnabled(eq(false));
061  }
062
063  private void verifyIsLate(boolean isLate) {
064    verifyIsLate(1, isLate);
065  }
066
067  @Test
068  public void onlyStudentSelectedDisablesView() {
069    this.bus.post(new StudentSelectedEvent(student));
070
071    verifyIsLate(false);
072    verify(this.view).setIsEnabled(eq(false));
073  }
074
075  @Test
076  public void onlyAssignmentSelectedDisablesView() {
077    this.bus.post(new GradeBookLoaded(book));
078    this.bus.post(new AssignmentSelectedEvent(assignment));
079
080    verifyIsLate(false);
081    verify(this.view).setIsEnabled(eq(false));
082  }
083
084  @Test
085  public void submissionStudentAndAssignmentEnablesView() {
086    postEventsToBus();
087
088    verify(this.view).setIsEnabled(true);
089  }
090
091  @Test
092  public void lateSubmissionIsLateInView() {
093    assignment.setDueDate(LocalDateTime.now().minusDays(5));
094
095    postEventsToBus();
096
097    verify(this.view).setIsEnabled(true);
098    verifyIsLate(true);
099  }
100
101  @Test
102  public void onTimeSubmissionIsNotLateInView() {
103    assignment.setDueDate(LocalDateTime.now().plusDays(5));
104
105    postEventsToBus();
106
107    verify(this.view).setIsEnabled(true);
108    verifyIsLate(3, false);
109  }
110
111  @Test
112  public void assignmentWithNoDueDateIsNotLate() {
113    assignment.setDueDate(null);
114
115    postEventsToBus();
116
117    verify(this.view).setIsEnabled(true);
118    int wantedNumberOfInvocations = 3;
119    verifyIsLate(wantedNumberOfInvocations, false);
120  }
121
122  private void verifyIsLate(int wantedNumberOfInvocations, boolean isLate) {
123    verify(this.view, times(wantedNumberOfInvocations)).setIsLate(eq(isLate));
124    assertThat(this.presenter.isLate(), equalTo(isLate));
125  }
126
127  @Test
128  public void latePOAIsStoredInPresenterWhenMarkedLateInView() {
129    ArgumentCaptor<POAGradeView.IsLateHandler> handler = ArgumentCaptor.forClass(POAGradeView.IsLateHandler.class);
130    verify(this.view).addIsLateHandler(handler.capture());
131
132    assignment.setDueDate(LocalDateTime.now().plusDays(5));
133
134    postEventsToBus();
135
136    verify(this.view).setIsEnabled(true);
137    verifyIsLate(3, false);
138
139    handler.getValue().setIsLate(true);
140    verifyIsLate(true);
141
142    handler.getValue().setIsLate(false);
143    verifyIsLate(4, false);
144  }
145
146  @Test
147  public void assignmentTotalPointsDisplayedInViewWhenAssignmentSelected() {
148    double points = 2.75;
149    assignment.setPoints(points);
150    this.bus.post(new GradeBookLoaded(book));
151    this.bus.post(new AssignmentSelectedEvent(assignment));
152
153    verify(this.view).setTotalPoints("2.75");
154  }
155
156  @Test
157  public void scoreInViewIsInErrorWhenValueIsNotAnInteger() {
158    ArgumentCaptor<POAGradeView.ScoreValueHandler> handler = ArgumentCaptor.forClass(POAGradeView.ScoreValueHandler.class);
159    verify(view).addScoreValueHandler(handler.capture());
160
161    handler.getValue().scoreValue("Not an Integer");
162
163    verify(this.view).setErrorInScore(true);
164    assertThat(presenter.getScore(), nullValue());
165  }
166
167  @Test
168  public void setPresentersScoreFromView() {
169    ArgumentCaptor<POAGradeView.ScoreValueHandler> handler = ArgumentCaptor.forClass(POAGradeView.ScoreValueHandler.class);
170    verify(view).addScoreValueHandler(handler.capture());
171
172    this.bus.post(new GradeBookLoaded(book));
173    this.bus.post(new AssignmentSelectedEvent(assignment.setPoints(2.0d)));
174    reset(this.view);
175
176    handler.getValue().scoreValue("1.75");
177
178    verify(this.view).setErrorInScore(false);
179    assertThat(presenter.getScore(), equalTo(1.75));
180  }
181
182  @Test
183  public void negativeScoreIsAnError() {
184    ArgumentCaptor<POAGradeView.ScoreValueHandler> handler = ArgumentCaptor.forClass(POAGradeView.ScoreValueHandler.class);
185    verify(view).addScoreValueHandler(handler.capture());
186
187    handler.getValue().scoreValue("-4.3");
188
189    verify(this.view).setErrorInScore(true);
190    assertThat(presenter.getScore(), nullValue());
191  }
192
193  @Test
194  public void errorStateShouldBeClearedWhenNewSubmissionIsSelected() {
195    ArgumentCaptor<POAGradeView.ScoreValueHandler> handler = ArgumentCaptor.forClass(POAGradeView.ScoreValueHandler.class);
196    verify(view).addScoreValueHandler(handler.capture());
197
198    handler.getValue().scoreValue("-4.3");
199
200    verify(this.view).setErrorInScore(true);
201
202    this.bus.post(new POASubmissionSelected(submission));
203
204    verify(this.view).setErrorInScore(false);
205  }
206
207  @Test
208  public void scoreShouldBeClearedWhenNewSubmissionIsSelected() {
209    ArgumentCaptor<POAGradeView.ScoreValueHandler> handler = ArgumentCaptor.forClass(POAGradeView.ScoreValueHandler.class);
210    verify(view).addScoreValueHandler(handler.capture());
211
212    handler.getValue().scoreValue("-4.3");
213
214    this.bus.post(new POASubmissionSelected(submission));
215
216    verify(this.view).setScore("");
217    assertThat(this.presenter.getScore(), nullValue());
218  }
219
220  @Test
221  public void scoreAndErrorShouldBeClearedWhenNewStudentIsSelected() {
222    ArgumentCaptor<POAGradeView.ScoreValueHandler> handler = ArgumentCaptor.forClass(POAGradeView.ScoreValueHandler.class);
223    verify(view).addScoreValueHandler(handler.capture());
224
225    handler.getValue().scoreValue("-4.3");
226    verify(this.view).setErrorInScore(true);
227
228    this.bus.post(new StudentSelectedEvent(student));
229
230    verify(this.view).setErrorInScore(false);
231    verify(this.view, times(2)).setScore("");
232    assertThat(this.presenter.getScore(), nullValue());
233  }
234
235  @Test
236  public void scoreAndErrorShouldBeClearedWhenNewAssignmentIsSelected() {
237    ArgumentCaptor<POAGradeView.ScoreValueHandler> handler = ArgumentCaptor.forClass(POAGradeView.ScoreValueHandler.class);
238    verify(view).addScoreValueHandler(handler.capture());
239
240    handler.getValue().scoreValue("-4.3");
241    verify(this.view).setErrorInScore(true);
242
243    this.bus.post(new GradeBookLoaded(book));
244    this.bus.post(new AssignmentSelectedEvent(assignment));
245
246    verify(this.view, times(2)).setErrorInScore(false);
247    verify(this.view).setScore(POAGradePresenter.formatTotalPoints(assignment.getPoints()));
248    assertThat(this.presenter.getScore(), equalTo(assignment.getPoints()));
249  }
250
251  @Test
252  public void scoreDefaultsToTotalPoints() {
253    postEventsToBus();
254
255    verify(this.view).setScore(POAGradePresenter.formatTotalPoints(assignment.getPoints()));
256  }
257
258  private void postEventsToBus() {
259    this.bus.post(new GradeBookLoaded(book));
260    this.bus.post(new POASubmissionSelected(submission));
261    this.bus.post(new StudentSelectedEvent(student));
262    this.bus.post(new AssignmentSelectedEvent(assignment));
263  }
264
265  @Test
266  public void aScoreOfEmptyStringShouldNotBeAnError() {
267    ArgumentCaptor<POAGradeView.ScoreValueHandler> handler = ArgumentCaptor.forClass(POAGradeView.ScoreValueHandler.class);
268    verify(view).addScoreValueHandler(handler.capture());
269
270    handler.getValue().scoreValue("");
271
272    verify(this.view).setErrorInScore(false);
273    assertThat(presenter.getScore(), nullValue());
274  }
275
276  @Test
277  public void aScoreThatIsGreaterThanTheTotalPointsShouldBeAnError() {
278    ArgumentCaptor<POAGradeView.ScoreValueHandler> handler = ArgumentCaptor.forClass(POAGradeView.ScoreValueHandler.class);
279    verify(view).addScoreValueHandler(handler.capture());
280
281    this.bus.post(new GradeBookLoaded(book));
282    this.bus.post(new AssignmentSelectedEvent(assignment));
283
284    handler.getValue().scoreValue(String.valueOf(assignment.getPoints() + 1.0d));
285
286    verify(this.view).setErrorInScore(true);
287    assertThat(presenter.getScore(), nullValue());
288  }
289
290  @Test
291  public void scoreDefaultsToCurrentGrade() {
292    double score = 0.75;
293    student.setGrade(assignment, new Grade(assignment, score));
294
295    postEventsToBus();
296
297    verify(this.view).setScore(POAGradePresenter.formatTotalPoints(score));
298  }
299
300  @Test
301  public void scoreDefaultsToTotalPointsWhenNoStudentIsSelected() {
302    this.bus.post(new GradeBookLoaded(book));
303    this.bus.post(new AssignmentSelectedEvent(assignment));
304
305    verify(this.view).setScore(POAGradePresenter.formatTotalPoints(assignment.getPoints()));
306  }
307
308  @Test
309  public void scoreHasNotBeenRecordedWhenNoStudentIsSelected() {
310    this.bus.post(new GradeBookLoaded(book));
311    this.bus.post(new AssignmentSelectedEvent(assignment));
312
313    verify(this.view, times(2)).setScoreHasBeenRecorded(false);
314  }
315
316  @Test
317  public void scoreHasNotBeenRecordedWhenThereIsNoGradeInGradeBook() {
318    assertThat(student.getGrade(assignment), nullValue());
319
320    postEventsToBus();
321
322    verify(this.view, times(5)).setScoreHasBeenRecorded(false);
323  }
324
325  @Test
326  public void scoreHasBeenRecordedForSelectedStudentWhenItIsInGradeBook() {
327    double score = 0.75;
328    student.setGrade(assignment, new Grade(assignment, score));
329
330    postEventsToBus();
331
332    verify(this.view).setScoreHasBeenRecorded(true);
333  }
334
335  @Test
336  public void scoreHasBeenRecordedShouldBeFalseWhenNewSubmissionIsSelected() {
337    this.bus.post(new POASubmissionSelected(submission));
338
339    verify(this.view).setScoreHasBeenRecorded(false);
340  }
341
342  @Test
343  public void scoreHasBeenRecordedShouldBeFalseWhenNewStudentIsSelected() {
344    this.bus.post(new StudentSelectedEvent(student));
345
346    verify(this.view, times(2)).setScoreHasBeenRecorded(false);
347  }
348
349  @Test
350  public void recordingGradeInViewPublishesRecordGradeMessage() {
351    ArgumentCaptor<POAGradeView.ScoreValueHandler> scoreHandler = ArgumentCaptor.forClass(POAGradeView.ScoreValueHandler.class);
352    verify(this.view).addScoreValueHandler(scoreHandler.capture());
353
354    ArgumentCaptor<POAGradeView.RecordGradeHandler> recordGrade = ArgumentCaptor.forClass(POAGradeView.RecordGradeHandler.class);
355    verify(this.view).addRecordGradeHandler(recordGrade.capture());
356
357    RecordGradeEventHandler eventHandler = mock(RecordGradeEventHandler.class);
358    this.bus.register(eventHandler);
359
360    this.assignment.setDueDate(LocalDateTime.now().minusDays(5));
361    postEventsToBus();
362
363    double score = 0.75;
364    scoreHandler.getValue().scoreValue(POAGradePresenter.formatTotalPoints(score));
365    recordGrade.getValue().recordGrade();
366
367    ArgumentCaptor<RecordGradeEvent> eventCaptor = ArgumentCaptor.forClass(RecordGradeEvent.class);
368    verify(eventHandler).handleRecordGradeEvent(eventCaptor.capture());
369
370    RecordGradeEvent event = eventCaptor.getValue();
371    assertThat(event.getScore(), equalTo(score));
372    assertThat(event.getStudent(), equalTo(this.student));
373    assertThat(event.getAssignment(), equalTo(this.assignment));
374    assertThat(event.isLate(), equalTo(true));
375
376    verify(this.view).setScoreHasBeenRecorded(true);
377  }
378
379  @Test
380  public void recordingGradeInViewPublishesDisplayNextPOAMessage() {
381    ArgumentCaptor<POAGradeView.ScoreValueHandler> scoreHandler = ArgumentCaptor.forClass(POAGradeView.ScoreValueHandler.class);
382    verify(this.view).addScoreValueHandler(scoreHandler.capture());
383
384    ArgumentCaptor<POAGradeView.RecordGradeHandler> recordGrade = ArgumentCaptor.forClass(POAGradeView.RecordGradeHandler.class);
385    verify(this.view).addRecordGradeHandler(recordGrade.capture());
386
387    DisplayNextPOAEventHandler eventHandler = mock(DisplayNextPOAEventHandler.class);
388    this.bus.register(eventHandler);
389
390    this.assignment.setDueDate(LocalDateTime.now().minusDays(5));
391    postEventsToBus();
392
393    double score = 0.75;
394    scoreHandler.getValue().scoreValue(POAGradePresenter.formatTotalPoints(score));
395    recordGrade.getValue().recordGrade();
396
397    ArgumentCaptor<SelectNextPOAEvent> eventCaptor = ArgumentCaptor.forClass(SelectNextPOAEvent.class);
398    verify(eventHandler).handleDisplayNextPOAEvent(eventCaptor.capture());
399  }
400
401  private interface RecordGradeEventHandler {
402    @Subscribe
403    void handleRecordGradeEvent(RecordGradeEvent event);
404  }
405
406  @Test
407  public void currentGradeIsDisplayedWhenGradedSubmissionIsSelected() {
408    this.bus.post(new GradeBookLoaded(book));
409    this.bus.post(new POASubmissionSelected(submissionForPOAWithGrade));
410    verify(this.view).setScore("");
411
412    this.bus.post(new StudentSelectedEvent(student));
413    verify(this.view, times(3)).setScore("");
414
415    this.bus.post(new AssignmentSelectedEvent(assignmentWithPOAGrade));
416
417    InOrder inOrder = inOrder(this.view);
418    inOrder.verify(this.view).setScore(POAGradePresenter.formatTotalPoints(scoreForPOAWithGrade));
419  }
420
421  @Test
422  public void currentGradeIsDisplayedWhenStudentIsSelected() {
423    this.bus.post(new GradeBookLoaded(book));
424    this.bus.post(new POASubmissionSelected(submissionForPOAWithGrade));
425    verify(this.view, times(1)).setScore("");
426
427    this.bus.post(new StudentSelectedEvent(student));
428    verify(this.view, times(3)).setScore("");
429
430    this.bus.post(new AssignmentSelectedEvent(assignmentWithPOAGrade));
431
432    InOrder inOrder = inOrder(this.view);
433    inOrder.verify(this.view).setScore(POAGradePresenter.formatTotalPoints(scoreForPOAWithGrade));
434
435    this.bus.post(new StudentSelectedEvent(student));
436    inOrder.verify(this.view).setScore(POAGradePresenter.formatTotalPoints(scoreForPOAWithGrade));
437  }
438
439  @Test
440  public void unknownStudentClearsUI() {
441    postEventsToBus();
442    verify(this.view, times(4)).setScore("");
443    verifyIsLate(3, false);
444
445    this.bus.post(new StudentSelectedEvent(null));
446
447    verify(this.view, times(5)).setScore("");
448    verifyIsLate(4, false);
449  }
450
451  private interface DisplayNextPOAEventHandler {
452    @Subscribe
453    void handleDisplayNextPOAEvent(SelectNextPOAEvent event);
454  }
455}