/** @flow */

'use strict';

const {runOnlyForReactRange} = require('./utils');
const listAppUtils = require('./list-app-utils');
const devToolsUtils = require('./devtools-utils');
const {test, expect} = require('@playwright/test');
const config = require('../../playwright.config');
const semver = require('semver');

test.use(config);
test.describe('Components', () => {
  let page;

  test.beforeEach(async ({browser}) => {
    page = await browser.newPage();

    await page.goto(config.use.url, {
      waitUntil: 'domcontentloaded',
    });

    await page.waitForSelector('#iframe');

    await devToolsUtils.clickButton(page, 'TabBarButton-components');
  });

  test('Should display initial React components', async () => {
    const appRowCount = await page.evaluate(() => {
      const {createTestNameSelector, findAllNodes} = window.REACT_DOM_APP;
      const container = document.getElementById('iframe').contentDocument;
      const rows = findAllNodes(container, [
        createTestNameSelector('ListItem'),
      ]);
      return rows.length;
    });
    expect(appRowCount).toBe(3);

    const devToolsRowCount = await devToolsUtils.getElementCount(
      page,
      'ListItem'
    );
    expect(devToolsRowCount).toBe(3);
  });

  test('Should display newly added React components', async () => {
    await listAppUtils.addItem(page, 'four');

    const count = await devToolsUtils.getElementCount(page, 'ListItem');
    expect(count).toBe(4);
  });

  test('Should allow elements to be inspected', async () => {
    // Select the first list item in DevTools.
    await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp');

    // Prop names/values may not be editable based on the React version.
    // If they're not editable, make sure they degrade gracefully
    const isEditableName = semver.gte(config.use.react_version, '17.0.0');
    const isEditableValue = semver.gte(config.use.react_version, '16.8.0');

    // Then read the inspected values.
    const {
      name: propName,
      value: propValue,
      existingNameElementsSize,
      existingValueElementsSize,
    } = await page.evaluate(
      isEditable => {
        const {createTestNameSelector, findAllNodes} =
          window.REACT_DOM_DEVTOOLS;
        const container = document.getElementById('devtools');

        // Get name of first prop
        const nameSelector = isEditable.name
          ? 'EditableName'
          : 'NonEditableName';
        // Get value of first prop
        const valueSelector = isEditable.value
          ? 'EditableValue'
          : 'NonEditableValue';

        const existingNameElements = findAllNodes(container, [
          createTestNameSelector('InspectedElementPropsTree'),
          createTestNameSelector('KeyValue'),
          createTestNameSelector(nameSelector),
        ]);
        const existingValueElements = findAllNodes(container, [
          createTestNameSelector('InspectedElementPropsTree'),
          createTestNameSelector('KeyValue'),
          createTestNameSelector(valueSelector),
        ]);

        const name = isEditable.name
          ? existingNameElements[0].value
          : existingNameElements[0].innerText;
        const value = isEditable.value
          ? existingValueElements[0].value
          : existingValueElements[0].innerText;

        return {
          name,
          value,
          existingNameElementsSize: existingNameElements.length,
          existingValueElementsSize: existingValueElements.length,
        };
      },
      {name: isEditableName, value: isEditableValue}
    );

    expect(existingNameElementsSize).toBe(1);
    expect(existingValueElementsSize).toBe(1);
    expect(propName).toBe('label');
    expect(propValue).toBe('"one"');
  });

  test('Should allow inspecting source of the element', async () => {
    // Source inspection is available only in modern renderer.
    runOnlyForReactRange('>=16.8');

    // Select the first list item in DevTools.
    await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp');

    // Then read the inspected values.
    const sourceText = await page.evaluate(() => {
      const {createTestNameSelector, findAllNodes} = window.REACT_DOM_DEVTOOLS;
      const container = document.getElementById('devtools');

      const source = findAllNodes(container, [
        createTestNameSelector('InspectedElementView-Source'),
      ])[0];

      return source.innerText;
    });

    // If React version is specified, the e2e-regression.html page will be used
    // If not, then e2e.html, see playwright.config.js, how url is constructed
    expect(sourceText).toMatch(/e2e-app[\-a-zA-Z]*\.js/);
  });

  test('should allow props to be edited', async () => {
    runOnlyForReactRange('>=16.8');

    // Select the first list item in DevTools.
    await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp');

    // Then edit the label prop.
    await page.evaluate(() => {
      const {createTestNameSelector, focusWithin} = window.REACT_DOM_DEVTOOLS;
      const container = document.getElementById('devtools');

      focusWithin(container, [
        createTestNameSelector('InspectedElementPropsTree'),
        createTestNameSelector('KeyValue'),
        createTestNameSelector('EditableValue'),
      ]);
    });

    page.keyboard.press('Backspace'); // "
    page.keyboard.press('Backspace'); // e
    page.keyboard.press('Backspace'); // n
    page.keyboard.press('Backspace'); // o
    page.keyboard.insertText('new"');
    page.keyboard.press('Enter');

    await page.waitForFunction(() => {
      const {createTestNameSelector, findAllNodes} = window.REACT_DOM_APP;
      const container = document.getElementById('iframe').contentDocument;
      const rows = findAllNodes(container, [
        createTestNameSelector('ListItem'),
      ])[0];
      return rows.innerText === 'new';
    });
  });

  test('should load and parse hook names for the inspected element', async () => {
    runOnlyForReactRange('>=16.8');

    // Select the List component DevTools.
    await devToolsUtils.selectElement(page, 'List', 'App');

    // Then click to load and parse hook names.
    await devToolsUtils.clickButton(page, 'LoadHookNamesButton');

    // Make sure the expected hook names are parsed and displayed eventually.
    await page.waitForFunction(
      hookNames => {
        const {createTestNameSelector, findAllNodes} =
          window.REACT_DOM_DEVTOOLS;
        const container = document.getElementById('devtools');

        const hooksTree = findAllNodes(container, [
          createTestNameSelector('InspectedElementHooksTree'),
        ])[0];

        if (!hooksTree) {
          return false;
        }

        const hooksTreeText = hooksTree.innerText;

        for (let i = 0; i < hookNames.length; i++) {
          if (!hooksTreeText.includes(hookNames[i])) {
            return false;
          }
        }

        return true;
      },
      ['State(items)', 'Ref(inputRef)']
    );
  });

  test('should allow searching for component by name', async () => {
    async function getComponentSearchResultsCount() {
      return await page.evaluate(() => {
        const {createTestNameSelector, findAllNodes} =
          window.REACT_DOM_DEVTOOLS;
        const container = document.getElementById('devtools');

        const element = findAllNodes(container, [
          createTestNameSelector('ComponentSearchInput-ResultsCount'),
        ])[0];
        return element.innerText;
      });
    }

    async function focusComponentSearch() {
      await page.evaluate(() => {
        const {createTestNameSelector, focusWithin} = window.REACT_DOM_DEVTOOLS;
        const container = document.getElementById('devtools');

        focusWithin(container, [
          createTestNameSelector('ComponentSearchInput-Input'),
        ]);
      });
    }

    await focusComponentSearch();
    page.keyboard.insertText('List');
    let count = await getComponentSearchResultsCount();
    expect(count).toBe('1 | 4');

    page.keyboard.insertText('Item');
    count = await getComponentSearchResultsCount();
    expect(count).toBe('1 | 3');

    page.keyboard.press('Enter');
    count = await getComponentSearchResultsCount();
    expect(count).toBe('2 | 3');

    page.keyboard.press('Enter');
    count = await getComponentSearchResultsCount();
    expect(count).toBe('3 | 3');

    page.keyboard.press('Enter');
    count = await getComponentSearchResultsCount();
    expect(count).toBe('1 | 3');

    page.keyboard.press('Shift+Enter');
    count = await getComponentSearchResultsCount();
    expect(count).toBe('3 | 3');

    page.keyboard.press('Shift+Enter');
    count = await getComponentSearchResultsCount();
    expect(count).toBe('2 | 3');

    page.keyboard.press('Shift+Enter');
    count = await getComponentSearchResultsCount();
    expect(count).toBe('1 | 3');
  });
});
