Skip to content

Commit

Permalink
[new] add PropTypes.literal
Browse files Browse the repository at this point in the history
  • Loading branch information
wojtekmaj committed Apr 19, 2023
1 parent aee5d72 commit 713634c
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 7 deletions.
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@
"transform": [
"loose-envify"
]
}
},
"packageManager": "yarn@1.22.19"
}

0 comments on commit 713634c

Please sign in to comment.