Skip to content

Commit

Permalink
Merge pull request #30 from Aldredcz/feature-mapOf-keys-validation
Browse files Browse the repository at this point in the history
mapOf (+orderedMapOf) - keys validation
  • Loading branch information
HurricaneJames authored Jul 30, 2016
2 parents 9d4aa4c + 8e49092 commit 0a3bc77
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 6 deletions.
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,26 @@ ImmutablePropTypes.mapContains // Immutable.Map.isMap - contains(shape)

* `ImmutablePropTypes.listOf` is based on `React.PropTypes.array` and is specific to `Immutable.List`.

* `ImmutablePropTypes.mapOf` is basically the same as `listOf`, but it is specific to `Immutable.Map` It will check that the prop is an Immutable.Map and that the values are of the specified type.
* `ImmutablePropTypes.mapOf` allows you to control both map values nad keys (in Immutable.Map, keys could be _anything_ including another Immutable collections). It accepts two arguments - first one for values, second one for keys (optional). If you are interested in validation of keys only, just pass `React.PropTypes.any` as the first argument.

* `ImmutablePropTypes.orderedMapOf` is basically the same as `listOf`, but it is specific to `Immutable.OrderedMap`.
```es6
// ...
aMap: ImmutablePropTypes.mapOf(
React.PropTypes.any, // validation for values
ImmutablePropTypes.mapContains({ // validation for keys
a: React.PropTypes.number.isRequired,
b: React.PropTypes.string
})
)
// ...
const aMap = Immutable.Map([
[Immutable.Map({a: 1, b: '2'}), 'foo'],
[Immutable.Map({a: 3}), [1, '2', 3]]
]);
<SomeComponent aMap={aMap} />
```

* `ImmutablePropTypes.orderedMapOf` is basically the same as `mapOf`, but it is specific to `Immutable.OrderedMap`.

* `ImmutablePropTypes.orderedSetOf` is basically the same as `listOf`, but it is specific to `Immutable.OrderedSet`.

Expand Down
39 changes: 35 additions & 4 deletions src/ImmutablePropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,47 @@ function createIterableTypeChecker(typeChecker, immutableClassName, immutableCla
return createChainableTypeChecker(validate);
}

function createKeysTypeChecker(typeChecker) {

function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
if (typeof typeChecker !== 'function') {
return new Error(
`Invalid keysTypeChecker (optional second argument) supplied to \`${componentName}\` ` +
`for propType \`${propFullName}\`, expected a function.`
);
}

var keys = propValue.keySeq().toArray();
for (var i = 0, len = keys.length; i < len; i++) {
var error = typeChecker(keys, i, componentName, location, `${propFullName} -> key(${keys[i]})`);
if (error instanceof Error) {
return error;
}
}
}
return createChainableTypeChecker(validate);
}

function createListOfTypeChecker(typeChecker) {
return createIterableTypeChecker(typeChecker, 'List', Immutable.List.isList);
}

function createMapOfTypeChecker(typeChecker) {
return createIterableTypeChecker(typeChecker, 'Map', Immutable.Map.isMap);
function createMapOfTypeCheckerFactory(valuesTypeChecker, keysTypeChecker, immutableClassName, immutableClassTypeValidator) {
function validate(...args) {
return createIterableTypeChecker(valuesTypeChecker, immutableClassName, immutableClassTypeValidator)(...args)
|| keysTypeChecker && createKeysTypeChecker(keysTypeChecker)(...args)
}

return createChainableTypeChecker(validate);
}

function createMapOfTypeChecker(valuesTypeChecker, keysTypeChecker) {
return createMapOfTypeCheckerFactory(valuesTypeChecker, keysTypeChecker, 'Map', Immutable.Map.isMap);
}

function createOrderedMapOfTypeChecker(typeChecker) {
return createIterableTypeChecker(typeChecker, 'OrderedMap', Immutable.OrderedMap.isOrderedMap);
function createOrderedMapOfTypeChecker(valuesTypeChecker, keysTypeChecker) {
return createMapOfTypeCheckerFactory(valuesTypeChecker, keysTypeChecker, 'OrderedMap', Immutable.OrderedMap.isOrderedMap);
}

function createSetOfTypeChecker(typeChecker) {
Expand Down
164 changes: 164 additions & 0 deletions src/__tests__/ImmutablePropTypes-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,88 @@ describe('ImmutablePropTypes', function() {
requiredMessage
);
});

it('should support keys validation by passing typeChecker as a second argument', function() {
typeCheckPass(
PropTypes.mapOf(
React.PropTypes.any,
React.PropTypes.string
),
Immutable.Map({a: 1, b: 2})
);
typeCheckPass(
PropTypes.mapOf(
React.PropTypes.any,
React.PropTypes.number
),
Immutable.Map([[1, 1], [1, 2]])
);
typeCheckPass(
PropTypes.mapOf(
React.PropTypes.any,
React.PropTypes.function
),
Immutable.Map([[() => 1 + 1, 1], [(foo) => 'bar', 2]])
);
});

it('should support keys validation with Immutable keys', function() {
typeCheckPass(
PropTypes.mapOf(
React.PropTypes.any,
PropTypes.mapContains({
a: React.PropTypes.number.isRequired,
b: React.PropTypes.string
})
),
Immutable.Map([
[Immutable.Map({a: 1, b: '2'}), 1],
[Immutable.Map({a: 3}), 2]
])
);
});

it('should warn with invalid keys in the map', function() {
typeCheckFail(
PropTypes.mapOf(
React.PropTypes.any,
React.PropTypes.number
),
Immutable.Map({a: 1, b: 2}),
'Invalid prop `testProp -> key(a)` of type `string` supplied to `testComponent`, ' +
'expected `number`.'
);

typeCheckFail(
PropTypes.mapOf(
React.PropTypes.any,
React.PropTypes.string
),
Immutable.Map([
[{a: 1}, 2],
['a', 1]
]),
'Invalid prop `testProp -> key([object Object])` of type `object` supplied to `testComponent`, ' +
'expected `string`.'
);
});

it('should cause inner warning with invalid immutable key in the map', function() {
typeCheckFail(
PropTypes.mapOf(
React.PropTypes.any,
PropTypes.mapContains({
a: React.PropTypes.number.isRequired,
b: React.PropTypes.string
})
),
Immutable.Map([
[Immutable.Map({b: '2'}), 1],
[Immutable.Map({a: 3}), 2]
]),
'Required prop `testProp -> key(Map { "b": "2" }).a` was not specified in `testComponent`.'
);
});
});

describe('OrderedMapOf Type', function() {
Expand Down Expand Up @@ -601,6 +683,88 @@ describe('ImmutablePropTypes', function() {
requiredMessage
);
});

it('should support keys validation by passing typeChecker as a second argument', function() {
typeCheckPass(
PropTypes.orderedMapOf(
React.PropTypes.any,
React.PropTypes.string
),
Immutable.OrderedMap({a: 1, b: 2})
);
typeCheckPass(
PropTypes.orderedMapOf(
React.PropTypes.any,
React.PropTypes.number
),
Immutable.OrderedMap([[1, 1], [1, 2]])
);
typeCheckPass(
PropTypes.orderedMapOf(
React.PropTypes.any,
React.PropTypes.function
),
Immutable.OrderedMap([[() => 1 + 1, 1], [(foo) => 'bar', 2]])
);
});

it('should support keys validation with Immutable keys', function() {
typeCheckPass(
PropTypes.orderedMapOf(
React.PropTypes.any,
PropTypes.mapContains({
a: React.PropTypes.number.isRequired,
b: React.PropTypes.string
})
),
Immutable.OrderedMap([
[Immutable.Map({a: 1, b: '2'}), 1],
[Immutable.Map({a: 3}), 2]
])
);
});

it('should warn with invalid keys in the map', function() {
typeCheckFail(
PropTypes.orderedMapOf(
React.PropTypes.any,
React.PropTypes.number
),
Immutable.OrderedMap({a: 1, b: 2}),
'Invalid prop `testProp -> key(a)` of type `string` supplied to `testComponent`, ' +
'expected `number`.'
);

typeCheckFail(
PropTypes.orderedMapOf(
React.PropTypes.any,
React.PropTypes.string
),
Immutable.OrderedMap([
[{a: 1}, 2],
['a', 1]
]),
'Invalid prop `testProp -> key([object Object])` of type `object` supplied to `testComponent`, ' +
'expected `string`.'
);
});

it('should cause inner warning with invalid immutable key in the map', function() {
typeCheckFail(
PropTypes.orderedMapOf(
React.PropTypes.any,
PropTypes.mapContains({
a: React.PropTypes.number.isRequired,
b: React.PropTypes.string
})
),
Immutable.OrderedMap([
[Immutable.Map({b: '2'}), 1],
[Immutable.Map({a: 3}), 2]
]),
'Required prop `testProp -> key(Map { "b": "2" }).a` was not specified in `testComponent`.'
);
});
});

describe('SetOf Type', function() {
Expand Down

0 comments on commit 0a3bc77

Please sign in to comment.