import {
  StoreBase,
  getAppInit,
  getStoreInit
} from "./chunk-M3J4MINX.js";

// test/Locator.ts
var Locator = class _Locator {
  // arg could be one of the following, must always
  // - DOM element
  // - string selector
  // - d3-selection
  constructor(arg, opts = {}) {
    //HTMLElement | SVGElement | SVGGElement | SVGLineElement | SVGRectElement | SVGSVGElement | SVGTextElement
    this.opts = {
      intervalWait: 25,
      maxWait: 3e3
    };
    this.pending = [];
    this.result = [];
    Object.assign(this.opts, opts);
    this.setRoot(arg);
  }
  // convenience method to allow await Locator.init(elem).whenVisible('')
  static init(elem) {
    return new _Locator(elem);
  }
  setRoot(arg) {
    if (typeof arg == "string") {
      const node = document.body.querySelector(arg);
      if (!node) throw `missing elem by selector='${arg}'`;
      this.rootElem = node;
    } else if (typeof arg.node == "function") {
      const numMatch = arg.size();
      if (!numMatch) throw `missing elem from d3-selection`;
      this.rootElem = arg.node();
    } else {
      this.rootElem = arg;
    }
    this.pending = [];
  }
  root(arg) {
    if (arg) this.setRoot(arg);
    return this.rootElem;
  }
  // selector: string selector such as '#my-id', '.my-class', `[data-testid='my-id-here']`, `[role='button']`, etc
  shows(selector, opts = {}) {
    this.pending.push([
      shows,
      {
        selector,
        targetCount: opts.count || 1,
        //'>=1',
        visibility: opts.visibility ?? true,
        intervalWait: opts.intervalWait || this.opts.intervalWait,
        maxWait: opts.maxWait || this.opts.maxWait,
        attr: opts.attr || {}
      }
    ]);
    return this;
  }
  // same argument as this.shows(), but visibility is not checked
  // and assumes that the elements targeted by the selector are
  // ready to be queried
  find(selector) {
    this.pending.push([find, { selector }]);
    return this;
  }
  // cannot chain another method after get(), since
  // the retured promise would require wrapping in parentheses
  //
  // get()
  // - run the pending functions in sequence and return the resolved
  //   queried elems or its properties based on the argument
  //
  // Arguments:
  //  arg: undefined | string | number | callback
  //
  // returns
  //  - no argument: get the matched elements as an array
  //  - callback function: passes the matched elements to the callback and returns its returned value
  //  - number: use as index key to return elems[index]
  //  - string: see the processing in the
  async get(arg) {
    const pending = this.pending;
    this.pending = [];
    let elems = [this.rootElem];
    for (const [fxn, opts] of pending) {
      elems = await fxn(elems, opts);
    }
    if (!arguments.length || arg === void 0) return elems;
    if (typeof arg === "function") return arg(elems);
    if (typeof arg === "number") return elems[arg];
    if (typeof arg === "string") {
      if (arg == "length") return elems.length;
      const key = arg.slice(1);
      if (!key) return elems;
      const char = arg[0];
      if (char === ".") return elems.map((elem) => elem[key]);
      if (char === "@") return elems.map((elem) => elem.getAttribute(key));
      if (char === ":") return elems.map((elem) => elem.style[key]);
    }
    throw `invalid Locator.get() argument='${arg}'`;
  }
  // propName: string property name, can be dot-separated like 'style.padding'
  // value: string, number, boolean, any valid value for the given property name
  async set(propName, value2) {
    if (!propName) throw `invalid Locator.set() propName='${propName}'`;
    const keyChain = propName.split(".");
    const lastKey = keyChain.pop();
    if (!lastKey) throw `invalid Locator.set() propName='${propName}'`;
    const elems = await this.get();
    for (const elem of elems) {
      let obj = elem;
      for (const key of keyChain) obj = obj[key];
      obj[lastKey] = value2;
    }
    return elems;
  }
  /*** convenient aliased methods for .get(arg) ***/
  async length() {
    return await this.get("length");
  }
  async value(val) {
    if (arguments.length) return await this.set("value", val);
    return await this.get(".value");
  }
  async text(str) {
    if (arguments.length) return await this.set("innerText", str);
    return await this.get(".innerText");
  }
  hides(selector) {
    return this.shows(selector, { visibility: false });
  }
  async click(arg) {
    const elems = await this.get(arg);
    const arr = Array.isArray(elems) ? elems : [elems];
    for (const elem of arr) {
      const box = elem.getBoundingClientRect();
      const clientX = box.x + 0.5 * box.width;
      const clientY = box.y + box.height;
      elem.dispatchEvent(new MouseEvent("click", { bubbles: true, clientX, clientY }));
    }
    return elems;
  }
};
async function shows(elems, opts) {
  return new Promise((resolve) => {
    const { selector, targetCount, intervalWait, maxWait, visibility, attr } = opts;
    const matchedElems = /* @__PURE__ */ new Set();
    let elapsed = 0, pending = false;
    const i = setInterval(() => {
      if (pending) return;
      pending = true;
      for (const elem of elems) {
        for (const descendant of elem.querySelectorAll(selector)) {
          if (matchedElems.has(descendant)) continue;
          if (descendant.checkVisibility() === visibility) {
            if (!attr) matchedElems.add(descendant);
            else {
              const kvEntries = Object.entries(attr);
              const matchedKeys = [];
              for (const [key, value2] of kvEntries) {
                if (elem.getAttribute(key) !== value2) matchedKeys.push(key);
              }
              if (matchedKeys.length === kvEntries.length) matchedElems.add(descendant);
            }
          }
        }
      }
      elapsed += intervalWait;
      if (matchedElems.size >= targetCount || elapsed > maxWait) {
        clearInterval(i);
        resolve([...matchedElems]);
      }
      pending = false;
    }, intervalWait);
  });
}
async function find(elems, opts) {
  for (const elem of elems) {
    const match = elem.querySelector(opts.selector);
    if (match) {
      return [match];
    }
  }
  return [];
}

// test/test.helpers.js
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
async function detectLst(_opts = {}) {
  const defaults = {
    target: _opts.target || _opts.elem,
    selector: _opts.selector,
    maxTime: 12e3,
    // default is increased from 5 to 12 specifically for gdc live tests with high latency
    observe: {
      childList: true,
      subtree: true,
      attributes: true,
      characterData: true
    }
  };
  if (!_opts.matcher) {
    defaults.count = 1;
    defaults.matchAs = "=";
  }
  const opts = Object.assign(defaults, _opts);
  if (!opts.target) throw `missing opts.target (alias for opts.elem)`;
  if (!opts.trigger && opts.count > 0) await sleep(0);
  const start = Date.now();
  return new Promise((resolve, reject) => {
    if (!opts.matcher && opts.selector) {
      const elems = opts.target.querySelectorAll(opts.selector);
      const matched = matchedCount(elems.length, opts);
      if (matched) {
        resolve([...elems]);
        return;
      }
    }
    const callback = (mutations) => {
      const elems = opts.selector ? [...opts.target.querySelectorAll(opts.selector)] : [opts.target];
      const mutated = mutations.filter((m) => elems.includes(m.target));
      if (!mutated.length && opts.count !== 0) return;
      const matched = opts.matcher ? opts.matcher(mutated, observer) : matchedCount(elems.length, opts);
      const expired = Date.now() - start > opts.maxTime;
      if (matched || expired) {
        observer.disconnect();
        if (t) clearTimeout(t);
        if (matched) resolve(Array.isArray(matched) ? matched : elems);
        else reject(`test exceeded maxTime=${opts.maxTime}`);
      }
    };
    const observer = new MutationObserver(callback);
    observer.observe(opts.target, opts.observe);
    let t;
    if (opts.maxTime) {
      t = setTimeout(() => {
        if (opts.count === 0) {
        } else reject(`the elem '${opts.target}' did not render within ${opts.maxTime} milliseconds`);
      }, opts.maxTime);
    }
    if (opts.trigger) opts.trigger();
  });
}
function matchedCount(actual, opts) {
  const expected = opts.count;
  switch (opts.matchAs) {
    case "=":
      return actual === expected;
    case "<":
      return actual < expected;
    case "<=":
      return actual <= expected;
    case ">":
      return actual > expected;
    case ">=":
      return actual >= expected;
    default:
      throw `unknown matchAs='${opts.matchAs}'`;
  }
}
async function detectAttr(opts) {
  if (!opts.observe) opts.observe = {};
  opts.observe.attributes = true;
  const lst = await detectLst(opts);
  return lst;
}
async function detectChildAttr(opts) {
  if (!opts.observe) opts.observe = {};
  opts.observe.subtree = true;
  opts.observe.attributes = true;
  if (opts.attr) {
    if (opts.matcher) console.warn(`will not use opts.attr since an opts.matcher is already available`);
    else {
      if (!("count" in opts)) opts.count = 1;
      opts.matcher = (mutations) => {
        const matched = mutations.filter((m) => {
          for (const key in opts.attr) {
            if (typeof opts.attr[key] === "function") {
              const value2 = m.target.getAttribute(key);
              if (!opts.attr[key](value2)) return false;
            } else if (value != opts.attr[key]) {
              return false;
            }
          }
          return true;
        });
        if (matched.length === opts.count) return matched.map((d) => d.target);
      };
    }
  }
  const lst = await detectLst(opts);
  return lst;
}
async function detectStyle(opts) {
  if (!opts.observe) opts.observe = {};
  opts.observe.attributes = true;
  opts.observe.attributeFilter = ["style"];
  const lst = await detectLst(opts);
  return lst;
}
async function detectChildStyle(opts) {
  if (!opts.observe) opts.observe = {};
  opts.observe.subtree = true;
  opts.observe.attributes = true;
  opts.observe.attributeFilter = ["style"];
  if (opts.style) {
    if (opts.matcher) console.warn(`will not use opts.style since an opts.matcher is already available`);
    else {
      if (!("count" in opts)) opts.count = 1;
      opts.matcher = (mutations) => {
        const matched = mutations.filter((m) => {
          for (const key in opts.style) {
            const value2 = m.target.style[key];
            if (typeof opts.style[key] == "function") {
              if (!opts.style[key](value2)) return false;
            } else if (value2 != opts.style[key]) return false;
          }
          return true;
        });
        if (matched.length === opts.count) return matched.map((d) => d.target);
      };
    }
  }
  const lst = await detectLst(opts);
  return lst;
}
async function detectGt(opts) {
  opts.matchAs = ">";
  const lst = await detectLst(opts);
  return lst;
}
async function detectGte(opts) {
  opts.matchAs = ">=";
  const lst = await detectLst(opts);
  return lst;
}
async function detectOne(opts) {
  opts.count = 1;
  const lst = await detectLst(opts);
  return lst[0];
}
async function whenVisible(elem, opts = {}) {
  const intervalWait = 25;
  const totalWait = opts.wait || 5e3;
  const numRepeats = totalWait / intervalWait;
  return new Promise((resolve, reject) => {
    let j = 0;
    const i = setInterval(() => {
      if (elem.style.display != "none") {
        clearInterval(i);
        resolve(elem);
      } else {
        j++;
        if (j > numRepeats) {
          reject(`elem did not become visible within 5 seconds`);
          clearInterval(i);
        }
      }
    }, intervalWait);
  });
}
var TestAppStore = class _TestAppStore extends StoreBase {
  static type = "store";
  constructor(opts) {
    super(opts);
    this.type = _TestAppStore.type;
    this.defaultState = {
      debug: true
    };
  }
  async init() {
  }
};
var storeInit = getStoreInit(TestAppStore);
var TestApp = class {
  static type = "app";
  constructor(opts) {
    this.type = "app";
    this.opts = opts;
    this.fetchOpts = opts.fetchOpts;
  }
  async init() {
    try {
      this.store = await storeInit({ app: this.api, state: this.opts.state });
      this.state = await this.store.copyState();
      if (this.opts.dom) this.dom = this.opts.dom;
      await this.api.dispatch();
    } catch (e) {
      if (e.stack) console.log(e.stack);
      else throw `TestApp Error: ${e}`;
    }
  }
};
var testAppInit = getAppInit(TestApp);

export {
  Locator,
  sleep,
  detectLst,
  detectAttr,
  detectChildAttr,
  detectStyle,
  detectChildStyle,
  detectGt,
  detectGte,
  detectOne,
  whenVisible
};
//# sourceMappingURL=chunk-DJQTUDJM.js.map
