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

Environment testers #291

Draft
wants to merge 3 commits 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
7 changes: 7 additions & 0 deletions .editorconfig
@@ -0,0 +1,7 @@
[*]
end_of_line = lf
insert_final_newline = true

[*.{js,json}]
indent_style = space
indent_size = 2
19 changes: 17 additions & 2 deletions .eslintrc
Expand Up @@ -3,13 +3,28 @@
"parserOptions": {
"ecmaVersion": 5,
},
"env": {
"node": true
},
"extends": "eslint:recommended",
"rules": {
"no-console": "off",
},
"overrides": [
{
"files": "**/__tests__/**/*",
"env": {
"jest": true,
"jasmine": true,
"es6": true
},
"rules": {
"no-unused-vars": "off"
},
"parserOptions": {
"ecmaVersion": "2019",
"ecmaVersion": 2019,
"ecmaFeatures": {
"jsx": true,
"jsx": true
}
},
},
Expand Down
Expand Up @@ -54,7 +54,7 @@ function typeCheckFailRequiredValues(declaration) {
const specifiedButIsNullMsg = 'The prop `testProp` is marked as required in ' +
'`testComponent`, but its value is `null`.';
const unspecifiedMsg = 'The prop `testProp` is marked as required in ' +
'`testComponent`, but its value is \`undefined\`.';
'`testComponent`, but its value is `undefined`.';

const propTypes = {testProp: declaration};

Expand Down Expand Up @@ -1289,7 +1289,7 @@ describe('PropTypesDevelopmentReact15', () => {
{key: 1},
'Warning: Failed prop type: Invalid prop `testProp` key `key` supplied to `testComponent`.' +
'\nBad object: {' +
'\n \"key\": 1' +
'\n "key": 1' +
'\n}' +
'\nValid keys: []'
);
Expand Down
2 changes: 1 addition & 1 deletion __tests__/PropTypesDevelopmentStandalone-test.js
Expand Up @@ -54,7 +54,7 @@ function typeCheckFailRequiredValues(declaration) {
const specifiedButIsNullMsg = 'The prop `testProp` is marked as required in ' +
'`testComponent`, but its value is `null`.';
const unspecifiedMsg = 'The prop `testProp` is marked as required in ' +
'`testComponent`, but its value is \`undefined\`.';
'`testComponent`, but its value is `undefined`.';

const propTypes = {testProp: declaration};

Expand Down
4 changes: 2 additions & 2 deletions __tests__/PropTypesProductionReact15-test.js
Expand Up @@ -505,7 +505,7 @@ describe('PropTypesProductionReact15', () => {
};
const iterable = {
'@@iterator': function() {
const i = 0;
let i = 0;
return {
next: function() {
const done = ++i > 2;
Expand All @@ -525,7 +525,7 @@ describe('PropTypesProductionReact15', () => {
};
const iterable = {
'@@iterator': function() {
const i = 0;
let i = 0;
return {
next: function() {
const done = ++i > 2;
Expand Down
125 changes: 125 additions & 0 deletions __tests__/arrayOf-test.js
@@ -0,0 +1,125 @@
const testInAllEnvironments = require('./environmentsTesters');

function makeTests({ getModules, expectPass, expectFail, expectFailRequiredValues, expectWarning }) {
let React;
let PropTypes;

beforeEach(() => {
({React, PropTypes} = getModules());
})

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

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

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

function Thing() { }
expectPass(PropTypes.arrayOf(PropTypes.instanceOf(Thing)), [
new Thing(),
new Thing(),
]);
});

it('should warn with invalid items in the array', () => {
expectFail(
PropTypes.arrayOf(PropTypes.number),
[1, 2, 'b'],
'Invalid prop `testProp[2]` of type `string` supplied to ' +
'`testComponent`, expected `number`.',
);
});

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

expectFail(
PropTypes.arrayOf(PropTypes.instanceOf(Thing)),
[new Thing(), 'xyz'],
'Invalid prop `testProp[1]` of type `String` supplied to ' +
'`testComponent`, expected instance of `' +
name +
'`.',
);
});

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

it('should not warn when passing an empty array', () => {
expectPass(PropTypes.arrayOf(PropTypes.number), []);
});

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

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

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

testInAllEnvironments(makeTests)
119 changes: 119 additions & 0 deletions __tests__/environmentsTesters/React15-dev.js
@@ -0,0 +1,119 @@
function makeEnvironmentsHelpers(makeTests) {
let React;
let PropTypes;

function resetWarningCache() {
jest.resetModules();

React = require('react');
PropTypes = require('../../factory')(React.isValidElement);
}

function getPropTypeWarningMessage(propTypes, object, componentName) {
if (!console.error.calls) {
spyOn(console, 'error');
} else {
console.error.calls.reset();
}
resetWarningCache();

PropTypes.checkPropTypes(propTypes, object, 'prop', componentName);
const callCount = console.error.calls.count();
if (callCount > 1) {
throw new Error('Too many warnings.');
}
const message = console.error.calls.argsFor(0)[0] || null;
console.error.calls.reset();

return message;
}

describe("React 15 dev", () => {
beforeEach(() => {
resetWarningCache();
});

makeTests({
getModules() {
return { React, PropTypes };
},

expectPass(declaration, value) {
const propTypes = {
testProp: declaration,
};
const props = {
testProp: value,
};
const message = getPropTypeWarningMessage(propTypes, props, 'testComponent');
expect(message).toBe(null);
},

expectFail(declaration, value, expectedMessage) {
const propTypes = {
testProp: declaration,
};
const props = {
testProp: value,
};
const message = getPropTypeWarningMessage(propTypes, props, 'testComponent');
expect(message).toContain(expectedMessage);
},

expectFailRequiredValues(declaration) {
const specifiedButIsNullMsg = 'The prop `testProp` is marked as required in ' +
'`testComponent`, but its value is `null`.';
const unspecifiedMsg = 'The prop `testProp` is marked as required in ' +
'`testComponent`, but its value is `undefined`.';

const propTypes = { testProp: declaration };

// Required prop is null
const message1 = getPropTypeWarningMessage(
propTypes,
{ testProp: null },
'testComponent',
);
expect(message1).toContain(specifiedButIsNullMsg);

// Required prop is undefined
const message2 = getPropTypeWarningMessage(
propTypes,
{ testProp: undefined },
'testComponent',
);
expect(message2).toContain(unspecifiedMsg);

// Required prop is not a member of props object
const message3 = getPropTypeWarningMessage(propTypes, {}, 'testComponent');
expect(message3).toContain(unspecifiedMsg);
},

expectWarning(declaration, value) {
resetWarningCache();
const props = { testProp: value };
const propName = 'testProp' + Math.random().toString();
const componentName = 'testComponent' + Math.random().toString();
for (let i = 0; i < 3; i++) {
declaration(props, propName, componentName, 'prop');
}
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'You are manually calling a React.PropTypes validation ',
);
console.error.calls.reset();
},

expectInvalidValidatorWarning(declaration, type) {
PropTypes.checkPropTypes({ foo: declaration }, { foo: {} }, 'prop', 'testComponent', null);
expect(console.error.calls.argsFor(0)[0]).toEqual(
'Warning: Failed prop type: testComponent: prop type `foo.bar` is invalid; '
+ 'it must be a function, usually from the `prop-types` package, but received `' + type + '`.'
);
console.error.calls.reset();
}
});
});
}

module.exports = makeEnvironmentsHelpers;
66 changes: 66 additions & 0 deletions __tests__/environmentsTesters/React15-prod.js
@@ -0,0 +1,66 @@
function makeEnvironmentsHelpers(makeTests) {
let React;
let PropTypes;

function resetWarningCache() {
jest.resetModules();

React = require('react');
PropTypes = require('../../factory')(React.isValidElement);
}

function expectNoop(declaration, value) {
if (!console.error.calls) {
spyOn(console, 'error');
} else {
console.error.calls.reset();
}

const props = {testProp: value};
const propName = 'testProp' + Math.random().toString();
const componentName = 'testComponent' + Math.random().toString();
// Try calling it manually
for (let i = 0; i < 3; i++) {
declaration(props, propName, componentName, 'prop');
}
// Try calling it via checkPropTypes
const propTypes = {
testProp: declaration,
};
PropTypes.checkPropTypes(propTypes, props, 'prop', 'testComponent');

// They should all be no-ops
expect(console.error.calls.count()).toBe(0);
console.error.calls.reset();
}

describe("React 15 prod", () => {
beforeAll(function() {
process.env.NODE_ENV = 'production';
})

afterAll(function() {
delete process.env.NODE_ENV;
});

beforeEach(() => {
resetWarningCache();
});

makeTests({
getModules() {
return { React, PropTypes };
},

expectPass: expectNoop,
expectFail: expectNoop,
expectFailRequiredValues: expectNoop,
expectWarning: expectNoop,

expectInvalidValidatorWarning(declaration, type) {
}
});
})
}

module.exports = makeEnvironmentsHelpers;