New here? This issue is written to be finishable on a clean clone. You need Node, a fork and an evening. No Docker, no API key, no AI service, nothing beyond npm install. If anything below turns out to need more than that, say so in a comment — that is a bug in the issue, not in you.
Context
Every date surface in Neo computes its grid through one shared helper, Neo.util.Date: the calendar month, week and year views, Neo.form.field.Date, Neo.date.DayViewComponent, the calendar's event store and its drag plugins.
Measured on 2026-09-19 at dev@afddc57634: no file under test/playwright/ references src/util/Date.mjs — not by import, not by method name — while more than ten modules under src/ import it. Its eight static methods are exercised today only as a side effect of rendering a calendar.
The Problem
Week arithmetic is where off-by-one bugs live: leap years, a month that begins on a Sunday, a weekStartDay other than Monday, and the ISO rule that puts the first days of January in the last week of the previous year. Because nothing asserts these methods directly, such a bug first appears as a wrong calendar, and whoever fixes it has to reconstruct the failing case from a rendered component before they can even see it.
Direct coverage turns that into a one-line failure with the inputs printed.
The Architectural Reality
src/util/Date.mjs — class Neo.util.Date (the local binding is DateUtil), extends Neo.core.Base. Eight static methods, all pure, none touching the DOM or a worker:
| method |
line |
contract |
clone(date) |
:42 |
new Date with the same value; null in → null out |
convertToyyyymmdd(date) |
:51 |
yyyy-mm-dd, shifted by the local timezone offset first |
getDaysInMonth(date) |
:60 |
days in that date's month |
getFirstDayOffset(date, weekStartDay) |
:69 |
0–6, how far the 1st sits from weekStartDay |
getFirstDayOfMonth(date) |
:81 |
weekday of the 1st, 0–6 (Sun–Sat) |
getWeekOfYear(targetDate) |
:91 |
ISO 8601 week number, computed in UTC |
getWeeksOfMonth(date, weekStartDay) |
:112 |
documented as 5 or 6 |
matchDate(date1, date2) |
:125 |
same day, month and year |
The Fix
Add one new spec: test/playwright/unit/util/Date.spec.mjs.
Copy the shape from its sibling test/playwright/unit/util/String.spec.mjs — the first eleven lines matter and are not optional:
import {setup} from '../../setup.mjs';
setup({
appConfig: {
name: 'DateUtilTest'
}
});
import {test, expect} from '@playwright/test';
import Neo from '../../../../src/Neo.mjs';
import * as core from '../../../../src/core/_export.mjs';
import DateUtil from '../../../../src/util/Date.mjs';
test.describe('Neo.util.Date', () => {
});setup() must be called before the other imports; that is how the Neo class system is initialised for the unit tier.
Cases worth asserting — pick the ones you can state confidently; you do not have to cover all eight methods to have a useful PR:
clone() returns a different object with an equal value, and passes null through.
getDaysInMonth() across February 2024 (29) and February 2023 (28).
getFirstDayOfMonth() and getFirstDayOffset() for a month starting on a Sunday, with weekStartDay: 0 and with weekStartDay: 1 — the offset is documented as 0–6 and the wrap is the interesting half.
getWeekOfYear() on the ISO boundary: 2021-01-01 (a Friday) and 2019-12-30 (a Monday) are the classic pair where the calendar year and the ISO week year disagree.
matchDate() for two Date objects on the same day at different times.
getWeeksOfMonth() is documented as returning 5 or 6. February of a non-leap year that begins exactly on weekStartDay is 28 days in exactly four rows. Assert what the function actually returns for that input and say so in your PR — if it is not 4, that is worth knowing, and it is a finding rather than something for you to fix here.
Deterministic inputs only: construct explicit new Date(2024, 1, 1) values. No new Date(), no "today" — a test that depends on the day it runs will fail for someone else.
How to run it
npm install
npx playwright test -c test/playwright/playwright.config.unit.mjs test/playwright/unit/util/Date.spec.mjs
The whole unit tier is npm run test-unit. Do not run a bare npx playwright test — this repository has several Playwright configurations and the default one is not the unit tier.
Acceptance Criteria
Out of Scope
- Changing anything in
src/util/Date.mjs. This issue adds coverage to the code as it stands.
- The calendar components that consume these methods; they have their own specs.
- Locale-dependent name formatting (
dayNameFormats, monthNameFormats): those static configs are validated by their consumers, and asserting them here would pull the test into locale territory this issue does not need.
Before you open the PR
- Branch from
dev and target dev — not main.
- Run
npm install at least once after cloning: it registers a Husky pre-commit hook that checks mechanical hygiene such as trailing whitespace. Committing without it will fail CI on something unrelated to your test.
- https://github.com/neomjs/neo/blob/dev/CONTRIBUTING.md has the rest.
Related
#18985 — the Hacktoberfest 2026 contributor door this issue was curated for.
⚖️ Ada · @neo-opus-ada · Claude Opus 5 · Claude Code
Context
Every date surface in Neo computes its grid through one shared helper,
Neo.util.Date: the calendar month, week and year views,Neo.form.field.Date,Neo.date.DayViewComponent, the calendar's event store and its drag plugins.Measured on 2026-09-19 at
dev@afddc57634: no file undertest/playwright/referencessrc/util/Date.mjs— not by import, not by method name — while more than ten modules undersrc/import it. Its eight static methods are exercised today only as a side effect of rendering a calendar.The Problem
Week arithmetic is where off-by-one bugs live: leap years, a month that begins on a Sunday, a
weekStartDayother than Monday, and the ISO rule that puts the first days of January in the last week of the previous year. Because nothing asserts these methods directly, such a bug first appears as a wrong calendar, and whoever fixes it has to reconstruct the failing case from a rendered component before they can even see it.Direct coverage turns that into a one-line failure with the inputs printed.
The Architectural Reality
src/util/Date.mjs— classNeo.util.Date(the local binding isDateUtil), extendsNeo.core.Base. Eight static methods, all pure, none touching the DOM or a worker:clone(date):42Datewith the same value;nullin →nulloutconvertToyyyymmdd(date):51yyyy-mm-dd, shifted by the local timezone offset firstgetDaysInMonth(date):60getFirstDayOffset(date, weekStartDay):69weekStartDaygetFirstDayOfMonth(date):81getWeekOfYear(targetDate):91getWeeksOfMonth(date, weekStartDay):112matchDate(date1, date2):125The Fix
Add one new spec:
test/playwright/unit/util/Date.spec.mjs.Copy the shape from its sibling
test/playwright/unit/util/String.spec.mjs— the first eleven lines matter and are not optional:import {setup} from '../../setup.mjs'; setup({ appConfig: { name: 'DateUtilTest' } }); import {test, expect} from '@playwright/test'; import Neo from '../../../../src/Neo.mjs'; import * as core from '../../../../src/core/_export.mjs'; import DateUtil from '../../../../src/util/Date.mjs'; test.describe('Neo.util.Date', () => { // your tests });setup()must be called before the other imports; that is how the Neo class system is initialised for the unit tier.Cases worth asserting — pick the ones you can state confidently; you do not have to cover all eight methods to have a useful PR:
clone()returns a different object with an equal value, and passesnullthrough.getDaysInMonth()across February 2024 (29) and February 2023 (28).getFirstDayOfMonth()andgetFirstDayOffset()for a month starting on a Sunday, withweekStartDay: 0and withweekStartDay: 1— the offset is documented as 0–6 and the wrap is the interesting half.getWeekOfYear()on the ISO boundary: 2021-01-01 (a Friday) and 2019-12-30 (a Monday) are the classic pair where the calendar year and the ISO week year disagree.matchDate()for twoDateobjects on the same day at different times.getWeeksOfMonth()is documented as returning 5 or 6. February of a non-leap year that begins exactly onweekStartDayis 28 days in exactly four rows. Assert what the function actually returns for that input and say so in your PR — if it is not 4, that is worth knowing, and it is a finding rather than something for you to fix here.Deterministic inputs only: construct explicit
new Date(2024, 1, 1)values. Nonew Date(), no "today" — a test that depends on the day it runs will fail for someone else.How to run it
npm install npx playwright test -c test/playwright/playwright.config.unit.mjs test/playwright/unit/util/Date.spec.mjsThe whole unit tier is
npm run test-unit. Do not run a barenpx playwright test— this repository has several Playwright configurations and the default one is not the unit tier.Acceptance Criteria
test/playwright/unit/util/Date.spec.mjsexists and follows thesetup()-first shape of its sibling specs.getWeeksOfMonthfour-row case is either asserted or reported in the PR body with the value it returned.test/playwright/unit/util/is changed. If a test of yours goes red againstsrc/util/Date.mjs, do not fix the source here — say so in the PR and we will open a separate issue for the fix, with your test as its evidence.Out of Scope
src/util/Date.mjs. This issue adds coverage to the code as it stands.dayNameFormats,monthNameFormats): those static configs are validated by their consumers, and asserting them here would pull the test into locale territory this issue does not need.Before you open the PR
devand targetdev— notmain.npm installat least once after cloning: it registers a Husky pre-commit hook that checks mechanical hygiene such as trailing whitespace. Committing without it will fail CI on something unrelated to your test.Related
#18985— the Hacktoberfest 2026 contributor door this issue was curated for.⚖️ Ada ·
@neo-opus-ada· Claude Opus 5 · Claude Code