Skip to content

Commit

Permalink
Remove Effection and get (most) tests passing for CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
cowboyd committed Dec 5, 2024
1 parent 92a6a92 commit 03aed66
Show file tree
Hide file tree
Showing 13 changed files with 54 additions and 110 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ jobs:
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- run: deno test -A
- run: |
deno run -A packages/cli/src/main.ts build
deno test -A
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
.netlify

.vscode
/build/
18 changes: 0 additions & 18 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion packages/cli/test/agent.test.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
<body>

<!-- Add your site or application content here -->
<p>Hello world! This is HTML5 Boilerplate.</p>
<p id="hello">Hello world! This is HTML5 Boilerplate.</p>
<section id="facts">
<h1 id="fact1">Interactors == Awesome</h1>
</section>
<script src="../build/agent.js"></script>

</body>
Expand Down
22 changes: 10 additions & 12 deletions packages/cli/test/agent.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { afterEach, beforeEach, describe, it } from "jsr:@std/testing/bdd";
import { expect } from "jsr:@std/expect";
import { type Browser, chromium } from "playwright";
import { HTML as $HTML } from "@interactors/html";
import { HTML as $HTML, Heading as $Heading, matching } from "@interactors/html";
import { Ok } from "effection";

const HTML = $HTML.builder((i) => i);
const Heading = $Heading.builder(i => i);

let browser: Browser;
let agent = {
Expand Down Expand Up @@ -52,8 +53,8 @@ describe("Interactor Agent", () => {
});

it("can call nested interactors", async function () {
const interaction = HTML({ title: "greeting" })
.find(HTML("Hello world! This is HTML5 Boilerplate."))
const interaction = HTML({ id: "facts" })
.find(HTML("Interactors == Awesome"))
.exists();
const result = await agent.run(interaction);
expect(result).toEqual({ ok: true });
Expand All @@ -69,18 +70,15 @@ describe("Interactor Agent", () => {
),
).toEqual({ ok: true });
expect(
await agent.run(HTML({ id: "hello" }).has({ text: "Goodbye world!" })),
await agent.run(HTML({ id: "hello" }).has({ text: "Hello world! This is HTML Boilerplate." })),
).toMatchObject({
ok: false,
});
});

// it('can make assertions based on matchers', async function () {

// expect(await agent.run(HTML({ id: matching(/hel/) }).has({ text: matching(/Hello world!/) }))).toEqual({
// ok: true
// });
// });

it.skip("can validate that a set of interactions can be run ahead of time", () => {});
it.skip('can make assertions based on matchers', async function () {
expect(await agent.run(HTML({ id: matching(/hel/) }).has({ text: matching(/Hello world!/) }))).toEqual({
ok: true
});
});
});
1 change: 0 additions & 1 deletion packages/core/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
},
"exports": "./mod.ts",
"imports": {
"@effection/core": "npm:@effection/core@2.2.0",
"@std/expect": "jsr:@std/expect@^1.0.8",
"@std/testing": "jsr:@std/testing@^1.0.5",
"@testing-library/dom": "npm:@testing-library/dom@^8.18.1",
Expand Down
19 changes: 11 additions & 8 deletions packages/core/src/converge.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { type Operation, sleep } from '@effection/core';
import { performance } from 'performance-api';
import { globals } from '@interactors/globals'
import { performance } from "performance-api";
import { globals } from "@interactors/globals";

export function* converge<T>(fn: () => T): Operation<T> {
export async function converge<T>(fn: () => T): Promise<T> {
let startTime = performance.now();
while(true) {
while (true) {
try {
return fn();
} catch(e) {
} catch (e) {
let diff = performance.now() - startTime;
if(diff > globals.interactorTimeout) {
if (diff > globals.interactorTimeout) {
throw e;
} else {
yield sleep(1);
await sleep(1);
}
}
}
}

async function sleep(durationMS: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, durationMS));
}
66 changes: 20 additions & 46 deletions packages/core/src/interaction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { type Operation, type Task, run, Symbol } from '@effection/core';
import { type InteractionOptions as SerializedInteractionOptions, globals, type InteractionType } from '@interactors/globals';
import type { InteractionOptions as SerializedInteractionOptions, InteractionType } from '@interactors/globals';
import type { Interactor, FilterObject, FilterFn, FilterParams } from './specification.ts';
import { serializeInteractionOptions } from './serialize.ts';

Expand All @@ -25,7 +24,7 @@ export interface Interaction<E extends Element, T = void> extends Promise<T> {
type: InteractionType;

interactor: Interactor<E, any>;
run: (interactor: Interactor<E, any>) => Operation<T>;
run: (interactor: Interactor<E, any>) => Promise<T>;
/**
* Return a description of the interaction
*/
Expand All @@ -41,11 +40,9 @@ export interface Interaction<E extends Element, T = void> extends Promise<T> {
/**
* Perform the interaction
*/
action: () => Task<T>;
action: () => Promise<T>;

check?: () => Task<T>;

halt: () => Promise<void>;
check?: () => Promise<T>;

[interactionSymbol]: true;
}
Expand All @@ -65,7 +62,7 @@ export interface AssertionInteraction<E extends Element, T = void> extends Inter
/**
* Perform the check
*/
check: () => Task<T>;
check: () => Promise<T>;
}

export type InteractionOptions<E extends Element, T> = {
Expand All @@ -74,70 +71,47 @@ export type InteractionOptions<E extends Element, T> = {
filters?: FilterParams<any, any>;
args: unknown[];
interactor: Interactor<E, any>;
run: (interactor: Interactor<E, any>) => Operation<T>;
run: (interactor: Interactor<E, any>) => Promise<T>;
}

export function createInteraction<E extends Element, T>(type: 'action', options: InteractionOptions<E, T>): ActionInteraction<E, T>
export function createInteraction<E extends Element, T>(type: 'assertion', options: InteractionOptions<E, T>): AssertionInteraction<E, T>
export function createInteraction<E extends Element, T, Q>(type: 'action', options: InteractionOptions<E, T>, apply: FilterFn<Q, Element>): ActionInteraction<E, T> & FilterObject<Q, Element>
export function createInteraction<E extends Element, T, Q>(type: 'assertion', options: InteractionOptions<E, T>, apply: FilterFn<Q, Element>): AssertionInteraction<E, T> & FilterObject<Q, Element>
export function createInteraction<E extends Element, T, Q>(type: InteractionType, options: InteractionOptions<E, T>, apply?: FilterFn<Q, Element>): Interaction<E, T> & Operation<T> {
let task: Task<T>;
let shouldCatchHalt = false

function operation(scope: Task): Operation<T> {
let run = () => options.run(options.interactor);

return (globals.wrapInteraction
? globals.wrapInteraction(() => scope.run(run), interaction)
: globals.wrapAction(interaction.description, () => scope.run(run), type)) as Operation<T>;
};

function action(): Task<T> {
if(!task) {
task = run(operation);
export function createInteraction<E extends Element, T, Q>(type: InteractionType, options: InteractionOptions<E, T>, apply?: FilterFn<Q, Element>): Interaction<E, T> {
let promise: Promise<T>;

let operation = () => options.run(options.interactor);

function action(): Promise<T> {
if(!promise) {
promise = operation();
}
return task;
return promise;
};

function catchHalt<T extends (reason: any) => any>(onReject?: T | null) {
return (reason: any) => {
if (shouldCatchHalt && reason instanceof Error && reason.message == 'halted') {
return reason
} else {
onReject?.(reason)
return reason
}
}
}

let serializedOptions = serializeInteractionOptions(type, options)
let interaction: Interaction<E, T> & Operation<T> = {
let interaction: Interaction<E, T> = {
type,
options: serializedOptions,
description: options.description,
interactor: options.interactor,
run: options.run,
args: options.args,
// args: options.args,
action,
check: type === 'assertion' ? action : undefined,
code: () => serializedOptions.code(),
halt: () => {
shouldCatchHalt = true
return action().halt()
},
[interactionSymbol]: true,
[Symbol.toStringTag]: `[interaction ${options.description}]`,
[Symbol.operation]: operation,
then(onFulfill, onReject) {
return action().then(onFulfill, catchHalt(onReject));
return action().then(onFulfill, onReject);
},
catch(onReject) {
return action().catch(catchHalt(onReject));
return action().catch(onReject);
},
finally(handler) {
return action().finally(handler);
}
},
}
if (apply) {
return Object.assign(interaction, { apply });
Expand Down
6 changes: 2 additions & 4 deletions packages/core/src/specification.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@

import type { Operation } from '@effection/core';
import type { FilterSet } from './filter-set.ts';
import type { Locator } from './locator.ts';
import type { ActionInteraction, AssertionInteraction, Interaction } from './interaction.ts';
Expand Down Expand Up @@ -128,7 +126,7 @@ export interface Interactor<E extends Element, F extends FilterParams<any, any>>
apply: FilterFn<string, Element>;
}

export type ActionFn<E extends Element, I extends Interactor<E, any>> = (interactor: I, ...args: any[]) => Operation<unknown>;
export type ActionFn<E extends Element, I extends Interactor<E, any>> = (interactor: I, ...args: any[]) => Promise<unknown>;

export type FilterFn<T, E extends Element> = (element: E) => T;

Expand Down Expand Up @@ -162,7 +160,7 @@ export type InteractorSpecification<E extends Element, F extends Filters<E>, A e
}

export type ActionMethods<E extends Element, A extends Actions<E, I>, I extends Interactor<E, any>> = {
[P in keyof A]: A[P] extends ((interactor: I, ...args: infer TArgs) => Operation<infer TReturn>)
[P in keyof A]: A[P] extends ((interactor: I, ...args: infer TArgs) => Promise<infer TReturn>)
? ((...args: TArgs) => ActionInteraction<E, TReturn>)
: never;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export const Datepicker = createInteractor<HTMLDivElement>("datepicker")
month: Calendar().find(Header()).text(),
})
.actions({
toggle: function* (interactor) {
yield interactor.find(TextField({ placeholder: "YYYY-MM-DD" })).click();
async toggle(interactor) {
await interactor.find(TextField({ placeholder: "YYYY-MM-DD" })).click();
},
});

Expand Down
5 changes: 1 addition & 4 deletions packages/core/test/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { type DOMWindow, JSDOM } from "jsdom";
import { afterEach, beforeEach } from "@std/testing/bdd";
import { addInteractionWrapper, globals, setDocumentResolver, setInteractorTimeout } from "@interactors/globals";
import { globals, setDocumentResolver, setInteractorTimeout } from "@interactors/globals";

let jsdom: JSDOM;
let removeWrapper: () => void;

export function dom(html: string): DOMWindow {
jsdom = new JSDOM(`<!doctype html><html><body>${html}</body></html>`, { runScripts: "dangerously" });

setDocumentResolver(() => jsdom.window.document);
removeWrapper = addInteractionWrapper(async (perform) => await perform());

return jsdom.window;
}
Expand All @@ -20,6 +18,5 @@ beforeEach(() => {
});

afterEach(() => {
removeWrapper?.();
jsdom?.window?.close();
});
1 change: 0 additions & 1 deletion packages/globals/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"imports": {
"@std/expect": "jsr:@std/expect@^1.0.0",
"@std/testing": "jsr:@std/testing@^1.0.0",
"@effection/core": "npm:@effection/core@2.2.0",
"jsdom": "npm:jsdom@^24.0.0"
},
"lint": {
Expand Down
12 changes: 0 additions & 12 deletions packages/globals/src/globals.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Operation } from "@effection/core";
import type { KeyboardLayout } from "./keyboard-layout.ts";

export type InteractionType = "action" | "assertion";

type Interaction<T = any> = Operation<T> & {
type: InteractionType;
description: string;
options: InteractionOptions;
interactor: unknown; // we can't type this any better here
code: () => string;
halt: () => Promise<void>;
};

interface Globals {
readonly document: Document;
readonly interactorTimeout: number;
Expand All @@ -35,8 +25,6 @@ export type InteractionOptions = InteractorOptions & {
ancestors?: InteractorOptions[];
};

export type InteractionWrapper<T = any> = (perform: () => Promise<T>, interaction: Interaction<T>) => Operation<T>;

declare global {
// deno-lint-ignore prefer-namespace-keyword
module globalThis {
Expand Down

0 comments on commit 03aed66

Please sign in to comment.