From c608076cec1942e7744a3c22ed6b39b2ead035d1 Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Fri, 28 Apr 2023 15:56:20 +0200 Subject: [PATCH 1/5] :sparkles: #19 - Basic Form.io conditionals. --- src/components/columns/columns.component.tsx | 6 +- src/fixtures/formio/formio-conditional.ts | 86 ++++++++++++++++++ src/fixtures/formio/index.ts | 1 + src/lib/renderer/renderer.stories.tsx | 93 +++++++++++++++++++- src/lib/renderer/renderer.tsx | 82 ++++++++++++++--- 5 files changed, 248 insertions(+), 20 deletions(-) create mode 100644 src/fixtures/formio/formio-conditional.ts diff --git a/src/components/columns/columns.component.tsx b/src/components/columns/columns.component.tsx index 99b3be8..3fa3d1a 100644 --- a/src/components/columns/columns.component.tsx +++ b/src/components/columns/columns.component.tsx @@ -36,11 +36,9 @@ export interface IFormioColumn { sizeMobile?: ColumnSize; } -export interface IColumnComponent extends IFormioColumn { - defaultValue: undefined; - - key: undefined; +type IFormioColumnComponentSchema = IFormioColumn & ComponentSchema; +export interface IColumnComponent extends IFormioColumnComponentSchema { type: 'column'; } diff --git a/src/fixtures/formio/formio-conditional.ts b/src/fixtures/formio/formio-conditional.ts new file mode 100644 index 0000000..06e11c0 --- /dev/null +++ b/src/fixtures/formio/formio-conditional.ts @@ -0,0 +1,86 @@ +import {IContentComponent} from '@components'; +import {IRendererComponent} from '@lib/renderer'; + +export const FORMIO_CONDITIONAL: Array = [ + // Reference field. + { + id: 'favoriteAnimal', + type: 'textfield', + label: 'Favorite animal', + key: 'favoriteAnimal', + }, + + // Case: hide unless "cat" + { + conditional: { + eq: 'cat', + show: true, + when: 'favoriteAnimal', + }, + id: 'motivationCat', + hidden: true, + type: 'textfield', + key: 'motivation', + label: 'Motivation', + placeholder: 'I like cats because...', + description: 'Please motivate why "cat" is your favorite animal...', + }, + + // Case hide unless "dog" + { + conditional: { + eq: 'dog', + show: true, + when: 'favoriteAnimal', + }, + id: 'motivationDog', + hidden: true, + type: 'textfield', + key: 'motivation', + label: 'Motivation', + placeholder: 'I like dogs because...', + description: 'Please motivate why "dog" is your favorite animal...', + }, + + // Case hide unless "" (empty string) + { + conditional: { + eq: '', + show: true, + when: 'favoriteAnimal', + }, + id: 'content1', + hidden: true, + type: 'content', + key: 'content', + html: 'Please enter you favorite animal.', + }, + + // Case show unless "cat" + { + conditional: { + eq: 'cat', + show: false, + when: 'favoriteAnimal', + }, + id: 'content2', + hidden: false, + type: 'content', + key: 'content', + html: 'Have you tried "cat"?', + }, + + // Case show unless "dog" + { + conditional: { + eq: 'dog', + show: false, + when: 'favoriteAnimal', + }, + id: 'content3', + hidden: false, + type: 'content', + key: 'content', + html: 'Have you tried "dog"?', + }, +]; diff --git a/src/fixtures/formio/index.ts b/src/fixtures/formio/index.ts index f5aeade..1641172 100644 --- a/src/fixtures/formio/index.ts +++ b/src/fixtures/formio/index.ts @@ -1,4 +1,5 @@ export * from './formio-columns'; +export * from './formio-conditional'; export * from './formio-example'; export * from './formio-length'; export * from './formio-pattern'; diff --git a/src/lib/renderer/renderer.stories.tsx b/src/lib/renderer/renderer.stories.tsx index ba59767..cf24de3 100644 --- a/src/lib/renderer/renderer.stories.tsx +++ b/src/lib/renderer/renderer.stories.tsx @@ -1,5 +1,16 @@ -import {FORMIO_EXAMPLE, FORMIO_LENGTH, FORMIO_PATTERN, FORMIO_REQUIRED} from '@fixtures'; -import {DEFAULT_RENDER_CONFIGURATION, RenderComponent, RenderForm} from '@lib/renderer/renderer'; +import { + FORMIO_CONDITIONAL, + FORMIO_EXAMPLE, + FORMIO_LENGTH, + FORMIO_PATTERN, + FORMIO_REQUIRED, +} from '@fixtures'; +import { + DEFAULT_RENDER_CONFIGURATION, + IRendererComponent, + RenderComponent, + RenderForm, +} from '@lib/renderer/renderer'; import {expect} from '@storybook/jest'; import type {ComponentStory, Meta} from '@storybook/react'; import {userEvent, within} from '@storybook/testing-library'; @@ -151,6 +162,78 @@ renderFormWithNestedKeyValidation.play = async ({canvasElement}) => { await canvas.findByText('Er zijn te weinig karakters opgegeven.'); }; +export const renderFormWithConditionalLogic: ComponentStory = args => ( + + + +); +renderFormWithConditionalLogic.args = { + configuration: DEFAULT_RENDER_CONFIGURATION, + form: { + display: 'form', + components: FORMIO_CONDITIONAL, + }, + initialValues: { + favoriteAnimal: '', + motivationCat: '', + motivationDog: '', + content: '', + }, +}; +renderFormWithConditionalLogic.play = async ({canvasElement}) => { + const canvas = within(canvasElement); + const input = canvas.getByLabelText('Favorite animal', { + selector: 'input', + }); + expect( + await canvas.queryByText('Please motivate why "cat" is your favorite animal...') + ).toBeNull(); + expect( + await canvas.queryByText('Please motivate why "dog" is your favorite animal...') + ).toBeNull(); + await canvas.findByText('Please enter you favorite animal.'); + await canvas.findByText('Have you tried "cat"?'); + await canvas.findByText('Have you tried "dog"?'); + await userEvent.type(input, 'horse', {delay: 30}); + expect( + await canvas.queryByText('Please motivate why "cat" is your favorite animal...') + ).toBeNull(); + expect( + await canvas.queryByText('Please motivate why "dog" is your favorite animal...') + ).toBeNull(); + expect(await canvas.queryByText('Please enter you favorite animal.')).toBeNull(); + await canvas.findByText('Have you tried "cat"?'); + await canvas.findByText('Have you tried "dog"?'); + await userEvent.clear(input); + await userEvent.type(input, 'cat', {delay: 30}); + await canvas.findByText('Please motivate why "cat" is your favorite animal...'); + expect( + await canvas.queryByText('Please motivate why "dog" is your favorite animal...') + ).toBeNull(); + expect(await canvas.queryByText('Please enter you favorite animal.')).toBeNull(); + expect(await canvas.queryByText('Have you tried "cat"?')).toBeNull(); + await canvas.findByText('Have you tried "dog"?'); + await userEvent.clear(input); + await userEvent.type(input, 'dog', {delay: 30}); + expect( + await canvas.queryByText('Please motivate why "cat" is your favorite animal...') + ).toBeNull(); + await canvas.findByText('Please motivate why "dog" is your favorite animal...'); + expect(await canvas.queryByText('Please enter you favorite animal.')).toBeNull(); + await canvas.findByText('Have you tried "cat"?'); + expect(await canvas.queryByText('Have you tried "dog"?')).toBeNull(); + await userEvent.clear(input); + expect( + await canvas.queryByText('Please motivate why "cat" is your favorite animal...') + ).toBeNull(); + expect( + await canvas.queryByText('Please motivate why "dog" is your favorite animal...') + ).toBeNull(); + await canvas.findByText('Please enter you favorite animal.'); + await canvas.findByText('Have you tried "cat"?'); + await canvas.findByText('Have you tried "dog"?'); +}; + export const formValidationWithLayoutComponent: ComponentStory = args => ( @@ -199,7 +282,11 @@ export const renderComponent: ComponentStory = args => ( ); renderComponent.args = { - component: FORMIO_EXAMPLE[0], + component: FORMIO_EXAMPLE[0] as IRendererComponent, + form: { + display: 'form', + components: FORMIO_EXAMPLE, + }, }; renderComponent.play = async ({canvasElement}) => { const canvas = within(canvasElement); diff --git a/src/lib/renderer/renderer.tsx b/src/lib/renderer/renderer.tsx index eab5236..7efa6a9 100644 --- a/src/lib/renderer/renderer.tsx +++ b/src/lib/renderer/renderer.tsx @@ -9,11 +9,13 @@ import { } from '@components'; import {DEFAULT_VALIDATORS, getFormErrors} from '@lib/validation'; import {IComponentProps, IFormioForm, IRenderConfiguration, IValues} from '@types'; -import {Formik, useField} from 'formik'; +import {Formik, useField, useFormikContext} from 'formik'; import {FormikHelpers} from 'formik/dist/types'; -import {ComponentSchema} from 'formiojs'; +import {ComponentSchema, Utils} from 'formiojs'; import React, {FormHTMLAttributes, useContext} from 'react'; +import getComponent = Utils.getComponent; + export const DEFAULT_RENDER_CONFIGURATION: IRenderConfiguration = { components: { columns: Columns, @@ -29,6 +31,14 @@ export const RenderContext = React.createContext( DEFAULT_RENDER_CONFIGURATION ); +export interface IRendererComponent extends ComponentSchema { + columns?: IFormioColumn[]; + components?: IRendererComponent[]; + id: string; + key: string; + type: string; +} + export interface IRenderFormProps { children: React.ReactNode; configuration: IRenderConfiguration; @@ -72,7 +82,7 @@ export const RenderForm = ({ }: IRenderFormProps): React.ReactElement => { const childComponents = form.components?.map((component: IRendererComponent, i: number) => ( - + )) || null; return ( @@ -95,15 +105,9 @@ export const RenderForm = ({ ); }; -export interface IRendererComponent extends ComponentSchema { - columns?: IFormioColumn[]; - components?: IRendererComponent[]; - id?: string; - type: string; -} - export interface IRenderComponentProps { component: IColumnComponent | IRendererComponent; + form: IFormioForm; } /** @const Form.io does not guarantee a key for a form component, we use this as a fallback. */ @@ -135,9 +139,25 @@ export const OF_MISSING_KEY = 'OF_MISSING_KEY'; * @external {FormikContext} Expects `Formik`/`FormikContext` to be available. * @external {RenderContext} Expects `RenderContext` to be available. */ -export const RenderComponent = ({component}: IRenderComponentProps): React.ReactElement => { +export const RenderComponent = ({ + component, + form, +}: IRenderComponentProps): React.ReactElement | null => { + const {setFieldValue} = useFormikContext(); const Component = useComponentType(component); const field = useField(component.key || OF_MISSING_KEY); + + // Basic Form.io conditional. + const show = useConditional(component, form); + + if (!show && component.clearOnHide) { + setFieldValue(component.key || OF_MISSING_KEY, null); + } + + if (!show) { + return null; + } + const [{value, onBlur, onChange}, {error}] = field; const callbacks = {onBlur, onChange}; const errors = error?.split('\n') || []; // Reconstruct array. @@ -154,14 +174,23 @@ export const RenderComponent = ({component}: IRenderComponentProps): React.React // Regular children, either from component or column. const childComponents = cComponents?.map((c: IRendererComponent, i: number) => ( - + )); // Columns from component. const childColumns = cColumns?.map((c: IFormioColumn, i) => ( )); @@ -172,6 +201,7 @@ export const RenderComponent = ({component}: IRenderComponentProps): React.React ); }; + /** * Fallback component, gets used when no other component is found within the `RenderContext` * The Fallback component makes sure (child) components keep being rendered with as little side @@ -190,3 +220,29 @@ export const useComponentType = ( const ComponentType = renderConfiguration.components[component.type]; return ComponentType || Fallback; }; + +/** + * Evaluates the `component.conditional` (if set) and returns whether the component should be shown. + * This does not evaluate complex form logic but merely the basic Form.io conditional logic (found + * in the "Advanced" tab). + * + * @external {FormikContext} Expects `Formik`/`FormikContext` to be available. + * @return {boolean} If a conditional passes, the show argument is returned to respect it's + * configuration. If a conditional does not pass, `!component.hidden` is used as return value. + */ +export const useConditional = ( + component: IColumnComponent | IRendererComponent, + form: IFormioForm +) => { + const {eq, show, when} = component?.conditional || {}; + const isConditionalSet = typeof show == 'boolean' && when; + const otherComponent = getComponent(form.components, when as string, false); + const [{value}] = useField(otherComponent.key || OF_MISSING_KEY); + const isEqual = eq === value; + + if (!isConditionalSet || !otherComponent || !isEqual) { + return !component.hidden; + } + + return show; +}; From dcb1ef6645b931bf86f3ec443ce60e00cbaa2729 Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Tue, 9 May 2023 15:16:10 +0200 Subject: [PATCH 2/5] :label: #19 - Refactor/simplify types. --- src/fixtures/formio/formio-example.ts | 6 -- src/lib/renderer/renderer.stories.tsx | 9 +-- src/lib/renderer/renderer.tsx | 79 +++++++++++++-------------- 3 files changed, 41 insertions(+), 53 deletions(-) diff --git a/src/fixtures/formio/formio-example.ts b/src/fixtures/formio/formio-example.ts index 067fa15..85907bd 100644 --- a/src/fixtures/formio/formio-example.ts +++ b/src/fixtures/formio/formio-example.ts @@ -47,10 +47,4 @@ export const FORMIO_EXAMPLE = [ multiple: true, input: true, }, - { - type: 'button', - action: 'submit', - label: 'Submit', - theme: 'primary', - }, ]; diff --git a/src/lib/renderer/renderer.stories.tsx b/src/lib/renderer/renderer.stories.tsx index cf24de3..ab06fdc 100644 --- a/src/lib/renderer/renderer.stories.tsx +++ b/src/lib/renderer/renderer.stories.tsx @@ -5,12 +5,7 @@ import { FORMIO_PATTERN, FORMIO_REQUIRED, } from '@fixtures'; -import { - DEFAULT_RENDER_CONFIGURATION, - IRendererComponent, - RenderComponent, - RenderForm, -} from '@lib/renderer/renderer'; +import {DEFAULT_RENDER_CONFIGURATION, RenderComponent, RenderForm} from '@lib/renderer/renderer'; import {expect} from '@storybook/jest'; import type {ComponentStory, Meta} from '@storybook/react'; import {userEvent, within} from '@storybook/testing-library'; @@ -282,7 +277,7 @@ export const renderComponent: ComponentStory = args => ( ); renderComponent.args = { - component: FORMIO_EXAMPLE[0] as IRendererComponent, + component: FORMIO_EXAMPLE[0], form: { display: 'form', components: FORMIO_EXAMPLE, diff --git a/src/lib/renderer/renderer.tsx b/src/lib/renderer/renderer.tsx index 7efa6a9..c2c7e45 100644 --- a/src/lib/renderer/renderer.tsx +++ b/src/lib/renderer/renderer.tsx @@ -1,20 +1,13 @@ -import { - Column, - Columns, - Content, - IColumnComponent, - IColumnProps, - IFormioColumn, - TextField, -} from '@components'; +import {Column, Columns, Content, IColumnProps, TextField} from '@components'; import {DEFAULT_VALIDATORS, getFormErrors} from '@lib/validation'; import {IComponentProps, IFormioForm, IRenderConfiguration, IValues} from '@types'; import {Formik, useField, useFormikContext} from 'formik'; import {FormikHelpers} from 'formik/dist/types'; -import {ComponentSchema, Utils} from 'formiojs'; +import {Utils} from 'formiojs'; +import {ConditionalOptions} from 'formiojs/types/components/schema'; import React, {FormHTMLAttributes, useContext} from 'react'; -import getComponent = Utils.getComponent; +const getComponent = Utils.getComponent; export const DEFAULT_RENDER_CONFIGURATION: IRenderConfiguration = { components: { @@ -31,12 +24,28 @@ export const RenderContext = React.createContext( DEFAULT_RENDER_CONFIGURATION ); -export interface IRendererComponent extends ComponentSchema { - columns?: IFormioColumn[]; - components?: IRendererComponent[]; - id: string; +/** Form.io does not guarantee a key for a form component, we use this as a fallback. */ +export const OF_MISSING_KEY = 'OF_MISSING_KEY'; + +/** + * Specifies the required and optional properties for a schema which can be rendered by the + * renderer. + * + * A schema implementing `IRenderable` is not limited to `ComponentSchema` (as columns can be + * rendered) and components will be rendered with the full (Component)Schema. + */ +export interface IRenderable { key: string; type: string; + + components?: IRenderable[]; + clearOnHide?: boolean; + columns?: IRenderable[]; + conditional?: ConditionalOptions; + hidden?: boolean; + id?: string; + + [index: string]: any; } export interface IRenderFormProps { @@ -81,8 +90,8 @@ export const RenderForm = ({ onSubmit, }: IRenderFormProps): React.ReactElement => { const childComponents = - form.components?.map((component: IRendererComponent, i: number) => ( - + form.components?.map((c: IRenderable) => ( + )) || null; return ( @@ -106,13 +115,10 @@ export const RenderForm = ({ }; export interface IRenderComponentProps { - component: IColumnComponent | IRendererComponent; + component: IRenderable; form: IFormioForm; } -/** @const Form.io does not guarantee a key for a form component, we use this as a fallback. */ -export const OF_MISSING_KEY = 'OF_MISSING_KEY'; - /** * Renderer for rendering a Form.io component passed as component. Iterates over children (and * columns) and returns a `React.ReactElement` containing the rendered component. @@ -143,15 +149,16 @@ export const RenderComponent = ({ component, form, }: IRenderComponentProps): React.ReactElement | null => { + const key = component.key || OF_MISSING_KEY; const {setFieldValue} = useFormikContext(); const Component = useComponentType(component); - const field = useField(component.key || OF_MISSING_KEY); + const field = useField(key); // Basic Form.io conditional. const show = useConditional(component, form); if (!show && component.clearOnHide) { - setFieldValue(component.key || OF_MISSING_KEY, null); + setFieldValue(key, null); } if (!show) { @@ -168,26 +175,21 @@ export const RenderComponent = ({ // // This allows for components to remain simple and increases compatibility with existing design // systems. - const _component = component as IRendererComponent; - const cComponents = component.components ? component.components : null; - const cColumns = _component.columns ? _component.columns : null; + const cComponents = component.components || null; + const cColumns = component.columns || null; // Regular children, either from component or column. - const childComponents = cComponents?.map((c: IRendererComponent, i: number) => ( - + const childComponents = cComponents?.map(c => ( + )); // Columns from component. - const childColumns = cColumns?.map((c: IFormioColumn, i) => ( + const childColumns = cColumns?.map(c => ( {props.children} => { const renderConfiguration = useContext(RenderContext); const ComponentType = renderConfiguration.components[component.type]; @@ -230,10 +232,7 @@ export const useComponentType = ( * @return {boolean} If a conditional passes, the show argument is returned to respect it's * configuration. If a conditional does not pass, `!component.hidden` is used as return value. */ -export const useConditional = ( - component: IColumnComponent | IRendererComponent, - form: IFormioForm -) => { +export const useConditional = (component: IRenderable, form: IFormioForm) => { const {eq, show, when} = component?.conditional || {}; const isConditionalSet = typeof show == 'boolean' && when; const otherComponent = getComponent(form.components, when as string, false); From 727269d903c2b151f12e833f726dc0d2ee8aba36 Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Tue, 9 May 2023 16:01:28 +0200 Subject: [PATCH 3/5] :sparkles: #19 - PR feedback. --- src/lib/renderer/renderer.tsx | 31 ++++--------------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/src/lib/renderer/renderer.tsx b/src/lib/renderer/renderer.tsx index c2c7e45..74bc908 100644 --- a/src/lib/renderer/renderer.tsx +++ b/src/lib/renderer/renderer.tsx @@ -7,8 +7,6 @@ import {Utils} from 'formiojs'; import {ConditionalOptions} from 'formiojs/types/components/schema'; import React, {FormHTMLAttributes, useContext} from 'react'; -const getComponent = Utils.getComponent; - export const DEFAULT_RENDER_CONFIGURATION: IRenderConfiguration = { components: { columns: Columns, @@ -150,12 +148,14 @@ export const RenderComponent = ({ form, }: IRenderComponentProps): React.ReactElement | null => { const key = component.key || OF_MISSING_KEY; - const {setFieldValue} = useFormikContext(); + const {setFieldValue, values} = useFormikContext(); const Component = useComponentType(component); const field = useField(key); // Basic Form.io conditional. - const show = useConditional(component, form); + const show = Utils.hasCondition(component) + ? Utils.checkCondition(component, null, values, form, null) + : !component.hidden; if (!show && component.clearOnHide) { setFieldValue(key, null); @@ -222,26 +222,3 @@ export const useComponentType = ( const ComponentType = renderConfiguration.components[component.type]; return ComponentType || Fallback; }; - -/** - * Evaluates the `component.conditional` (if set) and returns whether the component should be shown. - * This does not evaluate complex form logic but merely the basic Form.io conditional logic (found - * in the "Advanced" tab). - * - * @external {FormikContext} Expects `Formik`/`FormikContext` to be available. - * @return {boolean} If a conditional passes, the show argument is returned to respect it's - * configuration. If a conditional does not pass, `!component.hidden` is used as return value. - */ -export const useConditional = (component: IRenderable, form: IFormioForm) => { - const {eq, show, when} = component?.conditional || {}; - const isConditionalSet = typeof show == 'boolean' && when; - const otherComponent = getComponent(form.components, when as string, false); - const [{value}] = useField(otherComponent.key || OF_MISSING_KEY); - const isEqual = eq === value; - - if (!isConditionalSet || !otherComponent || !isEqual) { - return !component.hidden; - } - - return show; -}; From c78f36a7eeee4b27b5644f6556ea237365467390 Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Tue, 9 May 2023 16:19:32 +0200 Subject: [PATCH 4/5] :green_heart: #19 - Attempt to fix the build. --- src/fixtures/formio/formio-conditional.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/fixtures/formio/formio-conditional.ts b/src/fixtures/formio/formio-conditional.ts index 06e11c0..d6d4347 100644 --- a/src/fixtures/formio/formio-conditional.ts +++ b/src/fixtures/formio/formio-conditional.ts @@ -1,7 +1,4 @@ -import {IContentComponent} from '@components'; -import {IRendererComponent} from '@lib/renderer'; - -export const FORMIO_CONDITIONAL: Array = [ +export const FORMIO_CONDITIONAL = [ // Reference field. { id: 'favoriteAnimal', From 210bae72a751d9425339e111cfb5164433ba59f0 Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Thu, 11 May 2023 11:17:50 +0200 Subject: [PATCH 5/5] :ok_hand: #19 - PR feecback. - Move fixture to story. - Remove selector option in test. --- src/fixtures/formio/formio-conditional.ts | 83 -------------------- src/fixtures/formio/index.ts | 1 - src/lib/renderer/renderer.stories.tsx | 96 ++++++++++++++++++++--- 3 files changed, 85 insertions(+), 95 deletions(-) delete mode 100644 src/fixtures/formio/formio-conditional.ts diff --git a/src/fixtures/formio/formio-conditional.ts b/src/fixtures/formio/formio-conditional.ts deleted file mode 100644 index d6d4347..0000000 --- a/src/fixtures/formio/formio-conditional.ts +++ /dev/null @@ -1,83 +0,0 @@ -export const FORMIO_CONDITIONAL = [ - // Reference field. - { - id: 'favoriteAnimal', - type: 'textfield', - label: 'Favorite animal', - key: 'favoriteAnimal', - }, - - // Case: hide unless "cat" - { - conditional: { - eq: 'cat', - show: true, - when: 'favoriteAnimal', - }, - id: 'motivationCat', - hidden: true, - type: 'textfield', - key: 'motivation', - label: 'Motivation', - placeholder: 'I like cats because...', - description: 'Please motivate why "cat" is your favorite animal...', - }, - - // Case hide unless "dog" - { - conditional: { - eq: 'dog', - show: true, - when: 'favoriteAnimal', - }, - id: 'motivationDog', - hidden: true, - type: 'textfield', - key: 'motivation', - label: 'Motivation', - placeholder: 'I like dogs because...', - description: 'Please motivate why "dog" is your favorite animal...', - }, - - // Case hide unless "" (empty string) - { - conditional: { - eq: '', - show: true, - when: 'favoriteAnimal', - }, - id: 'content1', - hidden: true, - type: 'content', - key: 'content', - html: 'Please enter you favorite animal.', - }, - - // Case show unless "cat" - { - conditional: { - eq: 'cat', - show: false, - when: 'favoriteAnimal', - }, - id: 'content2', - hidden: false, - type: 'content', - key: 'content', - html: 'Have you tried "cat"?', - }, - - // Case show unless "dog" - { - conditional: { - eq: 'dog', - show: false, - when: 'favoriteAnimal', - }, - id: 'content3', - hidden: false, - type: 'content', - key: 'content', - html: 'Have you tried "dog"?', - }, -]; diff --git a/src/fixtures/formio/index.ts b/src/fixtures/formio/index.ts index 1641172..f5aeade 100644 --- a/src/fixtures/formio/index.ts +++ b/src/fixtures/formio/index.ts @@ -1,5 +1,4 @@ export * from './formio-columns'; -export * from './formio-conditional'; export * from './formio-example'; export * from './formio-length'; export * from './formio-pattern'; diff --git a/src/lib/renderer/renderer.stories.tsx b/src/lib/renderer/renderer.stories.tsx index ab06fdc..57a1f80 100644 --- a/src/lib/renderer/renderer.stories.tsx +++ b/src/lib/renderer/renderer.stories.tsx @@ -1,10 +1,4 @@ -import { - FORMIO_CONDITIONAL, - FORMIO_EXAMPLE, - FORMIO_LENGTH, - FORMIO_PATTERN, - FORMIO_REQUIRED, -} from '@fixtures'; +import {FORMIO_EXAMPLE, FORMIO_LENGTH, FORMIO_PATTERN, FORMIO_REQUIRED} from '@fixtures'; import {DEFAULT_RENDER_CONFIGURATION, RenderComponent, RenderForm} from '@lib/renderer/renderer'; import {expect} from '@storybook/jest'; import type {ComponentStory, Meta} from '@storybook/react'; @@ -166,7 +160,89 @@ renderFormWithConditionalLogic.args = { configuration: DEFAULT_RENDER_CONFIGURATION, form: { display: 'form', - components: FORMIO_CONDITIONAL, + components: [ + // Reference field. + { + id: 'favoriteAnimal', + type: 'textfield', + label: 'Favorite animal', + key: 'favoriteAnimal', + }, + + // Case: hide unless "cat" + { + conditional: { + eq: 'cat', + show: true, + when: 'favoriteAnimal', + }, + id: 'motivationCat', + hidden: true, + type: 'textfield', + key: 'motivation', + label: 'Motivation', + placeholder: 'I like cats because...', + description: 'Please motivate why "cat" is your favorite animal...', + }, + + // Case hide unless "dog" + { + conditional: { + eq: 'dog', + show: true, + when: 'favoriteAnimal', + }, + id: 'motivationDog', + hidden: true, + type: 'textfield', + key: 'motivation', + label: 'Motivation', + placeholder: 'I like dogs because...', + description: 'Please motivate why "dog" is your favorite animal...', + }, + + // Case hide unless "" (empty string) + { + conditional: { + eq: '', + show: true, + when: 'favoriteAnimal', + }, + id: 'content1', + hidden: true, + type: 'content', + key: 'content', + html: 'Please enter you favorite animal.', + }, + + // Case show unless "cat" + { + conditional: { + eq: 'cat', + show: false, + when: 'favoriteAnimal', + }, + id: 'content2', + hidden: false, + type: 'content', + key: 'content', + html: 'Have you tried "cat"?', + }, + + // Case show unless "dog" + { + conditional: { + eq: 'dog', + show: false, + when: 'favoriteAnimal', + }, + id: 'content3', + hidden: false, + type: 'content', + key: 'content', + html: 'Have you tried "dog"?', + }, + ], }, initialValues: { favoriteAnimal: '', @@ -177,9 +253,7 @@ renderFormWithConditionalLogic.args = { }; renderFormWithConditionalLogic.play = async ({canvasElement}) => { const canvas = within(canvasElement); - const input = canvas.getByLabelText('Favorite animal', { - selector: 'input', - }); + const input = canvas.getByLabelText('Favorite animal'); expect( await canvas.queryByText('Please motivate why "cat" is your favorite animal...') ).toBeNull();