@lvce-editor/test-worker 13.26.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, { hasText, nth }?: 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>;
@@ -1218,7 +1218,9 @@ const get = id => {
1218
1218
  };
1219
1219
 
1220
1220
  const getLocatorInvoke = locator => {
1221
+ // @ts-ignore
1221
1222
  if (locator.webViewId) {
1223
+ // @ts-ignore
1222
1224
  const module = get(locator.webViewId);
1223
1225
  return module.invoke;
1224
1226
  }
@@ -1233,22 +1235,40 @@ const locatorInvoke = async (locator, method, ...params) => {
1233
1235
  };
1234
1236
 
1235
1237
  const printLocator = locator => {
1236
- if (locator._nth !== -1) {
1237
- return `${locator._selector}:nth(${locator._nth})`;
1238
- }
1239
- if (locator._hasText) {
1240
- 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})`;
1241
1261
  }
1242
- return `${locator._selector}`;
1262
+ return result;
1243
1263
  };
1244
1264
 
1245
1265
  const toBeVisible = locator => {
1246
- return `expected selector to be visible ${locator._selector}`;
1266
+ return `expected selector to be visible ${printLocator(locator)}`;
1247
1267
  };
1248
1268
  const toHaveValue = (locator, {
1249
1269
  value
1250
1270
  }) => {
1251
- return `expected selector ${locator._selector} to have value ${value}`;
1271
+ return `expected selector ${printLocator(locator)} to have value ${value}`;
1252
1272
  };
1253
1273
  const toHaveText = async (locator, options) => {
1254
1274
  const locatorString = printLocator(locator);
@@ -1543,6 +1563,72 @@ const setMockRpc = mockRpc => {
1543
1563
  state.mockRpcs[mockRpc.name] = mockRpc;
1544
1564
  };
1545
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
+
1546
1632
  const performAction = async (locator, action, options) => {
1547
1633
  const invoke = getLocatorInvoke(locator);
1548
1634
  return invoke('TestFrameWork.performAction', locator, action, options);
@@ -1562,13 +1648,18 @@ const toButtonNumber = buttonType => {
1562
1648
  };
1563
1649
 
1564
1650
  class Locator {
1565
- constructor(selector, {
1566
- hasText = '',
1567
- nth = -1
1568
- } = {}) {
1651
+ constructor(selector, options = {}, parsed) {
1652
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
1653
+ throw new TypeError('options must be of type object');
1654
+ }
1655
+ const {
1656
+ hasText = '',
1657
+ nth = -1
1658
+ } = options;
1659
+ string(hasText, 'options.hasText must be of type string');
1660
+ number(nth, 'options.nth must be of type number');
1569
1661
  this._selector = selector;
1570
- this._nth = nth;
1571
- this._hasText = hasText;
1662
+ this._parsed = parsed || applyLocatorOptions(parseCssSelector(selector), options);
1572
1663
  }
1573
1664
  async click({
1574
1665
  button = 'left'
@@ -1589,20 +1680,15 @@ class Locator {
1589
1680
  return performAction(this, 'hover', options);
1590
1681
  }
1591
1682
  first() {
1592
- return new Locator(this._selector, {
1593
- nth: 0
1594
- });
1683
+ return new Locator(this._selector, {}, withNth(this._parsed, 0));
1595
1684
  }
1596
1685
  locator(subSelector) {
1597
- if (this._nth !== -1) {
1598
- return new Locator(`${this._selector}:nth-of-type(${this._nth + 1}) ${subSelector}`);
1599
- }
1600
- return new Locator(`${this._selector} ${subSelector}`);
1686
+ const selector = `${this._selector} ${subSelector}`;
1687
+ return new Locator(selector, {}, [...this._parsed, ...parseCssSelector(subSelector)]);
1601
1688
  }
1602
1689
  nth(nth) {
1603
- return new Locator(this._selector, {
1604
- nth
1605
- });
1690
+ number(nth, 'nth must be of type number');
1691
+ return new Locator(this._selector, {}, withNth(this._parsed, nth));
1606
1692
  }
1607
1693
  async type(text) {
1608
1694
  const options = {
@@ -1617,8 +1703,32 @@ class Locator {
1617
1703
  });
1618
1704
  }
1619
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
+ };
1620
1729
 
1621
1730
  const createLocator = (selector, options = {}) => {
1731
+ string(selector, 'selector must be of type string');
1622
1732
  return new Locator(selector, options);
1623
1733
  };
1624
1734
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/test-worker",
3
- "version": "13.26.0",
3
+ "version": "14.0.0",
4
4
  "description": "Test Worker",
5
5
  "repository": {
6
6
  "type": "git",