CSC/ECE 517 Fall 2025 - E2567. Review rubrics varying by round

From Expertiza_Wiki
Jump to navigation Jump to search

Introduction

Current Behavior

When creating or editing an assignment, the Rubrics tab shows per-round dropdowns only if “Review rubric varies by round?” is checked. The number of rounds is manually entered on the Due Dates tab. The dropdown options are just predefined text entries; they’re not sourced from the questionnaires in the database.

Motivation

This project aims to automate the round count from due dates that are review deadlines, and load rubric dropdowns from real questionnaires, to keep the Rubrics tab in sync with the backend’s actual review deadlines and persisted questionnaire records for consistent behavior. From a user perspective, enabling true multi-round rubric support improves grading consistency, supports richer peer-review workflows, and aligns the feedback process with the instructional goals of the Expertiza platform — enhancing both evaluation accuracy and the overall educational experience.

Project Requirements

Functional Requirements

  • Add a boolean attribute vary_by_round to the assignments table.
  • When vary_by_round is true, the Rubrics tab dynamically reflects the number of review rounds and displays Review Round 1, Review Round 2, etc., instead of a single Review rubric line. Instructors should be able to specify a distinct review rubric for each review round.
  • Infer the number of review rounds automatically by examining all DueDate objects associated with the assignment.
  • Only deadlines whose deadline_type_id equals 2 (review deadlines) should be counted.
  • Store rubric–round associations in the assignments_questionnaires table.
  • Add a new column named used_in_round to indicate which rubric applies to which review round.
  • When vary_by_round is disabled, retain the existing single-rubric interface.

Data Requirements

  • The assignments_questionnaires table must include an integer column used_in_round to track rubric usage per round.
  • The assignments table must include the boolean column vary_by_round indicating whether rubrics differ across rounds.

Test Requirements

  • Write tests for the new functionality.

Implementations

Architecture Overview

Backend

assignments Table and assignment_questionnaires Table

  • Added a boolean column vary_by_round with default false to toggle per-round rubric behavior to assignments table. Change lives in db/migrate/20251125012619_add_vary_by_round_to_assignments.rb, with schema regeneration reflected in db/schema.rb. As the assignment_questionnaires table already has the column used_in_round, we don't need to add extra code for this requirement.
class AddVaryByRoundToAssignments < ActiveRecord::Migration[8.0]
  def change
    add_column :assignments, :vary_by_round, :boolean, default: false, null: false
  end
end

Review-Round Derivation

  • DueDate defined a REVIEW_DEADLINE_TYPE_ID constant in app/models/due_date.rb, referring to review deadlines. This uses DueDate::REVIEW_DEADLINE_TYPE_ID so the round-count logic keys off the canonical review deadline marker, avoiding hard-coded magic numbers and ensuring the count stays consistent with how review due dates are modeled elsewhere.
REVIEW_DEADLINE_TYPE_ID = 2
  • Enabled Assignment to infer review rounds from its due dates, falling back to rounds_of_reviews only when no review deadlines exist, by adjusting the method num_review_rounds in app/models/assignment.rb.
# Returns review round count: prefer the number of review due dates; 
# if none exist, fall back to the persisted rounds_of_reviews column (or 0).
def num_review_rounds
  review_rounds = due_dates.where(deadline_type_id: DueDate::REVIEW_DEADLINE_TYPE_ID).count
  review_rounds.positive? ? review_rounds : (rounds_of_reviews || 0)
end
  • Updated existing method varying_rubrics_by_round? in app/models/assignment.rb, which serves as the main predicate for determining whether an assignment is effectively using per-round rubrics.
    • It first checks the assignment-level flag vary_by_round. If this flag is false, the method immediately returns false.
    • If vary_by_round is true, the method queries AssignmentQuestionnaire for at least one record with a non-null used_in_round value for the current assignment.
    • The method returns true only when both conditions are satisfied: per-round rubrics are enabled on the assignment and at least one attached questionnaire explicitly specifies a round.
    • To make this information easy to include in JSON responses, the non-predicate alias varying_rubrics_by_round simply delegates to varying_rubrics_by_round?, avoiding a question mark in the rendered JSON key.
# Returns true only if per-round rubrics are enabled on the assignment and at least one attached questionnaire specifies a round via used_in_round
def varying_rubrics_by_round?
  return false unless vary_by_round

  rubric_with_round = AssignmentQuestionnaire.where(assignment_id: id).where.not(used_in_round: nil).first
  # Check if any rubric has a specified round
  rubric_with_round.present?
end


# Alias without question mark for JSON rendering convenience
def varying_rubrics_by_round
  varying_rubrics_by_round?
end

API Surface

In reimplementation-back-end/app/controllers/assignments_controller.rb, the show action was expanded so that the frontend can drive the Rubrics tab based on backend data. The response now renders computed num_review_rounds and varying_rubrics_by_round, and includes assignment_questionnaires with embedded questionnaire objects, along with due_dates.

The update path continues to accept vary_by_round, rounds_of_reviews, and nested assignment_questionnaires_attributes (including id, questionnaire_id, used_in_round, weight, and notification_limit), ensuring that per-round rubric edits are persisted correctly.

# GET /assignments/:id
def show
  assignment = Assignment.includes(assignment_questionnaires: :questionnaire, due_dates: {}).find(params[:id])
  render json: assignment.as_json(
    methods: [:num_review_rounds, :varying_rubrics_by_round], # for review rubrics vary by round functionality
    include: {
      assignment_questionnaires: {
        include: :questionnaire
      },
      due_dates: {}
    }
  )
end

In assignment_params, we now explicitly permit vary_by_round and rounds_of_reviews, alongside the nested assignment_questionnaires_attributes hash (id, questionnaire_id, used_in_round, questionnaire_weight, notification_limit, _destroy). This enables toggling per-round rubric functionality and saving per-round questionnaire associations, weights, and notification limits through a single update request.

def assignment_params
  params.require(:assignment).permit(:title, :description, :name, :directory_path, :spec_location, :vary_by_round, :rounds_of_reviews,
                                     assignment_questionnaires_attributes: [:id, :questionnaire_id, :used_in_round, :questionnaire_weight, :notification_limit, :_destroy])
end

Frontend

Data Transformations

1. AssignmentUtil.ts:

1.1 transformAssignmentResponse now maps the enriched GET /assignments/:id payload into form values. It extracts assignment_questionnaires (with embedded questionnaire), due_dates, the computed num_review_rounds, and the computed varying_rubrics_by_round (falling back to vary_by_round). This allows the Rubrics tab to prefill per-round rubric selections and to determine the correct number of review rounds.

export const transformAssignmentResponse = (assignmentResponse: string) => {
  const assignment: IAssignmentResponse = JSON.parse(assignmentResponse);
  const assignmentValues: IAssignmentFormValues = {
    ...
    review_rubric_varies_by_round: assignment.varying_rubrics_by_round ?? assignment.vary_by_round,
    number_of_review_rounds: assignment.num_review_rounds,
    due_dates: assignment.due_dates,
    assignment_questionnaires: assignment.assignment_questionnaires,
  };
  return assignmentValues;
};

1.2 transformAssignmentRequest constructs assignment_questionnaires_attributes from the per-round form fields (e.g., questionnaire_round_X), preserves existing join IDs (assignment_questionnaire_id_X) during updates, and transmits vary_by_round and rounds_of_reviews based on the checkbox state and round count. This ensures that per-round rubric assignments are persisted using Rails’ nested attributes mechanism.

export interface IAssignmentFormValues {
  ...
  due_dates?: { id: number; deadline_type_id: number; round?: number }[];
  assignment_questionnaires?: {
    id: number;
    used_in_round?: number;
    questionnaire?: { id: number; name: string };
  }[];
  [key: string]: any;
}
export const transformAssignmentRequest = (values: IAssignmentFormValues) => {
  // Build nested attributes for assignment_questionnaires from the per-round form fields to create or update corresponding rows
  const assignmentQuestionnaires: { id?: number; questionnaire_id: number; used_in_round: number }[] = [];
  const roundCount = values.number_of_review_rounds ?? 0;
  for (let i = 1; i <= roundCount; i += 1) {
    const questionnaireId = values[`questionnaire_round_${i}`];
    if (questionnaireId) {
      const existingId = values[`assignment_questionnaire_id_${i}`];
      assignmentQuestionnaires.push({
        id: existingId,
        questionnaire_id: questionnaireId,
        used_in_round: i,
      });
    }
  }

  const assignment: IAssignmentRequest = {
    ...
    vary_by_round: values.review_rubric_varies_by_round,
    rounds_of_reviews: values.number_of_review_rounds,
    assignment_questionnaires_attributes: assignmentQuestionnaires,

  };
  console.log(assignment);
  return JSON.stringify({ assignment });
};

2. interfaces.ts exposes these response fields so type-checking stays accurate.

Form and Rubrics UI

In AssignmentEditor.tsx, formInitialValues is constructed from defaults when creating a new assignment (initialValues) and from loader data (assignmentData) when updating an existing one. In update mode, the editor also pre-seeds per-round fields (such as questionnaire_round_X and assignment_questionnaire_id_X) from existing assignment_questionnaires, ensuring that the Rubrics dropdowns display the current rubric selections.

  const roundSelections: Record<number, { id: number; name: string }> = {};
  (assignmentData.assignment_questionnaires || []).forEach((aq: any) => {
    if (aq.used_in_round && aq.questionnaire) {
      roundSelections[aq.used_in_round] = { id: aq.questionnaire.id, name: aq.questionnaire.name };
    }
  });

  // Build initial form values from existing assignment data (update) or defaults (create)
  const formInitialValues: any = mode === "update" ? { ...assignmentData } : { ...initialValues };

  if (mode === "update") {
    // Prefill per-round questionnaire selections and ids
    (assignmentData.assignment_questionnaires || []).forEach((aq: any) => {
      if (aq.used_in_round && aq.questionnaire) {
        formInitialValues[`questionnaire_round_${aq.used_in_round}`] = aq.questionnaire.id;
        formInitialValues[`assignment_questionnaire_id_${aq.used_in_round}`] = aq.id;
      }
    });
  }

Options for the rubric dropdowns now come from the full questionnaire list (assignmentData.questionnaires).

  const questionnaireOptions = (assignmentData.questionnaires || []).map((q: any) => ({
    label: q.name,
    value: q.id,
  }));

The number of round rows rendered is controlled by formik.values.number_of_review_rounds. When review_rubric_varies_by_round is true, a row is rendered per review round; otherwise, a single rubric row is displayed.

...
data={[
                      ...(() => {
                        const rounds = formik.values.number_of_review_rounds ?? 0;
                        if (formik.values.review_rubric_varies_by_round) {
                          return Array.from({ length: rounds }, (_, i) => ([
                            {
                              id: i + 1,
                              title: `Review round ${i + 1}:`,
                              questionnaire_options: questionnaireOptions,
                              selected_questionnaire: roundSelections[i + 1]?.id,
                              questionnaire_type: 'dropdown',
                            },
                       // rest of the code

The submission process continues to use transformAssignmentRequest to construct and submit assignment_questionnaires_attributes, along with vary_by_round and rounds_of_reviews, ensuring that backend updates remain synchronized with UI selections.

Design Principles

Guiding Principles Behind the Backend Implementations

  • Backward compatibility first: adding the vary_by_round flag and default fallbacks so existing assignments keep working even if they never set the new field (progressive enhancement instead of breaking change).
  • Single source of truth: rather than hard-coding round counts, the assignment will derive them directly from DueDate data (with a final fallback), ensuring round logic lives in one place that reflects the real deadlines.
  • Separation of concerns: keeping business logic inside models/helpers (Assignment and DueDate), while the controller simply exposes the computed values.
  • Testability: introducing factories for every new entity that specs needed and exercised each new behavior in model/request specs, which enforces the contract for the new logic.
  • Minimal API surface change: reusing existing endpoints, enriching responses instead of creating new routes, to limit churn for API consumers while still providing the data the frontend needs.

Guiding Principles Behind the Frontend Implementations

  • Single source of truth: the count of review rounds comes from centralized helper logic, avoiding duplicated calculations and keeping tabs in sync.
  • Resilience & sensible defaults: derive functions sanitize user input, fall back to backend metadata.
  • Testability: unit tests ensure the helper logic behaves the way we expect, which guards against regressions as requirements evolve.

Testing

Backend

RSpec

Testing Assignment Model

The following RSpec tests exercise the new behavior for computing review rounds and determining whether rubrics vary by round.

These definitions are shared by all test cases:

include RolesHelper
before(:all) { @roles = create_roles_hierarchy } # Create the full roles hierarchy once for creating the instructor role later
let(:institution) { Institution.create!(name: "NC State") } # All users belong to the same institution to satisfy foreign key constraints.
let(:instructor) { User.create!(name: "instructor", full_name: "Instructor User",
                                email: "instructor@example.com", password_digest: "password",
                                role_id: @roles[:instructor].id, institution_id: institution.id) }

1. Counts review due dates to determine number of rounds

This test verifies that num_review_rounds returns the correct number of review rounds by counting only review-type due dates (with deadline_type_id = DueDate::REVIEW_DEADLINE_TYPE_ID) attached to the assignment.

describe '#num_review_rounds' do
  it 'counts review due dates to determine the number of rounds' do
    assignment = Assignment.create!(name: 'Round Count', instructor: instructor, vary_by_round: true)
    AssignmentDueDate.create!(parent: assignment, due_at: 1.day.from_now,
                              deadline_type_id: DueDate::REVIEW_DEADLINE_TYPE_ID,
                              submission_allowed_id: 3, review_allowed_id: 3)
    AssignmentDueDate.create!(parent: assignment, due_at: 2.days.from_now,
                              deadline_type_id: DueDate::REVIEW_DEADLINE_TYPE_ID,
                              submission_allowed_id: 3, review_allowed_id: 3)

    expect(assignment.num_review_rounds).to eq(2)
  end
end

2. Ignores non-review deadlines when counting rounds

This test ensures that num_review_rounds ignores due dates that are not review deadlines. Even if multiple deadlines exist, only those with review deadline type should contribute to the round count.

describe '#num_review_rounds' do
  it 'ignores non-review deadlines when counting rounds' do
    assignment = Assignment.create!(name: 'Mixed Deadlines', instructor: instructor, vary_by_round: true)
    AssignmentDueDate.create!(parent: assignment, due_at: 1.day.from_now,
                              deadline_type_id: 99,
                              submission_allowed_id: 3, review_allowed_id: 3)
    AssignmentDueDate.create!(parent: assignment, due_at: 2.days.from_now,
                              deadline_type_id: DueDate::REVIEW_DEADLINE_TYPE_ID,
                              submission_allowed_id: 3, review_allowed_id: 3)

    expect(assignment.num_review_rounds).to eq(1)
  end
end

3. Returns false when per-round rubrics are disabled

This test checks that varying_rubrics_by_round? returns false when vary_by_round is disabled on the assignment, even if a rubric is linked to a specific round via used_in_round.

describe '#varying_rubrics_by_round?' do
  let(:questionnaire) {
    Questionnaire.create!(name: 'Review Q', instructor_id: instructor.id,
                          questionnaire_type: 'ReviewQuestionnaire',
                          display_type: 'Review', min_question_score: 0,
                          max_question_score: 5)
  }

  it 'returns false when vary_by_round is disabled even if rounds exist' do
    assignment = Assignment.create!(name: 'No Vary', instructor: instructor, vary_by_round: false)
    AssignmentQuestionnaire.create!(assignment: assignment, questionnaire: questionnaire, used_in_round: 1)

    expect(assignment.varying_rubrics_by_round?).to be false
  end
end

4. Returns true when per-round rubrics are enabled and at least one round-specific rubric exists

This test confirms that varying_rubrics_by_round? returns true when vary_by_round is enabled and there is at least one AssignmentQuestionnaire with a non-nil used_in_round, indicating a round-specific rubric.

describe '#varying_rubrics_by_round?' do
  let(:questionnaire) {
    Questionnaire.create!(name: 'Review Q', instructor_id: instructor.id,
                          questionnaire_type: 'ReviewQuestionnaire',
                          display_type: 'Review', min_question_score: 0,
                          max_question_score: 5)
  }

  it 'returns true when vary_by_round is enabled and a round-specific rubric exists' do
    assignment = Assignment.create!(name: 'Vary', instructor: instructor, vary_by_round: true)
    AssignmentQuestionnaire.create!(assignment: assignment, questionnaire: questionnaire, used_in_round: 1)

    expect(assignment.varying_rubrics_by_round?).to be true
  end
end

Testing results

The tests have successfully passed.


Testing Assignment Controller

The following request specs validate that the backend correctly exposes assignment details, including round-varying rubrics, due dates, and computed attributes via the GET /assignments/{id} endpoint.

A questionnaire is created once to be associated with assignments used in test cases:

let!(:questionnaire) do
  Questionnaire.create!(
    name: "Review Rubric",
    instructor: prof,
    private: false,
    min_question_score: 0,
    max_question_score: 10,
    questionnaire_type: "ReviewQuestionnaire"
  )
end

1. Successful retrieval of assignment with rubrics and due dates

This test verifies that when an assignment has per-round rubrics enabled, the API returns:

  • the assignment ID
  • associated assignment_questionnaires including embedded questionnaire details
  • review due dates
  • the computed number of review rounds
  • varying_rubrics_by_round = true
# -------------------------------------------------------------------------
# GET /assignments/{id} (Show assignment)
# -------------------------------------------------------------------------
path '/assignments/{id}' do
  parameter name: 'id', in: :path, type: :integer, description: 'Assignment ID'

  get 'Show assignment details with rubrics and due dates' do
    tags 'Assignments'
    produces 'application/json'
    parameter name: 'Content-Type', in: :header, type: :string
    let('Content-Type') { 'application/json' }

    response '200', 'assignment found' do
      let(:id) { assignment.id }

      before do
        assignment.update!(vary_by_round: true)
        AssignmentQuestionnaire.create!(assignment: assignment, questionnaire: questionnaire, used_in_round: 1)
        AssignmentDueDate.create!(
          parent: assignment,
          due_at: Time.zone.now + 1.day,
          deadline_type_id: DueDate::REVIEW_DEADLINE_TYPE_ID,
          submission_allowed_id: 1,
          review_allowed_id: 1,
          round: 1
        )
      end

      run_test! do
        data = JSON.parse(response.body)

        expect(data['id']).to eq(assignment.id)
        expect(data['assignment_questionnaires'].first['questionnaire']['id']).to eq(questionnaire.id)
        expect(data['due_dates'].length).to eq(1)
        expect(data['num_review_rounds']).to eq(1)
        expect(data['varying_rubrics_by_round']).to eq(true)
      end
    end

2. Assignment not found case

This test ensures a 404 error is returned when requesting an assignment that does not exist.

    response '404', 'assignment not found' do
      let(:id) { 999 }

      run_test! do
        data = JSON.parse(response.body)
        expect(response).to have_http_status(:not_found)
        expect(data['error']).to eq('Assignment not found')
      end
    end
  end
end

Testing results

The tests have successfully passed.

Swagger UI

We only enriched the existing GET/assignments/:id, so there is no new endpoints added for this project. Because the route itself didn't change and we didn't touch swagger/v1/swagger.yaml, so spot-checking through Swagger UI is not necessary.

Frontend

Vitest

The following Vitest/Jest-based frontend tests verify correct rendering and request construction for the updated Rubrics tab in the AssignmentEditor component, as well as correct payload generation in transformAssignmentRequest.

Each test initializes mock API hooks, Redux dispatch, and loader data to simulate the AssignmentEditor operating in update mode without making real network calls.

import React from "react";
import { render, screen, within } from "@testing-library/react";
import "@testing-library/jest-dom";
import { vi, beforeEach, describe, expect, it } from "vitest";
import AssignmentEditor from "./AssignmentEditor";
import { transformAssignmentRequest, IAssignmentFormValues } from "./AssignmentUtil";

// Mock useAPI to avoid real network calls
const sendRequestMock = vi.fn();
vi.mock("../../hooks/useAPI", () => ({
  __esModule: true,
  default: () => ({ data: null, error: null, sendRequest: sendRequestMock }),
}));

// Mock redux dispatch and selector
const dispatchMock = vi.fn();
vi.mock("react-redux", () => ({
  useDispatch: () => dispatchMock,
  useSelector: (selector: any) => selector({ authentication: { isAuthenticated: true } }),
}));

// Mock router context hooks
let loaderData: any;
vi.mock("react-router-dom", async () => {
  const actual = await vi.importActual<any>("react-router-dom");
  return {
    ...actual,
    useLoaderData: () => loaderData,
    useLocation: () => ({ state: {} }),
    useNavigate: () => vi.fn(),
  };
});

1. Renders one rubric row per review round

This test verifies that when rubrics vary by round, the UI shows a separate dropdown row for each review round.

it("shows one row per review round when rubrics vary by round", () => {
  loaderData = { ...baseAssignment };
  render(<AssignmentEditor mode="update" />);

  expect(screen.getByText("Review round 1:")).toBeInTheDocument();
  expect(screen.getByText("Review round 2:")).toBeInTheDocument();
});

2. Shows a single rubric when per-round mode is disabled

This test ensures that disabling the review_rubric_varies_by_round flag results in only one rubric row, regardless of round count.

it("shows a single rubric row when rubrics do not vary by round", () => {
  loaderData = { ...baseAssignment, review_rubric_varies_by_round: false };
  render(<AssignmentEditor mode="update" />);

  expect(screen.getByText("Review rubric:")).toBeInTheDocument();
  expect(screen.queryByText("Review round 2:")).not.toBeInTheDocument();
});

3. Prefills selected rubric for each round

This test confirms that the dropdown values are populated from assignment_questionnaires in loader data, ensuring round-specific rubric persistence across edits.

it("prefills the selected questionnaire per round from loader data", () => {
  loaderData = { ...baseAssignment };
  render(<AssignmentEditor mode="update" />);

  const round1Row = screen.getByText("Review round 1:").closest("tr");
  const select = within(round1Row as HTMLElement).getByRole("combobox") as HTMLSelectElement;
  expect(select.value).toBe("101");
});

4. Lists all questionnaires, including unlinked ones

This test checks that the dropdown displays all available questionnaires, not just those already linked to the assignment.

it("lists all available questionnaires, including unlinked ones", () => {
  loaderData = { ...baseAssignment };
  render(<AssignmentEditor mode="update" />);

  const allOptions = screen.getAllByRole("option").map((opt) => opt.textContent);
  expect(allOptions).toContain("Unlinked Rubric");
});


These tests below validate that the request payload is constructed correctly when form data is submitted.

5. Builds nested attributes for selected rounds

This test ensures transformAssignmentRequest generates the correct nested assignment_questionnaires_attributes, mapping selected rubrics to review rounds.

it("builds assignment_questionnaires_attributes for selected rounds", () => {
  const values: IAssignmentFormValues = {
    id: 1,
    name: "Test Assignment",
    directory_path: "assignment_1",
    spec_location: "http://example.com",
    private: false,
    review_rubric_varies_by_round: true,
    number_of_review_rounds: 2,
    questionnaire_round_1: 101,
    assignment_questionnaire_id_1: 10,
    questionnaire_round_2: 102,
    weights: [],
    notification_limits: [],
    use_date_updater: [],
    submission_allowed: [],
    review_allowed: [],
    teammate_allowed: [],
    metareview_allowed: [],
    reminder: [],
  };

  const payload = JSON.parse(transformAssignmentRequest(values));

  expect(payload.assignment.assignment_questionnaires_attributes).toEqual([
    { id: 10, questionnaire_id: 101, used_in_round: 1 },
    { questionnaire_id: 102, used_in_round: 2 },
  ]);
  expect(payload.assignment.vary_by_round).toBe(true);
  expect(payload.assignment.rounds_of_reviews).toBe(2);
});

6. Reuses existing ID and skips empty rounds

Ensures that a previously existing join row is preserved and that blank round selections are ignored.

it("includes existing id when present and skips rounds without selection", () => {
  const values: IAssignmentFormValues = {
    id: 1,
    name: "Test Assignment",
    questionnaire_round_1: 201,
    assignment_questionnaire_id_1: 99,
    review_rubric_varies_by_round: true,
    number_of_review_rounds: 2,
    weights: [],
    notification_limits: [],
    use_date_updater: [],
    submission_allowed: [],
    review_allowed: [],
    teammate_allowed: [],
    metareview_allowed: [],
    reminder: [],
  };

  const payload = JSON.parse(transformAssignmentRequest(values));
  expect(payload.assignment.assignment_questionnaires_attributes).toEqual([
    { id: 99, questionnaire_id: 201, used_in_round: 1 },
  ]);
});

7. Disables round-varying mode when unchecked

This test confirms that vary_by_round is set to false when the corresponding checkbox is disabled in the UI.

it("sets vary_by_round to false when checkbox is unchecked", () => {
  const values: IAssignmentFormValues = {
    id: 1,
    name: "Test Assignment",
    review_rubric_varies_by_round: false,
    number_of_review_rounds: 1,
    weights: [],
    notification_limits: [],
    use_date_updater: [],
    submission_allowed: [],
    review_allowed: [],
    teammate_allowed: [],
    metareview_allowed: [],
    reminder: [],
  };

  const payload = JSON.parse(transformAssignmentRequest(values));
  expect(payload.assignment.vary_by_round).toBe(false);
});

Testing results

To run only the test suite for the AssignmentEditor.tsx component, use:

npx vitest run src/pages/Assignments/AssignmentEditor.test.tsx --config vitest.config.mts

The test cases have successfully passed.

Links

Team

  • Mentor: Koushik Gudipelly (kgudipe@ncsu.edu)
  • Iman Khan (ikhan7@ncsu.edu)
  • Niranjan Rajendran (nrajend4@ncsu.edu)
  • Yu Wang (ywang374@ncsu.edu)