@lvce-editor/test-worker 13.27.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.d.ts CHANGED
@@ -28,15 +28,36 @@ interface ILocatorExternal {
28
28
  readonly type: (text: string) => Promise<void>;
29
29
  }
30
30
 
31
+ interface ParsedCssSelectorPart {
32
+ readonly selector: string;
33
+ readonly type: "css";
34
+ }
35
+
36
+ interface ParsedTextSelectorPart {
37
+ readonly text: string;
38
+ readonly type: "text";
39
+ }
40
+
41
+ interface ParsedHasTextSelectorPart {
42
+ readonly text: string;
43
+ readonly type: "has-text";
44
+ }
45
+
46
+ interface ParsedNthSelectorPart {
47
+ readonly index: number;
48
+ readonly type: "nth";
49
+ }
50
+ export type ParsedSelectorPart = ParsedCssSelectorPart | ParsedTextSelectorPart | ParsedHasTextSelectorPart | ParsedNthSelectorPart;
51
+ export type ParsedCssSelector = readonly ParsedSelectorPart[];
52
+
31
53
  interface ILocatorCreateOptions {
32
54
  readonly hasText?: string;
33
55
  readonly nth?: number;
34
56
  }
35
57
  export declare class Locator implements ILocatorExternal {
36
- readonly _selector: any;
37
- readonly _nth: number;
38
- readonly _hasText: string;
39
- constructor(selector: any, options?: ILocatorCreateOptions);
58
+ readonly _selector: string;
59
+ readonly _parsed: ParsedCssSelector;
60
+ constructor(selector: string, options?: ILocatorCreateOptions, parsed?: ParsedCssSelector);
40
61
  click({ button }?: {
41
62
  readonly button?: string;
42
63
  }): Promise<void>;
@@ -1235,22 +1235,40 @@ const locatorInvoke = async (locator, method, ...params) => {
1235
1235
  };
1236
1236
 
1237
1237
  const printLocator = locator => {
1238
- if (locator._nth !== -1) {
1239
- return `${locator._selector}:nth(${locator._nth})`;
1240
- }
1241
- if (locator._hasText) {
1242
- return `${locator._selector} "${locator._hasText}"`;
1238
+ let result = '';
1239
+ for (const part of locator._parsed) {
1240
+ if (part.type === 'css') {
1241
+ if (!result) {
1242
+ result = part.selector;
1243
+ continue;
1244
+ }
1245
+ result = `${result} >> ${part.selector}`;
1246
+ continue;
1247
+ }
1248
+ if (part.type === 'text') {
1249
+ if (!result) {
1250
+ result = `text=${part.text}`;
1251
+ continue;
1252
+ }
1253
+ result = `${result} text=${part.text}`;
1254
+ continue;
1255
+ }
1256
+ if (part.type === 'has-text') {
1257
+ result = `${result} "${part.text}"`;
1258
+ continue;
1259
+ }
1260
+ result = `${result}:nth(${part.index})`;
1243
1261
  }
1244
- return `${locator._selector}`;
1262
+ return result;
1245
1263
  };
1246
1264
 
1247
1265
  const toBeVisible = locator => {
1248
- return `expected selector to be visible ${locator._selector}`;
1266
+ return `expected selector to be visible ${printLocator(locator)}`;
1249
1267
  };
1250
1268
  const toHaveValue = (locator, {
1251
1269
  value
1252
1270
  }) => {
1253
- return `expected selector ${locator._selector} to have value ${value}`;
1271
+ return `expected selector ${printLocator(locator)} to have value ${value}`;
1254
1272
  };
1255
1273
  const toHaveText = async (locator, options) => {
1256
1274
  const locatorString = printLocator(locator);
@@ -1545,6 +1563,72 @@ const setMockRpc = mockRpc => {
1545
1563
  state.mockRpcs[mockRpc.name] = mockRpc;
1546
1564
  };
1547
1565
 
1566
+ class CssParsingError extends Error {
1567
+ constructor(message) {
1568
+ super(message);
1569
+ this.name = 'CssParsingError';
1570
+ }
1571
+ }
1572
+
1573
+ const startTokenRegex = /^[a-z][a-z0-9-]*/;
1574
+ const getStartToken = selector => {
1575
+ const match = selector.match(startTokenRegex);
1576
+ return match ? match[0] : '';
1577
+ };
1578
+
1579
+ const htmlElements = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdo', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'command', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'link', 'map', 'mark', 'menu', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr'];
1580
+
1581
+ const isElement = selector => {
1582
+ return htmlElements.includes(selector);
1583
+ };
1584
+
1585
+ const isCssSelector = selector => {
1586
+ if (!selector) {
1587
+ return false;
1588
+ }
1589
+ if (selector.startsWith('.') || selector.startsWith('#') || selector.startsWith('[') || selector.startsWith('*') || selector.startsWith(':')) {
1590
+ return true;
1591
+ }
1592
+ const startToken = getStartToken(selector);
1593
+ if (!startToken) {
1594
+ return false;
1595
+ }
1596
+ return isElement(startToken);
1597
+ };
1598
+
1599
+ const parseCssSelector = selector => {
1600
+ if (typeof selector !== 'string') {
1601
+ throw new TypeError('selector must be of type string');
1602
+ }
1603
+ if (selector.startsWith('text=')) {
1604
+ return [{
1605
+ text: selector.slice('text='.length),
1606
+ type: 'text'
1607
+ }];
1608
+ }
1609
+ if (selector.includes('text=')) {
1610
+ const index = selector.indexOf('text=');
1611
+ const cssSelector = selector.slice(0, index).trimEnd();
1612
+ if (!isCssSelector(cssSelector)) {
1613
+ throw new CssParsingError(`unsupported selector: ${selector}`);
1614
+ }
1615
+ return [{
1616
+ selector: cssSelector,
1617
+ type: 'css'
1618
+ }, {
1619
+ text: selector.slice(index + 'text='.length),
1620
+ type: 'text'
1621
+ }];
1622
+ }
1623
+ if (isCssSelector(selector)) {
1624
+ return [{
1625
+ selector,
1626
+ type: 'css'
1627
+ }];
1628
+ }
1629
+ throw new CssParsingError(`unsupported selector: ${selector}`);
1630
+ };
1631
+
1548
1632
  const performAction = async (locator, action, options) => {
1549
1633
  const invoke = getLocatorInvoke(locator);
1550
1634
  return invoke('TestFrameWork.performAction', locator, action, options);
@@ -1564,7 +1648,7 @@ const toButtonNumber = buttonType => {
1564
1648
  };
1565
1649
 
1566
1650
  class Locator {
1567
- constructor(selector, options = {}) {
1651
+ constructor(selector, options = {}, parsed) {
1568
1652
  if (!options || typeof options !== 'object' || Array.isArray(options)) {
1569
1653
  throw new TypeError('options must be of type object');
1570
1654
  }
@@ -1575,8 +1659,7 @@ class Locator {
1575
1659
  string(hasText, 'options.hasText must be of type string');
1576
1660
  number(nth, 'options.nth must be of type number');
1577
1661
  this._selector = selector;
1578
- this._nth = nth;
1579
- this._hasText = hasText;
1662
+ this._parsed = parsed || applyLocatorOptions(parseCssSelector(selector), options);
1580
1663
  }
1581
1664
  async click({
1582
1665
  button = 'left'
@@ -1597,20 +1680,15 @@ class Locator {
1597
1680
  return performAction(this, 'hover', options);
1598
1681
  }
1599
1682
  first() {
1600
- return new Locator(this._selector, {
1601
- nth: 0
1602
- });
1683
+ return new Locator(this._selector, {}, withNth(this._parsed, 0));
1603
1684
  }
1604
1685
  locator(subSelector) {
1605
- if (this._nth !== -1) {
1606
- return new Locator(`${this._selector}:nth-of-type(${this._nth + 1}) ${subSelector}`);
1607
- }
1608
- return new Locator(`${this._selector} ${subSelector}`);
1686
+ const selector = `${this._selector} ${subSelector}`;
1687
+ return new Locator(selector, {}, [...this._parsed, ...parseCssSelector(subSelector)]);
1609
1688
  }
1610
1689
  nth(nth) {
1611
- return new Locator(this._selector, {
1612
- nth
1613
- });
1690
+ number(nth, 'nth must be of type number');
1691
+ return new Locator(this._selector, {}, withNth(this._parsed, nth));
1614
1692
  }
1615
1693
  async type(text) {
1616
1694
  const options = {
@@ -1625,6 +1703,29 @@ class Locator {
1625
1703
  });
1626
1704
  }
1627
1705
  }
1706
+ const applyLocatorOptions = (parsed, {
1707
+ hasText = '',
1708
+ nth = -1
1709
+ }) => {
1710
+ let nextParsed = parsed;
1711
+ if (hasText) {
1712
+ nextParsed = [...nextParsed, {
1713
+ text: hasText,
1714
+ type: 'has-text'
1715
+ }];
1716
+ }
1717
+ if (nth !== -1) {
1718
+ nextParsed = withNth(nextParsed, nth);
1719
+ }
1720
+ return nextParsed;
1721
+ };
1722
+ const withNth = (parsed, nth) => {
1723
+ const filtered = parsed.filter(part => part.type !== 'nth');
1724
+ return [...filtered, {
1725
+ index: nth,
1726
+ type: 'nth'
1727
+ }];
1728
+ };
1628
1729
 
1629
1730
  const createLocator = (selector, options = {}) => {
1630
1731
  string(selector, 'selector must be of type string');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/test-worker",
3
- "version": "13.27.0",
3
+ "version": "14.0.0",
4
4
  "description": "Test Worker",
5
5
  "repository": {
6
6
  "type": "git",