lodash#noop JavaScript Examples

The following examples show how to use lodash#noop. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.js    From holo-schedule with MIT License 6 votes vote down vote up
notification = {
  async create(stringId, { title, message, iconUrl, onClick = noop }) {
    await browser.notifications.create(stringId, {
      type: 'basic',
      title,
      message,
      // Icon directly from the web will not be loaded successfully in Chrome
      // So we load it manually
      iconUrl: await fetch(iconUrl)
        .then(response => response.blob())
        .then(blob => new Promise((resolve, reject) => {
          const reader = new FileReader()
          reader.onloadend = () => resolve(reader.result)
          reader.onerror = reject
          reader.readAsDataURL(blob)
        }))
        .catch(err => {
          console.log('[background/notification]Failed to download icon', err.message)
          return browser.runtime.getURL('assets/default_avatar.png')
        }),
    })
    console.log('[background/notification]Successfully created a notification')

    const handleClicked = notificationId => {
      if (notificationId !== stringId) return

      onClick()

      browser.notifications.onClicked.removeListener(handleClicked)
    }

    // This callback will not be fired properly in Chrome
    // See: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Chrome_incompatibilities#notifications
    browser.notifications.onClicked.addListener(handleClicked)
  },
}
Example #2
Source File: common.js    From jafar with MIT License 6 votes vote down vote up
resources = {
  components: {
    inputText: { 
      renderer: noop,
      stateChange: (props) => {
        if (props.value === 'Monica') {
          return { ...props.state, mock: 1 };
        }
        if (props.state.loading) {
          return { ...props.state, loading: false };
        }
      },
    },
  },
  conversions: {
    addPrefix: { func: props => `${props.args.prefix}${props.value}` },
    removePrefix: { func: props => props.value.replace(props.args.prefix, '') }, // remove 'Formatted ' prefix
  },
  dependenciesChanges: {
    lastNameDependenciesChange: { func: noop },
  },
  validators: {
    uniqueName: {
      func: () => true,
      message: noop,
    },
  },
  terms: {
    myCustomTerm: { func: noop },
  },
  hooks: {
    beforeAction: noop,
    afterAction: noop,
    afterChangeData: () => {}, // don't convert to noop
  },
}
Example #3
Source File: index.js    From hzero-front with Apache License 2.0 6 votes vote down vote up
@Bind()
  handleOkBtnClick() {
    const { onOk, afterOk } = this.dealProps;
    let afterOkCallback = afterOk;
    if (!isFunction(afterOk)) {
      afterOkCallback = noop;
    }
    if (isFunction(onOk)) {
      const onOkReturn = onOk();
      if (onOkReturn === false) {
        return false;
      }
      if (isPromise(onOkReturn)) {
        onOkReturn.then(
          () => {
            this.setState({ visible: false });
            afterOkCallback();
          },
          () => {
            this.setState({ visible: true });
          }
        );
      } else {
        this.setState({ visible: false });
        afterOkCallback();
      }
    } else {
      this.setState({ visible: false });
      afterOkCallback();
    }
  }
Example #4
Source File: dropdown.js    From horondi_client_fe with MIT License 6 votes vote down vote up
Dropdown.defaultProps = {
  mappedItems: [],
  handler: noop,
  defaultValue: 0,
  value: 0,
  fromSideBar: false,
  styles: {
    rootItem: '',
    rootSelect: ''
  }
};
Example #5
Source File: dialog-window.reducer.test.js    From horondi_admin with MIT License 6 votes vote down vote up
describe('dialog window reducer tests', () => {
  it('should return default state', () => {
    expect(dialogWindowReducer(initialState)).toEqual(initialState);
  });
  it('should set isOpen to true', () => {
    expect(
      dialogWindowReducer(initialState, showDialog({ isOpen: true }))
    ).toEqual({
      ...initialState,
      isOpen: true
    });
  });
  it('should set isOpen to false', () => {
    expect(dialogWindowReducer(initialState, closeDialog())).toEqual({
      ...initialState,
      isOpen: false
    });
  });
  it('should be defined', () => {
    expect(initialState.dialogTitle).toBeDefined();
    expect(initialState.dialogContent).toBeDefined();
    expect(initialState.onClickHandler).toEqual(noop);
    expect(initialState.onClickHandler).not.toBeNull();
  });
});
Example #6
Source File: SettingsComponentBuilder.js    From ui-data-export with Apache License 2.0 6 votes vote down vote up
SettingsComponentBuilder = ({
  children,
  sendCallout = noop,
}) => {
  return (
    <Router>
      <OverlayContainer />
      <Paneset>
        <CalloutContext.Provider value={{ sendCallout }}>
          {children}
        </CalloutContext.Provider>
      </Paneset>
    </Router>
  );
}
Example #7
Source File: listen.js    From holo-schedule with MIT License 6 votes vote down vote up
listen = (name, { onConnect = noop, onMessage = noop } = {}) => {
  portByName[name] = {
    onConnect,
    onMessage,
    postMessage: message => {
      ports.forEach(port => {
        port.postMessage({ isResponse: false, name, message })
      })
    },
  }
  return portByName[name]
}
Example #8
Source File: EditJobProfileRoute.test.js    From ui-data-export with Apache License 2.0 6 votes vote down vote up
setupEditJobProfileRoute = ({
  matchParams = {},
  location = {
    search: '?location',
  },
} = {}) => {
  renderWithIntl(
    <SettingsComponentBuilder>
      <EditJobProfileRoute
        mutator={{ jobProfile: { PUT: noop } }}
        history={history}
        resources={{
          mappingProfiles: {
            hasLoaded: true,
            records: [],
          },
        }}
        location={location}
        match={{ params: matchParams }}
        onSubmit={noop}
        onCancel={noop}
      />
    </SettingsComponentBuilder>,
    translationsProperties
  );
}
Example #9
Source File: create.js    From holo-schedule with MIT License 6 votes vote down vote up
create = (name, { onMessage = noop } = {}) => {
  portByName[name] = {
    name,
    postMessage: message => new Promise((res, rej) => {
      const id = uniqueId()
      setPendingMessage(id, { res, rej, name, message })
      if (!port) {
        connectPort()
      }
      port.postMessage({ isResponse: false, id, name, message })
    }),
    onMessage,
  }
  return portByName[name]
}
Example #10
Source File: CreateJobProfileRoute.test.js    From ui-data-export with Apache License 2.0 6 votes vote down vote up
setupCreateJobProfileRoute = ({
  matchParams = {},
  location = {
    search: '?location',
  },
} = {}) => {
  renderWithIntl(
    <SettingsComponentBuilder>
      <CreateJobProfileRoute
        mutator={{ jobProfile: { POST: noop } }}
        history={history}
        resources={{
          mappingProfiles: {
            hasLoaded: true,
            records: [],
          },
        }}
        location={location}
        match={{ params: matchParams }}
        onSubmit={noop}
        onCancel={noop}
      />
    </SettingsComponentBuilder>,
    translationsProperties
  );
}
Example #11
Source File: product-materials-container.js    From horondi_admin with MIT License 5 votes vote down vote up
ProductMaterialsContainer.defaultProps = {
  toggleFieldsChanged: noop
};
Example #12
Source File: node-details.js    From ThreatMapper with Apache License 2.0 5 votes vote down vote up
NodeDetails.defaultProps = {
  renderNodeDetailsExtras: noop,
};
Example #13
Source File: routes.js    From horondi_admin with MIT License 5 votes vote down vote up
Routes.defaultProps = {
  validatorMethods: PropTypes.shape({
    deleteValidation: noop,
    toggleRerender: noop
  })
};
Example #14
Source File: mockReactI18next.js    From covid19_scenarios with MIT License 5 votes vote down vote up
initReactI18next = {
  type: '3rdParty',
  init: noop,
}
Example #15
Source File: store.js    From holo-schedule with MIT License 5 votes vote down vote up
createStore = () => {
  const data = {}
  const dataToSync = {}

  const callbacks = []
  console.log('[background/store]listening to store')
  const port = listen('store', {
    onConnect: $port => Object.entries(data).forEach(([key, value]) => {
      $port.postMessage({ key, value })
    }),
  })

  const uploadToSync = async () => {
    if (data[SHOULD_SYNC_SETTINGS]) {
      await storage.sync.set({ store: dataToSync })
    }
  }

  const set = async (obj, { local = true, sync = false } = { local: true, sync: false }) => {
    Object.entries(obj).forEach(([key, value]) => {
      console.log(`[background/store]${key} has been stored/updated successfully.`)

      const oldValue = data[key]
      data[key] = value

      callbacks.forEach(callback => callback(key, value, oldValue))

      port.postMessage({ key, value })
    })
    if (local) {
      await withLock(async () => storage.local.set({ store: { ...await getStorage('local'), ...obj } }))
    }
    if (sync) {
      Object.assign(dataToSync, obj)

      await uploadToSync()
    }
  }

  const downloadFromSync = async () => {
    Object.assign(dataToSync, await getStorage('sync'))
    if (data[SHOULD_SYNC_SETTINGS]) {
      await set(dataToSync, { local: false })
    }
  }

  return {
    data,
    dataToSync,
    callbacks,
    downloadFromSync,
    uploadToSync,
    set,
    async init() {
      await set(await getStorage('local'), { local: false })

      await downloadFromSync()

      this.subscribe(SHOULD_SYNC_SETTINGS, async () => {
        await uploadToSync()
      })
    },
    get(key) {
      return cloneDeep(data[key])
    },
    subscribe(key = '', callback = noop) {
      callbacks.push(($key, ...args) => {
        if (key === $key) {
          callback(...args)
        }
      })
    },
  }
}
Example #16
Source File: anatomyFieldHelpers.jsx    From covid19-testing with Apache License 2.0 5 votes vote down vote up
export function withManagedFocus() {
  return (Component) =>
    class extends React.PureComponent {
      static displayName = getDisplayName(Component, 'withManagedFocus');

      static propTypes = {
        /**
         * This function will be called with a reference to this instance. This is useful for programmatically
         * setting the underlying DOM element's focus (the element that is passed to inputRef).
         */
        focusRef: PropTypes.func,
        inputRef: PropTypes.func,
        onBlur: PropTypes.func,
        onFocus: PropTypes.func,
      };

      static defaultProps = {
        focusRef: noop,
        inputRef: noop,
        onBlur: noop,
        onFocus: noop,
      };

      state = {
        isFocused: false,
      };

      componentDidMount() {
        this.props.focusRef(this);
      }

      /**
       * Parents (or children) of this component can use this method in conjunction with the ref passed to focusRef
       * to set the browser focus to whatever element is passed to inputRef (via the children of this component).
       *
       * Note that this will automatically trigger onFocus as well via normal DOM behavior.
       */
      focus = () => {
        this.inputRef.focus();
      };

      onBlur = (event) => {
        this.setState({
          isFocused: false,
        });

        this.props.onBlur(event);
      };

      onFocus = (event) => {
        this.setState({
          isFocused: true,
        });

        this.props.onFocus(event);
      };

      setInputRef = (ref) => {
        this.inputRef = ref;
        this.props.inputRef(ref);
      };

      render() {
        const {focusRef, ...rest} = this.props;
        return (
          <Component
            {...this.state}
            {...rest}
            focus={this.focus}
            inputRef={this.setInputRef}
            onBlur={this.onBlur}
            onFocus={this.onFocus}
          />
        );
      }
    };
}
Example #17
Source File: Form.js    From jafar with MIT License 5 votes vote down vote up
init(model, resources, settings, onUpdateForm = noop) {
    this[internal] = { model, resources, settings };
    this.onUpdateForm = onUpdateForm;
    return safe.call(this, () => exec.call(this, Actions.INIT, [model, resources, settings]));
  }
Example #18
Source File: customized-delete-icon.js    From horondi_admin with MIT License 5 votes vote down vote up
CustomizedDeleteIcon.defaultProps = {
  onClickHandler: noop
};
Example #19
Source File: JobProfilesForm.js    From ui-data-export with Apache License 2.0 5 votes vote down vote up
JobProfilesForm = props => {
  const {
    onCancel = noop,
    hasLoaded = false,
    mappingProfiles = [],
    pristine,
    submitting,
    handleSubmit,
    paneTitle,
    metadata,
    headLine,
  } = props;

  const intl = useIntl();

  return (
    <Layer
      isOpen
      contentLabel={intl.formatMessage({ id: 'ui-data-export.mappingProfiles.newProfile' })}
    >
      <FullScreenForm
        id="job-profiles-form"
        noValidate
        paneTitle={paneTitle}
        isSubmitButtonDisabled={pristine || submitting}
        onSubmit={handleSubmit}
        onCancel={onCancel}
      >
        {!hasLoaded && <Preloader />}
        {hasLoaded && (
        <div className={css.jobProfilesFormContent}>
          {headLine}
          <div>{metadata}</div>
          <div data-test-job-profile-form-name>
            <Field
              label={<FormattedMessage id="stripes-data-transfer-components.name" />}
              name="name"
              id="job-profile-name"
              component={TextField}
              fullWidth
              required
            />
          </div>
          <div data-test-job-profile-form-mapping-profile>
            <Field
              label={<FormattedMessage id="ui-data-export.mappingProfile" />}
              name="mappingProfileId"
              id="mapping-profile-id"
              component={Select}
              dataOptions={mappingProfiles}
              placeholder={intl.formatMessage({ id: 'ui-data-export.mappingProfiles.selectProfile' })}
              fullWidth
              required
            />
          </div>
          <div data-test-job-profile-description>
            <Field
              label={<FormattedMessage id="ui-data-export.description" />}
              name="description"
              id="job-profile-description"
              component={TextArea}
              fullWidth
            />
          </div>
        </div>
        )}
      </FullScreenForm>
    </Layer>
  );
}
Example #20
Source File: delete-button.js    From horondi_admin with MIT License 5 votes vote down vote up
DeleteButton.defaultProps = {
  size: 'medium',
  onClickHandler: noop
};
Example #21
Source File: JobProfileDetails.test.js    From ui-data-export with Apache License 2.0 4 votes vote down vote up
describe('JobProfileDetails', () => {
  const stripes = {
    connect: Component => props => (
      <Component
        {... props}
        mutator={{}}
        resources={{}}
      />
    ),
  };
  const renderJobProfileDetails = () => {
    renderWithIntl(
      <SettingsComponentBuilder>
        <JobProfileDetails
          stripes={stripes}
          jobProfile={jobProfile}
          mappingProfile={mappingProfile}
          isDefaultProfile
          isProfileUsed
          onCancel={noop}
          onDelete={noop}
        />
      </SettingsComponentBuilder>,
      translationsProperties
    );
  };

  it('should display job profile details', () => {
    renderJobProfileDetails();

    const dialog = screen.getByRole('dialog');

    expect(dialog).toBeVisible();

    const headings = within(dialog).getAllByRole('heading', { name: jobProfile.name });

    headings.forEach(heading => expect(heading).toBeVisible());

    const summary = within(dialog).getByRole('region', { name: /summary/i });

    const labelsAndValues = [
      'Record created: 12/4/2018 11:22 AM',
      'Record last updated: 12/4/2018 1:28 PM',
      commonTranslations.name,
      jobProfile.name,
      translations.description,
      jobProfile.description,
      translations.mappingProfile,
      mappingProfile.name,
    ];

    labelsAndValues.forEach(el => expect(within(summary).getByText(el)).toBeVisible());
  });

  it('should display action buttons in the proper state', () => {
    renderJobProfileDetails();
    const actionButton = screen.getByText('Actions');

    userEvent.click(actionButton);

    const deleteButton = screen.getByText(commonTranslations.delete);
    const duplicateButton = screen.getByText(commonTranslations.duplicate);
    const editButton = screen.getByText(commonTranslations.edit);

    expect(deleteButton).toBeEnabled();
    expect(duplicateButton).toBeEnabled();
    expect(editButton).toBeEnabled();
  });

  describe('rendering details without description for a job profile which is not already in use', () => {
    const renderJobProfileWitoutDescription = () => {
      renderWithIntl(
        <SettingsComponentBuilder>
          <JobProfileDetails
            stripes={stripes}
            jobProfile={{
              ...jobProfile,
              description: null,
            }}
            mappingProfile={mappingProfile}
            isDefaultProfile={false}
            isProfileUsed={false}
            onCancel={noop}
          />
        </SettingsComponentBuilder>,
        translationsProperties
      );
    };

    it('should display no value in description', () => {
      renderJobProfileWitoutDescription();
      const description = document.querySelector('[data-test-job-profile-description]');

      expect(within(description).getByText('-')).toBeVisible();
    });

    it('should display action buttons in the proper state', () => {
      renderJobProfileWitoutDescription();
      const actionButton = screen.getByText('Actions');

      userEvent.click(actionButton);

      const deleteButton = screen.getByText(commonTranslations.delete);
      const duplicateButton = screen.getByText(commonTranslations.duplicate);
      const editButton = screen.getByText(commonTranslations.edit);

      expect(deleteButton).toBeEnabled();
      expect(duplicateButton).toBeEnabled();
      expect(editButton).toBeEnabled();
    });

    describe('clicking on delete profiles button', () => {
      it('should display delete confirmation modal', async () => {
        renderJobProfileWitoutDescription();
        const actionButton = screen.getByText('Actions');

        userEvent.click(actionButton);

        const deleteButton = screen.getByText(commonTranslations.delete);

        userEvent.click(deleteButton);

        const modal = screen.getAllByRole('dialog').find(dialog => within(dialog).getByRole('heading', { name: /delete/i }));

        expect(modal).toBeVisible();
        userEvent.click(within(modal).getByRole('button', { name: /cancel/i }));

        await waitForElementToBeRemoved(modal);
      });
    });
  });

  describe('rendering job profile details in loading state', () => {
    const renderJobProfileWithLoading = () => {
      renderWithIntl(
        <SettingsComponentBuilder>
          <JobProfileDetails
            stripes={stripes}
            isLoading
            isDefaultProfile={false}
            isProfileUsed
            onCancel={noop}
          />
        </SettingsComponentBuilder>,
        translationsProperties
      );
    };

    it('should display preloader', () => {
      renderJobProfileWithLoading();
      expect(document.querySelector('[data-test-preloader]')).toBeVisible();
    });
  });
});
Example #22
Source File: stepper-control-buttons.test.js    From horondi_admin with MIT License 4 votes vote down vote up
describe('Stepper control buttons tests', () => {
  const useSelectorMock = jest.spyOn(reactRedux, 'useSelector');
  const mockHandleNext = jest.fn(noop);
  const mockHandleBack = jest.fn(noop);
  const activeStep = stepsLabels.length - 1;
  let component;

  beforeEach(() => {
    useSelectorMock.mockImplementation((callback) => callback(mockStore));
    component = mount(
      <StepperControlButtons
        handleNext={mockHandleNext}
        handleBack={mockHandleBack}
        activeStep={activeStep}
        type={inputTypes.button}
      />
    );
  });

  afterEach(() => {
    component.unmount();
    mockHandleBack.mockClear();
    mockHandleNext.mockClear();
    useSelectorMock.mockClear();
  });

  it('Should render two buttons', () => {
    expect(component.find('button')).toHaveLength(2);
  });
  it('Should render the first button with BACK label', () => {
    const backBtn = component.find('button').first();
    expect(backBtn.text()).toBe(BACK);
  });
  it('Should render the second button with CREATE_PRODUCT label when activeStep equals stepsLabels.length - 1', () => {
    const btn = component.find('button').at(1);
    expect(btn.text()).toBe(CREATE_PRODUCT);
  });
  it('Should render the second button with NEXT label when activeStep does not equal stepsLabels.length - 1', () => {
    component.setProps({ activeStep: activeStep + 1 });
    const btn = component.find('button').at(1);
    expect(btn.text()).toBe(NEXT);
  });
  it('Should run handleBack when click on the first button', () => {
    if (activeStep === 0) {
      component.setProps({ activeStep: 5 });
    }
    component.find('button').first().simulate('click');
    expect(mockHandleBack).toHaveBeenCalledTimes(1);
  });
  it('Should run handleNext when click on the second button', () => {
    component.find('button').at(1).simulate('click');
    expect(mockHandleNext).toHaveBeenCalledTimes(1);
  });
  it('BACK button should be disabled when activeStep equals zero', () => {
    component.setProps({ activeStep: 0 });
    component.find('button').first().simulate('click');
    expect(mockHandleBack).toHaveBeenCalledTimes(0);
  });
  it('Should have default props', () => {
    expect(StepperControlButtons.defaultProps).toBeDefined();
    expect(StepperControlButtons.defaultProps.type).toBe('button');
  });
  it('Should have prop types', () => {
    expect(StepperControlButtons.propTypes.activeStep).toBe(
      PropTypes.number.isRequired
    );
    expect(StepperControlButtons.propTypes.handleNext).toBe(PropTypes.func);
    expect(StepperControlButtons.propTypes.handleBack).toBe(PropTypes.func);
    expect(StepperControlButtons.propTypes.type).toBe(PropTypes.string);
  });
  it('Should render CircularProgress when loading is true', () => {
    const circular = component.find('.MuiCircularProgress-svg');
    expect(circular).toHaveLength(1);
  });
  it('Should not render CircularProgress when loading is false', () => {
    mockStore.Products.loading = false;
    component = mount(
      <StepperControlButtons
        handleNext={mockHandleNext}
        handleBack={mockHandleBack}
        activeStep={activeStep}
        type={inputTypes.button}
      />
    );
    const circular = component.find('.MuiCircularProgress-svg');
    expect(circular).toHaveLength(0);
  });
});
Example #23
Source File: MappingProfilesTransformationsModal.js    From ui-data-export with Apache License 2.0 4 votes vote down vote up
MappingProfilesTransformationsModal = ({
  isOpen,
  initialTransformationsValues,
  initialSelectedTransformations = {},
  disabledRecordTypes = {},
  onCancel,
  onSubmit,
}) => {
  const [isFilterPaneOpen, setFilterPaneOpen] = useState(true);
  const [searchValue, setSearchValue] = useState(initialSearchFormValues.searchValue);
  const [searchFilters, setSearchFilters] = useState(initialSearchFormValues.filters);
  const [selectedTransformations, setSelectedTransformations] = useState(initialSelectedTransformations);
  const transformationsFormStateRef = useRef(null);
  const initialFormValues = useMemo(() => ({
    searchValue: initialSearchFormValues.searchValue,
    filters: searchFilters,
  }), [searchFilters]);
  const [validatedTransformations, setValidatedTransformations] = useState({});
  const [isSubmitButtonDisabled, setIsSubmitButtonDisabled] = useState(true);

  const resetSearchForm = useCallback(() => {
    setSearchFilters({
      ...initialSearchFormValues.filters,
      recordTypes: initialSearchFormValues.filters.recordTypes.filter(record => !disabledRecordTypes[record]),
    });
    setSearchValue(initialSearchFormValues.searchValue);
  }, [disabledRecordTypes]);

  useEffect(() => {
    if (!isOpen) {
      setFilterPaneOpen(true);
      resetSearchForm();
    }
  }, [isOpen, resetSearchForm]);

  const searchValueResults = !searchValue
    ? initialTransformationsValues.transformations
    : initialTransformationsValues.transformations
      .filter(value => value.displayName.toLowerCase().includes(searchValue));

  const searchResults = [];
  let displayedCheckedItemsAmount = 0;

  const filterMap = {
    recordTypes: (filters, transformation) => filters.includes(transformation.recordType),
    statuses: (filters, transformation) => filters.some(status => statusesFilterMap[status](transformation, selectedTransformations)),
  };

  searchValueResults.forEach(transformation => {
    const hasFilterMatch = Object.keys(filterMap)
      .every(filterKey => {
        const filter = get(searchFilters, filterKey, []);

        return filterMap[filterKey](filter, transformation);
      });

    if (hasFilterMatch) {
      searchResults.push(transformation);

      if (selectedTransformations[transformation.fieldId]) {
        displayedCheckedItemsAmount++;
      }
    }
  });

  const toggleFilterPane = useCallback(() => setFilterPaneOpen(curState => !curState), []);

  const handleFiltersChange = useCallback((key, value) => setSearchFilters(curFilters => ({
    ...curFilters,
    [key]: value,
  })), []);

  const handleSelectChange = useCallback(
    transformations => {
      if (isSubmitButtonDisabled) {
        setIsSubmitButtonDisabled(false);
      }

      setSelectedTransformations(transformations);
    }, [isSubmitButtonDisabled]
  );

  const handleSearchFormSubmit = useCallback(values => {
    setSearchValue(values.searchValue?.toLowerCase());
  }, []);

  const handleCancel = () => {
    setValidatedTransformations({});
    setIsSubmitButtonDisabled(false);
    setSelectedTransformations(initialSelectedTransformations);

    onCancel();
  };

  const handleSaveButtonClick = () => {
    const transformations = get(transformationsFormStateRef.current.getState(), 'values.transformations', []);
    const invalidTransformations = validateTransformations(transformations);
    const isTransformationFormValid = isEmpty(invalidTransformations);

    if (isTransformationFormValid) {
      const normalizedTransformations = normalizeTransformationFormValues(transformations);

      setValidatedTransformations({});
      setIsSubmitButtonDisabled(false);

      onSubmit(normalizedTransformations);
    } else {
      setValidatedTransformations(invalidTransformations);
      setIsSubmitButtonDisabled(true);
    }
  };

  const renderFooter = () => {
    return (
      <div className={css.modalFooter}>
        <Button
          data-test-transformations-cancel
          className="left"
          marginBottom0
          onClick={handleCancel}
        >
          <FormattedMessage id="stripes-components.cancel" />
        </Button>
        <div data-test-transformations-total-selected>
          <FormattedMessage
            id="ui-data-export.modal.totalSelected"
            values={{ count: Object.keys(selectedTransformations).length }}
          />
        </div>
        <Button
          data-test-transformations-save
          buttonStyle="primary"
          disabled={isSubmitButtonDisabled}
          marginBottom0
          onClick={handleSaveButtonClick}
        >
          <FormattedMessage id="stripes-components.saveAndClose" />
        </Button>
      </div>
    );
  };

  return (
    <Modal
      data-test-transformations-modal
      contentClass={css.modalContent}
      label={<FormattedMessage id="ui-data-export.mappingProfiles.transformations.selectTransformations" />}
      footer={renderFooter()}
      open={isOpen}
      dismissible
      enforceFocus={false}
      size="large"
      onClose={handleCancel}
    >
      <Paneset>
        <Pane
          data-test-transformations-search-pane
          defaultWidth="30%"
          paneTitle={<FormattedMessage id="ui-data-export.searchAndFilter" />}
          lastMenu={<CollapseFilterPaneButton onClick={toggleFilterPane} />}
          hidden={!isFilterPaneOpen}
        >
          <SearchForm
            initialValues={initialFormValues}
            disabledRecordTypes={disabledRecordTypes}
            onFiltersChange={handleFiltersChange}
            onReset={resetSearchForm}
            onSubmit={handleSearchFormSubmit}
          />
        </Pane>
        <Pane
          data-test-transformations-results-pane
          defaultWidth="fill"
          hasPadding={false}
          paneTitle={<FormattedMessage id="ui-data-export.transformations" />}
          paneSub={(
            <FormattedMessage
              id="ui-data-export.mappingProfiles.transformations.searchResultsCountHeader"
              values={{ count: searchResults.length }}
            />
          )}
          firstMenu={!isFilterPaneOpen ? <ExpandFilterPaneButton onClick={toggleFilterPane} /> : null}
          {...(!isFilterPaneOpen && fullWidthStyle)}
        >
          <TransformationsForm
            id="transformations-form"
            stateRef={transformationsFormStateRef}
            initialValues={initialTransformationsValues}
            searchResults={searchResults}
            validatedTransformations={validatedTransformations}
            isSelectAllChecked={displayedCheckedItemsAmount === searchResults.length}
            setValidatedTransformations={setValidatedTransformations}
            setIsSubmitButtonDisabled={setIsSubmitButtonDisabled}
            onSelectChange={handleSelectChange}
            onSubmit={noop}
          />
        </Pane>
      </Paneset>
    </Modal>
  );
}
Example #24
Source File: Form.spec.js    From jafar with MIT License 4 votes vote down vote up
describe('Form', () => {
  let simpleForm;
  let commonForm;
  let dependencyChangeForm;
  let dependencyChangeWithParserFormatterForm;
  let syncValidatorForm;
  let asyncValidatorForm;
  let stateChangesForm;
  let processQueueForm;
  let dependenciesForm;

  beforeEach(() => {
    simpleForm = cloneDeep(simpleFormMock);
    commonForm = cloneDeep(commonFormMock);
    dependencyChangeForm = cloneDeep(dependencyChangeFormMock);
    dependencyChangeWithParserFormatterForm = cloneDeep(dependencyChangeWithParserFormatterFormMock);
    syncValidatorForm = cloneDeep(syncValidatorFormMock);
    asyncValidatorForm = cloneDeep(asyncValidatorFormMock);
    stateChangesForm = cloneDeep(stateChangesFormMock);
    processQueueForm = cloneDeep(processQueueFormMock);
    dependenciesForm = cloneDeep(dependenciesMock);
  });

  describe('create form', () => {
    it('create few forms without id', async () => {
      let form = new Form();
      simpleForm.model.id = undefined;
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.id).toEqual('form-1');

      form = new Form();
      delete simpleForm.model.id;
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.id).toEqual('form-2');

      form = new Form();
      simpleForm.model.id = 'some-id';
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.id).toEqual('some-id');

      form = new Form();
      delete simpleForm.model.id;
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.id).toEqual('form-3');
    });

    it('init with toDto', async () => {
      simpleForm.model.data = { a: 1 };
      const expectedData = { a: 2 };
      simpleForm.resources.hooks = {
        toDto: ({ data }) => ({ a: data.a + 1 }),
      };
      let publicForm;
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources, undefined, x => { publicForm = x; });
      expect(form.data).toEqual(expectedData);
      expect(publicForm.model.data).toEqual(expectedData);
      expect(publicForm.model.initializedData).toEqual(expectedData);
      expect(publicForm.model.initialModel.data).toEqual({ a: 1 });
    });

    it('init persistent form with toDto and reset', async () => {
      const serverData = { name: 1 }
      simpleForm.model.data = serverData;
      let expectedDataAfterFirstInit = { name: 2 };
      simpleForm.resources.hooks = {
        toDto: ({ data }) => ({ name: data.name + 1 }),
      };
      let publicForm;
      let form = new Form();
      await form.init(simpleForm.model, simpleForm.resources, undefined, x => { publicForm = x; });
      expect(form.data).toEqual(expectedDataAfterFirstInit);
      expect(publicForm.model.data).toEqual(expectedDataAfterFirstInit);
      expect(publicForm.model.initializedData).toEqual(expectedDataAfterFirstInit);
      expect(publicForm.model.initialModel.data).toEqual(serverData);

      const expectedData = { name: 3 };
      await form.changeValue('name', 3);
      expect(form.data).toEqual(expectedData);

      form = new Form();
      await form.init(publicForm.model, simpleForm.resources, undefined, x => { publicForm = x; });
      expect(form.data).toEqual(expectedData);
      expect(publicForm.model.data).toEqual(expectedData);
      expect(publicForm.model.initializedData).toEqual(expectedDataAfterFirstInit);
      expect(publicForm.model.initialModel.data).toEqual(serverData);

      await form.reset();
      expect(form.data).toEqual(expectedDataAfterFirstInit);
      expect(publicForm.model.data).toEqual(expectedDataAfterFirstInit);
      expect(publicForm.model.initializedData).toEqual(expectedDataAfterFirstInit);
      expect(publicForm.model.initialModel.data).toEqual(serverData);
    });
  });

  describe('change field value', () => {
    it('should update the form data on change field value', async () => {
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.data).toEqual({});
      await form.changeValue('name', 'Gal');
      expect(form.data).toEqual({
        name: 'Gal',
      });
    });

    it('using updater function - with component value - changed ok', async () => {
      const form = new Form();
      simpleForm.model.fields.name.component = {
        name: 'inputText',
        value: 1,
      };
      simpleForm.model.data.name = 1;
      simpleForm.resources.components = {
        inputText: { renderer: noop },
      };

      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.pendingActions).toEqual([]);
      const promise1 = form.changeValue('name', ({ value }) => (value + 1));
      const promise2 = form.changeValue('name', ({ value }) => (value + 1));
      await Promise.all([promise1, promise2]);
      expect(form.fields.name.component.value).toEqual(3);
      expect(form.data.name).toEqual(3);
      expect(form.pendingActions).toEqual([]);
    });

    it('using updater function - without a component definition - throws error', async () => {
      const form = new Form();
      delete simpleForm.model.fields.name.component;
      simpleForm.model.data.name = 1;
      let error;
      log.error  = (err) => { error = err; };
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.pendingActions).toEqual([]);
      await form.changeValue('name', ({ value }) => (value + 1));
      expect(error.code).toEqual(errors.CHANGE_VALUE_UPDATER_NOT_SUPPORTED.code);
      expect(form.fields.name.component).toEqual(undefined);
      expect(form.data.name).toEqual(1);
      expect(form.pendingActions).toEqual([]);
    });

    it('should resolve after dependency change', async () => {
      const form = new Form();
      await form.init(dependencyChangeForm.model, dependencyChangeForm.resources);
      await form.changeValue('country', 'Israel');
      expect(form.data).toEqual({
        country: 'Israel', city: 'Tel Aviv', address: 'Ben Yehuda', countryCode: 'IL',
      });
    });

    it('should remove field the form data on set empty field value', async () => {
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.data).toEqual({});
      await form.changeValue('name', 'Gal');
      expect(form.data).toEqual({
        name: 'Gal',
      });
      await form.changeValue('name', '');
      expect(form.data).toEqual({});
    });

    it('should handle dependencies change changes value - to a field with parser formatter', async () => {
      const form = new Form();
      await form.init(dependencyChangeWithParserFormatterForm.model, dependencyChangeWithParserFormatterForm.resources);
      expect(form.data).toEqual({});
      await form.changeValue('country', 'Israel');
      expect(form.data).toEqual({ country: 'IL', city: 'JRM' });
      expect(form.fields.country.component.value).toEqual('Israel');
      expect(form.fields.city.component.value).toEqual('Jerusalem');
      await form.changeValue('city', 'Tel Aviv');
      expect(form.data).toEqual({ country: 'IL', city: 'TLV' });
      expect(form.fields.country.component.value).toEqual('Israel');
      expect(form.fields.city.component.value).toEqual('Tel Aviv');
    });

    it('after dependenciesChange return both new value and state, stateChange uses new data value and view value of itself', async () => {
      const form = new Form();
      let values;
      dependencyChangeWithParserFormatterForm.model.fields.address = {
        path: 'address',
        dependencies: ['city'],
        dependenciesChange: ({ dependencies, prevDependencies }) => {
          if (prevDependencies && (dependencies.city.value !== prevDependencies.city.value)) {
            return {
              value: dependencies.city.value,
              state: { a: 1 },
            }
          }
        },
        component: { 
          name: 'address',
        },
        parser: ({ value }) => value === 'Tel Aviv' ? 'TLV' : 'JRM',
        formatter: ({ value }) => value === 'TLV' ? 'Tel Aviv' : 'Jerusalem',
      };
      dependencyChangeWithParserFormatterForm.resources.components.address = {
        renderer: () => {},
        stateChange: ({ value, componentValue }) => {
          if (values) { // only after init
            values.push(`${value} ${componentValue}`);
          }
        },
      };
      await form.init(dependencyChangeWithParserFormatterForm.model, dependencyChangeWithParserFormatterForm.resources);
      values = [];
      await form.changeValue('city', 'Tel Aviv');
      await form.changeValue('city', 'Jerusalem');
      expect(values).toEqual([
        'TLV Tel Aviv',
        'JRM Jerusalem',
      ]);
    });

    describe('mode cases - with boolean required', () => {
      it('field.validators = [‘uniqueName’], field.required = true, incoming value = empty', async () => {
        syncValidatorForm.model.fields.name.required = true;
        syncValidatorForm.resources.validators.uniqueName.func = jest.fn();

        const form = new Form();
        await form.init(syncValidatorForm.model, syncValidatorForm.resources);
        await form.changeValue('name', '');

        // verify value unset
        expect(form.data.name).toEqual(undefined);
        // verify required error
        expect(form.fields.name.errors).toEqual([{ name: 'required', message: 'Field required' }]);
        // verify flags
        expect(form.fields.name.dirty).toEqual(false);
        expect(form.fields.name.empty).toEqual(true);
        expect(form.fields.name.invalid).toEqual(true);
        expect(form.invalid).toEqual(true);
        expect(form.errors).toEqual({ name: form.fields.name.errors });
        // verify validators not called
        expect(syncValidatorForm.resources.validators.uniqueName.func).not.toHaveBeenCalled();
      });

      it('field.validators = [‘uniqueName’], field.required = false, incoming value = empty', async () => {
        syncValidatorForm.model.fields.name.required = false;
        syncValidatorForm.resources.validators.uniqueName.func = jest.fn();

        const form = new Form();
        await form.init(syncValidatorForm.model, syncValidatorForm.resources);
        await form.changeValue('name', '');

        // verify value unset
        expect(form.data.name).toEqual(undefined);
        // verify no error
        expect(form.fields.name.errors).toEqual([]);
        // verify flags
        expect(form.fields.name.dirty).toEqual(false);
        expect(form.fields.name.empty).toEqual(true);
        expect(form.fields.name.invalid).toEqual(false);
        expect(form.invalid).toEqual(false);
        expect(form.errors).toEqual({});
        // verify validators not called
        expect(syncValidatorForm.resources.validators.uniqueName.func).not.toHaveBeenCalled();
      });

      it('field.validators = [‘uniqueName’], field.required = true, incoming value = not empty - valid name', async () => {
        syncValidatorForm.model.fields.name.required = true;
        syncValidatorForm.resources.validators.uniqueName.func = jest.fn(() => true);

        const form = new Form();
        await form.init(syncValidatorForm.model, syncValidatorForm.resources);
        await form.changeValue('name', 'Rachel');

        // verify value unset
        expect(form.data.name).toEqual('Rachel');
        // verify no error
        expect(form.fields.name.errors).toEqual([]);
        // verify flags
        expect(form.fields.name.dirty).toEqual(true);
        expect(form.fields.name.empty).toEqual(false);
        expect(form.fields.name.invalid).toEqual(false);
        expect(form.invalid).toEqual(false);
        expect(form.errors).toEqual({});
        // verify validators not called
        expect(syncValidatorForm.resources.validators.uniqueName.func).toHaveBeenCalled();
      });

      it('field.validators = [‘uniqueName’], field.required = true, incoming value = not empty - not valid name', async () => {
        syncValidatorForm.model.fields.name.required = true;
        syncValidatorForm.resources.validators.uniqueName.func = jest.fn(() => false);

        const form = new Form();
        await form.init(syncValidatorForm.model, syncValidatorForm.resources);
        await form.changeValue('name', 'Rachel');

        // verify value unset
        expect(form.data.name).toEqual('Rachel');
        // verify no error
        expect(form.fields.name.errors).toEqual([{ name: 'uniqueName', message: 'Name should be unique' }]);
        // verify flags
        expect(form.fields.name.dirty).toEqual(true);
        expect(form.fields.name.empty).toEqual(false);
        expect(form.fields.name.invalid).toEqual(true);
        expect(form.invalid).toEqual(true);
        expect(form.errors).toEqual({ name: form.fields.name.errors });
        // verify validators not called
        expect(syncValidatorForm.resources.validators.uniqueName.func).toHaveBeenCalled();
      });

      it('field.validators = [‘uniqueName’], field.required = false, incoming value = not empty - valid name', async () => {
        syncValidatorForm.model.fields.name.required = false;
        syncValidatorForm.resources.validators.uniqueName.func = jest.fn(() => true);

        const form = new Form();
        await form.init(syncValidatorForm.model, syncValidatorForm.resources);
        await form.changeValue('name', 'Rachel');

        // verify value unset
        expect(form.data.name).toEqual('Rachel');
        // verify no error
        expect(form.fields.name.errors).toEqual([]);
        // verify flags
        expect(form.fields.name.dirty).toEqual(true);
        expect(form.fields.name.empty).toEqual(false);
        expect(form.fields.name.invalid).toEqual(false);
        expect(form.invalid).toEqual(false);
        expect(form.errors).toEqual({});
        // verify validators not called
        expect(syncValidatorForm.resources.validators.uniqueName.func).toHaveBeenCalled();
      });

      it('field.validators = [‘uniqueName’], field.required = false, incoming value = not empty - not valid name', async () => {
        syncValidatorForm.model.fields.name.required = false;
        syncValidatorForm.resources.validators.uniqueName.func = jest.fn(() => false);

        const form = new Form();
        await form.init(syncValidatorForm.model, syncValidatorForm.resources);
        await form.changeValue('name', 'Rachel');

        // verify value unset
        expect(form.data.name).toEqual('Rachel');
        // verify no error
        expect(form.fields.name.errors).toEqual([{ name: 'uniqueName', message: 'Name should be unique' }]);
        // verify flags
        expect(form.fields.name.dirty).toEqual(true);
        expect(form.fields.name.empty).toEqual(false);
        expect(form.fields.name.invalid).toEqual(true);
        expect(form.invalid).toEqual(true);
        expect(form.errors).toEqual({ name: form.fields.name.errors });
        // verify validators not called
        expect(syncValidatorForm.resources.validators.uniqueName.func).toHaveBeenCalled();
      });
    });

    describe('mode cases - with requireTerm', () => {
      it('field.validators = [‘uniqueName’], field.required = true, incoming value = empty', async () => {
        syncValidatorForm.model.fields.name.requireTerm = { name: 'isRequired' };
        syncValidatorForm.resources.terms = { isRequired: { func: jest.fn(() => true) } };
        syncValidatorForm.resources.validators.uniqueName.func = jest.fn();

        const form = new Form();
        await form.init(syncValidatorForm.model, syncValidatorForm.resources);
        await form.changeValue('name', '');

        // verify value unset
        expect(form.data.name).toEqual(undefined);
        // verify required error
        expect(form.fields.name.errors).toEqual([{ name: 'required', message: 'Field required' }]);
        // verify flags
        expect(form.fields.name.dirty).toEqual(false);
        expect(form.fields.name.empty).toEqual(true);
        expect(form.fields.name.invalid).toEqual(true);
        expect(form.invalid).toEqual(true);
        expect(form.errors).toEqual({ name: form.fields.name.errors });
        // verify validators not called
        expect(syncValidatorForm.resources.validators.uniqueName.func).not.toHaveBeenCalled();
      });

      it('field.validators = [‘uniqueName’], field.required = false, incoming value = empty', async () => {
        syncValidatorForm.model.fields.name.requireTerm = { name: 'isRequired' };
        syncValidatorForm.resources.terms = { isRequired: { func: jest.fn(() => false) } };
        syncValidatorForm.resources.validators.uniqueName.func = jest.fn();

        const form = new Form();
        await form.init(syncValidatorForm.model, syncValidatorForm.resources);
        await form.changeValue('name', '');

        // verify value unset
        expect(form.data.name).toEqual(undefined);
        // verify no error
        expect(form.fields.name.errors).toEqual([]);
        // verify flags
        expect(form.fields.name.dirty).toEqual(false);
        expect(form.fields.name.empty).toEqual(true);
        expect(form.fields.name.invalid).toEqual(false);
        expect(form.invalid).toEqual(false);
        expect(form.errors).toEqual({});
        // verify validators not called
        expect(syncValidatorForm.resources.validators.uniqueName.func).not.toHaveBeenCalled();
      });

      it('field.validators = [‘uniqueName’], field.required = true, incoming value = not empty - valid name', async () => {
        syncValidatorForm.model.fields.name.requireTerm = { name: 'isRequired' };
        syncValidatorForm.resources.terms = { isRequired: { func: jest.fn(() => true) } };
        syncValidatorForm.resources.validators.uniqueName.func = jest.fn(() => true);

        const form = new Form();
        await form.init(syncValidatorForm.model, syncValidatorForm.resources);
        await form.changeValue('name', 'Rachel');

        // verify value unset
        expect(form.data.name).toEqual('Rachel');
        // verify no error
        expect(form.fields.name.errors).toEqual([]);
        // verify flags
        expect(form.fields.name.dirty).toEqual(true);
        expect(form.fields.name.empty).toEqual(false);
        expect(form.fields.name.invalid).toEqual(false);
        expect(form.invalid).toEqual(false);
        expect(form.errors).toEqual({});
        // verify validators not called
        expect(syncValidatorForm.resources.validators.uniqueName.func).toHaveBeenCalled();
      });

      it('field.validators = [‘uniqueName’], field.required = true, incoming value = not empty - not valid name', async () => {
        syncValidatorForm.model.fields.name.requireTerm = { name: 'isRequired' };
        syncValidatorForm.resources.terms = { isRequired: { func: jest.fn(() => true) } };
        syncValidatorForm.resources.validators.uniqueName.func = jest.fn(() => false);

        const form = new Form();
        await form.init(syncValidatorForm.model, syncValidatorForm.resources);
        await form.changeValue('name', 'Rachel');

        // verify value unset
        expect(form.data.name).toEqual('Rachel');
        // verify no error
        expect(form.fields.name.errors).toEqual([{ name: 'uniqueName', message: 'Name should be unique' }]);
        // verify flags
        expect(form.fields.name.dirty).toEqual(true);
        expect(form.fields.name.empty).toEqual(false);
        expect(form.fields.name.invalid).toEqual(true);
        expect(form.invalid).toEqual(true);
        expect(form.errors).toEqual({ name: form.fields.name.errors });
        // verify validators not called
        expect(syncValidatorForm.resources.validators.uniqueName.func).toHaveBeenCalled();
      });

      it('field.validators = [‘uniqueName’], field.required = false, incoming value = not empty - valid name', async () => {
        syncValidatorForm.model.fields.name.requireTerm = { name: 'isRequired' };
        syncValidatorForm.resources.terms = { isRequired: { func: jest.fn(() => false) } };
        syncValidatorForm.resources.validators.uniqueName.func = jest.fn(() => true);

        const form = new Form();
        await form.init(syncValidatorForm.model, syncValidatorForm.resources);
        await form.changeValue('name', 'Rachel');

        // verify value unset
        expect(form.data.name).toEqual('Rachel');
        // verify no error
        expect(form.fields.name.errors).toEqual([]);
        // verify flags
        expect(form.fields.name.dirty).toEqual(true);
        expect(form.fields.name.empty).toEqual(false);
        expect(form.fields.name.invalid).toEqual(false);
        expect(form.invalid).toEqual(false);
        expect(form.errors).toEqual({});
        // verify validators not called
        expect(syncValidatorForm.resources.validators.uniqueName.func).toHaveBeenCalled();
      });

      it('field.validators = [‘uniqueName’], field.required = false, incoming value = not empty - not valid name', async () => {
        syncValidatorForm.model.fields.name.requireTerm = { name: 'isRequired' };
        syncValidatorForm.resources.terms = { isRequired: { func: jest.fn(() => false) } };
        syncValidatorForm.resources.validators.uniqueName.func = jest.fn(() => false);

        const form = new Form();
        await form.init(syncValidatorForm.model, syncValidatorForm.resources);
        await form.changeValue('name', 'Rachel');

        // verify value unset
        expect(form.data.name).toEqual('Rachel');
        // verify no error
        expect(form.fields.name.errors).toEqual([{ name: 'uniqueName', message: 'Name should be unique' }]);
        // verify flags
        expect(form.fields.name.dirty).toEqual(true);
        expect(form.fields.name.empty).toEqual(false);
        expect(form.fields.name.invalid).toEqual(true);
        expect(form.invalid).toEqual(true);
        expect(form.errors).toEqual({ name: form.fields.name.errors });
        // verify validators not called
        expect(syncValidatorForm.resources.validators.uniqueName.func).toHaveBeenCalled();
      });
    });

    describe('fields dependencies', () => {
      it('should trigger dependent field evaluation', async () => {
        const invalidError = [{ name: 'descriptionContainsSubject', message: 'Subject should be included in description' }];
        const requiredError = [{ name: 'required', message: 'Field required' }];
        const form = new Form();
        await form.init(dependenciesForm.model, dependenciesForm.resources);
        expect(form.invalid).toEqual(false);

        // change subject to 'a'
        await form.changeValue('subject', 'a');
        // verify data
        expect(form.data).toEqual({ subject: 'a' });
        // verify errors
        expect(form.fields.subject.errors).toEqual(invalidError);
        expect(form.fields.description.errors).toEqual(requiredError);
        // verify form validity
        expect(form.invalid).toEqual(true);

        // change description to 'b'
        await form.changeValue('description', 'b');
        // verify data
        expect(form.data).toEqual({ subject: 'a', description: 'b' });
        // verify errors
        expect(form.fields.subject.errors).toEqual(invalidError);
        expect(form.fields.description.errors).toEqual(invalidError);
        // verify form validity
        expect(form.invalid).toEqual(true);

        // change description to 'ba'
        await form.changeValue('description', 'ba');
        // verify data
        expect(form.data).toEqual({ subject: 'a', description: 'ba' });
        // verify errors
        expect(form.fields.subject.errors).toEqual([]);
        expect(form.fields.description.errors).toEqual([]);
        // verify form validity
        expect(form.invalid).toEqual(false);
      });
    });
  });

  describe('2 Form instances with the same id', () => {
    it('destroy first without await -> init second without await - both with same form definition (id in specific)', async () => {
        let uiForm1;
        let uiForm2;
        const updateUiFrom1 = (form) => { uiForm1 = form; };
        const updateUiFrom2 = (form) => { uiForm2 = form; };
        const form1 = new Form();
        await form1.init(commonForm.model, commonForm.resources, undefined, updateUiFrom1);
        const resolve1 = form1.destroy();
        const form2 = new Form();
        const resolve2 = form2.init(commonForm.model, commonForm.resources, undefined, updateUiFrom2);
        await Promise.all([resolve1, resolve2]);

        expect(uiForm1).toEqual(undefined);
        expect(uiForm2).toBeTruthy();
    });
  });

  describe('process queue', () => {
    describe('should handle queue of processes one after another, and not mixed together', () => {
      it('form class demonstration', async () => {
        const form = new Form();
        await form.init(processQueueForm.model, processQueueForm.resources);
        expect(form.data).toEqual({});
        expect(form.invalid).toBeFalsy();

        // form class case - working ok cause we are waiting
        await form.changeValue('name', 'Rachel');
        await form.changeValue('name', 'Monica');

        expect(form.data).toEqual({ name: 'Monica' });
        expect(form.invalid).toBeFalsy();
      });

      /* TODO: add process queue to fix bug. After adding the process queue - this test should ensure it
      This is the bug that fails the below test for now:
      1. set name Rachel
      2. set name Monica
      3. validate monica + update form invalid -> Form is valid
      4. validate rachel + update form invalid -> Form is invalid

      The correct order - using process queue - needs to be (the test will pass after adding process queue)
      1. set name Rachel
      2. validate rachel + update form invalid -> Form is invalid
      3. set name Monica
      4. validate monica + update form invalid -> Form is valid
      */
      it('react-form demonstration', async () => {
        const form = new Form();
        await form.init(processQueueForm.model, processQueueForm.resources);
        expect(form.data).toEqual({});
        expect(form.invalid).toBeFalsy();

        // react form case - fails (without having await one after another in the same code block -
        // can call action parallel from the ui)
        form.changeValue('name', 'Rachel');
        await wait(250); // wait for debounce to finish
        form.changeValue('name', 'Monica');
        await wait(1000); // wait for process to finish

        expect(form.data).toEqual({ name: 'Monica' });
        expect(form.invalid).toBeFalsy();
      });
    });
  });

  describe('destroy form', () => {
    it('form should throw an error when try to call action after destroy', async () => {
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.id).toEqual('simple');
      await form.destroy();
      // call action of the form
      let error;
      try {
        await form.changeValue('name', 'Tag');
      } catch (err) {
        error = err;
      }
      expect(error.code).toEqual(errors.ACCESS_DESTROYED_FORM.code);
    });

    it('form should throw an error when try to call getter after destroy', async () => {
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.id).toEqual('simple');
      await form.destroy();
      // try get property of the form
      let error;
      try {
        const { id } = form; // eslint-disable-line
      } catch (err) {
        error = err;
      }
      expect(error.code).toEqual(errors.ACCESS_DESTROYED_FORM.code);
    });

    it('ui case - destroy form without await and call form action after without await - form should not handle '
        + 'new actions after destroy', async () => {
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.id).toEqual('simple');
      // UI case: when editing component that called "onValueChange" on blur and right after click "save" that
      // redirect from the page and destroy the form
      // destroy is async so by the time its "addAction" to queue happens - the changeValue already fires
      // order: changeValue called (on blur) -> destroy called -> changeValue await 250ms for debounce -> 
      // destroy "addAction" called -> changeValue "addAction" called after 250ms that will fail cuz form is undefined
      // after destroy - so this function make sure it wont throw error when process queue is closed
      // we dont want to throw error in this case - cuz the component actually called onValueChange before the destroy
      // so only ignore
      const promise1 = form.changeValue('name', 'Tag');
      const promise2 = form.changeValue('name', 'Tag2');
      const promise3 = form.destroy();
      let error;
      try {
        await Promise.all([promise1, promise2, promise3]);
      } catch (err) {
        error = err;
      }
      expect(error).toBeFalsy();
    });
  });

  describe('state changes', () => {
    it('changed ok', async () => {
      const form = new Form();
      simpleForm.model.fields.name.component = {
        name: 'inputText',
        state: {},
      };
      simpleForm.resources.components = {
        inputText: { renderer: noop },
      };

      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.pendingActions).toEqual([]);
      await form.changeState('name', { mockState: 'mockState' });
      expect(form.fields.name.component.state).toEqual({ mockState: 'mockState' });
      expect(form.pendingActions).toEqual([]);
    });

    it('using updater function - changed ok', async () => {
      const form = new Form();
      simpleForm.model.fields.name.component = {
        name: 'inputText',
        state: {
          num: 1,
        },
      };
      simpleForm.resources.components = {
        inputText: { renderer: noop },
      };

      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.pendingActions).toEqual([]);
      const promise1 = form.changeState('name', ({ state }) => ({ num: state.num + 1 }));
      const promise2 = form.changeState('name', ({ state }) => ({ num: state.num + 1 }));
      await Promise.all([promise1, promise2]);
      expect(form.fields.name.component.state).toEqual({ num: 3 });
      expect(form.pendingActions).toEqual([]);
    });

    it('loop state changes - ok', async () => {
      const form = new Form();
      await form.init(stateChangesForm.model, stateChangesForm.resources);
      expect(form.pendingActions).toEqual([]);
      const afterInitState = {
        items: [{ label: 'Monica', value: 'MONICA' }, { label: 'Ross', value: 'ROSS' }],
        isLoading: false,
        search: {
          value: '',
        },
      };
      expect(form.fields.name.component.state).toEqual(afterInitState);
      const newState = Object.assign(afterInitState, {
        search: {
          value: 'm',
        },
      });
      await form.changeState('name', newState);
      const afterChangeState = {
        items: [{ label: 'Monica', value: 'MONICA' }],
        isLoading: false,
        search: {
          value: 'm',
        },
      };
      expect(form.fields.name.component.state).toEqual(afterChangeState);
      expect(form.pendingActions).toEqual([]);
    });
  });

  describe('Field change ui', () => {
    it('changed ok - only component', async () => {
      const component = { name: 'myInputText' };
      simpleForm.resources.components = {
        myInputText: {
          renderer: () => {},
        },
      };
      const expectedComponent = { 
        name: 'myInputText', 
        value: undefined,
        state: {},
        modelState: {},
        prevState: undefined,
        prevValue: undefined,
      };
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      await form.changeUi('name', { component });
      expect(form.fields.name.component).toEqual(expectedComponent);
      expect(form.fields.name.formatter).toEqual(undefined);
      expect(form.fields.name.parser).toEqual(undefined);
    });

    it('changed ok - with label, description ,component, formatter, parser', async () => {
      const label = 'New Label';
      const description = 'New Description';
      const component = { name: 'myInputText' };
      const formatter = { name: 'formatter1' };
      const parser = { name: 'parser1' };
      const expectedComponent = { 
        name: 'myInputText', 
        value: undefined,
        state: {},
        modelState: {},
        prevState: undefined,
        prevValue: undefined,
      };
      simpleForm.resources.components = {
        myInputText: {
          renderer: () => {},
        },
      };
      simpleForm.resources.conversions = {
        formatter1: { func: () => {} },
        parser1: { func: () => {} },
      };
      const ui =  { label, description, component, formatter, parser };
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      await form.changeUi('name', ui);
      expect(form.fields.name.label).toEqual(label);
      expect(form.fields.name.description).toEqual(description);
      expect(form.fields.name.component).toEqual(expectedComponent);
      expect(form.fields.name.formatter).toEqual(formatter);
      expect(form.fields.name.parser).toEqual(parser);
    });

    it('changed ok - with component, formatter and no parser', async () => {
      const component = { name: 'myInputText' };
      const expectedComponent = { 
        name: 'myInputText', 
        value: undefined,
        state: {},
        modelState: {},
        prevState: undefined,
        prevValue: undefined,
      };
      const formatter = { name: 'formatter1' };
      simpleForm.resources.components = {
        myInputText: {
          renderer: () => {},
        },
      };
      simpleForm.resources.conversions = {
        formatter1: { func: () => {} },
      };
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      await form.changeUi('name', { component, formatter });
      expect(form.fields.name.component).toEqual(expectedComponent);
      expect(form.fields.name.formatter).toEqual(formatter);
      expect(form.fields.name.parser).toEqual(undefined);
    });

    it('didn\'t change - throws missing component', async () => {
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      let error;
      log.error  = (err) => { error = err; };
      await form.changeUi('name', { component: { name: 'myInputText2' } });
      expect(error.code).toEqual(errors.ACTION_FAILED.code);
      expect(error.subError.code).toEqual(errors.MISSING_COMPONENT.code);
    });
  });

  describe('change data', () => {
    it('should update the form data to a new object and evaluates form', async () => {
      const form = new Form();
      await form.init(commonForm.model, commonForm.resources);
      expect(form.data).toEqual({ name: 'Rachel', lastName: 'Green' });
      const newData = { name: 'Monica', lastName: 'Geller' };
      await form.changeData(newData);
      expect(form.data).toEqual(newData);
      expect(form.fields.name.component.value).toEqual('Formatted Monica');
    });
  });

  describe('change context', () => {
    it('should update the form context to a new object and evaluate form', async () => {
      const form = new Form();
      commonForm.model.fields.lastName.excludeTerm = { name: 'ifUser222' };
      commonForm.resources.terms.ifUser222 = { func: ({context}) => context.userId === '222' };
      await form.init(commonForm.model, commonForm.resources);
      expect(form.context).toEqual({ userId: '123', companyId: '789' });
      expect(form.fields.lastName.excluded).toBeFalsy();
      const newContext = { userId: '222',  companyId: '333' };
      await form.changeContext(newContext);
      expect(form.context).toEqual(newContext);
      expect(form.fields.lastName.excluded).toBeTruthy();
    });
  });

  describe('submit', () => {
    it('should throw invalid submit when form is invalid', async () => {
      simpleForm.model.fields.name.required = true;
      simpleForm.model.data = {};
      let error;
      log.error = (err) => { error = err; };
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.invalid).toEqual(true);
      const success = await form.submit();
      // verify submit fail
      expect(success).toEqual(undefined);
      expect(error.code).toEqual(errors.ACTION_FAILED.code);
      expect(error.subError.code).toEqual(errors.INVALID_SUBMIT.code);
    });

    it('validate return errors', async () => {
      simpleForm.model.fields.excludedField = { path: 'excludedField', excludeTerm: { name: 'excludeMe' } };
      simpleForm.resources.terms = { excludeMe: { func: () => true } };
      // validate returns errors of:
      // existing fields with errors, un existing fields, excluded fields, undefined fieldId, fields with empty errors
      const errors = [{ name: 'unique', message: 'already exists' }];
      simpleForm.resources.hooks = {
        validate: jest.fn(() => ({ 
          name: errors,
          lastName: [],
          bla: errors,
          excludedField: errors,
          undefined: errors,
        })),
        submit: jest.fn(),
      };
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      const success = await form.submit();
      // verify submit fail
      expect(success).toEqual(undefined);
      // check validate called
      expect(simpleForm.resources.hooks.validate).toHaveBeenCalled();
      // check submit not called
      expect(simpleForm.resources.hooks.submit).not.toHaveBeenCalled();
      // check only existing non-excluded fields has errors and invalid now
      expect(form.fields.name.errors).toEqual(errors);
      expect(form.fields.name.invalid).toEqual(true);
      expect(form.fields.lastName.errors).toEqual([]);
      expect(form.fields.lastName.invalid).toEqual(false);
      expect(form.fields.excludedField.errors).toEqual([]);
      expect(form.fields.excludedField.invalid).toEqual(false);
      // verify also form errors and invalid 
      expect(form.errors).toEqual({ name: errors });
      expect(form.invalid).toEqual(true);
    });

    it('validate return errors - undefined', async () => {
      // validate returns errors undefined
      const data = { name: 'Rachel' };
      simpleForm.model.data = data;
      simpleForm.resources.hooks = {
        validate: jest.fn(() => undefined),
        submit: jest.fn(),
      };
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      const success = await form.submit();
      // verify submit success
      expect(success).toEqual(true);
      // check validate called
      expect(simpleForm.resources.hooks.validate).toHaveBeenCalled();
      // check submit not called
      expect(simpleForm.resources.hooks.submit).toHaveBeenCalledWith({ data, context: {} });
      // verify form errors = [] and invalid = false
      expect(form.errors).toEqual({});
      expect(form.invalid).toEqual(false);
    });

    it('validate return errors object - empty', async () => {
      // validate returns errors undefined
      const data = { name: 'Rachel' };
      simpleForm.model.data = data;
      simpleForm.resources.hooks = {
        validate: jest.fn(() => {}),
        submit: jest.fn(),
      };
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      const success = await form.submit();
      // verify submit success
      expect(success).toEqual(true);
      // check validate called
      expect(simpleForm.resources.hooks.validate).toHaveBeenCalled();
      // check submit not called
      expect(simpleForm.resources.hooks.submit).toHaveBeenCalledWith({ data, context: {} });
      // verify form errors = [] and invalid = false
      expect(form.errors).toEqual({});
      expect(form.invalid).toEqual(false);
    });

    it('validate return errors - not empty - but all errors are empty', async () => {
      const data = { name: 'Rachel' };
      simpleForm.model.data = data;
      simpleForm.resources.hooks = {
        validate: jest.fn(() => ({ name: [] })),
        submit: jest.fn(),
      };
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      const success = await form.submit();
      // verify submit success
      expect(success).toEqual(true);
      // check validate called
      expect(simpleForm.resources.hooks.validate).toHaveBeenCalled();
      // check submit not called
      expect(simpleForm.resources.hooks.submit).toHaveBeenCalledWith({ data, context: {} });
      // verify form errors = [] and invalid = false
      expect(form.errors).toEqual({});
      expect(form.invalid).toEqual(false);
    });

    it('should wait for sync submit', async () => {
      const form = new Form();
      const data = { name: 'rachel' };
      simpleForm.model.data = data;
      let resultData;
      simpleForm.resources.hooks = {
        submit: ({ data }) => { resultData = data; },
      };
      await form.init(simpleForm.model, simpleForm.resources);
      const success = await form.submit();
      expect(success).toEqual(true);
      expect(resultData).toEqual(data);
    });

    it('should wait for sync submit with fromDto', async () => {
      const form = new Form();
      const data = { a: 1 };
      simpleForm.model.data = data;
      let resultData;
      simpleForm.resources.hooks = {
        fromDto: ({ data }) => ({ a: data.a + 1 }),
        submit: ({ data }) => { resultData = data; },
      };
      await form.init(simpleForm.model, simpleForm.resources);
      const success = await form.submit();
      expect(success).toEqual(true);
      expect(resultData).toEqual({ a: 2 });
    });

    it('should wait for async submit', async () => {
      const form = new Form();
      const data = { name: 'rachel' };
      simpleForm.model.data = data;
      let resultData;
      simpleForm.resources.hooks = {
        submit: props => new Promise((resolve) => {
          setTimeout(() => {
            resultData = props.data;
            resolve();
          }, 1);
        }),
      };
      await form.init(simpleForm.model, simpleForm.resources);
      const success = await form.submit();
      expect(success).toEqual(true);
      expect(resultData).toEqual(data);
      // verify data was not deleted after submit from the form
      expect(form.data).toEqual(resultData);
    });
  });

  describe('reset', () => {
    it('resets to the initial form after form had changes', async () => {
      delete commonForm.model.initializedData;
      let publicForm;

      const form = new Form();
      await form.init(commonForm.model, commonForm.resources, (x) => { publicForm = x; });
      const formAfterFirstUnit = publicForm;

      // change field value
      expect(form.data).toEqual({ name: 'Rachel', lastName: 'Green' });
      await form.changeValue('lastName', 'Geller');
      expect(form.data).toEqual({ name: 'Rachel', lastName: 'Geller' });
      // change ui
      expect(form.fields.lastName.component).toBeFalsy();
      await form.changeUi('lastName', { component: { name: 'inputText' } });
      expect(form.fields.lastName.component).toBeTruthy();
      // change state
      expect(form.fields.name.component.state).toEqual({});
      await form.changeState('name', { test: 'mock-test' });
      expect(form.fields.name.component.state).toEqual({ test: 'mock-test' });
      // reset
      await form.reset();

      /* make sure that after init both forms:
      1. form that just passed init
      2. form that had init -> change stuff -> reset
      check that both result the same public form after the action */
      expect(publicForm).toEqual(formAfterFirstUnit);
    });

    it('reset persistent form - resets to the initial form after form had changes and refresh page (and went through init form)', async () => {
      delete commonForm.model.initializedData;
      let publicForm;
      let form = new Form();
      await form.init(commonForm.model, commonForm.resources, undefined, (x) => { publicForm = x; });
      const formAfterFirstUnit = publicForm;
      // change field value
      expect(form.data).toEqual({ name: 'Rachel', lastName: 'Green' });
      await form.changeValue('lastName', 'Geller');
      expect(form.data).toEqual({ name: 'Rachel', lastName: 'Geller' });
      // change ui
      expect(form.fields.lastName.component).toBeFalsy();
      await form.changeUi('lastName', { component: { name: 'inputText' } });
      expect(form.fields.lastName.component).toBeTruthy();
      // change state
      expect(form.fields.name.component.state).toEqual({});
      await form.changeState('name', { test: 'mock-test' });
      expect(form.fields.name.component.state).toEqual({ test: 'mock-test' });
      // mock refresh page - init the form with the persistent model
      const formBeforeRefresh = publicForm;
      form = new Form();
      await form.init(publicForm.model, commonForm.resources, undefined, (x) => { publicForm = x; });
      // verify persistent form loaded ok
      expect(publicForm.model).toEqual(formBeforeRefresh.model);
      // reset
      await form.reset();
      // verify form reset to the state it was after first init 
      expect(publicForm.model).toEqual(formAfterFirstUnit.model);
    });
  });

  describe('validators', () => {
    it('should make form valid on sync validator success', async () => {
      const form = new Form();
      await form.init(syncValidatorForm.model, syncValidatorForm.resources);
      expect(form.data).toEqual({});
      await form.changeValue('name', 'Moshe');
      expect(form.data).toEqual({
        name: 'Moshe',
      });
      expect(form.invalid).toBeFalsy();
    });
    
    it('should make form invalid on sync validator fails', async () => {
      const form = new Form();
      await form.init(syncValidatorForm.model, syncValidatorForm.resources);
      expect(form.data).toEqual({});

      // gal is unique name that is already taken - suppose to fail
      await form.changeValue('name', 'Gal');
      expect(form.data).toEqual({
        name: 'Gal',
      });
      expect(form.invalid).toBeTruthy();
      expect(form.fields.name.errors).toEqual([{
        name: 'uniqueName',
        message: 'Name should be unique',
      }]);
    });

    it('should make form invalid on async validator fails', async () => {
      const form = new Form();
      await form.init(asyncValidatorForm.model, asyncValidatorForm.resources);
      expect(form.data).toEqual({});

      // gal is unique name that is already taken - suppose to fail
      await form.changeValue('name', 'Gal');

      expect(form.data).toEqual({
        name: 'Gal',
      });
      expect(form.invalid).toBeTruthy();
      expect(form.fields.name.errors).toEqual([{
        name: 'uniqueName',
        message: 'Name should be unique',
      }]);
    });

    it('should make form invalid on async validator fails with debounce usage inside changeValue', async () => {
      const form = new Form();
      await form.init(asyncValidatorForm.model, asyncValidatorForm.resources);
      expect(form.data).toEqual({});

      // gal is unique name that is already taken - suppose to fail
      const change1 = form.changeValue('name', 'Gal');
      const change2 = form.changeValue('lastName', 'Havivi');
      await Promise.all([change1, change2]);

      expect(form.data).toEqual({
        name: 'Gal',
        lastName: 'Havivi',
      });
      expect(form.invalid).toBeTruthy();
    });

    it('field with exclude term and required=true, when its excluded - its should be keep required value', async () => {
      const form = new Form();
      simpleForm.model.data = { name: 'Custom' };
      simpleForm.model.fields.lastName.required = true;
      simpleForm.model.fields.lastName.dependencies = ['name'];
      simpleForm.model.fields.lastName.excludeTerm = {
        not: true,
        name: 'equals',
        args: { fieldId: 'name', value: 'Custom' }
      },
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.fields.lastName.excluded).toBeFalsy();
      expect(form.fields.lastName.required).toBeTruthy();
      expect(form.fields.lastName.empty).toBeTruthy();
      expect(form.fields.lastName.errors[0].name).toEqual('required');

      // change value - to exclude the field
      await form.changeValue('name', 'mock');
      expect(form.fields.lastName.excluded).toBeTruthy();
      expect(form.fields.lastName.required).toBeTruthy();
      expect(form.fields.lastName.empty).toBeFalsy();
      expect(form.fields.lastName.errors).toEqual([]);

      // change value - to include the field
      await form.changeValue('name', 'Custom');
      expect(form.fields.lastName.excluded).toBeFalsy();
      expect(form.fields.lastName.required).toBeTruthy();
      expect(form.fields.lastName.empty).toBeTruthy();
      expect(form.fields.lastName.errors[0].name).toEqual('required');
    });    
  });

  describe('dirty', () => {
    it('should make form dirty', async () => {
      const form = new Form();
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.data).toEqual({});
      expect(form.dirty).toBeFalsy();
      await form.changeValue('name', 'Moshe');
      expect(form.data).toEqual({
        name: 'Moshe',
      });
      expect(form.dirty).toBeTruthy();
    });

    it('should make form dirty and then not dirty', async () => {
      const form = new Form();
      simpleForm.model.data = { name: 'Assaf' };
      await form.init(simpleForm.model, simpleForm.resources);
      expect(form.dirty).toBeFalsy();
      expect(form.data).toEqual({ name: 'Assaf' });
      await form.changeValue('name', 'Moshe');
      expect(form.data).toEqual({
        name: 'Moshe',
      });
      expect(form.dirty).toBeTruthy();
      await form.changeValue('name', 'Assaf');
      expect(form.data).toEqual({
        name: 'Assaf',
      });
      expect(form.dirty).toBeFalsy();
    });
  });
});
Example #25
Source File: index.js    From strapi-molecules with MIT License 4 votes vote down vote up
PreviewProvider = ({
  children,
  initialData,
  isCreatingEntry,
  layout,
  modifiedData,
  slug,
  canUpdate,
  canCreate,
  getPreviewUrlParams = noop,
  getRequestUrl = getRequestUrlBackup,
}) => {
  const { formatMessage } = useIntl();

  const [showWarningClone, setWarningClone] = useState(false);
  const [showWarningPublish, setWarningPublish] = useState(false);
  const [isButtonLoading, setButtonLoading] = useState(false);

  // for strapi 3.5.x
  const isPreviewable = get(
    layout,
    'schema.options.previewable',
    get(layout, ['options', 'previewable'], false),
  );
  const isCloneable = get(
    layout,
    'schema.options.cloneable',
    get(layout, ['options', 'cloneable'], false),
  );

  const toggleWarningClone = () => setWarningClone((prevState) => !prevState);
  const toggleWarningPublish = () =>
    setWarningPublish((prevState) => !prevState);

  const didChangeData = useMemo(() => {
    return (
      !isEqual(initialData, modifiedData) ||
      (isCreatingEntry && !isEmpty(modifiedData))
    );
  }, [initialData, isCreatingEntry, modifiedData]);

  const { state, pathname } = useLocation();
  const { push } = useHistory();
  const redirect = (id) => {
    if (state && state.from) {
      push({
        pathname: `${state.from}/${id}`,
        state: { from: state.from },
      });
    } else {
      // for clone from create view
      const [oldId, ...tmpUrl] = pathname.split('/').reverse();
      const rootUrl = tmpUrl.reverse().join('/');
      push({
        pathname: `${rootUrl}/${id}`,
        state: { from: rootUrl },
      });
    }
  };

  const previewHeaderActions = useMemo(() => {
    const headerActions = [];

    if (!((isCreatingEntry && canCreate) || (!isCreatingEntry && canUpdate))) {
      return headerActions;
    }
    if (isPreviewable) {
      const params = getPreviewUrlParams(initialData, modifiedData, layout);
      headerActions.push({
        disabled: didChangeData,
        label: formatMessage({
          id: getPreviewPluginTrad('containers.Edit.preview'),
        }),
        color: 'secondary',
        onClick: async () => {
          try {
            const data = await request(
              `/preview/preview-url/${layout.apiID}/${initialData.id}`,
              {
                method: 'GET',
                params,
              },
            );
            if (data.url) {
              window.open(data.url, '_blank');
            } else {
              strapi.notification.error(
                getPreviewPluginTrad('error.previewUrl.notFound'),
              );
            }
          } catch (_e) {
            strapi.notification.error(
              getPreviewPluginTrad('error.previewUrl.notFound'),
            );
          }
        },
        type: 'button',
        style: {
          paddingLeft: 15,
          paddingRight: 15,
          fontWeight: 600,
        },
      });
    }
    if (isCloneable) {
      if (initialData.cloneOf) {
        headerActions.push({
          disabled: didChangeData,
          label: formatMessage({
            id: getPreviewPluginTrad('containers.Edit.publish'),
          }),
          color: 'primary',
          onClick: toggleWarningPublish,
          type: 'button',
          style: {
            paddingLeft: 15,
            paddingRight: 15,
            fontWeight: 600,
          },
        });
      } else {
        headerActions.push({
          disabled: didChangeData,
          label: formatMessage({
            id: getPreviewPluginTrad('containers.Edit.clone'),
          }),
          color: 'secondary',
          onClick: toggleWarningClone,
          type: 'button',
          style: {
            paddingLeft: 15,
            paddingRight: 15,
            fontWeight: 600,
          },
        });
      }
    }

    return headerActions;
  }, [
    didChangeData,
    formatMessage,
    layout.apiID,
    isPreviewable,
    initialData.cloneOf,
    initialData.id,
    canCreate,
    canUpdate,
    isCreatingEntry,
  ]);

  const handleConfirmPreviewClone = async () => {
    try {
      // Show the loading state
      setButtonLoading(true);
      const clonedPayload = await request(getRequestUrl(slug), {
        method: 'POST',
        body: {
          ...initialData,
          cloneOf: initialData.id,
        },
      });

      strapi.notification.success(getPreviewPluginTrad('success.record.clone'));

      redirect(clonedPayload.id);
    } catch (err) {
      const errorMessage = get(
        err,
        'response.payload.message',
        formatMessage({ id: getPreviewPluginTrad('error.record.clone') }),
      );
      strapi.notification.error(errorMessage);
    } finally {
      setButtonLoading(false);
      toggleWarningClone();
    }
  };
  const handleConfirmPreviewPublish = async () => {
    try {
      // Show the loading state
      setButtonLoading(true);

      let targetId = initialData.cloneOf.id;
      const urlPart = getRequestUrl(slug);
      const body = prepareToPublish({
        ...initialData,
        id: targetId,
        cloneOf: null,
      });

      await request(`${urlPart}/${targetId}`, {
        method: 'PUT',
        body,
      });
      await request(`${urlPart}/${initialData.id}`, {
        method: 'DELETE',
      });

      strapi.notification.success(
        getPreviewPluginTrad('success.record.publish'),
      );
      redirect(targetId);
    } catch (err) {
      const errorMessage = get(
        err,
        'response.payload.message',
        formatMessage({ id: getPreviewPluginTrad('error.record.publish') }),
      );
      strapi.notification.error(errorMessage);
    } finally {
      setButtonLoading(false);
      toggleWarningPublish();
    }
  };

  const value = {
    previewHeaderActions,
  };

  return (
    <>
      <PreviewContext.Provider value={value}>
        {children}
      </PreviewContext.Provider>
      {isCloneable && (
        <PopUpWarning
          isOpen={showWarningClone}
          toggleModal={toggleWarningClone}
          content={{
            message: getPreviewPluginTrad('popUpWarning.warning.clone'),
            secondMessage: getPreviewPluginTrad(
              'popUpWarning.warning.clone-question',
            ),
          }}
          popUpWarningType="info"
          onConfirm={handleConfirmPreviewClone}
          isConfirmButtonLoading={isButtonLoading}
        />
      )}
      {isCloneable && (
        <PopUpWarning
          isOpen={showWarningPublish}
          toggleModal={toggleWarningPublish}
          content={{
            message: getPreviewPluginTrad('popUpWarning.warning.publish'),
            secondMessage: getPreviewPluginTrad(
              'popUpWarning.warning.publish-question',
            ),
          }}
          popUpWarningType="info"
          onConfirm={handleConfirmPreviewPublish}
          isConfirmButtonLoading={isButtonLoading}
        />
      )}
    </>
  );
}