<commit_before>/* Copyright (c) 2017 Canonical Ltd */
'use strict';

const React = require('react');
const ReactDOM = require('react-dom');
const ReactTestUtils = require('react-dom/test-utils');

const SvgIcon = require('../svg-icon/svg-icon');
const Terminal = require('./terminal');

const jsTestUtils = require('../../utils/component-test-utils');

describe('Terminal', () => {

  let websocket;

  function setupWebsocket() {
    websocket = function() {};
    websocket.prototype.send = sinon.stub();
    websocket.prototype.close = sinon.stub();
  }

  function renderComponent(options = {}) {
    setupWebsocket();

    return jsTestUtils.shallowRender(
      <Terminal
        addNotification={sinon.stub()}
        address="1.2.3.4:123"
        changeState={sinon.stub()}
        commands={options.commands}
        creds={{
          user: 'user',
          password: 'password',
          macaroons: {}
        }}
        WebSocket={websocket} />, true);
  };

  it('should render', () => {
    const expected = (
      <div className="juju-shell">
        <div className="juju-shell__header">
          <span className="juju-shell__header-label">Juju Shell</span>
          <div className="juju-shell__header-actions">
            <span onClick={sinon.stub()} tabIndex="0" role="button">
              <SvgIcon name="minimize-bar_16" size="16" />
            </span>
            <span onClick={sinon.stub()} tabIndex="0" role="button">
              <SvgIcon name="maximize-bar_16" size="16" />
            </span>
            <span onClick={sinon.stub()} tabIndex="0" role="button">
              <SvgIcon name="close_16" size="16" />
            </span>
          </div>
        </div>
        <div
          ref="terminal"
          className={'juju-shell__terminal juju-shell__terminal--min'}
          style={{}}>
        </div>
      </div>);
    expect(renderComponent().getRenderOutput()).toEqualJSX(expected);
  });

  it('instantiates the terminal and connects to the websocket on mount', () => {
    setupWebsocket();
    const component = ReactTestUtils.renderIntoDocument(
      <Terminal
        addNotification={sinon.stub()}
        address="1.2.3.4:123"
        changeState={sinon.stub()}
        creds={{
          user: 'user',
          password: 'password',
          macaroons: {}
        }}
        WebSocket={websocket} />
    );
    assert.equal(component.ws instanceof websocket, true);
    assert.equal(typeof component.term, 'object');
    ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(component).parentNode);
    assert.equal(websocket.prototype.close.callCount, 1);
  });

  it('sends supplied commands when it is set up', () => {
    setupWebsocket();
    const component = ReactTestUtils.renderIntoDocument(
      <Terminal
        addNotification={sinon.stub()}
        address="1.2.3.4:123"
        changeState={sinon.stub()}
        commands={['juju status']}
        creds={{
          user: 'user',
          password: 'password',
          macaroons: {}
        }}
        WebSocket={websocket} />
    );
    // Check that fit is called after receiving the first PS1.
    component.term.fit = sinon.stub(); // eslint-disable-line
    // Send the setup from the term.
    component.ws.onmessage({data: '["setup", {}]'});
    // Send the initial PS1
    component.ws.onmessage({data: '["stdout", "\\u001b[01;32"]'});
    assert.equal(component.term.fit.callCount, 1); // eslint-disable-line
    ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(component).parentNode);
    assert.equal(websocket.prototype.send.callCount, 1);
    assert.deepEqual(websocket.prototype.send.args[0], ['["stdin","juju status\\n"]']);
  });

  it('sends multiple commands when it is set up', () => {
    setupWebsocket();
    const component = ReactTestUtils.renderIntoDocument(
      <Terminal
        addNotification={sinon.stub()}
        address="1.2.3.4:123"
        changeState={sinon.stub()}
        commands={['juju status', 'juju switch']}
        creds={{
          user: 'user',
          password: 'password',
          macaroons: {}
        }}
        WebSocket={websocket} />
    );
    // Send the setup from the term.
    component.ws.onmessage({data: '["setup", {}]'});
    // Send the initial PS1
    component.ws.onmessage({data: '["stdout", "\\u001b[01;32"]'});
    ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(component).parentNode);
    assert.equal(websocket.prototype.send.callCount, 2);
    assert.deepEqual(websocket.prototype.send.args[0], ['["stdin","juju status\\n"]']);
    assert.deepEqual(websocket.prototype.send.args[1], ['["stdin","juju switch\\n"]']);
  });

  it('can be closed by clicking the X', () => {
    const renderer = renderComponent();
    const output = renderer.getRenderOutput();
    const instance = renderer.getMountedInstance();
    // Call the onClick for the X
    output.props.children[0].props.children[1].props.children[2].props.onClick();
    assert.deepEqual(instance.props.changeState.args[0], [{
      terminal: null
    }]);
  });

  it('handles unexpected WebSocket closures', () => {
    setupWebsocket();
    const component = ReactTestUtils.renderIntoDocument(
      <Terminal
        addNotification={sinon.stub()}
        address="1.2.3.4:123"
        changeState={sinon.stub()}
        commands={['juju status', 'juju switch']}
        creds={{
          user: 'user',
          password: 'password',
          macaroons: {}
        }}
        WebSocket={websocket} />
    );
    component.ws.onclose({
      // Should only throw the notification on code over 1000 which is an
      // expected closure.
      code: 1001
    });
    assert.deepEqual(component.props.addNotification.args[0], [{
      title: 'Terminal connection unexpectedly closed.',
      message: 'Terminal connection unexpectedly closed.',
      level: 'error'
    }]);<commit_msg>Add tests to check for the proper teardown and ws close handling.<commit_after>/* Copyright (c) 2017 Canonical Ltd */
'use strict';

const React = require('react');
const ReactDOM = require('react-dom');
const ReactTestUtils = require('react-dom/test-utils');

const SvgIcon = require('../svg-icon/svg-icon');
const Terminal = require('./terminal');

const jsTestUtils = require('../../utils/component-test-utils');

fdescribe('Terminal', () => {

  let websocket;

  function setupWebsocket() {
    websocket = function() {};
    websocket.prototype.send = sinon.stub();
    websocket.prototype.close = sinon.stub();
  }

  function renderComponent(options = {}) {
    setupWebsocket();

    return jsTestUtils.shallowRender(
      <Terminal
        addNotification={sinon.stub()}
        address="1.2.3.4:123"
        changeState={sinon.stub()}
        commands={options.commands}
        creds={{
          user: 'user',
          password: 'password',
          macaroons: {}
        }}
        WebSocket={websocket} />, true);
  };

  it('should render', () => {
    const expected = (
      <div className="juju-shell">
        <div className="juju-shell__header">
          <span className="juju-shell__header-label">Juju Shell</span>
          <div className="juju-shell__header-actions">
            <span onClick={sinon.stub()} tabIndex="0" role="button">
              <SvgIcon name="minimize-bar_16" size="16" />
            </span>
            <span onClick={sinon.stub()} tabIndex="0" role="button">
              <SvgIcon name="maximize-bar_16" size="16" />
            </span>
            <span onClick={sinon.stub()} tabIndex="0" role="button">
              <SvgIcon name="close_16" size="16" />
            </span>
          </div>
        </div>
        <div
          ref="terminal"
          className={'juju-shell__terminal juju-shell__terminal--min'}
          style={{}}>
        </div>
      </div>);
    expect(renderComponent().getRenderOutput()).toEqualJSX(expected);
  });

  it('instantiates the terminal and connects to the websocket on mount', () => {
    setupWebsocket();
    const component = ReactTestUtils.renderIntoDocument(
      <Terminal
        addNotification={sinon.stub()}
        address="1.2.3.4:123"
        changeState={sinon.stub()}
        creds={{
          user: 'user',
          password: 'password',
          macaroons: {}
        }}
        WebSocket={websocket} />
    );
    assert.equal(component.ws instanceof websocket, true);
    assert.equal(typeof component.term, 'object');
    ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(component).parentNode);
    assert.equal(websocket.prototype.close.callCount, 1);
    assert.deepEqual(websocket.prototype.close.args[0], [1000]);
  });

  it('sends supplied commands when it is set up', () => {
    setupWebsocket();
    const component = ReactTestUtils.renderIntoDocument(
      <Terminal
        addNotification={sinon.stub()}
        address="1.2.3.4:123"
        changeState={sinon.stub()}
        commands={['juju status']}
        creds={{
          user: 'user',
          password: 'password',
          macaroons: {}
        }}
        WebSocket={websocket} />
    );
    // Check that fit is called after receiving the first PS1.
    component.term.fit = sinon.stub(); // eslint-disable-line
    // Send the setup from the term.
    component.ws.onmessage({data: '["setup", {}]'});
    // Send the initial PS1
    component.ws.onmessage({data: '["stdout", "\\u001b[01;32"]'});
    assert.equal(component.term.fit.callCount, 1); // eslint-disable-line
    ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(component).parentNode);
    assert.equal(websocket.prototype.send.callCount, 1);
    assert.deepEqual(websocket.prototype.send.args[0], ['["stdin","juju status\\n"]']);
  });

  it('sends multiple commands when it is set up', () => {
    setupWebsocket();
    const component = ReactTestUtils.renderIntoDocument(
      <Terminal
        addNotification={sinon.stub()}
        address="1.2.3.4:123"
        changeState={sinon.stub()}
        commands={['juju status', 'juju switch']}
        creds={{
          user: 'user',
          password: 'password',
          macaroons: {}
        }}
        WebSocket={websocket} />
    );
    // Send the setup from the term.
    component.ws.onmessage({data: '["setup", {}]'});
    // Send the initial PS1
    component.ws.onmessage({data: '["stdout", "\\u001b[01;32"]'});
    ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(component).parentNode);
    assert.equal(websocket.prototype.send.callCount, 2);
    assert.deepEqual(websocket.prototype.send.args[0], ['["stdin","juju status\\n"]']);
    assert.deepEqual(websocket.prototype.send.args[1], ['["stdin","juju switch\\n"]']);
  });

  it('can be closed by clicking the X', () => {
    const renderer = renderComponent();
    const output = renderer.getRenderOutput();
    const instance = renderer.getMountedInstance();
    // Set the ws onclose to something we control to be sure that it is reset.
    instance.ws = {
      onclose: sinon.stub()
    };
    // Call the onClick for the X
    output.props.children[0].props.children[1].props.children[2].props.onClick();
    assert.equal(instance.ws.onclose.callCount, 0);
    assert.deepEqual(instance.props.changeState.args[0], [{
      terminal: null
    }]);
  });

  it('handles unexpected WebSocket closures', () => {
    setupWebsocket();
    const component = ReactTestUtils.renderIntoDocument(
      <Terminal
        addNotification={sinon.stub()}
        address="1.2.3.4:123"
        changeState={sinon.stub()}
        commands={['juju status', 'juju switch']}
        creds={{
          user: 'user',
          password: 'password',
          macaroons: {}
        }}
        WebSocket={websocket} />
    );
    component.ws.onclose({
      // Should only throw the notification on code over 1000 which is an
      // expected closure.
      code: 1001
    });
    assert.deepEqual(component.props.addNotification.args[0], [{
      title: 'Terminal connection unexpectedly closed.',
      message: 'Terminal connection unexpectedly closed.',
      level: 'error'
    }]);
{'fraction_non_alphanumeric': 0.1592457218807111, 'fraction_numerical': 0.01561721216148862, 'mean_word_length': 2.6929447852760737, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 65, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 10, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3401766', 'n_tokens_mistral': 3703, 'n_tokens_neox': 3626, 'n_words': 691}