# src/components.test.ts

    import * as jsonx from './index';
    import * as _jsonxComponents from './components';
    import mochaJSDOM from 'jsdom-global';
    import chai from 'chai';
    import sinon from 'sinon';
    import React from 'react';
    import ReactTestUtils from 'react-dom/test-utils'; // ES6
    import ReactDOM from 'react-dom';
    import ReactDOMElements from 'react-dom-factories';
    import { expect, } from 'chai';
    import { JSDOM, } from 'jsdom';
    chai.use(require('sinon-chai'));
    // import 'mocha-sinon';

    const sampleJSONX = {
      component: 'div',
      props: {
        id: 'generatedJSONX',
        className:'jsonx',
      },
      children: 'some div',
    };

    const sampleCustomElementJSONX = {
      component: 'div',
      props: {
        id: 'customJSONX',
        className:'jsonx',
      },
      thisprops: {
        title: ['extraname', ],
      },
      children: [
        {
          component: 'p',
          children:'some text',
        },
        {
          component: 'Welcome',
          props: {
            style: {
              color: 'red',
              fontWeight:'bold',
            },
            name:'fromCustom',
          },
          thisprops: {
            title: ['elementProperties', 'title', ],
          },
          children:'hello customElement2',
        },
        {
          component: 'WelcomeBindSpy',
          props: {
            style: {
              color: 'red',
              fontWeight:'bold',
            },
            name:'fromCustom',
          },
          thisprops: {
            title: ['elementProperties', 'title', ],
          },
          children:'hello customElement2',
        },
        {
          component: 'WelcomeNonBind',
          props: {
            style: {
              color: 'red',
              fontWeight:'bold',
            },
            name:'fromCustom',
          },
          thisprops: {
            title: ['elementProperties', 'title', ],
          },
          children:'hello customElement2',
        },
      ],
    };

    class Welcome extends React.Component {
      render() {
        return React.createElement('h1', { name: 'Welcome', }, `Hello, ${this.props.name} ${this.props.title||'NA'}`);
      }
    }

    class WelcomeBindSpy extends React.Component {
      render() {
        return React.createElement('h1', { name: 'Welcome', }, `Hello, ${this.props.name} ${this.props.title||'NA'}`);
      }
    }

    class WelcomeNonBind extends React.Component {
      render() {
        return React.createElement('h1', { name: 'Welcome', }, `Hello, ${this.props.name} ${this.props.title||'NA'}`);
      }
    }

    describe('jsonx components', function () {
      describe('advancedBinding', () => {
        it('should use advancedBinding based on user agent', () => {
          expect(_jsonxComponents.advancedBinding).to.be.true;
        });
      });
      describe('componentMap', () => {
        it('should export an object of components', () => {
          expect(_jsonxComponents.componentMap).to.be.a('object');
        });
        // it('should export an object of components', () => {
        //   global.window = {
        //     __jsonx_custom_elements: {
        //       cusEl: {},
        //     },
        //   };
        //   const comps = _jsonxComponents.componentMap;
        //   console.log({ comps });
        //   expect(_jsonxComponents.componentMap.cusEl).to.eql(global.window.__jsonx_custom_elements.cusEl);
        // });
      });
      describe('getBoundedComponents', () => {
        it('should bind this to reactComponents', () => {
          const bindSpy = sinon.spy();
          WelcomeBindSpy.bind = bindSpy;
          const reactComponents = {
            Welcome,
            WelcomeNonBind,
            WelcomeBindSpy,
          };
          const boundedComponents = ['Welcome', 'WelcomeBindSpy', ];
          const customThis = {
            props: {
              name:'customElementTest',
              extraname: 'customElementTestName',
              elementProperties: {
                title: 'AddedWithThis',
              },
            },
            boundedComponents,
            reactComponents,
            debug: false,
            returnJSON: true,
            logError:()=>{},
          };
          const customComponents = _jsonxComponents.getBoundedComponents.call(customThis,{ reactComponents, boundedComponents, advancedBinding:true, });

          const JSONXPropCheck = jsonx.getRenderedJSON.call(customThis, sampleCustomElementJSONX);

          expect(bindSpy.called).to.be.true;
          expect(JSONXPropCheck.props.title).to.eql(customThis.props.extraname);
          expect(customComponents.length).to.eql(reactComponents.length);
        });
      });
      describe('getComponentFromMap', () => {
        const reactBootstrap = {
          Welcome,
          WelcomeNonBind,
        };
        const componentLibraries = {
          reactBootstrap,
        };
        it('should return a function if jsonx.component is not a string', () => {
          expect(_jsonxComponents.getComponentFromMap({
            jsonx: {
              component:Welcome,
            }, })).to.be.a('function').and.to.eql(Welcome);
        });
        it('should return the dom element string if a valid DOM elmenet in ReactDOM', () => {
          ['div', 'span', 'p', 'section', ].forEach(el => {
            const jsonxObj = { jsonx: { component: el, }, };
            expect(_jsonxComponents.getComponentFromMap(jsonxObj)).to.eql(el);
          });
        });
        it('should return a custom element', () => {
          const jsonxObj = {
            jsonx: {
              component: 'Welcome',
            },
            reactComponents: {
              Welcome,
            },
          };
          expect(_jsonxComponents.getComponentFromMap(jsonxObj)).to.eql(Welcome);
        });
        it('should return a component library react element', () => {
          const jsonxObj = {
            jsonx: {
              component: 'reactBootstrap.Welcome',
            },
            componentLibraries,
          };
          expect(_jsonxComponents.getComponentFromMap(jsonxObj)).to.eql(Welcome);
        });
        it('should handle errors', () => {
          const logError = sinon.spy();
          expect(_jsonxComponents.getComponentFromMap.bind(null)).to.throw();
          try {
            _jsonxComponents.getComponentFromMap({ debug: true, logError, jsonx:false, });
          } catch (e) {
            expect(e).to.be.a('error');
            expect(logError.called).to.be.true;
          }
        });
      });
      describe('getComponentFromLibrary', () => {
        const reactBootstrap = {
          Welcome,
          WelcomeNonBind,
        };
        const componentLibraries = {
          reactBootstrap,
          testLib: {
            testGrouping: {
              testComponent: {},
            },
          },
        };
        it('should return undefined if not valid', () => {
          expect(_jsonxComponents.getComponentFromLibrary()).to.be.undefined;
        });
        it('should return a function if selecting valid component library', () => {
          const jsonxObj = {
            jsonx: {
              component: 'reactBootstrap.Welcome',
            },
            componentLibraries,
          };
          expect(_jsonxComponents.getComponentFromLibrary(jsonxObj)).to.be.eql(Welcome);
          const jsonxObjDeep = {
            jsonx: {
              component: 'testLib.testGrouping.testComponent',
            },
            componentLibraries,
          };
          expect(_jsonxComponents.getComponentFromLibrary(jsonxObjDeep)).to.be.eql(componentLibraries.testLib.testGrouping.testComponent);

        });
      });
      describe('componentMap', () => {
        // before(function () {
        //   this.jsdom = mochaJSDOM();
        // });
        // it('should accept components from a window property', function (done) {
        //   window.__jsonx_custom_elements = {
        //     Welcome,
        //     WelcomeNonBind,
        //     WelcomeBindSpy,
        //   };
        //   Object.defineProperty(window, '__jsonx_custom_elements', {
        //     value: {
        //       Welcome,
        //       WelcomeNonBind,
        //       WelcomeBindSpy,
        //     },
        //   });

        //   // delete require.cache[ require.resolve('../../dist/jsonx.cjs') ];
        //   import('./index')
        //     .then(jsonxModule => {
        //       const window_test_jsonx = jsonxModule;
        //       console.log('window.__jsonx_custom_elements', window.__jsonx_custom_elements);

        //       expect(window_test_jsonx._jsonxComponents.componentMap).to.haveOwnProperty('Welcome');
        //       expect(window_test_jsonx._jsonxComponents.componentMap).to.haveOwnProperty('WelcomeNonBind');
        //       expect(window_test_jsonx._jsonxComponents.componentMap).to.haveOwnProperty('WelcomeBindSpy');
        //       done();
        //     })
        //     .catch(done);
        // });
        // after(function () {
        //   this.jsdom();
        // });
      });
      describe('getFunctionFromEval', () => {
        const getFunctionFromEval = _jsonxComponents.getFunctionFromEval;
        it('should return a new function', () => {
          const myFunc = getFunctionFromEval({
            body: 'return 3;',
          });
          expect(myFunc()).to.eql(3);
        });
      });
      describe('getReactClassComponent', () => {
        const getReactClassComponent = _jsonxComponents.getReactClassComponent;
        const classBody = {
          componentDidMount: {
            body: 'console.log(\'mounted\',this.props)',
            arguments: [],
          },
          render: {
            body: {
              component: 'p',
              children: [
                {
                  component: 'span',
                  children: 'My Custom React Component Status: ',
                },
                {
                  component: 'span',
                  thisprops: {
                    children: ['status',],
                  },
                },
              ],
            },
          },
        };
        it('should create a React Component', () => {
          const MyCustomComponent = getReactClassComponent(classBody);
          const MyCustomComponentClass = getReactClassComponent(classBody, { returnFactory:false, });
          // const MyCustomComponentFactory = getReactClassComponent(classBody);
          // console.log({MyCustomComponentClass});
          expect(MyCustomComponent).to.be.a('function');
          expect(MyCustomComponentClass).to.be.a('function');
          expect(ReactTestUtils.isElement(MyCustomComponent)).to.be.false;
          // expect(ReactTestUtils.isCompositeComponent(MyCustomComponentClass())).to.be.true;
        });
        it('should allow for functions as object props', () => {
          const classBodyOpts = Object.assign({}, classBody);
          classBodyOpts.componentDidMount = function () {
            console.log('mounted!');
          };
          expect(getReactClassComponent.bind(null, classBodyOpts)).to.not.throw;
        });
        it('should allow for custom class names', () => {
          const MyCustomComponentNameClass = getReactClassComponent(classBody, '', { name: 'myClass', });
          expect(MyCustomComponentNameClass).to.be.a('function');
        });
        it('should throw an error if missing a render function', () => {
          expect(getReactClassComponent.bind()).to.throw;
        });
        it('should throw an error if missing a function is missing a body', () => {
          expect(getReactClassComponent.bind({ render: {}, })).to.throw;
        });
        it('should create suspense/lazy components', () => {
          const MyCustomLazyComponent = getReactClassComponent(
            {
              component: 'p',
              children: [
                {
                  component: 'span',
                  children: 'My Custom React Component Status: ',
                },
                {
                  component: 'span',
                  thisprops: {
                    children: ['status',],
                  },
                },
              ],
            },
            {
              name: 'myComp',
              lazy: (comp, options) => {
                return new Promise((resolve) => {
                  setTimeout(() => {
                    resolve([
                      comp, Object.assign(options, { lazy: false, }),]);
                  }, 3000);
                });
              },
            },
          );

          expect(MyCustomLazyComponent).to.be.a('object');
        });
      });
      describe('getReactFunctionComponent', () => {
        const getReactFunctionComponent = _jsonxComponents.getReactFunctionComponent;
        it('should react a React Function Component', () => {
          const MyCustomComponentNameless = getReactFunctionComponent(
            {
              component:'p',
              children:'hello',
            },
            'console.log("lazy function body");',
            { },
          );
          expect(MyCustomComponentNameless.name).to.eql('Anonymous');
          expect(MyCustomComponentNameless).to.be.a('function');
        });
        it('should create a React Function Component with a name', () => {
          const MyCustomComponent = getReactFunctionComponent(
            {
              component:'p',
              children:[
                {
                  component:'span',
                  children: 'My Custom React Component Status: ',
                },
                {
                  component:'span',
                  thisprops:{
                    children:['status', ],
                  },
                },
              ],
            },
            'console.log("lazy function body");',
            { name:'myComp', },
          );
          expect(MyCustomComponent.name).to.eql('myComp');
          expect(MyCustomComponent).to.be.a('function');
        });
        it('should create suspense/lazy components', () => {
          const MyCustomLazyComponent = getReactFunctionComponent(
            {
              component: 'p',
              children: [
                {
                  component: 'span',
                  children: 'My Custom React Component Status: ',
                },
                {
                  component: 'span',
                  thisprops: {
                    children: ['status',],
                  },
                },
              ],
            },
            'console.log("lazy function body");',
            {
              name: 'myComp',
              lazy: (comp, options) => {
                return new Promise((resolve) => {
                  setTimeout(() => {
                    resolve([
                      comp, Object.assign(options, { lazy: false, }),]);
                  }, 3000);
                });
              },
            },
          );

          expect(MyCustomLazyComponent).to.be.a('object');
        });
      });
      describe('getReactContext', () => {
        const getReactContext = _jsonxComponents.getReactContext;
        it('should return a React Context Object', () => {
          const context = getReactContext({ some: 'c', });
          expect(ReactTestUtils.isElement(context)).to.be.false;
          // expect(context).to.be.an.instanceOf(React.createContext);
        });
      });
    });