ts: Add strong type support for `addEventListener` (#2627)

This commit is contained in:
acheron 2023-09-13 00:12:41 +02:00 committed by GitHub
parent cec9946111
commit fdda604d41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 48 additions and 47 deletions

View File

@ -23,6 +23,7 @@ The minor version will be incremented upon a breaking change and the patch versi
- ts: Add support for unnamed(tuple) enum in accounts ([#2601](https://github.com/coral-xyz/anchor/pull/2601)).
- cli: Add program template with multiple files for instructions, state... ([#2602](https://github.com/coral-xyz/anchor/pull/2602)).
- lang: `Box` the inner enums of `anchor_lang::error::Error` to optimize `anchor_lang::Result` ([#2600](https://github.com/coral-xyz/anchor/pull/2600)).
- ts: Add strong type support for `Program.addEventListener` method ([#2627](https://github.com/coral-xyz/anchor/pull/2627)).
### Fixes

View File

@ -6,4 +6,4 @@ wallet = "~/.config/solana/id.json"
events = "2dhGsWUzy5YKUsjZdLHLmkNpUDAXkNa9MYWsPc4Ziqzy"
[scripts]
test = "yarn run mocha -t 1000000 tests/"
test = "yarn run ts-mocha -t 1000000 -p ./tsconfig.json tests/**/*.ts"

View File

@ -1,68 +1,54 @@
const anchor = require("@coral-xyz/anchor");
const { assert } = require("chai");
import * as anchor from "@coral-xyz/anchor";
import { assert } from "chai";
import { Events } from "../target/types/events";
describe("Events", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Events;
const program = anchor.workspace.Events as anchor.Program<Events>;
type Event = anchor.IdlEvents<typeof program["idl"]>;
const getEvent = async <E extends keyof Event>(
eventName: E,
methodName: keyof typeof program["methods"]
) => {
let listenerId: number;
const event = await new Promise<Event[E]>((res) => {
listenerId = program.addEventListener(eventName, (event) => {
res(event);
});
program.methods[methodName]().rpc();
});
await program.removeEventListener(listenerId);
return event;
};
describe("Normal event", () => {
it("Single event works", async () => {
let listener = null;
const event = await getEvent("MyEvent", "initialize");
let [event, slot] = await new Promise((resolve, _reject) => {
listener = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});
await program.removeEventListener(listener);
assert.isAbove(slot, 0);
assert.strictEqual(event.data.toNumber(), 5);
assert.strictEqual(event.label, "hello");
});
it("Multiple events work", async () => {
let listenerOne = null;
let listenerTwo = null;
const eventOne = await getEvent("MyEvent", "initialize");
const eventTwo = await getEvent("MyOtherEvent", "testEvent");
let [eventOne, slotOne] = await new Promise((resolve, _reject) => {
listenerOne = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});
let [eventTwo, slotTwo] = await new Promise((resolve, _reject) => {
listenerTwo = program.addEventListener(
"MyOtherEvent",
(event, slot) => {
resolve([event, slot]);
}
);
program.rpc.testEvent();
});
await program.removeEventListener(listenerOne);
await program.removeEventListener(listenerTwo);
assert.isAbove(slotOne, 0);
assert.strictEqual(eventOne.data.toNumber(), 5);
assert.strictEqual(eventOne.label, "hello");
assert.isAbove(slotTwo, 0);
assert.strictEqual(eventTwo.data.toNumber(), 6);
assert.strictEqual(eventTwo.label, "bye");
});
});
describe("Self-CPI event", () => {
describe("CPI event", () => {
it("Works without accounts being specified", async () => {
const tx = await program.methods.testEventCpi().transaction();
const config = {
commitment: "confirmed",
};
const config = { commitment: "confirmed" } as const;
const txHash = await program.provider.sendAndConfirm(tx, [], config);
const txResult = await program.provider.connection.getTransaction(
txHash,
@ -77,10 +63,10 @@ describe("Events", () => {
assert.strictEqual(event.name, "MyOtherEvent");
assert.strictEqual(event.data.label, "cpi");
assert.strictEqual(event.data.data.toNumber(), 7);
assert.strictEqual((event.data.data as anchor.BN).toNumber(), 7);
});
it("Malicious invocation throws", async () => {
it("Throws on unauthorized invocation", async () => {
const tx = new anchor.web3.Transaction();
tx.add(
new anchor.web3.TransactionInstruction({

View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"types": ["mocha", "chai"],
"lib": ["es2015"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true
}
}

View File

@ -11,6 +11,7 @@ import NamespaceFactory, {
SimulateNamespace,
MethodsNamespace,
ViewNamespace,
IdlEvents,
} from "./namespace/index.js";
import { utf8 } from "../utils/bytes/index.js";
import { EventManager } from "./event.js";
@ -357,9 +358,13 @@ export class Program<IDL extends Idl = Idl> {
* @param callback The function to invoke whenever the event is emitted from
* program logs.
*/
public addEventListener(
eventName: string,
callback: (event: any, slot: number, signature: string) => void
public addEventListener<E extends keyof IdlEvents<IDL>>(
eventName: E,
callback: (
event: IdlEvents<IDL>[E],
slot: number,
signature: string
) => void
): number {
return this._events.addEventListener(eventName, callback);
}