From 234c70c182a107143e86341386ea1b967be9a68c Mon Sep 17 00:00:00 2001 From: Adam Mahmood Date: Mon, 7 Jan 2019 08:52:26 +0000 Subject: [PATCH] Refactor answer validation to use a consistent pattern and utilise withEntityEditor --- .../authenticated/validation_spec.js | 8 +- .../Validation/{Date => }/AlignedColumn.js | 0 .../Design/Validation/AnswerValidation.js | 47 ++-- .../Validation/Date/DurationValidation.js | 99 --------- .../__snapshots__/DateValidation.test.js.snap | 160 ------------- .../DurationValidation.test.js.snap | 59 ----- .../Validation/{Date => }/DatePreview.js | 0 .../Validation/{Date => }/DatePreview.test.js | 2 +- .../Validation/{Date => }/DateValidation.js | 88 +++----- .../{Date => }/DateValidation.test.js | 154 ++++++------- .../Design/Validation/{Date => }/Duration.js | 1 + .../Validation/{Date => }/Duration.test.js | 2 +- .../Validation/{Date => }/DurationPreview.js | 0 .../{Date => }/DurationPreview.test.js | 2 +- .../Design/Validation/DurationValidation.js | 60 +++++ .../{Date => }/DurationValidation.test.js | 30 +-- .../Validation/{Date => }/EmphasisedText.js | 0 .../Design/Validation/MaxValue.js | 210 ------------------ .../Design/Validation/MaxValue.test.js | 161 -------------- .../Design/Validation/MaxValueValidation.js | 120 ++++++++++ .../Validation/MaxValueValidation.test.js | 107 +++++++++ .../Validation/MetadataContentPicker.js | 6 +- .../Design/Validation/MinValue.js | 133 ----------- .../Design/Validation/MinValue.test.js | 127 ----------- .../Design/Validation/MinValueValidation.js | 72 ++++++ .../Validation/MinValueValidation.test.js | 63 ++++++ .../Validation/PreviousAnswerContentPicker.js | 6 +- .../Design/Validation/Validation.js | 52 +++++ .../Design/Validation/Validation.test.js | 45 ++++ .../Design/Validation/ValidationPills.js | 109 ++++----- .../Design/Validation/ValidationPills.test.js | 16 +- .../__snapshots__/DatePreview.test.js.snap | 0 .../__snapshots__/DateValidation.test.js.snap | 146 ++++++++++++ .../__snapshots__/Duration.test.js.snap | 0 .../DurationPreview.test.js.snap | 0 .../DurationValidation.test.js.snap | 41 ++++ .../__snapshots__/MaxValue.test.js.snap | 60 ----- .../MaxValueValidation.test.js.snap | 28 +++ .../__snapshots__/MinValue.test.js.snap | 55 ----- .../MinValueValidation.test.js.snap | 36 +++ .../__snapshots__/Validation.test.js.snap | 25 +++ .../ValidationPills.test.js.snap | 4 +- .../withCustomNumberValueChange.test.js.snap | 9 + .../Design/Validation/{Date => }/builder.js | 19 +- .../Design/Validation/{Date => }/index.js | 50 +++-- .../{Date => }/readToWriteMapper.js | 19 ++ .../{Date => }/readToWriteMapper.test.js | 2 +- .../Validation/withCustomNumberValueChange.js | 47 ++++ .../withCustomNumberValueChange.test.js | 56 +++++ .../src/App/questionPage/Design/index.js | 2 +- eq-author/src/App/section/Design/index.js | 2 +- .../withChangeUpdate.test.js.snap | 9 + eq-author/src/enhancers/withChangeUpdate.js | 21 ++ .../src/enhancers/withChangeUpdate.test.js | 34 +++ eq-author/src/enhancers/withPropRemapped.js | 13 ++ .../src/enhancers/withPropRemapped.test.js | 22 ++ eq-author/src/enhancers/withPropRenamed.js | 10 + .../src/enhancers/withPropRenamed.test.js | 16 ++ eq-author/src/enhancers/withProps.js | 5 + eq-author/src/enhancers/withProps.test.js | 18 ++ eq-author/src/utils/enhancers.js | 25 --- eq-author/src/utils/enhancers.test.js | 45 ---- 62 files changed, 1347 insertions(+), 1411 deletions(-) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/AlignedColumn.js (100%) delete mode 100644 eq-author/src/App/questionPage/Design/Validation/Date/DurationValidation.js delete mode 100644 eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/DateValidation.test.js.snap delete mode 100644 eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/DurationValidation.test.js.snap rename eq-author/src/App/questionPage/Design/Validation/{Date => }/DatePreview.js (100%) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/DatePreview.test.js (97%) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/DateValidation.js (69%) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/DateValidation.test.js (50%) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/Duration.js (99%) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/Duration.test.js (94%) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/DurationPreview.js (100%) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/DurationPreview.test.js (87%) create mode 100644 eq-author/src/App/questionPage/Design/Validation/DurationValidation.js rename eq-author/src/App/questionPage/Design/Validation/{Date => }/DurationValidation.test.js (51%) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/EmphasisedText.js (100%) delete mode 100644 eq-author/src/App/questionPage/Design/Validation/MaxValue.js delete mode 100644 eq-author/src/App/questionPage/Design/Validation/MaxValue.test.js create mode 100644 eq-author/src/App/questionPage/Design/Validation/MaxValueValidation.js create mode 100644 eq-author/src/App/questionPage/Design/Validation/MaxValueValidation.test.js delete mode 100644 eq-author/src/App/questionPage/Design/Validation/MinValue.js delete mode 100644 eq-author/src/App/questionPage/Design/Validation/MinValue.test.js create mode 100644 eq-author/src/App/questionPage/Design/Validation/MinValueValidation.js create mode 100644 eq-author/src/App/questionPage/Design/Validation/MinValueValidation.test.js create mode 100644 eq-author/src/App/questionPage/Design/Validation/Validation.js create mode 100644 eq-author/src/App/questionPage/Design/Validation/Validation.test.js rename eq-author/src/App/questionPage/Design/Validation/{Date => }/__snapshots__/DatePreview.test.js.snap (100%) create mode 100644 eq-author/src/App/questionPage/Design/Validation/__snapshots__/DateValidation.test.js.snap rename eq-author/src/App/questionPage/Design/Validation/{Date => }/__snapshots__/Duration.test.js.snap (100%) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/__snapshots__/DurationPreview.test.js.snap (100%) create mode 100644 eq-author/src/App/questionPage/Design/Validation/__snapshots__/DurationValidation.test.js.snap delete mode 100644 eq-author/src/App/questionPage/Design/Validation/__snapshots__/MaxValue.test.js.snap create mode 100644 eq-author/src/App/questionPage/Design/Validation/__snapshots__/MaxValueValidation.test.js.snap delete mode 100644 eq-author/src/App/questionPage/Design/Validation/__snapshots__/MinValue.test.js.snap create mode 100644 eq-author/src/App/questionPage/Design/Validation/__snapshots__/MinValueValidation.test.js.snap create mode 100644 eq-author/src/App/questionPage/Design/Validation/__snapshots__/Validation.test.js.snap create mode 100644 eq-author/src/App/questionPage/Design/Validation/__snapshots__/withCustomNumberValueChange.test.js.snap rename eq-author/src/App/questionPage/Design/Validation/{Date => }/builder.js (51%) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/index.js (60%) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/readToWriteMapper.js (73%) rename eq-author/src/App/questionPage/Design/Validation/{Date => }/readToWriteMapper.test.js (99%) create mode 100644 eq-author/src/App/questionPage/Design/Validation/withCustomNumberValueChange.js create mode 100644 eq-author/src/App/questionPage/Design/Validation/withCustomNumberValueChange.test.js create mode 100644 eq-author/src/enhancers/__snapshots__/withChangeUpdate.test.js.snap create mode 100644 eq-author/src/enhancers/withChangeUpdate.js create mode 100644 eq-author/src/enhancers/withChangeUpdate.test.js create mode 100644 eq-author/src/enhancers/withPropRemapped.js create mode 100644 eq-author/src/enhancers/withPropRemapped.test.js create mode 100644 eq-author/src/enhancers/withPropRenamed.js create mode 100644 eq-author/src/enhancers/withPropRenamed.test.js create mode 100644 eq-author/src/enhancers/withProps.js create mode 100644 eq-author/src/enhancers/withProps.test.js delete mode 100644 eq-author/src/utils/enhancers.js delete mode 100644 eq-author/src/utils/enhancers.test.js diff --git a/eq-author/cypress/integration/authenticated/validation_spec.js b/eq-author/cypress/integration/authenticated/validation_spec.js index b47bb905d2..70c5e1f5ee 100644 --- a/eq-author/cypress/integration/authenticated/validation_spec.js +++ b/eq-author/cypress/integration/authenticated/validation_spec.js @@ -105,7 +105,7 @@ describe("Answer Validation", () => { }); it("Can toggle include/exclude", () => { toggleCheckboxOn("@minValueToggle"); - toggleCheckboxOn(testId("min-value-include")); + toggleCheckboxOn(testId("inclusive")); }); it("Can retain input value after on/off toggle", () => { toggleCheckboxOn("@minValueToggle"); @@ -141,7 +141,7 @@ describe("Answer Validation", () => { }); it("Can toggle include/exclude", () => { toggleCheckboxOn("@maxValueToggle"); - toggleCheckboxOn(testId("max-value-include")); + toggleCheckboxOn(testId("inclusive")); }); it("Can retain input value after on/off toggle", () => { toggleCheckboxOn("@maxValueToggle"); @@ -185,7 +185,7 @@ describe("Answer Validation", () => { }); it("Can toggle include/exclude", () => { toggleCheckboxOn("@minValueToggle"); - toggleCheckboxOn(testId("min-value-include")); + toggleCheckboxOn(testId("inclusive")); }); it("Can retain input value after on/off toggle", () => { toggleCheckboxOn("@minValueToggle"); @@ -221,7 +221,7 @@ describe("Answer Validation", () => { }); it("Can toggle include/exclude", () => { toggleCheckboxOn("@maxValueToggle"); - toggleCheckboxOn(testId("max-value-include")); + toggleCheckboxOn(testId("inclusive")); }); it("Can retain input value after on/off toggle", () => { toggleCheckboxOn("@maxValueToggle"); diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/AlignedColumn.js b/eq-author/src/App/questionPage/Design/Validation/AlignedColumn.js similarity index 100% rename from eq-author/src/App/questionPage/Design/Validation/Date/AlignedColumn.js rename to eq-author/src/App/questionPage/Design/Validation/AlignedColumn.js diff --git a/eq-author/src/App/questionPage/Design/Validation/AnswerValidation.js b/eq-author/src/App/questionPage/Design/Validation/AnswerValidation.js index 2aedf1bcb4..733d8beeb7 100644 --- a/eq-author/src/App/questionPage/Design/Validation/AnswerValidation.js +++ b/eq-author/src/App/questionPage/Design/Validation/AnswerValidation.js @@ -6,22 +6,25 @@ import PropTypes from "prop-types"; import { connect } from "react-redux"; import { gotoTab } from "redux/tabs/actions"; - -import SidebarButton, { Title, Detail } from "components/buttons/SidebarButton"; import ModalWithNav from "components/modals/ModalWithNav"; +import SidebarButton, { Title, Detail } from "components/buttons/SidebarButton"; + +import ValidationContext from "./ValidationContext"; +import DurationValidation from "./DurationValidation"; +import DateValidation from "./DateValidation"; +import MinValueValidation from "./MinValueValidation"; +import MaxValueValidation from "./MaxValueValidation"; +import DatePreview from "./DatePreview"; +import DurationPreview from "./DurationPreview"; -import MinValueValidation from "App/questionPage/Design/Validation/MinValue"; -import MaxValueValidation from "App/questionPage/Design/Validation/MaxValue"; import { EarliestDate, LatestDate, MinDuration, - MaxDuration -} from "App/questionPage/Design/Validation/Date"; - -import ValidationContext from "App/questionPage/Design/Validation/ValidationContext"; -import DatePreview from "App/questionPage/Design/Validation/Date/DatePreview"; -import DurationPreview from "App/questionPage/Design/Validation/Date/DurationPreview"; + MaxDuration, + MinValue, + MaxValue +} from "./"; import { CURRENCY, DATE, DATE_RANGE, NUMBER } from "constants/answer-types"; import { colors } from "constants/theme"; @@ -36,14 +39,18 @@ const validationTypes = [ { id: "minValue", title: "Min Value", - render: () => , + render: () => ( + {props => } + ), types: [CURRENCY, NUMBER], preview: ({ custom }) => custom }, { id: "maxValue", title: "Max Value", - render: () => , + render: () => ( + {props => } + ), types: [CURRENCY, NUMBER], preview: ({ custom, previousAnswer }) => custom ? custom : get(previousAnswer, "displayName") @@ -51,28 +58,36 @@ const validationTypes = [ { id: "earliestDate", title: "Earliest Date", - render: () => , + render: () => ( + {props => } + ), types: [DATE, DATE_RANGE], preview: DatePreview }, { id: "latestDate", title: "Latest Date", - render: () => , + render: () => ( + {props => } + ), types: [DATE, DATE_RANGE], preview: DatePreview }, { id: "minDuration", title: "Min Duration", - render: () => , + render: () => ( + {props => } + ), types: [DATE_RANGE], preview: DurationPreview }, { id: "maxDuration", title: "Max Duration", - render: () => , + render: () => ( + {props => } + ), types: [DATE_RANGE], preview: DurationPreview } diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/DurationValidation.js b/eq-author/src/App/questionPage/Design/Validation/Date/DurationValidation.js deleted file mode 100644 index a7c213b6be..0000000000 --- a/eq-author/src/App/questionPage/Design/Validation/Date/DurationValidation.js +++ /dev/null @@ -1,99 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; - -import { Grid, Column } from "components/Grid"; - -import DisabledMessage from "App/questionPage/Design/Validation/DisabledMessage"; -import ValidationView from "App/questionPage/Design/Validation/ValidationView"; -import Duration from "App/questionPage/Design/Validation/Date/Duration"; -import EmphasisedText from "App/questionPage/Design/Validation/Date/EmphasisedText"; -import AlignedColumn from "App/questionPage/Design/Validation/Date/AlignedColumn"; - -import { DAYS, MONTHS, YEARS } from "constants/durations"; - -const UNITS = [DAYS, MONTHS, YEARS]; - -class DurationValidation extends React.Component { - handleToggleChange = ({ value: enabled }) => { - const { - onToggleValidationRule, - duration: { id } - } = this.props; - - onToggleValidationRule({ - id, - enabled - }); - }; - - renderContent = () => { - const { - duration: { duration }, - displayName, - onChange, - onUpdate - } = this.props; - - return ( -
- - - {displayName} is - - - - - -
- ); - }; - - renderDisabled = () => ; - - render() { - const { - testId, - duration: { enabled } - } = this.props; - - return ( - - {enabled ? this.renderContent() : this.renderDisabled()} - - ); - } -} - -DurationValidation.propTypes = { - displayName: PropTypes.string.isRequired, - duration: PropTypes.shape({ - id: PropTypes.string.isRequired, - enabled: PropTypes.bool.isRequired, - duration: PropTypes.shape({ - unit: PropTypes.string.isRequired, - value: PropTypes.number.isRequired - }).isRequired - }).isRequired, - answer: PropTypes.shape({ - id: PropTypes.string.required, - properties: PropTypes.shape({ - format: PropTypes.string - }).isRequired - }).isRequired, - onToggleValidationRule: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, - onUpdate: PropTypes.func.isRequired, - testId: PropTypes.string.isRequired -}; - -export default DurationValidation; diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/DateValidation.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/DateValidation.test.js.snap deleted file mode 100644 index ce8878cb26..0000000000 --- a/eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/DateValidation.test.js.snap +++ /dev/null @@ -1,160 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Date Validation should filter the available units for the dd/mm/yyyy 1`] = ` -Array [ - "Days", - "Months", - "Years", -] -`; - -exports[`Date Validation should filter the available units for the mm/yyyy 1`] = ` -Array [ - "Months", - "Years", -] -`; - -exports[`Date Validation should filter the available units for the yyyy 1`] = ` -Array [ - "Years", -] -`; - -exports[`Date Validation should render all units for date range answers 1`] = ` -Array [ - "Days", - "Months", - "Years", -] -`; - -exports[`Date Validation should render disabled message when not enabled 1`] = ` - - - -`; - -exports[`Date Validation should render metadata and custom for date range type answer 1`] = ` - -`; - -exports[`Date Validation should render previous answer, metadata, custom and now for date type answer 1`] = ` - -`; - -exports[`Date Validation should render the form input with the values when enabled 1`] = ` - -
- - - - Some date - is - - - - - - - - - - - - - - - - - - - - - - - -
-
-`; diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/DurationValidation.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/DurationValidation.test.js.snap deleted file mode 100644 index 9f2949fbd2..0000000000 --- a/eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/DurationValidation.test.js.snap +++ /dev/null @@ -1,59 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Duration Validation should render disabled message when not enabled 1`] = ` - - - -`; - -exports[`Duration Validation should render the form input with the values when enabled 1`] = ` - -
- - - - Some date - is - - - - - - -
-
-`; diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/DatePreview.js b/eq-author/src/App/questionPage/Design/Validation/DatePreview.js similarity index 100% rename from eq-author/src/App/questionPage/Design/Validation/Date/DatePreview.js rename to eq-author/src/App/questionPage/Design/Validation/DatePreview.js diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/DatePreview.test.js b/eq-author/src/App/questionPage/Design/Validation/DatePreview.test.js similarity index 97% rename from eq-author/src/App/questionPage/Design/Validation/Date/DatePreview.test.js rename to eq-author/src/App/questionPage/Design/Validation/DatePreview.test.js index 72b05472f7..735195c9fc 100644 --- a/eq-author/src/App/questionPage/Design/Validation/Date/DatePreview.test.js +++ b/eq-author/src/App/questionPage/Design/Validation/DatePreview.test.js @@ -3,7 +3,7 @@ import { shallow } from "enzyme"; import { Detail as SidebarButtonDetail } from "components/buttons/SidebarButton"; -import { UnconnectedAnswerValidation } from "App/questionPage/Design/Validation/AnswerValidation"; +import { UnconnectedAnswerValidation } from "./AnswerValidation"; import { CUSTOM, diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/DateValidation.js b/eq-author/src/App/questionPage/Design/Validation/DateValidation.js similarity index 69% rename from eq-author/src/App/questionPage/Design/Validation/Date/DateValidation.js rename to eq-author/src/App/questionPage/Design/Validation/DateValidation.js index 5a48d63bd9..336327bce1 100644 --- a/eq-author/src/App/questionPage/Design/Validation/Date/DateValidation.js +++ b/eq-author/src/App/questionPage/Design/Validation/DateValidation.js @@ -1,22 +1,21 @@ import React from "react"; import PropTypes from "prop-types"; import styled from "styled-components"; -import { get } from "lodash"; +import { flowRight, get } from "lodash"; import { Input, Select } from "components/Forms"; import { Grid, Column } from "components/Grid"; -import PreviousAnswerContentPicker from "App/questionPage/Design/Validation/PreviousAnswerContentPicker"; -import MetadataContentPicker from "App/questionPage/Design/Validation/MetadataContentPicker"; -import DisabledMessage from "App/questionPage/Design/Validation/DisabledMessage"; -import { ValidationPills } from "App/questionPage/Design/Validation/ValidationPills"; -import ValidationView from "App/questionPage/Design/Validation/ValidationView"; -import Path from "App/questionPage/Design/Validation/path.svg?inline"; -import PathEnd from "App/questionPage/Design/Validation/path-end.svg?inline"; +import PreviousAnswerContentPicker from "./PreviousAnswerContentPicker"; +import MetadataContentPicker from "./MetadataContentPicker"; +import { ValidationPills } from "./ValidationPills"; +import Path from "./path.svg?inline"; +import PathEnd from "./path-end.svg?inline"; +import EmphasisedText from "./EmphasisedText"; +import AlignedColumn from "./AlignedColumn"; +import Duration from "./Duration"; -import EmphasisedText from "App/questionPage/Design/Validation/Date/EmphasisedText"; -import AlignedColumn from "App/questionPage/Design/Validation/Date/AlignedColumn"; -import Duration from "App/questionPage/Design/Validation/Date/Duration"; +import withChangeUpdate from "enhancers/withChangeUpdate"; import * as entityTypes from "constants/validation-entity-types"; import { DATE, DATE_RANGE } from "constants/answer-types"; @@ -68,13 +67,13 @@ const getUnits = ({ format, type }) => { return UNITS.slice(2); }; -class DateValidation extends React.Component { +export class UnwrappedDateValidation extends React.Component { PreviousAnswer = () => ( ( ); @@ -98,7 +100,7 @@ class DateValidation extends React.Component { ); - handleUpdate = update => this.props.onChange(update, this.props.onUpdate); - - handleEntityTypeChange = value => - this.handleUpdate({ name: "entityType", value }); - - handleToggleChange = ({ value: enabled }) => { - const { - onToggleValidationRule, - date: { id } - } = this.props; - - onToggleValidationRule({ - id, - enabled - }); - }; - - renderContent = () => { + render() { const { - date: { offset, relativePosition, entityType }, + validation: { offset, relativePosition, entityType }, answer: { properties: { format }, type }, displayName, onChange, - onUpdate + onUpdate, + onChangeUpdate } = this.props; const availableUnits = getUnits({ format, type }); @@ -193,37 +179,18 @@ class DateValidation extends React.Component { ); - }; - - renderDisabled = () => ; - - render() { - const { - testId, - date: { enabled } - } = this.props; - - return ( - - {enabled ? this.renderContent() : this.renderDisabled()} - - ); } } -DateValidation.propTypes = { - date: PropTypes.shape({ +UnwrappedDateValidation.propTypes = { + validation: PropTypes.shape({ id: PropTypes.string.isRequired, enabled: PropTypes.bool.isRequired, customDate: PropTypes.string, @@ -247,6 +214,7 @@ DateValidation.propTypes = { }).isRequired }).isRequired, onToggleValidationRule: PropTypes.func.isRequired, + onChangeUpdate: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired, displayName: PropTypes.string.isRequired, @@ -254,4 +222,4 @@ DateValidation.propTypes = { testId: PropTypes.string.isRequired }; -export default DateValidation; +export default flowRight(withChangeUpdate)(UnwrappedDateValidation); diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/DateValidation.test.js b/eq-author/src/App/questionPage/Design/Validation/DateValidation.test.js similarity index 50% rename from eq-author/src/App/questionPage/Design/Validation/Date/DateValidation.test.js rename to eq-author/src/App/questionPage/Design/Validation/DateValidation.test.js index 1c1f98d2f3..becc4b72f4 100644 --- a/eq-author/src/App/questionPage/Design/Validation/Date/DateValidation.test.js +++ b/eq-author/src/App/questionPage/Design/Validation/DateValidation.test.js @@ -1,25 +1,26 @@ import React from "react"; import { shallow } from "enzyme"; -import { ValidationPills } from "App/questionPage/Design/Validation/ValidationPills"; -import Duration from "App/questionPage/Design/Validation/Date/Duration"; +import { ValidationPills } from "./ValidationPills"; +import { UnwrappedDateValidation } from "./DateValidation"; +import Duration from "./Duration"; import { DATE, DATE_RANGE } from "constants/answer-types"; -import { CUSTOM, PREVIOUS_ANSWER } from "constants/validation-entity-types"; +import { CUSTOM } from "constants/validation-entity-types"; +import { byTestAttr } from "tests/utils/selectors"; -import DateValidation from "App/questionPage/Design/Validation/Date/DateValidation"; +const createWrapper = (props, render = shallow) => + render(); describe("Date Validation", () => { - let props; + let props, wrapper; + let onCustomNumberValueChange = jest.fn(); + let onChangeUpdate = jest.fn(); + let onChange = jest.fn(); + let onUpdate = jest.fn(); + let onToggleValidationRule = jest.fn(); beforeEach(() => { props = { - answer: { - id: "1", - properties: { - format: "dd/mm/yyyy" - }, - type: DATE - }, - date: { + validation: { id: "123", enabled: true, customDate: "2018-01-01", @@ -30,23 +31,27 @@ describe("Date Validation", () => { relativePosition: "Before", entityType: CUSTOM }, - onToggleValidationRule: jest.fn(), - onChange: jest.fn(), - onUpdate: jest.fn(), - displayName: "Some date", - readKey: "earliestDate", - testId: "example-test-id" + answer: { + id: "1", + type: DATE, + properties: { + format: "YYYY" + } + }, + onCustomNumberValueChange: onCustomNumberValueChange, + onChangeUpdate: onChangeUpdate, + onChange: onChange, + onUpdate: onUpdate, + onToggleValidationRule: onToggleValidationRule, + displayName: "foobar", + readKey: "read", + testId: "test-id" }; - }); - it("should render disabled message when not enabled", () => { - props.date.enabled = false; - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + wrapper = createWrapper(props); }); - it("should render the form input with the values when enabled", () => { - const wrapper = shallow(); + it("should render", () => { expect(wrapper).toMatchSnapshot(); }); @@ -55,7 +60,7 @@ describe("Date Validation", () => { ...props.answer, type: DATE }; - const wrapper = shallow(); + const wrapper = createWrapper({ ...props, answer }); expect(wrapper.find(ValidationPills)).toMatchSnapshot(); }); @@ -64,21 +69,11 @@ describe("Date Validation", () => { ...props.answer, type: DATE_RANGE }; - const wrapper = shallow(); + const wrapper = createWrapper({ ...props, answer }); expect(wrapper.find(ValidationPills)).toMatchSnapshot(); }); - it("should call onToggleValidationRule when enabled/disabled", () => { - const wrapper = shallow(); - wrapper.find("ValidationView").simulate("toggleChange", { value: true }); - expect(props.onToggleValidationRule).toHaveBeenCalledWith({ - id: "123", - enabled: true - }); - }); - it("should trigger update answer validation when the duration value changes", () => { - const wrapper = shallow(); const duration = wrapper.find(Duration); duration.simulate("change", "event"); expect(props.onChange).toHaveBeenCalledWith("event"); @@ -87,57 +82,38 @@ describe("Date Validation", () => { }); it("should trigger update answer validation when the relative position changes", () => { - const wrapper = shallow(); const relativePositionField = wrapper.find( - '[data-test="relative-position-select"]' + byTestAttr("relative-position-select") ); relativePositionField.simulate("change", "event"); - expect(props.onChange).toHaveBeenCalledWith("event"); + expect(onChange).toHaveBeenCalledWith("event"); relativePositionField.simulate("blur", "event"); - expect(props.onUpdate).toHaveBeenCalledWith("event"); + expect(onUpdate).toHaveBeenCalledWith("event"); }); it("should trigger update answer validation when the custom value changes", () => { - const wrapper = shallow(); const Custom = wrapper.find(ValidationPills).prop("Custom"); let customDateField = shallow(); customDateField.simulate("change", "event"); - expect(props.onChange).toHaveBeenCalledWith("event"); + expect(onChange).toHaveBeenCalledWith("event"); customDateField.simulate("blur", "event"); - expect(props.onUpdate).toHaveBeenCalledWith("event"); + expect(onUpdate).toHaveBeenCalledWith("event"); }); - it("should correctly handle entity type change", () => { - const wrapper = shallow(); - const pills = wrapper.find(ValidationPills); - pills.simulate("entityTypeChange", PREVIOUS_ANSWER); - expect(props.onChange).toHaveBeenCalledWith( - { - name: "entityType", - value: PREVIOUS_ANSWER - }, - props.onUpdate - ); - }); - - it("should correctly handle previous answer change", () => { + it("should correctly handle previous answer", () => { const previousAnswer = { id: 1 }; - - const wrapper = shallow(); const PreviousAnswer = wrapper.find(ValidationPills).prop("PreviousAnswer"); shallow().simulate("submit", { name: "previousAnswer", value: previousAnswer }); - expect(props.onChange).toHaveBeenCalledWith( - { - name: "previousAnswer", - value: previousAnswer - }, - props.onUpdate - ); + + expect(onChangeUpdate).toHaveBeenCalledWith({ + name: "previousAnswer", + value: { id: 1 } + }); }); it("should correctly handle metadata change", () => { @@ -145,52 +121,64 @@ describe("Date Validation", () => { id: 1 }; - const wrapper = shallow(); const Metadata = wrapper.find(ValidationPills).prop("Metadata"); shallow().simulate("submit", { name: "metadata", value: metadata }); - expect(props.onChange).toHaveBeenCalledWith( - { - name: "metadata", - value: metadata - }, - props.onUpdate - ); + expect(props.onChangeUpdate).toHaveBeenCalledWith({ + name: "metadata", + value: metadata + }); + }); + + it("should correctly render 'now' entity type", () => { + const Now = wrapper.find(ValidationPills).prop("Now"); + expect(shallow()).toMatchSnapshot(); }); it("should filter the available units for the mm/yyyy", () => { const answer = { + ...props.answer, properties: { format: "mm/yyyy" } }; - const wrapper = shallow(); + const wrapper = createWrapper({ ...props, answer }); const duration = wrapper.find(Duration); expect(duration.prop("units")).toMatchSnapshot(); }); it("should filter the available units for the dd/mm/yyyy", () => { - const answer = { properties: { format: "dd/mm/yyyy" } }; - const wrapper = shallow(); + const answer = { + ...props.answer, + properties: { + format: "dd/mm/yyyy" + } + }; + const wrapper = createWrapper({ ...props, answer }); const duration = wrapper.find(Duration); expect(duration.prop("units")).toMatchSnapshot(); }); it("should filter the available units for the yyyy", () => { - const answer = { properties: { format: "yyyy" } }; - const wrapper = shallow(); + const answer = { + ...props.answer, + properties: { + format: "yyyy" + } + }; + const wrapper = createWrapper({ ...props, answer }); const duration = wrapper.find(Duration); expect(duration.prop("units")).toMatchSnapshot(); }); it("should render all units for date range answers", () => { const answer = { - properties: {}, + ...props.answer, type: DATE_RANGE }; - const wrapper = shallow(); + const wrapper = createWrapper({ ...props, answer }); const duration = wrapper.find(Duration); expect(duration.prop("units")).toMatchSnapshot(); }); diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/Duration.js b/eq-author/src/App/questionPage/Design/Validation/Duration.js similarity index 99% rename from eq-author/src/App/questionPage/Design/Validation/Date/Duration.js rename to eq-author/src/App/questionPage/Design/Validation/Duration.js index 9291407aa5..d20d84af6c 100644 --- a/eq-author/src/App/questionPage/Design/Validation/Date/Duration.js +++ b/eq-author/src/App/questionPage/Design/Validation/Duration.js @@ -5,6 +5,7 @@ import { Number, Select } from "components/Forms"; import { Grid, Column } from "components/Grid"; import { DAYS, MONTHS, YEARS } from "constants/durations"; + const UNITS = [DAYS, MONTHS, YEARS]; const Duration = ({ diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/Duration.test.js b/eq-author/src/App/questionPage/Design/Validation/Duration.test.js similarity index 94% rename from eq-author/src/App/questionPage/Design/Validation/Date/Duration.test.js rename to eq-author/src/App/questionPage/Design/Validation/Duration.test.js index 9005d1f608..b22f3a3e49 100644 --- a/eq-author/src/App/questionPage/Design/Validation/Date/Duration.test.js +++ b/eq-author/src/App/questionPage/Design/Validation/Duration.test.js @@ -1,6 +1,6 @@ import React from "react"; import { shallow } from "enzyme"; -import Duration from "App/questionPage/Design/Validation/Date/Duration"; +import Duration from "./Duration"; import { Number, Select } from "components/Forms"; import { DAYS, MONTHS, YEARS } from "constants/durations"; diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/DurationPreview.js b/eq-author/src/App/questionPage/Design/Validation/DurationPreview.js similarity index 100% rename from eq-author/src/App/questionPage/Design/Validation/Date/DurationPreview.js rename to eq-author/src/App/questionPage/Design/Validation/DurationPreview.js diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/DurationPreview.test.js b/eq-author/src/App/questionPage/Design/Validation/DurationPreview.test.js similarity index 87% rename from eq-author/src/App/questionPage/Design/Validation/Date/DurationPreview.test.js rename to eq-author/src/App/questionPage/Design/Validation/DurationPreview.test.js index f1b83457ce..cd1c3981c8 100644 --- a/eq-author/src/App/questionPage/Design/Validation/Date/DurationPreview.test.js +++ b/eq-author/src/App/questionPage/Design/Validation/DurationPreview.test.js @@ -1,7 +1,7 @@ import React from "react"; import { shallow } from "enzyme"; -import DurationPreview from "App/questionPage/Design/Validation/Date/DurationPreview"; +import DurationPreview from "./DurationPreview"; import { DAYS } from "constants/durations"; diff --git a/eq-author/src/App/questionPage/Design/Validation/DurationValidation.js b/eq-author/src/App/questionPage/Design/Validation/DurationValidation.js new file mode 100644 index 0000000000..8242ab8755 --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/DurationValidation.js @@ -0,0 +1,60 @@ +import React from "react"; +import PropTypes from "prop-types"; + +import { Grid, Column } from "components/Grid"; + +import Duration from "./Duration"; +import EmphasisedText from "./EmphasisedText"; +import AlignedColumn from "./AlignedColumn"; + +import { DAYS, MONTHS, YEARS } from "constants/durations"; + +const UNITS = [DAYS, MONTHS, YEARS]; + +const DurationValidation = ({ + validation: { duration }, + displayName, + onChange, + onUpdate +}) => ( +
+ + + {displayName} is + + + + + +
+); + +DurationValidation.propTypes = { + displayName: PropTypes.string, + validation: PropTypes.shape({ + id: PropTypes.string, + enabled: PropTypes.bool, + duration: PropTypes.shape({ + unit: PropTypes.string, + value: PropTypes.number + }) + }), + answer: PropTypes.shape({ + id: PropTypes.string.required, + properties: PropTypes.shape({ + format: PropTypes.string + }) + }), + onToggleValidationRule: PropTypes.func, + onChange: PropTypes.func, + onUpdate: PropTypes.func, + testId: PropTypes.string +}; + +export default DurationValidation; diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/DurationValidation.test.js b/eq-author/src/App/questionPage/Design/Validation/DurationValidation.test.js similarity index 51% rename from eq-author/src/App/questionPage/Design/Validation/Date/DurationValidation.test.js rename to eq-author/src/App/questionPage/Design/Validation/DurationValidation.test.js index 0374ae16e8..759594933c 100644 --- a/eq-author/src/App/questionPage/Design/Validation/Date/DurationValidation.test.js +++ b/eq-author/src/App/questionPage/Design/Validation/DurationValidation.test.js @@ -1,12 +1,15 @@ import React from "react"; import { shallow } from "enzyme"; -import Duration from "App/questionPage/Design/Validation/Date/Duration"; +import Duration from "./Duration"; import { DATE_RANGE } from "constants/answer-types"; -import DurationValidation from "App/questionPage/Design/Validation/Date/DurationValidation"; +import DurationValidation from "./DurationValidation"; + +const createWrapper = (props, render = shallow) => + render(); describe("Duration Validation", () => { - let props; + let props, wrapper; beforeEach(() => { props = { @@ -17,7 +20,7 @@ describe("Duration Validation", () => { }, type: DATE_RANGE }, - duration: { + validation: { id: "123", enabled: true, duration: { @@ -31,30 +34,15 @@ describe("Duration Validation", () => { displayName: "Some date", testId: "duration-test-id" }; - }); - it("should render disabled message when not enabled", () => { - props.duration.enabled = false; - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + wrapper = createWrapper(props); }); - it("should render the form input with the values when enabled", () => { - const wrapper = shallow(); + it("should render", () => { expect(wrapper).toMatchSnapshot(); }); - it("should call onToggleValidationRule when enabled/disabled", () => { - const wrapper = shallow(); - wrapper.find("ValidationView").simulate("toggleChange", { value: true }); - expect(props.onToggleValidationRule).toHaveBeenCalledWith({ - id: "123", - enabled: true - }); - }); - it("should trigger update answer validation when the duration value changes", () => { - const wrapper = shallow(); const duration = wrapper.find(Duration); duration.simulate("change", "event"); expect(props.onChange).toHaveBeenCalledWith("event"); diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/EmphasisedText.js b/eq-author/src/App/questionPage/Design/Validation/EmphasisedText.js similarity index 100% rename from eq-author/src/App/questionPage/Design/Validation/Date/EmphasisedText.js rename to eq-author/src/App/questionPage/Design/Validation/EmphasisedText.js diff --git a/eq-author/src/App/questionPage/Design/Validation/MaxValue.js b/eq-author/src/App/questionPage/Design/Validation/MaxValue.js deleted file mode 100644 index 31c0b2dd8e..0000000000 --- a/eq-author/src/App/questionPage/Design/Validation/MaxValue.js +++ /dev/null @@ -1,210 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import styled from "styled-components"; -import { withApollo } from "react-apollo"; -import { propType } from "graphql-anywhere"; -import { flowRight, get, inRange, isNaN } from "lodash"; - -import { Field, Label } from "components/Forms"; -import { Grid, Column } from "components/Grid"; -import ToggleSwitch from "components/buttons/ToggleSwitch"; - -import PreviousAnswerContentPicker from "App/questionPage/Design/Validation/PreviousAnswerContentPicker"; -import DisabledMessage from "App/questionPage/Design/Validation/DisabledMessage"; -import { ValidationPills } from "App/questionPage/Design/Validation/ValidationPills"; -import ValidationTitle from "App/questionPage/Design/Validation/ValidationTitle"; -import ValidationView from "App/questionPage/Design/Validation/ValidationView"; -import ValidationContext from "App/questionPage/Design/Validation/ValidationContext"; -import ValidationInput from "App/questionPage/Design/Validation/ValidationInput"; -import PathEnd from "App/questionPage/Design/Validation/path-end.svg?inline"; - -import withUpdateAnswerValidation from "App/questionPage/Design/Validation/withUpdateAnswerValidation"; -import withToggleAnswerValidation from "App/questionPage/Design/Validation/withToggleAnswerValidation"; - -import MaxValueValidationRule from "graphql/fragments/max-value-validation-rule.graphql"; - -import * as answerTypes from "constants/answer-types"; - -const InlineField = styled(Field)` - display: flex; - flex-direction: row; - align-items: center; - margin-left: -0.8em; -`; - -const Connector = styled(PathEnd)` - margin-top: 0.75em; -`; - -export class MaxValue extends React.Component { - PreviousAnswer = () => ( - - ); - - Custom = () => ( - - ); - - handlePreviousAnswerChange = ({ value: { id } }) => { - const updateValidationRuleInput = { - id: this.props.maxValue.id, - maxValueInput: { - inclusive: this.props.maxValue.inclusive, - previousAnswer: id - } - }; - this.props.onUpdateAnswerValidation(updateValidationRuleInput); - }; - - handleCustomValueChange = ({ value }) => { - // clamp value of input to +/- limit - if ( - value !== "" && - !inRange(parseInt(value, 10), 0 - this.props.limit, this.props.limit + 1) - ) { - return false; - } - - const intValue = parseInt(value, 10); - - const updateValidationRuleInput = { - id: this.props.maxValue.id, - maxValueInput: { - inclusive: this.props.maxValue.inclusive, - custom: isNaN(intValue) ? null : intValue - } - }; - - this.props.onUpdateAnswerValidation(updateValidationRuleInput); - }; - - handleEntityTypeChange = value => { - const updateValidationRuleInput = { - id: this.props.maxValue.id, - maxValueInput: { - inclusive: this.props.maxValue.inclusive, - entityType: value, - previousAnswer: null, - custom: null - } - }; - - this.props.onUpdateAnswerValidation(updateValidationRuleInput); - }; - - handleToggleChange = ({ value }) => { - const toggleValidationRuleInput = { - id: this.props.maxValue.id, - enabled: value - }; - - this.props.onToggleValidationRule(toggleValidationRuleInput); - }; - - handleIncludeChange = ({ value }) => { - const updateValidationRuleInput = { - id: this.props.maxValue.id, - maxValueInput: { - custom: this.props.maxValue.custom, - inclusive: value - } - }; - this.props.onUpdateAnswerValidation(updateValidationRuleInput); - }; - - renderDisabled = () => ; - - renderContent = () => { - return ( - - - Max Value is - - - - - - - - - - - ); - }; - - render() { - const { maxValue } = this.props; - - return ( - - {maxValue.enabled ? this.renderContent() : this.renderDisabled()} - - ); - } -} - -MaxValue.defaultProps = { - limit: 999999999 -}; - -MaxValue.propTypes = { - answerId: PropTypes.string.isRequired, - maxValue: propType(MaxValueValidationRule).isRequired, - answerType: PropTypes.oneOf(Object.values(answerTypes)).isRequired, - onUpdateAnswerValidation: PropTypes.func.isRequired, - onToggleValidationRule: PropTypes.func.isRequired, - limit: PropTypes.number -}; - -const withQuestionPageEditing = flowRight( - withApollo, - withUpdateAnswerValidation, - withToggleAnswerValidation -); - -export const MaxValueWithAnswer = props => ( - - {({ answer }) => ( - - )} - -); - -export default withQuestionPageEditing(MaxValueWithAnswer); diff --git a/eq-author/src/App/questionPage/Design/Validation/MaxValue.test.js b/eq-author/src/App/questionPage/Design/Validation/MaxValue.test.js deleted file mode 100644 index 4e164c5993..0000000000 --- a/eq-author/src/App/questionPage/Design/Validation/MaxValue.test.js +++ /dev/null @@ -1,161 +0,0 @@ -import React from "react"; -import { shallow } from "enzyme"; - -import { MaxValue } from "App/questionPage/Design/Validation/MaxValue"; -import { ValidationPills } from "App/questionPage/Design/Validation/ValidationPills"; - -import { CUSTOM, PREVIOUS_ANSWER } from "constants/validation-entity-types"; -import { NUMBER } from "constants/answer-types"; - -const createWrapper = (props, render = shallow) => - render(); - -describe("MaxValue", () => { - let props, wrapper, onUpdateAnswerValidation, onToggleValidationRule; - beforeEach(() => { - onUpdateAnswerValidation = jest.fn(); - onToggleValidationRule = jest.fn(); - - props = { - maxValue: { - id: "1", - enabled: true, - custom: 123, - inclusive: true, - entityType: CUSTOM, - previousAnswer: null - }, - onUpdateAnswerValidation: onUpdateAnswerValidation, - onToggleValidationRule: onToggleValidationRule, - limit: 999999999, - answerType: NUMBER, - answerId: "1" - }; - - wrapper = createWrapper(props); - }); - - it("should render with content", () => { - expect(wrapper).toMatchSnapshot(); - }); - - it("should render with disabled content", () => { - props.maxValue.enabled = false; - expect(createWrapper(props)).toMatchSnapshot(); - }); - - it("should correctly handle toggle change", () => { - wrapper.simulate("toggleChange", { value: false }); - - expect(onToggleValidationRule).toHaveBeenCalledWith({ - id: props.maxValue.id, - enabled: false - }); - }); - - it("should correctly handle include change", () => { - wrapper.find("#max-value-include").simulate("change", { value: false }); - - expect(onUpdateAnswerValidation).toHaveBeenCalledWith({ - id: props.maxValue.id, - maxValueInput: { - custom: props.maxValue.custom, - inclusive: false - } - }); - }); - - it("should correctly handle entity type change", () => { - wrapper.find(ValidationPills).simulate("entityTypeChange", PREVIOUS_ANSWER); - - expect(onUpdateAnswerValidation).toHaveBeenCalledWith({ - id: props.maxValue.id, - maxValueInput: { - inclusive: props.maxValue.inclusive, - entityType: PREVIOUS_ANSWER, - previousAnswer: null, - custom: null - } - }); - }); - - it("should correctly handle previous answer", () => { - const previousAnswer = { - id: 1 - }; - const PreviousAnswer = wrapper.find(ValidationPills).prop("PreviousAnswer"); - shallow().simulate("submit", { - name: "previousAnswer", - value: previousAnswer - }); - - expect(onUpdateAnswerValidation).toHaveBeenCalledWith({ - id: props.maxValue.id, - maxValueInput: { - inclusive: props.maxValue.inclusive, - previousAnswer: previousAnswer.id - } - }); - }); - - describe("Custom Input", () => { - let CustomInput, customInputWrapper; - - beforeEach(() => { - CustomInput = wrapper.find(ValidationPills).prop("Custom"); - customInputWrapper = shallow(); - }); - - it("should correctly handle max value changes with in range values", () => { - customInputWrapper.simulate("change", { - value: 1 - }); - - expect(onUpdateAnswerValidation).toHaveBeenCalledWith({ - id: props.maxValue.id, - maxValueInput: { - inclusive: props.maxValue.inclusive, - custom: 1 - } - }); - }); - - it("should correctly handle max value change with empty string", () => { - customInputWrapper.simulate("change", { value: "" }); - - expect(onUpdateAnswerValidation).toHaveBeenCalledWith({ - id: props.maxValue.id, - maxValueInput: { - inclusive: props.maxValue.inclusive, - custom: null - } - }); - }); - - it("should correctly coerce string inputs to integers", () => { - customInputWrapper.simulate("change", { value: "1" }); - - expect(onUpdateAnswerValidation).toHaveBeenCalledWith({ - id: props.maxValue.id, - maxValueInput: { - inclusive: props.maxValue.inclusive, - custom: 1 - } - }); - }); - - it("should correctly handle max value change with out of range values", () => { - customInputWrapper.simulate("change", { value: 1000000000 }); - expect(onUpdateAnswerValidation).not.toHaveBeenCalled(); - - customInputWrapper.simulate("change", { value: -1000000000 }); - expect(onUpdateAnswerValidation).not.toHaveBeenCalled(); - - customInputWrapper.simulate("change", { value: 999999999 }); - expect(onUpdateAnswerValidation).toHaveBeenCalled(); - - customInputWrapper.simulate("change", { value: -999999999 }); - expect(onUpdateAnswerValidation).toHaveBeenCalled(); - }); - }); -}); diff --git a/eq-author/src/App/questionPage/Design/Validation/MaxValueValidation.js b/eq-author/src/App/questionPage/Design/Validation/MaxValueValidation.js new file mode 100644 index 0000000000..f8e931789e --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/MaxValueValidation.js @@ -0,0 +1,120 @@ +import React from "react"; +import PropTypes from "prop-types"; +import styled from "styled-components"; +import { get, flowRight } from "lodash"; + +import { Grid, Column } from "components/Grid"; + +import PreviousAnswerContentPicker from "./PreviousAnswerContentPicker"; +import { ValidationPills } from "./ValidationPills"; +import ValidationTitle from "./ValidationTitle"; +import ValidationInput from "./ValidationInput"; +import PathEnd from "./path-end.svg?inline"; +import withCustomNumberValueChange from "./withCustomNumberValueChange"; +import FieldWithInclude from "./FieldWithInclude"; + +import * as entityTypes from "constants/validation-entity-types"; +import withChangeUpdate from "enhancers/withChangeUpdate"; + +const Connector = styled(PathEnd)` + margin-top: 0.75em; +`; + +export class UnwrappedMaxValueValidation extends React.Component { + PreviousAnswer = () => ( + + + + ); + + Custom = () => ( + + + + ); + + render() { + const { + validation: { entityType }, + displayName, + onChangeUpdate + } = this.props; + + return ( + + + {displayName} is + + + + + + + ); + } +} + +UnwrappedMaxValueValidation.propTypes = { + limit: PropTypes.number, + validation: PropTypes.shape({ + id: PropTypes.string.isRequired, + enabled: PropTypes.bool.isRequired, + custom: PropTypes.number, + inclusive: PropTypes.bool.isRequired, + previousAnswer: PropTypes.shape({ + displayName: PropTypes.string.isRequired + }), + entityType: PropTypes.oneOf(Object.values(entityTypes)) + }).isRequired, + answer: PropTypes.shape({ + id: PropTypes.string.required, + properties: PropTypes.shape({ + format: PropTypes.string + }).isRequired + }).isRequired, + onCustomNumberValueChange: PropTypes.func.isRequired, + onChangeUpdate: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onUpdate: PropTypes.func.isRequired, + displayName: PropTypes.string.isRequired, + readKey: PropTypes.string.isRequired, + testId: PropTypes.string.isRequired +}; + +export default flowRight( + withCustomNumberValueChange, + withChangeUpdate +)(UnwrappedMaxValueValidation); diff --git a/eq-author/src/App/questionPage/Design/Validation/MaxValueValidation.test.js b/eq-author/src/App/questionPage/Design/Validation/MaxValueValidation.test.js new file mode 100644 index 0000000000..4030369425 --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/MaxValueValidation.test.js @@ -0,0 +1,107 @@ +import ValidationInput from "App/questionPage/Design/Validation/ValidationInput"; +import React from "react"; +import { shallow } from "enzyme"; + +import FieldWithInclude from "./FieldWithInclude"; +import PreviousAnswerContentPicker from "./PreviousAnswerContentPicker"; +import { UnwrappedMaxValueValidation } from "./MaxValueValidation"; +import { ValidationPills } from "./ValidationPills"; +import { CUSTOM } from "constants/validation-entity-types"; + +const createWrapper = (props, render = shallow) => + render(); + +describe("MaxValueValidation", () => { + let props, wrapper; + let onCustomNumberValueChange = jest.fn(); + let onChangeUpdate = jest.fn(); + let onChange = jest.fn(); + let onUpdate = jest.fn(); + + beforeEach(() => { + props = { + validation: { + id: "1", + enabled: true, + custom: 123, + inclusive: true, + entityType: CUSTOM, + previousAnswer: null + }, + answer: { + id: "1", + properties: { + format: "YYYY" + } + }, + onCustomNumberValueChange: onCustomNumberValueChange, + onChangeUpdate: onChangeUpdate, + onChange: onChange, + onUpdate: onUpdate, + displayName: "foobar", + readKey: "read", + testId: "test-id" + }; + + wrapper = createWrapper(props); + }); + + it("should render", () => { + expect(wrapper).toMatchSnapshot(); + }); + + describe("Previous Answer", () => { + let PreviousAnswer, previousAnswerWrapper; + + beforeEach(() => { + PreviousAnswer = wrapper.find(ValidationPills).prop("PreviousAnswer"); + previousAnswerWrapper = shallow(); + }); + + it("should correctly handle previous answer change", () => { + const previousAnswer = { + id: 1 + }; + previousAnswerWrapper + .find(PreviousAnswerContentPicker) + .simulate("submit", { + name: "previousAnswer", + value: previousAnswer + }); + + expect(onChangeUpdate).toHaveBeenCalledWith({ + name: "previousAnswer", + value: { id: 1 } + }); + }); + + it("should correctly handle include change", () => { + let value = "foobar"; + previousAnswerWrapper.find(FieldWithInclude).simulate("change", value); + expect(onChangeUpdate).toHaveBeenCalledWith(value); + }); + }); + + describe("Custom Answer", () => { + let CustomInput, customInputWrapper; + + beforeEach(() => { + CustomInput = wrapper.find(ValidationPills).prop("Custom"); + customInputWrapper = shallow(); + }); + + it("should correctly handle custom value changes", () => { + customInputWrapper.find(ValidationInput).simulate("change", { + value: 1 + }); + + expect(onCustomNumberValueChange).toHaveBeenCalledWith({ value: 1 }); + }); + + it("should correctly handle include change", () => { + let value = "foobar"; + customInputWrapper.find(FieldWithInclude).simulate("change", value); + expect(onChangeUpdate).toHaveBeenCalledWith(value); + }); + }); +}); diff --git a/eq-author/src/App/questionPage/Design/Validation/MetadataContentPicker.js b/eq-author/src/App/questionPage/Design/Validation/MetadataContentPicker.js index bf37701063..d4033904c5 100644 --- a/eq-author/src/App/questionPage/Design/Validation/MetadataContentPicker.js +++ b/eq-author/src/App/questionPage/Design/Validation/MetadataContentPicker.js @@ -25,7 +25,7 @@ UnwrappedMetadataContentPicker.propTypes = { path: PropTypes.string.isRequired }; -const MetadataContentPicker = props => ( +const GetAvailableMetadataQuery = props => ( {innerProps => ( @@ -33,8 +33,8 @@ const MetadataContentPicker = props => ( ); -MetadataContentPicker.propTypes = { +GetAvailableMetadataQuery.propTypes = { answerId: PropTypes.string.isRequired }; -export default MetadataContentPicker; +export default GetAvailableMetadataQuery; diff --git a/eq-author/src/App/questionPage/Design/Validation/MinValue.js b/eq-author/src/App/questionPage/Design/Validation/MinValue.js deleted file mode 100644 index 94089515bd..0000000000 --- a/eq-author/src/App/questionPage/Design/Validation/MinValue.js +++ /dev/null @@ -1,133 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; - -import { withApollo } from "react-apollo"; -import { propType } from "graphql-anywhere"; - -import { flowRight, inRange, isNaN } from "lodash"; - -import { Grid, Column } from "components/Grid"; -import DisabledMessage from "App/questionPage/Design/Validation/DisabledMessage"; - -import ValidationTitle from "App/questionPage/Design/Validation/ValidationTitle"; -import ValidationInput from "App/questionPage/Design/Validation/ValidationInput"; -import ValidationView from "App/questionPage/Design/Validation/ValidationView"; -import FieldWithInclude from "App/questionPage/Design/Validation/FieldWithInclude"; -import ValidationContext from "App/questionPage/Design/Validation/ValidationContext"; - -import withUpdateAnswerValidation from "App/questionPage/Design/Validation/withUpdateAnswerValidation"; -import withToggleAnswerValidation from "App/questionPage/Design/Validation/withToggleAnswerValidation"; - -import MinValueValidationRule from "graphql/fragments/min-value-validation-rule.graphql"; - -export const MinValue = ({ - minValue, - onUpdateAnswerValidation, - onToggleValidationRule, - limit -}) => { - const handleToggleChange = ({ value }) => { - const toggleValidationRuleInput = { - id: minValue.id, - enabled: value - }; - - onToggleValidationRule(toggleValidationRuleInput); - }; - - const handleMinValueChange = ({ value }) => { - // clamp value of input to +/- limit - if (value !== "" && !inRange(parseInt(value, 10), 0 - limit, limit + 1)) { - return false; - } - - const intValue = parseInt(value, 10); - - const updateValidationRuleInput = { - id: minValue.id, - minValueInput: { - inclusive: minValue.inclusive, - custom: isNaN(intValue) ? null : intValue - } - }; - - onUpdateAnswerValidation(updateValidationRuleInput); - }; - - const handleIncludeChange = ({ value }) => { - const updateValidationRuleInput = { - id: minValue.id, - minValueInput: { - custom: minValue.custom, - inclusive: value - } - }; - onUpdateAnswerValidation(updateValidationRuleInput); - }; - - const renderDisabled = () => ; - - const renderContent = () => ( - - - Min Value is - - - - - - - - ); - - return ( - - {minValue.enabled ? renderContent() : renderDisabled()} - - ); -}; - -MinValue.defaultProps = { - limit: 999999999 -}; - -MinValue.propTypes = { - minValue: propType(MinValueValidationRule).isRequired, - onUpdateAnswerValidation: PropTypes.func.isRequired, - onToggleValidationRule: PropTypes.func.isRequired, - limit: PropTypes.number -}; - -const withQuestionPageEditing = flowRight( - withApollo, - withUpdateAnswerValidation, - withToggleAnswerValidation -); - -export const MinValueWithAnswer = props => ( - - {({ answer }) => ( - - )} - -); - -export default withQuestionPageEditing(MinValueWithAnswer); diff --git a/eq-author/src/App/questionPage/Design/Validation/MinValue.test.js b/eq-author/src/App/questionPage/Design/Validation/MinValue.test.js deleted file mode 100644 index 833d512e5c..0000000000 --- a/eq-author/src/App/questionPage/Design/Validation/MinValue.test.js +++ /dev/null @@ -1,127 +0,0 @@ -import React from "react"; -import { shallow } from "enzyme"; - -import { MinValue } from "App/questionPage/Design/Validation/MinValue"; - -import { byTestAttr } from "tests/utils/selectors"; - -const createWrapper = (props, render = shallow) => { - return render(); -}; - -describe("MinValue", () => { - let onUpdateAnswerValidation; - let onToggleValidationRule; - let props; - - beforeEach(() => { - onUpdateAnswerValidation = jest.fn(); - onToggleValidationRule = jest.fn(); - - props = { - minValue: { - id: "1", - enabled: true, - custom: true, - inclusive: true - }, - onUpdateAnswerValidation: onUpdateAnswerValidation, - onToggleValidationRule: onToggleValidationRule, - limit: 999999999 - }; - }); - - it("should render with content", () => { - expect(createWrapper(props)).toMatchSnapshot(); - }); - - it("should render with disabled content", () => { - props.minValue.enabled = false; - expect(createWrapper(props)).toMatchSnapshot(); - }); - - it("should correctly handle toggle change", () => { - const wrapper = createWrapper(props); - wrapper.simulate("toggleChange", { value: false }); - - expect(onToggleValidationRule).toHaveBeenCalledWith({ - id: props.minValue.id, - enabled: false - }); - }); - - it("should correctly handle min value changes with in range values", () => { - const wrapper = createWrapper(props); - wrapper - .find(byTestAttr("min-value-input")) - .simulate("change", { value: "1" }); - - expect(onUpdateAnswerValidation).toHaveBeenCalledWith({ - id: props.minValue.id, - minValueInput: { - inclusive: props.minValue.inclusive, - custom: 1 - } - }); - }); - - it("should correctly handle min value change with empty string", () => { - const wrapper = createWrapper(props); - wrapper - .find(byTestAttr("min-value-input")) - .simulate("change", { value: "" }); - - expect(onUpdateAnswerValidation).toHaveBeenCalledWith({ - id: props.minValue.id, - minValueInput: { - inclusive: props.minValue.inclusive, - custom: null - } - }); - }); - - it("should correctly coerce string inputs to integers", () => { - const wrapper = createWrapper(props); - wrapper - .find(byTestAttr("min-value-input")) - .simulate("change", { value: "1" }); - - expect(onUpdateAnswerValidation).toHaveBeenCalledWith({ - id: props.minValue.id, - minValueInput: { - inclusive: props.minValue.inclusive, - custom: 1 - } - }); - }); - - it("should correctly handle min value change with out of range values", () => { - const wrapper = createWrapper(props); - - const minValueInput = wrapper.find(byTestAttr("min-value-input")); - minValueInput.simulate("change", { value: 1000000000 }); - expect(onUpdateAnswerValidation).not.toHaveBeenCalled(); - - minValueInput.simulate("change", { value: -1000000000 }); - expect(onUpdateAnswerValidation).not.toHaveBeenCalled(); - - minValueInput.simulate("change", { value: 999999999 }); - expect(onUpdateAnswerValidation).toHaveBeenCalled(); - - minValueInput.simulate("change", { value: -999999999 }); - expect(onUpdateAnswerValidation).toHaveBeenCalled(); - }); - - it("should correctly handle include change", () => { - const wrapper = createWrapper(props); - wrapper.find("#min-value-include").simulate("change", { value: false }); - - expect(onUpdateAnswerValidation).toHaveBeenCalledWith({ - id: props.minValue.id, - minValueInput: { - custom: props.minValue.custom, - inclusive: false - } - }); - }); -}); diff --git a/eq-author/src/App/questionPage/Design/Validation/MinValueValidation.js b/eq-author/src/App/questionPage/Design/Validation/MinValueValidation.js new file mode 100644 index 0000000000..3d3f3f5c19 --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/MinValueValidation.js @@ -0,0 +1,72 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { flowRight } from "lodash"; + +import { Grid, Column } from "components/Grid"; + +import FieldWithInclude from "./FieldWithInclude"; +import ValidationTitle from "./ValidationTitle"; +import ValidationInput from "./ValidationInput"; +import withCustomNumberValueChange from "./withCustomNumberValueChange"; +import withChangeUpdate from "enhancers/withChangeUpdate"; + +export const UnwrappedMinValueValidation = ({ + validation: { inclusive, custom }, + displayName, + onUpdate, + limit, + onChangeUpdate, + onCustomNumberValueChange +}) => ( + + + {displayName} is + + + + + + + +); + +UnwrappedMinValueValidation.propTypes = { + limit: PropTypes.number, + validation: PropTypes.shape({ + id: PropTypes.string.isRequired, + enabled: PropTypes.bool.isRequired, + custom: PropTypes.number, + inclusive: PropTypes.bool.isRequired + }).isRequired, + answer: PropTypes.shape({ + id: PropTypes.string.required, + properties: PropTypes.shape({ + format: PropTypes.string + }).isRequired + }).isRequired, + onCustomNumberValueChange: PropTypes.func.isRequired, + onChangeUpdate: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onUpdate: PropTypes.func.isRequired, + displayName: PropTypes.string.isRequired, + readKey: PropTypes.string.isRequired, + testId: PropTypes.string.isRequired +}; + +export default flowRight( + withCustomNumberValueChange, + withChangeUpdate +)(UnwrappedMinValueValidation); diff --git a/eq-author/src/App/questionPage/Design/Validation/MinValueValidation.test.js b/eq-author/src/App/questionPage/Design/Validation/MinValueValidation.test.js new file mode 100644 index 0000000000..9009061abb --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/MinValueValidation.test.js @@ -0,0 +1,63 @@ +import ValidationInput from "./ValidationInput"; +import React from "react"; +import { shallow } from "enzyme"; + +import FieldWithInclude from "./FieldWithInclude"; +import { UnwrappedMinValueValidation } from "./MinValueValidation"; + +const createWrapper = (props, render = shallow) => + render(); + +describe("MinValueValidation", () => { + let props, wrapper; + let onCustomNumberValueChange = jest.fn(); + let onChangeUpdate = jest.fn(); + let onChange = jest.fn(); + let onUpdate = jest.fn(); + + beforeEach(() => { + props = { + validation: { + id: "1", + enabled: true, + custom: 123, + inclusive: true + }, + answer: { + id: "1", + properties: { + format: "YYYY" + } + }, + onCustomNumberValueChange: onCustomNumberValueChange, + onChangeUpdate: onChangeUpdate, + onChange: onChange, + onUpdate: onUpdate, + displayName: "foobar", + readKey: "read", + testId: "test-id" + }; + + wrapper = createWrapper(props); + }); + + it("should render", () => { + expect(wrapper).toMatchSnapshot(); + }); + + it("should correctly handle include change", () => { + wrapper.find(FieldWithInclude).simulate("change", { value: false }); + + expect(onChangeUpdate).toHaveBeenCalledWith({ + value: false + }); + }); + + it("should correctly handle custom value changes", () => { + wrapper.find(ValidationInput).simulate("change", { + value: 1 + }); + + expect(onCustomNumberValueChange).toHaveBeenCalledWith({ value: 1 }); + }); +}); diff --git a/eq-author/src/App/questionPage/Design/Validation/PreviousAnswerContentPicker.js b/eq-author/src/App/questionPage/Design/Validation/PreviousAnswerContentPicker.js index 92bc9f7597..5b8ad67d2d 100644 --- a/eq-author/src/App/questionPage/Design/Validation/PreviousAnswerContentPicker.js +++ b/eq-author/src/App/questionPage/Design/Validation/PreviousAnswerContentPicker.js @@ -26,7 +26,7 @@ UnwrappedPreviousAnswerContentPicker.propTypes = { path: PropTypes.string.isRequired }; -const PreviousAnswerContentPicker = props => ( +const GetAvailablePreviewAnswersQuery = props => ( {innerProps => ( @@ -34,8 +34,8 @@ const PreviousAnswerContentPicker = props => ( ); -PreviousAnswerContentPicker.propTypes = { +GetAvailablePreviewAnswersQuery.propTypes = { answerId: PropTypes.string.isRequired }; -export default PreviousAnswerContentPicker; +export default GetAvailablePreviewAnswersQuery; diff --git a/eq-author/src/App/questionPage/Design/Validation/Validation.js b/eq-author/src/App/questionPage/Design/Validation/Validation.js new file mode 100644 index 0000000000..a704ab0693 --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/Validation.js @@ -0,0 +1,52 @@ +import React from "react"; +import PropTypes from "prop-types"; + +import DisabledMessage from "./DisabledMessage"; +import ValidationView from "./ValidationView"; + +class Validation extends React.Component { + handleToggleChange = ({ value: enabled }) => { + const { + onToggleValidationRule, + validation: { id } + } = this.props; + + onToggleValidationRule({ + id, + enabled + }); + }; + + renderDisabled = () => ; + + render() { + const { + testId, + validation: { enabled }, + children + } = this.props; + + return ( + + {enabled ? children(this.props) : this.renderDisabled()} + + ); + } +} + +Validation.propTypes = { + testId: PropTypes.string, + displayName: PropTypes.string, + validation: PropTypes.shape({ + id: PropTypes.string, + enabled: PropTypes.bool + }), + onToggleValidationRule: PropTypes.func, + children: PropTypes.func +}; + +export default Validation; diff --git a/eq-author/src/App/questionPage/Design/Validation/Validation.test.js b/eq-author/src/App/questionPage/Design/Validation/Validation.test.js new file mode 100644 index 0000000000..4f0c01b20f --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/Validation.test.js @@ -0,0 +1,45 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import Validation from "./Validation"; + +const createWrapper = (props, render = shallow) => + render(); + +describe("Validation", () => { + let props, wrapper; + let onToggleValidationRule = jest.fn(); + + beforeEach(() => { + props = { + testId: "test-id", + displayName: "display-name", + validation: { + id: "666", + enabled: true + }, + onToggleValidationRule: onToggleValidationRule, + children: () =>
foobar
+ }; + + wrapper = createWrapper(props); + }); + + it("should render with content", () => { + expect(wrapper).toMatchSnapshot(); + }); + + it("should render with disabled content", () => { + props.validation.enabled = false; + wrapper = createWrapper(props); + expect(wrapper).toMatchSnapshot(); + }); + + it("should correctly handle toggle change", () => { + wrapper.simulate("toggleChange", { value: false }); + expect(onToggleValidationRule).toHaveBeenCalledWith({ + enabled: false, + id: "666" + }); + }); +}); diff --git a/eq-author/src/App/questionPage/Design/Validation/ValidationPills.js b/eq-author/src/App/questionPage/Design/Validation/ValidationPills.js index 895d540d1c..3f940641e9 100644 --- a/eq-author/src/App/questionPage/Design/Validation/ValidationPills.js +++ b/eq-author/src/App/questionPage/Design/Validation/ValidationPills.js @@ -24,58 +24,63 @@ export const ValidationPills = ({ Metadata, Now, Custom -}) => ( - ( - - - - ) - } - : null, - isFunction(PreviousAnswer) - ? { - id: "PreviousAnswer", - title: "Previous answer", - render: () => ( - - - - ) - } - : null, - isFunction(Metadata) - ? { - id: "Metadata", - title: "Metadata", - render: () => ( - - - - ) - } - : null, - isFunction(Custom) - ? { - id: "Custom", - title: "Custom", - render: () => ( - - - - ) - } - : null - ])} - /> -); +}) => { + const handleOnChange = value => + onEntityTypeChange({ name: "entityType", value }); + + return ( + ( + + + + ) + } + : null, + isFunction(PreviousAnswer) + ? { + id: "PreviousAnswer", + title: "Previous answer", + render: () => ( + + + + ) + } + : null, + isFunction(Metadata) + ? { + id: "Metadata", + title: "Metadata", + render: () => ( + + + + ) + } + : null, + isFunction(Custom) + ? { + id: "Custom", + title: "Custom", + render: () => ( + + + + ) + } + : null + ])} + /> + ); +}; ValidationPills.propTypes = { entityType: PropTypes.oneOf(Object.values(entityTypes)).isRequired, diff --git a/eq-author/src/App/questionPage/Design/Validation/ValidationPills.test.js b/eq-author/src/App/questionPage/Design/Validation/ValidationPills.test.js index 7b784484a1..243eff11ed 100644 --- a/eq-author/src/App/questionPage/Design/Validation/ValidationPills.test.js +++ b/eq-author/src/App/questionPage/Design/Validation/ValidationPills.test.js @@ -2,7 +2,10 @@ import React from "react"; import { shallow } from "enzyme"; import { omit } from "lodash"; -import { Pills, ValidationPills } from "App/questionPage/Design/Validation/ValidationPills"; +import { + Pills, + ValidationPills +} from "App/questionPage/Design/Validation/ValidationPills"; import { PREVIOUS_ANSWER } from "constants/validation-entity-types"; @@ -53,4 +56,15 @@ describe("ValidationPills", () => { const custom = wrapper.find(Pills).prop("options")[0]; expect(shallow(custom.render())).toMatchSnapshot(); }); + + it("should correctly handle entity type change", () => { + const pills = wrapper.find(Pills); + const value = { foo: "bar" }; + + pills.simulate("change", value); + expect(props.onEntityTypeChange).toHaveBeenCalledWith({ + name: "entityType", + value + }); + }); }); diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/DatePreview.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/DatePreview.test.js.snap similarity index 100% rename from eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/DatePreview.test.js.snap rename to eq-author/src/App/questionPage/Design/Validation/__snapshots__/DatePreview.test.js.snap diff --git a/eq-author/src/App/questionPage/Design/Validation/__snapshots__/DateValidation.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/DateValidation.test.js.snap new file mode 100644 index 0000000000..54caf11ee9 --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/DateValidation.test.js.snap @@ -0,0 +1,146 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Date Validation should correctly render 'now' entity type 1`] = ` + + The date the respondent begins the survey + +`; + +exports[`Date Validation should filter the available units for the dd/mm/yyyy 1`] = ` +Array [ + "Days", + "Months", + "Years", +] +`; + +exports[`Date Validation should filter the available units for the mm/yyyy 1`] = ` +Array [ + "Months", + "Years", +] +`; + +exports[`Date Validation should filter the available units for the yyyy 1`] = ` +Array [ + "Years", +] +`; + +exports[`Date Validation should render 1`] = ` +
+ + + + foobar + is + + + + + + + + + + + + + + + + + + + + + + + +
+`; + +exports[`Date Validation should render all units for date range answers 1`] = ` +Array [ + "Days", + "Months", + "Years", +] +`; + +exports[`Date Validation should render metadata and custom for date range type answer 1`] = ` + +`; + +exports[`Date Validation should render previous answer, metadata, custom and now for date type answer 1`] = ` + +`; diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/Duration.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/Duration.test.js.snap similarity index 100% rename from eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/Duration.test.js.snap rename to eq-author/src/App/questionPage/Design/Validation/__snapshots__/Duration.test.js.snap diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/DurationPreview.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/DurationPreview.test.js.snap similarity index 100% rename from eq-author/src/App/questionPage/Design/Validation/Date/__snapshots__/DurationPreview.test.js.snap rename to eq-author/src/App/questionPage/Design/Validation/__snapshots__/DurationPreview.test.js.snap diff --git a/eq-author/src/App/questionPage/Design/Validation/__snapshots__/DurationValidation.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/DurationValidation.test.js.snap new file mode 100644 index 0000000000..7a17685504 --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/DurationValidation.test.js.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Duration Validation should render 1`] = ` +
+ + + + Some date + is + + + + + + +
+`; diff --git a/eq-author/src/App/questionPage/Design/Validation/__snapshots__/MaxValue.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/MaxValue.test.js.snap deleted file mode 100644 index e7f8cb8470..0000000000 --- a/eq-author/src/App/questionPage/Design/Validation/__snapshots__/MaxValue.test.js.snap +++ /dev/null @@ -1,60 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MaxValue should render with content 1`] = ` - - - - - Max Value is - - - - - - - - - - - - -`; - -exports[`MaxValue should render with disabled content 1`] = ` - - - -`; diff --git a/eq-author/src/App/questionPage/Design/Validation/__snapshots__/MaxValueValidation.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/MaxValueValidation.test.js.snap new file mode 100644 index 0000000000..55f15a3a4c --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/MaxValueValidation.test.js.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MaxValueValidation should render 1`] = ` + + + + foobar + is + + + + + + + +`; diff --git a/eq-author/src/App/questionPage/Design/Validation/__snapshots__/MinValue.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/MinValue.test.js.snap deleted file mode 100644 index 0d54394d65..0000000000 --- a/eq-author/src/App/questionPage/Design/Validation/__snapshots__/MinValue.test.js.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MinValue should render with content 1`] = ` - - - - - Min Value is - - - - - - - - - -`; - -exports[`MinValue should render with disabled content 1`] = ` - - - -`; diff --git a/eq-author/src/App/questionPage/Design/Validation/__snapshots__/MinValueValidation.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/MinValueValidation.test.js.snap new file mode 100644 index 0000000000..818aa30dfa --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/MinValueValidation.test.js.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MinValueValidation should render 1`] = ` + + + + foobar + is + + + + + + + + +`; diff --git a/eq-author/src/App/questionPage/Design/Validation/__snapshots__/Validation.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/Validation.test.js.snap new file mode 100644 index 0000000000..7e06bb5652 --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/Validation.test.js.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validation should render with content 1`] = ` + +
+ foobar +
+
+`; + +exports[`Validation should render with disabled content 1`] = ` + + + +`; diff --git a/eq-author/src/App/questionPage/Design/Validation/__snapshots__/ValidationPills.test.js.snap b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/ValidationPills.test.js.snap index 85b350c6f8..2c336cfa2e 100644 --- a/eq-author/src/App/questionPage/Design/Validation/__snapshots__/ValidationPills.test.js.snap +++ b/eq-author/src/App/questionPage/Design/Validation/__snapshots__/ValidationPills.test.js.snap @@ -2,7 +2,7 @@ exports[`ValidationPills should render 1`] = ` +`; diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/builder.js b/eq-author/src/App/questionPage/Design/Validation/builder.js similarity index 51% rename from eq-author/src/App/questionPage/Design/Validation/Date/builder.js rename to eq-author/src/App/questionPage/Design/Validation/builder.js index 1206b81a49..9074c4326a 100644 --- a/eq-author/src/App/questionPage/Design/Validation/Date/builder.js +++ b/eq-author/src/App/questionPage/Design/Validation/builder.js @@ -1,12 +1,14 @@ import { flowRight } from "lodash/fp"; -import withToggleAnswerValidation from "App/questionPage/Design/Validation/withToggleAnswerValidation"; -import withUpdateAnswerValidation from "App/questionPage/Design/Validation/withUpdateAnswerValidation"; +import withToggleAnswerValidation from "./withToggleAnswerValidation"; +import withUpdateAnswerValidation from "./withUpdateAnswerValidation"; import withEntityEditor from "components/withEntityEditor"; -import withAnswerValidation from "App/questionPage/Design/Validation/withAnswerValidation"; - -import { withProps, withPropRenamed, withPropRemapped } from "utils/enhancers"; +import withAnswerValidation from "./withAnswerValidation"; +import Validation from "./Validation"; +import withProps from "enhancers/withProps"; +import withPropRenamed from "enhancers/withPropRenamed"; +import withPropRemapped from "enhancers/withPropRemapped"; export default ( displayName, @@ -14,8 +16,7 @@ export default ( readKey, writeKey, fragment, - readToWriteMapper, - propKey + readToWriteMapper ) => flowRight( withProps({ displayName, testId, readKey }), @@ -28,5 +29,5 @@ export default ( readToWriteMapper(writeKey) ), withEntityEditor(readKey, fragment), - withPropRenamed(readKey, propKey) - ); + withPropRenamed(readKey, "validation") + )(Validation); diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/index.js b/eq-author/src/App/questionPage/Design/Validation/index.js similarity index 60% rename from eq-author/src/App/questionPage/Design/Validation/Date/index.js rename to eq-author/src/App/questionPage/Design/Validation/index.js index f50667754e..60e518e7da 100644 --- a/eq-author/src/App/questionPage/Design/Validation/Date/index.js +++ b/eq-author/src/App/questionPage/Design/Validation/index.js @@ -1,15 +1,17 @@ -import builder from "App/questionPage/Design/Validation/Date/builder"; +import builder from "./builder"; import { dateReadToWriteMapper, - durationReadToWriteMapper -} from "App/questionPage/Design/Validation/Date/readToWriteMapper"; -import DateValidation from "App/questionPage/Design/Validation/Date/DateValidation"; -import DurationValidation from "App/questionPage/Design/Validation/Date/DurationValidation"; + durationReadToWriteMapper, + minValueReadToWriteMapper, + maxValueReadToWriteMapper +} from "./readToWriteMapper"; import EarliestDateValidationRule from "graphql/fragments/earliest-date-validation-rule.graphql"; import LatestDateValidationRule from "graphql/fragments/latest-date-validation-rule.graphql"; import MinDurationValidationRule from "graphql/fragments/min-duration-validation-rule.graphql"; import MaxDurationValidationRule from "graphql/fragments/max-duration-validation-rule.graphql"; +import MinValueValidationRule from "graphql/fragments/min-value-validation-rule.graphql"; +import MaxValueValidationRule from "graphql/fragments/max-value-validation-rule.graphql"; export const LatestDate = builder( "Latest date", @@ -17,9 +19,8 @@ export const LatestDate = builder( "latestDate", "latestDateInput", LatestDateValidationRule, - dateReadToWriteMapper, - "date" -)(DateValidation); + dateReadToWriteMapper +); export const EarliestDate = builder( "Earliest date", @@ -27,9 +28,8 @@ export const EarliestDate = builder( "earliestDate", "earliestDateInput", EarliestDateValidationRule, - dateReadToWriteMapper, - "date" -)(DateValidation); + dateReadToWriteMapper +); export const MinDuration = builder( "Min duration", @@ -37,9 +37,8 @@ export const MinDuration = builder( "minDuration", "minDurationInput", MinDurationValidationRule, - durationReadToWriteMapper, - "duration" -)(DurationValidation); + durationReadToWriteMapper +); export const MaxDuration = builder( "Max duration", @@ -47,6 +46,23 @@ export const MaxDuration = builder( "maxDuration", "maxDurationInput", MaxDurationValidationRule, - durationReadToWriteMapper, - "duration" -)(DurationValidation); + durationReadToWriteMapper +); + +export const MinValue = builder( + "Min value", + "min-value-validation", + "minValue", + "minValueInput", + MinValueValidationRule, + minValueReadToWriteMapper +); + +export const MaxValue = builder( + "Max value", + "max-value-validation", + "maxValue", + "maxValueInput", + MaxValueValidationRule, + maxValueReadToWriteMapper +); diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/readToWriteMapper.js b/eq-author/src/App/questionPage/Design/Validation/readToWriteMapper.js similarity index 73% rename from eq-author/src/App/questionPage/Design/Validation/Date/readToWriteMapper.js rename to eq-author/src/App/questionPage/Design/Validation/readToWriteMapper.js index c248d69c00..6573bdf7ce 100644 --- a/eq-author/src/App/questionPage/Design/Validation/Date/readToWriteMapper.js +++ b/eq-author/src/App/questionPage/Design/Validation/readToWriteMapper.js @@ -49,3 +49,22 @@ export const durationReadToWriteMapper = outputKey => ({ id, ...rest }) => ({ id, [outputKey]: { ...omit("enabled", rest) } }); + +export const minValueReadToWriteMapper = outputKey => ({ id, ...rest }) => ({ + id, + [outputKey]: { ...omit("enabled", rest) } +}); + +export const maxValueReadToWriteMapper = outputKey => ({ + id, + previousAnswer, + entityType, + ...rest +}) => ({ + id, + [outputKey]: { + ...omit("enabled", rest), + entityType, + previousAnswer: getPreviousAnswer(entityType, previousAnswer) + } +}); diff --git a/eq-author/src/App/questionPage/Design/Validation/Date/readToWriteMapper.test.js b/eq-author/src/App/questionPage/Design/Validation/readToWriteMapper.test.js similarity index 99% rename from eq-author/src/App/questionPage/Design/Validation/Date/readToWriteMapper.test.js rename to eq-author/src/App/questionPage/Design/Validation/readToWriteMapper.test.js index 47a42c6d5c..93f07f3553 100644 --- a/eq-author/src/App/questionPage/Design/Validation/Date/readToWriteMapper.test.js +++ b/eq-author/src/App/questionPage/Design/Validation/readToWriteMapper.test.js @@ -1,7 +1,7 @@ import { dateReadToWriteMapper, durationReadToWriteMapper -} from "App/questionPage/Design/Validation/Date/readToWriteMapper"; +} from "./readToWriteMapper"; import { CUSTOM, METADATA, diff --git a/eq-author/src/App/questionPage/Design/Validation/withCustomNumberValueChange.js b/eq-author/src/App/questionPage/Design/Validation/withCustomNumberValueChange.js new file mode 100644 index 0000000000..e9fe27909c --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/withCustomNumberValueChange.js @@ -0,0 +1,47 @@ +import PropTypes from "prop-types"; +import React from "react"; +import { inRange, isNaN } from "lodash"; + +const withCustomNumberValueChange = WrappedComponent => { + return class extends React.Component { + static defaultProps = { + limit: 999999999 + }; + + static propTypes = { + limit: PropTypes.number.isRequired, + onChange: PropTypes.func.isRequired + }; + + handleCustomValueChange = ({ value }) => { + // clamp value of input to +/- limit + if ( + value !== "" && + !inRange( + parseInt(value, 10), + 0 - this.props.limit, + this.props.limit + 1 + ) + ) { + return false; + } + + const intValue = parseInt(value, 10); + this.props.onChange({ + name: "custom", + value: isNaN(intValue) ? null : intValue + }); + }; + + render() { + return ( + + ); + } + }; +}; + +export default withCustomNumberValueChange; diff --git a/eq-author/src/App/questionPage/Design/Validation/withCustomNumberValueChange.test.js b/eq-author/src/App/questionPage/Design/Validation/withCustomNumberValueChange.test.js new file mode 100644 index 0000000000..87d49544f6 --- /dev/null +++ b/eq-author/src/App/questionPage/Design/Validation/withCustomNumberValueChange.test.js @@ -0,0 +1,56 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import withCustomNumberValueChange from "./withCustomNumberValueChange"; + +const EnhancedComponent = withCustomNumberValueChange("div"); + +const createWrapper = (props, render = shallow) => + render(); + +describe("withCustomValueChange", () => { + let props, wrapper; + let onChange = jest.fn(); + + beforeEach(() => { + props = { + limit: 999999999, + onChange: onChange + }; + + wrapper = createWrapper(props); + }); + + it("should render", () => { + expect(wrapper).toMatchSnapshot(); + }); + + it("should correctly handle range values", () => { + wrapper.simulate("customNumberValueChange", { value: 1000000000 }); + expect(onChange).not.toHaveBeenCalled(); + + wrapper.simulate("customNumberValueChange", { value: -1000000000 }); + expect(onChange).not.toHaveBeenCalled(); + + wrapper.simulate("customNumberValueChange", { value: 999999999 }); + expect(onChange).toHaveBeenCalled(); + + wrapper.simulate("customNumberValueChange", { value: -999999999 }); + expect(onChange).toHaveBeenCalled(); + }); + + it("should correctly handle custom value changes with in range values", () => { + wrapper.simulate("customNumberValueChange", { value: "1" }); + expect(onChange).toHaveBeenCalledWith({ name: "custom", value: 1 }); + }); + + it("should correctly handle min value change with empty string", () => { + wrapper.simulate("customNumberValueChange", { value: "" }); + expect(onChange).toHaveBeenCalledWith({ name: "custom", value: null }); + }); + + it("should correctly coerce string inputs to integers", () => { + wrapper.simulate("customNumberValueChange", { value: "1" }); + expect(onChange).toHaveBeenCalledWith({ name: "custom", value: 1 }); + }); +}); diff --git a/eq-author/src/App/questionPage/Design/index.js b/eq-author/src/App/questionPage/Design/index.js index 6bd0f4a74c..6e039e4c13 100644 --- a/eq-author/src/App/questionPage/Design/index.js +++ b/eq-author/src/App/questionPage/Design/index.js @@ -20,7 +20,7 @@ import Error from "components/Error"; import withEntityEditor from "components/withEntityEditor"; import EditorLayout from "App/questionPage/Design/EditorLayout"; -import { withPropRenamed } from "utils/enhancers"; +import withPropRenamed from "enhancers/withPropRenamed"; import pageFragment from "graphql/fragments/page.graphql"; import { propType } from "graphql-anywhere"; diff --git a/eq-author/src/App/section/Design/index.js b/eq-author/src/App/section/Design/index.js index fd7a77f4f4..46ce604ef2 100644 --- a/eq-author/src/App/section/Design/index.js +++ b/eq-author/src/App/section/Design/index.js @@ -32,7 +32,7 @@ import Error from "components/Error"; import VisuallyHidden from "components/VisuallyHidden"; import withEntityEditor from "components/withEntityEditor"; -import { withPropRenamed } from "utils/enhancers"; +import withPropRenamed from "enhancers/withPropRenamed"; import sectionFragment from "graphql/fragments/section.graphql"; import { raiseToast } from "redux/toast/actions"; diff --git a/eq-author/src/enhancers/__snapshots__/withChangeUpdate.test.js.snap b/eq-author/src/enhancers/__snapshots__/withChangeUpdate.test.js.snap new file mode 100644 index 0000000000..7b13295f59 --- /dev/null +++ b/eq-author/src/enhancers/__snapshots__/withChangeUpdate.test.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`withChangeUpdate should render 1`] = ` +
+`; diff --git a/eq-author/src/enhancers/withChangeUpdate.js b/eq-author/src/enhancers/withChangeUpdate.js new file mode 100644 index 0000000000..5572acf756 --- /dev/null +++ b/eq-author/src/enhancers/withChangeUpdate.js @@ -0,0 +1,21 @@ +import PropTypes from "prop-types"; +import React from "react"; + +const withChangeUpdate = WrappedComponent => { + return class extends React.Component { + static propTypes = { + onChange: PropTypes.func.isRequired, + onUpdate: PropTypes.func.isRequired, + }; + + handleUpdate = update => this.props.onChange(update, this.props.onUpdate); + + render() { + return ( + + ); + } + }; +}; + +export default withChangeUpdate; diff --git a/eq-author/src/enhancers/withChangeUpdate.test.js b/eq-author/src/enhancers/withChangeUpdate.test.js new file mode 100644 index 0000000000..23cb2981e2 --- /dev/null +++ b/eq-author/src/enhancers/withChangeUpdate.test.js @@ -0,0 +1,34 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import withChangeUpdate from "./withChangeUpdate"; + +const EnhancedComponent = withChangeUpdate("div"); + +const createWrapper = (props, render = shallow) => + render(); + +describe("withChangeUpdate", () => { + let props, wrapper; + let onChange = jest.fn(); + let onUpdate = jest.fn(); + + beforeEach(() => { + props = { + onChange: onChange, + onUpdate: onUpdate + }; + + wrapper = createWrapper(props); + }); + + it("should render", () => { + expect(wrapper).toMatchSnapshot(); + }); + + it("should correctly handle change update", () => { + const update = { foo: "bar" }; + wrapper.simulate("changeUpdate", update); + expect(onChange).toHaveBeenCalledWith(update, onUpdate); + }); +}); diff --git a/eq-author/src/enhancers/withPropRemapped.js b/eq-author/src/enhancers/withPropRemapped.js new file mode 100644 index 0000000000..d7aeed3516 --- /dev/null +++ b/eq-author/src/enhancers/withPropRemapped.js @@ -0,0 +1,13 @@ +import React from "react"; + +export default ( + oldName, + newName, + mapper +) => Component => props => { + const newProps = { + [newName]: (...args) => props[oldName](mapper(...args)), + ...props + }; + return ; +}; diff --git a/eq-author/src/enhancers/withPropRemapped.test.js b/eq-author/src/enhancers/withPropRemapped.test.js new file mode 100644 index 0000000000..682661def7 --- /dev/null +++ b/eq-author/src/enhancers/withPropRemapped.test.js @@ -0,0 +1,22 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import withPropRemapped from "./withPropRemapped"; + +describe("withPropRemapped", () => { + const Component = "div"; + + it("should rename and transform a function prop so it can match the api expected by another enhancer", () => { + const EnhancedComponent = withPropRemapped( + "onSpecificUpdate", + "onUpdate", + thing => `prefix-${thing}` + )(Component); + const onSpecificUpdateStub = jest.fn(); + const wrapper = shallow( + + ); + wrapper.props().onUpdate("hello"); + expect(onSpecificUpdateStub).toHaveBeenCalledWith("prefix-hello"); + }); +}); diff --git a/eq-author/src/enhancers/withPropRenamed.js b/eq-author/src/enhancers/withPropRenamed.js new file mode 100644 index 0000000000..813e89fb0d --- /dev/null +++ b/eq-author/src/enhancers/withPropRenamed.js @@ -0,0 +1,10 @@ +import React from "react"; + +export default (oldName, newName) => Component => props => { + const newProps = { + [newName]: props[oldName], + ...props + }; + return ; +}; + diff --git a/eq-author/src/enhancers/withPropRenamed.test.js b/eq-author/src/enhancers/withPropRenamed.test.js new file mode 100644 index 0000000000..80dd1a2b29 --- /dev/null +++ b/eq-author/src/enhancers/withPropRenamed.test.js @@ -0,0 +1,16 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import withPropRenamed from "./withPropRenamed"; + +describe("withPropRemapped", () => { + const Component = "div"; + + it("should rename a prop to make it easy to combine enhancers", () => { + const EnhancedComponent = withPropRenamed("a", "b")(Component); + const wrapper = shallow(); + expect(wrapper.props()).toMatchObject({ + b: 1 + }); + }); +}); diff --git a/eq-author/src/enhancers/withProps.js b/eq-author/src/enhancers/withProps.js new file mode 100644 index 0000000000..54f54e2bc3 --- /dev/null +++ b/eq-author/src/enhancers/withProps.js @@ -0,0 +1,5 @@ +import React from "react"; + +export default partialProps => Component => props => ( + +); diff --git a/eq-author/src/enhancers/withProps.test.js b/eq-author/src/enhancers/withProps.test.js new file mode 100644 index 0000000000..7ebfa5542d --- /dev/null +++ b/eq-author/src/enhancers/withProps.test.js @@ -0,0 +1,18 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import withProps from "./withProps"; + +describe("withProps", () => { + const Component = "div"; + + it("should partially apply the props passed so that a component can be curried", () => { + const EnhancedComponent = withProps({ a: 1, b: 2 })(Component); + const wrapper = shallow(); + expect(wrapper.props()).toMatchObject({ + a: 1, + b: 2, + c: 3 + }); + }); +}); diff --git a/eq-author/src/utils/enhancers.js b/eq-author/src/utils/enhancers.js deleted file mode 100644 index 508218e71f..0000000000 --- a/eq-author/src/utils/enhancers.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react"; - -export const withPropRemapped = ( - oldName, - newName, - mapper -) => Component => props => { - const newProps = { - [newName]: (...args) => props[oldName](mapper(...args)), - ...props - }; - return ; -}; - -export const withProps = partialProps => Component => props => ( - -); - -export const withPropRenamed = (oldName, newName) => Component => props => { - const newProps = { - [newName]: props[oldName], - ...props - }; - return ; -}; diff --git a/eq-author/src/utils/enhancers.test.js b/eq-author/src/utils/enhancers.test.js deleted file mode 100644 index 8579ceb2d6..0000000000 --- a/eq-author/src/utils/enhancers.test.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react"; -import { shallow } from "enzyme"; - -import { withProps, withPropRenamed, withPropRemapped } from "./enhancers"; - -describe("Enhancers", () => { - const Component = "div"; - describe("withProps", () => { - it("should partially apply the props passed so that a component can be curried", () => { - const EnhancedComponent = withProps({ a: 1, b: 2 })(Component); - const wrapper = shallow(); - expect(wrapper.props()).toMatchObject({ - a: 1, - b: 2, - c: 3 - }); - }); - }); - - describe("withPropRenamed", () => { - it("should rename a prop to make it easy to combine enhancers", () => { - const EnhancedComponent = withPropRenamed("a", "b")(Component); - const wrapper = shallow(); - expect(wrapper.props()).toMatchObject({ - b: 1 - }); - }); - }); - - describe("withPropRemapped", () => { - it("should rename and transform a function prop so it can match the api expected by another enhancer", () => { - const EnhancedComponent = withPropRemapped( - "onSpecificUpdate", - "onUpdate", - thing => `prefix-${thing}` - )(Component); - const onSpecificUpdateStub = jest.fn(); - const wrapper = shallow( - - ); - wrapper.props().onUpdate("hello"); - expect(onSpecificUpdateStub).toHaveBeenCalledWith("prefix-hello"); - }); - }); -});