Skip to content

Feature/307/default imagereader imagewriter #308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 5, 2021
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