fix: enforce required validation on APM phone field - #273
fix: enforce required validation on APM phone field#273roshan-gorasia-cko wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes inconsistent APM phone field value shapes by standardizing on { dialing_code, number }, ensuring required validation works after the user clears the input, and preventing prefills from being submitted under the legacy value key. It also introduces a Vitest-based test harness to exercise the real (namespace-style) TypeScript sources without shipping test code in the SDK bundle.
Changes:
- Add
getComparableFieldValue()andnormalizePhoneValue()utilities to normalize phone values for validation and submission. - Update form validation, phone element behavior, and NextSteps seeding/prefill to use the canonical
{ dialing_code, number }shape. - Add Vitest + Node test support and CI wiring, with regression tests covering normalization and required validation.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Adds Vitest and its transitive dependencies to the lockfile. |
| vitest.config.ts | Configures Vitest to run Node-based TypeScript tests under test/**/*.test.ts. |
| test/support/loadNamespace.ts | Provides a harness to transpile and execute namespace-style SDK sources for testing. |
| test/apm/phone-value-shape.test.ts | Adds coverage for normalizePhoneValue() coercion into { dialing_code, number }. |
| test/apm/phone-validation.test.ts | Adds regression coverage for required validation across value vs number shapes. |
| src/apm/views/utils/form.ts | Switches validation to normalize values via getComparableFieldValue(). |
| src/apm/views/NextSteps.ts | Normalizes phone seed/prefill values via normalizePhoneValue(). |
| src/apm/utils.ts | Introduces utilities for comparable validation values and canonical phone normalization. |
| src/apm/types.ts | Updates InitialData.phone_number typing to prefer number and keep value as deprecated alias. |
| src/apm/elements/phone.ts | Aligns Phone element value shape to { dialing_code, number } and propagates clears for validation. |
| package.json | Adds vitest and test scripts; includes tests in verify. |
| eslint.config.mjs | Marks tests and Vitest config as Node-global (not browser-global). |
| .github/workflows/build-lint-test.yml | Runs yarn test in CI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/apm/views/utils/form.ts:33
- The required validation still checks
typeof value === "undefined"instead of the normalisedactualValue. If a phone value comes through as an object with{ number: undefined }(or{ value: undefined }),getComparableFieldValue()returnsundefinedbut the required rule will not fire, allowing an empty required phone to pass.
switch (true) {
case validation.required &&
(typeof value === "undefined" || (typeof actualValue === "string" && actualValue.length === 0)): {
return "Missing required value"
}
The APM phone form value has two shapes: the Phone component emits the
number under `number` once the shopper interacts, while the initial /
prefilled seed carries it under the legacy `value` key. validateField only
read `value`, so after the shopper typed a number and removed it the value
was `{ number: "" }` — an object without a `value` key — which the required
check treated as a non-empty value and passed. Required validation
effectively disappeared after any interaction.
- add getComparableFieldValue() in utils.ts to normalise a field value
(canonical `number`, falling back to legacy `value`) and use it in
validateField
- emit oninput when the number is cleared back into the dialing-code prefix
in phone.ts, so a full clear propagates to the form instead of leaving a
stale value
- add Vitest regression coverage on the existing test harness
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9a2bd67 to
b850415
Compare
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| function isMissingRequired(value: unknown): boolean { | ||
| const actualValue = getComparableFieldValue(value) | ||
| return ( | ||
| typeof value === "undefined" || | ||
| (typeof actualValue === "string" && actualValue.length === 0) | ||
| ) | ||
| } |
There was a problem hiding this comment.
nit: Not really sure we need this function. We create it in the tests and then test it?
If we want to test getComparableFieldValue(), we could just make explicit assertions on its return values explicitly instead? 🤔
Fixes a defect in the embedded APM (`client.apm.authorization` / `apm.tokenization`) phone field where the "required" validation stops firing after interaction.
The bug
If a required phone number is entered and then removed, the "required" validation stops firing and the field can be submitted empty.
The phone field is an object, and its digits live under different keys depending on origin: the Phone component emits `{ dialing_code, number }` after the shopper interacts, while the initial/prefilled seed uses a legacy `value` key. After a shopper types a number and clears it, the value is `{ number: "" }`, but `validateField` only read the `value` key — so it saw a non-empty object and the required check passed.
The fix
Tests