All files / src/utils/helpers assignmentStatus.js

100% Statements 13/13
100% Branches 18/18
100% Functions 3/3
100% Lines 12/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92                                                            325x                                 141x 141x 141x   141x   28x   80x   14x   19x                                               43x 43x         40x    
/**
 * Assignment open/close status, shared by the teacher assignment list and the
 * assignment detail page.
 *
 * `assignmentStatus` was inline in
 * `app/teacher-page/page/assignments/AssignmentsPage.jsx`, where it
 * drives both the status chip and the "Data Analytics" gate. The detail page
 * (`view-assignment/AssignmentSettings.jsx`) had its own, incompatible date
 * comparison for the same button — see `hasAssignmentOpened` below. Sharing one
 * implementation is what stops the two drifting again.
 *
 * Dates arrive from the API as ISO strings WITHOUT a zone marker but are stored
 * as UTC, hence `{ zone: "utc" }` on every parse. Comparisons are between
 * instants, never between calendar days.
 *
 * Developer: Allan Ninal
 */
 
import { DateTime } from "luxon";
 
/**
 * Parse an API date as UTC.
 *
 * An UNPARSEABLE string deliberately yields luxon's Invalid DateTime rather than
 * null. It is truthy, and every comparison against it is false (its valueOf is
 * NaN) — which is what makes `assignmentStatus` classify a garbage date as
 * "INVALID" rather than falling through to "UNDEPLOYED". Normalising it to null
 * here would change that, and the status chip renders the result.
 */
function toUTC(dateString) {
    return dateString ? DateTime.fromISO(dateString, { zone: "utc" }) : null;
}
 
/**
 * Classify an assignment's window.
 *
 * Moved verbatim in behaviour from Main.jsx, including the cases that look odd:
 * "ASSIGNED" and "UNDEPLOYED" both require BOTH dates, so an assignment with a
 * date_open but no date_close falls through to "INVALID". That is preserved
 * deliberately — the status chip renders these strings today, and changing the
 * classification would change what teachers see in the list.
 *
 * @param {string|null|undefined} dateOpen
 * @param {string|null|undefined} dateClose
 * @returns {"CLOSED"|"ASSIGNED"|"UNDEPLOYED"|"INVALID"}
 */
export function assignmentStatus(dateOpen, dateClose) {
    const todayUTC = DateTime.utc();
    const dateOpenUTC = toUTC(dateOpen);
    const dateCloseUTC = toUTC(dateClose);
 
    switch (true) {
        case Boolean(dateCloseUTC) && dateCloseUTC < todayUTC:
            return "CLOSED";
        case Boolean(dateOpenUTC) && Boolean(dateCloseUTC) && dateOpenUTC <= todayUTC && todayUTC < dateCloseUTC:
            return "ASSIGNED";
        case (!dateOpenUTC && !dateCloseUTC) || (dateOpenUTC > todayUTC && dateCloseUTC > todayUTC):
            return "UNDEPLOYED";
        default:
            return "INVALID";
    }
}
 
/**
 * Has the assignment's open date passed?
 *
 * Replaces this, which the detail page used to gate its Data Analytics button:
 *
 *     const today = new Date();
 *     today.setHours(0, 0, 0, 0);          // LOCAL midnight...
 *     return today >= new Date(dateOpen);  // ...compared to a UTC instant
 *
 * Flattening to local midnight makes the comparison point EARLIER than now, by
 * up to a day plus the UTC offset — so the button stayed hidden long after the
 * assignment had opened. Measured in UTC+8: an assignment that opened 10 minutes
 * ago reported `false`.
 *
 * This compares instants, so it flips exactly when the assignment opens.
 *
 * @param {string|null|undefined} dateOpen
 * @returns {boolean}
 */
export function hasAssignmentOpened(dateOpen) {
    const dateOpenUTC = toUTC(dateOpen);
    if (!dateOpenUTC) return false;
 
    // An unparseable date needs no special case: luxon's Invalid DateTime has a
    // NaN valueOf, so this comparison is false — verified, and an explicit
    // `.isValid` guard here was redundant (a mutation removing it killed no test).
    return dateOpenUTC <= DateTime.utc();
}