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', + ); + }); +});