Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/applications/education/22-1990n/app-entry.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'platform/polyfills';
import './sass/22-1990n.scss';

import startApp from 'platform/startup';

import routes from './routes';
import reducer from './reducers';
import manifest from './manifest.json';

startApp({
entryName: manifest.entryName,
url: manifest.rootUrl,
reducer,
routes,
});
17 changes: 17 additions & 0 deletions src/applications/education/22-1990n/components/GetFormHelp.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';

export const GetFormHelp = () => (
<div className="help-footer-box">
<p>
Call the GI Bill Hotline at <va-telephone contact="8884424551" /> (TTY:{' '}
<va-telephone contact="711" />
). We’re here Monday through Friday, 8:00 a.m. to 7:00 p.m. ET.
</p>
<p>
You can also{' '}
<a href="https://www.va.gov/contact-us/">contact us online</a>.
</p>
</div>
);

export default GetFormHelp;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import { expect } from 'chai';
import { render } from '@testing-library/react';
import GetFormHelp from './GetFormHelp';

describe('GetFormHelp component', () => {
it('renders without throwing', () => {
expect(() => render(<GetFormHelp />)).to.not.throw();
});

it('renders telephone elements', () => {
const { container } = render(<GetFormHelp />);
const phoneElements = container.querySelectorAll('va-telephone');
expect(phoneElements.length).to.be.greaterThan(0);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import {
ssnUI,
ssnSchema,
currentOrPastDateUI,
currentOrPastDateSchema,
radioUI,
radioSchema,
fullNameNoSuffixUI,
fullNameNoSuffixSchema,
textUI,
textSchema,
selectUI,
selectSchema,
phoneUI,
phoneSchema,
emailUI,
emailSchema,
} from 'platform/forms-system/src/js/web-component-patterns';

import { states } from 'platform/forms/address';

// ── Screen 1: Personal Information (Items 1, 2, 3) ──────────────────────────

export const personalInformationUiSchema = {
veteranSocialSecurityNumber: {
...ssnUI(),
'ui:options': {
hint: 'Enter your 9-digit Social Security number (for example: 123-45-6789)',
widgetClassNames: 'usa-input-medium',
},
},
veteranDateOfBirth: currentOrPastDateUI({
title: 'Date of birth',
hint: 'For example: January 19 1970',
errorMessages: {
required: 'Please enter a valid date of birth',
futureDate: 'Date of birth must be in the past',
},
}),
gender: radioUI({
title: 'Sex',
labels: {
F: 'Female',
M: 'Male',
},
required: () => true,
errorMessages: {
required: 'Please select your sex',
},
}),
};

export const personalInformationSchema = {
type: 'object',
required: ['veteranSocialSecurityNumber', 'veteranDateOfBirth', 'gender'],
properties: {
veteranSocialSecurityNumber: ssnSchema,
veteranDateOfBirth: currentOrPastDateSchema,
gender: radioSchema(['F', 'M']),
},
};

// ── Screen 2: Name (Item 4) ──────────────────────────────────────────────────

export const nameUiSchema = {
veteranFullName: fullNameNoSuffixUI(title => `Your ${title}`),
};

export const nameSchema = {
type: 'object',
required: ['veteranFullName'],
properties: {
veteranFullName: fullNameNoSuffixSchema,
},
};

// ── Screen 3: Address (Item 5) ───────────────────────────────────────────────

const stateLabels = states.USA.reduce((acc, { value, label }) => {
acc[value] = label;
return acc;
}, {});

export const addressUiSchema = {
veteranAddress: {
street: textUI({
title: 'Street address',
autocomplete: 'address-line1',
errorMessages: { required: 'Please enter a street address' },
}),
street2: textUI({
title: 'Apartment or unit number',
autocomplete: 'address-line2',
}),
city: textUI({
title: 'City',
autocomplete: 'address-level2',
errorMessages: { required: 'Please enter a city' },
}),
state: selectUI({
title: 'State',
labels: stateLabels,
autocomplete: 'address-level1',
errorMessages: { required: 'Please select a state' },
}),
postalCode: textUI({
title: 'ZIP code',
hint: 'Enter a 5-digit ZIP code (for example: 12345)',
autocomplete: 'postal-code',
inputType: 'text',
errorMessages: { required: 'Please enter a valid 5-digit ZIP code' },
}),
},
};

export const addressSchema = {
type: 'object',
required: ['veteranAddress'],
properties: {
veteranAddress: {
type: 'object',
required: ['street', 'city', 'state', 'postalCode'],
properties: {
street: { type: 'string', minLength: 1, maxLength: 50 },
street2: { type: 'string', maxLength: 50 },
city: { type: 'string', minLength: 1, maxLength: 30 },
state: selectSchema(Object.keys(stateLabels)),
postalCode: { type: 'string', pattern: '^\\d{5}(-\\d{4})?$', maxLength: 10 },
},
},
},
};

// ── Screen 4: Contact Information (Items 6A, 6B) ─────────────────────────────

function validateAtLeastOnePhone(errors, formData) {
if (!formData.homePhone && !formData.mobilePhone) {
errors.homePhone.addError('Please provide at least one phone number');
}
}

export const contactInformationUiSchema = {
'ui:validations': [validateAtLeastOnePhone],
homePhone: phoneUI({
title: 'Home phone number',
hint: 'Enter 10 digits (for example: 800-555-1234)',
errorMessages: {
pattern: 'Please enter a valid 10-digit phone number',
},
}),
mobilePhone: phoneUI({
title: 'Mobile phone number',
hint: 'Enter 10 digits (for example: 800-555-1234)',
errorMessages: {
pattern: 'Please enter a valid 10-digit phone number',
},
}),
email: emailUI({
title: 'Email address',
hint: 'For example: name@example.com',
errorMessages: {
pattern: 'Please enter a valid email address',
},
}),
};

export const contactInformationSchema = {
type: 'object',
properties: {
homePhone: phoneSchema,
mobilePhone: { type: 'string', pattern: '^\\d{10}$', minLength: 10, maxLength: 10 },
email: { type: 'string', format: 'email', maxLength: 255 },
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { expect } from 'chai';
import {
personalInformationUiSchema,
personalInformationSchema,
nameUiSchema,
nameSchema,
addressUiSchema,
addressSchema,
contactInformationUiSchema,
contactInformationSchema,
} from './applicantInformation';

describe('applicantInformation chapter', () => {
describe('personalInformationUiSchema', () => {
it('exports a valid uiSchema object', () => {
expect(personalInformationUiSchema).to.be.an('object');
expect(personalInformationUiSchema).to.have.property('veteranSocialSecurityNumber');
expect(personalInformationUiSchema).to.have.property('veteranDateOfBirth');
expect(personalInformationUiSchema).to.have.property('gender');
});

it('gender field has required function', () => {
const genderField = personalInformationUiSchema.gender;
expect(genderField).to.be.an('object');
});
});

describe('personalInformationSchema', () => {
it('has required fields', () => {
expect(personalInformationSchema.required).to.include('veteranSocialSecurityNumber');
expect(personalInformationSchema.required).to.include('veteranDateOfBirth');
expect(personalInformationSchema.required).to.include('gender');
});
});

describe('nameUiSchema', () => {
it('has veteranFullName field', () => {
expect(nameUiSchema).to.have.property('veteranFullName');
});
});

describe('addressUiSchema', () => {
it('has veteranAddress with street, city, state, postalCode', () => {
expect(addressUiSchema.veteranAddress).to.have.property('street');
expect(addressUiSchema.veteranAddress).to.have.property('city');
expect(addressUiSchema.veteranAddress).to.have.property('state');
expect(addressUiSchema.veteranAddress).to.have.property('postalCode');
});
});

describe('contactInformationUiSchema', () => {
it('has phone and email fields', () => {
expect(contactInformationUiSchema).to.have.property('homePhone');
expect(contactInformationUiSchema).to.have.property('mobilePhone');
expect(contactInformationUiSchema).to.have.property('email');
});

describe('validateAtLeastOnePhone (ui:validations)', () => {
let messages;
let errors;

beforeEach(() => {
messages = [];
errors = {
homePhone: { addError: msg => messages.push(msg || '') },
mobilePhone: { addError: msg => messages.push(msg || '') },
};
});

it('adds an error when both phones are missing', () => {
const validationFn = contactInformationUiSchema['ui:validations'][0];
validationFn(errors, { homePhone: '', mobilePhone: '' });
expect(messages.length).to.be.greaterThan(0);
});

it('does not error when homePhone is provided', () => {
const validationFn = contactInformationUiSchema['ui:validations'][0];
validationFn(errors, { homePhone: '8005551234', mobilePhone: '' });
expect(messages).to.have.lengthOf(0);
});

it('does not error when mobilePhone is provided', () => {
const validationFn = contactInformationUiSchema['ui:validations'][0];
validationFn(errors, { homePhone: '', mobilePhone: '8005551234' });
expect(messages).to.have.lengthOf(0);
});

it('does not error when both phones are provided', () => {
const validationFn = contactInformationUiSchema['ui:validations'][0];
validationFn(errors, { homePhone: '8005551234', mobilePhone: '8005551235' });
expect(messages).to.have.lengthOf(0);
});
});
});
});
Loading