<commit_before>const pathModule = require('path');
const expect = require('../unexpected-with-plugins');
const AssetGraph = require('../../lib/AssetGraph');
const sinon = require('sinon');

describe('assets/JavaScript', function () {
  it('should handle a test case that has a parse error in an inline JavaScript asset', async function () {
    let firstWarning;
    await new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    })
      .on('warn', (err) => (firstWarning = firstWarning || err))
      .loadAssets('parseErrorInInlineJavaScript.html');

    expect(firstWarning, 'to be an', Error);
    expect(
      firstWarning.message,
      'to match',
      /parseErrorInInlineJavaScript\.html/
    );
    expect(firstWarning.message, 'to contain', '(2:8)');
  });

  it('should handle a test case that has a parse error in an external JavaScript asset', async function () {
    let firstWarning;
    await new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    })
      .on('warn', (err) => (firstWarning = firstWarning || err))
      .loadAssets('parseErrorInExternalJavaScript.html')
      .populate();

    expect(firstWarning, 'to be an', Error);
    expect(firstWarning.message, 'to match', /parseError\.js/);
    expect(firstWarning.message, 'to contain', '(6:13)');
  });

  it('should handle a test case that has a parse error in an asset not in an assetgraph', function () {
    expect(
      () =>
        new AssetGraph().addAsset({ type: 'JavaScript', text: 'var test)' })
          .parseTree,
      'to throw'
    );
  });

  // https://github.com/Munter/netlify-plugin-checklinks/issues/286#issuecomment-725530864
  it('should report a syntax error in a JavaScript asset that uses module features', async function () {
    let firstWarning;
    await new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    })
      .on('warn', (err) => (firstWarning = firstWarning || err))
      .loadAssets('parseErrorAndModuleFeatures.js')
      .populate();

    expect(firstWarning, 'to be an', Error);
    expect(firstWarning.message, 'to match', /Unexpected token/);
    expect(firstWarning.message, 'to contain', '(2:18)');
  });

  it('should handle setting a new parseTree', function () {
    const one = new AssetGraph().addAsset({
      type: 'JavaScript',
      text: 'var test = "true";',
    });
    const two = new AssetGraph().addAsset({
      type: 'JavaScript',
      text: 'var test = "false";',
    });

    expect(one, 'not to have the same AST as', two);

    two.parseTree = one.parseTree;

    expect(one, 'to have the same AST as', two);
  });

  it('should handle minification', function () {
    const assetGraph = new AssetGraph();
    const one = assetGraph.addAsset({
      type: 'JavaScript',
      text: 'function test (argumentName) { return argumentName; }',
    });
    const two = assetGraph.addAsset({
      type: 'JavaScript',
      text: 'function test (argumentName) { return argumentName; }',
    });

    expect(one, 'to have the same AST as', two);

    two.minify();

    expect(one.text, 'not to equal', two.text);
    expect(
      two.text,
      'to equal',
      'function test(argumentName){return argumentName};'
    );
  });

  it('should handle invalid arguments for Amd define call', async function () {
    sinon.stub(console, 'info');

    const invalidArgument = new AssetGraph().addAsset({
      type: 'JavaScript',
      isRequired: true,
      text: 'define([1], function () {})',
    });

    invalidArgument.findOutgoingRelationsInParseTree();

    await new AssetGraph({ root: '.' })
      .loadAssets([invalidArgument])
      .populate();
  });

  it('should preserve the copyright notice in a JavaScript asset', async function () {
    const assetGraph = new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    });
    await assetGraph.loadAssets('copyrightNotice.js');

    const javaScript = assetGraph.findAssets({ type: 'JavaScript' })[0];
    javaScript.parseTree.body.push({
      type: 'VariableDeclaration',
      kind: 'var',
      declarations: [
        {
          type: 'VariableDeclarator',
          id: {
            type: 'Identifier',
            name: 'foo',
          },
          init: { type: 'Literal', value: 'quux' },
        },
      ],
    });
    javaScript.markDirty();

    expect(javaScript.text, 'to match', /Copyright blablabla/);
  });

  it('should preserve comments despite assetGraph.sourceMaps being false', async function () {
    const assetGraph = new AssetGraph();
    assetGraph.sourceMaps = false;

    const javaScript = assetGraph.addAsset({
      type: 'JavaScript',
      text: "// foo\nalert('bar');",
    });

    javaScript.parseTree; // eslint-disable-line no-unused-expressions
    javaScript.markDirty();

    expect(javaScript.text, 'to contain', '// foo');
  });

  it('should handle a test case with JavaScript assets that have regular comments as the first non-whitespace tokens', async function () {
    const assetGraph = new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    });
    await assetGraph.loadAssets('initialComment*.js');

    for (const javaScript of assetGraph.findAssets({ type: 'JavaScript' })) {
      javaScript.parseTree.body.push({
        type: 'VariableDeclaration',
        declarations: [
          {
            type: 'VariableDeclarator',
            id: {
              type: 'Identifier',
              name: 'foo',
            },
            kind: 'var',
            init: { type: 'Literal', value: 'quux' },
          },
        ],
      });
      javaScript.minify();
    }

    const javaScripts = assetGraph.findAssets({ type: 'JavaScript' });
    expect(javaScripts[0].text, 'not to match', /Initial comment/);
    expect(javaScripts[1].text, 'not to match', /Initial comment/);
  });

  it('should handle a test case with a JavaScript asset that has comments right before EOF, then marking it as dirty', async function () {
    const assetGraph = new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    });
    await assetGraph.loadAssets('commentsBeforeEof.js').populate();

    const javaScript = assetGraph.findAssets({ type: 'JavaScript' })[0];
    // The reserialized JavaScript asset should still contain @preserve comment before eof, but not the quux one

    javaScript.markDirty();

    expect(javaScript.text, 'to match', /@preserve/);
    expect(javaScript.text, 'to match', /quux/);

    javaScript.minify();

    // The reserialized JavaScript asset should contain both the @preserve and the quux comment when pretty printed
    expect(javaScript.text, 'to match', /@preserve/);
    expect(javaScript.text, 'not to match', /quux/);
  });

  it('should handle a test case with conditional compilation (@cc_on)', async function () {
    const assetGraph = new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    });
    await assetGraph.loadAssets('conditionalCompilation.js');

    for (const javaScript of assetGraph.findAssets({ type: 'JavaScript' })) {
      javaScript.markDirty();
    }

    expect(
      assetGraph.findAssets({ type: 'JavaScript' })[0].text,
      'to match',
      /@cc_on/
    );

    await assetGraph.compressJavaScript();

    // The @cc_on comment should still be in the serialization of the asset
    expect(
      assetGraph.findAssets({ type: 'JavaScript' })[0].text,
      'to match',
      /@cc_on/
    );
  });

  it('should handle a test case with global strict mode', async function () {
    const assetGraph = new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    });
    await assetGraph.loadAssets('globalstrict.html').populate();

    expect(
      assetGraph.findAssets({ fileName: 'globalstrict.js' })[0].strict,
      'to equal',
      true
    );
    expect(
      assetGraph.findAssets({ fileName: 'nonstrict.js' })[0].strict,
      'to equal',
      false
    );
  });

  it('should tolerate ES6 syntax', function () {
    const es6Text = "import gql from 'graphql-tag';\nlet a = 123;";
    const javaScript = new AssetGraph().addAsset({
      type: 'JavaScript',
      text: es6Text,
    });
    expect(javaScript.parseTree, 'to satisfy', {
      type: 'Program',
      body: [
        { type: 'ImportDeclaration' },
        { type: 'VariableDeclaration', kind: 'let' },
      ],
      sourceType: 'module',
    });
    javaScript.markDirty();
    expect(javaScript.text, 'to equal', es6Text);
  });

  it('should tolerate Object spread syntax', function () {
    const text = 'const foo = { ...bar };';
    const javaScript = new AssetGraph().addAsset({ type: 'JavaScript', text });
    expect(javaScript.parseTree, 'to satisfy', {
      type: 'Program',
      body: [
        {
          type: 'VariableDeclaration',
          kind: 'const',
          declarations: [
            {
              type: 'VariableDeclarator',
              init: {
                type: 'ObjectExpression',
                properties: [
                  {
                    type: 'SpreadElement',
                    argument: { type: 'Identifier', name: 'bar' },
                  },
                ],
              },
            },
          ],
        },
      ],
      sourceType: 'module',
    });
    javaScript.markDirty();
    expect(javaScript.text, 'to equal', text);
  });

  it('should support async iteration syntax', function () {
    const text = `async function foo() {
    for await (const bar of quux) {
    }
}
;`;
    const javaScript = new AssetGraph().addAsset({ type: 'JavaScript', text });
    javaScript.markDirty();
    expect(javaScript.text, 'to equal', text);
  });

  it('should tolerate JSX syntax', function () {
    const jsxText = 'function render() { return (<MyComponent />); }';
    const javaScript = new AssetGraph().addAsset({
      type: 'JavaScript',
      text: jsxText,
    });
    expect(javaScript.parseTree, 'to satisfy', {
      type: 'Program',
      body: [
        {
          type: 'FunctionDeclaration',
          body: {
            type: 'BlockStatement',
            body: [
              {
                type: 'ReturnStatement',
                argument: {
                  type: 'JSXElement',
                  openingElement: {
                    type: 'JSXOpeningElement',
                    name: {
                      type: 'JSXIdentifier',
                      name: 'MyComponent',
                    },
                  },
                  children: [],
                  closingElement: null,
                },
              },
            ],
          },
        },
      ],
      sourceType: 'module',
    });
    javaScript.markDirty();
    // This doesn't work because escodegen doesn't support JSX:
    // expect(javaScript.text, 'to equal', jsxText);
  });

  it('should fall back to "script" mode when a parse error is encountered', function () {
    const text = 'await = 123;';
    const javaScript = new AssetGraph().addAsset({ type: 'JavaScript', text });
    expect(javaScript.parseTree, 'to satisfy', {
      type: 'Program',
      body: [
        {
          type: 'ExpressionStatement',
          expression: {
            type: 'AssignmentExpression',
            operator: '=',
            left: { type: 'Identifier', name: 'await' },
            right: { type: 'Literal', value: 123 },
          },
        },
      ],
      sourceType: 'script',
    });
    javaScript.markDirty();
    expect(javaScript.text, 'to equal', text);
  });

  it('should emit an info event (and no warn events) when falling back to script mode', async function () {
    const errorSpy = sinon.spy().named('error');
    const warnSpy = sinon.spy().named('warn');
    const infoSpy = sinon.spy().named('info');
    await new AssetGraph()
      .on('error', errorSpy)
      .on('warn', warnSpy)
      .on('info', infoSpy)
      .loadAssets({
        type: 'JavaScript',
        url: 'http://example.com/script.js',
        text: 'await = 123;',
      })
      .populate();

    expect([errorSpy, warnSpy, infoSpy], 'to have calls satisfying', () => {
      infoSpy(
        "Could not parse http://example.com/script.js as a module, fall back to script mode\nCannot use keyword 'await' outside an async function (1:0)"
      );
    });
  });

  it('should emit one warn event (and no other events) when neither module nor script mode works', async function () {
    const errorSpy = sinon.spy().named('error');
    const warnSpy = sinon.spy().named('warn');
    const infoSpy = sinon.spy().named('info');
    await new AssetGraph()
      .on('error', errorSpy)
      .on('warn', warnSpy)
      .on('info', infoSpy)
      .loadAssets({
        type: 'JavaScript',
        url: 'http://example.com/script.js',
        text: 'qwvce)',
      })
      .populate();

    expect([errorSpy, warnSpy, infoSpy], 'to have calls satisfying', () => {
      warnSpy(
        'Parse error in http://example.com/script.js\nUnexpected token (1:5)'
      );
    });
  });

  describe('serialization', function () {
    describe('when there are more singlequotes than doublequotes in a string literal', function () {
      it('should use doublequotes when serializing', function () {
        const javaScript = new AssetGraph().addAsset({
          type: 'JavaScript',
          text: `var foo = 'b\\'\\'"ar';`,
        });
        // Force a reserialization from the AST:
        javaScript.parseTree; // eslint-disable-line no-unused-expressions
        javaScript.markDirty();
        expect(javaScript.text, 'to equal', `var foo = "b''\\"ar";`);
      });
    });

    describe('when there are more doublequotes than singlequotes in a string literal', function () {
      it('should use singlequotes when serializing', function () {
        const javaScript = new AssetGraph().addAsset({
          type: 'JavaScript',
          text: `var foo = "b\\"\\"'ar";`,
        });
        // Force a reserialization from the AST:
        javaScript.parseTree; // eslint-disable-line no-unused-expressions
        javaScript.markDirty();
        expect(javaScript.text, 'to equal', `var foo = 'b""\\'ar';`);
      });
    });

    describe('when there is the same number of doublequotes and singlequotes in a string literal', function () {
      it('should use singlequotes when serializing', function () {
        const javaScript = new AssetGraph().addAsset({
          type: 'JavaScript',
          text: `var foo = "b\\"'ar";`,
        });
        // Force a reserialization from the AST:
        javaScript.parseTree; // eslint-disable-line no-unused-expressions
        javaScript.markDirty();
        expect(javaScript.text, 'to equal', `var foo = 'b"\\'ar';`);
      });
    });
  });
});
<commit_msg>Add test for optional chaining syntax<commit_after>const pathModule = require('path');
const expect = require('../unexpected-with-plugins');
const AssetGraph = require('../../lib/AssetGraph');
const sinon = require('sinon');

describe('assets/JavaScript', function () {
  it('should handle a test case that has a parse error in an inline JavaScript asset', async function () {
    let firstWarning;
    await new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    })
      .on('warn', (err) => (firstWarning = firstWarning || err))
      .loadAssets('parseErrorInInlineJavaScript.html');

    expect(firstWarning, 'to be an', Error);
    expect(
      firstWarning.message,
      'to match',
      /parseErrorInInlineJavaScript\.html/
    );
    expect(firstWarning.message, 'to contain', '(2:8)');
  });

  it('should handle a test case that has a parse error in an external JavaScript asset', async function () {
    let firstWarning;
    await new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    })
      .on('warn', (err) => (firstWarning = firstWarning || err))
      .loadAssets('parseErrorInExternalJavaScript.html')
      .populate();

    expect(firstWarning, 'to be an', Error);
    expect(firstWarning.message, 'to match', /parseError\.js/);
    expect(firstWarning.message, 'to contain', '(6:13)');
  });

  it('should handle a test case that has a parse error in an asset not in an assetgraph', function () {
    expect(
      () =>
        new AssetGraph().addAsset({ type: 'JavaScript', text: 'var test)' })
          .parseTree,
      'to throw'
    );
  });

  // https://github.com/Munter/netlify-plugin-checklinks/issues/286#issuecomment-725530864
  it('should report a syntax error in a JavaScript asset that uses module features', async function () {
    let firstWarning;
    await new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    })
      .on('warn', (err) => (firstWarning = firstWarning || err))
      .loadAssets('parseErrorAndModuleFeatures.js')
      .populate();

    expect(firstWarning, 'to be an', Error);
    expect(firstWarning.message, 'to match', /Unexpected token/);
    expect(firstWarning.message, 'to contain', '(2:18)');
  });

  it('should handle setting a new parseTree', function () {
    const one = new AssetGraph().addAsset({
      type: 'JavaScript',
      text: 'var test = "true";',
    });
    const two = new AssetGraph().addAsset({
      type: 'JavaScript',
      text: 'var test = "false";',
    });

    expect(one, 'not to have the same AST as', two);

    two.parseTree = one.parseTree;

    expect(one, 'to have the same AST as', two);
  });

  it('should handle minification', function () {
    const assetGraph = new AssetGraph();
    const one = assetGraph.addAsset({
      type: 'JavaScript',
      text: 'function test (argumentName) { return argumentName; }',
    });
    const two = assetGraph.addAsset({
      type: 'JavaScript',
      text: 'function test (argumentName) { return argumentName; }',
    });

    expect(one, 'to have the same AST as', two);

    two.minify();

    expect(one.text, 'not to equal', two.text);
    expect(
      two.text,
      'to equal',
      'function test(argumentName){return argumentName};'
    );
  });

  it('should handle invalid arguments for Amd define call', async function () {
    sinon.stub(console, 'info');

    const invalidArgument = new AssetGraph().addAsset({
      type: 'JavaScript',
      isRequired: true,
      text: 'define([1], function () {})',
    });

    invalidArgument.findOutgoingRelationsInParseTree();

    await new AssetGraph({ root: '.' })
      .loadAssets([invalidArgument])
      .populate();
  });

  it('should preserve the copyright notice in a JavaScript asset', async function () {
    const assetGraph = new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    });
    await assetGraph.loadAssets('copyrightNotice.js');

    const javaScript = assetGraph.findAssets({ type: 'JavaScript' })[0];
    javaScript.parseTree.body.push({
      type: 'VariableDeclaration',
      kind: 'var',
      declarations: [
        {
          type: 'VariableDeclarator',
          id: {
            type: 'Identifier',
            name: 'foo',
          },
          init: { type: 'Literal', value: 'quux' },
        },
      ],
    });
    javaScript.markDirty();

    expect(javaScript.text, 'to match', /Copyright blablabla/);
  });

  it('should preserve comments despite assetGraph.sourceMaps being false', async function () {
    const assetGraph = new AssetGraph();
    assetGraph.sourceMaps = false;

    const javaScript = assetGraph.addAsset({
      type: 'JavaScript',
      text: "// foo\nalert('bar');",
    });

    javaScript.parseTree; // eslint-disable-line no-unused-expressions
    javaScript.markDirty();

    expect(javaScript.text, 'to contain', '// foo');
  });

  it('should handle a test case with JavaScript assets that have regular comments as the first non-whitespace tokens', async function () {
    const assetGraph = new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    });
    await assetGraph.loadAssets('initialComment*.js');

    for (const javaScript of assetGraph.findAssets({ type: 'JavaScript' })) {
      javaScript.parseTree.body.push({
        type: 'VariableDeclaration',
        declarations: [
          {
            type: 'VariableDeclarator',
            id: {
              type: 'Identifier',
              name: 'foo',
            },
            kind: 'var',
            init: { type: 'Literal', value: 'quux' },
          },
        ],
      });
      javaScript.minify();
    }

    const javaScripts = assetGraph.findAssets({ type: 'JavaScript' });
    expect(javaScripts[0].text, 'not to match', /Initial comment/);
    expect(javaScripts[1].text, 'not to match', /Initial comment/);
  });

  it('should handle a test case with a JavaScript asset that has comments right before EOF, then marking it as dirty', async function () {
    const assetGraph = new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    });
    await assetGraph.loadAssets('commentsBeforeEof.js').populate();

    const javaScript = assetGraph.findAssets({ type: 'JavaScript' })[0];
    // The reserialized JavaScript asset should still contain @preserve comment before eof, but not the quux one

    javaScript.markDirty();

    expect(javaScript.text, 'to match', /@preserve/);
    expect(javaScript.text, 'to match', /quux/);

    javaScript.minify();

    // The reserialized JavaScript asset should contain both the @preserve and the quux comment when pretty printed
    expect(javaScript.text, 'to match', /@preserve/);
    expect(javaScript.text, 'not to match', /quux/);
  });

  it('should handle a test case with conditional compilation (@cc_on)', async function () {
    const assetGraph = new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    });
    await assetGraph.loadAssets('conditionalCompilation.js');

    for (const javaScript of assetGraph.findAssets({ type: 'JavaScript' })) {
      javaScript.markDirty();
    }

    expect(
      assetGraph.findAssets({ type: 'JavaScript' })[0].text,
      'to match',
      /@cc_on/
    );

    await assetGraph.compressJavaScript();

    // The @cc_on comment should still be in the serialization of the asset
    expect(
      assetGraph.findAssets({ type: 'JavaScript' })[0].text,
      'to match',
      /@cc_on/
    );
  });

  it('should handle a test case with global strict mode', async function () {
    const assetGraph = new AssetGraph({
      root: pathModule.resolve(__dirname, '../../testdata/assets/JavaScript/'),
    });
    await assetGraph.loadAssets('globalstrict.html').populate();

    expect(
      assetGraph.findAssets({ fileName: 'globalstrict.js' })[0].strict,
      'to equal',
      true
    );
    expect(
      assetGraph.findAssets({ fileName: 'nonstrict.js' })[0].strict,
      'to equal',
      false
    );
  });

  it('should tolerate ES6 import syntax', function () {
    const es6Text = "import gql from 'graphql-tag';\nlet a = 123;";
    const javaScript = new AssetGraph().addAsset({
      type: 'JavaScript',
      text: es6Text,
    });
    expect(javaScript.parseTree, 'to satisfy', {
      type: 'Program',
      body: [
        { type: 'ImportDeclaration' },
        { type: 'VariableDeclaration', kind: 'let' },
      ],
      sourceType: 'module',
    });
    javaScript.markDirty();
    expect(javaScript.text, 'to equal', es6Text);
  });

  it('should tolerate Object spread syntax', function () {
    const text = 'const foo = { ...bar };';
    const javaScript = new AssetGraph().addAsset({ type: 'JavaScript', text });
    expect(javaScript.parseTree, 'to satisfy', {
      type: 'Program',
      body: [
        {
          type: 'VariableDeclaration',
          kind: 'const',
          declarations: [
            {
              type: 'VariableDeclarator',
              init: {
                type: 'ObjectExpression',
                properties: [
                  {
                    type: 'SpreadElement',
                    argument: { type: 'Identifier', name: 'bar' },
                  },
                ],
              },
            },
          ],
        },
      ],
      sourceType: 'module',
    });
    javaScript.markDirty();
    expect(javaScript.text, 'to equal', text);
  });

  it('should support async iteration syntax', function () {
    const text = `async function foo() {
    for await (const bar of quux) {
    }
}
;`;
    const javaScript = new AssetGraph().addAsset({ type: 'JavaScript', text });
    javaScript.markDirty();
    expect(javaScript.text, 'to equal', text);
  });

  it('should support optional chaining syntax', function () {
    const es6Text = 'foo = bar?.baz;';
    const javaScript = new AssetGraph().addAsset({
      type: 'JavaScript',
      text: es6Text,
    });
    expect(javaScript.parseTree, 'to satisfy', {
      type: 'Program',
      body: [
        {
          type: 'ExpressionStatement',
          expression: {
            type: 'AssignmentExpression',
            right: { type: 'ChainExpression' },
          },
        },
      ],
      sourceType: 'module',
    });
    javaScript.markDirty();
    expect(javaScript.text, 'to equal', es6Text);
  });

  it('should tolerate JSX syntax', function () {
    const jsxText = 'function render() { return (<MyComponent />); }';
    const javaScript = new AssetGraph().addAsset({
      type: 'JavaScript',
      text: jsxText,
    });
    expect(javaScript.parseTree, 'to satisfy', {
      type: 'Program',
      body: [
        {
          type: 'FunctionDeclaration',
          body: {
            type: 'BlockStatement',
            body: [
              {
                type: 'ReturnStatement',
                argument: {
                  type: 'JSXElement',
                  openingElement: {
                    type: 'JSXOpeningElement',
                    name: {
                      type: 'JSXIdentifier',
                      name: 'MyComponent',
                    },
                  },
                  children: [],
                  closingElement: null,
                },
              },
            ],
          },
        },
      ],
      sourceType: 'module',
    });
    javaScript.markDirty();
    // This doesn't work because escodegen doesn't support JSX:
    // expect(javaScript.text, 'to equal', jsxText);
  });

  it('should fall back to "script" mode when a parse error is encountered', function () {
    const text = 'await = 123;';
    const javaScript = new AssetGraph().addAsset({ type: 'JavaScript', text });
    expect(javaScript.parseTree, 'to satisfy', {
      type: 'Program',
      body: [
        {
          type: 'ExpressionStatement',
          expression: {
            type: 'AssignmentExpression',
            operator: '=',
            left: { type: 'Identifier', name: 'await' },
            right: { type: 'Literal', value: 123 },
          },
        },
      ],
      sourceType: 'script',
    });
    javaScript.markDirty();
    expect(javaScript.text, 'to equal', text);
  });

  it('should emit an info event (and no warn events) when falling back to script mode', async function () {
    const errorSpy = sinon.spy().named('error');
    const warnSpy = sinon.spy().named('warn');
    const infoSpy = sinon.spy().named('info');
    await new AssetGraph()
      .on('error', errorSpy)
      .on('warn', warnSpy)
      .on('info', infoSpy)
      .loadAssets({
        type: 'JavaScript',
        url: 'http://example.com/script.js',
        text: 'await = 123;',
      })
      .populate();

    expect([errorSpy, warnSpy, infoSpy], 'to have calls satisfying', () => {
      infoSpy(
        "Could not parse http://example.com/script.js as a module, fall back to script mode\nCannot use keyword 'await' outside an async function (1:0)"
      );
    });
  });

  it('should emit one warn event (and no other events) when neither module nor script mode works', async function () {
    const errorSpy = sinon.spy().named('error');
    const warnSpy = sinon.spy().named('warn');
    const infoSpy = sinon.spy().named('info');
    await new AssetGraph()
      .on('error', errorSpy)
      .on('warn', warnSpy)
      .on('info', infoSpy)
      .loadAssets({
        type: 'JavaScript',
        url: 'http://example.com/script.js',
        text: 'qwvce)',
      })
      .populate();

    expect([errorSpy, warnSpy, infoSpy], 'to have calls satisfying', () => {
      warnSpy(
        'Parse error in http://example.com/script.js\nUnexpected token (1:5)'
      );
    });
  });

  describe('serialization', function () {
    describe('when there are more singlequotes than doublequotes in a string literal', function () {
      it('should use doublequotes when serializing', function () {
        const javaScript = new AssetGraph().addAsset({
          type: 'JavaScript',
          text: `var foo = 'b\\'\\'"ar';`,
        });
        // Force a reserialization from the AST:
        javaScript.parseTree; // eslint-disable-line no-unused-expressions
        javaScript.markDirty();
        expect(javaScript.text, 'to equal', `var foo = "b''\\"ar";`);
      });
    });

    describe('when there are more doublequotes than singlequotes in a string literal', function () {
      it('should use singlequotes when serializing', function () {
        const javaScript = new AssetGraph().addAsset({
          type: 'JavaScript',
          text: `var foo = "b\\"\\"'ar";`,
        });
        // Force a reserialization from the AST:
        javaScript.parseTree; // eslint-disable-line no-unused-expressions
        javaScript.markDirty();
        expect(javaScript.text, 'to equal', `var foo = 'b""\\'ar';`);
      });
    });

    describe('when there is the same number of doublequotes and singlequotes in a string literal', function () {
      it('should use singlequotes when serializing', function () {
        const javaScript = new AssetGraph().addAsset({
          type: 'JavaScript',
          text: `var foo = "b\\"'ar";`,
        });
        // Force a reserialization from the AST:
        javaScript.parseTree; // eslint-disable-line no-unused-expressions
        javaScript.markDirty();
        expect(javaScript.text, 'to equal', `var foo = 'b"\\'ar';`);
      });
    });
  });
});
<|endoftext|>


<commit_before>(function() {
  'use strict';

  angular
    .module('utils.codehangar')
    .controller('RethinkCtrl', controller);

  controller.$inject = ['$timeout'];

  function controller($timeout) {

    var vm = this;

    vm.download = function(downloadForm) {
      downloadForm.$setDirty();

      console.log("downloadForm.$valid", downloadForm.$valid)
      if (downloadForm.$valid) {
        analytics.alias(vm.downloadEmail, {}, {}, function() {
          analytics.identify(vm.downloadEmail, {
            email: vm.downloadEmail
          });

          analytics.track('clicked download ReQLPro', {
            "downloadCTA": vm.downloadCTA,
            "downloadEmail": vm.downloadEmail
          });

          $timeout(function() {
            vm.showDownloadSuccess = true;
            console.log("vm.showDownloadSuccess", vm.showDownloadSuccess)
          });
        })
      }
    };

    vm.init = function() {
      vm.downloadCTA = 'Get the Beta';
    };

    vm.init();
  }
})();
<commit_msg>feat: add postmark version of download email capture<commit_after>(function() {
  'use strict';

  angular
    .module('utils.codehangar')
    .controller('RethinkCtrl', controller);

  controller.$inject = ['$timeout', '$http'];

  function controller($timeout, $http) {

    var vm = this;

    vm.download = function(downloadForm) {
      downloadForm.$setDirty();

      console.log("downloadForm.$valid", downloadForm.$valid)
      if (downloadForm.$valid) {
        analytics.alias(vm.downloadEmail, {}, {}, function() {
          analytics.identify(vm.downloadEmail, {
            email: vm.downloadEmail
          });

          analytics.track('clicked download ReQLPro', {
            "downloadCTA": vm.downloadCTA,
            "downloadEmail": vm.downloadEmail
          });

          $timeout(function() {
            vm.showDownloadSuccess = true;
            console.log("vm.showDownloadSuccess", vm.showDownloadSuccess)
          });
        })

        $http.post('https://contact-form-api.herokuapp.com/api/v1/contact/reqlpro', {
          email: vm.downloadEmail
        }).then(function(res) {
          console.log("res", res)
        }).catch(function(err) {
          console.log("err", err)
        });
      }
    };

    vm.init = function() {
      vm.downloadCTA = 'Get the Beta';
    };

    vm.init();
  }
})();
<|endoftext|>


<commit_before>import { h, render, rerender, Component } from '../../src/preact';
/** @jsx h */

describe('Lifecycle methods', () => {
	let scratch;

	before( () => {
		scratch = document.createElement('div');
		(document.body || document.documentElement).appendChild(scratch);
	});

	beforeEach( () => {
		scratch.innerHTML = '';
	});

	after( () => {
		scratch.parentNode.removeChild(scratch);
		scratch = null;
	});

	describe('#componentWillUpdate', () => {
		it('should NOT be called on initial render', () => {
			class ReceivePropsComponent extends Component {
				componentWillUpdate() {}
				render() {
					return
;
				}
			}
			sinon.spy(ReceivePropsComponent.prototype, 'componentWillUpdate');
			render(<ReceivePropsComponent />, scratch);
			expect(ReceivePropsComponent.prototype.componentWillUpdate).not.to.have.been.called;
		});
		it('should be called when rerender with new props from parent', () => {
			let doRender;
			class Outer extends Component {
				constructor() {
					super();
					this.state = { i: 0 };
				}
				componentDidMount() {
					doRender = () => this.setState({ i: this.state.i + 1 });
				}
				render(props, { i }) {
					return <Inner i={i} {...props} />;
				}
			}
			class Inner extends Component {
				componentWillUpdate(nextProps, nextState) {
					expect(nextProps).to.be.deep.equal({i: 1});
					expect(nextState).to.be.deep.equal({});
				}
				render() {
					return
;
				}
			}
			sinon.spy(Inner.prototype, 'componentWillUpdate');
			sinon.spy(Outer.prototype, 'componentDidMount');

			// Initial render
			render(<Outer />, scratch);
			expect(Inner.prototype.componentWillUpdate).not.to.have.been.called;

			// Rerender inner with new props
			doRender();
			rerender();
			expect(Inner.prototype.componentWillUpdate).to.have.been.called;
		});
		it('should be called on new state', () => {
			let doRender;
			class ReceivePropsComponent extends Component {
				componentWillUpdate() {}
				componentDidMount() {
					doRender = () => this.setState({ i: this.state.i + 1 });
				}
				render() {
					return
;
				}
			}
			sinon.spy(ReceivePropsComponent.prototype, 'componentWillUpdate');
			render(<ReceivePropsComponent />, scratch);
			expect(ReceivePropsComponent.prototype.componentWillUpdate).not.to.have.been.called;

			doRender();
			rerender();
			expect(ReceivePropsComponent.prototype.componentWillUpdate).to.have.been.called;
		});
	});

	describe('#componentWillReceiveProps', () => {
		it('should NOT be called on initial render', () => {
			class ReceivePropsComponent extends Component {
				componentWillReceiveProps() {}
				render() {
					return
;
				}
			}
			sinon.spy(ReceivePropsComponent.prototype, 'componentWillReceiveProps');
			render(<ReceivePropsComponent />, scratch);
			expect(ReceivePropsComponent.prototype.componentWillReceiveProps).not.to.have.been.called;
		});
		it('should be called when rerender with new props from parent', () => {
			let doRender;
			class Outer extends Component {
				constructor() {
					super();
					this.state = { i: 0 };
				}
				componentDidMount() {
					doRender = () => this.setState({ i: this.state.i + 1 });
				}
				render(props, { i }) {
					return <Inner i={i} {...props} />;
				}
			}
			class Inner extends Component {
				componentWillMount() {
					expect(this.props.i).to.be.equal(0);
				}
				componentWillReceiveProps(nextProps) {
					expect(nextProps.i).to.be.equal(1);
				}
				render() {
					return
;
				}
			}
			sinon.spy(Inner.prototype, 'componentWillReceiveProps');
			sinon.spy(Outer.prototype, 'componentDidMount');

      // Initial render
			render(<Outer />, scratch);
			expect(Inner.prototype.componentWillReceiveProps).not.to.have.been.called;

			// Rerender inner with new props
			doRender();
			rerender();
			expect(Inner.prototype.componentWillReceiveProps).to.have.been.called;
		});
		it('should be called in right execution order', () => {
			let doRender;
			class Outer extends Component {
				constructor() {
					super();
					this.state = { i: 0 };
				}
				componentDidMount() {
					doRender = () => this.setState({ i: this.state.i + 1 });
				}
				render(props, { i }) {
					return <Inner i={i} {...props} />;
				}
			}
			class Inner extends Component {
				componentDidUpdate() {
					expect(Inner.prototype.componentWillReceiveProps).to.have.been.called;
					expect(Inner.prototype.componentWillUpdate).to.have.been.called;
				}
				componentWillReceiveProps() {
					expect(Inner.prototype.componentWillUpdate).not.to.have.been.called;
					expect(Inner.prototype.componentDidUpdate).not.to.have.been.called;
				}
				componentWillUpdate() {
					expect(Inner.prototype.componentWillReceiveProps).to.have.been.called;
					expect(Inner.prototype.componentDidUpdate).not.to.have.been.called;
				}
				render() {
					return
;
				}
			}
			sinon.spy(Inner.prototype, 'componentWillReceiveProps');
			sinon.spy(Inner.prototype, 'componentDidUpdate');
			sinon.spy(Inner.prototype, 'componentWillUpdate');
			sinon.spy(Outer.prototype, 'componentDidMount');

			render(<Outer />, scratch);
			doRender();
			rerender();

			expect(Inner.prototype.componentWillReceiveProps).to.have.been.called;
			expect(Inner.prototype.componentWillUpdate).to.have.been.called;
			expect(Inner.prototype.componentDidUpdate).to.have.been.called;
		});
	});
});
<commit_msg>Add lifecycle tests for nested component mount/unmount (Issue #94)<commit_after>import { h, render, rerender, Component } from '../../src/preact';
/** @jsx h */

describe('Lifecycle methods', () => {
	let scratch;

	before( () => {
		scratch = document.createElement('div');
		(document.body || document.documentElement).appendChild(scratch);
	});

	beforeEach( () => {
		scratch.innerHTML = '';
	});

	after( () => {
		scratch.parentNode.removeChild(scratch);
		scratch = null;
	});

	describe('#componentWillUpdate', () => {
		it('should NOT be called on initial render', () => {
			class ReceivePropsComponent extends Component {
				componentWillUpdate() {}
				render() {
					return
;
				}
			}
			sinon.spy(ReceivePropsComponent.prototype, 'componentWillUpdate');
			render(<ReceivePropsComponent />, scratch);
			expect(ReceivePropsComponent.prototype.componentWillUpdate).not.to.have.been.called;
		});
		it('should be called when rerender with new props from parent', () => {
			let doRender;
			class Outer extends Component {
				constructor() {
					super();
					this.state = { i: 0 };
				}
				componentDidMount() {
					doRender = () => this.setState({ i: this.state.i + 1 });
				}
				render(props, { i }) {
					return <Inner i={i} {...props} />;
				}
			}
			class Inner extends Component {
				componentWillUpdate(nextProps, nextState) {
					expect(nextProps).to.be.deep.equal({i: 1});
					expect(nextState).to.be.deep.equal({});
				}
				render() {
					return
;
				}
			}
			sinon.spy(Inner.prototype, 'componentWillUpdate');
			sinon.spy(Outer.prototype, 'componentDidMount');

			// Initial render
			render(<Outer />, scratch);
			expect(Inner.prototype.componentWillUpdate).not.to.have.been.called;

			// Rerender inner with new props
			doRender();
			rerender();
			expect(Inner.prototype.componentWillUpdate).to.have.been.called;
		});
		it('should be called on new state', () => {
			let doRender;
			class ReceivePropsComponent extends Component {
				componentWillUpdate() {}
				componentDidMount() {
					doRender = () => this.setState({ i: this.state.i + 1 });
				}
				render() {
					return
;
				}
			}
			sinon.spy(ReceivePropsComponent.prototype, 'componentWillUpdate');
			render(<ReceivePropsComponent />, scratch);
			expect(ReceivePropsComponent.prototype.componentWillUpdate).not.to.have.been.called;

			doRender();
			rerender();
			expect(ReceivePropsComponent.prototype.componentWillUpdate).to.have.been.called;
		});
	});

	describe('#componentWillReceiveProps', () => {
		it('should NOT be called on initial render', () => {
			class ReceivePropsComponent extends Component {
				componentWillReceiveProps() {}
				render() {
					return
;
				}
			}
			sinon.spy(ReceivePropsComponent.prototype, 'componentWillReceiveProps');
			render(<ReceivePropsComponent />, scratch);
			expect(ReceivePropsComponent.prototype.componentWillReceiveProps).not.to.have.been.called;
		});
		it('should be called when rerender with new props from parent', () => {
			let doRender;
			class Outer extends Component {
				constructor() {
					super();
					this.state = { i: 0 };
				}
				componentDidMount() {
					doRender = () => this.setState({ i: this.state.i + 1 });
				}
				render(props, { i }) {
					return <Inner i={i} {...props} />;
				}
			}
			class Inner extends Component {
				componentWillMount() {
					expect(this.props.i).to.be.equal(0);
				}
				componentWillReceiveProps(nextProps) {
					expect(nextProps.i).to.be.equal(1);
				}
				render() {
					return
;
				}
			}
			sinon.spy(Inner.prototype, 'componentWillReceiveProps');
			sinon.spy(Outer.prototype, 'componentDidMount');

      // Initial render
			render(<Outer />, scratch);
			expect(Inner.prototype.componentWillReceiveProps).not.to.have.been.called;

			// Rerender inner with new props
			doRender();
			rerender();
			expect(Inner.prototype.componentWillReceiveProps).to.have.been.called;
		});
		it('should be called in right execution order', () => {
			let doRender;
			class Outer extends Component {
				constructor() {
					super();
					this.state = { i: 0 };
				}
				componentDidMount() {
					doRender = () => this.setState({ i: this.state.i + 1 });
				}
				render(props, { i }) {
					return <Inner i={i} {...props} />;
				}
			}
			class Inner extends Component {
				componentDidUpdate() {
					expect(Inner.prototype.componentWillReceiveProps).to.have.been.called;
					expect(Inner.prototype.componentWillUpdate).to.have.been.called;
				}
				componentWillReceiveProps() {
					expect(Inner.prototype.componentWillUpdate).not.to.have.been.called;
					expect(Inner.prototype.componentDidUpdate).not.to.have.been.called;
				}
				componentWillUpdate() {
					expect(Inner.prototype.componentWillReceiveProps).to.have.been.called;
					expect(Inner.prototype.componentDidUpdate).not.to.have.been.called;
				}
				render() {
					return
;
				}
			}
			sinon.spy(Inner.prototype, 'componentWillReceiveProps');
			sinon.spy(Inner.prototype, 'componentDidUpdate');
			sinon.spy(Inner.prototype, 'componentWillUpdate');
			sinon.spy(Outer.prototype, 'componentDidMount');

			render(<Outer />, scratch);
			doRender();
			rerender();

			expect(Inner.prototype.componentWillReceiveProps).to.have.been.calledBefore(Inner.prototype.componentWillUpdate);
			expect(Inner.prototype.componentWillUpdate).to.have.been.calledBefore(Inner.prototype.componentDidUpdate);
		});
	});

	describe('#component(Did|Will)(Mount|Unmount)', () => {
		it('should be invoked for nested components', () => {
			let setState;
			class Outer extends Component {
				constructor() {
					super();
					this.state = { show:true };
					setState = s => this.setState(s);
				}
				render(props, { show }) {
					return (

							{ show && (

									<Inner {...props} />

							) }

					);
				}
			}

			class Inner extends Component {
				componentWillMount() {}
				componentDidMount() {}
				componentWillUnmount() {}
				componentDidUnmount() {}
				render() {
					return
;
				}
			}

			let proto = Inner.prototype;
			let spies = ['componentWillMount', 'componentDidMount', 'componentWillUnmount', 'componentDidUnmount'];
			spies.forEach( s => sinon.spy(proto, s) );

			let reset = () => spies.forEach( s => proto[s].reset() );

			render(<Outer />, scratch);
			expect(proto.componentWillMount).to.have.been.called;
			expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount);
			expect(proto.componentDidMount).to.have.been.called;

			reset();
			setState({ show:false });
			rerender();

			expect(proto.componentWillUnmount).to.have.been.called;
			expect(proto.componentWillUnmount).to.have.been.calledBefore(proto.componentDidUnmount);
			expect(proto.componentDidUnmount).to.have.been.called;

			reset();
			setState({ show:true });
			rerender();

			expect(proto.componentWillMount).to.have.been.called;
			expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount);
			expect(proto.componentDidMount).to.have.been.called;
		});

		describe('when shouldComponentUpdate() returns false', () => {
			let setState;

			class Outer extends Component {
				constructor() {
					super();
					this.state = { show:true };
					setState = s => this.setState(s);
				}
				render(props, { show }) {
					return (

							{ show && (

									<Inner {...props} />

							) }

					);
				}
			}

			class Inner extends Component {
				shouldComponentUpdate(){ return false; }
				componentWillMount() {}
				componentDidMount() {}
				componentWillUnmount() {}
				componentDidUnmount() {}
				render() {
					return
;
				}
			}

			let proto = Inner.prototype;
			let spies = ['componentWillMount', 'componentDidMount', 'componentWillUnmount', 'componentDidUnmount'];
			spies.forEach( s => sinon.spy(proto, s) );

			let reset = () => spies.forEach( s => proto[s].reset() );

			beforeEach( () => reset() );

			it('should be invoke normally on initial mount', () => {
				render(<Outer />, scratch);
				expect(proto.componentWillMount).to.have.been.called;
				expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount);
				expect(proto.componentDidMount).to.have.been.called;
			});

			it('should be invoked normally on unmount', () => {
				setState({ show:false });
				rerender();

				expect(proto.componentWillUnmount).to.have.been.called;
				expect(proto.componentWillUnmount).to.have.been.calledBefore(proto.componentDidUnmount);
				expect(proto.componentDidUnmount).to.have.been.called;
			});

			it('should still invoke mount for shouldComponentUpdate():false', () => {
				setState({ show:true });
				rerender();

				expect(proto.componentWillMount).to.have.been.called;
				expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount);
				expect(proto.componentDidMount).to.have.been.called;
			});

			it('should still invoke unmount for shouldComponentUpdate():false', () => {
				setState({ show:false });
				rerender();

				expect(proto.componentWillUnmount).to.have.been.called;
				expect(proto.componentWillUnmount).to.have.been.calledBefore(proto.componentDidUnmount);
				expect(proto.componentDidUnmount).to.have.been.called;
			});
		});
	});
});
<|endoftext|>


<commit_before>/// <reference path="../../../../ext/ext-core-debug.js"/>
/// <reference path="../../../../Simplate.js"/>
/// <reference path="../../../../sdata/SDataSingleResourceRequest.js"/>
/// <reference path="../../../../sdata/SDataService.js"/>
/// <reference path="../../../../platform/View.js"/>
/// <reference path="../../../../platform/Detail.js"/>
/// <reference path="../../Format.js"/>

Ext.namespace("Mobile.SalesLogix.Return");

Mobile.SalesLogix.Return.Detail = Ext.extend(Sage.Platform.Mobile.Detail, {
    constructor: function(o) {
        Mobile.SalesLogix.Return.Detail.superclass.constructor.call(this);

        Ext.apply(this, o, {
            id: 'return_detail',
            title: 'Return',
            editor: 'return_edit',
            resourceKind: 'returns'
        });

        this.layout = [
            {name: 'ReturnNumber', label: 'return id'},
            {name: 'Account.AccountName', label: 'account'},
            {name: 'Priority', label: 'priority'},
            {name: 'ReturnType', label: 'type'},
            {name: 'ExpectedDate', label: 'reg date', renderer: Mobile.SalesLogix.Format.date},
            {name: 'AssignedTo.OwnerDescription', label: 'AssignedTo'},
            {name: 'ReturnedBy.FullName', label: 'returned by'},
            {name: 'ShipTo.FullName', label: 'ship to'},
            {name: 'CreateUser', label: 'create user'},
            {name: 'CreateDate', label: 'create date', renderer: Mobile.SalesLogix.Format.date},
          ];
    },
    init: function() {
        Mobile.SalesLogix.Return.Detail.superclass.init.call(this);
    },
    createRequest: function() {
        var request = Mobile.SalesLogix.Return.Detail.superclass.createRequest.call(this);

        request
            .setQueryArgs({
                'include': 'Account,AssignedTo,ReturnedBy,ShipTo',
                'select': [
                    'ReturnNumber',
                    'Account/AccountName',
                    'Priority',
                    'ReturnType',
                    'ExpectedDate',
                    'AssignedTo/OwnerDescription',
                    'ReturnedBy/FullName',
                    'ShipTo/FullName',
                    'CreateUser',
                    'CreateDate'
                  ].join(',')
            });

        return request;
    }
});<commit_msg>[#4179318] Changed returned by and ship to from FullName to NameLF.<commit_after>/// <reference path="../../../../ext/ext-core-debug.js"/>
/// <reference path="../../../../Simplate.js"/>
/// <reference path="../../../../sdata/SDataSingleResourceRequest.js"/>
/// <reference path="../../../../sdata/SDataService.js"/>
/// <reference path="../../../../platform/View.js"/>
/// <reference path="../../../../platform/Detail.js"/>
/// <reference path="../../Format.js"/>

Ext.namespace("Mobile.SalesLogix.Return");

Mobile.SalesLogix.Return.Detail = Ext.extend(Sage.Platform.Mobile.Detail, {
    constructor: function(o) {
        Mobile.SalesLogix.Return.Detail.superclass.constructor.call(this);

        Ext.apply(this, o, {
            id: 'return_detail',
            title: 'Return',
            editor: 'return_edit',
            resourceKind: 'returns'
        });

        this.layout = [
            {name: 'ReturnNumber', label: 'return id'},
            {name: 'Account.AccountName', label: 'account'},
            {name: 'Priority', label: 'priority'},
            {name: 'ReturnType', label: 'type'},
            {name: 'ExpectedDate', label: 'reg date', renderer: Mobile.SalesLogix.Format.date},
            {name: 'AssignedTo.OwnerDescription', label: 'AssignedTo'},
            {name: 'ReturnedBy.NameLF', label: 'returned by'},
            {name: 'ShipTo.NameLF', label: 'ship to'},
            {name: 'CreateUser', label: 'create user'},
            {name: 'CreateDate', label: 'create date', renderer: Mobile.SalesLogix.Format.date},
          ];
    },
    init: function() {
        Mobile.SalesLogix.Return.Detail.superclass.init.call(this);
    },
    createRequest: function() {
        var request = Mobile.SalesLogix.Return.Detail.superclass.createRequest.call(this);

        request
            .setQueryArgs({
                'include': 'Account,AssignedTo,ReturnedBy,ShipTo',
                'select': [
                    'ReturnNumber',
                    'Account/AccountName',
                    'Priority',
                    'ReturnType',
                    'ExpectedDate',
                    'AssignedTo/OwnerDescription',
                    'ReturnedBy/NameLF',
                    'ShipTo/NameLF',
                    'CreateUser',
                    'CreateDate'
                  ].join(',')
            });

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


<commit_before>Werld.Views.SplashScreen = Backbone.View.extend({
  initialize: function() {
    var splashImage = new Bitmap('../images/splash.jpg');
    splashImage.x = 0;
    splashImage.y = 0;

    var titleText = new Text(
      'Werld Online',
      '90px "PowellAntique" serif',
      '#dc9a44'
    );
    titleText.shadow = new Shadow('#000000', 4, 4, 0);
    titleText.textAlign = 'center';
    titleText.x = 320;
    titleText.y = 120;

    signInText = new Text('Sign In', '40px "PowellAntique" serif', '#dc9a44');
    signInText.textBaseline = 'bottom';
    signInText.x = 400;
    signInText.y = 320;
    signInText.shadow = new Shadow('#000000', 2, 2, 0);

    var signInGraphics = new Graphics();
    var signInGraphicsX = signInText.x + signInText.getMeasuredWidth() / 2;
    var signInGraphicsY = signInText.y - signInText.getMeasuredLineHeight() /2;
    var signInGraphicsInnerRadius = 10;
    var signInGraphicsOuterRadius = 110;
    signInGraphics.beginRadialGradientFill(
      ['#dc9a44', '#000000'], [0, 0.9],
      signInGraphicsX, signInGraphicsY, signInGraphicsInnerRadius,
      signInGraphicsX, signInGraphicsY, signInGraphicsOuterRadius
    );
    signInGraphics.drawCircle(
      signInGraphicsX, signInGraphicsY, signInGraphicsOuterRadius
    );

    var signInGraphicsShape = new Shape(signInGraphics);
    signInGraphicsShape.alpha = 0.3;
    signInGraphicsShape.compositeOperation = 'lighter';
    signInGraphicsShape.visible = false;

    var signInClickableGraphics = new Graphics();
    var signInClickableGraphicsWidth = signInText.getMeasuredWidth() + 50;
    var signInClickableGraphicsHeight = signInText.getMeasuredLineHeight() + 50;
    /* HACK: need to fill something to be able to attach mouse events. */
    signInClickableGraphics.beginFill('rgba(0,0,0,0.01)');
    signInClickableGraphics.drawEllipse(
      signInText.x - (signInClickableGraphicsWidth - signInText.getMeasuredWidth()) / 2,
      (2 * signInText.y - signInText.getMeasuredLineHeight() - signInClickableGraphicsHeight) / 2,
      signInClickableGraphicsWidth,
      signInClickableGraphicsHeight
    );

    var signInClickableGraphicsShape = new Shape(signInClickableGraphics);
    signInClickableGraphicsShape.onMouseOver = function() {
      signInClickableGraphicsShape.getStage().canvas.style.cursor = 'pointer';
      signInGraphicsShape.visible = true;
    };
    signInClickableGraphicsShape.onMouseOut = function() {
      signInClickableGraphicsShape.getStage().canvas.style.cursor = '';
      signInGraphicsShape.visible = false;
    };
    signInClickableGraphicsShape.onClick = function() {
      Werld.switchState(Werld.States.CHOOSING_NAME, {
        callback: function() {
          signInClickableGraphicsShape.onMouseOver = null;
          signInClickableGraphicsShape.onMouseOut = null;
          signInClickableGraphicsShape.onClick = null;
          signInClickableGraphicsShape.visible = false;
        }
      });
    };

    this.container = new Container();
    this.container.addChild(splashImage);
    this.container.addChild(titleText);
    this.container.addChild(signInClickableGraphicsShape);
    this.container.addChild(signInGraphicsShape);
    this.container.addChild(signInText);
  }
});
<commit_msg>Regular cursor after clicking sign in.<commit_after>Werld.Views.SplashScreen = Backbone.View.extend({
  initialize: function() {
    var splashImage = new Bitmap('../images/splash.jpg');
    splashImage.x = 0;
    splashImage.y = 0;

    var titleText = new Text(
      'Werld Online',
      '90px "PowellAntique" serif',
      '#dc9a44'
    );
    titleText.shadow = new Shadow('#000000', 4, 4, 0);
    titleText.textAlign = 'center';
    titleText.x = 320;
    titleText.y = 120;

    signInText = new Text('Sign In', '40px "PowellAntique" serif', '#dc9a44');
    signInText.textBaseline = 'bottom';
    signInText.x = 400;
    signInText.y = 320;
    signInText.shadow = new Shadow('#000000', 2, 2, 0);

    var signInGraphics = new Graphics();
    var signInGraphicsX = signInText.x + signInText.getMeasuredWidth() / 2;
    var signInGraphicsY = signInText.y - signInText.getMeasuredLineHeight() /2;
    var signInGraphicsInnerRadius = 10;
    var signInGraphicsOuterRadius = 110;
    signInGraphics.beginRadialGradientFill(
      ['#dc9a44', '#000000'], [0, 0.9],
      signInGraphicsX, signInGraphicsY, signInGraphicsInnerRadius,
      signInGraphicsX, signInGraphicsY, signInGraphicsOuterRadius
    );
    signInGraphics.drawCircle(
      signInGraphicsX, signInGraphicsY, signInGraphicsOuterRadius
    );

    var signInGraphicsShape = new Shape(signInGraphics);
    signInGraphicsShape.alpha = 0.3;
    signInGraphicsShape.compositeOperation = 'lighter';
    signInGraphicsShape.visible = false;

    var signInClickableGraphics = new Graphics();
    var signInClickableGraphicsWidth = signInText.getMeasuredWidth() + 50;
    var signInClickableGraphicsHeight = signInText.getMeasuredLineHeight() + 50;
    /* HACK: need to fill something to be able to attach mouse events. */
    signInClickableGraphics.beginFill('rgba(0,0,0,0.01)');
    signInClickableGraphics.drawEllipse(
      signInText.x - (signInClickableGraphicsWidth - signInText.getMeasuredWidth()) / 2,
      (2 * signInText.y - signInText.getMeasuredLineHeight() - signInClickableGraphicsHeight) / 2,
      signInClickableGraphicsWidth,
      signInClickableGraphicsHeight
    );

    var signInClickableGraphicsShape = new Shape(signInClickableGraphics);
    signInClickableGraphicsShape.onMouseOver = function() {
      signInClickableGraphicsShape.getStage().canvas.style.cursor = 'pointer';
      signInGraphicsShape.visible = true;
    };
    signInClickableGraphicsShape.onMouseOut = function() {
      signInClickableGraphicsShape.getStage().canvas.style.cursor = '';
      signInGraphicsShape.visible = false;
    };
    signInClickableGraphicsShape.onClick = function() {
      signInClickableGraphicsShape.getStage().canvas.style.cursor = '';
      Werld.switchState(Werld.States.CHOOSING_NAME, {
        callback: function() {
          signInClickableGraphicsShape.onMouseOver = null;
          signInClickableGraphicsShape.onMouseOut = null;
          signInClickableGraphicsShape.onClick = null;
          signInClickableGraphicsShape.visible = false;
        }
      });
    };

    this.container = new Container();
    this.container.addChild(splashImage);
    this.container.addChild(titleText);
    this.container.addChild(signInClickableGraphicsShape);
    this.container.addChild(signInGraphicsShape);
    this.container.addChild(signInText);
  }
});
<|endoftext|>


<commit_before>/*
 * Copyright 2015 Open Networking Laboratory
 *
 * 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.
 */

/*
 ONOS GUI -- Topology Toolbar Module.
 Functions for creating and interacting with the toolbar.
 */

(function () {
    'use strict';

    // injected references
    var $log, fs, tbs, ps, api;

    // internal state
    var toolbar, keyData, cachedState;

    // constants
    var name = 'topo-tbar',
        cooktag = 'topo_prefs';

    // key to button mapping data
    var k2b = {
        O: { id: 'summary-tog', gid: 'summary', isel: true},
        I: { id: 'instance-tog', gid: 'uiAttached', isel: true },
        D: { id: 'details-tog', gid: 'details', isel: true },

        H: { id: 'hosts-tog', gid: 'endstation', isel: false },
        M: { id: 'offline-tog', gid: 'switch', isel: true },
        P: { id: 'ports-tog', gid: 'ports', isel: true },
        B: { id: 'bkgrnd-tog', gid: 'map', isel: true },
        S: { id: 'sprite-tog', gid: 'cloud', isel: false },

        //X: { id: 'nodelock-tog', gid: 'lock', isel: false },
        Z: { id: 'oblique-tog', gid: 'oblique', isel: false },
        N: { id: 'filters-btn', gid: 'filters' },
        L: { id: 'cycleLabels-btn', gid: 'cycleLabels' },
        R: { id: 'resetZoom-btn', gid: 'resetZoom' },

        V: { id: 'relatedIntents-btn', gid: 'relatedIntents' },
        leftArrow: { id: 'prevIntent-btn', gid: 'prevIntent' },
        rightArrow: { id: 'nextIntent-btn', gid: 'nextIntent' },
        W: { id: 'intentTraffic-btn', gid: 'intentTraffic' },
        A: { id: 'allTraffic-btn', gid: 'allTraffic' },
        F: { id: 'flows-btn', gid: 'flows' },

        E: { id: 'eqMaster-btn', gid: 'eqMaster' }
    };

    // initial toggle state: default settings and tag to key mapping
    var defaultPrefsState = {
            summary: 1,
            insts: 1,
            detail: 1,
            hosts: 0,
            offdev: 1,
            porthl: 1,
            bg: 1,
            spr: 0,
            toolbar: 1
        },
        prefsMap = {
            summary: 'O',
            insts: 'I',
            detail: 'D',
            hosts: 'H',
            offdev: 'M',
            porthl: 'P',
            bg: 'B',
            spr: 'S'
            // NOTE: toolbar state is handled separately
        };

    function init(_api_) {
        api = _api_;

        // retrieve initial toggle button settings from user prefs
        setInitToggleState();
    }

    function topoDefPrefs() {
        return angular.extend({}, defaultPrefsState);
    }

    function setInitToggleState() {
        cachedState = ps.asNumbers(ps.getPrefs(cooktag));
        $log.debug('TOOLBAR---- read prefs state:', cachedState);

        if (!cachedState) {
            cachedState = topoDefPrefs();
            ps.setPrefs(cooktag, cachedState);
            $log.debug('TOOLBAR---- Set default prefs state:', cachedState);
        }

        angular.forEach(prefsMap, function (v, k) {
            var cfg = k2b[v];
            cfg && (cfg.isel = !!cachedState[k]);
        });
    }

    function initKeyData() {
        keyData = d3.map(k2b);
        keyData.forEach(function(key, value) {
            var data = api.getActionEntry(key);
            value.cb = data[0];                     // on-click callback
            value.tt = data[1] + ' (' + key + ')';  // tooltip
        });
    }

    function addButton(key) {
        var v = keyData.get(key);
        v.btn = toolbar.addButton(v.id, v.gid, v.cb, v.tt);
    }
    function addToggle(key, suppressIfMobile) {
        var v = keyData.get(key);
        if (suppressIfMobile && fs.isMobile()) { return; }
        v.tog = toolbar.addToggle(v.id, v.gid, v.isel, v.cb, v.tt);
    }

    function addFirstRow() {
        addToggle('I');
        addToggle('O');
        addToggle('D');
        toolbar.addSeparator();

        addToggle('H');
        addToggle('M');
        addToggle('P', true);
        addToggle('B');
        addToggle('S', true);
    }
    function addSecondRow() {
        //addToggle('X');
        addToggle('Z');
        addButton('N');
        addButton('L');
        addButton('R');
    }
    function addThirdRow() {
        addButton('V');
        addButton('leftArrow');
        addButton('rightArrow');
        addButton('W');
        addButton('A');
        addButton('F');
        toolbar.addSeparator();
        addButton('E');
    }

    function createToolbar() {
        initKeyData();
        toolbar = tbs.createToolbar(name);
        addFirstRow();
        toolbar.addRow();
        addSecondRow();
        toolbar.addRow();
        addThirdRow();
        if (cachedState.toolbar) {
            toolbar.show();
        } else {
            toolbar.hide();
        }
    }

    function destroyToolbar() {
        tbs.destroyToolbar(name);
    }

    // allows us to ensure the button states track key strokes
    function keyListener(key) {
        var v = keyData.get(key);

        if (v) {
            // we have a valid button mapping
            if (v.tog) {
                // it's a toggle button
                v.tog.toggleNoCb();
            }
        }
    }

    function toggleToolbar() {
        toolbar.toggle();
    }

    angular.module('ovTopo')
        .factory('TopoToolbarService',
        ['$log', 'FnService', 'ToolbarService', 'PrefsService',

        function (_$log_, _fs_, _tbs_, _ps_) {
            $log = _$log_;
            fs = _fs_;
            tbs = _tbs_;
            ps = _ps_;

            return {
                init: init,
                createToolbar: createToolbar,
                destroyToolbar: destroyToolbar,
                keyListener: keyListener,
                toggleToolbar: toggleToolbar
            };
        }]);
}());<commit_msg>GUI -- topo view - background map is now OFF by default, and toolbar is now HIDDEN by default.<commit_after>/*
 * Copyright 2015 Open Networking Laboratory
 *
 * 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.
 */

/*
 ONOS GUI -- Topology Toolbar Module.
 Functions for creating and interacting with the toolbar.
 */

(function () {
    'use strict';

    // injected references
    var $log, fs, tbs, ps, api;

    // internal state
    var toolbar, keyData, cachedState;

    // constants
    var name = 'topo-tbar',
        cooktag = 'topo_prefs';

    // key to button mapping data
    var k2b = {
        O: { id: 'summary-tog', gid: 'summary', isel: true},
        I: { id: 'instance-tog', gid: 'uiAttached', isel: true },
        D: { id: 'details-tog', gid: 'details', isel: true },

        H: { id: 'hosts-tog', gid: 'endstation', isel: false },
        M: { id: 'offline-tog', gid: 'switch', isel: true },
        P: { id: 'ports-tog', gid: 'ports', isel: true },
        B: { id: 'bkgrnd-tog', gid: 'map', isel: false },
        S: { id: 'sprite-tog', gid: 'cloud', isel: false },

        //X: { id: 'nodelock-tog', gid: 'lock', isel: false },
        Z: { id: 'oblique-tog', gid: 'oblique', isel: false },
        N: { id: 'filters-btn', gid: 'filters' },
        L: { id: 'cycleLabels-btn', gid: 'cycleLabels' },
        R: { id: 'resetZoom-btn', gid: 'resetZoom' },

        V: { id: 'relatedIntents-btn', gid: 'relatedIntents' },
        leftArrow: { id: 'prevIntent-btn', gid: 'prevIntent' },
        rightArrow: { id: 'nextIntent-btn', gid: 'nextIntent' },
        W: { id: 'intentTraffic-btn', gid: 'intentTraffic' },
        A: { id: 'allTraffic-btn', gid: 'allTraffic' },
        F: { id: 'flows-btn', gid: 'flows' },

        E: { id: 'eqMaster-btn', gid: 'eqMaster' }
    };

    // initial toggle state: default settings and tag to key mapping
    var defaultPrefsState = {
            summary: 1,
            insts: 1,
            detail: 1,
            hosts: 0,
            offdev: 1,
            porthl: 1,
            bg: 0,
            spr: 0,
            toolbar: 0
        },
        prefsMap = {
            summary: 'O',
            insts: 'I',
            detail: 'D',
            hosts: 'H',
            offdev: 'M',
            porthl: 'P',
            bg: 'B',
            spr: 'S'
            // NOTE: toolbar state is handled separately
        };

    function init(_api_) {
        api = _api_;

        // retrieve initial toggle button settings from user prefs
        setInitToggleState();
    }

    function topoDefPrefs() {
        return angular.extend({}, defaultPrefsState);
    }

    function setInitToggleState() {
        cachedState = ps.asNumbers(ps.getPrefs(cooktag));
        $log.debug('TOOLBAR---- read prefs state:', cachedState);

        if (!cachedState) {
            cachedState = topoDefPrefs();
            ps.setPrefs(cooktag, cachedState);
            $log.debug('TOOLBAR---- Set default prefs state:', cachedState);
        }

        angular.forEach(prefsMap, function (v, k) {
            var cfg = k2b[v];
            cfg && (cfg.isel = !!cachedState[k]);
        });
    }

    function initKeyData() {
        keyData = d3.map(k2b);
        keyData.forEach(function(key, value) {
            var data = api.getActionEntry(key);
            value.cb = data[0];                     // on-click callback
            value.tt = data[1] + ' (' + key + ')';  // tooltip
        });
    }

    function addButton(key) {
        var v = keyData.get(key);
        v.btn = toolbar.addButton(v.id, v.gid, v.cb, v.tt);
    }
    function addToggle(key, suppressIfMobile) {
        var v = keyData.get(key);
        if (suppressIfMobile && fs.isMobile()) { return; }
        v.tog = toolbar.addToggle(v.id, v.gid, v.isel, v.cb, v.tt);
    }

    function addFirstRow() {
        addToggle('I');
        addToggle('O');
        addToggle('D');
        toolbar.addSeparator();

        addToggle('H');
        addToggle('M');
        addToggle('P', true);
        addToggle('B');
        addToggle('S', true);
    }
    function addSecondRow() {
        //addToggle('X');
        addToggle('Z');
        addButton('N');
        addButton('L');
        addButton('R');
    }
    function addThirdRow() {
        addButton('V');
        addButton('leftArrow');
        addButton('rightArrow');
        addButton('W');
        addButton('A');
        addButton('F');
        toolbar.addSeparator();
        addButton('E');
    }

    function createToolbar() {
        initKeyData();
        toolbar = tbs.createToolbar(name);
        addFirstRow();
        toolbar.addRow();
        addSecondRow();
        toolbar.addRow();
        addThirdRow();
        if (cachedState.toolbar) {
            toolbar.show();
        } else {
            toolbar.hide();
        }
    }

    function destroyToolbar() {
        tbs.destroyToolbar(name);
    }

    // allows us to ensure the button states track key strokes
    function keyListener(key) {
        var v = keyData.get(key);

        if (v) {
            // we have a valid button mapping
            if (v.tog) {
                // it's a toggle button
                v.tog.toggleNoCb();
            }
        }
    }

    function toggleToolbar() {
        toolbar.toggle();
    }

    angular.module('ovTopo')
        .factory('TopoToolbarService',
        ['$log', 'FnService', 'ToolbarService', 'PrefsService',

        function (_$log_, _fs_, _tbs_, _ps_) {
            $log = _$log_;
            fs = _fs_;
            tbs = _tbs_;
            ps = _ps_;

            return {
                init: init,
                createToolbar: createToolbar,
                destroyToolbar: destroyToolbar,
                keyListener: keyListener,
                toggleToolbar: toggleToolbar
            };
        }]);
}());<|endoftext|>


<commit_before>$(function() {
  var focusedClass = 'keyboard-focused',
      defaultScrollSpeed = 200,
      prefixKeysPressed = {},
      prefixShortcutTimeout = 1100,
      shortcutKeys = {
        97:  'a',
        114: 'r',
        117: 'u',
        91:  '[',
        93:  ']'
      };

  $(document).on('keypress', function(event) {
    if ($(event.target).is(':input')) {
      return;
    }

    if (!($.isEmptyObject(prefixKeysPressed))) {
      event.preventDefault();
      handlePrefixedShortcuts(event.which);
      return;
    }

    resetPrefixKeys();
    switch (event.which) {
      case 106: // j
        focusNextFocusable();
        scrollToFocused();
        event.preventDefault();
        break;

      case 107: // k
        focusPreviousFocusable();
        scrollToFocused();
        event.preventDefault();
        break;

      case 120: // x
        switchSnapshotDiffTab();
        event.preventDefault();
        break;

      case 13:  // Enter
      case 111: // o
        openFocused();
        event.preventDefault();
        break;

      case 63:  // ?
        openHelpModal();
        event.preventDefault();
        break;

      case 71:  // G
        scrollAndFocusBottom();
        event.preventDefault();
        break;

      case 103:  // g prefix
        setPrefixKey(event.which);
        event.preventDefault();
        break;

      default: // check for shortcut keys
        if (handleShortcutKey(event.which)) {
          event.preventDefault();
        }
    }

    // Handlers for shortcuts:

    function focusNextFocusable() {
      moveFocus({ forward: true });
    }

    function focusPreviousFocusable() {
      moveFocus({ backward: true });
    }

    function handlePrefixedShortcuts(keyCode) {
      resetPrefixKeys();
      // handle different prefixes with diff. shortcuts
      if(keyCode == 103) {
        switch (keyCode) {
          case 103: // g
            scrollAndFocusTop();
            break;

          default: // ignore if it wasn't a prefixed shortcut
            resetPrefixKeys();
        }
      }
    }

    function handleShortcutKey(keyCode) {
      var key = shortcutKeys[keyCode];
      if (key) {
        var $shortcut = $('[data-keyboard-shortcut~="' + key + '"]')
              .addClass(focusedClass);
        if ($shortcut.length) {
          $shortcut[0].click();
          scrollToFocused();
          return true;
        }
      }
    }

    // @param $focusable [$Object] jQuery element within which to search for
    //   focused element(s)
    // @return [$Object] jQuery element that is focused
    // side effect: if no focused el. is found, it sets the first el. to
    //   focused.
    function getFocusedElement($focusable) {
      var $focused   = $focusable.filter('.' + focusedClass);
      if (!($focused.length)) {
        setFocus('first');
        $focused   = $focusable.filter('.' + focusedClass);
      }
      return $focused;
    }

    // @param movement [Object] options hash allows either forward, backward,
    //   first or last to set movement type
    // @return [Boolean] true if movement was successful, false otherwise
    function moveFocus(movement) {
      var $focusable  = $('[data-keyboard-focusable]:visible'),
          focusExists = !!($('.' + focusedClass).length),
          $focused    = getFocusedElement($focusable);
      if (movement.forward && !(focusExists)) {
        // if there was nothing in focus, we stop after moving focus to top
        return
      }
      if ($focused.length) {
        if (movement.first || movement.last) {
          var $nextFocus = (movement.first) ? $focusable.first() : $focusable.last();
          $focused.removeClass(focusedClass);
          $nextFocus.addClass(focusedClass);
          return true;
        } else {
          var dir     = (movement.forward) ? 1 : -1,
              moveTo  = $focusable.index($focused) + dir;
          if (moveTo >= 0 && moveTo < $focusable.length) {
            $focused.removeClass(focusedClass);
            $focusable.eq(moveTo).addClass(focusedClass);
            return true;
          }
        }
      }
      return false;
    }

    function openFocused() {
      var $focused = $('[data-keyboard-focusable]:visible.' + focusedClass);
      if ($focused.is('a')) {
        $focused[0].click();
      } else {
        var $link = $focused.find('a:visible:first');
        if ($link.length) {
          $link[0].click();
        }
      }
    }

    function openHelpModal() {
      $('.keyboard-shortcut-help').modal('toggle');
    }

    function resetPrefixKeys() {
      prefixKeysPressed = {};
    }

    function scrollAndFocusBottom() {
       $('html, body').animate({ scrollTop: $(document).height() },
          defaultScrollSpeed);
       moveFocus({ last: true });
    }

    function scrollAndFocusTop() {
      $('html, body').animate({ scrollTop: 0 }, defaultScrollSpeed);
      moveFocus({ first: true });
    }

    function scrollToFocused() {
      var $focused = $('.' + focusedClass);
      if ($focused.length && !$focused.visible()) {
        $('html, body').stop(true, true).animate({
          scrollTop: $focused.offset().top - $(window).height() / 4
        }, defaultScrollSpeed);
      }
    }

    function setPrefixKey(key) {
      if (key) {
        prefixKeysPressed[key] = 1;
        setTimeout(resetPrefixKeys, prefixShortcutTimeout);
      }
    }

    // @param whereToFocus [String] either 'first' or 'last'; used to select
    //   focusable element
    function setFocus(whereToFocus){
      $('[data-keyboard-focusable]:visible:' + whereToFocus)
        .addClass(focusedClass);
    }

    function switchSnapshotDiffTab() {
      var tabSelector = '.snapshot-diff-image .nav li'
          $active = $(tabSelector + '.active'),
          $next   = $active.next();
      if (!$next.length) {
        $next = $(tabSelector + ':first');
      }
      $next.find('a').click();
    }
  });
});
<commit_msg>Dynamically associate shortcut keycode with character<commit_after>$(function() {
  var focusedClass = 'keyboard-focused',
      defaultScrollSpeed = 200,
      prefixKeysPressed = {},
      prefixShortcutTimeout = 1100;

  $(document).on('keypress', function(event) {
    if ($(event.target).is(':input')) {
      return;
    }

    if (!($.isEmptyObject(prefixKeysPressed))) {
      event.preventDefault();
      handlePrefixedShortcuts(event.which);
      return;
    }

    resetPrefixKeys();
    switch (event.which) {
      case 106: // j
        focusNextFocusable();
        scrollToFocused();
        event.preventDefault();
        break;

      case 107: // k
        focusPreviousFocusable();
        scrollToFocused();
        event.preventDefault();
        break;

      case 120: // x
        switchSnapshotDiffTab();
        event.preventDefault();
        break;

      case 13:  // Enter
      case 111: // o
        openFocused();
        event.preventDefault();
        break;

      case 63:  // ?
        openHelpModal();
        event.preventDefault();
        break;

      case 71:  // G
        scrollAndFocusBottom();
        event.preventDefault();
        break;

      case 103:  // g prefix
        setPrefixKey(event.which);
        event.preventDefault();
        break;

      default: // check for shortcut keys
        if (handleShortcutKey(event.which)) {
          event.preventDefault();
        }
    }

    // Handlers for shortcuts:

    function focusNextFocusable() {
      moveFocus({ forward: true });
    }

    function focusPreviousFocusable() {
      moveFocus({ backward: true });
    }

    function handlePrefixedShortcuts(keyCode) {
      resetPrefixKeys();
      // handle different prefixes with diff. shortcuts
      if(keyCode == 103) {
        switch (keyCode) {
          case 103: // g
            scrollAndFocusTop();
            break;

          default: // ignore if it wasn't a prefixed shortcut
            resetPrefixKeys();
        }
      }
    }

    function handleShortcutKey(keyCode) {
      var key = String.fromCharCode(keyCode);
      if (key) {
        var $shortcut = $('[data-keyboard-shortcut~="' + key + '"]');
        if ($shortcut.length) {
          $shortcut.addClass(focusedClass);
          $shortcut[0].click();
          scrollToFocused();
          return true;
        }
      }
    }

    // @param $focusable [$Object] jQuery element within which to search for
    //   focused element(s)
    // @return [$Object] jQuery element that is focused
    // side effect: if no focused el. is found, it sets the first el. to
    //   focused.
    function getFocusedElement($focusable) {
      var $focused   = $focusable.filter('.' + focusedClass);
      if (!($focused.length)) {
        setFocus('first');
        $focused   = $focusable.filter('.' + focusedClass);
      }
      return $focused;
    }

    // @param movement [Object] options hash allows either forward, backward,
    //   first or last to set movement type
    // @return [Boolean] true if movement was successful, false otherwise
    function moveFocus(movement) {
      var $focusable  = $('[data-keyboard-focusable]:visible'),
          focusExists = !!($('.' + focusedClass).length),
          $focused    = getFocusedElement($focusable);
      if (movement.forward && !(focusExists)) {
        // if there was nothing in focus, we stop after moving focus to top
        return
      }
      if ($focused.length) {
        if (movement.first || movement.last) {
          var $nextFocus = (movement.first) ? $focusable.first() : $focusable.last();
          $focused.removeClass(focusedClass);
          $nextFocus.addClass(focusedClass);
          return true;
        } else {
          var dir     = (movement.forward) ? 1 : -1,
              moveTo  = $focusable.index($focused) + dir;
          if (moveTo >= 0 && moveTo < $focusable.length) {
            $focused.removeClass(focusedClass);
            $focusable.eq(moveTo).addClass(focusedClass);
            return true;
          }
        }
      }
      return false;
    }

    function openFocused() {
      var $focused = $('[data-keyboard-focusable]:visible.' + focusedClass);
      if ($focused.is('a')) {
        $focused[0].click();
      } else {
        var $link = $focused.find('a:visible:first');
        if ($link.length) {
          $link[0].click();
        }
      }
    }

    function openHelpModal() {
      $('.keyboard-shortcut-help').modal('toggle');
    }

    function resetPrefixKeys() {
      prefixKeysPressed = {};
    }

    function scrollAndFocusBottom() {
       $('html, body').animate({ scrollTop: $(document).height() },
          defaultScrollSpeed);
       moveFocus({ last: true });
    }

    function scrollAndFocusTop() {
      $('html, body').animate({ scrollTop: 0 }, defaultScrollSpeed);
      moveFocus({ first: true });
    }

    function scrollToFocused() {
      var $focused = $('.' + focusedClass);
      if ($focused.length && !$focused.visible()) {
        $('html, body').stop(true, true).animate({
          scrollTop: $focused.offset().top - $(window).height() / 4
        }, defaultScrollSpeed);
      }
    }

    function setPrefixKey(key) {
      if (key) {
        prefixKeysPressed[key] = 1;
        setTimeout(resetPrefixKeys, prefixShortcutTimeout);
      }
    }

    // @param whereToFocus [String] either 'first' or 'last'; used to select
    //   focusable element
    function setFocus(whereToFocus){
      $('[data-keyboard-focusable]:visible:' + whereToFocus)
        .addClass(focusedClass);
    }

    function switchSnapshotDiffTab() {
      var tabSelector = '.snapshot-diff-image .nav li'
          $active = $(tabSelector + '.active'),
          $next   = $active.next();
      if (!$next.length) {
        $next = $(tabSelector + ':first');
      }
      $next.find('a').click();
    }
  });
});
<|endoftext|>


<commit_before>/**
 * Artemis ACP Jquery for Menu Administration Interface
 * Uses Cookie to remember refresh state
 * @type {*|jQuery|HTMLElement}
 */

var menu_wrap = $('#devlpr .left_menu');
var body_wrap = $('#devlpr #main_content');
var app_wrap = $('#devlpr .app_menu');
var menu_header = $('#devlpr .left_menu > header');
var menu_li = $('#devlpr .menu li');

function menuToggle() {

    if (menu_wrap.hasClass('collapsed')) {
        // close menu
        $('.admin-menu-icon').show();
        $('.admin-menu-item').hide();
        menu_header.html('

');
        $('.menu-action').html('');
        Cookies.set('acpState', 0);
    } else {
        // open menu
        $('.admin-menu-icon').hide();
        $('.admin-menu-item').show();
        menu_header.html('
Artemis
');
        $('.menu-action').html(' Collapse Menu');
        Cookies.set('acpState', 1);
    }
}

var CookieState = Cookies.get('acpState');
if (CookieState !== undefined) {
    if (CookieState == 0) {
        menu_wrap.addClass('collapsed');
        body_wrap.addClass('collapsed');
        app_wrap.addClass('collapsed');
        $('.menu-action').html('');
    }
}

menuToggle();

$('.menu-action').bind('click', function (e) {
    menu_wrap.toggleClass('collapsed');
    body_wrap.toggleClass('collapsed');
    app_wrap.toggleClass('collapsed');
    menuToggle();
    e.preventDefault();
});

// Tray Loader
$('a[data-load]').bind('click', function (e) {
    var body = $('div#main_content');
    var ca = $(this).data('load');
    var cl = $('#main_content').data('load');
    if (ca != cl) {
        if (!body.hasClass('open')) {
            body.addClass('open');
        }
    } else {
        body.toggleClass('open');
    }
    body.data('load', ca);
    menu_li.removeClass('active');
    $(this).closest('li').addClass('active');
    $('.app_page_list').hide();
    $('#ap-' + ca).show();
    e.preventDefault();
});

$('.main_content_overlay').bind('click', function (e) {
    menu_li.removeClass('active');
    body_wrap.removeClass('open');
});

// Hover on dropdowns
$('li.dropdown').hover(
    function (e) {
        $(this).addClass('open');
    },
    function (e) {
        $(this).removeClass('open');
    }
);<commit_msg>Update Artemis<commit_after>/**
 * Artemis ACP Jquery for Menu Administration Interface
 * Uses Cookie to remember refresh state
 * @type {*|jQuery|HTMLElement}
 */

var menu_wrap = $('#devlpr .left_menu');
var body_wrap = $('#devlpr #main_content');
var app_wrap = $('#devlpr .app_menu');
var menu_header = $('#devlpr .left_menu > header');
var menu_li = $('#devlpr .menu li');

function menuToggle() {

    if (menu_wrap.hasClass('collapsed')) {
        // close menu
        $('.admin-menu-icon').show();
        $('.admin-menu-item').hide();
        menu_header.html('

');
        $('.menu-action').html('');
        Cookies.set('acpState', 0);
    } else {
        // open menu
        $('.admin-menu-icon').hide();
        $('.admin-menu-item').show();
        menu_header.html('
Artemis
');
        $('.menu-action').html(' Collapse Menu');
        Cookies.set('acpState', 1);
    }
}

var CookieState = Cookies.get('acpState');
if (CookieState !== undefined) {
    if (CookieState == 0) {
        menu_wrap.addClass('collapsed');
        body_wrap.addClass('collapsed');
        app_wrap.addClass('collapsed');
        $('.menu-action').html('');
    }
}

menuToggle();

$('.menu-action').bind('click', function (e) {
    menu_wrap.toggleClass('collapsed');
    body_wrap.toggleClass('collapsed');
    app_wrap.toggleClass('collapsed');
    menuToggle();
    e.preventDefault();
});

// Tray Loader
$('a[data-load]').bind('click', function (e) {
    var body = $('div#main_content');
    var ca = $(this).data('load');
    var cl = $('#main_content').data('load');
    if (ca != cl) {
        if (!body.hasClass('open')) {
            body.addClass('open');
        }
    } else {
        body.toggleClass('open');
    }
    body.data('load', ca);
    menu_li.removeClass('active');
    $(this).closest('li').addClass('active');
    $('.app_page_list').hide();
    $('#ap-' + ca).show();
    e.preventDefault();
});

$('.main_content_overlay').bind('click', function (e) {
    menu_li.removeClass('active');
    body_wrap.removeClass('open');
});

// Hover on dropdowns
$('li.dropdown').hover(
    function (e) {
        $(this).addClass('open');
    },
    function (e) {
        $(this).removeClass('open');
    }
);<|endoftext|>