<commit_before>/**
 * Copyright 2016 The AMP HTML Authors. All Rights Reserved.
 *
 * 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
 *
 *      http://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.
 */

import {createIframePromise} from '../../../../testing/iframe';
import {platformFor} from '../../../../src/services';
import {vsyncFor} from '../../../../src/services';
import {
    AmpAppBanner,
    AbstractAppBanner,
    AmpIosAppBanner,
    AmpAndroidAppBanner,
} from '../amp-app-banner';
import {xhrFor} from '../../../../src/services';
import '../../../amp-analytics/0.1/amp-analytics';
import * as sinon from 'sinon';
import {AmpDocSingle} from '../../../../src/service/ampdoc-impl';
import {viewerForDoc} from '../../../../src/services';

describe('amp-app-banner', () => {

  let sandbox;
  let vsync;
  let platform;
  let isAndroid = false;
  let isIos = false;
  let isChrome = false;
  let isSafari = false;
  let isEmbedded = false;
  let hasNavigateToCapability = true;

  const meta = {
    content: 'app-id=828256236, app-argument=medium://p/cb7f223fad86',
  };
  const manifest = {
    href: 'https://example.com/manifest.json',
    content: {
      'prefer_related_applications': true,
      'related_applications': [
        {
          'platform': 'play',
          'id': 'com.medium.reader',
          'url': 'https://play.google.com/com.medium.reader',
        },
      ],
    },
  };

  function runTask(task, state) {
    if (task.measure) {
      task.measure(state);
    }
    if (task.mutate) {
      task.mutate(state);
    }
  }

  function getTestFrame() {
    return createIframePromise(true).then(iframe => {
      const ampdoc = new AmpDocSingle(iframe.win);
      const viewer = viewerForDoc(ampdoc);
      sandbox.stub(viewer, 'isEmbedded', () => isEmbedded);
      sandbox.stub(viewer, 'hasCapability', () => hasNavigateToCapability);
      platform = platformFor(iframe.win);
      sandbox.stub(platform, 'isIos', () => isIos);
      sandbox.stub(platform, 'isAndroid', () => isAndroid);
      sandbox.stub(platform, 'isChrome', () => isChrome);
      sandbox.stub(platform, 'isSafari', () => isSafari);

      vsync = vsyncFor(iframe.win);
      sandbox.stub(vsync, 'runPromise', (task, state) => {
        runTask(task, state);
        return Promise.resolve();
      });
      sandbox.stub(vsync, 'run', runTask);
      return iframe;
    });
  }

  function getAppBanner(config = {}) {
    return getTestFrame().then(iframe => {
      const link = iframe.doc.createElement('link');
      link.setAttribute('rel', 'canonical');
      link.setAttribute('href', 'https://example.com/amps.html');
      iframe.doc.head.appendChild(link);

      if (config.meta) {
        const meta = iframe.doc.createElement('meta');
        meta.setAttribute('name', 'apple-itunes-app');
        meta.setAttribute('content', config.meta.content);
        iframe.doc.head.appendChild(meta);
      }

      const manifestObj = config.originManifest || config.manifest;
      if (manifestObj) {
        const rel = config.originManifest ? 'origin-manifest' : 'manifest';
        const manifest = iframe.doc.createElement('link');
        manifest.setAttribute('rel', rel);
        manifest.setAttribute('href', manifestObj.href);
        iframe.doc.head.appendChild(manifest);
        sandbox.mock(xhrFor(iframe.win)).expects('fetchJson')
            .returns(Promise.resolve(manifestObj.content));
      }

      const banner = iframe.doc.createElement('amp-app-banner');
      banner.setAttribute('layout', 'nodisplay');
      banner.getAmpDoc = () => iframe.ampdoc;
      if (!config.noOpenButton) {
        const openButton = iframe.doc.createElement('button');
        openButton.setAttribute('open-button', '');
        banner.appendChild(openButton);
      }

      return iframe.addElement(banner);
    });
  }

  function testSetupAndShowBanner() {
    return getAppBanner({meta, manifest}).then(banner => {
      expect(banner.parentElement).to.not.be.null;
      expect(banner.style.display).to.be.equal('');
      const bannerTop = banner.querySelector(
          'i-amphtml-app-banner-top-padding');
      expect(bannerTop).to.exist;
      const dismissBtn = banner.querySelector(
          '.amp-app-banner-dismiss-button');
      expect(dismissBtn).to.exist;
    });
  }

  function testButtonMissing() {
    return getAppBanner({
      meta,
      manifest,
      noOpenButton: true,
    }).should.eventually.be.rejectedWith(/<button open-button> is required/);
  }

  function testRemoveIfDismissed() {
    sandbox.stub(AbstractAppBanner.prototype, 'isDismissed', () => {
      return Promise.resolve(true);
    });
    return getAppBanner().then(banner => {
      expect(banner.parentElement).to.be.null;
      expect(banner.style.display).to.be.equal('none');
    });
  }

  function testManifestPreconnectPreload(rel) {
    const config = {};
    config[rel] = manifest;
    return () => {
      return getAppBanner(config).then(banner => {
        const impl = banner.implementation_;
        sandbox.stub(impl.preconnect, 'url');
        sandbox.stub(impl.preconnect, 'preload');
        impl.preconnectCallback(true);
        expect(impl.preconnect.url.called).to.be.true;
        expect(impl.preconnect.url).to.have.been.calledOnce;
        expect(impl.preconnect.url)
            .to.have.been.calledWith('https://play.google.com');
        expect(impl.preconnect.preload.called).to.be.true;
        expect(impl.preconnect.preload).to.be.calledOnce;
        expect(impl.preconnect.preload).to.have.been.calledWith(
            'https://example.com/manifest.json');
      });
    };
  }

  function testManifestParseAndHrefs(rel) {
    const config = {};
    config[rel] = manifest;
    return () => {
      sandbox.spy(AbstractAppBanner.prototype, 'setupOpenButton_');
      return getAppBanner({manifest}).then(el => {
        expect(AbstractAppBanner.prototype.setupOpenButton_)
            .to.have.been.calledWith(
              el.querySelector('button[open-button]'),
              'android-app://com.medium.reader/https/example.com/amps.html',
              'https://play.google.com/store/apps/details?id=com.medium.reader'
        );
      });
    };
  }

  beforeEach(() => {
    sandbox = sinon.sandbox.create();
    platform = platformFor(window);
    sandbox.stub(platform, 'isIos', () => isIos);
    sandbox.stub(platform, 'isAndroid', () => isAndroid);
    sandbox.stub(platform, 'isChrome', () => isChrome);
    sandbox.stub(platform, 'isSafari', () => isSafari);
    isAndroid = false;
    isIos = false;
    isChrome = false;
    isSafari = false;
    isEmbedded = false;
    hasNavigateToCapability = true;
  });

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

  describe('Choosing platform', () => {
    it('should upgrade to AmpIosAppBanner on iOS', () => {
      isIos = true;
      return getAppBanner({meta, manifest}).then(banner => {
        expect(banner.implementation_).to.be.instanceof(AmpIosAppBanner);
      });
    });

    it('should upgrade to AmpAndroidAppBanner on Android', () => {
      isAndroid = true;
      return getAppBanner({meta, manifest}).then(banner => {
        expect(banner.implementation_).to.be.instanceof(AmpAndroidAppBanner);
      });
    });

    it('should not upgrade if platform not supported', () => {
      return getAppBanner({meta, manifest}).then(banner => {
        expect(banner.implementation_).to.be.instanceof(AmpAppBanner);
        expect(banner.implementation_.upgradeCallback()).to.be.null;
      });
    });
  });

  describe('non-supported platform', () => {
    it('should remove the banner', () => {
      return getAppBanner().then(banner => {
        expect(banner.parentElement).to.be.null;
      });
    });
  });

  describe('iOS', () => {
    beforeEach(() => {
      isIos = true;
    });

    it('should preconnect to app store', () => {
      return getAppBanner().then(banner => {
        // Re-add to DOM so that we can call `preconnectCallback`.
        banner.ownerDocument.body.appendChild(banner);
        const impl = banner.implementation_;
        sandbox.stub(impl.preconnect, 'url');
        impl.preconnectCallback(true);
        expect(impl.preconnect.url.called).to.be.true;
        expect(impl.preconnect.url).to.be.calledOnce;
        expect(impl.preconnect.url)
            .to.have.been.calledWith('https://itunes.apple.com');
      });
    });

    it('should show banner and set up correctly', testSetupAndShowBanner);

    it('should throw if open button is missing', testButtonMissing);

    it('should remove banner if meta is not provided', () => {
      return getAppBanner({meta: null}).then(banner => {
        expect(banner.parentElement).to.be.null;
        expect(banner.style.display).to.be.equal('none');
      });
    });

    it('should remove banner if safari and not embedded', () => {
      isSafari = true;
      isEmbedded = false;
      return getAppBanner().then(banner => {
        expect(banner.parentElement).to.be.null;
        expect(banner.style.display).to.be.equal('none');
      });
    });

    it('should show banner if safari and embedded', () => {
      isSafari = true;
      isEmbedded = true;
      return getAppBanner({meta}).then(banner => {
        expect(banner.parentElement).to.not.be.null;
        expect(banner.style.display).to.be.equal('');
      });
    });

    it('should hide banner if embedded but viewer does not support ' +
        'navigateTo', () => {
      isSafari = true;
      isEmbedded = true;
      hasNavigateToCapability = false;
      return getAppBanner({meta}).then(banner => {
        expect(banner.parentElement).to.be.null;
        expect(banner.style.display).to.be.equal('none');
      });
    });

    it('should remove banner if already dismissed', testRemoveIfDismissed);

    it('should parse meta content and setup hrefs', () => {
      sandbox.spy(AbstractAppBanner.prototype, 'setupOpenButton_');
      return getAppBanner({meta}).then(el => {
        expect(AbstractAppBanner.prototype.setupOpenButton_)
            .to.have.been.calledWith(
                el.querySelector('button[open-button]'),
                'medium://p/cb7f223fad86',
                'https://itunes.apple.com/us/app/id828256236');
      });
    });

    it('should parse meta content and setup hrefs if app-argument is ' +
        'not provided', () => {
      sandbox.spy(AbstractAppBanner.prototype, 'setupOpenButton_');
      return getAppBanner({
        meta: {content: 'app-id=828256236'},
      }).then(el => {
        expect(AbstractAppBanner.prototype.setupOpenButton_)
            .to.have.been.calledWith(
            el.querySelector('button[open-button]'),
            'https://itunes.apple.com/us/app/id828256236',
            'https://itunes.apple.com/us/app/id828256236');
      });
    });

    it('should parse meta content and validate app-argument url', () => {
      return getAppBanner({
        meta: {content:
            'app-id=828256236, app-argument=javascript:alert("foo");'},
      }).should.eventually.be.rejectedWith(
         /The url in app-argument has invalid protocol/);
    });
  });

  describe('Android', () => {
    beforeEach(() => {
      isAndroid = true;
      isChrome = false;
    });

    it('should preconnect to play store and preload manifest',
        testManifestPreconnectPreload('manifest'));
    it('should preconnect to play store and preload origin-manifest',
        testManifestPreconnectPreload('originManifest'));

    it('should show banner and set up correctly', testSetupAndShowBanner);

    it('should throw if open button is missing', testButtonMissing);
    it('should remove banner if already dismissed', testRemoveIfDismissed);

    it('should remove banner if manifest is not provided', () => {
      return getAppBanner({manifest: null}).then(banner => {
        expect(banner.parentElement).to.be.null;
        expect(banner.style.display).to.be.equal('none');
      });
    });

    it('should remove banner if origin-manifest is not provided', () => {
      return getAppBanner({originManifest: null}).then(banner => {
        expect(banner.parentElement).to.be.null;
        expect(banner.style.display).to.be.equal('none');
      });
    });

    it('should remove banner if chrome', () => {
      isChrome = true;
      return getAppBanner().then(banner => {
        expect(banner.parentElement).to.be.null;
        expect(banner.style.display).to.be.equal('none');
      });
    });

    it('should parse manifest and set hrefs',
        testManifestParseAndHrefs('manifest'));
    it('should parse manifest and set hrefs',
        testManifestParseAndHrefs('originManifest'));
  });

  describe('Abstract App Banner', () => {
    it('should setup click listener', () => {
      return createIframePromise(true).then(iframe => {
        const doc = iframe.doc;
        const element = doc.createElement('div');
        doc.body.appendChild(element);
        const openButton = doc.createElement('button');
        element.appendChild(openButton);
        openButton.setAttribute('open-button', '');
        openButton.addEventListener = sandbox.spy();
        const banner = new AbstractAppBanner(element);
        banner.setupOpenButton_(openButton, 'open-button', 'install-link');
        expect(openButton.addEventListener).to.have.been.calledWith('click');
      });
    });

    it('should create dismiss button and setup click listener', () => {
      return createIframePromise(true).then(iframe => {
        const win = iframe.win;
        const doc = iframe.doc;
        vsync = vsyncFor(win);
        sandbox.stub(vsync, 'run', runTask);
        const element = doc.createElement('div');
        element.id = 'banner1';
        element.getAmpDoc = () => iframe.ampdoc;
        doc.body.appendChild(element);
        const banner = new AbstractAppBanner(element);
        banner.addDismissButton_();

        const bannerTop = element.querySelector(
            'i-amphtml-app-banner-top-padding');
        expect(bannerTop).to.exist;
        const dismissBtn = element.querySelector(
            '.amp-app-banner-dismiss-button');
        expect(dismissBtn).to.not.be.null;
        expect(dismissBtn.parentElement).to.be.equal(element);
        dismissBtn.dispatchEvent(new Event('click'));
        expect(element.parentElement).to.be.null;
        return banner.isDismissed().then(value => {
          expect(value).to.be.true;
        });
      });
    });
  });
});
<commit_msg>Add tests to amp-app-banner covering various browsers and platforms (#9631)<commit_after>/**
 * Copyright 2016 The AMP HTML Authors. All Rights Reserved.
 *
 * 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
 *
 *      http://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.
 */

import {createIframePromise} from '../../../../testing/iframe';
import {platformFor} from '../../../../src/services';
import {vsyncFor} from '../../../../src/services';
import {
    AmpAppBanner,
    AbstractAppBanner,
    AmpIosAppBanner,
    AmpAndroidAppBanner,
} from '../amp-app-banner';
import {xhrFor} from '../../../../src/services';
import {AmpDocSingle} from '../../../../src/service/ampdoc-impl';
import {viewerForDoc} from '../../../../src/services';

describes.realWin('amp-app-banner', {amp: true}, () => {

  let vsync;
  let platform;
  let isAndroid = false;
  let isIos = false;
  let isChrome = false;
  let isSafari = false;
  let isFirefox = false;
  let isEdge = false;
  let isEmbedded = false;
  let hasNavigateToCapability = true;

  const iosMeta = {
    content: 'app-id=828256236, app-argument=medium://p/cb7f223fad86',
  };
  const androidManifest = {
    href: 'https://example.com/manifest.json',
    content: {
      'prefer_related_applications': true,
      'related_applications': [
        {
          'platform': 'play',
          'id': 'com.medium.reader',
          'url': 'https://play.google.com/com.medium.reader',
        },
      ],
    },
  };

  function runTask(task, state) {
    if (task.measure) {
      task.measure(state);
    }
    if (task.mutate) {
      task.mutate(state);
    }
  }

  function getTestFrame() {
    return createIframePromise(true).then(iframe => {
      const ampdoc = new AmpDocSingle(iframe.win);
      const viewer = viewerForDoc(ampdoc);
      sandbox.stub(viewer, 'isEmbedded', () => isEmbedded);
      sandbox.stub(viewer, 'hasCapability', () => hasNavigateToCapability);
      platform = platformFor(iframe.win);
      sandbox.stub(platform, 'isIos', () => isIos);
      sandbox.stub(platform, 'isAndroid', () => isAndroid);
      sandbox.stub(platform, 'isChrome', () => isChrome);
      sandbox.stub(platform, 'isSafari', () => isSafari);
      sandbox.stub(platform, 'isFirefox', () => isFirefox);
      sandbox.stub(platform, 'isEdge', () => isEdge);

      vsync = vsyncFor(iframe.win);
      sandbox.stub(vsync, 'runPromise', (task, state) => {
        runTask(task, state);
        return Promise.resolve();
      });
      sandbox.stub(vsync, 'run', runTask);
      return iframe;
    });
  }

  function getAppBanner(config = {}) {
    return getTestFrame().then(iframe => {
      const link = iframe.doc.createElement('link');
      link.setAttribute('rel', 'canonical');
      link.setAttribute('href', 'https://example.com/amps.html');
      iframe.doc.head.appendChild(link);

      if (config.iosMeta) {
        const meta = iframe.doc.createElement('meta');
        meta.setAttribute('name', 'apple-itunes-app');
        meta.setAttribute('content', config.iosMeta.content);
        iframe.doc.head.appendChild(meta);
      }

      const manifestObj = config.originManifest || config.androidManifest;
      if (manifestObj) {
        const rel = config.originManifest ? 'origin-manifest' : 'manifest';
        const manifest = iframe.doc.createElement('link');
        manifest.setAttribute('rel', rel);
        manifest.setAttribute('href', manifestObj.href);
        iframe.doc.head.appendChild(manifest);
        sandbox.mock(xhrFor(iframe.win)).expects('fetchJson')
            .returns(Promise.resolve(manifestObj.content));
      }

      const banner = iframe.doc.createElement('amp-app-banner');
      banner.setAttribute('layout', 'nodisplay');
      banner.getAmpDoc = () => iframe.ampdoc;
      if (!config.noOpenButton) {
        const openButton = iframe.doc.createElement('button');
        openButton.setAttribute('open-button', '');
        banner.appendChild(openButton);
      }

      return iframe.addElement(banner);
    });
  }

  function testSetupAndShowBanner() {
    return getAppBanner({iosMeta, androidManifest}).then(banner => {
      expect(banner.parentElement).to.not.be.null;
      expect(banner.style.display).to.be.equal('');
      const bannerTop = banner.querySelector(
          'i-amphtml-app-banner-top-padding');
      expect(bannerTop).to.exist;
      const dismissBtn = banner.querySelector(
          '.amp-app-banner-dismiss-button');
      expect(dismissBtn).to.exist;
    });
  }

  function testRemoveBanner(config = {iosMeta, androidManifest}) {
    return getAppBanner(config).then(banner => {
      expect(banner.parentElement).to.be.null;
      expect(banner.style.display).to.be.equal('none');
    });
  }

  function testButtonMissing() {
    return getAppBanner({
      iosMeta,
      androidManifest,
      noOpenButton: true,
    }).should.eventually.be.rejectedWith(/<button open-button> is required/);
  }

  function testRemoveBannerIfDismissed() {
    sandbox.stub(AbstractAppBanner.prototype, 'isDismissed', () => {
      return Promise.resolve(true);
    });
    return testRemoveBanner();
  }

  function testSuiteIos() {
    it('should preconnect to app store', () => {
      return getAppBanner({iosMeta}).then(banner => {
        const impl = banner.implementation_;
        sandbox.stub(impl.preconnect, 'url');
        impl.preconnectCallback(true);
        expect(impl.preconnect.url.called).to.be.true;
        expect(impl.preconnect.url).to.be.calledOnce;
        expect(impl.preconnect.url)
            .to.have.been.calledWith('https://itunes.apple.com');
      });
    });

    it('should show banner and set up correctly', testSetupAndShowBanner);

    it('should throw if open button is missing', testButtonMissing);

    it('should remove banner if already dismissed',
        testRemoveBannerIfDismissed);

    it('should remove banner if meta is not provided', () => {
      testRemoveBanner({iosMeta: null});
    });

    it('should parse meta content and setup hrefs', () => {
      sandbox.spy(AbstractAppBanner.prototype, 'setupOpenButton_');
      return getAppBanner({iosMeta}).then(el => {
        expect(AbstractAppBanner.prototype.setupOpenButton_)
            .to.have.been.calledWith(
                el.querySelector('button[open-button]'),
                'medium://p/cb7f223fad86',
                'https://itunes.apple.com/us/app/id828256236');
      });
    });

    it('should parse meta content and setup hrefs if app-argument is ' +
        'not provided', () => {
      sandbox.spy(AbstractAppBanner.prototype, 'setupOpenButton_');
      return getAppBanner({
        iosMeta: {content: 'app-id=828256236'},
      }).then(el => {
        expect(AbstractAppBanner.prototype.setupOpenButton_)
            .to.have.been.calledWith(
            el.querySelector('button[open-button]'),
            'https://itunes.apple.com/us/app/id828256236',
            'https://itunes.apple.com/us/app/id828256236');
      });
    });

    it('should parse meta content and validate app-argument url', () => {
      return getAppBanner({
        iosMeta: {content:
            'app-id=828256236, app-argument=javascript:alert("foo");'},
      }).should.eventually.be.rejectedWith(
          /The url in app-argument has invalid protocol/);
    });
  }

  function testSuiteAndroid() {
    it('should preconnect to play store and preload manifest', () => {
      return getAppBanner({androidManifest}).then(banner => {
        const impl = banner.implementation_;
        sandbox.stub(impl.preconnect, 'url');
        sandbox.stub(impl.preconnect, 'preload');
        impl.preconnectCallback(true);
        expect(impl.preconnect.url.called).to.be.true;
        expect(impl.preconnect.url).to.have.been.calledOnce;
        expect(impl.preconnect.url)
            .to.have.been.calledWith('https://play.google.com');
        expect(impl.preconnect.preload.called).to.be.true;
        expect(impl.preconnect.preload).to.be.calledOnce;
        expect(impl.preconnect.preload).to.have.been.calledWith(
            'https://example.com/manifest.json');
      });
    });

    it('should preconnect to play store and preload origin-manifest', () => {
      return getAppBanner({originManifest: androidManifest}).then(banner => {
        const impl = banner.implementation_;
        sandbox.stub(impl.preconnect, 'url');
        sandbox.stub(impl.preconnect, 'preload');
        impl.preconnectCallback(true);
        expect(impl.preconnect.url.called).to.be.true;
        expect(impl.preconnect.url).to.have.been.calledOnce;
        expect(impl.preconnect.url)
            .to.have.been.calledWith('https://play.google.com');
        expect(impl.preconnect.preload.called).to.be.true;
        expect(impl.preconnect.preload).to.be.calledOnce;
        expect(impl.preconnect.preload).to.have.been.calledWith(
            'https://example.com/manifest.json');
      });
    });

    it('should show banner and set up correctly', testSetupAndShowBanner);

    it('should throw if open button is missing', testButtonMissing);

    it('should remove banner if already dismissed',
        testRemoveBannerIfDismissed);

    it('should remove banner if manifest is not provided', () => {
      testRemoveBanner({androidManifest: null, originManifest: null});
    });

    it('should parse manifest and set hrefs', () => {
      sandbox.spy(AbstractAppBanner.prototype, 'setupOpenButton_');
      return getAppBanner({androidManifest}).then(el => {
        expect(AbstractAppBanner.prototype.setupOpenButton_)
            .to.have.been.calledWith(
            el.querySelector('button[open-button]'),
            'android-app://com.medium.reader/https/example.com/amps.html',
            'https://play.google.com/store/apps/details?id=com.medium.reader'
        );
      });
    });

    it('should parse origin manifest and set hrefs', () => {
      sandbox.spy(AbstractAppBanner.prototype, 'setupOpenButton_');
      return getAppBanner({originManifest: androidManifest}).then(el => {
        expect(AbstractAppBanner.prototype.setupOpenButton_)
            .to.have.been.calledWith(
            el.querySelector('button[open-button]'),
            'android-app://com.medium.reader/https/example.com/amps.html',
            'https://play.google.com/store/apps/details?id=com.medium.reader'
        );
      });
    });
  }

  beforeEach(() => {
    isAndroid = false;
    isIos = false;
    isChrome = false;
    isSafari = false;
    isFirefox = false;
    isEdge = false;
    isEmbedded = false;
    hasNavigateToCapability = true;
  });

  describe('Choosing platform', () => {
    it('should upgrade to AmpIosAppBanner on iOS', () => {
      isIos = true;
      return getAppBanner({iosMeta, androidManifest}).then(banner => {
        expect(banner.implementation_).to.be.instanceof(AmpIosAppBanner);
      });
    });

    it('should upgrade to AmpAndroidAppBanner on Android', () => {
      isAndroid = true;
      return getAppBanner({iosMeta, androidManifest}).then(banner => {
        expect(banner.implementation_).to.be.instanceof(AmpAndroidAppBanner);
      });
    });

    it('should not upgrade if platform not supported', () => {
      isEdge = true;
      return getAppBanner({iosMeta, androidManifest}).then(banner => {
        expect(banner.implementation_).to.be.instanceof(AmpAppBanner);
        expect(banner.implementation_.upgradeCallback()).to.be.null;
      });
    });
  });

  describe('non-supported platform', () => {
    it('should remove the banner', () => {
      return getAppBanner().then(banner => {
        expect(banner.parentElement).to.be.null;
      });
    });
  });

  describe('iOS', () => {
    beforeEach(() => {
      isIos = true;
    });

    describe('Embedded', () => {
      beforeEach(() => {
        isEmbedded = true;
      });

      describe('Safari', () => {
        beforeEach(() => {
          isSafari = true;
        });

        testSuiteIos();

        it('should hide banner if embedded but viewer does not support ' +
            'navigateTo', () => {
          hasNavigateToCapability = false;
          testRemoveBanner();
        });
      });

      describe('Chrome', () => {
        beforeEach(() => {
          isChrome = true;
        });

        testSuiteIos();

        it('should hide banner if embedded but viewer does not support ' +
            'navigateTo', () => {
          hasNavigateToCapability = false;
          testRemoveBanner();
        });
      });

      describe('Firefox', () => {
        beforeEach(() => {
          isFirefox = true;
        });

        testSuiteIos();

        it('should hide banner if embedded but viewer does not support ' +
            'navigateTo', () => {
          hasNavigateToCapability = false;
          testRemoveBanner();
        });
      });

    });

    describe('Non-embedded', () => {
      beforeEach(() => {
        isEmbedded = false;
      });

      describe('Safari', () => {
        beforeEach(() => {
          isSafari = true;
        });

        it('should NOT show banner', testRemoveBanner);
      });

      describe('Chrome', () => {
        beforeEach(() => {
          isChrome = true;
        });

        testSuiteIos();
      });

      describe('Firefox', () => {
        beforeEach(() => {
          isFirefox = true;
        });

        testSuiteIos();
      });
    });
  });

  describe('Android', () => {
    beforeEach(() => {
      isAndroid = true;
    });

    describe('Embedded', () => {
      beforeEach(() => {
        isEmbedded = true;
      });

      describe('Chrome', () => {
        beforeEach(() => {
          isChrome = true;
        });

        testSuiteAndroid();

        it('should hide banner if embedded but viewer does not support ' +
            'navigateTo', () => {
          hasNavigateToCapability = false;
          testRemoveBanner();
        });
      });

      describe('Firefox', () => {
        beforeEach(() => {
          isFirefox = true;
        });

        testSuiteAndroid();

        it('should hide banner if embedded but viewer does not support ' +
            'navigateTo', () => {
          hasNavigateToCapability = false;
          testRemoveBanner();
        });
      });

    });

    describe('Non-embedded', () => {
      beforeEach(() => {
        isEmbedded = false;
      });

      describe('Chrome', () => {
        beforeEach(() => {
          isChrome = true;
        });

        it('should NOT show banner', testRemoveBanner);
      });

      describe('Firefox', () => {
        beforeEach(() => {
          isFirefox = true;
        });

        testSuiteAndroid();
      });
    });
  });

  describe('Windows Edge', () => {
    beforeEach(() => {
      isEdge = true;
    });

    it('Embedded should NOT show banner', () => {
      isEmbedded = true;
      testRemoveBanner();
    });

    it('Non-embedded should NOT show banner', () => {
      isEmbedded = false;
      testRemoveBanner();
    });
  });

  describe('Abstract App Banner', () => {
    it('should setup click listener', () => {
      return createIframePromise(true).then(iframe => {
        const doc = iframe.doc;
        const element = doc.createElement('div');
        doc.body.appendChild(element);
        const openButton = doc.createElement('button');
        element.appendChild(openButton);
        openButton.setAttribute('open-button', '');
        openButton.addEventListener = sandbox.spy();
        const banner = new AbstractAppBanner(element);
        banner.setupOpenButton_(openButton, 'open-button', 'install-link');
        expect(openButton.addEventListener).to.have.been.calledWith('click');
      });
    });

    it('should create dismiss button and setup click listener', () => {
      return createIframePromise(true).then(iframe => {
        const win = iframe.win;
        const doc = iframe.doc;
        vsync = vsyncFor(win);
        sandbox.stub(vsync, 'run', runTask);
        const element = doc.createElement('div');
        element.id = 'banner1';
        element.getAmpDoc = () => iframe.ampdoc;
        doc.body.appendChild(element);
        const banner = new AbstractAppBanner(element);
        banner.addDismissButton_();

        const bannerTop = element.querySelector(
            'i-amphtml-app-banner-top-padding');
        expect(bannerTop).to.exist;
        const dismissBtn = element.querySelector(
            '.amp-app-banner-dismiss-button');
        expect(dismissBtn).to.not.be.null;
        expect(dismissBtn.parentElement).to.be.equal(element);
        dismissBtn.dispatchEvent(new Event('click'));
        expect(element.parentElement).to.be.null;
        return banner.isDismissed().then(value => {
          expect(value).to.be.true;
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>// XSS Commit Checker
// If the file type if ejs and has <%- in it,
// We assume that it needs to be taken care for manual analysis
// Just report it using the event.

var events = require('events');

// report as many times as you want
exports.scan = function(diff, report){

  diff.forEach(function(file){
    // Only interested in files with .ejs as extension
    if(file.fileExtension !== 'ejs')
      return;
    file.additions.forEach(function(addition){
      if(addition.match(/<%-/gi) !== null){
        report( 'XSS in ' + addition);
      }
    });
  });

return;
}
<commit_msg>add handlebar style templating to xss tests<commit_after>// XSS Commit Checker
// If the file type if ejs and has <%- in it,
// We assume that it needs to be taken care for manual analysis
// Just report it using the event.

var events = require('events');

// report as many times as you want
exports.scan = function(diff, report){

  diff.forEach(function(file){
    // Only interested in files with .ejs as extension
    if(file.fileExtension !== 'ejs')
      return;
    file.additions.forEach(function(addition){
      if(addition.match(/<%-/gi) !== null || addition.match(/{{{/gi) !=== null){
        report( 'XSS in ' + addition);
      }
    });
  });

return;
}
<|endoftext|>


<commit_before>angular.module('mrlapp.service.ServoGui', []).controller('ServoGuiCtrl', ['$scope', 'mrl', function($scope, mrl) {
    console.info('ServoGuiCtrl')
    var _self = this
    var msg = this.msg

    // mode is either "status" or "control"
    // in status mode we take updates by the servo and its events
    // in control mode we take updates by the user
    // $scope.mode = "status"; now statusControlMode

    // useful for initialization of components
    var firstTime = true

    $scope.state = {
        useEncoderData: false,
        attached: false,
        showLimits: false,
        rest: 90
    }

    // init
    $scope.min = 0
    $scope.max = 180

    $scope.possibleControllers = null
    $scope.testTime = 300
    $scope.sliderEnabled = false
    $scope.speedDisplay = '0'
    $scope.idleSeconds = 0

    $scope.speed = null
    $scope.lockInputOutput = true

    $scope.activeTabIndex = 1

    $scope.optionsWithoutStart = {
      connect: true,
      range: {
        min: 0,
        max: 100,
      },
    };

    $scope.sliderPositions = [20, 80];

    // TODO - should be able to build this based on
    // current selection of controller
    $scope.pinList = []
    for (let i = 0; i < 58; ++i) {
        $scope.pinList.push(i + '')
        // make strings
    }

    $scope.setRest = function() {
        msg.send('setRest', $scope.service.rest)
    }

    $scope.toggleLock = function() {
        $scope.lockInputOutput = !$scope.lockInputOutput
    }

    $scope.setMinMax = function(min, max) {
        console.log('setMinMax ', min, max)
        msg.send('setMinMax', min, max)
        /*
        if ($scope.statusControlMode == 'control') {
            msg.send('setMinMax', $scope.limits.minValue, $scope.limits.maxValue)
        }
        */
    }

    $scope.setSpeed = function() {
        if ($scope.service.speed == null || $scope.service.speed == "201") {
            // if speed is null or speed is "max slider value" unset the speed
            msg.send("unsetSpeed")
        } else {
            msg.send("setSpeed", $scope.service.speed)
        }
    }

    // GOOD TEMPLATE TO FOLLOW
    this.updateState = function(service) {
        $scope.service = service

        // service.controller like many parts is overloaded status & control :(
        // so the ui may change controller - but at that moment the service may not
        // be attached - the "only" time its attached is when the service data comes
        // directly from the service and service.controller != null
        if (service.controller) {
            $scope.state.attached = true
        } else {
            $scope.state.attached = false
        }

        // done correctly - speedDisplay is a 'status' display !
        // its NOT used to set 'control' speed - control is sent
        // from the ui interface - but the ui component does not display what it sent
        // speedDisplay displays what was recieved - and is currently set
        if (service.speed) {
            $scope.speedDisplay = service.speed.toFixed(0)
        } else {
            $scope.speedDisplay = 'Max'
            service.speed = 201
            // max range of slider bar
        }

        $scope.idleSeconds = service.idleTimeout / 1000
        $scope.pos.options.minLimit = service.mapper.minX
        $scope.pos.options.maxLimit = service.mapper.maxX

        // ui initialization - good idea !
        // first time is 'status' - otherwise control
        if (firstTime) {

            // control assigned = status for "non" broadcastState methods
            // guarded by firstTime - it never will get  updated after
            // initialization
            $scope.state.rest = service.rest

            $scope.pos.value = service.currentOutputPos
            $scope.sliderEnabled = true

            $scope.activeTabIndex = service.controller == null ? 0 : 1

            $scope.state.inputMin = service.mapper.minX
            $scope.state.inputMax = service.mapper.maxX
            $scope.state.outputMin = service.mapper.minY
            $scope.state.outputMax = service.mapper.maxY

            firstTime = false
        }

        // set min/max mapper slider BAD IDEA !!!! control "OR" status NEVER BOTH !!!!
        // $scope.pinList = service.pinList
    }

    this.onMsg = function(inMsg) {
        var data = inMsg.data[0]
        switch (inMsg.method) {
        case 'onState':
            _self.updateState(data)
            $scope.$apply()
            break
        case 'onServoSetSpeed':
            if (data.speed) {
                $scope.speedDisplay = data.speed
            } else {
                $scope.speedDisplay = 'Max'
            }
            $scope.$apply()
            break
        case 'onRefreshControllers':
            $scope.possibleControllers = data
            $scope.$apply()
            break
        case 'onEncoderData':
            $scope.service.currentOutputPos = Math.floor(data.angle)
            $scope.$apply()
            break
        case 'onServoEnable':
            $scope.service.enabled = true
            $scope.$apply()
            break
        case 'onServoDisable':
            $scope.service.enabled = false
            $scope.$apply()
            break
        case 'onServoStopped':
            $scope.service.currentOutputPos = data.pos
            $scope.$apply()
            break
        case 'onServoStarted':
            // $scope.service.currentOutputPos = data.pos
            // $scope.$apply()
            break
        case 'onStatus':
            break
        default:
            console.info("ERROR - unhandled method " + $scope.name + " Method " + inMsg.method)
            break
        }
    }

    $scope.toggleEncoderData = function() {
        if ($scope.state.useEncoderData) {
            msg.subscribe("publishEncoderData")
            msg.send('broadcastState')
        } else {
            msg.unsubscribe("publishEncoderData")
        }
    }

    $scope.setPin = function() {
        msg.send('setPin', $scope.service.pin)
    }

    $scope.setAutoDisable = function() {
        msg.send("setIdleTimeout", $scope.service.idleTimeout)
        msg.send("setAutoDisable", $scope.service.autoDisable)
    }

    // regrettably the onMethodMap dynamic
    // generation of methods failed on this overloaded
    // sweep method - there are several overloads in the
    // Java service - although msg.sweep() was tried for ng-click
    // for some reason Js resolved msg.sweep(null, null, null, null) :P
    $scope.sweep = function() {
        msg.send('sweep')
    }

    $scope.moveTo = function(pos) {
        msg.send('moveTo', pos)
    }

    $scope.setIdleTimeout = function(idleTime) {
        msg.send('setIdleTimeout', idleTime * 1000)
    }

    $scope.attachController = function() {
        console.info("attachController")
        msg.send("setPin", $scope.service.pin)
        msg.send("attach", $scope.service.controller)
        msg.send("broadcastState")
    }

    $scope.map = function() {

        if ($scope.lockInputOutput) {
            $scope.service.mapper.minY = $scope.service.mapper.minX
            $scope.service.mapper.maxY = $scope.service.mapper.maxX
        }

        msg.send('map', $scope.service.mapper.minX, $scope.service.mapper.maxX, $scope.service.mapper.minY, $scope.service.mapper.maxY)
        msg.send("broadcastState")
    }

    // msg.subscribe("publishMoveTo") - can cause a infinite loopback control-> status
    // msg.subscribe("publishEncoderData") - not a good idea will swamp with data
    msg.subscribe("publishServoEnable")
    msg.subscribe("publishServoDisable")
    msg.subscribe("publishServoStopped")
    msg.subscribe("publishServoStarted")
    msg.subscribe("publishServoSetSpeed")
    msg.subscribe("refreshControllers")
    msg.subscribe(this)
    msg.send('refreshControllers')
}
])
<commit_msg>copy vars removed<commit_after>angular.module('mrlapp.service.ServoGui', []).controller('ServoGuiCtrl', ['$scope', 'mrl', function($scope, mrl) {
    console.info('ServoGuiCtrl')
    var _self = this
    var msg = this.msg

    // mode is either "status" or "control"
    // in status mode we take updates by the servo and its events
    // in control mode we take updates by the user
    // $scope.mode = "status"; now statusControlMode

    // useful for initialization of components
    var firstTime = true

    $scope.state = {
        useEncoderData: false,
        attached: false,
        showLimits: false,
        rest: 90
    }

    // init
    $scope.min = 0
    $scope.max = 180

    $scope.possibleControllers = null
    $scope.testTime = 300
    $scope.sliderEnabled = false
    $scope.speedDisplay = '0'
    $scope.idleSeconds = 0

    $scope.speed = null
    $scope.lockInputOutput = false

    $scope.activeTabIndex = 1

    $scope.optionsWithoutStart = {
      connect: true,
      range: {
        min: 0,
        max: 100,
      },
    };

    $scope.sliderPositions = [20, 80];

    // TODO - should be able to build this based on
    // current selection of controller
    $scope.pinList = []
    for (let i = 0; i < 58; ++i) {
        $scope.pinList.push(i + '')
        // make strings
    }

    $scope.setRest = function() {
        msg.send('setRest', $scope.service.rest)
    }

    $scope.toggleLock = function() {
        $scope.lockInputOutput = !$scope.lockInputOutput
    }

    $scope.setMinMax = function(min, max) {
        console.log('setMinMax ', min, max)
        msg.send('setMinMax', min, max)
        /*
        if ($scope.statusControlMode == 'control') {
            msg.send('setMinMax', $scope.limits.minValue, $scope.limits.maxValue)
        }
        */
    }

    $scope.setSpeed = function() {
        if ($scope.service.speed == null || $scope.service.speed == "201") {
            // if speed is null or speed is "max slider value" unset the speed
            msg.send("unsetSpeed")
        } else {
            msg.send("setSpeed", $scope.service.speed)
        }
    }

    // GOOD TEMPLATE TO FOLLOW
    this.updateState = function(service) {
        $scope.service = service

        // service.controller like many parts is overloaded status & control :(
        // so the ui may change controller - but at that moment the service may not
        // be attached - the "only" time its attached is when the service data comes
        // directly from the service and service.controller != null
        if (service.controller) {
            $scope.state.attached = true
        } else {
            $scope.state.attached = false
        }

        // done correctly - speedDisplay is a 'status' display !
        // its NOT used to set 'control' speed - control is sent
        // from the ui interface - but the ui component does not display what it sent
        // speedDisplay displays what was recieved - and is currently set
        if (service.speed) {
            $scope.speedDisplay = service.speed.toFixed(0)
        } else {
            $scope.speedDisplay = 'Max'
            service.speed = 201
            // max range of slider bar
        }

        $scope.idleSeconds = service.idleTimeout / 1000
//         $scope.pos.options.minLimit = service.mapper.minX
//         $scope.pos.options.maxLimit = service.mapper.maxX

        // ui initialization - good idea !
        // first time is 'status' - otherwise control
        if (firstTime) {

            // control assigned = status for "non" broadcastState methods
            // guarded by firstTime - it never will get  updated after
            // initialization
            $scope.state.rest = service.rest

            // $scope.pos.value = service.currentOutputPos
            $scope.sliderEnabled = true

            $scope.activeTabIndex = service.controller == null ? 0 : 1

            $scope.state.inputMin = service.mapper.minX
            $scope.state.inputMax = service.mapper.maxX
            $scope.state.outputMin = service.mapper.minY
            $scope.state.outputMax = service.mapper.maxY

            firstTime = false
        }

        // set min/max mapper slider BAD IDEA !!!! control "OR" status NEVER BOTH !!!!
        // $scope.pinList = service.pinList
    }

    this.onMsg = function(inMsg) {
        var data = inMsg.data[0]
        switch (inMsg.method) {
        case 'onState':
            _self.updateState(data)
            $scope.$apply()
            break
        case 'onServoSetSpeed':
            if (data.speed) {
                $scope.speedDisplay = data.speed
            } else {
                $scope.speedDisplay = 'Max'
            }
            $scope.$apply()
            break
        case 'onRefreshControllers':
            $scope.possibleControllers = data
            $scope.$apply()
            break
        case 'onEncoderData':
            $scope.service.currentOutputPos = Math.floor(data.angle)
            $scope.$apply()
            break
        case 'onServoEnable':
            $scope.service.enabled = true
            $scope.$apply()
            break
        case 'onServoDisable':
            $scope.service.enabled = false
            $scope.$apply()
            break
        case 'onServoStopped':
            $scope.service.currentOutputPos = data.pos
            $scope.$apply()
            break
        case 'onServoStarted':
            // $scope.service.currentOutputPos = data.pos
            // $scope.$apply()
            break
        case 'onStatus':
            break
        default:
            console.info("ERROR - unhandled method " + $scope.name + " Method " + inMsg.method)
            break
        }
    }

    $scope.toggleEncoderData = function() {
        if ($scope.state.useEncoderData) {
            msg.subscribe("publishEncoderData")
            msg.send('broadcastState')
        } else {
            msg.unsubscribe("publishEncoderData")
        }
    }

    $scope.setPin = function() {
        msg.send('setPin', $scope.service.pin)
    }

    $scope.setAutoDisable = function() {
        msg.send("setIdleTimeout", $scope.service.idleTimeout)
        msg.send("setAutoDisable", $scope.service.autoDisable)
    }

    // regrettably the onMethodMap dynamic
    // generation of methods failed on this overloaded
    // sweep method - there are several overloads in the
    // Java service - although msg.sweep() was tried for ng-click
    // for some reason Js resolved msg.sweep(null, null, null, null) :P
    $scope.sweep = function() {
        msg.send('sweep')
    }

    $scope.moveTo = function(pos) {
        msg.send('moveTo', pos)
    }

    $scope.setIdleTimeout = function(idleTime) {
        msg.send('setIdleTimeout', idleTime * 1000)
    }

    $scope.attachController = function() {
        console.info("attachController")
        msg.send("setPin", $scope.service.pin)
        msg.send("attach", $scope.service.controller)
        msg.send("broadcastState")
    }

    $scope.map = function() {

        if ($scope.lockInputOutput) {
            $scope.service.mapper.minY = $scope.service.mapper.minX
            $scope.service.mapper.maxY = $scope.service.mapper.maxX
        }

        msg.send('map', $scope.service.mapper.minX, $scope.service.mapper.maxX, $scope.service.mapper.minY, $scope.service.mapper.maxY)
        msg.send("broadcastState")
    }

    // msg.subscribe("publishMoveTo") - can cause a infinite loopback control-> status
    // msg.subscribe("publishEncoderData") - not a good idea will swamp with data
    msg.subscribe("publishServoEnable")
    msg.subscribe("publishServoDisable")
    msg.subscribe("publishServoStopped")
    msg.subscribe("publishServoStarted")
    msg.subscribe("publishServoSetSpeed")
    msg.subscribe("refreshControllers")
    msg.subscribe(this)
    msg.send('refreshControllers')
}
])
<|endoftext|>


<commit_before>// @flow

import { green } from 'chalk';
import inquirer from 'inquirer';
import R from 'ramda';
import { table } from 'table';
import urlRegex from 'url-regex';

import gql from '../../gql';
import { printGraphQLErrors } from '../../printErrors';
import { runGQLQuery } from '../../query';

import typeof Yargs from 'yargs';
import type { BaseArgs } from '..';

export const command = 'create';
export const description = 'Create new project';

export function builder(yargs: Yargs): Yargs {
  return yargs
    .usage(`$0 ${command} - ${description}`)
    .example('$0', 'Create new project');
}

type createProjectArgs = {
  clog: typeof console.log,
  cerr: typeof console.error,
};

export async function createProject({
  clog,
  cerr,
}:
createProjectArgs): Promise<number> {
  const query = gql`
    mutation AddProject($input: ProjectInput!) {
      addProject(input: $input) {
        id
        name
        customer {
          name
        }
        git_url
        active_systems_deploy
        active_systems_remove
        branches
        pullrequests
        openshift {
          name
        }
        created
      }
    }
  `;

  const projectInput = await inquirer.prompt([
    {
      type: 'input',
      name: 'name',
      message: 'Project name:',
      validate: input => Boolean(input) || 'Please enter a project name.',
    },
    {
      type: 'input',
      name: 'git_url',
      message: 'Git URL:',
      validate: input =>
        // Verify that it is a valid URL and...
        (urlRegex({ exact: true }).test(input) &&
          // ...that it is either a URL from the big three git hosts or includes `.git` at the end of the string.
          /(github\.com|bitbucket\.org|gitlab\.com|\.git$)/.test(input)) ||
        // If the input is invalid, prompt the user to enter a valid Git URL
        'Please enter a valid Git URL.',
    },
    {
      type: 'input',
      name: 'active_systems_deploy',
      message: 'Active system for task "deploy":',
      default: 'lagoon_openshiftBuildDeploy',
    },
    {
      type: 'input',
      name: 'active_systems_remove',
      message: 'Active system for task "remove":',
      default: 'lagoon_openshiftBuildRemove',
    },
    {
      type: 'input',
      name: 'branches',
      message: 'Deploy branches:',
      default: 'true',
    },
    {
      type: 'input',
      name: 'pullrequests',
      message: 'Pull requests:',
      default: null,
    },
    {
      type: 'input',
      name: 'production_environment',
      message: 'Production environment:',
      default: null,
    },
  ]);

  const result = await runGQLQuery({
    query,
    cerr,
    variables: {
      input: {
        ...projectInput,
        // FIXME: Get customer ID via allCustomers query
        customer: 1,
        // FIXME: Get the openshift ID via new allOpenshifts query
        openshift: 1,
      },
    },
  });

  const { errors } = result;
  if (errors != null) {
    return printGraphQLErrors(cerr, ...errors);
  }

  const project = R.path(['data', 'addProject'], result);

  const projectName = R.prop('name', project);

  clog(green(`Project "${projectName}" created successfully:`));

  clog(table([
    ['Project Name', projectName],
    ['Customer', R.path(['customer', 'name'], project)],
    ['Git URL', R.prop('git_url', project)],
    ['Active Systems Deploy', R.prop('active_systems_deploy', project)],
    ['Active Systems Remove', R.prop('active_systems_remove', project)],
    ['Branches', String(R.prop('branches', project))],
    ['Pull Requests', String(R.prop('pullrequests', project))],
    ['Openshift', R.path(['openshift', 'name'], project)],
    ['Created', R.path(['created'], project)],
  ]));

  return 0;
}

export async function handler({
  clog,
  cerr,
  config,
}:
BaseArgs): Promise<number> {
  return createProject({ clog, cerr });
}
<commit_msg>Fix default<commit_after>// @flow

import { green } from 'chalk';
import inquirer from 'inquirer';
import R from 'ramda';
import { table } from 'table';
import urlRegex from 'url-regex';

import gql from '../../gql';
import { printGraphQLErrors } from '../../printErrors';
import { runGQLQuery } from '../../query';

import typeof Yargs from 'yargs';
import type { BaseArgs } from '..';

export const command = 'create';
export const description = 'Create new project';

export function builder(yargs: Yargs): Yargs {
  return yargs
    .usage(`$0 ${command} - ${description}`)
    .example('$0', 'Create new project');
}

type createProjectArgs = {
  clog: typeof console.log,
  cerr: typeof console.error,
};

export async function createProject({
  clog,
  cerr,
}:
createProjectArgs): Promise<number> {
  const query = gql`
    mutation AddProject($input: ProjectInput!) {
      addProject(input: $input) {
        id
        name
        customer {
          name
        }
        git_url
        active_systems_deploy
        active_systems_remove
        branches
        pullrequests
        openshift {
          name
        }
        created
      }
    }
  `;

  const projectInput = await inquirer.prompt([
    {
      type: 'input',
      name: 'name',
      message: 'Project name:',
      validate: input => Boolean(input) || 'Please enter a project name.',
    },
    {
      type: 'input',
      name: 'git_url',
      message: 'Git URL:',
      validate: input =>
        // Verify that it is a valid URL and...
        (urlRegex({ exact: true }).test(input) &&
          // ...that it is either a URL from the big three git hosts or includes `.git` at the end of the string.
          /(github\.com|bitbucket\.org|gitlab\.com|\.git$)/.test(input)) ||
        // If the input is invalid, prompt the user to enter a valid Git URL
        'Please enter a valid Git URL.',
    },
    {
      type: 'input',
      name: 'active_systems_deploy',
      message: 'Active system for task "deploy":',
      default: 'lagoon_openshiftBuildDeploy',
    },
    {
      type: 'input',
      name: 'active_systems_remove',
      message: 'Active system for task "remove":',
      default: 'lagoon_openshiftRemove',
    },
    {
      type: 'input',
      name: 'branches',
      message: 'Deploy branches:',
      default: 'true',
    },
    {
      type: 'input',
      name: 'pullrequests',
      message: 'Pull requests:',
      default: null,
    },
    {
      type: 'input',
      name: 'production_environment',
      message: 'Production environment:',
      default: null,
    },
  ]);

  const result = await runGQLQuery({
    query,
    cerr,
    variables: {
      input: {
        ...projectInput,
        // FIXME: Get customer ID via allCustomers query
        customer: 1,
        // FIXME: Get the openshift ID via new allOpenshifts query
        openshift: 1,
      },
    },
  });

  const { errors } = result;
  if (errors != null) {
    return printGraphQLErrors(cerr, ...errors);
  }

  const project = R.path(['data', 'addProject'], result);

  const projectName = R.prop('name', project);

  clog(green(`Project "${projectName}" created successfully:`));

  clog(table([
    ['Project Name', projectName],
    ['Customer', R.path(['customer', 'name'], project)],
    ['Git URL', R.prop('git_url', project)],
    ['Active Systems Deploy', R.prop('active_systems_deploy', project)],
    ['Active Systems Remove', R.prop('active_systems_remove', project)],
    ['Branches', String(R.prop('branches', project))],
    ['Pull Requests', String(R.prop('pullrequests', project))],
    ['Openshift', R.path(['openshift', 'name'], project)],
    ['Created', R.path(['created'], project)],
  ]));

  return 0;
}

export async function handler({
  clog,
  cerr,
  config,
}:
BaseArgs): Promise<number> {
  return createProject({ clog, cerr });
}
<|endoftext|>


<commit_before>var striptags = require('striptags'),
  dom = require('../services/dom');

module.exports = function (result, args) {
  var el = result.el,
    bindings = result.bindings,
    rivets = result.rivets,
    tpl = `
      { max | charsRemaining data }
    `,
    span = dom.create(tpl);

  bindings.max = args.value;
  rivets.formatters.charsRemaining = function (max, data) {
    var length = parseInt(striptags(data.value).length),
      remaining = max - length;

    if (remaining > 0) {
      span.classList.remove('too-long');
      return 'Remaining: ' + remaining;
    } else if (remaining === 0) {
      span.classList.remove('too-long');
      return -remaining + ' characters over the limit';
    } else {
      span.classList.add('too-long');
      return -remaining + ' characters over the limit';
    }
  };

  el.appendChild(span);
  return result;
};
<commit_msg>hotfix for soft-maxlength<commit_after>var striptags = require('striptags'),
  dom = require('../services/dom');

module.exports = function (result, args) {
  var el = result.el,
    bindings = result.bindings,
    rivets = result.rivets,
    tpl = `
      { max | charsRemaining data }
    `,
    span = dom.create(tpl);

  bindings.max = args.value;
  rivets.formatters.charsRemaining = function (max, data) {
    var length = data.value ? parseInt(striptags(data.value).length) : 0,
      remaining = max - length;

    if (remaining > 0) {
      span.classList.remove('too-long');
      return 'Remaining: ' + remaining;
    } else if (remaining === 0) {
      span.classList.remove('too-long');
      return -remaining + ' characters over the limit';
    } else {
      span.classList.add('too-long');
      return -remaining + ' characters over the limit';
    }
  };

  el.appendChild(span);
  return result;
};
<|endoftext|>


<commit_before>//
// AJAX and Dynamic jQuery for MutSig table
//
// AJAX :
//           Connects to portal via AJAX and downloads JSON document containg MutSig information:
//              gene symbol, num mutations, q-value
//
// Dynamic jQuery:
//          Handles changes in select_cancer_type:
//              shows/hides "Recurrently Mutated Genes" button
//              shows/hides table containing MutSig information.
//          Takes user-selected mutated genes and adds them to the Gene Symbol box.
//
// Gideon Dresdner
// April 2012

var mutsig_to_tr = function(mutsig) {
    "use strict";
    var click_append = $('<input>');
    var tr = $('
');
    var td = $('');

    click_append.attr('type', 'checkbox');
    click_append.attr('value', mutsig.gene_symbol);

    td.append(mutsig.gene_symbol);
    tr.append(td);

    td = $('');
    td.append(mutsig.num_muts);
    tr.append(td);

    td = $('');
    td.append(mutsig.qval);
    tr.append(td);

    tr.append($('').append(click_append));

    return tr;
};

var studySelected = function() {
    "use strict";
    var cancerStudyId = $('#select_cancer_type').val();

    if (json.cancer_studies[cancerStudyId].has_mutsig_data) {
        $('#MutSig_view').show();
        $('.MutSig_wrapper').show();
    }
    else {
        $('#MutSig_view').hide();
        $('.MutSig_wrapper').hide();
    }

    // redo the query for every nontrivial selection
    $('.MutSig_wrapper').before('

');
    $('.MutSig_wrapper').remove();

    // if you select another cancer study, reset the arrow
    $($('#MutSig_view > .ui-icon')[0]).show();
    $($('#MutSig_view > .ui-icon')[1]).hide();

    // ignores '<', e.g. '<1E-8' === '1E-8'
    $.fn.dataTableExt.oSort['scientific-asc'] = function(a,b) {
        a = $('
').html(a).text();    // parse html entity <
        b = $('
').html(b).text();

        a = parseFloat(a.replace(/^[<>]/g,""));
        b = parseFloat(b.replace(/^[<>]/g,""));

        return ((a  b) ? 1 : 0));
    };

    $.fn.dataTableExt.oSort['scientific-desc'] = function(a,b) {
        return $.fn.dataTableExt.oSort['scientific-asc'](b,a);
    };

    $.get('MutSig.json',
            {'selected_cancer_type': cancerStudyId},
            function(mutsigs) {
                    var i;
                    var len = mutsigs.length;

                    // header
                    $('.MutSig').html('

'
                                    + 'Gene Symbol	'
                                    + 'Num Mutations	'
                                    + 'Q-Value	'
                                    + '<input class="checkall" type="checkbox">	'
                                    + '

');

                // append MutSig data to table
                    for (i = 0; i < len; i += 1) {
                        $('.MutSig').append(mutsig_to_tr(mutsigs[i]));
                    }

                // use dataTable jQuery plugin
                // to style and add nice functionalities
                    $.fn.dataTableExt.oStdClasses.sWrapper = "MutSig_wrapper";

                    $('.MutSig').dataTable( {
                        "sScrollY": "200px",
                        "aaSorting": [],     // disable autosorting
                        "aoColumns": [
                            null,
                            null,
                            { "bSortable" : true, "sType" : "scientific" },
                            { "bSortable" : false, "sWidth": "5%" }
                        ],
                        "bPaginate": false,
                        "bFilter": false,
                        "iDisplayLength": 5,
                        "bRetrieve": true,
                        "bDestroy": true
                    } );

                    $('.MutSig_wrapper').hide();        // hide MutSig table initially
                    $('.MutSig_wrapper').css('padding-bottom', '25px');
                    $('.MutSig').css('width', '0%');    // hack. keep columns in line

                return false;
            });
};

// updates the gene_list based on what happens in the MutSig table.
var updateGeneList = function() {
    "use strict";

    // push all the genes in the gene_list onto a list
    var gene_list = $('#gene_list').val();

    // if gene_list is currently empty put all the checked mutsig genes into it.
    if (gene_list === "") {

        gene_list = [];
        $('.MutSig :not(.checkall):checked').map(function() {     // don't select the Select All checkbox
            gene_list.push($(this).val());
        });
        gene_list = gene_list.join(" ");

    }

    else {
        // look for the selected mutsigs in gene_list
        // if they're not there, append them
        $('.MutSig :not(.checkall):checked').each(function() {
            var checked = $(this).val();

            if ( gene_list.search(new RegExp(checked, "i")) === -1 ) {
                checked = " " + checked;
                gene_list += checked;
            }
        });

        // look for the unselected mutsigs in the gene_list
        // if they're there, delete them
        // you should be forced to know that your gene is recurrently mutated
        $('.MutSig input:not(:checked)').each(function() {
            var unchecked = $(this).val();
            if ( gene_list.search(new RegExp(unchecked, "i")) !== -1) {

                // likely to be Onco Query
                if (gene_list.search(':') !== -1) {
                    var unchecked_regexp = new RegExp(new RegExp(unchecked).source + /\s*:\s*.*(\n|;)/.source, "ig");
                    gene_list = gene_list.replace(unchecked_regexp, "");
                }

                // still want to remove the gene even if it is not part of a (nontrivial) onco query statement
                var unchecked_regexp = new RegExp(new RegExp(unchecked).source + /\s?/.source, "ig");    // regexp of unchecked + \s
                gene_list = gene_list.replace(unchecked_regexp, "");
            }
        });
    }

    $('#gene_list').val(gene_list);

    // remove spaces in gene_list
    gene_list = gene_list.replace(/\s{2,}/, "");             // delete 2 or more spaces in a row
    gene_list = gene_list.replace(/^ /ig, "");              // delete leading space
    gene_list = gene_list.replace(/ $/ig, "");              // delete trailing space
};

// updates the MutSig table based on what happens in the gene_list
// namely, a user's deletions or additions of genes
var updateMutSigTable = function() {

    var gene_list = $('#gene_list').val();

    gene_list = gene_list.replace(/DATATYPES.*(;|\n)\s?/g, "");      // don't want to even look at Onco Queries like these

    // likely to be Onco Query
    if (gene_list.search(/:/) !== -1) {
        var commands = gene_list.split("\n"),
            commands_len = commands.length,
            genes = [],
            i;

        for (i = 0; i < commands_len; i += 1) {
            _commands = commands[i].split(";");

            var _commands_len = _commands.length, j;
            for (j = 0; j < _commands_len; j += 1) {
                genes.push(_commands[j]
                        .replace(/:.*/g, ""));
            }
        }
        gene_list = genes;
    }

    else {
        gene_list = gene_list.split(" ");
    }

    // clear all checks
    $('.MutSig :checkbox').attr('checked', false);

    // if genes in the gene_list are added
    // check them in the mutsig table
    var i,
        gene_list_len = gene_list.length;
    for (i = 0; i < gene_list_len; i += 1) {
        // select mutsig checkboxes that are in the gene list
        $('.MutSig :checkbox:[value=' +  gene_list[i].toUpperCase() + ']').
            attr('checked', true);

    }
    return false;
}

$(document).ready( function () {
    "use strict";

// -- initialize --
// bind handlers to events
    $('#MutSig_view').hide();   // MutSig toggle and table is initialized as hidden
    $('.MutSig_wrapper').hide();

    // make checkall check all
    $('.checkall').live('click', function() {
        $(this).parents().find('.MutSig input').attr('checked', this.checked);
    });

    $('.MutSig input').live('click', updateGeneList);
    updateGeneList();

    $('#gene_list').change(function () {
        if ( $('.MutSig').is(':visible') ) {
            updateMutSigTable();
        }
    });

    $('#toggle_mutsig').click(function() {
        $($('#MutSig_view > .ui-icon')[0]).toggle();
        $($('#MutSig_view > .ui-icon')[1]).toggle();
        updateMutSigTable();
        $('.MutSig_wrapper').slideToggle('slow');
        return false;
    });

    // make sure that the ui-icon is in the right place
    $('#gene_list').keyup(function() {
        if ( $('.MutSig').is(':visible') ) {
            $('.MutSig_wrapper').slideUp('slow');

            $($('#MutSig_view > .ui-icon')[0]).show();      // right arrow
            $($('#MutSig_view > .ui-icon')[1]).hide();      // down arrow
        }
    });
// -- end initialize --

    $('#select_cancer_type').change( function() {
        studySelected();
    });
});
<commit_msg>fixed "ON" gene bug in mutsig.js<commit_after>//
// AJAX and Dynamic jQuery for MutSig table
//
// AJAX :
//           Connects to portal via AJAX and downloads JSON document containg MutSig information:
//              gene symbol, num mutations, q-value
//
// Dynamic jQuery:
//          Handles changes in select_cancer_type:
//              shows/hides "Recurrently Mutated Genes" button
//              shows/hides table containing MutSig information.
//          Takes user-selected mutated genes and adds them to the Gene Symbol box.
//
// Gideon Dresdner
// April 2012

var mutsig_to_tr = function(mutsig) {
    "use strict";
    var click_append = $('<input>');
    var tr = $('
');
    var td = $('');

    click_append.attr('type', 'checkbox');
    click_append.attr('value', mutsig.gene_symbol);

    td.append(mutsig.gene_symbol);
    tr.append(td);

    td = $('');
    td.append(mutsig.num_muts);
    tr.append(td);

    td = $('');
    td.append(mutsig.qval);
    tr.append(td);

    tr.append($('').append(click_append));

    return tr;
};

var studySelected = function() {
    "use strict";
    var cancerStudyId = $('#select_cancer_type').val();

    if (json.cancer_studies[cancerStudyId].has_mutsig_data) {
        $('#MutSig_view').show();
        $('.MutSig_wrapper').show();
    }
    else {
        $('#MutSig_view').hide();
        $('.MutSig_wrapper').hide();
    }

    // redo the query for every nontrivial selection
    $('.MutSig_wrapper').before('

');
    $('.MutSig_wrapper').remove();

    // if you select another cancer study, reset the arrow
    $($('#MutSig_view > .ui-icon')[0]).show();
    $($('#MutSig_view > .ui-icon')[1]).hide();

    // ignores '<', e.g. '<1E-8' === '1E-8'
    $.fn.dataTableExt.oSort['scientific-asc'] = function(a,b) {
        a = $('
').html(a).text();    // parse html entity <
        b = $('
').html(b).text();

        a = parseFloat(a.replace(/^[<>]/g,""));
        b = parseFloat(b.replace(/^[<>]/g,""));

        return ((a  b) ? 1 : 0));
    };

    $.fn.dataTableExt.oSort['scientific-desc'] = function(a,b) {
        return $.fn.dataTableExt.oSort['scientific-asc'](b,a);
    };

    $.get('MutSig.json',
            {'selected_cancer_type': cancerStudyId},
            function(mutsigs) {
                    var i;
                    var len = mutsigs.length;

                    // header
                    $('.MutSig').html('

'
                                    + 'Gene Symbol	'
                                    + 'Num Mutations	'
                                    + 'Q-Value	'
                                    + '<input class="checkall" type="checkbox">	'
                                    + '

');

                // append MutSig data to table
                    for (i = 0; i < len; i += 1) {
                        $('.MutSig').append(mutsig_to_tr(mutsigs[i]));
                    }

                // use dataTable jQuery plugin
                // to style and add nice functionalities
                    $.fn.dataTableExt.oStdClasses.sWrapper = "MutSig_wrapper";

                    $('.MutSig').dataTable( {
                        "sScrollY": "200px",
                        "aaSorting": [],     // disable autosorting
                        "aoColumns": [
                            null,
                            null,
                            { "bSortable" : true, "sType" : "scientific" },
                            { "bSortable" : false, "sWidth": "5%" }
                        ],
                        "bPaginate": false,
                        "bFilter": false,
                        "iDisplayLength": 5,
                        "bRetrieve": true,
                        "bDestroy": true
                    } );

                    $('.MutSig_wrapper').hide();        // hide MutSig table initially
                    $('.MutSig_wrapper').css('padding-bottom', '25px');
                    $('.MutSig').css('width', '0%');    // hack. keep columns in line

                return false;
            });
};

// updates the gene_list based on what happens in the MutSig table.
var updateGeneList = function() {
    "use strict";

    // push all the genes in the gene_list onto a list
    var gene_list = $('#gene_list').val();

    // if gene_list is currently empty put all the checked mutsig genes into it.
    if (gene_list === "") {

        gene_list = [];
        $('.MutSig :not(.checkall):checked').map(function() {     // don't select the Select All checkbox
            gene_list.push($(this).val());
        });
        gene_list = gene_list.join(" ");

    }

    else {
        // look for the selected mutsigs in gene_list
        // if they're not there, append them
        $('.MutSig :not(.checkall):checked').each(function() {
            var checked = $(this).val();

            if ( gene_list.search(new RegExp(checked, "i")) === -1 ) {
                gene_list = gene_list.replace(/ $/ig, "");              // delete trailing space
                checked = " " + checked;
                gene_list += checked;
            }
        });

        // look for the unselected mutsigs in the gene_list
        // if they're there, delete them
        // you should be forced to know that your gene is recurrently mutated
        $('.MutSig input:not(.checkall):not(:checked)').each(function() {
            var unchecked = $(this).val();
            if ( gene_list.search(new RegExp(unchecked, "i")) !== -1) {

                // likely to be Onco Query
                if (gene_list.search(':') !== -1) {
                    var unchecked_regexp = new RegExp(new RegExp(unchecked).source + /\s*:\s*.*(\;|\n)/.source, "ig");
                    gene_list = gene_list.replace(unchecked_regexp, "");
                    console.log(unchecked_regexp);
                }

                // still want to remove the gene even if it is not part of a (nontrivial) onco query statement
                var unchecked_regexp = new RegExp(new RegExp(unchecked).source + /\s?/.source, "ig");    // regexp of unchecked + \s
                gene_list = gene_list.replace(unchecked_regexp, "");
            }
        });
    }

    $('#gene_list').val(gene_list);

    // remove spaces in gene_list
    gene_list = gene_list.replace(/\s{2,}/, "");             // delete 2 or more spaces in a row
    gene_list = gene_list.replace(/^ /ig, "");              // delete leading space
};

// updates the MutSig table based on what happens in the gene_list
// namely, a user's deletions or additions of genes
var updateMutSigTable = function() {

    var gene_list = $('#gene_list').val();

    gene_list = gene_list.replace(/DATATYPES.*(;|\n)\s?/g, "");      // don't want to even look at Onco Queries like these

    // likely to be Onco Query
    if (gene_list.search(/:/) !== -1) {
        var commands = gene_list.split("\n"),
            commands_len = commands.length,
            genes = [],
            i;

        for (i = 0; i < commands_len; i += 1) {
            _commands = commands[i].split(";");

            var _commands_len = _commands.length, j;
            for (j = 0; j < _commands_len; j += 1) {
                genes.push(_commands[j]
                        .replace(/:.*/g, ""));
            }
        }
        gene_list = genes;
    }

    else {
        gene_list = gene_list.split(" ");
    }

    // clear all checks
    $('.MutSig :checkbox').attr('checked', false);

    // if genes in the gene_list are added
    // check them in the mutsig table
    var i,
        gene_list_len = gene_list.length;
    for (i = 0; i < gene_list_len; i += 1) {
        // select mutsig checkboxes that are in the gene list
        $('.MutSig :checkbox:[value=' +  gene_list[i].toUpperCase() + ']').
            attr('checked', true);

    }
    return false;
}

$(document).ready( function () {
    "use strict";

// -- initialize --
// bind handlers to events
    $('#MutSig_view').hide();   // MutSig toggle and table is initialized as hidden
    $('.MutSig_wrapper').hide();

    // make checkall check all
    $('.checkall').live('click', function() {
        $(this).parents().find('.MutSig input').attr('checked', this.checked);
    });

    $('.MutSig input').live('click', updateGeneList);
    updateGeneList();

    $('#gene_list').change(function () {
        if ( $('.MutSig').is(':visible') ) {
            updateMutSigTable();
        }
    });

    $('#toggle_mutsig').click(function() {
        $($('#MutSig_view > .ui-icon')[0]).toggle();
        $($('#MutSig_view > .ui-icon')[1]).toggle();
        updateMutSigTable();
        $('.MutSig_wrapper').slideToggle('slow');
        return false;
    });

    // make sure that the ui-icon is in the right place
    $('#gene_list').keyup(function() {
        if ( $('.MutSig').is(':visible') ) {
            $('.MutSig_wrapper').slideUp('slow');

            $($('#MutSig_view > .ui-icon')[0]).show();      // right arrow
            $($('#MutSig_view > .ui-icon')[1]).hide();      // down arrow
        }
    });
// -- end initialize --

    $('#select_cancer_type').change( function() {
        studySelected();
    });
});
<|endoftext|>


<commit_before>//
//   Copyright 2009-2014 Ilkka Oksanen <iao@iki.fi>
//
//   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
//
//     http://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.
//

'use strict';

/* globals $, FileAPI, emojify, titlenotifier */

import Ember from 'ember';
import { play } from '../helpers/sound';
import UploadMixin from '../mixins/upload';

export default Ember.Component.extend(UploadMixin, {
    classNames: [ 'window', 'flex-grow-column' ],
    attributeBindings: [ 'row:data-row', 'column:data-column' ],

    classNameBindings: [
        'animating:velocity-animating:',
        'expanded:expanded:',
        'visible:visible:hidden',
        'ircServerWindow:irc-server-window:'
    ],

    expanded: false,
    initialAddition: true,
    animating: false,
    scrolling: false,

    $messagePanel: null,
    $images: null,
    logModeEnabled: false,
    wideMemberList: true,

    row: Ember.computed.alias('content.row'),
    column: Ember.computed.alias('content.column'),
    visible: Ember.computed.alias('content.visible'),

    windowChanged: function() {
        this.get('parentView').windowChanged(true);
    }.observes('visible', 'row', 'column'),

    lineAdded: function() {
        Ember.run.debounce(this, function() {
            let initialAddition = this.get('initialAddition');

            if (initialAddition) {
                this._addScrollHandler();
                this.set('initialAddition', false);
            }

            // Update images array
            this.$images = this.$('img[data-src]');

            this._goToBottom(!initialAddition);
        }, 200);

        if (!this.get('visible') || this.get('content.scrollLock')) {
            this.incrementProperty('content.newMessagesCount');
        }

        if (document.hidden) {
            // Browser title notification
            if (this.get('content.titleAlert')) {
                titlenotifier.add();
            }

            // Sound notification
            if (this.get('content.sounds')) {
                play();
            }
        }
    }.observes('content.messages.@each').on('init'),

    ircServerWindow: function() {
        return this.get('content.userId') === 'iSERVER' ? 'irc-server-window' : '';
    }.property('content.userId'),

    isGroup: function() {
        return this.get('content.type') === 'group';
    }.property('content.type'),

    cssType: function() {
        if (this.get('content.type') === 'group') {
            return 'group';
        } else if (this.get('content.userId') === 'iSERVER') {
            return 'server-1on1';
        } else {
            return 'private-1on1';
        }
    }.property('content.type'),

    actions: {
        expand() {
            this.set('expanded', true);
            this.get('parentView').windowChanged(true);
        },

        compress() {
            this.set('expanded', false);
            this.get('parentView').windowChanged(true);
        },

        hide() {
            this.set('content.visible', false);
            this.set('content.timeHidden', Date.now());
        },

        browse() {
            this.set('logModeEnabled', true);
            this.set('expanded', true);
            this.get('parentView').windowChanged(true);
        },

        toggleMemberListWidth() {
            this.toggleProperty('wideMemberList');
        },

        sendMessage() {
            this.sendAction('action', 'sendMessage', this.content, this.get('newMessage'));
            this.set('newMessage', '');
        },

        close() {
            this.sendAction('action', 'close', this.content);
        },

        menu(operation) {
            this.sendAction('menuAction', operation, this.content);
        }
    },

    mouseDown(event) {
        if (!$(event.target).hasClass('fa-arrows')) {
            return; // Not moving the window
        }

        let gridView = this.get('parentView');

        gridView.dragWindowStart(this, event);

        function handleDragMove(event) {
            gridView.dragWindow(event);
            event.preventDefault();
        }

        function handleDragEnd(event) {
            gridView.dragWindowEnd(event);
            event.preventDefault();

            document.removeEventListener('mousemove', handleDragMove, false);
            document.removeEventListener('mouseup', handleDragEnd, false);
        }

        document.addEventListener('mousemove', handleDragMove, false);
        document.addEventListener('mouseup', handleDragEnd, false);
    },

    layoutDone() {
        this._goToBottom(false);
    },

    didInsertElement() {
        let that = this;

        this.$messagePanel = this.$('.window-messages');

        this.$('.window-caption').tooltip();
        this.$messagePanel.tooltip({
            selector: '.timestamp',
            placement: 'right'
        });

        let selectedUserId;

        this.$('.window-members').contextmenu({
            target: '#window-contextMenu',
            before(e) {
                let $target = $(e.target);

                if ($target.hasClass('window-members')) {
                    return false;
                }

                e.preventDefault();
                let $row = $target.closest('.member-row');

                let selectedNick = $row.data('nick');
                let avatar = $row.find('.gravatar').attr('src');
                selectedUserId = $row.data('userid');

                this.getMenu().find('li').eq(0).html(
                    '' +  selectedNick);
                return true;
            },
            onItem(context, e) {
                let action = $(e.target).data('action');
                that.sendAction('action', action, that.content, selectedUserId);
            }
        });

        this.$('.window-members').click(function(e) {
            $(this).contextmenu('show', e);
            e.preventDefault();
            return false;
        });

        let emojisList = $.map(emojify.emojiNames, function(value, i) {
            return { id: i, name: value };
        });

        let emojiListTemplate = '
 ${name}
';

        this.$('.form-control').atwho({
            at: ':',
            displayTpl: emojiListTemplate,
            insertTpl: ':${name}:',
            data: emojisList
        });

        function getNick(item) {
            return item.nick;
        }

        let nickList = this.get('content.operatorNames').map(getNick)
            .concat(this.get('content.voiceNames').map(getNick))
            .concat(this.get('content.userNames').map(getNick));

        this.$('.form-control').atwho({
            at: '@',
            data: nickList
        });

        this.$messagePanel.magnificPopup({
            type: 'image',
            delegate: '.user-img',
            closeOnContentClick: true,
            image: {
                verticalFit: false,
                titleSrc(item) {
                    let href = item.el.attr('href');

                    return 'Link to the original image:' + href + '';
                }
            }
        });

        let fileInput = this.$('.btn-file input')[0];

        FileAPI.event.on(fileInput, 'change', function(evt) {
            let files = FileAPI.getFiles(evt); // Retrieve file list
            this.send('upload', files, 'jpeg');
        }.bind(this));

        this.get('parentView').windowChanged(false);
    },

    willDestroyElement() {
        let gridView = this.get('parentView');

        Ember.run.next(this, function() {
            gridView.windowChanged(true);
        });
    },

    _goToBottom(animate) {
        if (this.get('content.scrollLock')) {
            return;
        }

        let duration = animate ? 3000 : 0;

        this.$('.window-messages > div:last-child').velocity('stop').velocity('scroll', {
            container: this.$messagePanel,
            duration: duration,
            easing: 'spring',
            begin: function() {
                this.$messagePanel.css('overflow-y', 'hidden');
                this.set('scrolling', true);
            }.bind(this),
            complete: function() {
                this.$messagePanel.css('overflow-y', 'auto');
                this.set('scrolling', false);
                this._showImages();
            }.bind(this)
        });
    },

    _addScrollHandler() {
        this.$messagePanel.on('scroll', function() {
            Ember.run.throttle(this, function() {
                if (this.get('animating') || this.get('scrolling')) {
                    return;
                }

                let $panel = this.$messagePanel;
                let scrollPos = $panel.scrollTop();

                if (scrollPos + $panel.innerHeight() >= $panel.prop('scrollHeight')) {
                    this.set('content.scrollLock', false);
                    this.set('content.newMessagesCount', 0);
                    Ember.Logger.info('scrollock off');
                } else if (!this.get('content.deletedLine')) {
                    this.set('content.scrollLock', true);
                    Ember.Logger.info('scrollock on');
                }

                this.set('content.deletedLine', false); // Hack
                this._showImages();
            }, 100);
        }.bind(this));
    },

    _showImages() {
        if (!this.$images) {
            return;
        }

        let placeHolderHeight = 31;
        let panelHeight = this.$messagePanel.height();
        let that = this;

        this.$images = this.$images.filter(function() {
            let $img = $(this);

            // We want to know image's position in .window-messages container div. For position()
            // to work correctly, .window-messages has to have position set to 'relative'. See
            // jQuery offsetParent() documentation for details.
            let pos = $img.position().top;

            if (pos + placeHolderHeight >= 0 && pos <= panelHeight) {
                $img.attr('src', $img.data('src'));
                $img.one('load error', function() {
                    $img.removeClass('loader loader-small-dark');
                    $img.removeAttr('data-src');
                    that._goToBottom(true);
                });

                return false;
            }

            return true;
        });
    }
});
<commit_msg>Don't allow empty messages<commit_after>//
//   Copyright 2009-2014 Ilkka Oksanen <iao@iki.fi>
//
//   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
//
//     http://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.
//

'use strict';

/* globals $, FileAPI, emojify, titlenotifier */

import Ember from 'ember';
import { play } from '../helpers/sound';
import UploadMixin from '../mixins/upload';

export default Ember.Component.extend(UploadMixin, {
    classNames: [ 'window', 'flex-grow-column' ],
    attributeBindings: [ 'row:data-row', 'column:data-column' ],

    classNameBindings: [
        'animating:velocity-animating:',
        'expanded:expanded:',
        'visible:visible:hidden',
        'ircServerWindow:irc-server-window:'
    ],

    expanded: false,
    initialAddition: true,
    animating: false,
    scrolling: false,

    $messagePanel: null,
    $images: null,
    logModeEnabled: false,
    wideMemberList: true,

    row: Ember.computed.alias('content.row'),
    column: Ember.computed.alias('content.column'),
    visible: Ember.computed.alias('content.visible'),

    windowChanged: function() {
        this.get('parentView').windowChanged(true);
    }.observes('visible', 'row', 'column'),

    lineAdded: function() {
        Ember.run.debounce(this, function() {
            let initialAddition = this.get('initialAddition');

            if (initialAddition) {
                this._addScrollHandler();
                this.set('initialAddition', false);
            }

            // Update images array
            this.$images = this.$('img[data-src]');

            this._goToBottom(!initialAddition);
        }, 200);

        if (!this.get('visible') || this.get('content.scrollLock')) {
            this.incrementProperty('content.newMessagesCount');
        }

        if (document.hidden) {
            // Browser title notification
            if (this.get('content.titleAlert')) {
                titlenotifier.add();
            }

            // Sound notification
            if (this.get('content.sounds')) {
                play();
            }
        }
    }.observes('content.messages.@each').on('init'),

    ircServerWindow: function() {
        return this.get('content.userId') === 'iSERVER' ? 'irc-server-window' : '';
    }.property('content.userId'),

    isGroup: function() {
        return this.get('content.type') === 'group';
    }.property('content.type'),

    cssType: function() {
        if (this.get('content.type') === 'group') {
            return 'group';
        } else if (this.get('content.userId') === 'iSERVER') {
            return 'server-1on1';
        } else {
            return 'private-1on1';
        }
    }.property('content.type'),

    actions: {
        expand() {
            this.set('expanded', true);
            this.get('parentView').windowChanged(true);
        },

        compress() {
            this.set('expanded', false);
            this.get('parentView').windowChanged(true);
        },

        hide() {
            this.set('content.visible', false);
            this.set('content.timeHidden', Date.now());
        },

        browse() {
            this.set('logModeEnabled', true);
            this.set('expanded', true);
            this.get('parentView').windowChanged(true);
        },

        toggleMemberListWidth() {
            this.toggleProperty('wideMemberList');
        },

        sendMessage() {
            let message = this.get('newMessage');

            if (message) {
                this.sendAction('action', 'sendMessage', this.content, message);
                this.set('newMessage', '');
            }
        },

        close() {
            this.sendAction('action', 'close', this.content);
        },

        menu(operation) {
            this.sendAction('menuAction', operation, this.content);
        }
    },

    mouseDown(event) {
        if (!$(event.target).hasClass('fa-arrows')) {
            return; // Not moving the window
        }

        let gridView = this.get('parentView');

        gridView.dragWindowStart(this, event);

        function handleDragMove(event) {
            gridView.dragWindow(event);
            event.preventDefault();
        }

        function handleDragEnd(event) {
            gridView.dragWindowEnd(event);
            event.preventDefault();

            document.removeEventListener('mousemove', handleDragMove, false);
            document.removeEventListener('mouseup', handleDragEnd, false);
        }

        document.addEventListener('mousemove', handleDragMove, false);
        document.addEventListener('mouseup', handleDragEnd, false);
    },

    layoutDone() {
        this._goToBottom(false);
    },

    didInsertElement() {
        let that = this;

        this.$messagePanel = this.$('.window-messages');

        this.$('.window-caption').tooltip();
        this.$messagePanel.tooltip({
            selector: '.timestamp',
            placement: 'right'
        });

        let selectedUserId;

        this.$('.window-members').contextmenu({
            target: '#window-contextMenu',
            before(e) {
                let $target = $(e.target);

                if ($target.hasClass('window-members')) {
                    return false;
                }

                e.preventDefault();
                let $row = $target.closest('.member-row');

                let selectedNick = $row.data('nick');
                let avatar = $row.find('.gravatar').attr('src');
                selectedUserId = $row.data('userid');

                this.getMenu().find('li').eq(0).html(
                    '' +  selectedNick);
                return true;
            },
            onItem(context, e) {
                let action = $(e.target).data('action');
                that.sendAction('action', action, that.content, selectedUserId);
            }
        });

        this.$('.window-members').click(function(e) {
            $(this).contextmenu('show', e);
            e.preventDefault();
            return false;
        });

        let emojisList = $.map(emojify.emojiNames, function(value, i) {
            return { id: i, name: value };
        });

        let emojiListTemplate = '
 ${name}
';

        this.$('.form-control').atwho({
            at: ':',
            displayTpl: emojiListTemplate,
            insertTpl: ':${name}:',
            data: emojisList
        });

        function getNick(item) {
            return item.nick;
        }

        let nickList = this.get('content.operatorNames').map(getNick)
            .concat(this.get('content.voiceNames').map(getNick))
            .concat(this.get('content.userNames').map(getNick));

        this.$('.form-control').atwho({
            at: '@',
            data: nickList
        });

        this.$messagePanel.magnificPopup({
            type: 'image',
            delegate: '.user-img',
            closeOnContentClick: true,
            image: {
                verticalFit: false,
                titleSrc(item) {
                    let href = item.el.attr('href');

                    return 'Link to the original image:' + href + '';
                }
            }
        });

        let fileInput = this.$('.btn-file input')[0];

        FileAPI.event.on(fileInput, 'change', function(evt) {
            let files = FileAPI.getFiles(evt); // Retrieve file list
            this.send('upload', files, 'jpeg');
        }.bind(this));

        this.get('parentView').windowChanged(false);
    },

    willDestroyElement() {
        let gridView = this.get('parentView');

        Ember.run.next(this, function() {
            gridView.windowChanged(true);
        });
    },

    _goToBottom(animate) {
        if (this.get('content.scrollLock')) {
            return;
        }

        let duration = animate ? 3000 : 0;

        this.$('.window-messages > div:last-child').velocity('stop').velocity('scroll', {
            container: this.$messagePanel,
            duration: duration,
            easing: 'spring',
            begin: function() {
                this.$messagePanel.css('overflow-y', 'hidden');
                this.set('scrolling', true);
            }.bind(this),
            complete: function() {
                this.$messagePanel.css('overflow-y', 'auto');
                this.set('scrolling', false);
                this._showImages();
            }.bind(this)
        });
    },

    _addScrollHandler() {
        this.$messagePanel.on('scroll', function() {
            Ember.run.throttle(this, function() {
                if (this.get('animating') || this.get('scrolling')) {
                    return;
                }

                let $panel = this.$messagePanel;
                let scrollPos = $panel.scrollTop();

                if (scrollPos + $panel.innerHeight() >= $panel.prop('scrollHeight')) {
                    this.set('content.scrollLock', false);
                    this.set('content.newMessagesCount', 0);
                    Ember.Logger.info('scrollock off');
                } else if (!this.get('content.deletedLine')) {
                    this.set('content.scrollLock', true);
                    Ember.Logger.info('scrollock on');
                }

                this.set('content.deletedLine', false); // Hack
                this._showImages();
            }, 100);
        }.bind(this));
    },

    _showImages() {
        if (!this.$images) {
            return;
        }

        let placeHolderHeight = 31;
        let panelHeight = this.$messagePanel.height();
        let that = this;

        this.$images = this.$images.filter(function() {
            let $img = $(this);

            // We want to know image's position in .window-messages container div. For position()
            // to work correctly, .window-messages has to have position set to 'relative'. See
            // jQuery offsetParent() documentation for details.
            let pos = $img.position().top;

            if (pos + placeHolderHeight >= 0 && pos <= panelHeight) {
                $img.attr('src', $img.data('src'));
                $img.one('load error', function() {
                    $img.removeClass('loader loader-small-dark');
                    $img.removeAttr('data-src');
                    that._goToBottom(true);
                });

                return false;
            }

            return true;
        });
    }
});
<|endoftext|>


<commit_before>var postcss = require('postcss');

module.exports = postcss.plugin('postcss-host-context', function () {
    return function (css) {
        css.walkRules(function (rule) {
            var replaced = [];
            rule.selectors.forEach(function (selector) {
                if (selector.indexOf(':host-context') === -1) {
                    replaced.push(':host-context ' + selector);
                } else {
                    replaced.push(selector);
                }
            });
            if ( replaced.length ) {
                rule.selectors = replaced;
            }
        });
    };
});
<commit_msg>Delete postcss-host-context.js<commit_after><|endoftext|>