Skip to content

Commit 902c350

Browse files
committed
Merge branch 'develop'
2 parents d4312ab + af20157 commit 902c350

18 files changed

+9156
-415
lines changed

.github/workflows/ci.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ on:
44
branches-ignore:
55
- develop
66
- release/**
7+
paths-ignore:
8+
- '**/*.md'
79
pull_request:
810

911
jobs:
1012
sonar:
1113
runs-on: ubuntu-latest
14+
if: '!contains(github.event.head_commit.message, ''skip ci'')'
1215
steps:
1316
- name: Set up Git repository
1417
uses: actions/checkout@v2

.github/workflows/orga.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Close inactive issues
2+
on:
3+
schedule:
4+
- cron: "30 1 * * *"
5+
6+
jobs:
7+
close-issues:
8+
runs-on: ubuntu-latest
9+
permissions:
10+
issues: write
11+
pull-requests: write
12+
steps:
13+
- uses: actions/stale@v5
14+
with:
15+
days-before-issue-stale: 30
16+
days-before-issue-close: 14
17+
stale-issue-label: "stale"
18+
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
19+
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
20+
days-before-pr-stale: -1
21+
days-before-pr-close: -1
22+
repo-token: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/snapshot_release.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ on:
33
push:
44
branches:
55
- develop
6+
paths-ignore:
7+
- '**/*.md'
68
repository_dispatch:
79
types:
810
- snapshot-release

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## 2.1.0
6+
- Bugfix: Keyboard methods `pressKey` and `releaseKey` ignore updated autoDelayMs [(#188)](https://github.com/nut-tree/nut.js/issues/188)
7+
- Enhancement: Add mappings for missing numpad keys [(#367)](https://github.com/nut-tree/nut.js/issues/367)
8+
- Enhancement: macOS double click [(#373)](https://github.com/nut-tree/nut.js/issues/373)
9+
- Maintenance: Both `mouse.leftClick` and `mouse.rightClick` should reuse `click` [(#390)](https://github.com/nut-tree/nut.js/issues/390)
10+
- Feature: New image loader to fetch remote images [(#400)](https://github.com/nut-tree/nut.js/issues/400)
11+
- Bugfix: Mouse methods `pressButton` and `releaseButton` should respect auto delay [(#403)](https://github.com/nut-tree/nut.js/issues/403)
12+
513
## 2.0.1
6-
- Bugfix: Issue with keyboard.type in to Spotlight on MacOS [(#152)](https://github.com/nut-tree/nut.js/issues/152)
14+
- Bugfix: Issue with `keyboard.type` in to Spotlight on MacOS [(#152)](https://github.com/nut-tree/nut.js/issues/152)
715
- Enhancement: Numpad buttons don't work on Linux [(#360)](https://github.com/nut-tree/nut.js/issues/360)
816

917
## 2.0.0

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
[![SonarCloud Coverage](https://sonarcloud.io/api/project_badges/measure?project=nut-tree%3Anut.js&metric=coverage)](https://sonarcloud.io/component_measures?id=nut-tree%3Anut.js&metric=coverage)
1010

1111
[![Downloads per month](https://img.shields.io/npm/dm/@nut-tree/nut-js)](https://www.npmjs.com/package/@nut-tree/nut-js)
12+
[![@nut-tree/nut-js](https://snyk.io/advisor/npm-package/@nut-tree/nut-js/badge.svg)](https://snyk.io/advisor/npm-package/@nut-tree/nut-js)
13+
14+
<a href="https://console.dev" title="Visit Console - the best tools for developers"><img src="https://console.dev/img/badges/1.0/svg/console-badge-logo-dark.svg" alt="Console - Developer Tool of the Week" /></a>
1215

1316
<p align="center">
1417
Please visit
@@ -54,7 +57,7 @@ Check out this demo video to get a first impression of what nut.js is capable of
5457

5558
# Tutorials
5659

57-
Please consult the project website at [nutjs.dev](https://nutjs.dev/docs/tutorial-first_steps/prerequisits) for in-depth tutorials
60+
Please consult the project website at [nutjs.dev](https://nutjs.dev/docs/tutorial-first_steps/prerequisites) for in-depth tutorials
5861

5962
# Examples
6063

@@ -66,7 +69,7 @@ nut.js provides [public API documentation](https://nut-tree.github.io/apidoc/) a
6669

6770
# Community
6871

69-
Feel free to join our [Discord community](https://discord.gg/sJkN7789XR)
72+
Feel free to join our [Discord community](https://discord.gg/U5csuM4Esp)
7073

7174
# Modules
7275

index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const loadImage = providerRegistry.getImageReader().load;
5252
const saveImage = providerRegistry.getImageWriter().store;
5353

5454
const imageResource = (fileName: string) => loadImageResource(providerRegistry, screen.config.resourceDirectory, fileName);
55+
export {fetchFromUrl} from "./lib/imageResources.function";
5556

5657
export {
5758
clipboard,

lib/imageResources.function.spec.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import {loadImageResource} from "./imageResources.function";
1+
import {fetchFromUrl, loadImageResource} from "./imageResources.function";
22
import {mockPartial} from "sneer";
33
import {ProviderRegistry} from "./provider/provider-registry.class";
44
import {ImageReader} from "./provider";
55
import {join} from "path";
6+
import {ColorMode} from "./colormode.enum";
67

78
const loadMock = jest.fn();
89
const providerRegistryMock = mockPartial<ProviderRegistry>({
@@ -25,4 +26,46 @@ describe('imageResources', () => {
2526
// THEN
2627
expect(loadMock).toBeCalledWith(join(resourceDirectoryPath, imageFileName));
2728
});
29+
});
30+
31+
describe('fetchFromUrl', () => {
32+
it('should throw on malformed URLs', async () => {
33+
// GIVEN
34+
const malformedUrl = "foo";
35+
36+
// WHEN
37+
const SUT = () => fetchFromUrl(malformedUrl);
38+
39+
// THEN
40+
await expect(SUT).rejects.toThrowError("Invalid URL");
41+
});
42+
43+
it('should throw on non-image URLs', async () => {
44+
// GIVEN
45+
const nonImageUrl = 'https://www.npmjs.com/package/jimp';
46+
47+
// WHEN
48+
const SUT = () => fetchFromUrl(nonImageUrl);
49+
50+
// THEN
51+
await expect(SUT).rejects.toThrowError('Could not find MIME for Buffer');
52+
});
53+
54+
it('should return an RGB image from a valid URL', async () => {
55+
// GIVEN
56+
const validImageUrl = 'https://github.com/nut-tree/nut.js/raw/master/.gfx/nut.png';
57+
const expectedDimensions = {
58+
width: 502,
59+
height: 411
60+
};
61+
const expectedColorMode = ColorMode.RGB;
62+
63+
// WHEN
64+
const rgbImage = await fetchFromUrl(validImageUrl);
65+
66+
// THEN
67+
expect(rgbImage.colorMode).toBe(expectedColorMode);
68+
expect(rgbImage.width).toBe(expectedDimensions.width);
69+
expect(rgbImage.height).toBe(expectedDimensions.height);
70+
});
2871
});

lib/imageResources.function.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,43 @@
11
import {join, normalize} from "path";
22
import {ProviderRegistry} from "./provider/provider-registry.class";
3+
import {URL} from "url";
4+
import {Image} from "./image.class";
5+
import Jimp from "jimp";
6+
import {ColorMode} from "./colormode.enum";
37

48
export function loadImageResource(providerRegistry: ProviderRegistry, resourceDirectory: string, fileName: string) {
59
const fullPath = normalize(join(resourceDirectory, fileName));
610
return providerRegistry.getImageReader().load(fullPath);
11+
}
12+
13+
/**
14+
* fetchFromUrl loads remote image content at runtime to provide it for further use in on-screen image search
15+
* @param url The remote URl to fetch an image from as string or {@link URL}
16+
* @throws On malformed URL input or in case of non-image remote content
17+
*/
18+
export async function fetchFromUrl(url: string | URL): Promise<Image> {
19+
let imageUrl: URL;
20+
if (url instanceof URL) {
21+
imageUrl = url;
22+
} else {
23+
try {
24+
imageUrl = new URL(url);
25+
} catch (e) {
26+
throw e;
27+
}
28+
}
29+
return Jimp.read(imageUrl.href)
30+
.then((image) => {
31+
return new Image(
32+
image.bitmap.width,
33+
image.bitmap.height,
34+
image.bitmap.data,
35+
4,
36+
imageUrl.href,
37+
ColorMode.RGB
38+
);
39+
})
40+
.catch(err => {
41+
throw err;
42+
});
743
}

lib/keyboard.class.spec.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,48 @@ describe("Keyboard", () => {
149149
// THEN
150150
expect(keyMock).toHaveBeenCalledTimes(payload.length);
151151
});
152+
153+
describe("autoDelayMs", () => {
154+
it("pressKey should respect configured delay", async () => {
155+
// GIVEN
156+
const SUT = new KeyboardClass(providerRegistryMock);
157+
const delay = 100;
158+
SUT.config.autoDelayMs = delay;
159+
160+
const keyMock = jest.fn();
161+
providerRegistryMock.getKeyboard = jest.fn(() => mockPartial<KeyboardProviderInterface>({
162+
setKeyboardDelay: jest.fn(),
163+
pressKey: keyMock
164+
}));
165+
166+
// WHEN
167+
const start = Date.now();
168+
await SUT.pressKey(Key.A);
169+
const duration = Date.now() - start;
170+
171+
// THEN
172+
expect(duration).toBeGreaterThanOrEqual(delay);
173+
});
174+
175+
it("should pass a list of input keys down to the releaseKey call.", async () => {
176+
// GIVEN
177+
const SUT = new KeyboardClass(providerRegistryMock);
178+
const delay = 100;
179+
SUT.config.autoDelayMs = delay;
180+
181+
const keyMock = jest.fn();
182+
providerRegistryMock.getKeyboard = jest.fn(() => mockPartial<KeyboardProviderInterface>({
183+
setKeyboardDelay: jest.fn(),
184+
releaseKey: keyMock
185+
}));
186+
187+
// WHEN
188+
const start = Date.now();
189+
await SUT.releaseKey(Key.A);
190+
const duration = Date.now() - start;
191+
192+
// THEN
193+
expect(duration).toBeGreaterThanOrEqual(delay);
194+
});
195+
});
152196
});

lib/keyboard.class.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export class KeyboardClass {
7373
public pressKey(...keys: Key[]): Promise<KeyboardClass> {
7474
return new Promise<KeyboardClass>(async (resolve, reject) => {
7575
try {
76+
await sleep(this.config.autoDelayMs);
7677
await this.providerRegistry.getKeyboard().pressKey(...keys);
7778
resolve(this);
7879
} catch (e) {
@@ -95,6 +96,7 @@ export class KeyboardClass {
9596
public releaseKey(...keys: Key[]): Promise<KeyboardClass> {
9697
return new Promise<KeyboardClass>(async (resolve, reject) => {
9798
try {
99+
await sleep(this.config.autoDelayMs);
98100
await this.providerRegistry.getKeyboard().releaseKey(...keys);
99101
resolve(this);
100102
} catch (e) {

0 commit comments

Comments
 (0)