<commit_before>// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//
define(function(require) {
    "use strict";

    var IPython = require('base/js/namespace');
    var $ = require('jquery');
    var utils = require('base/js/utils');
    var dialog = require('base/js/dialog');
    var gapi_utils = require('./gapi_utils');
    var drive_utils = require('./drive_utils');

    var Contents = function(options) {
        // Constructor
        //
        // A contentmanager handles passing file operations
        // to the back-end.  This includes checkpointing
        // with the normal file operations.
        //
        // Parameters:
        //  options: dictionary
        //      Dictionary of keyword arguments.
        //          base_url: string
        this.base_url = options.base_url;

        /**
         * Used to store the revision id from the last save.  This is used
         * in checkpointing, so that when create_checkpoint is called, it
         * creates a checkpoint for that file.  It is a map from file ids
         * to revision ids, and is updated on each successful save to a file.
         */
        this.last_revision = {};
    };

    /**
     * Notebook Functions
     */

    /**
     * Load a notebook.
     *
     * Calls success_callback with notebook JSON object (as string), or
     * options.error with error.
     *
     * @method load_notebook
     * @param {String} path
     * @param {String} name
     * @param {Object} options
     */
    Contents.prototype.get = function (path, type, options) {
        return gapi_utils.gapi_ready
        .then($.proxy(drive_utils.get_resource_for_path, this, path, drive_utils.FileType.FILE))
        .then(function(resource) {
            return gapi_utils.download(resource['downloadUrl']);
         })
         .then(function(contents) {
             var model = JSON.parse(contents);
             // assume everything is writable for now
             return {content: model, name: model.metadata.name, path:path, writable:true};
         });
    };

    /**
     * Creates a new file at the specified directory path.
     *
     * @method new_untitled
     * @param {String} path The directory in which to create the new file
     * @param {Object} options Includes 'extension' - the extension to use if name not specified.
     */
    Contents.prototype.new_untitled = function(path, options) {
        var folder_id_prm = gapi_utils.gapi_ready
        .then($.proxy(drive_utils.get_id_for_path, this, path, drive_utils.FileType.Folder))
        var filename_prm = folder_id_prm.then(drive_utils.get_new_filename);
        return Promise.all([folder_id_prm, filename_prm]).then(function(values) {
	    var folder_id = values[0];
	    var filename = values[1];
            var data = {
                'cells' : [{
                    'cell_type': 'code',
                    'input': '',
                    'outputs': [],
                    'language': 'python',
                    'metadata': {}
                }],
                'metadata': {
                    'name': filename,
                },
                'nbformat': 3,
                'nbformat_minor': 0
            };
            var metadata = {
                'parents' : [{'id' : folder_id}],
                'title' : filename,
                'description': 'IP[y] file',
                'mimeType': drive_utils.NOTEBOOK_MIMETYPE
            }
            return drive_utils.upload_to_drive(JSON.stringify(data), metadata);
        })
        .then(function(response) {
            return {path: response['title']};
        })
	.catch(function(err) {console.log(err)});
    };

    Contents.prototype.delete_notebook = function(name, path) {
        var settings = {
            processData : false,
            cache : false,
            type : "DELETE",
            dataType : "json",
            success : $.proxy(this.events.trigger, this.events,
                'notebook_deleted.Contents',
                {
                    name: name,
                    path: path
                }),
            error : utils.log_ajax_error
        };
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            path,
            name
        );
        $.ajax(url, settings);
    };

    Contents.prototype.delete = function(path) {
        return drive_utils.get_id_for_path(path, drive_utils.FileType.FILE)
        .then(function(file_id){
            return gapi_utils.execute(gapi.client.drive.files.delete({'fileId': file_id}));
        });
    };

    Contents.prototype.rename_notebook = function(path, name, new_name) {
        var that = this;
        var data = {name: new_name};
        var settings = {
            processData : false,
            cache : false,
            type : "PATCH",
            data : JSON.stringify(data),
            dataType: "json",
            headers : {'Content-Type': 'application/json'},
            success :  function (json, status, xhr) {
                that.events.trigger('notebook_rename_success.Contents',
                    json);
            },
            error : function (xhr, status, error) {
                that.events.trigger('notebook_rename_error.Contents',
                    [xhr, status, error]);
            }
        }
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            path,
            name
        );
        $.ajax(url, settings);
    };

    Contents.prototype.save = function(path, model, options) {
        var that = this;
        var contents = JSON.stringify(model.content);
        return drive_utils.get_id_for_path(path, drive_utils.FileType.FILE)
        .then(function(file_id) {
            return drive_utils.upload_to_drive(contents, {}, file_id);
        })
        .then(function(resource) {
            that.last_revision[resource['id']] = resource['headRevisionId'];
            return {};
        });
    };

    /**
     * Checkpointing Functions
     */

    // NOTE: it would be better modify the API to combine create_checkpoint with
    // save
    Contents.prototype.create_checkpoint = function(path, name, options) {
        var that = this;
        return gapi_utils.gapi_ready
        .then($.proxy(drive_utils.get_id_for_path, this, path, drive_utils.FileType.FILE))
        .then(function(file_id) {
            var revision_id = that.last_revision[file_id];
            if (!revision_id) {
                return Promise.reject(new Error('File must be saved before checkpointing'));
            }
            var body = {'pinned': true};
            var request = gapi.client.drive.revisions.patch({
                'fileId': file_id,
                'revisionId': revision_id,
                'resource': body
            });
            return gapi_utils.execute(request);
        })
        .then(function(item) {
            return JSON.stringify({
                last_modified: item['modifiedDate'],
                id: item['id'],
                drive_resource: item
            });
        });
    };

    Contents.prototype.restore_checkpoint = function(path, name, checkpoint_id, options) {
        var file_id_prm = gapi_utils.gapi_ready
        .then($.proxy(drive_utils.get_id_for_path, this, path + '/' + name, drive_utils.FileType.FILE))

        var contents_prm = file_id_prm.then(function(file_id) {
            var request = gapi.client.drive.revisions.get({
                'fileId': file_id,
                'revisionId': checkpoint_id
            });
            return gapi_utils.execute(request);
        })
        .then(function(response) {
            return gapi_utils.download(response['downloadUrl']);
        })

        return $.when(file_id_prm, contents_prm)
        .then(function(file_id, contents) {
            console.log(contents);
            return drive_utils.upload_to_drive(contents, {}, file_id);
        });
    };

    Contents.prototype.list_checkpoints = function(path, name, options) {
        return gapi_utils.gapi_ready
        .then($.proxy(drive_utils.get_id_for_path, this, path + '/' + name, drive_utils.FileType.FILE))
        .then(function(file_id) {
            var request = gapi.client.drive.revisions.list({ 'fileId': file_id });
            return gapi_utils.execute(request);
        })
        .then(function(response) {
            // TODO: filter out non-pinned revisions
            return JSON.stringify(response['items']
            .filter(function(item) { return item['pinned']; })
            .map(function(item) {
                return {
                    last_modified: item['modifiedDate'],
                    id: item['id'],
                    drive_resource: item
                };
            }));
        });
    };

    /**
     * File management functions
     */

    /**
     * List notebooks and directories at a given path
     *
     * On success, load_callback is called with an array of dictionaries
     * representing individual files or directories.  Each dictionary has
     * the keys:
     *     type: "notebook" or "directory"
     *     name: the name of the file or directory
     *     created: created date
     *     last_modified: last modified dat
     *     path: the path
     * @method list_notebooks
     * @param {String} path The path to list notebooks in
     * @param {Object} options Object with the following keys
     *     success: success callback
     *     error: error callback
     */
    Contents.prototype.list_contents = function(path, options) {
        var that = this;
        return gapi_utils.gapi_ready
        .then($.proxy(drive_utils.get_id_for_path, this, path, drive_utils.FileType.FOLDER))
        .then(function(folder_id) {
            var query = ('(fileExtension = \'ipynb\' or'
                + ' mimeType = \'' + drive_utils.FOLDER_MIME_TYPE + '\')'
                + ' and \'' + folder_id + '\' in parents'
                + ' and trashed = false');
            var request = gapi.client.drive.files.list({
                'maxResults' : 1000,
                'q' : query
            });
            return gapi_utils.execute(request);
        })
        .then(function(response) {
            // Convert this list to the format that is passed to
            // load_callback.  Note that a files resource can represent
            // a file or a directory.
            // TODO: check that date formats are the same, and either
            // convert to the IPython format, or document the difference.
            var list = $.map(response['items'], function(files_resource) {
                var type = files_resource['mimeType'] == drive_utils.FOLDER_MIME_TYPE ? 'directory' : 'notebook';
                return {
                    type: type,
                    name: files_resource['title'],
                    path: path + '/' + files_resource['title'],
                    created: files_resource['createdDate'],
                    last_modified: files_resource['modifiedDate']
                };
            });
            return {content: list};
        });
    };

    IPython.Contents = Contents;

    return {'Contents': Contents};
});
<commit_msg>Sets the readonly flag according to Google Drive file metadata<commit_after>// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
//
define(function(require) {
    "use strict";

    var IPython = require('base/js/namespace');
    var $ = require('jquery');
    var utils = require('base/js/utils');
    var dialog = require('base/js/dialog');
    var gapi_utils = require('./gapi_utils');
    var drive_utils = require('./drive_utils');

    var Contents = function(options) {
        // Constructor
        //
        // A contentmanager handles passing file operations
        // to the back-end.  This includes checkpointing
        // with the normal file operations.
        //
        // Parameters:
        //  options: dictionary
        //      Dictionary of keyword arguments.
        //          base_url: string
        this.base_url = options.base_url;

        /**
         * Used to store the revision id from the last save.  This is used
         * in checkpointing, so that when create_checkpoint is called, it
         * creates a checkpoint for that file.  It is a map from file ids
         * to revision ids, and is updated on each successful save to a file.
         */
        this.last_revision = {};
    };

    /**
     * Notebook Functions
     */

    /**
     * Load a notebook.
     *
     * Calls success_callback with notebook JSON object (as string), or
     * options.error with error.
     *
     * @method load_notebook
     * @param {String} path
     * @param {String} name
     * @param {Object} options
     */
    Contents.prototype.get = function (path, type, options) {
        var metadata_prm = gapi_utils.gapi_ready.then(
            $.proxy(drive_utils.get_resource_for_path, this, path, drive_utils.FileType.FILE));
        var contents_prm = metadata_prm.then(function(resource) {
            return gapi_utils.download(resource['downloadUrl']);
        });
        return Promise.all([metadata_prm, contents_prm]).then(function(values) {
            var metadata = values[0];
            var contents = values[1];
            var model = JSON.parse(contents);
            return {
                content: model,
                name: model.metadata.name,
                path:path,
                writable: metadata['editable']
            };
        });
    };

    /**
     * Creates a new file at the specified directory path.
     *
     * @method new_untitled
     * @param {String} path The directory in which to create the new file
     * @param {Object} options Includes 'extension' - the extension to use if name not specified.
     */
    Contents.prototype.new_untitled = function(path, options) {
        var folder_id_prm = gapi_utils.gapi_ready
        .then($.proxy(drive_utils.get_id_for_path, this, path, drive_utils.FileType.Folder))
        var filename_prm = folder_id_prm.then(drive_utils.get_new_filename);
        return Promise.all([folder_id_prm, filename_prm]).then(function(values) {
	    var folder_id = values[0];
	    var filename = values[1];
            var data = {
                'cells' : [{
                    'cell_type': 'code',
                    'input': '',
                    'outputs': [],
                    'language': 'python',
                    'metadata': {}
                }],
                'metadata': {
                    'name': filename,
                },
                'nbformat': 3,
                'nbformat_minor': 0
            };
            var metadata = {
                'parents' : [{'id' : folder_id}],
                'title' : filename,
                'description': 'IP[y] file',
                'mimeType': drive_utils.NOTEBOOK_MIMETYPE
            }
            return drive_utils.upload_to_drive(JSON.stringify(data), metadata);
        })
        .then(function(response) {
            return {path: response['title']};
        })
	.catch(function(err) {console.log(err)});
    };

    Contents.prototype.delete_notebook = function(name, path) {
        var settings = {
            processData : false,
            cache : false,
            type : "DELETE",
            dataType : "json",
            success : $.proxy(this.events.trigger, this.events,
                'notebook_deleted.Contents',
                {
                    name: name,
                    path: path
                }),
            error : utils.log_ajax_error
        };
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            path,
            name
        );
        $.ajax(url, settings);
    };

    Contents.prototype.delete = function(path) {
        return drive_utils.get_id_for_path(path, drive_utils.FileType.FILE)
        .then(function(file_id){
            return gapi_utils.execute(gapi.client.drive.files.delete({'fileId': file_id}));
        });
    };

    Contents.prototype.rename_notebook = function(path, name, new_name) {
        var that = this;
        var data = {name: new_name};
        var settings = {
            processData : false,
            cache : false,
            type : "PATCH",
            data : JSON.stringify(data),
            dataType: "json",
            headers : {'Content-Type': 'application/json'},
            success :  function (json, status, xhr) {
                that.events.trigger('notebook_rename_success.Contents',
                    json);
            },
            error : function (xhr, status, error) {
                that.events.trigger('notebook_rename_error.Contents',
                    [xhr, status, error]);
            }
        }
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            path,
            name
        );
        $.ajax(url, settings);
    };

    Contents.prototype.save = function(path, model, options) {
        var that = this;
        var contents = JSON.stringify(model.content);
        return drive_utils.get_id_for_path(path, drive_utils.FileType.FILE)
        .then(function(file_id) {
            return drive_utils.upload_to_drive(contents, {}, file_id);
        })
        .then(function(resource) {
            that.last_revision[resource['id']] = resource['headRevisionId'];
            return {};
        });
    };

    /**
     * Checkpointing Functions
     */

    // NOTE: it would be better modify the API to combine create_checkpoint with
    // save
    Contents.prototype.create_checkpoint = function(path, name, options) {
        var that = this;
        return gapi_utils.gapi_ready
        .then($.proxy(drive_utils.get_id_for_path, this, path, drive_utils.FileType.FILE))
        .then(function(file_id) {
            var revision_id = that.last_revision[file_id];
            if (!revision_id) {
                return Promise.reject(new Error('File must be saved before checkpointing'));
            }
            var body = {'pinned': true};
            var request = gapi.client.drive.revisions.patch({
                'fileId': file_id,
                'revisionId': revision_id,
                'resource': body
            });
            return gapi_utils.execute(request);
        })
        .then(function(item) {
            return JSON.stringify({
                last_modified: item['modifiedDate'],
                id: item['id'],
                drive_resource: item
            });
        });
    };

    Contents.prototype.restore_checkpoint = function(path, name, checkpoint_id, options) {
        var file_id_prm = gapi_utils.gapi_ready
        .then($.proxy(drive_utils.get_id_for_path, this, path + '/' + name, drive_utils.FileType.FILE))

        var contents_prm = file_id_prm.then(function(file_id) {
            var request = gapi.client.drive.revisions.get({
                'fileId': file_id,
                'revisionId': checkpoint_id
            });
            return gapi_utils.execute(request);
        })
        .then(function(response) {
            return gapi_utils.download(response['downloadUrl']);
        })

        return $.when(file_id_prm, contents_prm)
        .then(function(file_id, contents) {
            console.log(contents);
            return drive_utils.upload_to_drive(contents, {}, file_id);
        });
    };

    Contents.prototype.list_checkpoints = function(path, name, options) {
        return gapi_utils.gapi_ready
        .then($.proxy(drive_utils.get_id_for_path, this, path + '/' + name, drive_utils.FileType.FILE))
        .then(function(file_id) {
            var request = gapi.client.drive.revisions.list({ 'fileId': file_id });
            return gapi_utils.execute(request);
        })
        .then(function(response) {
            // TODO: filter out non-pinned revisions
            return JSON.stringify(response['items']
            .filter(function(item) { return item['pinned']; })
            .map(function(item) {
                return {
                    last_modified: item['modifiedDate'],
                    id: item['id'],
                    drive_resource: item
                };
            }));
        });
    };

    /**
     * File management functions
     */

    /**
     * List notebooks and directories at a given path
     *
     * On success, load_callback is called with an array of dictionaries
     * representing individual files or directories.  Each dictionary has
     * the keys:
     *     type: "notebook" or "directory"
     *     name: the name of the file or directory
     *     created: created date
     *     last_modified: last modified dat
     *     path: the path
     * @method list_notebooks
     * @param {String} path The path to list notebooks in
     * @param {Object} options Object with the following keys
     *     success: success callback
     *     error: error callback
     */
    Contents.prototype.list_contents = function(path, options) {
        var that = this;
        return gapi_utils.gapi_ready
        .then($.proxy(drive_utils.get_id_for_path, this, path, drive_utils.FileType.FOLDER))
        .then(function(folder_id) {
            var query = ('(fileExtension = \'ipynb\' or'
                + ' mimeType = \'' + drive_utils.FOLDER_MIME_TYPE + '\')'
                + ' and \'' + folder_id + '\' in parents'
                + ' and trashed = false');
            var request = gapi.client.drive.files.list({
                'maxResults' : 1000,
                'q' : query
            });
            return gapi_utils.execute(request);
        })
        .then(function(response) {
            // Convert this list to the format that is passed to
            // load_callback.  Note that a files resource can represent
            // a file or a directory.
            // TODO: check that date formats are the same, and either
            // convert to the IPython format, or document the difference.
            var list = $.map(response['items'], function(files_resource) {
                var type = files_resource['mimeType'] == drive_utils.FOLDER_MIME_TYPE ? 'directory' : 'notebook';
                return {
                    type: type,
                    name: files_resource['title'],
                    path: path + '/' + files_resource['title'],
                    created: files_resource['createdDate'],
                    last_modified: files_resource['modifiedDate']
                };
            });
            return {content: list};
        });
    };

    IPython.Contents = Contents;

    return {'Contents': Contents};
});
<|endoftext|>


<commit_before>

void function(){
  var Regular = require_lib("index.js");
  var Component = Regular.extend();

  function destroy(component, container){
    component.destroy();
    expect(container.innerHTML).to.equal('');
  }

  describe("Nested Component", function(){
    var NameSpace = Regular.extend();
    var containerAll = document.createElement("div");

    it("the attribute with plain String will pass to sub_component", function(){

      //lazy bind watcher will not trigger in intialize state
      var container = document.createElement("div");
      var Component = NameSpace.extend({
        name: "test1",
        template: "
{hello}
"
      })
      var component = new NameSpace({
        template: "<test1 hello='leeluolee' />"
      }).$inject(container)

      expect( nes.one("p", container).innerHTML ).to.equal("leeluolee");
      component.destroy();
    });

    it("it should create two-way binding from parent to nested when pass Expression", function(){
      var container = document.createElement("div");
      var Component = NameSpace.extend({
        name: "test2",
        template: "
{hello}
"
      })
      var component = new NameSpace({
        template: "<test2 hello={name} />{name}",
        data: {name: "leeluolee"}
      }).$inject(container)

      expect( nes.one("p", container).innerHTML ).to.equal("leeluolee");

      component.$update("name", "luobo")

      expect( nes.one("p", container).innerHTML ).to.equal("luobo");
      dispatchMockEvent(nes.one("p", container), "click")
      expect( nes.one("p", container).innerHTML ).to.equal("haha");
      expect( nes.one(".name", container).innerHTML ).to.equal("haha");
      destroy(component, container);
    });
    it("it should create one-way binding from parent to nested when Expression is not setable", function(){
      var container = document.createElement("div");
      var Component = NameSpace.extend({
        name: "test2",
        template: "
{hello}
"
      })
      var component = new NameSpace({
        template: "<test2 hello={name+'1'} />{name}",
        data: {name: "leeluolee"}
      }).$inject(container)

      expect( nes.one("p", container).innerHTML ).to.equal("leeluolee1");
      expect( nes.one(".name", container).innerHTML ).to.equal("leeluolee");

      dispatchMockEvent(nes.one("p", container), "click")
      expect( nes.one("p", container).innerHTML ).to.equal("haha");
      expect( nes.one(".name", container).innerHTML ).to.equal("leeluolee");
      destroy(component, container);
    })

    it("context of transclude-html should point to outer component", function(){
      var container = document.createElement("div");
      var Component = NameSpace.extend({
        name: "test",
        template: "
haha<r-content>
"
      })

      var i = 0;
      var component = new NameSpace({
        template: "<test on-hello={this.hello}>{name}</test>",
        data: {name: "leeluolee"},
        hello: function(){
          i++
        }
      }).$inject(container);

      dispatchMockEvent(nes.one("span", container), "click");

      expect(i).to.equal(1);

      expect(nes.one("p span", container).innerHTML).to.equal("leeluolee");

      destroy(component, container);

    })

    it("nested component should get the outer component's data before create the binding", function(){

      var container = document.createElement("div");
      var Component = NameSpace.extend({
        name: "test",
        template: "
{user.name.first}
"
      })

      var component = new NameSpace({
        template: "<test user={user}></test>",
        data: {user: {name: {first: "Zheng"} } }
      }).$inject(container);

      expect(nes.one("p", container).innerHTML).to.equal("Zheng");

      destroy(component, container);

    })

    describe("nested Component with Event", function(){

      it("on-* should evaluate the Expression when the listener is called", function(){
        var container = document.createElement("div");
        var Component = NameSpace.extend({
          name: "test",
          template: "

"
        })

        var i =0;
        var component = new NameSpace({
          template: "<test on-hello={this.hello()} />",
          hello: function(){
            i++
          }
        }).$inject(container);

        dispatchMockEvent(nes.one("p", container), "click");

        expect(i).to.equal(1);

        destroy(component, container);

      })
      it("on-*='String' should proxy the listener to outer component", function(){
        var container = document.createElement("div");
        var Component = NameSpace.extend({
          name: "test",
          template: "

"
        })

        var i =0;
        var type=null;
        var component = new NameSpace({
          template: "<test on-hello='hello' />",
          init: function(){
            this.$on('hello', function(ev){
              type = ev.type;
              i++;
            })
          }
        }).$inject(container);

        dispatchMockEvent(nes.one("p", container), "click");

        expect(i).to.equal(1);
        expect(type).to.equal("click");

        destroy(component, container);

      })

  })
    // stop parent <-> component
    it("isolate = 3 should make the component isolate to/from parent", function(){
      var container = document.createElement("div");
      var Test = NameSpace.extend({
        name: 'test',
        template: "
{title}
"
      })

      var component = new NameSpace({
        template: "{title}<test ref=a title={title} isolate></test><test ref=b title={title} isolate=3></test>",
        data: {title: 'leeluolee'}
      }).$inject(containerAll);

      var p1= containerAll.getElementsByTagName("p")[0]
      var p2= containerAll.getElementsByTagName("p")[1]
      var span= containerAll.getElementsByTagName("span")[0]
      expect(p1.innerHTML).to.equal("leeluolee");
      expect(p2.innerHTML).to.equal("leeluolee");
      expect(span.innerHTML).to.equal("leeluolee");

      component.$update("title", 'leeluolee2')
      expect(p1.innerHTML).to.equal("leeluolee");
      expect(p2.innerHTML).to.equal("leeluolee");
      expect(span.innerHTML).to.equal("leeluolee2");

      component.$refs.a.$update("title", "leeluolee3");
      expect(p1.innerHTML).to.equal("leeluolee3");
      expect(p2.innerHTML).to.equal("leeluolee");
      expect(span.innerHTML).to.equal("leeluolee2");

      component.$refs.b.$update("title", "leeluolee4");
      expect(p1.innerHTML).to.equal("leeluolee3");
      expect(p2.innerHTML).to.equal("leeluolee4");
      expect(span.innerHTML).to.equal("leeluolee2");

      destroy(component, containerAll);

    })
    // stop parent ->  component
    it("isolate = 2 should make the component isolate from parent", function(){
      var Test = NameSpace.extend({
        name: 'test',
        template: "
{title}
"
      })
      var component = new NameSpace({
        template: "{title}<test ref=a title={title} isolate=2></test>",
        data: {title: 'leeluolee'}
      }).$inject(containerAll);
      var p1= containerAll.getElementsByTagName("p")[0]
      var span= containerAll.getElementsByTagName("span")[0]
      expect(p1.innerHTML).to.equal("leeluolee");
      expect(span.innerHTML).to.equal("leeluolee");

      component.$update("title", 'leeluolee2')

      expect(p1.innerHTML).to.equal("leeluolee");
      expect(span.innerHTML).to.equal("leeluolee2");

      component.$refs.a.$update("title", "leeluolee3");
      expect(p1.innerHTML).to.equal("leeluolee3");
      expect(span.innerHTML).to.equal("leeluolee3");

      destroy(component, containerAll);
    })
    // stop component -> parent
    it("isolate = 1 should make the component isolate to parent", function(){
      var Test = NameSpace.extend({
        name: 'test',
        template: "
{title}
"
      })
      var component = new NameSpace({
        template: "{title}<test ref=a title={title} isolate=1></test>",
        data: {title: 'leeluolee'}
      }).$inject(containerAll);
      var p1= containerAll.getElementsByTagName("p")[0]
      var span= containerAll.getElementsByTagName("span")[0]

      expect(p1.innerHTML).to.equal("leeluolee");
      expect(span.innerHTML).to.equal("leeluolee");

      component.$update("title", 'leeluolee2')

      expect(p1.innerHTML).to.equal("leeluolee2");
      expect(span.innerHTML).to.equal("leeluolee2");

      component.$refs.a.$update("title", "leeluolee3");
      expect(p1.innerHTML).to.equal("leeluolee3");
      expect(span.innerHTML).to.equal("leeluolee2");
      destroy(component, containerAll);
    })
    it("isolate & 1 should make the component isolate to parent", function(){
    })
})

}()
<commit_msg>add browser-nested.js<commit_after>

void function(){
  var Regular = require_lib("index.js");
  var Component = Regular.extend();

  function destroy(component, container){
    component.destroy();
    expect(container.innerHTML).to.equal('');
  }

  describe("Nested Component", function(){
    var NameSpace = Regular.extend();
    var containerAll = document.createElement("div");

    it("the attribute with plain String will pass to sub_component", function(){

      //lazy bind watcher will not trigger in intialize state
      var container = document.createElement("div");
      var Component = NameSpace.extend({
        name: "test1",
        template: "
{hello}
"
      })
      var component = new NameSpace({
        template: "<test1 hello='leeluolee' />"
      }).$inject(container)

      expect( nes.one("p", container).innerHTML ).to.equal("leeluolee");
      component.destroy();
    });

    it("it should create two-way binding from parent to nested when pass Expression", function(){
      var container = document.createElement("div");
      var Component = NameSpace.extend({
        name: "test2",
        template: "
{hello}
"
      })
      var component = new NameSpace({
        template: "<test2 hello={name} />{name}",
        data: {name: "leeluolee"}
      }).$inject(container)

      expect( nes.one("p", container).innerHTML ).to.equal("leeluolee");

      component.$update("name", "luobo")

      expect( nes.one("p", container).innerHTML ).to.equal("luobo");
      dispatchMockEvent(nes.one("p", container), "click")
      expect( nes.one("p", container).innerHTML ).to.equal("haha");
      expect( nes.one(".name", container).innerHTML ).to.equal("haha");
      destroy(component, container);
    });
    it("it should create one-way binding from parent to nested when Expression is not setable", function(){
      var container = document.createElement("div");
      var Component = NameSpace.extend({
        name: "test2",
        template: "
{hello}
"
      })
      var component = new NameSpace({
        template: "<test2 hello={name+'1'} />{name}",
        data: {name: "leeluolee"}
      }).$inject(container)

      expect( nes.one("p", container).innerHTML ).to.equal("leeluolee1");
      expect( nes.one(".name", container).innerHTML ).to.equal("leeluolee");

      dispatchMockEvent(nes.one("p", container), "click")
      expect( nes.one("p", container).innerHTML ).to.equal("haha");
      expect( nes.one(".name", container).innerHTML ).to.equal("leeluolee");
      destroy(component, container);
    })

    it("context of transclude-html should point to outer component", function(){
      var container = document.createElement("div");
      var Component = NameSpace.extend({
        name: "test",
        template: "
haha<r-content>
"
      })

      var i = 0;
      var component = new NameSpace({
        template: "<test on-hello={this.hello}>{name}</test>",
        data: {name: "leeluolee"},
        hello: function(){
          i++
        }
      }).$inject(container);

      dispatchMockEvent(nes.one("span", container), "click");

      expect(i).to.equal(1);

      expect(nes.one("p span", container).innerHTML).to.equal("leeluolee");

      destroy(component, container);

    })

    it("transclude-html with list ", function(){

    })

    it("nested component should get the outer component's data before create the binding", function(){

      var container = document.createElement("div");
      var Component = NameSpace.extend({
        name: "test",
        template: "
{user.name.first}
"
      })

      var component = new NameSpace({
        template: "<test user={user}></test>",
        data: {user: {name: {first: "Zheng"} } }
      }).$inject(container);

      expect(nes.one("p", container).innerHTML).to.equal("Zheng");

      destroy(component, container);

    })

    describe("nested Component with Event", function(){

      it("on-* should evaluate the Expression when the listener is called", function(){
        var container = document.createElement("div");
        var Component = NameSpace.extend({
          name: "test",
          template: "

"
        })

        var i =0;
        var component = new NameSpace({
          template: "<test on-hello={this.hello()} />",
          hello: function(){
            i++
          }
        }).$inject(container);

        dispatchMockEvent(nes.one("p", container), "click");

        expect(i).to.equal(1);

        destroy(component, container);

      })
      it("on-*='String' should proxy the listener to outer component", function(){
        var container = document.createElement("div");
        var Component = NameSpace.extend({
          name: "test",
          template: "

"
        })

        var i =0;
        var type=null;
        var component = new NameSpace({
          template: "<test on-hello='hello' />",
          init: function(){
            this.$on('hello', function(ev){
              type = ev.type;
              i++;
            })
          }
        }).$inject(container);

        dispatchMockEvent(nes.one("p", container), "click");

        expect(i).to.equal(1);
        expect(type).to.equal("click");

        destroy(component, container);

      })

  })
    // stop parent <-> component
    it("isolate = 3 should make the component isolate to/from parent", function(){
      var container = document.createElement("div");
      var Test = NameSpace.extend({
        name: 'test',
        template: "
{title}
"
      })

      var component = new NameSpace({
        template: "{title}<test ref=a title={title} isolate></test><test ref=b title={title} isolate=3></test>",
        data: {title: 'leeluolee'}
      }).$inject(containerAll);

      var p1= containerAll.getElementsByTagName("p")[0]
      var p2= containerAll.getElementsByTagName("p")[1]
      var span= containerAll.getElementsByTagName("span")[0]
      expect(p1.innerHTML).to.equal("leeluolee");
      expect(p2.innerHTML).to.equal("leeluolee");
      expect(span.innerHTML).to.equal("leeluolee");

      component.$update("title", 'leeluolee2')
      expect(p1.innerHTML).to.equal("leeluolee");
      expect(p2.innerHTML).to.equal("leeluolee");
      expect(span.innerHTML).to.equal("leeluolee2");

      component.$refs.a.$update("title", "leeluolee3");
      expect(p1.innerHTML).to.equal("leeluolee3");
      expect(p2.innerHTML).to.equal("leeluolee");
      expect(span.innerHTML).to.equal("leeluolee2");

      component.$refs.b.$update("title", "leeluolee4");
      expect(p1.innerHTML).to.equal("leeluolee3");
      expect(p2.innerHTML).to.equal("leeluolee4");
      expect(span.innerHTML).to.equal("leeluolee2");

      destroy(component, containerAll);

    })
    // stop parent ->  component
    it("isolate = 2 should make the component isolate from parent", function(){
      var Test = NameSpace.extend({
        name: 'test',
        template: "
{title}
"
      })
      var component = new NameSpace({
        template: "{title}<test ref=a title={title} isolate=2></test>",
        data: {title: 'leeluolee'}
      }).$inject(containerAll);
      var p1= containerAll.getElementsByTagName("p")[0]
      var span= containerAll.getElementsByTagName("span")[0]
      expect(p1.innerHTML).to.equal("leeluolee");
      expect(span.innerHTML).to.equal("leeluolee");

      component.$update("title", 'leeluolee2')

      expect(p1.innerHTML).to.equal("leeluolee");
      expect(span.innerHTML).to.equal("leeluolee2");

      component.$refs.a.$update("title", "leeluolee3");
      expect(p1.innerHTML).to.equal("leeluolee3");
      expect(span.innerHTML).to.equal("leeluolee3");

      destroy(component, containerAll);
    })
    // stop component -> parent
    it("isolate = 1 should make the component isolate to parent", function(){
      var Test = NameSpace.extend({
        name: 'test',
        template: "
{title}
"
      })
      var component = new NameSpace({
        template: "{title}<test ref=a title={title} isolate=1></test>",
        data: {title: 'leeluolee'}
      }).$inject(containerAll);
      var p1= containerAll.getElementsByTagName("p")[0]
      var span= containerAll.getElementsByTagName("span")[0]

      expect(p1.innerHTML).to.equal("leeluolee");
      expect(span.innerHTML).to.equal("leeluolee");

      component.$update("title", 'leeluolee2')

      expect(p1.innerHTML).to.equal("leeluolee2");
      expect(span.innerHTML).to.equal("leeluolee2");

      component.$refs.a.$update("title", "leeluolee3");
      expect(p1.innerHTML).to.equal("leeluolee3");
      expect(span.innerHTML).to.equal("leeluolee2");
      destroy(component, containerAll);
    })
    it("isolate & 1 should make the component isolate to parent", function(){
    })
})

}()
<|endoftext|>


<commit_before>// 1: assertThat
// To do: make all tests pass, leave the assert lines unchanged!

import {
  assertThat, equalTo,
  containsString, throws, returns,
} from 'hamjest';

describe('The core function, `assertThat()`', () => {
  it('is a function', () => {
    //// const typeOfAssertThat = typeof 'assertThat';
    const typeOfAssertThat = typeof assertThat;
    assertThat(typeOfAssertThat, equalTo('function'));
  });
  describe('requires at least two params', () => {
    it('1st: the actual value', () => {
      const actual = 'actual';
      const expected = equalTo('actual');
      assertThat(actual, expected);
    });
    it('2nd: a matcher for the expected value', () => {
      const matcher = equalTo('expected');
      assertThat('expected', matcher);
    });
    describe('the optional 3rd param', () => {
      it('goes first(!), and is the assertion reason', () => {
        let caughtError;
        const reason = 'This is the reason, the first `assertThat()` throws as part of its message.';
        try {
          assertThat(reason, true, equalTo(false));
        } catch (e) {
          caughtError = e;
        }
        assertThat(caughtError.message, containsString(reason));
      });
    });
  });

  describe('under the hood, it does', () => {
    it('nothing, WHEN actual and expected match (using the given matcher)', () => {
      const passingTest = () => assertThat(true, equalTo(true));
      assertThat(passingTest, returns());
    });
    it('throw an assertion, WHEN actual and expected don`t match', () => {
      const failingTest = () => assertThat(false, equalTo(true));
      assertThat(failingTest, throws());
    });
  });
});
<commit_msg>Try to make a kata out of each test.<commit_after>// 1: assertThat
// To do: make all tests pass, leave the assert lines unchanged!

import {
  assertThat, equalTo,
  containsString, throws, returns,
} from 'hamjest';

describe('The core function, `assertThat()`', () => {
  it('is a function', () => {
    //// const typeOfAssertThat = typeof 'assertThat';
    const typeOfAssertThat = typeof assertThat;
    assertThat(typeOfAssertThat, equalTo('function'));
  });
  describe('requires at least two params', () => {
    it('1st: the actual value', () => {
      //// const actual = undefined;
      const actual = 'actual';
      const expected = equalTo('actual');
      assertThat(actual, expected);
    });
    it('2nd: a matcher for the expected value', () => {
      ////
      const matcher = equalTo('expected');
      assertThat('expected', matcher);
    });
    describe('the optional 3rd param', () => {
      it('goes first(!), and is the assertion reason', () => {
        let caughtError;
        const reason = 'This is the reason, the first `assertThat()` throws as part of its message.';
        try {
          //// assertThat(reason, true, equalTo(true));
          assertThat(reason, true, equalTo(false));
        } catch (e) {
          caughtError = e;
        }
        assertThat(caughtError.message, containsString(reason));
      });
    });
  });

  describe('under the hood, it does', () => {
    it('nothing, WHEN actual and expected match (using the given matcher)', () => {
      //// const passingTest = () => assertThat(true, equalTo(false));
      const passingTest = () => assertThat(true, equalTo(true));
      assertThat(passingTest, returns());
    });
    it('throw an assertion, WHEN actual and expected don`t match', () => {
      //// const failingTest = () => 'use assertThat() to write a failing test';
      const failingTest = () => assertThat(false, equalTo(true));
      assertThat(failingTest, throws());
    });
  });
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Provider, connect } from "react-redux";

import { fetchAllAttendingActivities } from '../actions/activitiesActions';
import ActivityCardHomePage from '../components/ActivityCardHomePage';
import configureStore from "../configureStore";

import '../../styles/activityBox.css';

const store = configureStore();

class AttendingActivitiesContainer extends Component {

    componentDidMount() {
        this.props.fetchAttendingActivities();
    }

    createActivityCardComponent = () => {
        return this.props.attendingActivities.map(activity => {
            return (
                <ActivityCardHomePage
                    key={activity.id + activity.fields.activityName}
                    id={activity.pk}
                    activity={activity.fields}
                />
            )
        });
    };

    render() {
        const styles = {
            activitiesContainerStyle: {
                margin: "0px 10px 0px 10px"
            },
            activitiesStyle: {
                display: "flex",
                flexWrap: "wrap",
                flexDirection: "row",
                justifyContent: "flex-start"
            }
        };

        return (

                  {this.createActivityCardComponent()}

        );
    }

}

const mapStateToProps = state => {
    return {
        attendingActivities: state.activity.attendingActivityList
            .sort((a, b) => new Date(a.fields.date) > new Date(b.fields.date)) // Sort descending based on date
            .slice(0, 5), // Only get five first
    };
};

const mapDispatchToProps = dispatch => {
    return {
        fetchAttendingActivities: () => dispatch(fetchAllAttendingActivities()),
    }
};

// Fetch initial data for state
store.dispatch(fetchAllAttendingActivities());

AttendingActivitiesContainer = connect(mapStateToProps, mapDispatchToProps)(AttendingActivitiesContainer);

ReactDOM.render(
    <Provider store={store}>
        <AttendingActivitiesContainer />
    </Provider>, document.getElementById('attendingActivities')
);

<commit_msg>removed sliceing, so you can see all activities you are attending, not only 5, still sorted by date<commit_after>import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Provider, connect } from "react-redux";

import { fetchAllAttendingActivities } from '../actions/activitiesActions';
import ActivityCardHomePage from '../components/ActivityCardHomePage';
import configureStore from "../configureStore";

import '../../styles/activityBox.css';

const store = configureStore();

class AttendingActivitiesContainer extends Component {

    componentDidMount() {
        this.props.fetchAttendingActivities();
    }

    createActivityCardComponent = () => {
        return this.props.attendingActivities.map(activity => {
            return (
                <ActivityCardHomePage
                    key={activity.id + activity.fields.activityName}
                    id={activity.pk}
                    activity={activity.fields}
                />
            )
        });
    };

    render() {
        const styles = {
            activitiesContainerStyle: {
                margin: "0px 10px 0px 10px"
            },
            activitiesStyle: {
                display: "flex",
                flexWrap: "wrap",
                flexDirection: "row",
                justifyContent: "flex-start"
            }
        };

        return (

                  {this.createActivityCardComponent()}

        );
    }

}

const mapStateToProps = state => {
    return {
        attendingActivities: state.activity.attendingActivityList
            .sort((a, b) => new Date(a.fields.date) > new Date(b.fields.date)) // Sort descending based on date
            // Only get five first
    };
};

const mapDispatchToProps = dispatch => {
    return {
        fetchAttendingActivities: () => dispatch(fetchAllAttendingActivities()),
    }
};

// Fetch initial data for state
store.dispatch(fetchAllAttendingActivities());

AttendingActivitiesContainer = connect(mapStateToProps, mapDispatchToProps)(AttendingActivitiesContainer);

ReactDOM.render(
    <Provider store={store}>
        <AttendingActivitiesContainer />
    </Provider>, document.getElementById('attendingActivities')
);

<|endoftext|>


<commit_before>import React from 'react';
import TestUtils from 'react/lib/ReactTestUtils';
import AsyncComponent from './src/AsyncComponent';
import Transmitter from './src/Container';
import fromStore from './src/fromStore';
import Redux from 'redux';
import assert from 'assert';

function log(target) {
	console.log(require('util').inspect(target, { depth: 3, showHidden: true }));
}

function runAsync(block) {
	return new Promise(resolve => setTimeout(async () => resolve(await block()), 10));
}

describe('AsyncComponent', () => {
	const Component = props => <div />;

	it('should create React component', () => {
		const ComponentFetch = async props => {
			await Promise.resolve(13);
			return <Component />;
		};

		const Container = AsyncComponent(ComponentFetch);

		assert.ok(TestUtils.isElement(<Container />), 'Container should be a React component');
		assert.equal(Container.displayName, 'Transmitter(ComponentFetch)', 'Container should have Component\'s name with prefix');
	});

	it('should render original component with resolved data', () => {
		const ComponentFetch = async props => {
			const data = await Promise.resolve(13);
			return <Component data={data} />;
		};

		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();

		ReactShallow.render(<Container />);

		return runAsync(() => {
			const Output = ReactShallow.getRenderOutput();

			assert.ok(TestUtils.isElementOfType(Output, Component), 'Returned component should be rendered');
			assert.deepEqual(Output.props, {data: 13}, 'Component should be rendered with data fetched via async function');
		});
	});

	it('should refetch data when props is changed', () => {
		const ComponentFetch = async props => {
			const data = await Promise.resolve(props.id);
			return <Component data={data + 1} />;
		};

		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();

		// TODO: add spy for `componentWillReceiveProps`
		ReactShallow.render(<Container id={1} />);
		ReactShallow.render(<Container id={2} />);

		return runAsync(() => {
			const Output = ReactShallow.getRenderOutput();

			assert.deepEqual(Output.props, {data: 3}, '');
		});
	});

	it('should work with rejected promises', () => {
		const ErrorMessage = props => <div />;
		const ComponentFetch = async props => {
			try {
				const data = await Promise.reject(13);
				return <Component />;
			} catch (e) {
				return <ErrorMessage />;
			}
		};

		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();

		ReactShallow.render(<Container />);

		return runAsync(() => {
			const Output = ReactShallow.getRenderOutput();
			assert.ok(TestUtils.isElementOfType(Output, ErrorMessage), 'Async component should work with failure state of async function');
		});
	});

	it('should throw an error if function is not async', () => {
		const ComponentFetch = props => <div />;
		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();

		assert.throws(() => ReactShallow.render(<Container />), /doesn't return Promise/);
	});

	it('should render <noscript /> until promise is not fulfilled', () => {
		const ComponentFetch = async props => {
			const data = await Promise.resolve(13);
			return <Component />;
		};

		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();

		ReactShallow.render(<Container />);

		const Output = ReactShallow.getRenderOutput();
		assert.ok(TestUtils.isElementOfType(Output, 'noscript'), 'Not fulfilled async component should render <noscript />');
	});

	xit('should throw an error if async function result is not React element', () => {
		const ComponentFetch = async props => await {};
		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();

		// TODO: finish this
	});
});

describe('Transmitter.Container', () => {
	class Container extends Transmitter {
		constructor(props) {
			super(props);
		}

		render() {
			return null;
		}
	}

	it('should produce React element', () => {
		assert.ok(React.isValidElement(<Container />), 'Container should produce React component');
	});

	it('should throw an error if `observe` method is not defined', () => {
		const ReactShallow = TestUtils.createRenderer();

		assert.throws(() => ReactShallow.render(<Container />), /Transmitter.Container requires `observe` method/);
	});
});

describe('Transmitter.fromStore', () => {
	it('should throws an error if store does not have getState method', () => {
		assert.throws(() => fromStore({}), /Store should have getState method/);
	});

	it('should throws an error if store does not have subscribe method', () => {
		assert.throws(() => fromStore({getState: () => null}), /Store should have subscribe method/);
	});

	xit('should work with addListener', () => {});

	xit('should return unsubscribe', () => {});

	xit('should start with getState() result', () => {});

	xit('should push updates from store', () => {});

	xit('should unsubscribe on dispose', () => {});
});
<commit_msg>added test case for `onFetch`<commit_after>import React from 'react';
import TestUtils from 'react/lib/ReactTestUtils';
import AsyncComponent from './src/AsyncComponent';
import Transmitter from './src/Container';
import fromStore from './src/fromStore';
import Redux from 'redux';
import assert from 'assert';
import sinon from 'sinon';

function log(target) {
	console.log(require('util').inspect(target, { depth: 3, showHidden: true }));
}

function runAsync(block) {
	return new Promise(resolve => setTimeout(async () => resolve(await block()), 10));
}

describe('AsyncComponent', () => {
	const Component = props => <div />;

	it('should create React component', () => {
		const ComponentFetch = async props => {
			await Promise.resolve(13);
			return <Component />;
		};

		const Container = AsyncComponent(ComponentFetch);

		assert.ok(TestUtils.isElement(<Container />), 'Container should be a React component');
		assert.equal(Container.displayName, 'Transmitter(ComponentFetch)', 'Container should have Component\'s name with prefix');
	});

	it('should render original component with resolved data', () => {
		const ComponentFetch = async props => {
			const data = await Promise.resolve(13);
			return <Component data={data} />;
		};

		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();

		ReactShallow.render(<Container />);

		return runAsync(() => {
			const Output = ReactShallow.getRenderOutput();

			assert.ok(TestUtils.isElementOfType(Output, Component), 'Returned component should be rendered');
			assert.deepEqual(Output.props, {data: 13}, 'Component should be rendered with data fetched via async function');
		});
	});

	it('should refetch data when props is changed', () => {
		const ComponentFetch = async props => {
			const data = await Promise.resolve(props.id);
			return <Component data={data + 1} />;
		};

		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();

		// TODO: add spy for `componentWillReceiveProps`
		ReactShallow.render(<Container id={1} />);
		ReactShallow.render(<Container id={2} />);

		return runAsync(() => {
			const Output = ReactShallow.getRenderOutput();

			assert.deepEqual(Output.props, {data: 3}, '');
		});
	});

	it('should work with rejected promises', () => {
		const ErrorMessage = props => <div />;
		const ComponentFetch = async props => {
			try {
				const data = await Promise.reject(13);
				return <Component />;
			} catch (e) {
				return <ErrorMessage />;
			}
		};

		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();

		ReactShallow.render(<Container />);

		return runAsync(() => {
			const Output = ReactShallow.getRenderOutput();
			assert.ok(TestUtils.isElementOfType(Output, ErrorMessage), 'Async component should work with failure state of async function');
		});
	});

	it('should throw an error if function is not async', () => {
		const ComponentFetch = props => <div />;
		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();

		assert.throws(() => ReactShallow.render(<Container />), /doesn't return Promise/);
	});

	it('should render <noscript /> until promise is not fulfilled', () => {
		const ComponentFetch = async props => {
			const data = await Promise.resolve(13);
			return <Component />;
		};

		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();

		ReactShallow.render(<Container />);

		const Output = ReactShallow.getRenderOutput();
		assert.ok(TestUtils.isElementOfType(Output, 'noscript'), 'Not fulfilled async component should render <noscript />');
	});

	xit('should throw an error if async function result is not React element', () => {
		const ComponentFetch = async props => await {};
		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();

		// TODO: finish this
	});

	it('should call `onFetch` after loading is finished', () => {
		const ComponentFetch = async props => {
			const data = await Promise.resolve(13);
			return <Component data={data} />;
		};
		const Container = AsyncComponent(ComponentFetch);
		const ReactShallow = TestUtils.createRenderer();
		const onFetch = sinon.spy();

		ReactShallow.render(<Container onFetch={onFetch} />);

		return runAsync(() => {
			const Output = ReactShallow.getRenderOutput();

			assert.ok(onFetch.calledWith('success'), '`onFetch` should be called with actual loading status');
		});
	});
});

describe('Transmitter.Container', () => {
	class Container extends Transmitter {
		constructor(props) {
			super(props);
		}

		render() {
			return null;
		}
	}

	it('should produce React element', () => {
		assert.ok(React.isValidElement(<Container />), 'Container should produce React component');
	});

	it('should throw an error if `observe` method is not defined', () => {
		const ReactShallow = TestUtils.createRenderer();

		assert.throws(() => ReactShallow.render(<Container />), /Transmitter.Container requires `observe` method/);
	});
});

describe('Transmitter.fromStore', () => {
	it('should throws an error if store does not have getState method', () => {
		assert.throws(() => fromStore({}), /Store should have getState method/);
	});

	it('should throws an error if store does not have subscribe method', () => {
		assert.throws(() => fromStore({getState: () => null}), /Store should have subscribe method/);
	});

	xit('should work with addListener', () => {});

	xit('should return unsubscribe', () => {});

	xit('should start with getState() result', () => {});

	xit('should push updates from store', () => {});

	xit('should unsubscribe on dispose', () => {});
});
<|endoftext|>