<|start_filename|>test/ReactQmlRoot.js<|end_filename|>
'use strict';

var ReactQmlRoot = require('../src/ReactQmlRoot');
var assert = require('referee').assert;

describe('ReactQmlRoot', function () {

    afterEach(function () {
        ReactQmlRoot.root = null;
    });

    it('should be able to travers parents to the root', function () {
        var root = {};
        var l1 = { parent: root };
        var l2 = { parent: l1 };
        var l3 = { parent: l2 };
        ReactQmlRoot.updateRootFromContainer(l3);
        assert.same(ReactQmlRoot.root, root);
    });

});


<|start_filename|>src/ReactQml.js<|end_filename|>
'use strict';

var React = require('react');
var ReactQmlMount = require('./ReactQmlMount');
var ReactQmlQtQuick = require('./ReactQmlQtQuick');

// This makes sure that we use custom ReconcileTransaction that
// doesn't rely on DOM being available
var ReactUpdates = require('react/lib/ReactUpdates');
var ReactQmlReconcileTransaction = require('./ReactQmlReconcileTransaction');
ReactUpdates.injection.injectReconcileTransaction(ReactQmlReconcileTransaction);

module.exports = {
    // Qml components
    QtQuick: ReactQmlQtQuick,

    // Custom mounting
    render: ReactQmlMount.render,
    unmountComponentAtNode: ReactQmlMount.unmountComponentAtNode,

    // Proxying default React methods
    createFactory: React.createFactory,
    createClass: React.createClass,
    createElement: React.createElement,
    __spread: React.__spread
};


<|start_filename|>test/ReactQml.js<|end_filename|>
'use strict';

var React = require('../');
var assert = require('referee').assert;

var Qt = require('../support/Qt');

describe('ReactQml', function () {

    describe('interface', function () {

        it('should provide `render` method', function () {
            assert.isFunction(React.render);
        });

        it('should provide `unmountComponentAtNode`', function () {
            assert.isFunction(React.unmountComponentAtNode);
        });

    });

    describe('Mount', function () {
        var root;

        beforeEach(function () {
            root = new Qt.QtObject();
            root.objectName = 'QML_ROOT';
        });

        afterEach(function () {
            root.destroy();
            root = undefined;
        });

        it('should allow to render a ReactQmlComponent wrapped in a ReactCompositeComponent', function () {
            var Test = React.createClass({
                render: function () {
                    return React.createElement(React.QtQuick.Rectangle);
                }
            });
            React.render(React.createElement(Test), root);
            assert.match(root, {
                children: [{
                    _type: 'Rectangle',
                    children: []
                }]
            });
        });

        it('should allow to render a standalone ReactQmlComponent', function () {
            React.render(React.createElement(React.QtQuick.Rectangle), root);
            assert.match(root, {
                children: [{
                    _type: 'Rectangle',
                    children: {
                        length: 0
                    }
                }]
            });
        });

        it('should allow to unmount a ReactQmlComponent', function () {
            React.render(React.createElement(React.QtQuick.Rectangle), root);
            React.unmountComponentAtNode(root);
            assert.equals(root.children, []);
        });

        it('should allow to update properties by re-rendering a ReactQmlComponent', function () {
            React.render(React.createElement(React.QtQuick.Rectangle), root);
            React.render(React.createElement(React.QtQuick.Rectangle, {
                color: 'red'
            }), root);
            assert.match(root, {
                children: [{ color: 'red' }]
            });
        });

        it('should allow to unset properties by re-rendering a ReactQmlComponent', function () {
            React.render(React.createElement(React.QtQuick.Rectangle, {
                color: 'red'
            }), root);
            React.render(React.createElement(React.QtQuick.Rectangle), root);
            assert.match(root, {
                children: [{ color: undefined }]
            });
        });

        it('should allow render nested ReactQmlComponents', function () {
            React.render(React.createElement(React.QtQuick.Rectangle, null,
                React.createElement(React.QtQuick.Rectangle, null,
                    React.createElement(React.QtQuick.Rectangle)
                )
            ), root);
            assert.match(root.children, [{
                    children: [{
                        children: [{
                            children: { length: 0 }
                        }]
                    }]
            }]);
        });

        it('should allow to add children by re-rendering a ReactQmlComponent', function () {
            React.render(React.createElement(React.QtQuick.Rectangle), root);
            React.render(React.createElement(React.QtQuick.Rectangle, null,
                React.createElement(React.QtQuick.Rectangle)
            ), root);
            assert.match(root.children, [{
                children: [{
                    children: { length: 0 }
                }]
            }]);
        });

        it('should allow to remove children by re-rendering a ReactQmlComponent', function () {
            React.render(React.createElement(React.QtQuick.Rectangle, null,
                React.createElement(React.QtQuick.Rectangle, { key: 'one' }),
                React.createElement(React.QtQuick.Rectangle, { key: 'two' })
            ), root);
            React.render(React.createElement(React.QtQuick.Rectangle, null,
                React.createElement(React.QtQuick.Rectangle, { key: 'two' })
            ), root);
            assert.match(root.children, [{
                children: { 0: { objectName: 'two' }, length: 1 }
            }]);
        });


    });

});


<|start_filename|>src/ReactQmlMount.js<|end_filename|>
'use strict';

var React = require('react');
var ReactReconciler = require('react/lib/ReactReconciler');
var ReactUpdates = require('react/lib/ReactUpdates');
var ReactUpdateQueue = require('react/lib/ReactUpdateQueue');
var shouldUpdateReactComponent = require('react/lib/shouldUpdateReactComponent');
var instantiateReactComponent = require('react/lib/instantiateReactComponent');

var ReactQmlRoot = require('./ReactQmlRoot');
var ReactQmlComponent = require('./ReactQmlComponent');
var ReactQmlComponentCompositeWrapper = React.createClass({
    render: function () {
        return this.props.children;
    }
});

/**
 * Mounts this component and inserts it into the QML Document.
 *
 * @param {ReactComponent} componentInstance The instance to mount.
 * @param {string} rootID ID of the root node.
 * @param {QtObject} container container element to mount into.
 * @param {ReactReconcileTransaction} transaction
 */
function mountComponentIntoNode(componentInstance, rootID, container, transaction) {
    ReactReconciler.mountComponent(componentInstance, rootID, transaction, {
        qmlParent: container
    });
    // usually here we would actually mount node to native tree, but
    // in case of QML it is unnecessary because QML specifies `root`
    // during the creation of QML element
}

/**
 * Batched mount.
 *
 * @param {ReactComponent} componentInstance The instance to mount.
 * @param {string} rootID objectName of the root node (QtObject).
 * @param {QtObject} container QtObject to mount into.
 */
function batchedMountComponentIntoNode(componentInstance, rootID, container) {
    var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
    transaction.perform(
        mountComponentIntoNode, null,
        componentInstance, rootID, container, transaction
    );
    ReactUpdates.ReactReconcileTransaction.release(transaction);
}

// these two arrays act as two-directional map
var containers = [];
var components = [];

function getComponentForContainer(container) {
    return components[containers.indexOf(container)];
}

function updateRootComponent(prevComponent, nextElement, container, callback) {
    ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement);
    if (callback) {
        ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
    }
    return prevComponent;
}

function renderNewRootComponent(nextElement, container) {

    var componentInstance = instantiateReactComponent(nextElement, null);
    var reactRootID = 'qml' + Math.ceil(Math.random() * 100000);

    // The initial render is synchronous but any updates that happen during
    // rendering, in componentWillMount or componentDidMount, will be batched
    // according to the current batching strategy.
    ReactUpdates.batchedUpdates(
        batchedMountComponentIntoNode,
        componentInstance, reactRootID, container
    );

    return componentInstance;
}

exports.unmountComponentAtNode = function (container) {
    var index = containers.indexOf(container);
    if (index < 0) { return; }
    var instance = components.splice(index, 1)[0];
    containers.splice(index, 1);
    ReactReconciler.unmountComponent(instance);
};

exports.render = function (nextElement, container, callback) {
    if (!ReactQmlRoot.root) {
        ReactQmlRoot.updateRootFromContainer(container);
    }

    // This is necessary to avoid ReactQmlComponent being subclass of ReactCompositeComponent
    // at least as of version 0.13.1 Facebook employs similar hack
    if (nextElement.type.prototype.qmlType) {
        nextElement = React.createElement(ReactQmlComponentCompositeWrapper, null, nextElement);
    }

    var prevComponent = getComponentForContainer(container);

    if (prevComponent) {
        var prevElement = prevComponent._currentElement;
        if (shouldUpdateReactComponent(prevElement, nextElement)) {
            return updateRootComponent(prevComponent, nextElement, container, callback);
        } else {
            exports.unmountComponentAtNode(container);
        }
    }

    var component = renderNewRootComponent(nextElement, container);
    containers.push(container);
    components.push(component);
    if (callback) {
        callback.call(component);
    }
    return component;
};


<|start_filename|>src/ReactQmlRoot.js<|end_filename|>
'use strict';

var ReactQmlRoot = {
    updateRootFromContainer: function(item) {
        while (item.parent) {
            item = item.parent;
        }
        ReactQmlRoot.root = item;
    },
    root: null
};

module.exports = ReactQmlRoot;


<|start_filename|>index.js<|end_filename|>
'use strict';
module.exports = require('./src/ReactQml');


<|start_filename|>src/ReactQmlComponentMetaFactory.js<|end_filename|>
'use strict';
/* global Qt */
var ReactQmlRoot = require('./ReactQmlRoot');
var cache = Object.create(null);

/**
 * Creates a factory (a `Component` in QML terms) for given `qmlType`
 * @param {string} qmlLibrary
 * @param {string} qmlType
 * @constructor
 */
function ReactQmlComponentFactory(qmlLibrary, qmlType) {
    this.qmlComponent = Qt.createQmlObject(
        'import ' + qmlLibrary + ';Component{' + qmlType + '{}}',
        ReactQmlRoot.root
    );
}

/**
 * Produces a QML item of the factory type
 * @param {QtObject} parent
 * @param {Object} props
 */
ReactQmlComponentFactory.prototype.createObject = function(parent, props) {
    return this.qmlComponent.createObject(parent, props);
};

module.exports = {
    createFactory: function(qmlLibrary, qmlType) {
        var cacheKey = qmlLibrary + '/' + qmlType;
        var factory = cache[cacheKey];
        if (!factory) {
            factory = cache[cacheKey] = new ReactQmlComponentFactory(qmlLibrary, qmlType);
        }
        return factory
    }
};


<|start_filename|>src/ReactQmlQtQuick.js<|end_filename|>
'use strict';
var ReactQmlComponent = require('./ReactQmlComponent');
var QT_QUICK_LIB = 'QtQuick 2.4';
var QT_QUICK_DIALOG_LIB = 'QtQuick.Dialogs 1.2';
var QT_QUICK_WINDOW_LIB = 'QtQuick.Window 2.0';
var QT_QUICK_CONTROLS_LIB = 'QtQuick.Controls 1.3';

exports.Image = ReactQmlComponent.extend(QT_QUICK_LIB, 'Image');
exports.Item = ReactQmlComponent.extend(QT_QUICK_LIB, 'Item');
exports.Rectangle = ReactQmlComponent.extend(QT_QUICK_LIB, 'Rectangle');
exports.Text = ReactQmlComponent.extend(QT_QUICK_LIB, 'Text');
exports.TextInput = ReactQmlComponent.extend(QT_QUICK_LIB, 'TextInput');
exports.TextEdit = ReactQmlComponent.extend(QT_QUICK_LIB, 'TextEdit');

// TODO Add support for Grid / Row / Column / Flow layouts
// Required: children reordering when updating tree


exports.Window = ReactQmlComponent.extend(QT_QUICK_WINDOW_LIB, 'Window');

exports.Dialogs = {
    ColorDialog: ReactQmlComponent.extend(QT_QUICK_DIALOG_LIB, 'ColorDialog'),
    Dialog: ReactQmlComponent.extend(QT_QUICK_DIALOG_LIB, 'Dialog'),
    FileDialog: ReactQmlComponent.extend(QT_QUICK_DIALOG_LIB, 'FileDialog'),
    FontDialog: ReactQmlComponent.extend(QT_QUICK_DIALOG_LIB, 'FontDialog'),
    MessageDialog: ReactQmlComponent.extend(QT_QUICK_DIALOG_LIB, 'MessageDialog')
};

exports.Controls = {
    BusyIndicator: ReactQmlComponent.extend(QT_QUICK_CONTROLS_LIB, 'BusyIndicator'),
    Button: ReactQmlComponent.extend(QT_QUICK_CONTROLS_LIB, 'Button'),
    Calendar: ReactQmlComponent.extend(QT_QUICK_CONTROLS_LIB, 'Calendar'),
    CheckBox: ReactQmlComponent.extend(QT_QUICK_CONTROLS_LIB, 'CheckBox'),
    Label: ReactQmlComponent.extend(QT_QUICK_CONTROLS_LIB, 'Label'),
    ProgressBar: ReactQmlComponent.extend(QT_QUICK_CONTROLS_LIB, 'ProgressBar'),
    Slider: ReactQmlComponent.extend(QT_QUICK_CONTROLS_LIB, 'Slider'),
    SpinBox: ReactQmlComponent.extend(QT_QUICK_CONTROLS_LIB, 'SpinBox'),
    Switch: ReactQmlComponent.extend(QT_QUICK_CONTROLS_LIB, 'Switch'),
    TextArea: ReactQmlComponent.extend(QT_QUICK_CONTROLS_LIB, 'TextArea'),
    TextField: ReactQmlComponent.extend(QT_QUICK_CONTROLS_LIB, 'TextField')
};

// TODO Add support for Menu / MenuItem / MenuBar / MenuSeparator / Action / ScrollView
// Required: support for QML default properties:
// http://doc.qt.io/qt-5/qtqml-syntax-objectattributes.html#default-properties

// TODO Add support for RadioBox
// Required: support for ExclusiveGroup





<|start_filename|>test/Qt.js<|end_filename|>
'use strict';

var Qt = require('../support/Qt');
var assert = require('referee').assert;
var refute = require('referee').refute;
var qmlRectangleString = 'import QtQuick 2.0; Rectangle {}';
var qmlComponentString = 'import QtQuick 2.0; Component { Rectangle {} }';

describe('Qt', function () {

    it('should be registered as global', function () {
        assert.isObject(global.Qt);
    });

    describe('QtObject', function () {

        it('should be instantiable without a parent', function () {
            assert(new Qt.QtObject());
        });

        it('should be instantiable with a parent', function () {
            var parent = new Qt.QtObject();
            assert(new Qt.QtObject(parent));
        });

        it('should setup parent and children when created', function () {
            var parent = new Qt.QtObject();
            var child = new Qt.QtObject(parent);
            assert.same(child.parent, parent);
            assert.equals(parent.children, [child]);
        });

        it('should cleanup parent and children when destroyed', function () {
            var parent = new Qt.QtObject();
            var child = new Qt.QtObject(parent);
            child.destroy();
            refute(child.parent);
            assert.equals(parent.children, []);
        });

        it('should be serializable using JSON.stringify', function () {
            /*eslint no-new:0 */
            var parent = new Qt.QtObject();
            new Qt.QtObject(parent);
            assert.equals(JSON.stringify(parent), JSON.stringify({
                children: [{
                    children: [],
                    _type: 'QtObject'
                }],
                _type: 'QtObject'
            }));
        });

    });

    describe('createQmlObject', function () {

        it('should require a parent', function () {
            assert.exception(function () {
                Qt.createQmlObject(qmlRectangleString);
            }, { message: 'parent' });
        });

        it('should create instances of QtObject', function () {
            var parent = new Qt.QtObject();
            var item = Qt.createQmlObject(qmlRectangleString, parent);
            assert(item instanceof Qt.QtObject);
        });

        it('should set internal type based on Qml string', function () {
            var parent = new Qt.QtObject();
            var item = Qt.createQmlObject(qmlRectangleString, parent);
            assert.same(item._type, 'Rectangle');
        });

        it('should be able to create a component', function () {
            var parent = new Qt.QtObject();
            var component = Qt.createQmlObject(qmlComponentString, parent);
            assert.isFunction(component.createObject);
        });

    });

    describe('QtComponent', function () {
        it('should be able to create a objects with given parent and props', function () {
            var parent = new Qt.QtObject();
            var component = Qt.createQmlObject(qmlComponentString, parent);
            var obj = component.createObject(parent, { foo: 'bar' });
            assert.same(obj.parent, parent);
            assert.same(obj.foo, 'bar');
        });

    });
});


<|start_filename|>support/Qt.js<|end_filename|>
'use strict';

/**
 * This provides minimal QML environment that is used by ReactQml
 */

var assign = require('react/lib/Object.assign');

function QtObject(parent) {
    if (parent && !(parent instanceof QtObject)) {
        throw new Error('`parent` needs to be an instance of QtObject');
    }

    this.children = [];
    this._type = 'QtObject';

    // need to make parent non-enumerable to avoid
    // cyclic reference when doing JSON.stringify
    Object.defineProperty(this, 'parent', {
        value: parent,
        writable: true,
        enumerable: false
    });

    if (parent) {
        parent.children.push(this);
    }
}

assign(QtObject.prototype, {
    _type: 'QtObject',
    destroy: function () {
        if (!this.parent) { return; }
        var children = this.parent.children;
        var index = children.indexOf(this);
        if (index >= 0) {
            var child = children.splice(index, 1)[0];
            child.parent = null;
        }
    }
});

function QtComponent(type) {
    this._componentType = type;
}

QtComponent.prototype.createObject = function (parent, props) {
    var obj = new QtObject(parent);
    assign(obj, props);
    obj._type = this._componentType;
    return obj;
};

/**
 * @param evalText
 * @param parent
 * @returns {QtObject|QtComponent}
 */
function createQmlObject(evalText, parent) {
    if (!parent) {
        throw new Error('You need to provide a `parent` to Qt.createQmlObject');
    }
    var obj;
    var match = evalText.match(/import\s+[^;]+;\s*(Component\s*\{)?\s*([A-z][\w]*)[\s|\n]*\{/);
    if (match[1]) {
        obj = new QtComponent(match[2]);
    } else {
        obj = new QtObject(parent);
        obj._type = match[2];
    }
    return obj;
}

module.exports = global.Qt = {
    createQmlObject: createQmlObject,
    QtObject: QtObject
};


<|start_filename|>cli.js<|end_filename|>
#!/usr/bin/env node
'use strict';

// FIXME Add CLI support
console.error('CLI generation for react-qml projects is not supported yet.');


<|start_filename|>src/ReactQmlComponent.js<|end_filename|>
'use strict';

var ReactMultiChild = require('react/lib/ReactMultiChild');
var ReactQmlComponentMetaFactory = require('./ReactQmlComponentMetaFactory');
var assign = require('react/lib/Object.assign');

var Mixin = assign({}, ReactMultiChild.Mixin, {
    displayName: 'ReactQmlComponent',

    qmlLibrary: 'QtQuick 2.4',
    qmlType: 'Item',

    /**
     * @param {QtObject} parent
     * @param {Object} props
     * @protected
     */
    createQmlObject: function (parent, props) {
        var assignableProps = {};
        for (var key in props) {
            if (key === 'key' || key === 'children' || key === 'ref') { continue; }
            if (props.hasOwnProperty(key)) {
                assignableProps[key] = props[key];
            }
        }

        var factory = ReactQmlComponentMetaFactory.createFactory(this.qmlLibrary, this.qmlType);
        return factory.createObject(parent, assignableProps);
    },

    /**
     * Called by internal creator when Element is available
     * @param {ReactElement} element
     */
    construct: function(element) {
        this._currentElement = element;
    },

    /**
     * Used to describe which object should be returned
     * to the caller of `React.render`
     * @this {ReactQmlComponent}
     * @returns {ReactQmlComponent}
     */
    getPublicInstance: function() {
        return this;
    },

    moveChild: function(/* child, toIndex */) {
        // there is currently no easy way to move children in QML so ignore for now
    },

    createChild: function(/*child, childNode*/) {
        // node is created during `mountComponent`, don't need another one
        // unless I'm missing something from React internals
    },

    removeChild: function(/*child*/) {
        // node is destroyed during `unmountComponent`, don't need to do it again
        // unless I'm missing something from React internals
    },

    /**
     * Applies a diff between old an new props to an underlying node
     * @param {Object} oldProps
     * @param {Object} props
     */
    applyNodeProps: function(oldProps, props) {
        var key;

        // applying new / changed properties
        for (key in props) {
            if (key === 'children' || key === 'key' || key === 'ref') { continue; }
            if (props[key] === oldProps[key]) { continue; }
            if (props.hasOwnProperty(key)) {
                this.node[key] = props[key];
            }
        }

        // unsetting removed properties
        for (key in oldProps) {
            if (key in props || key === 'children') { continue; }
            if (oldProps.hasOwnProperty(key)) {
                this.node[key] = undefined;
            }
        }
    },

    mountComponent: function(rootID, transaction, context) {
        var props = this._currentElement.props;
        this._rootNodeID = rootID;
        this.node = this.createQmlObject(context.qmlParent, props);
        this.node.objectName = rootID;
        this.mountChildren(props.children, transaction, assign({}, context, { qmlParent: this.node }));
        return this.node;
    },

    receiveComponent: function(nextComponent, transaction, context) {
        var props = nextComponent.props;
        var oldProps = this._currentElement.props;
        this.updateChildren(props.children, transaction, assign({}, context, { qmlParent: this.node }));
        this.applyNodeProps(oldProps, props);
        this._currentElement = nextComponent;
    },

    unmountComponent: function() {
        // TODO check if it's necessary to unbind events here
        this.unmountChildren();
        this.node.destroy();
        this.node = null;
    }

});

/**
 * Provides an easy way to create derived classes.
 * @param {string} qmlLibrary
 * @param {string} qmlType
 * @returns {Function}
 */
module.exports = {
    extend: function (qmlLibrary, qmlType) {
        var Constructor = function() {
            // These properties are expected by ReactMultiChild
            this.node = null;
            this._currentElement = null;
            this._rootNodeID = null;
            this._mountIndex = null;
            this._renderedChildren = null;
        };
        Constructor.displayName = 'ReactQml' + qmlType;
        assign(Constructor.prototype, Mixin);
        Constructor.prototype.qmlType = qmlType;
        Constructor.prototype.qmlLibrary = qmlLibrary;
        return Constructor;
    }
};
