001package edu.pdx.cs.joy.grader;
002
003import edu.pdx.cs.joy.grader.gradebook.Assignment;
004import edu.pdx.cs.joy.grader.gradebook.Grade;
005import edu.pdx.cs.joy.grader.gradebook.GradeBook;
006import edu.pdx.cs.joy.grader.gradebook.Student;
007import org.hamcrest.MatcherAssert;
008import org.junit.jupiter.api.Test;
009
010import java.io.StringWriter;
011import java.util.Arrays;
012import java.util.Collection;
013import java.util.List;
014import java.util.stream.Collectors;
015
016import static org.hamcrest.MatcherAssert.assertThat;
017import static org.hamcrest.Matchers.*;
018
019public class ProjectTimeEstimatesSummaryTest {
020
021  private void noteSubmission(Student student, Assignment project, Double... estimates) {
022    Grade grade = new Grade(project, Grade.NO_GRADE);
023    for (Double estimate : estimates) {
024      Grade.SubmissionInfo submission = new Grade.SubmissionInfo();
025      if (estimate != null) {
026        submission.setEstimatedHours(estimate);
027      }
028      grade.noteSubmission(submission);
029    }
030    student.setGrade(project, grade);
031  }
032
033  private Student addStudent(GradeBook book, String studentName) {
034    Student student = new Student(studentName);
035    book.addStudent(student);
036    return student;
037  }
038
039  private Assignment addProject(GradeBook book, Assignment.ProjectType projectType) {
040    return addProject(book, "project", projectType);
041  }
042
043  private Assignment addProject(GradeBook book, String projectName, Assignment.ProjectType projectType) {
044    Assignment project = new Assignment(projectName, 1.0)
045      .setProjectType(projectType);
046    book.addAssignment(project);
047    return project;
048  }
049
050  @Test
051  void oneSubmittedProjectHasSummaryWithOneSubmission() {
052    GradeBook book = new GradeBook("test");
053
054    Assignment.ProjectType projectType = Assignment.ProjectType.APP_CLASSES;
055
056    noteSubmission(addStudent(book, "student"), addProject(book, projectType), 10.0);
057
058    ProjectTimeEstimatesSummary summary = new ProjectTimeEstimatesSummary();
059    ProjectTimeEstimatesSummary.TimeEstimatesSummaries summaries = summary.getTimeEstimateSummaries(book);
060    ProjectTimeEstimatesSummary.TimeEstimatesSummary estimates = summaries.getTimeEstimateSummary(projectType);
061    assertThat(estimates.getCount(), equalTo(1));
062  }
063
064  @Test
065  void maximumEstimateByStudentIsConsidered() {
066    GradeBook book = new GradeBook("test");
067
068    Assignment.ProjectType projectType = Assignment.ProjectType.APP_CLASSES;
069
070    double maximumEstimate = 11.0;
071    noteSubmission(addStudent(book, "student"), addProject(book, projectType), 10.0, maximumEstimate);
072
073    ProjectTimeEstimatesSummary summary = new ProjectTimeEstimatesSummary();
074    ProjectTimeEstimatesSummary.TimeEstimatesSummaries summaries = summary.getTimeEstimateSummaries(book);
075    ProjectTimeEstimatesSummary.TimeEstimatesSummary estimates = summaries.getTimeEstimateSummary(projectType);
076    assertThat(estimates.getCount(), equalTo(1));
077    assertThat(estimates.getMaximum(), equalTo(maximumEstimate));
078
079  }
080
081  @Test
082  void maximumFromMultipleStudents() {
083    GradeBook book = new GradeBook("test");
084
085    Assignment.ProjectType projectType = Assignment.ProjectType.APP_CLASSES;
086
087    double maximumEstimate = 11.0;
088    Assignment project = addProject(book, projectType);
089    noteSubmission(addStudent(book, "student1"), project, 10.0, maximumEstimate);
090    noteSubmission(addStudent(book, "student2"), project, 9.0);
091
092    ProjectTimeEstimatesSummary summary = new ProjectTimeEstimatesSummary();
093    ProjectTimeEstimatesSummary.TimeEstimatesSummaries summaries = summary.getTimeEstimateSummaries(book);
094    ProjectTimeEstimatesSummary.TimeEstimatesSummary estimates = summaries.getTimeEstimateSummary(projectType);
095    assertThat(estimates.getCount(), equalTo(2));
096    assertThat(estimates.getMaximum(), equalTo(maximumEstimate));
097  }
098
099  @Test
100  void minimumFromMultipleStudents() {
101    GradeBook book = new GradeBook("test");
102
103    Assignment.ProjectType projectType = Assignment.ProjectType.APP_CLASSES;
104
105    double minimumEstimate = 9.0;
106    Assignment project = addProject(book, projectType);
107    noteSubmission(addStudent(book, "student1"), project, 10.0, 11.0);
108    noteSubmission(addStudent(book, "student2"), project, minimumEstimate);
109
110    ProjectTimeEstimatesSummary summary = new ProjectTimeEstimatesSummary();
111    ProjectTimeEstimatesSummary.TimeEstimatesSummaries summaries = summary.getTimeEstimateSummaries(book);
112    ProjectTimeEstimatesSummary.TimeEstimatesSummary estimates = summaries.getTimeEstimateSummary(projectType);
113    assertThat(estimates.getCount(), equalTo(2));
114    assertThat(estimates.getMinimum(), equalTo(minimumEstimate));
115  }
116
117  @Test
118  void medianFromMultipleStudents() {
119    GradeBook book = new GradeBook("test");
120
121    Assignment.ProjectType projectType = Assignment.ProjectType.APP_CLASSES;
122
123    Assignment project = addProject(book, projectType);
124    double medianEstimate = 10.0;
125    noteSubmission(addStudent(book, "student1"), project, medianEstimate);
126    noteSubmission(addStudent(book, "student2"), project, 9.0);
127    noteSubmission(addStudent(book, "student3"), project, 11.0);
128
129    ProjectTimeEstimatesSummary summary = new ProjectTimeEstimatesSummary();
130    ProjectTimeEstimatesSummary.TimeEstimatesSummaries summaries = summary.getTimeEstimateSummaries(book);
131    ProjectTimeEstimatesSummary.TimeEstimatesSummary estimates = summaries.getTimeEstimateSummary(projectType);
132    assertThat(estimates.getCount(), equalTo(3));
133    assertThat(estimates.getMedian(), equalTo(medianEstimate));
134  }
135
136  @Test
137  void medianOfOddNumberOfValuesIsMiddleValue() {
138    double median = 2.0;
139    Collection<Double> doubles = List.of(1.0, median, 3.0);
140    MatcherAssert.assertThat(ProjectTimeEstimatesSummary.TimeEstimatesSummary.median(doubles), equalTo(median));
141  }
142
143  @Test
144  void medianOfEvenNumberOfValuesIsAverageOfMiddleValues() {
145    Collection<Double> doubles = List.of(1.0, 2.0, 3.0, 4.0);
146    MatcherAssert.assertThat(ProjectTimeEstimatesSummary.TimeEstimatesSummary.median(doubles), equalTo(2.5));
147  }
148
149  @Test
150  void medianSortsValues() {
151    double median = 2.0;
152    Collection<Double> doubles = List.of(median, 1.0, 3.0);
153    MatcherAssert.assertThat(ProjectTimeEstimatesSummary.TimeEstimatesSummary.median(doubles), equalTo(median));
154  }
155
156  @Test
157  void upperQuartileFromMultipleStudents() {
158    GradeBook book = new GradeBook("test");
159
160    Assignment.ProjectType projectType = Assignment.ProjectType.APP_CLASSES;
161
162    double upperQuartile = 4.0;
163    Assignment project = addProject(book, projectType);
164    noteSubmission(addStudent(book, "student1"), project, 1.0);
165    noteSubmission(addStudent(book, "student2"), project, 2.0);
166    noteSubmission(addStudent(book, "student3"), project, 3.0);
167    noteSubmission(addStudent(book, "student4"), project, upperQuartile);
168    noteSubmission(addStudent(book, "student5"), project, 5.0);
169
170    ProjectTimeEstimatesSummary summary = new ProjectTimeEstimatesSummary();
171    ProjectTimeEstimatesSummary.TimeEstimatesSummaries summaries = summary.getTimeEstimateSummaries(book);
172    ProjectTimeEstimatesSummary.TimeEstimatesSummary estimates = summaries.getTimeEstimateSummary(projectType);
173    assertThat(estimates.getUpperQuartile(), equalTo(upperQuartile));
174  }
175
176  @Test
177  void lowerQuartileFromMultipleStudents() {
178    GradeBook book = new GradeBook("test");
179
180    Assignment.ProjectType projectType = Assignment.ProjectType.APP_CLASSES;
181
182    double lowerQuartile = 2.0;
183    Assignment project = addProject(book, projectType);
184    noteSubmission(addStudent(book, "student1"), project, 1.0);
185    noteSubmission(addStudent(book, "student2"), project, lowerQuartile);
186    noteSubmission(addStudent(book, "student3"), project, 3.0);
187    noteSubmission(addStudent(book, "student4"), project, 4.0);
188    noteSubmission(addStudent(book, "student5"), project, 5.0);
189
190    ProjectTimeEstimatesSummary summary = new ProjectTimeEstimatesSummary();
191    ProjectTimeEstimatesSummary.TimeEstimatesSummaries summaries = summary.getTimeEstimateSummaries(book);
192    ProjectTimeEstimatesSummary.TimeEstimatesSummary estimates = summaries.getTimeEstimateSummary(projectType);
193    assertThat(estimates.getLowerQuartile(), equalTo(lowerQuartile));
194  }
195
196  @Test
197  void upperQuartileOfEvenNumberOfValues() {
198    double upperQuartile = 5.0;
199    List<Double> doubles = List.of(1.0, 2.0, 3.0, 4.0, upperQuartile, 6.0);
200    MatcherAssert.assertThat(ProjectTimeEstimatesSummary.TimeEstimatesSummary.upperQuartile(doubles), equalTo(upperQuartile));
201  }
202
203  @Test
204  void lowerQuartileOfEvenNumberOfValues() {
205    double lowerQuartile = 2.0;
206    List<Double> doubles = List.of(1.0, lowerQuartile, 3.0, 4.0, 5.0, 6.0);
207    MatcherAssert.assertThat(ProjectTimeEstimatesSummary.TimeEstimatesSummary.lowerQuartile(doubles), equalTo(lowerQuartile));
208  }
209
210  @Test
211  void lowerQuartileOfOddNumberOfValues() {
212    double lowerQuartile = 2.0;
213    List<Double> doubles = List.of(1.0, lowerQuartile, 3.0, 4.0, 5.0);
214    MatcherAssert.assertThat(ProjectTimeEstimatesSummary.TimeEstimatesSummary.lowerQuartile(doubles), equalTo(lowerQuartile));
215  }
216
217  @Test
218  void upperQuartileOfOneValueIsThatValue() {
219    double value = 1.0;
220    MatcherAssert.assertThat(ProjectTimeEstimatesSummary.TimeEstimatesSummary.upperQuartile(List.of(value)), equalTo(value));
221  }
222
223  @Test
224  void upperQuartileOfTwoValuesIsSecondValue() {
225    double value = 2.0;
226    MatcherAssert.assertThat(ProjectTimeEstimatesSummary.TimeEstimatesSummary.upperQuartile(List.of(1.0, value)), equalTo(value));
227  }
228
229  @Test
230  void upperQuartileOfThreeValuesIsAverageOfSecondAndThirdValue() {
231    MatcherAssert.assertThat(ProjectTimeEstimatesSummary.TimeEstimatesSummary.upperQuartile(List.of(1.0, 2.0, 3.0)), equalTo(2.5));
232  }
233
234  @Test
235  void upperQuartileOfFourValuesIsAverageOfThirdAndFourthValues() {
236    MatcherAssert.assertThat(ProjectTimeEstimatesSummary.TimeEstimatesSummary.upperQuartile(List.of(1.0, 2.0, 3.0, 4.0)), equalTo(3.5));
237  }
238
239  @Test
240  void averageFromMultipleStudents() {
241    GradeBook book = new GradeBook("test");
242
243    Assignment.ProjectType projectType = Assignment.ProjectType.APP_CLASSES;
244
245    Assignment project = addProject(book, projectType);
246    noteSubmission(addStudent(book, "student1"), project, 1.0);
247    noteSubmission(addStudent(book, "student2"), project, 2.0);
248    noteSubmission(addStudent(book, "student4"), project, 4.0);
249    noteSubmission(addStudent(book, "student5"), project, 5.0);
250
251    ProjectTimeEstimatesSummary summary = new ProjectTimeEstimatesSummary();
252    ProjectTimeEstimatesSummary.TimeEstimatesSummaries summaries = summary.getTimeEstimateSummaries(book);
253    ProjectTimeEstimatesSummary.TimeEstimatesSummary estimates = summaries.getTimeEstimateSummary(projectType);
254    assertThat(estimates.getAverage(), equalTo(3.0));
255  }
256
257  @Test
258  void generateMarkdown() {
259    GradeBook book = new GradeBook("test");
260
261    Assignment appClasses = addProject(book, "AppClasses", Assignment.ProjectType.APP_CLASSES);
262    Assignment textFile = addProject(book, "TextFile", Assignment.ProjectType.TEXT_FILE);
263    
264    Student student1 = addStudent(book, "student1");
265    Student student2 = addStudent(book, "student2");
266    Student student3 = addStudent(book, "student3");
267    Student student4 = addStudent(book, "student4");
268    Student student5 = addStudent(book, "student5");
269    
270    noteSubmission(student1, appClasses, 4.0);    
271    noteSubmission(student2, appClasses, 5.0);    
272    noteSubmission(student3, appClasses, 3.0);    
273    noteSubmission(student4, appClasses, 6.0);    
274    noteSubmission(student5, appClasses, 2.0);    
275    noteSubmission(student1, textFile, 7.0);
276    noteSubmission(student2, textFile, 6.0);
277    noteSubmission(student3, textFile, 8.0);
278    noteSubmission(student4, textFile, 6.0);    
279    noteSubmission(student5, textFile, 7.0);
280
281    ProjectTimeEstimatesSummary summary = new ProjectTimeEstimatesSummary();
282    ProjectTimeEstimatesSummary.TimeEstimatesSummaries summaries = summary.getTimeEstimateSummaries(book);
283    ProjectTimeEstimatesSummary.TimeEstimatesSummary appClassesEstimates = summaries.getTimeEstimateSummary(appClasses.getProjectType());
284    assertThat(appClassesEstimates.getCount(), equalTo(5));
285
286    StringWriter sw = new StringWriter();
287    summaries.generateMarkdown(sw, List.of(appClasses.getProjectType(), textFile.getProjectType()));
288    String markdown = sw.toString();
289
290//    System.out.println(markdown);
291
292    List<String> lines = markdown.lines().collect(Collectors.toList());
293
294    assertThat(lines.get(0), equalTo("|  | App Classes | Text File |"));
295    assertThat(lines.get(1), equalTo("| :--- | ---: | ---: |"));
296    assertThat(lines.get(2), equalTo("| Count | 5 | 5 |"));
297    assertThat(lines.get(3), matchesRegex("\\| Average \\| \\d hours \\| \\d hours \\|"));
298    assertThat(lines.get(4), matchesRegex("\\| Maximum \\| \\d hours \\| \\d hours \\|"));
299    assertThat(lines.get(5), matchesRegex("\\| Top 25% \\| \\d hours \\| \\d hours \\|"));
300    assertThat(lines.get(6), matchesRegex("\\| Median \\| \\d hours \\| \\d hours \\|"));
301    assertThat(lines.get(7), matchesRegex("\\| Bottom 25% \\| \\d hours \\| \\d hours \\|"));
302    assertThat(lines.get(8), matchesRegex("\\| Minimum \\| \\d hours \\| \\d hours \\|"));
303  }
304
305  @Test
306  void okayToGenerateMarkdownWhenProjectsWithoutType() {
307    GradeBook book = new GradeBook("test");
308    book.addAssignment(new Assignment("project1", 1.0));
309    book.addAssignment(new Assignment("project2", 1.0));
310    book.addAssignment(new Assignment("project3", 1.0));
311
312    ProjectTimeEstimatesSummary summary = new ProjectTimeEstimatesSummary();
313    ProjectTimeEstimatesSummary.TimeEstimatesSummaries summaries = summary.getTimeEstimateSummaries(book);
314
315    StringWriter sw = new StringWriter();
316    summaries.generateMarkdown(sw, List.of(Assignment.ProjectType.APP_CLASSES, Assignment.ProjectType.TEXT_FILE));
317    String markdown = sw.toString();
318
319    List<String> lines = markdown.lines().collect(Collectors.toList());
320
321    assertThat(lines.get(0), equalTo("|  | App Classes | Text File |"));
322    assertThat(lines.get(1), equalTo("| :--- | ---: | ---: |"));
323    assertThat(lines.get(2), equalTo("| Count | 0 | 0 |"));
324    assertThat(lines.get(3), equalTo("| Average | n/a | n/a |"));
325  }
326
327  @Test
328  void allProjectTypesAreFormatted() {
329    Arrays.stream(Assignment.ProjectType.values()).forEach(projectType -> MatcherAssert.assertThat(ProjectTimeEstimatesSummary.TimeEstimatesSummaries.formatProjectType(projectType), notNullValue()));
330  }
331
332  @Test
333  void submissionsWithoutEstimatesDoNotCount() {
334    GradeBook book = new GradeBook("test");
335
336    Assignment appClasses = addProject(book, "AppClasses", Assignment.ProjectType.APP_CLASSES);
337    Student student1 = addStudent(book, "student1");
338    noteSubmission(student1, appClasses, (Double) null);
339
340    ProjectTimeEstimatesSummary summary = new ProjectTimeEstimatesSummary();
341    ProjectTimeEstimatesSummary.TimeEstimatesSummaries summaries = summary.getTimeEstimateSummaries(book);
342    assertThat(summaries.getTimeEstimateSummary(Assignment.ProjectType.APP_CLASSES), nullValue());
343  }
344
345  @Test
346  void calculationsUsingMultipleGradeBooks() {
347    GradeBook book1 = new GradeBook("Book 1");
348    GradeBook book2 = new GradeBook("Book 2");
349
350    Assignment.ProjectType projectType = Assignment.ProjectType.APP_CLASSES;
351
352    Assignment project1 = addProject(book1, projectType);
353    noteSubmission(addStudent(book1, "student1"), project1, 1.0);
354    noteSubmission(addStudent(book1, "student2"), project1, 2.0);
355    Assignment project2 = addProject(book2, projectType);
356    noteSubmission(addStudent(book2, "student4"), project2, 4.0);
357    noteSubmission(addStudent(book2, "student5"), project2, 5.0);
358
359    ProjectTimeEstimatesSummary summary = new ProjectTimeEstimatesSummary();
360    ProjectTimeEstimatesSummary.TimeEstimatesSummaries summaries = summary.getTimeEstimateSummaries(List.of(book1, book2));
361    ProjectTimeEstimatesSummary.TimeEstimatesSummary estimates = summaries.getTimeEstimateSummary(projectType);
362    assertThat(estimates.getCount(), equalTo(4));
363    assertThat(estimates.getAverage(), equalTo(3.0));
364  }
365}