eaeb29cb创建于 2025年2月7日历史提交
import Map from '../../src/ol/Map.js';
import View from '../../src/ol/View.js';
import {setLevel as setLogLevel} from '../../src/ol/console.js';
import {defaults as defaultInteractions} from '../../src/ol/interaction/defaults.js';

setLogLevel('error');

(function (global) {
  function afterLoad(type, path, next) {
    const client = new XMLHttpRequest();
    client.open('GET', path, true);
    client.onload = function () {
      let data;
      if (type === 'xml') {
        data = client.responseXML;
      } else {
        data = client.responseText;
      }
      if (!data) {
        throw new Error(path + ' loading failed: ' + client.status);
      }
      next(data);
    };
    client.send();
  }

  /**
   * @param {string} path Relative path to file (e.g. 'spec/ol/foo.json').
   * @param {function(Object): void} next Function to call with response object on
   *     success.  On failure, an error is thrown with the reason.
   */
  global.afterLoadJson = function (path, next) {
    afterLoad('json', path, next);
  };

  /**
   * @param {string} path Relative path to file (e.g. 'spec/ol/foo.txt').
   * @param {function(string): void} next Function to call with response text on
   *     success.  On failure, an error is thrown with the reason.
   */
  global.afterLoadText = function (path, next) {
    afterLoad('text', path, next);
  };

  /**
   * @param {string} path Relative path to file (e.g. 'spec/ol/foo.xml').
   * @param {function(Document): void} next Function to call with response xml on
   *     success.  On failure, an error is thrown with the reason.
   */
  global.afterLoadXml = function (path, next) {
    afterLoad('xml', path, next);
  };

  // extensions to expect.js
  const expect = global.expect;

  /**
   * Assert value is within some tolerance of a number.
   * @param {number} n Number.
   * @param {number} tol Tolerance.
   * @return {expect.Assertion} The assertion.
   */
  expect.Assertion.prototype.roughlyEqual = function (n, tol) {
    this.assert(
      Math.abs(this.obj - n) <= tol,
      function () {
        return (
          'expected ' +
          expect.stringify(this.obj) +
          ' to be within ' +
          tol +
          ' of ' +
          n
        );
      },
      function () {
        return (
          'expected ' +
          expect.stringify(this.obj) +
          ' not to be within ' +
          tol +
          ' of ' +
          n
        );
      },
    );
    return this;
  };

  function getChildNodes(node, options) {
    // check whitespace
    if (options && options.includeWhiteSpace) {
      return node.childNodes;
    }
    const nodes = [];
    for (let i = 0, ii = node.childNodes.length; i < ii; i++) {
      const child = node.childNodes[i];
      if (child.nodeType == 1) {
        // element node, add it
        nodes.push(child);
      } else if (child.nodeType == 3) {
        // text node, add if non empty
        if (
          child.nodeValue &&
          child.nodeValue.replace(/^\s*(.*?)\s*$/, '$1') !== ''
        ) {
          nodes.push(child);
        }
      } else if (child.nodeType == 4) {
        // CDATA section, add it
        nodes.push(child);
      }
    }
    if (options && options.ignoreElementOrder) {
      nodes.sort(function (a, b) {
        return a.nodeName > b.nodeName ? 1 : a.nodeName < b.nodeName ? -1 : 0;
      });
    }
    return nodes;
  }

  function assertElementNodesEqual(node1, node2, options, errors) {
    const testPrefix = options && options.prefix === true;
    if (node1.nodeType !== node2.nodeType) {
      errors.push(
        'nodeType test failed for: ' +
          node1.nodeName +
          ' | ' +
          node2.nodeName +
          ' | expected ' +
          node1.nodeType +
          ' to equal ' +
          node2.nodeType,
      );
    }
    if (testPrefix) {
      if (node1.nodeName !== node2.nodeName) {
        errors.push(
          'nodeName test failed for: ' +
            node1.nodeName +
            ' | ' +
            node2.nodeName +
            ' | expected ' +
            node1.nodeName +
            ' to equal ' +
            node2.nodeName,
        );
      }
    } else {
      const n1 = node1.nodeName.split(':').pop();
      const n2 = node2.nodeName.split(':').pop();
      if (n1 !== n2) {
        errors.push(
          'nodeName test failed for: ' +
            node1.nodeName +
            ' | ' +
            node2.nodeName +
            ' | expected ' +
            n1 +
            ' to equal ' +
            n2,
        );
      }
    }
    // for text nodes compare value
    if (node1.nodeType === 3) {
      const nv1 = node1.nodeValue.replace(/\s/g, '');
      const nv2 = node2.nodeValue.replace(/\s/g, '');
      if (nv1 !== nv2) {
        errors.push(
          'nodeValue test failed | expected ' + nv1 + ' to equal ' + nv2,
        );
      }
    } else if (node1.nodeType === 4) {
      // for CDATA sections compare nodeValue directly
      if (node1.nodeValue !== node2.nodeValue) {
        errors.push(
          'nodeValue cdata test failed | expected ' +
            node1.nodeValue +
            ' to equal ' +
            node2.nodeValue,
        );
      }
    } else if (node1.nodeType === 1) {
      // for element type nodes compare namespace, attributes, and children
      // test namespace alias and uri
      if (node1.prefix || node2.prefix) {
        if (testPrefix) {
          if (node1.prefix !== node2.prefix) {
            errors.push(
              'Prefix test failed for: ' +
                node1.nodeName +
                ' | expected ' +
                node1.prefix +
                ' to equal ' +
                node2.prefix,
            );
          }
        }
      }
      if (node1.namespaceURI || node2.namespaceURI) {
        if (node1.namespaceURI !== node2.namespaceURI) {
          errors.push(
            'namespaceURI test failed for: ' +
              node1.nodeName +
              ' | expected ' +
              node1.namespaceURI +
              ' to equal ' +
              node2.namespaceURI,
          );
        }
      }
      // compare attributes - disregard xmlns given namespace handling above
      let node1AttrLen = 0;
      const node1Attr = {};
      let node2AttrLen = 0;
      const node2Attr = {};
      let ga, ea, gn, en;
      let i, ii;
      if (node1.attributes) {
        for (i = 0, ii = node1.attributes.length; i < ii; ++i) {
          ga = node1.attributes[i];
          if (ga.specified === undefined || ga.specified === true) {
            if (ga.name.split(':').shift() != 'xmlns') {
              gn = testPrefix ? ga.name : ga.name.split(':').pop();
              node1Attr[gn] = ga;
              ++node1AttrLen;
            }
          }
        }
      }
      if (node2.attributes) {
        for (i = 0, ii = node2.attributes.length; i < ii; ++i) {
          ea = node2.attributes[i];
          if (ea.specified === undefined || ea.specified === true) {
            if (ea.name.split(':').shift() != 'xmlns') {
              en = testPrefix ? ea.name : ea.name.split(':').pop();
              node2Attr[en] = ea;
              ++node2AttrLen;
            }
          }
        }
      }
      if (node1AttrLen !== node2AttrLen) {
        errors.push(
          'Number of attributes test failed for: ' +
            node1.nodeName +
            ' | expected ' +
            node1AttrLen +
            ' to equal ' +
            node2AttrLen,
        );
      }
      for (const name in node1Attr) {
        if (node2Attr[name] === undefined) {
          errors.push(
            'Attribute name ' +
              node1Attr[name].name +
              ' expected for element ' +
              node1.nodeName,
          );
          break;
        }
        // test attribute namespace
        if (node1Attr[name].namespaceURI !== node2Attr[name].namespaceURI) {
          errors.push(
            'namespaceURI attribute test failed for: ' +
              node1.nodeName +
              ' | expected ' +
              node1Attr[name].namespaceURI +
              ' to equal ' +
              node2Attr[name].namespaceURI,
          );
        }
        if (node1Attr[name].value !== node2Attr[name].value) {
          errors.push(
            'Attribute value test failed for: ' +
              node1.nodeName +
              ' | expected ' +
              node1Attr[name].value +
              ' to equal ' +
              node2Attr[name].value,
          );
        }
      }
      // compare children
      const node1ChildNodes = getChildNodes(node1, options);
      const node2ChildNodes = getChildNodes(node2, options);
      if (node1ChildNodes.length !== node2ChildNodes.length) {
        // check if all child nodes are text, they could be split up in
        // 4096 chunks
        // if so, ignore the childnode count error
        let allText = true;
        let c, cc;
        for (c = 0, cc = node1ChildNodes.length; c < cc; ++c) {
          if (node1ChildNodes[c].nodeType !== 3) {
            allText = false;
            break;
          }
        }
        for (c = 0, cc = node2ChildNodes.length; c < cc; ++c) {
          if (node2ChildNodes[c].nodeType !== 3) {
            allText = false;
            break;
          }
        }
        if (!allText) {
          errors.push(
            'Number of childNodes test failed for: ' +
              node1.nodeName +
              ' | expected ' +
              node1ChildNodes.length +
              ' to equal ' +
              node2ChildNodes.length,
          );
        }
      }
      // only compare if they are equal
      if (node1ChildNodes.length === node2ChildNodes.length) {
        for (let j = 0, jj = node1ChildNodes.length; j < jj; ++j) {
          assertElementNodesEqual(
            node1ChildNodes[j],
            node2ChildNodes[j],
            options,
            errors,
          );
        }
      }
    }
  }

  /**
   * @typedef {Object} XMLEqlOptions
   * @property {boolean} [includeWhiteSpace] IncludeWhiteSpace.
   * @property {boolean} [ignoreElementOrder] IgnoreElementOrder.
   */

  /**
   * Checks if the XML document sort of equals another XML document.
   * @param {Object} obj The other object.
   * @param {XMLEqlOptions} [options] The options.
   * @return {expect.Assertion} The assertion.
   */
  expect.Assertion.prototype.xmleql = function (obj, options) {
    if (obj && obj.nodeType == 9) {
      obj = obj.documentElement;
    }
    if (this.obj && this.obj.nodeType == 9) {
      this.obj = this.obj.documentElement;
    }
    const errors = [];
    assertElementNodesEqual(obj, this.obj, options, errors);
    const result = errors.length === 0;
    this.assert(
      !!result,
      function () {
        return (
          'expected ' +
          expect.stringify(this.obj) +
          ' to sort of equal ' +
          expect.stringify(obj) +
          '\n' +
          errors.join('\n')
        );
      },
      function () {
        return (
          'expected ' +
          expect.stringify(this.obj) +
          ' to sort of not equal ' +
          expect.stringify(obj) +
          '\n' +
          errors.join('\n')
        );
      },
    );
    return this;
  };

  global.createMapDiv = function (width, height) {
    const target = document.createElement('div');
    const style = target.style;
    style.position = 'absolute';
    style.left = '-1000px';
    style.top = '-1000px';
    style.width = width + 'px';
    style.height = height + 'px';
    document.body.appendChild(target);

    return target;
  };

  /**
   * @param {import('../../src/ol/Map.js').default|undefined} map Map
   * @param {HTMLElement} [target] Node in dom
   */
  global.disposeMap = function (map, target) {
    target?.remove();
    if (map) {
      map.getTargetElement()?.remove();
      map.dispose();
    }
  };

  const features = {
    ArrayBuffer: 'ArrayBuffer' in global,
    'ArrayBuffer.isView': 'ArrayBuffer' in global && !!ArrayBuffer.isView,
    FileReader: 'FileReader' in global,
    Uint8ClampedArray: 'Uint8ClampedArray' in global,
    WebGL: false,
  };

  /**
   * Allow tests to be skipped where certain features are not available.  The
   * provided key must be in the above `features` lookup.  Keys should
   * correspond to the feature that is required, but can be any string.
   * @param {string} key The required feature name.
   * @return {Object} An object with a `describe` function that will run tests
   *     if the required feature is available and skip them otherwise.
   */
  global.where = function (key) {
    if (!(key in features)) {
      throw new Error('where() called with unknown key: ' + key);
    }
    return {
      describe: features[key] ? global.describe : global.xdescribe,
      it: features[key] ? global.it : global.xit,
    };
  };

  // throw if anybody appends a div to the body and doesn't remove it
  afterEach(function () {
    const garbage = document.body.getElementsByTagName('div');
    if (garbage.length) {
      throw new Error('Found extra <div> elements in the body');
    }
  });

  /**
   * Defines and registers a custom HTML element `ol-map`.
   *
   * @param {Object} options Object holding different options used in
   *  constructor of OLComponent. Currently 'interactionOpts' can be set as
   *  child property.
   */
  global.defineCustomMapEl = function (options) {
    // custom HTML element holding the OL map
    class OLComponent extends HTMLElement {
      constructor() {
        super();
        const shadow = this.attachShadow({mode: 'open'});

        const style = document.createElement('style');
        style.innerText = `
          :host {
            display: block;
          }
        `;
        shadow.appendChild(style);

        const target = document.createElement('div');
        target.style.width = '100%';
        target.style.height = '100%';
        shadow.appendChild(target);

        this.map = new Map({
          target: target,
          interactions: defaultInteractions(options.interactionOpts),
          view: new View({
            center: [0, 0],
            resolutions: [1],
            zoom: 8,
          }),
        });
      }
    }
    if (customElements.get('ol-map') === undefined) {
      customElements.define('ol-map', OLComponent);
    }
  };
})(window);