diff --git a/acrobat/blocks/nonprofit/icons.js b/acrobat/blocks/nonprofit/icons.js
new file mode 100644
index 00000000..fe173edc
--- /dev/null
+++ b/acrobat/blocks/nonprofit/icons.js
@@ -0,0 +1,28 @@
+const nonprofitIcons = {
+ CHEVRON_DOWN:
+ '',
+ CHEVRON_RIGHT:
+ '',
+ BACK: '',
+ CLOSE:
+ '',
+ UPLOAD:
+ '',
+};
+
+export const NONPRFIT_ICONS = Object.freeze({
+ CHEVRON_DOWN: 'CHEVRON_DOWN',
+ CHEVRON_RIGHT: 'CHEVRON_RIGHT',
+ BACK: 'BACK',
+ CLOSE: 'CLOSE',
+ UPLOAD: 'UPLOAD',
+});
+
+export function getNonprofitIconTag(type) {
+ const iconString = nonprofitIcons[type];
+ const wrapper = document.createElement('div');
+ wrapper.innerHTML = iconString;
+ const icon = wrapper.querySelector('svg');
+ icon.classList.add('np-icon');
+ return icon;
+}
diff --git a/acrobat/blocks/nonprofit/icons/back.svg b/acrobat/blocks/nonprofit/icons/back.svg
deleted file mode 100644
index 0f7da59e..00000000
--- a/acrobat/blocks/nonprofit/icons/back.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/acrobat/blocks/nonprofit/icons/chevron-right.svg b/acrobat/blocks/nonprofit/icons/chevron-right.svg
deleted file mode 100644
index 302537fb..00000000
--- a/acrobat/blocks/nonprofit/icons/chevron-right.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/acrobat/blocks/nonprofit/icons/close.svg b/acrobat/blocks/nonprofit/icons/close.svg
deleted file mode 100644
index 83193803..00000000
--- a/acrobat/blocks/nonprofit/icons/close.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
\ No newline at end of file
diff --git a/acrobat/blocks/nonprofit/icons/upload.svg b/acrobat/blocks/nonprofit/icons/upload.svg
deleted file mode 100644
index 0a9eb531..00000000
--- a/acrobat/blocks/nonprofit/icons/upload.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/acrobat/blocks/nonprofit/nonprofit-select.js b/acrobat/blocks/nonprofit/nonprofit-select.js
index bafe015b..af1d5323 100644
--- a/acrobat/blocks/nonprofit/nonprofit-select.js
+++ b/acrobat/blocks/nonprofit/nonprofit-select.js
@@ -1,8 +1,7 @@
/* eslint-disable chai-friendly/no-unused-expressions */
/* eslint-disable max-len */
import ReactiveStore from '../../scripts/reactiveStore.js';
-
-const chevronDownSvg = '';
+import { getNonprofitIconTag, NONPRFIT_ICONS } from './icons.js';
export default function nonprofitSelect(props) {
const {
@@ -37,6 +36,7 @@ export default function nonprofitSelect(props) {
class: 'np-input np-select-search',
type: 'text',
placeholder,
+ 'data-for': name,
});
const valueTag = createTag('input', {
class: `np-select-value${required ? ' np-required-field' : ''}`,
@@ -54,7 +54,7 @@ export default function nonprofitSelect(props) {
}
const listContainerTag = createTag('div', { class: 'np-select-list-container' });
- const listTag = createTag('ul', { class: 'np-select-list' });
+ const listTag = createTag('ul', { class: 'np-select-list', 'data-for': name });
let searchTimeout;
let abortController;
@@ -208,12 +208,18 @@ export default function nonprofitSelect(props) {
listTag.append(itemTag);
});
+ const infoTagKeydown = () => {
+ focusedFromList = true;
+ searchTag.focus();
+ };
+
if (!loading && storeOptions.length === 0) {
const noOptionsTag = createTag(
'div',
{ class: 'np-select-list-tag np-select-no-options', tabindex: -1 },
noOptionsText,
);
+ noOptionsTag.addEventListener('keydown', infoTagKeydown);
noOptionsTag.addEventListener('focusout', focusOut);
listTag.append(noOptionsTag);
}
@@ -224,6 +230,7 @@ export default function nonprofitSelect(props) {
{ class: 'np-select-list-tag np-select-loader', tabindex: -1 },
`${loadingText}...`,
);
+ loadingTag.addEventListener('keydown', infoTagKeydown);
loadingTag.addEventListener('focusout', focusOut);
listTag.append(loadingTag);
}
@@ -245,10 +252,7 @@ export default function nonprofitSelect(props) {
controlTag.append(labelTag, searchTag, valueTag, listContainerTag);
if (!hideIcon) {
- const arrowIconTag = createTag('div', { class: 'np-select-icon' }, chevronDownSvg);
- arrowIconTag.addEventListener('click', () => {
- searchTag.focus();
- });
+ const arrowIconTag = getNonprofitIconTag(NONPRFIT_ICONS.CHEVRON_DOWN);
controlTag.append(arrowIconTag);
}
diff --git a/acrobat/blocks/nonprofit/nonprofit.css b/acrobat/blocks/nonprofit/nonprofit.css
index b10b6e27..ffaca851 100644
--- a/acrobat/blocks/nonprofit/nonprofit.css
+++ b/acrobat/blocks/nonprofit/nonprofit.css
@@ -23,6 +23,10 @@
box-sizing: border-box;
}
+.np-icon path {
+ fill: var(--color-gray-600);
+}
+
.np-stepper-container {
display: flex;
align-items: center;
@@ -78,8 +82,10 @@
.np-step-separator {
width: 12px;
height: 12px;
- background: url(./icons/chevron-right.svg) no-repeat;
- background-position: top 2px left 3px;
+}
+
+.np-step-separator.np-icon path {
+ fill: none;
}
.np-stepper-back {
@@ -98,10 +104,20 @@
transition: left 200ms;
}
-.np-stepper-back:hover {
+.np-stepper-back:hover,
+.np-stepper-back:focus {
background-color: var(--color-gray-300);
}
+.np-stepper-back:hover .np-icon path,
+.np-stepper-back:focus .np-icon path {
+ fill: var(--color-gray-800);
+}
+
+.np-stepper-back.disabled {
+ background-color: var(--color-gray-100);
+}
+
.np-content-container {
display: flex;
flex-direction: column;
@@ -136,6 +152,24 @@
gap: 25px;
}
+.np-button {
+ background-color: var(--link-color);
+ color: var(--color-white);
+ font-weight: 700;
+ font-size: 16px;
+ line-height: 20px;
+ border: none;
+ border-radius: 20px;
+ padding: 10px 20px;
+ cursor: pointer;
+}
+
+.np-button:disabled {
+ background-color: var(--np-button-disabled-background);
+ color: var(--np-button-disabled-color);
+ cursor: default;
+}
+
.np-form .np-button {
align-self: start;
}
@@ -172,33 +206,23 @@
color: var(--np-input-placeholder-color);
}
-.np-input[type='file'] {
- color: transparent;
- user-select: none;
- position: relative;
- background: url(./icons/upload.svg) no-repeat;
- background-size: 20px;
- background-position: right 10px bottom 10px;
+.np-control .np-icon {
+ position: absolute;
+ height: 12px;
+ right: 15px;
+ top: 39px;
+ display: flex;
cursor: pointer;
+ pointer-events: none;
}
-.np-input[type='file']:valid {
- color: inherit;
-}
-
-.np-input[type='file']:valid::after {
- display: none;
-}
-
-.np-input[type='file']::after {
- content: attr(data-placeholder);
- position: absolute;
- left: 15px;
- color: var(--np-input-placeholder-color);
+.np-input:disabled ~ .np-icon path,
+.np-stepper-back.disabled .np-icon path {
+ fill: var(--color-gray-400);
}
-.np-input[type='file']::file-selector-button {
- display: none;
+.np-input[type='file'] ~ .np-input {
+ cursor: pointer;
}
.np-select-search {
@@ -208,19 +232,6 @@
white-space: nowrap;
}
-.np-select-icon {
- position: absolute;
- padding: 6px;
- right: 6px;
- top: 36px;
- display: flex;
- cursor: pointer;
-}
-
-.np-input:disabled ~ .np-select-icon path {
- fill: var(--color-gray-400);
-}
-
.np-select-list-container {
display: none;
position: absolute;
@@ -352,24 +363,24 @@
background-color: var(--np-selected-organization-fallback-background);
}
-.np-selected-organization-avatar.fallback .np-selected-organization-logo {
- display: none;
+.np-selected-organization-logo {
+ object-fit: contain;
}
-.np-selected-organization-avatar:not(.fallback) .np-selected-organization-initials {
+.np-selected-organization-avatar.fallback .np-selected-organization-logo {
display: none;
}
-.np-selected-organization-logo {
- object-fit: contain;
-}
-
.np-selected-organization-initials {
color: var(--color-white);
font-size: 18px;
font-weight: 700;
}
+.np-selected-organization-avatar:not(.fallback) .np-selected-organization-initials {
+ display: none;
+}
+
.np-selected-organization-separator {
height: 1px;
background-color: var(--color-gray-300);
@@ -377,13 +388,17 @@
.np-selected-organization-clear {
position: absolute;
- width: 12px;
- height: 12px;
+ display: flex;
top: 15px;
right: 15px;
cursor: pointer;
}
+.np-selected-organization-clear:hover .np-icon path,
+.np-selected-organization-clear:focus .np-icon path {
+ fill: var(--color-gray-800);
+}
+
.np-personal-data-disclaimer {
font-size: 14px;
line-height: 18px;
@@ -391,24 +406,6 @@
color: var(--color-gray-600);
}
-.np-button {
- background-color: var(--link-color);
- color: var(--color-white);
- font-weight: 700;
- font-size: 16px;
- line-height: 20px;
- border: none;
- border-radius: 20px;
- padding: 10px 20px;
- cursor: pointer;
-}
-
-.np-button:disabled {
- background-color: var(--np-button-disabled-background);
- color: var(--np-button-disabled-color);
- cursor: default;
-}
-
.np-application-review-container {
display: flex;
flex-direction: column;
@@ -447,7 +444,7 @@
}
}
-/* Temporary stuff */
+/* Temporary stuff below (TODO - remove) */
.nonprofit {
display: flex;
flex-direction: column;
@@ -468,16 +465,7 @@
.np-controller-title {
font-weight: 700;
font-size: 16px;
- color: var(--feds-color-adobeBrand);
-}
-
-.np-controller-section {
- display: flex;
- width: 415px;
-
- & .np-controller-button:last-child {
- margin: 0;
- }
+ color: var(--link-color);
}
.np-controller-button {
@@ -505,3 +493,12 @@
color: var(--color-white);
}
}
+
+.np-controller-section {
+ display: flex;
+ width: 415px;
+
+ & .np-controller-button:last-child {
+ margin: 0;
+ }
+}
diff --git a/acrobat/blocks/nonprofit/nonprofit.js b/acrobat/blocks/nonprofit/nonprofit.js
index b844a24f..57a7f24b 100644
--- a/acrobat/blocks/nonprofit/nonprofit.js
+++ b/acrobat/blocks/nonprofit/nonprofit.js
@@ -6,6 +6,7 @@
import ReactiveStore from '../../scripts/reactiveStore.js';
import { setLibs } from '../../scripts/utils.js';
import { countries } from './constants.js';
+import { getNonprofitIconTag, NONPRFIT_ICONS } from './icons.js';
import nonprofitSelect from './nonprofit-select.js';
const miloLibs = setLibs('/libs');
@@ -23,11 +24,11 @@ const removeOptionElements = (element) => {
const PERCENT_API_URL = 'https://sandbox-api.poweredbypercent.com/v1';
const PERCENT_VALIDATION_API_URL = 'https://sandbox-validate.poweredbypercent.com/adobe-acrobat';
const PERCENT_PUBLISHABLE_KEY = 'sandbox_pk_8b320cc4-5950-4263-a3ac-828c64f6e19b';
-const SCENARIOS = Object.freeze({
+export const SCENARIOS = Object.freeze({
FOUND_IN_SEARCH: 'FOUND_IN_SEARCH',
NOT_FOUND_IN_SEARCH: 'NOT_FOUND_IN_SEARCH',
});
-const SEARCH_DEBOUNCE = 500;
+const SEARCH_DEBOUNCE = 500; // ms
const FETCH_ON_SCROLL_OFFSET = 100; // px
// #endregion
@@ -36,11 +37,15 @@ const nonprofitFormData = JSON.parse('{}');
// #region Stores
-const stepperStore = new ReactiveStore({ step: 1, scenario: SCENARIOS.FOUND_IN_SEARCH });
+export const stepperStore = new ReactiveStore({
+ step: 1,
+ scenario: SCENARIOS.FOUND_IN_SEARCH,
+ pending: false,
+});
-const organizationsStore = new ReactiveStore([]);
+export const organizationsStore = new ReactiveStore([]);
-const registriesStore = new ReactiveStore([]);
+export const registriesStore = new ReactiveStore([]);
const selectedOrganizationStore = new ReactiveStore();
@@ -48,58 +53,87 @@ const selectedOrganizationStore = new ReactiveStore();
// #region Percent API integration
+// #region Helpers
+
function getPercentErrorString(result) {
return `${result.error.title}: ${result.error.message}${result.error.reasons ? ` (${result.error.reasons.join(', ')})` : ''}`;
}
-let nextPageUrl;
-
-function fetchOrganizations(search, countryCode, abortController) {
- organizationsStore.startLoading(true);
- fetch(`${PERCENT_API_URL}/organisations?countryCode=${countryCode}&query=${search}`, {
- signal: abortController.signal,
- headers: { Authorization: PERCENT_PUBLISHABLE_KEY },
- })
- .then((response) => response.json())
- .then((result) => {
- nextPageUrl = result._links.next;
- organizationsStore.update(result.data);
- })
- .catch((error) => {
- window.lana?.log(`Could not fetch organizations: ${error}`);
- });
+async function validatePercentResponse(response) {
+ const result = await response.json();
+
+ if (!response.ok) {
+ throw new Error(getPercentErrorString(result));
+ }
+
+ return result;
+}
+
+// #endregion
+
+let nextOrganizationsPageUrl;
+
+async function fetchOrganizations(search, countryCode, abortController) {
+ try {
+ organizationsStore.startLoading(true);
+ const response = await fetch(
+ `${PERCENT_API_URL}/organisations?countryCode=${countryCode}&query=${search}`,
+ {
+ cache: 'force-cache',
+ signal: abortController.signal,
+ headers: { Authorization: PERCENT_PUBLISHABLE_KEY },
+ },
+ );
+
+ const result = await validatePercentResponse(response);
+
+ if (!result._links) {
+ nextOrganizationsPageUrl = null;
+ window.lana?.log('No next organization page link provided.');
+ } else nextOrganizationsPageUrl = result._links.next || null;
+ organizationsStore.update(result.data);
+ } catch (error) {
+ organizationsStore.update((prev) => prev);
+ window.lana?.log(`Could not fetch organizations: ${error}`);
+ }
}
-function fetchNextOrganizations(abortController) {
- if (!nextPageUrl) return;
- organizationsStore.startLoading();
- fetch(nextPageUrl, {
- signal: abortController.signal,
- headers: { Authorization: PERCENT_PUBLISHABLE_KEY },
- })
- .then((response) => response.json())
- .then((result) => {
- nextPageUrl = result._links.next;
- organizationsStore.update((prev) => [...prev, ...result.data]);
- })
- .catch((error) => {
- window.lana?.log(`Could not fetch next organizations: ${error}`);
+async function fetchNextOrganizations(abortController) {
+ if (!nextOrganizationsPageUrl) return;
+ try {
+ organizationsStore.startLoading();
+ const response = await fetch(nextOrganizationsPageUrl, {
+ cache: 'force-cache',
+ signal: abortController.signal,
+ headers: { Authorization: PERCENT_PUBLISHABLE_KEY },
});
+
+ const result = await validatePercentResponse(response);
+
+ nextOrganizationsPageUrl = result._links.next;
+ organizationsStore.update((prev) => [...prev, ...result.data]);
+ } catch (error) {
+ organizationsStore.update((prev) => prev);
+ window.lana?.log(`Could not fetch next organizations: ${error}`);
+ }
}
-function fetchRegistries(countryCode, abortController) {
- registriesStore.startLoading(true);
- fetch(`${PERCENT_API_URL}/registries?countryCode=${countryCode}`, {
- signal: abortController.signal,
- headers: { Authorization: PERCENT_PUBLISHABLE_KEY },
- })
- .then((response) => response.json())
- .then((result) => {
- registriesStore.update(result.data);
- })
- .catch((error) => {
- window.lana?.log(`Could not fetch registries: ${error}`);
+async function fetchRegistries(countryCode, abortController) {
+ try {
+ registriesStore.startLoading(true);
+ const response = await fetch(`${PERCENT_API_URL}/registries?countryCode=${countryCode}`, {
+ cache: 'force-cache',
+ signal: abortController.signal,
+ headers: { Authorization: PERCENT_PUBLISHABLE_KEY },
});
+
+ const result = await validatePercentResponse(response);
+
+ registriesStore.update(result.data);
+ } catch (error) {
+ registriesStore.update((prev) => prev);
+ window.lana?.log(`Could not fetch registries: ${error}`);
+ }
}
async function sendOrganizationData() {
@@ -109,11 +143,7 @@ async function sendOrganizationData() {
headers: { Authorization: `Bearer ${PERCENT_PUBLISHABLE_KEY}` },
});
- const inviteResult = await inviteResponse.json();
-
- if (!inviteResponse.ok) {
- throw new Error(getPercentErrorString(inviteResult));
- }
+ const inviteResult = await validatePercentResponse(inviteResponse);
const { validationInviteId } = inviteResult.data;
@@ -130,11 +160,7 @@ async function sendOrganizationData() {
body: evidenceUploadData,
});
- const uploadResult = await uploadResponse.json();
-
- if (!uploadResponse.ok) {
- throw new Error(getPercentErrorString(uploadResult));
- }
+ await validatePercentResponse(uploadResponse);
}
let body;
@@ -167,7 +193,7 @@ async function sendOrganizationData() {
});
}
- const response = await fetch(`${PERCENT_API_URL}/validation-submissions`, {
+ const submissionResponse = await fetch(`${PERCENT_API_URL}/validation-submissions`, {
method: 'POST',
body,
headers: {
@@ -176,11 +202,7 @@ async function sendOrganizationData() {
},
});
- const result = await response.json();
-
- if (!response.ok) {
- throw new Error(getPercentErrorString(result));
- }
+ await validatePercentResponse(submissionResponse);
return true;
} catch (error) {
@@ -194,35 +216,39 @@ async function sendOrganizationData() {
// UI
function getStepBackTag() {
- const containerTag = createTag('div', { class: 'np-stepper-back', tabindex: 0 });
- const iconTag = createTag('img', { src: '/acrobat/blocks/nonprofit/icons/back.svg' });
- containerTag.append(iconTag);
+ const buttonTag = createTag('div', { class: 'np-stepper-back', tabindex: 0 });
+ const backIconTag = getNonprofitIconTag(NONPRFIT_ICONS.BACK);
+ buttonTag.append(backIconTag);
- stepperStore.subscribe(({ step, scenario }) => {
+ stepperStore.subscribe(({ step, scenario, pending }) => {
+ if (pending) buttonTag.classList.add('disabled');
+ else buttonTag.classList.remove('disabled');
if (step === 1 || (step === 3 && scenario === SCENARIOS.FOUND_IN_SEARCH) || step === 5) {
- containerTag.style.display = 'none';
+ buttonTag.style.display = 'none';
return;
}
- containerTag.style.display = 'flex';
+ buttonTag.style.display = 'flex';
});
- containerTag.addEventListener('click', () => {
+ buttonTag.addEventListener('click', () => {
+ if (stepperStore.data.pending) return;
stepperStore.update((prev) => ({ ...prev, step: prev.step - 1 }));
});
- containerTag.addEventListener('keydown', (ev) => {
+ buttonTag.addEventListener('keydown', (ev) => {
+ if (stepperStore.data.pending) return;
if (ev.code !== 'Enter') return;
ev.preventDefault();
- containerTag.click();
+ buttonTag.click();
});
- return containerTag;
+ return buttonTag;
}
function renderStepper(containerTag) {
const stepperContainerTag = createTag('div', { class: 'np-stepper-container' });
const getStepTag = (number) => {
- const stepContainerTag = createTag('div', { class: 'np-step-container' });
+ const stepContainerTag = createTag('div', { class: 'np-step-container', 'data-step': number });
const stepIconTag = createTag('span', { class: 'np-step-icon' }, number);
const stepNameTag = createTag(
'span',
@@ -271,13 +297,14 @@ function renderStepper(containerTag) {
}
});
- const separatorTag = createTag('div', { class: 'np-step-separator' });
+ const separatorIconTag = getNonprofitIconTag(NONPRFIT_ICONS.CHEVRON_RIGHT);
+ separatorIconTag.classList.add('np-step-separator');
stepperContainerTag.append(
step1,
- separatorTag.cloneNode(),
+ separatorIconTag.cloneNode(true),
step2,
- separatorTag.cloneNode(),
+ separatorIconTag.cloneNode(true),
step3,
);
@@ -327,11 +354,30 @@ function getNonprofitInput(params) {
// File validation
if (type === 'file') {
- inputTag.removeAttribute('placeholder');
- inputTag.setAttribute('data-placeholder', placeholder);
+ // Hide input and render a text one
+ inputTag.style.display = 'none';
+ const textTag = createTag('input', {
+ type: 'text',
+ class: 'np-input',
+ placeholder,
+ readonly: 'readonly',
+ 'data-for': name,
+ });
+
+ textTag.addEventListener('click', () => {
+ inputTag.click();
+ });
+
+ textTag.addEventListener('keypress', (ev) => {
+ if (ev.code !== 'Enter') return;
+ ev.preventDefault();
+ inputTag.click();
+ });
+
+ // Validation
inputTag.addEventListener('change', () => {
if (!inputTag.files || inputTag.files.length === 0) {
- window.lana?.log('Evidence of nonprofit status file missing.');
+ textTag.value = '';
return;
}
@@ -352,8 +398,15 @@ function getNonprofitInput(params) {
inputTag.value = '';
inputTag.dispatchEvent(new Event('input'));
alert(window.mph['nonprofit-file-size-exceeded']);
+ return;
}
+
+ textTag.value = file.name;
});
+
+ const uploadIconTag = getNonprofitIconTag(NONPRFIT_ICONS.UPLOAD);
+
+ controlTag.append(textTag, uploadIconTag);
}
return controlTag;
@@ -396,11 +449,9 @@ function getSelectedOrganizationTag() {
const addressTag = createTag('span', { class: 'np-selected-organization-detail' });
const idTag = createTag('span', { class: 'np-selected-organization-detail' });
- const clearTag = createTag('img', {
- class: 'np-selected-organization-clear',
- src: '/acrobat/blocks/nonprofit/icons/close.svg',
- tabindex: 0,
- });
+ const clearTag = createTag('div', { class: 'np-selected-organization-clear', tabindex: 0 });
+ const clearIconTag = getNonprofitIconTag(NONPRFIT_ICONS.CLOSE);
+ clearTag.append(clearIconTag);
clearTag.addEventListener('keydown', (ev) => {
if (ev.code !== 'Enter') return;
@@ -488,7 +539,6 @@ function renderSelectNonprofit(containerTag) {
});
// #region Organization select
-
const organizationTag = nonprofitSelect({
createTag,
name: 'organizationId',
@@ -517,7 +567,7 @@ function renderSelectNonprofit(containerTag) {
itemTag.append(nameTag, idTag);
},
footerTag: (() => {
- const cannotFindTag = createTag('div', { class: 'np-select-list-tag' });
+ const cannotFindTag = createTag('div', { class: 'np-select-list-tag np-organization-cannot-find' });
const cannotFindLinkTag = createTag(
'a',
{ tabindex: 0 },
@@ -525,7 +575,11 @@ function renderSelectNonprofit(containerTag) {
);
// Cannot find action handler
const switchToNotFound = () => {
- stepperStore.update({ step: 2, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH });
+ stepperStore.update((prev) => ({
+ ...prev,
+ step: 2,
+ scenario: SCENARIOS.NOT_FOUND_IN_SEARCH,
+ }));
};
cannotFindLinkTag.addEventListener('click', switchToNotFound);
cannotFindLinkTag.addEventListener('keydown', (ev) => {
@@ -552,7 +606,7 @@ function renderSelectNonprofit(containerTag) {
if (
(Boolean(selectedOrganizationStore.data) && !hasNewInput)
|| organizationsStore.loading
- || !nextPageUrl
+ || !nextOrganizationsPageUrl
) return;
if (listTag.scrollTop + listTag.clientHeight + FETCH_ON_SCROLL_OFFSET >= listTag.scrollHeight) {
fetchNextOrganizations(abortController);
@@ -589,7 +643,7 @@ function renderSelectNonprofit(containerTag) {
nonprofitFormData.countryCode = formData.get('country');
nonprofitFormData.organizationId = formData.get('organizationId');
- stepperStore.update({ scenario: SCENARIOS.FOUND_IN_SEARCH, step: 2 });
+ stepperStore.update((prev) => ({ ...prev, scenario: SCENARIOS.FOUND_IN_SEARCH, step: 2 }));
});
containerTag.replaceChildren(descriptionTag, formTag);
@@ -698,7 +752,7 @@ function renderOrganizationDetails(containerTag) {
nonprofitFormData.evidenceNonProfitStatus = formData.get('evidenceNonProfitStatus');
nonprofitFormData.website = formData.get('website');
- stepperStore.update({ scenario: SCENARIOS.NOT_FOUND_IN_SEARCH, step: 3 });
+ stepperStore.update((prev) => ({ ...prev, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH, step: 3 }));
});
containerTag.replaceChildren(descriptionTag, formTag);
@@ -766,7 +820,7 @@ function renderOrganizationAddress(containerTag) {
nonprofitFormData.city = formData.get('city');
nonprofitFormData.zipCode = formData.get('zipCode');
- stepperStore.update({ scenario: SCENARIOS.NOT_FOUND_IN_SEARCH, step: 4 });
+ stepperStore.update((prev) => ({ ...prev, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH, step: 4 }));
});
containerTag.replaceChildren(descriptionTag, formTag);
@@ -832,12 +886,16 @@ function renderPersonalData(containerTag) {
input.setAttribute('disabled', 'disabled');
});
+ stepperStore.update((prev) => ({ ...prev, pending: true }));
+
const ok = await sendOrganizationData();
if (!ok) {
inputs.forEach((input) => {
input.removeAttribute('disabled');
});
+
+ stepperStore.update((prev) => ({ ...prev, pending: false }));
} else {
stepperStore.update((prev) => ({ ...prev, step: prev.step + 1 }));
}
@@ -862,7 +920,10 @@ function renderApplicationReview(containerTag) {
const detail2Tag = createTag(
'span',
{ class: 'np-application-review-detail' },
- window.mph['nonprofit-detail-2-application-review'],
+ window.mph['nonprofit-detail-2-application-review']?.replace(
+ '__EMAIL__',
+ nonprofitFormData.email,
+ ),
);
applicationReviewTag.append(titleTag, detail1Tag, detail2Tag);
@@ -879,7 +940,13 @@ function renderApplicationReview(containerTag) {
function renderStepContent(containerTag) {
const contentContainerTag = createTag('div', { class: 'np-content-container' });
+ let currentStep;
+ let currentScenario;
stepperStore.subscribe(({ step, scenario }) => {
+ if (step === currentStep && scenario === currentScenario) return;
+ currentStep = step;
+ currentScenario = scenario;
+
if (step === 1) renderSelectNonprofit(contentContainerTag);
if (step === 2 && scenario === SCENARIOS.FOUND_IN_SEARCH) renderPersonalData(contentContainerTag);
if (step === 2 && scenario === SCENARIOS.NOT_FOUND_IN_SEARCH) renderOrganizationDetails(contentContainerTag);
@@ -894,64 +961,68 @@ function renderStepContent(containerTag) {
// #endregion
-// function initStepperController(tag) {
-// const containerTag = createTag('div', { class: 'np-controller-container' });
-
-// const titleTag = createTag(
-// 'span',
-// { class: 'np-controller-title' },
-// 'Stepper controller (for testing)',
-// );
-
-// const scenariosTag = createTag('div', { class: 'np-controller-section' });
-// const stepsTag = createTag('div', { class: 'np-controller-section' });
-
-// stepperStore.subscribe(() => {
-// const { step, scenario } = stepperStore.data;
-
-// const foundInSearchTag = createTag(
-// 'button',
-// { class: `np-controller-button${scenario === SCENARIOS.FOUND_IN_SEARCH ? ' selected' : ''}` },
-// 'Found in search',
-// );
-// foundInSearchTag.addEventListener('click', () => {
-// const newStep = step > 3 ? 1 : step;
-// stepperStore.update((prev) => ({
-// ...prev,
-// step: newStep,
-// scenario: SCENARIOS.FOUND_IN_SEARCH,
-// }));
-// });
-// const notFoundInSearchTag = createTag(
-// 'button',
-// { class: `np-controller-button${scenario === SCENARIOS.NOT_FOUND_IN_SEARCH ? ' selected' : ''}` },
-// 'Not found in search',
-// );
-// notFoundInSearchTag.addEventListener('click', () => stepperStore.update((prev) => ({ ...prev, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH })));
-
-// const maxSteps = scenario === SCENARIOS.FOUND_IN_SEARCH ? 3 : 5;
-
-// stepsTag.replaceChildren();
-// Array.from({ length: maxSteps })
-// .map((_, index) => index + 1)
-// .forEach((value) => {
-// const stepTag = createTag(
-// 'button',
-// { class: `np-controller-button is-step ${step === value ? ' selected' : ''}` },
-// value,
-// );
-// stepTag.addEventListener('click', () => stepperStore.update((prev) => ({ ...prev, step: value })));
-// stepsTag.append(stepTag);
-// });
-
-// scenariosTag.replaceChildren(foundInSearchTag, notFoundInSearchTag);
-// });
-
-// containerTag.append(titleTag, scenariosTag, stepsTag);
-
-// const bufferTag = createTag('div', { class: 'np-controller-buffer ' });
-// tag.append(bufferTag, containerTag);
-// }
+// #region Stepper Controller (TODO - remove)
+
+function initStepperController(tag) {
+ const containerTag = createTag('div', { class: 'np-controller-container' });
+
+ const titleTag = createTag(
+ 'span',
+ { class: 'np-controller-title' },
+ 'Stepper controller (for testing)',
+ );
+
+ const scenariosTag = createTag('div', { class: 'np-controller-section' });
+ const stepsTag = createTag('div', { class: 'np-controller-section' });
+
+ stepperStore.subscribe(() => {
+ const { step, scenario } = stepperStore.data;
+
+ const foundInSearchTag = createTag(
+ 'button',
+ { class: `np-controller-button${scenario === SCENARIOS.FOUND_IN_SEARCH ? ' selected' : ''}` },
+ 'Found in search',
+ );
+ foundInSearchTag.addEventListener('click', () => {
+ const newStep = step > 3 ? 1 : step;
+ stepperStore.update((prev) => ({
+ ...prev,
+ step: newStep,
+ scenario: SCENARIOS.FOUND_IN_SEARCH,
+ }));
+ });
+ const notFoundInSearchTag = createTag(
+ 'button',
+ { class: `np-controller-button${scenario === SCENARIOS.NOT_FOUND_IN_SEARCH ? ' selected' : ''}` },
+ 'Not found in search',
+ );
+ notFoundInSearchTag.addEventListener('click', () => stepperStore.update((prev) => ({ ...prev, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH })));
+
+ const maxSteps = scenario === SCENARIOS.FOUND_IN_SEARCH ? 3 : 5;
+
+ stepsTag.replaceChildren();
+ Array.from({ length: maxSteps })
+ .map((_, index) => index + 1)
+ .forEach((value) => {
+ const stepTag = createTag(
+ 'button',
+ { class: `np-controller-button is-step ${step === value ? ' selected' : ''}` },
+ value,
+ );
+ stepTag.addEventListener('click', () => stepperStore.update((prev) => ({ ...prev, step: value })));
+ stepsTag.append(stepTag);
+ });
+
+ scenariosTag.replaceChildren(foundInSearchTag, notFoundInSearchTag);
+ });
+
+ containerTag.append(titleTag, scenariosTag, stepsTag);
+
+ const bufferTag = createTag('div', { class: 'np-controller-buffer ' });
+ tag.append(bufferTag, containerTag);
+}
+
+// #endregion
function initNonprofit(element) {
const containerTag = createTag('div', { class: 'np-container' });
@@ -961,7 +1032,8 @@ function initNonprofit(element) {
element.append(containerTag);
- // initStepperController(element);
+ // TODO - remove
+ initStepperController(element);
}
export default function init(element) {
diff --git a/acrobat/img/icons/ui/chevron-down.svg b/acrobat/img/icons/ui/chevron-down.svg
deleted file mode 100644
index 6485a4ba..00000000
--- a/acrobat/img/icons/ui/chevron-down.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/acrobat/img/icons/ui/close.svg b/acrobat/img/icons/ui/close.svg
deleted file mode 100644
index ea42f47d..00000000
--- a/acrobat/img/icons/ui/close.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-
diff --git a/test/blocks/nonprofit/mocks/body.html b/test/blocks/nonprofit/mocks/body.html
new file mode 100644
index 00000000..1857cd32
--- /dev/null
+++ b/test/blocks/nonprofit/mocks/body.html
@@ -0,0 +1,5 @@
+
+
+
diff --git a/test/blocks/nonprofit/mocks/data.js b/test/blocks/nonprofit/mocks/data.js
new file mode 100644
index 00000000..3abfeae1
--- /dev/null
+++ b/test/blocks/nonprofit/mocks/data.js
@@ -0,0 +1,13 @@
+export const mockedOrganizations = [
+ { id: 'atestid_1', name: 'A Test Organization 1' },
+ { id: 'atestid_2', name: 'A Test Organization 2' },
+ { id: 'atestid_3', name: 'A Test Organization 3' },
+ { id: 'atestid_4', name: 'A Test Organization 4' },
+];
+
+export const mockedRegistries = [
+ { name: 'A Test Registry 1' },
+ { name: 'A Test Registry 2' },
+ { name: 'A Test Registry 3' },
+ { name: 'A Test Registry 4' },
+];
diff --git a/test/blocks/nonprofit/nonprofit.test.js b/test/blocks/nonprofit/nonprofit.test.js
new file mode 100644
index 00000000..5aaabc11
--- /dev/null
+++ b/test/blocks/nonprofit/nonprofit.test.js
@@ -0,0 +1,583 @@
+/* eslint-disable max-len */
+/* eslint-disable func-names */
+/* eslint-disable compat/compat */
+import sinon from 'sinon';
+import { readFile, sendKeys } from '@web/test-runner-commands';
+import { expect } from '@esm-bundle/chai';
+// import userEvent from '@testing-library/user-event';
+import { delay, waitForElement } from '../../helpers/waitfor.js';
+import {
+ organizationsStore,
+ registriesStore,
+ SCENARIOS,
+ stepperStore,
+} from '../../../acrobat/blocks/nonprofit/nonprofit.js';
+import { mockedOrganizations, mockedRegistries } from './mocks/data.js';
+
+const { default: init } = await import('../../../acrobat/blocks/nonprofit/nonprofit.js');
+
+const body = await readFile({ path: './mocks/body.html' });
+
+describe('nonprofit - General', () => {
+ before(() => {
+ window.mph = {
+ 'nonprofit-title-select-non-profit': "What's your nonprofit organization?",
+ 'nonprofit-title-organization-details': 'Verify your organization details',
+ 'nonprofit-title-organization-address': "What's the physical address of your organization?",
+ 'nonprofit-title-personal-details': 'Confirm your details?',
+ 'nonprofit-subtitle-personal-details':
+ 'We need to confirm your name and email in order to finish checking if your organisation is eligible.',
+ 'nonprofit-title-application-review': 'Your application is being reviewed',
+ };
+ });
+
+ beforeEach(() => {
+ document.body.innerHTML = body;
+ const np = document.querySelector('.nonprofit');
+ init(np);
+ });
+
+ afterEach(() => {
+ stepperStore.update((prev) => ({ ...prev, step: 1, scenario: SCENARIOS.FOUND_IN_SEARCH }));
+ });
+
+ function validateStepTitle(value) {
+ const title = document.querySelector('.np-title');
+ expect(title.textContent).to.equal(value);
+ }
+
+ it('should display nonprofit', async () => {
+ const container = await waitForElement('.np-container');
+ const form = container.querySelector('.np-form');
+ const countryInput = form.querySelector('input[name="country"]');
+ const organizationInput = form.querySelector('input[name="organizationId"]');
+ expect(container).to.exist;
+ expect(countryInput).to.exist;
+ expect(organizationInput).to.exist;
+ });
+
+ it('should enable organizations select on country selection', async () => {
+ const countrySearch = document.querySelector('input[data-for="country"]');
+ const organizationSearch = document.querySelector('input[data-for="organizationId"]');
+
+ expect(organizationSearch.getAttribute('disabled')).to.equal('disabled');
+
+ countrySearch.focus();
+ await sendKeys({ press: 'ArrowDown' });
+ await sendKeys({ press: 'Enter' });
+
+ expect(organizationSearch.hasAttribute('disabled')).to.be.false;
+ });
+
+ it('should change step on stepperStore update', async () => {
+ validateStepTitle("What's your nonprofit organization?");
+
+ stepperStore.update({ step: 2, scenario: SCENARIOS.FOUND_IN_SEARCH });
+ validateStepTitle('Confirm your details?');
+
+ stepperStore.update({ step: 3, scenario: SCENARIOS.FOUND_IN_SEARCH });
+ validateStepTitle('Your application is being reviewed');
+
+ stepperStore.update({ step: 1, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH });
+ validateStepTitle("What's your nonprofit organization?");
+
+ stepperStore.update({ step: 2, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH });
+ validateStepTitle('Verify your organization details');
+
+ stepperStore.update({ step: 3, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH });
+ validateStepTitle("What's the physical address of your organization?");
+
+ stepperStore.update({ step: 4, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH });
+ validateStepTitle('Confirm your details?');
+
+ stepperStore.update({ step: 5, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH });
+ validateStepTitle('Your application is being reviewed');
+ });
+
+ it('should go to previous step on back button click', async () => {
+ stepperStore.update({ step: 2, scenario: SCENARIOS.FOUND_IN_SEARCH });
+ validateStepTitle('Confirm your details?');
+
+ const backButton = document.querySelector('.np-stepper-back');
+
+ expect(backButton.style.display).to.equal('flex');
+
+ backButton.click();
+
+ expect(stepperStore.data.step).to.equal(1);
+ });
+
+ it('should go to previous step on back button Enter keypress', async () => {
+ stepperStore.update({ step: 2, scenario: SCENARIOS.FOUND_IN_SEARCH });
+ validateStepTitle('Confirm your details?');
+
+ const backButton = document.querySelector('.np-stepper-back');
+
+ expect(backButton.style.display).to.equal('flex');
+
+ backButton.focus();
+ await sendKeys({ press: 'Enter' });
+
+ expect(stepperStore.data.step).to.equal(1);
+ });
+});
+
+describe('nonprofit - Organization search', () => {
+ const mockedFetchReturnData = {
+ default: { data: mockedOrganizations, _links: { next: '' } },
+ 'should search next organizations': {
+ data: mockedOrganizations,
+ _links: { next: 'a.test?link=' },
+ },
+ 'should navigate to alternate flow': {
+ data: [],
+ _links: { next: '' },
+ },
+ };
+
+ before(() => {
+ window.mph = {};
+ window.lana = { log: () => {} };
+ });
+
+ beforeEach(async function () {
+ document.body.innerHTML = body;
+ const np = document.querySelector('.nonprofit');
+ init(np);
+
+ const data = mockedFetchReturnData[this.currentTest.title] || mockedFetchReturnData.default;
+
+ sinon.stub(window, 'fetch');
+ window.fetch.returns(
+ Promise.resolve({
+ json: () => Promise.resolve(data),
+ ok: !data.error,
+ }),
+ );
+
+ const countrySearch = document.querySelector('input[data-for="country"]');
+ const organizationSearch = document.querySelector('input[data-for="organizationId"]');
+
+ countrySearch.focus();
+ await sendKeys({ press: 'ArrowDown' });
+ await sendKeys({ press: 'Enter' });
+
+ expect(document.activeElement).to.equal(organizationSearch);
+ });
+
+ afterEach(() => {
+ if (window.fetch.restore) window.fetch.restore();
+ if (window.lana.log.restore) window.lana.log.restore();
+ organizationsStore.update([]);
+ stepperStore.update((prev) => ({ ...prev, step: 1, scenario: SCENARIOS.FOUND_IN_SEARCH }));
+ });
+
+ it('should search organizations', async () => {
+ const selectedOrganizationContainer = document.querySelector(
+ '.np-selected-organization-container',
+ );
+
+ expect(selectedOrganizationContainer.style.display).to.equal('');
+
+ await sendKeys({ press: 'a' });
+
+ await waitForElement('.np-select-list[data-for="organizationId"] .np-select-item');
+
+ await sendKeys({ press: 'ArrowDown' });
+ await sendKeys({ press: 'Enter' });
+
+ expect(selectedOrganizationContainer.style.display).to.equal('flex');
+ });
+
+ it('should search next organizations', async () => {
+ const organizationSearch = document.querySelector('input[data-for="organizationId"]');
+ expect(document.activeElement).to.equal(organizationSearch);
+
+ await sendKeys({ press: 'a' });
+
+ await waitForElement('.np-select-list[data-for="organizationId"] .np-select-item');
+ const organizationsList = document.querySelector('.np-select-list[data-for="organizationId"]');
+ organizationsList.dispatchEvent(new Event('scroll'));
+
+ expect(window.fetch.getCall(1).args[0]).to.equal('a.test?link=');
+ });
+
+ it('should navigate options with arrows', async () => {
+ await sendKeys({ press: 'a' });
+
+ const firstOption = await waitForElement(
+ '.np-select-list[data-for="organizationId"] .np-select-item',
+ );
+ const lastOption = document.querySelector(
+ '.np-select-list[data-for="organizationId"] .np-select-item:last-child',
+ );
+
+ await sendKeys({ press: 'ArrowDown' });
+ await sendKeys({ press: 'ArrowDown' });
+ await sendKeys({ press: 'ArrowDown' });
+ await sendKeys({ press: 'ArrowDown' });
+
+ expect(document.activeElement).to.equal(lastOption);
+
+ await sendKeys({ press: 'ArrowUp' });
+ await sendKeys({ press: 'ArrowUp' });
+ await sendKeys({ press: 'ArrowUp' });
+
+ expect(document.activeElement).to.equal(firstOption);
+ });
+
+ it('should focus the organization search from new keypresses (except arrows and Enter) on select options', async () => {
+ await sendKeys({ press: 'a' });
+
+ const firstOption = await waitForElement(
+ '.np-select-list[data-for="organizationId"] .np-select-item',
+ );
+
+ await sendKeys({ press: 'ArrowDown' });
+ await sendKeys({ press: 'ArrowDown' });
+ await sendKeys({ press: 'ArrowUp' });
+
+ expect(document.activeElement).to.equal(firstOption);
+
+ await sendKeys({ press: 'a' });
+
+ const organizationSearch = document.querySelector('input[data-for="organizationId"]');
+
+ expect(document.activeElement).to.equal(organizationSearch);
+ expect(organizationSearch.value).to.equal('aa');
+ });
+
+ it('should reset select search value on blur without selection', async () => {
+ await sendKeys({ press: 'a' });
+
+ const organizationSearch = document.querySelector(
+ '.np-select-search[data-for="organizationId"]',
+ );
+
+ expect(organizationSearch.value).to.equal('a');
+
+ await waitForElement('.np-select-list[data-for="organizationId"] .np-select-no-options');
+ organizationSearch.blur();
+
+ expect(organizationSearch.value).to.equal('');
+ });
+
+ it('should navigate to personal data', async () => {
+ const selectedOrganizationContainer = document.querySelector(
+ '.np-selected-organization-container',
+ );
+
+ expect(selectedOrganizationContainer.style.display).to.equal('');
+
+ await sendKeys({ press: 'a' });
+
+ await waitForElement('.np-select-list[data-for="organizationId"] .np-select-item');
+
+ await sendKeys({ press: 'ArrowDown' });
+ await sendKeys({ press: 'Enter' });
+
+ expect(selectedOrganizationContainer.style.display).to.equal('flex');
+
+ const form = document.querySelector('.np-form');
+ form.dispatchEvent(new Event('submit'));
+
+ expect(stepperStore.data.step).to.equal(2);
+ expect(stepperStore.data.scenario).to.equal(SCENARIOS.FOUND_IN_SEARCH);
+ });
+
+ it('should navigate to alternate flow', async () => {
+ await sendKeys({ press: 'a' });
+
+ await waitForElement('.np-select-list[data-for="organizationId"] .np-select-no-options');
+
+ await sendKeys({ press: 'Tab' });
+
+ const cannotFind = document.querySelector('.np-organization-cannot-find a');
+ expect(document.activeElement).to.equal(cannotFind);
+
+ await sendKeys({ press: 'Enter' });
+
+ expect(stepperStore.data.step).to.equal(2);
+ expect(stepperStore.data.scenario).to.equal(SCENARIOS.NOT_FOUND_IN_SEARCH);
+ });
+});
+
+describe('nonprofit - Organization details', () => {
+ const mockedFetchReturnData = {
+ default: { data: mockedRegistries },
+ 'should search organizations': { data: mockedRegistries },
+ };
+
+ before(() => {
+ window.mph = {};
+ });
+
+ beforeEach(async function () {
+ document.body.innerHTML = body;
+ const np = document.querySelector('.nonprofit');
+ init(np);
+
+ const data = mockedFetchReturnData[this.currentTest.title] || mockedFetchReturnData.default;
+
+ sinon.stub(window, 'fetch');
+ window.fetch.returns(
+ Promise.resolve({
+ json: () => Promise.resolve(data),
+ ok: true,
+ }),
+ );
+
+ stepperStore.update({ step: 2, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH });
+ });
+
+ afterEach(() => {
+ if (window.fetch.restore) window.fetch.restore();
+ registriesStore.update([]);
+ });
+
+ it('should fetch registries on country select', async () => {
+ const countrySearch = document.querySelector('.np-select-search[data-for="country"]');
+
+ countrySearch.focus();
+ await sendKeys({ press: 'ArrowDown' });
+ await sendKeys({ press: 'Enter' });
+
+ await waitForElement('.np-select-list[data-for="registry"] .np-select-item');
+
+ expect(window.fetch.getCall(0)).to.not.be.null;
+ });
+
+ it('should not navigate without completed fields', async () => {
+ const submit = document.querySelector('input[type="submit"]');
+ expect(submit).to.exist;
+ submit.click();
+
+ expect(stepperStore.data.step).to.equal(2);
+ });
+
+ // userEvent crashes on import - this test is skipped
+ it.skip('should navigate to address details on submit', async () => {
+ const countrySearch = document.querySelector('.np-select-search[data-for="country"]');
+
+ countrySearch.focus();
+ await sendKeys({ press: 'ArrowDown' });
+ await sendKeys({ press: 'Enter' });
+
+ await waitForElement('.np-select-list[data-for="registry"] .np-select-item');
+
+ const registrySearch = document.querySelector('.np-select-search[data-for="registry"]');
+
+ registrySearch.focus();
+ await sendKeys({ press: 'ArrowDown' });
+ await sendKeys({ press: 'Enter' });
+
+ const organizationName = document.querySelector('.np-input[name="organizationName"]');
+ organizationName.value = 'A Test Name';
+ organizationName.dispatchEvent(new Event('input'));
+
+ const organizationRegistrationId = document.querySelector(
+ '.np-input[name="organizationRegistrationId"]',
+ );
+ organizationRegistrationId.value = 'atestid';
+ organizationRegistrationId.dispatchEvent(new Event('input'));
+
+ // const evidenceNonProfitStatus = document.querySelector(
+ // '.np-input[name="evidenceNonProfitStatus"]',
+ // );
+ // const file = new File(['evidenceofnonprofit'], 'evidenceofnonprofit.png', { type: 'image/png' });
+ // await userEvent.upload(evidenceNonProfitStatus, file);
+
+ const website = document.querySelector('.np-input[name="website"]');
+ website.value = 'www.atestwebsite.com';
+ website.dispatchEvent(new Event('input'));
+
+ const submit = document.querySelector('input[type="submit"]');
+ expect(submit).to.exist;
+ submit.click();
+
+ expect(stepperStore.data.step).to.equal(3);
+ });
+});
+
+describe('nonprofit - Address details', () => {
+ before(() => {
+ window.mph = {};
+ });
+
+ beforeEach(async () => {
+ document.body.innerHTML = body;
+ const np = document.querySelector('.nonprofit');
+ init(np);
+
+ stepperStore.update({ step: 3, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH });
+ });
+
+ it('should not navigate without completed fields', async () => {
+ const submit = document.querySelector('input[type="submit"]');
+ expect(submit).to.exist;
+ submit.click();
+
+ expect(stepperStore.data.step).to.equal(3);
+ });
+
+ it('should navigate to personal details on submit with all fields completed', async () => {
+ const streetAddress = document.querySelector('.np-input[name="streetAddress"]');
+ streetAddress.value = 'A Test Street, 40';
+ streetAddress.dispatchEvent(new Event('input'));
+
+ const addressDetails = document.querySelector('.np-input[name="addressDetails"]');
+ addressDetails.value = 'Block C, Floor 2';
+ addressDetails.dispatchEvent(new Event('input'));
+
+ const state = document.querySelector('.np-input[name="state"]');
+ state.value = 'A Test State';
+ state.dispatchEvent(new Event('input'));
+
+ const city = document.querySelector('.np-input[name="city"]');
+ city.value = 'A Test City';
+ city.dispatchEvent(new Event('input'));
+
+ const zipCode = document.querySelector('.np-input[name="zipCode"]');
+ zipCode.value = 'TEST-0123';
+ zipCode.dispatchEvent(new Event('input'));
+
+ const submit = document.querySelector('input[type="submit"]');
+ expect(submit).to.exist;
+ submit.click();
+
+ expect(stepperStore.data.step).to.equal(4);
+ });
+
+ it('should navigate to personal details on submit with just the required fields completed', async () => {
+ const streetAddress = document.querySelector('.np-input[name="streetAddress"]');
+ streetAddress.value = 'A Test Street, 40';
+ streetAddress.dispatchEvent(new Event('input'));
+
+ const city = document.querySelector('.np-input[name="city"]');
+ city.value = 'A Test City';
+ city.dispatchEvent(new Event('input'));
+
+ const zipCode = document.querySelector('.np-input[name="zipCode"]');
+ zipCode.value = 'TEST-0123';
+ zipCode.dispatchEvent(new Event('input'));
+
+ const submit = document.querySelector('input[type="submit"]');
+ expect(submit).to.exist;
+ submit.click();
+
+ expect(stepperStore.data.step).to.equal(4);
+ });
+});
+
+describe('nonprofit - Personal details', () => {
+ const fillFields = () => {
+ const firstName = document.querySelector('.np-input[name="firstName"]');
+ firstName.value = 'Atest';
+ firstName.dispatchEvent(new Event('input'));
+
+ const lastName = document.querySelector('.np-input[name="lastName"]');
+ lastName.value = 'Name';
+ lastName.dispatchEvent(new Event('input'));
+
+ const email = document.querySelector('.np-input[name="email"]');
+ email.value = 'atest@email.com';
+ email.dispatchEvent(new Event('input'));
+ };
+
+ before(() => {
+ window.mph = {};
+ window.lana = { log: () => {} };
+ });
+
+ beforeEach(async () => {
+ document.body.innerHTML = body;
+ const np = document.querySelector('.nonprofit');
+ init(np);
+ });
+
+ afterEach(() => {
+ if (window.fetch.restore) window.fetch.restore();
+ if (window.lana.log.restore) window.lana.log.restore();
+ });
+
+ it('should not submit without completed fields', async () => {
+ stepperStore.update({ step: 2, scenario: SCENARIOS.FOUND_IN_SEARCH });
+
+ const submit = document.querySelector('input[type="submit"]');
+ expect(submit).to.exist;
+ submit.click();
+
+ expect(stepperStore.data.step).to.equal(2);
+ });
+
+ it('should submit completed form in found in search scenario', async () => {
+ sinon.stub(window, 'fetch');
+ window.fetch.returns(
+ Promise.resolve({
+ json: () => Promise.resolve({ data: { validationInviteId: 'avalidationinviteid_0123' } }),
+ ok: true,
+ }),
+ );
+
+ stepperStore.update({ step: 2, scenario: SCENARIOS.FOUND_IN_SEARCH });
+
+ fillFields();
+
+ const submit = document.querySelector('input[type="submit"]');
+ expect(submit).to.exist;
+ submit.click();
+
+ await waitForElement('.np-application-review-container');
+
+ expect(stepperStore.data.step).to.equal(3);
+ });
+
+ it('should submit completed form in not found in search scenario', async () => {
+ sinon.stub(window, 'fetch');
+ window.fetch.returns(
+ Promise.resolve({
+ json: () => Promise.resolve({ data: { validationInviteId: 'avalidationinviteid_0123' } }),
+ ok: true,
+ }),
+ );
+
+ stepperStore.update({ step: 4, scenario: SCENARIOS.NOT_FOUND_IN_SEARCH });
+
+ fillFields();
+
+ const submit = document.querySelector('input[type="submit"]');
+ expect(submit).to.exist;
+ submit.click();
+
+ await waitForElement('.np-application-review-container');
+
+ expect(stepperStore.data.step).to.equal(5);
+ });
+
+ it('should not navigate on submission failure', async () => {
+ sinon.stub(window, 'fetch');
+ sinon.stub(window.lana, 'log');
+
+ window.fetch.returns(
+ Promise.resolve({
+ json: () => Promise.resolve({ error: { title: 'An error title', message: 'A submission failure' } }),
+ ok: false,
+ }),
+ );
+
+ stepperStore.update({ step: 2, scenario: SCENARIOS.FOUND_IN_SEARCH });
+
+ fillFields();
+
+ const submit = document.querySelector('input[type="submit"]');
+ expect(submit).to.exist;
+ submit.click();
+
+ await delay(500);
+
+ expect(stepperStore.data.step).to.equal(2);
+ expect(window.lana.log.getCall(0).args[0]).to.equal(
+ 'Could not send organization data: Error: An error title: A submission failure',
+ );
+ });
+});