From ef8cf26de9364dfb3bf5f29fee3fbe3bdf9e0c61 Mon Sep 17 00:00:00 2001 From: Dimitar Daskalov Date: Thu, 12 Sep 2024 15:07:35 +0300 Subject: [PATCH] docs: update the `README.md` files and add examples for `near-workspaces-js` with `near-sdk-js` --- README.md | 546 +++++++++++++++++++++++++----------------- packages/js/README.md | 156 +++++++----- 2 files changed, 423 insertions(+), 279 deletions(-) diff --git a/README.md b/README.md index 2ac4a9c0..5300ac7d 100644 --- a/README.md +++ b/README.md @@ -1,268 +1,287 @@
-

NEAR Workspaces (TypeScript/JavaScript Edition)

- [![Project license](https://img.shields.io/badge/license-Apache2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) - [![Project license](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) - [![Discord](https://img.shields.io/discord/490367152054992913?label=discord)](https://discord.gg/Vyp7ETM) - [![NPM version](https://img.shields.io/npm/v/near-workspaces.svg?style=flat-square)](https://npmjs.com/near-workspaces) - [![Size on NPM](https://img.shields.io/bundlephobia/minzip/near-workspaces.svg?style=flat-square)](https://npmjs.com/near-workspaces) +[![Project license](https://img.shields.io/badge/license-Apache2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Project license](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) +[![Discord](https://img.shields.io/discord/490367152054992913?label=discord)](https://discord.gg/Vyp7ETM) +[![NPM version](https://img.shields.io/npm/v/near-workspaces.svg?style=flat-square)](https://npmjs.com/near-workspaces) +[![Size on NPM](https://img.shields.io/bundlephobia/minzip/near-workspaces.svg?style=flat-square)](https://npmjs.com/near-workspaces)
-`NEAR Workspaces` is a library for automating workflows and writing tests for NEAR smart contracts. You can use it as is or integrate with test runner of your choise (AVA, Jest, Mocha, etc.). If you don't have a preference, we suggest you to use AVA. +`NEAR Workspaces` is a library for automating workflows and writing tests for NEAR smart contracts. It can be used standalone or integrated with test runners like AVA, Jest, or Mocha. If you don't have a preference, we recommend AVA. -Quick Start (without testing frameworks) -=========== -To get started with `Near Workspaces` you need to do only two things: +## Quick Start (without testing frameworks) -1. Initialize a `Worker`. +### 1. Initialize a `Worker`. - ```ts - const worker = await Worker.init(); - const root = worker.rootAccount; +```ts +const worker = await Worker.init(); +const root = worker.rootAccount; - const alice = await root.createSubAccount('alice'); - const contract = await root.devDeploy('path/to/compiled.wasm'); - ``` +const alice = await root.createSubAccount("alice"); +const contract = await root.devDeploy("path/to/compiled.wasm"); +``` - Let's step through this. - - 1. `Worker.init` initializes a new `SandboxWorker` or `TestnetWorker` depending on the config. `SandboxWorker` contains [NEAR Sandbox](https://github.com/near/sandbox), which is essentially a local mini-NEAR blockchain. You can create one `Worker` per test to get its own data directory and port (for Sandbox) or root account (for Testnet), so that tests can run in parallel without race conditions in accessing states. If there's no state intervention. you can also reuse the `Worker` to speedup the tests. - 2. The worker has a `root` account. For `SandboxWorker`, it's `test.near`. For `TestnetWorker`, it creates a unique account. The following accounts are created as subaccounts of the root account. The name of the account will change from different runs, so you should not refer to them by hard coded account name. You can access them via the account object, such as `root`, `alice` and `contract` above. - 3. `root.createSubAccount` creates a new subaccount of `root` with the given name, for example `alice.`. - 4. `root.devDeploy` creates an account with random name, then deploys the specified Wasm file to it. - 5. `path/to/compiled.wasm` will resolve relative to your project root. That is, the nearest directory with a `package.json` file, or your current working directory if no `package.json` is found. To construct a path relative to your test file, you can use `path.join(__dirname, '../etc/etc.wasm')` ([more info](https://nodejs.org/api/path.html#path_path_join_paths)). - 6. `worker` contains a reference to this data directory, so that multiple tests can use it as a starting point. - 7. If you're using a test framework, you can save the `worker` object and account objects `root`, `alice`, `contract` to test context to reuse them in subsequent tests. - 8. At the end of test, call `await worker.tearDown()` to shuts down the Worker. It gracefully shuts down the Sandbox instance it ran in the background. However, it keeps the data directory around. That's what stores the state of the two accounts that were created (`alice` and `contract-account-name` with its deployed contract). - -2. Writing tests. - - `near-workspaces` is designed for concurrency. Here's a simple way to get concurrent runs using plain JS: - - ```ts - import {strict as assert} from 'assert'; - - await Promise.all([ - async () => { - await alice.call( - contract, - 'some_update_function', - {some_string_argument: 'cool', some_number_argument: 42} - ); - const result = await contract.view( - 'some_view_function', - {account_id: alice} - ); - assert.equal(result, 'whatever'); - }, - async () => { - const result = await contract.view( - 'some_view_function', - {account_id: alice} - ); - /* Note that we expect the value returned from `some_view_function` to be - a default here, because this `fork` runs *at the same time* as the - previous, in a separate local blockchain */ - assert.equal(result, 'some default'); - } - ]); - ``` - - Let's step through this. - - 1. `worker` and accounts such as `alice` are created before. - 2. `call` syntax mirrors [near-cli](https://github.com/near/near-cli) and either returns the successful return value of the given function or throws the encountered error. If you want to inspect a full transaction and/or avoid the `throw` behavior, you can use `callRaw` instead. - 3. While `call` is invoked on the account _doing the call_ (`alice.call(contract, …)`), `view` is invoked on the account _being viewed_ (`contract.view(…)`). This is because the caller of a view is irrelevant and ignored. - - -See the [tests](https://github.com/near/workspaces-js/tree/main/__tests__) directory in this project for more examples. - -Quick Start with AVA -=========== -Since `near-workspaces` is designed for concurrency, AVA is a great fit, because it runs tests concurrently by default. To use`NEAR Workspaces` with AVA: - 1. Start with the basic setup described [here](https://github.com/avajs/ava). - 2. Add custom script for running tests on Testnet (if needed). Check instructions in `Running on Testnet` section. - 3. Add your tests following these example: - - ```ts - import {Worker} from 'near-workspaces'; - import anyTest, {TestFn} from 'ava' - - const test = anyTest as TestFn<{ - worker: Worker; - accounts: Record; - }>; - - /* If using `test.before`, each test is reusing the same worker; - If you'd like to make a copy of the worker, use `beforeEach` after `afterEach`, - which allows you to isolate the state for each test */ - test.before(async t => { - const worker = await Worker.init(); - const root = worker.rootAccount; - const contract = await root.devDeploy('path/to/contract/file.wasm'); - /* Account that you will be able to use in your tests */ - const ali = await root.createSubAccount('ali'); - t.context.worker = worker; - t.context.accounts = {root, contract, ali}; - }) - - test('Test name', async t => { - const {ali, contract} = t.context.accounts; - await ali.call(contract, 'set_status', {message: 'hello'}); - const result: string = await contract.view('get_status', {account_id: ali}); - t.is(result, 'hello'); - }); +#### Step-by-Step Explanation + +**_1. Worker Initialization:_** + +- `Worker.init` initializes a new `SandboxWorker` or `TestnetWorker` depending on the config. `SandboxWorker` contains [NEAR Sandbox](https://github.com/near/sandbox), which is essentially a local mini-NEAR blockchain. You can create one `Worker` per test to get its own data directory and port (for Sandbox) or root account (for Testnet), so that tests can run in parallel without race conditions in accessing states. +- If there's no state intervention. you can also reuse the `Worker` to speedup the tests. + +**_2. Root Account:_** + +- The worker has a `root` account. For `SandboxWorker`, it's `test.near`. For `TestnetWorker`, it creates a unique account. The following accounts are created as subaccounts of the root account. The name of the account will change from different runs, so you should not refer to them by hard coded account name. You can access them via the account object, such as `root`, `alice` and `contract` above. + +**_3. Creating Subaccounts:_** + +- `root.createSubAccount` creates a new subaccount of `root` with the given name, for example `alice.`. + +**_4. Deploying Contracts:_** + +- `root.devDeploy` creates an account with random name, then deploys the specified Wasm file to it. - test.after(async t => { - // Stop Sandbox server - await t.context.worker.tearDown().catch(error => { - console.log('Failed to tear down the worker:', error); +**_5. Path Resolution:_** + +- `path/to/compiled.wasm` will resolve relative to your project root. That is, the nearest directory with a `package.json` file, or your current working directory if no `package.json` is found. To construct a path relative to your test file, you can use `path.join(__dirname, '../etc/etc.wasm')` ([more info](https://nodejs.org/api/path.html#path_path_join_paths)). + +**_6. Data Directory:_** + +- `worker` contains a reference to this data directory, so that multiple tests can use it as a starting point. + +**_7. Test Framework Integration:_** + +- If you're using a test framework, you can save the `worker` object and account objects `root`, `alice`, `contract` to test context to reuse them in subsequent tests. + +**_8. Tearing Down:_** + +- At the end of test, call `await worker.tearDown()` to shuts down the Worker. It gracefully shuts down the Sandbox instance it ran in the background. However, it keeps the data directory around. That's what stores the state of the two accounts that were created (`alice` and `contract-account-name` with its deployed contract). + +### 2. Writing Tests. + +`near-workspaces` is designed for concurrency. Here's a simple way to get concurrent runs using plain JS: + +```ts +import { strict as assert } from "assert"; + +await Promise.all([ + async () => { + await alice.call(contract, "some_update_function", { + some_string_argument: "cool", + some_number_argument: 42, }); - }); - ``` + const result = await contract.view("some_view_function", { + account_id: alice, + }); + assert.equal(result, "whatever"); + }, + async () => { + const result = await contract.view("some_view_function", { + account_id: alice, + }); + /* Note that we expect the value returned from `some_view_function` to be + a default here, because this `fork` runs *at the same time* as the + previous, in a separate local blockchain */ + assert.equal(result, "some default"); + }, +]); +``` + +#### Step-by-Step Explanation + +**_1. Worker and Accounts:_** + +- `worker` and accounts such as`alice` are created before. + +**_2. Calling Contract Functions:_** + +- `call` syntax mirrors [near-cli](https://github.com/near/near-cli) and either returns the successful return value of the given function or throws the encountered error. If you want to inspect a full transaction and/or avoid the `throw` behavior, you can use `callRaw` instead. + +**_3. Viewing Contract Functions::_** + +- While `call` is invoked on the account _doing the call_ (`alice.call(contract, …)`), `view` is invoked on the account _being viewed_ (`contract.view(…)`). This is because the caller of a view is irrelevant and ignored. + +For more examples, see the [tests directory](https://github.com/near/workspaces-js/tree/main/__tests__) in this project. + +# Quick Start with AVA + +Since `near-workspaces` is designed for concurrency, AVA is a great fit, because it runs tests concurrently by default. +#### 1. Start with the basic setup described [here](https://github.com/avajs/ava). -"Spooning" Contracts from Testnet and Mainnet -============================================= +#### 2. Add custom script for running tests on Testnet (if needed). Check instructions in `"Running on Testnet"` section. -[Spooning a blockchain](https://coinmarketcap.com/alexandria/glossary/spoon-blockchain) is copying the data from one network into a different network. near-workspaces makes it easy to copy data from Mainnet or Testnet contracts into your local Sandbox environment: +#### 3. Write your tests following this example: + +```ts +import { Worker } from "near-workspaces"; +import anyTest, { TestFn } from "ava"; + +const test = anyTest as TestFn<{ + worker: Worker; + accounts: Record; +}>; + +/* If using `test.before`, each test is reusing the same worker; +If you'd like to make a copy of the worker, use `beforeEach` after `afterEach`, +which allows you to isolate the state for each test */ +test.before(async (t) => { + const worker = await Worker.init(); + const root = worker.rootAccount; + const contract = await root.devDeploy("path/to/contract/file.wasm"); + /* Account that you will be able to use in your tests */ + const ali = await root.createSubAccount("ali"); + t.context.worker = worker; + t.context.accounts = { root, contract, ali }; +}); + +test("Test name", async (t) => { + const { ali, contract } = t.context.accounts; + await ali.call(contract, "set_status", { message: "hello" }); + const result: string = await contract.view("get_status", { account_id: ali }); + t.is(result, "hello"); +}); + +test.after(async (t) => { + // Stop Sandbox server + await t.context.worker.tearDown().catch((error) => { + console.log("Failed to tear down the worker:", error); + }); +}); +``` + +# "Spooning" Contracts from Testnet and Mainnet + +[Spooning a blockchain](https://coinmarketcap.com/alexandria/glossary/spoon-blockchain) is copying the data from one network into a different network. `near-workspaces` makes it easy to copy data from Mainnet or Testnet contracts into your local Sandbox environment: ```ts const refFinance = await root.importContract({ - mainnetContract: 'v2.ref-finance.near', + mainnetContract: "v2.ref-finance.near", blockId: 50_000_000, withData: true, }); ``` -This would copy the Wasm bytes and contract state from [v2.ref-finance.near](https://explorer.near.org/accounts/v2.ref-finance.near) to your local blockchain as it existed at block `50_000_000`. This makes use of Sandbox's special [patch state](#patch-state-on-the-fly) feature to keep the contract name the same, even though the top level account might not exist locally (note that this means it only works in Sandbox testing mode). You can then interact with the contract in a deterministic way the same way you interact with all other accounts created with near-workspaces. +This would copy the Wasm bytes and contract state from [v2.ref-finance.near](https://explorer.near.org/accounts/v2.ref-finance.near) to your local blockchain as it existed at block `50_000_000`. This makes use of Sandbox's special [patch state](#patch-state-on-the-fly) feature to keep the contract name the same, even though the top level account might not exist locally (note that this means it only works in Sandbox testing mode). You can then interact with the contract in a deterministic way the same way you interact with all other accounts created with `near-workspaces`. -Gotcha: `withData` will only work out-of-the-box if the contract's data is 50kB or less. This is due to the default configuration of RPC servers; see [the "Heads Up" note here](https://docs.near.org/api/rpc/contracts#view-contract-state). Some teams at NEAR are hard at work giving you an easy way to run your own RPC server, at which point you can point tests at your custom RPC endpoint and get around the 50kB limit. +Note: `withData` will only work out-of-the-box if the contract's data is `50kB` or less. This is due to the default configuration of RPC servers, see the ["Heads Up" note here](https://docs.near.org/api/rpc/contracts#view-contract-state). -See an [example of spooning](https://github.com/near/workspaces-js/blob/main/__tests__/05.spoon-contract-to-sandbox.ava.ts) contracts. +For an example of spooning contracts, see the [spooning example](https://github.com/near/workspaces-js/blob/main/__tests__/05.spoon-contract-to-sandbox.ava.ts) contracts. -Running on Testnet -================== +# Running on Testnet -near-workspaces is set up so that you can write tests once and run them against a local Sandbox node (the default behavior) or against [NEAR TestNet](https://docs.near.org/concepts/basics/networks). Some reasons this might be helpful: +`near-workspaces` is designed so that you can write tests once and run them against either a local Sandbox node or the [NEAR TestNet](https://docs.near.org/concepts/basics/networks). -* Gives higher confidence that your contracts work as expected -* You can test against deployed testnet contracts -* If something seems off in Sandbox mode, you can compare it to testnet +**_Reasons to Use Testnet:_** -In order to use Workspaces JS in testnet mode you will need to have a testnet account. You can create one [here](https://wallet.testnet.near.org/). +- Higher confidence in contract behavior. -You can switch to testnet mode in three ways. +- Test against deployed Testnet contracts. -1. When creating Worker set network to `testnet` and pass your master account: +- Compare Sandbox mode with Testnet if discrepancies arise. - ```ts - const worker = await Worker.init({ - network: 'testnet', - testnetMasterAccountId: '', - }) - ``` - -2. Set the `NEAR_WORKSPACES_NETWORK` and `TESTNET_MASTER_ACCOUNT_ID` environment variables when running your tests: +In order to use Workspaces JS in testnet mode you will need to have a testnet account. You can create one [here](https://wallet.testnet.near.org/). - ```bash - NEAR_WORKSPACES_NETWORK=testnet TESTNET_MASTER_ACCOUNT_ID= node test.js - ``` +## Steps to Use Workspaces JS in Testnet Mode: - If you set this environment variables and pass `{network: 'testnet', testnetMasterAccountId: }` to `Worker.init`, the config object takes precedence. +### 1. Set Network to `testnet`: -3. If using `near-workspaces` with AVA, you can use a custom config file. Other test runners allow similar config files; adjust the following instructions for your situation. +```ts +const worker = await Worker.init({ + network: "testnet", + testnetMasterAccountId: "", +}); +``` - Create a file in the same directory as your `package.json` called `ava.testnet.config.cjs` with the following contents: +### 2. Use Environment Variables when running your tests: - ```js - module.exports = { - ...require('near-workspaces/ava.testnet.config.cjs'), - ...require('./ava.config.cjs'), - }; - module.exports.environmentVariables = { - TESTNET_MASTER_ACCOUNT_ID: '', - }; - ``` +```bash +NEAR_WORKSPACES_NETWORK=testnet TESTNET_MASTER_ACCOUNT_ID= node test.js +``` - The [near-workspaces/ava.testnet.config.cjs](https://github.com/near/workspaces-js/blob/main/ava.testnet.config.cjs) import sets the `NEAR_WORKSPACES_NETWORK` environment variable for you. A benefit of this approach is that you can then easily ignore files that should only run in Sandbox mode. +If you set this environment variables and pass `{network: 'testnet', testnetMasterAccountId: }` to `Worker.init`, the config object takes precedence. - Now you'll also want to add a `test:testnet` script to your `package.json`'s `scripts` section: +### 3. Custom Config File (`near-workspaces` with AVA): - ```diff - "scripts": { - "test": "ava", - + "test:testnet": "ava --config ./ava.testnet.config.cjs" - } - ``` +Create a `ava.testnet.config.cjs` in the same directory as your `package.json`: +```js +module.exports = { + ...require("near-workspaces/ava.testnet.config.cjs"), + ...require("./ava.config.cjs"), +}; +module.exports.environmentVariables = { + TESTNET_MASTER_ACCOUNT_ID: "", +}; +``` -Stepping through a testnet example ----------------------------------- +The [near-workspaces/ava.testnet.config.cjs](https://github.com/near/workspaces-js/blob/main/ava.testnet.config.cjs) import sets the `NEAR_WORKSPACES_NETWORK` environment variable for you. A benefit of this approach is that you can then easily ignore files that should only run in Sandbox mode. -Let's revisit a shortened version of the example from How It Works above, describing what will happen in Testnet. +Add a `test:testnet` script to your `package.json`: -1. Create a `Worker`. +```diff + "scripts": { + "test": "ava", ++ "test:testnet": "ava --config ./ava.testnet.config.cjs" + } +``` - ```ts - const worker = await Worker.init(); - ``` +## Example of Testnet Usage - `Worker.init` creates a unique testnet account as root account. +### 1. Create a `Worker`. -2. Write tests. +```ts +const worker = await Worker.init(); +``` - ```ts - await Promise.all([ - async () => { - await alice.call( - contract, - 'some_update_function', - {some_string_argument: 'cool', some_number_argument: 42} - ); - const result = await contract.view( - 'some_view_function', - {account_id: alice} - ); - assert.equal(result, 'whatever'); - }, - async () => { - const result = await contract.view( - 'some_view_function', - {account_id: alice} - ); - assert.equal(result, 'some default'); - } - ]); - ``` +`Worker.init` creates a unique testnet account as root account. -Note: Sometimes account creation rate limits are reached on testnet, simply wait a little while and try again. +### 2. Write tests. -Running tests only in Sandbox -------------------------------- +```ts +await Promise.all([ + async () => { + await alice.call(contract, "some_update_function", { + some_string_argument: "cool", + some_number_argument: 42, + }); + const result = await contract.view("some_view_function", { + account_id: alice, + }); + assert.equal(result, "whatever"); + }, + async () => { + const result = await contract.view("some_view_function", { + account_id: alice, + }); + assert.equal(result, "some default"); + }, +]); +``` -If some of your runs take advantage of Sandbox-specific features, you can skip these on testnet in two ways: +Note: Sometimes account creation rate limits are reached on `testnet`, simply wait a little while and try again. -1. You can skip entire sections of your files by checking `getNetworkFromEnv() === 'sandbox'`. +## Running tests only in Sandbox - ```ts - let worker = Worker.init(); - // things make sense to any network - const root = worker.rootAccount; - const alice = await root.createSubAccount('alice'); +If some of your runs take advantage of Sandbox-specific features, you can skip these on testnet in two ways: +#### 1. You can skip entire sections of your files by checking `getNetworkFromEnv() === 'sandbox'`. - if (getNetworkFromEnv() === 'sandbox') { - // thing that only makes sense with sandbox - } - ``` +```ts +let worker = Worker.init(); +// things make sense to any network +const root = worker.rootAccount; +const alice = await root.createSubAccount("alice"); + +if (getNetworkFromEnv() === "sandbox") { + // thing that only makes sense with sandbox +} +``` -2. Use a separate testnet config file, as described under the "Running on Testnet" heading above. Specify test files to include and exclude in config file. +#### 2. Use a separate `testnet` config file, as described under the `"Running on Testnet"` heading above. Specify test files to include and exclude in config file. -Patch State on the Fly -====================== +# Patch State on the Fly In Sandbox-mode, you can add or modify any contract state, contract code, account or access key with `patchState`. @@ -272,8 +291,7 @@ It is true that you can alter contract code, accounts, and access keys using nor To see an example of how to do this, see the [patch-state test](https://github.com/near/workspaces-js/blob/main/__tests__/02.patch-state.ava.ts). -Time Traveling -=============== +# Time Traveling In Sandbox-mode, you can forward time-related state (the block height, timestamp and epoch height) with `fastForward`. @@ -284,33 +302,31 @@ For an example, see the [fast-forward test](./__tests__/08.fast-forward.ava.ts) Note: `fastForward` does not speed up an in-flight transactions. -Pro Tips -======== +# Pro Tips -* `NEAR_WORKSPACES_DEBUG=true` – run tests with this environment variable set to get copious debug output and a full log file for each Sandbox instance. +- `NEAR_WORKSPACES_DEBUG=true` – run tests with this environment variable set to get copious debug output and a full log file for each Sandbox instance. -* `Worker.init` [config](https://github.com/near/workspaces-js/blob/main/packages/js/src/interfaces.ts) – you can pass a config object as the first argument to `Worker.init`. This lets you do things like: +- `Worker.init` [config](https://github.com/near/workspaces-js/blob/main/packages/js/src/interfaces.ts) – you can pass a config object as the first argument to `Worker.init`. This lets you do things like: - * skip initialization if specified data directory already exists (the default behavior) + - skip initialization if specified data directory already exists (the default behavior) ```ts - Worker.init( - { rm: false, homeDir: './test-data/alice-owns-an-nft' }, - ) + Worker.init({ rm: false, homeDir: "./test-data/alice-owns-an-nft" }); ``` - * always recreate such data directory instead with `rm: true` + - always recreate such data directory instead with `rm: true` + + - specify which port to run on - * specify which port to run on + - and more! - * and more! +# Env variables -Env variables -======== ```text NEAR_CLI_MAINNET_RPC_SERVER_URL NEAR_CLI_TESTNET_RPC_SERVER_URL ``` + Clear them in case you want to get back to the default RPC server. Example: @@ -318,4 +334,90 @@ Example: ```shell export NEAR_CLI_MAINNET_RPC_SERVER_URL= ``` + here is a testcase: [jsonrpc.ava.js](./packages/js/__tests__/jsonrpc.ava.js) + +## Example for `near-workspaces-js` with `near-sdk-js`. + +This guide will walk you through setting up and running `tests` for your `NEAR` contracts using `near-workspaces-js` in a `near-sdk-js` project. We will use the `benchmark` package from `near-sdk-js`, which includes scripts for building contracts, running tests, and generating reports. + +`near-sdk-js` utilizes `ava` package for testing, so we will use it for the example: + +1. Set up - Make sure `ava` and `near-workspaces` are added in your project. + +2. Worker instance - import and Initialize a `Worker`. + +3. Root account - set the `.rootAccount` from the `Worker`. + +4. Create the sub accounts, command is - `.createSubAccount`. + +5. Deploy the contracts, the commands are - `root.devDeploy` or `.deploy` + +6. Test framework integration - in our case `ava` contains a `test.context`, in there a data can be saved and reused in the other tests. + +7. Tearing down - At the end of test, call `await worker.tearDown()` to shut down the `Worker` if needed. + +#### Example test structure: + +```ts +/** Set up */ +import { Worker } from "near-workspaces"; +import test from "ava"; +import { generateGasObject, logTestResults } from "./util.js"; +import { addTestResults } from "./results-store.js"; + +test.before(async (t) => { + /** Worker instance */ + const worker = await Worker.init(); + /** Root account */ + const root = worker.rootAccount; + + /** Create the sub accounts */ + const callerContract = await root.createSubAccount("caller", { + initialBalance: "1000N", + }); + /** Deploy the contracts */ + await callerContract.deploy("build/deploy-contract.wasm"); + + const callerContractRs = await root.createSubAccount("callrs", { + initialBalance: "1000N", + }); + await callerContractRs.deploy("res/deploy_contract.wasm"); + + const ali = await root.createSubAccount("ali"); + const bob = await root.createSubAccount("bob"); + const carl = await root.createSubAccount("carl"); + + /** Test framework integration */ + t.context.worker = worker; + t.context.accounts = { + root, + callerContract, + ali, + bob, + carl, + callerContractRs, + }; +}); + +test("JS promise batch deploy contract and call", async (t) => { + const { bob, callerContract } = t.context.accounts; + + let r = await bob.callRaw(callerContract, "deploy_contract", "", { + gas: "300 Tgas", + }); + + let deployed = callerContract.getSubAccount("a"); + + t.deepEqual(JSON.parse(Buffer.from(r.result.status.SuccessValue, "base64")), { + currentAccountId: deployed.accountId, + signerAccountId: bob.accountId, + predecessorAccountId: callerContract.accountId, + input: "abc", + }); + + logTestResults(r); + const gasObject = generateGasObject(r); + addTestResults("JS_promise_batch_deploy_contract_and_call", gasObject); +}); +``` diff --git a/packages/js/README.md b/packages/js/README.md index 816f7449..394b409e 100644 --- a/packages/js/README.md +++ b/packages/js/README.md @@ -1,63 +1,105 @@
-

NEAR Workspaces (TypeScript/JavaScript Edition)

- [![Project license](https://img.shields.io/badge/license-Apache2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) - [![Project license](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) - [![Discord](https://img.shields.io/discord/490367152054992913?label=discord)](https://discord.gg/Vyp7ETM) - [![NPM version](https://img.shields.io/npm/v/near-workspaces.svg?style=flat-square)](https://npmjs.com/near-workspaces) - [![Size on NPM](https://img.shields.io/bundlephobia/minzip/near-workspaces.svg?style=flat-square)](https://npmjs.com/near-workspaces) +[![Project license](https://img.shields.io/badge/license-Apache2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Project license](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) +[![Discord](https://img.shields.io/discord/490367152054992913?label=discord)](https://discord.gg/Vyp7ETM) +[![NPM version](https://img.shields.io/npm/v/near-workspaces.svg?style=flat-square)](https://npmjs.com/near-workspaces) +[![Size on NPM](https://img.shields.io/bundlephobia/minzip/near-workspaces.svg?style=flat-square)](https://npmjs.com/near-workspaces)
-`NEAR Workspaces` is a library for automating workflows and writing tests for NEAR smart contracts. You can use it as is or integrate with test runner of your choise (AVA, Jest, Mocha, etc.). If you don't have a preference, we suggest you to use AVA. - -Quick Start (without testing frameworks) -=========== -To get started with `Near Workspaces` you need to do only two things: - -1. Initialize a `Worker`. - - ```ts - const worker = await Worker.init(); - const root = worker.rootAccount; - - const alice = await root.createSubAccount('alice'); - const contract = await root.devDeploy('path/to/compiled.wasm'); - ``` - -2. Writing tests. - - `near-workspaces` is designed for concurrency. Here's a simple way to get concurrent runs using plain JS: - - ```ts - import {strict as assert} from 'assert'; - - await Promise.all([ - async () => { - await alice.call( - contract, - 'some_update_function', - {some_string_argument: 'cool', some_number_argument: 42} - ); - const result = await contract.view( - 'some_view_function', - {account_id: alice} - ); - assert.equal(result, 'whatever'); - }, - async () => { - const result = await contract.view( - 'some_view_function', - {account_id: alice} - ); - /* Note that we expect the value returned from `some_view_function` to be - a default here, because this `fork` runs *at the same time* as the - previous, in a separate local blockchain */ - assert.equal(result, 'some default'); - } - ]); - ``` - ``` - -More info in our main README: https://github.com/near/workspaces-js +`NEAR Workspaces` is a library for automating workflows and writing tests for NEAR smart contracts. It can be used standalone or integrated with test runners like AVA, Jest, or Mocha. If you don't have a preference, we recommend AVA. + +## Quick Start (without testing frameworks) + +### 1. Initialize a `Worker`. + +```ts +const worker = await Worker.init(); +const root = worker.rootAccount; + +const alice = await root.createSubAccount("alice"); +const contract = await root.devDeploy("path/to/compiled.wasm"); +``` + +#### Step-by-Step Explanation + +**_1. Worker Initialization:_** + +- `Worker.init` initializes a new `SandboxWorker` or `TestnetWorker` depending on the config. `SandboxWorker` contains [NEAR Sandbox](https://github.com/near/sandbox), which is essentially a local mini-NEAR blockchain. You can create one `Worker` per test to get its own data directory and port (for Sandbox) or root account (for Testnet), so that tests can run in parallel without race conditions in accessing states. +- If there's no state intervention. you can also reuse the `Worker` to speedup the tests. + +**_2. Root Account:_** + +- The worker has a `root` account. For `SandboxWorker`, it's `test.near`. For `TestnetWorker`, it creates a unique account. The following accounts are created as subaccounts of the root account. The name of the account will change from different runs, so you should not refer to them by hard coded account name. You can access them via the account object, such as `root`, `alice` and `contract` above. + +**_3. Creating Subaccounts:_** + +- `root.createSubAccount` creates a new subaccount of `root` with the given name, for example `alice.`. + +**_4. Deploying Contracts:_** + +- `root.devDeploy` creates an account with random name, then deploys the specified Wasm file to it. + +**_5. Path Resolution:_** + +- `path/to/compiled.wasm` will resolve relative to your project root. That is, the nearest directory with a `package.json` file, or your current working directory if no `package.json` is found. To construct a path relative to your test file, you can use `path.join(__dirname, '../etc/etc.wasm')` ([more info](https://nodejs.org/api/path.html#path_path_join_paths)). + +**_6. Data Directory:_** + +- `worker` contains a reference to this data directory, so that multiple tests can use it as a starting point. + +**_7. Test Framework Integration:_** + +- If you're using a test framework, you can save the `worker` object and account objects `root`, `alice`, `contract` to test context to reuse them in subsequent tests. + +**_8. Tearing Down:_** + +- At the end of test, call `await worker.tearDown()` to shuts down the Worker. It gracefully shuts down the Sandbox instance it ran in the background. However, it keeps the data directory around. That's what stores the state of the two accounts that were created (`alice` and `contract-account-name` with its deployed contract). + +### 2. Writing Tests. + +`near-workspaces` is designed for concurrency. Here's a simple way to get concurrent runs using plain JS: + +```ts +import { strict as assert } from "assert"; + +await Promise.all([ + async () => { + await alice.call(contract, "some_update_function", { + some_string_argument: "cool", + some_number_argument: 42, + }); + const result = await contract.view("some_view_function", { + account_id: alice, + }); + assert.equal(result, "whatever"); + }, + async () => { + const result = await contract.view("some_view_function", { + account_id: alice, + }); + /* Note that we expect the value returned from `some_view_function` to be + a default here, because this `fork` runs *at the same time* as the + previous, in a separate local blockchain */ + assert.equal(result, "some default"); + }, +]); +``` + +#### Step-by-Step Explanation + +**_1. Worker and Accounts:_** + +- `worker` and accounts such as`alice` are created before. + +**_2. Calling Contract Functions:_** + +- `call` syntax mirrors [near-cli](https://github.com/near/near-cli) and either returns the successful return value of the given function or throws the encountered error. If you want to inspect a full transaction and/or avoid the `throw` behavior, you can use `callRaw` instead. + +**_3. Viewing Contract Functions::_** + +- While `call` is invoked on the account _doing the call_ (`alice.call(contract, …)`), `view` is invoked on the account _being viewed_ (`contract.view(…)`). This is because the caller of a view is irrelevant and ignored. + +More info in our main [README](https://github.com/near/near-workspaces-js/blob/main/README.md)