diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md
index 50a401b1..f64003b8 100755
--- a/.github/ISSUE_TEMPLATE/bug.md
+++ b/.github/ISSUE_TEMPLATE/bug.md
@@ -8,6 +8,7 @@ about: Create a bug report to help us enhance our images
**Short overview**
**Issue occurs on**
+
- [ ] Virtual machine
- [ ] Docker container
- [ ] Dev/Host system
@@ -17,4 +18,5 @@ about: Create a bug report to help us enhance our images
**Steps to reproduce error**
**Additional content**
-> Please provide any (mandatory) additional data to reproduce the error (Dockerfiles etc.)
\ No newline at end of file
+
+> Please provide any (mandatory) additional data to reproduce the error (Dockerfiles etc.)
diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md
index 7fc1723c..58244189 100755
--- a/.github/ISSUE_TEMPLATE/enhancement.md
+++ b/.github/ISSUE_TEMPLATE/enhancement.md
@@ -10,4 +10,5 @@ about: Suggest a possible enhancement to the project
**Detailed description**
**Additional content**
+
> Please provide any (mandatory) additional data for your enhancement
diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md
index 5c64de43..362bec4c 100755
--- a/.github/ISSUE_TEMPLATE/feature.md
+++ b/.github/ISSUE_TEMPLATE/feature.md
@@ -1,6 +1,6 @@
---
name: Feature request
-about: Open a feature request
+about: Open a feature request
---
**Short overview**
@@ -10,4 +10,5 @@ about: Open a feature request
**Detailed feature description**
**Additional content**
-> Please provide any (mandatory) additional data for your desired feature
\ No newline at end of file
+
+> Please provide any (mandatory) additional data for your desired feature
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
index 76b9c562..28b0e3fb 100755
--- a/.github/ISSUE_TEMPLATE/question.md
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -6,9 +6,9 @@ about: File a request to resolve open questions
**Short summary**
**Desired execution environment / tested on**
+
- [ ] Virtual machine
- [ ] Docker container
- [ ] Dev/Host system
-
-**Detailed question**
\ No newline at end of file
+**Detailed question**
diff --git a/.github/PULL_REQUEST_TEMPLATE/bugfix.md b/.github/PULL_REQUEST_TEMPLATE/bugfix.md
index bef0d185..acab9a72 100755
--- a/.github/PULL_REQUEST_TEMPLATE/bugfix.md
+++ b/.github/PULL_REQUEST_TEMPLATE/bugfix.md
@@ -12,4 +12,4 @@ about: Request to merge a bugfix
**Checklist**
- [ ] My change requires a change to the documentation.
-- [ ] I have updated the documentation accordingly.
\ No newline at end of file
+- [ ] I have updated the documentation accordingly.
diff --git a/.github/PULL_REQUEST_TEMPLATE/enhancement.md b/.github/PULL_REQUEST_TEMPLATE/enhancement.md
index 9f4128a4..900d3e43 100755
--- a/.github/PULL_REQUEST_TEMPLATE/enhancement.md
+++ b/.github/PULL_REQUEST_TEMPLATE/enhancement.md
@@ -12,4 +12,4 @@ about: Request to merge an enhancement
**Checklist**
- [ ] My change requires a change to the documentation.
-- [ ] I have updated the documentation accordingly.
\ No newline at end of file
+- [ ] I have updated the documentation accordingly.
diff --git a/.github/PULL_REQUEST_TEMPLATE/feature.md b/.github/PULL_REQUEST_TEMPLATE/feature.md
index f30068ca..9739cd4a 100755
--- a/.github/PULL_REQUEST_TEMPLATE/feature.md
+++ b/.github/PULL_REQUEST_TEMPLATE/feature.md
@@ -1,6 +1,6 @@
---
name: Feature
-about: Request to merge a new feature
+about: Request to merge a new feature
---
**Short summary**
@@ -12,4 +12,4 @@ about: Request to merge a new feature
**Checklist**
- [ ] My change requires a change to the documentation.
-- [ ] I have updated the documentation accordingly.
\ No newline at end of file
+- [ ] I have updated the documentation accordingly.
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index db62dcb3..315917b2 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -1,17 +1,17 @@
name: Run CI
on:
-# push:
-# branches-ignore:
-# - develop
-# - release/**
-# paths-ignore:
-# - '**/*.md'
+ # push:
+ # branches-ignore:
+ # - develop
+ # - release/**
+ # paths-ignore:
+ # - '**/*.md'
pull_request:
jobs:
sonar:
runs-on: ubuntu-latest
- if: '!contains(github.event.head_commit.message, ''skip ci'')'
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
steps:
- name: Set up Git repository
uses: actions/checkout@v2
@@ -56,8 +56,8 @@ jobs:
- sonar
strategy:
matrix:
- os: [ windows-latest, macos-latest ]
- node: [ 16 ]
+ os: [windows-latest, macos-latest]
+ node: [16]
runs-on: ${{matrix.os}}
steps:
- name: Set up Git repository
diff --git a/.github/workflows/snapshot_release.yaml b/.github/workflows/snapshot_release.yaml
index 1d797a87..a8ccbd52 100644
--- a/.github/workflows/snapshot_release.yaml
+++ b/.github/workflows/snapshot_release.yaml
@@ -4,7 +4,7 @@ on:
branches:
- develop
paths-ignore:
- - '**/*.md'
+ - "**/*.md"
repository_dispatch:
types:
- snapshot-release
@@ -13,8 +13,8 @@ jobs:
test:
strategy:
matrix:
- os: [ ubuntu-latest, windows-latest, macos-latest ]
- node: [ 16 ]
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ node: [16]
runs-on: ${{matrix.os}}
steps:
- name: Set up Git repository
@@ -48,7 +48,6 @@ jobs:
with:
run: npm --prefix e2e/electron-test cit
-
deploy:
needs:
- test
@@ -60,7 +59,7 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: 16
- registry-url: 'https://registry.npmjs.org'
+ registry-url: "https://registry.npmjs.org"
- name: Install
run: npm ci
- name: Install @nut-tree/libnut@next
@@ -73,7 +72,7 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- uses: actions/setup-node@v2
with:
- registry-url: 'https://npm.pkg.github.com'
+ registry-url: "https://npm.pkg.github.com"
- name: Publish snapshot release to GPR
run: npm run publish-next
env:
diff --git a/.github/workflows/tagged_release.yaml b/.github/workflows/tagged_release.yaml
index 23281cdb..df8126ba 100644
--- a/.github/workflows/tagged_release.yaml
+++ b/.github/workflows/tagged_release.yaml
@@ -8,8 +8,8 @@ jobs:
test:
strategy:
matrix:
- os: [ ubuntu-latest, windows-latest, macos-latest ]
- node: [ 16 ]
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ node: [16]
runs-on: ${{matrix.os}}
steps:
- name: Set up Git repository
@@ -52,7 +52,7 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: 16
- registry-url: 'https://registry.npmjs.org'
+ registry-url: "https://registry.npmjs.org"
- name: Install
run: npm ci
- name: Run typedoc
@@ -69,8 +69,8 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- uses: actions/setup-node@v2
with:
- registry-url: 'https://npm.pkg.github.com'
+ registry-url: "https://npm.pkg.github.com"
- name: Publish tagged release to GPR
run: npm publish
env:
- NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 00000000..d24fdfc6
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npx lint-staged
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..96e8983a
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+# Ignore artifacts:
+dist
+docs
+coverage
+node_modules
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1 @@
+{}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0a0d15ec..bc6a9987 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,14 +3,17 @@
All notable changes to this project will be documented in this file.
## 2.3.0
+
- Bugfix: Segmentation Fault when retrieving window title [(#377)](https://github.com/nut-tree/nut.js/issues/377)
- Enhancement: Automatically check and request required permissions on macOS [(#377)](https://github.com/nut-tree/nut.js/issues/377)
## 2.2.1
+
- Enhancement: Scale easing function result by base speed before applying [(#425)](https://github.com/nut-tree/nut.js/issues/425)
- Maintenance: Resolve security vulnerabilities [(#422)](https://github.com/nut-tree/nut.js/issues/422)
## 2.2.0
+
- Maintenance: Limit CI runs to PRs, not every push
- Maintenance: Upgrade node version to 16 for all CI runs
- Bugfix: Fix grave accent [(PR #414)](https://github.com/nut-tree/nut.js/pull/414)
@@ -18,10 +21,12 @@ All notable changes to this project will be documented in this file.
- Enhancement: Ship Windows runtime dependencies [(#365)](https://github.com/nut-tree/nut.js/issues/365)
## 2.1.1
+
- Bugfix: Modifier keys are not properly released on macOS [(#264)](https://github.com/nut-tree/nut.js/issues/264)
- Bugfix: Fix mouse clicks with modifiers on macOS [(#273)](https://github.com/nut-tree/nut.js/issues/273)
## 2.1.0
+
- Bugfix: Keyboard methods `pressKey` and `releaseKey` ignore updated autoDelayMs [(#188)](https://github.com/nut-tree/nut.js/issues/188)
- Enhancement: Add mappings for missing numpad keys [(#367)](https://github.com/nut-tree/nut.js/issues/367)
- Enhancement: macOS double click [(#373)](https://github.com/nut-tree/nut.js/issues/373)
@@ -30,10 +35,12 @@ All notable changes to this project will be documented in this file.
- Bugfix: Mouse methods `pressButton` and `releaseButton` should respect auto delay [(#403)](https://github.com/nut-tree/nut.js/issues/403)
## 2.0.1
+
- Bugfix: Issue with `keyboard.type` in to Spotlight on MacOS [(#152)](https://github.com/nut-tree/nut.js/issues/152)
- Enhancement: Numpad buttons don't work on Linux [(#360)](https://github.com/nut-tree/nut.js/issues/360)
## 2.0.0
+
- Feature: Apple Silicon [(libnut#49)](https://github.com/nut-tree/libnut/issues/49)
- Enhancement: Enable warning message for missing accessibility permissions on macOS [(#354)](https://github.com/nut-tree/nut.js/issues/354)
- Enhancement: Add runtime typechecks for `screen.find` etc. [(#351)](https://github.com/nut-tree/nut.js/issues/351)
@@ -62,14 +69,16 @@ All notable changes to this project will be documented in this file.
- Feature: Add methods to grab the current screen content as Buffer [(#278)](https://github.com/nut-tree/nut.js/issues/278)
## 1.7.0
+
- Enhancement: Trigger snapshot releases [(#234)](https://github.com/nut-tree/nut.js/issues/234)
- Feature: Cancel screen.waitFor if needed [(#241)](https://github.com/nut-tree/nut.js/issues/241)
- Enhancement: Move docs into separate repo [(#244)](https://github.com/nut-tree/nut.js/issues/244)
- Feature: Support for node 16 and Electron 13 [(#246)](https://github.com/nut-tree/nut.js/issues/246)
## 1.6.0
+
- Feature: Create screenshot from region [(#154)](https://github.com/nut-tree/nut.js/issues/154)
-- Bugfix: Endless loop in timeout function for long-running actions returning undefined [(#205)](https://github.com/nut-tree/nut.js/issues/205)
+- Bugfix: Endless loop in timeout function for long-running actions returning undefined [(#205)](https://github.com/nut-tree/nut.js/issues/205)
- Maintenance: Use default exports for all provider classes [(#163)](https://github.com/nut-tree/nut.js/issues/163)
- Enhancement: imprecise error message if image is too large [(#169)](https://github.com/nut-tree/nut.js/issues/169)
- Bugfix: `waitFor` does not properly cancel [(#174)](https://github.com/nut-tree/nut.js/issues/174)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index a2443044..753f9efe 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,4 +1,5 @@
# Code of Conduct
+
All participants of nut.js are expected to abide by our Code of Conduct, both online and during in-person events that are hosted and/or associated with nut.js.
## Our Pledge
@@ -15,22 +16,22 @@ We do not engage with nor tolerate any kind of extremism, political propaganda a
Examples of behavior that contributes to creating a positive environment
include:
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
+- Using welcoming and inclusive language
+- Being respectful of differing viewpoints and experiences
+- Gracefully accepting constructive criticism
+- Focusing on what is best for the community
+- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
+- The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+- Trolling, insulting/derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+ professional setting
## Our Responsibilities
diff --git a/README.md b/README.md
index ae786d8d..3e951671 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
-# nut.js (Native UI Toolkit)
+# nut.js (Native UI Toolkit)
-| |GitHub Actions|
-|:-: |:-: |
-|Master ||
-|Develop||
+| | GitHub Actions |
+| :-----: | :------------------------------------------------------------------------------------------------------------: |
+| Master |  |
+| Develop |  |
[](https://sonarcloud.io/dashboard?id=nut-tree%3Anut.js)
[](https://sonarcloud.io/component_measures?id=nut-tree%3Anut.js&metric=coverage)
@@ -48,7 +48,6 @@ A huge **"Thank you!"** goes out to all sponsors who make open source a bit more
[](https://www.mightyapp.com)
-
# Demo
Check out this demo video to get a first impression of what nut.js is capable of.
@@ -121,7 +120,16 @@ The following snippet shows a valid `nut.js` example:
```js
"use strict";
-const { mouse, left, right, up, down, straightTo, centerOf, Region} = require("@nut-tree/nut-js");
+const {
+ mouse,
+ left,
+ right,
+ up,
+ down,
+ straightTo,
+ centerOf,
+ Region,
+} = require("@nut-tree/nut-js");
const square = async () => {
await mouse.move(right(500));
@@ -131,14 +139,8 @@ const square = async () => {
};
(async () => {
- await square();
- await mouse.move(
- straightTo(
- centerOf(
- new Region(100, 100, 200, 300)
- )
- )
- );
+ await square();
+ await mouse.move(straightTo(centerOf(new Region(100, 100, 200, 300))));
})();
```
@@ -156,6 +158,7 @@ In case you're running Windows 10 N and want to use [ImageFinder plugins](https:
On macOS, Xcode command line tools are required.
You can install them by running
+
```bash
xcode-select --install
```
@@ -197,6 +200,7 @@ In general, `nut.js` requires
- libXtst
Installation on `*buntu` distributions:
+
```bash
sudo apt-get install libxtst-dev
```
@@ -205,7 +209,7 @@ Setups on other distributions might differ.
## Install `nut.js`
-Running
+Running
```bash
npm i @nut-tree/nut-js
@@ -223,7 +227,7 @@ will install `nut.js` and its required dependencies.
`nut.js` also provides snapshot releases which allows to test upcoming features.
-Running
+Running
```bash
npm i @nut-tree/nut-js@next
diff --git a/e2e/electron-test/index.css b/e2e/electron-test/index.css
index 4b175247..118138dd 100644
--- a/e2e/electron-test/index.css
+++ b/e2e/electron-test/index.css
@@ -1,19 +1,19 @@
body {
- width: 100vw;
- height: 100vh;
+ width: 100vw;
+ height: 100vh;
}
#content {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- height: 100vh;
- width: 100vw;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ width: 100vw;
}
#exit {
- color: white;
- font-size: 1.5rem;
- background: darkblue;
-}
\ No newline at end of file
+ color: white;
+ font-size: 1.5rem;
+ background: darkblue;
+}
diff --git a/e2e/electron-test/index.html b/e2e/electron-test/index.html
index 3dc08eb7..ff5ba54f 100644
--- a/e2e/electron-test/index.html
+++ b/e2e/electron-test/index.html
@@ -1,10 +1,13 @@
-
+
-
-
+
+
Hello from nut.js!
diff --git a/e2e/electron-test/main.js b/e2e/electron-test/main.js
index bff76b95..afd59a10 100644
--- a/e2e/electron-test/main.js
+++ b/e2e/electron-test/main.js
@@ -1,52 +1,56 @@
-const {app, ipcMain, BrowserWindow} = require('electron')
-const {getActiveWindow} = require("@nut-tree/nut-js");
-const path = require('path')
-const assert = require('assert');
+const { app, ipcMain, BrowserWindow } = require("electron");
+const { getActiveWindow } = require("@nut-tree/nut-js");
+const path = require("path");
+const assert = require("assert");
-const title = "nut.js Electron test"
+const title = "nut.js Electron test";
function createWindow() {
- const mainWindow = new BrowserWindow({
- width: 800,
- height: 600,
- title,
- webPreferences: {
- nodeIntegration: true,
- contextIsolation: false,
- preload: path.join(__dirname, 'preload.js')
- }
- })
- mainWindow.loadFile(path.join(__dirname, "index.html"))
- mainWindow.maximize();
-
- (async () => {
- // GIVEN
- const foregroundWindow = await getActiveWindow();
-
- // WHEN
- const windowTitle = await foregroundWindow.title;
-
- // THEN
- assert.strictEqual(windowTitle, title, `Wrong foreground window. Expected ${title}, got ${windowTitle}`);
- })();
+ const mainWindow = new BrowserWindow({
+ width: 800,
+ height: 600,
+ title,
+ webPreferences: {
+ nodeIntegration: true,
+ contextIsolation: false,
+ preload: path.join(__dirname, "preload.js"),
+ },
+ });
+ mainWindow.loadFile(path.join(__dirname, "index.html"));
+ mainWindow.maximize();
+
+ (async () => {
+ // GIVEN
+ const foregroundWindow = await getActiveWindow();
+
+ // WHEN
+ const windowTitle = await foregroundWindow.title;
+
+ // THEN
+ assert.strictEqual(
+ windowTitle,
+ title,
+ `Wrong foreground window. Expected ${title}, got ${windowTitle}`
+ );
+ })();
}
ipcMain.on("main", (event, args) => {
- if (args === "quit") {
- app.quit();
- }
+ if (args === "quit") {
+ app.quit();
+ }
});
app.whenReady().then(() => {
- setTimeout(() => process.exit(0), 15000);
- createWindow()
+ setTimeout(() => process.exit(0), 15000);
+ createWindow();
- app.on('activate', function () {
- if (BrowserWindow.getAllWindows().length === 0) createWindow()
- })
-})
+ app.on("activate", function () {
+ if (BrowserWindow.getAllWindows().length === 0) createWindow();
+ });
+});
-app.on('window-all-closed', function () {
- console.log("Bye!");
- app.quit();
-})
+app.on("window-all-closed", function () {
+ console.log("Bye!");
+ app.quit();
+});
diff --git a/e2e/electron-test/renderer.js b/e2e/electron-test/renderer.js
index c3f88f49..be415018 100644
--- a/e2e/electron-test/renderer.js
+++ b/e2e/electron-test/renderer.js
@@ -1,6 +1,6 @@
-const {ipcRenderer} = require("electron");
+const { ipcRenderer } = require("electron");
-const close = document.getElementById('exit');
+const close = document.getElementById("exit");
close.onclick = () => {
- ipcRenderer.send("main", "quit");
-}
+ ipcRenderer.send("main", "quit");
+};
diff --git a/e2e/window-test/constants.js b/e2e/window-test/constants.js
index 8b7a520c..7cda18ea 100644
--- a/e2e/window-test/constants.js
+++ b/e2e/window-test/constants.js
@@ -5,9 +5,9 @@ const HEIGTH = 300;
const TITLE = "libnut window test";
module.exports = {
- POS_X,
- POS_Y,
- WIDTH,
- HEIGTH,
- TITLE
-};
\ No newline at end of file
+ POS_X,
+ POS_Y,
+ WIDTH,
+ HEIGTH,
+ TITLE,
+};
diff --git a/e2e/window-test/index.css b/e2e/window-test/index.css
index 4b175247..118138dd 100644
--- a/e2e/window-test/index.css
+++ b/e2e/window-test/index.css
@@ -1,19 +1,19 @@
body {
- width: 100vw;
- height: 100vh;
+ width: 100vw;
+ height: 100vh;
}
#content {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- height: 100vh;
- width: 100vw;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ width: 100vw;
}
#exit {
- color: white;
- font-size: 1.5rem;
- background: darkblue;
-}
\ No newline at end of file
+ color: white;
+ font-size: 1.5rem;
+ background: darkblue;
+}
diff --git a/e2e/window-test/index.html b/e2e/window-test/index.html
index 7aceec05..1700b83b 100644
--- a/e2e/window-test/index.html
+++ b/e2e/window-test/index.html
@@ -1,10 +1,13 @@
-
+
-
-
+
+
libnut window test
diff --git a/e2e/window-test/main.js b/e2e/window-test/main.js
index c9e83602..2ecbd6e4 100644
--- a/e2e/window-test/main.js
+++ b/e2e/window-test/main.js
@@ -1,37 +1,37 @@
-const {app, ipcMain, BrowserWindow} = require('electron')
-const path = require('path');
+const { app, ipcMain, BrowserWindow } = require("electron");
+const path = require("path");
const { POS_X, POS_Y, WIDTH, HEIGTH } = require("./constants");
function createWindow() {
- const mainWindow = new BrowserWindow({
- width: WIDTH,
- height: HEIGTH,
- alwaysOnTop: true,
- webPreferences: {
- nodeIntegration: true,
- preload: path.join(__dirname, 'preload.js')
- }
- });
- mainWindow.loadFile(path.join(__dirname, "index.html"));
- mainWindow.setPosition(POS_X, POS_Y);
+ const mainWindow = new BrowserWindow({
+ width: WIDTH,
+ height: HEIGTH,
+ alwaysOnTop: true,
+ webPreferences: {
+ nodeIntegration: true,
+ preload: path.join(__dirname, "preload.js"),
+ },
+ });
+ mainWindow.loadFile(path.join(__dirname, "index.html"));
+ mainWindow.setPosition(POS_X, POS_Y);
}
ipcMain.on("main", (event, args) => {
- if (args === "quit") {
- app.quit();
- }
+ if (args === "quit") {
+ app.quit();
+ }
});
app.whenReady().then(() => {
- setTimeout(() => app.exit(1), 15000);
- createWindow()
+ setTimeout(() => app.exit(1), 15000);
+ createWindow();
- app.on('activate', function () {
- if (BrowserWindow.getAllWindows().length === 0) createWindow()
- })
-})
+ app.on("activate", function () {
+ if (BrowserWindow.getAllWindows().length === 0) createWindow();
+ });
+});
-app.on('window-all-closed', function () {
- console.log("Bye!");
- app.quit();
-})
+app.on("window-all-closed", function () {
+ console.log("Bye!");
+ app.quit();
+});
diff --git a/e2e/window-test/renderer.js b/e2e/window-test/renderer.js
index c3f88f49..be415018 100644
--- a/e2e/window-test/renderer.js
+++ b/e2e/window-test/renderer.js
@@ -1,6 +1,6 @@
-const {ipcRenderer} = require("electron");
+const { ipcRenderer } = require("electron");
-const close = document.getElementById('exit');
+const close = document.getElementById("exit");
close.onclick = () => {
- ipcRenderer.send("main", "quit");
-}
+ ipcRenderer.send("main", "quit");
+};
diff --git a/e2e/window-test/test.js b/e2e/window-test/test.js
index e76cc23c..3e71a52a 100644
--- a/e2e/window-test/test.js
+++ b/e2e/window-test/test.js
@@ -1,105 +1,105 @@
const Application = require("spectron").Application;
const electronPath = require("electron");
-const {getActiveWindow, getWindows} = require("@nut-tree/nut-js");
-const {POS_X, POS_Y, WIDTH, HEIGTH, TITLE} = require("./constants");
-const {join} = require("path");
+const { getActiveWindow, getWindows } = require("@nut-tree/nut-js");
+const { POS_X, POS_Y, WIDTH, HEIGTH, TITLE } = require("./constants");
+const { join } = require("path");
const sleep = async (ms) => {
- return new Promise(resolve => setTimeout(resolve, ms));
+ return new Promise((resolve) => setTimeout(resolve, ms));
};
let app;
const APP_TIMEOUT = 10000;
-jest.setTimeout(3 * APP_TIMEOUT)
+jest.setTimeout(3 * APP_TIMEOUT);
beforeEach(async () => {
- app = new Application({
- path: electronPath,
- args: [join(__dirname, 'main.js')],
- startTimeout: APP_TIMEOUT,
- waitTimeout: APP_TIMEOUT,
- });
- await app.start();
- await app.client.waitUntilWindowLoaded();
- await app.browserWindow.minimize();
- await app.browserWindow.restore();
- await app.browserWindow.focus();
+ app = new Application({
+ path: electronPath,
+ args: [join(__dirname, "main.js")],
+ startTimeout: APP_TIMEOUT,
+ waitTimeout: APP_TIMEOUT,
+ });
+ await app.start();
+ await app.client.waitUntilWindowLoaded();
+ await app.browserWindow.minimize();
+ await app.browserWindow.restore();
+ await app.browserWindow.focus();
});
describe("getWindows", () => {
- it("should list our started application window", async () => {
- // GIVEN
- const openWindows = await getWindows();
+ it("should list our started application window", async () => {
+ // GIVEN
+ const openWindows = await getWindows();
- // WHEN
- const windowNames = await Promise.all(openWindows.map((wnd) => wnd.title));
+ // WHEN
+ const windowNames = await Promise.all(openWindows.map((wnd) => wnd.title));
- // THEN
- expect(windowNames).toContain(TITLE);
- });
+ // THEN
+ expect(windowNames).toContain(TITLE);
+ });
});
describe("getActiveWindow", () => {
- it("should return our started application window", async () => {
- // GIVEN
-
- // WHEN
- const foregroundWindow = await getActiveWindow();
- const windowTitle = await foregroundWindow.title;
-
- // THEN
- expect(windowTitle).toBe(TITLE);
- });
-
- it("should determine correct coordinates for our application", async () => {
- // GIVEN
-
- // WHEN
- const foregroundWindow = await getActiveWindow();
- const activeWindowRegion = await foregroundWindow.region;
-
- // THEN
- expect(activeWindowRegion.left).toBe(POS_X);
- expect(activeWindowRegion.top).toBe(POS_Y);
- expect(activeWindowRegion.width).toBe(WIDTH);
- expect(activeWindowRegion.height).toBe(HEIGTH);
- });
-
- it("should determine correct coordinates for our application after moving the window", async () => {
- // GIVEN
- const xPosition = 42;
- const yPosition = 25;
- await app.browserWindow.setPosition(xPosition, yPosition);
- await sleep(1000);
-
- // WHEN
- const foregroundWindow = await getActiveWindow();
- const activeWindowRegion = await foregroundWindow.region;
-
- // THEN
- expect(activeWindowRegion.left).toBe(xPosition);
- expect(activeWindowRegion.top).toBe(yPosition);
- });
-
- it("should determine correct window size for our application after resizing the window", async () => {
- // GIVEN
- const newWidth = 400;
- const newHeight = 350;
- await app.browserWindow.setSize(newWidth, newHeight);
- await sleep(1000);
-
- // WHEN
- const foregroundWindow = await getActiveWindow();
- const activeWindowRegion = await foregroundWindow.region;
-
- // THEN
- expect(activeWindowRegion.width).toBe(newWidth);
- expect(activeWindowRegion.height).toBe(newHeight);
- });
+ it("should return our started application window", async () => {
+ // GIVEN
+
+ // WHEN
+ const foregroundWindow = await getActiveWindow();
+ const windowTitle = await foregroundWindow.title;
+
+ // THEN
+ expect(windowTitle).toBe(TITLE);
+ });
+
+ it("should determine correct coordinates for our application", async () => {
+ // GIVEN
+
+ // WHEN
+ const foregroundWindow = await getActiveWindow();
+ const activeWindowRegion = await foregroundWindow.region;
+
+ // THEN
+ expect(activeWindowRegion.left).toBe(POS_X);
+ expect(activeWindowRegion.top).toBe(POS_Y);
+ expect(activeWindowRegion.width).toBe(WIDTH);
+ expect(activeWindowRegion.height).toBe(HEIGTH);
+ });
+
+ it("should determine correct coordinates for our application after moving the window", async () => {
+ // GIVEN
+ const xPosition = 42;
+ const yPosition = 25;
+ await app.browserWindow.setPosition(xPosition, yPosition);
+ await sleep(1000);
+
+ // WHEN
+ const foregroundWindow = await getActiveWindow();
+ const activeWindowRegion = await foregroundWindow.region;
+
+ // THEN
+ expect(activeWindowRegion.left).toBe(xPosition);
+ expect(activeWindowRegion.top).toBe(yPosition);
+ });
+
+ it("should determine correct window size for our application after resizing the window", async () => {
+ // GIVEN
+ const newWidth = 400;
+ const newHeight = 350;
+ await app.browserWindow.setSize(newWidth, newHeight);
+ await sleep(1000);
+
+ // WHEN
+ const foregroundWindow = await getActiveWindow();
+ const activeWindowRegion = await foregroundWindow.region;
+
+ // THEN
+ expect(activeWindowRegion.width).toBe(newWidth);
+ expect(activeWindowRegion.height).toBe(newHeight);
+ });
});
afterEach(async () => {
- if (app && app.isRunning()) {
- await app.stop();
- }
+ if (app && app.isRunning()) {
+ await app.stop();
+ }
});
diff --git a/index.ts b/index.ts
index e0336f23..34c5f059 100644
--- a/index.ts
+++ b/index.ts
@@ -1,41 +1,41 @@
-import {AssertClass} from "./lib/assert.class";
-import {ClipboardClass} from "./lib/clipboard.class";
-import {KeyboardClass} from "./lib/keyboard.class";
-import {MouseClass} from "./lib/mouse.class";
-import {createMovementApi} from "./lib/movement.function";
-import {ScreenClass} from "./lib/screen.class";
-import {LineHelper} from "./lib/util/linehelper.class";
-import {createWindowApi} from "./lib/window.function";
+import { AssertClass } from "./lib/assert.class";
+import { ClipboardClass } from "./lib/clipboard.class";
+import { KeyboardClass } from "./lib/keyboard.class";
+import { MouseClass } from "./lib/mouse.class";
+import { createMovementApi } from "./lib/movement.function";
+import { ScreenClass } from "./lib/screen.class";
+import { LineHelper } from "./lib/util/linehelper.class";
+import { createWindowApi } from "./lib/window.function";
import providerRegistry from "./lib/provider/provider-registry.class";
-import {loadImageResource} from "./lib/imageResources.function";
+import { loadImageResource } from "./lib/imageResources.function";
export {
- AssertClass,
- ClipboardClass,
- KeyboardClass,
- MouseClass,
- ScreenClass,
- providerRegistry
-}
+ AssertClass,
+ ClipboardClass,
+ KeyboardClass,
+ MouseClass,
+ ScreenClass,
+ providerRegistry,
+};
-export {MatchRequest} from "./lib/match-request.class";
-export {MatchResult} from "./lib/match-result.class";
+export { MatchRequest } from "./lib/match-request.class";
+export { MatchResult } from "./lib/match-result.class";
export * from "./lib/provider";
-export {jestMatchers} from "./lib/expect/jest.matcher.function";
-export {sleep} from "./lib/sleep.function";
-export {Image} from "./lib/image.class";
-export {RGBA} from "./lib/rgba.class";
-export {Key} from "./lib/key.enum";
-export {Button} from "./lib/button.enum";
-export {centerOf, randomPointIn} from "./lib/location.function";
-export {OptionalSearchParameters} from "./lib/optionalsearchparameters.class";
-export {EasingFunction, linear} from "./lib/mouse-movement.function";
-export {Point} from "./lib/point.class";
-export {Region} from "./lib/region.class";
-export {Window} from "./lib/window.class";
-export {FileType} from "./lib/file-type.enum";
-export {ColorMode} from "./lib/colormode.enum";
+export { jestMatchers } from "./lib/expect/jest.matcher.function";
+export { sleep } from "./lib/sleep.function";
+export { Image } from "./lib/image.class";
+export { RGBA } from "./lib/rgba.class";
+export { Key } from "./lib/key.enum";
+export { Button } from "./lib/button.enum";
+export { centerOf, randomPointIn } from "./lib/location.function";
+export { OptionalSearchParameters } from "./lib/optionalsearchparameters.class";
+export { EasingFunction, linear } from "./lib/mouse-movement.function";
+export { Point } from "./lib/point.class";
+export { Region } from "./lib/region.class";
+export { Window } from "./lib/window.class";
+export { FileType } from "./lib/file-type.enum";
+export { ColorMode } from "./lib/colormode.enum";
const lineHelper = new LineHelper();
@@ -45,29 +45,37 @@ const mouse = new MouseClass(providerRegistry);
const screen = new ScreenClass(providerRegistry);
const assert = new AssertClass(screen);
-const {straightTo, up, down, left, right} = createMovementApi(providerRegistry, lineHelper);
-const {getWindows, getActiveWindow} = createWindowApi(providerRegistry);
+const { straightTo, up, down, left, right } = createMovementApi(
+ providerRegistry,
+ lineHelper
+);
+const { getWindows, getActiveWindow } = createWindowApi(providerRegistry);
const loadImage = providerRegistry.getImageReader().load;
const saveImage = providerRegistry.getImageWriter().store;
-const imageResource = (fileName: string) => loadImageResource(providerRegistry, screen.config.resourceDirectory, fileName);
-export {fetchFromUrl} from "./lib/imageResources.function";
+const imageResource = (fileName: string) =>
+ loadImageResource(
+ providerRegistry,
+ screen.config.resourceDirectory,
+ fileName
+ );
+export { fetchFromUrl } from "./lib/imageResources.function";
export {
- clipboard,
- keyboard,
- mouse,
- screen,
- assert,
- straightTo,
- up,
- down,
- left,
- right,
- getWindows,
- getActiveWindow,
- loadImage,
- saveImage,
- imageResource
+ clipboard,
+ keyboard,
+ mouse,
+ screen,
+ assert,
+ straightTo,
+ up,
+ down,
+ left,
+ right,
+ getWindows,
+ getActiveWindow,
+ loadImage,
+ saveImage,
+ imageResource,
};
diff --git a/jest.config.js b/jest.config.js
index 85056493..e870b4f2 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -7,11 +7,14 @@ module.exports = {
],
preset: "ts-jest",
testEnvironment: "node",
- testMatch: process.env.E2E_TEST ?
- ["**/__tests__/(e2e)/**/*.[jt]s?(x)", "**/?(*.)(e2e.)+(spec|test).[jt]s?(x)"] :
- ["**/__tests__/!(e2e)/**/*.[jt]s?(x)", "**/!(*.e2e.*)+(spec|test).[jt]s?(x)"],
- testPathIgnorePatterns: [
- "/node_modules/",
- "/dist/",
- ],
+ testMatch: process.env.E2E_TEST
+ ? [
+ "**/__tests__/(e2e)/**/*.[jt]s?(x)",
+ "**/?(*.)(e2e.)+(spec|test).[jt]s?(x)",
+ ]
+ : [
+ "**/__tests__/!(e2e)/**/*.[jt]s?(x)",
+ "**/!(*.e2e.*)+(spec|test).[jt]s?(x)",
+ ],
+ testPathIgnorePatterns: ["/node_modules/", "/dist/"],
};
diff --git a/lib/assert.class.spec.ts b/lib/assert.class.spec.ts
index 8f3b1e0b..5064b8db 100644
--- a/lib/assert.class.spec.ts
+++ b/lib/assert.class.spec.ts
@@ -1,93 +1,99 @@
-import {AssertClass} from "./assert.class";
-import {Region} from "./region.class";
-import {ScreenClass} from "./screen.class";
+import { AssertClass } from "./assert.class";
+import { Region } from "./region.class";
+import { ScreenClass } from "./screen.class";
import providerRegistry from "./provider/provider-registry.class";
-import {Image} from "../index";
-import {mockPartial} from "sneer";
+import { Image } from "../index";
+import { mockPartial } from "sneer";
-jest.mock('jimp', () => {
-});
+jest.mock("jimp", () => {});
jest.mock("./screen.class");
const needleId = "needleId";
describe("Assert", () => {
- it("isVisible should not throw if a match is found.", async () => {
- // GIVEN
- ScreenClass.prototype.find = jest.fn(() => Promise.resolve(new Region(0, 0, 100, 100)));
- const screenMock = new ScreenClass(providerRegistry);
- const SUT = new AssertClass(screenMock);
- const needle = mockPartial({
- id: needleId
- });
-
- // WHEN
-
- // THEN
- await expect(SUT.isVisible(needle)).resolves.not.toThrowError();
+ it("isVisible should not throw if a match is found.", async () => {
+ // GIVEN
+ ScreenClass.prototype.find = jest.fn(() =>
+ Promise.resolve(new Region(0, 0, 100, 100))
+ );
+ const screenMock = new ScreenClass(providerRegistry);
+ const SUT = new AssertClass(screenMock);
+ const needle = mockPartial({
+ id: needleId,
});
- it("isVisible should throw if a match is found.", async () => {
- // GIVEN
- ScreenClass.prototype.find = jest.fn(() => Promise.reject("foo"));
- const screenMock = new ScreenClass(providerRegistry);
- const SUT = new AssertClass(screenMock);
- const needle = mockPartial({
- id: needleId
- });
+ // WHEN
- // WHEN
+ // THEN
+ await expect(SUT.isVisible(needle)).resolves.not.toThrowError();
+ });
- // THEN
- await expect(SUT.isVisible(needle)).rejects.toThrowError(`Element '${needle.id}' not found`);
+ it("isVisible should throw if a match is found.", async () => {
+ // GIVEN
+ ScreenClass.prototype.find = jest.fn(() => Promise.reject("foo"));
+ const screenMock = new ScreenClass(providerRegistry);
+ const SUT = new AssertClass(screenMock);
+ const needle = mockPartial({
+ id: needleId,
});
- it("isVisible should throw if a match is found.", async () => {
- // GIVEN
- ScreenClass.prototype.find = jest.fn(() => Promise.reject("foo"));
- const screenMock = new ScreenClass(providerRegistry);
- const SUT = new AssertClass(screenMock);
- const searchRegion = new Region(10, 10, 10, 10);
- const needle = mockPartial({
- id: needleId
- });
-
- // WHEN
-
- // THEN
- await expect(SUT
- .isVisible(needle, searchRegion))
- .rejects.toThrowError(`Element '${needle.id}' not found in region ${searchRegion.toString()}`
- );
+ // WHEN
+
+ // THEN
+ await expect(SUT.isVisible(needle)).rejects.toThrowError(
+ `Element '${needle.id}' not found`
+ );
+ });
+
+ it("isVisible should throw if a match is found.", async () => {
+ // GIVEN
+ ScreenClass.prototype.find = jest.fn(() => Promise.reject("foo"));
+ const screenMock = new ScreenClass(providerRegistry);
+ const SUT = new AssertClass(screenMock);
+ const searchRegion = new Region(10, 10, 10, 10);
+ const needle = mockPartial({
+ id: needleId,
});
- it("isNotVisible should throw if a match is found.", async () => {
- // GIVEN
- ScreenClass.prototype.find = jest.fn(() => Promise.resolve(new Region(0, 0, 100, 100)));
- const screenMock = new ScreenClass(providerRegistry);
- const SUT = new AssertClass(screenMock);
- const needle = mockPartial({
- id: needleId
- });
-
- // WHEN
-
- // THEN
- await expect(SUT.notVisible(needle)).rejects.toThrowError(`'${needle.id}' is visible`);
+ // WHEN
+
+ // THEN
+ await expect(SUT.isVisible(needle, searchRegion)).rejects.toThrowError(
+ `Element '${needle.id}' not found in region ${searchRegion.toString()}`
+ );
+ });
+
+ it("isNotVisible should throw if a match is found.", async () => {
+ // GIVEN
+ ScreenClass.prototype.find = jest.fn(() =>
+ Promise.resolve(new Region(0, 0, 100, 100))
+ );
+ const screenMock = new ScreenClass(providerRegistry);
+ const SUT = new AssertClass(screenMock);
+ const needle = mockPartial({
+ id: needleId,
});
- it("isVisible should throw if a match is found.", async () => {
- // GIVEN
- ScreenClass.prototype.find = jest.fn(() => Promise.reject("foo"));
- const screenMock = new ScreenClass(providerRegistry);
- const SUT = new AssertClass(screenMock);
- const needle = mockPartial({
- id: needleId
- });
+ // WHEN
+
+ // THEN
+ await expect(SUT.notVisible(needle)).rejects.toThrowError(
+ `'${needle.id}' is visible`
+ );
+ });
+
+ it("isVisible should throw if a match is found.", async () => {
+ // GIVEN
+ ScreenClass.prototype.find = jest.fn(() => Promise.reject("foo"));
+ const screenMock = new ScreenClass(providerRegistry);
+ const SUT = new AssertClass(screenMock);
+ const needle = mockPartial({
+ id: needleId,
+ });
- // WHEN
+ // WHEN
- // THEN
- await expect(SUT.notVisible(needle)).resolves.not.toThrowError();
- });
+ // THEN
+ await expect(SUT.notVisible(needle)).resolves.not.toThrowError();
+ });
});
diff --git a/lib/assert.class.ts b/lib/assert.class.ts
index 6409e791..06b1dbe7 100644
--- a/lib/assert.class.ts
+++ b/lib/assert.class.ts
@@ -1,42 +1,49 @@
-import {Region} from "./region.class";
-import {ScreenClass} from "./screen.class";
-import {FirstArgumentType} from "./typings";
-import {OptionalSearchParameters} from "./optionalsearchparameters.class";
+import { Region } from "./region.class";
+import { ScreenClass } from "./screen.class";
+import { FirstArgumentType } from "./typings";
+import { OptionalSearchParameters } from "./optionalsearchparameters.class";
export class AssertClass {
- constructor(private screen: ScreenClass) {
- }
+ constructor(private screen: ScreenClass) {}
- public async isVisible(needle: FirstArgumentType, searchRegion?: Region, confidence?: number) {
- const identifier = (await needle).id;
+ public async isVisible(
+ needle: FirstArgumentType,
+ searchRegion?: Region,
+ confidence?: number
+ ) {
+ const identifier = (await needle).id;
- try {
- await this.screen.find(
- needle,
- {searchRegion, confidence} as OptionalSearchParameters,
- );
- } catch (err) {
- if (searchRegion !== undefined) {
- throw new Error(
- `Element '${identifier}' not found in region ${searchRegion.toString()}. Reason: ${err}`,
- );
- } else {
- throw new Error(`Element '${identifier}' not found. Reason: ${err}`);
- }
- }
+ try {
+ await this.screen.find(needle, {
+ searchRegion,
+ confidence,
+ } as OptionalSearchParameters);
+ } catch (err) {
+ if (searchRegion !== undefined) {
+ throw new Error(
+ `Element '${identifier}' not found in region ${searchRegion.toString()}. Reason: ${err}`
+ );
+ } else {
+ throw new Error(`Element '${identifier}' not found. Reason: ${err}`);
+ }
}
+ }
- public async notVisible(needle: FirstArgumentType, searchRegion?: Region, confidence?: number) {
- const identifier = (await needle).id;
+ public async notVisible(
+ needle: FirstArgumentType,
+ searchRegion?: Region,
+ confidence?: number
+ ) {
+ const identifier = (await needle).id;
- try {
- await this.screen.find(
- needle,
- {searchRegion, confidence} as OptionalSearchParameters,
- );
- } catch (err) {
- return;
- }
- throw new Error(`'${identifier}' is visible`);
+ try {
+ await this.screen.find(needle, {
+ searchRegion,
+ confidence,
+ } as OptionalSearchParameters);
+ } catch (err) {
+ return;
}
+ throw new Error(`'${identifier}' is visible`);
+ }
}
diff --git a/lib/clipboard.class.e2e.spec.ts b/lib/clipboard.class.e2e.spec.ts
index d4e35f1d..c1844258 100644
--- a/lib/clipboard.class.e2e.spec.ts
+++ b/lib/clipboard.class.e2e.spec.ts
@@ -1,13 +1,13 @@
-import {ClipboardClass} from "./clipboard.class";
+import { ClipboardClass } from "./clipboard.class";
import providerRegistry from "./provider/provider-registry.class";
describe("Clipboard class", () => {
- it("should paste copied input from system clipboard.", async () => {
- const SUT = new ClipboardClass(providerRegistry);
+ it("should paste copied input from system clipboard.", async () => {
+ const SUT = new ClipboardClass(providerRegistry);
- const textToCopy = "bar";
+ const textToCopy = "bar";
- SUT.copy(textToCopy);
- await expect(SUT.paste()).resolves.toEqual(textToCopy);
- });
+ SUT.copy(textToCopy);
+ await expect(SUT.paste()).resolves.toEqual(textToCopy);
+ });
});
diff --git a/lib/clipboard.class.spec.ts b/lib/clipboard.class.spec.ts
index c0119bc0..0cefb7f3 100644
--- a/lib/clipboard.class.spec.ts
+++ b/lib/clipboard.class.spec.ts
@@ -1,47 +1,50 @@
-import {ClipboardClass} from "./clipboard.class";
-import {ProviderRegistry} from "./provider/provider-registry.class";
-import {mockPartial} from "sneer";
-import {ClipboardProviderInterface} from "./provider";
+import { ClipboardClass } from "./clipboard.class";
+import { ProviderRegistry } from "./provider/provider-registry.class";
+import { mockPartial } from "sneer";
+import { ClipboardProviderInterface } from "./provider";
-jest.mock('jimp', () => {
-});
+jest.mock("jimp", () => {});
beforeEach(() => {
- jest.clearAllMocks();
+ jest.clearAllMocks();
});
-const providerRegistryMock = mockPartial({})
+const providerRegistryMock = mockPartial({});
describe("Clipboard class", () => {
- it("should call providers copy method.", () => {
- // GIVEN
- const SUT = new ClipboardClass(providerRegistryMock);
- const copyMock = jest.fn();
- providerRegistryMock.getClipboard = jest.fn(() => mockPartial({
- copy: copyMock
- }));
- const textToCopy = "bar";
-
- // WHEN
- SUT.copy(textToCopy);
-
- // THEN
- expect(copyMock).toHaveBeenCalledTimes(1);
- expect(copyMock).toHaveBeenCalledWith(textToCopy);
- });
-
- it("should call providers paste method.", () => {
- // GIVEN
- const SUT = new ClipboardClass(providerRegistryMock);
- const pasteMock = jest.fn();
- providerRegistryMock.getClipboard = jest.fn(() => mockPartial({
- paste: pasteMock
- }));
-
- // WHEN
- SUT.paste();
-
- // THEN
- expect(pasteMock).toHaveBeenCalledTimes(1);
- });
+ it("should call providers copy method.", () => {
+ // GIVEN
+ const SUT = new ClipboardClass(providerRegistryMock);
+ const copyMock = jest.fn();
+ providerRegistryMock.getClipboard = jest.fn(() =>
+ mockPartial({
+ copy: copyMock,
+ })
+ );
+ const textToCopy = "bar";
+
+ // WHEN
+ SUT.copy(textToCopy);
+
+ // THEN
+ expect(copyMock).toHaveBeenCalledTimes(1);
+ expect(copyMock).toHaveBeenCalledWith(textToCopy);
+ });
+
+ it("should call providers paste method.", () => {
+ // GIVEN
+ const SUT = new ClipboardClass(providerRegistryMock);
+ const pasteMock = jest.fn();
+ providerRegistryMock.getClipboard = jest.fn(() =>
+ mockPartial({
+ paste: pasteMock,
+ })
+ );
+
+ // WHEN
+ SUT.paste();
+
+ // THEN
+ expect(pasteMock).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/lib/clipboard.class.ts b/lib/clipboard.class.ts
index 67aa3744..1f08751c 100644
--- a/lib/clipboard.class.ts
+++ b/lib/clipboard.class.ts
@@ -1,15 +1,14 @@
/**
* {@link ClipboardClass} class gives access to a systems clipboard
*/
-import {ProviderRegistry} from "./provider/provider-registry.class";
+import { ProviderRegistry } from "./provider/provider-registry.class";
export class ClipboardClass {
/**
* {@link ClipboardClass} class constructor
* @param providerRegistry
*/
- constructor(private providerRegistry: ProviderRegistry) {
- }
+ constructor(private providerRegistry: ProviderRegistry) {}
/**
* {@link copy} copies a given text to the system clipboard
diff --git a/lib/colormode.enum.ts b/lib/colormode.enum.ts
index ce0b0434..24b7cbcd 100644
--- a/lib/colormode.enum.ts
+++ b/lib/colormode.enum.ts
@@ -2,6 +2,6 @@
* The {@link ColorMode} enum is used to specify the color mode of an {@link Image}
*/
export enum ColorMode {
- BGR,
- RGB
-}
\ No newline at end of file
+ BGR,
+ RGB,
+}
diff --git a/lib/expect/jest.matcher.function.ts b/lib/expect/jest.matcher.function.ts
index 9477f27d..fb70fe3e 100644
--- a/lib/expect/jest.matcher.function.ts
+++ b/lib/expect/jest.matcher.function.ts
@@ -1,23 +1,26 @@
-import {Point} from "../point.class";
-import {Region} from "../region.class";
-import {toBeAt} from "./matchers/toBeAt.function";
-import {toBeIn} from "./matchers/toBeIn.function";
-import {toShow} from "./matchers/toShow.function";
-import {FirstArgumentType} from "../typings";
-import {ScreenClass} from "../screen.class";
+import { Point } from "../point.class";
+import { Region } from "../region.class";
+import { toBeAt } from "./matchers/toBeAt.function";
+import { toBeIn } from "./matchers/toBeIn.function";
+import { toShow } from "./matchers/toShow.function";
+import { FirstArgumentType } from "../typings";
+import { ScreenClass } from "../screen.class";
declare global {
- namespace jest {
- interface Matchers {
- toBeAt: (position: Point) => {};
- toBeIn: (region: Region) => {};
- toShow: (needle: FirstArgumentType, confidence?: number) => {};
- }
+ namespace jest {
+ interface Matchers {
+ toBeAt: (position: Point) => {};
+ toBeIn: (region: Region) => {};
+ toShow: (
+ needle: FirstArgumentType,
+ confidence?: number
+ ) => {};
}
+ }
}
export const jestMatchers = {
- toBeAt,
- toBeIn,
- toShow,
+ toBeAt,
+ toBeIn,
+ toShow,
};
diff --git a/lib/expect/matchers/toBeAt.function.e2e.spec.ts b/lib/expect/matchers/toBeAt.function.e2e.spec.ts
index c40ebb63..ed589c33 100644
--- a/lib/expect/matchers/toBeAt.function.e2e.spec.ts
+++ b/lib/expect/matchers/toBeAt.function.e2e.spec.ts
@@ -2,7 +2,7 @@ import { mouse } from "../../../index";
import { Point } from "../../point.class";
import { toBeAt } from "./toBeAt.function";
-jest.mock('jimp', () => {});
+jest.mock("jimp", () => {});
const targetPoint = new Point(100, 100);
diff --git a/lib/expect/matchers/toBeIn.function.e2e.spec.ts b/lib/expect/matchers/toBeIn.function.e2e.spec.ts
index ba3f2501..57269bc5 100644
--- a/lib/expect/matchers/toBeIn.function.e2e.spec.ts
+++ b/lib/expect/matchers/toBeIn.function.e2e.spec.ts
@@ -3,7 +3,7 @@ import { Point } from "../../point.class";
import { Region } from "../../region.class";
import { toBeIn } from "./toBeIn.function";
-jest.mock('jimp', () => {});
+jest.mock("jimp", () => {});
const targetPoint = new Point(400, 400);
diff --git a/lib/expect/matchers/toShow.function.ts b/lib/expect/matchers/toShow.function.ts
index 3b3c3848..c1542779 100644
--- a/lib/expect/matchers/toShow.function.ts
+++ b/lib/expect/matchers/toShow.function.ts
@@ -1,28 +1,28 @@
-import {ScreenClass} from "../../screen.class";
-import {FirstArgumentType} from "../../typings";
-import {OptionalSearchParameters} from "../../optionalsearchparameters.class";
+import { ScreenClass } from "../../screen.class";
+import { FirstArgumentType } from "../../typings";
+import { OptionalSearchParameters } from "../../optionalsearchparameters.class";
export const toShow = async (
- received: ScreenClass,
- needle: FirstArgumentType,
- confidence?: number,
+ received: ScreenClass,
+ needle: FirstArgumentType,
+ confidence?: number
) => {
- let locationParams;
- if (confidence) {
- locationParams = new OptionalSearchParameters();
- locationParams.confidence = confidence;
- }
- const identifier = (await needle).id;
- try {
- await received.find(needle, locationParams);
- return {
- message: () => `Expected screen to not show ${identifier}`,
- pass: true,
- };
- } catch (err) {
- return {
- message: () => `Screen is not showing ${identifier}: ${err}`,
- pass: false,
- };
- }
+ let locationParams;
+ if (confidence) {
+ locationParams = new OptionalSearchParameters();
+ locationParams.confidence = confidence;
+ }
+ const identifier = (await needle).id;
+ try {
+ await received.find(needle, locationParams);
+ return {
+ message: () => `Expected screen to not show ${identifier}`,
+ pass: true,
+ };
+ } catch (err) {
+ return {
+ message: () => `Screen is not showing ${identifier}: ${err}`,
+ pass: false,
+ };
+ }
};
diff --git a/lib/file-type.enum.ts b/lib/file-type.enum.ts
index 6980ba84..030db70f 100644
--- a/lib/file-type.enum.ts
+++ b/lib/file-type.enum.ts
@@ -3,5 +3,5 @@
*/
export enum FileType {
PNG = ".png",
- JPG = ".jpg"
+ JPG = ".jpg",
}
diff --git a/lib/generate-output-path.function.spec.ts b/lib/generate-output-path.function.spec.ts
index 2187b5e1..0b4a51ef 100644
--- a/lib/generate-output-path.function.spec.ts
+++ b/lib/generate-output-path.function.spec.ts
@@ -25,7 +25,7 @@ describe("generate-output-path", () => {
const expectedPath = join(cwd(), `${pre}${filename}${ext}`);
// WHEN
- const result = generateOutputPath(filename, {prefix: pre});
+ const result = generateOutputPath(filename, { prefix: pre });
// THEN
expect(result).toEqual(expectedPath);
@@ -39,7 +39,7 @@ describe("generate-output-path", () => {
const expectedPath = join(cwd(), `${filename}${post}${ext}`);
// WHEN
- const result = generateOutputPath(filename, {postfix: post});
+ const result = generateOutputPath(filename, { postfix: post });
// THEN
expect(result).toEqual(expectedPath);
@@ -103,7 +103,7 @@ describe("generate-output-path", () => {
// WHEN
const result = generateOutputPath(filename, {
- type: FileType.JPG
+ type: FileType.JPG,
});
// THEN
diff --git a/lib/generate-output-path.function.ts b/lib/generate-output-path.function.ts
index acc25d83..bd41eeed 100644
--- a/lib/generate-output-path.function.ts
+++ b/lib/generate-output-path.function.ts
@@ -10,16 +10,16 @@ import { FileType } from "./file-type.enum";
export const generateOutputPath = (
filename: string,
params?: {
- type?: FileType,
- path?: string,
- prefix?: string,
- postfix?: string
+ type?: FileType;
+ path?: string;
+ prefix?: string;
+ postfix?: string;
}
) => {
const name = parse(filename).name;
- const imageType = (params && params.type) ? params.type : FileType.PNG;
- const path = (params && params.path) ? params.path : cwd();
- const prefix = (params && params.prefix) ? params.prefix : "";
- const postfix = (params && params.postfix) ? params.postfix : "";
+ const imageType = params && params.type ? params.type : FileType.PNG;
+ const path = params && params.path ? params.path : cwd();
+ const prefix = params && params.prefix ? params.prefix : "";
+ const postfix = params && params.postfix ? params.postfix : "";
return join(path, `${prefix}${name}${postfix}${imageType}`);
};
diff --git a/lib/image.class.spec.ts b/lib/image.class.spec.ts
index 47d866dc..17fb3f16 100644
--- a/lib/image.class.spec.ts
+++ b/lib/image.class.spec.ts
@@ -1,129 +1,138 @@
-import {Image, isImage} from "./image.class";
-import {imageToJimp} from "./provider/io/imageToJimp.function";
-import {ColorMode} from "./colormode.enum";
+import { Image, isImage } from "./image.class";
+import { imageToJimp } from "./provider/io/imageToJimp.function";
+import { ColorMode } from "./colormode.enum";
jest.mock("./provider/io/imageToJimp.function", () => {
- return {
- imageToJimp: jest.fn()
- }
+ return {
+ imageToJimp: jest.fn(),
+ };
});
afterEach(() => {
- jest.resetAllMocks();
+ jest.resetAllMocks();
});
describe("Image class", () => {
- it("should return alphachannel = true for > 3 channels", () => {
- const SUT = new Image(200, 200, Buffer.from([123]), 4, "id");
- expect(SUT.hasAlphaChannel).toBeTruthy();
+ it("should return alphachannel = true for > 3 channels", () => {
+ const SUT = new Image(200, 200, Buffer.from([123]), 4, "id");
+ expect(SUT.hasAlphaChannel).toBeTruthy();
+ });
+
+ it("should return alphachannel = false for <= 3 channels", () => {
+ const SUT = new Image(200, 200, Buffer.from([123]), 3, "id");
+ expect(SUT.hasAlphaChannel).toBeFalsy();
+ });
+ it("should return alphachannel = false for <= 3 channels", () => {
+ const SUT = new Image(200, 200, Buffer.from([123]), 2, "id");
+ expect(SUT.hasAlphaChannel).toBeFalsy();
+ });
+ it("should return alphachannel = false for <= 3 channels", () => {
+ const SUT = new Image(200, 200, Buffer.from([123]), 1, "id");
+ expect(SUT.hasAlphaChannel).toBeFalsy();
+ });
+
+ it("should throw for <= 0 channels", () => {
+ expect(() => new Image(200, 200, Buffer.from([123]), 0, "id")).toThrowError(
+ "Channel <= 0"
+ );
+ });
+
+ it("should have a default pixel density of 1.0", () => {
+ const SUT = new Image(200, 200, Buffer.from([123]), 1, "id");
+ expect(SUT.pixelDensity).toEqual({ scaleX: 1.0, scaleY: 1.0 });
+ });
+
+ describe("Colormode", () => {
+ it("should not try to convert an image to BGR if it already has the correct color mode", async () => {
+ // GIVEN
+ const bgrImage = new Image(100, 100, Buffer.from([]), 3, "testImage");
+
+ // WHEN
+ const convertedImage = await bgrImage.toBGR();
+
+ // THEN
+ expect(convertedImage).toBe(bgrImage);
+ expect(imageToJimp).not.toBeCalledTimes(1);
});
- it("should return alphachannel = false for <= 3 channels", () => {
- const SUT = new Image(200, 200, Buffer.from([123]), 3, "id");
- expect(SUT.hasAlphaChannel).toBeFalsy();
- });
- it("should return alphachannel = false for <= 3 channels", () => {
- const SUT = new Image(200, 200, Buffer.from([123]), 2, "id");
- expect(SUT.hasAlphaChannel).toBeFalsy();
- });
- it("should return alphachannel = false for <= 3 channels", () => {
- const SUT = new Image(200, 200, Buffer.from([123]), 1, "id");
- expect(SUT.hasAlphaChannel).toBeFalsy();
- });
-
- it("should throw for <= 0 channels", () => {
- expect(() => new Image(200, 200, Buffer.from([123]), 0, "id")).toThrowError("Channel <= 0");
+ it("should not try to convert an image to RGB if it already has the correct color mode", async () => {
+ // GIVEN
+ const rgbImage = new Image(
+ 100,
+ 100,
+ Buffer.from([]),
+ 3,
+ "testImage",
+ ColorMode.RGB
+ );
+
+ // WHEN
+ const convertedImage = await rgbImage.toRGB();
+
+ // THEN
+ expect(convertedImage).toBe(rgbImage);
+ expect(imageToJimp).not.toBeCalledTimes(1);
});
+ });
- it("should have a default pixel density of 1.0", () => {
- const SUT = new Image(200, 200, Buffer.from([123]), 1, "id");
- expect(SUT.pixelDensity).toEqual({scaleX: 1.0, scaleY: 1.0});
- });
+ describe("isImage typeguard", () => {
+ it("should identify an Image", () => {
+ // GIVEN
+ const img = new Image(100, 100, Buffer.from([]), 4, "foo");
- describe("Colormode", () => {
- it("should not try to convert an image to BGR if it already has the correct color mode", async () => {
- // GIVEN
- const bgrImage = new Image(100, 100, Buffer.from([]), 3, "testImage");
+ // WHEN
+ const result = isImage(img);
- // WHEN
- const convertedImage = await bgrImage.toBGR();
+ // THEN
+ expect(result).toBeTruthy();
+ });
- // THEN
- expect(convertedImage).toBe(bgrImage);
- expect(imageToJimp).not.toBeCalledTimes(1)
- });
+ it("should rule out non-objects", () => {
+ // GIVEN
+ const i = "foo";
- it("should not try to convert an image to RGB if it already has the correct color mode", async () => {
- // GIVEN
- const rgbImage = new Image(100, 100, Buffer.from([]), 3, "testImage", ColorMode.RGB);
+ // WHEN
+ const result = isImage(i);
- // WHEN
- const convertedImage = await rgbImage.toRGB();
+ // THEN
+ expect(result).toBeFalsy();
+ });
- // THEN
- expect(convertedImage).toBe(rgbImage);
- expect(imageToJimp).not.toBeCalledTimes(1)
- });
+ it("should rule out possible object with missing properties", () => {
+ // GIVEN
+ const img = {
+ width: 100,
+ height: 100,
+ data: Buffer.from([]),
+ channels: "foo",
+ id: "foo",
+ colorMode: ColorMode.BGR,
+ };
+
+ // WHEN
+ const result = isImage(img);
+
+ // THEN
+ expect(result).toBeFalsy();
});
- describe('isImage typeguard', () => {
- it('should identify an Image', () => {
- // GIVEN
- const img = new Image(100, 100, Buffer.from([]), 4, 'foo');
-
- // WHEN
- const result = isImage(img);
-
- // THEN
- expect(result).toBeTruthy();
- });
-
- it('should rule out non-objects', () => {
- // GIVEN
- const i = "foo";
-
- // WHEN
- const result = isImage(i);
-
- // THEN
- expect(result).toBeFalsy();
- });
-
- it('should rule out possible object with missing properties', () => {
- // GIVEN
- const img = {
- width: 100,
- height: 100,
- data: Buffer.from([]),
- channels: 'foo',
- id: 'foo',
- colorMode: ColorMode.BGR
- };
-
- // WHEN
- const result = isImage(img);
-
- // THEN
- expect(result).toBeFalsy();
- });
-
- it('should rule out possible object with wrong property type', () => {
- // GIVEN
- const img = {
- width: 100,
- height: 100,
- data: Buffer.from([]),
- channels: 'foo',
- id: 'foo',
- colorMode: ColorMode.BGR,
- pixelDensity: 25
- };
-
- // WHEN
- const result = isImage(img);
-
- // THEN
- expect(result).toBeFalsy();
- });
- })
+ it("should rule out possible object with wrong property type", () => {
+ // GIVEN
+ const img = {
+ width: 100,
+ height: 100,
+ data: Buffer.from([]),
+ channels: "foo",
+ id: "foo",
+ colorMode: ColorMode.BGR,
+ pixelDensity: 25,
+ };
+
+ // WHEN
+ const result = isImage(img);
+
+ // THEN
+ expect(result).toBeFalsy();
+ });
+ });
});
diff --git a/lib/image.class.ts b/lib/image.class.ts
index fb360225..ace79474 100644
--- a/lib/image.class.ts
+++ b/lib/image.class.ts
@@ -1,92 +1,114 @@
-import {imageToJimp} from "./provider/io/imageToJimp.function";
-import {ColorMode} from "./colormode.enum";
+import { imageToJimp } from "./provider/io/imageToJimp.function";
+import { ColorMode } from "./colormode.enum";
/**
* The {@link Image} class represents generic image data
*/
export class Image {
- /**
- * {@link Image} class constructor
- * @param width {@link Image} width in pixels
- * @param height {@link Image} height in pixels
- * @param data Generic {@link Image} data
- * @param channels Amount of {@link Image} channels
- * @param id Image identifier
- * @param colorMode An images color mode, defaults to {@link ColorMode.BGR}
- * @param pixelDensity Object containing scale info to work with e.g. Retina display data where the reported display size and pixel size differ (Default: {scaleX: 1.0, scaleY: 1.0})
- */
- constructor(
- public readonly width: number,
- public readonly height: number,
- public readonly data: Buffer,
- public readonly channels: number,
- public readonly id: string,
- public readonly colorMode: ColorMode = ColorMode.BGR,
- public readonly pixelDensity: { scaleX: number; scaleY: number } = {
- scaleX: 1.0,
- scaleY: 1.0,
- },
- ) {
- if (channels <= 0) {
- throw new Error("Channel <= 0");
- }
+ /**
+ * {@link Image} class constructor
+ * @param width {@link Image} width in pixels
+ * @param height {@link Image} height in pixels
+ * @param data Generic {@link Image} data
+ * @param channels Amount of {@link Image} channels
+ * @param id Image identifier
+ * @param colorMode An images color mode, defaults to {@link ColorMode.BGR}
+ * @param pixelDensity Object containing scale info to work with e.g. Retina display data where the reported display size and pixel size differ (Default: {scaleX: 1.0, scaleY: 1.0})
+ */
+ constructor(
+ public readonly width: number,
+ public readonly height: number,
+ public readonly data: Buffer,
+ public readonly channels: number,
+ public readonly id: string,
+ public readonly colorMode: ColorMode = ColorMode.BGR,
+ public readonly pixelDensity: { scaleX: number; scaleY: number } = {
+ scaleX: 1.0,
+ scaleY: 1.0,
}
-
- /**
- * {@link hasAlphaChannel} return true if an {@link Image} has an additional (fourth) alpha channel
- */
- public get hasAlphaChannel() {
- return this.channels > 3;
+ ) {
+ if (channels <= 0) {
+ throw new Error("Channel <= 0");
}
+ }
- /**
- * {@link toRGB} converts an {@link Image} from BGR color mode (default within nut.js) to RGB
- */
- public async toRGB(): Promise {
- if (this.colorMode === ColorMode.RGB) {
- return this;
- }
- const rgbImage = imageToJimp(this);
- return new Image(this.width, this.height, rgbImage.bitmap.data, this.channels, this.id, ColorMode.RGB, this.pixelDensity);
- }
+ /**
+ * {@link hasAlphaChannel} return true if an {@link Image} has an additional (fourth) alpha channel
+ */
+ public get hasAlphaChannel() {
+ return this.channels > 3;
+ }
- /**
- * {@link toBGR} converts an {@link Image} from RGB color mode to RGB
- */
- public async toBGR(): Promise {
- if (this.colorMode === ColorMode.BGR) {
- return this;
- }
- const rgbImage = imageToJimp(this);
- return new Image(this.width, this.height, rgbImage.bitmap.data, this.channels, this.id, ColorMode.BGR, this.pixelDensity);
+ /**
+ * {@link toRGB} converts an {@link Image} from BGR color mode (default within nut.js) to RGB
+ */
+ public async toRGB(): Promise {
+ if (this.colorMode === ColorMode.RGB) {
+ return this;
}
+ const rgbImage = imageToJimp(this);
+ return new Image(
+ this.width,
+ this.height,
+ rgbImage.bitmap.data,
+ this.channels,
+ this.id,
+ ColorMode.RGB,
+ this.pixelDensity
+ );
+ }
- /**
- * {@link fromRGBData} creates an {@link Image} from provided RGB data
- */
- public static fromRGBData(width: number, height: number, data: Buffer, channels: number, id: string): Image {
- const rgbImage = new Image(width, height, data, channels, id);
- const jimpImage = imageToJimp(rgbImage);
- return new Image(width, height, jimpImage.bitmap.data, channels, id);
+ /**
+ * {@link toBGR} converts an {@link Image} from RGB color mode to RGB
+ */
+ public async toBGR(): Promise {
+ if (this.colorMode === ColorMode.BGR) {
+ return this;
}
+ const rgbImage = imageToJimp(this);
+ return new Image(
+ this.width,
+ this.height,
+ rgbImage.bitmap.data,
+ this.channels,
+ this.id,
+ ColorMode.BGR,
+ this.pixelDensity
+ );
+ }
+
+ /**
+ * {@link fromRGBData} creates an {@link Image} from provided RGB data
+ */
+ public static fromRGBData(
+ width: number,
+ height: number,
+ data: Buffer,
+ channels: number,
+ id: string
+ ): Image {
+ const rgbImage = new Image(width, height, data, channels, id);
+ const jimpImage = imageToJimp(rgbImage);
+ return new Image(width, height, jimpImage.bitmap.data, channels, id);
+ }
}
const testImage = new Image(100, 100, Buffer.from([]), 4, "typeCheck");
const imageKeys = Object.keys(testImage);
export function isImage(possibleImage: any): possibleImage is Image {
- if (typeof possibleImage !== 'object') {
- return false;
+ if (typeof possibleImage !== "object") {
+ return false;
+ }
+ for (const key of imageKeys) {
+ if (!(key in possibleImage)) {
+ return false;
}
- for (const key of imageKeys) {
- if (!(key in possibleImage)) {
- return false;
- }
- const possibleImageKeyType = typeof possibleImage[key];
- const imageKeyType = typeof testImage[key as keyof typeof testImage];
- if (possibleImageKeyType !== imageKeyType) {
- return false
- }
+ const possibleImageKeyType = typeof possibleImage[key];
+ const imageKeyType = typeof testImage[key as keyof typeof testImage];
+ if (possibleImageKeyType !== imageKeyType) {
+ return false;
}
- return true;
-}
\ No newline at end of file
+ }
+ return true;
+}
diff --git a/lib/imageResources.function.spec.ts b/lib/imageResources.function.spec.ts
index 1a30f1ce..2c56f54c 100644
--- a/lib/imageResources.function.spec.ts
+++ b/lib/imageResources.function.spec.ts
@@ -1,71 +1,80 @@
-import {fetchFromUrl, loadImageResource} from "./imageResources.function";
-import {mockPartial} from "sneer";
-import {ProviderRegistry} from "./provider/provider-registry.class";
-import {ImageReader} from "./provider";
-import {join} from "path";
-import {ColorMode} from "./colormode.enum";
+import { fetchFromUrl, loadImageResource } from "./imageResources.function";
+import { mockPartial } from "sneer";
+import { ProviderRegistry } from "./provider/provider-registry.class";
+import { ImageReader } from "./provider";
+import { join } from "path";
+import { ColorMode } from "./colormode.enum";
const loadMock = jest.fn();
const providerRegistryMock = mockPartial({
- getImageReader(): ImageReader {
- return mockPartial({
- load: loadMock
- });
- }
+ getImageReader(): ImageReader {
+ return mockPartial({
+ load: loadMock,
+ });
+ },
});
-describe('imageResources', () => {
- it('should retrieve an ImageReader via providerRegistry and load an image relative to the provided resourceDirectory', async () => {
- // GIVEN
- const resourceDirectoryPath = '/foo/bar';
- const imageFileName = "image.png";
+describe("imageResources", () => {
+ it("should retrieve an ImageReader via providerRegistry and load an image relative to the provided resourceDirectory", async () => {
+ // GIVEN
+ const resourceDirectoryPath = "/foo/bar";
+ const imageFileName = "image.png";
- // WHEN
- await loadImageResource(providerRegistryMock, resourceDirectoryPath, imageFileName);
+ // WHEN
+ await loadImageResource(
+ providerRegistryMock,
+ resourceDirectoryPath,
+ imageFileName
+ );
- // THEN
- expect(loadMock).toBeCalledWith(join(resourceDirectoryPath, imageFileName));
- });
+ // THEN
+ expect(loadMock).toBeCalledWith(join(resourceDirectoryPath, imageFileName));
+ });
});
-describe('fetchFromUrl', () => {
- it('should throw on malformed URLs', async () => {
- // GIVEN
- const malformedUrl = "foo";
+describe("fetchFromUrl", () => {
+ it("should throw on malformed URLs", async () => {
+ // GIVEN
+ const malformedUrl = "foo";
- // WHEN
- const SUT = () => fetchFromUrl(malformedUrl);
+ // WHEN
+ const SUT = () => fetchFromUrl(malformedUrl);
- // THEN
- await expect(SUT).rejects.toThrowError("Failed to fetch image data. Reason: Invalid URL");
- });
+ // THEN
+ await expect(SUT).rejects.toThrowError(
+ "Failed to fetch image data. Reason: Invalid URL"
+ );
+ });
- it('should throw on non-image URLs', async () => {
- // GIVEN
- const nonImageUrl = 'https://www.npmjs.com/package/jimp';
+ it("should throw on non-image URLs", async () => {
+ // GIVEN
+ const nonImageUrl = "https://www.npmjs.com/package/jimp";
- // WHEN
- const SUT = () => fetchFromUrl(nonImageUrl);
+ // WHEN
+ const SUT = () => fetchFromUrl(nonImageUrl);
- // THEN
- await expect(SUT).rejects.toThrowError("Failed to parse image data. Reason: Could not find MIME for Buffer");
- });
+ // THEN
+ await expect(SUT).rejects.toThrowError(
+ "Failed to parse image data. Reason: Could not find MIME for Buffer"
+ );
+ });
- it('should return an RGB image from a valid URL', async () => {
- // GIVEN
- const validImageUrl = 'https://github.com/nut-tree/nut.js/raw/master/.gfx/nut.png';
- const expectedDimensions = {
- width: 502,
- height: 411
- };
- const expectedColorMode = ColorMode.RGB;
+ it("should return an RGB image from a valid URL", async () => {
+ // GIVEN
+ const validImageUrl =
+ "https://github.com/nut-tree/nut.js/raw/master/.gfx/nut.png";
+ const expectedDimensions = {
+ width: 502,
+ height: 411,
+ };
+ const expectedColorMode = ColorMode.RGB;
- // WHEN
- const rgbImage = await fetchFromUrl(validImageUrl);
+ // WHEN
+ const rgbImage = await fetchFromUrl(validImageUrl);
- // THEN
- expect(rgbImage.colorMode).toBe(expectedColorMode);
- expect(rgbImage.width).toBe(expectedDimensions.width);
- expect(rgbImage.height).toBe(expectedDimensions.height);
- });
-});
\ No newline at end of file
+ // THEN
+ expect(rgbImage.colorMode).toBe(expectedColorMode);
+ expect(rgbImage.width).toBe(expectedDimensions.width);
+ expect(rgbImage.height).toBe(expectedDimensions.height);
+ });
+});
diff --git a/lib/imageResources.function.ts b/lib/imageResources.function.ts
index 6d808355..14a0eb56 100644
--- a/lib/imageResources.function.ts
+++ b/lib/imageResources.function.ts
@@ -1,13 +1,17 @@
-import {join, normalize} from "path";
-import {ProviderRegistry} from "./provider/provider-registry.class";
-import {URL} from "url";
-import {Image} from "./image.class";
+import { join, normalize } from "path";
+import { ProviderRegistry } from "./provider/provider-registry.class";
+import { URL } from "url";
+import { Image } from "./image.class";
import Jimp from "jimp";
-import {ColorMode} from "./colormode.enum";
+import { ColorMode } from "./colormode.enum";
-export function loadImageResource(providerRegistry: ProviderRegistry, resourceDirectory: string, fileName: string) {
- const fullPath = normalize(join(resourceDirectory, fileName));
- return providerRegistry.getImageReader().load(fullPath);
+export function loadImageResource(
+ providerRegistry: ProviderRegistry,
+ resourceDirectory: string,
+ fileName: string
+) {
+ const fullPath = normalize(join(resourceDirectory, fileName));
+ return providerRegistry.getImageReader().load(fullPath);
}
/**
@@ -16,28 +20,28 @@ export function loadImageResource(providerRegistry: ProviderRegistry, resourceDi
* @throws On malformed URL input or in case of non-image remote content
*/
export async function fetchFromUrl(url: string | URL): Promise {
- let imageUrl: URL;
- if (url instanceof URL) {
- imageUrl = url;
- } else {
- try {
- imageUrl = new URL(url);
- } catch (e: any) {
- throw new Error(`Failed to fetch image data. Reason: ${e.message}`);
- }
+ let imageUrl: URL;
+ if (url instanceof URL) {
+ imageUrl = url;
+ } else {
+ try {
+ imageUrl = new URL(url);
+ } catch (e: any) {
+ throw new Error(`Failed to fetch image data. Reason: ${e.message}`);
}
- return Jimp.read(imageUrl.href)
- .then((image) => {
- return new Image(
- image.bitmap.width,
- image.bitmap.height,
- image.bitmap.data,
- 4,
- imageUrl.href,
- ColorMode.RGB
- );
- })
- .catch(err => {
- throw new Error(`Failed to parse image data. Reason: ${err.message}`);
- });
-}
\ No newline at end of file
+ }
+ return Jimp.read(imageUrl.href)
+ .then((image) => {
+ return new Image(
+ image.bitmap.width,
+ image.bitmap.height,
+ image.bitmap.data,
+ 4,
+ imageUrl.href,
+ ColorMode.RGB
+ );
+ })
+ .catch((err) => {
+ throw new Error(`Failed to parse image data. Reason: ${err.message}`);
+ });
+}
diff --git a/lib/key.enum.ts b/lib/key.enum.ts
index f6f6ef0b..f708d9f3 100644
--- a/lib/key.enum.ts
+++ b/lib/key.enum.ts
@@ -140,5 +140,5 @@ export enum Key {
AudioRewind,
AudioForward,
AudioRepeat,
- AudioRandom
+ AudioRandom,
}
diff --git a/lib/keyboard.class.spec.ts b/lib/keyboard.class.spec.ts
index 5c15158e..97019a24 100644
--- a/lib/keyboard.class.spec.ts
+++ b/lib/keyboard.class.spec.ts
@@ -1,196 +1,212 @@
-import {Key} from "./key.enum";
-import {KeyboardClass} from "./keyboard.class";
-import {ProviderRegistry} from "./provider/provider-registry.class";
-import {mockPartial} from "sneer";
-import {KeyboardProviderInterface} from "./provider";
+import { Key } from "./key.enum";
+import { KeyboardClass } from "./keyboard.class";
+import { ProviderRegistry } from "./provider/provider-registry.class";
+import { mockPartial } from "sneer";
+import { KeyboardProviderInterface } from "./provider";
jest.setTimeout(10000);
beforeEach(() => {
- jest.clearAllMocks();
+ jest.clearAllMocks();
});
const providerRegistryMock = mockPartial({
- getKeyboard(): KeyboardProviderInterface {
- return mockPartial({
- setKeyboardDelay: jest.fn(),
- })
- }
-})
-
-describe("Keyboard", () => {
- it("should have a default delay of 300 ms", () => {
- // GIVEN
- const SUT = new KeyboardClass(providerRegistryMock);
-
- // WHEN
-
- // THEN
- expect(SUT.config.autoDelayMs).toEqual(300);
- });
-
- it("should pass input strings down to the type call.", async () => {
- // GIVEN
- const SUT = new KeyboardClass(providerRegistryMock);
- const payload = "Test input!";
-
- const typeMock = jest.fn();
- providerRegistryMock.getKeyboard = jest.fn(() => mockPartial({
- setKeyboardDelay: jest.fn(),
- type: typeMock
- }));
-
- // WHEN
- await SUT.type(payload);
-
- // THEN
- expect(typeMock).toHaveBeenCalledTimes(payload.length);
- for (const char of payload.split("")) {
- expect(typeMock).toHaveBeenCalledWith(char);
- }
- });
-
- it("should pass multiple input strings down to the type call.", async () => {
- // GIVEN
- const SUT = new KeyboardClass(providerRegistryMock);
- const payload = ["Test input!", "Array test2"];
-
- const typeMock = jest.fn();
- providerRegistryMock.getKeyboard = jest.fn(() => mockPartial({
- setKeyboardDelay: jest.fn(),
- type: typeMock
- }));
-
- // WHEN
- await SUT.type(...payload);
-
- // THEN
- expect(typeMock).toHaveBeenCalledTimes(payload.join(" ").length);
- for (const char of payload.join(" ").split("")) {
- expect(typeMock).toHaveBeenCalledWith(char);
- }
- });
-
- it("should pass input keys down to the click call.", async () => {
- // GIVEN
- const SUT = new KeyboardClass(providerRegistryMock);
- const payload = [Key.A, Key.S, Key.D, Key.F];
-
- const clickMock = jest.fn();
- providerRegistryMock.getKeyboard = jest.fn(() => mockPartial({
- setKeyboardDelay: jest.fn(),
- click: clickMock
- }));
-
- // WHEN
- await SUT.type(...payload);
-
- // THEN
- expect(clickMock).toHaveBeenCalledTimes(1);
- expect(clickMock).toHaveBeenCalledWith(...payload);
+ getKeyboard(): KeyboardProviderInterface {
+ return mockPartial({
+ setKeyboardDelay: jest.fn(),
});
+ },
+});
- it("should pass a list of input keys down to the click call.", async () => {
- // GIVEN
- const SUT = new KeyboardClass(providerRegistryMock);
- const payload = [Key.A, Key.S, Key.D, Key.F];
-
- const clickMock = jest.fn();
- providerRegistryMock.getKeyboard = jest.fn(() => mockPartial({
- setKeyboardDelay: jest.fn(),
- click: clickMock
- }));
-
- // WHEN
- for (const key of payload) {
- await SUT.type(key);
- }
+describe("Keyboard", () => {
+ it("should have a default delay of 300 ms", () => {
+ // GIVEN
+ const SUT = new KeyboardClass(providerRegistryMock);
+
+ // WHEN
+
+ // THEN
+ expect(SUT.config.autoDelayMs).toEqual(300);
+ });
+
+ it("should pass input strings down to the type call.", async () => {
+ // GIVEN
+ const SUT = new KeyboardClass(providerRegistryMock);
+ const payload = "Test input!";
+
+ const typeMock = jest.fn();
+ providerRegistryMock.getKeyboard = jest.fn(() =>
+ mockPartial({
+ setKeyboardDelay: jest.fn(),
+ type: typeMock,
+ })
+ );
+
+ // WHEN
+ await SUT.type(payload);
+
+ // THEN
+ expect(typeMock).toHaveBeenCalledTimes(payload.length);
+ for (const char of payload.split("")) {
+ expect(typeMock).toHaveBeenCalledWith(char);
+ }
+ });
+
+ it("should pass multiple input strings down to the type call.", async () => {
+ // GIVEN
+ const SUT = new KeyboardClass(providerRegistryMock);
+ const payload = ["Test input!", "Array test2"];
+
+ const typeMock = jest.fn();
+ providerRegistryMock.getKeyboard = jest.fn(() =>
+ mockPartial({
+ setKeyboardDelay: jest.fn(),
+ type: typeMock,
+ })
+ );
+
+ // WHEN
+ await SUT.type(...payload);
+
+ // THEN
+ expect(typeMock).toHaveBeenCalledTimes(payload.join(" ").length);
+ for (const char of payload.join(" ").split("")) {
+ expect(typeMock).toHaveBeenCalledWith(char);
+ }
+ });
+
+ it("should pass input keys down to the click call.", async () => {
+ // GIVEN
+ const SUT = new KeyboardClass(providerRegistryMock);
+ const payload = [Key.A, Key.S, Key.D, Key.F];
+
+ const clickMock = jest.fn();
+ providerRegistryMock.getKeyboard = jest.fn(() =>
+ mockPartial({
+ setKeyboardDelay: jest.fn(),
+ click: clickMock,
+ })
+ );
+
+ // WHEN
+ await SUT.type(...payload);
+
+ // THEN
+ expect(clickMock).toHaveBeenCalledTimes(1);
+ expect(clickMock).toHaveBeenCalledWith(...payload);
+ });
+
+ it("should pass a list of input keys down to the click call.", async () => {
+ // GIVEN
+ const SUT = new KeyboardClass(providerRegistryMock);
+ const payload = [Key.A, Key.S, Key.D, Key.F];
+
+ const clickMock = jest.fn();
+ providerRegistryMock.getKeyboard = jest.fn(() =>
+ mockPartial({
+ setKeyboardDelay: jest.fn(),
+ click: clickMock,
+ })
+ );
+
+ // WHEN
+ for (const key of payload) {
+ await SUT.type(key);
+ }
- // THEN
- expect(clickMock).toHaveBeenCalledTimes(payload.length);
- });
+ // THEN
+ expect(clickMock).toHaveBeenCalledTimes(payload.length);
+ });
+
+ it("should pass a list of input keys down to the pressKey call.", async () => {
+ // GIVEN
+ const SUT = new KeyboardClass(providerRegistryMock);
+ const payload = [Key.A, Key.S, Key.D, Key.F];
+
+ const keyMock = jest.fn();
+ providerRegistryMock.getKeyboard = jest.fn(() =>
+ mockPartial({
+ setKeyboardDelay: jest.fn(),
+ pressKey: keyMock,
+ })
+ );
+
+ // WHEN
+ for (const key of payload) {
+ await SUT.pressKey(key);
+ }
- it("should pass a list of input keys down to the pressKey call.", async () => {
- // GIVEN
- const SUT = new KeyboardClass(providerRegistryMock);
- const payload = [Key.A, Key.S, Key.D, Key.F];
+ // THEN
+ expect(keyMock).toHaveBeenCalledTimes(payload.length);
+ });
+
+ it("should pass a list of input keys down to the releaseKey call.", async () => {
+ // GIVEN
+ const SUT = new KeyboardClass(providerRegistryMock);
+ const payload = [Key.A, Key.S, Key.D, Key.F];
+
+ const keyMock = jest.fn();
+ providerRegistryMock.getKeyboard = jest.fn(() =>
+ mockPartial({
+ setKeyboardDelay: jest.fn(),
+ releaseKey: keyMock,
+ })
+ );
+
+ // WHEN
+ for (const key of payload) {
+ await SUT.releaseKey(key);
+ }
- const keyMock = jest.fn();
- providerRegistryMock.getKeyboard = jest.fn(() => mockPartial({
- setKeyboardDelay: jest.fn(),
- pressKey: keyMock
- }));
+ // THEN
+ expect(keyMock).toHaveBeenCalledTimes(payload.length);
+ });
+
+ describe("autoDelayMs", () => {
+ it("pressKey should respect configured delay", async () => {
+ // GIVEN
+ const SUT = new KeyboardClass(providerRegistryMock);
+ const delay = 100;
+ SUT.config.autoDelayMs = delay;
+
+ const keyMock = jest.fn();
+ providerRegistryMock.getKeyboard = jest.fn(() =>
+ mockPartial({
+ setKeyboardDelay: jest.fn(),
+ pressKey: keyMock,
+ })
+ );
- // WHEN
- for (const key of payload) {
- await SUT.pressKey(key);
- }
+ // WHEN
+ const start = Date.now();
+ await SUT.pressKey(Key.A);
+ const duration = Date.now() - start;
- // THEN
- expect(keyMock).toHaveBeenCalledTimes(payload.length);
+ // THEN
+ expect(duration).toBeGreaterThanOrEqual(delay);
});
it("should pass a list of input keys down to the releaseKey call.", async () => {
- // GIVEN
- const SUT = new KeyboardClass(providerRegistryMock);
- const payload = [Key.A, Key.S, Key.D, Key.F];
-
- const keyMock = jest.fn();
- providerRegistryMock.getKeyboard = jest.fn(() => mockPartial({
- setKeyboardDelay: jest.fn(),
- releaseKey: keyMock
- }));
-
- // WHEN
- for (const key of payload) {
- await SUT.releaseKey(key);
- }
-
- // THEN
- expect(keyMock).toHaveBeenCalledTimes(payload.length);
- });
+ // GIVEN
+ const SUT = new KeyboardClass(providerRegistryMock);
+ const delay = 100;
+ SUT.config.autoDelayMs = delay;
+
+ const keyMock = jest.fn();
+ providerRegistryMock.getKeyboard = jest.fn(() =>
+ mockPartial({
+ setKeyboardDelay: jest.fn(),
+ releaseKey: keyMock,
+ })
+ );
+
+ // WHEN
+ const start = Date.now();
+ await SUT.releaseKey(Key.A);
+ const duration = Date.now() - start;
- describe("autoDelayMs", () => {
- it("pressKey should respect configured delay", async () => {
- // GIVEN
- const SUT = new KeyboardClass(providerRegistryMock);
- const delay = 100;
- SUT.config.autoDelayMs = delay;
-
- const keyMock = jest.fn();
- providerRegistryMock.getKeyboard = jest.fn(() => mockPartial({
- setKeyboardDelay: jest.fn(),
- pressKey: keyMock
- }));
-
- // WHEN
- const start = Date.now();
- await SUT.pressKey(Key.A);
- const duration = Date.now() - start;
-
- // THEN
- expect(duration).toBeGreaterThanOrEqual(delay);
- });
-
- it("should pass a list of input keys down to the releaseKey call.", async () => {
- // GIVEN
- const SUT = new KeyboardClass(providerRegistryMock);
- const delay = 100;
- SUT.config.autoDelayMs = delay;
-
- const keyMock = jest.fn();
- providerRegistryMock.getKeyboard = jest.fn(() => mockPartial({
- setKeyboardDelay: jest.fn(),
- releaseKey: keyMock
- }));
-
- // WHEN
- const start = Date.now();
- await SUT.releaseKey(Key.A);
- const duration = Date.now() - start;
-
- // THEN
- expect(duration).toBeGreaterThanOrEqual(delay);
- });
+ // THEN
+ expect(duration).toBeGreaterThanOrEqual(delay);
});
+ });
});
diff --git a/lib/keyboard.class.ts b/lib/keyboard.class.ts
index 25dbf9ec..a0245812 100644
--- a/lib/keyboard.class.ts
+++ b/lib/keyboard.class.ts
@@ -1,107 +1,108 @@
-import {Key} from "./key.enum";
-import {sleep} from "./sleep.function";
-import {ProviderRegistry} from "./provider/provider-registry.class";
+import { Key } from "./key.enum";
+import { sleep } from "./sleep.function";
+import { ProviderRegistry } from "./provider/provider-registry.class";
type StringOrKey = string[] | Key[];
const inputIsString = (input: (string | Key)[]): input is string[] => {
- return input.every((elem: string | Key) => typeof elem === "string");
+ return input.every((elem: string | Key) => typeof elem === "string");
};
/**
* {@link KeyboardClass} class provides methods to emulate keyboard input
*/
export class KeyboardClass {
-
+ /**
+ * Config object for {@link KeyboardClass} class
+ */
+ public config = {
/**
- * Config object for {@link KeyboardClass} class
+ * Configures the delay between single key events
*/
- public config = {
- /**
- * Configures the delay between single key events
- */
- autoDelayMs: 300,
- };
+ autoDelayMs: 300,
+ };
- /**
- * {@link KeyboardClass} class constructor
- * @param providerRegistry
- */
- constructor(private providerRegistry: ProviderRegistry) {
- this.providerRegistry.getKeyboard().setKeyboardDelay(this.config.autoDelayMs);
- }
+ /**
+ * {@link KeyboardClass} class constructor
+ * @param providerRegistry
+ */
+ constructor(private providerRegistry: ProviderRegistry) {
+ this.providerRegistry
+ .getKeyboard()
+ .setKeyboardDelay(this.config.autoDelayMs);
+ }
- /**
- * {@link type} types a sequence of {@link String} or single {@link Key}s via system keyboard
- * @example
- * ```typescript
- * await keyboard.type(Key.A, Key.S, Key.D, Key.F);
- * await keyboard.type("Hello, world!");
- * ```
- *
- * @param input Sequence of {@link String} or {@link Key} to type
- */
- public type(...input: StringOrKey): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- if (inputIsString(input)) {
- for (const char of input.join(" ")) {
- await sleep(this.config.autoDelayMs);
- await this.providerRegistry.getKeyboard().type(char);
- }
- } else {
- await this.providerRegistry.getKeyboard().click(...input as Key[]);
- }
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link type} types a sequence of {@link String} or single {@link Key}s via system keyboard
+ * @example
+ * ```typescript
+ * await keyboard.type(Key.A, Key.S, Key.D, Key.F);
+ * await keyboard.type("Hello, world!");
+ * ```
+ *
+ * @param input Sequence of {@link String} or {@link Key} to type
+ */
+ public type(...input: StringOrKey): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ if (inputIsString(input)) {
+ for (const char of input.join(" ")) {
+ await sleep(this.config.autoDelayMs);
+ await this.providerRegistry.getKeyboard().type(char);
+ }
+ } else {
+ await this.providerRegistry.getKeyboard().click(...(input as Key[]));
+ }
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
- /**
- * {@link pressKey} presses and holds a single {@link Key} for {@link Key} combinations
- * Modifier {@link Key}s are to be given in "natural" ordering, so first modifier {@link Key}s, followed by the {@link Key} to press
- * @example
- * ```typescript
- * // Will press and hold key combination STRG + V
- * await keyboard.pressKey(Key.STRG, Key.V);
- * ```
- *
- * @param keys Array of {@link Key}s to press and hold
- */
- public pressKey(...keys: Key[]): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- await sleep(this.config.autoDelayMs);
- await this.providerRegistry.getKeyboard().pressKey(...keys);
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link pressKey} presses and holds a single {@link Key} for {@link Key} combinations
+ * Modifier {@link Key}s are to be given in "natural" ordering, so first modifier {@link Key}s, followed by the {@link Key} to press
+ * @example
+ * ```typescript
+ * // Will press and hold key combination STRG + V
+ * await keyboard.pressKey(Key.STRG, Key.V);
+ * ```
+ *
+ * @param keys Array of {@link Key}s to press and hold
+ */
+ public pressKey(...keys: Key[]): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ await sleep(this.config.autoDelayMs);
+ await this.providerRegistry.getKeyboard().pressKey(...keys);
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
- /**
- * {@link pressKey} releases a single {@link Key} for {@link Key} combinations
- * Modifier {@link Key}s are to be given in "natural" ordering, so first modifier {@link Key}s, followed by the {@link Key} to press
- * @example
- * ```typescript
- * // Will release key combination STRG + V
- * await keyboard.releaseKey(Key.STRG, Key.V);
- * ```
- *
- * @param keys Array of {@link Key}s to release
- */
- public releaseKey(...keys: Key[]): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- await sleep(this.config.autoDelayMs);
- await this.providerRegistry.getKeyboard().releaseKey(...keys);
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link pressKey} releases a single {@link Key} for {@link Key} combinations
+ * Modifier {@link Key}s are to be given in "natural" ordering, so first modifier {@link Key}s, followed by the {@link Key} to press
+ * @example
+ * ```typescript
+ * // Will release key combination STRG + V
+ * await keyboard.releaseKey(Key.STRG, Key.V);
+ * ```
+ *
+ * @param keys Array of {@link Key}s to release
+ */
+ public releaseKey(...keys: Key[]): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ await sleep(this.config.autoDelayMs);
+ await this.providerRegistry.getKeyboard().releaseKey(...keys);
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
}
diff --git a/lib/location.function.spec.ts b/lib/location.function.spec.ts
index c3f7ad10..0697b817 100644
--- a/lib/location.function.spec.ts
+++ b/lib/location.function.spec.ts
@@ -1,45 +1,49 @@
-import {centerOf, randomPointIn} from "./location.function";
-import {Point} from "./point.class";
-import {Region} from "./region.class";
+import { centerOf, randomPointIn } from "./location.function";
+import { Point } from "./point.class";
+import { Region } from "./region.class";
describe("Location", () => {
- describe('centerOf', () => {
- it("should return the center point of an area.", () => {
- const expected = new Point(2, 2);
- const testRegion = new Region(0, 0, 4, 4);
+ describe("centerOf", () => {
+ it("should return the center point of an area.", () => {
+ const expected = new Point(2, 2);
+ const testRegion = new Region(0, 0, 4, 4);
- expect(centerOf(testRegion)).resolves.toEqual(expected);
- });
+ expect(centerOf(testRegion)).resolves.toEqual(expected);
+ });
- it("should throw on non Region input", async () => {
- const testRegion = {
- left: 0,
- top: 0,
- width: 4
- };
+ it("should throw on non Region input", async () => {
+ const testRegion = {
+ left: 0,
+ top: 0,
+ width: 4,
+ };
- await expect(centerOf(testRegion as Region)).rejects.toThrowError(/^centerOf requires a Region, but received/);
- });
+ await expect(centerOf(testRegion as Region)).rejects.toThrowError(
+ /^centerOf requires a Region, but received/
+ );
});
+ });
- describe('randomPointIn', () => {
- it("should return a random point inside of an area.", async () => {
- const testRegion = new Region(100, 20, 50, 35);
- const result = await randomPointIn(testRegion);
- expect(result.x).toBeGreaterThanOrEqual(testRegion.left);
- expect(result.x).toBeLessThanOrEqual(testRegion.left + testRegion.width);
- expect(result.y).toBeGreaterThanOrEqual(testRegion.top);
- expect(result.y).toBeLessThanOrEqual(testRegion.top + testRegion.height);
- });
+ describe("randomPointIn", () => {
+ it("should return a random point inside of an area.", async () => {
+ const testRegion = new Region(100, 20, 50, 35);
+ const result = await randomPointIn(testRegion);
+ expect(result.x).toBeGreaterThanOrEqual(testRegion.left);
+ expect(result.x).toBeLessThanOrEqual(testRegion.left + testRegion.width);
+ expect(result.y).toBeGreaterThanOrEqual(testRegion.top);
+ expect(result.y).toBeLessThanOrEqual(testRegion.top + testRegion.height);
+ });
- it("should throw on non Region input", async () => {
- const testRegion = {
- left: 0,
- top: 0,
- width: 4
- };
+ it("should throw on non Region input", async () => {
+ const testRegion = {
+ left: 0,
+ top: 0,
+ width: 4,
+ };
- await expect(randomPointIn(testRegion as Region)).rejects.toThrowError(/^randomPointIn requires a Region, but received/);
- });
+ await expect(randomPointIn(testRegion as Region)).rejects.toThrowError(
+ /^randomPointIn requires a Region, but received/
+ );
});
+ });
});
diff --git a/lib/location.function.ts b/lib/location.function.ts
index 56e14bd6..623f0a80 100644
--- a/lib/location.function.ts
+++ b/lib/location.function.ts
@@ -1,32 +1,42 @@
-import {Point} from "./point.class";
-import {isRegion, Region} from "./region.class";
+import { Point } from "./point.class";
+import { isRegion, Region } from "./region.class";
/**
* {@link centerOf} returns the center {@link Point} for a given {@link Region}
* @param target {@link Region} to determine the center {@link Point} for
*/
-export const centerOf = async (target: Region | Promise): Promise => {
- const targetRegion = await target;
- if (!isRegion(targetRegion)) {
- throw Error(`centerOf requires a Region, but received ${JSON.stringify(targetRegion)}`)
- }
- const x = Math.floor(targetRegion.left + targetRegion.width / 2);
- const y = Math.floor(targetRegion.top + targetRegion.height / 2);
+export const centerOf = async (
+ target: Region | Promise
+): Promise => {
+ const targetRegion = await target;
+ if (!isRegion(targetRegion)) {
+ throw Error(
+ `centerOf requires a Region, but received ${JSON.stringify(targetRegion)}`
+ );
+ }
+ const x = Math.floor(targetRegion.left + targetRegion.width / 2);
+ const y = Math.floor(targetRegion.top + targetRegion.height / 2);
- return new Point(x, y);
+ return new Point(x, y);
};
/**
* {@link randomPointIn} returns a random {@link Point} within a given {@link Region}
* @param target {@link Region} the random {@link Point} has to be within
*/
-export const randomPointIn = async (target: Region | Promise): Promise => {
- const targetRegion = await target;
- if (!isRegion(targetRegion)) {
- throw Error(`randomPointIn requires a Region, but received ${JSON.stringify(targetRegion)}`)
- }
- const x = Math.floor(targetRegion.left + Math.random() * targetRegion.width);
- const y = Math.floor(targetRegion.top + Math.random() * targetRegion.height);
+export const randomPointIn = async (
+ target: Region | Promise
+): Promise => {
+ const targetRegion = await target;
+ if (!isRegion(targetRegion)) {
+ throw Error(
+ `randomPointIn requires a Region, but received ${JSON.stringify(
+ targetRegion
+ )}`
+ );
+ }
+ const x = Math.floor(targetRegion.left + Math.random() * targetRegion.width);
+ const y = Math.floor(targetRegion.top + Math.random() * targetRegion.height);
- return new Point(x, y);
+ return new Point(x, y);
};
diff --git a/lib/match-request.class.spec.ts b/lib/match-request.class.spec.ts
index d2eaaa76..2e029aec 100644
--- a/lib/match-request.class.spec.ts
+++ b/lib/match-request.class.spec.ts
@@ -1,27 +1,16 @@
-import {Image} from "./image.class";
-import {MatchRequest} from "./match-request.class";
+import { Image } from "./image.class";
+import { MatchRequest } from "./match-request.class";
-jest.mock('jimp', () => {});
+jest.mock("jimp", () => {});
describe("MatchRequest", () => {
- it("should default to multi-scale matching", () => {
- const SUT = new MatchRequest(
- new Image(
- 100,
- 100,
- Buffer.from([]),
- 3,
- "haystack_image"
- ),
- new Image(
- 100,
- 100,
- Buffer.from([]),
- 3,
- "needle_image"
- ),
- 0.99);
+ it("should default to multi-scale matching", () => {
+ const SUT = new MatchRequest(
+ new Image(100, 100, Buffer.from([]), 3, "haystack_image"),
+ new Image(100, 100, Buffer.from([]), 3, "needle_image"),
+ 0.99
+ );
- expect(SUT.searchMultipleScales).toBeTruthy();
- });
+ expect(SUT.searchMultipleScales).toBeTruthy();
+ });
});
diff --git a/lib/match-request.class.ts b/lib/match-request.class.ts
index d09d026a..1b2d4e41 100644
--- a/lib/match-request.class.ts
+++ b/lib/match-request.class.ts
@@ -5,6 +5,6 @@ export class MatchRequest {
public readonly haystack: Image,
public readonly needle: Image,
public readonly confidence: number,
- public readonly searchMultipleScales: boolean = true,
+ public readonly searchMultipleScales: boolean = true
) {}
}
diff --git a/lib/mouse-movement.function.spec.ts b/lib/mouse-movement.function.spec.ts
index ea176e21..9e9fbabe 100644
--- a/lib/mouse-movement.function.spec.ts
+++ b/lib/mouse-movement.function.spec.ts
@@ -1,80 +1,85 @@
import {
- calculateStepDuration,
- linear,
- calculateMovementTimesteps, EasingFunction
+ calculateStepDuration,
+ linear,
+ calculateMovementTimesteps,
+ EasingFunction,
} from "./mouse-movement.function";
describe("MovementType", () => {
- describe("baseStepDuration", () => {
- it("should calculate the base step duration in nanoseconds", () => {
- // GIVEN
- const speedInPixelsPerSecond = 1000;
- const expectedBaseStepDuration = 1_000_000;
+ describe("baseStepDuration", () => {
+ it("should calculate the base step duration in nanoseconds", () => {
+ // GIVEN
+ const speedInPixelsPerSecond = 1000;
+ const expectedBaseStepDuration = 1_000_000;
- // WHEN
- const result = calculateStepDuration(speedInPixelsPerSecond);
+ // WHEN
+ const result = calculateStepDuration(speedInPixelsPerSecond);
- // THEN
- expect(result).toBe(expectedBaseStepDuration);
- });
+ // THEN
+ expect(result).toBe(expectedBaseStepDuration);
});
+ });
- describe("stepDuration", () => {
- it("should call easing function progress to calculate current step duration", () => {
- // GIVEN
- const amountOfSteps = 100;
- const speedInPixelsPerSecond = 1000;
- const easingFunction = jest.fn(() => 0);
+ describe("stepDuration", () => {
+ it("should call easing function progress to calculate current step duration", () => {
+ // GIVEN
+ const amountOfSteps = 100;
+ const speedInPixelsPerSecond = 1000;
+ const easingFunction = jest.fn(() => 0);
- // WHEN
- calculateMovementTimesteps(amountOfSteps, speedInPixelsPerSecond, easingFunction);
+ // WHEN
+ calculateMovementTimesteps(
+ amountOfSteps,
+ speedInPixelsPerSecond,
+ easingFunction
+ );
- // THEN
- expect(easingFunction).toBeCalledTimes(amountOfSteps);
- })
+ // THEN
+ expect(easingFunction).toBeCalledTimes(amountOfSteps);
});
+ });
- describe('linear', () => {
- it("should return a set of linear timesteps, 1000000 nanosecond per step.", () => {
- // GIVEN
- const expected = [1000000, 1000000, 1000000, 1000000, 1000000, 1000000];
+ describe("linear", () => {
+ it("should return a set of linear timesteps, 1000000 nanosecond per step.", () => {
+ // GIVEN
+ const expected = [1000000, 1000000, 1000000, 1000000, 1000000, 1000000];
- // WHEN
- const result = calculateMovementTimesteps(6, 1000, linear);
+ // WHEN
+ const result = calculateMovementTimesteps(6, 1000, linear);
- // THEN
- expect(result).toEqual(expected);
- });
+ // THEN
+ expect(result).toEqual(expected);
+ });
- it("should should return a set of linear timesteps, 2000000 nanoseconds per step.", () => {
- // GIVEN
- const expected = [2000000, 2000000, 2000000, 2000000, 2000000, 2000000];
+ it("should should return a set of linear timesteps, 2000000 nanoseconds per step.", () => {
+ // GIVEN
+ const expected = [2000000, 2000000, 2000000, 2000000, 2000000, 2000000];
- // WHEN
- const result = calculateMovementTimesteps(6, 500, linear);
+ // WHEN
+ const result = calculateMovementTimesteps(6, 500, linear);
- // THEN
- expect(result).toEqual(expected);
- });
+ // THEN
+ expect(result).toEqual(expected);
});
+ });
- describe('non-linear', () => {
- it("should return progress slowly in the first half, 2000000 nanoseconds per step, then continue with normal speed, 1000000 nanoseconds per step", () => {
- // GIVEN
- const mouseSpeed = 1000;
- const easingFunction: EasingFunction = (p: number) => {
- if (p < 0.5) {
- return -0.5;
- }
- return 0;
- };
- const expected = [2000000, 2000000, 2000000, 1000000, 1000000, 1000000];
+ describe("non-linear", () => {
+ it("should return progress slowly in the first half, 2000000 nanoseconds per step, then continue with normal speed, 1000000 nanoseconds per step", () => {
+ // GIVEN
+ const mouseSpeed = 1000;
+ const easingFunction: EasingFunction = (p: number) => {
+ if (p < 0.5) {
+ return -0.5;
+ }
+ return 0;
+ };
+ const expected = [2000000, 2000000, 2000000, 1000000, 1000000, 1000000];
- // WHEN
- const result = calculateMovementTimesteps(6, mouseSpeed, easingFunction);
+ // WHEN
+ const result = calculateMovementTimesteps(6, mouseSpeed, easingFunction);
- // THEN
- expect(result).toEqual(expected);
- });
+ // THEN
+ expect(result).toEqual(expected);
});
-});
\ No newline at end of file
+ });
+});
diff --git a/lib/mouse-movement.function.ts b/lib/mouse-movement.function.ts
index f67edd7a..95ccb4e3 100644
--- a/lib/mouse-movement.function.ts
+++ b/lib/mouse-movement.function.ts
@@ -4,31 +4,32 @@
* See https://easings.net/ for reference
*/
export interface EasingFunction {
- (progressPercentage: number): number;
+ (progressPercentage: number): number;
}
const nanoSecondsPerSecond = 1_000_000_000;
-export const calculateStepDuration = (speedInPixelsPerSecond: number) => (1 / speedInPixelsPerSecond) * nanoSecondsPerSecond;
+export const calculateStepDuration = (speedInPixelsPerSecond: number) =>
+ (1 / speedInPixelsPerSecond) * nanoSecondsPerSecond;
export const calculateMovementTimesteps = (
- amountOfSteps: number,
- speedInPixelsPerSecond: number,
- easingFunction: EasingFunction = linear
+ amountOfSteps: number,
+ speedInPixelsPerSecond: number,
+ easingFunction: EasingFunction = linear
): number[] => {
- const isEasingFunction = typeof easingFunction === "function";
- return Array(amountOfSteps)
- .fill(speedInPixelsPerSecond)
- .map((speed: number, idx: number) => {
- let speedInPixels = speed;
- if (isEasingFunction) {
- speedInPixels += easingFunction(idx / amountOfSteps) * speedInPixels;
- }
- const stepDuration = calculateStepDuration(speedInPixels);
- return (isFinite(stepDuration) && stepDuration > 0) ? stepDuration : 0;
- });
+ const isEasingFunction = typeof easingFunction === "function";
+ return Array(amountOfSteps)
+ .fill(speedInPixelsPerSecond)
+ .map((speed: number, idx: number) => {
+ let speedInPixels = speed;
+ if (isEasingFunction) {
+ speedInPixels += easingFunction(idx / amountOfSteps) * speedInPixels;
+ }
+ const stepDuration = calculateStepDuration(speedInPixels);
+ return isFinite(stepDuration) && stepDuration > 0 ? stepDuration : 0;
+ });
};
export const linear: EasingFunction = (_: number): number => {
- return 0;
+ return 0;
};
diff --git a/lib/mouse.class.spec.ts b/lib/mouse.class.spec.ts
index 35ff0f35..ae39b166 100644
--- a/lib/mouse.class.spec.ts
+++ b/lib/mouse.class.spec.ts
@@ -1,315 +1,350 @@
-import {Button} from "./button.enum";
-import {MouseClass} from "./mouse.class";
-import {Point} from "./point.class";
-import {LineHelper} from "./util/linehelper.class";
-import {ProviderRegistry} from "./provider/provider-registry.class";
-import {mockPartial} from "sneer";
-import {MouseProviderInterface} from "./provider";
+import { Button } from "./button.enum";
+import { MouseClass } from "./mouse.class";
+import { Point } from "./point.class";
+import { LineHelper } from "./util/linehelper.class";
+import { ProviderRegistry } from "./provider/provider-registry.class";
+import { mockPartial } from "sneer";
+import { MouseProviderInterface } from "./provider";
beforeEach(() => {
- jest.clearAllMocks();
+ jest.clearAllMocks();
});
const linehelper = new LineHelper();
const providerRegistryMock = mockPartial({
- getMouse(): MouseProviderInterface {
- return mockPartial({
- setMouseDelay: jest.fn()
- })
- }
+ getMouse(): MouseProviderInterface {
+ return mockPartial({
+ setMouseDelay: jest.fn(),
+ });
+ },
});
describe("Mouse class", () => {
- it("should have a default delay of 500 ms", () => {
- // GIVEN
- const SUT = new MouseClass(providerRegistryMock);
-
- // WHEN
-
- // THEN
- expect(SUT.config.autoDelayMs).toEqual(100);
- });
-
- it("should forward scrollLeft to the provider", async () => {
+ it("should have a default delay of 500 ms", () => {
+ // GIVEN
+ const SUT = new MouseClass(providerRegistryMock);
+
+ // WHEN
+
+ // THEN
+ expect(SUT.config.autoDelayMs).toEqual(100);
+ });
+
+ it("should forward scrollLeft to the provider", async () => {
+ // GIVEN
+ const SUT = new MouseClass(providerRegistryMock);
+ const scrollAmount = 5;
+
+ const scrollMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
+ setMouseDelay: jest.fn(),
+ scrollLeft: scrollMock,
+ })
+ );
+
+ // WHEN
+ const result = await SUT.scrollLeft(scrollAmount);
+
+ // THEN
+ expect(scrollMock).toBeCalledWith(scrollAmount);
+ expect(result).toBe(SUT);
+ });
+
+ it("should forward scrollRight to the provider", async () => {
+ // GIVEN
+ const SUT = new MouseClass(providerRegistryMock);
+ const scrollAmount = 5;
+
+ const scrollMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
+ setMouseDelay: jest.fn(),
+ scrollRight: scrollMock,
+ })
+ );
+
+ // WHEN
+ const result = await SUT.scrollRight(scrollAmount);
+
+ // THEN
+ expect(scrollMock).toBeCalledWith(scrollAmount);
+ expect(result).toBe(SUT);
+ });
+
+ it("should forward scrollDown to the provider", async () => {
+ // GIVEN
+ const SUT = new MouseClass(providerRegistryMock);
+ const scrollAmount = 5;
+
+ const scrollMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
+ setMouseDelay: jest.fn(),
+ scrollDown: scrollMock,
+ })
+ );
+
+ // WHEN
+ const result = await SUT.scrollDown(scrollAmount);
+
+ // THEN
+ expect(scrollMock).toBeCalledWith(scrollAmount);
+ expect(result).toBe(SUT);
+ });
+
+ it("should forward scrollUp to the provider", async () => {
+ // GIVEN
+ const SUT = new MouseClass(providerRegistryMock);
+ const scrollAmount = 5;
+
+ const scrollMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
+ setMouseDelay: jest.fn(),
+ scrollUp: scrollMock,
+ })
+ );
+
+ // WHEN
+ const result = await SUT.scrollUp(scrollAmount);
+
+ // THEN
+ expect(scrollMock).toBeCalledWith(scrollAmount);
+ expect(result).toBe(SUT);
+ });
+
+ it("update mouse position along path on move", async () => {
+ // GIVEN
+ const SUT = new MouseClass(providerRegistryMock);
+ const path = linehelper.straightLine(new Point(0, 0), new Point(10, 10));
+
+ const setPositionMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
+ setMouseDelay: jest.fn(),
+ setMousePosition: setPositionMock,
+ })
+ );
+
+ // WHEN
+ const result = await SUT.move(path);
+
+ // THEN
+ expect(setPositionMock).toBeCalledTimes(path.length);
+ expect(result).toBe(SUT);
+ });
+
+ it("should press and hold left mouse button, move and release left mouse button on drag", async () => {
+ // GIVEN
+ const SUT = new MouseClass(providerRegistryMock);
+ const path = linehelper.straightLine(new Point(0, 0), new Point(10, 10));
+
+ const setPositionMock = jest.fn();
+ const pressButtonMock = jest.fn();
+ const releaseButtonMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
+ setMouseDelay: jest.fn(),
+ setMousePosition: setPositionMock,
+ pressButton: pressButtonMock,
+ releaseButton: releaseButtonMock,
+ })
+ );
+
+ // WHEN
+ const result = await SUT.drag(path);
+
+ // THEN
+ expect(pressButtonMock).toBeCalledWith(Button.LEFT);
+ expect(setPositionMock).toBeCalledTimes(path.length);
+ expect(releaseButtonMock).toBeCalledWith(Button.LEFT);
+ expect(result).toBe(SUT);
+ });
+
+ describe("Mousebuttons", () => {
+ it.each([
+ [Button.LEFT, Button.LEFT],
+ [Button.MIDDLE, Button.MIDDLE],
+ [Button.RIGHT, Button.RIGHT],
+ ] as Array<[Button, Button]>)(
+ "should be pressed and released",
+ async (input: Button, expected: Button) => {
// GIVEN
const SUT = new MouseClass(providerRegistryMock);
- const scrollAmount = 5;
-
- const scrollMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
+ const pressButtonMock = jest.fn();
+ const releaseButtonMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
setMouseDelay: jest.fn(),
- scrollLeft: scrollMock
- }));
+ pressButton: pressButtonMock,
+ releaseButton: releaseButtonMock,
+ })
+ );
// WHEN
- const result = await SUT.scrollLeft(scrollAmount);
+ const pressed = await SUT.pressButton(input);
+ const released = await SUT.releaseButton(input);
// THEN
- expect(scrollMock).toBeCalledWith(scrollAmount);
- expect(result).toBe(SUT);
- });
-
- it("should forward scrollRight to the provider", async () => {
+ expect(pressButtonMock).toBeCalledWith(expected);
+ expect(releaseButtonMock).toBeCalledWith(expected);
+ expect(pressed).toBe(SUT);
+ expect(released).toBe(SUT);
+ }
+ );
+
+ describe("autoDelayMs", () => {
+ it("pressButton should respect configured delay", async () => {
// GIVEN
const SUT = new MouseClass(providerRegistryMock);
- const scrollAmount = 5;
+ const delay = 100;
+ SUT.config.autoDelayMs = delay;
- const scrollMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
+ const mouseMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
setMouseDelay: jest.fn(),
- scrollRight: scrollMock
- }));
+ pressButton: mouseMock,
+ })
+ );
// WHEN
- const result = await SUT.scrollRight(scrollAmount);
+ const start = Date.now();
+ await SUT.pressButton(Button.LEFT);
+ const duration = Date.now() - start;
// THEN
- expect(scrollMock).toBeCalledWith(scrollAmount);
- expect(result).toBe(SUT);
- });
+ expect(duration).toBeGreaterThanOrEqual(delay);
+ });
- it("should forward scrollDown to the provider", async () => {
+ it("releaseButton should respect configured delay", async () => {
// GIVEN
const SUT = new MouseClass(providerRegistryMock);
- const scrollAmount = 5;
+ const delay = 100;
+ SUT.config.autoDelayMs = delay;
- const scrollMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
+ const mouseMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
setMouseDelay: jest.fn(),
- scrollDown: scrollMock
- }));
+ releaseButton: mouseMock,
+ })
+ );
// WHEN
- const result = await SUT.scrollDown(scrollAmount);
+ const start = Date.now();
+ await SUT.releaseButton(Button.LEFT);
+ const duration = Date.now() - start;
// THEN
- expect(scrollMock).toBeCalledWith(scrollAmount);
- expect(result).toBe(SUT);
+ expect(duration).toBeGreaterThanOrEqual(delay);
+ });
+ });
+ });
+
+ describe("click and doubleClick", () => {
+ describe("click", () => {
+ it.each([
+ [Button.LEFT, Button.LEFT],
+ [Button.MIDDLE, Button.MIDDLE],
+ [Button.RIGHT, Button.RIGHT],
+ ] as Array<[Button, Button]>)(
+ "should click the respective button on the provider",
+ async (input: Button, expected: Button) => {
+ // GIVEN
+ const SUT = new MouseClass(providerRegistryMock);
+ const clickMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
+ setMouseDelay: jest.fn(),
+ click: clickMock,
+ })
+ );
+
+ // WHEN
+ await SUT.click(input);
+
+ // THEN
+ expect(clickMock).toBeCalledWith(expected);
+ }
+ );
});
- it("should forward scrollUp to the provider", async () => {
- // GIVEN
- const SUT = new MouseClass(providerRegistryMock);
- const scrollAmount = 5;
-
- const scrollMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
- setMouseDelay: jest.fn(),
- scrollUp: scrollMock
- }));
-
- // WHEN
- const result = await SUT.scrollUp(scrollAmount);
-
- // THEN
- expect(scrollMock).toBeCalledWith(scrollAmount);
- expect(result).toBe(SUT);
+ describe("doubleClick", () => {
+ it.each([
+ [Button.LEFT, Button.LEFT],
+ [Button.MIDDLE, Button.MIDDLE],
+ [Button.RIGHT, Button.RIGHT],
+ ] as Array<[Button, Button]>)(
+ "should click the respective button on the provider",
+ async (input: Button, expected: Button) => {
+ // GIVEN
+ const SUT = new MouseClass(providerRegistryMock);
+ const clickMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
+ setMouseDelay: jest.fn(),
+ doubleClick: clickMock,
+ })
+ );
+
+ // WHEN
+ await SUT.doubleClick(input);
+
+ // THEN
+ expect(clickMock).toBeCalledWith(expected);
+ }
+ );
});
- it("update mouse position along path on move", async () => {
+ describe("leftClick", () => {
+ it("should use click internally", async () => {
// GIVEN
const SUT = new MouseClass(providerRegistryMock);
- const path = linehelper.straightLine(new Point(0, 0), new Point(10, 10));
- const setPositionMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
+ const clickSpy = jest.spyOn(SUT, "click");
+ const clickMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
setMouseDelay: jest.fn(),
- setMousePosition: setPositionMock
- }));
+ click: clickMock,
+ })
+ );
// WHEN
- const result = await SUT.move(path);
+ const result = await SUT.leftClick();
// THEN
- expect(setPositionMock).toBeCalledTimes(path.length);
+ expect(clickSpy).toBeCalledWith(Button.LEFT);
+ expect(clickMock).toBeCalledWith(Button.LEFT);
expect(result).toBe(SUT);
+ });
});
- it("should press and hold left mouse button, move and release left mouse button on drag", async () => {
+ describe("rightClick", () => {
+ it("should use click internally", async () => {
// GIVEN
const SUT = new MouseClass(providerRegistryMock);
- const path = linehelper.straightLine(new Point(0, 0), new Point(10, 10));
- const setPositionMock = jest.fn();
- const pressButtonMock = jest.fn();
- const releaseButtonMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
+ const clickSpy = jest.spyOn(SUT, "click");
+ const clickMock = jest.fn();
+ providerRegistryMock.getMouse = jest.fn(() =>
+ mockPartial({
setMouseDelay: jest.fn(),
- setMousePosition: setPositionMock,
- pressButton: pressButtonMock,
- releaseButton: releaseButtonMock
- }));
+ click: clickMock,
+ })
+ );
// WHEN
- const result = await SUT.drag(path);
+ const result = await SUT.rightClick();
// THEN
- expect(pressButtonMock).toBeCalledWith(Button.LEFT);
- expect(setPositionMock).toBeCalledTimes(path.length);
- expect(releaseButtonMock).toBeCalledWith(Button.LEFT);
+ expect(clickSpy).toBeCalledWith(Button.RIGHT);
+ expect(clickMock).toBeCalledWith(Button.RIGHT);
expect(result).toBe(SUT);
+ });
});
-
- describe("Mousebuttons", () => {
- it.each([
- [Button.LEFT, Button.LEFT],
- [Button.MIDDLE, Button.MIDDLE],
- [Button.RIGHT, Button.RIGHT],
- ] as Array<[Button, Button]>)("should be pressed and released", async (input: Button, expected: Button) => {
- // GIVEN
- const SUT = new MouseClass(providerRegistryMock);
- const pressButtonMock = jest.fn();
- const releaseButtonMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
- setMouseDelay: jest.fn(),
- pressButton: pressButtonMock,
- releaseButton: releaseButtonMock
- }));
-
- // WHEN
- const pressed = await SUT.pressButton(input);
- const released = await SUT.releaseButton(input);
-
- // THEN
- expect(pressButtonMock).toBeCalledWith(expected);
- expect(releaseButtonMock).toBeCalledWith(expected);
- expect(pressed).toBe(SUT);
- expect(released).toBe(SUT);
- });
-
- describe("autoDelayMs", () => {
- it("pressButton should respect configured delay", async () => {
- // GIVEN
- const SUT = new MouseClass(providerRegistryMock);
- const delay = 100;
- SUT.config.autoDelayMs = delay;
-
- const mouseMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
- setMouseDelay: jest.fn(),
- pressButton: mouseMock
- }));
-
- // WHEN
- const start = Date.now();
- await SUT.pressButton(Button.LEFT);
- const duration = Date.now() - start;
-
- // THEN
- expect(duration).toBeGreaterThanOrEqual(delay);
- });
-
- it("releaseButton should respect configured delay", async () => {
- // GIVEN
- const SUT = new MouseClass(providerRegistryMock);
- const delay = 100;
- SUT.config.autoDelayMs = delay;
-
- const mouseMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
- setMouseDelay: jest.fn(),
- releaseButton: mouseMock
- }));
-
- // WHEN
- const start = Date.now();
- await SUT.releaseButton(Button.LEFT);
- const duration = Date.now() - start;
-
- // THEN
- expect(duration).toBeGreaterThanOrEqual(delay);
- });
- });
- });
-
- describe("click and doubleClick", () => {
- describe("click", () => {
- it.each([
- [Button.LEFT, Button.LEFT],
- [Button.MIDDLE, Button.MIDDLE],
- [Button.RIGHT, Button.RIGHT],
- ] as Array<[Button, Button]>)("should click the respective button on the provider", async (input: Button, expected: Button) => {
- // GIVEN
- const SUT = new MouseClass(providerRegistryMock);
- const clickMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
- setMouseDelay: jest.fn(),
- click: clickMock,
- }));
-
- // WHEN
- await SUT.click(input);
-
- // THEN
- expect(clickMock).toBeCalledWith(expected);
- });
- });
-
- describe("doubleClick", () => {
- it.each([
- [Button.LEFT, Button.LEFT],
- [Button.MIDDLE, Button.MIDDLE],
- [Button.RIGHT, Button.RIGHT],
- ] as Array<[Button, Button]>)("should click the respective button on the provider", async (input: Button, expected: Button) => {
- // GIVEN
- const SUT = new MouseClass(providerRegistryMock);
- const clickMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
- setMouseDelay: jest.fn(),
- doubleClick: clickMock,
- }));
-
- // WHEN
- await SUT.doubleClick(input);
-
- // THEN
- expect(clickMock).toBeCalledWith(expected);
- });
- });
-
- describe("leftClick", () => {
- it("should use click internally", async () => {
- // GIVEN
- const SUT = new MouseClass(providerRegistryMock);
-
- const clickSpy = jest.spyOn(SUT, "click");
- const clickMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
- setMouseDelay: jest.fn(),
- click: clickMock
- }));
-
- // WHEN
- const result = await SUT.leftClick();
-
- // THEN
- expect(clickSpy).toBeCalledWith(Button.LEFT);
- expect(clickMock).toBeCalledWith(Button.LEFT);
- expect(result).toBe(SUT);
- });
- });
-
- describe("rightClick", () => {
- it("should use click internally", async () => {
- // GIVEN
- const SUT = new MouseClass(providerRegistryMock);
-
- const clickSpy = jest.spyOn(SUT, "click");
- const clickMock = jest.fn();
- providerRegistryMock.getMouse = jest.fn(() => mockPartial({
- setMouseDelay: jest.fn(),
- click: clickMock
- }));
-
- // WHEN
- const result = await SUT.rightClick();
-
- // THEN
- expect(clickSpy).toBeCalledWith(Button.RIGHT);
- expect(clickMock).toBeCalledWith(Button.RIGHT);
- expect(result).toBe(SUT);
- });
- });
- });
+ });
});
diff --git a/lib/mouse.class.ts b/lib/mouse.class.ts
index 98a4cbde..9091e776 100644
--- a/lib/mouse.class.ts
+++ b/lib/mouse.class.ts
@@ -1,246 +1,259 @@
-import {Button} from "./button.enum";
-import {isPoint, Point} from "./point.class";
-import {busyWaitForNanoSeconds, sleep} from "./sleep.function";
-import {calculateMovementTimesteps, EasingFunction, linear} from "./mouse-movement.function";
-import {ProviderRegistry} from "./provider/provider-registry.class";
+import { Button } from "./button.enum";
+import { isPoint, Point } from "./point.class";
+import { busyWaitForNanoSeconds, sleep } from "./sleep.function";
+import {
+ calculateMovementTimesteps,
+ EasingFunction,
+ linear,
+} from "./mouse-movement.function";
+import { ProviderRegistry } from "./provider/provider-registry.class";
/**
* {@link MouseClass} class provides methods to emulate mouse input
*/
export class MouseClass {
+ /**
+ * Config object for {@link MouseClass} class
+ */
+ public config = {
/**
- * Config object for {@link MouseClass} class
+ * Configures the delay between single mouse events
*/
- public config = {
- /**
- * Configures the delay between single mouse events
- */
- autoDelayMs: 100,
-
- /**
- * Configures the speed in pixels/second for mouse movement
- */
- mouseSpeed: 1000,
- };
+ autoDelayMs: 100,
/**
- * {@link MouseClass} class constructor
- * @param providerRegistry
+ * Configures the speed in pixels/second for mouse movement
*/
- constructor(private providerRegistry: ProviderRegistry) {
- this.providerRegistry.getMouse().setMouseDelay(0);
- }
+ mouseSpeed: 1000,
+ };
- /**
- * {@link setPosition} instantly moves the mouse cursor to a given {@link Point}
- * @param target {@link Point} to move the cursor to
- */
- public async setPosition(target: Point): Promise {
- if (!isPoint(target)) {
- throw Error(`setPosition requires a Point, but received ${JSON.stringify(target)}`)
- }
- return new Promise(async (resolve, reject) => {
- try {
- await this.providerRegistry.getMouse().setMousePosition(target);
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link MouseClass} class constructor
+ * @param providerRegistry
+ */
+ constructor(private providerRegistry: ProviderRegistry) {
+ this.providerRegistry.getMouse().setMouseDelay(0);
+ }
- /**
- * {@link getPosition} returns a {@link Point} representing the current mouse position
- */
- public getPosition(): Promise {
- return this.providerRegistry.getMouse().currentMousePosition();
+ /**
+ * {@link setPosition} instantly moves the mouse cursor to a given {@link Point}
+ * @param target {@link Point} to move the cursor to
+ */
+ public async setPosition(target: Point): Promise {
+ if (!isPoint(target)) {
+ throw Error(
+ `setPosition requires a Point, but received ${JSON.stringify(target)}`
+ );
}
+ return new Promise(async (resolve, reject) => {
+ try {
+ await this.providerRegistry.getMouse().setMousePosition(target);
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
- /**
- * {@link move} moves the mouse cursor along a given path of {@link Point}s, according to a movement type
- * @param path Array of {@link Point}s to follow
- * @param movementType Defines the type of mouse movement. Would allow to configured acceleration etc. (Default: {@link linear}, no acceleration)
- */
- public async move(path: Point[] | Promise, movementType: EasingFunction = linear): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- const pathSteps = await path;
- const timeSteps = calculateMovementTimesteps(pathSteps.length, this.config.mouseSpeed, movementType);
- for (let idx = 0; idx < pathSteps.length; ++idx) {
- const node = pathSteps[idx];
- const minTime = timeSteps[idx];
- await busyWaitForNanoSeconds(minTime);
- await this.setPosition(node);
- }
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link getPosition} returns a {@link Point} representing the current mouse position
+ */
+ public getPosition(): Promise {
+ return this.providerRegistry.getMouse().currentMousePosition();
+ }
- /**
- * {@link leftClick} performs a click with the left mouse button
- */
- public async leftClick(): Promise {
- return this.click(Button.LEFT);
- }
+ /**
+ * {@link move} moves the mouse cursor along a given path of {@link Point}s, according to a movement type
+ * @param path Array of {@link Point}s to follow
+ * @param movementType Defines the type of mouse movement. Would allow to configured acceleration etc. (Default: {@link linear}, no acceleration)
+ */
+ public async move(
+ path: Point[] | Promise,
+ movementType: EasingFunction = linear
+ ): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const pathSteps = await path;
+ const timeSteps = calculateMovementTimesteps(
+ pathSteps.length,
+ this.config.mouseSpeed,
+ movementType
+ );
+ for (let idx = 0; idx < pathSteps.length; ++idx) {
+ const node = pathSteps[idx];
+ const minTime = timeSteps[idx];
+ await busyWaitForNanoSeconds(minTime);
+ await this.setPosition(node);
+ }
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
- /**
- * {@link rightClick} performs a click with the right mouse button
- */
- public async rightClick(): Promise {
- return this.click(Button.RIGHT);
- }
+ /**
+ * {@link leftClick} performs a click with the left mouse button
+ */
+ public async leftClick(): Promise {
+ return this.click(Button.LEFT);
+ }
- /**
- * {@link scrollDown} scrolls down for a given amount of "steps"
- * Please note that the actual scroll distance of a single "step" is OS dependent
- * @param amount The amount of "steps" to scroll
- */
- public async scrollDown(amount: number): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- await sleep(this.config.autoDelayMs);
- await this.providerRegistry.getMouse().scrollDown(amount);
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link rightClick} performs a click with the right mouse button
+ */
+ public async rightClick(): Promise {
+ return this.click(Button.RIGHT);
+ }
- /**
- * {@link scrollUp} scrolls up for a given amount of "steps"
- * Please note that the actual scroll distance of a single "step" is OS dependent
- * @param amount The amount of "steps" to scroll
- */
- public async scrollUp(amount: number): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- await sleep(this.config.autoDelayMs);
- await this.providerRegistry.getMouse().scrollUp(amount);
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link scrollDown} scrolls down for a given amount of "steps"
+ * Please note that the actual scroll distance of a single "step" is OS dependent
+ * @param amount The amount of "steps" to scroll
+ */
+ public async scrollDown(amount: number): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ await sleep(this.config.autoDelayMs);
+ await this.providerRegistry.getMouse().scrollDown(amount);
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
- /**
- * {@link scrollLeft} scrolls left for a given amount of "steps"
- * Please note that the actual scroll distance of a single "step" is OS dependent
- * @param amount The amount of "steps" to scroll
- */
- public async scrollLeft(amount: number): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- await sleep(this.config.autoDelayMs);
- await this.providerRegistry.getMouse().scrollLeft(amount);
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link scrollUp} scrolls up for a given amount of "steps"
+ * Please note that the actual scroll distance of a single "step" is OS dependent
+ * @param amount The amount of "steps" to scroll
+ */
+ public async scrollUp(amount: number): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ await sleep(this.config.autoDelayMs);
+ await this.providerRegistry.getMouse().scrollUp(amount);
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
- /**
- * {@link scrollRight} scrolls right for a given amount of "steps"
- * Please note that the actual scroll distance of a single "step" is OS dependent
- * @param amount The amount of "steps" to scroll
- */
- public async scrollRight(amount: number): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- await sleep(this.config.autoDelayMs);
- await this.providerRegistry.getMouse().scrollRight(amount);
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link scrollLeft} scrolls left for a given amount of "steps"
+ * Please note that the actual scroll distance of a single "step" is OS dependent
+ * @param amount The amount of "steps" to scroll
+ */
+ public async scrollLeft(amount: number): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ await sleep(this.config.autoDelayMs);
+ await this.providerRegistry.getMouse().scrollLeft(amount);
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
- /**
- * {@link drag} drags the mouse along a certain path
- * In summary, {@link drag} presses and holds the left mouse button, moves the mouse and releases the left button
- * @param path The path of {@link Point}s to drag along
- */
- public async drag(path: Point[] | Promise): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- await sleep(this.config.autoDelayMs);
- await this.providerRegistry.getMouse().pressButton(Button.LEFT);
- await this.move(path);
- await this.providerRegistry.getMouse().releaseButton(Button.LEFT);
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link scrollRight} scrolls right for a given amount of "steps"
+ * Please note that the actual scroll distance of a single "step" is OS dependent
+ * @param amount The amount of "steps" to scroll
+ */
+ public async scrollRight(amount: number): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ await sleep(this.config.autoDelayMs);
+ await this.providerRegistry.getMouse().scrollRight(amount);
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
- /**
- * {@link pressButton} presses and holds a mouse button
- * @param btn The {@link Button} to press and hold
- */
- public async pressButton(btn: Button): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- await sleep(this.config.autoDelayMs);
- await this.providerRegistry.getMouse().pressButton(btn);
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link drag} drags the mouse along a certain path
+ * In summary, {@link drag} presses and holds the left mouse button, moves the mouse and releases the left button
+ * @param path The path of {@link Point}s to drag along
+ */
+ public async drag(path: Point[] | Promise): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ await sleep(this.config.autoDelayMs);
+ await this.providerRegistry.getMouse().pressButton(Button.LEFT);
+ await this.move(path);
+ await this.providerRegistry.getMouse().releaseButton(Button.LEFT);
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
- /**
- * {@link releaseButton} releases a mouse button previously pressed via {@link pressButton}
- * @param btn The {@link Button} to release
- */
- public async releaseButton(btn: Button): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- await sleep(this.config.autoDelayMs);
- await this.providerRegistry.getMouse().releaseButton(btn);
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link pressButton} presses and holds a mouse button
+ * @param btn The {@link Button} to press and hold
+ */
+ public async pressButton(btn: Button): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ await sleep(this.config.autoDelayMs);
+ await this.providerRegistry.getMouse().pressButton(btn);
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
- /**
- * {@link click} clicks a mouse button
- * @param btn The {@link Button} to click
- */
- public async click(btn: Button): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- await sleep(this.config.autoDelayMs);
- await this.providerRegistry.getMouse().click(btn);
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link releaseButton} releases a mouse button previously pressed via {@link pressButton}
+ * @param btn The {@link Button} to release
+ */
+ public async releaseButton(btn: Button): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ await sleep(this.config.autoDelayMs);
+ await this.providerRegistry.getMouse().releaseButton(btn);
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
- /**
- * {@link doubleClick} performs a double click on a mouse button
- * @param btn The {@link Button} to click
- */
- public async doubleClick(btn: Button): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- await sleep(this.config.autoDelayMs);
- await this.providerRegistry.getMouse().doubleClick(btn);
- resolve(this);
- } catch (e) {
- reject(e);
- }
- });
- }
+ /**
+ * {@link click} clicks a mouse button
+ * @param btn The {@link Button} to click
+ */
+ public async click(btn: Button): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ await sleep(this.config.autoDelayMs);
+ await this.providerRegistry.getMouse().click(btn);
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
+
+ /**
+ * {@link doubleClick} performs a double click on a mouse button
+ * @param btn The {@link Button} to click
+ */
+ public async doubleClick(btn: Button): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ await sleep(this.config.autoDelayMs);
+ await this.providerRegistry.getMouse().doubleClick(btn);
+ resolve(this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
}
diff --git a/lib/movement.function.ts b/lib/movement.function.ts
index 41708d43..f5ae6652 100644
--- a/lib/movement.function.ts
+++ b/lib/movement.function.ts
@@ -1,33 +1,40 @@
-import {MovementApi} from "./movement-api.interface";
-import {isPoint, Point} from "./point.class";
-import {LineHelper} from "./util/linehelper.class";
-import {ProviderRegistry} from "./provider/provider-registry.class";
+import { MovementApi } from "./movement-api.interface";
+import { isPoint, Point } from "./point.class";
+import { LineHelper } from "./util/linehelper.class";
+import { ProviderRegistry } from "./provider/provider-registry.class";
-export const createMovementApi = (providerRegistry: ProviderRegistry, lineHelper: LineHelper): MovementApi => {
- return ({
- down: async (px: number): Promise => {
- const pos = await providerRegistry.getMouse().currentMousePosition();
- return lineHelper.straightLine(pos, new Point(pos.x, pos.y + px));
- },
- left: async (px: number): Promise => {
- const pos = await providerRegistry.getMouse().currentMousePosition();
- return lineHelper.straightLine(pos, new Point(pos.x - px, pos.y));
- },
- right: async (px: number): Promise => {
- const pos = await providerRegistry.getMouse().currentMousePosition();
- return lineHelper.straightLine(pos, new Point(pos.x + px, pos.y));
- },
- straightTo: async (target: Point | Promise): Promise => {
- const targetPoint = await target;
- if (!isPoint(targetPoint)) {
- throw Error(`straightTo requires a Point, but received ${JSON.stringify(targetPoint)}`)
- }
- const origin = await providerRegistry.getMouse().currentMousePosition();
- return lineHelper.straightLine(origin, targetPoint);
- },
- up: async (px: number): Promise => {
- const pos = await providerRegistry.getMouse().currentMousePosition();
- return lineHelper.straightLine(pos, new Point(pos.x, pos.y - px));
- },
- });
+export const createMovementApi = (
+ providerRegistry: ProviderRegistry,
+ lineHelper: LineHelper
+): MovementApi => {
+ return {
+ down: async (px: number): Promise => {
+ const pos = await providerRegistry.getMouse().currentMousePosition();
+ return lineHelper.straightLine(pos, new Point(pos.x, pos.y + px));
+ },
+ left: async (px: number): Promise => {
+ const pos = await providerRegistry.getMouse().currentMousePosition();
+ return lineHelper.straightLine(pos, new Point(pos.x - px, pos.y));
+ },
+ right: async (px: number): Promise => {
+ const pos = await providerRegistry.getMouse().currentMousePosition();
+ return lineHelper.straightLine(pos, new Point(pos.x + px, pos.y));
+ },
+ straightTo: async (target: Point | Promise): Promise => {
+ const targetPoint = await target;
+ if (!isPoint(targetPoint)) {
+ throw Error(
+ `straightTo requires a Point, but received ${JSON.stringify(
+ targetPoint
+ )}`
+ );
+ }
+ const origin = await providerRegistry.getMouse().currentMousePosition();
+ return lineHelper.straightLine(origin, targetPoint);
+ },
+ up: async (px: number): Promise => {
+ const pos = await providerRegistry.getMouse().currentMousePosition();
+ return lineHelper.straightLine(pos, new Point(pos.x, pos.y - px));
+ },
+ };
};
diff --git a/lib/optionalsearchparameters.class.ts b/lib/optionalsearchparameters.class.ts
index 3900df4e..babc01d1 100644
--- a/lib/optionalsearchparameters.class.ts
+++ b/lib/optionalsearchparameters.class.ts
@@ -1,17 +1,21 @@
-import {Region} from "./region.class";
-import {AbortSignal} from "node-abort-controller";
+import { Region } from "./region.class";
+import { AbortSignal } from "node-abort-controller";
/**
* {@link OptionalSearchParameters} serves as a data class holding location parameters for image search
*/
export class OptionalSearchParameters {
- /**
- * {@link OptionalSearchParameters} class constructor
- * @param searchRegion Optional {@link Region} to limit the search space to
- * @param confidence Optional confidence value to configure image match confidence
- * @param searchMultipleScales Optional flag to indicate if the search should be conducted at different scales
- * @param abort An {@link AbortSignal} to cancel an ongoing call to `waitFor`
- */
- constructor(public searchRegion?: Region, public confidence?: number, public searchMultipleScales?: boolean, public abort?: AbortSignal) {
- }
+ /**
+ * {@link OptionalSearchParameters} class constructor
+ * @param searchRegion Optional {@link Region} to limit the search space to
+ * @param confidence Optional confidence value to configure image match confidence
+ * @param searchMultipleScales Optional flag to indicate if the search should be conducted at different scales
+ * @param abort An {@link AbortSignal} to cancel an ongoing call to `waitFor`
+ */
+ constructor(
+ public searchRegion?: Region,
+ public confidence?: number,
+ public searchMultipleScales?: boolean,
+ public abort?: AbortSignal
+ ) {}
}
diff --git a/lib/point.class.spec.ts b/lib/point.class.spec.ts
index e1024bbd..ebb69699 100644
--- a/lib/point.class.spec.ts
+++ b/lib/point.class.spec.ts
@@ -1,61 +1,61 @@
-import {isPoint, Point} from "./point.class";
+import { isPoint, Point } from "./point.class";
describe("Point", () => {
- it("should return a proper string representation.", () => {
- const point = new Point(10, 15);
- const expected = "(10, 15)";
+ it("should return a proper string representation.", () => {
+ const point = new Point(10, 15);
+ const expected = "(10, 15)";
- expect(point.toString()).toEqual(expected);
- });
+ expect(point.toString()).toEqual(expected);
+ });
- describe('isPoint typeguard', () => {
- it('should identify a Point', () => {
- // GIVEN
- const p = new Point(100, 100);
+ describe("isPoint typeguard", () => {
+ it("should identify a Point", () => {
+ // GIVEN
+ const p = new Point(100, 100);
- // WHEN
- const result = isPoint(p);
+ // WHEN
+ const result = isPoint(p);
- // THEN
- expect(result).toBeTruthy();
- });
+ // THEN
+ expect(result).toBeTruthy();
+ });
- it('should rule out non-objects', () => {
- // GIVEN
- const p = "foo";
+ it("should rule out non-objects", () => {
+ // GIVEN
+ const p = "foo";
- // WHEN
- const result = isPoint(p);
+ // WHEN
+ const result = isPoint(p);
- // THEN
- expect(result).toBeFalsy();
- });
+ // THEN
+ expect(result).toBeFalsy();
+ });
- it('should rule out possible object with missing properties', () => {
- // GIVEN
- const p = {
- x: 100
- };
+ it("should rule out possible object with missing properties", () => {
+ // GIVEN
+ const p = {
+ x: 100,
+ };
- // WHEN
- const result = isPoint(p);
+ // WHEN
+ const result = isPoint(p);
- // THEN
- expect(result).toBeFalsy();
- });
+ // THEN
+ expect(result).toBeFalsy();
+ });
- it('should rule out possible object with wrong property type', () => {
- // GIVEN
- const p = {
- x: 100,
- y: 'foo'
- };
+ it("should rule out possible object with wrong property type", () => {
+ // GIVEN
+ const p = {
+ x: 100,
+ y: "foo",
+ };
- // WHEN
- const result = isPoint(p);
+ // WHEN
+ const result = isPoint(p);
- // THEN
- expect(result).toBeFalsy();
- });
- })
+ // THEN
+ expect(result).toBeFalsy();
+ });
+ });
});
diff --git a/lib/point.class.ts b/lib/point.class.ts
index 3c536a95..6b45b6b8 100644
--- a/lib/point.class.ts
+++ b/lib/point.class.ts
@@ -10,7 +10,7 @@ const testPoint = new Point(100, 100);
const pointKeys = Object.keys(testPoint);
export function isPoint(possiblePoint: any): possiblePoint is Point {
- if (typeof possiblePoint !== 'object') {
+ if (typeof possiblePoint !== "object") {
return false;
}
for (const key of pointKeys) {
@@ -19,8 +19,8 @@ export function isPoint(possiblePoint: any): possiblePoint is Point {
}
const possiblePointKeyType = typeof possiblePoint[key];
const pointKeyType = typeof testPoint[key as keyof typeof testPoint];
- if (possiblePointKeyType!== pointKeyType) {
- return false
+ if (possiblePointKeyType !== pointKeyType) {
+ return false;
}
}
return true;
diff --git a/lib/provider/image-processor.interface.ts b/lib/provider/image-processor.interface.ts
index 1eca6ffb..c77be27b 100644
--- a/lib/provider/image-processor.interface.ts
+++ b/lib/provider/image-processor.interface.ts
@@ -1,6 +1,6 @@
-import {Point} from "../point.class";
-import {RGBA} from "../rgba.class";
-import {Image} from "../image.class";
+import { Point } from "../point.class";
+import { RGBA } from "../rgba.class";
+import { Image } from "../image.class";
/**
* An ImageProcessor should provide an abstraction layer to perform
@@ -9,11 +9,13 @@ import {Image} from "../image.class";
* @interface ImageFinderInterface
*/
export interface ImageProcessor {
-
- /**
- * {@link colorAt} returns a pixels {@link RGBA} value
- * @param image The {@link Image} to query color information from
- * @param location The {@link Point} where to query color information
- */
- colorAt(image: Image | Promise, location: Point | Promise): Promise;
-}
\ No newline at end of file
+ /**
+ * {@link colorAt} returns a pixels {@link RGBA} value
+ * @param image The {@link Image} to query color information from
+ * @param location The {@link Point} where to query color information
+ */
+ colorAt(
+ image: Image | Promise,
+ location: Point | Promise
+ ): Promise;
+}
diff --git a/lib/provider/image-reader.type.ts b/lib/provider/image-reader.type.ts
index 04b8c4f1..6733fc22 100644
--- a/lib/provider/image-reader.type.ts
+++ b/lib/provider/image-reader.type.ts
@@ -1,4 +1,4 @@
-import {DataSourceInterface} from "./data-source.interface";
-import {Image} from "../image.class";
+import { DataSourceInterface } from "./data-source.interface";
+import { Image } from "../image.class";
export type ImageReader = DataSourceInterface;
diff --git a/lib/provider/image-writer.type.ts b/lib/provider/image-writer.type.ts
index 0d063fee..d8eceaf6 100644
--- a/lib/provider/image-writer.type.ts
+++ b/lib/provider/image-writer.type.ts
@@ -1,9 +1,9 @@
-import {Image} from "../image.class";
-import {DataSinkInterface} from "./data-sink.interface";
+import { Image } from "../image.class";
+import { DataSinkInterface } from "./data-sink.interface";
export interface ImageWriterParameters {
- image: Image,
- path: string
+ image: Image;
+ path: string;
}
export type ImageWriter = DataSinkInterface;
diff --git a/lib/provider/image/jimp-image-processor.class.ts b/lib/provider/image/jimp-image-processor.class.ts
index 832f71fe..4ba6d335 100644
--- a/lib/provider/image/jimp-image-processor.class.ts
+++ b/lib/provider/image/jimp-image-processor.class.ts
@@ -1,24 +1,33 @@
-import Jimp from 'jimp';
-import {Image} from "../../image.class";
-import {Point} from "../../point.class";
-import {ImageProcessor} from "../image-processor.interface";
-import {imageToJimp} from "../io/imageToJimp.function";
-import {RGBA} from "../../rgba.class";
+import Jimp from "jimp";
+import { Image } from "../../image.class";
+import { Point } from "../../point.class";
+import { ImageProcessor } from "../image-processor.interface";
+import { imageToJimp } from "../io/imageToJimp.function";
+import { RGBA } from "../../rgba.class";
export default class implements ImageProcessor {
- colorAt(image: Image | Promise, point: Point | Promise): Promise {
- return new Promise(async (resolve, reject) => {
- const location = await point;
- const img = await image;
- if (location.x < 0 || location.x >= img.width) {
- reject(`Query location out of bounds. Should be in range 0 <= x < image.width, is ${location.x}`);
- }
- if (location.y < 0 || location.y >= img.height) {
- reject(`Query location out of bounds. Should be in range 0 <= y < image.height, is ${location.y}`);
- }
- const jimpImage = imageToJimp(img);
- const rgba = Jimp.intToRGBA(jimpImage.getPixelColor(location.x, location.y));
- resolve(new RGBA(rgba.r, rgba.g, rgba.b, rgba.a));
- });
- }
+ colorAt(
+ image: Image | Promise,
+ point: Point | Promise
+ ): Promise {
+ return new Promise(async (resolve, reject) => {
+ const location = await point;
+ const img = await image;
+ if (location.x < 0 || location.x >= img.width) {
+ reject(
+ `Query location out of bounds. Should be in range 0 <= x < image.width, is ${location.x}`
+ );
+ }
+ if (location.y < 0 || location.y >= img.height) {
+ reject(
+ `Query location out of bounds. Should be in range 0 <= y < image.height, is ${location.y}`
+ );
+ }
+ const jimpImage = imageToJimp(img);
+ const rgba = Jimp.intToRGBA(
+ jimpImage.getPixelColor(location.x, location.y)
+ );
+ resolve(new RGBA(rgba.r, rgba.g, rgba.b, rgba.a));
+ });
+ }
}
diff --git a/lib/provider/index.ts b/lib/provider/index.ts
index f20d3799..aeb2ea2c 100644
--- a/lib/provider/index.ts
+++ b/lib/provider/index.ts
@@ -1,10 +1,10 @@
-export {ClipboardProviderInterface} from "./clipboard-provider.interface";
-export {DataSinkInterface} from "./data-sink.interface";
-export {DataSourceInterface} from "./data-source.interface";
-export {ImageFinderInterface} from "./image-finder.interface";
-export {ImageReader} from "./image-reader.type";
-export {ImageWriter, ImageWriterParameters} from "./image-writer.type";
-export {KeyboardProviderInterface} from "./keyboard-provider.interface";
-export {MouseProviderInterface} from "./mouse-provider.interface";
-export {ScreenProviderInterface} from "./screen-provider.interface";
-export {WindowProviderInterface} from "./window-provider.interface";
\ No newline at end of file
+export { ClipboardProviderInterface } from "./clipboard-provider.interface";
+export { DataSinkInterface } from "./data-sink.interface";
+export { DataSourceInterface } from "./data-source.interface";
+export { ImageFinderInterface } from "./image-finder.interface";
+export { ImageReader } from "./image-reader.type";
+export { ImageWriter, ImageWriterParameters } from "./image-writer.type";
+export { KeyboardProviderInterface } from "./keyboard-provider.interface";
+export { MouseProviderInterface } from "./mouse-provider.interface";
+export { ScreenProviderInterface } from "./screen-provider.interface";
+export { WindowProviderInterface } from "./window-provider.interface";
diff --git a/lib/provider/io/imageToJimp.function.spec.ts b/lib/provider/io/imageToJimp.function.spec.ts
index 30c527c1..74ded2c4 100644
--- a/lib/provider/io/imageToJimp.function.spec.ts
+++ b/lib/provider/io/imageToJimp.function.spec.ts
@@ -1,38 +1,44 @@
-import {Image} from "../../image.class";
-import {imageToJimp} from "./imageToJimp.function";
+import { Image } from "../../image.class";
+import { imageToJimp } from "./imageToJimp.function";
import Jimp from "jimp";
-jest.mock('jimp', () => {
- class JimpMock {
- bitmap = {
- width: 100,
- height: 100,
- data: Buffer.from([]),
- }
- hasAlpha = () => false
- static read = jest.fn(() => Promise.resolve(new JimpMock()))
- }
+jest.mock("jimp", () => {
+ class JimpMock {
+ bitmap = {
+ width: 100,
+ height: 100,
+ data: Buffer.from([]),
+ };
+ hasAlpha = () => false;
+ static read = jest.fn(() => Promise.resolve(new JimpMock()));
+ }
- return ({
- __esModule: true,
- default: JimpMock
- })
+ return {
+ __esModule: true,
+ default: JimpMock,
+ };
});
afterEach(() => jest.resetAllMocks());
-describe('imageToJimp', () => {
- it('should successfully convert an Image to a Jimp instance', async () => {
- // GIVEN
- const scanMock = jest.fn();
- Jimp.prototype.scan = scanMock;
- const inputImage = new Image(1, 1, Buffer.from([0, 0, 0]),3, "input_image");
+describe("imageToJimp", () => {
+ it("should successfully convert an Image to a Jimp instance", async () => {
+ // GIVEN
+ const scanMock = jest.fn();
+ Jimp.prototype.scan = scanMock;
+ const inputImage = new Image(
+ 1,
+ 1,
+ Buffer.from([0, 0, 0]),
+ 3,
+ "input_image"
+ );
- // WHEN
- const result = await imageToJimp(inputImage);
+ // WHEN
+ const result = await imageToJimp(inputImage);
- // THEN
- expect(result).toBeInstanceOf(Jimp);
- expect(scanMock).toHaveBeenCalledTimes(1);
- });
-});
\ No newline at end of file
+ // THEN
+ expect(result).toBeInstanceOf(Jimp);
+ expect(scanMock).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/lib/provider/io/imageToJimp.function.ts b/lib/provider/io/imageToJimp.function.ts
index b91cdff4..a1253290 100644
--- a/lib/provider/io/imageToJimp.function.ts
+++ b/lib/provider/io/imageToJimp.function.ts
@@ -1,20 +1,26 @@
import Jimp from "jimp";
-import {Image} from "../../image.class";
-import {ColorMode} from "../../colormode.enum";
+import { Image } from "../../image.class";
+import { ColorMode } from "../../colormode.enum";
export function imageToJimp(image: Image): Jimp {
- const jimpImage = new Jimp({
- data: image.data,
- width: image.width,
- height: image.height
- });
- if (image.colorMode === ColorMode.BGR) {
- // Image treats data in BGR format, so we have to switch red and blue color channels
- jimpImage.scan(0, 0, jimpImage.bitmap.width, jimpImage.bitmap.height, function (_, __, idx) {
- const red = this.bitmap.data[idx];
- this.bitmap.data[idx] = this.bitmap.data[idx + 2];
- this.bitmap.data[idx + 2] = red;
- });
- }
- return jimpImage;
-}
\ No newline at end of file
+ const jimpImage = new Jimp({
+ data: image.data,
+ width: image.width,
+ height: image.height,
+ });
+ if (image.colorMode === ColorMode.BGR) {
+ // Image treats data in BGR format, so we have to switch red and blue color channels
+ jimpImage.scan(
+ 0,
+ 0,
+ jimpImage.bitmap.width,
+ jimpImage.bitmap.height,
+ function (_, __, idx) {
+ const red = this.bitmap.data[idx];
+ this.bitmap.data[idx] = this.bitmap.data[idx + 2];
+ this.bitmap.data[idx + 2] = red;
+ }
+ );
+ }
+ return jimpImage;
+}
diff --git a/lib/provider/io/jimp-image-reader.class.spec.ts b/lib/provider/io/jimp-image-reader.class.spec.ts
index 837b6d5c..bd1267e7 100644
--- a/lib/provider/io/jimp-image-reader.class.spec.ts
+++ b/lib/provider/io/jimp-image-reader.class.spec.ts
@@ -1,58 +1,58 @@
import ImageReader from "./jimp-image-reader.class";
-import {join} from "path";
+import { join } from "path";
import Jimp from "jimp";
-jest.mock('jimp', () => {
- class JimpMock {
- bitmap = {
- width: 100,
- height: 100,
- data: Buffer.from([]),
- }
- hasAlpha = () => false
- static read = jest.fn(() => Promise.resolve(new JimpMock()))
- }
-
- return ({
- __esModule: true,
- default: JimpMock
- })
+jest.mock("jimp", () => {
+ class JimpMock {
+ bitmap = {
+ width: 100,
+ height: 100,
+ data: Buffer.from([]),
+ };
+ hasAlpha = () => false;
+ static read = jest.fn(() => Promise.resolve(new JimpMock()));
+ }
+
+ return {
+ __esModule: true,
+ default: JimpMock,
+ };
});
afterEach(() => jest.resetAllMocks());
-describe('Jimp image reader', () => {
- it('should return an Image object', async () => {
- // GIVEN
- const inputPath = join(__dirname, "__mocks__", "calculator.png");
- const scanMock = jest.fn();
- Jimp.prototype.scan = scanMock;
- const SUT = new ImageReader();
-
- // WHEN
- await SUT.load(inputPath);
-
- // THEN
- expect(scanMock).toHaveBeenCalledTimes(1);
- expect(Jimp.read).toBeCalledTimes(1);
- expect(Jimp.read).toBeCalledWith(inputPath);
+describe("Jimp image reader", () => {
+ it("should return an Image object", async () => {
+ // GIVEN
+ const inputPath = join(__dirname, "__mocks__", "calculator.png");
+ const scanMock = jest.fn();
+ Jimp.prototype.scan = scanMock;
+ const SUT = new ImageReader();
+
+ // WHEN
+ await SUT.load(inputPath);
+
+ // THEN
+ expect(scanMock).toHaveBeenCalledTimes(1);
+ expect(Jimp.read).toBeCalledTimes(1);
+ expect(Jimp.read).toBeCalledWith(inputPath);
+ });
+
+ it("should reject on loading failures", async () => {
+ // GIVEN
+ const inputPath = "/some/path/to/file";
+ const expectedError = "Error during load";
+ const SUT = new ImageReader();
+ Jimp.read = jest.fn(() => {
+ throw new Error(expectedError);
});
- it('should reject on loading failures', async () => {
- // GIVEN
- const inputPath = "/some/path/to/file";
- const expectedError = "Error during load";
- const SUT = new ImageReader();
- Jimp.read = jest.fn(() => {
- throw new Error(expectedError);
- })
-
- // WHEN
- try {
- await SUT.load(inputPath);
- } catch (err) {
- // THEN
- expect(err).toStrictEqual(Error(expectedError));
- }
- });
-});
\ No newline at end of file
+ // WHEN
+ try {
+ await SUT.load(inputPath);
+ } catch (err) {
+ // THEN
+ expect(err).toStrictEqual(Error(expectedError));
+ }
+ });
+});
diff --git a/lib/provider/io/jimp-image-reader.class.ts b/lib/provider/io/jimp-image-reader.class.ts
index f33089be..1a0400a4 100644
--- a/lib/provider/io/jimp-image-reader.class.ts
+++ b/lib/provider/io/jimp-image-reader.class.ts
@@ -1,28 +1,39 @@
-import Jimp from 'jimp';
-import {ImageReader} from "../image-reader.type";
-import {Image} from "../../image.class";
-import {ColorMode} from "../../colormode.enum";
+import Jimp from "jimp";
+import { ImageReader } from "../image-reader.type";
+import { Image } from "../../image.class";
+import { ColorMode } from "../../colormode.enum";
export default class implements ImageReader {
- load(parameters: string): Promise {
- return new Promise((resolve, reject) => {
- Jimp.read(parameters)
- .then(jimpImage => {
- // stay consistent with images retrieved from libnut which uses BGR format
- jimpImage.scan(0, 0, jimpImage.bitmap.width, jimpImage.bitmap.height, function (_, __, idx) {
- const red = this.bitmap.data[idx];
- this.bitmap.data[idx] = this.bitmap.data[idx + 2];
- this.bitmap.data[idx + 2] = red;
- });
- resolve(new Image(
- jimpImage.bitmap.width,
- jimpImage.bitmap.height,
- jimpImage.bitmap.data,
- jimpImage.hasAlpha() ? 4 : 3,
- parameters,
- ColorMode.BGR
- ));
- }).catch(err => reject(`Failed to load image from '${parameters}'. Reason: ${err}`));
+ load(parameters: string): Promise {
+ return new Promise((resolve, reject) => {
+ Jimp.read(parameters)
+ .then((jimpImage) => {
+ // stay consistent with images retrieved from libnut which uses BGR format
+ jimpImage.scan(
+ 0,
+ 0,
+ jimpImage.bitmap.width,
+ jimpImage.bitmap.height,
+ function (_, __, idx) {
+ const red = this.bitmap.data[idx];
+ this.bitmap.data[idx] = this.bitmap.data[idx + 2];
+ this.bitmap.data[idx + 2] = red;
+ }
+ );
+ resolve(
+ new Image(
+ jimpImage.bitmap.width,
+ jimpImage.bitmap.height,
+ jimpImage.bitmap.data,
+ jimpImage.hasAlpha() ? 4 : 3,
+ parameters,
+ ColorMode.BGR
+ )
+ );
})
- }
+ .catch((err) =>
+ reject(`Failed to load image from '${parameters}'. Reason: ${err}`)
+ );
+ });
+ }
}
diff --git a/lib/provider/io/jimp-image-writer.class.spec.ts b/lib/provider/io/jimp-image-writer.class.spec.ts
index 480cae06..aa4fc91e 100644
--- a/lib/provider/io/jimp-image-writer.class.spec.ts
+++ b/lib/provider/io/jimp-image-writer.class.spec.ts
@@ -1,43 +1,43 @@
import ImageWriter from "./jimp-image-writer.class";
-import {Image} from "../../image.class";
+import { Image } from "../../image.class";
import Jimp from "jimp";
-jest.mock('jimp', () => {
- class JimpMock {
- bitmap = {
- width: 100,
- height: 100,
- data: Buffer.from([]),
- }
- hasAlpha = () => false
- static read = jest.fn(() => Promise.resolve(new JimpMock()))
- }
+jest.mock("jimp", () => {
+ class JimpMock {
+ bitmap = {
+ width: 100,
+ height: 100,
+ data: Buffer.from([]),
+ };
+ hasAlpha = () => false;
+ static read = jest.fn(() => Promise.resolve(new JimpMock()));
+ }
- return ({
- __esModule: true,
- default: JimpMock
- })
+ return {
+ __esModule: true,
+ default: JimpMock,
+ };
});
afterEach(() => jest.resetAllMocks());
-describe('Jimp image writer', () => {
- it('should reject on writing failures', async () => {
- // GIVEN
- const outputFileName = "/does/not/compute.png"
- const outputFile = new Image(100, 200, Buffer.from([]), 3, outputFileName);
- const writeMock = jest.fn(() => Promise.resolve(new Jimp()));
- const scanMock = jest.fn();
- Jimp.prototype.scan = scanMock;
- Jimp.prototype.writeAsync = writeMock;
- const SUT = new ImageWriter();
+describe("Jimp image writer", () => {
+ it("should reject on writing failures", async () => {
+ // GIVEN
+ const outputFileName = "/does/not/compute.png";
+ const outputFile = new Image(100, 200, Buffer.from([]), 3, outputFileName);
+ const writeMock = jest.fn(() => Promise.resolve(new Jimp()));
+ const scanMock = jest.fn();
+ Jimp.prototype.scan = scanMock;
+ Jimp.prototype.writeAsync = writeMock;
+ const SUT = new ImageWriter();
- // WHEN
- await SUT.store({image: outputFile, path: outputFileName});
+ // WHEN
+ await SUT.store({ image: outputFile, path: outputFileName });
- // THEN
- expect(scanMock).toHaveBeenCalledTimes(1)
- expect(writeMock).toHaveBeenCalledTimes(1)
- expect(writeMock).toHaveBeenCalledWith(outputFileName)
- });
-});
\ No newline at end of file
+ // THEN
+ expect(scanMock).toHaveBeenCalledTimes(1);
+ expect(writeMock).toHaveBeenCalledTimes(1);
+ expect(writeMock).toHaveBeenCalledWith(outputFileName);
+ });
+});
diff --git a/lib/provider/io/jimp-image-writer.class.ts b/lib/provider/io/jimp-image-writer.class.ts
index 664b7794..b80343f6 100644
--- a/lib/provider/io/jimp-image-writer.class.ts
+++ b/lib/provider/io/jimp-image-writer.class.ts
@@ -1,14 +1,14 @@
-import {ImageWriter, ImageWriterParameters} from "../image-writer.type";
-import {imageToJimp} from "./imageToJimp.function";
+import { ImageWriter, ImageWriterParameters } from "../image-writer.type";
+import { imageToJimp } from "./imageToJimp.function";
export default class implements ImageWriter {
- store(parameters: ImageWriterParameters): Promise {
- return new Promise((resolve, reject) => {
- const jimpImage = imageToJimp(parameters.image);
- jimpImage
- .writeAsync(parameters.path)
- .then(_ => resolve())
- .catch(err => reject(err));
- });
- }
+ store(parameters: ImageWriterParameters): Promise {
+ return new Promise((resolve, reject) => {
+ const jimpImage = imageToJimp(parameters.image);
+ jimpImage
+ .writeAsync(parameters.path)
+ .then((_) => resolve())
+ .catch((err) => reject(err));
+ });
+ }
}
diff --git a/lib/provider/native/clipboardy-clipboard.class.spec.ts b/lib/provider/native/clipboardy-clipboard.class.spec.ts
index 9e96a795..1794d053 100644
--- a/lib/provider/native/clipboardy-clipboard.class.spec.ts
+++ b/lib/provider/native/clipboardy-clipboard.class.spec.ts
@@ -1,6 +1,6 @@
import ClipboardAction from "./clipboardy-clipboard.class";
-jest.mock('jimp', () => {});
+jest.mock("jimp", () => {});
beforeEach(() => {
jest.resetAllMocks();
diff --git a/lib/provider/native/clipboardy-clipboard.class.ts b/lib/provider/native/clipboardy-clipboard.class.ts
index a8ab2747..e3254ba9 100644
--- a/lib/provider/native/clipboardy-clipboard.class.ts
+++ b/lib/provider/native/clipboardy-clipboard.class.ts
@@ -2,8 +2,7 @@ import clippy from "clipboardy";
import { ClipboardProviderInterface } from "../clipboard-provider.interface";
export default class implements ClipboardProviderInterface {
- constructor() {
- }
+ constructor() {}
public async hasText(): Promise {
return new Promise((resolve, reject) => {
diff --git a/lib/provider/native/libnut-keyboard.class.spec.ts b/lib/provider/native/libnut-keyboard.class.spec.ts
index c1b44a19..15a27fd7 100644
--- a/lib/provider/native/libnut-keyboard.class.spec.ts
+++ b/lib/provider/native/libnut-keyboard.class.spec.ts
@@ -84,7 +84,11 @@ describe("libnut keyboard action", () => {
// THEN
expect(libnut.keyToggle).toBeCalledTimes(1);
- expect(libnut.keyToggle).toBeCalledWith(KeyboardAction.keyLookup(Key.A), "down", []);
+ expect(libnut.keyToggle).toBeCalledWith(
+ KeyboardAction.keyLookup(Key.A),
+ "down",
+ []
+ );
});
it("should treat a list of keys as modifiers + the actual key to press", async () => {
@@ -96,8 +100,11 @@ describe("libnut keyboard action", () => {
// THEN
expect(libnut.keyToggle).toBeCalledTimes(1);
- expect(libnut.keyToggle)
- .toBeCalledWith(KeyboardAction.keyLookup(Key.A), "down", [KeyboardAction.keyLookup(Key.LeftControl)]);
+ expect(libnut.keyToggle).toBeCalledWith(
+ KeyboardAction.keyLookup(Key.A),
+ "down",
+ [KeyboardAction.keyLookup(Key.LeftControl)]
+ );
});
it("should not forward the pressKey call to libnut for an unknown key", async () => {
@@ -135,7 +142,11 @@ describe("libnut keyboard action", () => {
// THEN
expect(libnut.keyToggle).toBeCalledTimes(1);
- expect(libnut.keyToggle).toBeCalledWith(KeyboardAction.keyLookup(Key.A), "up", []);
+ expect(libnut.keyToggle).toBeCalledWith(
+ KeyboardAction.keyLookup(Key.A),
+ "up",
+ []
+ );
});
it("should treat a list of keys as modifiers + the actual key to release", async () => {
@@ -147,8 +158,11 @@ describe("libnut keyboard action", () => {
// THEN
expect(libnut.keyToggle).toBeCalledTimes(1);
- expect(libnut.keyToggle)
- .toBeCalledWith(KeyboardAction.keyLookup(Key.A), "up", [KeyboardAction.keyLookup(Key.LeftControl)]);
+ expect(libnut.keyToggle).toBeCalledWith(
+ KeyboardAction.keyLookup(Key.A),
+ "up",
+ [KeyboardAction.keyLookup(Key.LeftControl)]
+ );
});
it("should not forward the releaseKey call to libnut for an unknown key", async () => {
diff --git a/lib/provider/native/libnut-keyboard.class.ts b/lib/provider/native/libnut-keyboard.class.ts
index 920d2a9b..e16e1790 100644
--- a/lib/provider/native/libnut-keyboard.class.ts
+++ b/lib/provider/native/libnut-keyboard.class.ts
@@ -1,232 +1,234 @@
import libnut = require("@nut-tree/libnut");
-import {Key} from "../../key.enum";
-import {KeyboardProviderInterface} from "../keyboard-provider.interface";
+import { Key } from "../../key.enum";
+import { KeyboardProviderInterface } from "../keyboard-provider.interface";
export default class KeyboardAction implements KeyboardProviderInterface {
-
- public static KeyLookupMap = new Map([
- [Key.A, "a"],
- [Key.B, "b"],
- [Key.C, "c"],
- [Key.D, "d"],
- [Key.E, "e"],
- [Key.F, "f"],
- [Key.G, "g"],
- [Key.H, "h"],
- [Key.I, "i"],
- [Key.J, "j"],
- [Key.K, "k"],
- [Key.L, "l"],
- [Key.M, "m"],
- [Key.N, "n"],
- [Key.O, "o"],
- [Key.P, "p"],
- [Key.Q, "q"],
- [Key.R, "r"],
- [Key.S, "s"],
- [Key.T, "t"],
- [Key.U, "u"],
- [Key.V, "v"],
- [Key.W, "w"],
- [Key.X, "x"],
- [Key.Y, "y"],
- [Key.Z, "z"],
-
- [Key.F1, "f1"],
- [Key.F2, "f2"],
- [Key.F3, "f3"],
- [Key.F4, "f4"],
- [Key.F5, "f5"],
- [Key.F6, "f6"],
- [Key.F7, "f7"],
- [Key.F8, "f8"],
- [Key.F9, "f9"],
- [Key.F10, "f10"],
- [Key.F11, "f11"],
- [Key.F12, "f12"],
- [Key.F13, "f13"],
- [Key.F14, "f14"],
- [Key.F15, "f15"],
- [Key.F16, "f16"],
- [Key.F17, "f17"],
- [Key.F18, "f18"],
- [Key.F19, "f19"],
- [Key.F20, "f20"],
- [Key.F21, "f21"],
- [Key.F22, "f22"],
- [Key.F23, "f23"],
- [Key.F24, "f24"],
-
- [Key.Num0, "0"],
- [Key.Num1, "1"],
- [Key.Num2, "2"],
- [Key.Num3, "3"],
- [Key.Num4, "4"],
- [Key.Num5, "5"],
- [Key.Num6, "6"],
- [Key.Num7, "7"],
- [Key.Num8, "8"],
- [Key.Num9, "9"],
- [Key.NumPad0, "numpad_0"],
- [Key.NumPad1, "numpad_1"],
- [Key.NumPad2, "numpad_2"],
- [Key.NumPad3, "numpad_3"],
- [Key.NumPad4, "numpad_4"],
- [Key.NumPad5, "numpad_5"],
- [Key.NumPad6, "numpad_6"],
- [Key.NumPad7, "numpad_7"],
- [Key.NumPad8, "numpad_8"],
- [Key.NumPad9, "numpad_9"],
- [Key.Decimal, "numpad_decimal"],
-
- [Key.Space, "space"],
- [Key.Escape, "escape"],
- [Key.Tab, "tab"],
- [Key.LeftAlt, "alt"],
- [Key.LeftControl, "control"],
- [Key.RightAlt, "alt"],
- [Key.RightControl, "control"],
-
- [Key.LeftShift, "shift"],
- [Key.LeftSuper, "command"],
- [Key.RightShift, "space"],
- [Key.RightSuper, "command"],
-
- [Key.Grave, "`"],
- [Key.Minus, "-"],
- [Key.Equal, "="],
- [Key.Backspace, "backspace"],
- [Key.LeftBracket, "["],
- [Key.RightBracket, "]"],
- [Key.Backslash, "\\"],
- [Key.Semicolon, ";"],
- [Key.Quote, "'"],
- [Key.Return, "enter"],
- [Key.Comma, ","],
- [Key.Period, "."],
- [Key.Slash, "/"],
-
- [Key.Left, "left"],
- [Key.Up, "up"],
- [Key.Right, "right"],
- [Key.Down, "down"],
-
- [Key.Print, "printscreen"],
- [Key.Pause, null],
- [Key.Insert, "insert"],
- [Key.Delete, "delete"],
- [Key.Home, "home"],
- [Key.End, "end"],
- [Key.PageUp, "pageup"],
- [Key.PageDown, "pagedown"],
-
- [Key.Add, "add"],
- [Key.Subtract, "subtract"],
- [Key.Multiply, "multiply"],
- [Key.Divide, "divide"],
- [Key.Enter, "enter"],
-
- [Key.CapsLock, "caps_lock"],
- [Key.ScrollLock, "scroll_lock"],
- [Key.NumLock, "num_lock"],
-
- [Key.AudioMute, "audio_mute"],
- [Key.AudioVolDown, "audio_vol_down"],
- [Key.AudioVolUp, "audio_vol_up"],
- [Key.AudioPlay, "audio_play"],
- [Key.AudioStop, "audio_stop"],
- [Key.AudioPause, "audio_pause"],
- [Key.AudioPrev, "audio_prev"],
- [Key.AudioNext, "audio_next"],
- [Key.AudioRewind, "audio_rewind"],
- [Key.AudioForward, "audio_forward"],
- [Key.AudioRepeat, "audio_repeat"],
- [Key.AudioRandom, "audio_random"]
- ]);
-
- public static keyLookup(key: Key): any {
- return this.KeyLookupMap.get(key);
- }
-
- private static mapModifierKeys(...keys: Key[]): string[] {
- return keys
- .map(modifier => KeyboardAction.keyLookup(modifier))
- .filter(modifierKey => modifierKey != null && modifierKey.length > 1);
- }
-
- private static key(key: Key, event: "up" | "down", ...modifiers: Key[]): Promise {
- return new Promise((resolve, reject) => {
- try {
- const nativeKey = KeyboardAction.keyLookup(key);
- const modifierKeys = this.mapModifierKeys(...modifiers);
- if (nativeKey) {
- libnut.keyToggle(nativeKey, event, modifierKeys);
- }
- resolve();
- } catch (e) {
- reject(e);
- }
- });
- }
-
- constructor() {
- }
-
- public type(input: string): Promise {
- return new Promise((resolve, reject) => {
- try {
- libnut.typeString(input);
- resolve();
- } catch (e) {
- reject(e);
- }
- });
- }
-
- public click(...keys: Key[]): Promise {
- return new Promise((resolve, reject) => {
- try {
- keys.reverse();
- const [key, ...modifiers] = keys;
- const nativeKey = KeyboardAction.keyLookup(key);
- const modifierKeys = KeyboardAction.mapModifierKeys(...modifiers);
- if (nativeKey) {
- libnut.keyTap(nativeKey, modifierKeys);
- }
- resolve();
- } catch (e) {
- reject(e);
- }
- });
- }
-
- public pressKey(...keys: Key[]): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- keys.reverse();
- const [key, ...modifiers] = keys;
- await KeyboardAction.key(key, "down", ...modifiers);
- resolve();
- } catch (e) {
- reject(e);
- }
- });
- }
-
- public releaseKey(...keys: Key[]): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- keys.reverse();
- const [key, ...modifiers] = keys;
- await KeyboardAction.key(key, "up", ...modifiers);
- resolve();
- } catch (e) {
- reject(e);
- }
- });
- }
-
- public setKeyboardDelay(delay: number): void {
- libnut.setKeyboardDelay(delay);
- }
+ public static KeyLookupMap = new Map([
+ [Key.A, "a"],
+ [Key.B, "b"],
+ [Key.C, "c"],
+ [Key.D, "d"],
+ [Key.E, "e"],
+ [Key.F, "f"],
+ [Key.G, "g"],
+ [Key.H, "h"],
+ [Key.I, "i"],
+ [Key.J, "j"],
+ [Key.K, "k"],
+ [Key.L, "l"],
+ [Key.M, "m"],
+ [Key.N, "n"],
+ [Key.O, "o"],
+ [Key.P, "p"],
+ [Key.Q, "q"],
+ [Key.R, "r"],
+ [Key.S, "s"],
+ [Key.T, "t"],
+ [Key.U, "u"],
+ [Key.V, "v"],
+ [Key.W, "w"],
+ [Key.X, "x"],
+ [Key.Y, "y"],
+ [Key.Z, "z"],
+
+ [Key.F1, "f1"],
+ [Key.F2, "f2"],
+ [Key.F3, "f3"],
+ [Key.F4, "f4"],
+ [Key.F5, "f5"],
+ [Key.F6, "f6"],
+ [Key.F7, "f7"],
+ [Key.F8, "f8"],
+ [Key.F9, "f9"],
+ [Key.F10, "f10"],
+ [Key.F11, "f11"],
+ [Key.F12, "f12"],
+ [Key.F13, "f13"],
+ [Key.F14, "f14"],
+ [Key.F15, "f15"],
+ [Key.F16, "f16"],
+ [Key.F17, "f17"],
+ [Key.F18, "f18"],
+ [Key.F19, "f19"],
+ [Key.F20, "f20"],
+ [Key.F21, "f21"],
+ [Key.F22, "f22"],
+ [Key.F23, "f23"],
+ [Key.F24, "f24"],
+
+ [Key.Num0, "0"],
+ [Key.Num1, "1"],
+ [Key.Num2, "2"],
+ [Key.Num3, "3"],
+ [Key.Num4, "4"],
+ [Key.Num5, "5"],
+ [Key.Num6, "6"],
+ [Key.Num7, "7"],
+ [Key.Num8, "8"],
+ [Key.Num9, "9"],
+ [Key.NumPad0, "numpad_0"],
+ [Key.NumPad1, "numpad_1"],
+ [Key.NumPad2, "numpad_2"],
+ [Key.NumPad3, "numpad_3"],
+ [Key.NumPad4, "numpad_4"],
+ [Key.NumPad5, "numpad_5"],
+ [Key.NumPad6, "numpad_6"],
+ [Key.NumPad7, "numpad_7"],
+ [Key.NumPad8, "numpad_8"],
+ [Key.NumPad9, "numpad_9"],
+ [Key.Decimal, "numpad_decimal"],
+
+ [Key.Space, "space"],
+ [Key.Escape, "escape"],
+ [Key.Tab, "tab"],
+ [Key.LeftAlt, "alt"],
+ [Key.LeftControl, "control"],
+ [Key.RightAlt, "alt"],
+ [Key.RightControl, "control"],
+
+ [Key.LeftShift, "shift"],
+ [Key.LeftSuper, "command"],
+ [Key.RightShift, "space"],
+ [Key.RightSuper, "command"],
+
+ [Key.Grave, "`"],
+ [Key.Minus, "-"],
+ [Key.Equal, "="],
+ [Key.Backspace, "backspace"],
+ [Key.LeftBracket, "["],
+ [Key.RightBracket, "]"],
+ [Key.Backslash, "\\"],
+ [Key.Semicolon, ";"],
+ [Key.Quote, "'"],
+ [Key.Return, "enter"],
+ [Key.Comma, ","],
+ [Key.Period, "."],
+ [Key.Slash, "/"],
+
+ [Key.Left, "left"],
+ [Key.Up, "up"],
+ [Key.Right, "right"],
+ [Key.Down, "down"],
+
+ [Key.Print, "printscreen"],
+ [Key.Pause, null],
+ [Key.Insert, "insert"],
+ [Key.Delete, "delete"],
+ [Key.Home, "home"],
+ [Key.End, "end"],
+ [Key.PageUp, "pageup"],
+ [Key.PageDown, "pagedown"],
+
+ [Key.Add, "add"],
+ [Key.Subtract, "subtract"],
+ [Key.Multiply, "multiply"],
+ [Key.Divide, "divide"],
+ [Key.Enter, "enter"],
+
+ [Key.CapsLock, "caps_lock"],
+ [Key.ScrollLock, "scroll_lock"],
+ [Key.NumLock, "num_lock"],
+
+ [Key.AudioMute, "audio_mute"],
+ [Key.AudioVolDown, "audio_vol_down"],
+ [Key.AudioVolUp, "audio_vol_up"],
+ [Key.AudioPlay, "audio_play"],
+ [Key.AudioStop, "audio_stop"],
+ [Key.AudioPause, "audio_pause"],
+ [Key.AudioPrev, "audio_prev"],
+ [Key.AudioNext, "audio_next"],
+ [Key.AudioRewind, "audio_rewind"],
+ [Key.AudioForward, "audio_forward"],
+ [Key.AudioRepeat, "audio_repeat"],
+ [Key.AudioRandom, "audio_random"],
+ ]);
+
+ public static keyLookup(key: Key): any {
+ return this.KeyLookupMap.get(key);
+ }
+
+ private static mapModifierKeys(...keys: Key[]): string[] {
+ return keys
+ .map((modifier) => KeyboardAction.keyLookup(modifier))
+ .filter((modifierKey) => modifierKey != null && modifierKey.length > 1);
+ }
+
+ private static key(
+ key: Key,
+ event: "up" | "down",
+ ...modifiers: Key[]
+ ): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ const nativeKey = KeyboardAction.keyLookup(key);
+ const modifierKeys = this.mapModifierKeys(...modifiers);
+ if (nativeKey) {
+ libnut.keyToggle(nativeKey, event, modifierKeys);
+ }
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
+
+ constructor() {}
+
+ public type(input: string): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ libnut.typeString(input);
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
+
+ public click(...keys: Key[]): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ keys.reverse();
+ const [key, ...modifiers] = keys;
+ const nativeKey = KeyboardAction.keyLookup(key);
+ const modifierKeys = KeyboardAction.mapModifierKeys(...modifiers);
+ if (nativeKey) {
+ libnut.keyTap(nativeKey, modifierKeys);
+ }
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
+
+ public pressKey(...keys: Key[]): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ keys.reverse();
+ const [key, ...modifiers] = keys;
+ await KeyboardAction.key(key, "down", ...modifiers);
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
+
+ public releaseKey(...keys: Key[]): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ keys.reverse();
+ const [key, ...modifiers] = keys;
+ await KeyboardAction.key(key, "up", ...modifiers);
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
+
+ public setKeyboardDelay(delay: number): void {
+ libnut.setKeyboardDelay(delay);
+ }
}
diff --git a/lib/provider/native/libnut-mouse.class.spec.ts b/lib/provider/native/libnut-mouse.class.spec.ts
index cd74c31e..e7f445d6 100644
--- a/lib/provider/native/libnut-mouse.class.spec.ts
+++ b/lib/provider/native/libnut-mouse.class.spec.ts
@@ -269,7 +269,7 @@ describe("libnut mouse action", () => {
describe("currentMousePosition", () => {
it("should return the current mouse position via libnut", async () => {
// GIVEN
- libnut.getMousePos = jest.fn(() => ({x: 10, y: 100}));
+ libnut.getMousePos = jest.fn(() => ({ x: 10, y: 100 }));
const SUT = new MouseAction();
// WHEN
@@ -319,7 +319,9 @@ describe("libnut mouse action", () => {
// WHEN
// THEN
- expect(SUT.setMousePosition(new Point(100, 100))).rejects.toThrowError(error);
+ expect(SUT.setMousePosition(new Point(100, 100))).rejects.toThrowError(
+ error
+ );
});
});
});
diff --git a/lib/provider/native/libnut-mouse.class.ts b/lib/provider/native/libnut-mouse.class.ts
index 11ff2027..f5edf945 100644
--- a/lib/provider/native/libnut-mouse.class.ts
+++ b/lib/provider/native/libnut-mouse.class.ts
@@ -1,7 +1,7 @@
import libnut = require("@nut-tree/libnut");
-import {Button} from "../../button.enum";
-import {Point} from "../../point.class";
-import {MouseProviderInterface} from "../mouse-provider.interface";
+import { Button } from "../../button.enum";
+import { Point } from "../../point.class";
+import { MouseProviderInterface } from "../mouse-provider.interface";
export default class MouseAction implements MouseProviderInterface {
public static buttonLookup(btn: Button): any {
@@ -9,58 +9,61 @@ export default class MouseAction implements MouseProviderInterface {
}
private static ButtonLookupMap: Map