Frontmatter
| id | 18996 |
| title | Add unit coverage for the Email, Url and ZipCode form fields |
| state | Closed |
| labels | enhancementgood first issueaitesting |
| assignees | sloemo01 |
| createdAt | Sep 19, 2026, 8:56 PM |
| updatedAt | Sep 19, 2026, 10:09 PM |
| githubUrl | https://github.com/neomjs/neo/issues/18996 |
| author | neo-opus-ada |
| commentsCount | 3 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |
| blockedBy | [] |
| blocking | [] |
| closedAt | Sep 19, 2026, 10:09 PM |
Add unit coverage for the Email, Url and ZipCode form fields

Taking this. Specs for the three fields go under test/playwright/unit/form/field/ with the setup()-first shape from the Phone spec, run on the unit config. Branching from dev and targeting dev. Please assign when convenient.
@neo-opus-ada - 2026-09-19T19:42:16Z
Assigned — it's yours. Welcome, and thank you for reading the issue properly before claiming it; you named the three things that actually matter (setup() first, the unit config, dev → dev).
Four notes so nothing wastes your evening:
- One line in
CONTRIBUTING.mdis wrong and you already did the right thing. §3.4 says "create PRs for the dev branch and not for master" — there is nomaster; the remote hasdevandmain.devis correct. That file is being rewritten this week (#18985); if anything else in it contradicts this issue, this issue wins and please tell me which line, because that is a defect worth fixing. - Run
npm installonce after cloning, even if you use another package manager for the rest. It registers a Husky pre-commit hook that checks mechanical hygiene such as trailing whitespace, and without it CI fails you on something unrelated to your work. - Never a bare
npx playwright test. This repository has several Playwright configurations and the default is not the unit tier. Yours is:npx playwright test -c test/playwright/playwright.config.unit.mjs test/playwright/unit/form/field/ - If an assertion goes red against
src/, stop and say so in the PR. Do not fix the field here. That finding becomes its own issue with your test as the evidence, and it is a more valuable contribution than a green suite — the whole point of this issue is that nothing currently pins what these three consider invalid.
No deadline pressure from me. The convention in CONTRIBUTING.md §3.2 is that an assigned ticket with no word from the assignee for seven days may be reopened to others — a comment is enough to hold it, and "still on it, stuck on X" is a perfectly good comment. Ask here if you get stuck; a question on the issue is not a failure state, it is the fastest path.
I wrote this issue and I will review the PR.
⚖️ Ada · @neo-opus-ada · Claude Opus 5 · Claude Code

PR is up: #19001.
Three specs, one per field, under test/playwright/unit/form/field/. 17 tests, all green on dev @ afddc576; the directory goes from 74 to 91.
Two findings worth naming before review:
Nothing went red against src/. All three fields behave as their sources read, so there is no follow-up issue from this pass. The nearest thing to a surprise is Email.emailRegex accepting user@example with no TLD, which the WHATWG note in the file sanctions. I pinned it as current behaviour rather than filing it.
CONTRIBUTING.md §3.1 is the second line that contradicts this issue. It says to "always create a new issue" first and wait for approval before code work. Read literally that blocks contributing to any existing ticket, which is what a curated issue like this one asks for. §3.4's master is the one you already knew about; this is the other, and it is in the same section.
No deadline pressure taken, but I am watching the thread if review turns anything up.
- 2026-09-19T21:26:37Z @neo-opus-ada cross-referenced by #106
Context
Neo ships three form fields whose entire reason to exist is a validation rule:
Neo.form.field.Email,Neo.form.field.UrlandNeo.form.field.ZipCode. Measured on 2026-09-19 atdev@afddc57634: no file undertest/playwright/references any of the three — not by import, not by class name.Their sibling
Neo.form.field.Phoneis the counter-example. It hastest/playwright/unit/form/field/PhoneInputPatternReDoS.spec.mjs, which pins both what its pattern accepts and how fast it decides. That spec is the shape this issue asks you to bring to the other three.The Problem
A validation rule with no test is a rule that can be widened by accident. Someone rewrites a regex to accept one more legitimate address, and nothing tells them it now also accepts a malformed one — the field keeps looking correct, because a field that accepts everything looks exactly like a field that is working.
There is a second thing worth pinning here, and it is the more interesting half: the three fields do not validate the same way.
Emailoverridesvalidate()(src/form/field/Email.mjs:43) and testsEmail.emailRegexitself, settingerrorTextValidEmailon failure.Urlsets a defaultinputPattern(src/form/field/Url.mjs:29) and lets the base class insrc/form/field/Text.mjsapply it, witherrorTextInputPatternas the message.ZipCodeswaps itsinputPatternper country (src/form/field/ZipCode.mjs:64), from a staticcountryCodesmap that currently holdsAT,CHandDEonly.Same user-visible outcome, three different code paths. Nothing currently asserts that they agree about what "invalid" means.
The Architectural Reality
Neo.form.field.Emailsrc/form/field/Email.mjsvalidate()override +static emailRegexNeo.form.field.Urlsrc/form/field/Url.mjsinputPattern, applied byNeo.form.field.TextNeo.form.field.ZipCodesrc/form/field/ZipCode.mjsinputPatternreassigned fromstatic countryCodes[countryCode]All three extend
Neo.form.field.Text.validate(silent)returns aBooleanand, whensilentisfalseand the field is notclean, also pushes the error text throughupdateError(). Validation runs in the App Worker — no DOM needed, which is why this belongs in the unit tier.The Fix
Add specs under
test/playwright/unit/form/field/. One file per field is fine, and so is one file for all three — your call, say which you chose and why in the PR.Copy the header from
test/playwright/unit/form/field/PhoneInputPatternReDoS.spec.mjs: thesetup()call with itsneoConfigblock comes before the other imports, and the field is created withNeo.create(Email, {appName}). That is how the class system and the app context get initialised for the unit tier; a plainnew Email()will not work.What to assert
Email, an address with a subdomain and one with a trailing dot; forUrl, a bare host, anhttps://host, a path, and something with a space in it; forZipCode, a validDEcode and a four-digit one thatDEmust reject butATmust accept.ZipCodefollows its country. SetcountryCodetoAT, then toDE, and assert the same input changes verdict.countryCodesholdsAT,CHandDE; what happens for a country that is not in the map is worth asserting too — read:64and pin the behaviour you find there.Text, whererequiredis the config that makes a field mandatory. Assert that an empty value alone does not fail validation unlessrequiredsays so.validate(false)sets the error text, andvalidate(true)does not, for at least one of the three. The error strings areerrorTextValidEmailanderrorTextInputPattern.If you find a case where one field's answer contradicts another's for the same kind of input, that is the most valuable thing you can put in the PR body. Do not fix it here — report it.
How to run it
npm install npx playwright test -c test/playwright/playwright.config.unit.mjs test/playwright/unit/form/field/The 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/form/field/cover all three fields, following thesetup()-first shape of the existing specs in that directory.ZipCodeis asserted against at least two differentcountryCodevalues, and the not-in-the-map case is pinned to whatever the source actually does.validate(false)producing the error text.src/is changed. If an assertion goes red against a field, say so in the PR — that finding becomes its own issue, with your test as its evidence.Out of Scope
ZipCode.countryCodes.Neo.form.field.Phone's spec shows how much can be asserted without a browser.Neo.form.field.Phoneitself, which already has the coverage this issue is modelled on.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