getNextKeyDef.js 5.51 KB
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.getNextKeyDef = getNextKeyDef;
var bracketDict;

(function (bracketDict) {
  bracketDict["{"] = "}";
  bracketDict["["] = "]";
})(bracketDict || (bracketDict = {}));

var legacyModifiers;

(function (legacyModifiers) {
  legacyModifiers["alt"] = "alt";
  legacyModifiers["ctrl"] = "ctrl";
  legacyModifiers["meta"] = "meta";
  legacyModifiers["shift"] = "shift";
})(legacyModifiers || (legacyModifiers = {}));

var legacyKeyMap;
/**
 * Get the next key from keyMap
 *
 * Keys can be referenced by `{key}` or `{special}` as well as physical locations per `[code]`.
 * Everything else will be interpreted as a typed character - e.g. `a`.
 * Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
 * Keeping the key pressed can be written as `{key>}`.
 * When keeping the key pressed you can choose how long (how many keydown and keypress) the key is pressed `{key>3}`.
 * You can then release the key per `{key>3/}` or keep it pressed and continue with the next key.
 * Modifiers like `{shift}` imply being kept pressed. This can be turned of per `{shift/}`.
 */

(function (legacyKeyMap) {
  legacyKeyMap["ctrl"] = "Control";
  legacyKeyMap["del"] = "Delete";
  legacyKeyMap["esc"] = "Escape";
  legacyKeyMap["space"] = " ";
})(legacyKeyMap || (legacyKeyMap = {}));

function getNextKeyDef(text, options) {
  var _options$keyboardMap$;

  const {
    type,
    descriptor,
    consumedLength,
    releasePrevious,
    releaseSelf,
    repeat
  } = readNextDescriptor(text);
  const keyDef = (_options$keyboardMap$ = options.keyboardMap.find(def => {
    if (type === '[') {
      var _def$code;

      return ((_def$code = def.code) == null ? void 0 : _def$code.toLowerCase()) === descriptor.toLowerCase();
    } else if (type === '{') {
      var _def$key;

      const key = mapLegacyKey(descriptor);
      return ((_def$key = def.key) == null ? void 0 : _def$key.toLowerCase()) === key.toLowerCase();
    }

    return def.key === descriptor;
  })) != null ? _options$keyboardMap$ : {
    key: 'Unknown',
    code: 'Unknown',
    [type === '[' ? 'code' : 'key']: descriptor
  };
  return {
    keyDef,
    consumedLength,
    releasePrevious,
    releaseSelf,
    repeat
  };
}

function readNextDescriptor(text) {
  let pos = 0;
  const startBracket = text[pos] in bracketDict ? text[pos] : '';
  pos += startBracket.length; // `foo{{bar` is an escaped char at position 3,
  // but `foo{{{>5}bar` should be treated as `{` pressed down for 5 keydowns.

  const startBracketRepeated = startBracket ? text.match(new RegExp(`^\\${startBracket}+`))[0].length : 0;
  const isEscapedChar = startBracketRepeated === 2 || startBracket === '{' && startBracketRepeated > 3;
  const type = isEscapedChar ? '' : startBracket;
  return {
    type,
    ...(type === '' ? readPrintableChar(text, pos) : readTag(text, pos, type))
  };
}

function readPrintableChar(text, pos) {
  const descriptor = text[pos];
  assertDescriptor(descriptor, text, pos);
  pos += descriptor.length;
  return {
    consumedLength: pos,
    descriptor,
    releasePrevious: false,
    releaseSelf: true,
    repeat: 1
  };
}

function readTag(text, pos, startBracket) {
  var _text$slice$match, _text$slice$match$, _text$slice$match2;

  const releasePreviousModifier = text[pos] === '/' ? '/' : '';
  pos += releasePreviousModifier.length;
  const descriptor = (_text$slice$match = text.slice(pos).match(/^\w+/)) == null ? void 0 : _text$slice$match[0];
  assertDescriptor(descriptor, text, pos);
  pos += descriptor.length;
  const repeatModifier = (_text$slice$match$ = (_text$slice$match2 = text.slice(pos).match(/^>\d+/)) == null ? void 0 : _text$slice$match2[0]) != null ? _text$slice$match$ : '';
  pos += repeatModifier.length;
  const releaseSelfModifier = text[pos] === '/' || !repeatModifier && text[pos] === '>' ? text[pos] : '';
  pos += releaseSelfModifier.length;
  const expectedEndBracket = bracketDict[startBracket];
  const endBracket = text[pos] === expectedEndBracket ? expectedEndBracket : '';

  if (!endBracket) {
    throw new Error(getErrorMessage([!repeatModifier && 'repeat modifier', !releaseSelfModifier && 'release modifier', `"${expectedEndBracket}"`].filter(Boolean).join(' or '), text[pos], text));
  }

  pos += endBracket.length;
  return {
    consumedLength: pos,
    descriptor,
    releasePrevious: !!releasePreviousModifier,
    repeat: repeatModifier ? Math.max(Number(repeatModifier.substr(1)), 1) : 1,
    releaseSelf: hasReleaseSelf(startBracket, descriptor, releaseSelfModifier, repeatModifier)
  };
}

function assertDescriptor(descriptor, text, pos) {
  if (!descriptor) {
    throw new Error(getErrorMessage('key descriptor', text[pos], text));
  }
}

function getEnumValue(f, key) {
  return f[key];
}

function hasReleaseSelf(startBracket, descriptor, releaseSelfModifier, repeatModifier) {
  if (releaseSelfModifier) {
    return releaseSelfModifier === '/';
  }

  if (repeatModifier) {
    return false;
  }

  if (startBracket === '{' && getEnumValue(legacyModifiers, descriptor.toLowerCase())) {
    return false;
  }

  return true;
}

function mapLegacyKey(descriptor) {
  var _getEnumValue;

  return (_getEnumValue = getEnumValue(legacyKeyMap, descriptor)) != null ? _getEnumValue : descriptor;
}

function getErrorMessage(expected, found, text) {
  return `Expected ${expected} but found "${found != null ? found : ''}" in "${text}"
    See https://github.com/testing-library/user-event/blob/main/README.md#keyboardtext-options
    for more information about how userEvent parses your input.`;
}