<commit_before>import React from "react";
import ReactDOM from "react-dom";
import ReactTestUtils from "react-dom/test-utils";
import * as ReactIs from "react-is";
import makeAsyncScriptLoader from "../src/async-script-loader";

class MockedComponent extends React.Component {
  render() {
    return ;
  }
}

const getScript = (URL, toBoolean = false) => {
  const scripTags = document.getElementsByTagName("script");
  for (let i = 0; i < scripTags.length; i += 1) {
    if (scripTags[i].src.indexOf(URL) > -1) {
      return toBoolean ? true : scripTags[i];
    }
  }
  return toBoolean ? false : undefined;
};

const hasScript = URL => getScript(URL, true);

const documentLoadScript = URL => {
  const script = getScript(URL);
  script.onload();
};

const documentErrorScript = URL => {
  const script = getScript(URL);
  script.onerror();
};

describe("AsyncScriptLoader", () => {
  it("should be imported successfully", () => {
    expect(makeAsyncScriptLoader).not.toBeNull();
  });

  it("should return a component that contains the passed component and fire asyncScriptOnLoad", () => {
    const URL = "http://example.com/?default=true";
    let asyncScriptOnLoadCalled = false;
    let scriptErrored = false;
    let scriptLoaded = false;
    const asyncScriptOnLoadSpy = entry => {
      scriptLoaded = entry.loaded;
      scriptErrored = entry.errored;
      asyncScriptOnLoadCalled = true;
    };
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponent);
    expect(ComponentWrapper.displayName).toEqual(
      "AsyncScriptLoader(MockedComponent)"
    );
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );
    documentLoadScript(URL);

    expect(ReactIs.isValidElementType(ComponentWrapper)).toEqual(true);
    expect(ReactIs.isForwardRef(<ComponentWrapper />)).toEqual(true);
    expect(hasScript(URL)).toEqual(true);
    expect(asyncScriptOnLoadCalled).toEqual(true);
    expect(scriptLoaded).toEqual(true);
    expect(scriptErrored).toEqual(undefined);
  });

  it("should fire asyncScriptOnLoad on errored script load", () => {
    const URL = "http://example.com/?errored=true";
    let asyncScriptOnLoadCalled = false;
    let scriptErrored = false;
    let scriptLoaded = false;
    const asyncScriptOnLoadSpy = entry => {
      scriptErrored = entry.errored;
      scriptLoaded = entry.loaded;
      asyncScriptOnLoadCalled = true;
    };
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );
    documentErrorScript(URL);

    expect(hasScript(URL)).toEqual(true);
    expect(asyncScriptOnLoadCalled).toEqual(true);
    expect(scriptErrored).toEqual(true);
    expect(scriptLoaded).toEqual(false);
  });

  it("should handle successfully already loaded global object and fire asyncScriptOnLoad", () => {
    const URL = "http://example.com/?global=true";
    const globalName = "SomeGlobal";
    window[globalName] = {};
    let asyncScriptOnLoadCalled = false;
    const asyncScriptOnLoadSpy = () => {
      asyncScriptOnLoadCalled = true;
    };
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL, {
      globalName: globalName
    })(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );

    expect(hasScript(URL)).toEqual(false);
    expect(asyncScriptOnLoadCalled).toEqual(true);
    delete window[globalName];
  });

  it("should accept a function for scriptURL", () => {
    const URL = "http://example.com/?url=function";
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(() => URL)(MockedComponent);
    ReactTestUtils.renderIntoDocument(<ComponentWrapper />);

    expect(hasScript(URL)).toEqual(true);
  });

  it("should add a id to the script tag", () => {
    const URL = "http://example.com/?has_an_id=true";
    const scriptId = "SOME_SCRIPT_ID";

    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL, { scriptId })(
      MockedComponent
    );
    ReactTestUtils.renderIntoDocument(<ComponentWrapper />);

    expect(hasScript(URL)).toEqual(true);
    const script = getScript(URL);
    expect(script.id).toEqual(scriptId);
  });

  it("should expose statics", done => {
    class MockedComponentWithStatic extends React.Component {
      static callsACallback(fn) {
        fn();
      }
      render() {
        return ;
      }
    }
    const URL = "http://example.com/?functions=true";
    const ComponentWrapper = makeAsyncScriptLoader(URL)(
      MockedComponentWithStatic
    );
    ComponentWrapper.callsACallback(done);
  });

  it("should not remove tag script on removeOnUnmount option not set", () => {
    const URL = "http://example.com/?removeOnUnmount=notset";
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponent);
    const instance = ReactTestUtils.renderIntoDocument(

        <ComponentWrapper />

    );

    expect(hasScript(URL)).toEqual(true);
    const unmounted = ReactDOM.unmountComponentAtNode(
      ReactDOM.findDOMNode(instance).parentNode
    );
    expect(unmounted).toEqual(true);
    expect(hasScript(URL)).toEqual(true);
  });

  it("should remove tag script on removeOnUnmount option set to true", () => {
    const URL = "http://example.com/?removeOnUnmount=true";
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL, {
      removeOnUnmount: true
    })(MockedComponent);
    const instance = ReactTestUtils.renderIntoDocument(

        <ComponentWrapper />

    );

    expect(hasScript(URL)).toEqual(true);
    const unmounted = ReactDOM.unmountComponentAtNode(
      ReactDOM.findDOMNode(instance).parentNode
    );
    expect(unmounted).toEqual(true);
    expect(hasScript(URL)).toEqual(false);
  });

  it("should allow you to access methods on the wrappedComponent via ref callback", done => {
    // internal component with method we want access to
    class InternalComponent extends React.Component {
      internalCallsACallback(fn) {
        fn();
      }
      render() {
        return
;
      }
    }
    const URL = "http://example.com/?ref=true";
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL)(InternalComponent);

    // wrapping component that applies a ref to our AsyncHOC(InternalComponent)
    // eslint-disable-next-line no-unused-vars
    class WrappingComponent extends React.Component {
      render() {
        return (

            <ComponentWrapper ref={r => (this._internalRef = r)} />

        );
      }
    }
    const instance = ReactTestUtils.renderIntoDocument(<WrappingComponent />);

    expect(hasScript(URL)).toEqual(true);
    expect(instance._internalRef.internalCallsACallback).toBeTruthy();
    instance._internalRef.internalCallsACallback(done);
  });

  it("should allow you to access methods on the wrappedComponent via createRef", done => {
    // internal component with method we want access to
    class InternalComponent extends React.Component {
      internalCallsACallback(fn) {
        fn();
      }
      render() {
        return
;
      }
    }
    const URL = "http://example.com/?createRef=true";
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL)(InternalComponent);

    // wrapping component that applies a ref to our AsyncHOC(InternalComponent)
    // eslint-disable-next-line no-unused-vars
    class WrappingComponent extends React.Component {
      constructor(props) {
        super(props);
        this._internalRef = React.createRef();
      }
      render() {
        return (

            <ComponentWrapper ref={this._internalRef} />

        );
      }
    }
    const instance = ReactTestUtils.renderIntoDocument(<WrappingComponent />);

    expect(hasScript(URL)).toEqual(true);
    expect(instance._internalRef.current.internalCallsACallback).toBeTruthy();
    instance._internalRef.current.internalCallsACallback(done);
  });

  it("should expose global callback", () => {
    const callbackName = 'exampleCallback';
    const URL = `http://example.com/?callback=${callbackName}`;
    const ComponentWrapper = makeAsyncScriptLoader(URL, {
      callbackName
    })(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper />
    );

    expect(typeof window[callbackName]).toBe('function');
    delete window[callbackName];
  })

  it("should call loaded if global callback called before onLoad", () => {
    const callbackName = 'exampleCallbackBeforeOnload';
    const URL = `http://example.com/?callback=${callbackName}`;
    let asyncScriptOnLoadCalled = false;
    const asyncScriptOnLoadSpy = () => {
      asyncScriptOnLoadCalled = true;
    };
    const ComponentWrapper = makeAsyncScriptLoader(URL, {
      callbackName
    })(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );

    window[callbackName]();
    expect(asyncScriptOnLoadCalled).toEqual(true);
    delete window[callbackName];
  })

  it("should call loaded only once if global callback then onload", () => {
    const callbackName = 'exampleCallbackThenOnload';
    const URL = `http://example.com/?callback=${callbackName}`;
    let asyncScriptOnLoadCalled = false;
    let callCount = 0;
    const asyncScriptOnLoadSpy = () => {
      asyncScriptOnLoadCalled = true;
      callCount++;
    };
    const ComponentWrapper = makeAsyncScriptLoader(URL, {
      callbackName
    })(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );

    window[callbackName]();
    expect(asyncScriptOnLoadCalled).toEqual(true);
    documentLoadScript(URL);
    expect(callCount).toEqual(1);
    delete window[callbackName];
  })

  it("should call loaded only once if onload then global callback", () => {
    const callbackName = 'exampleOnloadThenCallback';
    const URL = `http://example.com/?callback=${callbackName}`;
    let asyncScriptOnLoadCalled = false;
    let callCount = 0;
    const asyncScriptOnLoadSpy = () => {
      asyncScriptOnLoadCalled = true;
      callCount++;
    };
    const ComponentWrapper = makeAsyncScriptLoader(URL, {
      callbackName
    })(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );

    documentLoadScript(URL);
    expect(asyncScriptOnLoadCalled).toEqual(true);
    window[callbackName]();
    expect(callCount).toEqual(1);
    delete window[callbackName];
  })
});
<commit_msg>Run prettier and fix lint errors<commit_after>import React from "react";
import ReactDOM from "react-dom";
import ReactTestUtils from "react-dom/test-utils";
import * as ReactIs from "react-is";
import makeAsyncScriptLoader from "../src/async-script-loader";

class MockedComponent extends React.Component {
  render() {
    return ;
  }
}

const getScript = (URL, toBoolean = false) => {
  const scripTags = document.getElementsByTagName("script");
  for (let i = 0; i < scripTags.length; i += 1) {
    if (scripTags[i].src.indexOf(URL) > -1) {
      return toBoolean ? true : scripTags[i];
    }
  }
  return toBoolean ? false : undefined;
};

const hasScript = URL => getScript(URL, true);

const documentLoadScript = URL => {
  const script = getScript(URL);
  script.onload();
};

const documentErrorScript = URL => {
  const script = getScript(URL);
  script.onerror();
};

describe("AsyncScriptLoader", () => {
  it("should be imported successfully", () => {
    expect(makeAsyncScriptLoader).not.toBeNull();
  });

  it("should return a component that contains the passed component and fire asyncScriptOnLoad", () => {
    const URL = "http://example.com/?default=true";
    let asyncScriptOnLoadCalled = false;
    let scriptErrored = false;
    let scriptLoaded = false;
    const asyncScriptOnLoadSpy = entry => {
      scriptLoaded = entry.loaded;
      scriptErrored = entry.errored;
      asyncScriptOnLoadCalled = true;
    };
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponent);
    expect(ComponentWrapper.displayName).toEqual(
      "AsyncScriptLoader(MockedComponent)"
    );
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );
    documentLoadScript(URL);

    expect(ReactIs.isValidElementType(ComponentWrapper)).toEqual(true);
    expect(ReactIs.isForwardRef(<ComponentWrapper />)).toEqual(true);
    expect(hasScript(URL)).toEqual(true);
    expect(asyncScriptOnLoadCalled).toEqual(true);
    expect(scriptLoaded).toEqual(true);
    expect(scriptErrored).toEqual(undefined);
  });

  it("should fire asyncScriptOnLoad on errored script load", () => {
    const URL = "http://example.com/?errored=true";
    let asyncScriptOnLoadCalled = false;
    let scriptErrored = false;
    let scriptLoaded = false;
    const asyncScriptOnLoadSpy = entry => {
      scriptErrored = entry.errored;
      scriptLoaded = entry.loaded;
      asyncScriptOnLoadCalled = true;
    };
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );
    documentErrorScript(URL);

    expect(hasScript(URL)).toEqual(true);
    expect(asyncScriptOnLoadCalled).toEqual(true);
    expect(scriptErrored).toEqual(true);
    expect(scriptLoaded).toEqual(false);
  });

  it("should handle successfully already loaded global object and fire asyncScriptOnLoad", () => {
    const URL = "http://example.com/?global=true";
    const globalName = "SomeGlobal";
    window[globalName] = {};
    let asyncScriptOnLoadCalled = false;
    const asyncScriptOnLoadSpy = () => {
      asyncScriptOnLoadCalled = true;
    };
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL, {
      globalName: globalName
    })(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );

    expect(hasScript(URL)).toEqual(false);
    expect(asyncScriptOnLoadCalled).toEqual(true);
    delete window[globalName];
  });

  it("should accept a function for scriptURL", () => {
    const URL = "http://example.com/?url=function";
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(() => URL)(MockedComponent);
    ReactTestUtils.renderIntoDocument(<ComponentWrapper />);

    expect(hasScript(URL)).toEqual(true);
  });

  it("should add a id to the script tag", () => {
    const URL = "http://example.com/?has_an_id=true";
    const scriptId = "SOME_SCRIPT_ID";

    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL, { scriptId })(
      MockedComponent
    );
    ReactTestUtils.renderIntoDocument(<ComponentWrapper />);

    expect(hasScript(URL)).toEqual(true);
    const script = getScript(URL);
    expect(script.id).toEqual(scriptId);
  });

  it("should expose statics", done => {
    class MockedComponentWithStatic extends React.Component {
      static callsACallback(fn) {
        fn();
      }
      render() {
        return ;
      }
    }
    const URL = "http://example.com/?functions=true";
    const ComponentWrapper = makeAsyncScriptLoader(URL)(
      MockedComponentWithStatic
    );
    ComponentWrapper.callsACallback(done);
  });

  it("should not remove tag script on removeOnUnmount option not set", () => {
    const URL = "http://example.com/?removeOnUnmount=notset";
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponent);
    const instance = ReactTestUtils.renderIntoDocument(

        <ComponentWrapper />

    );

    expect(hasScript(URL)).toEqual(true);
    const unmounted = ReactDOM.unmountComponentAtNode(
      ReactDOM.findDOMNode(instance).parentNode
    );
    expect(unmounted).toEqual(true);
    expect(hasScript(URL)).toEqual(true);
  });

  it("should remove tag script on removeOnUnmount option set to true", () => {
    const URL = "http://example.com/?removeOnUnmount=true";
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL, {
      removeOnUnmount: true
    })(MockedComponent);
    const instance = ReactTestUtils.renderIntoDocument(

        <ComponentWrapper />

    );

    expect(hasScript(URL)).toEqual(true);
    const unmounted = ReactDOM.unmountComponentAtNode(
      ReactDOM.findDOMNode(instance).parentNode
    );
    expect(unmounted).toEqual(true);
    expect(hasScript(URL)).toEqual(false);
  });

  it("should allow you to access methods on the wrappedComponent via ref callback", done => {
    // internal component with method we want access to
    class InternalComponent extends React.Component {
      internalCallsACallback(fn) {
        fn();
      }
      render() {
        return
;
      }
    }
    const URL = "http://example.com/?ref=true";
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL)(InternalComponent);

    // wrapping component that applies a ref to our AsyncHOC(InternalComponent)
    // eslint-disable-next-line no-unused-vars
    class WrappingComponent extends React.Component {
      render() {
        return (

            <ComponentWrapper ref={r => (this._internalRef = r)} />

        );
      }
    }
    const instance = ReactTestUtils.renderIntoDocument(<WrappingComponent />);

    expect(hasScript(URL)).toEqual(true);
    expect(instance._internalRef.internalCallsACallback).toBeTruthy();
    instance._internalRef.internalCallsACallback(done);
  });

  it("should allow you to access methods on the wrappedComponent via createRef", done => {
    // internal component with method we want access to
    class InternalComponent extends React.Component {
      internalCallsACallback(fn) {
        fn();
      }
      render() {
        return
;
      }
    }
    const URL = "http://example.com/?createRef=true";
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL)(InternalComponent);

    // wrapping component that applies a ref to our AsyncHOC(InternalComponent)
    // eslint-disable-next-line no-unused-vars
    class WrappingComponent extends React.Component {
      constructor(props) {
        super(props);
        this._internalRef = React.createRef();
      }
      render() {
        return (

            <ComponentWrapper ref={this._internalRef} />

        );
      }
    }
    const instance = ReactTestUtils.renderIntoDocument(<WrappingComponent />);

    expect(hasScript(URL)).toEqual(true);
    expect(instance._internalRef.current.internalCallsACallback).toBeTruthy();
    instance._internalRef.current.internalCallsACallback(done);
  });

  it("should expose global callback", () => {
    const callbackName = "exampleCallback";
    const URL = `http://example.com/?callback=${callbackName}`;
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL, {
      callbackName
    })(MockedComponent);
    ReactTestUtils.renderIntoDocument(<ComponentWrapper />);

    expect(typeof window[callbackName]).toBe("function");
    delete window[callbackName];
  });

  it("should call loaded if global callback called before onLoad", () => {
    const callbackName = "exampleCallbackBeforeOnload";
    const URL = `http://example.com/?callback=${callbackName}`;
    let asyncScriptOnLoadCalled = false;
    const asyncScriptOnLoadSpy = () => {
      asyncScriptOnLoadCalled = true;
    };
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL, {
      callbackName
    })(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );

    window[callbackName]();
    expect(asyncScriptOnLoadCalled).toEqual(true);
    delete window[callbackName];
  });

  it("should call loaded only once if global callback then onload", () => {
    const callbackName = "exampleCallbackThenOnload";
    const URL = `http://example.com/?callback=${callbackName}`;
    let asyncScriptOnLoadCalled = false;
    let callCount = 0;
    const asyncScriptOnLoadSpy = () => {
      asyncScriptOnLoadCalled = true;
      callCount++;
    };
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL, {
      callbackName
    })(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );

    window[callbackName]();
    expect(asyncScriptOnLoadCalled).toEqual(true);
    documentLoadScript(URL);
    expect(callCount).toEqual(1);
    delete window[callbackName];
  });

  it("should call loaded only once if onload then global callback", () => {
    const callbackName = "exampleOnloadThenCallback";
    const URL = `http://example.com/?callback=${callbackName}`;
    let asyncScriptOnLoadCalled = false;
    let callCount = 0;
    const asyncScriptOnLoadSpy = () => {
      asyncScriptOnLoadCalled = true;
      callCount++;
    };
    // eslint-disable-next-line no-unused-vars
    const ComponentWrapper = makeAsyncScriptLoader(URL, {
      callbackName
    })(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );

    documentLoadScript(URL);
    expect(asyncScriptOnLoadCalled).toEqual(true);
    window[callbackName]();
    expect(callCount).toEqual(1);
    delete window[callbackName];
  });
});
<|endoftext|>


<commit_before>/**
 * Nanocloud turns any traditional software into a cloud solution, without
 * changing or redeveloping existing source code.
 *
 * Copyright (C) 2016 Nanocloud Software
 *
 * This file is part of Nanocloud.
 *
 * Nanocloud is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Nanocloud is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General
 * Public License
 * along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

import Ember from 'ember';

/* global $:false */
var FileUploader = Ember.Object.extend(Ember.Evented, {
  completed: false,
  progress: 0,
  uploading: false,

  init() {
    this._super(...arguments);
    this.startUpload();
  },

  name: Ember.computed('file', function() {
    return this.get('file').name;
  }),

  startUpload() {
    let req = new XMLHttpRequest();

    this.set('req', req);

    req.open('POST', '/api/upload?filename=' + encodeURIComponent(this.get('file').name));
    req.setRequestHeader('Authorization', 'Bearer ' + this.get('token'));

    req.upload.onprogress = (event) => {
      if (event.lengthComputable) {
        let p = event.loaded / event.total;
        if (p === 1) {
          this.set('uploading', false);
        }
        this.set('progress', Math.round(event.loaded / event.total * 100));
      }
    };

    req.onload = () => {
      this.set('completed', true);
      this.trigger('completed');
      this.set('uploading', false);
    };

    this.set('uploading', true);

    var formData = new FormData();
    formData.append(this.get('file').name, this.get('file'));

    req.send(formData);
  },

  cancel(preventEvent) {
    this.get('req').abort();
    this.set('uploading', false);

    if (!preventEvent) {
      this.trigger('canceled');
    }
  }
});

export default Ember.Component.extend({
  classNames: ['vdi-drag-n-drop'],
  classNameBindings: ['show:state-show:state-hide'],
  session: Ember.inject.service('session'),

  progress: Ember.computed('queue.@each.uploading', 'queue.@each.progress', function() {
    let q = this.get('queue');
    let len = q.get('length');
    if (len) {
      return Math.round(q.filterBy('uploading').reduce((a, b) => a + b.get('progress'), 0) / q.get('length'));
    } else {
      return 0;
    }
  }),

  uploading: Ember.computed('queue.@each.uploading', function() {
    return this.get('queue').isAny('uploading');
  }),

  show: false,
  queue: null,
  state: null,

  init: function() {
    this._super(...arguments);
    Ember.$('body').off('dragenter dragover');
    this.set('queue', Ember.A([]));
  },

  showElement() {
    this.set('show', true);
  },

  hideElement() {
    this.set('show', false);
  },

  dragLeave() {
    this.set('dragAndDropActive', false);
    this.hideElement();
  },

  drop(event) {
    event.preventDefault();
    this.set('dragAndDropActive', false);
    this.hideElement();

    this.startDownload(event.dataTransfer.files);
  },

  startDownload(files){
    let q = this.get('queue');

    for (let i = 0; i < files.length; i++) {
      let f = FileUploader.create({
        file: files[i],
        token: this.get('session.access_token')
      });
      f.one('completed', this, () => {
        this.completeNotif();
      });

      f.one('canceled', this, this.abortNotif);
      q.pushObject(f);
    }
  },

  removeCompleteDownload() {
    let q = this.get('queue');
    let len = q.get('length');
    let i = 0;

    while (i < len) {
      if (!q.objectAt(i).get('uploading')) {
        q.removeAt(i);
        --len;
      } else {
        ++i;
      }
    }
  },

  didInsertElement() {
    Ember.$('body').on('dragenter dragover', (event) => {
      event.preventDefault();

      this.set('show', true);
      if (this.get('dragAndDropActive') === false) {
        this.set('dragAndDropActive', true);
        this.showElement();
      }
    });

    if (this.get('assignBrowse')) {
      var input = $('<input>', { type: "file" })
      .css({
        "visibility": "hidden",
        "position": "absolute",
        "width": "1px",
        "height": "1px"
      })
      .on('change', (event) => {
        this.startDownload(event.target.files);
      });
      $('.' + this.get('assignBrowse'))
        .after(input)
        .on('click', function() {
          input.click();
        });
    }
  },

  completeNotif() {
    this.sendAction('complete');
    this.toast.success('Upload successful');
  },

  abortNotif() {
    this.toast.info('Abort successful');
  },

  downloadCompleted() {
    if (this.get('state') !== 'Aborted') {
      this.completeNotif();

      this.set('state', 'Completed');
      Ember.run.next(() => {
        Ember.$('.state').fadeOut(700, () => {
          this.set('state', null);
          Ember.$('.state').fadeIn(0);
        });
      }, 3000);
    }
    else {
      this.set('state', null);
    }
  },

  stopUpload() {
    this.get('queue').invoke('cancel', true);
    this.abortNotif();
    this.set('state', 'Aborted');
    this.downloadCompleted();
  },

  actions: {
    cancelUpload() {
      this.stopUpload();
    },

    flushHistory() {
      this.removeCompleteDownload();
    }
  },
});
<commit_msg>Add notification in front.<commit_after>/**
 * Nanocloud turns any traditional software into a cloud solution, without
 * changing or redeveloping existing source code.
 *
 * Copyright (C) 2016 Nanocloud Software
 *
 * This file is part of Nanocloud.
 *
 * Nanocloud is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Nanocloud is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General
 * Public License
 * along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

import Ember from 'ember';

/* global $:false */
var FileUploader = Ember.Object.extend(Ember.Evented, {
  completed: false,
  progress: 0,
  uploading: false,
  forbidden: false,

  init() {
    this._super(...arguments);
    this.startUpload();
  },

  name: Ember.computed('file', function() {
    return this.get('file').name;
  }),

  startUpload() {
    let req = new XMLHttpRequest();

    this.set('req', req);

    req.open('POST', '/api/upload?filename=' + encodeURIComponent(this.get('file').name));
    req.setRequestHeader('Authorization', 'Bearer ' + this.get('token'));

    req.upload.onprogress = (event) => {
      if (event.lengthComputable) {
        let p = event.loaded / event.total;
        if (p === 1) {
          this.set('uploading', false);
        }
        this.set('progress', Math.round(event.loaded / event.total * 100));
      }
    };

    req.onload = (res) => {
      if (res.target.status === 403) {
        this.set('forbidden', true);
        this.trigger('forbidden');
      } else {
        this.set('completed', true);
        this.trigger('completed');
      }
      this.set('uploading', false);
    };

    this.set('uploading', true);

    var formData = new FormData();
    formData.append(this.get('file').name, this.get('file'));

    req.send(formData);
  },

  cancel(preventEvent) {
    this.get('req').abort();
    this.set('uploading', false);

    if (!preventEvent) {
      this.trigger('canceled');
    }
  }
});

export default Ember.Component.extend({
  classNames: ['vdi-drag-n-drop'],
  classNameBindings: ['show:state-show:state-hide'],
  session: Ember.inject.service('session'),

  progress: Ember.computed('queue.@each.uploading', 'queue.@each.progress', function() {
    let q = this.get('queue');
    let len = q.get('length');
    if (len) {
      return Math.round(q.filterBy('uploading').reduce((a, b) => a + b.get('progress'), 0) / q.get('length'));
    } else {
      return 0;
    }
  }),

  uploading: Ember.computed('queue.@each.uploading', function() {
    return this.get('queue').isAny('uploading');
  }),

  show: false,
  queue: null,
  state: null,

  init: function() {
    this._super(...arguments);
    Ember.$('body').off('dragenter dragover');
    this.set('queue', Ember.A([]));
  },

  showElement() {
    this.set('show', true);
  },

  hideElement() {
    this.set('show', false);
  },

  dragLeave() {
    this.set('dragAndDropActive', false);
    this.hideElement();
  },

  drop(event) {
    event.preventDefault();
    this.set('dragAndDropActive', false);
    this.hideElement();

    this.startDownload(event.dataTransfer.files);
  },

  startDownload(files){
    let q = this.get('queue');

    for (let i = 0; i < files.length; i++) {
      let f = FileUploader.create({
        file: files[i],
        token: this.get('session.access_token')
      });
      f.one('completed', this, this.completeNotif);
      f.one('forbidden', this, this.limitNotif);
      f.one('canceled', this, this.abortNotif);
      q.pushObject(f);
    }
  },

  removeCompleteDownload() {
    let q = this.get('queue');
    let len = q.get('length');
    let i = 0;

    while (i < len) {
      if (!q.objectAt(i).get('uploading')) {
        q.removeAt(i);
        --len;
      } else {
        ++i;
      }
    }
  },

  didInsertElement() {
    Ember.$('body').on('dragenter dragover', (event) => {
      event.preventDefault();

      this.set('show', true);
      if (this.get('dragAndDropActive') === false) {
        this.set('dragAndDropActive', true);
        this.showElement();
      }
    });

    if (this.get('assignBrowse')) {
      var input = $('<input>', { type: "file" })
      .css({
        "visibility": "hidden",
        "position": "absolute",
        "width": "1px",
        "height": "1px"
      })
      .on('change', (event) => {
        this.startDownload(event.target.files);
      });
      $('.' + this.get('assignBrowse'))
        .after(input)
        .on('click', function() {
          input.click();
        });
    }
  },

  completeNotif() {
    this.sendAction('complete');
    this.toast.success('Upload successful');
  },

  abortNotif() {
    this.toast.info('Abort successful');
  },

  limitNotif() {
    this.toast.error('The upload limit is reached');
  },

  downloadCompleted() {
    if (this.get('state') !== 'Aborted') {
      this.completeNotif();

      this.set('state', 'Completed');
      Ember.run.next(() => {
        Ember.$('.state').fadeOut(700, () => {
          this.set('state', null);
          Ember.$('.state').fadeIn(0);
        });
      }, 3000);
    }
    else {
      this.set('state', null);
    }
  },

  stopUpload() {
    this.get('queue').invoke('cancel', true);
    this.abortNotif();
    this.set('state', 'Aborted');
    this.downloadCompleted();
  },

  actions: {
    cancelUpload() {
      this.stopUpload();
    },

    flushHistory() {
      this.removeCompleteDownload();
    }
  },
});
<|endoftext|>


<commit_before>import React from "react";
import ReactDOM from "react-dom";
import ReactTestUtils from "react-dom/test-utils";
import * as ReactIs from "react-is";
import makeAsyncScriptLoader from "../src/async-script-loader";

class MockedComponent extends React.Component {
  render() {
    return ;
  }
}

const getScript = (URL, toBoolean = false) => {
  const scripTags = document.getElementsByTagName("script");
  for (let i = 0; i < scripTags.length; i += 1) {
    if (scripTags[i].src.indexOf(URL) > -1) {
      return toBoolean ? true : scripTags[i];
    }
  }
  return toBoolean ? false : undefined;
}

const hasScript = (URL) => getScript(URL, true)

const documentLoadScript = (URL) => {
  const script = getScript(URL);
  script.onload();
}

const documentErrorScript = (URL) => {
  const script = getScript(URL);
  script.onerror();
}

describe("AsyncScriptLoader", () => {
  it("should be imported successfully", () => {
    assert.isNotNull(makeAsyncScriptLoader);
  });

  it("should return a component that contains the passed component and fire asyncScriptOnLoad", () => {
    const URL = "http://example.com/?default=true";
    let asyncScriptOnLoadCalled = false;
    let scriptErrored = false;
    let scriptLoaded = false;
    const asyncScriptOnLoadSpy = (entry) => {
      scriptLoaded = entry.loaded;
      scriptErrored = entry.errored;
      asyncScriptOnLoadCalled = true;
    }
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponent);
    assert.equal(ComponentWrapper.displayName, "AsyncScriptLoader(MockedComponent)");
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );
    documentLoadScript(URL);

    assert.equal(ReactIs.isValidElementType(ComponentWrapper), true, "is valid elemnt type");
    assert.equal(hasScript(URL), true, "Url in document");
    assert.equal(asyncScriptOnLoadCalled, true, "asyncScriptOnLoad callback called");
    assert.equal(scriptLoaded, true, "script loaded state set");
    assert.equal(scriptErrored, undefined, "script errored state unset");
  });

  it("should fire asyncScriptOnLoad on errored script load", () => {
    const URL = "http://example.com/?errored=true";
    let asyncScriptOnLoadCalled = false;
    let scriptErrored = false;
    let scriptLoaded = false;
    const asyncScriptOnLoadSpy = (entry) => {
      scriptErrored = entry.errored;
      scriptLoaded = entry.loaded;
      asyncScriptOnLoadCalled = true;
    }
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );
    documentErrorScript(URL);

    assert.equal(hasScript(URL), true, "Url in document");
    assert.equal(asyncScriptOnLoadCalled, true, "asyncScriptOnLoad callback called");
    assert.equal(scriptErrored, true, "script errored state set");
    assert.equal(scriptLoaded, false, "script loaded state set");
  });

  it("should handle successfully already loaded global object and fire asyncScriptOnLoad", () => {
    const URL = "http://example.com/?global=true";
    const globalName = "SomeGlobal";
    window[globalName] = {};
    let asyncScriptOnLoadCalled = false;
    const asyncScriptOnLoadSpy = () => {
      asyncScriptOnLoadCalled = true;
    }
    const ComponentWrapper = makeAsyncScriptLoader(URL, { globalName: globalName })(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );

    assert.equal(hasScript(URL), false, "Url not in document");
    assert.equal(asyncScriptOnLoadCalled, true, "asyncScriptOnLoad callback called");
    delete window[globalName];
  });

  it("should accept a function for scriptURL", () => {
    const URL = "http://example.com/?url=function";
    const ComponentWrapper = makeAsyncScriptLoader(() => URL)(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper />
    );

    assert.equal(hasScript(URL), true, "Url in document");
  });

  it("should expose statics", (done) => {
    class MockedComponentWithStatic extends React.Component {
      static callsACallback(fn) { fn(); }
      render() { return ; }
    }
    const URL = "http://example.com/?functions=true";
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponentWithStatic);
    ComponentWrapper.callsACallback(done);
  });

  it("should not remove tag script on removeOnUnmount option not set", () => {
    const URL = "http://example.com/?removeOnUnmount=notset";
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponent);
    const instance = ReactTestUtils.renderIntoDocument(

        <ComponentWrapper />

    );

    assert.equal(hasScript(URL), true, "Url in document");
    const unmounted = ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(instance).parentNode);
    assert.equal(unmounted, true, "successfully unmounted");
    assert.equal(hasScript(URL), true, "Url still in document after unmounting");
  });

  it("should remove tag script on removeOnUnmount option set to true", () => {
    const URL = "http://example.com/?removeOnUnmount=true";
    const ComponentWrapper = makeAsyncScriptLoader(URL, { removeOnUnmount: true })(MockedComponent);
    const instance = ReactTestUtils.renderIntoDocument(

        <ComponentWrapper />

    );

    assert.equal(hasScript(URL), true, "Url in document");
    const unmounted = ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(instance).parentNode);
    assert.equal(unmounted, true, "successfully unmounted");
    assert.equal(hasScript(URL), false, "Url not in document after unmounting");
  });

  it("should allow you to access methods on the wrappedComponent via ref callback", (done) => {
    // internal component with method we want access to
    class InternalComponent extends React.Component {
      internalCallsACallback(fn) { fn(); }
      render() { return (
 )}
    }
    const URL = "http://example.com/?ref=true";
    const ComponentWrapper = makeAsyncScriptLoader(URL)(InternalComponent);

    // wrapping component that applies a ref to our AsyncHOC(InternalComponent)
    class WrappingComponent extends React.Component {
      render() { return (
<ComponentWrapper ref={(r) => this._internalRef = r} />
)}
    }
    const instance = ReactTestUtils.renderIntoDocument(
      <WrappingComponent />
    );

    assert.equal(hasScript(URL), true, "Url in document");
    assert.isOk(instance._internalRef.internalCallsACallback, "internal components method available");
    instance._internalRef.internalCallsACallback(done);
  });

  it("should allow you to access methods on the wrappedComponent via createRef", (done) => {
    // internal component with method we want access to
    class InternalComponent extends React.Component {
      internalCallsACallback(fn) { fn(); }
      render() { return (
 )}
    }
    const URL = "http://example.com/?createRef=true";
    const ComponentWrapper = makeAsyncScriptLoader(URL)(InternalComponent);

    // wrapping component that applies a ref to our AsyncHOC(InternalComponent)
    class WrappingComponent extends React.Component {
      constructor(props) {
        super(props);
        this._internalRef = React.createRef();
      }
      render() { return (
<ComponentWrapper ref={this._internalRef} />
)}
    }
    const instance = ReactTestUtils.renderIntoDocument(
      <WrappingComponent />
    );

    assert.equal(hasScript(URL), true, "Url in document");
    assert.isOk(instance._internalRef.current.internalCallsACallback, "internal components method available");
    instance._internalRef.current.internalCallsACallback(done);
  });
});
<commit_msg>add test for isForwardRef<commit_after>import React from "react";
import ReactDOM from "react-dom";
import ReactTestUtils from "react-dom/test-utils";
import * as ReactIs from "react-is";
import makeAsyncScriptLoader from "../src/async-script-loader";

class MockedComponent extends React.Component {
  render() {
    return ;
  }
}

const getScript = (URL, toBoolean = false) => {
  const scripTags = document.getElementsByTagName("script");
  for (let i = 0; i < scripTags.length; i += 1) {
    if (scripTags[i].src.indexOf(URL) > -1) {
      return toBoolean ? true : scripTags[i];
    }
  }
  return toBoolean ? false : undefined;
}

const hasScript = (URL) => getScript(URL, true)

const documentLoadScript = (URL) => {
  const script = getScript(URL);
  script.onload();
}

const documentErrorScript = (URL) => {
  const script = getScript(URL);
  script.onerror();
}

describe("AsyncScriptLoader", () => {
  it("should be imported successfully", () => {
    assert.isNotNull(makeAsyncScriptLoader);
  });

  it("should return a component that contains the passed component and fire asyncScriptOnLoad", () => {
    const URL = "http://example.com/?default=true";
    let asyncScriptOnLoadCalled = false;
    let scriptErrored = false;
    let scriptLoaded = false;
    const asyncScriptOnLoadSpy = (entry) => {
      scriptLoaded = entry.loaded;
      scriptErrored = entry.errored;
      asyncScriptOnLoadCalled = true;
    }
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponent);
    assert.equal(ComponentWrapper.displayName, "AsyncScriptLoader(MockedComponent)");
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );
    documentLoadScript(URL);

    assert.equal(ReactIs.isValidElementType(ComponentWrapper), true, "is valid elemnt type");
    assert.equal(ReactIs.isForwardRef(<ComponentWrapper />), true, "is valid forwardRef");
    assert.equal(hasScript(URL), true, "Url in document");
    assert.equal(asyncScriptOnLoadCalled, true, "asyncScriptOnLoad callback called");
    assert.equal(scriptLoaded, true, "script loaded state set");
    assert.equal(scriptErrored, undefined, "script errored state unset");
  });

  it("should fire asyncScriptOnLoad on errored script load", () => {
    const URL = "http://example.com/?errored=true";
    let asyncScriptOnLoadCalled = false;
    let scriptErrored = false;
    let scriptLoaded = false;
    const asyncScriptOnLoadSpy = (entry) => {
      scriptErrored = entry.errored;
      scriptLoaded = entry.loaded;
      asyncScriptOnLoadCalled = true;
    }
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );
    documentErrorScript(URL);

    assert.equal(hasScript(URL), true, "Url in document");
    assert.equal(asyncScriptOnLoadCalled, true, "asyncScriptOnLoad callback called");
    assert.equal(scriptErrored, true, "script errored state set");
    assert.equal(scriptLoaded, false, "script loaded state set");
  });

  it("should handle successfully already loaded global object and fire asyncScriptOnLoad", () => {
    const URL = "http://example.com/?global=true";
    const globalName = "SomeGlobal";
    window[globalName] = {};
    let asyncScriptOnLoadCalled = false;
    const asyncScriptOnLoadSpy = () => {
      asyncScriptOnLoadCalled = true;
    }
    const ComponentWrapper = makeAsyncScriptLoader(URL, { globalName: globalName })(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper asyncScriptOnLoad={asyncScriptOnLoadSpy} />
    );

    assert.equal(hasScript(URL), false, "Url not in document");
    assert.equal(asyncScriptOnLoadCalled, true, "asyncScriptOnLoad callback called");
    delete window[globalName];
  });

  it("should accept a function for scriptURL", () => {
    const URL = "http://example.com/?url=function";
    const ComponentWrapper = makeAsyncScriptLoader(() => URL)(MockedComponent);
    ReactTestUtils.renderIntoDocument(
      <ComponentWrapper />
    );

    assert.equal(hasScript(URL), true, "Url in document");
  });

  it("should expose statics", (done) => {
    class MockedComponentWithStatic extends React.Component {
      static callsACallback(fn) { fn(); }
      render() { return ; }
    }
    const URL = "http://example.com/?functions=true";
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponentWithStatic);
    ComponentWrapper.callsACallback(done);
  });

  it("should not remove tag script on removeOnUnmount option not set", () => {
    const URL = "http://example.com/?removeOnUnmount=notset";
    const ComponentWrapper = makeAsyncScriptLoader(URL)(MockedComponent);
    const instance = ReactTestUtils.renderIntoDocument(

        <ComponentWrapper />

    );

    assert.equal(hasScript(URL), true, "Url in document");
    const unmounted = ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(instance).parentNode);
    assert.equal(unmounted, true, "successfully unmounted");
    assert.equal(hasScript(URL), true, "Url still in document after unmounting");
  });

  it("should remove tag script on removeOnUnmount option set to true", () => {
    const URL = "http://example.com/?removeOnUnmount=true";
    const ComponentWrapper = makeAsyncScriptLoader(URL, { removeOnUnmount: true })(MockedComponent);
    const instance = ReactTestUtils.renderIntoDocument(

        <ComponentWrapper />

    );

    assert.equal(hasScript(URL), true, "Url in document");
    const unmounted = ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(instance).parentNode);
    assert.equal(unmounted, true, "successfully unmounted");
    assert.equal(hasScript(URL), false, "Url not in document after unmounting");
  });

  it("should allow you to access methods on the wrappedComponent via ref callback", (done) => {
    // internal component with method we want access to
    class InternalComponent extends React.Component {
      internalCallsACallback(fn) { fn(); }
      render() { return (
 )}
    }
    const URL = "http://example.com/?ref=true";
    const ComponentWrapper = makeAsyncScriptLoader(URL)(InternalComponent);

    // wrapping component that applies a ref to our AsyncHOC(InternalComponent)
    class WrappingComponent extends React.Component {
      render() { return (
<ComponentWrapper ref={(r) => this._internalRef = r} />
)}
    }
    const instance = ReactTestUtils.renderIntoDocument(
      <WrappingComponent />
    );

    assert.equal(hasScript(URL), true, "Url in document");
    assert.isOk(instance._internalRef.internalCallsACallback, "internal components method available");
    instance._internalRef.internalCallsACallback(done);
  });

  it("should allow you to access methods on the wrappedComponent via createRef", (done) => {
    // internal component with method we want access to
    class InternalComponent extends React.Component {
      internalCallsACallback(fn) { fn(); }
      render() { return (
 )}
    }
    const URL = "http://example.com/?createRef=true";
    const ComponentWrapper = makeAsyncScriptLoader(URL)(InternalComponent);

    // wrapping component that applies a ref to our AsyncHOC(InternalComponent)
    class WrappingComponent extends React.Component {
      constructor(props) {
        super(props);
        this._internalRef = React.createRef();
      }
      render() { return (
<ComponentWrapper ref={this._internalRef} />
)}
    }
    const instance = ReactTestUtils.renderIntoDocument(
      <WrappingComponent />
    );

    assert.equal(hasScript(URL), true, "Url in document");
    assert.isOk(instance._internalRef.current.internalCallsACallback, "internal components method available");
    instance._internalRef.current.internalCallsACallback(done);
  });
});
<|endoftext|>


<commit_before>/**
 * modules/adsense data store: report tests.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import { STORE_NAME } from './constants';
import {
	createTestRegistry,
	muteConsole,
	subscribeUntil,
	unsubscribeFromAll,
} from 'tests/js/utils';
import * as fixtures from './__fixtures__';

describe( 'modules/adsense report', () => {
	let registry;

	beforeAll( () => {
		API.setUsingCache( false );
	} );

	beforeEach( () => {
		registry = createTestRegistry();
	} );

	afterAll( () => {
		API.setUsingCache( true );
	} );

	afterEach( () => {
		unsubscribeFromAll( registry );
	} );

	describe( 'actions', () => {

	} );

	describe( 'selectors', () => {
		describe( 'getReport', () => {
			it( 'uses a resolver to make a network request', async () => {
				fetchMock.getOnce(
					/^\/google-site-kit\/v1\/modules\/adsense\/data\/earnings/,
					{ body: fixtures.report, status: 200 }
				);

				const initialReport = registry.select( STORE_NAME ).getReport( {} );

				expect( initialReport ).toEqual( undefined );
				await subscribeUntil( registry,
					() => (
						registry.select( STORE_NAME ).getReport( {} ) !== undefined
					),
				);

				const report = registry.select( STORE_NAME ).getReport( {} );

				expect( fetchMock ).toHaveFetchedTimes( 1 );
				expect( report ).toEqual( fixtures.report );
			} );

			it( 'does not make a network request if report for given options is already present', async () => {
				const options = {
					dateRange: 'last-90-days',
				};

				// Load data into this store so there are matches for the data we're about to select,
				// even though the selector hasn't fulfilled yet.
				registry.dispatch( STORE_NAME ).receiveGetReport( fixtures.report, { options } );

				const report = registry.select( STORE_NAME ).getReport( options );

				await subscribeUntil( registry, () => registry
					.select( STORE_NAME )
					.hasFinishedResolution( 'getReport', [ options ] )
				);

				expect( fetchMock ).not.toHaveFetched();
				expect( report ).toEqual( fixtures.report );
			} );

			it( 'dispatches an error if the request fails', async () => {
				const response = {
					code: 'internal_server_error',
					message: 'Internal server error',
					data: { status: 500 },
				};
				fetchMock.getOnce(
					/^\/google-site-kit\/v1\/modules\/adsense\/data\/earnings/,
					{ body: response, status: 500 }
				);

				const options = {
					dateRange: 'last-90-days',
					dimensions: [ 'DATE' ],
				};

				muteConsole( 'error' );
				registry.select( STORE_NAME ).getReport( options );
				await subscribeUntil( registry,
					() => registry.select( STORE_NAME ).isFetchingGetReport( options ) === false,
				);

				expect( fetchMock ).toHaveFetchedTimes( 1 );

				const report = registry.select( STORE_NAME ).getReport( options );
				expect( report ).toEqual( undefined );
			} );
		} );
	} );
} );
<commit_msg>Fix report test issue.<commit_after>/**
 * modules/adsense data store: report tests.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import { STORE_NAME } from './constants';
import {
	createTestRegistry,
	muteConsole,
	subscribeUntil,
	unsubscribeFromAll,
} from 'tests/js/utils';
import * as fixtures from './__fixtures__';

describe( 'modules/adsense report', () => {
	let registry;

	beforeAll( () => {
		API.setUsingCache( false );
	} );

	beforeEach( () => {
		registry = createTestRegistry();
	} );

	afterAll( () => {
		API.setUsingCache( true );
	} );

	afterEach( () => {
		unsubscribeFromAll( registry );
	} );

	describe( 'actions', () => {

	} );

	describe( 'selectors', () => {
		describe( 'getReport', () => {
			it( 'uses a resolver to make a network request', async () => {
				fetchMock.getOnce(
					/^\/google-site-kit\/v1\/modules\/adsense\/data\/earnings/,
					{ body: fixtures.report, status: 200 }
				);

				const initialReport = registry.select( STORE_NAME ).getReport( { dateRange: 'last-90-days' } );

				expect( initialReport ).toEqual( undefined );
				await subscribeUntil( registry,
					() => (
						registry.select( STORE_NAME ).getReport( { dateRange: 'last-90-days' } ) !== undefined
					),
				);

				const report = registry.select( STORE_NAME ).getReport( { dateRange: 'last-90-days' } );

				expect( fetchMock ).toHaveFetchedTimes( 1 );
				expect( report ).toEqual( fixtures.report );
			} );

			it( 'does not make a network request if report for given options is already present', async () => {
				const options = {
					dateRange: 'last-90-days',
				};

				// Load data into this store so there are matches for the data we're about to select,
				// even though the selector hasn't fulfilled yet.
				registry.dispatch( STORE_NAME ).receiveGetReport( fixtures.report, { options } );

				const report = registry.select( STORE_NAME ).getReport( options );

				await subscribeUntil( registry, () => registry
					.select( STORE_NAME )
					.hasFinishedResolution( 'getReport', [ options ] )
				);

				expect( fetchMock ).not.toHaveFetched();
				expect( report ).toEqual( fixtures.report );
			} );

			it( 'dispatches an error if the request fails', async () => {
				const response = {
					code: 'internal_server_error',
					message: 'Internal server error',
					data: { status: 500 },
				};
				fetchMock.getOnce(
					/^\/google-site-kit\/v1\/modules\/adsense\/data\/earnings/,
					{ body: response, status: 500 }
				);

				const options = {
					dateRange: 'last-90-days',
					dimensions: [ 'DATE' ],
				};

				muteConsole( 'error' );
				registry.select( STORE_NAME ).getReport( options );
				await subscribeUntil( registry,
					() => registry.select( STORE_NAME ).isFetchingGetReport( options ) === false,
				);

				expect( fetchMock ).toHaveFetchedTimes( 1 );

				const report = registry.select( STORE_NAME ).getReport( options );
				expect( report ).toEqual( undefined );
			} );
		} );
	} );
} );
<|endoftext|>


<commit_before>/**
 * `modules/idea-hub` data store: idea-state.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import omit from 'lodash/omit';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { createFetchStore } from '../../../googlesitekit/data/create-fetch-store';
import { actions as errorStoreActions } from '../../../googlesitekit/data/create-error-store';
import { actions as moduleDataActions } from './module-data';
const { receiveError, clearErrors } = errorStoreActions;

const fetchPostUpdateIdeaStateStore = createFetchStore( {
	baseName: 'updateIdeaState',
	controlCallback: ( { name, saved, dismissed } ) => {
		const params = { name };

		if ( saved !== undefined ) {
			params.saved = saved;
		} else {
			params.dismissed = dismissed;
		}

		return API.set(
			'modules',
			'idea-hub',
			'update-idea-state',
			params
		).then( async ( result ) => {
			await API.invalidateCache(
				'modules',
				'idea-hub',
				'new-ideas'
			).catch( () => {} );
			await API.invalidateCache(
				'modules',
				'idea-hub',
				'saved-ideas'
			).catch( () => {} );

			return result;
		} );
	},
	argsToParams( { name, saved, dismissed } ) {
		return { name, saved, dismissed };
	},
	validateParams( { name, saved, dismissed } = {} ) {
		invariant(
			typeof name === 'string' && name.length > 0,
			'name must be a non empty string'
		);
		invariant(
			saved !== undefined || dismissed !== undefined,
			'either saved or dismissed property must be set'
		);
	},
	reducerCallback: ( state, idea ) => {
		const { newIdeas = [], savedIdeas = [] } = state;

		const optIn = ( { _id } ) => _id === idea._id;
		const optOut = ( { _id } ) => _id !== idea._id;

		if ( idea.dismissed === true ) {
			return {
				...state,
				newIdeas: newIdeas.filter( optOut ),
				savedIdeas: savedIdeas.filter( optOut ),
			};
		} else if ( idea.saved === true ) {
			let ideaDetails = newIdeas.find( optIn );
			if ( ! ideaDetails ) {
				ideaDetails = idea;
			}

			return {
				...state,
				newIdeas: newIdeas.filter( optOut ),
				savedIdeas: [ ...savedIdeas, ideaDetails ],
			};
		}

		let ideaDetails = savedIdeas.find( optIn );
		if ( ! ideaDetails ) {
			ideaDetails = idea;
		}

		return {
			...state,
			newIdeas: [ ...newIdeas, ideaDetails ],
			savedIdeas: savedIdeas.filter( optOut ),
		};
	},
} );

const baseInitialState = {
	activities: {},
};

const SET_ACTIVITY = 'SET_ACTIVITY';
const REMOVE_ACTIVITY = 'REMOVE_ACTIVITY';

const baseActions = {
	/**
	 * Updates a given Idea's state.
	 *
	 * @since 1.36.0
	 *
	 * @param {Object}  ideaState           Idea Hub Idea state.
	 * @param {string}  ideaState.name      Idea Hub Idea name.
	 * @param {boolean} ideaState.saved     Whether the Idea is saved [optional].
	 * @param {boolean} ideaState.dismissed Whether the Idea is dismissed [optional].
	 * @return {Object} Object with `response` and `error`.
	 */
	*updateIdeaState( ideaState ) {
		const {
			response,
			error,
		} = yield fetchPostUpdateIdeaStateStore.actions.fetchUpdateIdeaState(
			ideaState
		);

		if ( ! error ) {
			yield moduleDataActions.incrementInteractions();
		}

		return { response, error };
	},
	/**
	 * Saves an Idea.
	 *
	 * @since 1.36.0
	 *
	 * @param {string} ideaName Idea Hub Idea name.
	 * @return {Object} Object with `response` and `error`.
	 */
	*saveIdea( ideaName ) {
		invariant( typeof ideaName === 'string', 'ideaName must be a string.' );

		yield clearErrors( 'updateIdeaState' );

		const { response, error } = yield baseActions.updateIdeaState( {
			name: ideaName,
			saved: true,
		} );

		if ( error ) {
			yield receiveError( error, 'updateIdeaState', [ ideaName ] );
		}

		return { response, error };
	},
	/**
	 * Unsaves an Idea.
	 *
	 * @since 1.36.0
	 *
	 * @param {string} ideaName Idea Hub Idea name.
	 * @return {Object} Object with `response` and `error`.
	 */
	*unsaveIdea( ideaName ) {
		invariant( typeof ideaName === 'string', 'ideaName must be a string.' );

		yield clearErrors( 'updateIdeaState' );

		const { response, error } = yield baseActions.updateIdeaState( {
			name: ideaName,
			saved: false,
		} );

		if ( error ) {
			yield receiveError( error, 'updateIdeaState', [ ideaName ] );
		}

		return { response, error };
	},
	/**
	 * Dismisses an Idea.
	 *
	 * @since 1.36.0
	 *
	 * @param {string} ideaName Idea Hub Idea name.
	 * @return {Object} Object with `response` and `error`.
	 */
	*dismissIdea( ideaName ) {
		invariant( typeof ideaName === 'string', 'ideaName must be a string.' );

		yield clearErrors( 'updateIdeaState' );

		const { response, error } = yield baseActions.updateIdeaState( {
			name: ideaName,
			dismissed: true,
		} );

		if ( error ) {
			yield receiveError( error, 'updateIdeaState', [ ideaName ] );
		}

		return { response, error };
	},

	/**
	 * Sets an actvity.
	 *
	 * @since 1.37.0
	 *
	 * @param {string} key   The idea name.
	 * @param {string} value Store the current activity of the idea, i.e whether a draft is being created or has been created.
	 * @return {Object} Redux-style action.
	 */
	setActivity( key, value ) {
		invariant(
			typeof key === 'string' && key.length > 0,
			'key is required.'
		);

		return {
			payload: { key, value },
			type: SET_ACTIVITY,
		};
	},

	/**
	 * Removes an actvity.
	 *
	 * @since 1.37.0
	 *
	 * @param {string} key The idea name.
	 * @return {Object} Redux-style action.
	 */
	removeActivity( key ) {
		invariant(
			typeof key === 'string' && key.length > 0,
			'key is required.'
		);

		return {
			payload: { key },
			type: REMOVE_ACTIVITY,
		};
	},
};

export const baseReducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case SET_ACTIVITY: {
			const { key, value } = payload;

			return {
				...state,
				activities: {
					...state.activities,
					[ key ]: value,
				},
			};
		}

		case REMOVE_ACTIVITY: {
			const { key } = payload;

			return {
				...state,
				activities: omit( state.activities, [ key ] ),
			};
		}

		default: {
			return state;
		}
	}
};

export const baseSelectors = {
	/**
	 * Gets the existing activity by key.
	 *
	 * @since 1.37.0
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @param {string} key   Get data stored in this key.
	 * @return {*} Value stored in state by key. Returns `undefined` if key isn't found.
	 */
	getActivity( state, key ) {
		return state.activities[ key ];
	},
};

const store = Data.combineStores( fetchPostUpdateIdeaStateStore, {
	actions: baseActions,
	initialState: baseInitialState,
	reducer: baseReducer,
	selectors: baseSelectors,
} );

export const initialState = store.initialState;
export const actions = store.actions;
export const controls = store.controls;
export const reducer = store.reducer;
export const resolvers = store.resolvers;
export const selectors = store.selectors;

export default store;
<commit_msg>Revert changes to idea-state.js.<commit_after>/**
 * `modules/idea-hub` data store: idea-state.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import omit from 'lodash/omit';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { createFetchStore } from '../../../googlesitekit/data/create-fetch-store';
import { actions as errorStoreActions } from '../../../googlesitekit/data/create-error-store';
import { actions as moduleDataActions } from './module-data';
const { receiveError, clearError } = errorStoreActions;

const fetchPostUpdateIdeaStateStore = createFetchStore( {
	baseName: 'updateIdeaState',
	controlCallback: ( { name, saved, dismissed } ) => {
		const params = { name };

		if ( saved !== undefined ) {
			params.saved = saved;
		} else {
			params.dismissed = dismissed;
		}

		return API.set(
			'modules',
			'idea-hub',
			'update-idea-state',
			params
		).then( async ( result ) => {
			await API.invalidateCache(
				'modules',
				'idea-hub',
				'new-ideas'
			).catch( () => {} );
			await API.invalidateCache(
				'modules',
				'idea-hub',
				'saved-ideas'
			).catch( () => {} );

			return result;
		} );
	},
	argsToParams( { name, saved, dismissed } ) {
		return { name, saved, dismissed };
	},
	validateParams( { name, saved, dismissed } = {} ) {
		invariant(
			typeof name === 'string' && name.length > 0,
			'name must be a non empty string'
		);
		invariant(
			saved !== undefined || dismissed !== undefined,
			'either saved or dismissed property must be set'
		);
	},
	reducerCallback: ( state, idea ) => {
		const { newIdeas = [], savedIdeas = [] } = state;

		const optIn = ( { _id } ) => _id === idea._id;
		const optOut = ( { _id } ) => _id !== idea._id;

		if ( idea.dismissed === true ) {
			return {
				...state,
				newIdeas: newIdeas.filter( optOut ),
				savedIdeas: savedIdeas.filter( optOut ),
			};
		} else if ( idea.saved === true ) {
			let ideaDetails = newIdeas.find( optIn );
			if ( ! ideaDetails ) {
				ideaDetails = idea;
			}

			return {
				...state,
				newIdeas: newIdeas.filter( optOut ),
				savedIdeas: [ ...savedIdeas, ideaDetails ],
			};
		}

		let ideaDetails = savedIdeas.find( optIn );
		if ( ! ideaDetails ) {
			ideaDetails = idea;
		}

		return {
			...state,
			newIdeas: [ ...newIdeas, ideaDetails ],
			savedIdeas: savedIdeas.filter( optOut ),
		};
	},
} );

const baseInitialState = {
	activities: {},
};

const SET_ACTIVITY = 'SET_ACTIVITY';
const REMOVE_ACTIVITY = 'REMOVE_ACTIVITY';

const baseActions = {
	/**
	 * Updates a given Idea's state.
	 *
	 * @since 1.36.0
	 *
	 * @param {Object}  ideaState           Idea Hub Idea state.
	 * @param {string}  ideaState.name      Idea Hub Idea name.
	 * @param {boolean} ideaState.saved     Whether the Idea is saved [optional].
	 * @param {boolean} ideaState.dismissed Whether the Idea is dismissed [optional].
	 * @return {Object} Object with `response` and `error`.
	 */
	*updateIdeaState( ideaState ) {
		const {
			response,
			error,
		} = yield fetchPostUpdateIdeaStateStore.actions.fetchUpdateIdeaState(
			ideaState
		);

		if ( ! error ) {
			yield moduleDataActions.incrementInteractions();
		}

		return { response, error };
	},
	/**
	 * Saves an Idea.
	 *
	 * @since 1.36.0
	 *
	 * @param {string} ideaName Idea Hub Idea name.
	 * @return {Object} Object with `response` and `error`.
	 */
	*saveIdea( ideaName ) {
		invariant( typeof ideaName === 'string', 'ideaName must be a string.' );

		yield clearError( 'saveIdea', [ ideaName ] );

		const { response, error } = yield baseActions.updateIdeaState( {
			name: ideaName,
			saved: true,
		} );

		if ( error ) {
			yield receiveError( error, 'saveIdea', [ ideaName ] );
		}

		return { response, error };
	},
	/**
	 * Unsaves an Idea.
	 *
	 * @since 1.36.0
	 *
	 * @param {string} ideaName Idea Hub Idea name.
	 * @return {Object} Object with `response` and `error`.
	 */
	*unsaveIdea( ideaName ) {
		invariant( typeof ideaName === 'string', 'ideaName must be a string.' );

		yield clearError( 'unsaveIdea', [ ideaName ] );

		const { response, error } = yield baseActions.updateIdeaState( {
			name: ideaName,
			saved: false,
		} );

		if ( error ) {
			yield receiveError( error, 'unsaveIdea', [ ideaName ] );
		}

		return { response, error };
	},
	/**
	 * Dismisses an Idea.
	 *
	 * @since 1.36.0
	 *
	 * @param {string} ideaName Idea Hub Idea name.
	 * @return {Object} Object with `response` and `error`.
	 */
	*dismissIdea( ideaName ) {
		invariant( typeof ideaName === 'string', 'ideaName must be a string.' );

		yield clearError( 'dismissIdea', [ ideaName ] );

		const { response, error } = yield baseActions.updateIdeaState( {
			name: ideaName,
			dismissed: true,
		} );

		if ( error ) {
			yield receiveError( error, 'dismissIdea', [ ideaName ] );
		}

		return { response, error };
	},

	/**
	 * Sets an actvity.
	 *
	 * @since 1.37.0
	 *
	 * @param {string} key   The idea name.
	 * @param {string} value Store the current activity of the idea, i.e whether a draft is being created or has been created.
	 * @return {Object} Redux-style action.
	 */
	setActivity( key, value ) {
		invariant(
			typeof key === 'string' && key.length > 0,
			'key is required.'
		);

		return {
			payload: { key, value },
			type: SET_ACTIVITY,
		};
	},

	/**
	 * Removes an actvity.
	 *
	 * @since 1.37.0
	 *
	 * @param {string} key The idea name.
	 * @return {Object} Redux-style action.
	 */
	removeActivity( key ) {
		invariant(
			typeof key === 'string' && key.length > 0,
			'key is required.'
		);

		return {
			payload: { key },
			type: REMOVE_ACTIVITY,
		};
	},
};

export const baseReducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case SET_ACTIVITY: {
			const { key, value } = payload;

			return {
				...state,
				activities: {
					...state.activities,
					[ key ]: value,
				},
			};
		}

		case REMOVE_ACTIVITY: {
			const { key } = payload;

			return {
				...state,
				activities: omit( state.activities, [ key ] ),
			};
		}

		default: {
			return state;
		}
	}
};

export const baseSelectors = {
	/**
	 * Gets the existing activity by key.
	 *
	 * @since 1.37.0
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @param {string} key   Get data stored in this key.
	 * @return {*} Value stored in state by key. Returns `undefined` if key isn't found.
	 */
	getActivity( state, key ) {
		return state.activities[ key ];
	},
};

const store = Data.combineStores( fetchPostUpdateIdeaStateStore, {
	actions: baseActions,
	initialState: baseInitialState,
	reducer: baseReducer,
	selectors: baseSelectors,
} );

export const initialState = store.initialState;
export const actions = store.actions;
export const controls = store.controls;
export const reducer = store.reducer;
export const resolvers = store.resolvers;
export const selectors = store.selectors;

export default store;
<|endoftext|>


<commit_before>function fileExists(path) {
  var exists = false;
  jQuery.ajax({
    url: path,
    success: function() { exists = true },
    async: false
  });
  return exists;
}

QUnit.test( "Parse bower.json and check if main files exist", function( assert ) {
  var baseDir = '../../'
  var bowerFile = baseDir + 'bower.json';
  var result = null;
  jQuery.ajax({
    url: bowerFile,
    success: function(data) { result = data },
    async: false
  });

  assert.ok(result != null, "bower.json exists");

  jQuery.each(result.main, function(i) {
    var file = result.main[i];
    assert.ok(fileExists(baseDir + file) == true, file + " exists");
  });
});
<commit_msg>Optimize testMainFiles_spec<commit_after>function fileExists(path) {
  var res = jQuery.ajax({url: path, async: false});
  return (res.status == 200)? true : false
}

QUnit.test( "Parse bower.json and check if main files exist", function( assert ) {
  var baseDir = '../../'
  var bowerFile = baseDir + 'bower.json';
  var result = JSON.parse(jQuery.ajax({url: bowerFile, async: false}).responseText);

  assert.ok(result != null, "bower.json exists");

  jQuery.each(result.main, function(i) {
    var file = result.main[i];
    assert.ok(fileExists(baseDir + file) == true, file + " exists");
  });
});
<|endoftext|>


<commit_before>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import { waitForElementToBeRemoved } from '@testing-library/react';

/**
 * Internal dependencies
 */
import useInsertElement from '../components/canvas/useInsertElement';
import { TEXT_ELEMENT_DEFAULT_FONT } from '../app/font/defaultFonts';
import { useStory } from '../app/story';
import { Fixture } from './fixture';

describe('Element: Text', () => {
  let fixture;

  const addText = async (extraProps = null) => {
    const insertElement = await fixture.renderHook(() => useInsertElement());
    await fixture.act(() =>
      insertElement('text', {
        font: TEXT_ELEMENT_DEFAULT_FONT,
        content: 'hello world!',
        x: 40,
        y: 40,
        width: 250,
        ...extraProps,
      })
    );
  };

  beforeEach(async () => {
    fixture = new Fixture();
    await fixture.render();
    await addText();
  });

  afterEach(() => {
    fixture.restore();
  });

  describe('CUJ: Creator Can Style Text: Use font picker', () => {
    // There are 3 curated fonts included by default even though the total is more.
    const DEFAULT_VISIBLE_FONTS = 3;
    // Timeout used for submitting / search update + 50ms (250 + 50).
    const TIMEOUT = 300;
    const openFontPicker = async () => {
      const input = await fixture.screen.getByLabelText('Edit: Font family');
      await fixture.events.click(input);
    };

    beforeEach(async () => {
      await openFontPicker();
    });

    it('should render the font picker', async () => {
      await fixture.snapshot('font picker open');
    });

    it('should apply the selected font', async () => {
      await fixture.events.keyboard.type('Yrsa');
      // Ensure the debounced callback has taken effect.
      await fixture.events.sleep(TIMEOUT);
      const option = fixture.screen.getByText('Yrsa');
      await fixture.events.click(option);
      await fixture.events.sleep(TIMEOUT);
      await openFontPicker();
      const selected = fixture.screen.getAllByRole('option', {
        name: 'Selected Yrsa',
      });
      expect(selected.length).toBe(1);

      const {
        state: {
          currentPage: { elements },
        },
      } = await fixture.renderHook(() => useStory());
      expect(elements[1].font.family).toBe('Yrsa');
    });

    it('should display only the fonts from curated list by default', () => {
      const options = document
        .getElementById('editor-font-picker-list')
        .querySelectorAll('li[role="option"]');
      expect(options.length).toBe(DEFAULT_VISIBLE_FONTS);
    });

    describe('when searching fonts', () => {
      it('should display the correct fonts when searching', async () => {
        await fixture.events.keyboard.type('Ab');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        let options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(2);
        expect(options[0].textContent).toBe('Abel');
        expect(options[1].textContent).toBe('Abhaya Libre');

        await fixture.events.keyboard.type('el');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(1);
        expect(options[0].textContent).toBe('Abel');
      });

      it('should not search with less than 2 characters', async () => {
        await fixture.events.keyboard.type('A');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        let options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS);
      });

      it('should restore default fonts list when emptying search', async () => {
        await fixture.events.keyboard.type('Ab');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        const options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(2);

        await fixture.events.keyboard.press('Backspace');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        const defaultOptions = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        // Back to all options.
        expect(defaultOptions.length).toBe(DEFAULT_VISIBLE_FONTS);
      });

      it('should show empty list in case of no results', async () => {
        await fixture.events.keyboard.type('No fonts here');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        expect(fixture.screen.getByText('No matches found')).toBeDefined();
      });
    });

    describe('with recent fonts', () => {
      it('should not display any recent fonts by default', () => {
        let options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS);
      });

      it('should add up to 5 recent fonts, displaying the most recent first', async () => {
        await fixture.events.keyboard.type('Space Mono');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        let option = fixture.screen.getByText('Space Mono');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();

        let options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS + 1);
        expect(options[0].textContent).toBe('Space Mono');

        await fixture.events.keyboard.type('Abel');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        option = fixture.screen.getByText('Abel');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();
        options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS + 2);

        await fixture.events.keyboard.type('Abhaya Libre');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        option = fixture.screen.getByText('Abhaya Libre');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();
        options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS + 3);

        await fixture.events.keyboard.type('Source Serif Pro');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        option = fixture.screen.getByText('Source Serif Pro');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();
        options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS + 4);

        await fixture.events.keyboard.type('Roboto');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        option = fixture.screen.getByText('Roboto');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();
        options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS + 5);

        await fixture.events.keyboard.type('Yrsa');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        option = fixture.screen.getByText('Yrsa');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();

        options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');

        // Ensure there are only 5 extra options added.
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS + 5);
        // Ensure the first one is the last chosen.
        expect(options[0].textContent).toBe('Yrsa');
      });

      it('should display the selected recent font with a tick', async () => {
        const option = fixture.screen.getByText('Source Serif Pro');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();
        const selected = fixture.screen.getAllByRole('option', {
          name: 'Selected Source Serif Pro',
        });
        // One from the recent fonts and one from the general list.
        expect(selected.length).toBe(2);
      });

      it('should include recent fonts to search', async () => {
        await fixture.events.keyboard.type('Abe');
        await fixture.events.sleep(TIMEOUT);
        const option = fixture.screen.getByText('Abel');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();

        await fixture.events.keyboard.type('Abe');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        let options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');

        // Twice for 'Abel', once for Abhaya Libre.
        expect(options.length).toBe(2);
      });
    });

    describe('using keyboard only', () => {
      it('should allow selecting a font with arrow keys and Enter', async () => {
        await fixture.events.keyboard.press('down');
        await fixture.events.keyboard.press('down');
        await fixture.events.keyboard.press('down');
        await fixture.events.keyboard.press('Enter');
        await openFontPicker();

        await fixture.events.keyboard.type('Ubuntu');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        const selected = fixture.screen.getAllByRole('option', {
          name: 'Selected Ubuntu',
        });
        // 1 selected + 1 recent font.
        expect(selected.length).toBe(2);
      });

      it('should close the font picker with Esc', async () => {
        const input = await fixture.screen.getByLabelText('Edit: Font family');
        expect(input.getAttribute('aria-expanded')).toBe('true');
        await fixture.events.keyboard.press('Esc');
        await waitForElementToBeRemoved(
          document.getElementById('editor-font-picker-list')
        );
      });
    });

    it('should reset font weights when font family is updated', async () => {
      await fixture.events.keyboard.type('Yrsa');
      // Ensure the debounced callback has taken effect.
      await fixture.events.sleep(TIMEOUT);
      const option = fixture.screen.getByText('Yrsa');
      await fixture.events.click(option);
      await fixture.events.sleep(TIMEOUT);

      const { fontWeight } = fixture.editor.inspector.designPanel.textStyle;
      expect(fontWeight.value).toBe('Regular');

      await fixture.events.click(fontWeight.select);
      await fixture.events.click(fontWeight.option('Bold'));
      expect(fontWeight.value).toBe('Bold');

      await openFontPicker();

      await fixture.events.keyboard.type('Roboto');
      // Ensure the debounced callback has taken effect.
      await fixture.events.sleep(TIMEOUT);
      const option2 = fixture.screen.getByText('Roboto');
      await fixture.events.click(option2);
      await fixture.events.sleep(TIMEOUT);

      expect(fontWeight.value).toBe('Regular');
    });
  });
});
<commit_msg>bump up sleep for failing test that should work just fine<commit_after>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import { waitForElementToBeRemoved } from '@testing-library/react';

/**
 * Internal dependencies
 */
import useInsertElement from '../components/canvas/useInsertElement';
import { TEXT_ELEMENT_DEFAULT_FONT } from '../app/font/defaultFonts';
import { useStory } from '../app/story';
import { Fixture } from './fixture';

describe('Element: Text', () => {
  let fixture;

  const addText = async (extraProps = null) => {
    const insertElement = await fixture.renderHook(() => useInsertElement());
    await fixture.act(() =>
      insertElement('text', {
        font: TEXT_ELEMENT_DEFAULT_FONT,
        content: 'hello world!',
        x: 40,
        y: 40,
        width: 250,
        ...extraProps,
      })
    );
  };

  beforeEach(async () => {
    fixture = new Fixture();
    await fixture.render();
    await addText();
  });

  afterEach(() => {
    fixture.restore();
  });

  describe('CUJ: Creator Can Style Text: Use font picker', () => {
    // There are 3 curated fonts included by default even though the total is more.
    const DEFAULT_VISIBLE_FONTS = 3;
    // Timeout used for submitting / search update + 50ms (250 + 50).
    const TIMEOUT = 300;
    const openFontPicker = async () => {
      const input = await fixture.screen.getByLabelText('Edit: Font family');
      await fixture.events.click(input);
    };

    beforeEach(async () => {
      await openFontPicker();
    });

    it('should render the font picker', async () => {
      await fixture.snapshot('font picker open');
    });

    it('should apply the selected font', async () => {
      await fixture.events.keyboard.type('Yrsa');
      // Ensure the debounced callback has taken effect.
      await fixture.events.sleep(TIMEOUT);
      const option = fixture.screen.getByText('Yrsa');
      await fixture.events.click(option);
      await fixture.events.sleep(TIMEOUT);
      await openFontPicker();
      const selected = fixture.screen.getAllByRole('option', {
        name: 'Selected Yrsa',
      });
      expect(selected.length).toBe(1);

      const {
        state: {
          currentPage: { elements },
        },
      } = await fixture.renderHook(() => useStory());
      expect(elements[1].font.family).toBe('Yrsa');
    });

    it('should display only the fonts from curated list by default', () => {
      const options = document
        .getElementById('editor-font-picker-list')
        .querySelectorAll('li[role="option"]');
      expect(options.length).toBe(DEFAULT_VISIBLE_FONTS);
    });

    describe('when searching fonts', () => {
      it('should display the correct fonts when searching', async () => {
        await fixture.events.keyboard.type('Ab');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        let options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(2);
        expect(options[0].textContent).toBe('Abel');
        expect(options[1].textContent).toBe('Abhaya Libre');

        await fixture.events.keyboard.type('el');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(1);
        expect(options[0].textContent).toBe('Abel');
      });

      it('should not search with less than 2 characters', async () => {
        await fixture.events.keyboard.type('A');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        let options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS);
      });

      it('should restore default fonts list when emptying search', async () => {
        await fixture.events.keyboard.type('Ab');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        const options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(2);

        await fixture.events.keyboard.press('Backspace');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        const defaultOptions = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        // Back to all options.
        expect(defaultOptions.length).toBe(DEFAULT_VISIBLE_FONTS);
      });

      it('should show empty list in case of no results', async () => {
        await fixture.events.keyboard.type('No fonts here');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        expect(fixture.screen.getByText('No matches found')).toBeDefined();
      });
    });

    describe('with recent fonts', () => {
      it('should not display any recent fonts by default', () => {
        let options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS);
      });

      it('should add up to 5 recent fonts, displaying the most recent first', async () => {
        await fixture.events.keyboard.type('Space Mono');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        let option = fixture.screen.getByText('Space Mono');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();

        let options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS + 1);
        expect(options[0].textContent).toBe('Space Mono');

        await fixture.events.keyboard.type('Abel');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        option = fixture.screen.getByText('Abel');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();
        options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS + 2);

        await fixture.events.keyboard.type('Abhaya Libre');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        option = fixture.screen.getByText('Abhaya Libre');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();
        options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS + 3);

        await fixture.events.keyboard.type('Source Serif Pro');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        option = fixture.screen.getByText('Source Serif Pro');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();
        options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS + 4);

        await fixture.events.keyboard.type('Roboto');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        option = fixture.screen.getByText('Roboto');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();
        options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS + 5);

        await fixture.events.keyboard.type('Yrsa');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        option = fixture.screen.getByText('Yrsa');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();

        options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');

        // Ensure there are only 5 extra options added.
        expect(options.length).toBe(DEFAULT_VISIBLE_FONTS + 5);
        // Ensure the first one is the last chosen.
        expect(options[0].textContent).toBe('Yrsa');
      });

      it('should display the selected recent font with a tick', async () => {
        const option = fixture.screen.getByText('Source Serif Pro');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();
        const selected = fixture.screen.getAllByRole('option', {
          name: 'Selected Source Serif Pro',
        });
        // One from the recent fonts and one from the general list.
        expect(selected.length).toBe(2);
      });

      it('should include recent fonts to search', async () => {
        await fixture.events.keyboard.type('Abe');
        await fixture.events.sleep(TIMEOUT);
        const option = fixture.screen.getByText('Abel');
        await fixture.events.click(option);
        await fixture.events.sleep(TIMEOUT);
        await openFontPicker();

        await fixture.events.keyboard.type('Abe');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        let options = document
          .getElementById('editor-font-picker-list')
          .querySelectorAll('li[role="option"]');

        // Twice for 'Abel', once for Abhaya Libre.
        expect(options.length).toBe(2);
      });
    });

    describe('using keyboard only', () => {
      it('should allow selecting a font with arrow keys and Enter', async () => {
        await fixture.events.keyboard.press('down');
        await fixture.events.keyboard.press('down');
        await fixture.events.keyboard.press('down');
        await fixture.events.keyboard.press('Enter');
        await openFontPicker();

        await fixture.events.keyboard.type('Ubuntu');
        // Ensure the debounced callback has taken effect.
        await fixture.events.sleep(TIMEOUT);
        const selected = fixture.screen.getAllByRole('option', {
          name: 'Selected Ubuntu',
        });
        // 1 selected + 1 recent font.
        expect(selected.length).toBe(2);
      });

      it('should close the font picker with Esc', async () => {
        const input = await fixture.screen.getByLabelText('Edit: Font family');
        expect(input.getAttribute('aria-expanded')).toBe('true');
        await fixture.events.keyboard.press('Esc');
        await waitForElementToBeRemoved(
          document.getElementById('editor-font-picker-list')
        );
      });
    });

    it('should reset font weights when font family is updated', async () => {
      await fixture.events.keyboard.type('Yrsa');
      // Ensure the debounced callback has taken effect.
      await fixture.events.sleep(TIMEOUT);
      const option = fixture.screen.getByText('Yrsa');
      await fixture.events.click(option);
      await fixture.events.sleep(TIMEOUT);

      const { fontWeight } = fixture.editor.inspector.designPanel.textStyle;
      expect(fontWeight.value).toBe('Regular');

      await fixture.events.click(fontWeight.select);
      await fixture.events.click(fontWeight.option('Bold'));
      expect(fontWeight.value).toBe('Bold');

      await openFontPicker();

      await fixture.events.keyboard.type('Roboto');
      // Ensure the debounced callback has taken effect.
      await fixture.events.sleep(TIMEOUT);
      const option2 = fixture.screen.getByText('Roboto');
      await fixture.events.click(option2);
      await fixture.events.sleep(500);

      expect(fontWeight.value).toBe('Regular');
    });
  });
});
<|endoftext|>