Skip to content
2 changes: 2 additions & 0 deletions e2e/plugin-test/keyboard.class.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { jestMatchers, Key, keyboard, screen } from "@nut-tree/nut-js";
import "@nut-tree/template-matcher";

jest.mock('jimp', () => {});

jest.setTimeout(30000);
expect.extend(jestMatchers);

Expand Down
5 changes: 5 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ const assert = new AssertClass(screen);
const {straightTo, up, down, left, right} = createMovementApi(nativeActions, lineHelper);
const {getWindows, getActiveWindow} = createWindowApi(nativeActions);

const loadImage = providerRegistry.getImageReader().load;
const saveImage = providerRegistry.getImageWriter().store;

export {
clipboard,
keyboard,
Expand All @@ -63,4 +66,6 @@ export {
right,
getWindows,
getActiveWindow,
loadImage,
saveImage
};
1 change: 1 addition & 0 deletions lib/adapter/native.adapter.class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jest.mock("../provider/native/clipboardy-clipboard.class");
jest.mock("../provider/native/libnut-mouse.class");
jest.mock("../provider/native/libnut-keyboard.class");
jest.mock("../provider/native/libnut-window.class");
jest.mock('jimp', () => {});

let clipboardMock: ClipboardAction;
let keyboardMock: KeyboardAction;
Expand Down
1 change: 1 addition & 0 deletions lib/adapter/vision.adapter.class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {VisionAdapter} from "./vision.adapter.class";
import providerRegistry from "../provider/provider-registry.class";
import {MatchResult} from "../match-result.class";

jest.mock('jimp', () => {});
jest.mock("../provider/native/libnut-screen.class");

const finderMock = {
Expand Down
1 change: 1 addition & 0 deletions lib/assert.class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {Region} from "./region.class";
import {ScreenClass} from "./screen.class";
import providerRegistry from "./provider/provider-registry.class";

jest.mock('jimp', () => {});
jest.mock("./adapter/native.adapter.class");
jest.mock("./adapter/vision.adapter.class");
jest.mock("./screen.class");
Expand Down
1 change: 1 addition & 0 deletions lib/clipboard.class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {NativeAdapter} from "./adapter/native.adapter.class";
import {ClipboardClass} from "./clipboard.class";
import providerRegistry from "./provider/provider-registry.class";

jest.mock('jimp', () => {});
jest.mock("./adapter/native.adapter.class");

beforeEach(() => {
Expand Down
2 changes: 2 additions & 0 deletions lib/expect/matchers/toBeAt.function.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { mouse } from "../../../index";
import { Point } from "../../point.class";
import { toBeAt } from "./toBeAt.function";

jest.mock('jimp', () => {});

const targetPoint = new Point(100, 100);

describe(".toBeAt", () => {
Expand Down
2 changes: 2 additions & 0 deletions lib/expect/matchers/toBeIn.function.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Point } from "../../point.class";
import { Region } from "../../region.class";
import { toBeIn } from "./toBeIn.function";

jest.mock('jimp', () => {});

const targetPoint = new Point(400, 400);

describe(".toBeIn", () => {
Expand Down
Binary file added lib/provider/io/__mocks__/calculator.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 59 additions & 0 deletions lib/provider/io/jimp-image-reader.class.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import ImageReader from "./jimp-image-reader.class";
import {join} from "path";
import Jimp from "jimp";

jest.mock('gifwrap', () => {});
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);
});

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));
}
});
});
25 changes: 25 additions & 0 deletions lib/provider/io/jimp-image-reader.class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Jimp from 'jimp';
import {ImageReader} from "../image-reader.type";
import {Image} from "../../image.class";

export default class implements ImageReader {
load(parameters: string): Promise<Image> {
return new Promise<Image>((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
));
}).catch(err => reject(`Failed to load image from '${parameters}'. Reason: ${err}`));
})
}
}
44 changes: 44 additions & 0 deletions lib/provider/io/jimp-image-writer.class.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import ImageWriter from "./jimp-image-writer.class";
import {Image} from "../../image.class";
import Jimp from "jimp";

jest.mock('gifwrap', () => {});
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 writer', () => {
it('should reject on writing failures', async () => {
// GIVEN
const outputFile = new Image(100, 200, Buffer.from([]), 3);
const outputFileName = "/does/not/compute.png"
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({data: outputFile, path: outputFileName});

// THEN
expect(scanMock).toHaveBeenCalledTimes(1)
expect(writeMock).toHaveBeenCalledTimes(1)
expect(writeMock).toHaveBeenCalledWith(outputFileName)
});
});
24 changes: 24 additions & 0 deletions lib/provider/io/jimp-image-writer.class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Jimp from 'jimp';
import {ImageWriter, ImageWriterParameters} from "../image-writer.type";

export default class implements ImageWriter {
store(parameters: ImageWriterParameters): Promise<void> {
return new Promise((resolve, reject) => {
const jimpImage = new Jimp({
data: parameters.data.data,
width: parameters.data.width,
height: parameters.data.height
});
// libnut returns 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;
});
jimpImage
.writeAsync(parameters.path)
.then(_ => resolve())
.catch(err => reject(err));
});
}
}
2 changes: 2 additions & 0 deletions lib/provider/native/clipboardy-clipboard.class.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import ClipboardAction from "./clipboardy-clipboard.class";

jest.mock('jimp', () => {});

beforeEach(() => {
jest.resetAllMocks();
});
Expand Down
5 changes: 5 additions & 0 deletions lib/provider/provider-registry.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import Window from "./native/libnut-window.class";
import {ImageReader} from "./image-reader.type";
import {ImageWriter} from "./image-writer.type";

import ImageReaderImpl from "./io/jimp-image-reader.class";
import ImageWriterImpl from "./io/jimp-image-writer.class";

export interface ProviderRegistry {
getClipboard(): ClipboardProviderInterface;
registerClipboardProvider(value: ClipboardProviderInterface): void;
Expand Down Expand Up @@ -143,5 +146,7 @@ providerRegistry.registerKeyboardProvider(new Keyboard());
providerRegistry.registerMouseProvider(new Mouse());
providerRegistry.registerScreenProvider(new Screen());
providerRegistry.registerWindowProvider(new Window());
providerRegistry.registerImageWriter(new ImageWriterImpl());
providerRegistry.registerImageReader(new ImageReaderImpl());

export default providerRegistry;
1 change: 1 addition & 0 deletions lib/screen.class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { mockPartial } from "sneer";
import { FileType } from "./file-type.enum";
import providerRegistry from "./provider/provider-registry.class";

jest.mock('jimp', () => {});
jest.mock("./adapter/native.adapter.class");
jest.mock("./adapter/vision.adapter.class");

Expand Down
2 changes: 2 additions & 0 deletions lib/sleep.function.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {busyWaitForNanoSeconds, sleep} from "./sleep.function";

const maxTimeDeltaInMs = 3;

jest.mock('jimp', () => {});

describe("sleep", () => {
it("should resolve after x ms", async () => {
// GIVEN
Expand Down
1 change: 1 addition & 0 deletions lib/window.class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Window} from "./window.class";
import {NativeAdapter} from "./adapter/native.adapter.class";
import providerRegistry from "./provider/provider-registry.class";

jest.mock('jimp', () => {});
jest.mock("./adapter/native.adapter.class");

describe("Window class", () => {
Expand Down
2 changes: 2 additions & 0 deletions lib/window.function.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {NativeAdapter} from "./adapter/native.adapter.class";
import {Window} from "./window.class";
import providerRegistry from "./provider/provider-registry.class";

jest.mock('jimp', () => {});

describe("WindowApi", () => {
describe("getWindows", () => {
it("should return a list of open Windows", async () => {
Expand Down
Loading