LearnNewsExamplesServices
Frontmatter
id18996
titleAdd unit coverage for the Email, Url and ZipCode form fields
stateClosed
labels
enhancementgood first issueaitesting
assigneessloemo01
createdAtSep 19, 2026, 8:56 PM
updatedAtSep 19, 2026, 10:09 PM
githubUrlhttps://github.com/neomjs/neo/issues/18996
authorneo-opus-ada
commentsCount3
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 19, 2026, 10:09 PM

Add unit coverage for the Email, Url and ZipCode form fields

Closed v13.2.0/archive-v13-2-0-chunk-22 enhancementgood first issueaitesting
neo-opus-ada
neo-opus-ada commented on Sep 19, 2026, 8:56 PM

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

Neo ships three form fields whose entire reason to exist is a validation rule: Neo.form.field.Email, Neo.form.field.Url and Neo.form.field.ZipCode. Measured on 2026-09-19 at dev@afddc57634: no file under test/playwright/ references any of the three — not by import, not by class name.

Their sibling Neo.form.field.Phone is the counter-example. It has test/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.

  • Email overrides validate() (src/form/field/Email.mjs:43) and tests Email.emailRegex itself, setting errorTextValidEmail on failure.
  • Url sets a default inputPattern (src/form/field/Url.mjs:29) and lets the base class in src/form/field/Text.mjs apply it, with errorTextInputPattern as the message.
  • ZipCode swaps its inputPattern per country (src/form/field/ZipCode.mjs:64), from a static countryCodes map that currently holds AT, CH and DE only.

Same user-visible outcome, three different code paths. Nothing currently asserts that they agree about what "invalid" means.

The Architectural Reality

field file validation mechanism
Neo.form.field.Email src/form/field/Email.mjs own validate() override + static emailRegex
Neo.form.field.Url src/form/field/Url.mjs default inputPattern, applied by Neo.form.field.Text
Neo.form.field.ZipCode src/form/field/ZipCode.mjs inputPattern reassigned from static countryCodes[countryCode]

All three extend Neo.form.field.Text. validate(silent) returns a Boolean and, when silent is false and the field is not clean, also pushes the error text through updateError(). 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: the setup() call with its neoConfig block comes before the other imports, and the field is created with Neo.create(Email, {appName}). That is how the class system and the app context get initialised for the unit tier; a plain new Email() will not work.

What to assert

  • Accepts and rejects. For each field, a list of values that must pass and a list that must fail, each written out literally. Aim for the boundary, not the obvious middle: for Email, an address with a subdomain and one with a trailing dot; for Url, a bare host, an https:// host, a path, and something with a space in it; for ZipCode, a valid DE code and a four-digit one that DE must reject but AT must accept.
  • ZipCode follows its country. Set countryCode to AT, then to DE, and assert the same input changes verdict. countryCodes holds AT, CH and DE; what happens for a country that is not in the map is worth asserting too — read :64 and pin the behaviour you find there.
  • Empty is not invalid. All three sit on top of Text, where required is the config that makes a field mandatory. Assert that an empty value alone does not fail validation unless required says so.
  • validate(false) sets the error text, and validate(true) does not, for at least one of the three. The error strings are errorTextValidEmail and errorTextInputPattern.

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 bare npx playwright test — this repository has several Playwright configurations and the default one is not the unit tier.

Acceptance Criteria

  • New spec or specs under test/playwright/unit/form/field/ cover all three fields, following the setup()-first shape of the existing specs in that directory.
  • Each field has both an accepted set and a rejected set, with the values written out literally.
  • ZipCode is asserted against at least two different countryCode values, and the not-in-the-map case is pinned to whatever the source actually does.
  • At least one assertion covers validate(false) producing the error text.
  • The specs pass on the unit configuration, and the PR body says how you ran them.
  • No file under 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

  • Changing any validation rule or regex. This issue pins the current behaviour; changing it is a separate conversation.
  • Adding countries to ZipCode.countryCodes.
  • Rendering the fields or testing their DOM. This is the unit tier; Neo.form.field.Phone's spec shows how much can be asserted without a browser.
  • Neo.form.field.Phone itself, which already has the coverage this issue is modelled on.

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

sloemo01
sloemo01 Sep 19, 2026, 9:40 PM

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:

  1. One line in CONTRIBUTING.md is wrong and you already did the right thing. §3.4 says "create PRs for the dev branch and not for master" — there is no master; the remote has dev and main. dev is 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.
  2. Run npm install once 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.
  3. 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/
    
  4. 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

sloemo01 cross-referenced by PR #19001 on Sep 19, 2026, 9:49 PM
sloemo01
sloemo01 Sep 19, 2026, 9:49 PM

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