selectOption function infrastructure ✓ 81.0%

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

Metrics

LOC: 165 Complexity: 35 Params: 3 Coverage: 81.0% (51/63 lines, 0x executed)

Signature

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

Architecture violations

View all

  • [warning] max-cyclomatic-complexity: 'selectOption' has cyclomatic complexity 35 (max 10)
  • [warning] max-lines: 'selectOption' has 165 lines (max 80)

Source Code

async function selectOption(
  wrapper: HTMLElement,
  value: string,
): Promise<boolean> {
  const searchInput = wrapper.querySelector<HTMLInputElement>(
    ".ant-select-selection-search-input, .ant-select-input",
  );

  const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
    window.HTMLInputElement.prototype,
    "value",
  )?.set;

  // aria-controls on the combobox input points to the listbox ID managed by
  // this specific <Select> instance. We use it to scope ALL dropdown queries
  // to the correct portal element, preventing cross-contamination when another
  // select's dropdown is still animating closed (race condition).
  const listboxId = searchInput?.getAttribute("aria-controls") ?? null;

  const OPTION_SELECTOR =
    ".ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option";

  /**
   * Returns the dropdown portal that belongs to THIS select wrapper.
   * Primary: resolves via aria-controls → listbox element → closest dropdown.
   * Fallback: first visible dropdown (legacy / SSR builds that omit aria attrs).
   */
  function getOwnDropdown(): HTMLElement | null {
    if (listboxId) {
      const lb = document.getElementById(listboxId);
      if (lb) {
        const dd = lb.closest<HTMLElement>(".ant-select-dropdown");
        if (dd && !dd.classList.contains("ant-select-dropdown-hidden"))
          return dd;
      }
    }
    // Fallback: first visible dropdown
    return document.querySelector<HTMLElement>(
      ".ant-select-dropdown:not(.ant-select-dropdown-hidden)",
    );
  }

  /**
   * Waits until THIS select's dropdown is fully closed (or max ~600 ms).
   * Prevents subsequent selects from picking options from a lingering portal.
   */
  async function waitForDropdownClose(): Promise<void> {
    const deadline = Date.now() + 600;
    while (Date.now() < deadline) {
      const dd = listboxId
        ? document
            .getElementById(listboxId)
            ?.closest<HTMLElement>(".ant-select-dropdown")
        : null;
      if (
        !dd ||
        dd.classList.contains("ant-select-dropdown-hidden") ||
        !document.contains(dd)
      )
        return;
      await new Promise((r) => setTimeout(r, 50));
    }
  }

  /**
   * Try to find an option matching `val` in THIS wrapper's dropdown.
   * Strategy: exact match first, then word-boundary prefix match.
   * Word-boundary avoids false positives like "TO" matching "Mato Grosso".
   */
  function findMatchingOption(val: string): HTMLElement | null {
    if (!val) return null;
    const norm = val.toLowerCase();
    const prefixRe = new RegExp(
      `\\b${val.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
      "i",
    );
    const dd = getOwnDropdown();
    if (!dd) return null;
    const options = dd.querySelectorAll<HTMLElement>(".ant-select-item-option");
    for (const opt of options) {
      const t = opt.getAttribute("title") ?? opt.textContent?.trim() ?? "";
      if (t.toLowerCase() === norm) return opt;
    }
    for (const opt of options) {
      const t = opt.getAttribute("title") ?? opt.textContent?.trim() ?? "";
      if (prefixRe.test(t)) return opt;
    }
    return null;
  }

  /** Pick a random non-disabled option from THIS wrapper's dropdown. */
  function pickRandomOption(): HTMLElement | null {
    const dd = getOwnDropdown();
    if (!dd) return null;
    const options = Array.from(
      dd.querySelectorAll<HTMLElement>(
        ".ant-select-item-option:not(.ant-select-item-option-disabled)",
      ),
    );
    if (options.length > 0) {
      return options[Math.floor(Math.random() * options.length)];
    }
    return null;
  }

  /** Clear the search input so the full option list is restored. */
  function clearSearchInput(): void {
    if (searchInput && !searchInput.readOnly && nativeInputValueSetter) {
      nativeInputValueSetter.call(searchInput, "");
      searchInput.dispatchEvent(new Event("input", { bubbles: true }));
    }
  }

  // Phase 1 — Check options that are already visible right after the dropdown opens
  // (static selects). waitForElement resolves immediately when the element exists,
  // so this adds no delay for already-rendered dropdowns.
  await waitForElement(OPTION_SELECTOR, 800);

  const match1 = findMatchingOption(value);
  if (match1) {
    simulateClick(match1);
    await waitForDropdownClose();
    return true;
  }

  // Phase 2 — For AJAX / searchable selects: type the value to trigger server-side
  // filtering or lazy-loading. Only done when Phase 1 found no match.
  if (searchInput && value && !searchInput.readOnly && nativeInputValueSetter) {
    nativeInputValueSetter.call(searchInput, value);
    searchInput.dispatchEvent(new Event("input", { bubbles: true }));
    searchInput.dispatchEvent(new Event("change", { bubbles: true }));

    await waitForElement(OPTION_SELECTOR, 2000);

    const match2 = findMatchingOption(value);
    if (match2) {
      simulateClick(match2);
      await waitForDropdownClose();
      return true;
    }
  }

  // Phase 3 — No match found. Clear any typed search to restore the full option
  // list, then pick a random valid option so the field always ends up with a
  // legitimate value from the actual select options.
  clearSearchInput();
  await waitForElement(OPTION_SELECTOR, 1500);

  const random = pickRandomOption();
  if (random) {
    const randomText =
      random.getAttribute("title") ?? random.textContent?.trim() ?? "";
    log.debug(
      `Fase 3 — nenhuma opção correspondeu a "${value}"; selecionando aleatório: "${randomText}"`,
    );
    simulateClick(random);
    await waitForDropdownClose();
    return true;
  }

  log.warn(
    `Fase 3 — dropdown aberto mas nenhuma opção encontrada (.ant-select-item-option)`,
  );
  return false;
}

Members

Name Kind Visibility Status Signature
waitForDropdownClose function - waitForDropdownClose(): : Promise<void>
findMatchingOption function - findMatchingOption(val: string): : HTMLElement | null
pickRandomOption function - pickRandomOption(): : HTMLElement | null
clearSearchInput function - clearSearchInput(): : void

Dependencies (Outgoing)

graph LR selectOption["selectOption"] waitForElement["waitForElement"] findMatchingOption["findMatchingOption"] simulateClick["simulateClick"] waitForDropdownClose["waitForDropdownClose"] clearSearchInput["clearSearchInput"] pickRandomOption["pickRandomOption"] t["t"] selectOption -->|calls| waitForElement selectOption -->|calls| findMatchingOption selectOption -->|calls| simulateClick selectOption -->|calls| waitForDropdownClose selectOption -->|calls| clearSearchInput selectOption -->|calls| pickRandomOption selectOption -->|dynamic_call| t style selectOption fill:#dbeafe,stroke:#2563eb,stroke-width:2px click selectOption "fe5ab67a02962060.html" click waitForElement "230bbf0882ca7c81.html" click findMatchingOption "fca1c8c5a316b388.html" click simulateClick "7c6c21320aaee4c4.html" click waitForDropdownClose "c6924900ef0c48e4.html" click clearSearchInput "be331800440f77a6.html" click pickRandomOption "911dad38971ca0fd.html" click t "8e8864a3c5cfd1e1.html"
TargetType
waitForElement calls
findMatchingOption calls
simulateClick calls
waitForDropdownClose calls
clearSearchInput calls
pickRandomOption calls
t dynamic_call

Impact (Incoming)

graph LR selectOption["selectOption"] fill["fill"] fill -->|calls| selectOption style selectOption fill:#dbeafe,stroke:#2563eb,stroke-width:2px click selectOption "fe5ab67a02962060.html" click fill "90d6eb0f98a487a1.html"
SourceType
fill calls