<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import { ThemeProvider } from 'styled-components';
import { mount as enzymeMount } from 'enzyme';
import { simulate, assertStyleMatch } from '../../__spec_helper__/test-utils';
import theme from '../../style/themes/small';
import ActionPopover from './index';
import { MenuButton, Menu } from './action-popover.style';
import Icon from '../icon';

jest.useFakeTimers();

describe('ActionPopover', () => {
  const container = { current: null };
  const wrapper = { current: null };

  const mount = (jsx) => {
    wrapper.current = enzymeMount(jsx, { attachTo: container.current });
  };

  const DOM = (jsx) => {
    ReactDOM.render(jsx, container.current);
  };

  const onClick = jest.fn();
  const onClickWrapper = arg => () => onClick(arg);

  function render(props = {}, renderer = mount) {
    const defaultProps = {
      children: [
        <ActionPopover.Item icon='email' { ...{ onClick: onClickWrapper('email') } }>Email Invoice</ActionPopover.Item>,
        <ActionPopover.Item icon='print' { ...{ onClick: onClickWrapper('print') } }>Print Invoice</ActionPopover.Item>,
        <ActionPopover.Item
          icon='pdf' { ...{ onClick: onClickWrapper('pdf') } }
          disabled
        >Download PDF
        </ActionPopover.Item>,
        <ActionPopover.Divider />,
        <ActionPopover.Item icon='csv' { ...{ onClick: onClickWrapper('csv') } }>Download CSV</ActionPopover.Item>
      ],
      ...props
    };

    renderer(
      <ThemeProvider { ...{ theme } }>
        <React.Fragment>
          <input id='before' />
          <ActionPopover { ...defaultProps } { ...props } />
          <input id='after' />
        </React.Fragment>
      </ThemeProvider>
    );
  }

  function getElements() {
    const cw = wrapper.current;
    if (cw) {
      return {
        items: cw.find(ActionPopover.Item),
        menubutton: cw.find(MenuButton),
        menu: cw.find(Menu)
      };
    }
    const button = document.querySelector('div[id^=ActionPopoverButton]');
    const icon = button.querySelector('span[data-component=icon');
    return { button, icon };
  }

  beforeEach(() => {
    container.current = document.createElement('div');
    document.body.appendChild(container.current);
  });

  afterEach(() => {
    document.body.removeChild(container.current);
    container.current = null;
    wrapper.current = null;
    onClick.mockReset();
  });

  it('renders in ReactDOM', () => {
    render(null, DOM);
  });

  it('displays the horizontal elipsis icon as the menu button', () => {
    render();
    const { menubutton } = getElements();
    const icon = menubutton.find(Icon).first();
    expect(icon.prop('type')).toBe('ellipsis_vertical');
  });

  it('has a default aria-label', () => {
    render();
    const { menubutton } = getElements();
    expect(menubutton.prop('aria-label')).toBe('actions');
  });

  it('renders with the menu closed by default', () => {
    render();
    const { menu } = getElements();
    assertStyleMatch({
      display: 'none'
    }, menu);
  });

  describe.each([
    ['Click handlers', 'Clicking', item => item.simulate('click')],
    ['Keypress handlers', 'Pressing Enter', item => simulate.keydown.pressEnter(item)]
  ])('%s', (group, prefix, mutator) => {
    beforeEach(() => {
      render();
      const { menubutton } = getElements();

      menubutton.simulate('click');
    });
    describe('MenuItem', () => {
      beforeEach(() => {
        const { items } = getElements();

        mutator(items.at(1));
      });
      it(`${prefix} calls the onClick handler`, () => {
        expect(onClick).toHaveBeenCalledWith('print');
      });

      it(`${prefix} closes the menu`, () => {
        const { menu } = getElements();

        assertStyleMatch({
          display: 'none'
        }, menu);
      });

      it(`${prefix} focuses the Menubutton`, () => {
        const { menubutton } = getElements();

        expect(menubutton).toBeFocused();
      });
    });
    describe('MenuItem (disabled)', () => {
      beforeEach(() => {
        const { items } = getElements();

        mutator(items.at(2));
      });
      it(`${prefix} does not call the onClick handler`, () => {
        expect(onClick).not.toHaveBeenCalled();
      });

      it(`${prefix} does not close the menu`, () => {
        const { menu } = getElements();

        assertStyleMatch({
          display: 'block'
        }, menu);
      });

      it(`${prefix} does not focus the Menubutton`, () => {
        const { menubutton } = getElements();

        expect(menubutton).not.toBeFocused();
      });
    });
  });

  describe('Click handlers', () => {
    describe('MenuButton', () => {
      beforeEach(() => {
        render();
        const { menubutton } = getElements();

        menubutton.simulate('click');
      });
      it('Clicking opens the menu', () => {
        const { menu } = getElements();
        assertStyleMatch({
          display: 'block'
        }, menu);
      });

      it('Clicking focuses the first element', () => {
        const { items } = getElements();

        expect(items.at(0)).toBeFocused();
      });

      it('Clicking closes the menu', () => {
        const { menubutton } = getElements();
        menubutton.simulate('click');

        const { menu } = getElements();
        assertStyleMatch({
          display: 'none'
        }, menu);
      });
    });

    describe('document', () => {
      it('Clicking on the component does not closes the menu using the document listener', () => {
        // This test doesn't really replicate the functionality but it is useful to check that the document listener
        // is filtering based on what element is clicked
        // In a normal situation the React SynteticEvent will bubble and trigger a handler which will close the menu
        // This triggers a DOMEvent bypassing the SynteticEvent listeners
        render();
        const { menubutton, items } = getElements();

        menubutton.simulate('click');

        act(() => {
          document.dispatchEvent(new CustomEvent('click', {
            detail: {
              enzymeTestingTarget: items.first().getDOMNode()
            }
          }));
        });

        act(() => {
          wrapper.current.update();
        });

        const { menu } = getElements();
        assertStyleMatch({
          display: 'block'
        }, menu);
      });

      it('Clicking elsewhere on the document closes the menu', () => {
        render();
        const { menubutton } = getElements();

        menubutton.simulate('click');

        act(() => {
          document.dispatchEvent(new CustomEvent('click', {
            detail: {
              enzymeTestingTarget: document.body
            }
          }));
        });

        act(() => {
          wrapper.current.update();
        });

        const { menu } = getElements();
        assertStyleMatch({
          display: 'none'
        }, menu);
      });
    });
  });

  describe('Keypress handlers', () => {
    describe('MenuButton', () => {
      it.each(
        ['DownArrow', 'Space', 'Enter', 'UpArrow']
      )('Pressing %s key opens the menu', (key) => {
        render();
        const { menubutton } = getElements();

        simulate.keydown[`press${key}`](menubutton);

        const { menu } = getElements();
        assertStyleMatch({
          display: 'block'
        }, menu);
      });

      it.each(
        ['DownArrow', 'Space', 'Enter']
      )('Pressing %s key selects the first item', (key) => {
        render();
        const { menubutton } = getElements();

        simulate.keydown[`press${key}`](menubutton);

        const { items } = getElements();
        expect(items.first()).toBeFocused();
      });

      it('Pressing UpArrow re-selects the previously selected item', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();
        simulate.keydown.pressDownArrow(items.first());
        simulate.keydown.pressDownArrow(items.at(1));
        simulate.keydown.pressEscape(items.at(2));

        simulate.keydown.pressUpArrow(menubutton);

        expect(items.at(2)).toBeFocused();
      });
    });

    describe('MenuItem', () => {
      it.each([
        ['Tab', item => simulate.keydown.pressTab(item)],
        ['Shift+Tab', (item) => {
          simulate.keydown.pressTab(item, { shiftKey: true });

          act(() => {
            jest.runAllTimers();
          });

          act(() => {
            wrapper.current.update();
          });
        }],
        ['Escape', item => simulate.keydown.pressEscape(item)]
      ])('Pressing %s key closes the menu', (key, mutator) => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        mutator(items.first());

        const { menu } = getElements();

        assertStyleMatch({
          display: 'none'
        }, menu);

        // See cypress test to check that focus moves to the next element, not possible in enzyme
        // TODO: add cypress test to check focus moves to next element, not possible in enzyme
      });

      it('Pressing Escape focuses the MenuButton', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        simulate.keydown.pressEscape(items.first());

        expect(menubutton).toBeFocused();
      });

      it.each([
        ['Down', 'next', '', (items) => {
          simulate.keydown.pressDownArrow(items.first());
          expect(items.at(1)).toBeFocused();

          simulate.keydown.pressDownArrow(items.at(1));
          // we're checking that we can focus the disabled item, this is intentional behaviour
          expect(items.at(2)).toBeFocused();
          expect(items.at(2).prop('disabled')).toBe(true);
          expect(items.at(2).getDOMNode().getAttribute('aria-disabled')).toBe('true');

          simulate.keydown.pressDownArrow(items.at(2));
          expect(items.at(3)).toBeFocused();
        }],
        ['Down', 'first', 'if the focus on the last item', (items) => {
          simulate.keydown.pressEnd(items.first());
          expect(items.last()).toBeFocused();

          simulate.keydown.pressDownArrow(items.last());
          expect(items.first()).toBeFocused();
        }],
        ['Up', 'previous', '', (items) => {
          simulate.keydown.pressDownArrow(items.first());
          simulate.keydown.pressDownArrow(items.at(1));
          simulate.keydown.pressDownArrow(items.at(2));

          simulate.keydown.pressUpArrow(items.at(3));
          expect(items.at(2)).toBeFocused();

          simulate.keydown.pressUpArrow(items.at(2));
          expect(items.at(1)).toBeFocused();

          simulate.keydown.pressUpArrow(items.at(1));
          expect(items.first()).toBeFocused();
        }],
        ['Up', 'last', 'if the focus is on the first item', (items) => {
          simulate.keydown.pressUpArrow(items.first());

          expect(items.last()).toBeFocused();
        }],
        ['Home', 'first', '', (items) => {
          simulate.keydown.pressDownArrow(items.first());

          simulate.keydown.pressHome(items.at(1));

          expect(items.first()).toBeFocused();
        }],
        ['End', 'first', '', (items) => {
          simulate.keydown.pressEnd(items.first());

          expect(items.last()).toBeFocused();
        }]
      ])('Pressing %s focuses the %s item %s', (key, direction, condition, mutator) => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        mutator(items);
      });

      it('Pressing Space does nothing', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        simulate.keydown.pressSpace(items.at(0));

        const { menu } = getElements();
        expect(items.at(0)).toBeFocused();
        assertStyleMatch({
          display: 'block'
        }, menu);
      });

      it('Pressing a-z selects the next item in the list starting with the letter that was pressed', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        // moves to first element starting with P
        simulate.keydown.pressP(items.first());
        expect(items.at(1)).toBeFocused();

        // moves to first element starting with D
        simulate.keydown.pressD(items.at(1));
        // we're checking that we can focus the disabled item, this is intentional behaviour
        expect(items.at(2)).toBeFocused();
        expect(items.at(2).prop('disabled')).toBe(true);
        expect(items.at(2).getDOMNode().getAttribute('aria-disabled')).toBe('true');

        // moves to next element starting with D
        simulate.keydown.pressD(items.at(2));
        expect(items.at(3)).toBeFocused();

        // moves to next element starting with D wrapping back to the first element
        simulate.keydown.pressD(items.at(3));
        expect(items.at(2)).toBeFocused();

        // does nothing when there are no matches
        simulate.keydown.pressZ(items.at(2));
        expect(items.at(2)).toBeFocused();

        // does nothing withen a number key is pressed
        simulate.keydown.press1(items.at(2));
        expect(items.at(2)).toBeFocused();
      });
    });
  });

  it('validates the children prop', () => {
    jest.spyOn(global.console, 'error').mockImplementation(() => {});
    ReactDOM.render(
      <ThemeProvider { ...{ theme } }>
        <ActionPopover>
invalid children
</ActionPopover>
      </ThemeProvider>, container.current
    );
    expect(console.error).toHaveBeenCalledWith('Warning: Failed prop type: `ActionPopover` only accepts children of'
    + ' type `ActionPopover.Item` and `ActionPopover.Divider`.\n    in ActionPopover');
    global.console.error.mockReset();
  });
});
<commit_msg>test: update comment in test FE-1253<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import { ThemeProvider } from 'styled-components';
import { mount as enzymeMount } from 'enzyme';
import { simulate, assertStyleMatch } from '../../__spec_helper__/test-utils';
import theme from '../../style/themes/small';
import ActionPopover from './index';
import { MenuButton, Menu } from './action-popover.style';
import Icon from '../icon';

jest.useFakeTimers();

describe('ActionPopover', () => {
  const container = { current: null };
  const wrapper = { current: null };

  const mount = (jsx) => {
    wrapper.current = enzymeMount(jsx, { attachTo: container.current });
  };

  const DOM = (jsx) => {
    ReactDOM.render(jsx, container.current);
  };

  const onClick = jest.fn();
  const onClickWrapper = arg => () => onClick(arg);

  function render(props = {}, renderer = mount) {
    const defaultProps = {
      children: [
        <ActionPopover.Item icon='email' { ...{ onClick: onClickWrapper('email') } }>Email Invoice</ActionPopover.Item>,
        <ActionPopover.Item icon='print' { ...{ onClick: onClickWrapper('print') } }>Print Invoice</ActionPopover.Item>,
        <ActionPopover.Item
          icon='pdf' { ...{ onClick: onClickWrapper('pdf') } }
          disabled
        >Download PDF
        </ActionPopover.Item>,
        <ActionPopover.Divider />,
        <ActionPopover.Item icon='csv' { ...{ onClick: onClickWrapper('csv') } }>Download CSV</ActionPopover.Item>
      ],
      ...props
    };

    renderer(
      <ThemeProvider { ...{ theme } }>
        <React.Fragment>
          <input id='before' />
          <ActionPopover { ...defaultProps } { ...props } />
          <input id='after' />
        </React.Fragment>
      </ThemeProvider>
    );
  }

  function getElements() {
    const cw = wrapper.current;
    if (cw) {
      return {
        items: cw.find(ActionPopover.Item),
        menubutton: cw.find(MenuButton),
        menu: cw.find(Menu)
      };
    }
    const button = document.querySelector('div[id^=ActionPopoverButton]');
    const icon = button.querySelector('span[data-component=icon');
    return { button, icon };
  }

  beforeEach(() => {
    container.current = document.createElement('div');
    document.body.appendChild(container.current);
  });

  afterEach(() => {
    document.body.removeChild(container.current);
    container.current = null;
    wrapper.current = null;
    onClick.mockReset();
  });

  it('renders in ReactDOM', () => {
    render(null, DOM);
  });

  it('displays the horizontal elipsis icon as the menu button', () => {
    render();
    const { menubutton } = getElements();
    const icon = menubutton.find(Icon).first();
    expect(icon.prop('type')).toBe('ellipsis_vertical');
  });

  it('has a default aria-label', () => {
    render();
    const { menubutton } = getElements();
    expect(menubutton.prop('aria-label')).toBe('actions');
  });

  it('renders with the menu closed by default', () => {
    render();
    const { menu } = getElements();
    assertStyleMatch({
      display: 'none'
    }, menu);
  });

  describe.each([
    ['Click handlers', 'Clicking', item => item.simulate('click')],
    ['Keypress handlers', 'Pressing Enter', item => simulate.keydown.pressEnter(item)]
  ])('%s', (group, prefix, mutator) => {
    beforeEach(() => {
      render();
      const { menubutton } = getElements();

      menubutton.simulate('click');
    });
    describe('MenuItem', () => {
      beforeEach(() => {
        const { items } = getElements();

        mutator(items.at(1));
      });
      it(`${prefix} calls the onClick handler`, () => {
        expect(onClick).toHaveBeenCalledWith('print');
      });

      it(`${prefix} closes the menu`, () => {
        const { menu } = getElements();

        assertStyleMatch({
          display: 'none'
        }, menu);
      });

      it(`${prefix} focuses the Menubutton`, () => {
        const { menubutton } = getElements();

        expect(menubutton).toBeFocused();
      });
    });
    describe('MenuItem (disabled)', () => {
      beforeEach(() => {
        const { items } = getElements();

        mutator(items.at(2));
      });
      it(`${prefix} does not call the onClick handler`, () => {
        expect(onClick).not.toHaveBeenCalled();
      });

      it(`${prefix} does not close the menu`, () => {
        const { menu } = getElements();

        assertStyleMatch({
          display: 'block'
        }, menu);
      });

      it(`${prefix} does not focus the Menubutton`, () => {
        const { menubutton } = getElements();

        expect(menubutton).not.toBeFocused();
      });
    });
  });

  describe('Click handlers', () => {
    describe('MenuButton', () => {
      beforeEach(() => {
        render();
        const { menubutton } = getElements();

        menubutton.simulate('click');
      });
      it('Clicking opens the menu', () => {
        const { menu } = getElements();
        assertStyleMatch({
          display: 'block'
        }, menu);
      });

      it('Clicking focuses the first element', () => {
        const { items } = getElements();

        expect(items.at(0)).toBeFocused();
      });

      it('Clicking closes the menu', () => {
        const { menubutton } = getElements();
        menubutton.simulate('click');

        const { menu } = getElements();
        assertStyleMatch({
          display: 'none'
        }, menu);
      });
    });

    describe('document', () => {
      it('Clicking on the component does not closes the menu using the document listener', () => {
        // This test doesn't really replicate the functionality but it is useful to check that the document listener
        // is filtering based on what element is clicked
        // In a normal situation the React SynteticEvent will bubble and trigger a handler which will close the menu
        // This triggers a DOMEvent bypassing the SynteticEvent listeners
        render();
        const { menubutton, items } = getElements();

        menubutton.simulate('click');

        act(() => {
          document.dispatchEvent(new CustomEvent('click', {
            detail: {
              enzymeTestingTarget: items.first().getDOMNode()
            }
          }));
        });

        act(() => {
          wrapper.current.update();
        });

        const { menu } = getElements();
        assertStyleMatch({
          display: 'block'
        }, menu);
      });

      it('Clicking elsewhere on the document closes the menu', () => {
        render();
        const { menubutton } = getElements();

        menubutton.simulate('click');

        act(() => {
          document.dispatchEvent(new CustomEvent('click', {
            detail: {
              enzymeTestingTarget: document.body
            }
          }));
        });

        act(() => {
          wrapper.current.update();
        });

        const { menu } = getElements();
        assertStyleMatch({
          display: 'none'
        }, menu);
      });
    });
  });

  describe('Keypress handlers', () => {
    describe('MenuButton', () => {
      it.each(
        ['DownArrow', 'Space', 'Enter', 'UpArrow']
      )('Pressing %s key opens the menu', (key) => {
        render();
        const { menubutton } = getElements();

        simulate.keydown[`press${key}`](menubutton);

        const { menu } = getElements();
        assertStyleMatch({
          display: 'block'
        }, menu);
      });

      it.each(
        ['DownArrow', 'Space', 'Enter']
      )('Pressing %s key selects the first item', (key) => {
        render();
        const { menubutton } = getElements();

        simulate.keydown[`press${key}`](menubutton);

        const { items } = getElements();
        expect(items.first()).toBeFocused();
      });

      it('Pressing UpArrow re-selects the previously selected item', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();
        simulate.keydown.pressDownArrow(items.first());
        simulate.keydown.pressDownArrow(items.at(1));
        simulate.keydown.pressEscape(items.at(2));

        simulate.keydown.pressUpArrow(menubutton);

        expect(items.at(2)).toBeFocused();
      });
    });

    describe('MenuItem', () => {
      it.each([
        ['Tab', item => simulate.keydown.pressTab(item)],
        ['Shift+Tab', (item) => {
          simulate.keydown.pressTab(item, { shiftKey: true });

          act(() => {
            jest.runAllTimers();
          });

          act(() => {
            wrapper.current.update();
          });
        }],
        ['Escape', item => simulate.keydown.pressEscape(item)]
      ])('Pressing %s key closes the menu', (key, mutator) => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        mutator(items.first());

        const { menu } = getElements();

        assertStyleMatch({
          display: 'none'
        }, menu);
        // FIXME: Test pressing Tab moves focus to the next element
        // FIXME: Test pressing Shift+Tab moves focus to the previous element
        // It's not possible to test this in enzyme because JSDOM does not support user events. It's also not
        // possible to test it in cypress because cypress uses syntetic events. We should add a test for this when
        // support for native events is implemented in cypress https://github.com/cypress-io/cypress/issues/311
      });

      it('Pressing Escape focuses the MenuButton', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        simulate.keydown.pressEscape(items.first());

        expect(menubutton).toBeFocused();
      });

      it.each([
        ['Down', 'next', '', (items) => {
          simulate.keydown.pressDownArrow(items.first());
          expect(items.at(1)).toBeFocused();

          simulate.keydown.pressDownArrow(items.at(1));
          // we're checking that we can focus the disabled item, this is intentional behaviour
          expect(items.at(2)).toBeFocused();
          expect(items.at(2).prop('disabled')).toBe(true);
          expect(items.at(2).getDOMNode().getAttribute('aria-disabled')).toBe('true');

          simulate.keydown.pressDownArrow(items.at(2));
          expect(items.at(3)).toBeFocused();
        }],
        ['Down', 'first', 'if the focus on the last item', (items) => {
          simulate.keydown.pressEnd(items.first());
          expect(items.last()).toBeFocused();

          simulate.keydown.pressDownArrow(items.last());
          expect(items.first()).toBeFocused();
        }],
        ['Up', 'previous', '', (items) => {
          simulate.keydown.pressDownArrow(items.first());
          simulate.keydown.pressDownArrow(items.at(1));
          simulate.keydown.pressDownArrow(items.at(2));

          simulate.keydown.pressUpArrow(items.at(3));
          expect(items.at(2)).toBeFocused();

          simulate.keydown.pressUpArrow(items.at(2));
          expect(items.at(1)).toBeFocused();

          simulate.keydown.pressUpArrow(items.at(1));
          expect(items.first()).toBeFocused();
        }],
        ['Up', 'last', 'if the focus is on the first item', (items) => {
          simulate.keydown.pressUpArrow(items.first());

          expect(items.last()).toBeFocused();
        }],
        ['Home', 'first', '', (items) => {
          simulate.keydown.pressDownArrow(items.first());

          simulate.keydown.pressHome(items.at(1));

          expect(items.first()).toBeFocused();
        }],
        ['End', 'first', '', (items) => {
          simulate.keydown.pressEnd(items.first());

          expect(items.last()).toBeFocused();
        }]
      ])('Pressing %s focuses the %s item %s', (key, direction, condition, mutator) => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        mutator(items);
      });

      it('Pressing Space does nothing', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        simulate.keydown.pressSpace(items.at(0));

        const { menu } = getElements();
        expect(items.at(0)).toBeFocused();
        assertStyleMatch({
          display: 'block'
        }, menu);
      });

      it('Pressing a-z selects the next item in the list starting with the letter that was pressed', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        // moves to first element starting with P
        simulate.keydown.pressP(items.first());
        expect(items.at(1)).toBeFocused();

        // moves to first element starting with D
        simulate.keydown.pressD(items.at(1));
        // we're checking that we can focus the disabled item, this is intentional behaviour
        expect(items.at(2)).toBeFocused();
        expect(items.at(2).prop('disabled')).toBe(true);
        expect(items.at(2).getDOMNode().getAttribute('aria-disabled')).toBe('true');

        // moves to next element starting with D
        simulate.keydown.pressD(items.at(2));
        expect(items.at(3)).toBeFocused();

        // moves to next element starting with D wrapping back to the first element
        simulate.keydown.pressD(items.at(3));
        expect(items.at(2)).toBeFocused();

        // does nothing when there are no matches
        simulate.keydown.pressZ(items.at(2));
        expect(items.at(2)).toBeFocused();

        // does nothing withen a number key is pressed
        simulate.keydown.press1(items.at(2));
        expect(items.at(2)).toBeFocused();
      });
    });
  });

  it('validates the children prop', () => {
    jest.spyOn(global.console, 'error').mockImplementation(() => {});
    ReactDOM.render(
      <ThemeProvider { ...{ theme } }>
        <ActionPopover>
invalid children
</ActionPopover>
      </ThemeProvider>, container.current
    );
    expect(console.error).toHaveBeenCalledWith('Warning: Failed prop type: `ActionPopover` only accepts children of'
    + ' type `ActionPopover.Item` and `ActionPopover.Divider`.\n    in ActionPopover');
    global.console.error.mockReset();
  });
});
<|endoftext|>


<commit_before>const resolve = require("path").resolve

const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const VueLoaderPlugin = require("vue-loader/lib/plugin")
const VuetifyLoaderPlugin = require("vuetify-loader/lib/plugin")

module.exports = {
  mode: process.env.NODE_ENV,

  entry: "./src/client/index.js",

  output: {
    filename: "bundle.js",
    path: resolve(__dirname, "..", "public"),
    sourceMapFilename: "[file].map"
  },

  devtool: "#source-map",

  module: {
    rules: [{
      test: /\.js$/,
      loader: "babel-loader",
      exclude: /node_modules/
    }, {
      test: /\.js$/,
      use: "imports-loader?define=>undefined",
      include: /node_modules\/chess.js/,
    }, {
      test: /\.vue$/,
      loader: "vue-loader",
    }, {
      test: /\.css$/,
      use: [
        MiniCssExtractPlugin.loader,
        "css-loader"
      ]
    }, {
      test: /\.scss$/,
      use: [
        MiniCssExtractPlugin.loader,
        "css-loader",
        "sass-loader"
      ]
    }, {
      test: /\.styl$/,
      use: [
        MiniCssExtractPlugin.loader,
        "css-loader",
        "stylus-loader"
      ]
    }]
  },

  resolve: {
    extensions: [".js", ".vue"],
    alias: {
      "~": resolve(__dirname, "..", "src"),
      "vue$": "vue/dist/vue.esm.js",
    }
  },

  plugins: [
    new MiniCssExtractPlugin({
      filename: "bundle.css"
    }),

    new VueLoaderPlugin(),
    new VuetifyLoaderPlugin()
  ]
}
<commit_msg>remove trailing comma<commit_after>const resolve = require("path").resolve

const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const VueLoaderPlugin = require("vue-loader/lib/plugin")
const VuetifyLoaderPlugin = require("vuetify-loader/lib/plugin")

module.exports = {
  mode: process.env.NODE_ENV,

  entry: "./src/client/index.js",

  output: {
    filename: "bundle.js",
    path: resolve(__dirname, "..", "public"),
    sourceMapFilename: "[file].map"
  },

  devtool: "#source-map",

  module: {
    rules: [{
      test: /\.js$/,
      loader: "babel-loader",
      exclude: /node_modules/
    }, {
      test: /\.js$/,
      use: "imports-loader?define=>undefined",
      include: /node_modules\/chess.js/,
    }, {
      test: /\.vue$/,
      loader: "vue-loader",
    }, {
      test: /\.css$/,
      use: [
        MiniCssExtractPlugin.loader,
        "css-loader"
      ]
    }, {
      test: /\.scss$/,
      use: [
        MiniCssExtractPlugin.loader,
        "css-loader",
        "sass-loader"
      ]
    }, {
      test: /\.styl$/,
      use: [
        MiniCssExtractPlugin.loader,
        "css-loader",
        "stylus-loader"
      ]
    }]
  },

  resolve: {
    extensions: [".js", ".vue"],
    alias: {
      "~": resolve(__dirname, "..", "src"),
      "vue$": "vue/dist/vue.esm.js"
    }
  },

  plugins: [
    new MiniCssExtractPlugin({
      filename: "bundle.css"
    }),

    new VueLoaderPlugin(),
    new VuetifyLoaderPlugin()
  ]
}
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import cn from 'classnames';
import { propTypes as mobxPropTypes } from 'mobx-react';

export { React, cn, ReactDOM, mobxPropTypes };
export { observable, action, computed } from 'mobx';
export { observer, inject } from 'mobx-react';
<commit_msg>export some more helpful decorators<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import cn from 'classnames';
import { propTypes as mobxPropTypes } from 'mobx-react';

export { React, cn, ReactDOM, mobxPropTypes };
export { observable, action, computed } from 'mobx';
export { observer, inject, Provider } from 'mobx-react';
<|endoftext|>


<commit_before><commit_msg>メニューの有効・無効を切り替える<commit_after><|endoftext|>


<commit_before>import d3 from 'd3';
import PrometheusGraph from '~/monitoring/prometheus_graph';
import Deployments from '~/monitoring/deployments';
import { prometheusMockData } from './prometheus_mock_data';

describe('Metrics deployments', () => {
  const fixtureName = 'static/environments/metrics.html.raw';
  let deployment;
  let prometheusGraph;

  const graphElement = () => document.querySelector('.prometheus-graph');

  preloadFixtures(fixtureName);

  beforeEach((done) => {
    // Setup the view
    loadFixtures(fixtureName);

    d3.selectAll('.prometheus-graph')
      .append('g')
      .attr('class', 'graph-container');

    prometheusGraph = new PrometheusGraph();
    deployment = new Deployments(1000, 500);

    spyOn(prometheusGraph, 'init');
    spyOn($, 'ajax').and.callFake(() => {
      const d = $.Deferred();
      d.resolve({
        deployments: [{
          id: 1,
          created_at: deployment.chartData[10].time,
          sha: 'testing',
          tag: false,
          ref: {
            name: 'testing',
          },
        }, {
          id: 2,
          created_at: deployment.chartData[15].time,
          sha: '',
          tag: true,
          ref: {
            name: 'tag',
          },
        }],
      });

      setTimeout(done);

      return d.promise();
    });

    prometheusGraph.transformData(prometheusMockData.metrics);

    deployment.init(prometheusGraph.data.memory_values);
  });

  it('creates line on graph for deploment', () => {
    expect(
      graphElement().querySelectorAll('.deployment-line').length,
    ).toBe(2);
  });

  it('creates hidden deploy boxes', () => {
    expect(
      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box').length,
    ).toBe(2);
  });

  it('hides the info boxes by default', () => {
    expect(
      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
    ).toBe(2);
  });

  it('shows sha short code when tag is false', () => {
    expect(
      graphElement().querySelector('.deploy-info-1 .js-deploy-info-box').textContent.trim(),
    ).toContain('testin');
  });

  it('shows ref name when tag is true', () => {
    expect(
      graphElement().querySelector('.deploy-info-2 .js-deploy-info-box').textContent.trim(),
    ).toContain('tag');
  });

  it('shows info box when moving mouse over line', () => {
    deployment.mouseOverDeployInfo(deployment.data[0].xPos);

    expect(
      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
    ).toBe(1);

    expect(
      graphElement().querySelector('.deploy-info-1 .js-deploy-info-box.hidden'),
    ).toBeNull();
  });

  it('hides previously visible info box when moving mouse away', () => {
    deployment.mouseOverDeployInfo(500);

    expect(
      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
    ).toBe(2);

    expect(
      graphElement().querySelector('.deploy-info-1 .js-deploy-info-box.hidden'),
    ).not.toBeNull();
  });
});
<commit_msg>Updated specs<commit_after>import d3 from 'd3';
import PrometheusGraph from '~/monitoring/prometheus_graph';
import Deployments from '~/monitoring/deployments';
import { prometheusMockData } from './prometheus_mock_data';

describe('Metrics deployments', () => {
  const fixtureName = 'static/environments/metrics.html.raw';
  let deployment;
  let prometheusGraph;

  const graphElement = () => document.querySelector('.prometheus-graph');

  preloadFixtures(fixtureName);

  beforeEach((done) => {
    // Setup the view
    loadFixtures(fixtureName);

    d3.selectAll('.prometheus-graph')
      .append('g')
      .attr('class', 'graph-container');

    prometheusGraph = new PrometheusGraph();
    deployment = new Deployments(1000, 500);

    spyOn(prometheusGraph, 'init');
    spyOn($, 'ajax').and.callFake(() => {
      const d = $.Deferred();
      d.resolve({
        deployments: [{
          id: 1,
          created_at: deployment.chartData[10].time,
          sha: 'testing',
          tag: false,
          ref: {
            name: 'testing',
          },
        }, {
          id: 2,
          created_at: deployment.chartData[15].time,
          sha: '',
          tag: true,
          ref: {
            name: 'tag',
          },
        }],
      });

      setTimeout(done);

      return d.promise();
    });

    prometheusGraph.configureGraph();
    prometheusGraph.transformData(prometheusMockData.metrics);

    deployment.init(prometheusGraph.graphSpecificProperties.memory_values.data);
  });

  it('creates line on graph for deploment', () => {
    expect(
      graphElement().querySelectorAll('.deployment-line').length,
    ).toBe(2);
  });

  it('creates hidden deploy boxes', () => {
    expect(
      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box').length,
    ).toBe(2);
  });

  it('hides the info boxes by default', () => {
    expect(
      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
    ).toBe(2);
  });

  it('shows sha short code when tag is false', () => {
    expect(
      graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box').textContent.trim(),
    ).toContain('testin');
  });

  it('shows ref name when tag is true', () => {
    expect(
      graphElement().querySelector('.deploy-info-2-cpu_values .js-deploy-info-box').textContent.trim(),
    ).toContain('tag');
  });

  it('shows info box when moving mouse over line', () => {
    deployment.mouseOverDeployInfo(deployment.data[0].xPos, 'cpu_values');

    expect(
      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
    ).toBe(1);

    expect(
      graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'),
    ).toBeNull();
  });

  it('hides previously visible info box when moving mouse away', () => {
    deployment.mouseOverDeployInfo(500, 'cpu_values');

    expect(
      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
    ).toBe(2);

    expect(
      graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'),
    ).not.toBeNull();
  });
});
<|endoftext|>


<commit_before>define
([
    'jquery', 'jquery/superdesk', 'dust/core', 'utils/sha512', 'jquery/tmpl', 'jquery/rest', 'bootstrap',
    'tmpl!auth',
],
function($, superdesk, dust, jsSHA)
{
<<<<<<< HEAD
    var AuthLogin = function(username, password, logintoken){
=======
    var AuthDetails = function(username){
		var authDetails = new $.rest('Superdesk/User');
		authDetails.resetData().xfilter('Name,Id,EMail').select({ name: username }).done(function(users){
			var user = users.UserList[0];
			localStorage.setItem('superdesk.login.id', user.Id);
			localStorage.setItem('superdesk.login.name', user.Name);
			localStorage.setItem('superdesk.login.email', user.EMail);
		});
		return $(authDetails);
	},
	AuthLogin = function(username, password, logintoken){
>>>>>>> 718d8c014dc7f1c47606de731769286a6bcebb81
		var shaObj = new jsSHA(logintoken, "ASCII"),shaPassword = new jsSHA(password, "ASCII"),
			authLogin = new $.rest('Authentication');
			authLogin.resetData().insert({
			UserName: username,
			LoginToken: logintoken,
			HashedLoginToken: shaObj.getHMAC(username+shaPassword.getHash("SHA-512", "HEX"), "ASCII", "SHA-512", "HEX")
		}).done(function(user){
			localStorage.setItem('superdesk.login.session', user.Session);
<<<<<<< HEAD
			localStorage.setItem('superdesk.login.id', user.Id);
			localStorage.setItem('superdesk.login.name', user.UserName);
			localStorage.setItem('superdesk.login.email', user.EMail);
			$(authLogin).trigger('success');
=======
			//localStorage.setItem('superdesk.login.id', user.Id);
			localStorage.setItem('superdesk.login.name', user.UserName);
			localStorage.setItem('superdesk.login.email', user.EMail);
			$(authLogin).trigger('success');
/*			authDetails = AuthDetails(username);
			$(authDetails).on('failed', function(){
				$(authLogin).trigger('failed', 'authDetails');
			});
*/
>>>>>>> 718d8c014dc7f1c47606de731769286a6bcebb81
		});
		return $(authLogin);
	},
	AuthToken = function(username, password) {
		var authToken = new $.rest('Authentication');
		authToken.resetData().select({ userName: username }).done(
			function(data){
				authLogin = AuthLogin(username, password, data.Token);
				authLogin.on('failed', function(){
					$(authToken).trigger('failed', 'authToken');
				}).on('success', function(){
					$(authToken).trigger('success');
				});
			}
		);
		return $(authToken);
	},
	AuthApp =
    {
        success: $.noop,
        require: function()
        {
			if(this.showed) return;
            var self = this; // rest
			self.showed = true;
            $.tmpl('auth', null, function(e, o)
            {
                var dialog = $(o).eq(0).dialog
                ({
                    draggable: false,
                    resizable: false,
                    modal: true,
                    width: "40.1709%",
                    buttons:
                    [
                         { text: "Login", click: function(){ $(form).trigger('submit'); }, class: "btn btn-primary"},
                         { text: "Close", click: function(){ $(this).dialog('close'); }, class: "btn"}
                    ]
                }),
                    form = dialog.find('form');
                form.off('submit.superdesk')//
                .on('submit.superdesk', function(event)
                {
                    var username = $(this).find('#username'), password=$(this).find('#password');
					AuthToken(username.val(), password.val()).on('failed',function(evt, type){
						username.val('');
						password.val('')
					}).on('success', function(){
                        AuthApp.success && AuthApp.success.apply();
						$(dialog).dialog('close');
						self.showed = false;
					});
                    event.preventDefault();

                });
            });
        }
    };
    return AuthApp;
});<commit_msg>NR-226: Create login functionality in Ally framework<commit_after>define
([
    'jquery', 'jquery/superdesk', 'dust/core', 'utils/sha512', 'jquery/tmpl', 'jquery/rest', 'bootstrap',
    'tmpl!auth',
],
function($, superdesk, dust, jsSHA)
{
<<<<<<< HEAD
    var AuthLogin = function(username, password, logintoken){
=======
    var AuthDetails = function(username){
		var authDetails = new $.rest('Superdesk/User');
		authDetails.resetData().xfilter('Name,Id,EMail').select({ name: username }).done(function(users){
			var user = users.UserList[0];
			localStorage.setItem('superdesk.login.id', user.Id);
			localStorage.setItem('superdesk.login.name', user.Name);
			localStorage.setItem('superdesk.login.email', user.EMail);
		});
		return $(authDetails);
	},
	AuthLogin = function(username, password, logintoken){
>>>>>>> 718d8c014dc7f1c47606de731769286a6bcebb81
		var shaObj = new jsSHA(logintoken, "ASCII"),shaPassword = new jsSHA(password, "ASCII"),
			authLogin = new $.rest('Authentication');
			authLogin.resetData().insert({
			UserName: username,
			LoginToken: logintoken,
			HashedLoginToken: shaObj.getHMAC(username+shaPassword.getHash("SHA-512", "HEX"), "ASCII", "SHA-512", "HEX")
		}).done(function(user){
			localStorage.setItem('superdesk.login.session', user.Session);
			localStorage.setItem('superdesk.login.id', user.Id);
			localStorage.setItem('superdesk.login.name', user.UserName);
			localStorage.setItem('superdesk.login.email', user.EMail);
			$(authLogin).trigger('success');
		});
		return $(authLogin);
	},
	AuthToken = function(username, password) {
		var authToken = new $.rest('Authentication');
		authToken.resetData().select({ userName: username }).done(
			function(data){
				authLogin = AuthLogin(username, password, data.Token);
				authLogin.on('failed', function(){
					$(authToken).trigger('failed', 'authToken');
				}).on('success', function(){
					$(authToken).trigger('success');
				});
			}
		);
		return $(authToken);
	},
	AuthApp =
    {
        success: $.noop,
        require: function()
        {
			if(this.showed) return;
            var self = this; // rest
			self.showed = true;
            $.tmpl('auth', null, function(e, o)
            {
                var dialog = $(o).eq(0).dialog
                ({
                    draggable: false,
                    resizable: false,
                    modal: true,
                    width: "40.1709%",
                    buttons:
                    [
                         { text: "Login", click: function(){ $(form).trigger('submit'); }, class: "btn btn-primary"},
                         { text: "Close", click: function(){ $(this).dialog('close'); }, class: "btn"}
                    ]
                }),
                    form = dialog.find('form');
                form.off('submit.superdesk')//
                .on('submit.superdesk', function(event)
                {
                    var username = $(this).find('#username'), password=$(this).find('#password');
					AuthToken(username.val(), password.val()).on('failed',function(evt, type){
						username.val('');
						password.val('')
					}).on('success', function(){
                        AuthApp.success && AuthApp.success.apply();
						$(dialog).dialog('close');
						self.showed = false;
					});
                    event.preventDefault();

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


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Trans, t } from '@lingui/macro';

import { findByBossId } from 'raids';
import lazyLoadComponent from 'common/lazyLoadComponent';
import retryingPromise from 'common/retryingPromise';
import makeWclUrl from 'common/makeWclUrl';
import Tooltip from 'common/Tooltip';
import { getResultTab } from 'interface/selectors/url/report';
import { hasPremium } from 'interface/selectors/user';
import Warning from 'interface/common/Alert/Warning';
import Ad from 'interface/common/Ad';
import ReadableList from 'interface/common/ReadableList';
import Contributor from 'interface/contributor/Button';
import WipefestLogo from 'interface/images/Wipefest-logo.png';
import { i18n } from 'interface/RootLocalizationProvider';
import LoadingBar from 'interface/layout/NavigationBar/LoadingBar';
import Panel from 'interface/others/Panel';
import ChangelogTab from 'interface/others/ChangelogTab';
import Checklist from 'parser/shared/modules/features/Checklist/Module';
import StatTracker from 'parser/shared/modules/StatTracker';

import './Results.scss';
import Header from './Header';
import About from './About';
import Overview from './Overview';
import Statistics from './Statistics';
import Character from './Character';
import EncounterStats from './EncounterStats';
import EVENT_PARSING_STATE from '../EVENT_PARSING_STATE';
import BOSS_PHASES_STATE from '../BOSS_PHASES_STATE';
import ScrollToTop from './ScrollToTop';

const TimelineTab = lazyLoadComponent(() => retryingPromise(() => import(/* webpackChunkName: 'TimelineTab' */ './Timeline/Container').then(exports => exports.default)), 0);
const EventsTab = lazyLoadComponent(() => retryingPromise(() => import(/* webpackChunkName: 'EventsTab' */ 'interface/others/EventsTab').then(exports => exports.default)));

const CORE_TABS = {
  OVERVIEW: 'overview',
  STATISTICS: 'statistics',
  TIMELINE: 'timeline',
  CHARACTER: 'character',
  EVENTS: 'events',
  ABOUT: 'about',
};

class Results extends React.PureComponent {
  static propTypes = {
    parser: PropTypes.shape({
    }),
    characterProfile: PropTypes.object,
    selectedTab: PropTypes.string,
    makeTabUrl: PropTypes.func.isRequired,
    report: PropTypes.shape({
      code: PropTypes.string.isRequired,
    }).isRequired,
    fight: PropTypes.shape({
      start_time: PropTypes.number.isRequired,
      end_time: PropTypes.number.isRequired,
      boss: PropTypes.number.isRequired,
    }).isRequired,
    player: PropTypes.shape({
      name: PropTypes.string.isRequired,
    }).isRequired,
    isLoadingParser: PropTypes.bool,
    isLoadingEvents: PropTypes.bool,
    bossPhaseEventsLoadingState: PropTypes.oneOf(BOSS_PHASES_STATE),
    isLoadingCharacterProfile: PropTypes.bool,
    parsingState: PropTypes.oneOf(EVENT_PARSING_STATE),
    progress: PropTypes.number,
    premium: PropTypes.bool,
  };
  static childContextTypes = {
    updateResults: PropTypes.func.isRequired,
    parser: PropTypes.object,
  };
  getChildContext() {
    return {
      updateResults: this.forceUpdate.bind(this),
      parser: this.props.parser,
    };
  }
  static contextTypes = {
    config: PropTypes.object.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      adjustForDowntime: false,
    };
  }

  componentDidMount() {
    this.scrollToTop();
  }
  componentDidUpdate(prevProps, prevState, prevContext) {
    if (this.props.selectedTab !== prevProps.selectedTab) {
      // TODO: To improve user experience we could try to avoid scrolling when the header is still within vision.
      this.scrollToTop();
    }
  }
  scrollToTop() {
    window.scrollTo(0, 0);
  }

  get warning() {
    const parser = this.props.parser;
    const boss = parser.boss;
    if (boss && boss.fight.resultsWarning) {
      return boss.fight.resultsWarning;
    }
    return null;
  }
  get isLoading() {
    return this.props.isLoadingParser
      || this.props.isLoadingEvents
      || this.props.bossPhaseEventsLoadingState === BOSS_PHASES_STATE.LOADING
      || this.props.isLoadingCharacterProfile
      || this.props.parsingState !== EVENT_PARSING_STATE.DONE;
  }

  renderContent(selectedTab, results) {
    const { parser } = this.props;

    switch (selectedTab) {
      case CORE_TABS.OVERVIEW: {
        if (this.isLoading) {
          return this.renderLoadingIndicator();
        }
        const checklist = parser.getModule(Checklist, true);
        return (
          <Overview
            checklist={checklist && checklist.render()}
            issues={results.issues}
          />
        );
      }
      case CORE_TABS.STATISTICS:
        if (this.isLoading) {
          return this.renderLoadingIndicator();
        }
        return (
          <Statistics parser={parser}>{results.statistics}</Statistics>
        );
      case CORE_TABS.TIMELINE:
        if (this.isLoading) {
          return this.renderLoadingIndicator();
        }
        return (
          <TimelineTab parser={parser} />
        );
      case CORE_TABS.EVENTS:
        if (this.isLoading) {
          return this.renderLoadingIndicator();
        }
        return (

            <EventsTab parser={parser} />

        );
      case CORE_TABS.CHARACTER: {
        if (this.isLoading) {
          return this.renderLoadingIndicator();
        }
        const statTracker = parser.getModule(StatTracker);

        return (

            <Character
              statTracker={statTracker}
              combatant={parser.selectedCombatant}
            />
            <EncounterStats
              currentBoss={parser.fight.boss}
              difficulty={parser.fight.difficulty}
              spec={parser.selectedCombatant._combatantInfo.specID}
              duration={parser.fight.end_time - parser.fight.start_time}
            />

        );
      }
      case CORE_TABS.ABOUT: {
        const config = this.context.config;
        return (

            <About config={config} />
            <ChangelogTab />

        );
      }
      default:
        if (this.isLoading) {
          return this.renderLoadingIndicator();
        }
        return (

            {results.tabs.find(tab => tab.url === selectedTab).render()}

        );
    }
  }
  renderLoadingIndicator() {
    const { progress, isLoadingParser, isLoadingEvents, bossPhaseEventsLoadingState, isLoadingCharacterProfile, parsingState } = this.props;

    return (

        <Panel
          title="Loading..."
          className="loading-indicators"
        >
          <LoadingBar progress={progress} style={{ marginBottom: 30 }} />

              Spec analyzer from WoWAnalyzer

            <div className={`col-md-4 ${isLoadingParser ? 'loading' : 'ok'}`}>
              {isLoadingParser ? 'Loading...' : 'OK'}

              Player events from Warcraft Logs

            <div className={`col-md-4 ${isLoadingEvents ? 'loading' : 'ok'}`}>
              {isLoadingEvents ? 'Loading...' : 'OK'}

              Boss events from Warcraft Logs

            <div className={`col-md-4 ${bossPhaseEventsLoadingState === BOSS_PHASES_STATE.LOADING ? 'loading' : (bossPhaseEventsLoadingState === BOSS_PHASES_STATE.SKIPPED ? 'skipped' : 'ok')}`}>
              {bossPhaseEventsLoadingState === BOSS_PHASES_STATE.SKIPPED && 'Skipped'}
              {bossPhaseEventsLoadingState === BOSS_PHASES_STATE.LOADING && 'Loading...'}
              {bossPhaseEventsLoadingState === BOSS_PHASES_STATE.DONE && 'OK'}

              Character info from Blizzard

            <div className={`col-md-4 ${isLoadingCharacterProfile ? 'loading' : 'ok'}`}>
              {isLoadingCharacterProfile ? 'Loading...' : 'OK'}

              Analyzing events

            <div className={`col-md-4 ${parsingState === EVENT_PARSING_STATE.WAITING ? 'waiting' : (parsingState === EVENT_PARSING_STATE.PARSING ? 'loading' : 'ok')}`}>
              {parsingState === EVENT_PARSING_STATE.WAITING && 'Waiting'}
              {parsingState === EVENT_PARSING_STATE.PARSING && 'Loading...'}
              {parsingState === EVENT_PARSING_STATE.DONE && 'OK'}

        </Panel>

    );
  }
  render() {
    const { parser, report, fight, player, characterProfile, makeTabUrl, selectedTab, premium } = this.props;
    const config = this.context.config;

    const boss = findByBossId(fight.boss);

    const results = !this.isLoading && parser.generateResults({
      i18n, // TODO: Remove and use singleton
      adjustForDowntime: this.state.adjustForDowntime,
    });

    const contributorinfo = <ReadableList>{(config.contributors.length !== 0) ? config.contributors.map(contributor => <Contributor key={contributor.nickname} {...contributor} />) : 'CURRENTLY UNMAINTAINED'}</ReadableList>;

    return (
      <div className={`results boss-${fight.boss}`}>

        {boss && boss.fight.resultsWarning && (

            <Warning style={{ marginBottom: 30 }}>
              {boss.fight.resultsWarning}
            </Warning>

        )}

        {this.renderContent(selectedTab, results)}

        {premium === false && (

            <Ad />

        )}

              Provided by

                <Trans>{config.spec.specName} {config.spec.className} analysis has been provided by {contributorinfo}. They love hearing what you think, so please let them know! <Link to={makeTabUrl('about')}>More information about this spec's analyzer.</Link></Trans>

              View on

              <Tooltip content={i18n._(t`Opens in a new tab. View the original report.`)}>

                   Warcraft Logs

              </Tooltip>

              <Tooltip content={i18n._(t`Opens in a new tab. View insights and timelines for raid encounters.`)}>
                <a
                  href={`https://www.wipefest.net/report/${report.code}/fight/${fight.id}`}
                  target="_blank"
                  rel="noopener noreferrer"
                  className="btn"
                  style={{ fontSize: 20, padding: '6px 0' }}
                >
                   Wipefest

              </Tooltip>

              <Tooltip content={<Trans>Scroll back to the top.</Trans>}>
                <ScrollToTop />
              </Tooltip>

    );
  }
}

const mapStateToProps = state => ({
  selectedTab: getResultTab(state) || CORE_TABS.OVERVIEW,
  premium: hasPremium(state),
});

export default connect(
  mapStateToProps
)(Results);
<commit_msg>Fix report history<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Trans, t } from '@lingui/macro';

import { findByBossId } from 'raids';
import lazyLoadComponent from 'common/lazyLoadComponent';
import retryingPromise from 'common/retryingPromise';
import makeWclUrl from 'common/makeWclUrl';
import Tooltip from 'common/Tooltip';
import getFightName from 'common/getFightName';
import REPORT_HISTORY_TYPES from 'interface/home/ReportHistory/REPORT_HISTORY_TYPES';
import { appendReportHistory } from 'interface/actions/reportHistory';
import { getResultTab } from 'interface/selectors/url/report';
import { hasPremium } from 'interface/selectors/user';
import Warning from 'interface/common/Alert/Warning';
import Ad from 'interface/common/Ad';
import ReadableList from 'interface/common/ReadableList';
import Contributor from 'interface/contributor/Button';
import WipefestLogo from 'interface/images/Wipefest-logo.png';
import { i18n } from 'interface/RootLocalizationProvider';
import LoadingBar from 'interface/layout/NavigationBar/LoadingBar';
import Panel from 'interface/others/Panel';
import ChangelogTab from 'interface/others/ChangelogTab';
import Checklist from 'parser/shared/modules/features/Checklist/Module';
import StatTracker from 'parser/shared/modules/StatTracker';

import './Results.scss';
import Header from './Header';
import About from './About';
import Overview from './Overview';
import Statistics from './Statistics';
import Character from './Character';
import EncounterStats from './EncounterStats';
import EVENT_PARSING_STATE from '../EVENT_PARSING_STATE';
import BOSS_PHASES_STATE from '../BOSS_PHASES_STATE';
import ScrollToTop from './ScrollToTop';

const TimelineTab = lazyLoadComponent(() => retryingPromise(() => import(/* webpackChunkName: 'TimelineTab' */ './Timeline/Container').then(exports => exports.default)), 0);
const EventsTab = lazyLoadComponent(() => retryingPromise(() => import(/* webpackChunkName: 'EventsTab' */ 'interface/others/EventsTab').then(exports => exports.default)));

const CORE_TABS = {
  OVERVIEW: 'overview',
  STATISTICS: 'statistics',
  TIMELINE: 'timeline',
  CHARACTER: 'character',
  EVENTS: 'events',
  ABOUT: 'about',
};

class Results extends React.PureComponent {
  static propTypes = {
    parser: PropTypes.shape({
    }),
    characterProfile: PropTypes.object,
    selectedTab: PropTypes.string,
    makeTabUrl: PropTypes.func.isRequired,
    report: PropTypes.shape({
      code: PropTypes.string.isRequired,
    }).isRequired,
    fight: PropTypes.shape({
      start_time: PropTypes.number.isRequired,
      end_time: PropTypes.number.isRequired,
      boss: PropTypes.number.isRequired,
    }).isRequired,
    player: PropTypes.shape({
      name: PropTypes.string.isRequired,
    }).isRequired,
    isLoadingParser: PropTypes.bool,
    isLoadingEvents: PropTypes.bool,
    bossPhaseEventsLoadingState: PropTypes.oneOf(BOSS_PHASES_STATE),
    isLoadingCharacterProfile: PropTypes.bool,
    parsingState: PropTypes.oneOf(EVENT_PARSING_STATE),
    progress: PropTypes.number,
    premium: PropTypes.bool,
    appendReportHistory: PropTypes.func.isRequired,
  };
  static childContextTypes = {
    updateResults: PropTypes.func.isRequired,
    parser: PropTypes.object,
  };
  getChildContext() {
    return {
      updateResults: this.forceUpdate.bind(this),
      parser: this.props.parser,
    };
  }
  static contextTypes = {
    config: PropTypes.object.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      adjustForDowntime: false,
    };
  }

  componentDidMount() {
    this.scrollToTop();
    this.appendHistory(this.props.report, this.props.fight, this.props.player);
  }
  componentDidUpdate(prevProps, prevState, prevContext) {
    if (this.props.selectedTab !== prevProps.selectedTab) {
      // TODO: To improve user experience we could try to avoid scrolling when the header is still within vision.
      this.scrollToTop();
    }
  }
  scrollToTop() {
    window.scrollTo(0, 0);
  }

  appendHistory(report, fight, player) {
    this.props.appendReportHistory({
      code: report.code,
      title: report.title,
      start: Math.floor(report.start / 1000),
      end: Math.floor(report.end / 1000),
      fightId: fight.id,
      fightName: getFightName(report, fight),
      playerId: player.id,
      playerName: player.name,
      playerClass: player.type,
      type: REPORT_HISTORY_TYPES.REPORT,
    });
  }

  get warning() {
    const parser = this.props.parser;
    const boss = parser.boss;
    if (boss && boss.fight.resultsWarning) {
      return boss.fight.resultsWarning;
    }
    return null;
  }
  get isLoading() {
    return this.props.isLoadingParser
      || this.props.isLoadingEvents
      || this.props.bossPhaseEventsLoadingState === BOSS_PHASES_STATE.LOADING
      || this.props.isLoadingCharacterProfile
      || this.props.parsingState !== EVENT_PARSING_STATE.DONE;
  }

  renderContent(selectedTab, results) {
    const { parser } = this.props;

    switch (selectedTab) {
      case CORE_TABS.OVERVIEW: {
        if (this.isLoading) {
          return this.renderLoadingIndicator();
        }
        const checklist = parser.getModule(Checklist, true);
        return (
          <Overview
            checklist={checklist && checklist.render()}
            issues={results.issues}
          />
        );
      }
      case CORE_TABS.STATISTICS:
        if (this.isLoading) {
          return this.renderLoadingIndicator();
        }
        return (
          <Statistics parser={parser}>{results.statistics}</Statistics>
        );
      case CORE_TABS.TIMELINE:
        if (this.isLoading) {
          return this.renderLoadingIndicator();
        }
        return (
          <TimelineTab parser={parser} />
        );
      case CORE_TABS.EVENTS:
        if (this.isLoading) {
          return this.renderLoadingIndicator();
        }
        return (

            <EventsTab parser={parser} />

        );
      case CORE_TABS.CHARACTER: {
        if (this.isLoading) {
          return this.renderLoadingIndicator();
        }
        const statTracker = parser.getModule(StatTracker);

        return (

            <Character
              statTracker={statTracker}
              combatant={parser.selectedCombatant}
            />
            <EncounterStats
              currentBoss={parser.fight.boss}
              difficulty={parser.fight.difficulty}
              spec={parser.selectedCombatant._combatantInfo.specID}
              duration={parser.fight.end_time - parser.fight.start_time}
            />

        );
      }
      case CORE_TABS.ABOUT: {
        const config = this.context.config;
        return (

            <About config={config} />
            <ChangelogTab />

        );
      }
      default:
        if (this.isLoading) {
          return this.renderLoadingIndicator();
        }
        return (

            {results.tabs.find(tab => tab.url === selectedTab).render()}

        );
    }
  }
  renderLoadingIndicator() {
    const { progress, isLoadingParser, isLoadingEvents, bossPhaseEventsLoadingState, isLoadingCharacterProfile, parsingState } = this.props;

    return (

        <Panel
          title="Loading..."
          className="loading-indicators"
        >
          <LoadingBar progress={progress} style={{ marginBottom: 30 }} />

              Spec analyzer from WoWAnalyzer

            <div className={`col-md-4 ${isLoadingParser ? 'loading' : 'ok'}`}>
              {isLoadingParser ? 'Loading...' : 'OK'}

              Player events from Warcraft Logs

            <div className={`col-md-4 ${isLoadingEvents ? 'loading' : 'ok'}`}>
              {isLoadingEvents ? 'Loading...' : 'OK'}

              Boss events from Warcraft Logs

            <div className={`col-md-4 ${bossPhaseEventsLoadingState === BOSS_PHASES_STATE.LOADING ? 'loading' : (bossPhaseEventsLoadingState === BOSS_PHASES_STATE.SKIPPED ? 'skipped' : 'ok')}`}>
              {bossPhaseEventsLoadingState === BOSS_PHASES_STATE.SKIPPED && 'Skipped'}
              {bossPhaseEventsLoadingState === BOSS_PHASES_STATE.LOADING && 'Loading...'}
              {bossPhaseEventsLoadingState === BOSS_PHASES_STATE.DONE && 'OK'}

              Character info from Blizzard

            <div className={`col-md-4 ${isLoadingCharacterProfile ? 'loading' : 'ok'}`}>
              {isLoadingCharacterProfile ? 'Loading...' : 'OK'}

              Analyzing events

            <div className={`col-md-4 ${parsingState === EVENT_PARSING_STATE.WAITING ? 'waiting' : (parsingState === EVENT_PARSING_STATE.PARSING ? 'loading' : 'ok')}`}>
              {parsingState === EVENT_PARSING_STATE.WAITING && 'Waiting'}
              {parsingState === EVENT_PARSING_STATE.PARSING && 'Loading...'}
              {parsingState === EVENT_PARSING_STATE.DONE && 'OK'}

        </Panel>

    );
  }
  render() {
    const { parser, report, fight, player, characterProfile, makeTabUrl, selectedTab, premium } = this.props;
    const config = this.context.config;

    const boss = findByBossId(fight.boss);

    const results = !this.isLoading && parser.generateResults({
      i18n, // TODO: Remove and use singleton
      adjustForDowntime: this.state.adjustForDowntime,
    });

    const contributorinfo = <ReadableList>{(config.contributors.length !== 0) ? config.contributors.map(contributor => <Contributor key={contributor.nickname} {...contributor} />) : 'CURRENTLY UNMAINTAINED'}</ReadableList>;

    return (
      <div className={`results boss-${fight.boss}`}>

        {boss && boss.fight.resultsWarning && (

            <Warning style={{ marginBottom: 30 }}>
              {boss.fight.resultsWarning}
            </Warning>

        )}

        {this.renderContent(selectedTab, results)}

        {premium === false && (

            <Ad />

        )}

              Provided by

                <Trans>{config.spec.specName} {config.spec.className} analysis has been provided by {contributorinfo}. They love hearing what you think, so please let them know! <Link to={makeTabUrl('about')}>More information about this spec's analyzer.</Link></Trans>

              View on

              <Tooltip content={i18n._(t`Opens in a new tab. View the original report.`)}>

                   Warcraft Logs

              </Tooltip>

              <Tooltip content={i18n._(t`Opens in a new tab. View insights and timelines for raid encounters.`)}>
                <a
                  href={`https://www.wipefest.net/report/${report.code}/fight/${fight.id}`}
                  target="_blank"
                  rel="noopener noreferrer"
                  className="btn"
                  style={{ fontSize: 20, padding: '6px 0' }}
                >
                   Wipefest

              </Tooltip>

              <Tooltip content={<Trans>Scroll back to the top.</Trans>}>
                <ScrollToTop />
              </Tooltip>

    );
  }
}

const mapStateToProps = state => ({
  selectedTab: getResultTab(state) || CORE_TABS.OVERVIEW,
  premium: hasPremium(state),
});

export default connect(
  mapStateToProps,
  {
    appendReportHistory,
  }
)(Results);
<|endoftext|>


<commit_before>(function(window){

  if (typeof console === 'undefined') {
    /**
     * @see http://getfirebug.com/wiki/index.php/Console_API
     */
    window.console = {
      toString: function (){
        return 'Console.js version 0.9';
      }
    };
  }

  console._output = console.log || window.opera && opera.postError || function dump (message) {
    console.history = console.history || [];
    return console.history.push(message);
  };

  /**
   * Limit of objects dimensions
   * console.dimensions_limit = 1
   * console.dir({a:{b:1}}) ==> {a: {?}}
   * console.dimensions_limit = 2
   * console.dir({a:{b:1}}) ==> {a: {b: 1}}
   */
  console.dimensions_limit = 3;

  /**
   * repeatString("Ha ", 2) ==> "Ha Ha "
   * @param {String} string
   * @param {Number} times
   */
  function repeatString (string, times) {
    if (times < 1) {
      return '';
    }
    var result = string;
    for (var i=times; --i;) {
      result += string;
    }
    return result;
  }

  console._indent = '  ';

  /**
   * @param {Object} object
   * @return {String}
   */
  function primitiveOf(object) {
    var value = object.valueOf();
    switch (typeof value) {
      case "object":
        return "";
      case "string":
        return '"' + value +'"';
      default:
        return String(value);
    }
  }

  /**
   * source_of({x:2, y:8}) === '{x: 2, y: 8}'
   * @param {*} arg
   * @param {Number} limit dimension of objects
   * @param {Array} stack of parent objects
   * @return {String} string representation of input
   */
  console._source_of = function source_of (arg, limit, stack) {

    var aType = typeof arg;
    switch (aType) {
      case 'string':
        return '"' + arg +'"';
      case 'function':
        break;
      case 'object':
        if (arg === null) {
          return 'null';
        }
        break;
      default:
        return String(arg);
    }

    var prefix;
    var kind = Object.prototype.toString.call(arg).slice(8, -1);
    if (kind == 'Object') {
      prefix = '';
    } else {
      prefix = kind + ' ';
      var primitive = primitiveOf(arg);
      if (primitive) {
        prefix += primitive + ' ';
      }
    }
    if (!limit) {
      return prefix + '{?}';
    }
    // Check circular references
    var stack_length = stack.length;
    for (var si=0; si<stack_length; si++) {
      if (stack[si] === arg) {
        return '#';
      }
    }
    stack[stack_length++] = arg;
    var indent = repeatString(console._indent, stack_length);
    if (Object.getOwnPropertyNames) {
      var keys = Object.getOwnPropertyNames(arg);
    } else {
      keys = [];
      for (var key in arg) {
        keys.push(key);
      }
    }
    var result = prefix + '{';
    if (!keys.length) {
      return result + "}";
    }
    keys = keys.sort();
    var arr_obj = [];
    for (var n=0, nn=keys.length; n<nn; n++) {
      key = keys[n];
      try {
        var value = source_of(arg[key], limit-1, stack);
        arr_obj.push("\n"+ indent + key +': '+ value);
      } catch (e) {}
    }
    return result + arr_obj.join(', ') +'\n'+ repeatString(console._indent, stack_length - 1) + '}';

  };

  if (!console.dir || browser_suck_at_logging) {
    /**
     * @return {String} human-readable representation of input
     */
    console.dir = function dir (/* ...arguments */) {
      var result = [];
      for (var i=0; i<arguments.length; i++) {
        result.push(console._source_of(arguments[i], console.dimensions_limit, []));
      }
      return console._output(result.join(console._args_separator));
    };
  }

  /**
   * @param {*} arg
   * @param {Boolean} [within=false]
   */
  console._inspect = function inspect(arg, within) {
    var result = '';

    // TODO: http://twitter.com/abozhilov/status/27768472491
    if (Object(arg) !== arg) {
      if (within && typeof arg == 'string') {
        return '"' + arg + '"';
      }
      return arg;
    }

    if (arg && arg.nodeType == 1) {
      // Is element?
      result = '<'+ arg.tagName;
      for (var i=0, ii=arg.attributes.length; i<ii; i++) {
        if (arg.attributes[i].specified) {
          result +=' '+ arg.attributes[i].name +'="'+ arg.attributes[i].value +'"';
        }
      }
      if (arg.childNodes && arg.childNodes.length === 0) {
        result += '/';
      }
      return result + '>';
    }

    var kind = Object.prototype.toString.call(arg).replace('[object ', '').replace(']','');
    switch (kind) {
      case 'String':
        return 'String "' + arg +'"';

      case 'Number':
      case 'Boolean':
        return kind + ' ' + arg;

      case 'Array':
      case 'HTMLCollection':
      case 'NodeList':
        // Is array-like object?
        result = kind == 'Array' ? '[' : kind + ' [';
        var arr_list = [];
        for (var j=0, jj=arg.length; j<jj; j++) {
          arr_list[j] = inspect(arg[j], true);
        }
        return result + arr_list.join(', ') +']';

      case 'Function':
      case 'Date':
        return arg;

      case 'RegExp':
        return "/"+ arg.source +"/";

      default:
        if (typeof arg === 'object') {
          var prefix;
          if (kind == 'Object') {
            prefix = '';
          } else {
            prefix = kind + ' ';
          }
          if (within) {
            return prefix + '{?}';
          }
          if (Object.getOwnPropertyNames) {
            var keys = Object.getOwnPropertyNames(arg);
          } else {
            keys = [];
            for (var key in arg) {
              if (arg.hasOwnProperty(key)) {
                keys.push(key);
              }
            }
          }
          result = prefix + '{';
          if (!keys.length) {
            return result + "}";
          }
          keys = keys.sort();
          var properties = [];
          for (var n=0, nn=keys.length; n<nn; n++) {
            key = keys[n];
            try {
              var value = inspect(arg[key], true);
              properties.push(key +': '+ value);
            } catch (e) {}
          }
          return result + properties.join(', ') +'}';
        } else {
          return arg;
        }
    }
  };

  var browser_suck_at_logging = /*@cc_on 1 || @*/ window.opera;

  var log_methods = ['log', 'info', 'warn', 'error', 'debug', 'dirxml'];

  console._args_separator = '\n';
  console._interpolate = /%[sdifo]/gi;

  for (var i=0; i<log_methods.length; i++) {
    var _log = console[log_methods[i]];
    if (browser_suck_at_logging || !console[log_methods[i]]) {
      console[log_methods[i]] = function logger (first_arg) {
        var result = [];
        var args = Array.prototype.slice.call(arguments, 0);
        if (typeof first_arg === 'string' && console._interpolate.test(first_arg)) {
          args.shift();
          result.push(first_arg.replace(console._interpolate, function(){
            return console._inspect(args.shift());
          }));
        }
        for (var i=0; i<args.length; i++) {
          result.push(console._inspect(args[i]));
        }
        return (_log || console._output)(result.join(console._args_separator));
      };
    }
  }

  /**
   * Simplified version of http://eriwen.com/javascript/js-stack-trace/
   */
  console.trace = console.trace || function trace() {
    try {
      i.dont.exist++;
    } catch(e) {
      var stack = e.stack || e.stacktrace;
      if (stack) {
        try {
          stack = stack.split('\n').slice(2).join('\n');
        } catch (err) {}
        console._output(stack);
      }
    }
  };

  /**
   * console.assert(false, "I'm gonna fail")
   * @param {Boolean} is_ok
   * @param {String} message optional
   */
  console.assert = console.assert || function assert (is_ok, message) {
    if (!is_ok) console._output( 'ASSERT FAIL: '+ message );
  };

  /**
   * @param {String} name optional
   */
  console.group = console.group || function group (name) {
    console._output('\n-------- '+ name +' --------');
  };

  console.groupCollapsed = console.groupCollapsed || console.group;

  /**
   * Print 3 line breaks
   */
  console.groupEnd = console.groupEnd || function groupEnd () {
    console._output('\n\n\n');
  };

  /**
   * @param {String} title optional
   */
  console.count = console.count || function count (title) {
    title = title || '';
    count.counters = count.counters || {};
    if (count.counters[title]) {
      count.counters[title]++;
    } else {
      count.counters[title] = 1;
    }
    console._output(title +' '+ count.counters[title]);
  };

  /**
   * @param {String} title optional
   */
  console.profile = console.profile || function profile (title) {
    return 'Not implemented';
  };
  console.profileEnd = console.profileEnd || function profileEnd () {
    return 'Not implemented';
  };

  console._timers = {};

  /**
   * @param {String} name such as "my damn slow parser"
   */
  console.time = console.time || function time (name) {
    var start = (new Date).getTime();
    console._timers[name] = {'start': start};
  };

  /**
   * @param {String} name such as "my damn slow parser"
   */
  console.timeEnd = console.timeEnd || function timeEnd (name) {
    var end = (new Date).getTime();
    console.info(name +': '+ (end - console._timers[name].start) +'ms');
    console._timers[name].end = end;
  };

  if (typeof require === 'function' && typeof exports !== 'undefined') {
    exports.console = window.console;
    console._output = require('system').print;
  }

})(this);<commit_msg>...use the same for console._inspect<commit_after>(function(window){

  if (typeof console === 'undefined') {
    /**
     * @see http://getfirebug.com/wiki/index.php/Console_API
     */
    window.console = {
      toString: function (){
        return 'Console.js version 0.9';
      }
    };
  }

  console._output = console.log || window.opera && opera.postError || function dump (message) {
    console.history = console.history || [];
    return console.history.push(message);
  };

  /**
   * Limit of objects dimensions
   * console.dimensions_limit = 1
   * console.dir({a:{b:1}}) ==> {a: {?}}
   * console.dimensions_limit = 2
   * console.dir({a:{b:1}}) ==> {a: {b: 1}}
   */
  console.dimensions_limit = 3;

  /**
   * repeatString("Ha ", 2) ==> "Ha Ha "
   * @param {String} string
   * @param {Number} times
   */
  function repeatString (string, times) {
    if (times < 1) {
      return '';
    }
    var result = string;
    for (var i=times; --i;) {
      result += string;
    }
    return result;
  }

  console._indent = '  ';

  /**
   * @param {Object} object
   * @return {String}
   */
  function primitiveOf(object) {
    var value = object.valueOf();
    switch (typeof value) {
      case "object":
        return "";
      case "string":
        return '"' + value +'"';
      default:
        return String(value);
    }
  }

  /**
   * source_of({x:2, y:8}) === '{x: 2, y: 8}'
   * @param {*} arg
   * @param {Number} limit dimension of objects
   * @param {Array} stack of parent objects
   * @return {String} string representation of input
   */
  console._source_of = function source_of (arg, limit, stack) {

    var aType = typeof arg;
    switch (aType) {
      case 'string':
        return '"' + arg +'"';
      case 'function':
        break;
      case 'object':
        if (arg === null) {
          return 'null';
        }
        break;
      default:
        return String(arg);
    }

    var prefix;
    var kind = Object.prototype.toString.call(arg).slice(8, -1);
    if (kind == 'Object') {
      prefix = '';
    } else {
      prefix = kind + ' ';
      var primitive = primitiveOf(arg);
      if (primitive) {
        prefix += primitive + ' ';
      }
    }
    if (!limit) {
      return prefix + '{?}';
    }
    // Check circular references
    var stack_length = stack.length;
    for (var si=0; si<stack_length; si++) {
      if (stack[si] === arg) {
        return '#';
      }
    }
    stack[stack_length++] = arg;
    var indent = repeatString(console._indent, stack_length);
    if (Object.getOwnPropertyNames) {
      var keys = Object.getOwnPropertyNames(arg);
    } else {
      keys = [];
      for (var key in arg) {
        keys.push(key);
      }
    }
    var result = prefix + '{';
    if (!keys.length) {
      return result + "}";
    }
    keys = keys.sort();
    var arr_obj = [];
    for (var n=0, nn=keys.length; n<nn; n++) {
      key = keys[n];
      try {
        var value = source_of(arg[key], limit-1, stack);
        arr_obj.push("\n"+ indent + key +': '+ value);
      } catch (e) {}
    }
    return result + arr_obj.join(', ') +'\n'+ repeatString(console._indent, stack_length - 1) + '}';

  };

  if (!console.dir || browser_suck_at_logging) {
    /**
     * @return {String} human-readable representation of input
     */
    console.dir = function dir (/* ...arguments */) {
      var result = [];
      for (var i=0; i<arguments.length; i++) {
        result.push(console._source_of(arguments[i], console.dimensions_limit, []));
      }
      return console._output(result.join(console._args_separator));
    };
  }

  /**
   * @param {*} arg
   * @param {Boolean} [within=false]
   */
  console._inspect = function inspect(arg, within) {
    var result = '';

    // TODO: http://twitter.com/abozhilov/status/27768472491
    if (Object(arg) !== arg) {
      if (within && typeof arg == 'string') {
        return '"' + arg + '"';
      }
      return arg;
    }

    if (arg && arg.nodeType == 1) {
      // Is element?
      result = '<'+ arg.tagName;
      for (var i=0, ii=arg.attributes.length; i<ii; i++) {
        if (arg.attributes[i].specified) {
          result +=' '+ arg.attributes[i].name +'="'+ arg.attributes[i].value +'"';
        }
      }
      if (arg.childNodes && arg.childNodes.length === 0) {
        result += '/';
      }
      return result + '>';
    }

    var kind = Object.prototype.toString.call(arg).slice(8, -1);
    switch (kind) {
      case 'String':
        return 'String "' + arg +'"';

      case 'Number':
      case 'Boolean':
        return kind + ' ' + arg;

      case 'Array':
      case 'HTMLCollection':
      case 'NodeList':
        // Is array-like object?
        result = kind == 'Array' ? '[' : kind + ' [';
        var arr_list = [];
        for (var j=0, jj=arg.length; j<jj; j++) {
          arr_list[j] = inspect(arg[j], true);
        }
        return result + arr_list.join(', ') +']';

      case 'Function':
      case 'Date':
        return arg;

      case 'RegExp':
        return "/"+ arg.source +"/";

      default:
        if (typeof arg === 'object') {
          var prefix;
          if (kind == 'Object') {
            prefix = '';
          } else {
            prefix = kind + ' ';
          }
          if (within) {
            return prefix + '{?}';
          }
          if (Object.getOwnPropertyNames) {
            var keys = Object.getOwnPropertyNames(arg);
          } else {
            keys = [];
            for (var key in arg) {
              if (arg.hasOwnProperty(key)) {
                keys.push(key);
              }
            }
          }
          result = prefix + '{';
          if (!keys.length) {
            return result + "}";
          }
          keys = keys.sort();
          var properties = [];
          for (var n=0, nn=keys.length; n<nn; n++) {
            key = keys[n];
            try {
              var value = inspect(arg[key], true);
              properties.push(key +': '+ value);
            } catch (e) {}
          }
          return result + properties.join(', ') +'}';
        } else {
          return arg;
        }
    }
  };

  var browser_suck_at_logging = /*@cc_on 1 || @*/ window.opera;

  var log_methods = ['log', 'info', 'warn', 'error', 'debug', 'dirxml'];

  console._args_separator = '\n';
  console._interpolate = /%[sdifo]/gi;

  for (var i=0; i<log_methods.length; i++) {
    var _log = console[log_methods[i]];
    if (browser_suck_at_logging || !console[log_methods[i]]) {
      console[log_methods[i]] = function logger (first_arg) {
        var result = [];
        var args = Array.prototype.slice.call(arguments, 0);
        if (typeof first_arg === 'string' && console._interpolate.test(first_arg)) {
          args.shift();
          result.push(first_arg.replace(console._interpolate, function(){
            return console._inspect(args.shift());
          }));
        }
        for (var i=0; i<args.length; i++) {
          result.push(console._inspect(args[i]));
        }
        return (_log || console._output)(result.join(console._args_separator));
      };
    }
  }

  /**
   * Simplified version of http://eriwen.com/javascript/js-stack-trace/
   */
  console.trace = console.trace || function trace() {
    try {
      i.dont.exist++;
    } catch(e) {
      var stack = e.stack || e.stacktrace;
      if (stack) {
        try {
          stack = stack.split('\n').slice(2).join('\n');
        } catch (err) {}
        console._output(stack);
      }
    }
  };

  /**
   * console.assert(false, "I'm gonna fail")
   * @param {Boolean} is_ok
   * @param {String} message optional
   */
  console.assert = console.assert || function assert (is_ok, message) {
    if (!is_ok) console._output( 'ASSERT FAIL: '+ message );
  };

  /**
   * @param {String} name optional
   */
  console.group = console.group || function group (name) {
    console._output('\n-------- '+ name +' --------');
  };

  console.groupCollapsed = console.groupCollapsed || console.group;

  /**
   * Print 3 line breaks
   */
  console.groupEnd = console.groupEnd || function groupEnd () {
    console._output('\n\n\n');
  };

  /**
   * @param {String} title optional
   */
  console.count = console.count || function count (title) {
    title = title || '';
    count.counters = count.counters || {};
    if (count.counters[title]) {
      count.counters[title]++;
    } else {
      count.counters[title] = 1;
    }
    console._output(title +' '+ count.counters[title]);
  };

  /**
   * @param {String} title optional
   */
  console.profile = console.profile || function profile (title) {
    return 'Not implemented';
  };
  console.profileEnd = console.profileEnd || function profileEnd () {
    return 'Not implemented';
  };

  console._timers = {};

  /**
   * @param {String} name such as "my damn slow parser"
   */
  console.time = console.time || function time (name) {
    var start = (new Date).getTime();
    console._timers[name] = {'start': start};
  };

  /**
   * @param {String} name such as "my damn slow parser"
   */
  console.timeEnd = console.timeEnd || function timeEnd (name) {
    var end = (new Date).getTime();
    console.info(name +': '+ (end - console._timers[name].start) +'ms');
    console._timers[name].end = end;
  };

  if (typeof require === 'function' && typeof exports !== 'undefined') {
    exports.console = window.console;
    console._output = require('system').print;
  }

})(this);<|endoftext|>