Skip to content

Commit 1af1ae5

Browse files
authored
Merge pull request #4 from withuno/smithki/strongly-typed-events
Add strong typing for events emitted by login targets
2 parents 6dfda19 + eb15606 commit 1af1ae5

File tree

4 files changed

+26
-23
lines changed

4 files changed

+26
-23
lines changed

README.md

+4-7
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,19 @@ Locust login targets will emit events when certain things happen. To listen for
3939

4040
```ts
4141
const target = getLoginTarget();
42-
target.on("valueChanged", info => {
43-
if (info.type === "username") {
44-
console.log("New username:", info.value);
42+
target.events.on("valueChanged", ({ type, value }) => {
43+
if (type === "username") {
44+
console.log("New username:", value);
4545
}
4646
});
4747
// `type` can be "username" or "password"
4848
```
4949

50-
> **Note**
51-
> Login targets inherit from [`EventEmitter`](https://github.com/primus/eventemitter3), so you can use all other methods provided by their implementation.
52-
5350
You can also listen to form submissions:
5451

5552
```ts
5653
const target = getLoginTarget();
57-
target.once("formSubmitted", ({ source }) => {
54+
target.events.once("formSubmitted", ({ source }) => {
5855
// `source` will either be "submitButton" or "form"
5956
});
6057
```

src/login/login-inputs.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,11 @@ function guessUsernameInput(formEl: HTMLFormElement) {
9999

100100
export function setInputValue(input: HTMLInputElement, value: string) {
101101
nativeInputValueSetter.call(input, value);
102-
const inputEvent = new Event('input', { bubbles: true });
103-
input.dispatchEvent(inputEvent);
104-
const changeEvent = new Event('change', { bubbles: true });
105-
input.dispatchEvent(changeEvent);
102+
input.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: true }));
103+
input.dispatchEvent(new KeyboardEvent('keypress', { bubbles: true, cancelable: true }));
104+
input.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, cancelable: true }));
105+
input.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
106+
input.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
106107
}
107108

108109
export function sortFormElements<Elements extends HTMLElement[]>(

src/login/login-target.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,19 @@ interface ChangeListener {
2626
listener: () => void;
2727
}
2828

29+
type LoginTargetEventEmitter = EventEmitter<{
30+
formSubmitted: (ctx: { source: 'form' | 'submitButton' }) => void;
31+
valueChanged: (ctx: { type: 'username' | 'password'; value: string }) => void;
32+
}>;
33+
2934
/**
3035
* The LoginTarget class which represents a 'target' for logging in
3136
* with some credentials
3237
* @class LoginTarget
3338
*/
34-
export class LoginTarget extends EventEmitter {
39+
export class LoginTarget {
3540
public baseScore = 0;
41+
public events = new EventEmitter() as LoginTargetEventEmitter;
3642

3743
private _form: HTMLFormElement | null = null;
3844
private _usernameField: HTMLInputElement | null = null;
@@ -230,7 +236,7 @@ export class LoginTarget extends EventEmitter {
230236
* @fires LoginTarget#valueChanged
231237
* @fires LoginTarget#formSubmitted
232238
*/
233-
_listenForUpdates<El extends HTMLElement>(type: LoginTargetType, input: El) {
239+
private _listenForUpdates<El extends HTMLElement>(type: LoginTargetType, input: El) {
234240
if (/username|password|submit|form/.test(type) !== true) {
235241
throw new Error(`Failed listening for field changes: Unrecognised type: ${type}`);
236242
}
@@ -247,10 +253,10 @@ export class LoginTarget extends EventEmitter {
247253
if (type === 'submit' || type === 'form') {
248254
// Listener function for the submission of the form
249255
const source = type === 'form' ? 'form' : 'submitButton';
250-
handleEvent = () => this.emit('formSubmitted', { source });
256+
handleEvent = () => this.events.emit('formSubmitted', { source });
251257
} else {
252258
const emit = (value: any) => {
253-
this.emit('valueChanged', {
259+
this.events.emit('valueChanged', {
254260
type,
255261
value,
256262
});
@@ -275,7 +281,7 @@ export class LoginTarget extends EventEmitter {
275281
* @returns A promise that resolves once either the delay has
276282
* expired for the page has begun unloading.
277283
*/
278-
_waitForNoUnload() {
284+
private _waitForNoUnload() {
279285
const unloadObserver = getSharedObserver();
280286
return Promise.race([
281287
new Promise((resolve) => {

test/unit/login/login-target.spec.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { expect } from 'chai';
2+
import { EventEmitter } from 'eventemitter3';
23
import sinon from 'sinon';
34

45
import { setInputValue } from '@src/login/login-inputs';
@@ -10,15 +11,13 @@ describe('LoginTarget', function () {
1011
});
1112

1213
it('implements event emitter methods', function () {
13-
expect(this.target).to.have.property('emit').that.is.a('function');
14-
expect(this.target).to.have.property('on').that.is.a('function');
15-
expect(this.target).to.have.property('once').that.is.a('function');
14+
expect(this.target.events).to.be.instanceOf(EventEmitter);
1615
});
1716

1817
it('fires events when username inputs are updated', function () {
1918
let currentValue = '';
2019
this.target.usernameField = document.createElement('input');
21-
this.target.on('valueChanged', (info) => {
20+
this.target.events.on('valueChanged', (info) => {
2221
if (info.type === 'username') {
2322
currentValue = info.value;
2423
}
@@ -29,7 +28,7 @@ describe('LoginTarget', function () {
2928

3029
it('fires events when the submit button is clicked', function () {
3130
let formSubmitted = 0;
32-
this.target.on('formSubmitted', (info) => {
31+
this.target.events.on('formSubmitted', (info) => {
3332
if (info.source === 'submitButton') {
3433
formSubmitted += 1;
3534
}
@@ -42,7 +41,7 @@ describe('LoginTarget', function () {
4241

4342
it('fires events when the form is submitted', function () {
4443
let formSubmitted = 0;
45-
this.target.on('formSubmitted', (info) => {
44+
this.target.events.on('formSubmitted', (info) => {
4645
if (info.source === 'form') {
4746
formSubmitted += 1;
4847
}
@@ -62,7 +61,7 @@ describe('LoginTarget', function () {
6261
it('fires events when password inputs are updated', function () {
6362
let currentValue = '';
6463
this.target.passwordField = document.createElement('input');
65-
this.target.on('valueChanged', (info) => {
64+
this.target.events.on('valueChanged', (info) => {
6665
if (info.type === 'password') {
6766
currentValue = info.value;
6867
}

0 commit comments

Comments
 (0)