import { FocusZoneDirection } from '@fluentui/accessibility';
import { FocusTrapZone, FocusZone, FocusTrapZoneProps } from '@fluentui/react-bindings';
import * as React from 'react';
import * as ReactTestUtils from 'react-dom/test-utils';
import { keyboardKey } from '@fluentui/keyboard-key';

// rAF does not exist in node - let's mock it
window.requestAnimationFrame = (callback: FrameRequestCallback) => {
  const r = window.setTimeout(callback, 0);
  jest.runAllTimers();
  return r;
};

jest.useFakeTimers();

class FocusTrapZoneTestComponent extends React.Component<{}, { isShowingFirst: boolean; isShowingSecond: boolean }> {
  constructor(props: {}) {
    super(props);
    this.state = { isShowingFirst: true, isShowingSecond: false };
  }

  render() {
    return (
      <div>
        <FocusTrapZone forceFocusInsideTrapOnOutsideFocus isClickableOutsideFocusTrap={false}>
          <button className={'a'} onClick={this._toggleFirst}>
            a
          </button>
          <button className={'b'} onClick={this._toggleSecond}>
            b
          </button>
        </FocusTrapZone>

        {this.state.isShowingFirst && (
          <FocusTrapZone forceFocusInsideTrapOnOutsideFocus={false} isClickableOutsideFocusTrap={false}>
            <FocusZone data-is-visible={true}>First</FocusZone>
          </FocusTrapZone>
        )}
        {this.state.isShowingSecond && (
          <FocusTrapZone forceFocusInsideTrapOnOutsideFocus={false} isClickableOutsideFocusTrap={true}>
            <FocusZone data-is-visible={true}>First</FocusZone>
          </FocusTrapZone>
        )}
      </div>
    );
  }

  _toggleFirst = () => this.setState({ isShowingFirst: !this.state.isShowingFirst });
  _toggleSecond = () => this.setState({ isShowingSecond: !this.state.isShowingSecond });
}

describe('FocusTrapZone', () => {
  // document.activeElement can be used to detect activeElement after component mount, but it does not
  // update based on focus events due to limitations of ReactDOM. Use lastFocusedElement to detect focus
  // change events.
  let lastFocusedElement: HTMLElement | undefined;
  const ftzClassname = 'ftzTestClassname';

  const _onFocus = (ev: any): void => (lastFocusedElement = ev.target);

  const setupElement = (
    element: HTMLElement,
    {
      clientRect,
      isVisible = true,
    }: {
      clientRect: {
        top: number;
        left: number;
        bottom: number;
        right: number;
      };
      isVisible?: boolean;
    },
  ): void => {
    // @ts-ignore
    element.getBoundingClientRect = () => ({
      top: clientRect.top,
      left: clientRect.left,
      bottom: clientRect.bottom,
      right: clientRect.right,
      width: clientRect.right - clientRect.left,
      height: clientRect.bottom - clientRect.top,
    });

    element.setAttribute('data-is-visible', String(isVisible));
    element.focus = () => ReactTestUtils.Simulate.focus(element);
  };

  /**
   * Helper to get FocusTrapZone bumpers. Requires classname attribute of
   * 'ftzClassname' on FTZ.
   */
  function getFtzBumpers(
    element: HTMLElement,
  ): {
    firstBumper: Element;
    lastBumper: Element;
  } {
    const ftz = element.querySelector(`.${ftzClassname}`) as HTMLElement;
    const ftzNodes = ftz.children;
    const firstBumper = ftzNodes[0];
    const lastBumper = ftzNodes[ftzNodes.length - 1];

    return { firstBumper, lastBumper };
  }

  beforeEach(() => {
    lastFocusedElement = undefined;
  });

  describe('Tab and shift-tab wrap at extreme ends of the FTZ', () => {
    it('can tab across FocusZones with different button structures', async () => {
      expect.assertions(3);

      const topLevelDiv = ReactTestUtils.renderIntoDocument<{}>(
        <div onFocusCapture={_onFocus}>
          <FocusTrapZone forceFocusInsideTrapOnOutsideFocus={false} className={ftzClassname}>
            <FocusZone direction={FocusZoneDirection.horizontal} data-is-visible={true}>
              <div data-is-visible={true}>
                <button className="a">a</button>
              </div>
              <div data-is-visible={true}>
                <button className="b">b</button>
              </div>
              <div data-is-visible={true}>
                <button className="c">c</button>
              </div>
            </FocusZone>
            <FocusZone direction={FocusZoneDirection.horizontal} data-is-visible={true}>
              <div data-is-visible={true}>
                <div data-is-visible={true}>
                  <button className="d">d</button>
                  <button className="e">e</button>
                  <button className="f">f</button>
                </div>
              </div>
            </FocusZone>
          </FocusTrapZone>
        </div>,
      ) as HTMLElement;

      const buttonA = topLevelDiv.querySelector('.a') as HTMLElement;
      const buttonB = topLevelDiv.querySelector('.b') as HTMLElement;
      const buttonC = topLevelDiv.querySelector('.c') as HTMLElement;
      const buttonD = topLevelDiv.querySelector('.d') as HTMLElement;
      const buttonE = topLevelDiv.querySelector('.e') as HTMLElement;
      const buttonF = topLevelDiv.querySelector('.f') as HTMLElement;

      // Assign bounding locations to buttons.
      setupElement(buttonA, { clientRect: { top: 0, bottom: 30, left: 0, right: 30 } });
      setupElement(buttonB, { clientRect: { top: 0, bottom: 30, left: 30, right: 60 } });
      setupElement(buttonC, { clientRect: { top: 0, bottom: 30, left: 60, right: 90 } });
      setupElement(buttonD, { clientRect: { top: 30, bottom: 60, left: 0, right: 30 } });
      setupElement(buttonE, { clientRect: { top: 30, bottom: 60, left: 30, right: 60 } });
      setupElement(buttonF, { clientRect: { top: 30, bottom: 60, left: 60, right: 90 } });

      const { firstBumper, lastBumper } = getFtzBumpers(topLevelDiv);

      ReactTestUtils.Simulate.focus(buttonA);
      expect(lastFocusedElement).toBe(buttonA);

      // Simulate shift+tab event which would focus first bumper
      ReactTestUtils.Simulate.focus(firstBumper);
      expect(lastFocusedElement).toBe(buttonD);

      // Simulate tab event which would focus last bumper
      ReactTestUtils.Simulate.focus(lastBumper);
      expect(lastFocusedElement).toBe(buttonA);
    });

    it('can tab across a FocusZone with different button structures', async () => {
      expect.assertions(3);

      const topLevelDiv = ReactTestUtils.renderIntoDocument<{}>(
        <div onFocusCapture={_onFocus}>
          <FocusTrapZone forceFocusInsideTrapOnOutsideFocus={false} className={ftzClassname}>
            <div data-is-visible={true}>
              <button className="x">x</button>
            </div>
            <FocusZone direction={FocusZoneDirection.horizontal} data-is-visible={true}>
              <div data-is-visible={true}>
                <button className="a">a</button>
              </div>
              <div data-is-visible={true}>
                <div data-is-visible={true}>
                  <button className="b">b</button>
                  <button className="c">c</button>
                  <button className="d">d</button>
                </div>
              </div>
            </FocusZone>
          </FocusTrapZone>
        </div>,
      ) as HTMLElement;

      const buttonX = topLevelDiv.querySelector('.x') as HTMLElement;
      const buttonA = topLevelDiv.querySelector('.a') as HTMLElement;
      const buttonB = topLevelDiv.querySelector('.b') as HTMLElement;
      const buttonC = topLevelDiv.querySelector('.c') as HTMLElement;
      const buttonD = topLevelDiv.querySelector('.d') as HTMLElement;

      const { firstBumper, lastBumper } = getFtzBumpers(topLevelDiv);

      // Assign bounding locations to buttons.
      setupElement(buttonX, { clientRect: { top: 0, bottom: 30, left: 0, right: 30 } });
      setupElement(buttonA, { clientRect: { top: 0, bottom: 30, left: 0, right: 30 } });
      setupElement(buttonB, { clientRect: { top: 0, bottom: 30, left: 30, right: 60 } });
      setupElement(buttonC, { clientRect: { top: 0, bottom: 30, left: 60, right: 90 } });
      setupElement(buttonD, { clientRect: { top: 30, bottom: 60, left: 0, right: 30 } });

      ReactTestUtils.Simulate.focus(buttonX);
      expect(lastFocusedElement).toBe(buttonX);

      // Simulate shift+tab event which would focus first bumper
      ReactTestUtils.Simulate.focus(firstBumper);
      expect(lastFocusedElement).toBe(buttonA);

      // Simulate tab event which would focus last bumper
      ReactTestUtils.Simulate.focus(lastBumper);
      expect(lastFocusedElement).toBe(buttonX);
    });

    it('can trap focus when FTZ bookmark elements are FocusZones, and those elements have inner elements focused that are not the first inner element', async () => {
      expect.assertions(4);

      const topLevelDiv = ReactTestUtils.renderIntoDocument<{}>(
        <div onFocusCapture={_onFocus}>
          <button className={'z1'}>z1</button>
          <FocusTrapZone forceFocusInsideTrapOnOutsideFocus={false} className={ftzClassname}>
            <FocusZone direction={FocusZoneDirection.horizontal} data-is-visible={true}>
              <button className={'a'}>a</button>
              <button className={'b'}>b</button>
              <button className={'c'}>c</button>
            </FocusZone>
            <button className={'d'}>d</button>
            <FocusZone direction={FocusZoneDirection.horizontal} data-is-visible={true}>
              <button className={'e'}>e</button>
              <button className={'f'}>f</button>
              <button className={'g'}>g</button>
            </FocusZone>
          </FocusTrapZone>
          <button className={'z2'}>z2</button>
        </div>,
      ) as HTMLElement;

      const buttonZ1 = topLevelDiv.querySelector('.z1') as HTMLElement;
      const buttonA = topLevelDiv.querySelector('.a') as HTMLElement;
      const buttonB = topLevelDiv.querySelector('.b') as HTMLElement;
      const buttonC = topLevelDiv.querySelector('.c') as HTMLElement;
      const buttonD = topLevelDiv.querySelector('.d') as HTMLElement;
      const buttonE = topLevelDiv.querySelector('.e') as HTMLElement;
      const buttonF = topLevelDiv.querySelector('.f') as HTMLElement;
      const buttonG = topLevelDiv.querySelector('.g') as HTMLElement;
      const buttonZ2 = topLevelDiv.querySelector('.z2') as HTMLElement;

      const { firstBumper, lastBumper } = getFtzBumpers(topLevelDiv);

      // Assign bounding locations to buttons.
      setupElement(buttonZ1, { clientRect: { top: 0, bottom: 10, left: 0, right: 10 } });
      setupElement(buttonA, { clientRect: { top: 10, bottom: 30, left: 0, right: 10 } });
      setupElement(buttonB, { clientRect: { top: 10, bottom: 30, left: 10, right: 20 } });
      setupElement(buttonC, { clientRect: { top: 10, bottom: 30, left: 20, right: 30 } });
      setupElement(buttonD, { clientRect: { top: 30, bottom: 40, left: 0, right: 10 } });
      setupElement(buttonE, { clientRect: { top: 40, bottom: 60, left: 0, right: 10 } });
      setupElement(buttonF, { clientRect: { top: 40, bottom: 60, left: 10, right: 20 } });
      setupElement(buttonG, { clientRect: { top: 40, bottom: 60, left: 20, right: 30 } });
      setupElement(buttonZ2, { clientRect: { top: 60, bottom: 70, left: 0, right: 10 } });

      // Focus the middle button in the first FZ.
      ReactTestUtils.Simulate.focus(buttonA);
      ReactTestUtils.Simulate.keyDown(buttonA, { which: keyboardKey.ArrowRight });
      expect(lastFocusedElement).toBe(buttonB);

      // Focus the middle button in the second FZ.
      ReactTestUtils.Simulate.focus(buttonE);
      ReactTestUtils.Simulate.keyDown(buttonE, { which: keyboardKey.ArrowRight });
      expect(lastFocusedElement).toBe(buttonF);

      // Simulate tab event which would focus last bumper
      ReactTestUtils.Simulate.focus(lastBumper);
      expect(lastFocusedElement).toBe(buttonB);

      // Simulate shift+tab event which would focus first bumper
      ReactTestUtils.Simulate.focus(firstBumper);
      expect(lastFocusedElement).toBe(buttonF);
    });
  });

  describe('Tab and shift-tab do nothing (keep focus where it is) when the FTZ contains 0 tabbable items', () => {
    function setupTest(props: FocusTrapZoneProps) {
      const topLevelDiv = ReactTestUtils.renderIntoDocument<{}>(
        <div onFocusCapture={_onFocus}>
          <button className={'z1'}>z1</button>
          <FocusTrapZone className={ftzClassname} forceFocusInsideTrapOnOutsideFocus={true} {...props}>
            <button className={'a'} tabIndex={-1}>
              a
            </button>
            <button className={'b'} tabIndex={-1}>
              b
            </button>
            <button className={'c'} tabIndex={-1}>
              c
            </button>
          </FocusTrapZone>
          <button className={'z2'}>z2</button>
        </div>,
      ) as HTMLElement;

      const buttonZ1 = topLevelDiv.querySelector('.z1') as HTMLElement;
      const buttonA = topLevelDiv.querySelector('.a') as HTMLElement;
      const buttonB = topLevelDiv.querySelector('.b') as HTMLElement;
      const buttonC = topLevelDiv.querySelector('.c') as HTMLElement;
      const buttonZ2 = topLevelDiv.querySelector('.z2') as HTMLElement;

      const { firstBumper, lastBumper } = getFtzBumpers(topLevelDiv);

      // Have to set bumpers as "visible" for focus utilities to find them.
      // This is needed for 0 tabbable element tests to make sure that next tabbable element
      // from one bumper is the other bumper.
      firstBumper.setAttribute('data-is-visible', String(true));
      lastBumper.setAttribute('data-is-visible', String(true));

      // Assign bounding locations to buttons.
      setupElement(buttonZ1, { clientRect: { top: 0, bottom: 10, left: 0, right: 10 } });
      setupElement(buttonA, { clientRect: { top: 10, bottom: 20, left: 0, right: 10 } });
      setupElement(buttonB, { clientRect: { top: 20, bottom: 30, left: 0, right: 10 } });
      setupElement(buttonC, { clientRect: { top: 30, bottom: 40, left: 0, right: 10 } });
      setupElement(buttonZ2, { clientRect: { top: 40, bottom: 50, left: 0, right: 10 } });

      return { buttonZ1, buttonA, buttonB, buttonC, buttonZ2, firstBumper, lastBumper };
    }

    it('focuses first focusable element when focusing first bumper', async () => {
      expect.assertions(2);
      const { buttonB, buttonA, firstBumper } = setupTest({});

      ReactTestUtils.Simulate.focus(buttonB);
      expect(lastFocusedElement).toBe(buttonB);

      // Simulate shift+tab event which would focus first bumper
      ReactTestUtils.Simulate.focus(firstBumper);
      expect(lastFocusedElement).toBe(buttonA);
    });

    it('focuses first focusable element when focusing last bumper', async () => {
      expect.assertions(2);

      const { buttonA, buttonB, lastBumper } = setupTest({});

      ReactTestUtils.Simulate.focus(buttonB);
      expect(lastFocusedElement).toBe(buttonB);

      // Simulate tab event which would focus last bumper
      ReactTestUtils.Simulate.focus(lastBumper);
      expect(lastFocusedElement).toBe(buttonA);
    });
  });

  describe('Focus behavior based on default and explicit prop values', () => {
    function setupTest(props: FocusTrapZoneProps) {
      // data-is-visible is embedded in buttons here for testing focus behavior on initial render.
      // Components have to be marked visible before setupElement has a chance to apply the data-is-visible attribute.
      const topLevelDiv = (ReactTestUtils.renderIntoDocument(
        <div>
          <div onFocusCapture={_onFocus}>
            <button className={'z1'}>z1</button>
            <FocusTrapZone data-is-visible={true} {...props} className={ftzClassname}>
              <button className={'a'} data-is-visible={true}>
                a
              </button>
              <button className={'b'} data-is-visible={true}>
                b
              </button>
              <button className={'c'} data-is-visible={true}>
                c
              </button>
            </FocusTrapZone>
            <button className={'z2'}>z2</button>
          </div>
        </div>,
      ) as unknown) as HTMLElement;

      const buttonZ1 = topLevelDiv.querySelector('.z1') as HTMLElement;
      const buttonA = topLevelDiv.querySelector('.a') as HTMLElement;
      const buttonB = topLevelDiv.querySelector('.b') as HTMLElement;
      const buttonC = topLevelDiv.querySelector('.c') as HTMLElement;
      const buttonZ2 = topLevelDiv.querySelector('.z2') as HTMLElement;

      const { firstBumper, lastBumper } = getFtzBumpers(topLevelDiv);

      // Assign bounding locations to buttons.
      setupElement(buttonZ1, { clientRect: { top: 0, bottom: 10, left: 0, right: 10 } });
      setupElement(buttonA, { clientRect: { top: 10, bottom: 20, left: 0, right: 10 } });
      setupElement(buttonB, { clientRect: { top: 20, bottom: 30, left: 0, right: 10 } });
      setupElement(buttonC, { clientRect: { top: 30, bottom: 40, left: 0, right: 10 } });
      setupElement(buttonZ2, { clientRect: { top: 40, bottom: 50, left: 0, right: 10 } });

      return { buttonZ1, buttonA, buttonB, buttonC, buttonZ2, firstBumper, lastBumper };
    }

    it('Focuses first element when FTZ does not have focus and first bumper receives focus', async () => {
      expect.assertions(2);

      const { buttonA, buttonZ1, firstBumper } = setupTest({ isClickableOutsideFocusTrap: true });

      ReactTestUtils.Simulate.focus(buttonZ1);
      expect(lastFocusedElement).toBe(buttonZ1);

      ReactTestUtils.Simulate.focus(firstBumper);
      expect(lastFocusedElement).toBe(buttonA);
    });

    it('Focuses last element when FTZ does not have focus and last bumper receives focus', async () => {
      expect.assertions(2);

      const { buttonC, buttonZ2, lastBumper } = setupTest({ isClickableOutsideFocusTrap: true });

      ReactTestUtils.Simulate.focus(buttonZ2);
      expect(lastFocusedElement).toBe(buttonZ2);

      ReactTestUtils.Simulate.focus(lastBumper);
      expect(lastFocusedElement).toBe(buttonC);
    });

    it('Focuses first on mount', async () => {
      expect.assertions(1);

      const { buttonA } = setupTest({});

      expect(document.activeElement).toBe(buttonA);
    });

    it('Does not focus first on mount with disableFirstFocus', async () => {
      expect.assertions(1);

      const activeElement = document.activeElement;

      setupTest({ disableFirstFocus: true });

      // document.activeElement can be used to detect activeElement after component mount, but it does not
      // update based on focus events due to limitations of ReactDOM.
      // Make sure activeElement didn't change.
      expect(document.activeElement).toBe(activeElement);
    });

    it('Does not focus first on mount while disabled', async () => {
      expect.assertions(1);

      const activeElement = document.activeElement;

      setupTest({ disabled: true });

      // document.activeElement can be used to detect activeElement after component mount, but it does not
      // update based on focus events due to limitations of ReactDOM.
      // Make sure activeElement didn't change.
      expect(document.activeElement).toBe(activeElement);
    });

    it('Focuses on firstFocusableSelector on mount', async () => {
      expect.assertions(1);

      const { buttonC } = setupTest({ firstFocusableSelector: '.c' });

      expect(document.activeElement).toBe(buttonC);
    });

    it('Does not focus on firstFocusableSelector on mount while disabled', async () => {
      expect.assertions(1);

      const activeElement = document.activeElement;

      setupTest({ firstFocusableSelector: '.c', disabled: true });

      expect(document.activeElement).toBe(activeElement);
    });

    it('Falls back to first focusable element with invalid firstFocusableSelector', async () => {
      const { buttonA } = setupTest({ firstFocusableSelector: '.invalidSelector' });

      expect(document.activeElement).toBe(buttonA);
    });
  });

  describe('Focusing the FTZ', () => {
    function setupTest(focusPreviouslyFocusedInnerElement: boolean) {
      let focusTrapZoneRef: FocusTrapZone | null = null;
      const topLevelDiv = ReactTestUtils.renderIntoDocument<{}>(
        <div onFocusCapture={_onFocus}>
          <FocusTrapZone
            forceFocusInsideTrapOnOutsideFocus={false}
            focusPreviouslyFocusedInnerElement={focusPreviouslyFocusedInnerElement}
            data-is-focusable={true}
            ref={ftz => {
              focusTrapZoneRef = ftz;
            }}
          >
            <button className={'f'}>f</button>
            <FocusZone>
              <button className={'a'}>a</button>
              <button className={'b'}>b</button>
            </FocusZone>
          </FocusTrapZone>
          <button className={'z'}>z</button>
        </div>,
      ) as HTMLElement;

      const buttonF = topLevelDiv.querySelector('.f') as HTMLElement;
      const buttonA = topLevelDiv.querySelector('.a') as HTMLElement;
      const buttonB = topLevelDiv.querySelector('.b') as HTMLElement;
      const buttonZ = topLevelDiv.querySelector('.z') as HTMLElement;

      // Assign bounding locations to buttons.
      setupElement(buttonF, { clientRect: { top: 0, bottom: 10, left: 0, right: 10 } });
      setupElement(buttonA, { clientRect: { top: 10, bottom: 20, left: 0, right: 10 } });
      setupElement(buttonB, { clientRect: { top: 20, bottom: 30, left: 0, right: 10 } });
      setupElement(buttonZ, { clientRect: { top: 30, bottom: 40, left: 0, right: 10 } });

      return { focusTrapZone: focusTrapZoneRef, buttonF, buttonA, buttonB, buttonZ };
    }

    it('goes to previously focused element when focusing the FTZ', async () => {
      expect.assertions(4);

      const { focusTrapZone, buttonF, buttonB, buttonZ } = setupTest(true /* focusPreviouslyFocusedInnerElement */);

      // By calling `componentDidMount`, FTZ will behave as just initialized and focus needed element
      // @ts-ignore
      focusTrapZone.componentDidMount();
      expect(lastFocusedElement).toBe(buttonF);

      // Focus inside the trap zone, not the first element.
      ReactTestUtils.Simulate.focus(buttonB);
      expect(lastFocusedElement).toBe(buttonB);

      // Focus outside the trap zone
      ReactTestUtils.Simulate.focus(buttonZ);
      expect(lastFocusedElement).toBe(buttonZ);

      // By calling `componentDidMount`, FTZ will behave as just initialized and focus needed element
      // FTZ should return to originally focused inner element.
      // @ts-ignore
      focusTrapZone.componentDidMount();
      expect(lastFocusedElement).toBe(buttonB);
    });

    it('goes to first focusable element when focusing the FTZ', async () => {
      expect.assertions(4);

      const { focusTrapZone, buttonF, buttonB, buttonZ } = setupTest(false /* focusPreviouslyFocusedInnerElement */);

      // By calling `componentDidMount`, FTZ will behave as just initialized and focus needed element
      // Focus within should go to 1st focusable inner element.
      // @ts-ignore
      focusTrapZone.componentDidMount();
      expect(lastFocusedElement).toBe(buttonF);

      // Focus inside the trap zone, not the first element.
      ReactTestUtils.Simulate.focus(buttonB);
      expect(lastFocusedElement).toBe(buttonB);

      // Focus outside the trap zone
      ReactTestUtils.Simulate.focus(buttonZ);
      expect(lastFocusedElement).toBe(buttonZ);

      // By calling `componentDidMount`, FTZ will behave as just initialized and focus needed element
      // Focus should go to the first focusable element
      // @ts-ignore
      focusTrapZone.componentDidMount();
      expect(lastFocusedElement).toBe(buttonF);
    });
  });

  describe('Nested FocusTrapZones Stack Behavior', () => {
    const getFocusStack = (): FocusTrapZone[] => (FocusTrapZone as any)._focusStack;
    beforeAll(() => (getFocusStack().length = 0));

    it('FocusTrapZone maintains a proper stack of FocusTrapZones as more are mounted/unmounted.', async () => {
      let focusTrapZoneFocusStack: FocusTrapZone[] = getFocusStack();
      const topLevelDiv = ReactTestUtils.renderIntoDocument<{}>(
        <div>
          <FocusTrapZoneTestComponent />
        </div>,
      ) as HTMLElement;
      const buttonA = topLevelDiv.querySelector('.a') as HTMLElement;

      const buttonB = topLevelDiv.querySelector('.b') as HTMLElement;

      expect(focusTrapZoneFocusStack.length).toBe(2);
      const baseFocusTrapZone = focusTrapZoneFocusStack[0];
      expect(baseFocusTrapZone.props.forceFocusInsideTrapOnOutsideFocus).toBe(true);
      expect(baseFocusTrapZone.props.isClickableOutsideFocusTrap).toBe(false);

      const firstFocusTrapZone = focusTrapZoneFocusStack[1];
      expect(firstFocusTrapZone.props.forceFocusInsideTrapOnOutsideFocus).toBe(false);
      expect(firstFocusTrapZone.props.isClickableOutsideFocusTrap).toBe(false);

      // There should be now 3 focus trap zones (base/first/second)
      ReactTestUtils.Simulate.click(buttonB);
      expect(focusTrapZoneFocusStack.length).toBe(3);
      expect(focusTrapZoneFocusStack[0]).toBe(baseFocusTrapZone);
      expect(focusTrapZoneFocusStack[1]).toBe(firstFocusTrapZone);
      const secondFocusTrapZone = focusTrapZoneFocusStack[2];
      expect(secondFocusTrapZone.props.forceFocusInsideTrapOnOutsideFocus).toBe(false);
      expect(secondFocusTrapZone.props.isClickableOutsideFocusTrap).toBe(true);

      // we remove the middle one
      // unmounting a focus trap zone should remove it from the focus stack.
      // but we also check that it removes the right focustrapzone (the middle one)
      ReactTestUtils.Simulate.click(buttonA);
      focusTrapZoneFocusStack = getFocusStack();

      expect(focusTrapZoneFocusStack.length).toBe(2);
      expect(focusTrapZoneFocusStack[0]).toBe(baseFocusTrapZone);
      expect(focusTrapZoneFocusStack[1]).toBe(secondFocusTrapZone);

      // finally remove the last focus trap zone.
      ReactTestUtils.Simulate.click(buttonB);
      focusTrapZoneFocusStack = getFocusStack();

      expect(focusTrapZoneFocusStack.length).toBe(1);
      expect(focusTrapZoneFocusStack[0]).toBe(baseFocusTrapZone);
    });
  });
});
