diff --git a/.travis.yml b/.travis.yml index b20b3a1e3..ec098d567 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,8 @@ _examples: &examples before_install: - nvm install $NODE_VERSION - npm install -g mocha + - npm install -g ts-mocha + - npm install -g typescript - npm install -g @project-serum/anchor - npm install -g @project-serum/serum - npm install -g @project-serum/common diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bf12da48..fea62935d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ incremented for features. * ts: Allow preloading instructions for state rpc transactions ([cf9c84](https://github.com/project-serum/anchor/commit/cf9c847e4144989b5bc1936149d171e90204777b)). * cli: Specify programs to embed into local validator genesis via Anchor.toml while testing. * cli: Allow skipping the creation of a local validator when testing against localnet. +* cli: Adds support for tests with Typescript ([#94](https://github.com/project-serum/anchor/pull/94)). ## Fixes diff --git a/cli/src/main.rs b/cli/src/main.rs index a59a56260..3eeb016b2 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -35,7 +35,11 @@ pub struct Opts { #[derive(Debug, Clap)] pub enum Command { /// Initializes a workspace. - Init { name: String }, + Init { + name: String, + #[clap(short, long)] + typescript: bool, + }, /// Builds the workspace. Build { /// Output directory for the IDL. @@ -151,7 +155,7 @@ pub enum IdlCommand { fn main() -> Result<()> { let opts = Opts::parse(); match opts.command { - Command::Init { name } => init(name), + Command::Init { name, typescript } => init(name, typescript), Command::New { name } => new(name), Command::Build { idl } => build(idl), Command::Deploy { url, keypair } => deploy(url, keypair), @@ -170,7 +174,7 @@ fn main() -> Result<()> { } } -fn init(name: String) -> Result<()> { +fn init(name: String, typescript: bool) -> Result<()> { let cfg = Config::discover()?; if cfg.is_some() { @@ -197,8 +201,17 @@ fn init(name: String) -> Result<()> { // Build the test suite. fs::create_dir("tests")?; - let mut mocha = File::create(&format!("tests/{}.js", name))?; - mocha.write_all(template::mocha(&name).as_bytes())?; + if typescript { + // Build typescript config + let mut ts_config = File::create("tsconfig.json")?; + ts_config.write_all(template::ts_config().as_bytes())?; + + let mut mocha = File::create(&format!("tests/{}.ts", name))?; + mocha.write_all(template::ts_mocha(&name).as_bytes())?; + } else { + let mut mocha = File::create(&format!("tests/{}.js", name))?; + mocha.write_all(template::mocha(&name).as_bytes())?; + } // Build the migrations directory. fs::create_dir("migrations")?; @@ -612,18 +625,31 @@ fn test(skip_deploy: bool, skip_local_validator: bool) -> Result<()> { None } }; - let log_streams = stream_logs(&cfg.cluster.url())?; + let ts_config_exist = Path::new("tsconfig.json").exists(); + // Run the tests. - let exit = std::process::Command::new("mocha") - .arg("-t") - .arg("1000000") - .arg("tests/") - .env("ANCHOR_PROVIDER_URL", cfg.cluster.url()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .output()?; + let exit = match ts_config_exist { + true => std::process::Command::new("ts-mocha") + .arg("-p") + .arg("./tsconfig.json") + .arg("-t") + .arg("1000000") + .arg("tests/**/*.ts") + .env("ANCHOR_PROVIDER_URL", cfg.cluster.url()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output()?, + false => std::process::Command::new("mocha") + .arg("-t") + .arg("1000000") + .arg("tests/") + .env("ANCHOR_PROVIDER_URL", cfg.cluster.url()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output()?, + }; if !exit.status.success() { if let Some(mut validator_handle) = validator_handle { diff --git a/cli/src/template.rs b/cli/src/template.rs index 1ffbee30b..b3fe02ad5 100644 --- a/cli/src/template.rs +++ b/cli/src/template.rs @@ -128,3 +128,40 @@ describe('{}', () => {{ name.to_camel_case(), ) } + +pub fn ts_mocha(name: &str) -> String { + format!( + r#"import * as anchor from '@project-serum/anchor'; + +describe('{}', () => {{ + + // Configure the client to use the local cluster. + anchor.setProvider(anchor.Provider.env()); + + it('Is initialized!', async () => {{ + // Add your test here. + const program = anchor.workspace.{}; + const tx = await program.rpc.initialize(); + console.log("Your transaction signature", tx); + }}); +}}); +"#, + name, + name.to_camel_case(), + ) +} + +pub fn ts_config() -> String { + r#"{ + "compilerOptions": { + "types": ["mocha", "chai"], + "typeRoots": ["./node_modules/@types"], + "lib": ["es2015"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true + } +} +"# + .to_string() +} diff --git a/examples/tutorial/basic-2/package.json b/examples/tutorial/basic-2/package.json new file mode 100644 index 000000000..12fecd0f7 --- /dev/null +++ b/examples/tutorial/basic-2/package.json @@ -0,0 +1,25 @@ +{ + "name": "basic-2", + "version": "1.0.0", + "description": "", + "main": "index.js", + "directories": { + "test": "tests" + }, + "scripts": { + "test": "ts-mocha -t 100000 -p ./tsconfig.json tests/**/*.{j,t}s" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@project-serum/anchor": "^0.2.2-beta.1", + "@types/chai": "^4.2.15" + }, + "devDependencies": { + "@types/expect": "^24.3.0", + "@types/jest": "^26.0.20", + "@types/mocha": "^8.2.1", + "@types/node": "^14.14.31", + "chai": "^4.3.0" + } +} diff --git a/examples/tutorial/basic-2/tests/basic-2.js b/examples/tutorial/basic-2/tests/basic-2.js deleted file mode 100644 index 31ef358bb..000000000 --- a/examples/tutorial/basic-2/tests/basic-2.js +++ /dev/null @@ -1,47 +0,0 @@ -const assert = require("assert"); -const anchor = require('@project-serum/anchor'); - -describe("basic-2", () => { - const provider = anchor.Provider.local(); - - // Configure the client to use the local cluster. - anchor.setProvider(provider); - - // Counter for the tests. - const counter = new anchor.web3.Account(); - - // Program for the tests. - const program = anchor.workspace.Basic2; - - it("Creates a counter", async () => { - await program.rpc.create(provider.wallet.publicKey, { - accounts: { - counter: counter.publicKey, - rent: anchor.web3.SYSVAR_RENT_PUBKEY, - }, - signers: [counter], - instructions: [ - await program.account.counter.createInstruction(counter), - ], - }); - - let counterAccount = await program.account.counter(counter.publicKey); - - assert.ok(counterAccount.authority.equals(provider.wallet.publicKey)); - assert.ok(counterAccount.count.toNumber() === 0); - }); - - it("Updates a counter", async () => { - await program.rpc.increment({ - accounts: { - counter: counter.publicKey, - authority: provider.wallet.publicKey, - }, - }); - - counterAccount = await program.account.counter(counter.publicKey); - - assert.ok(counterAccount.authority.equals(provider.wallet.publicKey)); - assert.ok(counterAccount.count.toNumber() == 1); - }); -}); diff --git a/examples/tutorial/basic-2/tests/basic-2.ts b/examples/tutorial/basic-2/tests/basic-2.ts new file mode 100644 index 000000000..d4d524d1b --- /dev/null +++ b/examples/tutorial/basic-2/tests/basic-2.ts @@ -0,0 +1,44 @@ +import assert from 'assert' +import * as anchor from '@project-serum/anchor' +describe('basic-2', () => { + const provider = anchor.Provider.local() + + // Configure the client to use the local cluster. + anchor.setProvider(provider) + + // Counter for the tests. + const counter = new anchor.web3.Account() + + // Program for the tests. + const program = anchor.workspace.Basic2 + + it('Creates a counter', async () => { + await program.rpc.create(provider.wallet.publicKey, { + accounts: { + counter: counter.publicKey, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [counter], + instructions: [await program.account.counter.createInstruction(counter)], + }) + + let counterAccount = await program.account.counter(counter.publicKey) + + assert.ok(counterAccount.authority.equals(provider.wallet.publicKey)) + assert.ok(counterAccount.count.toNumber() === 0) + }) + + it('Updates a counter', async () => { + await program.rpc.increment({ + accounts: { + counter: counter.publicKey, + authority: provider.wallet.publicKey, + }, + }) + + const counterAccount = await program.account.counter(counter.publicKey) + + assert.ok(counterAccount.authority.equals(provider.wallet.publicKey)) + assert.ok(counterAccount.count.toNumber() == 1) + }) +}) diff --git a/examples/tutorial/basic-2/tsconfig.json b/examples/tutorial/basic-2/tsconfig.json new file mode 100644 index 000000000..cd5d2e3d0 --- /dev/null +++ b/examples/tutorial/basic-2/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["mocha", "chai"], + "typeRoots": ["./node_modules/@types"], + "lib": ["es2015"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true + } +}