From 492a89ea9e079cbe9ef904f422400d8a7f59751c Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Wed, 29 Jan 2025 16:21:33 -0800 Subject: [PATCH] Correct Rollover Year Options Courses can always be rolled over into the previous, current, and several future years. The academic year doesn't map to the calendar year, so we always need to ensure we're starting at the right place otherwise the current year will disappear in January. From January through the end of June we need to back up even further because the previous academic year is now two years behind the current calendar year. --- .../addon/components/course/rollover.js | 9 +- .../components/course/rollover-test.js | 147 ++++++++++++++++-- 2 files changed, 144 insertions(+), 12 deletions(-) diff --git a/packages/ilios-common/addon/components/course/rollover.js b/packages/ilios-common/addon/components/course/rollover.js index b37eec1aad..3592d2a030 100644 --- a/packages/ilios-common/addon/components/course/rollover.js +++ b/packages/ilios-common/addon/components/course/rollover.js @@ -26,10 +26,15 @@ export default class CourseRolloverComponent extends Component { constructor() { super(...arguments); - const currentYear = DateTime.now().year; + let { month, year } = DateTime.now(); + year--; // start with the previous year + if (month < 7) { + // before July 1st (start of a new academic year) show the year before + year--; + } this.years = []; for (let i = 0; i < 6; i++) { - this.years.push(currentYear + i); + this.years.push(year + i); } } diff --git a/packages/test-app/tests/integration/components/course/rollover-test.js b/packages/test-app/tests/integration/components/course/rollover-test.js index 795d364d71..d0dc6317e7 100644 --- a/packages/test-app/tests/integration/components/course/rollover-test.js +++ b/packages/test-app/tests/integration/components/course/rollover-test.js @@ -8,11 +8,26 @@ import { hbs } from 'ember-cli-htmlbars'; import { DateTime } from 'luxon'; import { setupMirage } from 'test-app/tests/test-support/mirage'; import queryString from 'query-string'; +import { freezeDateAt, unfreezeDate } from 'ilios-common'; module('Integration | Component | course/rollover', function (hooks) { setupRenderingTest(hooks); setupMirage(hooks); + hooks.afterEach(() => { + unfreezeDate(); + }); + + const earliestRolloverYear = (jsDate) => { + let { month, year } = DateTime.fromJSDate(jsDate); + year--; // start with the previous year + if (month < 7) { + // before July 1st (start of a new academic year) show the year before + year--; + } + return year; + }; + test('it renders', async function (assert) { const school = this.server.create('school'); const course = this.server.create('course', { @@ -24,12 +39,12 @@ module('Integration | Component | course/rollover', function (hooks) { await render(hbs``); - const currentYear = DateTime.now().year; + const firstYear = earliestRolloverYear(new Date()); const yearSelect = '.year-select select'; const title = '.title input'; for (let i = 0; i < 6; i++) { - assert.dom(`${yearSelect} option:nth-of-type(${i + 1})`).hasText(`${currentYear + i}`); + assert.dom(`${yearSelect} option:nth-of-type(${i + 1})`).hasText(`${firstYear + i}`); } assert.dom(title).exists({ count: 1 }); assert.strictEqual(find(title).value.trim(), course.title); @@ -53,12 +68,12 @@ module('Integration | Component | course/rollover', function (hooks) { await render(hbs``); - const currentYear = DateTime.now().year; + const firstYear = earliestRolloverYear(new Date()); const yearSelect = '.year-select select'; for (let i = 0; i < 6; i++) { assert .dom(`${yearSelect} option:nth-of-type(${i + 1})`) - .hasText(`${currentYear + i} - ${currentYear + i + 1}`); + .hasText(`${firstYear + i} - ${firstYear + i + 1}`); } }); @@ -74,10 +89,10 @@ module('Integration | Component | course/rollover', function (hooks) { this.set('course', courseModel); this.server.post(`/api/courses/${course.id}/rollover`, function (schema, request) { - const currentYear = DateTime.now().year; + const firstYear = earliestRolloverYear(new Date()); const data = queryString.parse(request.requestBody); assert.ok('year' in data); - assert.strictEqual(parseInt(data.year, 10), currentYear); + assert.strictEqual(parseInt(data.year, 10), firstYear); assert.strictEqual(data.newCourseTitle, course.title); assert.ok('newStartDate' in data); return this.serialize( @@ -163,24 +178,24 @@ module('Integration | Component | course/rollover', function (hooks) { test('disable years when title already exists', async function (assert) { const title = 'to be rolled'; - const currentYear = DateTime.now().year; + const firstYear = earliestRolloverYear(new Date()); const school = this.server.create('school'); const course = this.server.create('course', { title, school, - year: currentYear - 1, + year: firstYear - 1, }); this.server.create('course', { id: 2, school, title, - year: currentYear, + year: firstYear, }); this.server.create('course', { id: 3, school, title, - year: currentYear + 2, + year: firstYear + 2, }); const courseModel = await this.owner.lookup('service:store').findRecord('course', course.id); @@ -510,4 +525,116 @@ module('Integration | Component | course/rollover', function (hooks) { await click(firstCohort); await click('.done'); }); + + test('dates are correct in December', async function (assert) { + const december11th2024 = DateTime.fromObject({ + year: 2024, + month: 12, + day: 11, + hour: 8, + }); + freezeDateAt(december11th2024.toJSDate()); + const school = this.server.create('school'); + const course = this.server.create('course', { + title: 'old course', + school, + }); + const courseModel = await this.owner.lookup('service:store').findRecord('course', course.id); + this.set('course', courseModel); + + await render(hbs``); + + const yearSelect = '.year-select select'; + const title = '.title input'; + + const years = [2023, 2024, 2025, 2026, 2027, 2028]; + years.forEach((year, i) => { + assert.dom(`${yearSelect} option:nth-of-type(${i + 1})`).hasText(year.toString()); + }); + assert.strictEqual(find(title).value.trim(), course.title); + }); + + test('dates are correct in January', async function (assert) { + const january1st2025 = DateTime.fromObject({ + year: 2025, + month: 1, + day: 1, + hour: 8, + }); + freezeDateAt(january1st2025.toJSDate()); + const school = this.server.create('school'); + const course = this.server.create('course', { + title: 'old course', + school, + }); + const courseModel = await this.owner.lookup('service:store').findRecord('course', course.id); + this.set('course', courseModel); + + await render(hbs``); + + const yearSelect = '.year-select select'; + const title = '.title input'; + + const years = [2023, 2024, 2025, 2026, 2027, 2028]; + years.forEach((year, i) => { + assert.dom(`${yearSelect} option:nth-of-type(${i + 1})`).hasText(year.toString()); + }); + assert.strictEqual(find(title).value.trim(), course.title); + }); + + test('dates are correct in June', async function (assert) { + const june30th2025 = DateTime.fromObject({ + year: 2025, + month: 6, + day: 30, + hour: 8, + }); + freezeDateAt(june30th2025.toJSDate()); + const school = this.server.create('school'); + const course = this.server.create('course', { + title: 'old course', + school, + }); + const courseModel = await this.owner.lookup('service:store').findRecord('course', course.id); + this.set('course', courseModel); + + await render(hbs``); + + const yearSelect = '.year-select select'; + const title = '.title input'; + + const years = [2023, 2024, 2025, 2026, 2027, 2028]; + years.forEach((year, i) => { + assert.dom(`${yearSelect} option:nth-of-type(${i + 1})`).hasText(year.toString()); + }); + assert.strictEqual(find(title).value.trim(), course.title); + }); + + test('dates are correct in July', async function (assert) { + const july1st2025 = DateTime.fromObject({ + year: 2025, + month: 7, + day: 1, + hour: 8, + }); + freezeDateAt(july1st2025.toJSDate()); + const school = this.server.create('school'); + const course = this.server.create('course', { + title: 'old course', + school, + }); + const courseModel = await this.owner.lookup('service:store').findRecord('course', course.id); + this.set('course', courseModel); + + await render(hbs``); + + const yearSelect = '.year-select select'; + const title = '.title input'; + + const years = [2024, 2025, 2026, 2027, 2028, 2029]; + years.forEach((year, i) => { + assert.dom(`${yearSelect} option:nth-of-type(${i + 1})`).hasText(year.toString()); + }); + assert.strictEqual(find(title).value.trim(), course.title); + }); });