diff --git a/README.md b/README.md index 59d0e17..9432fcd 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ Your project's description... ## Environments -- Preview: https://main--forms-ue--pranay-tippa.hlx.page/ -- Live: https://main--forms-ue--pranay-tippa.hlx.live/ +- Preview: https://main--venkyuenew--Venky2423.hlx.page/ +- Live: https://main--venkyuenew--Venky2423.hlx.live/ ## Installation @@ -23,4 +23,4 @@ npm run lint 1. Add the [AEM Code Sync GitHub App](https://github.com/apps/aem-code-sync) to the repository 1. Install the [AEM CLI](https://github.com/adobe/helix-cli): `npm install -g @adobe/aem-cli` 1. Start AEM Proxy: `aem up` (opens your browser at `http://localhost:3000`) -1. Open the `forms-ue` directory in your favorite IDE and start coding :) +1. Open the `venkyuenew` directory in your favorite IDE and start coding :) diff --git a/blocks/form/components/file/file.js b/blocks/form/components/file/file.js new file mode 100644 index 0000000..bfaea23 --- /dev/null +++ b/blocks/form/components/file/file.js @@ -0,0 +1,271 @@ +import { updateOrCreateInvalidMsg, stripTags } from '../../util.js'; +import { fileAttachmentText, dragDropText, defaultErrorMessages } from '../../constant.js'; + +const fileSizeRegex = /^(\d*\.?\d+)(\\?(?=[KMGT])([KMGT])(?:i?B)?|B?)$/i; + +/** + * converts a string of the form "10MB" to bytes. If the string is malformed 0 is returned + * @param {*} str + * @returns + */ +function getSizeInBytes(str) { + const sizes = { + KB: 1, MB: 2, GB: 3, TB: 4, + }; + let sizeLimit = 0; + const matches = fileSizeRegex.exec(str.trim()); + if (matches != null) { + const symbol = matches[2] || 'kb'; + const size = parseFloat(matches[1]); + const i = 1024 ** sizes[symbol.toUpperCase()]; + sizeLimit = Math.round(size * i); + } + return sizeLimit; +} + +/** + * matches the given mediaType with the accepted mediaTypes + * @param {*} mediaType mediaType of the file to match + * @param {[]} accepts accepted mediaTypes + * @returns false if the mediaType is not accepted + */ +function matchMediaType(mediaType, accepts) { + return !mediaType || accepts.some((accept) => { + const trimmedAccept = accept.trim(); + const prefixAccept = trimmedAccept.split('/')[0]; + const suffixAccept = trimmedAccept.split('.')[1]; + return ((trimmedAccept.includes('*') && mediaType.startsWith(prefixAccept)) + || (trimmedAccept.includes('.') && mediaType.endsWith(suffixAccept)) + || (trimmedAccept === mediaType)); + }); +} + +/** + * checks whether the size of the files in the array is withing the maxFileSize or not + * @param {string|number} maxFileSize maxFileSize in bytes or string with the unit + * @param {File[]} files array of File objects + * @returns false if any file is larger than the maxFileSize + */ +function checkMaxFileSize(maxFileSize, files) { + const sizeLimit = typeof maxFileSize === 'string' ? getSizeInBytes(maxFileSize) : maxFileSize; + return Array.from(files).find((file) => file.size > sizeLimit) === undefined; +} + +/** + * checks whether the mediaType of the files in the array are accepted or not + * @param {[]} acceptedMediaTypes + * @param {File[]} files + * @returns false if the mediaType of any file is not accepted + */ +function checkAccept(acceptedMediaTypes, files) { + if (!acceptedMediaTypes || acceptedMediaTypes.length === 0 || !files.length) { + return true; + } + const invalidFile = Array.from(files) + .some((file) => !matchMediaType(file.type, acceptedMediaTypes)); + return !invalidFile; +} + +/** + * triggers file Validation for the given input element and updates the error message + * @param {HTMLInputElement} input + * @param {FileList} files + */ +function fileValidation(input, files) { + const multiple = input.hasAttribute('multiple'); + const acceptedFile = (input.getAttribute('accept') || '').split(','); + const minItems = (parseInt(input.dataset.minItems, 10) || 1); + const maxItems = (parseInt(input.dataset.maxItems, 10) || -1); + const fileSize = `${input.dataset.maxFileSize || '2MB'}`; + let constraint = ''; + let errorMessage = ''; + const wrapper = input.closest('.field-wrapper'); + if (!checkAccept(acceptedFile, files)) { + constraint = 'accept'; + } else if (!checkMaxFileSize(fileSize, files)) { + constraint = 'maxFileSize'; + } else if (multiple && maxItems !== -1 && files.length > maxItems) { + constraint = 'maxItems'; + errorMessage = defaultErrorMessages.maxItems.replace(/\$0/, maxItems); + } else if (multiple && minItems !== 1 && files.length < minItems) { + constraint = 'minItems'; + errorMessage = defaultErrorMessages.minItems.replace(/\$0/, minItems); + } + if (constraint.length) { + const finalMessage = wrapper.dataset[constraint] + || errorMessage + || defaultErrorMessages[constraint]; + input.setCustomValidity(finalMessage); + updateOrCreateInvalidMsg( + input, + finalMessage, + ); + } else { + input.setCustomValidity(''); + updateOrCreateInvalidMsg(input, ''); + } +} + +function formatBytes(bytes) { + if (!+bytes) return '0 Bytes'; + const k = 1024; + const sizes = ['bytes', 'kb', 'mb', 'gb', 'tb', 'pb']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${parseFloat((bytes / k ** i).toFixed(0))} ${sizes[i]}`; +} + +function updateButtonIndex(elements = []) { + elements.forEach((element, index) => { + element.dataset.index = index; + }); +} + +function dispatchChangeEvent(input, files) { + if (!files.length) { + input.value = null; + } + const options = { bubbles: true, detail: { files, deletion: true } }; + const changeEvent = new CustomEvent('change', options); + input.dispatchEvent(changeEvent); +} + +/** + * creates an HTML element for the attached file + * @param {File} file + * @param {number} index + */ +function fileElement(file, index) { + const el = document.createElement('div'); + el.dataset.index = index; + el.classList.add('file-description'); + el.innerHTML = `${file.name} + ${formatBytes(file.size)} + `; + return el; +} + +/** + * creates an HTML elements for drag & drop + * @param {HTMLElement} wrapper + */ +function createDragAndDropArea(wrapper, field) { + const input = wrapper.querySelector('input'); + const dragArea = ` +
+
${field?.properties?.dragDropText ?? dragDropText}
+ + `; + const dragContainer = document.createElement('div'); + if (input.title) { + dragContainer.title = stripTags(input.title, ''); + } + dragContainer.className = 'file-drag-area'; + dragContainer.innerHTML = dragArea; + dragContainer.appendChild(input.cloneNode(true)); + input.parentNode.replaceChild(dragContainer, input); + return dragContainer; +} + +function createFileHandler(allFiles, input) { + return { + removeFile: (index) => { + allFiles.splice(index, 1); + const fileListElement = input.closest('.field-wrapper').querySelector('.files-list'); + fileListElement.querySelector(`[data-index="${index}"]`).remove(); + fileValidation(input, allFiles); + updateButtonIndex(Array.from(fileListElement.children)); + dispatchChangeEvent(input, allFiles); + }, + + attachFiles: (inputEl, files) => { + const multiple = inputEl.hasAttribute('multiple'); + let newFiles = Array.from(files); + if (!multiple) { + allFiles.splice(0, allFiles.length); + newFiles = [newFiles[0]]; + } + const currentLength = allFiles.length; + allFiles.push(...newFiles); + const newFileElements = newFiles + .map((file, index) => fileElement(file, index + currentLength)); + const fileListElement = inputEl.closest('.field-wrapper').querySelector('.files-list'); + if (multiple) { + fileListElement.append(...newFileElements); + } else { + fileListElement.replaceChildren(...newFileElements); + } + fileValidation(inputEl, allFiles); + dispatchChangeEvent(input, allFiles); + }, + + previewFile: (index) => { + const file = allFiles[index]; + let url = file.data || window.URL.createObjectURL(file); + if (file.data) { + const lastIndex = url.lastIndexOf('/'); + /* added check for query param since sas url contains query params & + does not have file name, encoding is not required in this case + */ + if (lastIndex >= 0 && url.indexOf('?') === -1) { + // encode the filename after last slash to ensure the handling of special characters + url = `${url.substr(0, lastIndex)}/${encodeURIComponent(url.substr(lastIndex + 1))}`; + } + } + window.open(url, '', 'scrollbars=no,menubar=no,height=600,width=800,resizable=yes,toolbar=no,status=no'); + }, + }; +} + +// eslint-disable-next-line no-unused-vars +export default async function decorate(fieldDiv, field, htmlForm) { + const allFiles = []; + const dragArea = createDragAndDropArea(fieldDiv, field); + const input = fieldDiv.querySelector('input'); + fieldDiv.classList.add('decorated'); + const fileListElement = document.createElement('div'); + fileListElement.classList.add('files-list'); + const attachButton = dragArea.querySelector('.file-attachButton'); + attachButton.addEventListener('click', () => input.click()); + const fileHandler = createFileHandler(allFiles, input); + input.addEventListener('change', (event) => { + if (!event?.detail?.deletion) { + event.stopPropagation(); + fileHandler.attachFiles(input, event.target.files); + } + }); + dragArea.addEventListener('drop', (event) => { + event.preventDefault(); + dragArea.classList.remove('file-dragArea-active'); + fileHandler.attachFiles(input, (event?.dataTransfer?.files || [])); + }); + dragArea.addEventListener('paste', (event) => { + event.preventDefault(); + fileHandler.attachFiles(input, (event?.clipboardData?.files || [])); + }); + dragArea.addEventListener('dragover', (event) => { + event.preventDefault(); + dragArea.classList.add('file-dragArea-active'); + }); + dragArea.addEventListener('dragleave', () => { + dragArea.classList.remove('file-dragArea-active'); + }); + fileListElement.addEventListener('click', (e) => { + if (e.target.tagName === 'BUTTON') { + fileHandler.removeFile(e.target?.parentElement?.dataset?.index || 0); + } else if (e.target.tagName === 'SPAN') { + fileHandler.previewFile(e.target?.parentElement?.dataset?.index || 0); + } + }); + fieldDiv.insertBefore(fileListElement, input.nextElementSibling); + // pre-fill file attachment + if (field.value) { + const preFillFiles = Array.isArray(field.value) ? field.value : [field.value]; + const dataTransfer = new DataTransfer(); + const file = new File([preFillFiles[0].data], preFillFiles[0].name, { ...preFillFiles[0] }); + dataTransfer.items.add(file); + // Pre-fill input field to mark it as a valid field. + input.files = dataTransfer.files; + fileHandler.attachFiles(input, preFillFiles); + } + return fieldDiv; +} diff --git a/blocks/form/components/repeat/repeat.js b/blocks/form/components/repeat/repeat.js new file mode 100644 index 0000000..4dcdfa3 --- /dev/null +++ b/blocks/form/components/repeat/repeat.js @@ -0,0 +1,115 @@ +import { getId } from '../../util.js'; + +function update(fieldset, index, labelTemplate) { + const legend = fieldset.querySelector(':scope>.field-label')?.firstChild; + const text = labelTemplate?.replace('#', index + 1); + if (legend) { + legend.textContent = text; + } + if (typeof fieldset.id === 'undefined') { + fieldset.id = getId(fieldset.name); + } + fieldset.setAttribute('data-index', index); + if (index > 0) { + fieldset.querySelectorAll('.field-wrapper').forEach((f) => { + const [label, input, description] = ['label', 'input,select,button,textarea', 'description'] + .map((x) => f.querySelector(x)); + input.id = getId(input.name); + if (label) { + label.htmlFor = input.id; + } + if (description) { + input.setAttribute('aria-describedby', `${input.Id}-description`); + description.id = `${input.id}-description`; + } + }); + } +} + +function createButton(label, icon) { + const button = document.createElement('button'); + button.className = `item-${icon}`; + button.type = 'button'; + const text = document.createElement('span'); + text.textContent = label; + button.append(document.createElement('i'), text); + return button; +} + +function insertRemoveButton(fieldset, wrapper, form) { + const removeButton = createButton('', 'remove'); + removeButton.addEventListener('click', () => { + fieldset.remove(); + wrapper.querySelector('.item-add').setAttribute('data-visible', 'true'); + wrapper.querySelectorAll('[data-repeatable="true"]').forEach((el, index) => { + update(el, index, wrapper['#repeat-template-label']); + }); + const event = new CustomEvent('item:remove', { + detail: { item: { name: fieldset.name, id: fieldset.id } }, + bubbles: false, + }); + form.dispatchEvent(event); + }); + const legend = fieldset.querySelector(':scope>.field-label'); + legend.append(removeButton); +} + +const add = (wrapper, form) => (e) => { + const { currentTarget } = e; + const { parentElement } = currentTarget; + const fieldset = parentElement['#repeat-template']; + const max = parentElement.getAttribute('data-max'); + const min = parentElement.getAttribute('data-min'); + const childCount = parentElement.children.length - 1; + const newFieldset = fieldset.cloneNode(true); + newFieldset.setAttribute('data-index', childCount); + update(newFieldset, childCount, parentElement['#repeat-template-label']); + if (childCount >= +min) { + insertRemoveButton(newFieldset, wrapper, form); + } + if (+max <= childCount + 1) { + e.currentTarget.setAttribute('data-visible', 'false'); + } + currentTarget.insertAdjacentElement('beforebegin', newFieldset); + const event = new CustomEvent('item:add', { + detail: { item: { name: newFieldset.name, id: newFieldset.id } }, + bubbles: false, + }); + form.dispatchEvent(event); +}; + +function getInstances(el) { + let nextSibling = el.nextElementSibling; + const siblings = [el]; + while (nextSibling && nextSibling.matches('[data-repeatable="true"]:not([data-repeatable="0"])')) { + siblings.push(nextSibling); + nextSibling = siblings.nextSiblingElement; + } + return siblings; +} + +export default function transferRepeatableDOM(form) { + form.querySelectorAll('[data-repeatable="true"][data-index="0"]').forEach((el) => { + const instances = getInstances(el); + const div = document.createElement('div'); + div.setAttribute('data-min', el.dataset.min); + div.setAttribute('data-max', el.dataset.max); + el.insertAdjacentElement('beforebegin', div); + div.append(...instances); + const addLabel = 'Add'; + const addButton = createButton(addLabel, 'add'); + addButton.addEventListener('click', add(div, form)); + const cloneNode = el.cloneNode(true); + cloneNode.removeAttribute('id'); + div['#repeat-template'] = cloneNode; + div['#repeat-template-label'] = el.querySelector(':scope>.field-label')?.textContent; + if (+el.min === 0) { + el.remove(); + } else { + update(el, 0, div['#repeat-template-label']); + el.setAttribute('data-index', 0); + } + div.append(addButton); + div.className = 'repeat-wrapper'; + }); +} diff --git a/blocks/form/components/wizard/wizard.js b/blocks/form/components/wizard/wizard.js new file mode 100644 index 0000000..da3c1a3 --- /dev/null +++ b/blocks/form/components/wizard/wizard.js @@ -0,0 +1,191 @@ +import { createButton } from '../../util.js'; + +export class WizardLayout { + inputFields = 'input,textarea,select'; + + constructor(includePrevBtn = true, includeNextBtn = true) { + this.includePrevBtn = includePrevBtn; + this.includeNextBtn = includeNextBtn; + } + + // eslint-disable-next-line class-methods-use-this + getSteps(panel) { + return [...panel.children].filter((step) => step.tagName.toLowerCase() === 'fieldset'); + } + + assignIndexToSteps(panel) { + const steps = this.getSteps(panel); + panel.style.setProperty('--wizard-step-count', steps.length); + steps.forEach((step, index) => { + step.dataset.index = index; + step.style.setProperty('--wizard-step-index', index); + }); + } + + // eslint-disable-next-line class-methods-use-this + getEligibleSibling(current, forward = true) { + const direction = forward ? 'nextElementSibling' : 'previousElementSibling'; + + for (let sibling = current[direction]; sibling; sibling = sibling[direction]) { + if (sibling.dataset.visible !== 'false' && sibling.tagName === 'FIELDSET') { + return sibling; + } + } + return null; + } + + /** + * @param {FormElement | Fieldset} container + * @returns return false, if there are invalid fields + */ + validateContainer(container) { + const fieldElements = [...container.querySelectorAll(this.inputFields)]; + const isValid = fieldElements.reduce((valid, fieldElement) => { + const isFieldValid = fieldElement.checkValidity(); + return valid && isFieldValid; + }, true); + + if (!isValid) { + container.querySelector(':invalid')?.focus(); + } + return isValid; + } + + navigate(panel, forward = true) { + const current = panel.querySelector('.current-wizard-step'); + const currentMenuItem = panel.querySelector('.wizard-menu-active-item'); + + let valid = true; + if (forward) { + valid = this.validateContainer(current); + } + const navigateTo = valid ? this.getEligibleSibling(current, forward) : current; + + if (navigateTo && current !== navigateTo) { + current.classList.remove('current-wizard-step'); + navigateTo.classList.add('current-wizard-step'); + // add/remove active class from menu item + const navigateToMenuItem = panel.querySelector(`li[data-index="${navigateTo.dataset.index}"]`); + currentMenuItem.classList.remove('wizard-menu-active-item'); + navigateToMenuItem.classList.add('wizard-menu-active-item'); + const event = new CustomEvent('wizard:navigate', { + detail: { + prevStep: { id: current.id, index: +current.dataset.index }, + currStep: { id: navigateTo.id, index: +navigateTo.dataset.index }, + }, + bubbles: false, + }); + panel.dispatchEvent(event); + } + } + + static handleMutation(panel, mutationsList) { + mutationsList.forEach((mutation) => { + const { type, target, attributeName } = mutation; + const menuItems = panel.querySelector('.wizard-menu-items'); + // Check if the mutation is a change in attributes(data-visible) + if (type === 'attributes' && attributeName === 'data-visible') { + const element = mutation.target; + const menuItem = panel.querySelector(`li[data-index="${element.dataset.index}"]`); + menuItem.dataset.visible = element.dataset.visible; + } else if (type === 'attributes' && attributeName === 'data-active') { + // for active panel + panel.querySelector('.current-wizard-step')?.classList.remove('current-wizard-step'); + const activePanel = panel.querySelector(`#${target?.id}`); + activePanel?.classList.add('current-wizard-step'); + // for active menu item + panel.querySelector('.wizard-menu-active-item')?.classList.remove('wizard-menu-active-item'); + menuItems.querySelector(`[data-index="${activePanel.dataset.index}"]`)?.classList.add('wizard-menu-active-item'); + target.querySelector('[data-active="true"]')?.focus(); + } + }); + } + + static attachMutationObserver(panel) { + const children = panel.querySelectorAll(':scope > .panel-wrapper'); + // Options for the observer (attributes to observe for) + const config = { attributes: true, subtree: false }; + // Create an observer instance linked to the callback function + const observer = new window.MutationObserver((mutationsList) => { + WizardLayout.handleMutation(panel, mutationsList); + }); + // Start observing each target node for configured mutations + children.forEach((targetNode) => { + observer.observe(targetNode, config); + }); + } + + static createMenu(children) { + const ul = document.createElement('ul'); + ul.className = 'wizard-menu-items'; + children.forEach((child, index) => { + const li = document.createElement('li'); + li.innerHTML = child.querySelector('legend')?.innerHTML || ''; + li.className = 'wizard-menu-item'; + li.dataset.index = index; + if (child.hasAttribute('data-visible')) { + li.dataset.visible = child.dataset.visible; + } + ul.append(li); + }); + return ul; + } + + addButton(wrapper, panel, buttonDef, forward = true) { + const button = createButton(buttonDef); + button.classList.add(buttonDef.id); + button.addEventListener('click', () => this.navigate(panel, forward)); + wrapper.append(button); + } + + applyLayout(panel) { + const children = panel.querySelectorAll(':scope > .panel-wrapper'); + if (children.length) { + // create wizard menu + const wizardMenu = WizardLayout.createMenu(Array.from(children)); + wizardMenu.querySelector('li').classList.add('wizard-menu-active-item'); + // Insert the menu before the first child of the wizard + panel.insertBefore(wizardMenu, children[0]); + WizardLayout.attachMutationObserver(panel); + } + + const wrapper = document.createElement('div'); + wrapper.className = 'wizard-button-wrapper'; + if (this.includePrevBtn && children.length) { + this.addButton(wrapper, panel, { + label: { value: 'Back' }, fieldType: 'button', name: 'back', id: 'wizard-button-prev', + }, false); + } + + if (this.includeNextBtn && children.length) { + this.addButton(wrapper, panel, { + label: { value: 'Next' }, fieldType: 'button', name: 'next', id: 'wizard-button-next', + }); + } + + const resetBtn = panel.querySelector('.reset-wrapper'); + if (resetBtn) { + wrapper.append(resetBtn); + } + + const submitBtn = panel.querySelector('.submit-wrapper'); + if (submitBtn) { + wrapper.append(submitBtn); + } + this.assignIndexToSteps(panel); + panel.append(wrapper); + panel.querySelector('fieldset')?.classList.add('current-wizard-step'); + panel.classList.add('wizard'); + // panel.classList.add('left'); + } +} + +const layout = new WizardLayout(); + +export default function wizardLayout(panel) { + layout.applyLayout(panel); + return panel; +} + +export const navigate = layout.navigate.bind(layout); +export const validateContainer = layout.validateContainer.bind(layout); diff --git a/blocks/form/form.css b/blocks/form/form.css index c8aa90f..6b56db2 100644 --- a/blocks/form/form.css +++ b/blocks/form/form.css @@ -130,8 +130,8 @@ main .form select { } main .form select { - padding-top: 0; - padding-bottom: 0; + padding-top: 0; + padding-bottom: 0; } main .form input[type='file'] { @@ -269,16 +269,16 @@ main .form form .item-remove { } main .form .file-description{ - display: flex; - gap: 20px; - align-items: center; - margin-top: 5px; + display: flex; + gap: 20px; + align-items: center; + margin-top: 5px; } main .form .file-description .file-description-name { - cursor: pointer; - text-decoration: underline; - flex: 1; + cursor: pointer; + text-decoration: underline; + flex: 1; } main .form form .item-remove span { @@ -332,13 +332,13 @@ main .form .button-wrapper button[disabled]:hover { /** Wizard Styling */ main .form form .wizard { - display: grid; - counter-reset: wizard-step-counter; - grid-template-columns: minmax(0, 1fr); - border: var(--form-wizard-border) solid var(--form-wizard-border-color); - background-color: var( --form-wizard-background-color); - gap: unset; - padding: 20px var(--form-wizard-padding); + display: grid; + counter-reset: wizard-step-counter; + grid-template-columns: minmax(0, 1fr); + border: var(--form-wizard-border) solid var(--form-wizard-border-color); + background-color: var( --form-wizard-background-color); + gap: unset; + padding: 20px var(--form-wizard-padding); } main .form form .wizard > legend{ @@ -347,7 +347,7 @@ main .form form .wizard > legend{ } main .form form .wizard > .field-description{ - width: 100%; + width: 100%; } main .form form .wizard > fieldset > legend{ @@ -386,27 +386,27 @@ main .form form .wizard .wizard-button-wrapper .field-wrapper{ } main .form form .wizard .wizard-menu-item { - counter-increment: wizard-step-counter; + counter-increment: wizard-step-counter; } main .form form .wizard .wizard-menu-item::before { - content: counter(wizard-step-counter); - border: 2px solid var(--form-wizard-number-color); - color: var(--form-wizard-number-color); - border-radius: 50%; - width: 35px; - height: 35px; - margin-right: 10px; - text-align: center; - line-height: 2; - display: inline-block; + content: counter(wizard-step-counter); + border: 2px solid var(--form-wizard-number-color); + color: var(--form-wizard-number-color); + border-radius: 50%; + width: 35px; + height: 35px; + margin-right: 10px; + text-align: center; + line-height: 2; + display: inline-block; } main .form form .wizard .wizard-menu-active-item::before{ - border: 2px solid var(--form-wizard-number-color); - background: var(--form-wizard-number-color); - color: var(--background-color-primary); - font-weight: 600; + border: 2px solid var(--form-wizard-number-color); + background: var(--form-wizard-number-color); + color: var(--background-color-primary); + font-weight: 600; } @media (width >= 600px) { @@ -417,28 +417,28 @@ main .form form .wizard .wizard-menu-active-item::before{ /* wizard title on left */ main .form form .wizard.left{ - display: flex; + display: flex; } main .form form .wizard.left .wizard-menu-items{ - flex-direction: column; - width: 300px; + flex-direction: column; + width: 300px; } main form .panel-wrapper { - display: flex; - padding: 0; - flex-direction: column; + display: flex; + padding: 0; + flex-direction: column; } main .form form .panel-wrapper{ - display: grid; - grid-template-columns: repeat(var(--form-columns), minmax(0, 1fr)); - gap: var(--form-field-vert-gap) var(--form-field-horz-gap); + display: grid; + grid-template-columns: repeat(var(--form-columns), minmax(0, 1fr)); + gap: var(--form-field-vert-gap) var(--form-field-horz-gap); } main .form form .wizard.left .panel-wrapper{ - flex: 1; + flex: 1; } @media (width >= 1200px) { @@ -474,19 +474,19 @@ main .form .form-message.error-message { } main .form .file-wrapper .file-drag-area { - border: 1px dashed var(--border-color); - border-radius: 5px; - padding: 10px 5px; - display: flex; - flex-direction: column; - gap: 20px; - justify-content: center; - align-items: center; + border: 1px dashed var(--border-color); + border-radius: 5px; + padding: 10px 5px; + display: flex; + flex-direction: column; + gap: 20px; + justify-content: center; + align-items: center; } /* grid css */ main .form form .field-wrapper .field-description{ - grid-column: span 12; + grid-column: span 12; } main .form form .field-wrapper.col-12 { @@ -498,7 +498,7 @@ main .form form .field-wrapper.col-11 { } main .form form .field-wrapper.col-10 { - grid-column: span 10; + grid-column: span 10; } main .form form .field-wrapper.col-9 { @@ -514,23 +514,23 @@ main .form form .field-wrapper.col-7 { } main .form form .field-wrapper.col-6 { - grid-column: span 6; + grid-column: span 6; } main .form form .field-wrapper.col-5 { - grid-column: span 5; + grid-column: span 5; } main .form form .field-wrapper.col-4 { - grid-column: span 4; + grid-column: span 4; } main .form form .field-wrapper.col-3 { - grid-column: span 3; + grid-column: span 3; } main .form form .field-wrapper.col-2 { - grid-column: span 2; + grid-column: span 2; } main .form form .field-wrapper.col-1 { @@ -538,11 +538,11 @@ main .form form .field-wrapper.col-1 { } main .form .form-image-wrapper img { - max-width: 100%; - height: auto; + max-width: 100%; + height: auto; } main form .wizard > .current-wizard-step.panel-wrapper:first-of-type ~ .wizard-button-wrapper > .wizard-button-prev, main form .wizard > .current-wizard-step.panel-wrapper:last-of-type ~ .wizard-button-wrapper > .wizard-button-next { display: none; -} +} \ No newline at end of file diff --git a/blocks/form/form.js b/blocks/form/form.js index 07455bf..f2dff46 100644 --- a/blocks/form/form.js +++ b/blocks/form/form.js @@ -4,11 +4,12 @@ import { getId, stripTags, checkValidation, + toClassName, } from './util.js'; import GoogleReCaptcha from './integrations/recaptcha.js'; -import componentDecorater from './mappings.js'; +import componentDecorator from './mappings.js'; import DocBasedFormToAF from './transform.js'; -import transferRepeatableDOM from './components/repeat.js'; +import transferRepeatableDOM from './components/repeat/repeat.js'; import { handleSubmit } from './submit.js'; import { getSubmitBaseUrl, emailPattern } from './constant.js'; @@ -172,7 +173,7 @@ function createRadioOrCheckboxGroup(fd) { const wrapper = createFieldSet({ ...fd }); const type = fd.fieldType.split('-')[0]; fd.enum.forEach((value, index) => { - const label = typeof fd.enumNames?.[index] === 'object' ? fd.enumNames[index].value : fd.enumNames?.[index] || value; + const label = (typeof fd.enumNames?.[index] === 'object' && fd.enumNames?.[index] !== null) ? fd.enumNames[index].value : fd.enumNames?.[index] || value; const id = getId(fd.name); const field = createRadioOrCheckbox({ name: fd.name, @@ -182,7 +183,7 @@ function createRadioOrCheckboxGroup(fd) { enum: [value], required: fd.required, }); - field.classList.remove('field-wrapper', `field-${fd.name}`); + field.classList.remove('field-wrapper', `field-${toClassName(fd.name)}`); const input = field.querySelector('input'); input.id = id; input.dataset.fieldType = fd.fieldType; @@ -219,11 +220,12 @@ function createPlainText(fd) { function createImage(fd) { const field = createFieldWrapper(fd); + const imagePath = fd.source || fd.properties['fd:repoPath'] || ''; const image = ` - - - ${fd.altText || fd.name} + + + ${fd.altText || fd.name} `; field.innerHTML = image; return field; @@ -309,8 +311,8 @@ function inputDecorator(field, element) { if (field.maxFileSize) { input.dataset.maxFileSize = field.maxFileSize; } - if (field.default) { - input.value = field.default; + if (field.default !== undefined) { + input.setAttribute('value', field.default); } if (input.type === 'email') { input.pattern = emailPattern; @@ -353,14 +355,11 @@ export async function generateFormRendition(panel, container, getItems = (p) => element.className += ` ${field.appliedCssClassNames}`; } colSpanDecorator(field, element); - const decorator = await componentDecorater(field); if (field?.fieldType === 'panel') { await generateFormRendition(field, element, getItems); return element; } - if (typeof decorator === 'function') { - return decorator(element, field, container); - } + await componentDecorator(element, field, container); return element; } return null; @@ -368,11 +367,7 @@ export async function generateFormRendition(panel, container, getItems = (p) => const children = await Promise.all(promises); container.append(...children.filter((_) => _ != null)); - const decorator = await componentDecorater(panel); - if (typeof decorator === 'function') { - return decorator(container, panel); - } - return container; + await componentDecorator(container, panel); } function enableValidation(form) { diff --git a/blocks/form/mappings.js b/blocks/form/mappings.js index a00ea6e..fe04e69 100644 --- a/blocks/form/mappings.js +++ b/blocks/form/mappings.js @@ -26,7 +26,7 @@ async function loadComponent(componentName, element, fd, container) { try { const mod = await import( `${window.hlx.codeBasePath}/blocks/form/components/${componentName}/${componentName}.js` - ); + ); if (mod.default) { await mod.default(element, fd, container); } diff --git a/blocks/form/rules-doc/RuleCompiler.js b/blocks/form/rules-doc/RuleCompiler.js index d30818a..7c23d52 100644 --- a/blocks/form/rules-doc/RuleCompiler.js +++ b/blocks/form/rules-doc/RuleCompiler.js @@ -1,3 +1,22 @@ +/** *********************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + + * Adobe permits you to use and modify this file solely in accordance with + * the terms of the Adobe license agreement accompanying it. + ************************************************************************ */ const cellNameRegex = /^\$?[A-Z]+\$?(\d+)$/; function visitor(nameMap, fields, bExcelFormula) { diff --git a/blocks/form/rules-doc/RuleEngine.js b/blocks/form/rules-doc/RuleEngine.js index a4942a5..cac9ba0 100644 --- a/blocks/form/rules-doc/RuleEngine.js +++ b/blocks/form/rules-doc/RuleEngine.js @@ -1,3 +1,22 @@ +/** *********************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + + * Adobe permits you to use and modify this file solely in accordance with + * the terms of the Adobe license agreement accompanying it. + ************************************************************************ */ /* eslint-disable max-classes-per-file */ import Formula from './parser/Formula.js'; import transformRule from './RuleCompiler.js'; diff --git a/blocks/form/rules-doc/index.js b/blocks/form/rules-doc/index.js index ceeca4e..4a1bf75 100644 --- a/blocks/form/rules-doc/index.js +++ b/blocks/form/rules-doc/index.js @@ -1,3 +1,22 @@ +/** *********************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + + * Adobe permits you to use and modify this file solely in accordance with + * the terms of the Adobe license agreement accompanying it. + ************************************************************************ */ export default async function applyRuleEngine(form, formTag) { try { const { fieldIdMap, rules } = form.properties.rules; diff --git a/blocks/form/rules/RuleEngineWorker.js b/blocks/form/rules/RuleEngineWorker.js index 1cee356..415e16f 100644 --- a/blocks/form/rules/RuleEngineWorker.js +++ b/blocks/form/rules/RuleEngineWorker.js @@ -1,3 +1,22 @@ +/** *********************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + + * Adobe permits you to use and modify this file solely in accordance with + * the terms of the Adobe license agreement accompanying it. + ************************************************************************ */ import { createFormInstance } from './model/afb-runtime.js'; import registerCustomFunctions from './functionRegistration.js'; diff --git a/blocks/form/rules/functionRegistration.js b/blocks/form/rules/functionRegistration.js index 2d9e3e7..94f3e2c 100644 --- a/blocks/form/rules/functionRegistration.js +++ b/blocks/form/rules/functionRegistration.js @@ -1,3 +1,22 @@ +/** *********************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + + * Adobe permits you to use and modify this file solely in accordance with + * the terms of the Adobe license agreement accompanying it. + ************************************************************************ */ import { registerFunctions } from './model/afb-runtime.js'; export default async function registerCustomFunctions() { diff --git a/blocks/form/rules/functions.js b/blocks/form/rules/functions.js index 07fa2ed..828c3f1 100644 --- a/blocks/form/rules/functions.js +++ b/blocks/form/rules/functions.js @@ -1,18 +1,23 @@ -import { getSubmitBaseUrl } from '../constant.js'; - -/** - * Converts a JSON string to an object. - * @param {string} str - The JSON string to convert to an object. - * @returns {object} - The parsed JSON object. Returns an empty object if an exception occurs. - */ -export function toObject(str) { - try { - return JSON.parse(str); - } catch (e) { - return {}; - } -} +/** *********************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + * Adobe permits you to use and modify this file solely in accordance with + * the terms of the Adobe license agreement accompanying it. + ************************************************************************ */ +import { getSubmitBaseUrl } from '../constant.js'; /** * Prefixes the URL with the context path. * @param {string} url - The URL to externalize. @@ -40,24 +45,40 @@ function validateURL(url) { } } +/** + * Converts a JSON string to an object. + * @param {string} str - The JSON string to convert to an object. + * @returns {object} - The parsed JSON object. Returns an empty object if an exception occurs. + * @memberof module:FormView~customFunctions + */ +function toObject(str) { + try { + return JSON.parse(str); + } catch (e) { + return {}; + } +} + /** * Navigates to the specified URL. - * @param {string} destinationURL - The URL to navigate to. If not specified, - * a new blank window will be opened. - * @param {string} destinationType - The type of destination. Supports the following - * values: "_newwindow", "_blank", "_parent", "_self", "_top", or the name of the window. + * @param {string} destinationURL - The URL to navigate to. + * If not specified, a new blank window will be opened. + * @param {string} destinationType - The type of destination. + * Supports the following values: "_newwindow", "_blank", "_parent", "_self", "_top", + * or the name of the window. * @returns {Window} - The newly opened window. */ -export function navigateTo(destinationURL, destinationType) { +function navigateTo(destinationURL, destinationType) { let param = null; const windowParam = window; let arg = null; - // eslint-disable-next-line default-case switch (destinationType) { case '_newwindow': param = '_blank'; arg = 'width=1000,height=800'; break; + default: + break; } if (!param) { if (destinationType) { @@ -67,36 +88,35 @@ export function navigateTo(destinationURL, destinationType) { } } if (validateURL(destinationURL)) { - windowParam.open(externalize(destinationURL), param, arg); + windowParam.open(destinationURL, param, arg); } } /** - * Default error handler for the invoke service API in AEM Forms. + * Default error handler for the invoke service API. * @param {object} response - The response body of the invoke service API. * @param {object} headers - The response headers of the invoke service API. - * @param {object} globals - An object containing form instance and invoke method - * to call other custom functions. + * @param {scope} globals - An object containing read-only form instance, + * read-only target field instance and methods for form modifications. * @returns {void} */ -export function defaultErrorHandler(response, headers, globals) { +function defaultErrorHandler(response, headers, globals) { if (response && response.validationErrors) { response.validationErrors?.forEach((violation) => { if (violation.details) { if (violation.fieldName) { - globals.form.visit((f) => { - if (f.qualifiedName === violation.fieldName) { - f.markAsInvalid(violation.details.join('\n')); - } - }); + globals.functions.markFieldAsInvalid(violation.fieldName, violation.details.join('\n'), { useQualifiedName: true }); } else if (violation.dataRef) { - globals.form.visit((f) => { - if (f.dataRef === violation.dataRef) { - f.markAsInvalid(violation.details.join('\n')); - } - }); + globals.functions.markFieldAsInvalid(violation.dataRef, violation.details.join('\n'), { useDataRef: true }); } } }); } } + +export { + validateURL, + navigateTo, + toObject, + defaultErrorHandler, +}; diff --git a/blocks/form/rules/index.js b/blocks/form/rules/index.js index 64758e7..aafcf1a 100644 --- a/blocks/form/rules/index.js +++ b/blocks/form/rules/index.js @@ -1,3 +1,22 @@ +/** *********************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + + * Adobe permits you to use and modify this file solely in accordance with + * the terms of the Adobe license agreement accompanying it. + ************************************************************************ */ import { submitSuccess, submitFailure } from '../submit.js'; import { createHelpText, createLabel, updateOrCreateInvalidMsg, getCheckboxGroupValue, diff --git a/blocks/form/rules/worker.js b/blocks/form/rules/worker.js index 0074767..75471ae 100644 --- a/blocks/form/rules/worker.js +++ b/blocks/form/rules/worker.js @@ -1,3 +1,22 @@ +/** *********************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + + * Adobe permits you to use and modify this file solely in accordance with + * the terms of the Adobe license agreement accompanying it. + ************************************************************************ */ export default async function initializeRuleEngineWorker(formDef, renderHTMLForm) { if (typeof Worker === 'undefined') { const ruleEngine = await import('./model/afb-runtime.js'); diff --git a/blocks/form/util.js b/blocks/form/util.js index a9f9458..cedbe90 100644 --- a/blocks/form/util.js +++ b/blocks/form/util.js @@ -26,7 +26,7 @@ export function stripTags(input, allowd = allowedTags) { * @param {string} name The unsanitized string * @returns {string} The class name */ -function toClassName(name) { +export function toClassName(name) { return typeof name === 'string' ? name .toLowerCase() diff --git a/component-models.json b/component-models.json index 4070b2a..1e88a55 100644 --- a/component-models.json +++ b/component-models.json @@ -355,7 +355,63 @@ "name": "unboundFormElement", "label": "Mark as Unbound Form Element", "valueType": "boolean" - } + }, +{ + "component": "select", + "name": "colspan", + "label": "Columns", + "valueType": "string", + "options": [ + { + "name": "1 column", + "value": "1" + }, + { + "name": "2 column", + "value": "2" + }, + { + "name": "3 column", + "value": "3" + }, + { + "name": "4 column", + "value": "4" + }, + { + "name": "5 column", + "value": "5" + }, + { + "name": "6 column", + "value": "6" + }, + { + "name": "7 column", + "value": "7" + }, + { + "name": "8 column", + "value": "8" + }, + { + "name": "9 column", + "value": "9" + }, + { + "name": "10 column", + "value": "10" + }, + { + "name": "11 column", + "value": "11" + }, + { + "name": "12 column", + "value": "12" + } + ] +} ] }, { @@ -479,7 +535,63 @@ "name": "unboundFormElement", "label": "Mark as Unbound Form Element", "valueType": "boolean" - } + }, +{ + "component": "select", + "name": "colspan", + "label": "Columns", + "valueType": "string", + "options": [ + { + "name": "1 column", + "value": "1" + }, + { + "name": "2 column", + "value": "2" + }, + { + "name": "3 column", + "value": "3" + }, + { + "name": "4 column", + "value": "4" + }, + { + "name": "5 column", + "value": "5" + }, + { + "name": "6 column", + "value": "6" + }, + { + "name": "7 column", + "value": "7" + }, + { + "name": "8 column", + "value": "8" + }, + { + "name": "9 column", + "value": "9" + }, + { + "name": "10 column", + "value": "10" + }, + { + "name": "11 column", + "value": "11" + }, + { + "name": "12 column", + "value": "12" + } + ] +} ] }, { @@ -623,7 +735,63 @@ "name": "unboundFormElement", "label": "Mark as Unbound Form Element", "valueType": "boolean" - } + }, +{ + "component": "select", + "name": "colspan", + "label": "Columns", + "valueType": "string", + "options": [ + { + "name": "1 column", + "value": "1" + }, + { + "name": "2 column", + "value": "2" + }, + { + "name": "3 column", + "value": "3" + }, + { + "name": "4 column", + "value": "4" + }, + { + "name": "5 column", + "value": "5" + }, + { + "name": "6 column", + "value": "6" + }, + { + "name": "7 column", + "value": "7" + }, + { + "name": "8 column", + "value": "8" + }, + { + "name": "9 column", + "value": "9" + }, + { + "name": "10 column", + "value": "10" + }, + { + "name": "11 column", + "value": "11" + }, + { + "name": "12 column", + "value": "12" + } + ] +} ] }, { @@ -735,6 +903,62 @@ "name": "readOnly", "label": "Read-only", "valueType": "boolean" + }, +{ + "component": "select", + "name": "colspan", + "label": "Columns", + "valueType": "string", + "options": [ + { + "name": "1 column", + "value": "1" + }, + { + "name": "2 column", + "value": "2" + }, + { + "name": "3 column", + "value": "3" + }, + { + "name": "4 column", + "value": "4" + }, + { + "name": "5 column", + "value": "5" + }, + { + "name": "6 column", + "value": "6" + }, + { + "name": "7 column", + "value": "7" + }, + { + "name": "8 column", + "value": "8" + }, + { + "name": "9 column", + "value": "9" + }, + { + "name": "10 column", + "value": "10" + }, + { + "name": "11 column", + "value": "11" + }, + { + "name": "12 column", + "value": "12" + } + ] } ] }, @@ -805,7 +1029,7 @@ } ] }, - { + { "id": "panel", "fields": [ { @@ -858,6 +1082,62 @@ "name": "readOnly", "label": "Read-only", "valueType": "boolean" + }, + { + "component": "select", + "name": "colspan", + "label": "Columns", + "valueType": "string", + "options": [ + { + "name": "1 column", + "value": "1" + }, + { + "name": "2 column", + "value": "2" + }, + { + "name": "3 column", + "value": "3" + }, + { + "name": "4 column", + "value": "4" + }, + { + "name": "5 column", + "value": "5" + }, + { + "name": "6 column", + "value": "6" + }, + { + "name": "7 column", + "value": "7" + }, + { + "name": "8 column", + "value": "8" + }, + { + "name": "9 column", + "value": "9" + }, + { + "name": "10 column", + "value": "10" + }, + { + "name": "11 column", + "value": "11" + }, + { + "name": "12 column", + "value": "12" + } + ] } ] }, @@ -1257,7 +1537,63 @@ "name": "unboundFormElement", "label": "Mark as Unbound Form Element", "valueType": "boolean" - } + }, +{ + "component": "select", + "name": "colspan", + "label": "Columns", + "valueType": "string", + "options": [ + { + "name": "1 column", + "value": "1" + }, + { + "name": "2 column", + "value": "2" + }, + { + "name": "3 column", + "value": "3" + }, + { + "name": "4 column", + "value": "4" + }, + { + "name": "5 column", + "value": "5" + }, + { + "name": "6 column", + "value": "6" + }, + { + "name": "7 column", + "value": "7" + }, + { + "name": "8 column", + "value": "8" + }, + { + "name": "9 column", + "value": "9" + }, + { + "name": "10 column", + "value": "10" + }, + { + "name": "11 column", + "value": "11" + }, + { + "name": "12 column", + "value": "12" + } + ] +} ] }, { @@ -1418,7 +1754,63 @@ "name": "readOnly", "label": "Read-only", "valueType": "boolean" - } + }, +{ + "component": "select", + "name": "colspan", + "label": "Columns", + "valueType": "string", + "options": [ + { + "name": "1 column", + "value": "1" + }, + { + "name": "2 column", + "value": "2" + }, + { + "name": "3 column", + "value": "3" + }, + { + "name": "4 column", + "value": "4" + }, + { + "name": "5 column", + "value": "5" + }, + { + "name": "6 column", + "value": "6" + }, + { + "name": "7 column", + "value": "7" + }, + { + "name": "8 column", + "value": "8" + }, + { + "name": "9 column", + "value": "9" + }, + { + "name": "10 column", + "value": "10" + }, + { + "name": "11 column", + "value": "11" + }, + { + "name": "12 column", + "value": "12" + } + ] +} ] }, { @@ -1554,7 +1946,63 @@ "name": "unboundFormElement", "label": "Mark as Unbound Form Element", "valueType": "boolean" - } + }, +{ + "component": "select", + "name": "colspan", + "label": "Columns", + "valueType": "string", + "options": [ + { + "name": "1 column", + "value": "1" + }, + { + "name": "2 column", + "value": "2" + }, + { + "name": "3 column", + "value": "3" + }, + { + "name": "4 column", + "value": "4" + }, + { + "name": "5 column", + "value": "5" + }, + { + "name": "6 column", + "value": "6" + }, + { + "name": "7 column", + "value": "7" + }, + { + "name": "8 column", + "value": "8" + }, + { + "name": "9 column", + "value": "9" + }, + { + "name": "10 column", + "value": "10" + }, + { + "name": "11 column", + "value": "11" + }, + { + "name": "12 column", + "value": "12" + } + ] +} ] }, { @@ -1654,7 +2102,63 @@ "label": "Enable Component", "valueType": "boolean", "value": true - } + }, +{ + "component": "select", + "name": "colspan", + "label": "Columns", + "valueType": "string", + "options": [ + { + "name": "1 column", + "value": "1" + }, + { + "name": "2 column", + "value": "2" + }, + { + "name": "3 column", + "value": "3" + }, + { + "name": "4 column", + "value": "4" + }, + { + "name": "5 column", + "value": "5" + }, + { + "name": "6 column", + "value": "6" + }, + { + "name": "7 column", + "value": "7" + }, + { + "name": "8 column", + "value": "8" + }, + { + "name": "9 column", + "value": "9" + }, + { + "name": "10 column", + "value": "10" + }, + { + "name": "11 column", + "value": "11" + }, + { + "name": "12 column", + "value": "12" + } + ] +} ] }, { @@ -1728,4 +2232,4 @@ } ] } -] \ No newline at end of file +] diff --git a/paths.json b/paths.json index 90e3951..1258f3d 100644 --- a/paths.json +++ b/paths.json @@ -1,5 +1,5 @@ { "mappings": [ - "/:/" + "/content/venkyue38/:/" ] } diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..aaf26bd --- /dev/null +++ b/test/README.md @@ -0,0 +1,46 @@ + + +## Running test cases +To execute both unit and e2e test use command +```sh +npm run test +``` +### Unit Tests +Run the following command to execute unit tests +```sh +npm run test:unit +``` + +### Integration Tests + +#### Test collateral Creation + +##### Doc Based Forms +1. Make sure you have access to the drive folder specified in the fstab of this project. +2. Create all collaterals under the `/drafts/tests/doc-based` folder. +3. Publish it using AEM sidekick. + + +##### Cross-walk Forms +1. Use cloud instance:https://author-p133911-e1313554.adobeaemcloud.com/. +2. Create test collateral inside `/formsanddocuments/xwalk-test-collateral` folder and then publish it. +3. Create a page with a form block in `/drafts/tests/x-walk` folder in drive. +4. Add the published guideContainer.model.json url of the test collateral in the form block. +5. Now publish the page using AEM sidekick. + + +#### Running tests + +Run the following command to execute integration tests +```sh +npm run test:e2e +``` +To run a specific test file, use the following command +```sh +npm run test:e2e +``` + +To debug a specific test file, use the following command +```sh +npm run debug:e2e +``` diff --git a/test/e2e/fixtures.js b/test/e2e/fixtures.js new file mode 100644 index 0000000..32609cc --- /dev/null +++ b/test/e2e/fixtures.js @@ -0,0 +1,31 @@ +import { test as base, expect } from '@playwright/test'; +import fsPromises from 'fs/promises'; +import crypto from 'crypto'; +import path from 'path'; +import { URL, fileURLToPath } from 'url'; + +const filename = fileURLToPath(import.meta.url); +const dirname = path.dirname(filename); + +const test = base.extend({ + saveCoverageInfo: [async ({ page }, use) => { + await page.coverage.startJSCoverage({ resetOnNavigation: false }); + await use(); + const coverage = await page.coverage.stopJSCoverage(); + const srcPath = '/blocks/form/'; + const basePath = path.normalize(`${dirname}/../..`); + const srcCoverage = coverage + .filter(({ url }) => url.includes(srcPath)) + .map(({ source, ...entry }) => { + const fileName = new URL(entry.url).pathname; + return { ...entry, url: `file://${basePath}${fileName}` }; + }); + await fsPromises.mkdir('coverage/tmp', { recursive: true }); + await fsPromises.writeFile( + `coverage/tmp/coverage-${crypto.randomUUID()}.json`, + JSON.stringify({ result: srcCoverage }), + ); + }, { auto: true }], +}); + +export { test, expect }; diff --git a/test/e2e/upload/empty.pdf b/test/e2e/upload/empty.pdf new file mode 100644 index 0000000..e69de29 diff --git a/test/e2e/utils.js b/test/e2e/utils.js new file mode 100644 index 0000000..492865d --- /dev/null +++ b/test/e2e/utils.js @@ -0,0 +1,50 @@ +import { execSync } from 'node:child_process'; + +function executeGitCommand(command) { + return execSync(command) + .toString('utf8') + .replace(/[\n\r\s]+$/, ''); +} + +// eslint-disable-next-line max-len +export async function fillField(page, componentTitle, inputValues) { + switch (componentTitle) { + case 'Text Input': + await page.getByLabel(componentTitle).fill(inputValues.textInput); + break; + case 'Email Input': + await page.getByLabel(componentTitle).fill(inputValues.emailInput); + break; + case 'Telephone Input': + case 'Number Input': + await page.getByLabel(componentTitle).fill(inputValues.numberInput); + break; + case 'Check Box Group': + await page.getByRole('checkbox', { name: 'Item 1' }).click(); + break; + case 'Radio Button': + await page.getByRole('radio', { name: 'Item 2' }).click(); + break; + case 'Dropdown': + await page.getByLabel(componentTitle).selectOption(inputValues.dropDown); + break; + case 'File Attachment': + await page.getByLabel(componentTitle).setInputFiles(inputValues.FilePath); + break; + case 'Date Input': + await page.getByLabel(componentTitle).focus(); + await page.getByLabel(componentTitle).fill(inputValues.dataInput); + break; + default: + console.error(`${componentTitle} Title is not visible`); + break; + } +} + +const getCurrentBranch = () => executeGitCommand('git rev-parse --abbrev-ref HEAD'); +const openPage = async (page, relativeURL) => { + const url = `https://${getCurrentBranch()}--aem-boilerplate-forms--adobe-rnd.hlx.live${relativeURL}`; + await page.goto(url, { waitUntil: 'networkidle' }); +}; + +export { openPage, getCurrentBranch }; diff --git a/test/e2e/x-walk/errormessages.spec.js b/test/e2e/x-walk/errormessages.spec.js new file mode 100644 index 0000000..d8121ad --- /dev/null +++ b/test/e2e/x-walk/errormessages.spec.js @@ -0,0 +1,81 @@ +import { test, expect } from '../fixtures.js'; +import { openPage } from '../utils.js'; + +test.describe('error messages test', () => { + const testURL = '/drafts/tests/x-walk/errormessages'; + + test('required OOTB error message ', async ({ page }) => { + await openPage(page, testURL); + const submitButton = await page.getByRole('button', { name: 'Submit' }); + await submitButton.click(); + const f1 = await page.locator('input[name="f1"]').locator('..'); + expect(await f1.locator('.field-description').innerText()).toBe('Please fill in this field.'); + const f2 = await page.locator('input[name="f2"]').locator('..'); + expect(await f2.locator('.field-description').innerText()).toBe('Please fill in this field.'); + const f3 = await page.locator('fieldset[name="f3"]'); + expect(await f3.locator('.field-description').innerText()).toBe('Please fill in this field.'); + const f4 = await page.locator('fieldset[name="f4"]'); + expect(await f4.locator('.field-description').innerText()).toBe('Please fill in this field.'); + const f5 = await page.locator('input[name="f5"]').locator('../..'); + expect(await f5.locator('.field-description').innerText()).toBe('Please fill in this field.'); + const f6 = await page.locator('input[name="f6"]').locator('..'); + expect(await f6.locator('.field-description').innerText()).toBe('Please fill in this field.'); + }); + + test('custom required error message', async ({ page }) => { + await openPage(page, testURL); + const submitButton = await page.getByRole('button', { name: 'Submit' }); + await submitButton.click(); + const f7 = await page.locator('input[name="f7"]').locator('..'); + expect(await f7.locator('.field-description').innerText()).toBe('This is a required text field, please fill this.'); + }); + + test('minLength and maxLength error messages', async ({ page }) => { + await openPage(page, testURL); + const f1 = await page.locator('input[name="f1"]'); + await f1.fill('a'); + await f1.press('Tab'); + expect(await f1.locator('..').locator('.field-description').innerText()).toBe('Please lengthen this text to 2 characters or more.'); + await f1.fill('abcdef'); + await f1.press('Tab'); + expect(await f1.inputValue()).toBe('abcde'); // cannot input more than 5 characters + }); + + test('pattern error message', async ({ page }) => { + await openPage(page, testURL); + const f6 = await page.locator('input[name="f6"]'); + await f6.fill('abc'); + await f6.press('Tab'); + expect(await f6.locator('..').locator('.field-description').innerText()).toBe('Specify the value in allowed format : email.'); + }); + + test('rangeOverflow and rangeUnderflow error messages', async ({ page }) => { + await openPage(page, testURL); + const f2 = await page.locator('input[name="f2"]'); + await f2.fill('1'); + await f2.press('Tab'); + expect(await f2.locator('..').locator('.field-description').innerText()).toBe('Value must be greater than or equal to 2.'); + await f2.fill('11'); + await f2.press('Tab'); + expect(await f2.locator('..').locator('.field-description').innerText()).toBe('Value must be less than or equal to 10.'); + }); + + test('error message set via rule editor', async ({ page }) => { + await openPage(page, testURL); + const f8 = await page.locator('input[name="f8"]'); + const button = await page.getByRole('button', { name: 'Button' }); + button.click(); + expect(await f8.locator('..').locator('.field-description').innerText()).toBe('error message set via rule editor'); + }); + + test('clear errormessage when field is valid', async ({ page }) => { + await openPage(page, testURL); + const f1 = await page.locator('input[name="f1"]'); + await f1.fill('a'); + await f1.press('Tab'); + expect(await f1.locator('..').locator('.field-description').innerText()).toBe('Please lengthen this text to 2 characters or more.'); + const button = await page.getByRole('button', { name: 'clear' }); + await button.click(); + await expect(await f1.locator('..').locator('.field-description')).not.toBeVisible(); + }); +}); diff --git a/test/e2e/x-walk/prefill.spec.js b/test/e2e/x-walk/prefill.spec.js new file mode 100644 index 0000000..8a9ef80 --- /dev/null +++ b/test/e2e/x-walk/prefill.spec.js @@ -0,0 +1,54 @@ +import { expect, test } from '@playwright/test'; +import { openPage } from '../utils.js'; + +const submitUrl = 'https://publish-p133911-e1313554.adobeaemcloud.com/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYveHdhbGstdGVzdC1jb2xsYXRlcmFsL2ZkbXByZWZpbGwvZmRtc3VibWlzc2lvbg=='; +const dropDownSelector = 'div.drop-down-wrapper select'; +const submitBaseUrl = 'https://publish-p133911-e1313554.adobeaemcloud.com'; +const petName = 'adobe'; +const id = '25'; +const status = 'sold'; +const ruleText = 'test'; + +test.describe('test cases prefill', async () => { + const testURL = '/drafts/tests/x-walk/fdmsubmission'; + const testURL1 = '/drafts/tests/x-walk/fdminvokeservice'; + test('test case for fdmSubmission, invokeService and custom prefill', async ({ page }) => { + await openPage(page, testURL); + await page.getByLabel('Name').fill(petName); + // eslint-disable-next-line no-shadow + await page.evaluate(async (submitUrl) => { + // eslint-disable-next-line no-undef,no-underscore-dangle + myForm._jsonModel.action = submitUrl; + }, submitUrl); + await page.getByLabel('id').fill(id); + await page.getByLabel('Status').selectOption(status); + await page.getByRole('button', { name: 'Submit' }).click(); + await openPage(page, testURL1); + // eslint-disable-next-line no-use-before-define + await setSubmitBaseUrl(page); + await page.getByLabel('Text Input').fill(ruleText); + await page.getByLabel('Text Input').blur(); + await expect(page.getByLabel('Name')).toHaveValue(petName); + expect(await page.getByLabel('id').inputValue()).toBe(id); + await expect(page.locator(dropDownSelector)).toHaveValue(status); + // Upon hitting the button the page will be redirecting to EDS Pre-fill Form. + await page.getByRole('button', { name: 'Button' }).click(); + // eslint-disable-next-line no-use-before-define + await setSubmitBaseUrl(page); + await expect(page.getByLabel('Name')).toBeVisible({ timeout: 15000 }); + expect(await page.getByLabel('Name').inputValue()).toBe(petName); + expect(await page.getByLabel('id').inputValue()).toBe(id); + await expect(page.locator(dropDownSelector)).toHaveValue(status); + }); + + const setSubmitBaseUrl = async (page) => { + // eslint-disable-next-line no-shadow + await page.evaluate(async (submitBaseUrl) => { + /* eslint-disable import/no-absolute-path */ + // eslint-disable-next-line import/no-unresolved + await import('/blocks/form/constant.js').then((module) => { + module.setSubmitBaseUrl(submitBaseUrl); + }); + }, submitBaseUrl); + }; +}); diff --git a/test/e2e/x-walk/resetButton.spec.js b/test/e2e/x-walk/resetButton.spec.js new file mode 100644 index 0000000..ae1e1ed --- /dev/null +++ b/test/e2e/x-walk/resetButton.spec.js @@ -0,0 +1,91 @@ +import { test, expect } from '../fixtures.js'; +import { fillField, openPage } from '../utils.js'; + +const wizardCount = ".repeat-wrapper fieldset[class='panel-wrapper field-wrapper wizard']"; +const wizardPanelCount = 'ul.wizard-menu-items li.wizard-menu-item'; +const titles = ['Text Input', 'Check Box Group', 'Number Input', 'Radio Button', 'Telephone Input', 'Email Input', 'File Attachment', 'Dropdown', 'Date Input']; +const fileName = 'empty.pdf'; +const dropDownSelector = 'div.drop-down-wrapper select'; +const inputValues = { + textInput: 'adobe', + emailInput: 'test@adobe.com', + numberInput: '123', + dropDown: 'Orange', + FilePath: './test/e2e/upload/empty.pdf', + dataInput: '2022-12-23', +}; + +test.describe('resetButton validation test', () => { + const testURL = '/drafts/tests/x-walk/wizardvalidation'; + test('resetButton validation on wizard panels', async ({ page }) => { + await openPage(page, testURL); + for (let i = 0; i < 4; i += 1) { + // eslint-disable-next-line no-await-in-loop + await page.getByText('Button').click(); + } + await page.getByRole('button', { name: 'Reset' }).click(); + const Count = await page.locator(wizardCount).count(); + expect(Count).toEqual(1); + }); + + test('resetButton validation on repeatable wizard', async ({ page }) => { + await openPage(page, testURL); + const count = await page.locator(wizardPanelCount).count(); + + for (let i = 0; i < count - 1; i += 1) { + // eslint-disable-next-line no-await-in-loop + await page.getByRole('button', { name: 'Next' }).click({ force: true }); + } + await page.getByRole('button', { name: 'Reset' }).click(); + await Promise.all( + [ + expect(page.getByText('Next')).toBeVisible(), + !expect(page.getByText('Back')).not.toBeVisible(), + ], + ); + }); + + test('Check for reset functionality', async ({ page }) => { + const testURL1 = '/drafts/tests/x-walk/resetvalidation'; + await openPage(page, testURL1); + // eslint-disable-next-line no-restricted-syntax + for (const title of titles) { + // eslint-disable-next-line no-await-in-loop,max-len + await fillField(page, title, inputValues); + } + await page.getByRole('button', { name: 'Reset' }).click(); + // eslint-disable-next-line no-restricted-syntax + for (const title of titles) { + // eslint-disable-next-line no-await-in-loop,no-use-before-define + await checkIfReset(page, title); + } + }); + // eslint-disable-next-line no-shadow + const checkIfReset = async (page, ComponentsTitle) => { + switch (ComponentsTitle) { + case 'Text Input': + case 'Email Input': + case 'Telephone Input': + case 'Date Input': + case 'Number Input': + expect(await page.getByLabel(ComponentsTitle).inputValue()).toBe(''); + break; + case 'Check Box Group': + expect(await page.getByRole('checkbox', { name: 'Item 1' }).isChecked()).toBe(false); + break; + case 'Radio Button': + expect(await page.getByRole('radio', { name: 'Item 2' }).isChecked()).toBe(false); + break; + case 'Dropdown': + // eslint-disable-next-line no-case-declarations + await expect(page.locator(dropDownSelector)).toHaveValue(''); + break; + case 'File Attachment': + expect(await page.getByLabel(fileName).isVisible()).toBe(false); + break; + default: + console.error(`${ComponentsTitle} Title is not visible`); + break; + } + }; +}); diff --git a/test/e2e/x-walk/submitButton.spec.js b/test/e2e/x-walk/submitButton.spec.js new file mode 100644 index 0000000..a32688c --- /dev/null +++ b/test/e2e/x-walk/submitButton.spec.js @@ -0,0 +1,34 @@ +import { test, expect } from '../fixtures.js'; +import { fillField, openPage } from '../utils.js'; + +const inputValues = { + textInput: 'adobe', + emailInput: 'test@adobe.com', + numberInput: '123', + dropDown: 'Orange', + FilePath: './test/e2e/upload/empty.pdf', + dataInput: '2022-12-23', +}; +const partialUrl = '/L2NvbnRlbnQvZm9ybXMvYWYveHdhbGstdGVzdC1jb2xsYXRlcmFsL3N1Ym1pdHZhbGlkYXRpb24='; +const titles = ['Text Input', 'Check Box Group', 'Number Input', 'Radio Button', 'Telephone Input', 'Email Input', 'File Attachment', 'Dropdown', 'Date Input']; +test.describe('Form with Submit Button', async () => { + const testURL = '/drafts/tests/x-walk/submitvalidation'; + + test('Clicking the button should submit the form', async ({ page }) => { + await openPage(page, testURL); + await page.evaluate(async () => { + // eslint-disable-next-line no-undef,no-underscore-dangle + myForm._jsonModel.action = 'https://publish-p133911-e1313554.adobeaemcloud.com/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYveHdhbGstdGVzdC1jb2xsYXRlcmFsL3N1Ym1pdHZhbGlkYXRpb24='; + }); + // eslint-disable-next-line no-restricted-syntax + for (const title of titles) { + // eslint-disable-next-line no-await-in-loop,max-len + await fillField(page, title, inputValues); + } + // eslint-disable-next-line max-len + const responsePromise = page.waitForResponse((response) => response.url().includes(partialUrl) && response.status() === 200); + await page.getByRole('button', { name: 'Submit' }).click(); + await expect(page.getByText('Thank you for submitting the form.')).toBeVisible(); + await responsePromise; + }); +}); diff --git a/test/e2e/x-walk/wizard.spec.js b/test/e2e/x-walk/wizard.spec.js new file mode 100644 index 0000000..5394a3d --- /dev/null +++ b/test/e2e/x-walk/wizard.spec.js @@ -0,0 +1,27 @@ +import { test, expect } from '../fixtures.js'; +import { openPage } from '../utils.js'; + +test.describe('wizard tests', () => { + const testURL = '/drafts/tests/x-walk/wizardvalidation'; + test('setFocus test', async ({ page }) => { + await openPage(page, testURL); + await expect(await page.$eval('fieldset[name="item_1"]', (el) => el.classList.contains('current-wizard-step'))).toBeTruthy(); // check first panel is active + await expect(await page.$eval('li[data-index="0"]', (el) => el.classList.contains('wizard-menu-active-item'))).toBeTruthy(); // check first menu item is active + + const textinput = await page.getByLabel('Text Input'); + await textinput.fill('xyz'); + await textinput.press('Tab'); + + await expect(await page.$eval('fieldset[name="item_2"]', (el) => el.classList.contains('current-wizard-step'))).toBeTruthy(); // check second panel is active + await expect(await page.$eval('li[data-index="1"]', (el) => el.classList.contains('wizard-menu-active-item'))).toBeTruthy(); // check second menu item is active + await expect(await page.locator('div').filter({ hasText: /^Number Input$/ })).toHaveAttribute('data-active', 'true'); // check number input is active + + const numberinput = await page.getByLabel('Number Input'); + await numberinput.fill('3'); + await numberinput.press('Tab'); + + await expect(await page.$eval('fieldset[name="item_3"]', (el) => el.classList.contains('current-wizard-step'))).toBeTruthy(); // check third panel is active + await expect(await page.$eval('li[data-index="2"]', (el) => el.classList.contains('wizard-menu-active-item'))).toBeTruthy(); // check third menu item is active + await expect(await page.locator('div').filter({ hasText: /^Email Input$/ })).toHaveAttribute('data-active', 'true'); // check email input is active + }); +}); diff --git a/test/merge-coverage.js b/test/merge-coverage.js new file mode 100644 index 0000000..60483b9 --- /dev/null +++ b/test/merge-coverage.js @@ -0,0 +1,17 @@ +import { execSync } from 'child_process'; + +const runCommand = (command, failOnError = true) => { + try { + execSync(command, { stdio: 'inherit' }); + } catch (error) { + // console.error(error.message); + if (failOnError) process.exit(1); + } +}; + +runCommand('c8 --reporter=json npm run test:unit'); +runCommand('mv coverage/coverage-final.json coverage/coverage-final-unit.json'); +runCommand("c8 --reporter=json npx playwright test --project='chromium'", false); +runCommand('mv coverage/coverage-final.json coverage/coverage-final-e2e.json'); +runCommand('nyc merge coverage .nyc_output/coverage.json'); +runCommand('nyc report --check-coverage --lines 92 --functions 91 --branches 92'); diff --git a/test/unit/customComponent.test.js b/test/unit/customComponent.test.js new file mode 100644 index 0000000..a5c8c3b --- /dev/null +++ b/test/unit/customComponent.test.js @@ -0,0 +1,5 @@ +import path from 'path'; +import { testBasicMarkup } from './testUtils.js'; + +window.hlx = { codeBasePath: '' }; +testBasicMarkup(path.resolve('./test/unit/fixtures/custom-component/range.js'), false, ['range'], '../../test/unit/fixtures'); diff --git a/test/unit/customFunction.test.js b/test/unit/customFunction.test.js new file mode 100644 index 0000000..50e18b7 --- /dev/null +++ b/test/unit/customFunction.test.js @@ -0,0 +1,13 @@ +/* eslint-env mocha */ +import assert from 'assert'; + +import { days } from '../../blocks/form/functions.js'; + +describe('Custom Functions', () => { + it('should load custom functions', () => { + const endDate = new Date(); endDate.setDate(endDate.getDate() + 1); + const startDate = new Date(); + const value = days(endDate, startDate); + assert.equal(value, 1, 'expected 1 day difference'); + }); +}); diff --git a/test/unit/fixtures/blocks/form/components/range/range.js b/test/unit/fixtures/blocks/form/components/range/range.js new file mode 100644 index 0000000..473b316 --- /dev/null +++ b/test/unit/fixtures/blocks/form/components/range/range.js @@ -0,0 +1,49 @@ +function updateBubble(input, element) { + const step = input.step || 1; + const max = input.max || 0; + const min = input.min || 1; + const value = input.value || 1; + const current = Math.ceil((value - min) / step); + const total = Math.ceil((max - min) / step); + const bubble = element.querySelector('.range-bubble'); + // during initial render the width is 0. Hence using a default here. + const bubbleWidth = bubble.getBoundingClientRect().width || 31; + const left = `${(current / total) * 100}% - ${(current / total) * bubbleWidth}px`; + bubble.innerText = `${value}`; + const steps = { + '--total-steps': Math.ceil((max - min) / step), + '--current-steps': Math.ceil((value - min) / step), + }; + const style = Object.entries(steps).map(([varName, varValue]) => `${varName}:${varValue}`).join(';'); + bubble.style.left = `calc(${left})`; + element.setAttribute('style', style); +} + +// eslint-disable-next-line no-unused-vars +export default function decorateRange(fieldDiv, field) { + const input = fieldDiv.querySelector('input'); + // modify the type in case it is not range. + input.type = 'range'; + input.min = input.min || 1; + // create a wrapper div to provide the min/max and current value + const div = document.createElement('div'); + div.className = 'range-widget-wrapper'; + input.after(div); + const hover = document.createElement('span'); + hover.className = 'range-bubble'; + const rangeMinEl = document.createElement('span'); + rangeMinEl.className = 'range-min'; + const rangeMaxEl = document.createElement('span'); + rangeMaxEl.className = 'range-max'; + rangeMinEl.innerText = `${input.min || 1}`; + rangeMaxEl.innerText = `${input.max}`; + div.appendChild(hover); + // move the input element within the wrapper div + div.appendChild(input); + div.appendChild(rangeMinEl); + div.appendChild(rangeMaxEl); + input.addEventListener('input', (e) => { + updateBubble(e.target, div); + }); + updateBubble(input, div); +} diff --git a/test/unit/fixtures/components/button/basic-button.html b/test/unit/fixtures/components/button/basic-button.html new file mode 100644 index 0000000..3415084 --- /dev/null +++ b/test/unit/fixtures/components/button/basic-button.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/test/unit/fixtures/components/button/basic-button.js b/test/unit/fixtures/components/button/basic-button.js new file mode 100644 index 0000000..8c9564f --- /dev/null +++ b/test/unit/fixtures/components/button/basic-button.js @@ -0,0 +1,13 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'button-id', + fieldType: 'button', + name: 'button-name', + label: { + visible: true, + value: 'A sample button', + }, + }, + ], +}; diff --git a/test/unit/fixtures/components/button/disabled.html b/test/unit/fixtures/components/button/disabled.html new file mode 100644 index 0000000..d5d243f --- /dev/null +++ b/test/unit/fixtures/components/button/disabled.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/test/unit/fixtures/components/button/disabled.js b/test/unit/fixtures/components/button/disabled.js new file mode 100644 index 0000000..6fc99a9 --- /dev/null +++ b/test/unit/fixtures/components/button/disabled.js @@ -0,0 +1,14 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'button-id', + fieldType: 'button', + name: 'button-name', + enabled: false, + label: { + visible: true, + value: 'A sample button', + }, + }, + ], +}; diff --git a/test/unit/fixtures/components/button/submit-button.html b/test/unit/fixtures/components/button/submit-button.html new file mode 100644 index 0000000..119fd7c --- /dev/null +++ b/test/unit/fixtures/components/button/submit-button.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/test/unit/fixtures/components/button/submit-button.js b/test/unit/fixtures/components/button/submit-button.js new file mode 100644 index 0000000..32ec6f8 --- /dev/null +++ b/test/unit/fixtures/components/button/submit-button.js @@ -0,0 +1,14 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'button-id', + fieldType: 'button', + name: 'button-name', + buttonType: 'submit', + label: { + visible: true, + value: 'A sample button', + }, + }, + ], +}; diff --git a/test/unit/fixtures/components/checkbox-group/basic.html b/test/unit/fixtures/components/checkbox-group/basic.html new file mode 100644 index 0000000..ac68f6f --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/basic.html @@ -0,0 +1,17 @@ +
+ Check Box Group +
+ + +
+
+ + +
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/checkbox-group/basic.js b/test/unit/fixtures/components/checkbox-group/basic.js new file mode 100644 index 0000000..2100de0 --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/basic.js @@ -0,0 +1,32 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'checkboxgroup-id', + fieldType: 'checkbox-group', + name: 'checkboxgroup-name', + visible: true, + type: 'number[]', + enumNames: [ + 'Item 1', + ' Item 2', + ], + label: { + visible: true, + value: 'Check Box Group', + }, + properties: { + 'afs:layout': { + orientation: 'vertical', + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/aaa/jcr:content/guideContainer/checkboxgroup', + }, + enum: [ + 0, + 1, + ], + ':type': 'forms-components-examples/components/form/checkboxgroup', + }], +}; diff --git a/test/unit/fixtures/components/checkbox-group/default-value.html b/test/unit/fixtures/components/checkbox-group/default-value.html new file mode 100644 index 0000000..f983390 --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/default-value.html @@ -0,0 +1,28 @@ +
+ Check Box Group +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/checkbox-group/default-value.js b/test/unit/fixtures/components/checkbox-group/default-value.js new file mode 100644 index 0000000..dbd7ffb --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/default-value.js @@ -0,0 +1,45 @@ +import assert from 'assert'; + +export const fieldDef = { + items: [{ + id: 'default-checkbox-group-id', + fieldType: 'checkbox-group', + name: 'default-checkbox-group', + visible: true, + type: 'number[]', + enumNames: [ + 'Item 1', + 'Item 2', + 'Item 3', + 'Item 4', + ], + label: { + visible: true, + value: 'Check Box Group', + }, + properties: { + 'afs:layout': { + orientation: 'vertical', + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/aaa/jcr:content/guideContainer/checkboxgroup', + }, + enum: [ + 0, + 1, + 2, + 3, + ], + default: [1, 2], + ':type': 'forms-components-examples/components/form/checkboxgroup', + }], +}; + +export const extraChecks = [(block) => { + assert.equal(block.querySelector('#default-checkbox-group').checked, false); + assert.equal(block.querySelector('#default-checkbox-group-1').checked, true); + assert.equal(block.querySelector('#default-checkbox-group-2').checked, true); + assert.equal(block.querySelector('#default-checkbox-group-3').checked, false); +}]; diff --git a/test/unit/fixtures/components/checkbox-group/multiple-checkbox-group-with-same-name.html b/test/unit/fixtures/components/checkbox-group/multiple-checkbox-group-with-same-name.html new file mode 100644 index 0000000..3685ab7 --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/multiple-checkbox-group-with-same-name.html @@ -0,0 +1,25 @@ +
+
+
+ + +
+
+ + +
+
+
+
+ Check Box Group +
+ + +
+
+ + +
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/checkbox-group/multiple-checkbox-group-with-same-name.js b/test/unit/fixtures/components/checkbox-group/multiple-checkbox-group-with-same-name.js new file mode 100644 index 0000000..7b9846c --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/multiple-checkbox-group-with-same-name.js @@ -0,0 +1,34 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [ + { + id: 'p1', + fieldType: 'panel', + name: 'panel1', + type: 'object', + items: [{ + id: 'cbsamenameid1', + fieldType: 'checkbox-group', + name: 'cbsamename', + enum: [0, 1], + }], + }, + { + id: 'cbsamenameid2', + fieldType: 'checkbox-group', + name: 'cbsamename', + visible: true, + type: 'number[]', + enumNames: [ + 'Item 1', + 'Item 2', + ], + label: { + value: 'Check Box Group', + }, + enum: [ + 0, + 1, + ], + }], +}; diff --git a/test/unit/fixtures/components/checkbox-group/null-enumNames.html b/test/unit/fixtures/components/checkbox-group/null-enumNames.html new file mode 100644 index 0000000..b140a11 --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/null-enumNames.html @@ -0,0 +1,21 @@ +
+
+ + +
+
+ + +
+
+ + +
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/checkbox-group/null-enumNames.js b/test/unit/fixtures/components/checkbox-group/null-enumNames.js new file mode 100644 index 0000000..e6d8a6b --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/null-enumNames.js @@ -0,0 +1,18 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'wen-id', + fieldType: 'checkbox-group', + name: 'wen', + type: 'number[]', + enum: [ + 0, + 1, + 2, + ], + enumNames: [ + 'zero', + null, + ], + }], +}; diff --git a/test/unit/fixtures/components/checkbox-group/space-in-name.html b/test/unit/fixtures/components/checkbox-group/space-in-name.html new file mode 100644 index 0000000..3c31926 --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/space-in-name.html @@ -0,0 +1,16 @@ +
+
+ + +
+
+ + +
+
diff --git a/test/unit/fixtures/components/checkbox-group/space-in-name.js b/test/unit/fixtures/components/checkbox-group/space-in-name.js new file mode 100644 index 0000000..6f9bc91 --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/space-in-name.js @@ -0,0 +1,17 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'wl-id', + fieldType: 'checkbox-group', + name: 'wl checkbox', + type: 'number[]', + enumNames: [ + 'Item 1', + 'Item 2', + ], + enum: [ + 0, + 1, + ], + }], +}; diff --git a/test/unit/fixtures/components/checkbox-group/with-error-message.html b/test/unit/fixtures/components/checkbox-group/with-error-message.html new file mode 100644 index 0000000..800d88c --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/with-error-message.html @@ -0,0 +1,11 @@ +
+ Check Box Group +
+
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/checkbox-group/with-error-message.js b/test/unit/fixtures/components/checkbox-group/with-error-message.js new file mode 100644 index 0000000..4d9a2df --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/with-error-message.js @@ -0,0 +1,46 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [ + { + id: 'checkboxgroup-71b8ae56cd', + fieldType: 'checkbox-group', + name: 'checkboxgroup1705052019500', + visible: true, + type: 'number[]', + required: true, + enabled: true, + constraintMessages: { + required: 'Required error message', + }, + readOnly: false, + enforceEnum: true, + enumNames: [ + 'Item 1', + ' Item 2', + ], + label: { + visible: true, + value: 'Check Box Group', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + orientation: 'horizontal', + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/xzzxzx/jcr:content/guideContainer/checkboxgroup', + }, + enum: [ + 0, + 1, + ], + ':type': 'forms-components-examples/components/form/checkboxgroup', + }, + ], +}; diff --git a/test/unit/fixtures/components/checkbox-group/without-enumNames.html b/test/unit/fixtures/components/checkbox-group/without-enumNames.html new file mode 100644 index 0000000..4bbe26b --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/without-enumNames.html @@ -0,0 +1,16 @@ +
+
+ + +
+
+ + +
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/checkbox-group/without-enumNames.js b/test/unit/fixtures/components/checkbox-group/without-enumNames.js new file mode 100644 index 0000000..82c235c --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/without-enumNames.js @@ -0,0 +1,13 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'wen-id', + fieldType: 'checkbox-group', + name: 'wen', + type: 'number[]', + enum: [ + 0, + 1, + ], + }], +}; diff --git a/test/unit/fixtures/components/checkbox-group/without-label.html b/test/unit/fixtures/components/checkbox-group/without-label.html new file mode 100644 index 0000000..9ac72c1 --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/without-label.html @@ -0,0 +1,16 @@ +
+
+ + +
+
+ + +
+
diff --git a/test/unit/fixtures/components/checkbox-group/without-label.js b/test/unit/fixtures/components/checkbox-group/without-label.js new file mode 100644 index 0000000..ce242f7 --- /dev/null +++ b/test/unit/fixtures/components/checkbox-group/without-label.js @@ -0,0 +1,17 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'wl-id', + fieldType: 'checkbox-group', + name: 'wl', + type: 'number[]', + enumNames: [ + 'Item 1', + 'Item 2', + ], + enum: [ + 0, + 1, + ], + }], +}; diff --git a/test/unit/fixtures/components/checkbox/basic-checkbox.html b/test/unit/fixtures/components/checkbox/basic-checkbox.html new file mode 100644 index 0000000..8ded89b --- /dev/null +++ b/test/unit/fixtures/components/checkbox/basic-checkbox.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/test/unit/fixtures/components/checkbox/basic-checkbox.js b/test/unit/fixtures/components/checkbox/basic-checkbox.js new file mode 100644 index 0000000..02482b3 --- /dev/null +++ b/test/unit/fixtures/components/checkbox/basic-checkbox.js @@ -0,0 +1,36 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'checkbox-7b8a92de4e', + fieldType: 'checkbox', + name: 'subscribe', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + enforceEnum: true, + label: { + visible: true, + value: 'Do you like to subscribe to activity?', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + orientation: 'horizontal', + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/checkbox', + }, + enum: [ + 'on', + ], + ':type': 'forms-components-examples/components/form/checkbox', + }, + ], +}; diff --git a/test/unit/fixtures/components/checkbox/checkbox-advance.html b/test/unit/fixtures/components/checkbox/checkbox-advance.html new file mode 100644 index 0000000..59e5e23 --- /dev/null +++ b/test/unit/fixtures/components/checkbox/checkbox-advance.html @@ -0,0 +1,13 @@ +
+ + +
+

Check, If you wish to subscribe to activities

+
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/checkbox/checkbox-advance.js b/test/unit/fixtures/components/checkbox/checkbox-advance.js new file mode 100644 index 0000000..1f2b6aa --- /dev/null +++ b/test/unit/fixtures/components/checkbox/checkbox-advance.js @@ -0,0 +1,42 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'checkbox-7b8a92de4e', + fieldType: 'checkbox', + name: 'subscribe', + visible: true, + description: '

Check, If you wish to subscribe to activities

', + tooltip: '

subscribe is optional

', + type: 'string', + required: true, + enabled: true, + readOnly: true, + enforceEnum: true, + default: 'yes', + label: { + visible: false, + value: 'Do you like to subscribe to activity?', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + tooltipVisible: false, + orientation: 'horizontal', + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/checkbox', + }, + enum: [ + 'yes', + 'no', + ], + ':type': 'forms-components-examples/components/form/checkbox', + }, + ], +}; diff --git a/test/unit/fixtures/components/checkbox/checkbox-constraints.html b/test/unit/fixtures/components/checkbox/checkbox-constraints.html new file mode 100644 index 0000000..41279a4 --- /dev/null +++ b/test/unit/fixtures/components/checkbox/checkbox-constraints.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/test/unit/fixtures/components/checkbox/checkbox-constraints.js b/test/unit/fixtures/components/checkbox/checkbox-constraints.js new file mode 100644 index 0000000..7945d4f --- /dev/null +++ b/test/unit/fixtures/components/checkbox/checkbox-constraints.js @@ -0,0 +1,38 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'checkbox-7b8a92de4e', + fieldType: 'checkbox', + name: 'subscribe', + visible: true, + type: 'string', + required: true, + enabled: true, + readOnly: false, + enforceEnum: true, + label: { + visible: true, + value: 'Do you like to subscribe to activity?', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + tooltipVisible: false, + orientation: 'horizontal', + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/checkbox', + }, + enum: [ + 'on', + ], + ':type': 'forms-components-examples/components/form/checkbox', + }, + ], +}; diff --git a/test/unit/fixtures/components/checkbox/default-checked.html b/test/unit/fixtures/components/checkbox/default-checked.html new file mode 100644 index 0000000..2827d4e --- /dev/null +++ b/test/unit/fixtures/components/checkbox/default-checked.html @@ -0,0 +1,5 @@ +
+ + +
\ No newline at end of file diff --git a/test/unit/fixtures/components/checkbox/default-checked.js b/test/unit/fixtures/components/checkbox/default-checked.js new file mode 100644 index 0000000..58eeda3 --- /dev/null +++ b/test/unit/fixtures/components/checkbox/default-checked.js @@ -0,0 +1,25 @@ +import assert from 'assert'; + +export const fieldDef = { + items: [{ + id: 'checkbox-7b8a92de4e', + fieldType: 'checkbox', + name: 'subscribe', + type: 'string', + label: { + value: 'Do you like to subscribe to activity?', + }, + enum: [ + 'on', + 'off', + ], + default: 'on', + }, + ], +}; + +export const extraChecks = [ + (html) => { + assert.equal(html.querySelector('#checkbox-7b8a92de4e').checked, true); + }, +]; diff --git a/test/unit/fixtures/components/date-input/date-input-advance.html b/test/unit/fixtures/components/date-input/date-input-advance.html new file mode 100644 index 0000000..25c5200 --- /dev/null +++ b/test/unit/fixtures/components/date-input/date-input-advance.html @@ -0,0 +1,9 @@ +
+ + +
+

Your date of birth should be between 1980 to 2022.

+
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/date-input/date-input-advance.js b/test/unit/fixtures/components/date-input/date-input-advance.js new file mode 100644 index 0000000..47a7a47 --- /dev/null +++ b/test/unit/fixtures/components/date-input/date-input-advance.js @@ -0,0 +1,39 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'datepicker-6dd0c75352', + fieldType: 'date-input', + name: 'dob', + visible: false, + description: '

Your date of birth should be between 1980 to 2022.

', + tooltip: '

Please enter your date of birth.

', + type: 'string', + required: true, + enabled: false, + readOnly: true, + placeholder: '2000-02-13', + label: { + visible: false, + value: 'Date Of Birth', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + tooltipVisible: false, + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/datepicker', + }, + format: 'date', + minimum: '1980-01-30', + maximum: '2022-01-30', + ':type': 'forms-components-examples/components/form/datepicker', + }, + ], +}; diff --git a/test/unit/fixtures/components/date-input/date-input-contraints.html b/test/unit/fixtures/components/date-input/date-input-contraints.html new file mode 100644 index 0000000..aad0af0 --- /dev/null +++ b/test/unit/fixtures/components/date-input/date-input-contraints.html @@ -0,0 +1,9 @@ +
+ + +
+

Your date of birth should be between 1980 to 2022.

+
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/date-input/date-input-contraints.js b/test/unit/fixtures/components/date-input/date-input-contraints.js new file mode 100644 index 0000000..e4b7708 --- /dev/null +++ b/test/unit/fixtures/components/date-input/date-input-contraints.js @@ -0,0 +1,39 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'datepicker-6dd0c75352', + fieldType: 'date-input', + name: 'dob', + visible: true, + description: '

Your date of birth should be between 1980 to 2022.

', + tooltip: '

Please enter your date of birth.

', + type: 'string', + required: true, + enabled: true, + readOnly: false, + placeholder: '2000-02-13', + label: { + visible: true, + value: 'Date Of Birth', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + tooltipVisible: false, + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/datepicker', + }, + format: 'date', + minimum: '1980-01-30', + maximum: '2022-01-30', + ':type': 'forms-components-examples/components/form/datepicker', + }, + ], +}; diff --git a/test/unit/fixtures/components/date-input/date-input.js b/test/unit/fixtures/components/date-input/date-input.js new file mode 100644 index 0000000..8f14359 --- /dev/null +++ b/test/unit/fixtures/components/date-input/date-input.js @@ -0,0 +1,35 @@ +import { formatDate } from '../../../../../blocks/form/rules/model/afb-formatters.min.js'; + +export const fieldDef = { + items: [{ + id: 'datepicker-6dd0c75352', + fieldType: 'date-input', + name: 'dob', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + placeholder: '2000-02-13', + label: { + visible: true, + value: 'Date Of Birth', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + format: 'date', + }, + ], +}; + +const { locale } = new Intl.DateTimeFormat().resolvedOptions(); +const today = formatDate(new Date(), locale, 'short'); + +export const markUp = ` +
+ + +
To enter today's date use ${today}
+
`.replace(/\n/g, ''); diff --git a/test/unit/fixtures/components/dropdown/dropdown-contraints.html b/test/unit/fixtures/components/dropdown/dropdown-contraints.html new file mode 100644 index 0000000..39ccfac --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown-contraints.html @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/test/unit/fixtures/components/dropdown/dropdown-contraints.js b/test/unit/fixtures/components/dropdown/dropdown-contraints.js new file mode 100644 index 0000000..8cea12a --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown-contraints.js @@ -0,0 +1,45 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'dropdown-c6f396a9b8', + fieldType: 'drop-down', + name: 'country', + visible: true, + description: '

Please select the country

', + tooltip: '

Please select country

', + type: 'string', + required: true, + enabled: true, + readOnly: false, + enforceEnum: true, + enumNames: [ + 'India', + 'United States', + ], + placeholder: 'Please select', + label: { + visible: true, + value: 'Country', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + tooltipVisible: false, + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/dropdown', + }, + enum: [ + 'IN', + 'US', + ], + ':type': 'forms-components-examples/components/form/dropdown', + }, + ], +}; diff --git a/test/unit/fixtures/components/dropdown/dropdown-default.html b/test/unit/fixtures/components/dropdown/dropdown-default.html new file mode 100644 index 0000000..089c1ae --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown-default.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/test/unit/fixtures/components/dropdown/dropdown-default.js b/test/unit/fixtures/components/dropdown/dropdown-default.js new file mode 100644 index 0000000..5ee589f --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown-default.js @@ -0,0 +1,36 @@ +import assert from 'assert'; + +export const fieldDef = { + items: [{ + id: 'dropdown-c6f396a9b8', + fieldType: 'drop-down', + name: 'country', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + enforceEnum: true, + enumNames: [ + 'India', + 'United States', + ], + placeholder: 'Please select', + default: 'IN', + label: { + visible: true, + value: 'Country', + }, + enum: [ + 'IN', + 'US', + ], + ':type': 'forms-components-examples/components/form/dropdown', + }, + ], +}; + +export const extraChecks = [ + (html) => { + assert.equal(html.querySelector('#dropdown-c6f396a9b8').value, 'IN'); + }, +]; diff --git a/test/unit/fixtures/components/dropdown/dropdown-hidden.html b/test/unit/fixtures/components/dropdown/dropdown-hidden.html new file mode 100644 index 0000000..f80865a --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown-hidden.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/test/unit/fixtures/components/dropdown/dropdown-hidden.js b/test/unit/fixtures/components/dropdown/dropdown-hidden.js new file mode 100644 index 0000000..1a413d8 --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown-hidden.js @@ -0,0 +1,27 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'dropdown-c6f396a9b8', + fieldType: 'drop-down', + name: 'country', + visible: false, + type: 'string', + enabled: true, + readOnly: false, + enforceEnum: true, + enumNames: [ + 'India', + 'United States', + ], + label: { + visible: true, + value: 'Country', + }, + enum: [ + 'IN', + 'US', + ], + ':type': 'forms-components-examples/components/form/dropdown', + }, + ], +}; diff --git a/test/unit/fixtures/components/dropdown/dropdown-multiple-default.html b/test/unit/fixtures/components/dropdown/dropdown-multiple-default.html new file mode 100644 index 0000000..40bd675 --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown-multiple-default.html @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/test/unit/fixtures/components/dropdown/dropdown-multiple-default.js b/test/unit/fixtures/components/dropdown/dropdown-multiple-default.js new file mode 100644 index 0000000..d6975bd --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown-multiple-default.js @@ -0,0 +1,43 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'dropdown-c6f396a9b8', + fieldType: 'drop-down', + name: 'country', + visible: true, + description: '

Please select the country

', + tooltip: 'Please select country', + type: 'string[]', + required: true, + enabled: true, + readOnly: true, + enforceEnum: true, + enumNames: [ + 'India', + 'United States', + ], + placeholder: 'Please select', + default: ['IN', 'US'], + label: { + visible: false, + value: 'Country', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + enum: [ + 'IN', + 'US', + ], + ':type': 'forms-components-examples/components/form/dropdown', + }, + ], +}; + +// export const extraChecks = [ +// (html) => { +// assert.equal(html.querySelector('#dropdown-c6f396a9b8').value, 'IN'); +// }, +// ]; diff --git a/test/unit/fixtures/components/dropdown/dropdown-multiple.html b/test/unit/fixtures/components/dropdown/dropdown-multiple.html new file mode 100644 index 0000000..9e779ae --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown-multiple.html @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/test/unit/fixtures/components/dropdown/dropdown-multiple.js b/test/unit/fixtures/components/dropdown/dropdown-multiple.js new file mode 100644 index 0000000..c248060 --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown-multiple.js @@ -0,0 +1,36 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'dropdown-c6f396a9b8', + fieldType: 'drop-down', + name: 'country', + visible: true, + description: '

Please select the country

', + tooltip: '

Please select country

', + type: 'string[]', + required: true, + enabled: true, + readOnly: true, + enforceEnum: true, + enumNames: [ + 'India', + 'United States', + ], + placeholder: 'Please select', + label: { + visible: false, + value: 'Country', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + enum: [ + 'IN', + 'US', + ], + ':type': 'forms-components-examples/components/form/dropdown', + }, + ], +}; diff --git a/test/unit/fixtures/components/dropdown/dropdown-placeholder.html b/test/unit/fixtures/components/dropdown/dropdown-placeholder.html new file mode 100644 index 0000000..1a91f70 --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown-placeholder.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/test/unit/fixtures/components/dropdown/dropdown-placeholder.js b/test/unit/fixtures/components/dropdown/dropdown-placeholder.js new file mode 100644 index 0000000..c3f95d5 --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown-placeholder.js @@ -0,0 +1,35 @@ +import assert from 'assert'; + +export const fieldDef = { + items: [{ + id: 'dropdown-c6f396a9b8', + fieldType: 'drop-down', + name: 'country', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + enforceEnum: true, + enumNames: [ + 'India', + 'United States', + ], + placeholder: 'Please select', + label: { + visible: true, + value: 'Country', + }, + enum: [ + 'IN', + 'US', + ], + ':type': 'forms-components-examples/components/form/dropdown', + }, + ], +}; + +export const extraChecks = [ + (html) => { + assert.equal(html.querySelector('#dropdown-c6f396a9b8').value, ''); + }, +]; diff --git a/test/unit/fixtures/components/dropdown/dropdown.html b/test/unit/fixtures/components/dropdown/dropdown.html new file mode 100644 index 0000000..4cc7889 --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/test/unit/fixtures/components/dropdown/dropdown.js b/test/unit/fixtures/components/dropdown/dropdown.js new file mode 100644 index 0000000..5bffe16 --- /dev/null +++ b/test/unit/fixtures/components/dropdown/dropdown.js @@ -0,0 +1,27 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'dropdown-c6f396a9b8', + fieldType: 'drop-down', + name: 'country', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + enforceEnum: true, + enumNames: [ + 'India', + 'United States', + ], + label: { + visible: true, + value: 'Country', + }, + enum: [ + 'IN', + 'US', + ], + ':type': 'forms-components-examples/components/form/dropdown', + }, + ], +}; diff --git a/test/unit/fixtures/components/file-attachment/file-attachment-advance.html b/test/unit/fixtures/components/file-attachment/file-attachment-advance.html new file mode 100644 index 0000000..d48931c --- /dev/null +++ b/test/unit/fixtures/components/file-attachment/file-attachment-advance.html @@ -0,0 +1,13 @@ +
+ +
+
+
Drag and Drop To Upload
+ + +
+
+

You can upload any PDF file which is less than 1 MB

+
+
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/file-attachment/file-attachment-advance.js b/test/unit/fixtures/components/file-attachment/file-attachment-advance.js new file mode 100644 index 0000000..b7e4628 --- /dev/null +++ b/test/unit/fixtures/components/file-attachment/file-attachment-advance.js @@ -0,0 +1,44 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'fileinput-fc4d042b5e', + fieldType: 'file-input', + name: 'idProof', + visible: false, + description: '

You can upload any PDF file which is less than 1 MB

', + tooltip: '

Please upload any PDF file.

', + type: 'file[]', + required: true, + enabled: true, + minItems: 2, + maxItems: 4, + readOnly: false, + maxFileSize: '1MB', + accept: [ + 'audio/*', + ' video/*', + ' image/*', + ' application/pdf', + ], + label: { + visible: false, + value: 'Identity Proof', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + tooltipVisible: false, + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/fileinput', + }, + ':type': 'forms-components-examples/components/form/fileinput', + }, + ], +}; diff --git a/test/unit/fixtures/components/file-attachment/file-attachment-constraints.html b/test/unit/fixtures/components/file-attachment/file-attachment-constraints.html new file mode 100644 index 0000000..9928e36 --- /dev/null +++ b/test/unit/fixtures/components/file-attachment/file-attachment-constraints.html @@ -0,0 +1,13 @@ +
+ +
+
+
Drag and Drop To Upload
+ + +
+
+

You can upload any PDF file which is less than 1 MB

+
+
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/file-attachment/file-attachment-constraints.js b/test/unit/fixtures/components/file-attachment/file-attachment-constraints.js new file mode 100644 index 0000000..5bebc20 --- /dev/null +++ b/test/unit/fixtures/components/file-attachment/file-attachment-constraints.js @@ -0,0 +1,42 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'fileinput-fc4d042b5e', + fieldType: 'file-input', + name: 'idProof', + visible: true, + description: '

You can upload any PDF file which is less than 1 MB

', + tooltip: '

Please upload any PDF file.

', + type: 'file', + required: true, + enabled: true, + readOnly: false, + maxFileSize: '1MB', + accept: [ + 'audio/*', + ' video/*', + ' image/*', + ' application/pdf', + ], + label: { + visible: true, + value: 'Identity Proof', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + tooltipVisible: false, + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/fileinput', + }, + ':type': 'forms-components-examples/components/form/fileinput', + }, + ], +}; diff --git a/test/unit/fixtures/components/file-attachment/file-attachment.html b/test/unit/fixtures/components/file-attachment/file-attachment.html new file mode 100644 index 0000000..81ff36b --- /dev/null +++ b/test/unit/fixtures/components/file-attachment/file-attachment.html @@ -0,0 +1,11 @@ +
+
+
+
Drag and Drop To Upload
+ + +
+
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/file-attachment/file-attachment.js b/test/unit/fixtures/components/file-attachment/file-attachment.js new file mode 100644 index 0000000..375eaf0 --- /dev/null +++ b/test/unit/fixtures/components/file-attachment/file-attachment.js @@ -0,0 +1,29 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'fileinput-fc4d042b5e', + fieldType: 'file-input', + name: 'idProof', + visible: true, + type: 'file', + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'Identity Proof', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/fileinput', + }, + ':type': 'forms-components-examples/components/form/fileinput', + }, + ], +}; diff --git a/test/unit/fixtures/components/image/image.html b/test/unit/fixtures/components/image/image.html new file mode 100644 index 0000000..90333b5 --- /dev/null +++ b/test/unit/fixtures/components/image/image.html @@ -0,0 +1,7 @@ +
+ + + + alt + +
\ No newline at end of file diff --git a/test/unit/fixtures/components/image/image.js b/test/unit/fixtures/components/image/image.js new file mode 100644 index 0000000..841f606 --- /dev/null +++ b/test/unit/fixtures/components/image/image.js @@ -0,0 +1,13 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'image-id', + fieldType: 'image', + name: 'image-name', + value: '/content/forms/af/grid/jcr:content/guideContainer/image.img.png/1709790619048.png', + source: '/content/dam/images/aaa.png', + visible: true, + altText: 'alt', + }, + ], +}; diff --git a/test/unit/fixtures/components/imagewithrepopath/imagewithrepopath.html b/test/unit/fixtures/components/imagewithrepopath/imagewithrepopath.html new file mode 100644 index 0000000..f11b1c6 --- /dev/null +++ b/test/unit/fixtures/components/imagewithrepopath/imagewithrepopath.html @@ -0,0 +1,9 @@ +
+ + + + alt + +
\ No newline at end of file diff --git a/test/unit/fixtures/components/imagewithrepopath/imagewithrepopath.js b/test/unit/fixtures/components/imagewithrepopath/imagewithrepopath.js new file mode 100644 index 0000000..9ff64a7 --- /dev/null +++ b/test/unit/fixtures/components/imagewithrepopath/imagewithrepopath.js @@ -0,0 +1,15 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'image-id', + fieldType: 'image', + name: 'image-name', + value: '/content/forms/af/grid/jcr:content/guideContainer/image.img.png/1709790619048.png', + properties: { + 'fd:repoPath': '/content/forms/af/grid/jcr:content/guideContainer/image.img.png/1709790619048.png', + }, + visible: true, + altText: 'alt', + }, + ], +}; diff --git a/test/unit/fixtures/components/number-input/number-input-advance.html b/test/unit/fixtures/components/number-input/number-input-advance.html new file mode 100644 index 0000000..3062df1 --- /dev/null +++ b/test/unit/fixtures/components/number-input/number-input-advance.html @@ -0,0 +1,10 @@ +
+ + +
+

Hint - Zipcode should be a 5 digit number.

+
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/number-input/number-input-advance.js b/test/unit/fixtures/components/number-input/number-input-advance.js new file mode 100644 index 0000000..71bce69 --- /dev/null +++ b/test/unit/fixtures/components/number-input/number-input-advance.js @@ -0,0 +1,41 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'numberinput-40db827550', + fieldType: 'number-input', + name: 'zipcode', + visible: false, + description: '

Hint - Zipcode should be a 5 digit number.

', + tooltip: '

Please enter 5 digit zipcode number.

', + type: 'integer', + required: true, + enabled: false, + readOnly: true, + step: 2, + maximum: 99999, + minimum: 11111, + exclusiveMinimum: 11111, + exclusiveMaximum: 99999, + placeholder: '50065', + label: { + visible: false, + value: 'Zip Code', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + tooltipVisible: false, + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/numberinput', + }, + ':type': 'forms-components-examples/components/form/numberinput', + }, + ], +}; diff --git a/test/unit/fixtures/components/number-input/number-input-contraints.html b/test/unit/fixtures/components/number-input/number-input-contraints.html new file mode 100644 index 0000000..4ca2a8f --- /dev/null +++ b/test/unit/fixtures/components/number-input/number-input-contraints.html @@ -0,0 +1,9 @@ +
+ + +
+

Hint - Zipcode should be a 5 digit number.

+
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/number-input/number-input-contraints.js b/test/unit/fixtures/components/number-input/number-input-contraints.js new file mode 100644 index 0000000..197617d --- /dev/null +++ b/test/unit/fixtures/components/number-input/number-input-contraints.js @@ -0,0 +1,40 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'numberinput-40db827550', + fieldType: 'number-input', + name: 'zipcode', + visible: true, + description: '

Hint - Zipcode should be a 5 digit number.

', + tooltip: '

Please enter 5 digit zipcode number.

', + type: 'integer', + required: true, + enabled: true, + readOnly: false, + maximum: 99999, + minimum: 11111, + exclusiveMinimum: 11111, + exclusiveMaximum: 99999, + placeholder: '50065', + label: { + visible: true, + value: 'Zip Code', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + tooltipVisible: false, + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/numberinput', + }, + ':type': 'forms-components-examples/components/form/numberinput', + }, + ], +}; diff --git a/test/unit/fixtures/components/number-input/number-input.html b/test/unit/fixtures/components/number-input/number-input.html new file mode 100644 index 0000000..793287c --- /dev/null +++ b/test/unit/fixtures/components/number-input/number-input.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/test/unit/fixtures/components/number-input/number-input.js b/test/unit/fixtures/components/number-input/number-input.js new file mode 100644 index 0000000..6be16ea --- /dev/null +++ b/test/unit/fixtures/components/number-input/number-input.js @@ -0,0 +1,35 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'numberinput-40db827550', + fieldType: 'number-input', + name: 'zipcode', + visible: true, + type: 'integer', + required: false, + enabled: true, + readOnly: false, + placeholder: '50065', + default: 0, + label: { + visible: true, + value: 'Zip Code', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + tooltipVisible: false, + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/numberinput', + }, + ':type': 'forms-components-examples/components/form/numberinput', + }, + ], +}; diff --git a/test/unit/fixtures/components/panel/basic-panel.html b/test/unit/fixtures/components/panel/basic-panel.html new file mode 100644 index 0000000..3196535 --- /dev/null +++ b/test/unit/fixtures/components/panel/basic-panel.html @@ -0,0 +1,78 @@ +
+ Insured Property Information (if different from above) +
+
+ +
+
+
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/panel/basic-panel.js b/test/unit/fixtures/components/panel/basic-panel.js new file mode 100644 index 0000000..2fe6abe --- /dev/null +++ b/test/unit/fixtures/components/panel/basic-panel.js @@ -0,0 +1,272 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [ + { + id: 'panelcontainer-eff9c5f6ff', + fieldType: 'panel', + name: 'panel_2', + label: { + value: 'Insured Property Information (if different from above)', + }, + properties: { + 'fd:dor': { + dorExclusion: false, + dorExcludeTitle: false, + dorExcludeDescription: false, + }, + 'fd:path': '/content/forms/af/Claims_Form1701932419220/jcr:content/guideContainer/tabsOnTop1701932611697/panel_2', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/panelcontainer', + items: [ + { + id: 'textinput-13e16ec0e4', + fieldType: 'text-input', + name: 'Insured_Property_Address1701932419752', + type: 'string', + label: { + value: 'Insured Property Address (Optional)', + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/Claims_Form1701932419220/jcr:content/guideContainer/tabsOnTop1701932611697/panel_2/Insured_Property_Address1701932419752', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-e677433d15', + fieldType: 'text-input', + name: 'Insured_Property_City1701932419752', + type: 'string', + label: { + value: 'City (Optional)', + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/Claims_Form1701932419220/jcr:content/guideContainer/tabsOnTop1701932611697/panel_2/Insured_Property_City1701932419752', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'dropdown-86dd596192', + fieldType: 'drop-down', + name: 'Insured_Property_State1701932419753', + type: 'string[]', + enforceEnum: true, + enumNames: [ + 'Select a State', + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'District of Columbia', + 'Florida', + 'Georgia', + 'Guam', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Puerto Rico', + 'Rhode Island', + 'Saipan', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virgin Islands', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', + ], + label: { + value: 'Your State', + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/Claims_Form1701932419220/jcr:content/guideContainer/tabsOnTop1701932611697/panel_2/Insured_Property_State1701932419753', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + enum: [ + '', + 'AL', + 'AK', + 'AZ', + 'AR', + 'CA', + 'CO', + 'CT', + 'DE', + 'DC', + 'FL', + 'GA', + 'GU', + 'HI', + 'ID', + 'IL', + 'IN', + 'IA', + 'KS', + 'KY', + 'LA', + 'ME', + 'MD', + 'MA', + 'MI', + 'MN', + 'MS', + 'MO', + 'MT', + 'NE', + 'NV', + 'NH', + 'NJ', + 'NM', + 'NY', + 'NC', + 'ND', + 'OH', + 'OK', + 'OR', + 'PA', + 'PR', + 'RI', + 'MP', + 'SC', + 'SD', + 'TN', + 'TX', + 'UT', + 'VT', + 'VI', + 'VA', + 'WA', + 'WV', + 'WI', + 'WY', + ], + ':type': 'formsninja/components/adaptiveForm/dropdown', + }, + { + id: 'numberinput-ad04048bdf', + fieldType: 'number-input', + name: 'Insured_Property_Zip_Postal_Code1701932419753', + type: 'number', + label: { + value: 'Zip/Postal Code (Optional)', + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/Claims_Form1701932419220/jcr:content/guideContainer/tabsOnTop1701932611697/panel_2/Insured_Property_Zip_Postal_Code1701932419753', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/numberinput', + }, + { + id: 'textinput-7689e7b5ac', + fieldType: 'text-input', + name: 'Insured_Property_Province_Region1701932419757', + type: 'string', + label: { + value: 'Province/Region (Optional)', + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/Claims_Form1701932419220/jcr:content/guideContainer/tabsOnTop1701932611697/panel_2/Insured_Property_Province_Region1701932419757', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-8b986fa9bb', + fieldType: 'text-input', + name: 'Insured_Property_Country1701932419758', + type: 'string', + label: { + value: 'Country (Optional)', + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/Claims_Form1701932419220/jcr:content/guideContainer/tabsOnTop1701932611697/panel_2/Insured_Property_Country1701932419758', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + ], + }, + ], +}; diff --git a/test/unit/fixtures/components/panel/doc-based-repeatable-min.html b/test/unit/fixtures/components/panel/doc-based-repeatable-min.html new file mode 100644 index 0000000..1a31a35 --- /dev/null +++ b/test/unit/fixtures/components/panel/doc-based-repeatable-min.html @@ -0,0 +1,23 @@ +
+
+ Traveler Info +
+
+
+ + +
diff --git a/test/unit/fixtures/components/panel/doc-based-repeatable-min.js b/test/unit/fixtures/components/panel/doc-based-repeatable-min.js new file mode 100644 index 0000000..c143d3f --- /dev/null +++ b/test/unit/fixtures/components/panel/doc-based-repeatable-min.js @@ -0,0 +1,14 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + total: 12, + offset: 0, + limit: 12, + data: [{ + Name: 'doc-traveler', Type: 'fieldset', Description: '', Placeholder: '', Label: 'Traveler Info', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '2', Max: '3', Value: '', Options: '', OptionNames: '', Fieldset: '', Repeatable: 'true', + }, { + Name: 'doc-name', Type: 'text', Description: '', Placeholder: '', Label: 'Name', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: 'doc-traveler', Repeatable: '', + }, { + Name: 'doc-age', Type: 'number', Description: '', Placeholder: '', Label: 'Age', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: 'doc-traveler', Repeatable: '', + }], + ':type': 'sheet', +}; diff --git a/test/unit/fixtures/components/panel/repeatable-panel-minItems.html b/test/unit/fixtures/components/panel/repeatable-panel-minItems.html new file mode 100644 index 0000000..38f6c46 --- /dev/null +++ b/test/unit/fixtures/components/panel/repeatable-panel-minItems.html @@ -0,0 +1,33 @@ +
+
+
+ Panel Label +
+ + +
+
+ + +
+
+
+ Panel Label +
+ + +
+
+ + +
+
+ +
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/panel/repeatable-panel-minItems.js b/test/unit/fixtures/components/panel/repeatable-panel-minItems.js new file mode 100644 index 0000000..cfc7753 --- /dev/null +++ b/test/unit/fixtures/components/panel/repeatable-panel-minItems.js @@ -0,0 +1,103 @@ +export const fieldDef = { + items: [ + { + fieldType: 'panel', + id: 'panel1', + name: 'panel1', + label: { + value: 'Panel Label', + }, + repeatable: true, + minOccur: 2, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + items: [ + { + id: 'textinput1', + fieldType: 'text-input', + name: 'textinput1', + type: 'string', + label: { + value: 'Insured Property Address (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + id: 'textinput2', + fieldType: 'text-input', + name: 'TextInput2', + type: 'string', + label: { + value: 'City (Optional)', + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/Claims_Form1701932419220/jcr:content/guideContainer/tabsOnTop1701932611697/panel_2/Insured_Property_City1701932419752', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + ], + }, + ], +}; + +export const expectedDiffs = [ + { + node: 'FORM/FIELDSET', + attribute: 'data-id', + }, + { + node: 'FORM/FIELDSET', + attribute: 'id', + }, + { + node: 'FORM/FIELDSET/DIV/FIELDSET[@id=\'uniqueId2\']', + attribute: 'data-id', + }, + { + node: 'FORM/FIELDSET/DIV/FIELDSET[@id=\'uniqueId2\']', + attribute: 'id', + }, + { + node: 'FORM/FIELDSET/DIV/FIELDSET[@id=\'uniqueId2\']/LEGEND', + attribute: 'for', + }, + { + node: 'FORM/FIELDSET/DIV/FIELDSET[@id=\'uniqueId2\']/DIV[1]', + attribute: 'data-id', + }, + { + node: 'FORM/FIELDSET/DIV/FIELDSET[@id=\'uniqueId2\']/DIV[1]/LABEL', + attribute: 'for', + }, + { + node: 'FORM/FIELDSET/DIV/FIELDSET[@id=\'uniqueId2\']/DIV[1]/INPUT', + attribute: 'id', + }, + { + node: 'FORM/FIELDSET/DIV/FIELDSET[@id=\'uniqueId2\']/DIV[2]', + attribute: 'data-id', + }, + { + node: 'FORM/FIELDSET/DIV/FIELDSET[@id=\'uniqueId2\']/DIV[2]/LABEL', + attribute: 'for', + }, + { + node: 'FORM/FIELDSET/DIV/FIELDSET[@id=\'uniqueId2\']/DIV[2]/INPUT', + attribute: 'id', + }, +]; diff --git a/test/unit/fixtures/components/panel/repeatable-panel-siblings.html b/test/unit/fixtures/components/panel/repeatable-panel-siblings.html new file mode 100644 index 0000000..0abf95d --- /dev/null +++ b/test/unit/fixtures/components/panel/repeatable-panel-siblings.html @@ -0,0 +1,66 @@ +
+
+
+ Panel Label +
+ + +
+
+ + +
+
+
+ Panel Label +
+ + +
+
+ + +
+
+ +
+
+
+
+
+ Panel Label +
+ + +
+
+ + +
+
+
+ Panel Label +
+ + +
+
+ + +
+
+ +
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/panel/repeatable-panel-siblings.js b/test/unit/fixtures/components/panel/repeatable-panel-siblings.js new file mode 100644 index 0000000..44d7101 --- /dev/null +++ b/test/unit/fixtures/components/panel/repeatable-panel-siblings.js @@ -0,0 +1,110 @@ +export const fieldDef = { + items: [ + { + fieldType: 'panel', + id: 'panel1', + name: 'panel1', + label: { + value: 'Panel Label', + }, + repeatable: true, + minOccur: 2, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + items: [ + { + id: 'textinput1', + fieldType: 'text-input', + name: 'textinput1', + type: 'string', + label: { + value: 'Insured Property Address (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + id: 'textinput2', + fieldType: 'text-input', + name: 'TextInput2', + type: 'string', + label: { + value: 'City (Optional)', + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/Claims_Form1701932419220/jcr:content/guideContainer/tabsOnTop1701932611697/panel_2/Insured_Property_City1701932419752', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + ], + }, + { + fieldType: 'panel', + id: 'panel2', + name: 'panel2', + label: { + value: 'Panel Label', + }, + repeatable: true, + minOccur: 2, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + items: [ + { + id: 'textinput3', + fieldType: 'text-input', + name: 'textinput3', + type: 'string', + label: { + value: 'Insured Property Address (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + id: 'textinput4', + fieldType: 'text-input', + name: 'TextInput4', + type: 'string', + label: { + value: 'City (Optional)', + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/Claims_Form1701932419220/jcr:content/guideContainer/tabsOnTop1701932611697/panel_2/Insured_Property_City1701932419752', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + ], + }, + ], +}; + +export const expectedDiffs = 22; diff --git a/test/unit/fixtures/components/plainText/plain-text.html b/test/unit/fixtures/components/plainText/plain-text.html new file mode 100644 index 0000000..8388658 --- /dev/null +++ b/test/unit/fixtures/components/plainText/plain-text.html @@ -0,0 +1,3 @@ +
+

<p>not a rich text</p>

+
\ No newline at end of file diff --git a/test/unit/fixtures/components/plainText/plain-text.js b/test/unit/fixtures/components/plainText/plain-text.js new file mode 100644 index 0000000..560ff6f --- /dev/null +++ b/test/unit/fixtures/components/plainText/plain-text.js @@ -0,0 +1,8 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'plain-text-id', + fieldType: 'plain-text', + value: '

not a rich text

', + }], +}; diff --git a/test/unit/fixtures/components/plainText/rich-text-script.html b/test/unit/fixtures/components/plainText/rich-text-script.html new file mode 100644 index 0000000..9590333 --- /dev/null +++ b/test/unit/fixtures/components/plainText/rich-text-script.html @@ -0,0 +1,3 @@ +
+

The script should be removed.alert("hello");

+
\ No newline at end of file diff --git a/test/unit/fixtures/components/plainText/rich-text-script.js b/test/unit/fixtures/components/plainText/rich-text-script.js new file mode 100644 index 0000000..a6ca0ea --- /dev/null +++ b/test/unit/fixtures/components/plainText/rich-text-script.js @@ -0,0 +1,9 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'plain-text-id', + fieldType: 'plain-text', + richText: true, + value: 'The script should be removed. ', + }], +}; diff --git a/test/unit/fixtures/components/plainText/rich-text.html b/test/unit/fixtures/components/plainText/rich-text.html new file mode 100644 index 0000000..95ec7fd --- /dev/null +++ b/test/unit/fixtures/components/plainText/rich-text.html @@ -0,0 +1,3 @@ +
+

is bold text

+
\ No newline at end of file diff --git a/test/unit/fixtures/components/plainText/rich-text.js b/test/unit/fixtures/components/plainText/rich-text.js new file mode 100644 index 0000000..12fb0d5 --- /dev/null +++ b/test/unit/fixtures/components/plainText/rich-text.js @@ -0,0 +1,9 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'plain-text-id', + fieldType: 'plain-text', + richText: true, + value: 'is bold text', + }], +}; diff --git a/test/unit/fixtures/components/radio-group/multiple-radio-group-with-same-name.html b/test/unit/fixtures/components/radio-group/multiple-radio-group-with-same-name.html new file mode 100644 index 0000000..3bc7e57 --- /dev/null +++ b/test/unit/fixtures/components/radio-group/multiple-radio-group-with-same-name.html @@ -0,0 +1,25 @@ +
+
+
+ + +
+
+ + +
+
+
+
+ Check Box Group +
+ + +
+
+ + +
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/radio-group/multiple-radio-group-with-same-name.js b/test/unit/fixtures/components/radio-group/multiple-radio-group-with-same-name.js new file mode 100644 index 0000000..478be86 --- /dev/null +++ b/test/unit/fixtures/components/radio-group/multiple-radio-group-with-same-name.js @@ -0,0 +1,34 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [ + { + id: 'p1', + fieldType: 'panel', + name: 'panel1', + type: 'object', + items: [{ + id: 'rb1id', + fieldType: 'radio-group', + name: 'rb1', + enum: [0, 1], + }], + }, + { + id: 'rb1id2', + fieldType: 'radio-group', + name: 'rb1', + visible: true, + type: 'number[]', + enumNames: [ + 'Item 1', + 'Item 2', + ], + label: { + value: 'Check Box Group', + }, + enum: [ + 0, + 1, + ], + }], +}; diff --git a/test/unit/fixtures/components/radio-group/radio-group.html b/test/unit/fixtures/components/radio-group/radio-group.html new file mode 100644 index 0000000..3a575a1 --- /dev/null +++ b/test/unit/fixtures/components/radio-group/radio-group.html @@ -0,0 +1,16 @@ +
+
+
+
+
+
+
+
+ Radio Button +
+
+ \ No newline at end of file diff --git a/test/unit/fixtures/components/radio-group/with-error-message.js b/test/unit/fixtures/components/radio-group/with-error-message.js new file mode 100644 index 0000000..6c38639 --- /dev/null +++ b/test/unit/fixtures/components/radio-group/with-error-message.js @@ -0,0 +1,46 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [ + { + id: 'radiobutton-abace222cd', + fieldType: 'radio-group', + name: 'radiobutton1705050944702', + visible: true, + type: 'string', + required: true, + enabled: true, + constraintMessages: { + required: 'required message', + }, + readOnly: false, + enforceEnum: true, + enumNames: [ + 'Item 1', + 'Item 2', + ], + label: { + visible: true, + value: 'Radio Button', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + orientation: 'horizontal', + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/xzzxzx/jcr:content/guideContainer/radiobutton', + }, + enum: [ + '0', + '1', + ], + ':type': 'forms-components-examples/components/form/radiobutton', + }, + ], +}; diff --git a/test/unit/fixtures/components/text-area/text-area.html b/test/unit/fixtures/components/text-area/text-area.html new file mode 100644 index 0000000..5dc7cf2 --- /dev/null +++ b/test/unit/fixtures/components/text-area/text-area.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/test/unit/fixtures/components/text-area/text-area.js b/test/unit/fixtures/components/text-area/text-area.js new file mode 100644 index 0000000..240754f --- /dev/null +++ b/test/unit/fixtures/components/text-area/text-area.js @@ -0,0 +1,19 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'textinput1-4e4f61cad9', + fieldType: 'multiline-input', + name: 'address', + visible: true, + type: 'string', + required: true, + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'Address', + }, + ':type': 'forms-components-examples/components/form/textinput', + }, + ], +}; diff --git a/test/unit/fixtures/components/text-input/disabled.html b/test/unit/fixtures/components/text-input/disabled.html new file mode 100644 index 0000000..94fc6f9 --- /dev/null +++ b/test/unit/fixtures/components/text-input/disabled.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/test/unit/fixtures/components/text-input/disabled.js b/test/unit/fixtures/components/text-input/disabled.js new file mode 100644 index 0000000..0fd22e6 --- /dev/null +++ b/test/unit/fixtures/components/text-input/disabled.js @@ -0,0 +1,22 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + action: '/adobe/forms/af/form1.json', + items: [{ + id: 'textinput-4e4f61cad9', + fieldType: 'text-input', + name: 'lastName', + visible: false, + type: 'string', + label: { + value: 'Last Name', + }, + enabled: false, + rules: { + value: '5 + 2', + }, + ':type': 'forms-components-examples/components/form/textinput', + }, + ], +}; + +export const formPath = 'http://localhost:3000/adobe/forms/af/form1.json'; diff --git a/test/unit/fixtures/components/text-input/hidden.html b/test/unit/fixtures/components/text-input/hidden.html new file mode 100644 index 0000000..0243ee3 --- /dev/null +++ b/test/unit/fixtures/components/text-input/hidden.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/test/unit/fixtures/components/text-input/hidden.js b/test/unit/fixtures/components/text-input/hidden.js new file mode 100644 index 0000000..9f2d092 --- /dev/null +++ b/test/unit/fixtures/components/text-input/hidden.js @@ -0,0 +1,21 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + action: '/adobe/forms/af/form1.json', + items: [{ + id: 'textinput-4e4f61cad9', + fieldType: 'text-input', + name: 'lastName', + visible: false, + type: 'string', + label: { + value: 'Last Name', + }, + rules: { + value: '5 + 2', + }, + ':type': 'forms-components-examples/components/form/textinput', + }, + ], +}; + +export const formPath = 'http://localhost:3000/adobe/forms/af/form1.json'; diff --git a/test/unit/fixtures/components/text-input/rich-text-label-script.html b/test/unit/fixtures/components/text-input/rich-text-label-script.html new file mode 100644 index 0000000..1c297a8 --- /dev/null +++ b/test/unit/fixtures/components/text-input/rich-text-label-script.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/test/unit/fixtures/components/text-input/rich-text-label-script.js b/test/unit/fixtures/components/text-input/rich-text-label-script.js new file mode 100644 index 0000000..afaf9d6 --- /dev/null +++ b/test/unit/fixtures/components/text-input/rich-text-label-script.js @@ -0,0 +1,36 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + action: '/adobe/forms/af/form1.json', + items: [{ + id: 'textinput-4e4f61cad9', + fieldType: 'text-input', + name: 'lastName', + visible: true, + type: 'string', + required: true, + enabled: true, + readOnly: false, + autoComplete: 'family-name', + default: 'Tan', + label: { + visible: true, + richText: true, + value: 'Last Name', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/textinput_1442950437', + }, + ':type': 'forms-components-examples/components/form/textinput', + }, + ], +}; + +export const formPath = 'http://localhost:3000/adobe/forms/af/form1.json'; diff --git a/test/unit/fixtures/components/text-input/rich-text-label.html b/test/unit/fixtures/components/text-input/rich-text-label.html new file mode 100644 index 0000000..8df6d95 --- /dev/null +++ b/test/unit/fixtures/components/text-input/rich-text-label.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/test/unit/fixtures/components/text-input/rich-text-label.js b/test/unit/fixtures/components/text-input/rich-text-label.js new file mode 100644 index 0000000..33ec300 --- /dev/null +++ b/test/unit/fixtures/components/text-input/rich-text-label.js @@ -0,0 +1,36 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + action: '/adobe/forms/af/form1.json', + items: [{ + id: 'textinput-4e4f61cad9', + fieldType: 'text-input', + name: 'lastName', + visible: true, + type: 'string', + required: true, + enabled: true, + readOnly: false, + autoComplete: 'family-name', + default: 'Tan', + label: { + visible: true, + richText: true, + value: 'Last Name', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/textinput_1442950437', + }, + ':type': 'forms-components-examples/components/form/textinput', + }, + ], +}; + +export const formPath = 'http://localhost:3000/adobe/forms/af/form1.json'; diff --git a/test/unit/fixtures/components/text-input/text-input-advance.html b/test/unit/fixtures/components/text-input/text-input-advance.html new file mode 100644 index 0000000..025b2c0 --- /dev/null +++ b/test/unit/fixtures/components/text-input/text-input-advance.html @@ -0,0 +1,9 @@ +
+ + +
+

Hint - First name should be minimum 3 characters and a maximum of 10 characters.

+
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/text-input/text-input-advance.js b/test/unit/fixtures/components/text-input/text-input-advance.js new file mode 100644 index 0000000..6b2911c --- /dev/null +++ b/test/unit/fixtures/components/text-input/text-input-advance.js @@ -0,0 +1,36 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + action: '/adobe/forms/af/form1.json', + items: [{ + id: 'textinput-ccedc502ec', + fieldType: 'text-input', + name: 'displayName', + visible: false, + type: 'string', + enabled: true, + readOnly: true, + autoComplete: 'off', + placeholder: 'Aya Tan', + description: '

Hint - First name should be minimum 3 characters and a maximum of 10 characters.

', + tooltip: '', + label: { + visible: false, + value: 'Display Name', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/textinput_1215745486', + }, + ':type': 'forms-components-examples/components/form/textinput', + }, + ], +}; + +export const formPath = 'http://localhost:3000/adobe/forms/af/form1.json'; diff --git a/test/unit/fixtures/components/text-input/text-input-constraints.html b/test/unit/fixtures/components/text-input/text-input-constraints.html new file mode 100644 index 0000000..bfcf22f --- /dev/null +++ b/test/unit/fixtures/components/text-input/text-input-constraints.html @@ -0,0 +1,10 @@ +
+ + +
+

Hint - First name should be minimum 3 characters and a maximum of 10 characters.

+
+
\ No newline at end of file diff --git a/test/unit/fixtures/components/text-input/text-input-constraints.js b/test/unit/fixtures/components/text-input/text-input-constraints.js new file mode 100644 index 0000000..66c8b49 --- /dev/null +++ b/test/unit/fixtures/components/text-input/text-input-constraints.js @@ -0,0 +1,43 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + action: '/adobe/forms/af/form1.json', + items: [{ + id: 'textinput-b101bbcdfb', + fieldType: 'text-input', + name: 'firstName', + visible: true, + type: 'string', + required: true, + enabled: false, + readOnly: false, + minLength: 3, + maxLength: 10, + pattern: '/^[a-zA-Z\\s]+$/', + autoComplete: 'given-name', + placeholder: 'Aya', + description: '

Hint - First name should be minimum 3 characters and a maximum of 10 characters.

', + tooltip: '

Enter your first name.

', + label: { + visible: true, + value: 'First Name', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + tooltipVisible: false, + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/textinput', + }, + ':type': 'forms-components-examples/components/form/textinput', + }, + ], +}; + +export const formPath = 'http://localhost:3000/adobe/forms/af/form1.json'; diff --git a/test/unit/fixtures/components/text-input/text-input.html b/test/unit/fixtures/components/text-input/text-input.html new file mode 100644 index 0000000..ad2e499 --- /dev/null +++ b/test/unit/fixtures/components/text-input/text-input.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/test/unit/fixtures/components/text-input/text-input.js b/test/unit/fixtures/components/text-input/text-input.js new file mode 100644 index 0000000..2951afa --- /dev/null +++ b/test/unit/fixtures/components/text-input/text-input.js @@ -0,0 +1,42 @@ +import assert from 'assert'; + +export const fieldDef = { + action: '/adobe/forms/af/form1.json', + items: [{ + id: 'textinput-4e4f61cad9', + fieldType: 'text-input', + name: 'lastName', + visible: true, + type: 'string', + required: true, + enabled: true, + readOnly: false, + autoComplete: 'family-name', + default: 'Tan', + label: { + visible: true, + value: 'Last Name', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/textinput_1442950437', + }, + ':type': 'forms-components-examples/components/form/textinput', + }, + ], +}; + +export const extraChecks = [ + (html) => { + assert.equal(html.querySelector('#textinput-4e4f61cad9').value, 'Tan'); + }, +]; + +export const formPath = 'http://localhost:3000/adobe/forms/af/form1.json'; diff --git a/test/unit/fixtures/custom-component/range.html b/test/unit/fixtures/custom-component/range.html new file mode 100644 index 0000000..40a217e --- /dev/null +++ b/test/unit/fixtures/custom-component/range.html @@ -0,0 +1,10 @@ +
+ +
+ + + + +
+
\ No newline at end of file diff --git a/test/unit/fixtures/custom-component/range.js b/test/unit/fixtures/custom-component/range.js new file mode 100644 index 0000000..b74d662 --- /dev/null +++ b/test/unit/fixtures/custom-component/range.js @@ -0,0 +1,13 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + items: [{ + id: 'range-id', + fieldType: 'range', + name: 'range-name', + label: { + visible: true, + value: 'Range', + }, + }, + ], +}; diff --git a/test/unit/fixtures/doc-based-submit/enquire.js b/test/unit/fixtures/doc-based-submit/enquire.js new file mode 100644 index 0000000..b33a374 --- /dev/null +++ b/test/unit/fixtures/doc-based-submit/enquire.js @@ -0,0 +1,56 @@ +import assert from 'assert'; +import nock from 'nock'; +import { fieldDef } from '../form/enquire.js'; + +const thankYouMessage = 'Thanks for your submission'; + +const scope = nock('http://localhost:3000') + .post('/enquire', function test(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + assert.equal(contentType, 'application/json', 'content type not set to application/json'); + const { data } = body; + const expected = { + 'quote[destination]': 'Select your destination', + 'quote[destinationState]': 'Select your state', + 'quote[departureDate]': '', + 'quote[returnDate]': '', + 'quote[residence]': 'Select your residence', + 'quote[citizenship]': '', + 'quote[tripCostAmount]': '2', + 'quote[initialTripPaymentDate]': '', + name: '', + age: '', + }; + // eslint-disable-next-line no-underscore-dangle + assert.equal(data.__id__ > 0, true, 'id is not empty'); + Object.entries(expected).forEach(([key, value]) => { + assert.equal(data[key], value, `${key} do not match`); + }); + return true; + }) + .reply(200, { + thankYouMessage, + }); + +export const sample = fieldDef; + +export const formPath = 'http://localhost:3000/enquire.json'; + +export function op(block) { + const range = block.querySelector('input[name="quote[tripCostAmount]"]'); + range.value = 2; + const btn = block.querySelector('button[type="submit"'); + btn.click(); + const form = block.querySelector('form'); + form.dispatchEvent(new Event('submit')); +} + +export function expect() { + assert.equal(scope.isDone(), true, 'submit call was not made'); + // const el = block.querySelector('.form-message.success-message'); + // assert.equal(el.textContent, thankYouMessage); +} + +export const opDelay = 200; diff --git a/test/unit/fixtures/doc-based-submit/failed-submit.js b/test/unit/fixtures/doc-based-submit/failed-submit.js new file mode 100644 index 0000000..f6276d1 --- /dev/null +++ b/test/unit/fixtures/doc-based-submit/failed-submit.js @@ -0,0 +1,49 @@ +import assert from 'assert'; +import nock from 'nock'; +import { fireEvent } from '@testing-library/dom'; + +const scope = nock('http://localhost:3000') + .post('/adobe/forms/af/form1') + .reply(500, {}); + +export const formPath = 'http://localhost:3000/adobe/forms/af/form1.json'; + +export const sample = { + total: 3, + offset: 0, + limit: 11, + ':type': 'sheet', + data: [ + { + Type: 'text', + Name: 'f2', + }, + { + Type: 'text', + Name: 'f1', + }, + { + Type: 'submit', + Name: 'submit', + Label: 'Submit', + }, + ], +}; + +export function op(block) { + const btn = block.querySelector('button'); + // the click handler doesn't submit the form in tests. + // hence submitting manually + btn.addEventListener('click', () => { + fireEvent.submit(btn.form); + }); + btn.click(); +} + +export function expect(block) { + assert.equal(scope.isDone(), true, 'submit call was not made'); + const el = block.querySelector('.form-message.error-message'); + assert.equal(el.textContent, 'Some error occured while submitting the form'); +} + +export const opDelay = 200; diff --git a/test/unit/fixtures/doc-based-submit/failed-validations.js b/test/unit/fixtures/doc-based-submit/failed-validations.js new file mode 100644 index 0000000..d6b7950 --- /dev/null +++ b/test/unit/fixtures/doc-based-submit/failed-validations.js @@ -0,0 +1,55 @@ +import assert from 'assert'; +import nock from 'nock'; +import { fireEvent } from '@testing-library/dom'; + +const thankYouMessage = 'thank you for submitting the form'; + +const scope = nock('http://localhost:3000') + .post('/submit') + .reply(200, { + thankYouMessage, + }); + +export const formPath = 'http://localhost:3000/submit.json'; + +export const sample = { + total: 3, + offset: 0, + limit: 11, + ':type': 'sheet', + data: [ + { + Type: 'text', + Name: 'f2', + }, + { + Type: 'text', + Name: 'f1', + Mandatory: true, + }, + { + Type: 'submit', + Name: 'submit', + Label: 'Submit', + }, + ], +}; + +export function op(block) { + const btn = block.querySelector('button'); + // the click handler doesn't submit the form in tests. + // hence submitting manually + btn.addEventListener('click', () => { + fireEvent.submit(btn.form); + }); + btn.click(); +} + +export function expect(block) { + assert.equal(scope.isDone(), false, 'submit call was made'); + const t3 = block.querySelector('input[name="f1"]').parentElement; + const message = t3.querySelector('.field-invalid .field-description'); + assert.equal(message.textContent, 'Please fill in this field.', 'no error message shown'); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/doc-based-submit/successful-submit-radio-checkbox.js b/test/unit/fixtures/doc-based-submit/successful-submit-radio-checkbox.js new file mode 100644 index 0000000..0cb8144 --- /dev/null +++ b/test/unit/fixtures/doc-based-submit/successful-submit-radio-checkbox.js @@ -0,0 +1,94 @@ +import assert from 'assert'; +import nock from 'nock'; + +const thankYouMessage = 'thank you for submitting the form'; + +const scope = nock('http://localhost:3000') + .post('/submit-success-radio', function test(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + assert.equal(contentType, 'application/json', 'content type not set to application/json'); + const { data } = body; + assert.equal(Object.keys(data).length, 5, 'more data received than expected'); + assert.equal(data.f1, '10'); + assert.equal(data.f2, '102'); + assert.equal(data.f3, 'checkbox'); + assert.equal(data.f4, 'radio-on'); + // eslint-disable-next-line no-underscore-dangle + assert.equal(data.__id__.toString().length > 0, true, 'id not present'); + return true; + }) + .reply(200, { + thankYouMessage, + }); + +export const formPath = 'http://localhost:3000/submit-success-radio.json'; + +export const sample = { + total: 3, + offset: 0, + limit: 11, + ':type': 'sheet', + data: [ + { + Type: 'text', + Name: 'f2', + }, + { + Type: 'text', + Name: 'f1', + }, + { + Type: 'checkbox', + Name: 'f3', + Value: 'checkbox', + }, + { + Type: 'fieldset', + Name: 'panel1', + }, + { + Type: 'radio', + Name: 'f4', + Value: 'radio-on', + Fieldset: 'panel1', + }, + { + Type: 'radio', + Name: 'f4', + Value: 'radio-off', + Fieldset: 'panel1', + }, + { + Type: 'submit', + Name: 'submit', + Label: 'Submit', + }, + ], +}; + +export function op(block) { + global.window = Object.create(window); + const f1 = block.querySelector('input[name="f1"]'); + f1.value = '10'; + const f2 = block.querySelector('input[name="f2"]'); + f2.value = '102'; + const f3 = block.querySelector('input[name="f3"]'); + f3.click(); + const f4 = block.querySelector('input[type="radio"]'); + f4.click(); + + const btn = block.querySelector('button'); + btn.click(); + const form = block.querySelector('form'); + form.dispatchEvent(new Event('submit')); +} + +export function expect() { + assert.equal(scope.isDone(), true, 'submit call was not made'); + // const el = block.querySelector('.form-message.success-message'); + // assert.equal(el.textContent, thankYouMessage); +} + +export const opDelay = 200; diff --git a/test/unit/fixtures/doc-based-submit/successful-submit-redirect.js b/test/unit/fixtures/doc-based-submit/successful-submit-redirect.js new file mode 100644 index 0000000..6c4f35e --- /dev/null +++ b/test/unit/fixtures/doc-based-submit/successful-submit-redirect.js @@ -0,0 +1,76 @@ +import assert from 'assert'; +import nock from 'nock'; +import sinon from 'sinon'; +import { fireEvent } from '@testing-library/dom'; + +const scope = nock('http://localhost:3000') + .post('/submit-success-redirect', function test(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + assert.equal(contentType, 'application/json', 'content type not set to application/json'); + const { data } = body; + assert.equal(Object.keys(data).length, 3, 'more data received than expected'); + assert.equal(data.f1, '10'); + assert.equal(data.f2, '102'); + // eslint-disable-next-line no-underscore-dangle + assert.equal(data.__id__.toString().length > 0, true, 'id not present'); + return true; + }) + .reply(200, { + redirectUrl: 'http://localhost:3000/abc.html', + }); + +export const formPath = 'http://localhost:3000/submit-success-redirect.json'; + +export const sample = { + total: 3, + offset: 0, + limit: 11, + ':type': 'sheet', + data: [ + { + Type: 'text', + Name: 'f2', + }, + { + Type: 'text', + Name: 'f1', + }, + { + Type: 'submit', + Name: 'submit', + Label: 'Submit', + }, + ], +}; +const mock = sinon.fake(); + +export function op(block) { + global.window = Object.create(window); + Object.defineProperty(global.window, 'location', { + value: { + assign: mock, + }, + writable: true, + }); + const f1 = block.querySelector('input[name="f1"]'); + f1.value = '10'; + const f2 = block.querySelector('input[name="f2"]'); + f2.value = '102'; + + const btn = block.querySelector('button'); + btn.addEventListener('click', () => { + fireEvent.submit(btn.form); + }); + // btn.click(); + fireEvent(btn, new MouseEvent('click', { bubbles: true, cancelable: true })); +} + +export function expect() { + assert.equal(scope.isDone(), true, 'submit call was not made'); + // assert(mock.calledOnce); + // assert(mock.calledWith('http://localhost:3000/abc.html')); +} + +export const opDelay = 200; diff --git a/test/unit/fixtures/doc-based-submit/successful-submit-repeatable-checkbox.js b/test/unit/fixtures/doc-based-submit/successful-submit-repeatable-checkbox.js new file mode 100644 index 0000000..2a6039e --- /dev/null +++ b/test/unit/fixtures/doc-based-submit/successful-submit-repeatable-checkbox.js @@ -0,0 +1,89 @@ +import assert from 'assert'; +import nock from 'nock'; + +const thankYouMessage = 'thank you for submitting the form'; + +const scope = nock('http://localhost:3000') + .post('/submit-success-repeatable-checkbox', function test(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + assert.equal(contentType, 'application/json', 'content type not set to application/json'); + const { data } = body; + assert.equal(Object.keys(data).length, 4, 'more data received than expected'); + assert.equal(data.f1, '10,20'); + assert.equal(data.f2, '102,202'); + assert.equal(data.f3, 'checkbox,checkbox'); + // assert.equal(data.f4, 'radio-on,radio-off'); + // eslint-disable-next-line no-underscore-dangle + assert.equal(data.__id__.toString().length > 0, true, 'id not present'); + return true; + }) + .reply(200, { + thankYouMessage, + }); + +export const formPath = 'http://localhost:3000/submit-success-repeatable-checkbox.json'; + +export const sample = { + total: 3, + offset: 0, + limit: 11, + ':type': 'sheet', + data: [ + { + Type: 'fieldset', + Name: 'panel1', + Repeatable: 'true', + }, + { + Type: 'text', + Name: 'f2', + Fieldset: 'panel1', + }, + { + Type: 'text', + Name: 'f1', + Fieldset: 'panel1', + }, + { + Type: 'checkbox', + Name: 'f3', + Value: 'checkbox', + Fieldset: 'panel1', + }, + { + Type: 'submit', + Name: 'submit', + Label: 'Submit', + }, + ], +}; + +export function op(block) { + global.window = Object.create(window); + + // repeat panel + const btn = block.querySelector('.repeat-wrapper > .item-add'); + btn.click(); + const f1Repeat = block.querySelectorAll('input[name="f1"]'); + f1Repeat[0].value = '10'; + f1Repeat[1].value = '20'; + const f2Repeat = block.querySelectorAll('input[name="f2"]'); + f2Repeat[0].value = '102'; + f2Repeat[1].value = '202'; + const f3Repeat = block.querySelectorAll('input[name="f3"]'); + f3Repeat[0].click(); + f3Repeat[1].click(); + + const form = block.querySelector('form'); + form.dispatchEvent(new Event('submit')); +} + +export function expect() { + assert.equal(scope.isDone(), true, 'submit call was not made'); + // const el = block.querySelector('.form-message.success-message'); + // assert.equal(el.textContent, thankYouMessage); +} + +export const opDelay = 200; diff --git a/test/unit/fixtures/doc-based-submit/successful-submit-repeatable-radio.js b/test/unit/fixtures/doc-based-submit/successful-submit-repeatable-radio.js new file mode 100644 index 0000000..09045b4 --- /dev/null +++ b/test/unit/fixtures/doc-based-submit/successful-submit-repeatable-radio.js @@ -0,0 +1,98 @@ +import assert from 'assert'; +import nock from 'nock'; + +const thankYouMessage = 'thank you for submitting the form'; + +// eslint-disable-next-line no-unused-vars +const scope = nock('http://localhost:3000') + .post('/submit-success-repeatable-radio', function test(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + assert.equal(contentType, 'application/json', 'content type not set to application/json'); + const { data } = body; + assert.equal(Object.keys(data).length, 4, 'more data received than expected'); + assert.equal(data.f1, '10,20'); + assert.equal(data.f2, '102,202'); + assert.equal(data.f4, 'radio-on,radio-off'); + // eslint-disable-next-line no-underscore-dangle + assert.equal(data.__id__.toString().length > 0, true, 'id not present'); + return true; + }) + .reply(200, { + thankYouMessage, + }); + +export const formPath = 'http://localhost:3000/submit-success-repeatable-radio.json'; + +export const sample = { + total: 3, + offset: 0, + limit: 11, + ':type': 'sheet', + data: [ + { + Type: 'fieldset', + Name: 'panel1', + Repeatable: 'true', + }, + { + Type: 'text', + Name: 'f2', + Fieldset: 'panel1', + }, + { + Type: 'text', + Name: 'f1', + Fieldset: 'panel1', + }, + { + Type: 'radio', + Name: 'f4', + Value: 'radio-on', + Fieldset: 'panel1', + }, + { + Type: 'radio', + Name: 'f4', + Value: 'radio-off', + Fieldset: 'panel1', + }, + { + Type: 'submit', + Name: 'submit', + Label: 'Submit', + }, + ], +}; + +export function op(block) { + global.window = Object.create(window); + + // repeat panel + const btn = block.querySelector('.repeat-wrapper > .item-add'); + btn.click(); + const f1Repeat = block.querySelectorAll('input[name="f1"]'); + f1Repeat[0].value = '10'; + f1Repeat[1].value = '20'; + const f2Repeat = block.querySelectorAll('input[name="f2"]'); + f2Repeat[0].value = '102'; + f2Repeat[1].value = '202'; + + const f4Repeat = block.querySelectorAll('input[name="f4"]'); + f4Repeat[0].click(); + f4Repeat[3].click(); + + const form = block.querySelector('form'); + form.dispatchEvent(new Event('submit')); +} + +export function expect() { + // assert.equal(scope.isDone(), true, 'submit call was not made'); + // const el = block.querySelector('.form-message.success-message'); + // assert.equal(el.textContent, thankYouMessage); +} + +export const opDelay = 200; + +export const ignore = true; diff --git a/test/unit/fixtures/doc-based-submit/successful-submit-repeatable.js b/test/unit/fixtures/doc-based-submit/successful-submit-repeatable.js new file mode 100644 index 0000000..79a9369 --- /dev/null +++ b/test/unit/fixtures/doc-based-submit/successful-submit-repeatable.js @@ -0,0 +1,100 @@ +import assert from 'assert'; +import nock from 'nock'; + +const thankYouMessage = 'thank you for submitting the form'; + +const scope = nock('http://localhost:3000') + .post('/submit-success-repeatable', function test(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + assert.equal(contentType, 'application/json', 'content type not set to application/json'); + const { data } = body; + assert.equal(Object.keys(data).length, 5, 'more data received than expected'); + assert.equal(data.f1, '10,20'); + assert.equal(data.f2, '102,202'); + assert.equal(data.f3, 'checkbox'); + assert.equal(data.f4, 'radio-on'); + // eslint-disable-next-line no-underscore-dangle + assert.equal(data.__id__.toString().length > 0, true, 'id not present'); + return true; + }) + .reply(200, { + thankYouMessage, + }); + +export const formPath = 'http://localhost:3000/submit-success-repeatable.json'; + +export const sample = { + total: 3, + offset: 0, + limit: 11, + ':type': 'sheet', + data: [ + { + Type: 'fieldset', + Name: 'panel1', + Repeatable: 'true', + }, + { + Type: 'text', + Name: 'f2', + Fieldset: 'panel1', + }, + { + Type: 'text', + Name: 'f1', + Fieldset: 'panel1', + }, + { + Type: 'checkbox', + Name: 'f3', + Value: 'checkbox', + }, + { + Type: 'radio', + Name: 'f4', + Value: 'radio-on', + }, + { + Type: 'radio', + Name: 'f4', + Value: 'radio-off', + }, + { + Type: 'submit', + Name: 'submit', + Label: 'Submit', + }, + ], +}; + +export function op(block) { + global.window = Object.create(window); + + const f3 = block.querySelector('input[name="f3"]'); + f3.click(); + const f4 = block.querySelector('input[type="radio"]'); + f4.click(); + + // repeat panel + const btn = block.querySelector('.repeat-wrapper > .item-add'); + btn.click(); + const f1Repeat = block.querySelectorAll('input[name="f1"]'); + f1Repeat[0].value = '10'; + f1Repeat[1].value = '20'; + const f2Repeat = block.querySelectorAll('input[name="f2"]'); + f2Repeat[0].value = '102'; + f2Repeat[1].value = '202'; + + const form = block.querySelector('form'); + form.dispatchEvent(new Event('submit')); +} + +export function expect() { + assert.equal(scope.isDone(), true, 'submit call was not made'); + // const el = block.querySelector('.form-message.success-message'); + // assert.equal(el.textContent, thankYouMessage); +} + +export const opDelay = 200; diff --git a/test/unit/fixtures/doc-based-submit/successful-submit.js b/test/unit/fixtures/doc-based-submit/successful-submit.js new file mode 100644 index 0000000..acda22d --- /dev/null +++ b/test/unit/fixtures/doc-based-submit/successful-submit.js @@ -0,0 +1,67 @@ +import assert from 'assert'; +import nock from 'nock'; + +const thankYouMessage = 'thank you for submitting the form'; + +const scope = nock('http://localhost:3000') + .post('/submit-success', function test(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + assert.equal(contentType, 'application/json', 'content type not set to application/json'); + const { data } = body; + assert.equal(Object.keys(data).length, 3, 'more data received than expected'); + assert.equal(data.f1, '10'); + assert.equal(data.f2, '102'); + // eslint-disable-next-line no-underscore-dangle + assert.equal(data.__id__.toString().length > 0, true, 'id not present'); + return true; + }) + .reply(200, { + thankYouMessage, + }); + +export const formPath = 'http://localhost:3000/submit-success.json'; + +export const sample = { + total: 3, + offset: 0, + limit: 11, + ':type': 'sheet', + data: [ + { + Type: 'text', + Name: 'f2', + }, + { + Type: 'text', + Name: 'f1', + }, + { + Type: 'submit', + Name: 'submit', + Label: 'Submit', + }, + ], +}; + +export function op(block) { + global.window = Object.create(window); + const f1 = block.querySelector('input[name="f1"]'); + f1.value = '10'; + const f2 = block.querySelector('input[name="f2"]'); + f2.value = '102'; + + const btn = block.querySelector('button'); + btn.click(); + const form = block.querySelector('form'); + form.dispatchEvent(new Event('submit')); +} + +export function expect() { + assert.equal(scope.isDone(), true, 'submit call was not made'); + // const el = block.querySelector('.form-message.success-message'); + // assert.equal(el.textContent, thankYouMessage); +} + +export const opDelay = 200; diff --git a/test/unit/fixtures/docForms/computeAmount.js b/test/unit/fixtures/docForms/computeAmount.js new file mode 100644 index 0000000..041614f --- /dev/null +++ b/test/unit/fixtures/docForms/computeAmount.js @@ -0,0 +1,25 @@ +import assert from 'assert'; +import { advanceEnquiry } from '../../forms/advanceEnquiry.js'; + +export const sample = advanceEnquiry; +export const formPath = 'http://localhost:3000/enquiry.json'; + +export function op(block) { + const startDate = block.querySelector('#startdate'); + startDate.value = '2024-03-10'; + startDate.dispatchEvent(new Event('change', { bubbles: true })); + const endDate = block.querySelector('#enddate'); + endDate.value = '2024-03-12'; + endDate.dispatchEvent(new Event('change', { bubbles: true })); +} + +export function expect(block) { + const amount = block.querySelector('#amount'); + assert.equal(amount.value, '2000', 'Expected amoun to computed'); + const budget = block.querySelector('#budget'); + budget.value = '3000'; + budget.dispatchEvent(new Event('change', { bubbles: true })); + assert.equal(amount.value, '6000', 'Expected amount to be set'); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/docForms/customFunction.js b/test/unit/fixtures/docForms/customFunction.js new file mode 100644 index 0000000..e8a8c1c --- /dev/null +++ b/test/unit/fixtures/docForms/customFunction.js @@ -0,0 +1,62 @@ +import assert from 'assert'; + +export const sample = { + total: 2, + offset: 0, + limit: 2, + data: [{ + Name: 'firstName', + Type: 'text', + Description: '', + Label: 'First Name', + Mandatory: '', + Value: '', + Visible: '', + 'Visible Expression': '', + }, { + Name: 'lastName', + Type: 'text', + Description: '', + Label: 'Last Name', + Mandatory: '', + Value: '', + Visible: '', + 'Visible Expression': '', + }, { + Name: 'fullName', + Type: 'text', + Description: '', + Label: 'Full Name', + Mandatory: '', + Value: '', + Visible: 'false', + 'Value Expression': '=getFullName(F2,F3)', + Options: '', + }], + ':type': 'sheet', +}; + +export function op(block) { + const firstName = block.querySelector('#firstname'); + firstName.value = 'Aya'; + firstName.dispatchEvent(new Event('change', { bubbles: true })); + + const lastName = block.querySelector('#lastname'); + lastName.value = 'Tan'; + lastName.dispatchEvent(new Event('change', { bubbles: true })); +} + +export const formPath = 'http://localhost:3000/info.json'; + +export function expect(block) { + const fullName = block.querySelector('#fullname'); + console.log('text', fullName); + assert.equal(fullName.value, 'Aya Tan', 'value is not set in the field'); + + const firstName = block.querySelector('#firstname'); + firstName.value = null; + firstName.dispatchEvent(new Event('change', { bubbles: true })); + assert.equal(fullName.value, 'Tan', 'value is not set in the field'); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/docForms/radio.js b/test/unit/fixtures/docForms/radio.js new file mode 100644 index 0000000..74a36d6 --- /dev/null +++ b/test/unit/fixtures/docForms/radio.js @@ -0,0 +1,67 @@ +/* eslint-disable */ +import assert from 'assert'; + +export const sample = { + total: 2, + offset: 0, + limit: 2, + data: [ + { + Name: 'radioFieldset', + Type: 'fieldset', + Description: '', + Label: 'Fill Extra Fields', + Mandatory: '', + Visible: '', + 'Visible Expression': '', + }, + { + Name: 'radioOption', + Type: 'radio', + Description: '', + Label: 'Yes', + Mandatory: '', + Value: 'yes', + Visible: '', + 'Visible Expression': '', + Fieldset: 'radioFieldset', + }, + { + Name: 'radioOption', + Type: 'radio', + Description: '', + Label: 'No', + Mandatory: '', + Value: 'no', + Visible: '', + 'Visible Expression': '', + Fieldset: 'radioFieldset', + }, + { + Name: 'checkboxField', + Type: 'checkbox', + Description: '', + Label: 'Extra Checkbox', + Mandatory: '', + Value: 'on', + Visible: 'false', + 'Visible Expression': '=F3="yes"', + Options: '', + }], + ':type': 'sheet', +}; + +export function op(block) { + const radio = block.querySelector('input[type="radio"]'); + radio.click(); + radio.dispatchEvent(new Event('change', { bubbles: true })); +} + +export const formPath = 'http://localhost:3000/spouse.json'; + +export function expect(block) { + const checkbox = block.querySelector('input[type="checkbox"]').closest('.field-wrapper'); + assert.equal(checkbox.dataset.visible, 'true', 'field was not made visible'); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/docForms/showEmail.js b/test/unit/fixtures/docForms/showEmail.js new file mode 100644 index 0000000..43ae75a --- /dev/null +++ b/test/unit/fixtures/docForms/showEmail.js @@ -0,0 +1,23 @@ +import assert from 'assert'; +import { advanceEnquiry } from '../../forms/advanceEnquiry.js'; + +export const sample = advanceEnquiry; +export const formPath = 'http://localhost:3000/enquiry.json'; + +export function op(block) { + const subscribe = block.querySelector('#subscribe'); + subscribe.checked = true; + subscribe.dispatchEvent(new Event('change', { bubbles: true })); +} + +export function expect(block) { + const email = block.querySelector('.field-email'); + assert.equal(email.dataset.visible, 'true', 'field was not made visible'); + + const subscribe = block.querySelector('#subscribe'); + subscribe.checked = false; + subscribe.dispatchEvent(new Event('change', { bubbles: true })); + assert.equal(email.dataset.visible, 'false', 'field was not made hidden'); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/docForms/singleRadio.js b/test/unit/fixtures/docForms/singleRadio.js new file mode 100644 index 0000000..385cfd4 --- /dev/null +++ b/test/unit/fixtures/docForms/singleRadio.js @@ -0,0 +1,56 @@ +/* eslint-disable */ +import assert from 'assert'; + +export const sample = { + total: 2, + offset: 0, + limit: 2, + data: [ + { + Name: 'radioFieldset', + Type: 'fieldset', + Description: '', + Label: 'Fill Extra Fields', + Mandatory: '', + Visible: '', + 'Visible Expression': '', + }, + { + Name: 'radioOption', + Type: 'radio', + Description: '', + Label: 'Yes', + Mandatory: '', + Value: 'yes', + Visible: '', + 'Visible Expression': '', + Fieldset: 'radioFieldset', + }, + { + Name: 'checkboxField', + Type: 'checkbox', + Description: '', + Label: 'Extra Checkbox', + Mandatory: '', + Value: 'on', + Visible: 'false', + 'Visible Expression': '=F3="yes"', + Options: '', + }], + ':type': 'sheet', +}; + +export function op(block) { + const radio = block.querySelector('input[type="radio"]'); + radio.click(); + radio.dispatchEvent(new Event('change', { bubbles: true })); +} + +export const formPath = 'http://localhost:3000/spouse.json'; + +export function expect(block) { + const checkbox = block.querySelector('input[type="checkbox"]').closest('.field-wrapper'); + assert.equal(checkbox.dataset.visible, 'true', 'field was not made visible'); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/docForms/value.js b/test/unit/fixtures/docForms/value.js new file mode 100644 index 0000000..aa44bda --- /dev/null +++ b/test/unit/fixtures/docForms/value.js @@ -0,0 +1,57 @@ +import assert from 'assert'; + +export const sample = { + total: 2, + offset: 0, + limit: 2, + data: [{ + Name: 'infoName', + Type: 'text', + Description: '', + Label: 'Name', + Mandatory: '', + Value: '', + Visible: '', + 'Visible Expression': '', + }, { + Name: 'maritalStatus', + Type: 'select', + Description: '', + Label: 'Marital Status', + Mandatory: '', + Value: '', + Visible: '', + 'Visible Expression': '', + Options: 'married,single,divorced', + }, { + Name: 'maritalStatusText', + Id: 'maritalStatusText', + Type: 'text', + Description: '', + Label: 'Spouse', + Mandatory: '', + Value: '', + Visible: 'false', + 'Value Expression': '=F2 & " is " & F3', + Options: '', + }], + ':type': 'sheet', +}; + +export function op(block) { + const name = block.querySelector('#infoname'); + name.value = 'john doe'; + name.dispatchEvent(new Event('change', { bubbles: true })); + const ms = block.querySelector('select'); + ms.value = 'married'; + ms.dispatchEvent(new Event('change', { bubbles: true })); +} + +export const formPath = 'http://localhost:3000/spouse.json'; + +export function expect(block) { + const text = block.querySelector('#maritalStatusText'); + assert.equal(text.value, 'john doe is married', 'value is not set in the field'); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/docForms/visible.js b/test/unit/fixtures/docForms/visible.js new file mode 100644 index 0000000..4f04513 --- /dev/null +++ b/test/unit/fixtures/docForms/visible.js @@ -0,0 +1,49 @@ +import assert from 'assert'; + +export const sample = { + total: 2, + offset: 0, + limit: 2, + data: [{ + Name: 'maritalStatus', + Type: 'select', + Description: '', + Label: 'Marital Status', + Mandatory: '', + Value: '', + Visible: '', + 'Visible Expression': '', + Options: 'married,single,divorced', + }, { + Name: 'spouseName', + Type: 'text', + Description: '', + Label: 'Spouse', + Mandatory: '', + Value: '', + Visible: 'false', + 'Visible Expression': '=F2="married"', + Options: '', + }], + ':type': 'sheet', +}; + +export function op(block) { + const ms = block.querySelector('select'); + ms.value = 'married'; + ms.dispatchEvent(new Event('change', { bubbles: true })); +} + +export const formPath = 'http://localhost:3000/spouse.json'; + +export function expect(block) { + const spouse = block.querySelector('.field-spousename'); + assert.equal(spouse.dataset.visible, 'true', 'field was not made visible'); + + const ms = block.querySelector('select'); + ms.value = 'single'; + ms.dispatchEvent(new Event('change', { bubbles: true })); + assert.equal(spouse.dataset.visible, 'false', 'field was not made hidden'); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/dynamic/custom-function.js b/test/unit/fixtures/dynamic/custom-function.js new file mode 100644 index 0000000..3627a61 --- /dev/null +++ b/test/unit/fixtures/dynamic/custom-function.js @@ -0,0 +1,88 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'textinput-9ae7fb61e4', + fieldType: 'text-input', + name: 'firstname', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'firstname', + }, + events: { + change: [ + "if(contains($event.payload.changes[].propertyName, 'value'), dispatchEvent(FullName, 'custom:setProperty', {value : getFullName($field.$value,lastname.$value)}), {})", + ], + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + id: 'textinput-8eca50b734', + fieldType: 'text-input', + name: 'lastname', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'lastname', + }, + events: { + change: [ + "if(contains($event.payload.changes[].propertyName, 'value'), dispatchEvent(FullName, 'custom:setProperty', {value : getFullName(firstname.$value,$field.$value)}), {})", + ], + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + id: 'textinput-a01ceb1c74', + fieldType: 'text-input', + name: 'FullName', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'FullName', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + ], +}; + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + const inputEle1 = block.querySelector('#textinput-9ae7fb61e4'); + inputEle1.dispatchEvent(new Event('focus')); + inputEle1.value = 'fname'; + inputEle1.dispatchEvent(new Event('change', { bubbles: true })); + inputEle1.dispatchEvent(new Event('blur')); + const inputEle2 = block.querySelector('#textinput-8eca50b734'); + inputEle2.dispatchEvent(new Event('focus')); + inputEle2.value = 'lname'; + inputEle2.dispatchEvent(new Event('change', { bubbles: true })); + inputEle2.dispatchEvent(new Event('blur')); +} + +export function expect(block) { + assert.equal(getValue(block, '#textinput-a01ceb1c74'), 'fname lname'); +} diff --git a/test/unit/fixtures/dynamic/date-input-blur.js b/test/unit/fixtures/dynamic/date-input-blur.js new file mode 100644 index 0000000..35bde22 --- /dev/null +++ b/test/unit/fixtures/dynamic/date-input-blur.js @@ -0,0 +1,42 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'datepicker-6dd0c75352', + fieldType: 'date-input', + name: 'dob', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + displayFormat: 'd MMMM, y', + default: '2000-02-13', + label: { + visible: true, + value: 'Date Of Birth', + }, + events: { + 'custom:setProperty': ['$event.payload'], + }, + format: 'date', + }, + ], +}; + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + const inputEle = block.querySelector('#datepicker-6dd0c75352'); + inputEle.dispatchEvent(new Event('focus')); + inputEle.value = '2000-02-14'; + inputEle.dispatchEvent(new Event('change', { bubbles: true })); + inputEle.dispatchEvent(new Event('blur')); +} + +export function expect(block) { + assert.equal(getValue(block, '#datepicker-6dd0c75352'), '14 February, 2000'); +} diff --git a/test/unit/fixtures/dynamic/date-input-display-value-expression-display-format.js b/test/unit/fixtures/dynamic/date-input-display-value-expression-display-format.js new file mode 100644 index 0000000..c9936b6 --- /dev/null +++ b/test/unit/fixtures/dynamic/date-input-display-value-expression-display-format.js @@ -0,0 +1,41 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'datepicker-ff38b6d2af', + fieldType: 'date-input', + name: 'dateInputTest', + visible: true, + type: 'string', + displayValueExpression: '(toString($field.$value) & \' today\')', + displayFormat: 'EEEE, MMMM d, y', + required: false, + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'Test displayValueExpression', + }, + format: 'date', + }, + ], +}; + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + const Ele = block.querySelector('#datepicker-ff38b6d2af'); + Ele.dispatchEvent(new Event('focus')); + Ele.value = '1999-12-12'; + Ele.dispatchEvent(new Event('change', { bubbles: true })); + // Dispatch blur event + Ele.dispatchEvent(new Event('blur')); +} + +export function expect(block) { + assert.equal(getValue(block, '#datepicker-ff38b6d2af'), '1999-12-12 today'); +} diff --git a/test/unit/fixtures/dynamic/date-input-display-value-expression.js b/test/unit/fixtures/dynamic/date-input-display-value-expression.js new file mode 100644 index 0000000..8425348 --- /dev/null +++ b/test/unit/fixtures/dynamic/date-input-display-value-expression.js @@ -0,0 +1,40 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'datepicker-ff38b6d2af', + fieldType: 'date-input', + name: 'dateInputTest', + visible: true, + type: 'string', + displayValueExpression: '(toString($field.$value) & \' today\')', + required: false, + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'Test displayValueExpression', + }, + format: 'date', + }, + ], +}; + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + const Ele = block.querySelector('#datepicker-ff38b6d2af'); + Ele.dispatchEvent(new Event('focus')); + Ele.value = '1999-12-12'; + Ele.dispatchEvent(new Event('change', { bubbles: true })); + // Dispatch blur event + Ele.dispatchEvent(new Event('blur')); +} + +export function expect(block) { + assert.equal(getValue(block, '#datepicker-ff38b6d2af'), '1999-12-12 today'); +} diff --git a/test/unit/fixtures/dynamic/date-input-focus.js b/test/unit/fixtures/dynamic/date-input-focus.js new file mode 100644 index 0000000..6020e8d --- /dev/null +++ b/test/unit/fixtures/dynamic/date-input-focus.js @@ -0,0 +1,39 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'datepicker-6dd0c75352', + fieldType: 'date-input', + name: 'dob', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + displayFormat: 'd MMMM, y', + default: '2000-02-13', + label: { + visible: true, + value: 'Date Of Birth', + }, + events: { + 'custom:setProperty': ['$event.payload'], + }, + format: 'date', + }, + ], +}; + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + const inputEle = block.querySelector('#datepicker-6dd0c75352'); + inputEle.dispatchEvent(new Event('focus')); +} + +export function expect(block) { + assert.equal(getValue(block, '#datepicker-6dd0c75352'), '2000-02-13'); +} diff --git a/test/unit/fixtures/dynamic/email-input-display-value-expression.js b/test/unit/fixtures/dynamic/email-input-display-value-expression.js new file mode 100644 index 0000000..52c427d --- /dev/null +++ b/test/unit/fixtures/dynamic/email-input-display-value-expression.js @@ -0,0 +1,39 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'emailinput-93c6f2da4d', + fieldType: 'email', + name: 'emailInputTest', + visible: true, + type: 'string', + displayValueExpression: '($field.$value & \'@adobe.com\')', + required: false, + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'Test displayValueExpression', + }, + }, + ], +}; + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + const Ele = block.querySelector('#emailinput-93c6f2da4d'); + Ele.dispatchEvent(new Event('focus')); + Ele.value = 'test'; + Ele.dispatchEvent(new Event('change', { bubbles: true })); + // Dispatch blur event + Ele.dispatchEvent(new Event('blur')); +} + +export function expect(block) { + assert.equal(getValue(block, '#emailinput-93c6f2da4d'), 'test@adobe.com'); +} diff --git a/test/unit/fixtures/dynamic/error-messages.js b/test/unit/fixtures/dynamic/error-messages.js new file mode 100644 index 0000000..5a1d8f9 --- /dev/null +++ b/test/unit/fixtures/dynamic/error-messages.js @@ -0,0 +1,153 @@ +import assert from 'assert'; +import { setValue } from '../../testUtils.js'; + +export const sample = { + action: 'http://localhost:3000/submit', + items: [ + { + fieldType: 'text-input', + id: 'f1', + name: 'f1_text', + required: true, + constraintMessages: { + required: 'Please fill in this field.', + }, + }, + { + fieldType: 'number-input', + id: 'f2', + name: 'f2_number', + required: true, + maximum: 10, + minimum: 2, + constraintMessages: { + required: 'Please fill in this field.', + }, + }, + { + fieldType: 'radio-group', + id: 'f3', + name: 'f3_radio', + required: true, + enum: ['a', 'b'], + enumNames: ['a', 'b'], + constraintMessages: { + required: 'Please fill in this field.', + }, + }, + { + fieldType: 'checkbox-group', + id: 'f4', + name: 'f4_checkbox', + required: true, + enum: ['a', 'b'], + enumNames: ['a', 'b'], + constraintMessages: { + required: 'Please fill in this field.', + }, + }, + { + id: 'f5', + fieldType: 'file-input', + name: 'f5_file', + type: 'file', + required: true, + accept: [ + 'audio/*', + ' video/*', + ' image/*', + ' application/pdf', + ], + constraintMessages: { + required: 'Please fill in this field.', + }, + }, + { + fieldType: 'email', + id: 'f6', + name: 'f6_text', + required: true, + constraintMessages: { + required: 'Please fill in this field.', + }, + }, + { + fieldType: 'button', + id: 'button', + buttonType: 'submit', + events: { + click: 'submitForm()', + }, + }, + ], +}; + +export function op(block) { + const btn = block.querySelector('#button'); + btn.click(); + const form = block.querySelector('form'); + form.dispatchEvent(new Event('submit')); +} + +export function expect(block) { + // text input error message + const f1 = block.querySelector('#f1').parentElement; + const f1Message = f1.querySelector('.field-invalid .field-description'); + assert.equal(f1Message.textContent, 'Please fill in this field.', 'Required error message for text input'); + setValue(block, '#f1', 'abc'); + assert.equal(f1.querySelector('.field-invalid .field-description'), undefined, 'Not Required error message for text input'); + + // number input error message + const f2 = block.querySelector('#f2').parentElement; + const f2Message = f2.querySelector('.field-invalid .field-description'); + assert.equal(f2Message.textContent, 'Please fill in this field.', 'Required error message for number input'); + setValue(block, '#f2', 5); + assert.equal(f2.querySelector('.field-invalid .field-description'), undefined, 'Not Required error message for number input'); + setValue(block, '#f2', 1); + const minMessage = f2.querySelector('.field-invalid .field-description').textContent; + assert.equal(minMessage, 'Value must be greater than or equal to 2.', 'minimum error message for number input'); + setValue(block, '#f2', 11); + const maxMessage = f2.querySelector('.field-invalid .field-description').textContent; + assert.equal(maxMessage, 'Value must be less than or equal to 10.', 'maximum error message for number input'); + + // radio buttons error message + const f3 = block.querySelector('#f3'); + const f3Message = f3.querySelector('.field-invalid .field-description'); + assert.equal(f3Message.textContent, 'Please fill in this field.', 'Required error message for radio buttons'); + const radio = f3.querySelectorAll('input')[0]; + radio.click(); + radio.dispatchEvent(new Event('change', { bubbles: true })); + assert.equal(f3.querySelector('.field-invalid .field-description'), undefined, 'Not required error message for radio buttons'); + + // checkbox group error message + const f4 = block.querySelector('#f4'); + const f4Message = f4.querySelector('.field-invalid .field-description'); + assert.equal(f4Message.textContent, 'Please fill in this field.', 'Required error message for checkbox group'); + const checkbox = f4.querySelectorAll('input')[0]; + checkbox.click(); + checkbox.dispatchEvent(new Event('change', { bubbles: true })); + assert.equal(f4.querySelector('.field-invalid .field-description'), undefined, 'Not required error message for checkbox group'); + + // file input error message + const f5 = block.querySelector('#f5').closest('.field-wrapper'); + const f5Message = f5.querySelector('.field-invalid .field-description'); + // eslint-disable-next-line max-len + assert.equal(f5Message.textContent, 'Please fill in this field.', 'Required error message for file input'); + const input = block.querySelector('#f5'); + const file = new File([new ArrayBuffer(1024)], 'file1.png', { type: 'image/png' }); + const event = new Event('change', { + bubbles: true, + cancelable: true, + }); + event.files = [file]; + input.dispatchEvent(event); + + // email input error message + const f6 = block.querySelector('#f6').closest('.field-wrapper'); + const f6Message = f6.querySelector('.field-invalid .field-description'); + assert.equal(f6Message.textContent, 'Please fill in this field.', 'Required error message for file input'); + setValue(block, '#f6', 'abc'); + assert.equal(f6.querySelector('.field-invalid .field-description').textContent, 'Specify the value in allowed format : email.', 'Invalid email error message'); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/dynamic/file-attachment-accept-clear.js b/test/unit/fixtures/dynamic/file-attachment-accept-clear.js new file mode 100644 index 0000000..bf51ea1 --- /dev/null +++ b/test/unit/fixtures/dynamic/file-attachment-accept-clear.js @@ -0,0 +1,53 @@ +/* eslint-env mocha */ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'fileinput-fc4d042b5e', + fieldType: 'file-input', + name: 'idProof', + visible: false, + description: '

You can upload any PDF file which is lessthan 1 MB

', + tooltip: '

Please upload any PDF file.

', + type: 'file', + enabled: true, + readOnly: false, + maxFileSize: '1MB', + accept: [ + 'application/pdf', + ], + label: { + visible: false, + value: 'Identity Proof', + }, + ':type': 'forms-components-examples/components/form/fileinput', + }, + ], +}; + +export function op(block) { + const input = block.querySelector('input'); + const file1 = new File([new ArrayBuffer(512)], 'file1.png', { type: 'image/png' }); + const event = new Event('change', { + bubbles: true, + cancelable: true, + }); + input.files = [file1]; + input.dispatchEvent(event); + + const removeButton = block.querySelector('.files-list button'); + const event2 = new Event('click', { + bubbles: true, + cancelable: true, + }); + removeButton.dispatchEvent(event2); +} + +export function expect(block) { + const input = block.querySelector('input'); + const wrapper = input.closest('.field-wrapper'); + assert.equal(wrapper.classList.contains('field-invalid'), false, 'should not have invalid css'); + assert.equal(input.validity.valid, true, 'should be valid'); + assert.equal(input.validationMessage, '', 'should not have any error'); +} diff --git a/test/unit/fixtures/dynamic/file-attachment-accept-min-max-items.js b/test/unit/fixtures/dynamic/file-attachment-accept-min-max-items.js new file mode 100644 index 0000000..35cb298 --- /dev/null +++ b/test/unit/fixtures/dynamic/file-attachment-accept-min-max-items.js @@ -0,0 +1,83 @@ +/* eslint-env mocha */ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'file-max-2', + fieldType: 'file-input', + name: 'file-max-2', + visible: false, + description: '

You can upload any PDF file which is less than 1 MB

', + tooltip: '

Please upload any PDF file.

', + type: 'file[]', + required: true, + enabled: true, + readOnly: false, + maxFileSize: '2MB', + accept: [ + 'application/pdf', + ], + maxItems: 2, + label: { + visible: false, + value: 'Identity Proof', + }, + ':type': 'forms-components-examples/components/form/fileinput', + }, + { + id: 'file-min-2', + fieldType: 'file-input', + name: 'file-min-2', + visible: false, + description: '

You can upload any PDF file which is less than 1 MB

', + tooltip: '

Please upload any PDF file.

', + type: 'file[]', + required: true, + enabled: true, + readOnly: false, + minItems: 2, + maxFileSize: '1MB', + accept: [ + 'application/pdf', + ], + label: { + visible: false, + value: 'Identity Proof', + }, + ':type': 'forms-components-examples/components/form/fileinput', + }, + ], +}; + +export function op(block) { + let input = block.querySelector('#file-max-2'); + const file1 = new File([new ArrayBuffer(512)], 'file1.png', { type: 'application/pdf' }); + const file2 = new File([new ArrayBuffer(512)], 'file2.png', { type: 'application/pdf' }); + const file3 = new File([new ArrayBuffer(512)], 'file3.png', { type: 'application/pdf' }); + let event = new Event('change', { + bubbles: true, + cancelable: true, + }); + input.files = [file1, file2, file3]; + input.dispatchEvent(event); + + input = block.querySelector('#file-min-2'); + const file4 = new File([new ArrayBuffer(512)], 'file1.png', { type: 'application/pdf' }); + event = new Event('change', { + bubbles: true, + cancelable: true, + }); + input.files = [file4]; + input.dispatchEvent(event); +} + +export function expect(block) { + let input = block.querySelector('#file-max-2'); + assert.equal(input.validity.valid, false, 'should be invalid'); + assert.equal(input.validationMessage, 'Specify a number of items equal to or less than 2.', 'should show proper error'); + + input = block.querySelector('#file-min-2'); + assert.equal(input.validity.valid, false, 'should be invalid'); + assert.equal(input.validationMessage, 'Specify a number of items equal to or greater than 2.', 'should show proper error'); +} diff --git a/test/unit/fixtures/dynamic/file-attachment-accept.js b/test/unit/fixtures/dynamic/file-attachment-accept.js new file mode 100644 index 0000000..237e3c6 --- /dev/null +++ b/test/unit/fixtures/dynamic/file-attachment-accept.js @@ -0,0 +1,75 @@ +/* eslint-env mocha */ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'fileinput-fc4d042b5e', + fieldType: 'file-input', + name: 'idProof', + description: '

You can upload any PDF file which is lessthan 1 MB

', + tooltip: '

Please upload any PDF file.

', + type: 'file', + enabled: true, + readOnly: false, + maxFileSize: '1MB', + accept: [ + 'application/pdf', + ], + label: { + visible: false, + value: 'Identity Proof', + }, + ':type': 'forms-components-examples/components/form/fileinput', + }, + { + id: 'file-input-no-accept', + fieldType: 'file-input', + name: 'file-input-no-accept', + description: '

You can upload any file which is less than 1 MB

', + tooltip: '

Please upload any file.

', + type: 'file', + enabled: true, + readOnly: false, + accept: [ + 'image/*', + ], + maxFileSize: '1MB', + label: { + visible: false, + value: 'Identity Proof', + }, + ':type': 'forms-components-examples/components/form/fileinput', + }, + ], +}; + +export function op(block) { + let input = block.querySelector('#fileinput-fc4d042b5e'); + let file1 = new File([new ArrayBuffer(512)], 'file1.png', { type: 'image/png' }); + let event = new Event('change', { + bubbles: true, + cancelable: true, + }); + input.files = [file1]; + input.dispatchEvent(event); + + input = block.querySelector('#file-input-no-accept'); + file1 = new File([new ArrayBuffer(512)], 'file1.png', { type: 'image/png' }); + event = new Event('change', { + bubbles: true, + cancelable: true, + }); + input.files = [file1]; + input.dispatchEvent(event); +} + +export function expect(block) { + let input = block.querySelector('#fileinput-fc4d042b5e'); + assert.equal(input.validity.valid, false, 'should be invalid'); + assert.equal(input.validationMessage, 'The specified file type not supported.', 'should show proper error'); + + input = block.querySelector('#file-input-no-accept'); + assert.equal(input.validity.valid, true, 'second field should be valid'); + assert.equal(input.validationMessage, '', 'second field should have no error'); +} diff --git a/test/unit/fixtures/dynamic/file-attachment-max-file-size.js b/test/unit/fixtures/dynamic/file-attachment-max-file-size.js new file mode 100644 index 0000000..c88dbc3 --- /dev/null +++ b/test/unit/fixtures/dynamic/file-attachment-max-file-size.js @@ -0,0 +1,48 @@ +/* eslint-env mocha */ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'fileinput-fc4d042b5e', + fieldType: 'file-input', + name: 'idProof', + visible: false, + description: '

You can upload any PDF file which is lessthan 1 MB

', + tooltip: '

Please upload any PDF file.

', + type: 'file', + required: true, + enabled: true, + readOnly: false, + maxFileSize: '1MB', + accept: [ + 'audio/*', + ' video/*', + ' image/*', + ' application/pdf', + ], + label: { + visible: false, + value: 'Identity Proof', + }, + ':type': 'forms-components-examples/components/form/fileinput', + }, + ], +}; + +export function op(block) { + const input = block.querySelector('input'); + const file1 = new File([new ArrayBuffer(2 * 1024 * 1024)], 'file1.png', { type: 'image/png' }); + const event = new Event('change', { + bubbles: true, + cancelable: true, + }); + input.files = [file1]; + input.dispatchEvent(event); +} + +export function expect(block) { + const input = block.querySelector('input'); + assert.equal(input.validity.valid, false, 'should be invalid'); + assert.equal(input.validationMessage, 'File too large. Reduce size and try again.', 'should show proper error'); +} diff --git a/test/unit/fixtures/dynamic/file-attachment-multiple.js b/test/unit/fixtures/dynamic/file-attachment-multiple.js new file mode 100644 index 0000000..cc39332 --- /dev/null +++ b/test/unit/fixtures/dynamic/file-attachment-multiple.js @@ -0,0 +1,77 @@ +/* eslint-env mocha */ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'fileinput-fc4d042b5e', + fieldType: 'file-input', + name: 'idProof', + visible: false, + description: '

You can upload any PDF file which is lessthan 1 MB

', + tooltip: '

Please upload any PDF file.

', + type: 'file[]', + required: true, + enabled: true, + minItems: 2, + maxItems: 4, + readOnly: false, + maxFileSize: '1', + accept: [ + 'audio/*', + ' video/*', + ' image/*', + ' application/pdf', + ], + label: { + visible: false, + value: 'Identity Proof', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + tooltipVisible: false, + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/all-in-one/jcr:content/guideContainer/fileinput', + }, + ':type': 'forms-components-examples/components/form/fileinput', + }, + ], +}; + +export function op(block) { + const input = block.querySelector('input'); + const fileList = block.querySelector('.files-list'); + const file1 = new File([new ArrayBuffer(1024)], 'file1.png', { type: 'image/png' }); + const file2 = new File([new ArrayBuffer(2048)], 'file2.png', { type: 'image/png' }); + const event = new Event('change', { + bubbles: true, + cancelable: true, + }); + input.files = [file1, file2]; + input.dispatchEvent(event); + assert.equal(fileList.children.length, 2, 'Should render two files'); + assert.equal(fileList.innerHTML.includes('file1.png'), true, 'Should show file1.png'); + assert.equal(fileList.innerHTML.includes('file2.png'), true, 'Should show file2.png'); + + const removeButton = fileList.querySelector('button'); + const event2 = new Event('click', { + bubbles: true, + cancelable: true, + }); + removeButton.dispatchEvent(event2); +} + +export function expect(block) { + const fileList = block.querySelector('.files-list'); + assert.equal(fileList.children.length, 1, 'Should render one file'); + assert.equal(fileList.innerHTML.includes('file1.png'), false, 'Should not show file1.png'); + assert.equal(fileList.innerHTML.includes('file2.png'), true, 'Should show file2.png'); +} diff --git a/test/unit/fixtures/dynamic/file-attachment.js b/test/unit/fixtures/dynamic/file-attachment.js new file mode 100644 index 0000000..789e028 --- /dev/null +++ b/test/unit/fixtures/dynamic/file-attachment.js @@ -0,0 +1,75 @@ +/* eslint-env mocha */ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'fileinput-fc4d042b5e', + fieldType: 'file-input', + name: 'idProof', + visible: false, + description: '

You can upload any PDF file which is lessthan 1 MB

', + tooltip: '

Please upload any PDF file.

', + type: 'file', + required: true, + enabled: true, + readOnly: false, + maxFileSize: '1MB', + accept: [ + 'audio/*', + ' video/*', + ' image/*', + ' application/pdf', + ], + label: { + visible: false, + value: 'Identity Proof', + }, + properties: { + dragDropText: 'Upload', + }, + ':type': 'forms-components-examples/components/form/fileinput', + }, + ], +}; + +export function op(block) { + const input = block.querySelector('input'); + const fileList = block.querySelector('.files-list'); + const file1 = new File([new ArrayBuffer(1024)], 'file1.png', { type: 'image/png' }); + const event = new Event('change', { + bubbles: true, + cancelable: true, + }); + input.files = [file1]; + input.dispatchEvent(event); + assert.equal(fileList.children.length, 1, 'Should render file1'); + assert.equal(fileList.innerHTML.includes('file1.png'), true, 'Should show file1.png'); + assert.equal(block.querySelector('.file-dragText').innerHTML, 'Upload', ' Should show Upload'); + + const file2 = new File([new ArrayBuffer(1024)], 'file2.png', { type: 'image/png' }); + const event2 = new Event('change', { + bubbles: true, + cancelable: true, + }); + input.files = [file2]; + input.dispatchEvent(event2); +} + +export function expect(block) { + const fileList = block.querySelector('.files-list'); + assert.equal(fileList.children.length, 1, 'Should render file2'); + assert.equal(fileList.innerHTML.includes('file1.png'), false, 'Should not show file1.png'); + assert.equal(fileList.innerHTML.includes('file2.png'), true, 'Should show file2.png'); + + const file = new File([new ArrayBuffer(1024)], 'file.png', { type: 'image/png' }); + const file1 = new File([new ArrayBuffer(1024)], 'file1.png', { type: 'image/png' }); + const event = new Event('change', { + bubbles: true, + cancelable: true, + }); + const input = block.querySelector('input'); + input.files = [file, file1]; + input.dispatchEvent(event); + assert.equal(fileList.children.length, 1, 'Should render only one file but added two file'); +} diff --git a/test/unit/fixtures/dynamic/navigateTo.js b/test/unit/fixtures/dynamic/navigateTo.js new file mode 100644 index 0000000..9536a22 --- /dev/null +++ b/test/unit/fixtures/dynamic/navigateTo.js @@ -0,0 +1,155 @@ +import assert from 'assert'; +import sinon from 'sinon'; + +export const sample = { + items: [ + { + id: 'navigateToNew', + fieldType: 'button', + name: 'navigateToNew', + visible: true, + type: 'string', + enabled: true, + label: { + visible: true, + value: 'Navigate To New Window', + }, + events: { + click: [ + "navigateTo('https://www.google.com', '_newwindow')", + ], + }, + }, + { + id: 'navigateToBlank', + fieldType: 'button', + name: 'navigateToBlank', + visible: true, + type: 'string', + enabled: true, + label: { + visible: true, + value: 'Navigate To Blank', + }, + events: { + click: [ + "navigateTo('https://www.google.com', '_blank')", + ], + }, + }, + { + id: 'navigateToParent', + fieldType: 'button', + name: 'navigateToParent', + visible: true, + type: 'string', + enabled: true, + label: { + visible: true, + value: 'Navigate To Parent', + }, + events: { + click: [ + "navigateTo('https://www.google.com', '_parent')", + ], + }, + }, + { + id: 'navigateToSelf', + fieldType: 'button', + name: 'navigateToSelf', + visible: true, + type: 'string', + enabled: true, + label: { + visible: true, + value: 'Navigate To self', + }, + events: { + click: [ + "navigateTo('https://www.google.com', '_self')", + ], + }, + }, + { + id: 'navigateToTop', + fieldType: 'button', + name: 'navigateToTop', + visible: true, + type: 'string', + enabled: true, + label: { + visible: true, + value: 'Navigate To top', + }, + events: { + click: [ + "navigateTo('https://www.google.com', '_top')", + ], + }, + }, + { + id: 'navigateToRelative', + fieldType: 'button', + name: 'navigateToRelative', + visible: true, + type: 'string', + enabled: true, + label: { + visible: true, + value: 'Navigate To relative url', + }, + events: { + click: [ + "navigateTo('/a/b/c', '_blank')", + ], + }, + }, + ], +}; + +let mock; +export function before() { + mock = sinon.fake(); +} + +export function op(block) { + global.window = Object.create(window); + Object.defineProperty(global.window, 'open', { + value: mock, + writable: true, + }); + Object.defineProperty(global.window, 'location', { + value: { + href: 'https://localhost:3000/', + }, + writable: false, + }); + let btn = block.querySelector('#navigateToNew'); + btn.click(); + + btn = block.querySelector('#navigateToBlank'); + btn.click(); + + btn = block.querySelector('#navigateToParent'); + btn.click(); + + btn = block.querySelector('#navigateToSelf'); + btn.click(); + + btn = block.querySelector('#navigateToTop'); + btn.click(); + + btn = block.querySelector('#navigateToRelative'); + btn.click(); +} + +export function expect() { + assert.equal(mock.callCount, 6, 'window.open was not called 6 times'); + assert.deepEqual(mock.getCall(0).args, ['https://www.google.com', '_blank', 'width=1000,height=800'], 'window.open was not called with blank'); + assert.deepEqual(mock.getCall(1).args, ['https://www.google.com', '_blank', null], 'window.open was not called with blank and args'); + assert.deepEqual(mock.getCall(2).args, ['https://www.google.com', '_parent', null], 'window.open was not called with parent'); + assert.deepEqual(mock.getCall(3).args, ['https://www.google.com', '_self', null], 'window.open was not called with self'); + assert.deepEqual(mock.getCall(4).args, ['https://www.google.com', '_top', null], 'window.open was not called with top'); + assert.deepEqual(mock.getCall(5).args, ['/a/b/c', '_blank', null], 'window.open was not called with relative url'); +} diff --git a/test/unit/fixtures/dynamic/number-input-blur.js b/test/unit/fixtures/dynamic/number-input-blur.js new file mode 100644 index 0000000..b2085fd --- /dev/null +++ b/test/unit/fixtures/dynamic/number-input-blur.js @@ -0,0 +1,40 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'numberinput-40db827550', + fieldType: 'number-input', + name: 'currency', + visible: true, + type: 'integer', + displayFormat: '¤#,##0.00', + required: false, + enabled: true, + readOnly: false, + default: '123', + label: { + visible: true, + value: 'Currency', + }, + }, + ], +}; + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + const inputEle = block.querySelector('#numberinput-40db827550'); + inputEle.dispatchEvent(new Event('focus')); + inputEle.value = '1234'; + inputEle.dispatchEvent(new Event('change', { bubbles: true })); + // Dispatch blur event + inputEle.dispatchEvent(new Event('blur')); +} + +export function expect(block) { + assert.equal(getValue(block, '#numberinput-40db827550'), '$1,234.00'); +} diff --git a/test/unit/fixtures/dynamic/number-input-display-value-expression-display-format.js b/test/unit/fixtures/dynamic/number-input-display-value-expression-display-format.js new file mode 100644 index 0000000..dead220 --- /dev/null +++ b/test/unit/fixtures/dynamic/number-input-display-value-expression-display-format.js @@ -0,0 +1,41 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'numberinput-40db827550', + fieldType: 'number-input', + name: 'numberInputTest', + visible: true, + type: 'integer', + displayValueExpression: '(toString($field.$value) & \'adobe\')', + displayFormat: '¤#,##0.00', + required: false, + enabled: true, + readOnly: false, + default: '123', + label: { + visible: true, + value: 'Test displayValueExpression', + }, + }, + ], +}; + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + const inputEle = block.querySelector('#numberinput-40db827550'); + inputEle.dispatchEvent(new Event('focus')); + inputEle.value = '1234'; + inputEle.dispatchEvent(new Event('change', { bubbles: true })); + // Dispatch blur event + inputEle.dispatchEvent(new Event('blur')); +} + +export function expect(block) { + assert.equal(getValue(block, '#numberinput-40db827550'), '1234adobe'); +} diff --git a/test/unit/fixtures/dynamic/number-input-display-value-expression.js b/test/unit/fixtures/dynamic/number-input-display-value-expression.js new file mode 100644 index 0000000..f308fbf --- /dev/null +++ b/test/unit/fixtures/dynamic/number-input-display-value-expression.js @@ -0,0 +1,40 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'numberinput-40db827550', + fieldType: 'number-input', + name: 'numberInputTest', + visible: true, + type: 'integer', + displayValueExpression: '(toString($field.$value) & \'adobe\')', + required: false, + enabled: true, + readOnly: false, + default: '123', + label: { + visible: true, + value: 'Test displayValueExpression', + }, + }, + ], +}; + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + const inputEle = block.querySelector('#numberinput-40db827550'); + inputEle.dispatchEvent(new Event('focus')); + inputEle.value = '1234'; + inputEle.dispatchEvent(new Event('change', { bubbles: true })); + // Dispatch blur event + inputEle.dispatchEvent(new Event('blur')); +} + +export function expect(block) { + assert.equal(getValue(block, '#numberinput-40db827550'), '1234adobe'); +} diff --git a/test/unit/fixtures/dynamic/number-input-focus.js b/test/unit/fixtures/dynamic/number-input-focus.js new file mode 100644 index 0000000..d0ff6fd --- /dev/null +++ b/test/unit/fixtures/dynamic/number-input-focus.js @@ -0,0 +1,36 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'numberinput-40db827550', + fieldType: 'number-input', + name: 'currency', + visible: true, + type: 'integer', + displayFormat: '¤#,##0.00', + required: false, + enabled: true, + readOnly: false, + default: '123', + label: { + visible: true, + value: 'Currency', + }, + }, + ], +}; + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + const inputEle = block.querySelector('#numberinput-40db827550'); + inputEle.dispatchEvent(new Event('focus')); +} + +export function expect(block) { + assert.equal(getValue(block, '#numberinput-40db827550'), '123'); +} diff --git a/test/unit/fixtures/dynamic/repeat-add-doc-based.js b/test/unit/fixtures/dynamic/repeat-add-doc-based.js new file mode 100644 index 0000000..d62f508 --- /dev/null +++ b/test/unit/fixtures/dynamic/repeat-add-doc-based.js @@ -0,0 +1,15 @@ +import assert from 'assert'; + +import { fieldDef } from '../form/enquire.js'; + +export const sample = fieldDef; + +export function op(block) { + const btn = block.querySelector('.repeat-wrapper > .item-add'); + btn.click(); +} + +export function expect(block) { + const instances = block.querySelectorAll('.repeat-wrapper > fieldset'); + assert.equal(instances.length, 2); +} diff --git a/test/unit/fixtures/dynamic/repeat-at-end.js b/test/unit/fixtures/dynamic/repeat-at-end.js new file mode 100644 index 0000000..1bca5c3 --- /dev/null +++ b/test/unit/fixtures/dynamic/repeat-at-end.js @@ -0,0 +1,46 @@ +/* eslint-env mocha */ +import assert from 'assert'; + +export const sample = { + items: [ + { + repeatable: true, + type: 'object', + fieldType: 'panel', + name: 'panel1', + minOccur: 1, + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: 'f1 & \'2\'', + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + }], + }, + { + fieldType: 'button', + id: 'btn', + name: 'btn', + events: { + click: ['addInstance(panel1)'], + }, + }, + ], +}; + +export function op(block) { + assert.equal(block.querySelectorAll('.repeat-wrapper fieldset').length, 1); + const btn = block.querySelector('#btn'); + btn.click(); + btn.click(); +} + +export function expect(block) { + assert.equal(block.querySelectorAll('.repeat-wrapper fieldset').length, 3); +} diff --git a/test/unit/fixtures/dynamic/repeat-remove-doc-based.js b/test/unit/fixtures/dynamic/repeat-remove-doc-based.js new file mode 100644 index 0000000..30d945a --- /dev/null +++ b/test/unit/fixtures/dynamic/repeat-remove-doc-based.js @@ -0,0 +1,18 @@ +import assert from 'assert'; + +import { fieldDef } from '../form/enquire.js'; + +export const sample = fieldDef; + +export function op(block) { + const repeatablePanel = block.querySelector('.repeat-wrapper'); + const addButton = repeatablePanel.querySelector('.item-add'); + addButton.click(); + const removeButton = repeatablePanel.querySelectorAll('fieldset')?.[1]?.querySelector('.item-remove'); + removeButton.click(); +} + +export function expect(block) { + const instances = block.querySelectorAll('.repeat-wrapper > fieldset'); + assert.equal(instances.length, 1); +} diff --git a/test/unit/fixtures/dynamic/repeat-remove.js b/test/unit/fixtures/dynamic/repeat-remove.js new file mode 100644 index 0000000..a1aa94d --- /dev/null +++ b/test/unit/fixtures/dynamic/repeat-remove.js @@ -0,0 +1,57 @@ +/* eslint-env mocha */ +import assert from 'assert'; + +export const sample = { + items: [ + { + repeatable: true, + type: 'object', + fieldType: 'panel', + name: 'panel1', + minOccur: 1, + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: 'f1 & \'2\'', + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + }, + { + fieldType: 'button', + id: 'remove-btn', + name: 'remove-btn', + events: { + click: ['removeInstance(panel1, $parent.$index)'], + }, + }, + ], + }, + { + fieldType: 'button', + id: 'btn', + name: 'btn', + events: { + click: ['addInstance(panel1)'], + }, + }, + ], +}; + +export function op(block) { + assert.equal(block.querySelectorAll('.repeat-wrapper fieldset').length, 1); + const btn = block.querySelector('#btn'); + btn.click(); + btn.click(); + const removeBtn = block.querySelector('#remove-btn'); + removeBtn.click(); +} + +export function expect(block) { + assert.equal(block.querySelectorAll('.repeat-wrapper fieldset').length, 3); +} diff --git a/test/unit/fixtures/dynamic/repeat-with-min-0.js b/test/unit/fixtures/dynamic/repeat-with-min-0.js new file mode 100644 index 0000000..98843b8 --- /dev/null +++ b/test/unit/fixtures/dynamic/repeat-with-min-0.js @@ -0,0 +1,46 @@ +// import assert from 'assert'; + +export const sample = { + items: [ + { + repeatable: true, + type: 'object', + fieldType: 'panel', + name: 'panel1', + minOccur: 0, + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: 'f1 & \'2\'', + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + }], + }, + { + fieldType: 'button', + id: 'btn', + name: 'btn', + events: { + click: ['addInstance(panel1)'], + }, + }, + ], +}; + +export function op(block) { + // uncomment to fail the test case + // assert.equal(block.querySelectorAll('.field-panel1 > fieldset').length, 1); + const btn = block.querySelector('#btn'); + btn.click(); +} + +export function expect() { + // uncomment to fail the test case + // assert.equal(block.querySelectorAll('.field-panel1 > fieldset').length, 2); +} diff --git a/test/unit/fixtures/dynamic/text-input-display-value-expression.js b/test/unit/fixtures/dynamic/text-input-display-value-expression.js new file mode 100644 index 0000000..bb9d435 --- /dev/null +++ b/test/unit/fixtures/dynamic/text-input-display-value-expression.js @@ -0,0 +1,39 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'textinput-40db827550', + fieldType: 'text-input', + name: 'textInputTest', + visible: true, + type: 'string', + displayValueExpression: '($field.$value & \'adobe\')', + required: false, + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'Test displayValueExpression', + }, + }, + ], +}; + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + const textEle = block.querySelector('#textinput-40db827550'); + textEle.dispatchEvent(new Event('focus')); + textEle.value = 'test'; + textEle.dispatchEvent(new Event('change', { bubbles: true })); + // Dispatch blur event + textEle.dispatchEvent(new Event('blur')); +} + +export function expect(block) { + assert.equal(getValue(block, '#textinput-40db827550'), 'testadobe'); +} diff --git a/test/unit/fixtures/dynamic/update-describe.js b/test/unit/fixtures/dynamic/update-describe.js new file mode 100644 index 0000000..fa873bf --- /dev/null +++ b/test/unit/fixtures/dynamic/update-describe.js @@ -0,0 +1,63 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'textinput-d75607c6f5', + fieldType: 'text-input', + name: 'firstname', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'firstname', + }, + events: { + change: [ + "if(contains($event.payload.changes[].propertyName, 'value'), if($field.$value == 'fname', dispatchEvent(lastname, 'custom:setProperty', {description : 'please fill lastname'}), {}), {})", + ], + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + id: 'textinput-5931f78b8c', + fieldType: 'text-input', + name: 'lastname', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'lastname', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + ], +}; + +function getValue(block, id) { + const input = block.querySelector(id); + const inputtext = input.textContent; + return inputtext; +} + +export function op(block) { + const inputEle1 = block.querySelector('#textinput-d75607c6f5'); + inputEle1.dispatchEvent(new Event('focus')); + inputEle1.value = 'fname'; + inputEle1.dispatchEvent(new Event('change', { bubbles: true })); + inputEle1.dispatchEvent(new Event('blur')); +} + +export function expect(block) { + assert.equal(getValue(block, '#textinput-5931f78b8c-description'), 'please fill lastname'); +} diff --git a/test/unit/fixtures/dynamic/update-enabled.js b/test/unit/fixtures/dynamic/update-enabled.js new file mode 100644 index 0000000..2371152 --- /dev/null +++ b/test/unit/fixtures/dynamic/update-enabled.js @@ -0,0 +1,80 @@ +/* eslint-env mocha */ +import assert from 'assert'; +import { setValue } from '../../testUtils.js'; + +export const sample = { + items: [ + { + fieldType: 'text-input', + id: 'text-input', + name: 'name', + events: { + change: [ + "if(contains($event.payload.changes[].propertyName, 'value'),[dispatchEvent(checkboxgroup, 'custom:setProperty', {enabled : false()}),dispatchEvent(radiogroup, 'custom:setProperty', {enabled : false()})],{})", + "if(contains($event.payload.changes[].propertyName, 'value'),[dispatchEvent(dropdown, 'custom:setProperty', {enabled : false()})],{})", + ], + 'custom:setProperty': [ + '$event.payload', + ], + + }, + }, + { + fieldType: 'checkbox-group', + id: 'checkbox-group', + name: 'checkboxgroup', + enum: [ + '0', + '1', + ], + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + fieldType: 'drop-down', + id: 'drop-down', + name: 'dropdown', + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + fieldType: 'radio-group', + id: 'radio-group', + name: 'radiogroup', + enum: [ + 'a', + 'b', + ], + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + ], +}; + +function getValue(block, id, fieldType, property = 'disabled') { + const fieldEl = block.querySelector(id); + // ('invoked getter for', property); + if (fieldType === 'checkbox-group' || fieldType === 'radio-group') { + const child = fieldEl.querySelector(`input[name=${fieldEl.id}]`); + return child[property]; + } + return fieldEl[property]; +} + +export function op(block) { + setValue(block, '#text-input', 'abc'); +} + +export function expect(block) { + assert.equal(getValue(block, '#checkbox-group', 'checkbox-group', 'disabled'), true); + assert.equal(getValue(block, '#drop-down', 'disabled'), true); +} diff --git a/test/unit/fixtures/dynamic/update-label.js b/test/unit/fixtures/dynamic/update-label.js new file mode 100644 index 0000000..3bf01aa --- /dev/null +++ b/test/unit/fixtures/dynamic/update-label.js @@ -0,0 +1,64 @@ +/* eslint-env mocha */ +import assert from 'assert'; +import { setValue } from '../../testUtils.js'; + +export const sample = { + items: [ + { + fieldType: 'text-input', + id: 'text-input-1', + name: 'firstName', + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'lastName', + events: { + change: [ + "if(contains($event.payload.changes[].propertyName, 'value'),[dispatchEvent(firstName, 'custom:setProperty', {label : {value : 'Updated First Name'}})],{})", + "if(contains($event.payload.changes[].propertyName, 'value'),[dispatchEvent(button, 'custom:setProperty', {label : {value : 'New Button Label'}})],{})", + ], + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + fieldType: 'button', + id: 'button', + name: 'button', + label: { + value: 'button label', + visible: true, + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + ], +}; + +function getLabel(block, id, fieldType) { + const fieldEl = block.querySelector(id); + if (fieldType === 'button') { + return fieldEl.textContent; + } + const labelEl = fieldEl.closest('.field-wrapper').querySelector('.field-label'); + return labelEl.textContent; +} + +export function op(block) { + setValue(block, '#text-input-2', 'abc'); // change last name +} + +export function expect(block) { + assert.equal(getLabel(block, '#text-input-1'), 'Updated First Name'); // firstName's label (create new) + assert.equal(getLabel(block, '#button', 'button'), 'New Button Label'); // button's label (update existing) +} diff --git a/test/unit/fixtures/dynamic/update-readonly.js b/test/unit/fixtures/dynamic/update-readonly.js new file mode 100644 index 0000000..40a5ebf --- /dev/null +++ b/test/unit/fixtures/dynamic/update-readonly.js @@ -0,0 +1,78 @@ +/* eslint-env mocha */ +import assert from 'assert'; +import { setValue } from '../../testUtils.js'; + +export const sample = { + items: [ + { + fieldType: 'text-input', + id: 'text-input', + name: 'name', + events: { + change: [ + "if(contains($event.payload.changes[].propertyName, 'value'),[dispatchEvent(checkboxgroup, 'custom:setProperty', {readOnly : true()}),dispatchEvent(radiogroup, 'custom:setProperty', {readOnly : true()})],{})", + "if(contains($event.payload.changes[].propertyName, 'value'),[dispatchEvent(dropdown, 'custom:setProperty', {readOnly : true()})],{})", + ], + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + fieldType: 'checkbox-group', + id: 'checkbox-group', + name: 'checkboxgroup', + enum: [ + '0', + '1', + ], + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + fieldType: 'drop-down', + id: 'drop-down', + name: 'dropdown', + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + fieldType: 'radio-group', + id: 'radio-group', + name: 'radiogroup', + enum: [ + 'a', + 'b', + ], + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + ], +}; + +function getValue(block, id, fieldType, property = 'disabled') { + const fieldEl = block.querySelector(id); + if (fieldType === 'checkbox-group' || fieldType === 'radio-group') { + const child = fieldEl.querySelector(`input[name=${fieldEl.id}]`); + return child[property]; + } + return fieldEl[property]; +} + +export function op(block) { + setValue(block, '#text-input', 'abc'); +} + +export function expect(block) { + assert.equal(getValue(block, '#checkbox-group', 'checkbox-group', 'disabled'), true); + assert.equal(getValue(block, '#drop-down', 'disabled'), true); +} diff --git a/test/unit/fixtures/dynamic/update-text-input-disable.js b/test/unit/fixtures/dynamic/update-text-input-disable.js new file mode 100644 index 0000000..218a354 --- /dev/null +++ b/test/unit/fixtures/dynamic/update-text-input-disable.js @@ -0,0 +1,56 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'textinput-23810dfb63', + fieldType: 'text-input', + name: 'firstname', + visible: true, + type: 'string', + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'firstname', + }, + events: { + change: [ + "if(contains($event.payload.changes[].propertyName, 'value'), if($field.$value == 'fname', dispatchEvent(lastname, 'custom:setProperty', {enabled : false()}), {}), {})", + ], + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + id: 'textinput-8738b2081c', + fieldType: 'text-input', + name: 'lastname', + type: 'string', + label: { + value: 'Text Input', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + ], +}; +function getValue(block, id, fieldType, property = 'disabled') { + const fieldEl = block.querySelector(id); + return fieldEl[property]; +} +export function op(block) { + const inputEle1 = block.querySelector('#textinput-23810dfb63'); + inputEle1.dispatchEvent(new Event('focus')); + inputEle1.value = 'fname'; + inputEle1.dispatchEvent(new Event('change', { bubbles: true })); + inputEle1.dispatchEvent(new Event('blur')); +} + +export function expect(block) { + assert.equal(getValue(block, '#textinput-8738b2081c', 'disabled'), true); +} diff --git a/test/unit/fixtures/dynamic/update-text-input-enable.js b/test/unit/fixtures/dynamic/update-text-input-enable.js new file mode 100644 index 0000000..c47a9d7 --- /dev/null +++ b/test/unit/fixtures/dynamic/update-text-input-enable.js @@ -0,0 +1,58 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'textinput-44e6227ac3', + fieldType: 'text-input', + name: 'firstname', + visible: true, + type: 'string', + enabled: true, + label: { + visible: true, + value: 'firstname', + }, + events: { + change: [ + "if(contains($event.payload.changes[].propertyName, 'value'), if($field.$value == 'textinput', dispatchEvent(lastname, 'custom:setProperty', {readOnly : true()}), {}), {})", + ], + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + { + id: 'textinput-5dc4ea1da3', + fieldType: 'text-input', + name: 'lastname', + type: 'string', + enabled: true, + readOnly: false, + label: { + visible: true, + value: 'Text Input', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + }, + ], +}; +function getValue(block, id, fieldType, property = 'disabled') { + const fieldEl = block.querySelector(id); + return fieldEl[property]; +} +export function op(block) { + const inputEle1 = block.querySelector('#textinput-44e6227ac3'); + inputEle1.dispatchEvent(new Event('focus')); + inputEle1.value = 'textinput'; + inputEle1.dispatchEvent(new Event('change', { bubbles: true })); + inputEle1.dispatchEvent(new Event('blur')); +} + +export function expect(block) { + assert.equal(getValue(block, '#textinput-5dc4ea1da3', 'disabled'), true); +} diff --git a/test/unit/fixtures/dynamic/update-value-cb-group.js b/test/unit/fixtures/dynamic/update-value-cb-group.js new file mode 100644 index 0000000..6f03828 --- /dev/null +++ b/test/unit/fixtures/dynamic/update-value-cb-group.js @@ -0,0 +1,54 @@ +import assert from 'assert'; + +export const sample = { + items: [{ + fieldType: 'checkbox-group', + id: 'cb-id', + name: 'cb', + enum: [0, 1, 2], + default: [1], + }, + { + fieldType: 'text-input', + id: 'text-input', + name: 'f1', + rules: { + value: 'join(\',\', cb.$value)', + }, + }, + { + fieldType: 'number-input', + id: 'number-input-f2', + name: 'f2', + }, + { + fieldType: 'checkbox-group', + id: 'cb-id-2', + name: 'cb2', + enum: [true, false], + rules: { + value: 'if(f2 > 2, true(), false())', + }, + }], +}; + +export function op(block) { + assert.equal(block.querySelector('#cb-1').checked, true, 'checkbox 1 is selected'); + assert.equal(block.querySelector('#text-input').value, '1', 'text-input initial value is 1'); + assert.equal(block.querySelector('#cb2-1').checked, true, 'second checkbox (false) is selected'); + const radio = block.querySelector('#cb-2'); + radio.click(); + radio.dispatchEvent(new Event('change', { bubbles: true })); + const numberInput = block.querySelector('#number-input-f2'); + numberInput.click(); + numberInput.value = 3; + numberInput.dispatchEvent(new Event('change', { bubbles: true })); +} + +export function expect(block) { + assert.equal(block.querySelector('#cb-1').checked, true, 'checkbox 1 is still selected'); + assert.equal(block.querySelector('#cb-2').checked, true, 'checkbox 2 is also selected'); + assert.equal(block.querySelector('#text-input').value, '1,2', 'text-input value is 1,2 now'); + assert.equal(block.querySelector('#cb2').checked, true, 'second checkbox (true) is not selected'); + assert.equal(block.querySelector('#cb2-1').checked, false, 'second checkbox (false) is selected'); +} diff --git a/test/unit/fixtures/dynamic/update-value-cb.js b/test/unit/fixtures/dynamic/update-value-cb.js new file mode 100644 index 0000000..923dbdd --- /dev/null +++ b/test/unit/fixtures/dynamic/update-value-cb.js @@ -0,0 +1,27 @@ +import assert from 'assert'; + +export const sample = { + items: [{ + fieldType: 'checkbox', + id: 'checkbox-dynamic-test', + name: 'checkbox-dynamic-test', + enum: ['on'], + label: { + value: 'Checkbox dynamic test', + }, + }], +}; + +export function op(block) { + const cb = block.querySelector('#checkbox-dynamic-test'); + cb.click(); + cb.dispatchEvent(new Event('change', { bubbles: true })); // switch on + cb.click(); + cb.dispatchEvent(new Event('change', { bubbles: true })); // switch off +} + +export function expect(block) { + const cb = block.querySelector('#checkbox-dynamic-test'); + assert.equal(cb.checked, false, 'checkbox is off'); + assert.equal(cb.value, 'on'); +} diff --git a/test/unit/fixtures/dynamic/update-value-radio.js b/test/unit/fixtures/dynamic/update-value-radio.js new file mode 100644 index 0000000..239fc3d --- /dev/null +++ b/test/unit/fixtures/dynamic/update-value-radio.js @@ -0,0 +1,57 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'radiobutton-b8bdfa4485', + fieldType: 'radio-group', + name: 'propertyLocation', + type: 'string', + enumNames: [ + 'Inside the U.S.', + 'Outside the U.S.', + ], + label: { + value: 'Is this transaction regarding a property located in the United States?', + }, + enum: [ + '0', + '1', + ], + ':type': 'forms-components-examples/components/form/radiobutton', + }, + { + id: 'dropdown-1c1b7aadd1', + fieldType: 'drop-down', + name: 'transactionType', + type: 'string', + enumNames: [ + 'Transaction 1', + 'Transaction 2', + ], + label: { + value: 'Transaction Type', + }, + rules: { + visible: "!(propertyLocation.$value == '1')", + }, + enum: [ + '0', + '1', + ], + placeholder: 'Select a Transaction', + ':type': 'forms-components-examples/components/form/dropdown', + }, + ], +}; + +export function op(block) { + assert.strictEqual(block.querySelector('#dropdown-1c1b7aadd1').dataset.visible, undefined); + const radio = block.querySelector('#radiobutton-b8bdfa4485').querySelectorAll('input')[1]; + radio.click(); + radio.dispatchEvent(new Event('change', { bubbles: true })); +} + +export function expect(block) { + assert.equal(block.querySelector('#dropdown-1c1b7aadd1').closest('.field-wrapper').dataset.visible, 'false'); +} diff --git a/test/unit/fixtures/dynamic/update-value-same-dataref-cbg.js b/test/unit/fixtures/dynamic/update-value-same-dataref-cbg.js new file mode 100644 index 0000000..c9483ec --- /dev/null +++ b/test/unit/fixtures/dynamic/update-value-same-dataref-cbg.js @@ -0,0 +1,48 @@ +import assert from 'assert'; + +// checkbox group with same name should affect other checkbox group +// if they have same dataRef + +export const sample = { + items: [ + { + id: 'p1', + fieldType: 'panel', + items: [{ + id: 'cb1id', + fieldType: 'checkbox-group', + name: 'cb1-uv', + type: 'number[]', + enum: [0, 1], + }], + }, + { + id: 'cb1id2', + fieldType: 'checkbox-group', + name: 'cb1-uv', + visible: true, + type: 'number[]', + enumNames: [ + 'Item 1', + 'Item 2', + ], + label: { + value: 'Check Box Group', + }, + enum: [ + 0, + 1, + ], + }], +}; + +export function op(block) { + const radio = block.querySelector('#cb1-uv'); + radio.click(); + radio.dispatchEvent(new Event('change', { bubbles: true })); +} + +export function expect(block) { + assert.equal(block.querySelector('#cb1-uv-2').checked, true, 'checkbox in other group is selected'); + assert.equal(block.querySelector('#cb1-uv-3').checked, false, 'checkbox in other group is selected'); +} diff --git a/test/unit/fixtures/dynamic/update-value-same-dataref-rbg.js b/test/unit/fixtures/dynamic/update-value-same-dataref-rbg.js new file mode 100644 index 0000000..8a115e8 --- /dev/null +++ b/test/unit/fixtures/dynamic/update-value-same-dataref-rbg.js @@ -0,0 +1,45 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'p1', + fieldType: 'panel', + items: [{ + id: 'rb1id', + fieldType: 'radio-group', + name: 'rb1-uv', + type: 'number[]', + enum: [0, 1], + }], + }, + { + id: 'rb1id2', + fieldType: 'radio-group', + name: 'rb1-uv', + visible: true, + type: 'number[]', + enumNames: [ + 'Item 1', + 'Item 2', + ], + label: { + value: 'Check Box Group', + }, + enum: [ + 0, + 1, + ], + }], +}; + +export function op(block) { + const radio = block.querySelector('#rb1-uv'); + radio.click(); + radio.dispatchEvent(new Event('change', { bubbles: true })); +} + +export function expect(block) { + assert.equal(block.querySelector('#rb1-uv-2').checked, true, 'checkbox in other group is selected'); + assert.equal(block.querySelector('#rb1-uv-3').checked, false, 'checkbox in other group is selected'); +} diff --git a/test/unit/fixtures/dynamic/update-value-same-name-cbg.js b/test/unit/fixtures/dynamic/update-value-same-name-cbg.js new file mode 100644 index 0000000..dfeb105 --- /dev/null +++ b/test/unit/fixtures/dynamic/update-value-same-name-cbg.js @@ -0,0 +1,53 @@ +import assert from 'assert'; + +// checkbox group with same name should not affect other checkbox group +// if they have different dataRef + +export const sample = { + items: [ + { + id: 'p1', + fieldType: 'panel', + name: 'panel1', + type: 'object', + items: [{ + id: 'cb1id', + fieldType: 'checkbox-group', + name: 'cb1-same-name', + enum: [0, 1], + enumNames: [ + 'Item 1', + 'Item 2', + ], + }], + }, + { + id: 'cb1id2', + fieldType: 'checkbox-group', + name: 'cb1-same-name', + visible: true, + type: 'number[]', + enumNames: [ + 'Item 1', + 'Item 2', + ], + label: { + value: 'Check Box Group', + }, + enum: [ + 0, + 1, + ], + }], +}; + +export function op(block) { + const radio = block.querySelector('#cb1-same-name'); + radio.click(); + radio.dispatchEvent(new Event('change', { bubbles: true })); +} + +export function expect(block) { + assert.equal(block.querySelector('#cb1-same-name-2').checked, false, 'checkbox in other group is not selected'); + assert.equal(block.querySelector('#cb1-same-name-3').checked, false, 'checkbox in other group is not selected'); +} diff --git a/test/unit/fixtures/dynamic/update-value-same-name-rbg.js b/test/unit/fixtures/dynamic/update-value-same-name-rbg.js new file mode 100644 index 0000000..f9c817b --- /dev/null +++ b/test/unit/fixtures/dynamic/update-value-same-name-rbg.js @@ -0,0 +1,50 @@ +import assert from 'assert'; + +export const sample = { + items: [ + { + id: 'p1', + fieldType: 'panel', + name: 'panel1', + type: 'object', + items: [{ + id: 'rb1id', + fieldType: 'radio-group', + name: 'rb1-same-name', + enum: [0, 1], + enumNames: [ + 'Item 1', + 'Item 2', + ], + }], + }, + { + id: 'rb1id2', + fieldType: 'radio-group', + name: 'rb1-same-name', + visible: true, + type: 'number[]', + enumNames: [ + 'Item 1', + 'Item 2', + ], + label: { + value: 'Check Box Group', + }, + enum: [ + 0, + 1, + ], + }], +}; + +export function op(block) { + const radio = block.querySelector('#rb1-same-name'); + radio.click(); + radio.dispatchEvent(new Event('change', { bubbles: true })); +} + +export function expect(block) { + assert.equal(block.querySelector('#rb1-same-name-2').checked, false, 'radio in other group is not selected'); + assert.equal(block.querySelector('#rb1-same-name-3').checked, false, 'radio in other group is not selected'); +} diff --git a/test/unit/fixtures/dynamic/update-value-same-name.js b/test/unit/fixtures/dynamic/update-value-same-name.js new file mode 100644 index 0000000..fadcb3b --- /dev/null +++ b/test/unit/fixtures/dynamic/update-value-same-name.js @@ -0,0 +1,38 @@ +import assert from 'assert'; + +export const sample = { + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + }, + { + fieldType: 'text-input', + id: 'text-input-3', + name: 'f2', + }], +}; + +function setValue(block, id, value) { + const input = block.querySelector(id); + input.value = value; + input.dispatchEvent(new Event('change', { bubbles: true })); +} + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + setValue(block, '#text-input', '5'); +} + +export function expect(block) { + assert.equal(getValue(block, '#text-input-3'), '5'); +} diff --git a/test/unit/fixtures/dynamic/update-value.js b/test/unit/fixtures/dynamic/update-value.js new file mode 100644 index 0000000..0104111 --- /dev/null +++ b/test/unit/fixtures/dynamic/update-value.js @@ -0,0 +1,31 @@ +import assert from 'assert'; +import { setValue } from '../../testUtils.js'; + +export const sample = { + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: 'f1 & \'2\'', + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + }], +}; + +function getValue(block, id, property = 'value') { + const input = block.querySelector(id); + return input[property]; +} + +export function op(block) { + setValue(block, '#text-input-2', '5'); +} + +export function expect(block) { + assert.equal(getValue(block, '#text-input'), '52'); +} diff --git a/test/unit/fixtures/dynamic/wizard-menu-item.js b/test/unit/fixtures/dynamic/wizard-menu-item.js new file mode 100644 index 0000000..dcb657d --- /dev/null +++ b/test/unit/fixtures/dynamic/wizard-menu-item.js @@ -0,0 +1,337 @@ +import assert from 'assert'; + +export const sample = { + id: 'L2NvbnRlbnQvZm9ybXMvYWYvd2l6YXJkLXdpdGgtbWVudQ==', + fieldType: 'form', + title: 'wizard with menu', + lang: 'en', + action: '/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvd2l6YXJkLXdpdGgtbWVudQ==', + properties: { + 'fd:dor': { + dorType: 'none', + }, + 'fd:path': '/content/forms/af/wizard-with-menu/jcr:content/guideContainer', + 'fd:schemaType': 'BASIC', + 'fd:formDataEnabled': false, + }, + columnClassNames: { + wizard: 'aem-GridColumn aem-GridColumn--default--12', + }, + gridClassNames: 'aem-Grid aem-Grid--12 aem-Grid--default--12', + columnCount: 12, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':itemsOrder': [ + 'wizard', + ], + metadata: { + grammar: 'json-formula-1.0.0', + version: '1.0.0', + }, + adaptiveform: '0.12.1', + ':items': { + wizard: { + id: 'wizard-208196f3a7', + fieldType: 'panel', + name: 'wizard1708942357733', + visible: true, + description: '

Description

\r\n', + enabled: true, + repeatable: false, + readOnly: false, + label: { + visible: true, + value: 'Wizard', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'fd:dor': { + dorExclusion: false, + dorExcludeTitle: false, + dorExcludeDescription: false, + }, + 'fd:path': '/content/forms/af/wizard-with-menu/jcr:content/guideContainer/wizard', + }, + ':itemsOrder': [ + 'panelcontainer', + 'panelcontainer_760408194', + 'panelcontainer_105587269', + ], + ':items': { + panelcontainer: { + id: 'panelcontainer-17b7de03b4', + fieldType: 'panel', + name: 'panelcontainer1708942364313', + visible: true, + enabled: true, + repeatable: false, + readOnly: false, + columnClassNames: { + button: 'aem-GridColumn aem-GridColumn--default--12', + textinput: 'aem-GridColumn aem-GridColumn--default--12', + }, + gridClassNames: 'aem-Grid aem-Grid--12 aem-Grid--default--12', + columnCount: 12, + label: { + visible: true, + value: 'Panel 1', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'fd:dor': { + dorExclusion: false, + dorExcludeTitle: false, + dorExcludeDescription: false, + }, + 'fd:path': '/content/forms/af/wizard-with-menu/jcr:content/guideContainer/wizard/panelcontainer', + }, + ':itemsOrder': [ + 'textinput', + 'button', + ], + appliedCssClassNames: 'model', + ':items': { + textinput: { + id: 'textinput-38918fdb29', + fieldType: 'text-input', + name: 'textinput1708942379003', + type: 'string', + label: { + value: 'Text Input', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/wizard-with-menu/jcr:content/guideContainer/wizard/panelcontainer/textinput', + }, + ':type': 'forms-components-examples/components/form/textinput', + }, + button: { + id: 'showPanelButtonId', + fieldType: 'button', + name: 'button1708942440251', + type: 'string', + buttonType: 'button', + properties: { + 'fd:dor': { + dorExclusion: true, + }, + 'fd:path': '/content/forms/af/wizard-with-menu/jcr:content/guideContainer/wizard/panelcontainer/button', + 'fd:rules': { + validationStatus: 'valid', + }, + 'fd:buttonType': 'button', + }, + label: { + value: 'Show panel 3', + }, + events: { + click: [ + "dispatchEvent($form.wizard1708942357733.panelcontainer_1055872691708942373716, 'custom:setProperty', {visible : true()})", + ], + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'forms-components-examples/components/form/button', + }, + }, + ':type': 'forms-components-examples/components/form/panelcontainer', + allowedComponents: { + components: [], + applicable: false, + }, + }, + panelcontainer_760408194: { + id: 'panelcontainer-57f2f9bd13', + fieldType: 'panel', + name: 'panelcontainer_7604081941708942368411', + visible: true, + enabled: true, + repeatable: false, + readOnly: false, + columnClassNames: { + radiobutton: 'aem-GridColumn aem-GridColumn--default--12', + }, + gridClassNames: 'aem-Grid aem-Grid--12 aem-Grid--default--12', + columnCount: 12, + label: { + visible: true, + value: 'Panel 2', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'fd:dor': { + dorExclusion: false, + dorExcludeTitle: false, + dorExcludeDescription: false, + }, + 'fd:path': '/content/forms/af/wizard-with-menu/jcr:content/guideContainer/wizard/panelcontainer_760408194', + }, + ':itemsOrder': [ + 'radiobutton', + ], + appliedCssClassNames: 'model', + ':items': { + radiobutton: { + id: 'radiobutton-c4b771a8be', + fieldType: 'radio-group', + name: 'radiobutton1708942388803', + type: 'string', + enforceEnum: true, + enumNames: [ + 'Item 1', + 'Item 2', + ], + label: { + value: 'Radio Button', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + orientation: 'horizontal', + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/wizard-with-menu/jcr:content/guideContainer/wizard/panelcontainer_760408194/radiobutton', + }, + enum: [ + '0', + '1', + ], + ':type': 'forms-components-examples/components/form/radiobutton', + }, + }, + ':type': 'forms-components-examples/components/form/panelcontainer', + allowedComponents: { + components: [], + applicable: false, + }, + }, + panelcontainer_105587269: { + id: 'panelcontainer-c9434ae14a', + fieldType: 'panel', + name: 'panelcontainer_1055872691708942373716', + visible: false, + enabled: true, + repeatable: false, + readOnly: false, + columnClassNames: { + checkboxgroup: 'aem-GridColumn aem-GridColumn--default--12', + }, + gridClassNames: 'aem-Grid aem-Grid--12 aem-Grid--default--12', + columnCount: 12, + label: { + visible: true, + value: 'Panel 3', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'fd:dor': { + dorExclusion: false, + dorExcludeTitle: false, + dorExcludeDescription: false, + }, + 'fd:path': '/content/forms/af/wizard-with-menu/jcr:content/guideContainer/wizard/panelcontainer_105587269', + }, + ':itemsOrder': [ + 'checkboxgroup', + ], + appliedCssClassNames: 'model', + ':items': { + checkboxgroup: { + id: 'checkboxgroup-4b0c7cce2c', + fieldType: 'checkbox-group', + name: 'checkboxgroup1708942405166', + type: 'number[]', + enforceEnum: true, + enumNames: [ + 'Item 1', + ' Item 2', + ], + label: { + value: 'Check Box Group', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'afs:layout': { + orientation: 'horizontal', + }, + 'fd:dor': { + dorExclusion: false, + }, + 'fd:path': '/content/forms/af/wizard-with-menu/jcr:content/guideContainer/wizard/panelcontainer_105587269/checkboxgroup', + }, + enum: [ + 0, + 1, + ], + ':type': 'forms-components-examples/components/form/checkboxgroup', + }, + }, + ':type': 'forms-components-examples/components/form/panelcontainer', + allowedComponents: { + components: [], + applicable: false, + }, + }, + }, + ':type': 'forms-components-examples/components/form/wizard', + }, + }, + ':type': 'forms-components-examples/components/form/container', +}; + +export function op(block) { + const allItems = block.querySelectorAll('.wizard-menu-item'); + assert.equal(allItems.length, 3); + const hideItems = Array.from(allItems).filter((item) => item.dataset.visible === 'false'); + assert.equal(hideItems.length, 1); + const showPanelButton = block.querySelector('#showPanelButtonId'); + showPanelButton.click(); + + // const backButton = block.querySelector('.wizard-button-prev'); + // const displayValue = document.defaultView.getComputedStyle(backButton).display; + // assert.equal(displayValue, 'none'); +} + +export function expect(block) { + const allItems = block.querySelectorAll('.wizard-menu-item'); + assert.equal(allItems.length, 3); + const hideItems = Array.from(allItems).filter((item) => item.dataset.visible === 'false'); + assert.equal(hideItems.length, 0); +} diff --git a/test/unit/fixtures/dynamic/wizard-next.js b/test/unit/fixtures/dynamic/wizard-next.js new file mode 100644 index 0000000..54a2f49 --- /dev/null +++ b/test/unit/fixtures/dynamic/wizard-next.js @@ -0,0 +1,20 @@ +import assert from 'assert'; + +import { fieldDef } from '../form/claim.js'; + +export const sample = fieldDef; + +export function op(block) { + const btn = block.querySelector('.wizard .field-next'); + btn.click(); +} + +export function expect(block) { + const instances = block.querySelectorAll('.wizard > fieldset'); + const step1 = instances?.[0]; + assert.equal(step1?.dataset.index, 0); + assert.equal(step1?.classList.contains('current-wizard-step'), false); + const step2 = instances?.[1]; + assert.equal(step2?.dataset.index, 1); + assert.equal(step2?.classList.contains('current-wizard-step'), true); +} diff --git a/test/unit/fixtures/form-fetch/basic-fetch-html.js b/test/unit/fixtures/form-fetch/basic-fetch-html.js new file mode 100644 index 0000000..6047229 --- /dev/null +++ b/test/unit/fixtures/form-fetch/basic-fetch-html.js @@ -0,0 +1,68 @@ +import assert from 'assert'; +import { fetchForm } from '../../../../blocks/form/form.js'; + +function escapeHTML(str) { + return (str.replace(/[&<>'"]/g, (tag) => ({ + '&': '&', + '<': '<', + '>': '>', + "'": ''', + '"': '"', + }[tag]))); +} + +const data = { + id: 'someid', + items: [ + { + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: 'f1', + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + }, + { + fieldType: 'button', + id: 'button', + events: { + click: 'submitForm()', + }, + }, + ], +}; + +const doc = `
${escapeHTML(JSON.stringify(data))}
`; + +export function before() { + const headers = new Headers(); + headers.set('Content-Type', 'application/json'); + + global.fetch.mockData['http://localhost:3000/adobe/forms/myform.html'] = { + headers, + data: {}, + }; + global.fetch.mockData['http://localhost:3000/adobe/forms/myform.md.html'] = { + headers, + data: doc, + }; +} + +export const formPath = 'http://localhost:3000/adobe/forms/myform.html'; + +export async function expect() { + assert.equal(await fetchForm(formPath), escapeHTML(JSON.stringify(data))); +} + +export function after() { + global.fetch.mockData = {}; +} + +export function op() {} + +export const opDelay = 100; diff --git a/test/unit/fixtures/form/blockCollectionForm.html b/test/unit/fixtures/form/blockCollectionForm.html new file mode 100644 index 0000000..1cdeba3 --- /dev/null +++ b/test/unit/fixtures/form/blockCollectionForm.html @@ -0,0 +1,102 @@ +
+

Please Enter Your Information

+
+
+

It is being collected for research purposes

+
+
+ + +
+
+ + +
+
+ + +
+
+ Names +
+
+ + +
+
+ + +
+
+ Person Type +
+
+

What is the nature of your inquiry?

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+ +
+
+ +
\ No newline at end of file diff --git a/test/unit/fixtures/form/blockCollectionForm.js b/test/unit/fixtures/form/blockCollectionForm.js new file mode 100644 index 0000000..b70e986 --- /dev/null +++ b/test/unit/fixtures/form/blockCollectionForm.js @@ -0,0 +1,46 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + total: 19, + offset: 0, + limit: 19, + data: [{ + Name: 'headline', Type: 'heading', Label: '', Placeholder: '', Value: 'Please Enter Your Information', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: '', + }, { + Name: 'description', Type: 'plaintext', Label: 'It is being collected for research purposes', Placeholder: '', Value: '', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: '', + }, { + Name: 'First Name', Type: 'text', Label: 'Given Name', Placeholder: 'e.g. Joe', Value: '', Options: '', Mandatory: 'x', Style: '', ID: '', Fieldset: 'name', + }, { + Name: 'Middle Initial', Type: 'text', Label: 'Middle Initial', Placeholder: '', Value: '', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: 'name', + }, { + Name: 'Last Name', Type: 'text', Label: 'Surname', Placeholder: 'e.g. Doe', Value: '', Options: '', Mandatory: 'true', Style: '', ID: '', Fieldset: 'name', + }, { + Name: 'name1', Type: 'fieldset', Label: 'Names', Placeholder: '', Value: '', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: '', + }, { + Name: 'personType', Type: 'radio', Label: 'Employee', Placeholder: '', Value: '', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: 'personTypeFS', + }, { + Name: 'personType', Type: 'radio', Label: 'Public', Placeholder: '', Value: '', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: 'personTypeFS', + }, { + Name: 'personTypeFS', Type: 'fieldset', Label: 'Person Type', Placeholder: '', Value: '', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: '', + }, { + Name: 'natureOfInquiry', Type: 'heading', Label: '', Placeholder: '', Value: 'What is the nature of your inquiry?', Options: '', Mandatory: '', Style: 'sub-heading', ID: '', Fieldset: '', + }, { + Name: 'robots', Type: 'checkbox', Label: 'Robots', Placeholder: '', Value: '', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: '', + }, { + Name: 'monsters', Type: 'checkbox', Label: 'Monsters', Placeholder: '', Value: '', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: '', + }, { + Name: 'somethingElse', Type: 'checkbox', Label: 'Something Else?', Placeholder: '', Value: '', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: '', + }, { + Name: 'somethingElseDetails', Type: 'text-area', Label: 'If Something Else, Tell Us More', Placeholder: '', Value: '', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: '', + }, { + Name: 'toggleExamples', Type: 'toggle', Label: 'Toggle Something', Placeholder: '', Value: 'yes', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: '', + }, { + Name: 'select', Type: 'select', Label: 'Country', Placeholder: '', Value: 'Germany', Options: 'USA, Germany, Switzerland, Canada', Mandatory: '', Style: '', ID: '', Fieldset: '', + }, { + Name: 'extetrnalSelect', Type: 'select', Label: 'State', Placeholder: 'Select A State...', Value: '', Options: 'https://main--helix-block-collection--adobe.hlx.page/block-collection/form.json?sheet=states', Mandatory: '', Style: '', ID: '', Fieldset: '', + }, { + Name: 'apply', Type: 'submit', Label: '', Placeholder: '', Value: '', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: '', + }, { + Name: '', Type: 'confirmation', Label: '', Placeholder: '', Value: 'https://main--helix-block-collection--adobe.hlx.live/block-collection/form-thank-you', Options: '', Mandatory: '', Style: '', ID: '', Fieldset: '', + }], + ':type': 'sheet', +}; diff --git a/test/unit/fixtures/form/claim.html b/test/unit/fixtures/form/claim.html new file mode 100644 index 0000000..bd781b9 --- /dev/null +++ b/test/unit/fixtures/form/claim.html @@ -0,0 +1,320 @@ +
+
    +
  • Your Contact Information
  • +
  • Insured Property Information (if different from above)
  • +
  • Alt. Contact Information (person to respond to if different from above)
  • +
  • Policy Information
  • +
  • + +
+
+ Your Contact Information +
+
+
+ +
+
+
+
+
+
+
+ +
+
+
Drag and Drop To Upload
+ + +
+
+
+
+
+ Insured Property Information (if different from + above) +
+
+
+ +
+
+
+
+
+
+ Alt. Contact Information (person to respond to if + different from above) +
+
+
+ +
+
+
+
+
+
+ Policy Information +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/test/unit/fixtures/form/claim.js b/test/unit/fixtures/form/claim.js new file mode 100644 index 0000000..e895881 --- /dev/null +++ b/test/unit/fixtures/form/claim.js @@ -0,0 +1,975 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + id: 'L2NvbnRlbnQvZm9ybXMvYWYvQ2xhaW1zX0Zvcm0xNzAxOTMyNDE5MjIw', + fieldType: 'form', + title: 'Claims Form', + lang: 'en', + action: '/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvQ2xhaW1zX0Zvcm0xNzAxOTMyNDE5MjIw', + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + adaptiveform: '0.12.1', + metadata: { + version: '1.0.0', + grammar: 'json-formula-1.0.0', + }, + items: [ + { + id: 'wizard-023f2d9d1a', + fieldType: 'panel', + name: 'tabsOnTop1701932611697', + label: { + value: '', + visible: false, + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/wizard', + items: [ + { + id: 'panelcontainer-067e2c88e1', + fieldType: 'panel', + name: 'panel_1', + label: { + value: 'Your Contact Information', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/panelcontainer', + items: [ + { + id: 'textinput-5d4030d204', + fieldType: 'text-input', + name: 'Your_Name1701932419743', + type: 'string', + label: { + value: 'Your Name', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-565b8216b3', + fieldType: 'text-input', + name: 'Your_Address1701932419744', + type: 'string', + label: { + value: 'Your Address', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-61988223e7', + fieldType: 'text-input', + name: 'Your_City1701932419744', + type: 'string', + label: { + value: 'Your City', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'dropdown-d741ae7efa', + fieldType: 'drop-down', + name: 'Your_State1701932419744', + type: 'string[]', + enforceEnum: true, + enumNames: [ + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'District of Columbia', + 'Florida', + 'Georgia', + 'Guam', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Puerto Rico', + 'Rhode Island', + 'Saipan', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virgin Islands', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', + ], + placeholder: 'Select a State', + label: { + value: 'Your State', + visible: true, + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + enum: [ + 'AL', + 'AK', + 'AZ', + 'AR', + 'CA', + 'CO', + 'CT', + 'DE', + 'DC', + 'FL', + 'GA', + 'GU', + 'HI', + 'ID', + 'IL', + 'IN', + 'IA', + 'KS', + 'KY', + 'LA', + 'ME', + 'MD', + 'MA', + 'MI', + 'MN', + 'MS', + 'MO', + 'MT', + 'NE', + 'NV', + 'NH', + 'NJ', + 'NM', + 'NY', + 'NC', + 'ND', + 'OH', + 'OK', + 'OR', + 'PA', + 'PR', + 'RI', + 'MP', + 'SC', + 'SD', + 'TN', + 'TX', + 'UT', + 'VT', + 'VI', + 'VA', + 'WA', + 'WV', + 'WI', + 'WY', + ], + ':type': 'formsninja/components/adaptiveForm/dropdown', + }, + { + id: 'numberinput-514f9eef52', + fieldType: 'number-input', + name: 'Your_Zip_Postal_Code1701932419750', + type: 'number', + label: { + value: 'Your Zip/Postal Code', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/numberinput', + }, + { + id: 'textinput-c9d3b41b38', + fieldType: 'text-input', + name: 'Your_Province_Region1701932419750', + type: 'string', + label: { + value: 'Your Province/Region (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-e0fca0afc8', + fieldType: 'text-input', + name: 'Your_Country1701932419751', + type: 'string', + label: { + value: 'Your Country', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-5a8ff8edd6', + fieldType: 'text-input', + name: 'Your_Phone_Number1701932419751', + type: 'string', + label: { + value: 'Your Phone Number', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-57fd75ed71', + fieldType: 'text-input', + name: 'Your_Fax_Number1701932419751', + type: 'string', + label: { + value: 'Your Fax Number (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-93659472e1', + fieldType: 'number-input', + name: 'Your_E_mail_Address1701932419752', + type: 'string', + label: { + value: 'Your E-mail Address', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'fileinput-9d805e9eca', + fieldType: 'file-input', + name: 'fileinput1713343980571', + type: 'file', + accept: [ + 'audio/*', + ' video/*', + ' image/*', + ' text/*', + ' application/pdf', + ], + properties: { + dragDropText: 'Drag and Drop To Upload', + }, + label: { + value: 'Your ID', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/fileinput', + }, + ], + }, + { + id: 'panelcontainer-eff9c5f6ff', + fieldType: 'panel', + name: 'panel_2', + label: { + value: 'Insured Property Information (if different from above)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/panelcontainer', + items: [ + { + id: 'textinput-13e16ec0e4', + fieldType: 'text-input', + name: 'Insured_Property_Address1701932419752', + type: 'string', + label: { + value: 'Insured Property Address (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-e677433d15', + fieldType: 'text-input', + name: 'Insured_Property_City1701932419752', + type: 'string', + label: { + value: 'City (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'dropdown-86dd596192', + fieldType: 'drop-down', + name: 'Insured_Property_State1701932419753', + type: 'string[]', + enforceEnum: true, + enumNames: [ + 'Select a State', + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'District of Columbia', + 'Florida', + 'Georgia', + 'Guam', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Puerto Rico', + 'Rhode Island', + 'Saipan', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virgin Islands', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', + ], + label: { + value: 'Your State', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + enum: [ + '', + 'AL', + 'AK', + 'AZ', + 'AR', + 'CA', + 'CO', + 'CT', + 'DE', + 'DC', + 'FL', + 'GA', + 'GU', + 'HI', + 'ID', + 'IL', + 'IN', + 'IA', + 'KS', + 'KY', + 'LA', + 'ME', + 'MD', + 'MA', + 'MI', + 'MN', + 'MS', + 'MO', + 'MT', + 'NE', + 'NV', + 'NH', + 'NJ', + 'NM', + 'NY', + 'NC', + 'ND', + 'OH', + 'OK', + 'OR', + 'PA', + 'PR', + 'RI', + 'MP', + 'SC', + 'SD', + 'TN', + 'TX', + 'UT', + 'VT', + 'VI', + 'VA', + 'WA', + 'WV', + 'WI', + 'WY', + ], + ':type': 'formsninja/components/adaptiveForm/dropdown', + }, + { + id: 'numberinput-ad04048bdf', + fieldType: 'number-input', + name: 'Insured_Property_Zip_Postal_Code1701932419753', + type: 'number', + label: { + value: 'Zip/Postal Code (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/numberinput', + }, + { + id: 'textinput-7689e7b5ac', + fieldType: 'text-input', + name: 'Insured_Property_Province_Region1701932419757', + type: 'string', + label: { + value: 'Province/Region (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-8b986fa9bb', + fieldType: 'text-input', + name: 'Insured_Property_Country1701932419758', + type: 'string', + label: { + value: 'Country (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'reset-0d74964a9e', + fieldType: 'button', + name: 'reset1713344016774', + type: 'string', + buttonType: 'reset', + properties: { + 'fd:buttonType': 'reset', + }, + label: { + value: 'Reset', + }, + events: { + click: [ + 'dispatchEvent("reset")', + ], + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/actions/reset', + }, + ], + }, + { + id: 'panelcontainer-7f723ada4a', + fieldType: 'panel', + name: 'panel_3', + label: { + value: 'Alt. Contact Information (person to respond to if different from above)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/panelcontainer', + items: [ + { + id: 'textinput-5b77efe57c', + fieldType: 'text-input', + name: 'Alt__Contact_Name1701932419759', + type: 'string', + label: { + value: 'Name (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-b9632b9253', + fieldType: 'text-input', + name: 'Alt__Contact_Address1701932419759', + type: 'string', + label: { + value: 'Address (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-ed1d7b810b', + fieldType: 'text-input', + name: 'Alt__Contact_City1701932419759', + type: 'string', + label: { + value: 'City (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'dropdown-33e445011b', + fieldType: 'drop-down', + name: 'Alt__Contact_State1701932419760', + type: 'string[]', + enforceEnum: true, + enumNames: [ + 'Select a State', + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'District of Columbia', + 'Florida', + 'Georgia', + 'Guam', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Puerto Rico', + 'Rhode Island', + 'Saipan', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virgin Islands', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', + ], + label: { + value: 'State', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + enum: [ + '', + 'AL', + 'AK', + 'AZ', + 'AR', + 'CA', + 'CO', + 'CT', + 'DE', + 'DC', + 'FL', + 'GA', + 'GU', + 'HI', + 'ID', + 'IL', + 'IN', + 'IA', + 'KS', + 'KY', + 'LA', + 'ME', + 'MD', + 'MA', + 'MI', + 'MN', + 'MS', + 'MO', + 'MT', + 'NE', + 'NV', + 'NH', + 'NJ', + 'NM', + 'NY', + 'NC', + 'ND', + 'OH', + 'OK', + 'OR', + 'PA', + 'PR', + 'RI', + 'MP', + 'SC', + 'SD', + 'TN', + 'TX', + 'UT', + 'VT', + 'VI', + 'VA', + 'WA', + 'WV', + 'WI', + 'WY', + ], + ':type': 'formsninja/components/adaptiveForm/dropdown', + }, + { + id: 'numberinput-1f684a11ca', + fieldType: 'number-input', + name: 'Alt__Contact_Zip_Postal_Code1701932419760', + type: 'number', + label: { + value: 'Zip/Postal Code (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/numberinput', + }, + { + id: 'textinput-f233c65d7e', + fieldType: 'text-input', + name: 'Alt__Contact_Province_Region1701932419761', + type: 'string', + label: { + value: 'Province/Region (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-1dfc4961ef', + fieldType: 'text-input', + name: 'Alt__Contact_Country1701932419761', + type: 'string', + label: { + value: 'Country (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-326ed01cdc', + fieldType: 'text-input', + name: 'Alt__Contact_Phone_Number1701932419774', + type: 'string', + label: { + value: 'Phone Number (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + ], + }, + { + id: 'panelcontainer-bf496d0549', + fieldType: 'panel', + name: 'panel_4', + label: { + value: 'Policy Information', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/panelcontainer', + items: [ + { + id: 'textinput-b5a7157d65', + fieldType: 'text-input', + name: 'Policy_Date1701932419775', + type: 'string', + label: { + value: 'Policy Date (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-0376dd350d', + fieldType: 'text-input', + name: 'Policy_Number1701932419775', + type: 'string', + label: { + value: 'Policy Number (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-9effbdf380', + fieldType: 'text-input', + name: 'Name_of_Issuing_Agent1701932419775', + type: 'string', + label: { + value: 'Name of Issuing Agent (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-3e25a00762', + fieldType: 'text-input', + name: 'Agent_s_File_Number_GF__1701932419775', + type: 'string', + label: { + value: "Agent's File Number/GF # (Optional)", + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'textinput-9d1dce9b76', + fieldType: 'text-input', + name: 'Name_of_Insured_or_Borrower1701932419781', + type: 'string', + label: { + value: 'Name of Insured or Borrower (Optional)', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'text-845379fd97', + fieldType: 'text-input', + name: 'Briefly_Describe_Your_Concern1701932419781', + richText: false, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/text', + }, + ], + }, + { + id: 'textinput-451b95e8b3', + fieldType: 'text-input', + name: 'button-4986153201701932419789', + type: 'string', + label: { + value: 'Review', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/textinput', + }, + { + id: 'panelcontainer-1b66045361', + fieldType: 'panel', + name: 'panel_5', + label: { + value: '', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + ':type': 'formsninja/components/adaptiveForm/panelcontainer', + }, + ], + }, + ], +}; diff --git a/test/unit/fixtures/form/doc-fieldset-after-field.html b/test/unit/fixtures/form/doc-fieldset-after-field.html new file mode 100644 index 0000000..c0b3653 --- /dev/null +++ b/test/unit/fixtures/form/doc-fieldset-after-field.html @@ -0,0 +1,19 @@ +
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/test/unit/fixtures/form/doc-fieldset-after-field.js b/test/unit/fixtures/form/doc-fieldset-after-field.js new file mode 100644 index 0000000..567e80a --- /dev/null +++ b/test/unit/fixtures/form/doc-fieldset-after-field.js @@ -0,0 +1,46 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + total: 3, + offset: 0, + limit: 11, + ':type': 'sheet', + data: [ + { + Type: 'text', + Name: 'f2', + Fieldset: 'panel1', + }, + { + Type: 'text', + Name: 'f1', + Fieldset: 'panel1', + }, + { + Type: 'fieldset', + Name: 'panel1', + Repeatable: 'true', + }, + { + Type: 'checkbox', + Name: 'f3', + Value: 'checkbox', + }, + { + Type: 'radio', + Name: 'f4', + Value: 'radio-on', + }, + { + Type: 'radio', + Name: 'f4', + Value: 'radio-off', + }, + { + Type: 'submit', + Name: 'submit', + Label: 'Submit', + }, + ], +}; + +export const ignore = true; diff --git a/test/unit/fixtures/form/doc-fieldset.html b/test/unit/fixtures/form/doc-fieldset.html new file mode 100644 index 0000000..1fda1c6 --- /dev/null +++ b/test/unit/fixtures/form/doc-fieldset.html @@ -0,0 +1,19 @@ +
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/test/unit/fixtures/form/doc-fieldset.js b/test/unit/fixtures/form/doc-fieldset.js new file mode 100644 index 0000000..b3be9f3 --- /dev/null +++ b/test/unit/fixtures/form/doc-fieldset.js @@ -0,0 +1,44 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + total: 3, + offset: 0, + limit: 11, + ':type': 'sheet', + data: [ + { + Type: 'fieldset', + Name: 'panel1', + Repeatable: 'true', + }, + { + Type: 'text', + Name: 'f2', + Fieldset: 'panel1', + }, + { + Type: 'text', + Name: 'f1', + Fieldset: 'panel1', + }, + { + Type: 'checkbox', + Name: 'f3', + Value: 'checkbox', + }, + { + Type: 'radio', + Name: 'f4', + Value: 'radio-on', + }, + { + Type: 'radio', + Name: 'f4', + Value: 'radio-off', + }, + { + Type: 'submit', + Name: 'submit', + Label: 'Submit', + }, + ], +}; diff --git a/test/unit/fixtures/form/docBasedForm.html b/test/unit/fixtures/form/docBasedForm.html new file mode 100644 index 0000000..222876d --- /dev/null +++ b/test/unit/fixtures/form/docBasedForm.html @@ -0,0 +1,56 @@ +
+

Please Enter Your Information

+
+
+ +
contract info
+
+
+
+
+
+
+
+
+ +
+
+
\ No newline at end of file diff --git a/test/unit/fixtures/form/docBasedForm.js b/test/unit/fixtures/form/docBasedForm.js new file mode 100644 index 0000000..dbac5d3 --- /dev/null +++ b/test/unit/fixtures/form/docBasedForm.js @@ -0,0 +1,117 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + total: 11, + offset: 0, + limit: 11, + data: [ + { + Name: 'headline', + Type: 'heading', + Description: '', + Placeholder: '', + Label: '', + 'Read Only': '', + Mandatory: '', + Pattern: '', + Step: '', + Min: '', + Max: '', + Value: 'Please Enter Your Information', + Options: '', + OptionNames: '', + Fieldset: '', + }, { + Name: 'selectAn', + Type: 'checkbox', + Description: 'contract info', + Placeholder: 'contract info', + Label: 'Local and custom contracting for GAMMAGARD LIQUID® and FLEXBUMIN', + 'Read Only': '', + Mandatory: '', + Pattern: '', + Step: '', + Min: '', + Max: '', + Value: 'Local and custom contracting for GAMMAGARD LIQUID® and FLEXBUMIN', + Options: '', + OptionNames: '', + Fieldset: 'Contract Types', + }, { + Name: 'selectAn', + Type: 'checkbox', + Description: '', + Placeholder: '', + Label: 'Facilitate contractual arrangements for other Takeda plasma-derived brands', + 'Read Only': '', + Mandatory: '', + Pattern: '', + Step: '', + Min: '', + Max: '', + Value: 'Facilitate contractual arrangements for other Takeda plasma-derived brands', + Options: '', + OptionNames: '', + Fieldset: 'Contract Types', + }, { + Name: 'selectAn', + Type: 'checkbox', + Description: '', + Placeholder: '', + Label: "Let's discuss the best option for me", + 'Read Only': '', + Mandatory: '', + Pattern: '', + Step: '', + Min: '', + Max: '', + Value: "Let's discuss the best option for me", + Options: '', + OptionNames: '', + Fieldset: 'Contract Types', + }, { + Name: 'firstName', Type: 'text', Description: '', Placeholder: '', Label: 'First Name*', 'Read Only': '', Mandatory: 'true', Pattern: '', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: '', + }, { + Name: 'lastName', Type: 'text', Description: '', Placeholder: '', Label: 'Last Name*', 'Read Only': '', Mandatory: 'true', Pattern: '[0-9]{3}-[0-9]{3}-[0-9]{4}', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: '', + }, { + Name: 'phoneNumber', Type: 'text', Description: '', Placeholder: '', Label: 'Phone Number*', 'Read Only': '', Mandatory: 'true', Pattern: '[a-z0-9._%+-]+@[a-z0-9.-]+\\\\.[a-z]{2,}$,', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: '', + }, { + Name: 'emailAddress', Type: 'text', Description: '', Placeholder: '', Label: 'Email Address*', 'Read Only': '', Mandatory: 'true', Pattern: '', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: '', + }, { + Name: 'selectA', Type: 'select', Description: '', Placeholder: '', Label: 'Select a Job Title*', 'Read Only': '', Mandatory: 'true', Pattern: '', Step: '', Min: '', Max: '', Value: '', Options: 'Physician,Nurse,Nurse Practitioner / Physician Assistant,Pharmacist,Other', OptionNames: 'Select an option,Physician,Nurse,Nurse Practitioner / Physician Assistant,Pharmacist,Other', Fieldset: '', + }, { + Name: 'program-info', + Type: 'checkbox', + Description: '', + Placeholder: '', + Label: 'Programs and educational resources available to Takeda customers', + 'Read Only': '', + Mandatory: '', + Pattern: '', + Step: '', + Min: '', + Max: '', + Value: 'requests info about programs and educational resources', + Options: '', + OptionNames: '', + Fieldset: '', + }, { + Name: 'product-info', + Type: 'checkbox', + Description: '', + Placeholder: '', + Label: 'Products', + 'Read Only': '', + Mandatory: '', + Pattern: '', + Step: '', + Min: '', + Max: '', + Value: 'requests info about products', + Options: '', + OptionNames: '', + Fieldset: '', + }, { + Name: 'input_2', Type: 'submit', Description: '', Placeholder: '', Label: 'Submit', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: '', + }], + ':type': 'sheet', +}; diff --git a/test/unit/fixtures/form/enquire.html b/test/unit/fixtures/form/enquire.html new file mode 100644 index 0000000..dad8c8c --- /dev/null +++ b/test/unit/fixtures/form/enquire.html @@ -0,0 +1,687 @@ + + +
+
+ + +
+
+
+
+
+ Traveler Info +
+
+
+
+
\ No newline at end of file diff --git a/test/unit/fixtures/form/enquire.js b/test/unit/fixtures/form/enquire.js new file mode 100644 index 0000000..87f970e --- /dev/null +++ b/test/unit/fixtures/form/enquire.js @@ -0,0 +1,32 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + total: 12, + offset: 0, + limit: 12, + data: [{ + Name: 'quote[destination]', Type: 'select', Description: '', Placeholder: '', Label: 'Destination', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: 'Select your destination', Options: 'Select your destination,AFG,ALA,ALB,DZA,ASM,AND,AGO,AIA,ATA,ATG,ARG,ARM,ABW,AUS,AUT,AZE,BHS,BHR,BGD,BRB,BLR,BEL,BLZ,BEN,BMU,BTN,BOL,BES,BIH,BWA,BVT,BRA,IOT,VGB,BRN,BGR,BFA,BDI,KHM,CMR,CAN,CPV,Caribbean,CYM,CAF,TCD,CHL,CHN,CXR,CCK,COL,COM,COG,COD,COK,CRI,CIV,HRV,CUB,CUW,CYP,CZE,DNK,DJI,DMA,DOM,ECU,EGY,SLV,GNQ,ERI,EST,SWZ,ETH,FLK,FRO,FJI,FIN,FRA,GUF,PYF,ATF,GAB,GMB,GEO,DEU,GHA,GIB,GRC,GRL,GRD,GLP,GUM,GTM,GGY,GIN,GNB,GUY,HTI,HMD,VAT,HND,HKG,HUN,ISL,IND,IDN,IRQ,IRL,IMN,ISR,ITA,JAM,JPN,JEY,JOR,KAZ,KEN,KIR,KOR,KWT,KGZ,LAO,LVA,LBN,LSO,LBR,LBY,LIE,LTU,LUX,MAC,MKD,MDG,MWI,MYS,MDV,MLI,MLT,MHL,MTQ,MRT,MUS,MYT,MEX,FSM,MDA,MCO,MNG,MNE,MSR,MAR,MOZ,MMR,NAM,NRU,NPL,NLD,NCL,NZL,NIC,NER,NGA,NIU,NFK,MNP,NOR,OMN,PAK,PLW,PSE,PAN,PNG,PRY,PER,PHL,PCN,POL,PRT,PRI,QAT,REU,ROU,RUS,RWA,BLM,SHN,KNA,LCA,MAF,SPM,VCT,WSM,SMR,STP,SAU,SEN,SRB,SYC,SLE,SGP,SXM,SVK,SVN,SLB,SOM,ZAF,SGS,SSD,ESP,LKA,SDN,SUR,SJM,SWE,CHE,SYR,TWN,TJK,TZA,THA,TLS,TGO,TKL,TON,TTO,TUN,TUR,TKM,TCA,TUV,VIR,UGA,UKR,ARE,GBR,USA,UMI,URY,UZB,VUT,VEN,VNM,WLF,ESH,YEM,ZMB,ZWE,Other', OptionNames: "Select your destination,Afghanistan,Aland Islands,Albania,Algeria,American Samoa,Andorra,Angola,Anguilla,Antarctica,Antigua and Barbuda,Argentina,Armenia,Aruba,Australia,Austria,Azerbaijan,Bahamas,Bahrain,Bangladesh,Barbados,Belarus,Belgium,Belize,Benin,Bermuda,Bhutan,Bolivia,Bonaire,Bosnia and Herzegovina,Botswana,Bouvet Island,Brazil,British Indian Ocean Territory,British Virgin Islands,Brunei Darussalam,Bulgaria,Burkina Faso,Burundi,Cambodia,Cameroon,Canada,Cape Verde,Caribbean,Cayman Islands,Central African Republic,Chad,Chile,China,Christmas Island,Cocos (Keeling) Islands,Colombia,Comoros,Congo,Congo, Democratic Republic of the,Cook Islands,Costa Rica,Cote d'Ivoire,Croatia,Cuba,Curacao,Cyprus,Czech Republic,Denmark,Djibouti,Dominica,Dominican Republic,Ecuador,Egypt,El Salvador,Equatorial Guinea,Eritrea,Estonia,eSwatini,Ethiopia,Falkland Islands (Malvinas),Faroe Islands,Fiji,Finland,France,French Guiana,French Polynesia,French Southern Territories,Gabon,Gambia,Georgia,Germany,Ghana,Gibraltar,Greece,Greenland,Grenada,Guadeloupe,Guam,Guatemala,Guernsey,Guinea,Guinea-Bissau,Guyana,Haiti,Heard Island and McDonald Islands,Holy See (Vatican City State),Honduras,Hong Kong,Hungary,Iceland,India,Indonesia,Iraq,Ireland,Isle of Man,Israel,Italy,Jamaica,Japan,Jersey,Jordan,Kazakhstan,Kenya,Kiribati,Korea, (South) Republic of,Kuwait,Kyrgyzstan,Lao People's Democratic Republic (Laos),Latvia,Lebanon,Lesotho,Liberia,Libyan Arab Jamahiriya (Libya),Liechtenstein,Lithuania,Luxembourg,Macao,Macedonia, the former Yugoslav Republic of,Madagascar,Malawi,Malaysia,Maldives,Mali,Malta,Marshall Islands,Martinique,Mauritania,Mauritius,Mayotte,Mexico,Micronesia, Federated States of,Moldova,Monaco,Mongolia,Montenegro,Montserrat,Morocco,Mozambique,Myanmar,Namibia,Nauru,Nepal,Netherlands,New Caledonia,New Zealand,Nicaragua,Niger,Nigeria,Niue,Norfolk Island,Northern Mariana Islands,Norway,Oman,Pakistan,Palau,Palestinian Territory, Occupied,Panama,Papua New Guinea,Paraguay,Peru,Philippines,Pitcairn,Poland,Portugal,Puerto Rico,Qatar,Reunion,Romania,Russia,Rwanda,Saint Barthelemy,Saint Helena,Saint Kitts and Nevis,Saint Lucia,Saint Martin,Saint Pierre and Miquelon,Saint Vincent and the Grenadines,Samoa,San Marino,Sao Tome and Principe,Saudi Arabia,Senegal,Serbia,Seychelles,Sierra Leone,Singapore,Sint Maarten,Slovakia,Slovenia,Solomon Islands,Somalia,South Africa,South Georgia and the South Sandwich Islands,South Sudan,Spain,Sri Lanka,Sudan,Suriname,Svalbard and Jan Mayen,Sweden,Switzerland,Syrian Arab Republic,Taiwan,Tajikistan,Tanzania, United Republic of,Thailand,Timor-Leste,Togo,Tokelau,Tonga,Trinidad and Tobago,Tunisia,Turkey,Turkmenistan,Turks and Caicos Islands,Tuvalu,U.S. Virgin Islands,Uganda,Ukraine,United Arab Emirates,United Kingdom,United States - Continental,United States Minor Outlying Islands,Uruguay,Uzbekistan,Vanuatu,Venezuela,Vietnam,Wallis and Futuna,Western Sahara,Yemen,Zambia,Zimbabwe,Other", Fieldset: '', Repeatable: '', + }, { + Name: 'quote[destinationState]', Type: 'select', Description: '', Placeholder: '', Label: 'State', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: 'Select your state', Options: 'Select your state,AL,AK,AZ,AR,CA,CO,CT,DE,DC,FL,GA,HI,ID,IL,IN,IA,KS,KY,LA,ME,MD,MA,MI,MN,MS,MO,MT,NE,NV,NH,NJ,NM,NY,NC,ND,OH,OK,OR,PA,RI,SC,SD,TN,TX,UT,VT,VA,WA,WV,WI,WY,AS,AF,AA,AC,AE,AM,AP,CZ,GU,UM,PR,VI', OptionNames: 'Select your state,Alabama,Alaska,Arizona,Arkansas,California,Colorado,Connecticut,Delaware,District of Columbia,Florida,Georgia,Hawaii,Idaho,Illinois,Indiana,Iowa,Kansas,Kentucky,Louisiana,Maine,Maryland,Massachusetts,Michigan,Minnesota,Mississippi,Missouri,Montana,Nebraska,Nevada,New Hampshire,New Jersey,New Mexico,New York,North Carolina,North Dakota,Ohio,Oklahoma,Oregon,Pennsylvania,Rhode Island,South Carolina,South Dakota,Tennessee,Texas,Utah,Vermont,Virginia,Washington,West Virginia,Wisconsin,Wyoming,American Samoa,Armed Forces Africa,Armed Forces Americas,Armed Forces Canada,Armed Forces Europe,Armed Forces Middle East,Armed Forces Pacific,Canal Zone,Guam,United States Minor Outlying Islands,Puerto Rico,U.S. Virgin Islands', Fieldset: '', Repeatable: '', + }, { + Name: 'quote[departureDate]', Type: 'date', Description: '', Placeholder: 'mm/dd/yyyy', Label: 'Departure', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: '', Repeatable: '', + }, { + Name: 'quote[returnDate]', Type: 'date', Description: '', Placeholder: 'mm/dd/yyyy', Label: 'Return', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: '', Repeatable: '', + }, { + Name: 'quote[residence]', Type: 'select', Description: '', Placeholder: '', Label: 'Residence', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: 'Select your residence', Options: 'Select your residence,AL,AK,AZ,AR,CA,CO,CT,DE,DC,FL,GA,HI,ID,IL,IN,IA,KS,KY,LA,ME,MD,MA,MI,MN,MS,MO,MT,NE,NV,NH,NJ,NM,NY,NC,ND,OH,OK,OR,PA,RI,SC,SD,TN,TX,UT,VT,VA,WA,WV,WI,WY,AS,AF,AA,AC,AE,AM,AP,CZ,GU,UM,PR,VI,AB,BC,MB,NB,NF,NT,NS,NU,ON,PE,QC,SK,YT,OT', OptionNames: 'Select your residence,Alabama,Alaska,Arizona,Arkansas,California,Colorado,Connecticut,Delaware,District of Columbia,Florida,Georgia,Hawaii,Idaho,Illinois,Indiana,Iowa,Kansas,Kentucky,Louisiana,Maine,Maryland,Massachusetts,Michigan,Minnesota,Mississippi,Missouri,Montana,Nebraska,Nevada,New Hampshire,New Jersey,New Mexico,New York,North Carolina,North Dakota,Ohio,Oklahoma,Oregon,Pennsylvania,Rhode Island,South Carolina,South Dakota,Tennessee,Texas,Utah,Vermont,Virginia,Washington,West Virginia,Wisconsin,Wyoming,American Samoa,Armed Forces Africa,Armed Forces Americas,Armed Forces Canada,Armed Forces Europe,Armed Forces Middle East,Armed Forces Pacific,Canal Zone,Guam,United States Minor Outlying Islands,Puerto Rico,U.S. Virgin Islands,Alberta,British Columbia,Manitoba,New Brunswick,Newfoundland,Northwest Territories,Nova Scotia,Nunavut,Ontario,Prince Edward Island,Quebec,Saskatchewan,Yukon,Neither U.S. nor Canada', Fieldset: '', Repeatable: '', + }, { + Name: 'quote[citizenship]', Type: 'select', Description: '', Placeholder: 'separator', Label: 'Citizenship', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: 'separator', Options: ',USA,CAN,AFG,ALA,ALB,DZA,AND,AGO,AIA,ATA,ATG,ARG,ARM,ABW,AUS,AUT,AZE,BHS,BHR,BGD,BRB,BLR,BEL,BLZ,BEN,BMU,BTN,BOL,BES,BIH,BWA,BVT,BRA,IOT,VGB,BRN,BGR,BFA,BDI,KHM,CMR,CAN,CPV,CYM,CAF,TCD,CHL,CHN,CXR,CCK,COL,COM,COG,COD,COK,CRI,CIV,HRV,CUB,CUW,CYP,CZE,DNK,DJI,DMA,DOM,ECU,EGY,SLV,GNQ,ERI,EST,SWZ,ETH,FLK,FRO,FJI,FIN,FRA,GUF,PYF,ATF,GAB,GMB,GEO,DEU,GHA,GIB,GRC,GRL,GRD,GLP,GTM,GGY,GIN,GNB,GUY,HTI,HMD,VAT,HND,HKG,HUN,ISL,IND,IDN,IRN,IRQ,IRL,IMN,ISR,ITA,JAM,JPN,JEY,JOR,KAZ,KEN,KIR,PRK,KOR,KWT,KGZ,LAO,LVA,LBN,LSO,LBR,LBY,LIE,LTU,LUX,MAC,MKD,MDG,MWI,MYS,MDV,MLI,MLT,MHL,MTQ,MRT,MUS,MYT,MEX,FSM,MDA,MCO,MNG,MNE,MSR,MAR,MOZ,MMR,NAM,NRU,NPL,NLD,ANT,NCL,NZL,NIC,NER,NGA,NIU,NFK,NOR,OMN,PAK,PLW,PSE,PAN,PNG,PRY,PER,PHL,PCN,POL,PRT,QAT,REU,ROU,RUS,RWA,BLM,SHN,KNA,LCA,MAF,SPM,VCT,WSM,SMR,STP,SAU,SEN,SRB,SYC,SLE,SGP,SXM,SVK,SVN,SLB,SOM,ZAF,SGS,SSD,ESP,LKA,SDN,SUR,SJM,SWE,CHE,SYR,TWN,TJK,TZA,THA,TLS,TGO,TKL,TON,TTO,TUN,TUR,TKM,TCA,TUV,UGA,UKR,ARE,GBR,USA,URY,UZB,VUT,VEN,VNM,WLF,ESH,YEM,ZMB,ZWE', OptionNames: "Select your country,United States,Canada,Afghanistan,Aland Islands,Albania,Algeria,Andorra,Angola,Anguilla,Antarctica,Antigua and Barbuda,Argentina,Armenia,Aruba,Australia,Austria,Azerbaijan,Bahamas,Bahrain,Bangladesh,Barbados,Belarus,Belgium,Belize,Benin,Bermuda,Bhutan,Bolivia,Bonaire,Bosnia and Herzegovina,Botswana,Bouvet Island,Brazil,British Indian Ocean Territory,British Virgin Islands,Brunei Darussalam,Bulgaria,Burkina Faso,Burundi,Cambodia,Cameroon,Canada,Cape Verde,Cayman Islands,Central African Republic,Chad,Chile,China,Christmas Island,Cocos (Keeling) Islands,Colombia,Comoros,Congo,Congo, Democratic Republic of the,Cook Islands,Costa Rica,Cote d'Ivoire,Croatia,Cuba,Curacao,Cyprus,Czech Republic,Denmark,Djibouti,Dominica,Dominican Republic,Ecuador,Egypt,El Salvador,Equatorial Guinea,Eritrea,Estonia,eSwatini,Ethiopia,Falkland Islands (Malvinas),Faroe Islands,Fiji,Finland,France,French Guiana,French Polynesia,French Southern Territories,Gabon,Gambia,Georgia,Germany,Ghana,Gibraltar,Greece,Greenland,Grenada,Guadeloupe,Guatemala,Guernsey,Guinea,Guinea-Bissau,Guyana,Haiti,Heard Island and McDonald Islands,Holy See (Vatican City State),Honduras,Hong Kong,Hungary,Iceland,India,Indonesia,Iran, Islamic Republic of,Iraq,Ireland,Isle of Man,Israel,Italy,Jamaica,Japan,Jersey,Jordan,Kazakhstan,Kenya,Kiribati,Korea, (North) Democratic People's Republic of,Korea, (South) Republic of,Kuwait,Kyrgyzstan,Lao People's Democratic Republic (Laos),Latvia,Lebanon,Lesotho,Liberia,Libyan Arab Jamahiriya (Libya),Liechtenstein,Lithuania,Luxembourg,Macao,Macedonia, the former Yugoslav Republic of,Madagascar,Malawi,Malaysia,Maldives,Mali,Malta,Marshall Islands,Martinique,Mauritania,Mauritius,Mayotte,Mexico,Micronesia, Federated States of,Moldova,Monaco,Mongolia,Montenegro,Montserrat,Morocco,Mozambique,Myanmar,Namibia,Nauru,Nepal,Netherlands,Netherlands Antilles,New Caledonia,New Zealand,Nicaragua,Niger,Nigeria,Niue,Norfolk Island,Norway,Oman,Pakistan,Palau,Palestinian Territory, Occupied,Panama,Papua New Guinea,Paraguay,Peru,Philippines,Pitcairn,Poland,Portugal,Qatar,Reunion,Romania,Russia,Rwanda,Saint Barthelemy,Saint Helena,Saint Kitts and Nevis,Saint Lucia,Saint Martin,Saint Pierre and Miquelon,Saint Vincent and the Grenadines,Samoa,San Marino,Sao Tome and Principe,Saudi Arabia,Senegal,Serbia,Seychelles,Sierra Leone,Singapore,Sint Maarten,Slovakia,Slovenia,Solomon Islands,Somalia,South Africa,South Georgia and the South Sandwich Islands,South Sudan,Spain,Sri Lanka,Sudan,Suriname,Svalbard and Jan Mayen,Sweden,Switzerland,Syrian Arab Republic,Taiwan,Tajikistan,Tanzania, United Republic of,Thailand,Timor-Leste,Togo,Tokelau,Tonga,Trinidad and Tobago,Tunisia,Turkey,Turkmenistan,Turks and Caicos Islands,Tuvalu,Uganda,Ukraine,United Arab Emirates,United Kingdom,United States,Uruguay,Uzbekistan,Vanuatu,Venezuela,Vietnam,Wallis and Futuna,Western Sahara,Yemen,Zambia,Zimbabwe", Fieldset: '', Repeatable: '', + }, { + Name: 'quote[tripCostAmount]', Type: 'range', Description: '', Placeholder: '', Label: 'Total Trip Cost', 'Read Only': '', Mandatory: '', Pattern: '', Step: '1', Min: '1', Max: '9', Value: '', Options: '', OptionNames: '', Fieldset: '', Repeatable: '', + }, { + Name: 'quote[initialTripPaymentDate]', Type: 'date', Description: '', Placeholder: '', Label: 'Initial Trip Payment Date', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: '', Repeatable: '', + }, { + Name: 'traveler', Type: 'fieldset', Description: '', Placeholder: '', Label: 'Traveler Info', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '1', Max: '3', Value: '', Options: '', OptionNames: '', Fieldset: '', Repeatable: 'true', + }, { + Name: 'name', Type: 'text', Description: '', Placeholder: '', Label: 'Name', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: 'traveler', Repeatable: '', + }, { + Name: 'age', Type: 'number', Description: '', Placeholder: '', Label: 'Age', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: 'traveler', Repeatable: '', + }, { + Name: 'submit', Type: 'submit', Description: '', Placeholder: '', Label: 'See Plans and Prices', 'Read Only': '', Mandatory: '', Pattern: '', Step: '', Min: '', Max: '', Value: '', Options: '', OptionNames: '', Fieldset: '', Repeatable: '', + }], + ':type': 'sheet', +}; diff --git a/test/unit/fixtures/form/tripPlanner.html b/test/unit/fixtures/form/tripPlanner.html new file mode 100644 index 0000000..bb4b977 --- /dev/null +++ b/test/unit/fixtures/form/tripPlanner.html @@ -0,0 +1,63 @@ +
+
+
+ + +
+
+
+ Traveler Info +
+
+
+
+ +
+
+
+
Drag and Drop To Upload
+ +
+
diff --git a/test/unit/fixtures/form/tripPlanner.js b/test/unit/fixtures/form/tripPlanner.js new file mode 100644 index 0000000..df64ff4 --- /dev/null +++ b/test/unit/fixtures/form/tripPlanner.js @@ -0,0 +1,35 @@ +// eslint-disable-next-line import/prefer-default-export +export const fieldDef = { + total: 12, + offset: 0, + limit: 12, + data: [{ + Name: 'startDate', Type: 'date', Placeholder: '', Label: 'Start Date', Mandatory: 'true', Value: '', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', 'Required Error Message': 'This field is required', + }, { + Name: 'endDate', Type: 'date', Placeholder: '', Label: 'End Date', Mandatory: 'true', Value: '', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', 'Required Error Message': '', 'Min Error Message': 'End date should be greater than start date', 'Max Error Message': 'End date should be less than 10 days', + }, { + Name: 'destination', Type: 'select', Placeholder: 'Select your destination', Label: 'Destination', Mandatory: '', Value: 'US', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: 'United States, India, Australia, United Kindom', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', 'Required Error Message': '', + }, { + Name: 'class', Type: 'select', Placeholder: 'Please Select', Label: 'Class of service', Mandatory: '', Value: '', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: 'Economy, Business, First', OptionNames: 'Economy Class, Business Class, First Class', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', 'Required Error Message': '', + }, { + Name: 'budget', Type: 'number', Placeholder: '', Label: 'Room Budget', Mandatory: '', Value: '1000', Visible: '', Min: '500', Max: '10000', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '500', ReadOnly: '', Describe: '', 'Max Error Message': 'Maximum of Xyz', 'Min Error Message': 'Minimum of Xyz', + }, { + Name: 'amount', Type: 'number', Placeholder: '', Label: 'Estimated Trip Cost', Mandatory: '', Value: '', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', 'Required Error Message': '', + }, { + Name: 'panel-1', Type: 'fieldset', Placeholder: '', Label: 'Traveler Info', Mandatory: '', Value: '', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '12', Checked: '', Step: '', ReadOnly: '', Describe: '', 'Required Error Message': '', 'Min Error Message': 'Min should be 1 travler', 'Max Error Message': 'Max should be 3 travler', + }, { + Name: 'name', Type: 'text', Placeholder: '', Label: 'Name', Mandatory: '', Value: '', Visible: '', Min: '', Max: '', Fieldset: 'panel-1', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', 'Required Error Message': '', + }, { + Name: 'age', Type: 'number', Placeholder: '', Label: 'Age', Mandatory: '', Value: '', Visible: '', Min: '', Max: '', Fieldset: 'panel-1', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', 'Required Error Message': '', + }, { + Name: 'subscribe', Type: 'checkbox', Placeholder: '', Label: 'Do you like subscribe for Magazine & Activities?', Mandatory: '', Value: 'true', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '', Checked: 'false', Step: '', ReadOnly: '', Describe: '', 'Required Error Message': '', + }, { + Name: 'email', Type: 'email', Placeholder: '', Label: 'Email', Mandatory: '', Value: '', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '', Checked: '', Step: '', ReadOnly: '', Describe: '', 'Required Error Message': '', + }, { + Name: 'submit', Type: 'submit', Placeholder: '', Label: 'Submit', Mandatory: '', Value: '', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '', Checked: '', Step: '', ReadOnly: '', Describe: '', 'Required Error Message': '', + }, + { + Name: 'attach', Type: 'file', Placeholder: '', Label: 'Attach', Mandatory: '', Value: '', Visible: '', Min: '', Max: '1MB', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '', Checked: '', Step: '', ReadOnly: '', Description: '', 'Required Error Message': '', Accept: 'application/pdf', + }], + ':type': 'sheet', +}; diff --git a/test/unit/fixtures/prefill/basic-data.js b/test/unit/fixtures/prefill/basic-data.js new file mode 100644 index 0000000..5c4f9cb --- /dev/null +++ b/test/unit/fixtures/prefill/basic-data.js @@ -0,0 +1,47 @@ +import assert from 'assert'; + +const data = { + f1: 10, +}; + +export const sample = { + action: 'http://localhost:3000/submit', + id: 'someid', + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: 'f1 & \'2\'', + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + }, { + fieldType: 'button', + id: 'button', + events: { + click: 'submitForm()', + }, + }], +}; + +export function before() { + global.fetch.mockData['http://localhost:3000/adobe/forms/af/data/someid'] = data; +} + +export function op() { +} + +export function expect(block) { + const textInput = block.querySelector('#text-input'); + const textInput2 = block.querySelector('#text-input-2'); + assert.equal(textInput.value, '102'); + assert.equal(textInput2.value, '10'); +} + +export function after() { + global.fetch.mockData = {}; +} diff --git a/test/unit/fixtures/prefill/bound-data.js b/test/unit/fixtures/prefill/bound-data.js new file mode 100644 index 0000000..7e6b1d9 --- /dev/null +++ b/test/unit/fixtures/prefill/bound-data.js @@ -0,0 +1,55 @@ +import assert from 'assert'; + +const data = { + data: { + afData: { + afBoundData: { + data: { + f1: 10, + }, + }, + }, + }, +}; + +export const sample = { + action: 'http://localhost:3000/submit', + id: 'someid', + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: 'f1 & \'2\'', + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + }, { + fieldType: 'button', + id: 'button', + events: { + click: 'submitForm()', + }, + }], +}; + +export function before() { + global.fetch.mockData['http://localhost:3000/adobe/forms/af/data/someid'] = data; +} + +export function op() { +} + +export function expect(block) { + const textInput = block.querySelector('#text-input'); + const textInput2 = block.querySelector('#text-input-2'); + assert.equal(textInput.value, '102'); + assert.equal(textInput2.value, '10'); +} + +export function after() { + global.fetch.mockData = {}; +} diff --git a/test/unit/fixtures/prefill/submit-after-prefill-with-extra-data.js b/test/unit/fixtures/prefill/submit-after-prefill-with-extra-data.js new file mode 100644 index 0000000..d29cf41 --- /dev/null +++ b/test/unit/fixtures/prefill/submit-after-prefill-with-extra-data.js @@ -0,0 +1,70 @@ +import assert from 'assert'; +import nock from 'nock'; +import multipart from 'parse-multipart-data'; + +const data = { + f1: 10, + id: 11, +}; + +const scope = nock('http://localhost:3005') + .post('/submit-success', function test(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + const boundary = contentType.match('multipart/form-data; boundary=(.+)')?.[1]; + const parts = multipart.parse(Buffer.from(body), boundary); + // eslint-disable-next-line no-unused-vars + const data1 = parts.reduce((acc, part) => ({ + ...acc, + [part.name]: JSON.parse(part.data.toString()), + }), {}); + assert.deepStrictEqual(data1.data, { f1: 10, f2: '102', id: 11 }); + return true; + }) + .reply(200, { + thankYouMessage: 'thank you for submitting the form', + }); + +export const sample = { + action: 'http://localhost:3005/submit-success', + id: 'someid2', + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: 'f1 & \'2\'', + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + }, { + fieldType: 'button', + id: 'button', + events: { + click: 'submitForm()', + }, + }], +}; + +export function before() { + global.fetch.mockData['http://localhost:3000/adobe/forms/af/data/someid2'] = data; +} + +export function op(block) { + const btn = block.querySelector('#button'); + btn.click(); +} + +export function expect() { + assert.equal(scope.isDone(), true, 'submit was success'); +} + +export function after() { + global.fetch.mockData = {}; +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/prefill/submit-after-prefill.js b/test/unit/fixtures/prefill/submit-after-prefill.js new file mode 100644 index 0000000..0adc56e --- /dev/null +++ b/test/unit/fixtures/prefill/submit-after-prefill.js @@ -0,0 +1,68 @@ +import assert from 'assert'; +import nock from 'nock'; +import multipart from 'parse-multipart-data'; + +const data = { + f1: 10, +}; + +const scope = nock('http://localhost:3000') + .post('/submit-success', function test(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + const boundary = contentType.match('multipart/form-data; boundary=(.+)')?.[1]; + const parts = multipart.parse(Buffer.from(body), boundary); + const data1 = parts.reduce((acc, part) => ({ + ...acc, + [part.name]: JSON.parse(part.data.toString()), + }), {}); + assert.deepStrictEqual(data1.data, { f1: 10, f2: '102' }); + return true; + }) + .reply(200, { + thankYouMessage: 'thank you for submitting the form', + }); + +export const sample = { + action: 'http://localhost:3000/submit-success', + id: 'someid3', + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: 'f1 & \'2\'', + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + }, { + fieldType: 'button', + id: 'button', + events: { + click: 'submitForm()', + }, + }], +}; + +export function before() { + global.fetch.mockData['http://localhost:3000/adobe/forms/af/data/someid3'] = data; +} + +export function op(block) { + const btn = block.querySelector('#button'); + btn.click(); +} + +export function expect() { + assert.equal(scope.isDone(), true, 'submit was success'); +} + +export function after() { + global.fetch.mockData = {}; +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/reset/successful-reset.js b/test/unit/fixtures/reset/successful-reset.js new file mode 100644 index 0000000..cb933a0 --- /dev/null +++ b/test/unit/fixtures/reset/successful-reset.js @@ -0,0 +1,40 @@ +import assert from 'assert'; + +import { fieldDef } from '../form/claim.js'; +import { setValue } from '../../testUtils.js'; + +export const sample = fieldDef; + +export function op(block) { + setValue(block, '#textinput-5d4030d204', 'Aya Tan'); + const input = block.querySelector('#fileinput-9d805e9eca'); + const file1 = new File([new ArrayBuffer(1024)], 'file1.png', { type: 'image/png' }); + const event = new Event('change', { + bubbles: true, + cancelable: true, + }); + input.files = [file1]; + input.dispatchEvent(event); + const fileList = block.querySelector('.files-list'); + assert.equal(fileList.children.length, 1, 'Should render file1'); + assert.equal(fileList.innerHTML.includes('file1.png'), true, 'Should show file1.png'); + const nxt = block.querySelector('.wizard .field-next'); + nxt.click(); + const instances = block.querySelector('.wizard-menu-active-item'); + assert.equal(instances?.dataset.index, 1); + const resetButton = block.querySelector('#reset-0d74964a9e'); + resetButton.click(); + const form = block.querySelector('form'); + form.dispatchEvent(new Event('reset')); +} + +export function expect(block) { + const instances = block.querySelector('.wizard-menu-active-item'); + assert.equal(instances?.dataset.index, 0); + assert.equal(block.querySelector('#textinput-5d4030d204').value, ''); + const fileList = block.querySelector('.files-list'); + assert.equal(fileList.children.length, 0, 'Should not render file1'); + assert.equal(fileList.innerHTML.includes('file1.png'), false, 'Should not show file1.png'); +} + +export const refresh = true; diff --git a/test/unit/fixtures/submit/failed-submit.js b/test/unit/fixtures/submit/failed-submit.js new file mode 100644 index 0000000..946b79e --- /dev/null +++ b/test/unit/fixtures/submit/failed-submit.js @@ -0,0 +1,44 @@ +import assert from 'assert'; +import nock from 'nock'; + +const scope = nock('http://localhost:3000') + .post('/submit') + .reply(500, {}); + +export const sample = { + action: 'http://localhost:3000/submit', + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: 'f1 & \'2\'', + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + }, { + fieldType: 'button', + id: 'button', + events: { + click: 'submitForm()', + }, + }], +}; + +export function op(block) { + const btn = block.querySelector('#button'); + btn.click(); + const form = block.querySelector('form'); + form.dispatchEvent(new Event('submit')); +} + +export function expect(block) { + assert.equal(scope.isDone(), true); + const el = block.querySelector('.form-message.error-message'); + assert.equal(el.textContent, 'Some error occured while submitting the form'); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/submit/failed-validations.js b/test/unit/fixtures/submit/failed-validations.js new file mode 100644 index 0000000..8d44e3d --- /dev/null +++ b/test/unit/fixtures/submit/failed-validations.js @@ -0,0 +1,75 @@ +import assert from 'assert'; +import nock from 'nock'; +import multipart from 'parse-multipart-data'; + +const thankYouMessage = 'thank you for submitting the form'; + +const scope = nock('http://localhost:3000') + .post('/submit', function parseBody(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + const boundary = contentType.match('multipart/form-data; boundary=(.+)')?.[1]; + const parts = multipart.parse(Buffer.from(body), boundary); + assert.equal(parts.length, 2); + const data = parts.reduce((acc, part) => ({ + ...acc, + [part.name]: JSON.parse(part.data.toString()), + }), {}); + assert.deepStrictEqual(data.data, { f1: '10', f2: '102' }); + return true; + }) + .reply(200, { + thankYouMessage, + }); + +export const sample = { + action: 'http://localhost:3000/submit', + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: "f1 & '2'", + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + default: '10', + }, + { + fieldType: 'text-input', + id: 'text-input-3', + name: 'f3', + required: true, + constraintMessages: { + required: 'Please fill in this field.', + }, + }, + { + fieldType: 'button', + id: 'button', + buttonType: 'submit', + events: { + click: 'submitForm()', + }, + }], +}; + +export function op(block) { + const btn = block.querySelector('#button'); + btn.click(); + const form = block.querySelector('form'); + form.dispatchEvent(new Event('submit')); +} + +export function expect(block) { + assert.equal(scope.isDone(), false); + const t3 = block.querySelector('#text-input-3').parentElement; + const message = t3.querySelector('.field-invalid .field-description'); + assert.equal(message.textContent, 'Please fill in this field.', 'no error message shown'); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/submit/successful-submit-baseurl.js b/test/unit/fixtures/submit/successful-submit-baseurl.js new file mode 100644 index 0000000..ac3acbf --- /dev/null +++ b/test/unit/fixtures/submit/successful-submit-baseurl.js @@ -0,0 +1,83 @@ +import assert from 'assert'; +import nock from 'nock'; +import multipart from 'parse-multipart-data'; +import { setSubmitBaseUrl } from '../../../../blocks/form/constant.js'; + +const thankYouMessage = 'thank you for submitting the form'; + +const scope = nock('http://abc.com') + .post('/submit-success-baseurl', function test(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + const boundary = contentType.match('multipart/form-data; boundary=(.+)')?.[1]; + const parts = multipart.parse(Buffer.from(body), boundary); + assert.equal(parts.length, 2); + const data = parts.reduce((acc, part) => ({ + ...acc, + [part.name]: JSON.parse(part.data.toString()), + }), {}); + assert.deepStrictEqual(data.data, { f1: '10', f2: '102', 'radio-group-boolean': true }); + return true; + }) + .reply(200, { + thankYouMessage, + }); + +export const sample = { + action: '/submit-success-baseurl', + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: "f1 & '2'", + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + default: '10', + }, + { + fieldType: 'radio-group', + id: 'radio-group-boolean', + name: 'radio-group-boolean', + type: 'boolean', + enumNames: ['yes', 'no'], + }, + { + fieldType: 'button', + id: 'button', + events: { + click: 'submitForm()', + }, + }], +}; + +export function before() { + setSubmitBaseUrl('http://abc.com'); +} + +export function op(block) { + const radio = block.querySelectorAll('input[name="radio-group-boolean"]')[0]; + radio.click(); + radio.dispatchEvent(new Event('change', { bubbles: true })); + const btn = block.querySelector('#button'); + btn.click(); + const form = block.querySelector('form'); + form.dispatchEvent(new Event('submit')); +} + +export function expect(block) { + assert.equal(scope.isDone(), true, 'submit call was not made'); + const el = block.querySelector('.form-message.success-message'); + assert.equal(el.textContent, thankYouMessage); +} + +export function after() { + setSubmitBaseUrl(''); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/submit/successful-submit-redirect.js b/test/unit/fixtures/submit/successful-submit-redirect.js new file mode 100644 index 0000000..e8101e0 --- /dev/null +++ b/test/unit/fixtures/submit/successful-submit-redirect.js @@ -0,0 +1,69 @@ +import assert from 'assert'; +import nock from 'nock'; +import multipart from 'parse-multipart-data'; +import sinon from 'sinon'; + +const scope = nock('http://localhost:3000') + .post('/submit-success', function test(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + const boundary = contentType.match('multipart/form-data; boundary=(.+)')?.[1]; + const parts = multipart.parse(Buffer.from(body), boundary); + assert.equal(parts.length, 2); + const data = parts.reduce((acc, part) => ({ + ...acc, + [part.name]: JSON.parse(part.data.toString()), + }), {}); + assert.deepStrictEqual(data.data, { f1: '10', f2: '102' }); + return true; + }) + .reply(200, { + redirectUrl: 'http://localhost:3000/abc.html', + }); + +export const sample = { + action: 'http://localhost:3000/submit-success', + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: "f1 & '2'", + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + default: '10', + }, { + fieldType: 'button', + id: 'button', + events: { + click: 'submitForm()', + }, + }], +}; +const mock = sinon.fake(); +export function op(block) { + global.window = Object.create(window); + Object.defineProperty(global.window, 'location', { + value: { + assign: mock, + }, + writable: true, + }); + const btn = block.querySelector('#button'); + btn.click(); + const form = block.querySelector('form'); + form.dispatchEvent(new Event('submit')); +} + +export function expect() { + assert.equal(scope.isDone(), true, 'submit call was not made'); + assert(mock.calledOnce); + assert(mock.calledWith('http://localhost:3000/abc.html')); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/submit/successful-submit.js b/test/unit/fixtures/submit/successful-submit.js new file mode 100644 index 0000000..5d2c9fa --- /dev/null +++ b/test/unit/fixtures/submit/successful-submit.js @@ -0,0 +1,76 @@ +import assert from 'assert'; +import nock from 'nock'; +import multipart from 'parse-multipart-data'; + +const thankYouMessage = 'thank you for submitting the form'; + +const scope = nock('http://localhost:3000') + .post('/submit-success', function test(body) { + // using a function syntax here instead of array because the this parameter is + // set during the call + const contentType = this.headers['content-type']; + const boundary = contentType.match('multipart/form-data; boundary=(.+)')?.[1]; + const parts = multipart.parse(Buffer.from(body), boundary); + assert.equal(parts.length, 2); + const data = parts.reduce((acc, part) => ({ + ...acc, + [part.name]: JSON.parse(part.data.toString()), + }), {}); + assert.deepStrictEqual(data.data, { f1: '10', f2: '102', 'radio-group-boolean': true }); + return true; + }) + .reply(200, { + thankYouMessage, + }); + +export const sample = { + action: 'http://localhost:3000/submit-success', + items: [{ + fieldType: 'text-input', + id: 'text-input', + name: 'f2', + rules: { + value: "f1 & '2'", + }, + }, + { + fieldType: 'text-input', + id: 'text-input-2', + name: 'f1', + default: '10', + }, + { + fieldType: 'radio-group', + id: 'radio-group-boolean', + name: 'radio-group-boolean', + type: 'boolean', + enumNames: ['yes', 'no'], + }, + { + fieldType: 'button', + id: 'button', + events: { + click: 'submitForm()', + }, + }], +}; + +export function op(block) { + const radio = block.querySelectorAll('input[name="radio-group-boolean"]')[0]; + radio.click(); + radio.dispatchEvent(new Event('change', { bubbles: true })); + const btn = block.querySelector('#button'); + btn.click(); + const form = block.querySelector('form'); + form.dispatchEvent(new Event('submit')); +} + +export function expect(block) { + assert.equal(scope.isDone(), true, 'submit call was not made'); + const el = block.querySelector('.form-message.success-message'); + assert.equal(el.textContent, thankYouMessage); + assert.equal(el.nextSibling.nodeName, 'FORM'); + assert.equal(el.parentElement.nodeName, 'DIV'); +} + +export const opDelay = 100; diff --git a/test/unit/fixtures/ue/events/event-add.js b/test/unit/fixtures/ue/events/event-add.js new file mode 100644 index 0000000..b43b6d0 --- /dev/null +++ b/test/unit/fixtures/ue/events/event-add.js @@ -0,0 +1,43 @@ +// eslint-disable-next-line import/prefer-default-export +export const ueAddEvent = { + request: { + connections: [ + { + name: 'aemconnection', + protocol: 'xwalk', + uri: 'https://author-p10652-e203356-cmstg.adobeaemcloud.com', + }, + ], + target: { + container: { + resource: 'urn:aemconnection:/content/ng-new/index/jcr:content/root/section_0/form/panelcontainer', + type: 'container', + prop: '', + }, + }, + content: { + name: 'panel', + xwalk: { + page: { + resourceType: 'core/fd/components/form/panelcontainer/v1/panelcontainer', + template: { + 'jcr:title': 'Panel', + fieldType: 'panel', + enabled: true, + visible: true, + }, + }, + }, + }, + }, + response: { + updates: [ + { + resource: 'urn:aemconnection:/content/ng-new/index/jcr:content/root/section_0/form', + type: 'container', + content: '\n
\n
\n
\n
\n                "{\\"id\\":\\"L2NvbnRlbnQvbmctbmV3L2luZGV4L2pjcjpjb250ZW50L3Jvb3Qvc2VjdGlvbl8wL2Zvcm0=\\",\\"fieldType\\":\\"form\\",\\"lang\\":\\"en-US\\",\\"action\\":\\"\\/adobe\\/forms\\/af\\/submit\\/L2NvbnRlbnQvbmctbmV3L2luZGV4L2pjcjpjb250ZW50L3Jvb3Qvc2VjdGlvbl8wL2Zvcm0=\\",\\"properties\\":{\\"fd:path\\":\\"\\/content\\/ng-new\\/index\\/jcr:content\\/root\\/section_0\\/form\\",\\"fd:schemaType\\":\\"BASIC\\",\\"fd:roleAttribute\\":null,\\"fd:formDataEnabled\\":false},\\"events\\":{\\"custom:setProperty\\":[\\"$event.payload\\"]},\\":itemsOrder\\":[\\"panelcontainer\\"],\\"metadata\\":{\\"version\\":\\"1.0.0\\",\\"grammar\\":\\"json-formula-1.0.0\\"},\\"adaptiveform\\":\\"0.14.0\\",\\":type\\":\\"fd\\/franklin\\/components\\/form\\/v1\\/form\\",\\":items\\":{\\"panelcontainer\\":{\\"id\\":\\"panelcontainer-c39a980bad\\",\\"fieldType\\":\\"panel\\",\\"name\\":\\"panelcontainer1721825915375\\",\\"visible\\":true,\\"enabled\\":true,\\"label\\":{\\"value\\":\\"Wizard\\"},\\"events\\":{\\"custom:setProperty\\":[\\"$event.payload\\"]},\\"properties\\":{\\"fd:dor\\":{\\"dorExclusion\\":false,\\"dorExcludeTitle\\":false,\\"dorExcludeDescription\\":false},\\"fd:path\\":\\"\\/content\\/ng-new\\/index\\/jcr:content\\/root\\/section_0\\/form\\/panelcontainer\\"},\\":itemsOrder\\":[\\"panelcontainer\\"],\\":type\\":\\"wizard\\",\\":items\\":{\\"panelcontainer\\":{\\"id\\":\\"panelcontainer-215d71f184\\",\\"fieldType\\":\\"panel\\",\\"name\\":\\"panelcontainer\\",\\"visible\\":true,\\"enabled\\":true,\\"label\\":{\\"value\\":\\"Panel\\"},\\"events\\":{\\"custom:setProperty\\":[\\"$event.payload\\"]},\\"properties\\":{\\"fd:dor\\":{\\"dorExclusion\\":false,\\"dorExcludeTitle\\":false,\\"dorExcludeDescription\\":false},\\"fd:path\\":\\"\\/content\\/ng-new\\/index\\/jcr:content\\/root\\/section_0\\/form\\/panelcontainer\\/panelcontainer\\"},\\":type\\":\\"core\\/fd\\/components\\/form\\/panelcontainer\\/v1\\/panelcontainer\\"}}}}}"\n            
\n
\n
\n
\n', + }, + ], + resource: 'urn:aemconnection:/content/ng-new/index/jcr:content/root/section_0/form/panelcontainer/panelcontainer', + }, +}; diff --git a/test/unit/fixtures/ue/events/event-patch.js b/test/unit/fixtures/ue/events/event-patch.js new file mode 100644 index 0000000..5a87fd1 --- /dev/null +++ b/test/unit/fixtures/ue/events/event-patch.js @@ -0,0 +1,36 @@ +// eslint-disable-next-line import/prefer-default-export +export const uePatchEvent = { + request: { + connections: [ + { + name: 'aemconnection', + protocol: 'xwalk', + uri: 'https://author-p10652-e203356-cmstg.adobeaemcloud.com', + }, + ], + target: { + resource: 'urn:aemconnection:/content/ng-new/index/jcr:content/root/section_0/form/panelcontainer/panelcontainer', + type: 'container', + prop: '', + }, + patch: [ + { + op: 'replace', + path: '/jcr:title', + value: 'Panel new', + }, + ], + }, + response: { + updates: [ + { + resource: 'urn:aemconnection:/content/ng-new/index/jcr:content/root/section_0/form', + content: '\n
\n
\n
\n
\n                "{\\"id\\":\\"L2NvbnRlbnQvbmctbmV3L2luZGV4L2pjcjpjb250ZW50L3Jvb3Qvc2VjdGlvbl8wL2Zvcm0=\\",\\"fieldType\\":\\"form\\",\\"lang\\":\\"en-US\\",\\"action\\":\\"\\/adobe\\/forms\\/af\\/submit\\/L2NvbnRlbnQvbmctbmV3L2luZGV4L2pjcjpjb250ZW50L3Jvb3Qvc2VjdGlvbl8wL2Zvcm0=\\",\\"properties\\":{\\"fd:path\\":\\"\\/content\\/ng-new\\/index\\/jcr:content\\/root\\/section_0\\/form\\",\\"fd:schemaType\\":\\"BASIC\\",\\"fd:roleAttribute\\":null,\\"fd:formDataEnabled\\":false},\\"events\\":{\\"custom:setProperty\\":[\\"$event.payload\\"]},\\":itemsOrder\\":[\\"panelcontainer\\"],\\"metadata\\":{\\"version\\":\\"1.0.0\\",\\"grammar\\":\\"json-formula-1.0.0\\"},\\"adaptiveform\\":\\"0.14.0\\",\\":type\\":\\"fd\\/franklin\\/components\\/form\\/v1\\/form\\",\\":items\\":{\\"panelcontainer\\":{\\"id\\":\\"panelcontainer-c39a980bad\\",\\"fieldType\\":\\"panel\\",\\"name\\":\\"panelcontainer1721825915375\\",\\"visible\\":true,\\"enabled\\":true,\\"label\\":{\\"value\\":\\"Wizard\\"},\\"events\\":{\\"custom:setProperty\\":[\\"$event.payload\\"]},\\"properties\\":{\\"fd:dor\\":{\\"dorExclusion\\":false,\\"dorExcludeTitle\\":false,\\"dorExcludeDescription\\":false},\\"fd:path\\":\\"\\/content\\/ng-new\\/index\\/jcr:content\\/root\\/section_0\\/form\\/panelcontainer\\"},\\":itemsOrder\\":[\\"panelcontainer\\"],\\":type\\":\\"wizard\\",\\":items\\":{\\"panelcontainer\\":{\\"id\\":\\"panelcontainer-215d71f184\\",\\"fieldType\\":\\"panel\\",\\"name\\":\\"panelcontainer1721841368776\\",\\"visible\\":true,\\"enabled\\":true,\\"label\\":{\\"value\\":\\"Panel new\\"},\\"events\\":{\\"custom:setProperty\\":[\\"$event.payload\\"]},\\"properties\\":{\\"fd:dor\\":{\\"dorExclusion\\":false,\\"dorExcludeTitle\\":false,\\"dorExcludeDescription\\":false},\\"fd:path\\":\\"\\/content\\/ng-new\\/index\\/jcr:content\\/root\\/section_0\\/form\\/panelcontainer\\/panelcontainer\\"},\\":type\\":\\"core\\/fd\\/components\\/form\\/panelcontainer\\/v1\\/panelcontainer\\"}}}}}"\n            
\n
\n
\n
\n', + }, + ], + }, + patch: { + name: 'jcr:title', + value: 'Panel new 1', + }, +}; diff --git a/test/unit/fixtures/ue/events/formdefinition-add.js b/test/unit/fixtures/ue/events/formdefinition-add.js new file mode 100644 index 0000000..80f1a39 --- /dev/null +++ b/test/unit/fixtures/ue/events/formdefinition-add.js @@ -0,0 +1,30 @@ +// eslint-disable-next-line import/prefer-default-export +export const ueFormDefForAddTest = { + id: 'L2NvbnRlbnQvbmctbmV3L2luZGV4L2pjcjpjb250ZW50L3Jvb3Qvc2VjdGlvbl8wL2Zvcm0=', + fieldType: 'form', + lang: 'en', + action: '/adobe/forms/af/submit/L2NvbnRlbnQvbmctbmV3L2luZGV4L2pjcjpjb250ZW50L3Jvb3Qvc2VjdGlvbl8wL2Zvcm0=', + properties: { + 'fd:path': '/content/ng-new/index/jcr:content/root/section_0/form', + }, + ':itemsOrder': [ + 'panelcontainer', + ], + ':type': 'fd/franklin/components/form/v1/form', + ':items': { + panelcontainer: { + id: 'panelcontainer-c39a980bad', + fieldType: 'panel', + name: 'panelcontainer1721825915375', + visible: true, + enabled: true, + label: { + value: 'Wizard', + }, + properties: { + 'fd:path': '/content/ng-new/index/jcr:content/root/section_0/form/panelcontainer', + }, + ':type': 'wizard', + }, + }, +}; diff --git a/test/unit/fixtures/ue/events/formdefinition-patch.js b/test/unit/fixtures/ue/events/formdefinition-patch.js new file mode 100644 index 0000000..faeb8f5 --- /dev/null +++ b/test/unit/fixtures/ue/events/formdefinition-patch.js @@ -0,0 +1,51 @@ +// eslint-disable-next-line import/prefer-default-export +export const ueFormDefForPatchTest = { + id: 'L2NvbnRlbnQvbmctbmV3L2luZGV4L2pjcjpjb250ZW50L3Jvb3Qvc2VjdGlvbl8wL2Zvcm0=', + fieldType: 'form', + lang: 'en', + action: '/adobe/forms/af/submit/L2NvbnRlbnQvbmctbmV3L2luZGV4L2pjcjpjb250ZW50L3Jvb3Qvc2VjdGlvbl8wL2Zvcm0=', + properties: { + 'fd:path': '/content/ng-new/index/jcr:content/root/section_0/form', + }, + ':itemsOrder': [ + 'panelcontainer', + ], + ':type': 'fd/franklin/components/form/v1/form', + ':items': { + panelcontainer: { + id: 'panelcontainer-c39a980bad', + fieldType: 'panel', + name: 'panelcontainer1721825915375', + visible: true, + enabled: true, + label: { + value: 'Wizard', + }, + properties: { + 'fd:path': '/content/ng-new/index/jcr:content/root/section_0/form/panelcontainer', + }, + ':itemsOrder': [ + 'panelcontainer', + ], + ':type': 'wizard', + ':items': { + panelcontainer: { + id: 'panelcontainer-215d71f184', + fieldType: 'panel', + name: 'panelcontainer1721841368776', + visible: true, + enabled: true, + columnCount: 12, + gridClassNames: 'aem-Grid aem-Grid--12 aem-Grid--default--12', + label: { + value: 'Panel', + }, + properties: { + 'fd:path': '/content/ng-new/index/jcr:content/root/section_0/form/panelcontainer/panelcontainer', + }, + ':type': 'core/fd/components/form/panelcontainer/v1/panelcontainer', + }, + }, + }, + }, +}; diff --git a/test/unit/form.authoring.test.js b/test/unit/form.authoring.test.js new file mode 100644 index 0000000..e7cbb57 --- /dev/null +++ b/test/unit/form.authoring.test.js @@ -0,0 +1,146 @@ +/* eslint-env mocha */ +import assert from 'assert'; +import path from 'path'; +import fs from 'fs'; +import { + annotateFormForEditing, getItems, getFieldById, applyChanges, +} from '../../scripts/form-editor-support.js'; +import { generateFormRendition } from '../../blocks/form/form.js'; +import { ueFormDef } from './forms/universaleditorform.js'; +import { ueAddEvent } from './fixtures/ue/events/event-add.js'; +import { uePatchEvent } from './fixtures/ue/events/event-patch.js'; +import { ueFormDefForAddTest } from './fixtures/ue/events/formdefinition-add.js'; +import { ueFormDefForPatchTest } from './fixtures/ue/events/formdefinition-patch.js'; +import { renderForm } from './testUtils.js'; + +describe('Universal Editor Authoring Test Cases', () => { + it('test form annotation for UE', async () => { + document.documentElement.classList.add('adobe-ue-edit'); + const formEl = document.createElement('form'); + + await generateFormRendition(ueFormDef, formEl, getItems); + + annotateFormForEditing(formEl, ueFormDef); + + assert.equal(formEl.classList.contains('edit-mode'), true, 'form is not having edit-mode class'); + + const formFieldMap = {}; + + function testAnnotation(node, fd, auetype, auemodel) { + assert.equal(node.dataset.aueType, auetype, `data-aue-type not set ${fd.id}`); + assert.equal(node.dataset.aueResource, `urn:aemconnection:${fd.properties['fd:path']}`, `data-aue-resource not set ${fd.id}`); + assert.equal(node.dataset.aueModel, auemodel, `data-aue-model not set ${fd.id}`); + assert.equal(node.dataset.aueLabel, fd.label.value, `data-aue-label not set ${fd.id}`); + if (auetype === 'container') { + assert.equal(node.dataset.aueFilter, 'form'); + } + } + + function testPlainTextAnnotation(node, fd, auetype, auemodel) { + assert.equal(node.dataset.aueType, auetype, `data-aue-type not set ${fd.id}`); + assert.equal(node.dataset.aueResource, `urn:aemconnection:${fd.properties['fd:path']}`, `data-aue-resource not set ${fd.id}`); + assert.equal(node.dataset.aueModel, auemodel, `data-aue-model not set ${fd.id}`); + assert.equal(node.dataset.aueLabel, 'Text'); + assert.equal(node.dataset.aueProp, 'value'); + assert.equal(node.dataset.aueBehavior, 'component'); + } + + function testChildren(items, formDef, fieldMap) { + items.forEach((node) => { + if (node.classList.contains('field-wrapper')) { + const fd = getFieldById(ueFormDef, node.dataset.id, formFieldMap); + if (node.classList.contains('panel-wrapper') && !fd.properties['fd:fragment']) { + if (fd[':type'] === 'wizard') { + testAnnotation(node, fd, 'container', fd[':type']); + } else { + testAnnotation(node, fd, 'container', fd.fieldType); + } + testChildren(node.childNodes, formDef, fieldMap); + } else if (fd.properties['fd:fragment'] && node.classList.contains('edit-mode')) { + testAnnotation(node, fd, 'component', 'form-fragment'); + const textNodeCount = Array.from(node.childNodes) + .filter((child) => child.nodeType === 3).length; + assert.equal(textNodeCount, Object.keys(fd[':items']).length, `fragment items not set ${textNodeCount} ${fd.id}`); + } else if (fd.fieldType === 'plain-text') { + testPlainTextAnnotation(node, fd, 'richtext', fd.fieldType); + } else if (fd[':type'] === 'rating') { + testAnnotation(node, fd, 'component', fd[':type']); + } else if (!fd.properties['fd:fragment']) { + testAnnotation(node, fd, 'component', fd.fieldType); + } + } + }); + } + testChildren(formEl.childNodes, ueFormDef, formFieldMap); + }); + + it('test form component definitions for UE', async () => { + const definitionFilePath = path.resolve('component-definition.json'); + const modelsFilePath = path.resolve('component-models.json'); + const filtersFilePath = path.resolve('component-filters.json'); + const componentDefinitions = fs.readFileSync(definitionFilePath, 'utf8'); + const componentModels = fs.readFileSync(modelsFilePath, 'utf8'); + const filters = fs.readFileSync(filtersFilePath, 'utf8'); + try { + const definition = JSON.parse(componentDefinitions); + const componentModelsArray = JSON.parse(componentModels); + const filtersArray = JSON.parse(filters); + const { components: formComponents } = filtersArray.find((filter) => filter.id === 'form'); + const idsArray = componentModelsArray.map((component) => component.id); + if (definition) { + definition?.groups.forEach((group) => { + if (group.id === 'form-general') { + group.components.forEach((component) => { + const cmpId = component.id; + if (!formComponents.includes(cmpId)) { + throw new Error(`component not present in filter ${component.id}`); + } + const { fieldType } = component.plugins.xwalk.page.template; + let cmpIdfromFieldType = fieldType; + if (fieldType === 'image' || fieldType === 'button') { + cmpIdfromFieldType = `form-${fieldType}`; + } else if (cmpId === 'form-fragment') { + cmpIdfromFieldType = 'form-fragment'; + } + if (!idsArray.includes(cmpIdfromFieldType)) { + throw new Error(`component model not found for component ${component.id}`); + } + }); + } + }); + } + } catch (err) { + assert.equal(true, false, err); + } + }); + + it('test UE add event', async () => { + await renderForm(ueFormDefForAddTest); + window.hlx.codeBasePath = '../../'; + const applied = await applyChanges({ detail: ueAddEvent }); + assert.equal(applied, true); + const formEl = document.querySelector('form'); + assert.equal(formEl.childNodes.length, 1); // only 1 panel is there + const panel = formEl.querySelector('.panel-wrapper'); + // 1 legend + wizard menu item + panel + wizard button wrapper + assert.equal(panel.childNodes.length, 4); + document.body.replaceChildren(); + }); + + it('test UE patch event', async () => { + await renderForm(ueFormDefForPatchTest); + window.hlx.codeBasePath = '../../'; + const applied = await applyChanges({ detail: uePatchEvent }); + assert.equal(applied, true); + const formEl = document.querySelector('form'); + assert.equal(formEl.childNodes.length, 1); // only 1 panel is there + const panel = formEl.querySelector('.panel-wrapper'); + // 1 legend + wizard menu item + panel + wizard button wrapper + assert.equal(panel.childNodes.length, 4); + const labelEl = panel.querySelector('legend[for="panelcontainer-215d71f184"]'); + assert.equal(labelEl.textContent, 'Panel new'); + const wizardMenuItems = panel.querySelectorAll('.wizard-menu-item'); + assert.equal(wizardMenuItems.length, 1); + document.body.replaceChildren(); + }); +}); diff --git a/test/unit/forms/advanceEnquiry.js b/test/unit/forms/advanceEnquiry.js new file mode 100644 index 0000000..f082d2b --- /dev/null +++ b/test/unit/forms/advanceEnquiry.js @@ -0,0 +1,32 @@ +// eslint-disable-next-line import/prefer-default-export +export const advanceEnquiry = { + total: 13, + offset: 0, + limit: 13, + data: [{ + Name: 'startDate', Type: 'date', Placeholder: '', Label: 'Start Date', Mandatory: 'true', Value: '45360', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', + }, { + Name: 'endDate', Type: 'date', Placeholder: '', Label: 'End Date', Mandatory: 'true', Value: '45363', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', + }, { + Name: 'destination', Type: 'select', Placeholder: 'Select your destination', Label: 'Destination', Mandatory: '', Value: 'US', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: 'https://main--test-xwalk--jalagari.hlx.page/enquiry.json?sheet=country', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', + }, { + Name: 'class', Type: 'select', Placeholder: 'Please Select', Label: 'Class of service', Mandatory: '', Value: '', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: 'Economy, Business, First', OptionNames: 'Economy Class, Business Class, First Class', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', + }, { + Name: 'budget', Type: 'number', Placeholder: '', Label: 'Room Budget', Mandatory: '', Value: '1000', Visible: '', Min: '500', Max: '10000', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '500', ReadOnly: '', Describe: '', + }, { + Name: 'amount', Type: 'number', Placeholder: '', Label: 'Estimated Trip Cost', Mandatory: '', Value: '3000', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '=G6*DAYS(G3,G2)', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: 'true', Describe: '', + }, { + Name: 'panel-1', Type: 'fieldset', Placeholder: '', Label: 'Traveler Info', Mandatory: '', Value: '', Visible: '', Min: '1', Max: '3', Fieldset: '', Repeatable: 'true', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '12', Checked: '', Step: '', ReadOnly: '', Describe: '', + }, { + Name: 'name', Type: 'text', Placeholder: '', Label: 'Name', Mandatory: '', Value: '', Visible: '', Min: '', Max: '', Fieldset: 'panel-1', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', + }, { + Name: 'age', Type: 'number', Placeholder: '', Label: 'Age', Mandatory: '', Value: '', Visible: '', Min: '', Max: '', Fieldset: 'panel-1', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '6', Checked: '', Step: '', ReadOnly: '', Describe: '', + }, { + Name: 'subscribe', Type: 'checkbox', Placeholder: '', Label: 'Do you like subscribe for Magazine & Activities?', Mandatory: '', Value: 'true', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '', Checked: 'false', Step: '', ReadOnly: '', Describe: '', + }, { + Name: 'email', Type: 'email', Placeholder: '', Label: 'Email', Mandatory: '', Value: '', Visible: 'false', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '=R11=TRUE()', 'Column Span': '', Checked: '', Step: '', ReadOnly: '', Describe: '', + }, { + Name: 'submit', Type: 'submit', Placeholder: '', Label: 'Submit', Mandatory: '', Value: '', Visible: '', Min: '', Max: '', Fieldset: '', Repeatable: '', Options: '', OptionNames: '', 'Value Expression': '', 'Visible Expression': '', 'Column Span': '', Checked: '', Step: '', ReadOnly: '', Describe: '', + }], + ':type': 'sheet', +}; diff --git a/test/unit/forms/universaleditorform.js b/test/unit/forms/universaleditorform.js new file mode 100644 index 0000000..b0a5a67 --- /dev/null +++ b/test/unit/forms/universaleditorform.js @@ -0,0 +1,127 @@ +// eslint-disable-next-line import/prefer-default-export +export const ueFormDef = { + id: '$form', + fieldType: 'form', + ':items': { + textinput: { + id: 'text-input', + fieldType: 'text-input', + label: { + value: 'Text Input', + }, + properties: { + 'fd:path': '/content/test2/index/jcr:content/root/section_0/form/textinput', + }, + }, + rating: { + id: 'rating-input', + fieldType: 'text-input', + label: { + value: 'Text Input', + }, + properties: { + 'fd:path': '/content/test2/index/jcr:content/root/section_0/form/textinput', + }, + ':type': 'rating', + }, + panelcontainer: { + id: 'panelcontainer-084914a499', + fieldType: 'panel', + name: 'panelcontainer1716813758388', + label: { + value: 'Panel', + }, + properties: { + 'fd:path': '/content/test2/index/jcr:content/root/section_0/form/panelcontainer', + }, + ':itemsOrder': [ + 'textinput', + ], + ':type': 'core/fd/components/form/panelcontainer/v1/panelcontainer', + ':items': { + textinput: { + id: 'textinput-d330c9b8e7', + fieldType: 'text-input', + name: 'textinput1716813763576', + type: 'string', + label: { + value: 'Text Input', + }, + properties: { + 'fd:path': '/content/test2/index/jcr:content/root/section_0/form/panelcontainer/textinput', + }, + ':type': 'core/fd/components/form/textinput/v1/textinput', + }, + }, + }, + fragment_1198843043: { + id: 'fragment-1c5674199a', + fieldType: 'panel', + name: 'fragment_11988430431717085995616', + properties: { + 'fd:path': '/content/test2/index/jcr:content/root/section_0/form/fragment_1198843043', + 'fd:fragment': true, + }, + label: { + value: 'Fragment new', + }, + ':items': { + textinput_360975137: { + id: 'textinput-105b08b11c', + fieldType: 'text-input', + name: 'textinput1716794906685', + type: 'string', + label: { + value: 'street address', + }, + properties: { + 'fd:path': '/content/test2/formfrag3/jcr:content/root/section/form/textinput', + }, + ':type': 'core/fd/components/form/textinput/v1/textinput', + }, + }, + ':itemsOrder': [ + 'textinput_360975137', + ], + ':type': 'core/fd/components/form/panelcontainer/v1/panelcontainer', + }, + panelcontainer_1558601327: { + id: 'panelcontainer-4ea210a289', + fieldType: 'panel', + name: 'panelcontainer_15586013271719389324331', + label: { + value: 'Wizard', + }, + properties: { + 'fd:path': '/content/forms-demo-ng/index/jcr:content/root/section_0/form_329757644/panelcontainer_1558601327', + }, + ':itemsOrder': [ + 'textinput', + ], + ':items': { + textinput: { + id: 'textinput-748372f950', + fieldType: 'text-input', + name: 'textinput1719389342149', + visible: true, + type: 'string', + enabled: true, + label: { + value: 'Text Input', + }, + events: { + 'custom:setProperty': [ + '$event.payload', + ], + }, + properties: { + 'fd:path': '/content/forms-demo-ng/index/jcr:content/root/section_0/form_329757644/panelcontainer_1558601327/textinput', + }, + ':type': 'core/fd/components/form/textinput/v1/textinput', + }, + }, + ':type': 'wizard', + }, + }, + ':itemsOrder': ['textinput', 'panelcontainer', 'fragment_1198843043'], +}; diff --git a/test/unit/recaptcha.test.js b/test/unit/recaptcha.test.js new file mode 100644 index 0000000..2ad7c4e --- /dev/null +++ b/test/unit/recaptcha.test.js @@ -0,0 +1,60 @@ +/* eslint-env mocha */ +import assert from 'assert'; +import sinon from 'sinon'; +import jsdom from 'jsdom'; +import GoogleReCaptcha from '../../blocks/form/integrations/recaptcha.js'; + +const siteKey = 'test-site-key'; +const testToken = 'token123'; +let form; + +describe('Google recaptcha Integeration', () => { + beforeEach(() => { + global.window.grecaptcha = { + ready: (callback) => { + callback(); + }, + execute: (key, options) => { + if (key === siteKey && options.action === 'submit') { + return Promise.resolve(testToken); + } + return Promise.resolve(null); + }, + }; + // Mock the IntersectionObserver + global.IntersectionObserver = sinon.stub().returns({ + observe: sinon.spy(), + disconnect: sinon.spy(), + }); + + // Mock the form and button + const { JSDOM } = jsdom; + const dom = new JSDOM('
'); + form = dom.window.document.querySelector('form'); + dom.window.grecaptcha = global.grecaptcha; + }); + + it('should load the captcha when the submit button is intersecting', () => { + const recaptcha = new GoogleReCaptcha(siteKey, 123); + recaptcha.loadCaptcha(form); + + // Simulate the IntersectionObserver callback + const callback = global.IntersectionObserver.getCall(0).args[0]; + callback([{ isIntersecting: true }]); + + const script = document.head.querySelector('script'); + assert.equal(script.src, `https://www.google.com/recaptcha/api.js?render=${siteKey}`, 'Expected the script to be loaded'); + }); + + it('getToken should return null if siteKey is not set', async () => { + const recaptcha = new GoogleReCaptcha(null, 123); + const token = await recaptcha.getToken(); + assert.equal(token, null, 'Expected token to be null'); + }); + + it('getToken should return token', async () => { + const recaptcha = new GoogleReCaptcha(siteKey, 123); + const token = await recaptcha.getToken(); + assert.equal(token, testToken, 'Expected token to be null'); + }); +}); diff --git a/test/unit/render.test.js b/test/unit/render.test.js new file mode 100644 index 0000000..8c05552 --- /dev/null +++ b/test/unit/render.test.js @@ -0,0 +1,18 @@ +/* eslint-env mocha */ +import { + executeTestInFolder, testBasicMarkup, testDynamism, testFormFetch, +} from './testUtils.js'; + +executeTestInFolder('./test/unit/fixtures/components/'); +executeTestInFolder('./test/unit/fixtures/components/text-input/', testBasicMarkup, true); + +executeTestInFolder('./test/unit/fixtures/form/'); +executeTestInFolder('./test/unit/fixtures/dynamic/', testDynamism); +executeTestInFolder('./test/unit/fixtures/reset/', testDynamism); + +executeTestInFolder('./test/unit/fixtures/submit/', testDynamism); +executeTestInFolder('./test/unit/fixtures/doc-based-submit/', testDynamism, true); + +executeTestInFolder('./test/unit/fixtures/prefill/', testDynamism); +executeTestInFolder('./test/unit/fixtures/docForms/', testDynamism, true); +executeTestInFolder('./test/unit/fixtures/form-fetch/', testFormFetch); diff --git a/test/unit/setup-env.js b/test/unit/setup-env.js new file mode 100644 index 0000000..699269e --- /dev/null +++ b/test/unit/setup-env.js @@ -0,0 +1,69 @@ +/* eslint-disable no-underscore-dangle */ +import 'jsdom-global'; +import 'jsdom-global/register.js'; +import fetch from 'node-fetch'; +import sinon from 'sinon'; + +global.MutationObserver = sinon.stub().returns({ + observe: sinon.spy(), + disconnect: sinon.spy(), +}); + +class Headers { + constructor() { + this.headers = {}; + } + + set(key, value) { + this.headers[key] = value; + } + + get(key) { + return this.headers[key]; + } +} + +global.Headers = Headers; + +global.fetch = (url, opts) => { + let finalUrl = url; + if (!(url.startsWith('https') || url.startsWith('http'))) { + finalUrl = `http://localhost:3000${url}`; + } + + const headers = new Headers(); + headers.set('Content-Type', 'application/json'); + if (global.fetch?.mockData?.[finalUrl]) { + return { + headers, + json: () => (global.fetch?.mockData?.[finalUrl] || {}), + }; + } return fetch(finalUrl, opts); +}; + +global.fetch.mockData = {}; + +global.HTMLElement.prototype.scrollIntoView = function empty() {}; + +global.DataTransfer = function DataTransfer() { + this.items = { + add: (f) => { + this.files.push(f); + }, + }; + this.files = []; + return this; +}; + +global.DOMParser = window.DOMParser; + +Object.defineProperties(HTMLInputElement.prototype, { + files: { + set(v) { + this._files = v; + }, + get() { + return this._files; + }, + }, +}); diff --git a/test/unit/testUtils.js b/test/unit/testUtils.js new file mode 100644 index 0000000..30eb47d --- /dev/null +++ b/test/unit/testUtils.js @@ -0,0 +1,238 @@ +/* eslint-env mocha */ +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import * as dom from 'dom-compare'; +import decorate, { DELAY_MS, generateFormRendition } from '../../blocks/form/form.js'; +import { annotateFormForEditing, getItems } from '../../scripts/form-editor-support.js'; +import { resetIds } from '../../blocks/form/util.js'; +import { getCustomComponents, setCustomComponents } from '../../blocks/form/mappings.js'; + +function escapeHTML(str) { + return (str.replace(/[&<>'"]/g, (tag) => ({ + '&': '&', + '<': '<', + '>': '>', + "'": ''', + '"': '"', + }[tag]))); +} + +export function createBlock(def) { + const pre = `

+    ${escapeHTML(JSON.stringify(JSON.stringify(def)))}
+  
`; + const div = document.createElement('div'); + div.innerHTML = pre; + return div; +} + +export function createBlockWithUrl(def, url) { + global.fetch.mockData = { + [url]: def, + }; + const anchor = ``; + const div = document.createElement('div'); + div.innerHTML = anchor; + return div; +} + +function createElementFromHTML(htmlString, fieldDef) { + const { action } = fieldDef; + const source = fieldDef?.[':type'] || 'aem'; + const form = document.createElement('form'); + form.innerHTML = htmlString.trim(); + form.dataset.action = action; + form.dataset.source = source; + form.dataset.rules = source === 'aem'; + form.dataset.redirectUrl = fieldDef.redirectUrl || ''; + form.dataset.thankYouMsg = fieldDef.thankYouMsg || ''; + form.dataset.id = fieldDef.id; + form.noValidate = true; + // Change this to div.childNodes to support multiple top-level nodes. + return form; +} + +export function testBasicMarkup(filePath, bUrlMode = false, customComponents = [], codeBasePath = '../..') { + it(`Rendering of ${filePath?.substr(filePath.lastIndexOf('/') + 1)}`, async () => { + resetIds(); + const module = await import(filePath); + const { + fieldDef, expectedDiffs = 0, extraChecks, formPath, ignore = false, + } = module; + if (ignore) { + return; + } + let { markUp } = module; + const htmlFile = `${filePath.substr(0, filePath.lastIndexOf('/') + 1)}${filePath?.substr(filePath.lastIndexOf('/') + 1).split('.').slice(0, -1).join('.')}.html`; + // read html file async + if (!markUp) { + markUp = fs.readFileSync(htmlFile, 'utf8').replace(/\n\s+/g, ' '); + } + if (bUrlMode && !formPath) { + assert.equal(true, false, 'formpath is not defined'); + } + const oldCustomComponents = getCustomComponents(); + window.hlx = { codeBasePath }; + setCustomComponents(customComponents); + const block = bUrlMode ? createBlockWithUrl(fieldDef, `${formPath}`) : createBlock(fieldDef); + if (fieldDef && markUp) { + await decorate(block); + const form = block.querySelector('form'); + console.log('----------Actual----------'); + console.log(form.outerHTML); + console.log('----------Expected----------'); + console.log(createElementFromHTML(markUp, fieldDef).outerHTML); + const result = dom.default.compare(createElementFromHTML(markUp, fieldDef), form); + const differences = result.getDifferences(); + console.log('---------diff--------'); + console.log(differences); + if (Array.isArray(expectedDiffs)) { + assert.equal(differences.length, Array.isArray(expectedDiffs) ? expectedDiffs.length : expectedDiffs, 'Number of differences do not match expected differences'); + const computedDiffs = differences.map((d) => { + const match = d.message.match(/Attribute '([^']+)':/); + return { + node: d.node, + attribute: match?.[1], + }; + }); + assert.deepStrictEqual(computedDiffs, expectedDiffs); + } else if (expectedDiffs) { + const diffs = differences.filter((d) => { + const match = d.message.match(/Attribute '([^']+)': expected value '([^']+)'/); + return ['id', 'for', 'data-id'].includes(match?.[1]) && match?.[2]?.startsWith('uniqueId'); + }); + assert.equal(diffs.length, expectedDiffs, 'expected diffs do not match'); + } else { + assert.equal(differences.length, 0, 'HTML do not match'); + } + if (extraChecks) { + extraChecks.forEach((check) => check(form)); + } + } + setCustomComponents(oldCustomComponents); + }); +} + +function runAfterdelay(fn, delay) { + return new Promise((resolve) => { + setTimeout(async () => { + fn(); + resolve(); + }, delay); + }); +} + +async function test( + sample, + before = () => {}, + op = () => {}, + expect = () => {}, + opDelay = DELAY_MS, + after = () => {}, + bUrlMode = false, + formPath = '', + refresh = false, +) { + let block = bUrlMode ? createBlockWithUrl(sample, formPath) : createBlock(sample); + before(); + // console.log('before'); + await decorate(block); + // console.log('decorate'); + await runAfterdelay(() => { + // console.log('before op'); + op(block); + }, opDelay); + // console.log('op'); + if (refresh) { + block = bUrlMode ? createBlockWithUrl(sample, formPath) : createBlock(sample); + await decorate(block); + } + await runAfterdelay(() => { + // console.log('before expect'); + expect(block); + }, opDelay); + // console.log('expect'); + after(block); +} + +export async function testDynamism(filePath, bUrlMode = false) { + const testName = `checking dynamic behaviour for ${filePath?.substr(filePath.lastIndexOf('/') + 1).split('.')[0]}`; + it(testName, async () => { + const { + sample, before, op, expect, opDelay, after, formPath, ignore = false, refresh = false, + } = await import(filePath); + if (ignore) { + return; + } + resetIds(); + await test(sample, before, op, expect, opDelay, after, bUrlMode, formPath, refresh); + }); +} + +export function executeTestInFolder(folderPath = './test/fixtures/', testFn = testBasicMarkup, bUrlMode = false) { + const fixturesFolderPath = path.resolve(folderPath); + const folders = fs.readdirSync(fixturesFolderPath); + folders.forEach((folder) => { + const isDirectory = fs.lstatSync(`${fixturesFolderPath}/${folder}`).isDirectory(); + const name = isDirectory ? `${folder}` : `${folderPath.replace(/\/$/, '').split('/').slice(-1)[0]}-${folder}`; + if (isDirectory || folder.endsWith('.js')) { + describe(`Test suit for - ${name}${bUrlMode ? ' - with url' : ''}`, () => { + if (fs.lstatSync(`${fixturesFolderPath}/${folder}`).isDirectory()) { + const fileNames = fs.readdirSync(`${folderPath}${folder}`); + fileNames.filter((f) => f.endsWith('.js')).forEach((fileName) => { + testFn(`${fixturesFolderPath}/${folder}/${fileName}`, bUrlMode); + }); + } else if (folder.endsWith('.js')) { + testFn(`${fixturesFolderPath}/${folder}`, bUrlMode); + } + }); + } + }); +} + +export function setValue(block, id, value) { + const input = block.querySelector(id); + input.value = value; + input.dispatchEvent(new Event('change', { bubbles: true })); +} + +export function testFormFetch(filePath) { + it(`Fetching of ${filePath?.substr(filePath.lastIndexOf('/') + 1)}`, async () => { + resetIds(); + const module = await import(filePath); + const { + before, op, expect, ignore = false, opDelay, after, + } = module; + if (ignore) { + return; + } + await test(null, before, op, expect, opDelay, after); + }); +} + +export async function renderForm(formDef) { + document.documentElement.classList.add('adobe-ue-edit'); + const mainEl = document.createElement('main'); + const divInsideMain = document.createElement('div'); + const formWrapperDiv = document.createElement('div'); + formWrapperDiv.classList.add('form-wrapper'); + const formBlockDiv = document.createElement('div'); + formBlockDiv.classList.add('block'); + formBlockDiv.classList.add('form'); + formBlockDiv.dataset.aueResource = `urn:aemconnection:${formDef.properties['fd:path']}`; + formBlockDiv.dataset.aueModel = 'form'; + const div1 = document.createElement('div'); + const div2 = document.createElement('div'); + div1.appendChild(div2); + formBlockDiv.appendChild(div1); + formWrapperDiv.appendChild(formBlockDiv); + divInsideMain.appendChild(formWrapperDiv); + const formEl = document.createElement('form'); + formEl.dataset.id = formDef.id; + div2.appendChild(formEl); + mainEl.appendChild(divInsideMain); + await generateFormRendition(formDef, formEl, getItems); + annotateFormForEditing(formEl, formDef); + document.body.appendChild(mainEl); +}