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

[new] add PropTypes.literal #396

Closed
wants to merge 1 commit into from
Closed
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ MyComponent.propTypes = {
// JS's instanceof operator.
optionalMessage: PropTypes.instanceOf(Message),

// A specific value
optionalLiteral: PropTypes.literal('value'),

// You can ensure that your prop is limited to specific values by treating
// it as an enum.
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
Expand Down
55 changes: 55 additions & 0 deletions __tests__/PropTypesDevelopmentReact15.js
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,61 @@ describe('PropTypesDevelopmentReact15', () => {
});
});

describe('Literal Types', () => {
it('should warn for invalid values', () => {
const personName = 'Andrew';

typeCheckFail(
PropTypes.literal(personName),
'bar',
'Invalid prop `testProp` with value `"bar"` supplied to ' +
'`testComponent`, expected `"' +
personName +
'"`.',
);
typeCheckFail(
PropTypes.literal(personName),
5,
'Invalid prop `testProp` with value `5` supplied to ' +
'`testComponent`, expected `"' +
personName +
'"`.',
);
typeCheckFail(
PropTypes.literal(personName),
{},
'Invalid prop `testProp` with value `{}` supplied to ' +
'`testComponent`, expected `"' +
personName +
'"`.',
);
});

it('should not warn for valid values', () => {
const literal = 'foo';

typeCheckPass(PropTypes.literal('foo'), 'foo');
typeCheckPass(PropTypes.literal(5), 5);
});

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

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

it('should warn if called manually in development', () => {
spyOn(console, 'error');
expectWarningInDevelopment(PropTypes.literal('foo'), 'bar');
expectWarningInDevelopment(PropTypes.literal('foo'), 5);
expectWarningInDevelopment(PropTypes.literal('foo').isRequired, 'bar');
expectWarningInDevelopment(PropTypes.literal('foo').isRequired, 5);
});
});

describe('React Component Types', () => {
it('should warn for invalid values', () => {
const failMessage = 'Invalid prop `testProp` supplied to ' +
Expand Down
55 changes: 55 additions & 0 deletions __tests__/PropTypesDevelopmentStandalone-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,61 @@ describe('PropTypesDevelopmentStandalone', () => {
});
});

describe('Literal Types', () => {
it('should warn for invalid values', () => {
const personName = 'Andrew';

typeCheckFail(
PropTypes.literal(personName),
'bar',
'Invalid prop `testProp` with value `"bar"` supplied to ' +
'`testComponent`, expected `"' +
personName +
'"`.',
);
typeCheckFail(
PropTypes.literal(personName),
5,
'Invalid prop `testProp` with value `5` supplied to ' +
'`testComponent`, expected `"' +
personName +
'"`.',
);
typeCheckFail(
PropTypes.literal(personName),
{},
'Invalid prop `testProp` with value `{}` supplied to ' +
'`testComponent`, expected `"' +
personName +
'"`.',
);
});

it('should not warn for valid values', () => {
const literal = 'foo';

typeCheckPass(PropTypes.literal('foo'), 'foo');
typeCheckPass(PropTypes.literal(5), 5);
});

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

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

it('should warn if called manually in development', () => {
spyOn(console, 'error');
expectThrowsInDevelopment(PropTypes.literal('foo'), 'bar');
expectThrowsInDevelopment(PropTypes.literal('foo'), 5);
expectThrowsInDevelopment(PropTypes.literal('foo').isRequired, 'bar');
expectThrowsInDevelopment(PropTypes.literal('foo').isRequired, 5);
});
});

describe('React Component Types', () => {
it('should warn for invalid values', () => {
const failMessage = 'Invalid prop `testProp` supplied to ' +
Expand Down
34 changes: 30 additions & 4 deletions __tests__/PropTypesProductionReact15-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -480,11 +480,37 @@ describe('PropTypesProductionReact15', () => {

it('should warn if called manually in development', () => {
spyOn(console, 'error');
expectNoop(PropTypes.instanceOf(Date), {});
expectNoop(PropTypes.instanceOf(Date), new Date());
expectNoop(PropTypes.instanceOf(Date).isRequired, {});
expectNoop(PropTypes.literal('foo'), 'bar');
expectNoop(PropTypes.literal('foo'), 5);
expectNoop(PropTypes.literal('foo').isRequired, 'bar');
expectNoop(PropTypes.literal('foo').isRequired, 5);
});
});

describe('Literal Types', () => {
it('should not warn for valid values', () => {
const literal = 'foo';

expectNoop(PropTypes.literal('foo'), 'foo');
expectNoop(PropTypes.literal(5), 5);
});

it('should be implicitly optional and not warn without values', () => {
expectNoop(PropTypes.literal('foo'), null);
expectNoop(PropTypes.literal(5), undefined);
});

it('should warn for missing required values', () => {
expectNoop(PropTypes.literal('foo').isRequired);
});

it('should warn if called manually in development', () => {
spyOn(console, 'error');
expectNoop(PropTypes.literal('foo'), {});
expectNoop(PropTypes.literal('foo'), new Date());
expectNoop(PropTypes.literal('foo').isRequired, {});
expectNoop(
PropTypes.instanceOf(Date).isRequired,
PropTypes.literal('foo').isRequired,
new Date(),
);
});
Expand Down
4 changes: 2 additions & 2 deletions checkPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ function checkPropTypes(typeSpecs, values, location, componentName, getStack) {
location + ' `' + typeSpecName + '` is invalid; the type checker ' +
'function must return `null` or an `Error` but returned a ' + typeof error + '. ' +
'You may have forgotten to pass an argument to the type checker ' +
'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' +
'shape all require an argument).'
'creator (arrayOf, instanceOf, literal, objectOf, oneOf, oneOfType, ' +
'and shape all require an argument).'
);
}
if (error instanceof Error && !(error.message in loggedTypeFailures)) {
Expand Down
1 change: 1 addition & 0 deletions factoryWithThrowingShims.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ module.exports = function() {
element: shim,
elementType: shim,
instanceOf: getShim,
literal: getShim,
node: shim,
objectOf: getShim,
oneOf: getShim,
Expand Down
12 changes: 12 additions & 0 deletions factoryWithTypeCheckers.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
element: createElementTypeChecker(),
elementType: createElementTypeTypeChecker(),
instanceOf: createInstanceTypeChecker,
literal: createLiteralTypeChecker,
node: createNodeChecker(),
objectOf: createObjectOfTypeChecker,
oneOf: createEnumTypeChecker,
Expand Down Expand Up @@ -307,6 +308,17 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
return createChainableTypeChecker(validate);
}

function createLiteralTypeChecker(expectedValue) {
function validate(props, propName, componentName, location, propFullName) {
if (props[propName] !== expectedValue) {
var actualValue = props[propName];
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` with value ' + ('`' + JSON.stringify(actualValue) + '` supplied to `' + componentName + '`, expected ') + ('`' + JSON.stringify(expectedValue) + '`.'));
}
return null;
}
return createChainableTypeChecker(validate);
}

function createEnumTypeChecker(expectedValues) {
if (!Array.isArray(expectedValues)) {
if (process.env.NODE_ENV !== 'production') {
Expand Down