@interactors/core 0.3.0 → 1.0.0-effection.1

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.
Files changed (152) hide show
  1. package/dist/cjs/constructor.js +66 -143
  2. package/dist/cjs/constructor.js.map +1 -1
  3. package/dist/cjs/converge.js +3 -5
  4. package/dist/cjs/converge.js.map +1 -1
  5. package/dist/cjs/format.js +50 -0
  6. package/dist/cjs/format.js.map +1 -0
  7. package/dist/cjs/index.js +1 -20
  8. package/dist/cjs/index.js.map +1 -1
  9. package/dist/cjs/inspector.js +3 -2
  10. package/dist/cjs/inspector.js.map +1 -1
  11. package/dist/cjs/interaction.js +43 -35
  12. package/dist/cjs/interaction.js.map +1 -1
  13. package/dist/cjs/match.js +17 -7
  14. package/dist/cjs/match.js.map +1 -1
  15. package/dist/cjs/matcher.js +13 -1
  16. package/dist/cjs/matcher.js.map +1 -1
  17. package/dist/cjs/matchers/and.js +3 -0
  18. package/dist/cjs/matchers/and.js.map +1 -1
  19. package/dist/cjs/matchers/every.js +3 -0
  20. package/dist/cjs/matchers/every.js.map +1 -1
  21. package/dist/cjs/matchers/including.js +4 -0
  22. package/dist/cjs/matchers/including.js.map +1 -1
  23. package/dist/cjs/matchers/matching.js +4 -0
  24. package/dist/cjs/matchers/matching.js.map +1 -1
  25. package/dist/cjs/matchers/not.js +3 -0
  26. package/dist/cjs/matchers/not.js.map +1 -1
  27. package/dist/cjs/matchers/or.js +3 -0
  28. package/dist/cjs/matchers/or.js.map +1 -1
  29. package/dist/cjs/matchers/some.js +3 -0
  30. package/dist/cjs/matchers/some.js.map +1 -1
  31. package/dist/cjs/resolvers.js +98 -0
  32. package/dist/cjs/resolvers.js.map +1 -0
  33. package/dist/cjs/serialize.js +48 -0
  34. package/dist/cjs/serialize.js.map +1 -0
  35. package/dist/constructor.d.ts +3 -10
  36. package/dist/constructor.d.ts.map +1 -1
  37. package/dist/converge.d.ts +2 -1
  38. package/dist/converge.d.ts.map +1 -1
  39. package/dist/esm/constructor.js +64 -134
  40. package/dist/esm/constructor.js.map +1 -1
  41. package/dist/esm/converge.js +3 -5
  42. package/dist/esm/converge.js.map +1 -1
  43. package/dist/esm/{format-table.js → format.js} +19 -1
  44. package/dist/esm/format.js.map +1 -0
  45. package/dist/esm/index.js +0 -4
  46. package/dist/esm/index.js.map +1 -1
  47. package/dist/esm/inspector.js +3 -2
  48. package/dist/esm/inspector.js.map +1 -1
  49. package/dist/esm/interaction.js +40 -29
  50. package/dist/esm/interaction.js.map +1 -1
  51. package/dist/esm/match.js +17 -7
  52. package/dist/esm/match.js.map +1 -1
  53. package/dist/esm/matcher.js +11 -0
  54. package/dist/esm/matcher.js.map +1 -1
  55. package/dist/esm/matchers/and.js +4 -1
  56. package/dist/esm/matchers/and.js.map +1 -1
  57. package/dist/esm/matchers/every.js +4 -1
  58. package/dist/esm/matchers/every.js.map +1 -1
  59. package/dist/esm/matchers/including.js +4 -0
  60. package/dist/esm/matchers/including.js.map +1 -1
  61. package/dist/esm/matchers/matching.js +4 -0
  62. package/dist/esm/matchers/matching.js.map +1 -1
  63. package/dist/esm/matchers/not.js +4 -1
  64. package/dist/esm/matchers/not.js.map +1 -1
  65. package/dist/esm/matchers/or.js +4 -1
  66. package/dist/esm/matchers/or.js.map +1 -1
  67. package/dist/esm/matchers/some.js +4 -1
  68. package/dist/esm/matchers/some.js.map +1 -1
  69. package/dist/esm/resolvers.js +88 -0
  70. package/dist/esm/resolvers.js.map +1 -0
  71. package/dist/esm/serialize.js +43 -0
  72. package/dist/esm/serialize.js.map +1 -0
  73. package/dist/format.d.ts +10 -0
  74. package/dist/format.d.ts.map +1 -0
  75. package/dist/index.d.ts +4 -6
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/inspector.d.ts +2 -2
  78. package/dist/inspector.d.ts.map +1 -1
  79. package/dist/interaction.d.ts +38 -10
  80. package/dist/interaction.d.ts.map +1 -1
  81. package/dist/match.d.ts +5 -5
  82. package/dist/match.d.ts.map +1 -1
  83. package/dist/matcher.d.ts +2 -0
  84. package/dist/matcher.d.ts.map +1 -1
  85. package/dist/matchers/and.d.ts.map +1 -1
  86. package/dist/matchers/every.d.ts.map +1 -1
  87. package/dist/matchers/including.d.ts.map +1 -1
  88. package/dist/matchers/matching.d.ts.map +1 -1
  89. package/dist/matchers/not.d.ts.map +1 -1
  90. package/dist/matchers/or.d.ts.map +1 -1
  91. package/dist/matchers/some.d.ts.map +1 -1
  92. package/dist/resolvers.d.ts +9 -0
  93. package/dist/resolvers.d.ts.map +1 -0
  94. package/dist/serialize.d.ts +13 -0
  95. package/dist/serialize.d.ts.map +1 -0
  96. package/dist/specification.d.ts +49 -51
  97. package/dist/specification.d.ts.map +1 -1
  98. package/dist/tsconfig.build.tsbuildinfo +1 -1
  99. package/package.json +3 -3
  100. package/src/constructor.ts +76 -160
  101. package/src/converge.ts +3 -6
  102. package/src/format.ts +58 -0
  103. package/src/index.ts +4 -6
  104. package/src/inspector.ts +5 -4
  105. package/src/interaction.ts +85 -36
  106. package/src/match.ts +23 -15
  107. package/src/matcher.ts +11 -0
  108. package/src/matchers/and.ts +4 -1
  109. package/src/matchers/every.ts +4 -1
  110. package/src/matchers/including.ts +4 -1
  111. package/src/matchers/matching.ts +4 -1
  112. package/src/matchers/not.ts +4 -1
  113. package/src/matchers/or.ts +4 -1
  114. package/src/matchers/some.ts +4 -1
  115. package/src/resolvers.ts +100 -0
  116. package/src/serialize.ts +54 -0
  117. package/src/specification.ts +58 -61
  118. package/CHANGELOG.md +0 -14
  119. package/dist/cjs/dispatch.js +0 -34
  120. package/dist/cjs/dispatch.js.map +0 -1
  121. package/dist/cjs/element/fill-in.js +0 -71
  122. package/dist/cjs/element/fill-in.js.map +0 -1
  123. package/dist/cjs/focused.js +0 -47
  124. package/dist/cjs/focused.js.map +0 -1
  125. package/dist/cjs/format-table.js +0 -30
  126. package/dist/cjs/format-table.js.map +0 -1
  127. package/dist/cjs/perform.js +0 -13
  128. package/dist/cjs/perform.js.map +0 -1
  129. package/dist/dispatch.d.ts +0 -6
  130. package/dist/dispatch.d.ts.map +0 -1
  131. package/dist/element/fill-in.d.ts +0 -12
  132. package/dist/element/fill-in.d.ts.map +0 -1
  133. package/dist/esm/dispatch.js +0 -26
  134. package/dist/esm/dispatch.js.map +0 -1
  135. package/dist/esm/element/fill-in.js +0 -67
  136. package/dist/esm/element/fill-in.js.map +0 -1
  137. package/dist/esm/focused.js +0 -41
  138. package/dist/esm/focused.js.map +0 -1
  139. package/dist/esm/format-table.js.map +0 -1
  140. package/dist/esm/perform.js +0 -9
  141. package/dist/esm/perform.js.map +0 -1
  142. package/dist/focused.d.ts +0 -30
  143. package/dist/focused.d.ts.map +0 -1
  144. package/dist/format-table.d.ts +0 -6
  145. package/dist/format-table.d.ts.map +0 -1
  146. package/dist/perform.d.ts +0 -3
  147. package/dist/perform.d.ts.map +0 -1
  148. package/src/dispatch.ts +0 -24
  149. package/src/element/fill-in.ts +0 -76
  150. package/src/focused.ts +0 -43
  151. package/src/format-table.ts +0 -34
  152. package/src/perform.ts +0 -11
package/src/match.ts CHANGED
@@ -9,7 +9,6 @@ const check = (value: unknown): string => value ? "✓" : "⨯";
9
9
  export class Match<E extends Element, F extends Filters<E>> {
10
10
  public matchLocator?: MatchLocator<E>;
11
11
  public matchFilter: MatchFilter<E, F>;
12
- public matches: boolean;
13
12
 
14
13
  constructor(
15
14
  public element: E,
@@ -18,7 +17,10 @@ export class Match<E extends Element, F extends Filters<E>> {
18
17
  ) {
19
18
  this.matchLocator = locator && new MatchLocator(element, locator);
20
19
  this.matchFilter = new MatchFilter(element, filterSet);
21
- this.matches = (this.matchLocator ? this.matchLocator.matches : true) && this.matchFilter.matches;
20
+ }
21
+
22
+ get matches(): boolean {
23
+ return (this.matchLocator ? this.matchLocator.matches : true) && this.matchFilter.matches;
22
24
  }
23
25
 
24
26
  asTableHeader(name: string): string[] {
@@ -51,7 +53,6 @@ export class Match<E extends Element, F extends Filters<E>> {
51
53
  }
52
54
 
53
55
  export class MatchLocator<E extends Element> {
54
- public matches: boolean;
55
56
  public expected: MaybeMatcher<string> | null;
56
57
  public actual: string | null;
57
58
 
@@ -61,7 +62,10 @@ export class MatchLocator<E extends Element> {
61
62
  ) {
62
63
  this.expected = locator.value;
63
64
  this.actual = locator.apply(element);
64
- this.matches = applyMatcher(this.expected, this.actual);
65
+ }
66
+
67
+ get matches(): boolean {
68
+ return applyMatcher(this.expected, this.actual)
65
69
  }
66
70
 
67
71
  formatActual(): string {
@@ -78,7 +82,6 @@ export class MatchLocator<E extends Element> {
78
82
  }
79
83
 
80
84
  export class MatchFilter<E extends Element, F extends Filters<E>> {
81
- public matches: boolean;
82
85
  public items: MatchFilterItem<unknown, E, F>[];
83
86
 
84
87
  constructor(
@@ -88,7 +91,10 @@ export class MatchFilter<E extends Element, F extends Filters<E>> {
88
91
  this.items = Object.entries(filter.all).map(([key, expected]) => {
89
92
  return new MatchFilterItem(element, filter, key, expected)
90
93
  });
91
- this.matches = this.items.every((match) => match.matches)
94
+ }
95
+
96
+ get matches(): boolean {
97
+ return this.items.every((match) => match.matches)
92
98
  }
93
99
 
94
100
  asTableRow(): string[] {
@@ -114,23 +120,25 @@ export function applyFilter<T>(definition: FilterDefinition<T, any>, element: El
114
120
  }
115
121
 
116
122
  export class MatchFilterItem<T, E extends Element, F extends Filters<E>> {
117
- public actual: T;
118
- public matches: boolean;
119
-
120
123
  constructor(
121
124
  public element: E,
122
125
  public filter: FilterSet<E, F>,
123
126
  public key: string,
124
127
  public expected: MaybeMatcher<T>,
125
- ) {
126
- if(this.filter.specification.filters && this.filter.specification.filters[this.key]) {
128
+ ) {}
129
+
130
+ get matches(): boolean {
131
+ return applyMatcher(this.expected, this.actual);
132
+ }
133
+
134
+ get actual(): T {
135
+ if (this.filter.specification.filters && this.filter.specification.filters[this.key]) {
127
136
  let definition = this.filter.specification.filters[this.key];
128
- if(typeof(definition) === 'function') {
129
- this.actual = definition(this.element) as T;
137
+ if (typeof definition === 'function') {
138
+ return definition(this.element) as T;
130
139
  } else {
131
- this.actual = definition.apply(this.element) as T;
140
+ return definition.apply(this.element) as T;
132
141
  }
133
- this.matches = applyMatcher(this.expected, this.actual);
134
142
  } else {
135
143
  throw new Error(`interactor does not define a filter named ${JSON.stringify(this.key)}`);
136
144
  }
package/src/matcher.ts CHANGED
@@ -3,6 +3,7 @@ import isEqual from 'lodash.isequal';
3
3
  export interface Matcher<T> {
4
4
  match(actual: T): boolean;
5
5
  description(): string;
6
+ code?(): string;
6
7
  }
7
8
 
8
9
  export type MaybeMatcher<T> = Matcher<T> | T;
@@ -26,3 +27,13 @@ export function applyMatcher<T>(value: MaybeMatcher<T>, actual: T): boolean {
26
27
  return isEqual(value, actual);
27
28
  }
28
29
  }
30
+
31
+ export function matcherCode<T>(value: MaybeMatcher<T>): string {
32
+ if (isMatcher(value) && value.code) {
33
+ return value.code();
34
+ } else if (value instanceof RegExp) {
35
+ return value.toString();
36
+ } else {
37
+ return JSON.stringify(value);
38
+ }
39
+ }
@@ -1,4 +1,4 @@
1
- import { Matcher, MaybeMatcher, matcherDescription, applyMatcher } from '../matcher';
1
+ import { Matcher, MaybeMatcher, matcherDescription, applyMatcher, matcherCode } from '../matcher';
2
2
 
3
3
  export function and<T>(...args: MaybeMatcher<T>[]): Matcher<T> {
4
4
  return {
@@ -8,5 +8,8 @@ export function and<T>(...args: MaybeMatcher<T>[]): Matcher<T> {
8
8
  description(): string {
9
9
  return args.map(matcherDescription).join(' and ');
10
10
  },
11
+ code(): string {
12
+ return `and(${args.map(arg => matcherCode(arg)).join(', ')})`
13
+ }
11
14
  }
12
15
  }
@@ -1,4 +1,4 @@
1
- import { Matcher, MaybeMatcher, applyMatcher, matcherDescription } from '../matcher';
1
+ import { Matcher, MaybeMatcher, applyMatcher, matcherDescription, matcherCode } from '../matcher';
2
2
 
3
3
  export function every<T>(expected: MaybeMatcher<T>): Matcher<Iterable<T>> {
4
4
  return {
@@ -8,5 +8,8 @@ export function every<T>(expected: MaybeMatcher<T>): Matcher<Iterable<T>> {
8
8
  description(): string {
9
9
  return `every item ${matcherDescription(expected)}`;
10
10
  },
11
+ code(): string {
12
+ return `every(${matcherCode(expected)})`
13
+ }
11
14
  }
12
15
  }
@@ -1,4 +1,4 @@
1
- import { Matcher } from '../matcher';
1
+ import { Matcher, matcherCode} from '../matcher';
2
2
 
3
3
  export function including(subString: string): Matcher<string> {
4
4
  return {
@@ -8,5 +8,8 @@ export function including(subString: string): Matcher<string> {
8
8
  description(): string {
9
9
  return `including ${JSON.stringify(subString)}`;
10
10
  },
11
+ code(): string {
12
+ return `including(${matcherCode(subString)})`
13
+ }
11
14
  }
12
15
  }
@@ -1,4 +1,4 @@
1
- import { Matcher } from '../matcher';
1
+ import { Matcher, matcherCode } from '../matcher';
2
2
 
3
3
  export function matching(regexp: RegExp): Matcher<string> {
4
4
  return {
@@ -8,5 +8,8 @@ export function matching(regexp: RegExp): Matcher<string> {
8
8
  description(): string {
9
9
  return `matching ${regexp}`;
10
10
  },
11
+ code(): string {
12
+ return `matching(${matcherCode(regexp)})`
13
+ }
11
14
  }
12
15
  }
@@ -1,4 +1,4 @@
1
- import { Matcher, MaybeMatcher, matcherDescription, applyMatcher } from '../matcher';
1
+ import { Matcher, MaybeMatcher, matcherDescription, applyMatcher, matcherCode } from '../matcher';
2
2
 
3
3
  export function not<T>(matcher: MaybeMatcher<T>): Matcher<T> {
4
4
  return {
@@ -8,5 +8,8 @@ export function not<T>(matcher: MaybeMatcher<T>): Matcher<T> {
8
8
  description(): string {
9
9
  return `not ${matcherDescription(matcher)}`;
10
10
  },
11
+ code(): string {
12
+ return `not(${matcherCode(matcher)})`
13
+ }
11
14
  }
12
15
  }
@@ -1,4 +1,4 @@
1
- import { Matcher, MaybeMatcher, matcherDescription, applyMatcher } from '../matcher';
1
+ import { Matcher, MaybeMatcher, matcherDescription, applyMatcher, matcherCode } from '../matcher';
2
2
 
3
3
  export function or<T>(...args: MaybeMatcher<T>[]): Matcher<T> {
4
4
  return {
@@ -8,5 +8,8 @@ export function or<T>(...args: MaybeMatcher<T>[]): Matcher<T> {
8
8
  description(): string {
9
9
  return args.map(matcherDescription).join(' or ');
10
10
  },
11
+ code(): string {
12
+ return `or(${args.map(arg => matcherCode(arg)).join(', ')})`
13
+ }
11
14
  }
12
15
  }
@@ -1,4 +1,4 @@
1
- import { Matcher, MaybeMatcher, applyMatcher, matcherDescription } from '../matcher';
1
+ import { Matcher, MaybeMatcher, applyMatcher, matcherDescription, matcherCode } from '../matcher';
2
2
 
3
3
  export function some<T>(expected: MaybeMatcher<T>): Matcher<Iterable<T>> {
4
4
  return {
@@ -8,5 +8,8 @@ export function some<T>(expected: MaybeMatcher<T>): Matcher<Iterable<T>> {
8
8
  description(): string {
9
9
  return `some item ${matcherDescription(expected)}`;
10
10
  },
11
+ code(): string {
12
+ return `some(${matcherCode(expected)})`
13
+ }
11
14
  }
12
15
  }
@@ -0,0 +1,100 @@
1
+
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+
4
+ import { globals } from '@interactors/globals';
5
+ import { Match } from './match';
6
+ import { NoSuchElementError, NotAbsentError, AmbiguousElementError } from './errors';
7
+ import { formatDescription, formatMatchesTable } from './format';
8
+ import { InteractorOptions } from './specification';
9
+
10
+ const defaultSelector = 'div';
11
+
12
+ export function findElements<E extends Element>(parentElement: Element, interactor: InteractorOptions<any, any, any>): E[] {
13
+ if (!interactor.name) {
14
+ throw new Error('One of your interactors was created without a name. Please provide a label for your interactor:\n\tHTML.extend(\'my interactor\') || createInteractor(\'my interactor\')');
15
+ }
16
+ if (typeof interactor.specification.selector === 'function') {
17
+ return interactor.specification.selector(parentElement);
18
+ } else if (interactor.specification.selector === ':root') {
19
+ // this is a bit of a hack, because otherwise there isn't a good way of selecting the root element
20
+ return [parentElement.ownerDocument.querySelector(':root') as E];
21
+ } else {
22
+ return Array.from(parentElement.querySelectorAll(interactor.specification.selector || defaultSelector));
23
+ }
24
+ }
25
+
26
+ function findMatches(parentElement: Element, interactor: InteractorOptions<any, any, any>): Match<Element, any>[] {
27
+ return findElements(parentElement, interactor).map((e) => new Match(e, interactor.filter, interactor.locator));
28
+ }
29
+
30
+ function throwNoSuchElementError(matches: Match<Element, any>[], interactor: InteractorOptions<any, any, any>): never {
31
+ if (matches.length === 0) {
32
+ throw new NoSuchElementError(`did not find ${formatDescription(interactor)}`);
33
+ } else {
34
+ throw new NoSuchElementError(`did not find ${formatDescription(interactor)}, did you mean one of:\n\n${formatMatchesTable(interactor, matches)}`);
35
+ }
36
+ }
37
+
38
+ function findFirstMatch(parentElement: Element, interactor: InteractorOptions<any, any, any>): Match<Element, any> {
39
+ let matches: Match<Element, any>[] = []
40
+ for (let element of findElements(parentElement, interactor)) {
41
+ let match = new Match(element, interactor.filter, interactor.locator)
42
+ matches.push(match)
43
+ if (match.matches) {
44
+ return match
45
+ }
46
+ }
47
+ throwNoSuchElementError(matches, interactor);
48
+ }
49
+
50
+ export function hasMatchMatching(parentElement: Element, interactor: InteractorOptions<any, any, any>): boolean {
51
+ return findElements(parentElement, interactor).some(element => new Match(element, interactor.filter, interactor.locator).matches);
52
+ }
53
+
54
+ function findMatchesNonEmpty(parentElement: Element, interactor: InteractorOptions<any, any, any>): Match<Element, any>[] {
55
+ let matches = findMatches(parentElement, interactor);
56
+ let matching = matches.filter((m) => m.matches);
57
+ if (matching.length > 0) {
58
+ return matching;
59
+ }
60
+ throwNoSuchElementError(matches, interactor);
61
+ }
62
+
63
+ // Given a parent element, and an interactor, find exactly one matching element
64
+ // and return it. If no elements match, raise an error. If more than one
65
+ // element matches, raise an error.
66
+ export function resolveUnique(parentElement: Element, interactor: InteractorOptions<any, any, any>): Element {
67
+ let matching = findMatchesNonEmpty(parentElement, interactor);
68
+
69
+ if (matching.length === 1) {
70
+ return matching[0].element;
71
+ } else {
72
+ let alternatives = matching.map((m) => '- ' + m.elementDescription());
73
+ throw new AmbiguousElementError(`${formatDescription(interactor)} matches multiple elements:\n\n${alternatives.join('\n')}`);
74
+ }
75
+ }
76
+
77
+ // Given a parent element, and an interactor, find the first matching element and
78
+ // return it. If no elements match, raise an error.
79
+ export function resolveFirst(parentElement: Element, interactor: InteractorOptions<any, any, any>): Element {
80
+ return findFirstMatch(parentElement, interactor).element
81
+ }
82
+
83
+ // Given a parent element, and an interactor, check if there are any matching
84
+ // elements, and throw an error if there are. Otherwise return undefined.
85
+ export function resolveEmpty(parentElement: Element, interactor: InteractorOptions<any, any, any>): void {
86
+ let matching = findMatches(parentElement, interactor).filter((m) => m.matches)
87
+
88
+ if (matching.length !== 0) {
89
+ let alternatives = matching.map((m) => '- ' + m.elementDescription());
90
+ throw new NotAbsentError(`${formatDescription(interactor)} exists but should not:\n\n${alternatives.join('\n')}`);
91
+ }
92
+ }
93
+
94
+ export function unsafeSyncResolveParent(options: InteractorOptions<any, any, any>): Element {
95
+ return options.ancestors.reduce(resolveUnique, globals.document.documentElement);
96
+ }
97
+
98
+ export function unsafeSyncResolveUnique<E extends Element>(options: InteractorOptions<E, any, any>): E {
99
+ return resolveUnique(unsafeSyncResolveParent(options), options) as E;
100
+ }
@@ -0,0 +1,54 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { InteractionOptions as SerializedInteractionOptions, InteractionType } from "@interactors/globals";
3
+ import { pascalCase } from "change-case";
4
+ import { InteractionOptions } from "./interaction";
5
+ import { matcherCode } from "./matcher";
6
+ import { InteractorOptions } from "./specification";
7
+
8
+ export function serializeInteractorOptions(options: InteractorOptions<any, any, any>): {
9
+ interactor: string;
10
+ filter: { [name: string]: unknown };
11
+ locator: string;
12
+ code: () => string;
13
+ } {
14
+ let locator = matcherCode(options.locator?.value);
15
+ return {
16
+ interactor: options.name,
17
+ filter: options.filter.all,
18
+ locator,
19
+ code() {
20
+ let interactorName = pascalCase(options.name);
21
+ let filters = Object.entries(options.filter.all).map(([name, filter]) => `"${name}": ${matcherCode(filter)}`);
22
+ return `${interactorName}(${[locator, filters.length && `{ ${filters.join(", ")} }`]
23
+ .filter(Boolean)
24
+ .join(", ")})`;
25
+ },
26
+ };
27
+ }
28
+
29
+ export function serializeInteractionOptions<E extends Element, T>(
30
+ type: InteractionType,
31
+ { name, interactor: { options }, args, filters }: InteractionOptions<E, T>
32
+ ): SerializedInteractionOptions {
33
+ let interactor = serializeInteractorOptions(options);
34
+ let ancestors = options.ancestors.map((ancestor) => serializeInteractorOptions(ancestor));
35
+ return {
36
+ ...interactor,
37
+ ancestors,
38
+ name,
39
+ type,
40
+ args: filters ? [filters] : args ? args : undefined,
41
+ code() {
42
+ let serializedArgs = "";
43
+ if (filters) {
44
+ let serializedFilters = Object.entries(filters).map(([name, filter]) => `"${name}": ${matcherCode(filter)}`);
45
+ serializedArgs = `{ ${serializedFilters.join(", ")} }`;
46
+ } else if (args) {
47
+ serializedArgs = args.map((arg) => JSON.stringify(arg)).join(", ");
48
+ }
49
+ return `${[...ancestors, interactor]
50
+ .map(({ code }, index) => (index == 0 ? code() : `${code()})`))
51
+ .join(".find(")}.${name}(${serializedArgs})`;
52
+ },
53
+ };
54
+ }
@@ -1,49 +1,28 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
 
3
+ import { Operation } from '@effection/core';
3
4
  import { FilterSet } from './filter-set';
4
5
  import { Locator } from './locator';
5
- import { Interaction, ReadonlyInteraction } from './interaction';
6
+ import { ActionInteraction, AssertionInteraction } from './interaction';
6
7
  import { MergeObjects } from './merge-objects';
7
8
  import { MaybeMatcher } from './matcher';
8
9
 
9
10
  export type EmptyObject = Record<never, never>;
10
11
 
11
- export interface ExistsAssertionsImplementation {
12
-
13
- /**
14
- * An assertion which checks that an element matching the interactor exists.
15
- * Throws an error if the element does not exist.
16
- *
17
- * ## Example
18
- *
19
- * ``` typescript
20
- * await Link('Next').exists();
21
- * ```
22
- */
23
- exists(): ReadonlyInteraction<void> & FilterObject<boolean, Element>;
24
-
25
- /**
26
- * An assertion which checks that an element matching the interactor does not
27
- * exist. Throws an error if the element exists.
28
- *
29
- * ## Example
30
- *
31
- * ``` typescript
32
- * await Link('Next').absent();
33
- * ```
34
- */
35
- absent(): ReadonlyInteraction<void> & FilterObject<boolean, Element>;
36
- }
37
-
38
- export interface BaseInteractor<E extends Element, F extends FilterParams<any, any>> {
12
+ /**
13
+ * Instances of an interactor returned by an {@link InteractorConstructor}, use
14
+ * this class as its base. They are also extended with any additional actions
15
+ * defined in their {@link InteractorSpecification}.
16
+ */
17
+ export interface Interactor<E extends Element, F extends FilterParams<any, any>> {
39
18
  /**
40
19
  * @hidden
41
20
  */
42
21
  options: InteractorOptions<E, any, any>;
43
22
 
44
- /**
45
- * @returns a human readable description of this interactor
46
- */
23
+ /**
24
+ * @returns a human readable description of this interactor
25
+ */
47
26
  description: string;
48
27
 
49
28
  /**
@@ -60,7 +39,7 @@ export interface BaseInteractor<E extends Element, F extends FilterParams<any, a
60
39
  * await Link('Next').perform((e) => e.click());
61
40
  * ```
62
41
  */
63
- perform(fn: (element: E) => void): Interaction<void>;
42
+ perform(fn: (element: E) => void): ActionInteraction<E, void>;
64
43
 
65
44
  /**
66
45
  * Perform a one-off assertion on the given interactor. Takes a function which
@@ -76,7 +55,31 @@ export interface BaseInteractor<E extends Element, F extends FilterParams<any, a
76
55
  * await Link('Next').assert((e) => assert(e.tagName === 'A'));
77
56
  * ```
78
57
  */
79
- assert(fn: (element: E) => void): ReadonlyInteraction<void>;
58
+ assert(fn: (element: E) => void): AssertionInteraction<E, void>;
59
+
60
+ /**
61
+ * An assertion which checks that an element matching the interactor exists.
62
+ * Throws an error if the element does not exist.
63
+ *
64
+ * ## Example
65
+ *
66
+ * ``` typescript
67
+ * await Link('Next').exists();
68
+ * ```
69
+ */
70
+ exists(): AssertionInteraction<E, void> & FilterObject<boolean, Element>;
71
+
72
+ /**
73
+ * An assertion which checks that an element matching the interactor does not
74
+ * exist. Throws an error if the element exists.
75
+ *
76
+ * ## Example
77
+ *
78
+ * ``` typescript
79
+ * await Link('Next').absent();
80
+ * ```
81
+ */
82
+ absent(): AssertionInteraction<E, void> & FilterObject<boolean, Element>;
80
83
 
81
84
  /**
82
85
  * Checks that there is one element matching the interactor, and that this
@@ -89,7 +92,7 @@ export interface BaseInteractor<E extends Element, F extends FilterParams<any, a
89
92
  * await Link('Home').has({ href: '/' })
90
93
  * ```
91
94
  */
92
- has(filters: F): ReadonlyInteraction<void>;
95
+ has(filters: F): AssertionInteraction<E, void>;
93
96
 
94
97
  /**
95
98
  * Identical to {@link has}, but reads better with some filters.
@@ -100,20 +103,8 @@ export interface BaseInteractor<E extends Element, F extends FilterParams<any, a
100
103
  * await CheckBox('Accept conditions').is({ checked: true })
101
104
  * ```
102
105
  */
103
- is(filters: F): ReadonlyInteraction<void>;
106
+ is(filters: F): AssertionInteraction<E, void>;
104
107
 
105
- /**
106
- * @hidden
107
- */
108
- apply: FilterFn<string, Element>;
109
- }
110
-
111
- /**
112
- * Instances of an interactor returned by an {@link InteractorConstructor}, use
113
- * this class as its base. They are also extended with any additional actions
114
- * defined in their {@link InteractorSpecification}.
115
- */
116
- export interface Interactor<E extends Element, F extends FilterParams<any, any>> extends BaseInteractor<E, F>, ExistsAssertionsImplementation {
117
108
  /**
118
109
  * Returns a copy of the given interactor which is scoped to this interactor.
119
110
  * When there are multiple matches for an interactor, this makes it possible
@@ -130,10 +121,15 @@ export interface Interactor<E extends Element, F extends FilterParams<any, any>>
130
121
  * @returns a scoped copy of the initial interactor
131
122
  * @typeParam T the type of the interactor that we are going to scope
132
123
  */
133
- find<T extends Interactor<any, any>>(interactor: T): T;
124
+ find<T extends Interactor<any, any>>(interactor: T): T;
125
+
126
+ /**
127
+ * @hidden
128
+ */
129
+ apply: FilterFn<string, Element>;
134
130
  }
135
131
 
136
- export type ActionFn<E extends Element> = (interactor: Interactor<E, EmptyObject>, ...args: any[]) => Promise<unknown>;
132
+ export type ActionFn<E extends Element, I extends Interactor<E, any>> = (interactor: I, ...args: any[]) => Operation<unknown>;
137
133
 
138
134
  export type FilterFn<T, E extends Element> = (element: E) => T;
139
135
 
@@ -146,11 +142,11 @@ export type FilterDefinition<T, E extends Element> = FilterFn<T, E> | FilterObje
146
142
 
147
143
  export type Filters<E extends Element> = Record<string, FilterDefinition<unknown, E>>;
148
144
 
149
- export type Actions<E extends Element> = Record<string, ActionFn<E>>;
145
+ export type Actions<E extends Element, I extends Interactor<E, any>> = Record<string, ActionFn<E, I>>;
150
146
 
151
147
  export type SelectorFn<E extends Element> = (parentElement: Element) => E[];
152
148
 
153
- export type InteractorSpecification<E extends Element, F extends Filters<E>, A extends Actions<E>> = {
149
+ export type InteractorSpecification<E extends Element, F extends Filters<E>, A extends Actions<E, Interactor<E, EmptyObject>>> = {
154
150
  /**
155
151
  * The CSS selector that this interactor uses to find matching elements
156
152
  */
@@ -166,16 +162,16 @@ export type InteractorSpecification<E extends Element, F extends Filters<E>, A e
166
162
  locator?: FilterDefinition<string, E>;
167
163
  }
168
164
 
169
- export type ActionMethods<E extends Element, A extends Actions<E>> = {
170
- [P in keyof A]: A[P] extends ((interactor: Interactor<E, EmptyObject>, ...args: infer TArgs) => Promise<infer TReturn>)
171
- ? ((...args: TArgs) => Interaction<TReturn>)
165
+ export type ActionMethods<E extends Element, A extends Actions<E, I>, I extends Interactor<E, any>> = {
166
+ [P in keyof A]: A[P] extends ((interactor: I, ...args: infer TArgs) => Operation<infer TReturn>)
167
+ ? ((...args: TArgs) => ActionInteraction<E, TReturn>)
172
168
  : never;
173
169
  }
174
170
 
175
171
  export type FilterMethods<E extends Element, F extends Filters<E>> = {
176
172
  [P in keyof F]:
177
- F[P] extends FilterFn<infer TReturn, any> ? (() => Interaction<TReturn> & FilterObject<TReturn, Element>) :
178
- F[P] extends FilterObject<infer TReturn, any> ? (() => Interaction<TReturn> & FilterObject<TReturn, Element>) :
173
+ F[P] extends FilterFn<infer TReturn, any> ? (() => AssertionInteraction<E, TReturn> & FilterObject<TReturn, Element>) :
174
+ F[P] extends FilterObject<infer TReturn, any> ? (() => AssertionInteraction<E, TReturn> & FilterObject<TReturn, Element>) :
179
175
  never;
180
176
  }
181
177
 
@@ -202,11 +198,11 @@ export type FilterParams<E extends Element, F extends Filters<E>> = keyof F exte
202
198
  * @typeParam F the filters of this interactor, this is usually inferred from the specification
203
199
  * @typeParam A the actions of this interactor, this is usually inferred from the specification
204
200
  */
205
- export interface InteractorConstructor<E extends Element, FP extends FilterParams<any, any>, FM extends FilterMethods<any, any>, AM extends ActionMethods<any, any>> {
201
+ export interface InteractorConstructor<E extends Element, FP extends FilterParams<any, any>, FM extends FilterMethods<any, any>, AM extends ActionMethods<any, any, any>> {
206
202
  selector(value: string | SelectorFn<E>): InteractorConstructor<E, FP, FM, AM>;
207
203
  locator(value: FilterDefinition<string, E>): InteractorConstructor<E, FP, FM, AM>;
208
204
  filters<FR extends Filters<E>>(filters: FR): InteractorConstructor<E, MergeObjects<FP, FilterParams<E, FR>>, MergeObjects<FM, FilterMethods<E, FR>>, AM>;
209
- actions<AR extends Actions<E>>(actions: AR): InteractorConstructor<E, FP, FM, MergeObjects<AM, ActionMethods<E, AR>>>;
205
+ actions<I extends Interactor<E, FP> & FM & AM, AR extends Actions<E, I>>(actions: AR): InteractorConstructor<E, FP, FM, MergeObjects<AM, ActionMethods<E, AR, I>>>;
210
206
  extend<ER extends E = E>(name: string): InteractorConstructor<ER, FP, FM, AM>;
211
207
 
212
208
  /**
@@ -230,6 +226,7 @@ export interface InteractorConstructor<E extends Element, FP extends FilterParam
230
226
  *
231
227
  * ``` typescript
232
228
  * Link('Home');
229
+ * Link(/^home/i);
233
230
  * ```
234
231
  *
235
232
  * Or with a locator and options:
@@ -241,10 +238,10 @@ export interface InteractorConstructor<E extends Element, FP extends FilterParam
241
238
  * @param value The locator value, which should match the value of applying the locator function defined in the {@link InteractorSpecification} to the element.
242
239
  * @param filters An object describing a set of filters to apply, which should match the value of applying the filters defined in the {@link InteractorSpecification} to the element.
243
240
  */
244
- (value: MaybeMatcher<string>, filters?: FP): Interactor<E, FP> & FM & AM;
241
+ (value: MaybeMatcher<string> | RegExp, filters?: FP): Interactor<E, FP> & FM & AM;
245
242
  }
246
243
 
247
- export type InteractorOptions<E extends Element, F extends Filters<E>, A extends Actions<E>> = {
244
+ export type InteractorOptions<E extends Element, F extends Filters<E>, A extends Actions<E, Interactor<E, EmptyObject>>> = {
248
245
  name: string;
249
246
  specification: InteractorSpecification<E, F, A>;
250
247
  locator?: Locator<E>;
package/CHANGELOG.md DELETED
@@ -1,14 +0,0 @@
1
- # @interactors/core
2
-
3
- ## 0.3.0
4
-
5
- ### Minor Changes
6
-
7
- - 53dea63: Enable delegation to locator of an interactor if no filter given
8
-
9
- ## 0.2.0
10
-
11
- ### Minor Changes
12
-
13
- - 0c96dab: Add locator delegation. Locators can be delegated to another delegator.
14
- - f4949ac: Selector can be a function to return arbitrary elements
@@ -1,34 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dispatchClick = exports.dispatchKeyUp = exports.dispatchKeyDown = exports.dispatchInput = exports.dispatchChange = void 0;
4
- function dispatchChange(element) {
5
- var _a;
6
- let Event = ((_a = element.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.Event) || window.Event;
7
- return element.dispatchEvent(new Event('change', { bubbles: true, cancelable: false }));
8
- }
9
- exports.dispatchChange = dispatchChange;
10
- function dispatchInput(element, options = {}) {
11
- var _a;
12
- let InputEvent = ((_a = element.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.InputEvent) || window.InputEvent;
13
- return element.dispatchEvent(new InputEvent('input', Object.assign({ bubbles: true, cancelable: false }, options)));
14
- }
15
- exports.dispatchInput = dispatchInput;
16
- function dispatchKeyDown(element, options = {}) {
17
- var _a;
18
- let KeyboardEvent = ((_a = element.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.KeyboardEvent) || window.KeyboardEvent;
19
- return element.dispatchEvent(new KeyboardEvent('keydown', Object.assign({ bubbles: true, cancelable: true }, options)));
20
- }
21
- exports.dispatchKeyDown = dispatchKeyDown;
22
- function dispatchKeyUp(element, options = {}) {
23
- var _a;
24
- let KeyboardEvent = ((_a = element.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.KeyboardEvent) || window.KeyboardEvent;
25
- return element.dispatchEvent(new KeyboardEvent('keyup', Object.assign({ bubbles: true, cancelable: true }, options)));
26
- }
27
- exports.dispatchKeyUp = dispatchKeyUp;
28
- function dispatchClick(element, options = {}) {
29
- var _a;
30
- let MouseEvent = ((_a = element.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.MouseEvent) || window.MouseEvent;
31
- return element.dispatchEvent(new MouseEvent('click', Object.assign({ bubbles: true, cancelable: true }, options)));
32
- }
33
- exports.dispatchClick = dispatchClick;
34
- //# sourceMappingURL=dispatch.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/dispatch.ts"],"names":[],"mappings":";;;AAAA,SAAgB,cAAc,CAAC,OAAoB;;IACjD,IAAI,KAAK,GAAG,CAAA,MAAA,OAAO,CAAC,aAAa,CAAC,WAAW,0CAAE,KAAK,KAAI,MAAM,CAAC,KAAK,CAAC;IACrE,OAAO,OAAO,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAC1F,CAAC;AAHD,wCAGC;AAED,SAAgB,aAAa,CAAC,OAAoB,EAAE,UAA0B,EAAE;;IAC9E,IAAI,UAAU,GAAG,CAAA,MAAA,OAAO,CAAC,aAAa,CAAC,WAAW,0CAAE,UAAU,KAAI,MAAM,CAAC,UAAU,CAAC;IACpF,OAAO,OAAO,CAAC,aAAa,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AACtH,CAAC;AAHD,sCAGC;AAED,SAAgB,eAAe,CAAC,OAAoB,EAAE,UAA6B,EAAE;;IACnF,IAAI,aAAa,GAAG,CAAA,MAAA,OAAO,CAAC,aAAa,CAAC,WAAW,0CAAE,aAAa,KAAI,MAAM,CAAC,aAAa,CAAC;IAC7F,OAAO,OAAO,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAC1H,CAAC;AAHD,0CAGC;AAED,SAAgB,aAAa,CAAC,OAAoB,EAAE,UAA6B,EAAE;;IACjF,IAAI,aAAa,GAAG,CAAA,MAAA,OAAO,CAAC,aAAa,CAAC,WAAW,0CAAE,aAAa,KAAI,MAAM,CAAC,aAAa,CAAC;IAC7F,OAAO,OAAO,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AACxH,CAAC;AAHD,sCAGC;AAED,SAAgB,aAAa,CAAC,OAAoB,EAAE,UAA0B,EAAE;;IAC9E,IAAI,UAAU,GAAG,CAAA,MAAA,OAAO,CAAC,aAAa,CAAC,WAAW,0CAAE,UAAU,KAAI,MAAM,CAAC,UAAU,CAAC;IACpF,OAAO,OAAO,CAAC,aAAa,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AACrH,CAAC;AAHD,sCAGC"}