mirror of
https://github.com/Mintplex-Labs/nut.js.git
synced 2026-07-19 22:15:20 -04:00
Merge branch 'develop'
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 3.1.2
|
||||
|
||||
- Bugfix: Screen capture broken on macOS 13 [(#469)](https://github.com/nut-tree/nut.js/issues/469)
|
||||
- Enhancement: Enable newly introduced keys to be used as modifiers [(#490)](https://github.com/nut-tree/nut.js/issues/490)
|
||||
- Enhancement: Extend move API to handle single point case [(#499)](https://github.com/nut-tree/nut.js/issues/499)
|
||||
- Feature: Add color queries to search for pixels of a certain color [(#500)](https://github.com/nut-tree/nut.js/issues/500)
|
||||
- Bugfix: screen.highlight closes Electron window [(#505)](https://github.com/nut-tree/nut.js/issues/505)
|
||||
|
||||
## 3.1.1
|
||||
|
||||
- Bugfix: Fix mouse drift on Windows [(nut-tree/libnut-core#126)](https://github.com/nut-tree/libnut-core/issues/126)
|
||||
|
||||
+44
-52
@@ -49,32 +49,30 @@ export class KeyboardClass {
|
||||
*
|
||||
* @param input Sequence of {@link String} or {@link Key} to type
|
||||
*/
|
||||
public type(...input: StringOrKey): Promise<KeyboardClass> {
|
||||
return new Promise<KeyboardClass>(async (resolve, reject) => {
|
||||
try {
|
||||
if (inputIsString(input)) {
|
||||
for (const char of input.join(" ")) {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getKeyboard().type(char);
|
||||
this.providerRegistry.getLogProvider().debug(`Tapped ${char}`);
|
||||
}
|
||||
this.providerRegistry.getLogProvider().info(`Typed string ${input}`);
|
||||
} else {
|
||||
await this.providerRegistry.getKeyboard().click(...(input as Key[]));
|
||||
const key = input[input.length - 1];
|
||||
const modifiers = input.slice(0, -1);
|
||||
const keyName = Key[key];
|
||||
const modifierNames = modifiers.map((modifier) => Key[modifier]);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Tapped key ${keyName} with modifiers ${modifierNames}`);
|
||||
public async type(...input: StringOrKey): Promise<KeyboardClass> {
|
||||
try {
|
||||
if (inputIsString(input)) {
|
||||
for (const char of input.join(" ")) {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getKeyboard().type(char);
|
||||
this.providerRegistry.getLogProvider().debug(`Tapped ${char}`);
|
||||
}
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
this.providerRegistry.getLogProvider().info(`Typed string ${input}`);
|
||||
} else {
|
||||
await this.providerRegistry.getKeyboard().click(...(input as Key[]));
|
||||
const key = input[input.length - 1];
|
||||
const modifiers = input.slice(0, -1);
|
||||
const keyName = Key[key];
|
||||
const modifierNames = modifiers.map((modifier) => Key[modifier]);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Tapped key ${keyName} with modifiers ${modifierNames}`);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,19 +86,17 @@ export class KeyboardClass {
|
||||
*
|
||||
* @param keys Array of {@link Key}s to press and hold
|
||||
*/
|
||||
public pressKey(...keys: Key[]): Promise<KeyboardClass> {
|
||||
return new Promise<KeyboardClass>(async (resolve, reject) => {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getKeyboard().pressKey(...keys);
|
||||
const keyNames = keys.map((key) => Key[key]);
|
||||
this.providerRegistry.getLogProvider().info(`Pressed keys ${keyNames}`);
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
public async pressKey(...keys: Key[]): Promise<KeyboardClass> {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getKeyboard().pressKey(...keys);
|
||||
const keyNames = keys.map((key) => Key[key]);
|
||||
this.providerRegistry.getLogProvider().info(`Pressed keys ${keyNames}`);
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,20 +110,16 @@ export class KeyboardClass {
|
||||
*
|
||||
* @param keys Array of {@link Key}s to release
|
||||
*/
|
||||
public releaseKey(...keys: Key[]): Promise<KeyboardClass> {
|
||||
return new Promise<KeyboardClass>(async (resolve, reject) => {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getKeyboard().releaseKey(...keys);
|
||||
const keyNames = keys.map((key) => Key[key]);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Released keys ${keyNames}`);
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
public async releaseKey(...keys: Key[]): Promise<KeyboardClass> {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getKeyboard().releaseKey(...keys);
|
||||
const keyNames = keys.map((key) => Key[key]);
|
||||
this.providerRegistry.getLogProvider().info(`Released keys ${keyNames}`);
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+136
-162
@@ -55,15 +55,13 @@ export class MouseClass {
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.trace("Setting mouse position", target);
|
||||
return new Promise<MouseClass>(async (resolve, reject) => {
|
||||
try {
|
||||
await this.providerRegistry.getMouse().setMousePosition(target);
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await this.providerRegistry.getMouse().setMousePosition(target);
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,34 +86,32 @@ export class MouseClass {
|
||||
path: Point[] | Promise<Point[]>,
|
||||
movementType: EasingFunction = linear
|
||||
): Promise<MouseClass> {
|
||||
return new Promise<MouseClass>(async (resolve, reject) => {
|
||||
try {
|
||||
let pathSteps = await path;
|
||||
if (!Array.isArray(pathSteps)) {
|
||||
pathSteps = [pathSteps];
|
||||
}
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(
|
||||
`Moving mouse to target point ${pathSteps[pathSteps.length - 1]}`
|
||||
);
|
||||
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) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
try {
|
||||
let pathSteps = await path;
|
||||
if (!Array.isArray(pathSteps)) {
|
||||
pathSteps = [pathSteps];
|
||||
}
|
||||
});
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(
|
||||
`Moving mouse to target point ${pathSteps[pathSteps.length - 1]}`
|
||||
);
|
||||
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);
|
||||
}
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,19 +134,17 @@ export class MouseClass {
|
||||
* @param amount The amount of "steps" to scroll
|
||||
*/
|
||||
public async scrollDown(amount: number): Promise<MouseClass> {
|
||||
return new Promise<MouseClass>(async (resolve, reject) => {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().scrollDown(amount);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Scrolled down ${amount} steps`);
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().scrollDown(amount);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Scrolled down ${amount} steps`);
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,19 +153,17 @@ export class MouseClass {
|
||||
* @param amount The amount of "steps" to scroll
|
||||
*/
|
||||
public async scrollUp(amount: number): Promise<MouseClass> {
|
||||
return new Promise<MouseClass>(async (resolve, reject) => {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().scrollUp(amount);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Scrolled up ${amount} steps`);
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().scrollUp(amount);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Scrolled up ${amount} steps`);
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,19 +172,17 @@ export class MouseClass {
|
||||
* @param amount The amount of "steps" to scroll
|
||||
*/
|
||||
public async scrollLeft(amount: number): Promise<MouseClass> {
|
||||
return new Promise<MouseClass>(async (resolve, reject) => {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().scrollLeft(amount);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Scrolled left ${amount} steps`);
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().scrollLeft(amount);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Scrolled left ${amount} steps`);
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,19 +191,17 @@ export class MouseClass {
|
||||
* @param amount The amount of "steps" to scroll
|
||||
*/
|
||||
public async scrollRight(amount: number): Promise<MouseClass> {
|
||||
return new Promise<MouseClass>(async (resolve, reject) => {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().scrollRight(amount);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Scrolled right ${amount} steps`);
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().scrollRight(amount);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Scrolled right ${amount} steps`);
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,24 +210,18 @@ export class MouseClass {
|
||||
* @param path The path of {@link Point}s to drag along
|
||||
*/
|
||||
public async drag(path: Point[] | Promise<Point[]>): Promise<MouseClass> {
|
||||
return new Promise<MouseClass>(async (resolve, reject) => {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().pressButton(Button.LEFT);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info("Pressed left mouse button");
|
||||
await this.move(path);
|
||||
await this.providerRegistry.getMouse().releaseButton(Button.LEFT);
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info("Released left mouse button");
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().pressButton(Button.LEFT);
|
||||
this.providerRegistry.getLogProvider().info("Pressed left mouse button");
|
||||
await this.move(path);
|
||||
await this.providerRegistry.getMouse().releaseButton(Button.LEFT);
|
||||
this.providerRegistry.getLogProvider().info("Released left mouse button");
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,20 +229,18 @@ export class MouseClass {
|
||||
* @param btn The {@link Button} to press and hold
|
||||
*/
|
||||
public async pressButton(btn: Button): Promise<MouseClass> {
|
||||
return new Promise<MouseClass>(async (resolve, reject) => {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().pressButton(btn);
|
||||
const btnName = Button[btn];
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Pressed ${btnName} mouse button`);
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().pressButton(btn);
|
||||
const btnName = Button[btn];
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Pressed ${btnName} mouse button`);
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,20 +248,18 @@ export class MouseClass {
|
||||
* @param btn The {@link Button} to release
|
||||
*/
|
||||
public async releaseButton(btn: Button): Promise<MouseClass> {
|
||||
return new Promise<MouseClass>(async (resolve, reject) => {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().releaseButton(btn);
|
||||
const btnName = Button[btn];
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Pressed ${btnName} mouse button`);
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().releaseButton(btn);
|
||||
const btnName = Button[btn];
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Pressed ${btnName} mouse button`);
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,20 +267,18 @@ export class MouseClass {
|
||||
* @param btn The {@link Button} to click
|
||||
*/
|
||||
public async click(btn: Button): Promise<MouseClass> {
|
||||
return new Promise<MouseClass>(async (resolve, reject) => {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().click(btn);
|
||||
const btnName = Button[btn];
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Pressed ${btnName} mouse button`);
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().click(btn);
|
||||
const btnName = Button[btn];
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Pressed ${btnName} mouse button`);
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -310,19 +286,17 @@ export class MouseClass {
|
||||
* @param btn The {@link Button} to click
|
||||
*/
|
||||
public async doubleClick(btn: Button): Promise<MouseClass> {
|
||||
return new Promise<MouseClass>(async (resolve, reject) => {
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().doubleClick(btn);
|
||||
const btnName = Button[btn];
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Pressed ${btnName} mouse button`);
|
||||
resolve(this);
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await sleep(this.config.autoDelayMs);
|
||||
await this.providerRegistry.getMouse().doubleClick(btn);
|
||||
const btnName = Button[btn];
|
||||
this.providerRegistry
|
||||
.getLogProvider()
|
||||
.info(`Pressed ${btnName} mouse button`);
|
||||
return this;
|
||||
} catch (e) {
|
||||
this.providerRegistry.getLogProvider().error(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ describe("JimpImageProcessor", () => {
|
||||
const result = SUT.colorAt(inputImage, outOfBoundsPoint);
|
||||
|
||||
// THEN
|
||||
await expect(result).rejects.toBe(
|
||||
await expect(result).rejects.toThrowError(
|
||||
`Query location out of bounds. Should be in range 0 <= x < image.width, is ${outOfBoundsPoint.x}`
|
||||
);
|
||||
}
|
||||
@@ -52,7 +52,7 @@ describe("JimpImageProcessor", () => {
|
||||
const result = SUT.colorAt(inputImage, outOfBoundsPoint);
|
||||
|
||||
// THEN
|
||||
await expect(result).rejects.toBe(
|
||||
await expect(result).rejects.toThrowError(
|
||||
`Query location out of bounds. Should be in range 0 <= y < image.height, is ${outOfBoundsPoint.y}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,30 +6,26 @@ import { imageToJimp } from "../io/imageToJimp.function";
|
||||
import { RGBA } from "../../rgba.class";
|
||||
|
||||
export default class implements ImageProcessor {
|
||||
colorAt(
|
||||
async colorAt(
|
||||
image: Image | Promise<Image>,
|
||||
point: Point | Promise<Point>
|
||||
): Promise<RGBA> {
|
||||
return new Promise<RGBA>(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}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
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}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const jimpImage = imageToJimp(img);
|
||||
const rgba = Jimp.intToRGBA(
|
||||
jimpImage.getPixelColor(location.x, location.y)
|
||||
const location = await point;
|
||||
const img = await image;
|
||||
if (location.x < 0 || location.x >= img.width) {
|
||||
throw Error(
|
||||
`Query location out of bounds. Should be in range 0 <= x < image.width, is ${location.x}`
|
||||
);
|
||||
resolve(new RGBA(rgba.r, rgba.g, rgba.b, rgba.a));
|
||||
});
|
||||
}
|
||||
if (location.y < 0 || location.y >= img.height) {
|
||||
throw Error(
|
||||
`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)
|
||||
);
|
||||
return new RGBA(rgba.r, rgba.g, rgba.b, rgba.a);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ jest.mock("jimp", () => {
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
describe("Jimp image writer", () => {
|
||||
it("should reject on writing failures", async () => {
|
||||
it("should resolve on writing", async () => {
|
||||
// GIVEN
|
||||
const outputFileName = "/does/not/compute.png";
|
||||
const outputFile = new Image(
|
||||
@@ -48,4 +48,28 @@ describe("Jimp image writer", () => {
|
||||
expect(writeMock).toHaveBeenCalledTimes(1);
|
||||
expect(writeMock).toHaveBeenCalledWith(outputFileName);
|
||||
});
|
||||
|
||||
it("should reject on writing failures", () => {
|
||||
// GIVEN
|
||||
const outputFileName = "/does/not/compute.png";
|
||||
const outputFile = new Image(
|
||||
100,
|
||||
200,
|
||||
Buffer.from([]),
|
||||
3,
|
||||
outputFileName,
|
||||
4,
|
||||
100 * 4
|
||||
);
|
||||
const writeMock = jest.fn(() => Promise.reject("write error"));
|
||||
Jimp.prototype.scan = jest.fn();
|
||||
Jimp.prototype.writeAsync = writeMock;
|
||||
const SUT = new ImageWriter();
|
||||
|
||||
// WHEN
|
||||
const result = SUT.store({ image: outputFile, path: outputFileName });
|
||||
|
||||
// THEN
|
||||
expect(result).rejects.toBe("write error");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ImageFinderInterface,
|
||||
ImageWriter,
|
||||
ImageWriterParameters,
|
||||
LogProviderInterface,
|
||||
ScreenProviderInterface,
|
||||
} from "./provider";
|
||||
import { OptionalSearchParameters } from "./optionalsearchparameters.class";
|
||||
@@ -25,8 +26,17 @@ import { Point } from "./point.class";
|
||||
jest.mock("jimp", () => {});
|
||||
|
||||
const searchRegion = new Region(0, 0, 1000, 1000);
|
||||
const loggingMock = mockPartial<LogProviderInterface>({
|
||||
debug: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
});
|
||||
|
||||
const providerRegistryMock = mockPartial<ProviderRegistry>({
|
||||
getLogProvider(): LogProviderInterface {
|
||||
return loggingMock;
|
||||
},
|
||||
getScreen(): ScreenProviderInterface {
|
||||
return mockPartial<ScreenProviderInterface>({
|
||||
grabScreenRegion(): Promise<Image> {
|
||||
@@ -45,6 +55,12 @@ const providerRegistryMock = mockPartial<ProviderRegistry>({
|
||||
screenSize(): Promise<Region> {
|
||||
return Promise.resolve(searchRegion);
|
||||
},
|
||||
screenWidth(): Promise<number> {
|
||||
return Promise.resolve(searchRegion.width);
|
||||
},
|
||||
screenHeight(): Promise<number> {
|
||||
return Promise.resolve(searchRegion.height);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -54,6 +70,32 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe("Screen.", () => {
|
||||
describe("dimensions", () => {
|
||||
it("should return the screen width", async () => {
|
||||
// GIVEN
|
||||
const SUT = new ScreenClass(providerRegistryMock);
|
||||
|
||||
// WHEN
|
||||
const width = await SUT.width();
|
||||
|
||||
// THEN
|
||||
expect(width).toEqual(searchRegion.width);
|
||||
expect(loggingMock.debug).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should return the screen height", async () => {
|
||||
// GIVEN
|
||||
const SUT = new ScreenClass(providerRegistryMock);
|
||||
|
||||
// WHEN
|
||||
const height = await SUT.height();
|
||||
|
||||
// THEN
|
||||
expect(height).toEqual(searchRegion.height);
|
||||
expect(loggingMock.debug).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("find", () => {
|
||||
describe("queries", () => {
|
||||
it("should choose the correct finder implementation for images", async () => {
|
||||
@@ -639,16 +681,16 @@ describe("Screen.", () => {
|
||||
},
|
||||
};
|
||||
|
||||
const findMatchMock = jest.fn(() => Promise.resolve(matchResult));
|
||||
const findMatchMock = jest.fn(() => Promise.resolve([matchResult]));
|
||||
providerRegistryMock.getColorFinder = jest.fn(() =>
|
||||
mockPartial<ColorFinderInterface>({
|
||||
findMatch: findMatchMock,
|
||||
findMatches: findMatchMock,
|
||||
})
|
||||
);
|
||||
providerRegistryMock.getLogProvider = () => new NoopLogProvider();
|
||||
|
||||
// WHEN
|
||||
await SUT.find(needle);
|
||||
await SUT.findAll(needle);
|
||||
|
||||
// THEN
|
||||
expect(findMatchMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
+3
-1
@@ -425,7 +425,9 @@ export class ScreenClass {
|
||||
.debug(`Autohighlight is enabled`);
|
||||
resultRegions.forEach((region) => {
|
||||
if (isRegion(region)) {
|
||||
this.highlight(region);
|
||||
this.highlight(region).catch((e) => {
|
||||
this.providerRegistry.getLogProvider().error(e);
|
||||
});
|
||||
}
|
||||
});
|
||||
return resultRegions;
|
||||
|
||||
Generated
+52
-52
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@nut-tree/nut-js",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@nut-tree/nut-js",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"cpu": [
|
||||
"x64",
|
||||
"arm64"
|
||||
@@ -18,9 +18,9 @@
|
||||
"win32"
|
||||
],
|
||||
"dependencies": {
|
||||
"@nut-tree/libnut": "2.5.1",
|
||||
"@nut-tree/libnut": "2.5.2",
|
||||
"clipboardy": "2.3.0",
|
||||
"jimp": "0.16.2",
|
||||
"jimp": "0.16.13",
|
||||
"node-abort-controller": "2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -1898,22 +1898,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@nut-tree/libnut": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut/-/libnut-2.5.1.tgz",
|
||||
"integrity": "sha512-W41FRAuA2fT7Y44+9A59NWzYhNA6Qibuvxgx9Sf5bQ5QrLIAyKGWjRpW/doUXaTAwUOvcqg7TC2w7jrn9nwkAA==",
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut/-/libnut-2.5.2.tgz",
|
||||
"integrity": "sha512-IwlGgalGQjjCrbPq52HTLtsR6MRsGJ6c4GhgHmgwJPD/yBVCzsU5EA2rwDgjKSgK+jiLOpykySDZQ+owhyd1zw==",
|
||||
"dependencies": {
|
||||
"@nut-tree/libnut-darwin": "2.5.1",
|
||||
"@nut-tree/libnut-linux": "2.5.1",
|
||||
"@nut-tree/libnut-win32": "2.5.1"
|
||||
"@nut-tree/libnut-darwin": "2.5.2",
|
||||
"@nut-tree/libnut-linux": "2.5.2",
|
||||
"@nut-tree/libnut-win32": "2.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.15.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@nut-tree/libnut-darwin": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut-darwin/-/libnut-darwin-2.5.1.tgz",
|
||||
"integrity": "sha512-JIr5Plmm9n5aoOAmwjghCtbmOY4FzOTypRN/mlb5N+68aX+w5RpgOV2HXmcQPbtqYTTOoSEGRBdzK8M36KHRsw==",
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut-darwin/-/libnut-darwin-2.5.2.tgz",
|
||||
"integrity": "sha512-OKqCgpBbZj2oNQkLM+mM7TyiLBAQsRyN7Q5UXVKbyXnzcQTU1FZBJ1mLGvdfzzspajznL3Mkl08AQE+xYYbTMw==",
|
||||
"cpu": [
|
||||
"x64",
|
||||
"arm64"
|
||||
@@ -1934,9 +1934,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@nut-tree/libnut-linux": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut-linux/-/libnut-linux-2.5.1.tgz",
|
||||
"integrity": "sha512-2rXZsOpx5U6cJE1zGfrDa6fGaAr1cQFJInsmpV55vg93/k0X5RY6iXLNbBXot/NBJKrTcQRIHeKpffLRk8fKPg==",
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut-linux/-/libnut-linux-2.5.2.tgz",
|
||||
"integrity": "sha512-uY6XDuNE8soYRUhV23g6KQN5BRR43MYNLyPnrrQa5Wtfgm6oW9kBRu6JtkWz6Fn6trAWrVAKnbPoTiEgHuGz9w==",
|
||||
"cpu": [
|
||||
"x64",
|
||||
"arm64"
|
||||
@@ -1957,9 +1957,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@nut-tree/libnut-win32": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut-win32/-/libnut-win32-2.5.1.tgz",
|
||||
"integrity": "sha512-Y95nMNM6WeioPrfzsGjvzUxtIQGYXfooFjSEUaGOA3pxP6bQlmfhVPYZrxwYtFr7mDoxHE9/PrDg1qiYtYhZkw==",
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut-win32/-/libnut-win32-2.5.2.tgz",
|
||||
"integrity": "sha512-cnNAQ0pzUMH7hrSjTWRiFfXid3sbSZ78jpv1rA154dlka3wqo6m1OtaX4PLVQK4irZl5REz74iPdHcjeTy9rtg==",
|
||||
"cpu": [
|
||||
"x64",
|
||||
"arm64"
|
||||
@@ -5783,14 +5783,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jimp": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.2.tgz",
|
||||
"integrity": "sha512-UpItBk81a92f8oEyoGYbO3YK4QcM0hoIyuGHmShoF9Ov63P5Qo7Q/X2xsAgnODmSuDJFOtrPtJd5GSWW4LKdOQ==",
|
||||
"version": "0.16.13",
|
||||
"resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.13.tgz",
|
||||
"integrity": "sha512-Bxz8q7V4rnCky9A0ktTNGA9SkNFVWRHodddI/DaAWZJzF7sVUlFYKQ60y9JGqrKpi48ECA/TnfMzzc5C70VByA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"@jimp/custom": "^0.16.2",
|
||||
"@jimp/plugins": "^0.16.2",
|
||||
"@jimp/types": "^0.16.2",
|
||||
"@jimp/custom": "^0.16.13",
|
||||
"@jimp/plugins": "^0.16.13",
|
||||
"@jimp/types": "^0.16.13",
|
||||
"regenerator-runtime": "^0.13.3"
|
||||
}
|
||||
},
|
||||
@@ -8660,9 +8660,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
|
||||
"integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==",
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
|
||||
"integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
@@ -10197,37 +10197,37 @@
|
||||
}
|
||||
},
|
||||
"@nut-tree/libnut": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut/-/libnut-2.5.1.tgz",
|
||||
"integrity": "sha512-W41FRAuA2fT7Y44+9A59NWzYhNA6Qibuvxgx9Sf5bQ5QrLIAyKGWjRpW/doUXaTAwUOvcqg7TC2w7jrn9nwkAA==",
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut/-/libnut-2.5.2.tgz",
|
||||
"integrity": "sha512-IwlGgalGQjjCrbPq52HTLtsR6MRsGJ6c4GhgHmgwJPD/yBVCzsU5EA2rwDgjKSgK+jiLOpykySDZQ+owhyd1zw==",
|
||||
"requires": {
|
||||
"@nut-tree/libnut-darwin": "2.5.1",
|
||||
"@nut-tree/libnut-linux": "2.5.1",
|
||||
"@nut-tree/libnut-win32": "2.5.1"
|
||||
"@nut-tree/libnut-darwin": "2.5.2",
|
||||
"@nut-tree/libnut-linux": "2.5.2",
|
||||
"@nut-tree/libnut-win32": "2.5.2"
|
||||
}
|
||||
},
|
||||
"@nut-tree/libnut-darwin": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut-darwin/-/libnut-darwin-2.5.1.tgz",
|
||||
"integrity": "sha512-JIr5Plmm9n5aoOAmwjghCtbmOY4FzOTypRN/mlb5N+68aX+w5RpgOV2HXmcQPbtqYTTOoSEGRBdzK8M36KHRsw==",
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut-darwin/-/libnut-darwin-2.5.2.tgz",
|
||||
"integrity": "sha512-OKqCgpBbZj2oNQkLM+mM7TyiLBAQsRyN7Q5UXVKbyXnzcQTU1FZBJ1mLGvdfzzspajznL3Mkl08AQE+xYYbTMw==",
|
||||
"requires": {
|
||||
"@nut-tree/node-mac-permissions": "2.2.1",
|
||||
"bindings": "1.5.0"
|
||||
}
|
||||
},
|
||||
"@nut-tree/libnut-linux": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut-linux/-/libnut-linux-2.5.1.tgz",
|
||||
"integrity": "sha512-2rXZsOpx5U6cJE1zGfrDa6fGaAr1cQFJInsmpV55vg93/k0X5RY6iXLNbBXot/NBJKrTcQRIHeKpffLRk8fKPg==",
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut-linux/-/libnut-linux-2.5.2.tgz",
|
||||
"integrity": "sha512-uY6XDuNE8soYRUhV23g6KQN5BRR43MYNLyPnrrQa5Wtfgm6oW9kBRu6JtkWz6Fn6trAWrVAKnbPoTiEgHuGz9w==",
|
||||
"requires": {
|
||||
"@nut-tree/node-mac-permissions": "2.2.1",
|
||||
"bindings": "1.5.0"
|
||||
}
|
||||
},
|
||||
"@nut-tree/libnut-win32": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut-win32/-/libnut-win32-2.5.1.tgz",
|
||||
"integrity": "sha512-Y95nMNM6WeioPrfzsGjvzUxtIQGYXfooFjSEUaGOA3pxP6bQlmfhVPYZrxwYtFr7mDoxHE9/PrDg1qiYtYhZkw==",
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@nut-tree/libnut-win32/-/libnut-win32-2.5.2.tgz",
|
||||
"integrity": "sha512-cnNAQ0pzUMH7hrSjTWRiFfXid3sbSZ78jpv1rA154dlka3wqo6m1OtaX4PLVQK4irZl5REz74iPdHcjeTy9rtg==",
|
||||
"requires": {
|
||||
"@nut-tree/node-mac-permissions": "2.2.1",
|
||||
"bindings": "1.5.0"
|
||||
@@ -13142,14 +13142,14 @@
|
||||
}
|
||||
},
|
||||
"jimp": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.2.tgz",
|
||||
"integrity": "sha512-UpItBk81a92f8oEyoGYbO3YK4QcM0hoIyuGHmShoF9Ov63P5Qo7Q/X2xsAgnODmSuDJFOtrPtJd5GSWW4LKdOQ==",
|
||||
"version": "0.16.13",
|
||||
"resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.13.tgz",
|
||||
"integrity": "sha512-Bxz8q7V4rnCky9A0ktTNGA9SkNFVWRHodddI/DaAWZJzF7sVUlFYKQ60y9JGqrKpi48ECA/TnfMzzc5C70VByA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"@jimp/custom": "^0.16.2",
|
||||
"@jimp/plugins": "^0.16.2",
|
||||
"@jimp/types": "^0.16.2",
|
||||
"@jimp/custom": "^0.16.13",
|
||||
"@jimp/plugins": "^0.16.13",
|
||||
"@jimp/types": "^0.16.13",
|
||||
"regenerator-runtime": "^0.13.3"
|
||||
}
|
||||
},
|
||||
@@ -15275,9 +15275,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"yaml": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
|
||||
"integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==",
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
|
||||
"integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nut-tree/nut-js",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"license": "Apache-2.0",
|
||||
"main": "dist/index",
|
||||
"typings": "dist/index",
|
||||
@@ -62,9 +62,9 @@
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nut-tree/libnut": "2.5.1",
|
||||
"@nut-tree/libnut": "2.5.2",
|
||||
"clipboardy": "2.3.0",
|
||||
"jimp": "0.16.2",
|
||||
"jimp": "0.16.13",
|
||||
"node-abort-controller": "2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Reference in New Issue
Block a user