Selaa lähdekoodia

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

acheron 2 vuotta sitten
vanhempi
sitoutus
fdda604d41

+ 1 - 0
CHANGELOG.md

@@ -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
 

+ 1 - 1
tests/events/Anchor.toml

@@ -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"

+ 29 - 43
tests/events/tests/events.js → tests/events/tests/events.ts

@@ -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;
-
-      let [event, slot] = await new Promise((resolve, _reject) => {
-        listener = program.addEventListener("MyEvent", (event, slot) => {
-          resolve([event, slot]);
-        });
-        program.rpc.initialize();
-      });
-      await program.removeEventListener(listener);
+      const event = await getEvent("MyEvent", "initialize");
 
-      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;
-
-      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);
+      const eventOne = await getEvent("MyEvent", "initialize");
+      const eventTwo = await getEvent("MyOtherEvent", "testEvent");
 
-      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({

+ 9 - 0
tests/events/tsconfig.json

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

+ 8 - 3
ts/packages/anchor/src/program/index.ts

@@ -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);
   }