@interactors/core 0.3.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/CHANGELOG.md +14 -0
- package/README.md +9 -0
- package/dist/cjs/constructor.js +260 -0
- package/dist/cjs/constructor.js.map +1 -0
- package/dist/cjs/converge.js +27 -0
- package/dist/cjs/converge.js.map +1 -0
- package/dist/cjs/create-interactor.js +24 -0
- package/dist/cjs/create-interactor.js.map +1 -0
- package/dist/cjs/dispatch.js +34 -0
- package/dist/cjs/dispatch.js.map +1 -0
- package/dist/cjs/element/click.js +12 -0
- package/dist/cjs/element/click.js.map +1 -0
- package/dist/cjs/element/fill-in.js +71 -0
- package/dist/cjs/element/fill-in.js.map +1 -0
- package/dist/cjs/errors.js +32 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/escape-html.js +13 -0
- package/dist/cjs/escape-html.js.map +1 -0
- package/dist/cjs/filter-set.js +50 -0
- package/dist/cjs/filter-set.js.map +1 -0
- package/dist/cjs/focused.js +47 -0
- package/dist/cjs/focused.js.map +1 -0
- package/dist/cjs/format-table.js +30 -0
- package/dist/cjs/format-table.js.map +1 -0
- package/dist/cjs/index.js +47 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/inspector.js +22 -0
- package/dist/cjs/inspector.js.map +1 -0
- package/dist/cjs/interaction.js +53 -0
- package/dist/cjs/interaction.js.map +1 -0
- package/dist/cjs/locator.js +19 -0
- package/dist/cjs/locator.js.map +1 -0
- package/dist/cjs/match.js +135 -0
- package/dist/cjs/match.js.map +1 -0
- package/dist/cjs/matcher.js +30 -0
- package/dist/cjs/matcher.js.map +1 -0
- package/dist/cjs/matchers/and.js +16 -0
- package/dist/cjs/matchers/and.js.map +1 -0
- package/dist/cjs/matchers/every.js +16 -0
- package/dist/cjs/matchers/every.js.map +1 -0
- package/dist/cjs/matchers/including.js +15 -0
- package/dist/cjs/matchers/including.js.map +1 -0
- package/dist/cjs/matchers/matching.js +15 -0
- package/dist/cjs/matchers/matching.js.map +1 -0
- package/dist/cjs/matchers/not.js +16 -0
- package/dist/cjs/matchers/not.js.map +1 -0
- package/dist/cjs/matchers/or.js +16 -0
- package/dist/cjs/matchers/or.js.map +1 -0
- package/dist/cjs/matchers/some.js +16 -0
- package/dist/cjs/matchers/some.js.map +1 -0
- package/dist/cjs/merge-objects.js +3 -0
- package/dist/cjs/merge-objects.js.map +1 -0
- package/dist/cjs/perform.js +13 -0
- package/dist/cjs/perform.js.map +1 -0
- package/dist/cjs/specification.js +4 -0
- package/dist/cjs/specification.js.map +1 -0
- package/dist/constructor.d.ts +11 -0
- package/dist/constructor.d.ts.map +1 -0
- package/dist/converge.d.ts +2 -0
- package/dist/converge.d.ts.map +1 -0
- package/dist/create-interactor.d.ts +18 -0
- package/dist/create-interactor.d.ts.map +1 -0
- package/dist/dispatch.d.ts +6 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/element/click.d.ts +2 -0
- package/dist/element/click.d.ts.map +1 -0
- package/dist/element/fill-in.d.ts +12 -0
- package/dist/element/fill-in.d.ts.map +1 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/escape-html.d.ts +2 -0
- package/dist/escape-html.d.ts.map +1 -0
- package/dist/esm/constructor.js +248 -0
- package/dist/esm/constructor.js.map +1 -0
- package/dist/esm/converge.js +23 -0
- package/dist/esm/converge.js.map +1 -0
- package/dist/esm/create-interactor.js +20 -0
- package/dist/esm/create-interactor.js.map +1 -0
- package/dist/esm/dispatch.js +26 -0
- package/dist/esm/dispatch.js.map +1 -0
- package/dist/esm/element/click.js +5 -0
- package/dist/esm/element/click.js.map +1 -0
- package/dist/esm/element/fill-in.js +67 -0
- package/dist/esm/element/fill-in.js.map +1 -0
- package/dist/esm/errors.js +25 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/escape-html.js +9 -0
- package/dist/esm/escape-html.js.map +1 -0
- package/dist/esm/filter-set.js +46 -0
- package/dist/esm/filter-set.js.map +1 -0
- package/dist/esm/focused.js +41 -0
- package/dist/esm/focused.js.map +1 -0
- package/dist/esm/format-table.js +26 -0
- package/dist/esm/format-table.js.map +1 -0
- package/dist/esm/index.js +17 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/inspector.js +18 -0
- package/dist/esm/inspector.js.map +1 -0
- package/dist/esm/interaction.js +45 -0
- package/dist/esm/interaction.js.map +1 -0
- package/dist/esm/locator.js +15 -0
- package/dist/esm/locator.js.map +1 -0
- package/dist/esm/match.js +127 -0
- package/dist/esm/match.js.map +1 -0
- package/dist/esm/matcher.js +21 -0
- package/dist/esm/matcher.js.map +1 -0
- package/dist/esm/matchers/and.js +12 -0
- package/dist/esm/matchers/and.js.map +1 -0
- package/dist/esm/matchers/every.js +12 -0
- package/dist/esm/matchers/every.js.map +1 -0
- package/dist/esm/matchers/including.js +11 -0
- package/dist/esm/matchers/including.js.map +1 -0
- package/dist/esm/matchers/matching.js +11 -0
- package/dist/esm/matchers/matching.js.map +1 -0
- package/dist/esm/matchers/not.js +12 -0
- package/dist/esm/matchers/not.js.map +1 -0
- package/dist/esm/matchers/or.js +12 -0
- package/dist/esm/matchers/or.js.map +1 -0
- package/dist/esm/matchers/some.js +12 -0
- package/dist/esm/matchers/some.js.map +1 -0
- package/dist/esm/merge-objects.js +2 -0
- package/dist/esm/merge-objects.js.map +1 -0
- package/dist/esm/perform.js +9 -0
- package/dist/esm/perform.js.map +1 -0
- package/dist/esm/specification.js +3 -0
- package/dist/esm/specification.js.map +1 -0
- package/dist/filter-set.d.ts +10 -0
- package/dist/filter-set.d.ts.map +1 -0
- package/dist/focused.d.ts +30 -0
- package/dist/focused.d.ts.map +1 -0
- package/dist/format-table.d.ts +6 -0
- package/dist/format-table.d.ts.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/inspector.d.ts +17 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/interaction.d.ts +42 -0
- package/dist/interaction.d.ts.map +1 -0
- package/dist/locator.d.ts +10 -0
- package/dist/locator.d.ts.map +1 -0
- package/dist/match.d.ts +54 -0
- package/dist/match.d.ts.map +1 -0
- package/dist/matcher.d.ts +9 -0
- package/dist/matcher.d.ts.map +1 -0
- package/dist/matchers/and.d.ts +3 -0
- package/dist/matchers/and.d.ts.map +1 -0
- package/dist/matchers/every.d.ts +3 -0
- package/dist/matchers/every.d.ts.map +1 -0
- package/dist/matchers/including.d.ts +3 -0
- package/dist/matchers/including.d.ts.map +1 -0
- package/dist/matchers/matching.d.ts +3 -0
- package/dist/matchers/matching.d.ts.map +1 -0
- package/dist/matchers/not.d.ts +3 -0
- package/dist/matchers/not.d.ts.map +1 -0
- package/dist/matchers/or.d.ts +3 -0
- package/dist/matchers/or.d.ts.map +1 -0
- package/dist/matchers/some.d.ts +3 -0
- package/dist/matchers/some.d.ts.map +1 -0
- package/dist/merge-objects.d.ts +2 -0
- package/dist/merge-objects.d.ts.map +1 -0
- package/dist/perform.d.ts +3 -0
- package/dist/perform.d.ts.map +1 -0
- package/dist/specification.d.ts +217 -0
- package/dist/specification.d.ts.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +65 -0
- package/src/constructor.ts +298 -0
- package/src/converge.ts +22 -0
- package/src/create-interactor.ts +21 -0
- package/src/dispatch.ts +24 -0
- package/src/element/click.ts +5 -0
- package/src/element/fill-in.ts +76 -0
- package/src/errors.ts +15 -0
- package/src/escape-html.ts +8 -0
- package/src/filter-set.ts +45 -0
- package/src/focused.ts +43 -0
- package/src/format-table.ts +34 -0
- package/src/index.ts +20 -0
- package/src/inspector.ts +42 -0
- package/src/interaction.ts +82 -0
- package/src/locator.ts +15 -0
- package/src/match.ts +162 -0
- package/src/matcher.ts +28 -0
- package/src/matchers/and.ts +12 -0
- package/src/matchers/every.ts +12 -0
- package/src/matchers/including.ts +12 -0
- package/src/matchers/matching.ts +12 -0
- package/src/matchers/not.ts +12 -0
- package/src/matchers/or.ts +12 -0
- package/src/matchers/some.ts +12 -0
- package/src/merge-objects.ts +2 -0
- package/src/perform.ts +11 -0
- package/src/specification.ts +253 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { Interactor, InteractorConstructor, InteractorSpecification } from './specification';
|
|
2
|
+
export { Interaction, ReadonlyInteraction, isInteraction } from './interaction';
|
|
3
|
+
export { createInteractor } from './create-interactor';
|
|
4
|
+
export { createInspector } from './inspector'
|
|
5
|
+
export { perform } from './perform';
|
|
6
|
+
export { focused, focus, blur } from './focused';
|
|
7
|
+
export { isVisible } from 'element-is-visible';
|
|
8
|
+
export { Matcher } from './matcher';
|
|
9
|
+
export * from './dispatch';
|
|
10
|
+
|
|
11
|
+
export { including } from './matchers/including';
|
|
12
|
+
export { matching } from './matchers/matching';
|
|
13
|
+
export { and } from './matchers/and';
|
|
14
|
+
export { or } from './matchers/or';
|
|
15
|
+
export { not } from './matchers/not';
|
|
16
|
+
export { some } from './matchers/some';
|
|
17
|
+
export { every } from './matchers/every';
|
|
18
|
+
|
|
19
|
+
export { fillIn } from './element/fill-in';
|
|
20
|
+
export { click } from './element/click';
|
package/src/inspector.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
import { instantiateBaseInteractor, findElements, unsafeSyncResolveParent } from "./constructor";
|
|
4
|
+
import { BaseInteractor, Filters, InteractorConstructor, FilterParams } from "./specification";
|
|
5
|
+
|
|
6
|
+
type GetElement<I extends InteractorConstructor<any, any, any, any>> = I extends InteractorConstructor<infer E, any, any, any> ? E : never
|
|
7
|
+
type GetFilters<I extends InteractorConstructor<any, any, any, any>> = I extends InteractorConstructor<any, infer F, any, any> ? F : never
|
|
8
|
+
type GetActions<I extends InteractorConstructor<any, any, any, any>> = I extends InteractorConstructor<any, any, any, infer A> ? A : never
|
|
9
|
+
|
|
10
|
+
export interface InteractorInspector<E extends Element, FP extends FilterParams<any, any>> extends BaseInteractor<E, FP> {
|
|
11
|
+
element: E,
|
|
12
|
+
find<T extends InteractorConstructor<any, any, any, any>>(interactor: T): Inspector<T>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Inspector<C extends InteractorConstructor<any, any, any, any>> {
|
|
16
|
+
/**
|
|
17
|
+
* Finds all matched by selector elements and wraps each of them to intreactor
|
|
18
|
+
*/
|
|
19
|
+
all(): (InteractorInspector<GetElement<C>, GetFilters<C>> & GetActions<C>)[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createInspector<IC extends InteractorConstructor<any, any, any, any>>(
|
|
23
|
+
constructor: IC,
|
|
24
|
+
parentElement?: Element
|
|
25
|
+
): Inspector<IC> {
|
|
26
|
+
// NOTE: Wait for https://github.com/thefrontside/bigtest/pull/895 to use specification instead
|
|
27
|
+
let options = constructor().options as ReturnType<IC>['options']
|
|
28
|
+
return {
|
|
29
|
+
all() {
|
|
30
|
+
let elements = findElements<GetElement<IC>>(parentElement ?? unsafeSyncResolveParent(options), options);
|
|
31
|
+
return elements.map(
|
|
32
|
+
element => (Object.assign(
|
|
33
|
+
instantiateBaseInteractor(options, () => element) as (BaseInteractor<GetElement<IC>, Filters<GetFilters<IC>>> & GetActions<IC>), {
|
|
34
|
+
element,
|
|
35
|
+
find<T extends InteractorConstructor<any, any, any, any>>(constructor: T): Inspector<T> {
|
|
36
|
+
return createInspector(constructor, element)
|
|
37
|
+
}
|
|
38
|
+
}))
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { globals } from '@interactors/globals';
|
|
2
|
+
import type { FilterObject } from './specification';
|
|
3
|
+
|
|
4
|
+
const interactionSymbol = Symbol.for('interaction');
|
|
5
|
+
|
|
6
|
+
export function isInteraction(x: unknown): x is Interaction<unknown> {
|
|
7
|
+
return typeof x === 'object' && x != null && interactionSymbol in x
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* An interaction represents some type of action or assertion that can be
|
|
12
|
+
* taken on an {@link Interactor}.
|
|
13
|
+
*
|
|
14
|
+
* The interaction can function as a lazy promise. That means that calling
|
|
15
|
+
* `then` on the interaction or awaiting it using `await`, will run the
|
|
16
|
+
* interaction and the promise will resolve once the action is complete.
|
|
17
|
+
* However, an interaction which is not awaited will not run by itself.
|
|
18
|
+
*
|
|
19
|
+
* @typeParam T the return value of the promise that this interaction evaluates to.
|
|
20
|
+
*/
|
|
21
|
+
export interface Interaction<T> extends Promise<T> {
|
|
22
|
+
/**
|
|
23
|
+
* Return a description of the interaction
|
|
24
|
+
*/
|
|
25
|
+
description: string;
|
|
26
|
+
/**
|
|
27
|
+
* Perform the interaction
|
|
28
|
+
*/
|
|
29
|
+
action: () => Promise<T>;
|
|
30
|
+
|
|
31
|
+
[interactionSymbol]: true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Like {@link Interaction}, except that it is used for assertions only.
|
|
36
|
+
*
|
|
37
|
+
* @typeParam T the return value of the promise that this interaction evaluates to.
|
|
38
|
+
*/
|
|
39
|
+
export interface ReadonlyInteraction<T> extends Interaction<T> {
|
|
40
|
+
/**
|
|
41
|
+
* Perform the check
|
|
42
|
+
*/
|
|
43
|
+
check: () => Promise<T>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createInteraction<T>(description: string, action: () => Promise<T>): Interaction<T> {
|
|
47
|
+
let promise: Promise<T>;
|
|
48
|
+
return {
|
|
49
|
+
description,
|
|
50
|
+
action,
|
|
51
|
+
[interactionSymbol]: true,
|
|
52
|
+
[Symbol.toStringTag]: `[interaction ${description}]`,
|
|
53
|
+
then(onFulfill, onReject) {
|
|
54
|
+
if(!promise) { promise = this.action(); }
|
|
55
|
+
return promise.then(onFulfill, onReject);
|
|
56
|
+
},
|
|
57
|
+
catch(onReject) {
|
|
58
|
+
if(!promise) { promise = this.action(); }
|
|
59
|
+
return promise.catch(onReject);
|
|
60
|
+
},
|
|
61
|
+
finally(handler) {
|
|
62
|
+
if(!promise) { promise = this.action(); }
|
|
63
|
+
return promise.finally(handler);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function interaction<T>(description: string, action: () => Promise<T>): Interaction<T> {
|
|
69
|
+
return createInteraction(description, globals.wrapAction(description, action, 'interaction'))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function check<T>(description: string, check: () => Promise<T>): ReadonlyInteraction<T> {
|
|
73
|
+
return { check() { return this.action() }, ...createInteraction(description, globals.wrapAction(description, check, 'check')) };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function interactionFilter<T, Q>(description: string, action: () => Promise<T>, filter: (element: Element) => Q): Interaction<T> & FilterObject<Q, Element> {
|
|
77
|
+
return { apply: filter, ...interaction(description, action) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function checkFilter<T, Q>(description: string, action: () => Promise<T>, filter: (element: Element) => Q): ReadonlyInteraction<T> & FilterObject<Q, Element> {
|
|
81
|
+
return { apply: filter , ...check(description, action) };
|
|
82
|
+
}
|
package/src/locator.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FilterDefinition } from './specification';
|
|
2
|
+
import { matcherDescription, MaybeMatcher } from './matcher';
|
|
3
|
+
import { applyFilter } from './match';
|
|
4
|
+
|
|
5
|
+
export class Locator<E extends Element> {
|
|
6
|
+
constructor(private definition: FilterDefinition<string, E>, public value: MaybeMatcher<string>) {}
|
|
7
|
+
|
|
8
|
+
get description(): string {
|
|
9
|
+
return matcherDescription(this.value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
apply(element: E): string {
|
|
13
|
+
return applyFilter(this.definition, element);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/match.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Locator } from './locator';
|
|
2
|
+
import { FilterSet } from './filter-set';
|
|
3
|
+
import { Filters, FilterDefinition } from './specification';
|
|
4
|
+
import { escapeHtml } from './escape-html';
|
|
5
|
+
import { MaybeMatcher, applyMatcher, matcherDescription } from './matcher';
|
|
6
|
+
|
|
7
|
+
const check = (value: unknown): string => value ? "✓" : "⨯";
|
|
8
|
+
|
|
9
|
+
export class Match<E extends Element, F extends Filters<E>> {
|
|
10
|
+
public matchLocator?: MatchLocator<E>;
|
|
11
|
+
public matchFilter: MatchFilter<E, F>;
|
|
12
|
+
public matches: boolean;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
public element: E,
|
|
16
|
+
public filterSet: FilterSet<E, F>,
|
|
17
|
+
public locator?: Locator<E>,
|
|
18
|
+
) {
|
|
19
|
+
this.matchLocator = locator && new MatchLocator(element, locator);
|
|
20
|
+
this.matchFilter = new MatchFilter(element, filterSet);
|
|
21
|
+
this.matches = (this.matchLocator ? this.matchLocator.matches : true) && this.matchFilter.matches;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
asTableHeader(name: string): string[] {
|
|
25
|
+
if(this.matchLocator) {
|
|
26
|
+
return [name, ...this.filterSet.asTableHeader()];
|
|
27
|
+
} else {
|
|
28
|
+
return this.filterSet.asTableHeader();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
asTableRow(): string[] {
|
|
33
|
+
if(this.matchLocator) {
|
|
34
|
+
return [this.matchLocator.description(), ...this.matchFilter.asTableRow()]
|
|
35
|
+
} else {
|
|
36
|
+
return this.matchFilter.asTableRow();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get sortWeight(): number {
|
|
41
|
+
return (this.matchLocator?.sortWeight || 0) + this.matchFilter.sortWeight;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
elementDescription(): string {
|
|
45
|
+
let tag = this.element.tagName.toLowerCase();
|
|
46
|
+
let attrs = Array.from(this.element.attributes).map((attr) => {
|
|
47
|
+
return `${attr.name}="${escapeHtml(attr.value)}"`
|
|
48
|
+
});
|
|
49
|
+
return `<${[tag, ...attrs].join(' ')}>`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class MatchLocator<E extends Element> {
|
|
54
|
+
public matches: boolean;
|
|
55
|
+
public expected: MaybeMatcher<string> | null;
|
|
56
|
+
public actual: string | null;
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
public element: E,
|
|
60
|
+
public locator: Locator<E>,
|
|
61
|
+
) {
|
|
62
|
+
this.expected = locator.value;
|
|
63
|
+
this.actual = locator.apply(element);
|
|
64
|
+
this.matches = applyMatcher(this.expected, this.actual);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
formatActual(): string {
|
|
68
|
+
return JSON.stringify(this.actual);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
description(): string {
|
|
72
|
+
return `${check(this.matches)} ${this.formatActual()}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get sortWeight(): number {
|
|
76
|
+
return this.matches ? 10 : 0;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class MatchFilter<E extends Element, F extends Filters<E>> {
|
|
81
|
+
public matches: boolean;
|
|
82
|
+
public items: MatchFilterItem<unknown, E, F>[];
|
|
83
|
+
|
|
84
|
+
constructor(
|
|
85
|
+
public element: E,
|
|
86
|
+
public filter: FilterSet<E, F>,
|
|
87
|
+
) {
|
|
88
|
+
this.items = Object.entries(filter.all).map(([key, expected]) => {
|
|
89
|
+
return new MatchFilterItem(element, filter, key, expected)
|
|
90
|
+
});
|
|
91
|
+
this.matches = this.items.every((match) => match.matches)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
asTableRow(): string[] {
|
|
95
|
+
return this.items.map((f) => f.description());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get sortWeight(): number {
|
|
99
|
+
return this.items.reduce((agg, i) => agg + i.sortWeight, 0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
formatAsExpectations(): string {
|
|
103
|
+
return this.items.filter((i) => !i.matches).map((i) => i.formatAsExpectation()).join('\n\n');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
|
+
export function applyFilter<T>(definition: FilterDefinition<T, any>, element: Element): T {
|
|
109
|
+
if(typeof(definition) === 'function') {
|
|
110
|
+
return definition(element) as T;
|
|
111
|
+
} else {
|
|
112
|
+
return definition.apply(element) as T;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export class MatchFilterItem<T, E extends Element, F extends Filters<E>> {
|
|
117
|
+
public actual: T;
|
|
118
|
+
public matches: boolean;
|
|
119
|
+
|
|
120
|
+
constructor(
|
|
121
|
+
public element: E,
|
|
122
|
+
public filter: FilterSet<E, F>,
|
|
123
|
+
public key: string,
|
|
124
|
+
public expected: MaybeMatcher<T>,
|
|
125
|
+
) {
|
|
126
|
+
if(this.filter.specification.filters && this.filter.specification.filters[this.key]) {
|
|
127
|
+
let definition = this.filter.specification.filters[this.key];
|
|
128
|
+
if(typeof(definition) === 'function') {
|
|
129
|
+
this.actual = definition(this.element) as T;
|
|
130
|
+
} else {
|
|
131
|
+
this.actual = definition.apply(this.element) as T;
|
|
132
|
+
}
|
|
133
|
+
this.matches = applyMatcher(this.expected, this.actual);
|
|
134
|
+
} else {
|
|
135
|
+
throw new Error(`interactor does not define a filter named ${JSON.stringify(this.key)}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
formatActual(): string {
|
|
140
|
+
return JSON.stringify(this.actual);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
formatExpected(): string {
|
|
144
|
+
return matcherDescription(this.expected);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
description(): string {
|
|
148
|
+
return `${check(this.matches)} ${this.formatActual()}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
formatAsExpectation(): string {
|
|
152
|
+
return [
|
|
153
|
+
`╒═ Filter: ${this.key}`,
|
|
154
|
+
`├─ Expected: ${this.formatExpected()}`,
|
|
155
|
+
`└─ Received: ${this.formatActual()}`,
|
|
156
|
+
].join('\n')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get sortWeight(): number {
|
|
160
|
+
return this.matches ? 1 : 0;
|
|
161
|
+
}
|
|
162
|
+
}
|
package/src/matcher.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import isEqual from 'lodash.isequal';
|
|
2
|
+
|
|
3
|
+
export interface Matcher<T> {
|
|
4
|
+
match(actual: T): boolean;
|
|
5
|
+
description(): string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type MaybeMatcher<T> = Matcher<T> | T;
|
|
9
|
+
|
|
10
|
+
export function isMatcher<T>(value: MaybeMatcher<T>): value is Matcher<T> {
|
|
11
|
+
return value && typeof (value as Matcher<T>).match === 'function' && typeof (value as Matcher<T>).description === 'function';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function matcherDescription<T>(value: MaybeMatcher<T>): string {
|
|
15
|
+
if(isMatcher(value)) {
|
|
16
|
+
return value.description();
|
|
17
|
+
} else {
|
|
18
|
+
return JSON.stringify(value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function applyMatcher<T>(value: MaybeMatcher<T>, actual: T): boolean {
|
|
23
|
+
if(isMatcher(value)) {
|
|
24
|
+
return value.match(actual);
|
|
25
|
+
} else {
|
|
26
|
+
return isEqual(value, actual);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Matcher, MaybeMatcher, matcherDescription, applyMatcher } from '../matcher';
|
|
2
|
+
|
|
3
|
+
export function and<T>(...args: MaybeMatcher<T>[]): Matcher<T> {
|
|
4
|
+
return {
|
|
5
|
+
match(actual: T): boolean {
|
|
6
|
+
return args.every((matcher) => applyMatcher(matcher, actual));
|
|
7
|
+
},
|
|
8
|
+
description(): string {
|
|
9
|
+
return args.map(matcherDescription).join(' and ');
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Matcher, MaybeMatcher, applyMatcher, matcherDescription } from '../matcher';
|
|
2
|
+
|
|
3
|
+
export function every<T>(expected: MaybeMatcher<T>): Matcher<Iterable<T>> {
|
|
4
|
+
return {
|
|
5
|
+
match(actual: Iterable<T>): boolean {
|
|
6
|
+
return Array.from(actual).every((value) => applyMatcher(expected, value));
|
|
7
|
+
},
|
|
8
|
+
description(): string {
|
|
9
|
+
return `every item ${matcherDescription(expected)}`;
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Matcher } from '../matcher';
|
|
2
|
+
|
|
3
|
+
export function including(subString: string): Matcher<string> {
|
|
4
|
+
return {
|
|
5
|
+
match(actual: string): boolean {
|
|
6
|
+
return actual.includes(subString);
|
|
7
|
+
},
|
|
8
|
+
description(): string {
|
|
9
|
+
return `including ${JSON.stringify(subString)}`;
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Matcher } from '../matcher';
|
|
2
|
+
|
|
3
|
+
export function matching(regexp: RegExp): Matcher<string> {
|
|
4
|
+
return {
|
|
5
|
+
match(actual: string): boolean {
|
|
6
|
+
return actual.match(regexp) != null;
|
|
7
|
+
},
|
|
8
|
+
description(): string {
|
|
9
|
+
return `matching ${regexp}`;
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Matcher, MaybeMatcher, matcherDescription, applyMatcher } from '../matcher';
|
|
2
|
+
|
|
3
|
+
export function not<T>(matcher: MaybeMatcher<T>): Matcher<T> {
|
|
4
|
+
return {
|
|
5
|
+
match(actual: T): boolean {
|
|
6
|
+
return !applyMatcher(matcher, actual);
|
|
7
|
+
},
|
|
8
|
+
description(): string {
|
|
9
|
+
return `not ${matcherDescription(matcher)}`;
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Matcher, MaybeMatcher, matcherDescription, applyMatcher } from '../matcher';
|
|
2
|
+
|
|
3
|
+
export function or<T>(...args: MaybeMatcher<T>[]): Matcher<T> {
|
|
4
|
+
return {
|
|
5
|
+
match(actual: T): boolean {
|
|
6
|
+
return args.some((matcher) => applyMatcher(matcher, actual));
|
|
7
|
+
},
|
|
8
|
+
description(): string {
|
|
9
|
+
return args.map(matcherDescription).join(' or ');
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Matcher, MaybeMatcher, applyMatcher, matcherDescription } from '../matcher';
|
|
2
|
+
|
|
3
|
+
export function some<T>(expected: MaybeMatcher<T>): Matcher<Iterable<T>> {
|
|
4
|
+
return {
|
|
5
|
+
match(actual: Iterable<T>): boolean {
|
|
6
|
+
return Array.from(actual).some((value) => applyMatcher(expected, value));
|
|
7
|
+
},
|
|
8
|
+
description(): string {
|
|
9
|
+
return `some item ${matcherDescription(expected)}`;
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
}
|
package/src/perform.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { Interactor, FilterParams } from './specification';
|
|
3
|
+
|
|
4
|
+
export function perform<E extends Element, F extends FilterParams<any, any>, T extends unknown[]>(fn: (element: E, ...args: T) => void): (interactor: Interactor<E, F>, ...args: T) => Promise<void> {
|
|
5
|
+
console.warn('`perform` is deprecated, please use `({ perform }) => perform(...)` instead!');
|
|
6
|
+
return async (interactor: Interactor<E, F>, ...args: T) => {
|
|
7
|
+
return await interactor.perform(element => {
|
|
8
|
+
fn(element, ...args);
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
}
|