diff --git a/index.js b/index.js
index 0d6f44e..2de0d85 100644
--- a/index.js
+++ b/index.js
@@ -3,13 +3,17 @@ import Tester from './src/Tester';
import TestHookStore from './src/TestHookStore';
import useCavy from './src/useCavy';
import wrap from './src/wrap';
+import cavyCreateElement, { setJSXConfig, Fragment } from './src/cavyCreateElement';
const Cavy = {
hook,
Tester,
TestHookStore,
useCavy,
- wrap
+ wrap,
+ cavyCreateElement,
+ setJSXConfig,
+ Fragment
};
module.exports = Cavy;
diff --git a/sample-app/CavyDirectory/babel.config.js b/sample-app/CavyDirectory/babel.config.js
index f842b77..d05c847 100644
--- a/sample-app/CavyDirectory/babel.config.js
+++ b/sample-app/CavyDirectory/babel.config.js
@@ -1,3 +1,15 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
+ plugins: [
+ [ '@wordpress/babel-plugin-import-jsx-pragma', {
+ scopeVariable: 'cavyCreateElement',
+ scopeVariableFrag: 'Fragment',
+ source: 'cavy',
+ isDefault: false,
+ } ],
+ [ '@babel/plugin-transform-react-jsx', {
+ pragma: 'cavyCreateElement',
+ pragmaFrag: 'Fragment',
+ } ],
+ ]
};
diff --git a/sample-app/CavyDirectory/index.js b/sample-app/CavyDirectory/index.js
index 747718b..99b7dc1 100644
--- a/sample-app/CavyDirectory/index.js
+++ b/sample-app/CavyDirectory/index.js
@@ -6,7 +6,7 @@ import React, { Component } from 'react';
import {AppRegistry} from 'react-native';
import {name as appName} from './app.json';
-import { Tester, TestHookStore } from 'cavy';
+import { Tester, TestHookStore, setJSXConfig } from 'cavy';
import EmployeeDirectoryApp from './app/EmployeeDirectoryApp';
@@ -14,6 +14,11 @@ import EmployeeListSpec from './specs/EmployeeListSpec';
const testHookStore = new TestHookStore();
+setJSXConfig({
+ testHookStore,
+ cavyIdPropName: 'yourPropNameForCavyId'
+});
+
class AppWrapper extends Component {
render() {
return (
diff --git a/sample-app/CavyDirectory/package.json b/sample-app/CavyDirectory/package.json
index 8149373..150e108 100644
--- a/sample-app/CavyDirectory/package.json
+++ b/sample-app/CavyDirectory/package.json
@@ -15,6 +15,8 @@
"devDependencies": {
"@babel/core": "^7.6.2",
"@babel/runtime": "^7.6.2",
+ "@babel/plugin-transform-react-jsx": "^7.9.1",
+ "@wordpress/babel-plugin-import-jsx-pragma": "^2.5.0",
"babel-jest": "^24.9.0",
"jest": "^24.9.0",
"metro-react-native-babel-preset": "^0.56.0",
diff --git a/src/cavyCreateElement.js b/src/cavyCreateElement.js
new file mode 100644
index 0000000..762382e
--- /dev/null
+++ b/src/cavyCreateElement.js
@@ -0,0 +1,104 @@
+import React from 'react';
+import cavy from "./generateTestHook";
+import wrap, {isNotReactClass, } from "./wrap";
+
+// Public: Drop in replacement for React.createElement that let cavy
+// to hook into element creation reducing normal code impact
+// directly collecting component refs when the cavyIdProp is set.
+// It automatically wraps function components to add forwardRef
+//
+// If you configure this in you project the code goes from this:
+//
+// import React from 'react';
+// import { View, Text } from 'react-native';
+// import { useCavy, wrap } from 'cavy';
+//
+// export default ({ data }) => {
+// const generateTestHook = useCavy();
+// const TestableText = wrap(Text);
+//
+// return (
+//
+//
+// {data.title}
+//
+//
+// )
+// };
+//
+// to this:
+//
+// import React from 'react';
+// import { View, Text } from 'react-native';
+//
+// export default ({ data }) => {//
+// return (
+//
+//
+// {data.title}
+//
+//
+// )
+// };
+//
+// Eve if the configuration is a bit invasive (replace of jsx transpilation)
+// it is simple and keeps you src code more free from cavy implementation
+//
+
+// Configuration of jsx element creation
+let pragmaConfig = {
+ testHookStore: null,
+ cavyIdPropName: 'cavyTestId'
+};
+
+// Should be called at the startup of you index.test.js
+//import { setJSXConfig } from 'cavy-jsx';
+//
+// const testHookStore = new TestHookStore();
+//
+// setJSXConfig({
+// testHookStore,
+// cavyIdPropName: 'yourPropNameForCavyId'
+// });
+//
+export function setJSXConfig(userConfig) {
+ if(!userConfig || !userConfig.testHookStore){
+ throw new Error(`You must set testHookStore in pragma config`);
+ }
+ pragmaConfig = {...pragmaConfig, ...userConfig};
+}
+
+//
+// Simple wrapper of React.createElement that handles element with [cavyIdPropName]
+// wrapping function component and collecting its reference in cavy
+//
+export default function cavyCreateElement(componentType, props, children) {
+
+ // Check it the jsx config has been set
+ if(!pragmaConfig || !pragmaConfig.testHookStore || pragmaConfig.cavyIdPropName){
+ throw new Error('Configure JSX pragma before using it');
+ }
+ // Handle only elements with [cavyIdPropName] set
+ if (pragmaConfig.cavyIdPropName in props) {
+ const generateTestHook = cavy(pragmaConfig.testHookStore);
+ let WrappedType = componentType;
+ // Auto wraps function components
+ if (typeof componentType === 'function' && isNotReactClass(componentType)) {
+ WrappedType = wrap(componentType);
+ }
+ let newProps;
+ if( 'ref' in props){
+ // If the ref is already set keep it and ads identifier to cavy
+ newProps = { ...props, ref: generateTestHook(props[pragmaConfig.cavyIdPropName], props.ref) };
+ }else {
+ // Otherwise just add identifier
+ newProps = { ...props, ref: generateTestHook(props[pragmaConfig.cavyIdPropName]) };
+ }
+ return React.createElement(WrappedType, newProps, children);
+ }
+
+ return React.createElement.apply(undefined, arguments);
+}
+
+// JSX replacement must also contains fragment
+export const Fragment = React.Fragment;
diff --git a/src/wrap.js b/src/wrap.js
index d40bf30..bc3333c 100644
--- a/src/wrap.js
+++ b/src/wrap.js
@@ -96,7 +96,7 @@ export default function wrap(Component) {
// checks here. This code is taken from examples in React source code e.g:
//
// https://github.com/facebook/react/blob/12be8938a5d71ffdc21ee7cf770bf1cb63ae038e/packages/react-refresh/src/ReactFreshRuntime.js#L138
-function isNotReactClass(Component) {
+export function isNotReactClass(Component) {
return !(Component.prototype && Component.prototype.isReactComponent);
}