fill method infrastructure ✓ 100.0%

Last updated: 2026-03-04T23:21:38.391Z

Metrics

LOC: 148 Complexity: 17 Params: 2 Coverage: 100.0% (47/47 lines, 0x executed)

Signature

fill(wrapper: HTMLElement, value: string): : Promise<boolean>

Architecture violations

View all

  • [warning] max-cyclomatic-complexity: 'fill' has cyclomatic complexity 17 (max 10)
  • [warning] max-lines: 'fill' has 148 lines (max 80)

Source Code

  async fill(wrapper: HTMLElement, value: string): Promise<boolean> {
    const wrapperSelector = getUniqueSelector(wrapper);

    const control = wrapper.querySelector<HTMLElement>(
      ".react-select__control",
    );
    if (!control) {
      log.warn(`Control não encontrado em: ${wrapperSelector}`);
      return false;
    }

    const isMulti =
      wrapper.querySelector(".react-select__value-container--is-multi") !==
      null;

    // Searchable: has .react-select__input (real text input, not dummyInput)
    const searchInput = wrapper.querySelector<HTMLInputElement>(
      ".react-select__input",
    );
    const isSearchable = searchInput !== null;

    // The accessible combobox input for focus/keyboard events
    const comboboxInput =
      wrapper.querySelector<HTMLInputElement>("input[role='combobox']") ??
      searchInput;

    // Step 1: Focus the input so react-select sets isFocused=true.
    // Without this, the first mousedown only focuses (doesn't open the menu).
    comboboxInput?.focus();
    await new Promise<void>((r) => setTimeout(r, 30));

    // Step 2: Prefer clicking the dropdown indicator — its onMouseDown always
    // calls openMenu() directly, unlike the control which first checks isFocused.
    const indicator = wrapper.querySelector<HTMLElement>(
      ".react-select__dropdown-indicator",
    );
    const trigger = indicator ?? control;
    trigger.dispatchEvent(
      new MouseEvent("mousedown", { bubbles: true, cancelable: true }),
    );
    trigger.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
    trigger.dispatchEvent(new MouseEvent("click", { bubbles: true }));

    // Step 3: ArrowDown on the focused input opens the menu in react-select v5
    // regardless of onMouseDown outcome — acts as a reliable fallback.
    comboboxInput?.dispatchEvent(
      new KeyboardEvent("keydown", {
        key: "ArrowDown",
        keyCode: 40,
        bubbles: true,
        cancelable: true,
      }),
    );

    // Step 4: Wait for the menu to render (may be portaled to document.body)
    const menu = await waitForReactSelectMenu(wrapper, 1500);
    if (!menu) {
      log.warn(`Menu react-select não apareceu para: ${wrapperSelector}`);
      return false;
    }

    // Step 5: For searchable fields — type the value to filter options
    // For async-loaded options (API calls), we need to poll until they appear.
    if (isSearchable && searchInput) {
      const nativeSetter = Object.getOwnPropertyDescriptor(
        window.HTMLInputElement.prototype,
        "value",
      )?.set;
      nativeSetter
        ? nativeSetter.call(searchInput, value)
        : (searchInput.value = value);
      searchInput.dispatchEvent(new Event("input", { bubbles: true }));
      searchInput.dispatchEvent(new Event("change", { bubbles: true }));

      // Poll for options to appear (local or async-loaded)
      await waitForAsyncOptions(menu, 2500);
    }

    let available = Array.from(
      menu.querySelectorAll<HTMLElement>(
        ".react-select__option:not(.react-select__option--is-disabled)",
      ),
    );

    // Step 6: If filtering removed all options, clear the input so the full
    // list is restored and we can still pick the closest match.
    if (available.length === 0 && isSearchable && searchInput) {
      log.debug(
        `Nenhuma opção após filtro — limpando input para: ${wrapperSelector}`,
      );
      const nativeSetter = Object.getOwnPropertyDescriptor(
        window.HTMLInputElement.prototype,
        "value",
      )?.set;
      nativeSetter
        ? nativeSetter.call(searchInput, "")
        : (searchInput.value = "");
      searchInput.dispatchEvent(new Event("input", { bubbles: true }));

      // Wait for full list to re-populate (async or local)
      await waitForAsyncOptions(menu, 1500);

      available = Array.from(
        menu.querySelectorAll<HTMLElement>(
          ".react-select__option:not(.react-select__option--is-disabled)",
        ),
      );
    }

    if (available.length === 0) {
      log.warn(`Nenhuma opção disponível para: ${wrapperSelector}`);
      // Close dropdown
      document.body.dispatchEvent(
        new MouseEvent("mousedown", { bubbles: true }),
      );
      return false;
    }

    if (isMulti) {
      const count = Math.min(3, available.length);
      const shuffled = [...available]
        .sort(() => Math.random() - 0.5)
        .slice(0, count);
      for (const opt of shuffled) {
        opt.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
        opt.dispatchEvent(new MouseEvent("click", { bubbles: true }));
        await new Promise<void>((r) => setTimeout(r, 60));
      }
      // Close the menu if still open after multi selection
      document.body.dispatchEvent(
        new MouseEvent("mousedown", { bubbles: true }),
      );
      return true;
    }

    // Single-select: prefer option whose text matches the desired value
    const lower = value.toLowerCase();
    const matched =
      available.find(
        (opt) =>
          opt.textContent?.toLowerCase().includes(lower) ||
          opt.dataset["value"]?.toLowerCase() === lower,
      ) ?? available[0];

    matched.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
    matched.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    return true;
  },

Dependencies (Outgoing)

graph LR fill["fill"] waitForReactSelectMenu["waitForReactSelectMenu"] waitForAsyncOptions["waitForAsyncOptions"] fill -->|calls| waitForReactSelectMenu fill -->|calls| waitForAsyncOptions style fill fill:#dbeafe,stroke:#2563eb,stroke-width:2px click fill "7aa5c88a65ec6164.html" click waitForReactSelectMenu "46ce212e5e187ed9.html" click waitForAsyncOptions "5cd4187e2bc102f2.html"

No incoming dependencies.