diff --git a/package-lock.json b/package-lock.json index c6349618..a8248606 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "ag-website-vue", - "version": "1.6.1", + "version": "1.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ag-website-vue", - "version": "1.6.1", + "version": "1.7.0", "dependencies": { "@fortawesome/fontawesome-free": "^5.15.4", "@types/minimatch": "^3.0.5", - "ag-client-typescript": "2.5.0", + "ag-client-typescript": "2.6.0", "chart.js": "^3.9.1", "chartjs-adapter-moment": "^1.0.1", "chartjs-plugin-zoom": "^1.2.1", @@ -4736,9 +4736,9 @@ } }, "node_modules/ag-client-typescript": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ag-client-typescript/-/ag-client-typescript-2.5.0.tgz", - "integrity": "sha512-ro4rw3gyreSDXJOMAIJQbg738eFh6rqlTlaa+oqD04hCO+G1CJ+LvneYBucdwRYmnaGcS/h/t9dXagKMJ+lssw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/ag-client-typescript/-/ag-client-typescript-2.6.0.tgz", + "integrity": "sha512-6WLbPhHISELCVC9hcTJzkKZnVbKJ8wM4ICif2s4O5TxSz5jc5/7GCnyzfEhrLn1NyCGCedppswK1uiLUkxm6jA==", "dependencies": { "axios": "^0.21.1" } diff --git a/package.json b/package.json index 678bcaa9..566a18b0 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "dependencies": { "@fortawesome/fontawesome-free": "^5.15.4", "@types/minimatch": "^3.0.5", - "ag-client-typescript": "2.5.0", + "ag-client-typescript": "2.6.0", "chart.js": "^3.9.1", "chartjs-adapter-moment": "^1.0.1", "chartjs-plugin-zoom": "^1.2.1", diff --git a/src/components/project_view/handgrading/group_summary_panel.vue b/src/components/project_view/handgrading/group_summary_panel.vue index ded35cdd..e770b257 100644 --- a/src/components/project_view/handgrading/group_summary_panel.vue +++ b/src/components/project_view/handgrading/group_summary_panel.vue @@ -4,7 +4,7 @@ 'graded': status === HandgradingStatus.graded, 'ungraded': status === HandgradingStatus.ungraded, 'in-progress': status === HandgradingStatus.in_progress, - 'no-submission': status === HandgradingStatus.no_submission, + 'no-handgradeable-submission': status === HandgradingStatus.no_submission, }" v-on="$listeners">
@@ -78,7 +78,7 @@ export default class GroupSummaryPanel extends Vue { color: $ocean-blue; } -.no-submission { +.no-handgradeable-submission { color: $stormy-gray-dark; } diff --git a/src/components/project_view/handgrading/handgrading_container.vue b/src/components/project_view/handgrading/handgrading_container.vue index 77d36aed..42f11a9e 100644 --- a/src/components/project_view/handgrading/handgrading_container.vue +++ b/src/components/project_view/handgrading/handgrading_container.vue @@ -68,12 +68,14 @@
- + :value="HandgradingStatus.no_handgradeable_submission"> +
@@ -90,7 +92,7 @@ :class="{ 'active': d_currently_grading !== null && d_currently_grading.group === group_summary.pk, - 'disabled': group_summary.num_submissions === 0 + 'disabled': !group_summary.has_handgradeable_submission }"> @@ -234,13 +236,13 @@ export default class HandgradingContainer extends Vue implements ag_cli.Handgrad get total_num_to_grade() { return this.staff_filtered_groups.filter( - group => get_handgrading_status(group) !== HandgradingStatus.no_submission + group => get_handgrading_status(group) !== HandgradingStatus.no_handgradeable_submission ).length; } @handle_global_errors_async async select_for_grading(group: ag_cli.GroupWithHandgradingResultSummary) { - if (group.num_submissions !== 0) { + if (group.has_handgradeable_submission) { await toggle(this, 'd_loading_result', async () => { this.d_currently_grading = await ag_cli.HandgradingResult.get_or_create(group.pk); }); @@ -250,7 +252,7 @@ export default class HandgradingContainer extends Vue implements ag_cli.Handgrad get previous() { let index = this.index_of_currently_grading - 1; while (index >= 0) { - if (this.staff_filtered_groups[index].num_submissions !== 0) { + if (this.staff_filtered_groups[index].has_handgradeable_submission) { return this.staff_filtered_groups[index]; } index -= 1; @@ -261,7 +263,7 @@ export default class HandgradingContainer extends Vue implements ag_cli.Handgrad get next() { let index = this.index_of_currently_grading + 1; while (index < this.staff_filtered_groups.length) { - if (this.staff_filtered_groups[index].num_submissions !== 0) { + if (this.staff_filtered_groups[index].has_handgradeable_submission) { return this.staff_filtered_groups[index]; } index += 1; diff --git a/src/components/project_view/handgrading/handgrading_status.ts b/src/components/project_view/handgrading/handgrading_status.ts index 620bedb2..c34a1ed5 100644 --- a/src/components/project_view/handgrading/handgrading_status.ts +++ b/src/components/project_view/handgrading/handgrading_status.ts @@ -1,15 +1,15 @@ import { GroupWithHandgradingResultSummary } from 'ag-client-typescript'; export enum HandgradingStatus { - no_submission = "No Submission", + no_handgradeable_submission = "No Submission", ungraded = "Ungraded", in_progress = "In Progress", graded = "Graded", } export function get_handgrading_status(group_summary: GroupWithHandgradingResultSummary) { - if (group_summary.num_submissions === 0) { - return HandgradingStatus.no_submission; + if (!group_summary.has_handgradeable_submission) { + return HandgradingStatus.no_handgradeable_submission; } let result = group_summary.handgrading_result; diff --git a/tests/data_utils.ts b/tests/data_utils.ts index c03440d1..e6dc2dfe 100644 --- a/tests/data_utils.ts +++ b/tests/data_utils.ts @@ -697,15 +697,17 @@ export function make_group_summary( project_pk: number, num_members: number = 1, group_args: Partial = {}, + has_handgradeable_submission: boolean = false, handgrading_result: { finished_grading: boolean; total_points: number; total_points_possible: number; - } | null = null + } | null = null, ): GroupWithHandgradingResultSummary { let group = make_group(project_pk, num_members, group_args); return { handgrading_result: handgrading_result, + has_handgradeable_submission: has_handgradeable_submission, ...group }; } diff --git a/tests/test_components/test_project_view/test_handgrading/test_group_summary_panel.ts b/tests/test_components/test_project_view/test_handgrading/test_group_summary_panel.ts index 634cefdd..e94ad05a 100644 --- a/tests/test_components/test_project_view/test_handgrading/test_group_summary_panel.ts +++ b/tests/test_components/test_project_view/test_handgrading/test_group_summary_panel.ts @@ -29,7 +29,7 @@ test('Group member names displayed', () => { test('Score displayed when status is graded', () => { let summary = data_ut.make_group_summary( - project.pk, 1, {num_submissions: 1}, + project.pk, 1, {num_submissions: 1}, true, {finished_grading: true, total_points: 4, total_points_possible: 5} ); let wrapper = managed_mount(GroupSummaryPanel, { @@ -43,7 +43,7 @@ test('Score displayed when status is graded', () => { test('Non-graded statuses show status text', async () => { let in_progress_summary = data_ut.make_group_summary( - project.pk, 1, {num_submissions: 1}, + project.pk, 1, {num_submissions: 1}, true, {finished_grading: false, total_points: 3, total_points_possible: 5} ); let wrapper = managed_mount(GroupSummaryPanel, { @@ -54,14 +54,20 @@ test('Non-graded statuses show status text', async () => { expect(wrapper.find('.status').text()).toEqual('In Progress'); let ungraded_summary = data_ut.make_group_summary( - project.pk, 1, {num_submissions: 1}, + project.pk, 1, {num_submissions: 1}, true, ); await set_props(wrapper, {group_summary: ungraded_summary}); expect(wrapper.find('.status').text()).toEqual('Ungraded'); let no_submission_summary = data_ut.make_group_summary( - project.pk, 1, {num_submissions: 0}, + project.pk, 1, {num_submissions: 0}, false, ); await set_props(wrapper, {group_summary: no_submission_summary}); expect(wrapper.find('.status').text()).toEqual('No Submission'); + + let no_handgradeable_submission_summary = data_ut.make_group_summary( + project.pk, 1, {num_submissions: 1}, false, + ); + await set_props(wrapper, {group_summary: no_handgradeable_submission_summary}); + expect(wrapper.find('.status').text()).toEqual('No Submission'); }); diff --git a/tests/test_components/test_project_view/test_handgrading/test_handgrading_container.ts b/tests/test_components/test_project_view/test_handgrading/test_handgrading_container.ts index 65970c37..e033d0b4 100644 --- a/tests/test_components/test_project_view/test_handgrading/test_handgrading_container.ts +++ b/tests/test_components/test_project_view/test_handgrading/test_handgrading_container.ts @@ -10,13 +10,14 @@ import { HandgradingStatus } from '@/components/project_view/handgrading/handgra import * as data_ut from '@/tests/data_utils'; import { managed_mount } from '@/tests/setup'; -import { checkbox_is_checked, compress_whitespace, find_by_name, wait_until } from '@/tests/utils'; +import { checkbox_is_checked, compress_whitespace, expect_has_class, expect_not_has_class, find_by_name, wait_until } from '@/tests/utils'; let course: ag_cli.Course; let project: ag_cli.Project; let rubric: ag_cli.HandgradingRubric; let no_submissions_group: ag_cli.GroupWithHandgradingResultSummary; +let no_handgradeable_submissions_group: ag_cli.GroupWithHandgradingResultSummary; let ungraded_group: ag_cli.GroupWithHandgradingResultSummary; let in_progress_group: ag_cli.GroupWithHandgradingResultSummary; let graded_group: ag_cli.GroupWithHandgradingResultSummary; @@ -43,11 +44,14 @@ beforeEach(() => { ).rejects(new ag_cli.HttpError(500, 'Mock me please')); no_submissions_group = data_ut.make_group_summary( - project.pk, 1, {member_names: ['none@me.com']}); + project.pk, 1, {member_names: ['none@me.com'], num_submissions: 0}, false); + no_handgradeable_submissions_group = data_ut.make_group_summary( + project.pk, 1, {member_names: ['none@me.com'], num_submissions: 1}, false); ungraded_group = data_ut.make_group_summary( - project.pk, 1, {member_names: ['not_yet@me.com'], num_submissions: 1}); + project.pk, 1, {member_names: ['not_yet@me.com'], num_submissions: 1}, true); in_progress_group = data_ut.make_group_summary( project.pk, 1, {member_names: ['progress@me.com'], num_submissions: 1}, + true, { finished_grading: false, total_points: 4, @@ -56,6 +60,7 @@ beforeEach(() => { ); graded_group = data_ut.make_group_summary( project.pk, 1, {member_names: ['graded@me.com'], num_submissions: 1}, + true, { finished_grading: true, total_points: 5, @@ -70,6 +75,7 @@ beforeEach(() => { staff_group = data_ut.make_group_summary( project.pk, 1, {member_names: staff.map(user => user.username), num_submissions: 1}, + true, { finished_grading: true, total_points: 6, @@ -82,6 +88,7 @@ beforeEach(() => { admin_group = data_ut.make_group_summary( project.pk, 1, {member_names: [admin.username], num_submissions: 1}, + true, { finished_grading: true, total_points: 5, @@ -99,6 +106,7 @@ describe('Filter group summaries tests', () => { beforeEach(async () => { set_summaries([ no_submissions_group, + no_handgradeable_submissions_group, ungraded_group, in_progress_group, graded_group, @@ -112,21 +120,22 @@ describe('Filter group summaries tests', () => { test('Include/exclude staff', async () => { expect(checkbox_is_checked(wrapper.find('#include-staff'))).toBe(false); expect(wrapper.vm.d_include_staff).toBe(false); - expect(wrapper.findAllComponents({name: 'GroupSummaryPanel'}).length).toBe(4); + expect(wrapper.findAllComponents({name: 'GroupSummaryPanel'}).length).toBe(5); expect(summary_pks(wrapper).includes(staff_group.pk)).toBe(false); wrapper.find('#include-staff').setChecked(); await wrapper.vm.$nextTick(); expect(wrapper.vm.d_include_staff).toBe(true); - expect(wrapper.findAllComponents({name: 'GroupSummaryPanel'}).length).toBe(6); + expect(wrapper.findAllComponents({name: 'GroupSummaryPanel'}).length).toBe(7); expect(summary_pks(wrapper).includes(staff_group.pk)).toBe(true); }); test('Filter by status', async () => { expect(checkbox_is_checked(wrapper.find('#all'))).toBe(true); - expect(wrapper.findAllComponents({name: 'GroupSummaryPanel'}).length).toBe(4); + expect(wrapper.findAllComponents({name: 'GroupSummaryPanel'}).length).toBe(5); expect(summary_pks(wrapper)).toEqual([ no_submissions_group.pk, + no_handgradeable_submissions_group.pk, ungraded_group.pk, in_progress_group.pk, graded_group.pk, @@ -147,10 +156,12 @@ describe('Filter group summaries tests', () => { expect(wrapper.findAllComponents({name: 'GroupSummaryPanel'}).length).toBe(1); expect(summary_pks(wrapper)).toEqual([ungraded_group.pk]); - wrapper.find('#no-submission').setChecked(); + wrapper.find('#no-handgradeable-submission').setChecked(); await wrapper.vm.$nextTick(); - expect(wrapper.findAllComponents({name: 'GroupSummaryPanel'}).length).toBe(1); - expect(summary_pks(wrapper)).toEqual([no_submissions_group.pk]); + expect(wrapper.findAllComponents({name: 'GroupSummaryPanel'}).length).toBe(2); + expect( + summary_pks(wrapper) + ).toEqual([no_submissions_group.pk, no_handgradeable_submissions_group.pk]); }); test('Filter by username', async () => { @@ -170,21 +181,21 @@ describe('Filter group summaries tests', () => { expect(wrapper.vm.d_include_staff).toBe(false); expect( compress_whitespace(wrapper.findComponent({ref: 'progress_text'}).text()) - ).toEqual('1/3 (4 total)'); + ).toEqual('1/3 (5 total)'); wrapper.vm.d_include_staff = true; await wrapper.vm.$nextTick(); expect( compress_whitespace(wrapper.findComponent({ref: 'progress_text'}).text()) - ).toEqual('3/5 (6 total)'); + ).toEqual('3/5 (7 total)'); // Filtering by status does NOT change the "total graded" display wrapper.vm.d_status_filter = HandgradingStatus.graded; await wrapper.vm.$nextTick(); expect( compress_whitespace(wrapper.findComponent({ref: 'progress_text'}).text()) - ).toEqual('3/5 (6 total)'); + ).toEqual('3/5 (7 total)'); // Entering search text does NOT change the "total graded" display @@ -192,7 +203,7 @@ describe('Filter group summaries tests', () => { await wrapper.vm.$nextTick(); expect( compress_whitespace(wrapper.findComponent({ref: 'progress_text'}).text()) - ).toEqual('3/5 (6 total)'); + ).toEqual('3/5 (7 total)'); }); }); @@ -257,7 +268,12 @@ describe('Select group tests', () => { let wrapper: Wrapper; beforeEach(async () => { - set_summaries([no_submissions_group, ungraded_group, graded_group]); + set_summaries([ + no_submissions_group, + no_handgradeable_submissions_group, + ungraded_group, + graded_group + ]); wrapper = await make_wrapper(); }); @@ -266,8 +282,12 @@ describe('Select group tests', () => { get_or_create_stub.withArgs(ungraded_group.pk).resolves(result); expect(wrapper.findComponent({name: 'Handgrading'}).exists()).toBe(false); - await wrapper.findAllComponents({name: 'GroupSummaryPanel'}).at(1).trigger('click'); + let to_click = wrapper.findAllComponents({name: 'GroupSummaryPanel'}).at(2); + await to_click.trigger('click'); await expect_result_is_selected(wrapper, result); + + expect_not_has_class(to_click, 'disabled'); + expect_has_class(to_click, 'active'); }); test('Select graded group for grading', async () => { @@ -275,17 +295,45 @@ describe('Select group tests', () => { get_or_create_stub.withArgs(graded_group.pk).resolves(result); expect(wrapper.findComponent({name: 'Handgrading'}).exists()).toBe(false); - await wrapper.findAllComponents({name: 'GroupSummaryPanel'}).at(2).trigger('click'); + let to_click = wrapper.findAllComponents({name: 'GroupSummaryPanel'}).at(3); + await to_click.trigger('click'); await expect_result_is_selected(wrapper, result); + + expect_not_has_class(to_click, 'disabled'); + expect_has_class(to_click, 'active'); }); test('Group selected for grading has no submissions', async () => { + let result = data_ut.make_handgrading_result(rubric, no_submissions_group.pk, 42); + get_or_create_stub.withArgs(no_submissions_group.pk).resolves(result); + expect(wrapper.findComponent({name: 'Handgrading'}).exists()).toBe(false); - wrapper.findAllComponents({name: 'GroupSummaryPanel'}).at(0).trigger('click'); - await wrapper.vm.$nextTick(); + let to_click = wrapper.findAllComponents({name: 'GroupSummaryPanel'}).at(0); + await to_click.trigger('click'); expect(wrapper.vm.d_currently_grading).toBe(null); expect(wrapper.findComponent({name: 'Handgrading'}).exists()).toBe(false); + expect(get_or_create_stub.called).toBe(false); + + expect_has_class(to_click, 'disabled'); + expect_not_has_class(to_click, 'active'); + }); + + test('Group selected for grading has no handgradeable submissions', async () => { + let result = data_ut.make_handgrading_result( + rubric, no_handgradeable_submissions_group.pk, 42); + get_or_create_stub.withArgs(no_handgradeable_submissions_group.pk).resolves(result); + + expect(wrapper.findComponent({name: 'Handgrading'}).exists()).toBe(false); + let to_click = wrapper.findAllComponents({name: 'GroupSummaryPanel'}).at(1); + await to_click.trigger('click'); + + expect(wrapper.vm.d_currently_grading).toBe(null); + expect(wrapper.findComponent({name: 'Handgrading'}).exists()).toBe(false); + expect(get_or_create_stub.called).toBe(false); + + expect_has_class(to_click, 'disabled'); + expect_not_has_class(to_click, 'active'); }); }); @@ -318,7 +366,12 @@ describe('Select next/prev for grading', () => { }); test('Select next and prev skips no_submission', async () => { - set_summaries([ungraded_group, no_submissions_group, graded_group]); + set_summaries([ + ungraded_group, + no_submissions_group, + no_handgradeable_submissions_group, + graded_group + ]); let wrapper = await make_wrapper(); await wrapper.findAllComponents({name: 'GroupSummaryPanel'}).at(0).trigger('click'); @@ -360,6 +413,17 @@ describe('Select next/prev for grading', () => { expect(find_by_name(wrapper, 'Handgrading').vm.is_last).toBe(false); }); + test('Current group is first with handgradeable submission', async () => { + set_summaries([no_handgradeable_submissions_group, ungraded_group, graded_group]); + + let wrapper = await make_wrapper(); + await wrapper.findAllComponents({name: 'GroupSummaryPanel'}).at(1).trigger('click'); + await expect_result_is_selected(wrapper, ungraded_result); + + expect(find_by_name(wrapper, 'Handgrading').vm.is_first).toBe(true); + expect(find_by_name(wrapper, 'Handgrading').vm.is_last).toBe(false); + }); + test('Current group is last with submission', async () => { set_summaries([ungraded_group, graded_group, no_submissions_group]); @@ -371,6 +435,17 @@ describe('Select next/prev for grading', () => { expect(find_by_name(wrapper, 'Handgrading').vm.is_last).toBe(true); }); + test('Current group is last with handgradeable submission', async () => { + set_summaries([ungraded_group, graded_group, no_handgradeable_submissions_group]); + + let wrapper = await make_wrapper(); + await wrapper.findAllComponents({name: 'GroupSummaryPanel'}).at(1).trigger('click'); + await expect_result_is_selected(wrapper, graded_result); + + expect(find_by_name(wrapper, 'Handgrading').vm.is_first).toBe(false); + expect(find_by_name(wrapper, 'Handgrading').vm.is_last).toBe(true); + }); + test('Current group is only group', async () => { set_summaries([ungraded_group]); diff --git a/tests/test_components/test_project_view/test_handgrading/test_handgrading_status.ts b/tests/test_components/test_project_view/test_handgrading/test_handgrading_status.ts index a8240e13..4039551c 100644 --- a/tests/test_components/test_project_view/test_handgrading/test_handgrading_status.ts +++ b/tests/test_components/test_project_view/test_handgrading/test_handgrading_status.ts @@ -12,24 +12,25 @@ beforeEach(() => { test('get_handgrading_status', () => { let graded = data_ut.make_group_summary( - project.pk, 1, {num_submissions: 1}, + project.pk, 1, {num_submissions: 1}, true, {finished_grading: true, total_points: 4, total_points_possible: 5} ); expect(get_handgrading_status(graded)).toEqual(HandgradingStatus.graded); let in_progress = data_ut.make_group_summary( - project.pk, 1, {num_submissions: 1}, + project.pk, 1, {num_submissions: 1}, true, {finished_grading: false, total_points: 3, total_points_possible: 5} ); expect(get_handgrading_status(in_progress)).toEqual(HandgradingStatus.in_progress); let ungraded = data_ut.make_group_summary( - project.pk, 1, {num_submissions: 1}, + project.pk, 1, {num_submissions: 1}, true, ); expect(get_handgrading_status(ungraded)).toEqual(HandgradingStatus.ungraded); - let no_submission = data_ut.make_group_summary( - project.pk, 1, {num_submissions: 0}, + let no_handgradeable_submission = data_ut.make_group_summary( + project.pk, 1, {num_submissions: 1}, false ); - expect(get_handgrading_status(no_submission)).toEqual(HandgradingStatus.no_submission); + expect(get_handgrading_status(no_handgradeable_submission)) + .toEqual(HandgradingStatus.no_handgradeable_submission); }); diff --git a/tests/utils.ts b/tests/utils.ts index 74e62fd3..fd67dda1 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -219,3 +219,17 @@ type SetDataInputPartial = { export function api_error_count(wrapper: Wrapper, selector: RefSelector) { return ( wrapper.findComponent(selector).vm).d_api_errors.length; } + +export function expect_has_class(wrapper: Wrapper, class_: string) { + let class_list = Array.from(wrapper.element.classList); + if (!class_list.includes(class_)) { + fail(`CSS class "${class_}" not found in [${class_list.join(', ')}]`); + } +} + +export function expect_not_has_class(wrapper: Wrapper, class_: string) { + let class_list = Array.from(wrapper.element.classList); + if (class_list.includes(class_)) { + fail(`CSS class "${class_}" unexpectedly found in [${class_list.join(', ')}]`); + } +}