Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.set and .setOf #191

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
120 changes: 120 additions & 0 deletions __tests__/PropTypesDevelopmentReact15.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ describe('PropTypesDevelopmentReact15', () => {
typeCheckPass(PropTypes.object, {});
typeCheckPass(PropTypes.object, new Date());
typeCheckPass(PropTypes.object, /please/);
typeCheckPass(PropTypes.set, new Set());
typeCheckPass(PropTypes.symbol, Symbol());
});

Expand Down Expand Up @@ -283,6 +284,12 @@ describe('PropTypesDevelopmentReact15', () => {
expectWarningInDevelopment(PropTypes.string.isRequired, 'foo');
expectWarningInDevelopment(PropTypes.string.isRequired, null);
expectWarningInDevelopment(PropTypes.string.isRequired, undefined);
expectWarningInDevelopment(PropTypes.set, 0);
expectWarningInDevelopment(PropTypes.set, new Set());
expectWarningInDevelopment(PropTypes.set.isRequired, 0);
expectWarningInDevelopment(PropTypes.set.isRequired, new Set());
expectWarningInDevelopment(PropTypes.set.isRequired, null);
expectWarningInDevelopment(PropTypes.set.isRequired, undefined);
expectWarningInDevelopment(PropTypes.symbol, 0);
expectWarningInDevelopment(PropTypes.symbol, Symbol('Foo'));
expectWarningInDevelopment(PropTypes.symbol.isRequired, 0);
Expand Down Expand Up @@ -1181,6 +1188,119 @@ describe('PropTypesDevelopmentReact15', () => {
});
});

describe('SetOf Type', () => {
it('should fail for invalid argument', () => {
typeCheckFail(
PropTypes.setOf({foo: PropTypes.string}),
{foo: 'bar'},
'Property `testProp` of component `testComponent` has invalid PropType notation inside setOf.',
);
});

it('should support the setOf propTypes', () => {
typeCheckPass(PropTypes.setOf(PropTypes.number), new Set([1, 2, 3]));
typeCheckPass(PropTypes.setOf(PropTypes.string), new Set(['a', 'b', 'c']));
typeCheckPass(PropTypes.setOf(PropTypes.oneOf(['a', 'b'])), new Set(['a', 'b']));
typeCheckPass(PropTypes.setOf(PropTypes.symbol), new Set([Symbol(), Symbol()]));
});

it('should support setOf with complex types', () => {
typeCheckPass(
PropTypes.setOf(PropTypes.shape({a: PropTypes.number.isRequired})),
new Set([{a: 1}, {a: 2}]),
);

function Thing() {}
typeCheckPass(PropTypes.setOf(PropTypes.instanceOf(Thing)), new Set([
new Thing(),
new Thing(),
]));
});

it('should warn with invalid items in the set', () => {
typeCheckFail(
PropTypes.setOf(PropTypes.number),
new Set([1, 2, 'b']),
'Invalid value inside testProp of type `string` supplied to ' +
'`testComponent`, expected `number`.',
);
});

it('should warn with invalid complex types', () => {
function Thing() {}
var name = Thing.name || '<<anonymous>>';

typeCheckFail(
PropTypes.setOf(PropTypes.instanceOf(Thing)),
new Set([new Thing(), 'xyz']),
'Invalid value inside testProp of type `String` supplied to ' +
'`testComponent`, expected instance of `' +
name +
'`.',
);
});

it('should warn when passed something other than a set', () => {
typeCheckFail(
PropTypes.setOf(PropTypes.number),
{'0': 'maybe-set', length: 1},
'Invalid prop `testProp` of type `object` supplied to ' +
'`testComponent`, expected a set.',
);
typeCheckFail(
PropTypes.setOf(PropTypes.number),
123,
'Invalid prop `testProp` of type `number` supplied to ' +
'`testComponent`, expected a set.',
);
typeCheckFail(
PropTypes.setOf(PropTypes.number),
'string',
'Invalid prop `testProp` of type `string` supplied to ' +
'`testComponent`, expected a set.',
);
});

it('should not warn when passing an empty set', () => {
typeCheckPass(PropTypes.setOf(PropTypes.number), new Set());
});

it('should be implicitly optional and not warn without values', () => {
typeCheckPass(PropTypes.setOf(PropTypes.number), null);
typeCheckPass(PropTypes.setOf(PropTypes.number), undefined);
});

it('should warn for missing required values', () => {
typeCheckFailRequiredValues(
PropTypes.setOf(PropTypes.number).isRequired,
);
});

it('should warn if called manually in development', () => {
spyOn(console, 'error');
expectWarningInDevelopment(PropTypes.setOf({foo: PropTypes.string}), {
foo: 'bar',
});
expectWarningInDevelopment(PropTypes.setOf(PropTypes.number), new Set([
1,
2,
'b',
]));
expectWarningInDevelopment(PropTypes.setOf(PropTypes.number), {
'0': 'maybe-array',
length: 1,
});
expectWarningInDevelopment(
PropTypes.setOf(PropTypes.number).isRequired,
null,
);
expectWarningInDevelopment(
PropTypes.setOf(PropTypes.number).isRequired,
undefined,
);
});
});

describe('Symbol Type', () => {
it('should warn for non-symbol', () => {
typeCheckFail(
Expand Down
121 changes: 121 additions & 0 deletions __tests__/PropTypesDevelopmentStandalone-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ describe('PropTypesDevelopmentStandalone', () => {
typeCheckPass(PropTypes.object, {});
typeCheckPass(PropTypes.object, new Date());
typeCheckPass(PropTypes.object, /please/);
typeCheckPass(PropTypes.set, new Set());
typeCheckPass(PropTypes.symbol, Symbol());
});

Expand Down Expand Up @@ -279,6 +280,12 @@ describe('PropTypesDevelopmentStandalone', () => {
expectThrowsInDevelopment(PropTypes.string.isRequired, 'foo');
expectThrowsInDevelopment(PropTypes.string.isRequired, null);
expectThrowsInDevelopment(PropTypes.string.isRequired, undefined);
expectThrowsInDevelopment(PropTypes.set, 0);
expectThrowsInDevelopment(PropTypes.set, new Set());
expectThrowsInDevelopment(PropTypes.set.isRequired, 0);
expectThrowsInDevelopment(PropTypes.set.isRequired, new Set());
expectThrowsInDevelopment(PropTypes.set.isRequired, null);
expectThrowsInDevelopment(PropTypes.set.isRequired, undefined);
expectThrowsInDevelopment(PropTypes.symbol, 0);
expectThrowsInDevelopment(PropTypes.symbol, Symbol('Foo'));
expectThrowsInDevelopment(PropTypes.symbol.isRequired, 0);
Expand Down Expand Up @@ -1070,6 +1077,120 @@ describe('PropTypesDevelopmentStandalone', () => {
});
});

describe('SetOf Type', () => {
it('should fail for invalid argument', () => {
typeCheckFail(
PropTypes.setOf({foo: PropTypes.string}),
{foo: 'bar'},
'Property `testProp` of component `testComponent` has invalid PropType notation inside setOf.',
);
});

it('should support the setOf propTypes', () => {
typeCheckPass(PropTypes.setOf(PropTypes.number), new Set([1, 2, 3]));
typeCheckPass(PropTypes.setOf(PropTypes.string), new Set(['a', 'b', 'c']));
typeCheckPass(PropTypes.setOf(PropTypes.oneOf(['a', 'b'])), new Set(['a', 'b']));
typeCheckPass(PropTypes.setOf(PropTypes.symbol), new Set([Symbol(), Symbol()]));
});

it('should support setOf with complex types', () => {
typeCheckPass(
PropTypes.setOf(PropTypes.shape({a: PropTypes.number.isRequired})),
new Set([{a: 1}, {a: 2}]),
);

function Thing() {}
typeCheckPass(PropTypes.setOf(PropTypes.instanceOf(Thing)), new Set([
new Thing(),
new Thing(),
]));
});

it('should warn with invalid items in the set', () => {
typeCheckFail(
PropTypes.setOf(PropTypes.number),
new Set([1, 2, 'b']),
'Invalid value inside testProp of type `string` supplied to ' +
'`testComponent`, expected `number`.',
);
});

it('should warn with invalid complex types', () => {
function Thing() {}
var name = Thing.name || '<<anonymous>>';

typeCheckFail(
PropTypes.setOf(PropTypes.instanceOf(Thing)),
new Set([new Thing(), 'xyz']),
'Invalid value inside testProp of type `String` supplied to ' +
'`testComponent`, expected instance of `' +
name +
'`.',
);
});

it('should warn when passed something other than a set', () => {
typeCheckFail(
PropTypes.setOf(PropTypes.number),
{'0': 'maybe-set', length: 1},
'Invalid prop `testProp` of type `object` supplied to ' +
'`testComponent`, expected a set.',
);
typeCheckFail(
PropTypes.setOf(PropTypes.number),
123,
'Invalid prop `testProp` of type `number` supplied to ' +
'`testComponent`, expected a set.',
);
typeCheckFail(
PropTypes.setOf(PropTypes.number),
'string',
'Invalid prop `testProp` of type `string` supplied to ' +
'`testComponent`, expected a set.',
);
});

it('should not warn when passing an empty set', () => {
typeCheckPass(PropTypes.setOf(PropTypes.number), new Set());
});

it('should be implicitly optional and not warn without values', () => {
typeCheckPass(PropTypes.setOf(PropTypes.number), null);
typeCheckPass(PropTypes.setOf(PropTypes.number), undefined);
});

it('should warn for missing required values', () => {
typeCheckFailRequiredValues(
PropTypes.setOf(PropTypes.number).isRequired,
);
});

it('should warn if called manually in development', () => {
spyOn(console, 'error');
expectThrowsInDevelopment(PropTypes.setOf({foo: PropTypes.string}), {
foo: 'bar',
});
expectThrowsInDevelopment(PropTypes.setOf(PropTypes.number), new Set([
1,
2,
'b',
]));
expectThrowsInDevelopment(PropTypes.setOf(PropTypes.number), {
'0': 'maybe-array',
length: 1,
});
expectThrowsInDevelopment(
PropTypes.setOf(PropTypes.number).isRequired,
null,
);
expectThrowsInDevelopment(
PropTypes.setOf(PropTypes.number).isRequired,
undefined,
);
});
});


describe('Symbol Type', () => {
it('should warn for non-symbol', () => {
typeCheckFail(
Expand Down
2 changes: 2 additions & 0 deletions factoryWithThrowingShims.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ module.exports = function() {
oneOf: getShim,
oneOfType: getShim,
shape: getShim,
set: shim,
setOf: getShim,
exact: getShim
};

Expand Down
51 changes: 51 additions & 0 deletions factoryWithTypeCheckers.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
oneOf: createEnumTypeChecker,
oneOfType: createUnionTypeChecker,
shape: createShapeTypeChecker,
set: createSetTypeChecker(),
setOf: createSetOfTypeChecker,
exact: createStrictShapeTypeChecker,
};

Expand Down Expand Up @@ -379,6 +381,52 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
return createChainableTypeChecker(validate);
}

function createSetTypeChecker(expectedType) {
function validate(props, propName, componentName, location, propFullName, secret) {
var propValue = props[propName];
if (!(propValue instanceof Set)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed, this should module-level-cache the getter for Set.prototype.size, call-bind it, and invoke it here.

var propType = getPropType(propValue);

return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a set.'));
}
return null;
}
return createChainableTypeChecker(validate);
}

function createSetOfTypeChecker(typeChecker) {
function validate(props, propName, componentName, location, propFullName) {
if (typeof typeChecker !== 'function') {
return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside setOf.');
}
var propValue = props[propName];
if (!(propValue instanceof Set)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here

var propType = getPropType(propValue);
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a set.'));
}
// Check the types of the values inside the set using forEach(), which has broader support than values().
var insideValidateResult = null;
var insideValidateContainer = {};
var insideLocation = 'inside ' + propFullName;
propValue.forEach(function checkValue(value) {
if (null !== insideValidateResult) {
return;
}
insideValidateContainer.value = value;
var error = typeChecker(insideValidateContainer, 'value', componentName, 'value', insideLocation, ReactPropTypesSecret);
if (error instanceof Error) {
// If the error contains the inside location ("inside someSet") in quotes, remove those quotes.
if (error instanceof PropTypeError && error.message.includes('`' + insideLocation + '`')) {
error = new PropTypeError(error.message.replace('`' + insideLocation + '`', insideLocation));
}
insideValidateResult = error;
}
});
return insideValidateResult;
}
return createChainableTypeChecker(validate);
}

function createStrictShapeTypeChecker(shapeTypes) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
Expand Down Expand Up @@ -505,6 +553,8 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
return 'date';
} else if (propValue instanceof RegExp) {
return 'regexp';
} else if (propValue instanceof Set) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

return 'set';
}
}
return propType;
Expand All @@ -521,6 +571,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
case 'boolean':
case 'date':
case 'regexp':
case 'set':
return 'a ' + type;
default:
return type;
Expand Down