diff --git a/.github/workflows/repo.yml b/.github/workflows/repo.yml new file mode 100644 index 00000000..a44f52ad --- /dev/null +++ b/.github/workflows/repo.yml @@ -0,0 +1,75 @@ +name: Build & Test + +on: push + +env: + NODE_VERSION: 18.x + +jobs: + setup: + runs-on: ubuntu-latest + steps: + - run: echo "Triggered by ${{ github.event_name }} event." + + - name: Check out repository code ${{ github.repository }} on ${{ github.ref }} + uses: actions/checkout@v3 + + - name: Set up Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v3 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + + - name: Cache node modules + uses: actions/cache@v3 + with: + path: node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Installing dependencies + if: steps.cache.outputs.cache-hit != 'true' + uses: borales/actions-yarn@v4 + with: + cmd: install --frozen-lockfile + + build: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Check out repository code ${{ github.repository }} on ${{ github.ref }} + uses: actions/checkout@v3 + + - name: Restore node modules from cache + uses: actions/cache@v3 + with: + path: node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Build + uses: borales/actions-yarn@v4 + with: + cmd: build + + test: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Check out repository code ${{ github.repository }} on ${{ github.ref }} + uses: actions/checkout@v3 + + - name: Restore node modules from cache + uses: actions/cache@v3 + with: + path: node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Test + uses: borales/actions-yarn@v4 + with: + cmd: test \ No newline at end of file diff --git a/Changelog.md b/Changelog.md index 066b2ed6..624ab8b2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,16 @@ +## 2.0.0-rc.21 +### Changed + - FIO-8092: update isEmpty to isComponentDataEmpty and account for differing component data types + +## 2.0.0-rc.20 +### Changed + - FIO-8086: don't multiple validate select components + - FIO-8079: add stricter time validation + +## 2.0.0-rc.19 +### Changed + - FIO-8047: add dereferencing processor for datatable comp + ## 2.0.0-rc.18 ### Changed - FIO-8055: validate components that include custom validations, even when their data is empty diff --git a/package.json b/package.json index b00acee5..e4056650 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@formio/core", - "version": "2.0.0-rc.18", + "version": "2.0.0-rc.21", "description": "The core Form.io renderering framework.", "main": "lib/index.js", "exports": { diff --git a/src/process/__tests__/fixtures/util.ts b/src/process/__tests__/fixtures/util.ts new file mode 100644 index 00000000..be91b99b --- /dev/null +++ b/src/process/__tests__/fixtures/util.ts @@ -0,0 +1,12 @@ +import get from 'lodash/get'; +import { ProcessorContext, ProcessorScope, Component } from 'types'; +export const generateProcessorContext = (component: Component, data: any): ProcessorContext => { + return { + component, + path: component.key, + data, + row: data, + scope: {} as ProcessorScope, + value: get(data, component.key), + } +} diff --git a/src/process/normalize/__tests__/normalize.test.ts b/src/process/normalize/__tests__/normalize.test.ts new file mode 100644 index 00000000..5e6817d5 --- /dev/null +++ b/src/process/normalize/__tests__/normalize.test.ts @@ -0,0 +1,20 @@ +import { expect } from 'chai'; + +import { TimeComponent } from 'types'; +import { normalizeProcessSync } from '../'; +import { generateProcessorContext } from '../../__tests__/fixtures/util'; + +const timeField: TimeComponent = { + type: 'time', + key: 'time', + label: 'Time', + input: true, + dataFormat: 'HH:mm:ss' +}; + +it('Should normalize a time component with a valid time value that doees not match dataFormat', async () => { + const data = { time: '12:00' }; + const context = generateProcessorContext(timeField, data); + normalizeProcessSync(context); + expect(context.data).to.deep.equal({time: '12:00:00'}); +}); diff --git a/src/process/normalize/index.ts b/src/process/normalize/index.ts index f63a26d6..870f71b6 100644 --- a/src/process/normalize/index.ts +++ b/src/process/normalize/index.ts @@ -4,6 +4,8 @@ import isString from 'lodash/isString'; import toString from 'lodash/toString'; import isNil from 'lodash/isNil'; import isObject from 'lodash/isObject'; +import dayjs from 'dayjs'; +import customParseFormat from 'dayjs/plugin/customParseFormat'; import { AddressComponent, DayComponent, @@ -18,11 +20,14 @@ import { TextFieldComponent, DefaultValueScope, ProcessorInfo, - ProcessorContext + ProcessorContext, + TimeComponent } from "types"; type NormalizeScope = DefaultValueScope; +dayjs.extend(customParseFormat) + const isAddressComponent = (component: any): component is AddressComponent => component.type === "address"; const isDayComponent = (component: any): component is DayComponent => component.type === "day"; const isEmailComponent = (component:any): component is EmailComponent => component.type === "email"; @@ -32,6 +37,7 @@ const isSelectComponent = (component: any): component is SelectComponent => comp const isSelectBoxesComponent = (component: any): component is SelectBoxesComponent => component.type === "selectboxes"; const isTagsComponent = (component: any): component is TagsComponent => component.type === "tags"; const isTextFieldComponent = (component: any): component is TextFieldComponent => component.type === "textfield"; +const isTimeComponent = (component: any): component is TimeComponent => component.type === "time"; const normalizeAddressComponentValue = (component: AddressComponent, value: any) => { if (!component.multiple && Boolean(component.enableManualMode) && value && !value.mode) { @@ -249,6 +255,17 @@ const normalizeTextFieldComponentValue = ( return value; } +// Allow submissions of time components in their visual "format" property by coercing them to the "dataFormat" property +// i.e. "HH:mm" -> "HH:mm:ss" +const normalizeTimeComponentValue = (component: TimeComponent, value: string) => { + const defaultDataFormat = 'HH:mm:ss'; + const defaultFormat = 'HH:mm'; + if (dayjs(value, component.format || defaultFormat, true).isValid()) { + return dayjs(value, component.format || defaultFormat, true).format(component.dataFormat || defaultDataFormat); + } + return value; +}; + export const normalizeProcess: ProcessorFn = async (context) => { return normalizeProcessSync(context); } @@ -275,6 +292,8 @@ export const normalizeProcessSync: ProcessorFnSync = (context) = set(data, path, normalizeTagsComponentValue(component, value)); } else if (isTextFieldComponent(component)) { set(data, path, normalizeTextFieldComponentValue(component, defaultValues, value, path)); + } else if (isTimeComponent(component)) { + set(data, path, normalizeTimeComponentValue(component, value)); } // Next perform component-type-agnostic transformations (i.e. super()) diff --git a/src/process/validation/i18n/en.ts b/src/process/validation/i18n/en.ts index db850eaf..f9174040 100644 --- a/src/process/validation/i18n/en.ts +++ b/src/process/validation/i18n/en.ts @@ -33,5 +33,6 @@ export const EN_ERRORS = { valueIsNotAvailable: '{{ field }} is an invalid value.', captchaTokenValidation: 'ReCAPTCHA: Token validation error', captchaTokenNotSpecified: 'ReCAPTCHA: Token is not specified in submission', - captchaFailure: 'ReCaptcha: Response token not found' + captchaFailure: 'ReCaptcha: Response token not found', + time: '{{field}} is not a valid time.', }; diff --git a/src/process/validation/rules/__tests__/fixtures/util.ts b/src/process/validation/rules/__tests__/fixtures/util.ts index ed227f1c..e3b8aabc 100644 --- a/src/process/validation/rules/__tests__/fixtures/util.ts +++ b/src/process/validation/rules/__tests__/fixtures/util.ts @@ -1,10 +1,10 @@ import { get } from "lodash"; import { Component, DataObject, ProcessorType, ValidationContext } from "types"; -export const generateProcessContext = (component: Component, data: DataObject): ValidationContext => { +export const generateProcessorContext = (component: Component, data: DataObject): ValidationContext => { const path = component.key; const value = get(data, path); - return { + return { component, data, scope: {errors: []}, diff --git a/src/process/validation/rules/__tests__/validateAvailableItems.test.ts b/src/process/validation/rules/__tests__/validateAvailableItems.test.ts index a74ecc10..f35f2641 100644 --- a/src/process/validation/rules/__tests__/validateAvailableItems.test.ts +++ b/src/process/validation/rules/__tests__/validateAvailableItems.test.ts @@ -8,7 +8,7 @@ import { simpleTextField, simpleSelectOptions, } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateAvailableItems } from '../validateAvailableItems'; it('Validating a component without the available items validation parameter will return null', async () => { @@ -16,7 +16,7 @@ it('Validating a component without the available items validation parameter will const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -31,7 +31,7 @@ it('Validating a simple select boxes component without the available items valid biz: false, }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -41,7 +41,7 @@ it('Validating a simple radio component without the available items validation p const data = { component: 'bar', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -54,7 +54,7 @@ it('Validating a simple radio component with the available items validation para const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidOption'); @@ -76,7 +76,7 @@ it('Validating a simple static values select component without the available ite const data = { component: 'foo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -98,7 +98,7 @@ it('Validating a simple static values select component with the available items const data = { component: 'foo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -115,7 +115,7 @@ it('Validating a simple URL select component without the available items validat const data = { component: 'foo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -131,7 +131,7 @@ it('Validating a simple JSON select component (string JSON) without the availabl const data = { component: 'foo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -148,7 +148,7 @@ it('Validating a simple JSON select component (string JSON) with the available i const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidOption'); @@ -166,7 +166,7 @@ it('Validating a simple JSON select component (string JSON) with the available i const data = { component: 'foo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -183,7 +183,7 @@ it('Validating a simple JSON select component (nested string JSON) with the avai const data = { component: { foo: 'foo', bar: 'bar' }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -200,7 +200,7 @@ it('Validating a simple JSON select component (nested string JSON) with the avai const data = { component: { foo: 'bar', bar: 'baz' }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidOption'); @@ -219,7 +219,7 @@ it('Validating a simple JSON select component (nested string JSON with valueProp const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidOption'); @@ -238,7 +238,7 @@ it('Validating a simple JSON select component (nested string JSON with valueProp const data = { component: 'foo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -254,7 +254,7 @@ it('Validating a simple JSON select component (actual JSON) without the availabl const data = { component: 'foo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -271,7 +271,7 @@ it('Validating a simple JSON select component (actual JSON) with the available i const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidOption'); @@ -289,7 +289,7 @@ it('Validating a simple JSON select component (actual JSON) with the available i const data = { component: 'foo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -309,7 +309,7 @@ it('Validating a simple JSON select component (nested actual JSON) with the avai const data = { component: { foo: 'baz', bar: 'biz' }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidOption'); @@ -330,7 +330,7 @@ it('Validating a simple JSON select component (nested actual JSON) with the avai const data = { component: { foo: 'foo', bar: 'bar' }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); @@ -351,7 +351,7 @@ it('Validating a simple JSON select component (nested actual JSON with valueProp const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidOption'); @@ -373,7 +373,7 @@ it('Validating a simple JSON select component (nested actual JSON with valueProp const data = { component: 'foo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateAvailableItems(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateCustom.test.ts b/src/process/validation/rules/__tests__/validateCustom.test.ts index 5ec2b198..d1de423e 100644 --- a/src/process/validation/rules/__tests__/validateCustom.test.ts +++ b/src/process/validation/rules/__tests__/validateCustom.test.ts @@ -4,7 +4,7 @@ import { FieldError } from 'error'; import { TextFieldComponent } from 'types'; import { simpleTextField } from './fixtures/components'; import { validateCustom } from '../validateCustom'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; it('A simple custom validation will correctly be interpolated', async () => { const component: TextFieldComponent = { @@ -16,7 +16,7 @@ it('A simple custom validation will correctly be interpolated', async () => { const data = { component: 'any thing', } - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateCustom(context); expect(result).to.be.instanceOf(FieldError); expect(result && result.errorKeyOrMessage).to.equal('Invalid entry'); @@ -32,7 +32,7 @@ it('A custom validation that includes data will correctly be interpolated', asyn const data = { simpleComponent: 'any thing', } - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateCustom(context); expect(result).to.equal(null); }); @@ -47,7 +47,7 @@ it('A custom validation of empty component data will still validate', async () = const data = { simpleComponent: '', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateCustom(context); expect(result).to.be.instanceOf(FieldError); expect(result && result.errorKeyOrMessage).to.equal('Invalid entry'); diff --git a/src/process/validation/rules/__tests__/validateDate.test.ts b/src/process/validation/rules/__tests__/validateDate.test.ts index 520a4cdb..63cb5609 100644 --- a/src/process/validation/rules/__tests__/validateDate.test.ts +++ b/src/process/validation/rules/__tests__/validateDate.test.ts @@ -3,14 +3,14 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { calendarTextField, simpleDateTimeField, simpleTextField } from './fixtures/components'; import { validateDate } from '../validateDate'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; it('Validating a component without a date/time concern will return null', async () => { const component = simpleTextField; const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDate(context); expect(result).to.equal(null); }); @@ -18,7 +18,7 @@ it('Validating a component without a date/time concern will return null', async it('Validating a date/time component with no data will return null', async () => { const component = simpleDateTimeField; const data = {}; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDate(context); expect(result).to.equal(null); }); @@ -28,7 +28,7 @@ it('Validating a date/time component with an invalid date string value will retu const data = { component: 'hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDate(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidDate'); @@ -39,7 +39,7 @@ it('Validating a date/time component with an valid date string value will return const data = { component: '2023-03-09T12:00:00-06:00', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDate(context); expect(result).to.equal(null); }); @@ -49,7 +49,7 @@ it('Validating a date/time component with an invalid Date object will return a F const data = { component: new Date('Hello, world!'), }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDate(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidDate'); @@ -60,7 +60,7 @@ it('Validating a date/time component with a valid Date object will return null', const data = { component: new Date(), }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDate(context); expect(result).to.equal(null); }); @@ -68,7 +68,7 @@ it('Validating a date/time component with a valid Date object will return null', it('Validating a textField calendar picker component with no data will return null', async () => { const component = calendarTextField; const data = {}; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDate(context); expect(result).to.equal(null); }); @@ -78,7 +78,7 @@ it('Validating a textField calendar picker component with an invalid date string const data = { component: 'hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDate(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidDate'); @@ -89,7 +89,7 @@ it('Validating a textField calendar picker component with an valid date string v const data = { component: '2023-03-09T12:00:00-06:00', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDate(context); expect(result).to.equal(null); }); @@ -99,7 +99,7 @@ it('Validating a textField calendar picker component with an invalid Date object const data = { component: new Date('Hello, world!'), }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDate(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidDate'); @@ -110,7 +110,7 @@ it('Validating a textField calendar picker component with a valid Date object wi const data = { component: new Date(), }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDate(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateDay.test.ts b/src/process/validation/rules/__tests__/validateDay.test.ts index 2f9567cc..52abde36 100644 --- a/src/process/validation/rules/__tests__/validateDay.test.ts +++ b/src/process/validation/rules/__tests__/validateDay.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleDayField, simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateDay } from '../validateDay'; it('Validating a non-day component will return null', async () => { @@ -10,7 +10,7 @@ it('Validating a non-day component will return null', async () => { const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDay(context); expect(result).to.equal(null); }); @@ -20,7 +20,7 @@ it('Validating a day component with an invalid date string value will return a F const data = { component: 'hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDay(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidDay'); @@ -31,7 +31,7 @@ it('Validating a day component with an valid date string value will return null' const data = { component: '03/23/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDay(context); expect(result).to.equal(null); }); @@ -41,7 +41,7 @@ it('Validating a day component with an invalid Date object will return a FieldEr const data = { component: new Date('Hello, world!'), }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDay(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidDay'); @@ -52,7 +52,7 @@ it('Validating a day component with a valid Date object will return a field erro const data = { component: new Date(), }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateDay(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidDay'); diff --git a/src/process/validation/rules/__tests__/validateEmail.test.ts b/src/process/validation/rules/__tests__/validateEmail.test.ts index fad95140..f9f84678 100644 --- a/src/process/validation/rules/__tests__/validateEmail.test.ts +++ b/src/process/validation/rules/__tests__/validateEmail.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleEmailField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateEmail } from '../validateEmail'; it('Validating a valid email will return null', async () => { @@ -10,7 +10,7 @@ it('Validating a valid email will return null', async () => { const data = { component: 'sales@form.io', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateEmail(context); expect(result).to.equal(null); }); @@ -20,7 +20,7 @@ it('Validating an invalid email will return a FieldError', async () => { const data = { component: 'salesatform.io', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateEmail(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.contain('invalid_email'); diff --git a/src/process/validation/rules/__tests__/validateJson.test.ts b/src/process/validation/rules/__tests__/validateJson.test.ts index 6f8019fd..d4d529e8 100644 --- a/src/process/validation/rules/__tests__/validateJson.test.ts +++ b/src/process/validation/rules/__tests__/validateJson.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error/FieldError'; import { simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateJson } from '../validateJson'; it('A simple component without JSON logic validation will return null', async () => { @@ -10,7 +10,7 @@ it('A simple component without JSON logic validation will return null', async () const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateJson(context); expect(result).to.equal(null); }); @@ -38,7 +38,7 @@ it('A simple component with JSON logic evaluation will return a FieldError if th const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateJson(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.contain('Input must be \'foo\''); @@ -67,7 +67,7 @@ it('A simple component with JSON logic evaluation will return null if the JSON l const data = { component: 'foo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateJson(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMask.test.ts b/src/process/validation/rules/__tests__/validateMask.test.ts index 3cca5421..3a6ae3ae 100644 --- a/src/process/validation/rules/__tests__/validateMask.test.ts +++ b/src/process/validation/rules/__tests__/validateMask.test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateMask } from '../validateMask'; it('Validating a mask component should return a FieldError if the value does not match the mask', async () => { @@ -9,7 +9,7 @@ it('Validating a mask component should return a FieldError if the value does not const data = { component: '1234', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMask(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('mask'); @@ -20,7 +20,7 @@ it('Validating a mask component should return null if the value matches the mask const data = { component: '123-456-7890', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMask(context); expect(result).to.equal(null); }); @@ -43,7 +43,7 @@ it('Validating a multi-mask component should return a FieldError if the value do let data = { component: { maskName: 'maskOne', value: '14567890' }, }; - let context = generateProcessContext(component, data); + let context = generateProcessorContext(component, data); let result = await validateMask(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('mask'); @@ -51,7 +51,7 @@ it('Validating a multi-mask component should return a FieldError if the value do data = { component: { maskName: 'maskTwo', value: '1234567' }, }; - context = generateProcessContext(component, data); + context = generateProcessorContext(component, data); result = await validateMask(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('mask'); @@ -75,14 +75,14 @@ it('Validating a mutil-mask component should return null if the value matches th let data = { component: { maskName: 'maskOne', value: '456-7890' }, }; - let context = generateProcessContext(component, data); + let context = generateProcessorContext(component, data); let result = await validateMask(context); expect(result).to.equal(null); data = { component: { maskName: 'maskTwo', value: '123-456-7890' }, }; - context = generateProcessContext(component, data); + context = generateProcessorContext(component, data); result = await validateMask(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMaximumDay.test.ts b/src/process/validation/rules/__tests__/validateMaximumDay.test.ts index 81a4f8be..6f84f59f 100644 --- a/src/process/validation/rules/__tests__/validateMaximumDay.test.ts +++ b/src/process/validation/rules/__tests__/validateMaximumDay.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleDayField, simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateMaximumDay } from '../validateMaximumDay'; it('Validating a non-day component will return null', async () => { @@ -10,7 +10,7 @@ it('Validating a non-day component will return null', async () => { const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumDay(context); expect(result).to.equal(null); }); @@ -20,7 +20,7 @@ it('Validating a day component with a day after the maximum day will return a Fi const data = { component: '04/02/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumDay(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('maxDay'); @@ -31,7 +31,7 @@ it('Validating a day component with a day before the maximum day will return nul const data = { component: '03/23/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumDay(context); expect(result).to.equal(null); }); @@ -41,7 +41,7 @@ it('Validating a day component with a day after the maximum day will return a Fi const data = { component: '04/02/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumDay(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('maxDay'); @@ -52,7 +52,7 @@ it('Validating a day-first day component with a day after the maximum day will r const data = { component: '02/04/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumDay(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('maxDay'); @@ -63,7 +63,7 @@ it('Validating a day-first day component with a day before the maximum day will const data = { component: '23/03/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumDay(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMaximumLength.test.ts b/src/process/validation/rules/__tests__/validateMaximumLength.test.ts index 88cf906e..2bbb8292 100644 --- a/src/process/validation/rules/__tests__/validateMaximumLength.test.ts +++ b/src/process/validation/rules/__tests__/validateMaximumLength.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateMaximumLength } from '../validateMaximumLength'; it('Validating a component without a maxLength property will return null', async () => { @@ -10,7 +10,7 @@ it('Validating a component without a maxLength property will return null', async const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumLength(context); expect(result).to.equal(null); }); @@ -20,7 +20,7 @@ it('Validating a component with a maxLength property and a length greater than m const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumLength(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('maxLength'); @@ -31,7 +31,7 @@ it('Validating a component with a maxLength property and a length less than maxL const data = { component: 'foo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumLength(context); expect(result).to.equal(null); }); @@ -41,7 +41,7 @@ it('Validating a component with a maxLength property that is an empty string wil const data = { component: '', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumLength(context); expect(result).to.equal(null); }) diff --git a/src/process/validation/rules/__tests__/validateMaximumSelectedCount.test.ts b/src/process/validation/rules/__tests__/validateMaximumSelectedCount.test.ts index cf7e4ac4..05dcb75e 100644 --- a/src/process/validation/rules/__tests__/validateMaximumSelectedCount.test.ts +++ b/src/process/validation/rules/__tests__/validateMaximumSelectedCount.test.ts @@ -3,14 +3,14 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleSelectBoxes, simpleTextField } from './fixtures/components'; import { validateMaximumSelectedCount } from '../validateMaximumSelectedCount'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; it('Validting a non-select boxes component will return null', async () => { const component = simpleTextField; const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumSelectedCount(context); expect(result).to.equal(null); }); @@ -25,7 +25,7 @@ it('Validating a select boxes component without maxSelectedCount will return nul biz: false, }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumSelectedCount(context); expect(result).to.equal(null); }); @@ -40,7 +40,7 @@ it('Validating a select boxes component where the number of selected fields is g biz: false, }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumSelectedCount(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.contain('maxSelectedCount'); @@ -56,7 +56,7 @@ it('Validating a select boxes component where the number of selected fields is e biz: false, }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumSelectedCount(context); expect(result).to.equal(null); }); @@ -71,7 +71,7 @@ it('Validating a select boxes component where the number of selected fields is l biz: false, }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumSelectedCount(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMaximumValue.test.ts b/src/process/validation/rules/__tests__/validateMaximumValue.test.ts index dc26b6e3..6a35e558 100644 --- a/src/process/validation/rules/__tests__/validateMaximumValue.test.ts +++ b/src/process/validation/rules/__tests__/validateMaximumValue.test.ts @@ -3,14 +3,14 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleNumberField, simpleTextField } from './fixtures/components'; import { validateMaximumValue } from '../validateMaximumValue'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; it('Validating a component without the max property will return null', async () => { const component = simpleTextField; const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumValue(context); expect(result).to.equal(null); }); @@ -20,7 +20,7 @@ it('Validating a number component without the max property will return null', as const data = { component: 3, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumValue(context); expect(result).to.equal(null); }); @@ -30,7 +30,7 @@ it('Validating a number component that contains the max property will return nul const data = { component: 35, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumValue(context); expect(result).to.equal(null); }); @@ -40,7 +40,7 @@ it('Validating a number component that contains the max property will return a F const data = { component: 55, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumValue(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('max'); @@ -51,7 +51,7 @@ it('Validating a number component that contains the max property will return nul const data = { component: 50, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumValue(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMaximumWords.test.ts b/src/process/validation/rules/__tests__/validateMaximumWords.test.ts index 2e4465c7..257ee25d 100644 --- a/src/process/validation/rules/__tests__/validateMaximumWords.test.ts +++ b/src/process/validation/rules/__tests__/validateMaximumWords.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateMaximumWords } from '../validateMaximumWords'; it('Validating a component without the maxWords property will return null', async () => { @@ -10,7 +10,7 @@ it('Validating a component without the maxWords property will return null', asyn const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumWords(context); expect(result).to.equal(null); }); @@ -20,7 +20,7 @@ it('Validating a component with the maxWords property will return a FieldError i const data = { component: "Hello, world, it's me!", }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumWords(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('maxWords'); @@ -31,7 +31,7 @@ it('Validating a component with the maxWords property will return null if the nu const data = { component: 'Hello, world, again!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumWords(context); expect(result).to.equal(null); }); @@ -41,7 +41,7 @@ it('Validating a component with the maxWords property will return null if the nu const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumWords(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMaximumYear.test.ts b/src/process/validation/rules/__tests__/validateMaximumYear.test.ts index 512d4592..c03d19ed 100644 --- a/src/process/validation/rules/__tests__/validateMaximumYear.test.ts +++ b/src/process/validation/rules/__tests__/validateMaximumYear.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { DayComponent } from 'types'; import { FieldError } from 'error'; import { simpleDayField, simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateMaximumYear } from '../validateMaximumYear'; it('Validating a component without the maxYear parameter will return null', async () => { @@ -11,7 +11,7 @@ it('Validating a component without the maxYear parameter will return null', asyn const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumYear(context); expect(result).to.equal(null); }); @@ -21,7 +21,7 @@ it('Validating a day component without the maxYear parameter will return null', const data = { component: '01/22/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumYear(context); expect(result).to.equal(null); }); @@ -38,7 +38,7 @@ it('Validating a day component with the maxYear parameter will return a FieldErr const data = { component: '01/22/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumYear(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('maxYear'); @@ -56,7 +56,7 @@ it('Validating a day component with the maxYear parameter will return null if th const data = { component: '01/22/2022', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumYear(context); expect(result).to.equal(null); }); @@ -73,7 +73,7 @@ it('Validating a day component with the maxYear parameter will return null if th const data = { component: '01/22/2021', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMaximumYear(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMinimumDay.test.ts b/src/process/validation/rules/__tests__/validateMinimumDay.test.ts index 2bea3e93..a7ae2878 100644 --- a/src/process/validation/rules/__tests__/validateMinimumDay.test.ts +++ b/src/process/validation/rules/__tests__/validateMinimumDay.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleDayField, simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateMinimumDay } from '../validateMinimumDay'; it('Validating a non-day component will return null', async () => { @@ -10,7 +10,7 @@ it('Validating a non-day component will return null', async () => { const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumDay(context); expect(result).to.equal(null); }); @@ -20,7 +20,7 @@ it('Validating a day component with a day before the minimum day will return a F const data = { component: '03/23/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumDay(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('minDay'); @@ -31,7 +31,7 @@ it('Validating a day component with a day after the minimum day will return null const data = { component: '04/02/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumDay(context); expect(result).to.equal(null); }); @@ -41,7 +41,7 @@ it('Validating a day-first day component with a day before the minimum day will const data = { component: '02/02/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumDay(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.contain('minDay'); @@ -52,7 +52,7 @@ it('Validating a day-first day component with a day after the minimum day will r const data = { component: '23/04/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumDay(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMinimumLength.test.ts b/src/process/validation/rules/__tests__/validateMinimumLength.test.ts index 8b423a3f..1308d5fc 100644 --- a/src/process/validation/rules/__tests__/validateMinimumLength.test.ts +++ b/src/process/validation/rules/__tests__/validateMinimumLength.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateMinimumLength } from '../validateMinimumLength'; it('Validating a component without a minLength property will return null', async () => { @@ -10,7 +10,7 @@ it('Validating a component without a minLength property will return null', async const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumLength(context); expect(result).to.equal(null); }); @@ -20,7 +20,7 @@ it('Validating a component with a minLength property and a length less than minL const data = { component: 'foo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumLength(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('minLength'); @@ -31,7 +31,7 @@ it('Validating a component with a minLength property and a length equal to minLe const data = { component: 'fooo', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumLength(context); expect(result).to.equal(null); }); @@ -41,7 +41,7 @@ it('Validating a component with a minLength property and a length greater than m const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumLength(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMinimumSelectedCount.test.ts b/src/process/validation/rules/__tests__/validateMinimumSelectedCount.test.ts index 00831b88..4bc0ed5f 100644 --- a/src/process/validation/rules/__tests__/validateMinimumSelectedCount.test.ts +++ b/src/process/validation/rules/__tests__/validateMinimumSelectedCount.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleSelectBoxes, simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateMinimumSelectedCount } from '../validateMinimumSelectedCount'; it('Validting a non-select boxes component will return null', async () => { @@ -10,7 +10,7 @@ it('Validting a non-select boxes component will return null', async () => { const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumSelectedCount(context); expect(result).to.equal(null); }); @@ -25,7 +25,7 @@ it('Validating a select boxes component without minSelectedCount will return nul biz: false, }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumSelectedCount(context); expect(result).to.equal(null); }); @@ -40,7 +40,7 @@ it('Validating a select boxes component where the number of selected fields is l biz: false, }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumSelectedCount(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.contain('minSelectedCount'); @@ -56,7 +56,7 @@ it('Validating a select boxes component where the number of selected fields is e biz: false, }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumSelectedCount(context); expect(result).to.equal(null); }); @@ -71,7 +71,7 @@ it('Validating a select boxes component where the number of selected fields is g biz: false, }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumSelectedCount(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMinimumValue.test.ts b/src/process/validation/rules/__tests__/validateMinimumValue.test.ts index 7b730cd1..791bd302 100644 --- a/src/process/validation/rules/__tests__/validateMinimumValue.test.ts +++ b/src/process/validation/rules/__tests__/validateMinimumValue.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleNumberField, simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateMinimumValue } from '../validateMinimumValue'; it('Validating a component without the min property will return null', async () => { @@ -10,7 +10,7 @@ it('Validating a component without the min property will return null', async () const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumValue(context); expect(result).to.equal(null); }); @@ -20,7 +20,7 @@ it('Validating a number component without the min property will return null', as const data = { component: 3, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumValue(context); expect(result).to.equal(null); }); @@ -30,7 +30,7 @@ it('Validating a number component that contains the min property will return nul const data = { component: 55, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumValue(context); expect(result).to.equal(null); }); @@ -40,7 +40,7 @@ it('Validating a number component that contains the min property will return a F const data = { component: 35, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumValue(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.contain('min'); @@ -51,7 +51,7 @@ it('Validating a number component that contains the min property will return nul const data = { component: 50, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumValue(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMinimumWords.test.ts b/src/process/validation/rules/__tests__/validateMinimumWords.test.ts index 5cb61b20..ce6fd2d8 100644 --- a/src/process/validation/rules/__tests__/validateMinimumWords.test.ts +++ b/src/process/validation/rules/__tests__/validateMinimumWords.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateMinimumWords } from '../validateMinimumWords'; it('Validating a component without the maxWords property will return null', async () => { @@ -10,7 +10,7 @@ it('Validating a component without the maxWords property will return null', asyn const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumWords(context); expect(result).to.equal(null); }); @@ -20,7 +20,7 @@ it('Validating a component with the minWords property will return a FieldError i const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumWords(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('minWords'); @@ -31,7 +31,7 @@ it('Validating a component with the minWords property will return null if the nu const data = { component: 'Hello, world, again!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumWords(context); expect(result).to.equal(null); }); @@ -41,7 +41,7 @@ it('Validating a component with the minWords property will return null if the nu const data = { component: 'Hello, world, it is I!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumWords(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMinimumYear.test.ts b/src/process/validation/rules/__tests__/validateMinimumYear.test.ts index 9459f1fb..10dc272b 100644 --- a/src/process/validation/rules/__tests__/validateMinimumYear.test.ts +++ b/src/process/validation/rules/__tests__/validateMinimumYear.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { DayComponent } from 'types'; import { FieldError } from 'error'; import { simpleDayField, simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateMinimumYear } from '../validateMinimumYear'; it('Validating a component without the minYear parameter will return null', async () => { @@ -11,7 +11,7 @@ it('Validating a component without the minYear parameter will return null', asyn const data = { component: 'Hello, world!', } - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumYear(context); expect(result).to.equal(null); }); @@ -21,7 +21,7 @@ it('Validating a day component without the minYear parameter will return null', const data = { component: '01/22/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumYear(context); expect(result).to.equal(null); }); @@ -38,7 +38,7 @@ it('Validating a day component with the minYear parameter will return a FieldErr const data = { component: '01/22/2022', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumYear(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.contain('minYear'); @@ -56,7 +56,7 @@ it('Validating a day component with the minYear parameter will return null if th const data = { component: '01/22/2022', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumYear(context); expect(result).to.equal(null); }); @@ -73,7 +73,7 @@ it('Validating a day component with the minYear parameter will return null if th const data = { component: '01/22/2023', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateMinimumYear(context); expect(result).to.equal(null); }); diff --git a/src/process/validation/rules/__tests__/validateMultiple.test.ts b/src/process/validation/rules/__tests__/validateMultiple.test.ts index eea548b6..abbc28f1 100644 --- a/src/process/validation/rules/__tests__/validateMultiple.test.ts +++ b/src/process/validation/rules/__tests__/validateMultiple.test.ts @@ -192,7 +192,8 @@ describe('validateMultiple', () => { describe('validateMultipleSync', () => { describe('values that should be arrays', () => { - it('should return an error for a select component with multiple that is not an array', () => { + // TODO: skipping the following tests until we can resolve whether or not we want to validateMultiple on select components + xit('should return an error for a select component with multiple that is not an array', () => { const component: Component = { type: 'select', input: true, @@ -216,7 +217,7 @@ describe('validateMultiple', () => { expect(validateMultipleSync(context)).to.be.instanceOf(FieldError); }); - it('should return null for a select component with multiple that is an array', () => { + xit('should return null for a select component with multiple that is an array', () => { const component: Component = { type: 'select', input: true, @@ -240,7 +241,7 @@ describe('validateMultiple', () => { expect(validateMultipleSync(context)).to.be.null; }); - it('should return an error for a select component without multiple that is an array', () => { + xit('should return an error for a select component without multiple that is an array', () => { const component: Component = { type: 'select', input: true, @@ -263,7 +264,7 @@ describe('validateMultiple', () => { expect(validateMultipleSync(context)).to.be.instanceOf(FieldError); }); - it('should return null for a select component without multiple that is not an array', () => { + xit('should return null for a select component without multiple that is not an array', () => { const component: Component = { type: 'select', input: true, @@ -286,6 +287,30 @@ describe('validateMultiple', () => { expect(validateMultipleSync(context)).to.be.null; }); + it('should not validate a select component with multiple', () => { + const component: Component = { + type: 'select', + input: true, + key: 'select', + multiple: true, + }; + const context: ValidationContext = { + component, + data: { + select: ['foo', 'bar'], + }, + value: ['foo', 'bar'], + row: { + select: ['foo', 'bar'], + }, + scope: { + errors: [] + }, + path: component.key + }; + expect(validateMultipleSync(context)).to.be.null; + }); + it('should return null for a sketchpad component', () => { const component: Component = { type: 'sketchpad', diff --git a/src/process/validation/rules/__tests__/validateRegexPattern.test.ts b/src/process/validation/rules/__tests__/validateRegexPattern.test.ts index 4ff0131e..2c7b0815 100644 --- a/src/process/validation/rules/__tests__/validateRegexPattern.test.ts +++ b/src/process/validation/rules/__tests__/validateRegexPattern.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateRegexPattern } from '../validateRegexPattern'; it('Validating a component without a pattern parameter will return null', async () => { @@ -10,7 +10,7 @@ it('Validating a component without a pattern parameter will return null', async const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateRegexPattern(context); expect(result).to.equal(null); }); @@ -20,7 +20,7 @@ it('Validating a component with a pattern parameter will return a FieldError if const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateRegexPattern(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('pattern'); }); @@ -30,7 +30,7 @@ it('Validating a component with a pattern parameter will return null if the valu const data = { component: '12345', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateRegexPattern(context); expect(result).to.equal(null); }); @@ -41,7 +41,7 @@ it('Validating a component with an empty value will not trigger the pattern vali component: '' }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateRegexPattern(context); expect(result).to.equal(null); }) diff --git a/src/process/validation/rules/__tests__/validateRemoteSelectValue.test.ts b/src/process/validation/rules/__tests__/validateRemoteSelectValue.test.ts index 684252d0..58439df9 100644 --- a/src/process/validation/rules/__tests__/validateRemoteSelectValue.test.ts +++ b/src/process/validation/rules/__tests__/validateRemoteSelectValue.test.ts @@ -3,7 +3,7 @@ import { get } from 'lodash'; import { DataObject, SelectComponent } from 'types'; import { FieldError } from 'error'; import { simpleSelectOptions, simpleTextField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateRemoteSelectValue, generateUrl } from '../validateRemoteSelectValue'; it('Validating a component without the remote value validation parameter will return null', async () => { @@ -11,7 +11,7 @@ it('Validating a component without the remote value validation parameter will re const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateRemoteSelectValue(context); expect(result).to.equal(null); }); @@ -31,7 +31,7 @@ it('Validating a select component without the remote value validation parameter value: 2, }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateRemoteSelectValue(context); expect(result).to.equal(null); }); @@ -83,7 +83,7 @@ it('Validating a select component with the remote validation parameter will retu value: 2, }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateRemoteSelectValue(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('select'); @@ -107,7 +107,7 @@ it('Validating a select component with the remote validation parameter will retu value: 2, }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateRemoteSelectValue(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('select'); @@ -132,7 +132,7 @@ it('Validating a select component with the remote validation parameter will retu }, }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); context.fetch = (url: string, options?: RequestInit | undefined) => { return Promise.resolve({ ok: true, diff --git a/src/process/validation/rules/__tests__/validateRequired.test.ts b/src/process/validation/rules/__tests__/validateRequired.test.ts index d5eecaa9..7e57b2a9 100644 --- a/src/process/validation/rules/__tests__/validateRequired.test.ts +++ b/src/process/validation/rules/__tests__/validateRequired.test.ts @@ -4,14 +4,14 @@ import { FieldError } from 'error'; import { validateRequired } from '../validateRequired'; import { conditionallyHiddenRequiredHiddenField, hiddenRequiredField, requiredNonInputField, simpleTextField } from './fixtures/components'; import { processOne } from 'processes/processOne'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { ProcessorsContext, ValidationScope } from 'types'; import { validateAllProcess, validateProcessInfo } from 'processes/validation'; it('Validating a simple component that is required and not present in the data will return a field error', async () => { const component = { ...simpleTextField, validate: { required: true } }; const data = {}; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateRequired(context); expect(result).to.be.instanceOf(FieldError); expect(result && result.errorKeyOrMessage).to.equal('required'); @@ -20,7 +20,7 @@ it('Validating a simple component that is required and not present in the data w it('Validating a simple component that is required and present in the data will return null', async () => { const component = { ...simpleTextField, validate: { required: true } }; const data = { component: 'a simple value' }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateRequired(context); expect(result).to.equal(null); }); @@ -28,7 +28,7 @@ it('Validating a simple component that is required and present in the data will it('Validating a simple component that is not required and present in the data will return null', async () => { const component = simpleTextField; const data = { component: 'a simple value' }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateRequired(context); expect(result).to.equal(null); }); @@ -36,7 +36,7 @@ it('Validating a simple component that is not required and present in the data w it('Validating a simple component that is not required and not present in the data will return null', async () => { const component = simpleTextField; const data = {}; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateRequired(context); expect(result).to.equal(null); }); @@ -44,7 +44,7 @@ it('Validating a simple component that is not required and not present in the da it('Should validate a hidden component that does not contain data', async () => { const component = hiddenRequiredField; const data = {otherData: 'hideme'}; - const context = generateProcessContext(component, data) as ProcessorsContext; + const context = generateProcessorContext(component, data) as ProcessorsContext; context.processors = [validateProcessInfo]; await processOne(context); expect(context.scope.errors.length).to.equal(1); @@ -54,7 +54,7 @@ it('Should validate a hidden component that does not contain data', async () => it('Should not validate a hidden component that is conditionally hidden', async () => { const component = conditionallyHiddenRequiredHiddenField; const data = {otherData: 'hideme'}; - const context = generateProcessContext(component, data) as ProcessorsContext; + const context = generateProcessorContext(component, data) as ProcessorsContext; context.processors = [validateProcessInfo]; await processOne(context); expect(context.scope.errors.length).to.equal(0); @@ -64,7 +64,7 @@ it('Should not validate a hidden component that has the hidden property set to t const component = hiddenRequiredField; component.hidden = true; const data = {}; - const context = generateProcessContext(component, data) as ProcessorsContext; + const context = generateProcessorContext(component, data) as ProcessorsContext; context.processors = [validateProcessInfo]; await processOne(context); expect(context.scope.errors.length).to.equal(0); @@ -79,7 +79,7 @@ it('Validating a simple component that is required but conditionally hidden', as eq: 'hideme' }; const data = {otherData: 'hideme'}; - const context = generateProcessContext(component, data) as ProcessorsContext; + const context = generateProcessorContext(component, data) as ProcessorsContext; context.processors = [validateProcessInfo]; await processOne(context); expect(context.scope.errors.length).to.equal(0); @@ -90,7 +90,7 @@ it('Validating a simple component that is required but not persistent', async () component.validate = { required: true }; component.persistent = false; const data = {otherData: 'hideme'}; - const context = generateProcessContext(component, data) as ProcessorsContext; + const context = generateProcessorContext(component, data) as ProcessorsContext; context.processors = [validateProcessInfo]; await processOne(context); expect(context.scope.errors.length).to.equal(0); @@ -101,7 +101,7 @@ it('Validating a simple component that is required but persistent set to client- component.validate = { required: true }; component.persistent = 'client-only'; const data = {otherData: 'hideme'}; - const context = generateProcessContext(component, data) as ProcessorsContext; + const context = generateProcessorContext(component, data) as ProcessorsContext; context.processors = [validateProcessInfo]; await processOne(context); expect(context.scope.errors.length).to.equal(0); @@ -110,7 +110,7 @@ it('Validating a simple component that is required but persistent set to client- it('Should not validate a non input comonent', async () => { const component = requiredNonInputField; const data = {}; - const context = generateProcessContext(component, data) as ProcessorsContext; + const context = generateProcessorContext(component, data) as ProcessorsContext; context.processors = [validateProcessInfo]; await processOne(context); expect(context.scope.errors.length).to.equal(0); @@ -126,7 +126,7 @@ it('Should validate a conditionally hidden compoentn with validateWhenHidden fla eq: 'hideme' }; const data = {otherData: 'hideme'}; - const context = generateProcessContext(component, data) as ProcessorsContext; + const context = generateProcessorContext(component, data) as ProcessorsContext; context.processors = [validateProcessInfo]; await processOne(context); expect(context.scope.errors.length).to.equal(1); diff --git a/src/process/validation/rules/__tests__/validateTime.test.ts b/src/process/validation/rules/__tests__/validateTime.test.ts new file mode 100644 index 00000000..6ec05331 --- /dev/null +++ b/src/process/validation/rules/__tests__/validateTime.test.ts @@ -0,0 +1,45 @@ +import { expect } from 'chai'; +import { TimeComponent } from 'types'; +import { FieldError } from 'error'; + +import { generateProcessorContext } from './fixtures/util'; +import { validateTime } from '../validateTime'; + +const timeField: TimeComponent = { + type: 'time', + key: 'time', + label: 'Time', + input: true, + dataFormat: 'HH:mm:ss' +}; + +it('Should validate a time component with a valid time value', async () => { + const data = { time: '12:00:00' }; + const context = generateProcessorContext(timeField, data); + const result = await validateTime(context); + expect(result).to.equal(null); +}); + +it('Should return a FieldError when validating a time component with an invalid time value', async () => { + const data = { time: '25:00:00' }; + const context = generateProcessorContext(timeField, data); + const result = await validateTime(context); + expect(result).to.be.instanceOf(FieldError); + expect(result?.errorKeyOrMessage).to.contain('time'); +}); + +it('Should return a FieldError when validating a time component with a valid format but one that does not match the dataFormat', async () => { + const data = { time: '12:00' }; + const context = generateProcessorContext(timeField, data); + const result = await validateTime(context); + expect(result).to.be.instanceOf(FieldError); + expect(result?.errorKeyOrMessage).to.contain('time'); +}); + +it('Should return a FieldError when validating a time component with an invalid format', async () => { + const data = { time: '12:' }; + const context = generateProcessorContext(timeField, data); + const result = await validateTime(context); + expect(result).to.be.instanceOf(FieldError); + expect(result?.errorKeyOrMessage).to.contain('time'); +}); diff --git a/src/process/validation/rules/__tests__/validateUrl.test.ts b/src/process/validation/rules/__tests__/validateUrl.test.ts index 664d1ced..027af5f8 100644 --- a/src/process/validation/rules/__tests__/validateUrl.test.ts +++ b/src/process/validation/rules/__tests__/validateUrl.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; import { simpleUrlField } from './fixtures/components'; -import { generateProcessContext } from './fixtures/util'; +import { generateProcessorContext } from './fixtures/util'; import { validateUrl } from '../validateUrl'; it('Validating a URL component whose data contains an invalid URL returns a FieldError', async () => { @@ -10,7 +10,7 @@ it('Validating a URL component whose data contains an invalid URL returns a Fiel const data = { component: 'htp:/ww.google', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateUrl(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.contain('invalid_url'); @@ -21,7 +21,7 @@ it('Validating a URL component whose data contains an invalid URL returns a Fiel const data = { component: 'Hello, world!', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateUrl(context); expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.contain('invalid_url'); @@ -54,7 +54,7 @@ it('Validating a URL component whose data contains a valid HTTPS URL returns nul const data = { component: 'https://www.google.com', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateUrl(context); expect(result).to.equal(null); }); @@ -64,7 +64,7 @@ it('Validating a URL component whose data contains a valid HTTP URL returns null const data = { component: 'http://www.google.com', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateUrl(context); expect(result).to.equal(null); }); @@ -84,7 +84,7 @@ it('Validating a URL component whose data contains a strange but valid URL retur const data = { component: 'www.hhh.by', }; - const context = generateProcessContext(component, data); + const context = generateProcessorContext(component, data); const result = await validateUrl(context); expect(result).to.equal(null); }) diff --git a/src/process/validation/rules/validateMultiple.ts b/src/process/validation/rules/validateMultiple.ts index fafa61ab..8fc64db2 100644 --- a/src/process/validation/rules/validateMultiple.ts +++ b/src/process/validation/rules/validateMultiple.ts @@ -17,6 +17,10 @@ export const isEligible = (component: Component) => { return false; } return true; + // TODO: For backwards compatibility, skip multiple validation for select components until we can investigate + // how this validation might break existing forms + case 'select': + return false; default: return true; } diff --git a/src/process/validation/rules/validateTime.ts b/src/process/validation/rules/validateTime.ts index cb1f0a65..047439e0 100644 --- a/src/process/validation/rules/validateTime.ts +++ b/src/process/validation/rules/validateTime.ts @@ -1,9 +1,13 @@ import { RuleFn, RuleFnSync, TimeComponent, ValidationContext } from "types"; -import { isEmpty } from "../util"; +import { isComponentDataEmpty } from 'utils/formUtil'; import { FieldError, ProcessorError } from 'error'; + import { dayjs } from 'utils/date'; import { ProcessorInfo } from "types/process/ProcessorInfo"; +import customParsers from 'dayjs/plugin/customParseFormat'; + +dayjs.extend(customParsers); const isValidatableTimeComponent = (comp: any): comp is TimeComponent => { return comp && comp.type === 'time'; @@ -18,17 +22,17 @@ export const shouldValidate = (context: ValidationContext) => { }; export const validateTimeSync: RuleFnSync = (context: ValidationContext) => { - const { component, value, config } = context; + const { component, data, path, value, config } = context; if (!shouldValidate(context)) { return null; } try { - if (!value || isEmpty(component, value)) return null; + if (!value || isComponentDataEmpty(component, data, path)) return null; // Server side evaluations of validity should use the "dataFormat" vs the "format" which is used on the client. const format = config?.server ? ((component as TimeComponent).dataFormat || 'HH:mm:ss') : ((component as TimeComponent).format || 'HH:mm'); - const isValid = dayjs(String(value), format).isValid(); + const isValid = dayjs(String(value), format, true).isValid(); return isValid ? null : new FieldError('time', context); } catch (err) { diff --git a/src/process/validation/util.ts b/src/process/validation/util.ts index ccb3ae51..4d3267f0 100644 --- a/src/process/validation/util.ts +++ b/src/process/validation/util.ts @@ -1,5 +1,4 @@ import { FieldError } from 'error'; -import { isArray, isEqual } from 'lodash'; import { Component, ValidationContext } from 'types'; import { Evaluator, unescapeHTML } from 'utils'; import { VALIDATION_ERRORS } from './i18n'; @@ -51,28 +50,6 @@ export function isObject(obj: any): obj is Object { return typeof obj != null && (typeof obj === 'object' || typeof obj === 'function'); } -export function getEmptyValue(component: Component) { - switch (component.type) { - case 'textarea': - case 'textfield': - case 'time': - case 'datetime': - case 'day': - return ''; - case 'datagrid': - case 'editgrid': - return []; - - default: - return null; - } -} - -export function isEmpty(component: Component, value: unknown) { - const isEmptyArray = (isArray(value) && value.length === 1) ? isEqual(value[0], getEmptyValue(component)) : false; - return value == null || (isArray(value) && value.length === 0) || isEmptyArray; -} - /** * Interpolates @formio/core errors so that they are compatible with the renderer * @param {FieldError[]} errors @@ -95,7 +72,7 @@ export const interpolateErrors = (errors: FieldError[], lang: string = 'en') => paths.push(part); } }); - return { + return { message: unescapeHTML(Evaluator.interpolateString(toInterpolate, context)), level: error.level, path: paths, diff --git a/src/types/Component.ts b/src/types/Component.ts index 4fc84ce8..36a287bc 100644 --- a/src/types/Component.ts +++ b/src/types/Component.ts @@ -86,6 +86,10 @@ export type ContainerComponent = NestedComponent & { components: Component[]; }; +export type HasChildComponents = BaseComponent & { + components: Component[]; +} + export type AddressComponent = ContainerComponent & { switchToManualModeLabel: string; provider: string; @@ -140,6 +144,7 @@ export type NumberComponent = BaseComponent & { export type NestedArrayComponent = NestedComponent & { disableAddingRemovingRows: boolean; + components: Component[]; }; export type DataGridComponent = NestedArrayComponent; @@ -494,7 +499,7 @@ export type TextAreaComponent = TextFieldComponent & { }; export type TimeComponent = TextFieldComponent & { - format: string; + format?: string; dataFormat: string; }; diff --git a/src/utils/__tests__/formUtil.test.ts b/src/utils/__tests__/formUtil.test.ts index 3107997a..18f7e8a3 100644 --- a/src/utils/__tests__/formUtil.test.ts +++ b/src/utils/__tests__/formUtil.test.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; -import { getContextualRowData, eachComponentDataAsync } from "../formUtil"; +import { getContextualRowData, eachComponentDataAsync, isComponentDataEmpty } from "../formUtil"; describe('getContextualRowData', () => { it('Should return the data at path without the last element given nested containers', () => { @@ -15,7 +15,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'c' + key: 'c' }, path, data); const expected = { c: 'hello' }; expect(actual).to.deep.equal(expected); @@ -33,7 +33,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'b' + key: 'b' }, path, data); const expected = { b: { c: 'hello' } }; expect(actual).to.deep.equal(expected); @@ -51,7 +51,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'a' + key: 'a' }, path, data); const expected = { a: { b: { c: 'hello' } } }; expect(actual).to.deep.equal(expected); @@ -70,7 +70,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'd' + key: 'd' }, path, data); const expected = { a: { b: { c: 'hello' } }, d: 'there' }; expect(actual).to.deep.equal(expected); @@ -84,7 +84,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'b' + key: 'b' }, path, data); const expected = {b: 'hello', c: 'world'}; expect(actual).to.deep.equal(expected); @@ -98,7 +98,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'b' + key: 'b' }, path, data); const expected = {b: 'foo', c: 'bar'}; expect(actual).to.deep.equal(expected); @@ -112,7 +112,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'a' + key: 'a' }, path, data); const expected = { a: [{b: 'hello', c: 'world'}, {b: 'foo', c: 'bar'}], @@ -128,7 +128,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'a' + key: 'a' }, path, data); const expected = { a: [{b: 'hello', c: 'world'}, {b: 'foo', c: 'bar'}], @@ -146,7 +146,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'c' + key: 'c' }, path, data); const expected = {c: 'hello', d: 'world'}; expect(actual).to.deep.equal(expected); @@ -162,7 +162,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'c' + key: 'c' }, path, data); const expected = {c: 'foo', d: 'bar'}; expect(actual).to.deep.equal(expected); @@ -178,7 +178,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'b' + key: 'b' }, path, data); const expected = {b: [{c: 'hello', d: 'world'}, {c: 'foo', d: 'bar'}]}; expect(actual).to.deep.equal(expected); @@ -194,7 +194,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'a' + key: 'a' }, path, data); const expected = {a: {b: [{c: 'hello', d: 'world'}, {c: 'foo', d: 'bar'}]}}; expect(actual).to.deep.equal(expected); @@ -210,7 +210,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'a' + key: 'a' }, path, data); const expected = {a: {b: [{c: 'hello', d: 'world'}, {c: 'foo', d: 'bar'}]}}; expect(actual).to.deep.equal(expected); @@ -226,7 +226,7 @@ describe('getContextualRowData', () => { const actual = getContextualRowData({ type: 'textfield', input: true, - key: 'c.e' + key: 'c.e' }, path, data); const expected = {c: {e: 'zed'}, d: 'world'}; expect(actual).to.deep.equal(expected); @@ -364,3 +364,344 @@ describe('eachComponentDataAsync', () => { }); }); }); + +describe('isEmpty', () => { + it('Should return true for an empty object', () => { + const component = { + type: 'textfield', + input: true, + key: 'textField', + }; + const data = {}; + const actual = isComponentDataEmpty(component, data, 'textField'); + const expected = true; + expect(actual).to.equal(expected); + }); + + it('Should return false for a non-empty object', () => { + const component = { + type: 'textfield', + input: true, + key: 'textField', + }; + const data = { + textField: 'hello', + }; + const actual = isComponentDataEmpty(component, data, 'textField'); + const expected = false; + expect(actual).to.equal(expected); + }); + + it('Should return true for a checkbox component set to false', () => { + const component = { + type: 'checkbox', + input: true, + key: 'checkbox', + }; + const data = { + checkbox: false, + }; + const actual = isComponentDataEmpty(component, data, 'checkbox'); + const expected = true; + expect(actual).to.equal(expected); + }); + + it('Should return false for a checkbox component set to true', () => { + const component = { + type: 'checkbox', + input: true, + key: 'checkbox', + }; + const data = { + checkbox: true, + }; + const actual = isComponentDataEmpty(component, data, 'checkbox'); + const expected = false; + expect(actual).to.equal(expected); + }); + + it('Should return true for an empty dataGrid component', () => { + const component = { + type: 'datagrid', + input: true, + key: 'dataGrid', + }; + const data = { + dataGrid: [], + }; + const actual = isComponentDataEmpty(component, data, 'dataGrid'); + const expected = true; + expect(actual).to.equal(expected); + }); + + it('Should return true for a non-empty dataGrid component with empty child components', () => { + const component = { + type: 'datagrid', + input: true, + key: 'dataGrid', + components: [ + { + type: 'textfield', + input: true, + key: 'textField', + }, + { + type: 'checkbox', + input: true, + key: 'checkbox' + }, + { + type: 'textarea', + wysiwyg: true, + input: true, + key: 'textArea' + } + ], + }; + const data = { + dataGrid: [ + { + textField: '', + checkbox: false, + textArea: '

 

', + }, + ], + }; + const actual = isComponentDataEmpty(component, data, 'dataGrid'); + const expected = true; + expect(actual).to.equal(expected); + }); + + it('Should return false for a datagrid with non-empty child components', () => { + const component = { + type: 'datagrid', + input: true, + key: 'dataGrid', + components: [ + { + type: 'textfield', + input: true, + key: 'textField', + }, + { + type: 'checkbox', + input: true, + key: 'checkbox' + }, + { + type: 'textarea', + wysiwyg: true, + input: true, + key: 'textArea' + } + ], + }; + const data = { + dataGrid: [ + { + textField: 'hello', + checkbox: true, + textArea: '

world

', + }, + ], + }; + const actual = isComponentDataEmpty(component, data, 'dataGrid'); + const expected = false; + expect(actual).to.equal(expected); + }); + + it('Should return true for an empty Select Boxes component', () => { + const component = { + type: 'selectboxes', + input: true, + key: 'selectBoxes', + data: { + values: [ + { + label: 'foo', + value: 'foo', + }, + { + label: 'bar', + value: 'bar', + }, + ], + }, + }; + const data = { + selectBoxes: {}, + }; + const actual = isComponentDataEmpty(component, data, 'selectBoxes'); + const expected = true; + expect(actual).to.equal(expected); + }); + + it('Should return true for a non-empty Select Boxes component with no selected values', () => { + const component = { + type: 'selectboxes', + input: true, + key: 'selectBoxes', + data: { + values: [ + { + label: 'foo', + value: 'foo', + }, + { + label: 'bar', + value: 'bar', + }, + ], + }, + }; + const data = { + selectBoxes: { + foo: false, + bar: false, + }, + }; + const actual = isComponentDataEmpty(component, data, 'selectBoxes'); + const expected = true; + expect(actual).to.equal(expected); + }); + + it('Should return false for a non-empty Select Boxes component with selected values', () => { + const component = { + type: 'selectboxes', + input: true, + key: 'selectBoxes', + data: { + values: [ + { + label: 'foo', + value: 'foo', + }, + { + label: 'bar', + value: 'bar', + }, + ], + }, + }; + const data = { + selectBoxes: { + foo: true, + bar: false, + }, + }; + const actual = isComponentDataEmpty(component, data, 'selectBoxes'); + const expected = false; + expect(actual).to.equal(expected); + }); + + it('Should return true for an empty Select component', () => { + const component = { + type: 'select', + input: true, + key: 'select', + data: { + values: [ + { + label: 'foo', + value: 'foo', + }, + { + label: 'bar', + value: 'bar', + }, + ], + }, + }; + const data = { + select: '', + }; + const actual = isComponentDataEmpty(component, data, 'select'); + const expected = true; + expect(actual).to.equal(expected); + }); + + it('Should return true for an empty plain Text Area component', () => { + const component = { + type: 'textarea', + input: true, + key: 'textArea', + }; + const data = { + textArea: '', + }; + const actual = isComponentDataEmpty(component, data, 'textArea'); + const expected = true; + expect(actual).to.equal(expected); + }); + + it('Should return true for a non-empty non-plain Text Area component with only WYSIWYG or editor HTML', () => { + const component = { + type: 'textarea', + input: true, + key: 'textArea', + wysiwyg: true + }; + const data = { + textArea: '

 

', + }; + const actual = isComponentDataEmpty(component, data, 'textArea'); + const expected = true; + expect(actual).to.equal(expected); + }); + + it('Should return true for a non-empty text area with only whitespace', () => { + const component = { + type: 'textarea', + input: true, + key: 'textArea', + }; + const data = { + textArea: ' ', + }; + const actual = isComponentDataEmpty(component, data, 'textArea'); + const expected = true; + expect(actual).to.equal(expected); + }); + + it('Should return false for a non-empty Text Field', () => { + const component = { + type: 'textfield', + input: true, + key: 'textField', + }; + const data = { + textField: 'hello', + }; + const actual = isComponentDataEmpty(component, data, 'textField'); + const expected = false; + expect(actual).to.equal(expected); + }); + + it('Should return true for an empty Text Field component', () => { + const component = { + type: 'textfield', + input: true, + key: 'textField', + }; + const data = { + textField: '', + }; + const actual = isComponentDataEmpty(component, data, 'textField'); + const expected = true; + expect(actual).to.equal(expected); + }); + + it('Should return true for a non-empty Text Field component with only whitespace', () => { + const component = { + type: 'textfield', + input: true, + key: 'textField', + }; + const data = { + textField: ' ', + }; + const actual = isComponentDataEmpty(component, data, 'textField'); + const expected = true; + expect(actual).to.equal(expected); + }); +}); diff --git a/src/utils/date.ts b/src/utils/date.ts index 96f97d7d..946aac2c 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -81,7 +81,7 @@ export function formatDate(value: any, format: any, timezone: any): string { * @param date * @return {(null|Date)} */ - export function getDateSetting(date: any) { +export function getDateSetting(date: any) { if (isNil(date) || isNaN(date) || date === '') { return null; } @@ -125,9 +125,9 @@ export function formatDate(value: any, format: any, timezone: any): string { } return dateSetting.toDate(); - } +} - export const getDateValidationFormat = (component: DayComponent) => { +export const getDateValidationFormat = (component: DayComponent) => { return component.dayFirst ? 'DD-MM-YYYY' : 'MM-DD-YYYY'; }; diff --git a/src/utils/formUtil.ts b/src/utils/formUtil.ts index d468a6ab..e73b655c 100644 --- a/src/utils/formUtil.ts +++ b/src/utils/formUtil.ts @@ -1,8 +1,32 @@ -import { last, get, set, isEmpty, isNil, isObject, has, isString, forOwn, round, chunk, pad, isPlainObject } from "lodash"; +import { + last, + get, + set, + isEmpty, + isNil, + isObject, + has, + isString, + forOwn, + round, + chunk, + pad, + isPlainObject, + isArray, + isEqual +} from "lodash"; import { compare, applyPatch } from 'fast-json-patch'; import { AsyncComponentDataCallback, CheckboxComponent, + DataGridComponent, + EditGridComponent, + DataTableComponent, + DateTimeComponent, + TextAreaComponent, + TextFieldComponent, + HasChildComponents, + SelectBoxesComponent, Component, ComponentDataCallback, DataObject, @@ -1026,3 +1050,98 @@ export function findComponent(components: any, key: any, path: any, fn: any) { } }); } + +const isCheckboxComponent = (component: Component): component is CheckboxComponent => component.type === 'checkbox'; +const isDataGridComponent = (component: Component): component is DataGridComponent => component.type === 'datagrid'; +const isEditGridComponent = (component: Component): component is EditGridComponent => component.type === 'editgrid'; +const isDataTableComponent = (component: Component): component is DataTableComponent => component.type === 'datatable'; +const hasChildComponents = (component: any): component is HasChildComponents => component.components != null; +const isDateTimeComponent = (component: Component): component is DateTimeComponent => component.type === 'datetime'; +const isSelectBoxesComponent = (component: Component): component is SelectBoxesComponent => component.type === 'selectboxes'; +const isTextAreaComponent = (component: Component): component is TextAreaComponent => component.type === 'textarea'; +const isTextFieldComponent = (component: Component): component is TextFieldComponent => component.type === 'textfield'; + +export function getEmptyValue(component: Component) { + switch (component.type) { + case 'textarea': + case 'textfield': + case 'time': + case 'datetime': + case 'day': + return ''; + case 'datagrid': + case 'editgrid': + return []; + + default: + return null; + } +} + +const replaceBlanks = (value: unknown) => { + const nbsp = '

 

'; + const br = '


'; + const brNbsp = '


 

'; + const regExp = new RegExp(`^${nbsp}|${nbsp}$|^${br}|${br}$|^${brNbsp}|${brNbsp}$`, 'g'); + return typeof value === 'string' ? value.replace(regExp, '').trim() : value; +}; + +function trimBlanks(value: unknown) { + if (!value) { + return value; + } + + if (Array.isArray(value)) { + value = value.map((val: any) => replaceBlanks(val)); + } + else { + value = replaceBlanks(value); + } + return value; +} + +function isValueEmpty(component: Component, value: any) { + const compValueIsEmptyArray = (isArray(value) && value.length === 1) ? isEqual(value[0], getEmptyValue(component)) : false; + return value == null || value === '' || (isArray(value) && value.length === 0) || compValueIsEmptyArray; +} + +export function isComponentDataEmpty(component: Component, data: any, path: string): boolean { + const value = get(data, path); + if (isCheckboxComponent(component)) { + return isValueEmpty(component, value) || value === false; + } else if (isDataGridComponent(component) || isEditGridComponent(component) || isDataTableComponent(component) || hasChildComponents(component)) { + if (component.components?.length) { + let childrenEmpty = true; + // TODO: eachComponentData currently can't handle passing child components directly because it won't get the path right; + // wrapping component in an array and skipping it's callback is a workaround to start with the correct path, but it is not ideal + eachComponentData([component], data, (thisComponent, data, row, path, components, index) => { + if (component.key === thisComponent.key) return; + if (!isComponentDataEmpty(thisComponent, data, path)) { + childrenEmpty = false; + } + }); + return isValueEmpty(component, value) || childrenEmpty; + } + return isValueEmpty(component, value); + } else if (isDateTimeComponent(component)) { + return isValueEmpty(component, value) || value.toString() === 'Invalid date'; + } else if (isSelectBoxesComponent(component)) { + let selectBoxEmpty = true; + for (const key in value) { + if (value[key]) { + selectBoxEmpty = false; + break; + } + } + return isValueEmpty(component, value) || selectBoxEmpty; + } else if (isTextAreaComponent(component)) { + const isPlain = !component.wysiwyg && !component.editor; + return isPlain ? typeof value === 'string' ? isValueEmpty(component, value.trim()) : isValueEmpty(component, value) : isValueEmpty(component, trimBlanks(value)); + } else if (isTextFieldComponent(component)) { + if (component.allowMultipleMasks && !!component.inputMasks && !!component.inputMasks.length) { + return isValueEmpty(component, value) || (component.multiple ? value.length === 0 : (!value.maskName || !value.value)); + } + return isValueEmpty(component, value?.toString().trim()); + } + return isValueEmpty(component, value); +} diff --git a/src/utils/operators/DateGreaterThan.js b/src/utils/operators/DateGreaterThan.js index 81eda99f..cc545cf3 100644 --- a/src/utils/operators/DateGreaterThan.js +++ b/src/utils/operators/DateGreaterThan.js @@ -1,5 +1,6 @@ import ConditionOperator from './ConditionOperator'; import moment from 'moment'; +import { isPartialDay, getDateValidationFormat } from '../../utils/date'; export default class DateGeaterThan extends ConditionOperator { static get operatorKey() { return 'dateGreaterThan'; @@ -10,7 +11,7 @@ export default class DateGeaterThan extends ConditionOperator { } getFormattedDates({ value, comparedValue, conditionTriggerComponent }) { - const hasValidationFormat = conditionTriggerComponent ? conditionTriggerComponent.getValidationFormat : null; + const hasValidationFormat = conditionTriggerComponent && conditionTriggerComponent.component.type === 'day' ? getDateValidationFormat(conditionTriggerComponent.component) : null; const date = hasValidationFormat ? moment(value, conditionTriggerComponent.getValidationFormat()) : moment(value); const comparedDate = hasValidationFormat ? moment(comparedValue, conditionTriggerComponent.getValidationFormat()) : moment(comparedValue); @@ -30,7 +31,7 @@ export default class DateGeaterThan extends ConditionOperator { conditionTriggerComponent = instance.root.getComponent(conditionComponentPath); } - if ( conditionTriggerComponent && conditionTriggerComponent.isPartialDay && conditionTriggerComponent.isPartialDay(value)) { + if (conditionTriggerComponent && conditionTriggerComponent.component.type === 'day' && isPartialDay(conditionTriggerComponent.component, value)) { return false; }