diff --git a/.gitignore b/.gitignore
index 1fa415f..319a083 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,18 @@ Jenkinsfile
install.sh
uninstall.sh
/.github/workflows/
+/testApp/node_modules/
+/testApp/suite/_/assets.js
+/testApp/suite/_/binaries.js
+/testApp/suite/_/giver.js
+/testApp/suite/_/init-tests.js
+/testApp/suite/aggregations.js
+/testApp/suite/auth.js
+/testApp/suite/client.js
+/testApp/suite/contracts.js
+/testApp/suite/crypto.js
+/testApp/suite/deploy-ex.js
+/testApp/suite/queries.js
+/testApp/suite/run-local.js
+/testApp/suite/test-error-messages.js
+/testApp/package-lock.json
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..80ef41d
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,10 @@
+.DS_Store
+.gitignore
+.gitattributes
+**/.idea/
+node_modules/
+package-lock.json
+**/*.so
+**/*.a
+/.github/workflows/
+testApp/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04b4178..65981bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,12 @@
# Release Notes
All notable changes to this project will be documented in this file.
+## 0.25.0 - Jul 8, 2020
+
+### New
+- `testApp` to run main js test suite inside puppeteer
+- supports for core contexts
+
## 0.19.1 - January 29, 2020
### Fixed
diff --git a/README.md b/README.md
index c3a785e..05379e4 100644
--- a/README.md
+++ b/README.md
@@ -4,27 +4,13 @@ TON Labs SDK Client Library for Web
![npm publish](https://github.com/tonlabs/ton-client-web-js/workflows/npm%20publish/badge.svg)
-## Run an example:
-* Download and run an example:
-```
-$ git clone https://github.com/tonlabs/ton-client-web-js.git
-$ cd ton-client-web-js
-$ npm install
-$ cd example
-$ npm install
-$ npm run web
-```
-* Open http://localhost:4000/ in the browser
-
-This example connects to net.ton.dev and returns the top 10 accounts holding the most grams (filtered by balance, top to bottom).
-
+# Using `ton-client-web-js`
-## Using ton-client-web-js
+## Requirements
-### Requirements
-You can run ton-client-web-js in a browser.
+You can run `ton-client-web-js` in a browser.
-In this example, we'll use webpack to manage assets. Since it requires[Node.js](nodejs.org), you’ll need to install the latest version.
+In this example, we'll use webpack to manage assets. Since it requires [Node.js](nodejs.org), you’ll need to install the latest version.
Let's start with a clean [webpack](https://webpack.js.org/) project:
@@ -132,4 +118,4 @@ See [https://docs.ton.dev/86757ecb2/p/92b041-overview](https://docs.ton.dev/8675
---
-Copyright 2018-2020 TON DEV SOLUTIONS LTD.
\ No newline at end of file
+Copyright 2018-2020 TON DEV SOLUTIONS LTD.
diff --git a/example/index.js b/example/index.js
deleted file mode 100644
index 161ddc8..0000000
--- a/example/index.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { TONClient, setWasmOptions } from 'ton-client-web-js';
-
-function debugLog(message) {
- document.body.insertAdjacentHTML("beforeend", `
${message}
`);
-}
-
-window.addEventListener('load', () => {
- (async () => {
- setWasmOptions({
- debugLog,
- });
- let createStart = Date.now();
- const client = await TONClient.create({
- servers: ['net.ton.dev']
- });
- debugLog(`Client creation time: ${(Date.now() - createStart)}`);
- debugLog(`Client uses binary version: ${await client.config.getVersion()}`);
- debugLog(`Client connected to: ${await client.config.data.servers}`);
- const queryStart = Date.now();
- const accounts = await client.queries.accounts.query({}, 'id balance(format:DEC)', [{path:'balance', direction:'DESC'}], 10);
- debugLog(`Query time: ${(Date.now() - queryStart)}`);
- debugLog(`${accounts.map(x => `${x.id} | ${x.balance} |
`).join('')}
`);
- debugLog(`Now is: ${new Date()}`);
- debugLog(`sha512: ${await client.crypto.sha512({text:'text'})}`);
- debugLog(`random: ${await client.crypto.randomGenerateBytes(12)}`);
- })();
-});
diff --git a/example/web/server.py b/example/web/server.py
deleted file mode 100644
index 0a1e78d..0000000
--- a/example/web/server.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import SimpleHTTPServer
-import SocketServer
-
-PORT = 8888
-
-Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
-Handler.extensions_map.update({
- '.wasm': 'application/wasm',
-})
-
-httpd = SocketServer.TCPServer(("", PORT), Handler)
-
-print "serving at port", PORT
-httpd.serve_forever()
diff --git a/install.js b/install.js
index d3e3da8..b809437 100644
--- a/install.js
+++ b/install.js
@@ -4,7 +4,7 @@ const http = require('http');
const zlib = require('zlib');
const {version} = require('./package.json');
-const bv = process.env.TON_SDK_BIN_VERSION || (version).split('.')[0];
+const bv = process.env.TON_SDK_BIN_VERSION || version.split('.')[0];
const root = process.cwd();
const binariesHost = 'sdkbinaries-ws.tonlabs.io';
@@ -69,14 +69,37 @@ function downloadAndGunzip(dest, url) {
}
+// dev_dl - used when this package installed during CI phase or development process.
+// Binaries will be copied from location specified by TC_BIN_SRC environment variable.
+function dev_dl(dst, binSrc) {
+ const srcPath = path.resolve(binSrc, dst);
+ if (!fs.existsSync(srcPath)) {
+ process.stdout.write(`Skipping ${dst} from ${srcPath} ...\n`);
+ return;
+ }
+ process.stdout.write(`Copying ${dst} from ${srcPath} ...\n`);
+ const dstPath = path.resolve(root, dst);
+ const dstDir = path.dirname(path.resolve(dstPath));
+ if (!fs.existsSync(dstDir)) {
+ fs.mkdirSync(dstDir, { recursive: true });
+ }
+ fs.copyFileSync(srcPath, dstPath);
+}
+
+
async function dl(dst, src) {
- const dst_path = `${root}/${dst}`;
- const src_url = `http://${binariesHost}/${src}.gz`;
- process.stdout.write(`Downloading from ${src_url} to ${dst_path} ...`);
- await downloadAndGunzip(dst_path, src_url);
+ if ((process.env.TC_BIN_SRC || '') !== '') {
+ dev_dl(dst, process.env.TC_BIN_SRC);
+ return;
+ }
+ const dstPath = path.resolve(root, dst);
+ const srcUrl = `http://${binariesHost}/${src}.gz`;
+ process.stdout.write(`Downloading from ${srcUrl} to ${dstPath} ...`);
+ await downloadAndGunzip(dstPath, srcUrl);
process.stdout.write('\n');
}
+
async function main() {
await dl('tonclient.wasm', `tonclient_${bv}_wasm`);
await dl('index.js', `tonclient_${bv}_wasm_js`);
diff --git a/package.json b/package.json
index ba0ed1e..72865e1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ton-client-web-js",
- "version": "0.24.0",
+ "version": "0.25.0",
"description": "TON Client Library for Web",
"scripts": {
"install": "node install.js"
@@ -14,10 +14,10 @@
"author": "TON DEV SOLUTIONS LTD.",
"license": "Apache-2.0",
"licenses": [
- {
- "type": "Apache-2.0",
- "url": "http://www.apache.org/licenses/LICENSE-2.0"
- }
+ {
+ "type": "Apache-2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ }
],
"repository": {
"type": "git",
@@ -25,7 +25,7 @@
},
"homepage": "https://ton.dev/node-se",
"dependencies": {
- "ton-client-js": "^0.24.0"
+ "ton-client-js": "^0"
},
"main": "index.js"
}
diff --git a/testApp/README.md b/testApp/README.md
new file mode 100644
index 0000000..65d0f03
--- /dev/null
+++ b/testApp/README.md
@@ -0,0 +1,32 @@
+# ton-client-web-js
+
+TON Labs Client Library for Web Test App
+
+This project contains the `testApp` that runs the main test suite from `ton-client-js`.
+
+# Requirements
+
+Common:
+- NodeJs (at least 12)
+
+# Run Tests
+
+```shell script
+npm install
+node prepare-suite.js
+node run-suite.js
+```
+
+# Options
+
+There are some options for preparing and testing:
+- `ton-client-js` package besided of the `ton-client-web-js` folder – if it exists then this local package will be used instead of the npm version (local `ton-client-js` will be packed with `npm pack` and then installed into the `testApp` package). This may be useful for running tests with a non published version of the `ton-client-js`.
+- `USE_NODE_SE` and `TON_NETWORK_ADDRESS` env variables – used to specify blockchain network for tests. Must be set before `prepare-suite.js` script execution.
+- `TC_BIN_SRC` env variable – can be used to specify a local path of a folder with alternative WASM binaries. Variable must be set before `prepare-suite.js` execution. This may be useful for running tests with a non published version of the core library. The required content for this folder is:
+ - `tonclient.wasm`
+ - `index.js`
+- `TON-SDK` project besided of the `ton-client-web-js` folder – if it exists and `TC_BIN_SRC` doesn't specified then `TC_BIN_SRC` will be set to build output for the WASM core library. This may be useful for running tests with a non published version of the core library.
+
+
+---
+Copyright 2018-2020 TON DEV SOLUTIONS LTD.
diff --git a/example/babel.config.js b/testApp/babel.config.js
similarity index 100%
rename from example/babel.config.js
rename to testApp/babel.config.js
diff --git a/testApp/index.js b/testApp/index.js
new file mode 100644
index 0000000..4654611
--- /dev/null
+++ b/testApp/index.js
@@ -0,0 +1,5 @@
+import {startTests} from "./suite/_/run";
+
+window.addEventListener('load', () => {
+ startTests(() => {});
+});
diff --git a/example/package.json b/testApp/package.json
similarity index 84%
rename from example/package.json
rename to testApp/package.json
index 4a6c6c5..eb699c8 100644
--- a/example/package.json
+++ b/testApp/package.json
@@ -1,10 +1,10 @@
{
- "name": "ton-client-web-js-example",
- "version": "1.0.0",
- "description": "TON Client Library for Web - Example",
+ "name": "ton-client-web-js-test-suite-runner",
+ "version": "0.25.0",
+ "description": "TON Client Library for Web - Test Suite Runner",
"scripts": {
- "nmp install": "npm install",
- "web": "webpack-dev-server -d --config webpack.config.js --progress --colors --host 0.0.0.0",
+ "npm install": "npm install",
+ "web": "webpack-dev-server -d --config webpack.config.js --progress --colors --host 127.0.0.1",
"web:bundle": "webpack -p --config webpack.config.js --progress --colors --display-error-details"
},
"keywords": [
@@ -21,9 +21,15 @@
},
"homepage": "https://ton.dev/node-se",
"dependencies": {
+ "error-polyfill": "^0.1.2",
+ "javascript-biginteger": "^0.9.2",
+ "jest-lite": "^1.0.0-alpha.4",
+ "opentracing": "^0.14.4",
+ "ton-client-js": "^0",
"ton-client-web-js": "../"
},
"devDependencies": {
+ "puppeteer": "^4.0.1",
"webpack": "4.41.5",
"webpack-cli": "3.3.10",
"webpack-dev-server": "3.10.1",
diff --git a/testApp/prepare-suite.js b/testApp/prepare-suite.js
new file mode 100644
index 0000000..0c27459
--- /dev/null
+++ b/testApp/prepare-suite.js
@@ -0,0 +1,200 @@
+const os = require('os');
+const { spawn } = require('child_process');
+const fs = require('fs');
+const path = require('path');
+
+const srcTestsPath = path.resolve(__dirname, 'node_modules', 'ton-client-js', '__tests__');
+const dstTestsPath = path.resolve(__dirname, 'suite');
+const coreSourcePath = path.resolve(__dirname, '..', '..', 'TON-SDK', 'ton_client');
+const runEnv = { ...process.env };
+if (!runEnv.TC_BIN_SRC && fs.existsSync(coreSourcePath)) {
+ runEnv.TC_BIN_SRC = path.resolve(coreSourcePath, 'platforms', 'ton-client-web', 'build');
+}
+
+
+function run(name, ...args) {
+ return new Promise((resolve, reject) => {
+ try {
+ const isWindows = os.platform() === 'win32';
+ const spawned = isWindows
+ ? spawn('cmd.exe', ['/c', name].concat(args), {
+ env: runEnv,
+ })
+ : spawn(name, args, {
+ env: runEnv,
+ });
+ const errors = [];
+ const output = [];
+
+ spawned.stdout.on('data', function (data) {
+ output.push(data);
+ process.stdout.write(data);
+ });
+
+ spawned.stderr.on('data', (data) => {
+ errors.push(data);
+ process.stderr.write(data.toString());
+ });
+
+ spawned.on('error', (err) => {
+ reject(err);
+ });
+
+ spawned.on('close', (code) => {
+ if (code === 0) {
+ resolve(output.join(''));
+ } else {
+ reject(errors.join(''));
+ }
+ });
+ } catch (error) {
+ reject(error);
+ }
+ });
+}
+
+function replaceLocalhost(address) {
+ const ipv4 = Object.values(os.networkInterfaces())
+ .reduce((acc, x) => acc.concat(x), [])
+ .find(x => x.family === 'IPv4' && !x.internal);
+ const ip = ipv4 && ipv4.address;
+ return address
+ .replace(/(.*\D)?0\.0\.0\.0(\D.*)?/g, `$1${ip}$2`)
+ .replace(/(.*\D)?localhost(\D.*)?/g, `$1${ip}$2`)
+}
+
+function extIs(file, ext) {
+ return path.extname(file).toLowerCase() === ext;
+}
+
+function getTgzNames() {
+ return fs.readdirSync(__dirname).filter(x => extIs(x, '.tgz'));
+}
+
+function copyTestSuiteFile(source, targetDir, convert) {
+ const target = path.resolve(targetDir, path.basename(source));
+ const content = convert(source, fs.readFileSync(source));
+ if (content !== null && content !== undefined) {
+ if (!fs.existsSync(targetDir)) {
+ fs.mkdirSync(targetDir, { recursive: true });
+ }
+ fs.writeFileSync(target, content);
+ }
+}
+
+function copyTestSuiteFolder(sourceDir, targetDir, convert) {
+ fs.readdirSync(sourceDir).forEach((file) => {
+ const source = path.resolve(sourceDir, file);
+ if (fs.lstatSync(source).isDirectory()) {
+ copyTestSuiteFolder(source, path.resolve(targetDir, file), convert);
+ } else {
+ copyTestSuiteFile(source, targetDir, convert);
+ }
+ });
+}
+
+function replaceSection(filePath, section, content) {
+ const sectionMatch = new RegExp(`(\/\/${section})[^]*(\/\/${section})`, 'g');
+ const script = fs.readFileSync(filePath, { encoding: 'utf8' }).toString()
+ .replace(sectionMatch, `$1\n${content}\n$2`);
+ fs.writeFileSync(filePath, script, { encoding: 'utf8' });
+
+}
+
+function rewriteRunScript() {
+ const imports = [];
+ fs.readdirSync(srcTestsPath).forEach((file) => {
+ if (extIs(file, '.js')) {
+ imports.push(`import ${file.slice(0, -3).replace(/-/g, '_')}_suite from '../${file}';`);
+ }
+ });
+
+ const giverKeysPath = path.resolve(os.homedir(), 'giverKeys.json');
+ const giverKeys = fs.existsSync(giverKeysPath)
+ ? JSON.parse(fs.readFileSync(giverKeysPath, { encoding: 'utf8' }))
+ : null;
+ const assets = [
+ `export default {`,
+ ` env: {`,
+ ` USE_NODE_SE: '${process.env.USE_NODE_SE || 'true'}',`,
+ ` TON_NETWORK_ADDRESS: '${replaceLocalhost(process.env.TON_NETWORK_ADDRESS || 'http://127.0.0.1:8080')}',`,
+ ` },`,
+ ` giverKeys: ${JSON.stringify(giverKeys)},`,
+ ` contracts: {`,
+ ];
+ const collectContracts = (abiVersion) => {
+ const dir = path.resolve(srcTestsPath, 'contracts', `abi_v${abiVersion}`);
+ fs.readdirSync(dir).forEach((file) => {
+ if (!extIs(file, '.tvc')) {
+ return;
+ }
+ const name = file.slice(0, -4);
+ const abi = path.resolve(dir, `${name}.abi.json`);
+ const tvc = path.resolve(dir, `${name}.tvc`);
+ assets.push(
+ ` '${name}-${abiVersion}': {`,
+ ` abi: ${fs.readFileSync(abi, 'utf8').trim().split('\n').join('').split('\t').join('')},`,
+ ` imageBase64: '${fs.readFileSync(tvc).toString('base64')}',`,
+ ` },`,
+ );
+ });
+ };
+ [1, 2].forEach((abiVersion) => {
+ collectContracts(abiVersion);
+ });
+ assets.push(
+ ' },',
+ '};',
+ );
+ replaceSection(path.resolve(dstTestsPath, '_', 'run.js'), 'IMPORTS', imports.join('\n'));
+ fs.writeFileSync(path.resolve(dstTestsPath, '_', 'assets.js'), assets.join('\n'), { encoding: 'utf8' });
+}
+
+function copyTestSuite() {
+ copyTestSuiteFolder(srcTestsPath, dstTestsPath, (srcPath, content) => {
+ const name = path.basename(srcPath);
+ const skipFile = !extIs(name, '.js')
+ || name.toLowerCase() === 'testing-platform.js';
+ if (skipFile) {
+ return null;
+ }
+ const converted = content.toString()
+ .replace(/(from\s+["'])\.\.\/\.\.(\/src\/)/gm, '$1ton-client-js$2')
+ .replace(/(from\s+["'])\.\.(\/src\/)/gm, '$1ton-client-js$2')
+ .replace(/(from\s+["'])\.\.\/\.\.(\/types)/gm, '$1ton-client-js$2')
+ .replace(/(from\s+["'])\.\.(\/types)/gm, '$1ton-client-js$2')
+ .replace(/_000/gm, '000');
+ return Buffer.from(converted, 'utf8');
+ });
+ rewriteRunScript();
+}
+
+function removeTgzFiles() {
+ for (const tgz of getTgzNames()) {
+ console.log('Remove', tgz);
+ fs.unlinkSync(path.resolve(__dirname, tgz));
+ }
+}
+
+(async () => {
+ console.log('Save', 'package.json');
+ const packageJson = fs.readFileSync(path.resolve(__dirname, 'package.json'));
+ try {
+ removeTgzFiles();
+
+ if (fs.existsSync(path.resolve(__dirname, '..', '..', 'ton-client-js'))) {
+ await run('npm', 'pack', '../../ton-client-js');
+ }
+ await run('npm', 'pack', '../');
+
+ const tgzNames = getTgzNames();
+ console.log('Install', tgzNames.join(' '));
+ await run('npm', 'install', '--force', ...tgzNames);
+
+ removeTgzFiles();
+ copyTestSuite();
+ } finally {
+ console.log('Restore', 'package.json');
+ fs.writeFileSync(path.resolve(__dirname, 'package.json'), packageJson);
+ }
+})();
diff --git a/testApp/run-suite.js b/testApp/run-suite.js
new file mode 100644
index 0000000..98a9cb9
--- /dev/null
+++ b/testApp/run-suite.js
@@ -0,0 +1,146 @@
+const os = require('os');
+const runEnv = { ...process.env };
+const {spawn} = require('child_process');
+const puppeteer = require('puppeteer');
+const path = require('path');
+
+function run(name, args, onOutput, onError) {
+ return new Promise((resolve, reject) => {
+ try {
+ const isWindows = os.platform() === 'win32';
+ const spawned = isWindows
+ ? spawn('cmd.exe', ['/c', name].concat(args), {
+ env: runEnv,
+ })
+ : spawn(name, args, {
+ env: runEnv,
+ });
+ const errors = [];
+ const output = [];
+
+ spawned.stdout.on('data', function (data) {
+ const text = data.toString();
+ output.push(text);
+ onOutput(text);
+ });
+
+ spawned.stderr.on('data', (data) => {
+ const text = data.toString();
+ errors.push(text);
+ if (onError) {
+ onError(text);
+ } else {
+ process.stderr.write(text);
+ }
+ });
+
+ spawned.on('error', (err) => {
+ reject(err);
+ });
+
+ spawned.on('close', (code) => {
+ if (code === 0) {
+ resolve(output.join(''));
+ } else {
+ reject(errors.join(''));
+ }
+ });
+ } catch (error) {
+ reject(error);
+ }
+ });
+}
+
+function extractReport(expect, line) {
+ const pos = line.toString().indexOf(expect);
+ if (pos < 0) {
+ return null;
+ }
+ return JSON.parse(line.substr(pos + expect.length));
+}
+
+let success = 0;
+let failure = 0;
+
+function onOutputLine(line) {
+ const startLog = extractReport('[TEST_START]', line);
+ if (startLog) {
+ return;
+ }
+ const successLog = extractReport('[TEST_SUCCESS]', line);
+ if (successLog) {
+ success += 1;
+ console.log(`✓ ${successLog.name} (${success} / ${failure})`);
+ return;
+ }
+ const failureLog = extractReport('[TEST_FAILURE]', line);
+ if (failureLog) {
+ failure += 1;
+ console.log(`\x1b[0;31m𐄂 ${failureLog.name} (${success} / ${failure}) - ${JSON.stringify(failureLog.error, undefined, ' ')}\x1b[m`);
+ return;
+ }
+ const completeLog = extractReport('[TEST_COMPLETE]', line);
+ if (completeLog) {
+ console.log(`---`);
+ console.log(`success: ${success}`);
+ console.log(`failure: ${failure}`);
+ process.exit(failure > 0 ? 1 : 0);
+ }
+ console.log(line);
+}
+
+let logText = '';
+
+function onTestLog(text) {
+ if (text.indexOf('\n') < 0) {
+ logText += text;
+ return;
+ }
+ const lines = (logText + text).split('\n');
+ logText = lines[lines.length - 1];
+ lines.slice(0, -1).forEach(onOutputLine);
+}
+
+function startWebPackDevServer() {
+ return new Promise((resolve, reject) => {
+ run(
+ path.resolve(__dirname, 'node_modules', '.bin', 'webpack-dev-server'),
+ ['-d', '--config', 'webpack.config.js', '--progress', '--colors', '--host', '127.0.0.1'],
+ (text) => {
+ if (text.includes(': Compiled successfully.')) {
+ resolve();
+ }
+ },
+ () => {
+ },
+ ).catch(reject);
+ });
+}
+
+async function main() {
+ await startWebPackDevServer();
+ const browser = await puppeteer.launch();
+ const page = await browser.newPage();
+ page.on('console', (msg) => {
+ const text = `${msg.text()}\n`;
+ onTestLog(text);
+ if (text.includes('[TEST_COMPLETE]')) {
+ (async () => {
+ await page.close();
+ await browser.close();
+ process.exit(0);
+ })();
+ }
+ });
+ await page.goto('http://localhost:4000', {});
+}
+
+
+(async () => {
+ try {
+ await main();
+ } catch (error) {
+ console.log(error);
+ process.exit(1);
+ }
+})();
diff --git a/testApp/suite/_/run.js b/testApp/suite/_/run.js
new file mode 100644
index 0000000..b31288f
--- /dev/null
+++ b/testApp/suite/_/run.js
@@ -0,0 +1,82 @@
+import { BigInteger as bigInt } from 'javascript-biginteger';
+import { tests } from './init-tests';
+
+//IMPORTS
+import aggregations_suite from '../aggregations.js';
+import auth_suite from '../auth.js';
+import client_suite from '../client.js';
+import contracts_suite from '../contracts.js';
+import crypto_suite from '../crypto.js';
+import deploy_ex_suite from '../deploy-ex.js';
+import queries_suite from '../queries.js';
+import run_local_suite from '../run-local.js';
+import test_error_messages_suite from '../test-error-messages.js';
+//IMPORTS
+
+
+function errorToJson(error) {
+ const json = {};
+ Object.entries(error).forEach(([key, value]) => {
+ json[key] = value;
+ });
+ if (error.message && !json.message) {
+ json.message = error.message;
+ }
+ if (Object.keys(json).length === 0) {
+ json.message = error.toString();
+ }
+ return json;
+}
+
+export async function startTests(onStateChange) {
+ try {
+ await tests.init();
+ jest.setTimeout(300000);
+
+ const state = {
+ version: await tests.client.config.getVersion(),
+ passed: 0,
+ failed: 0,
+ finished: false,
+ };
+ onStateChange(state);
+
+ jest.addEventHandler((event) => {
+ if (event.name === 'test_start') {
+ console.log(`[TEST_START] ${JSON.stringify({
+ name: event.test.name,
+ })}`);
+ } else if (event.name === 'test_success') {
+ state.passed += 1;
+ console.log(`[TEST_SUCCESS] ${JSON.stringify({
+ name: event.test.name,
+ })}`);
+ } else if (event.name === 'test_failure') {
+ state.failed += 1;
+ console.log(`[TEST_FAILURE] ${JSON.stringify({
+ name: event.test.name,
+ error: errorToJson(event.error),
+ errors: event.test.errors && event.test.errors.map(errorToJson),
+ })}`);
+ } else {
+ return;
+ }
+ onStateChange(state);
+ });
+ onStateChange(state);
+ jest.run().then((results) => {
+ results.forEach((result) => {
+ result.errors = result.errors.map((e) => {
+ return e.toString().replace(/\n\s+at\s+.*/gi, '')
+ });
+ });
+ console.log(`[TEST_COMPLETE] ${JSON.stringify(results)}`);
+ state.finished = true;
+ onStateChange(state);
+ }).catch((error) => {
+ console.log('>>>', error);
+ });
+ } catch (error) {
+ console.log('>>>', error);
+ }
+}
diff --git a/testApp/suite/_/testing-platform.js b/testApp/suite/_/testing-platform.js
new file mode 100644
index 0000000..1a0b48c
--- /dev/null
+++ b/testApp/suite/_/testing-platform.js
@@ -0,0 +1,157 @@
+import { initTONClient as initTC } from 'ton-client-web-js';
+import * as jestLite from 'jest-lite';
+import { Buffer as buffer } from 'buffer';
+import * as p1 from 'error-polyfill';
+// import * as p2 from 'react-native-console-time-polyfill';
+import { BigInteger as bigInt } from 'javascript-biginteger';
+import assets from './assets';
+
+if (bigInt && !global.BigInt) {
+ global.BigInt = bigInt;
+}
+if (buffer && !global.Buffer) {
+ global.Buffer = buffer;
+}
+
+global.jest = jestLite.jest;
+['test', 'expect', 'afterAll', 'afterEach', 'beforeAll', 'beforeEach'].forEach((name) => {
+ jestLite.jest[name] = jestLite[name];
+ global[name] = jestLite[name];
+});
+
+['addEventHandler', 'run'].forEach((name) => {
+ jestLite.jest[name] = jestLite[name];
+});
+
+jest.test.each = variants => (title, fn) => variants.forEach((v) => {
+ jest.test(title.replace(/%i/g, v), () => fn(v));
+});
+
+const testTimeoutSymbol = Symbol.for('TEST_TIMEOUT_SYMBOL');
+jest.setTimeout = (timeout) => {
+ global[testTimeoutSymbol] = timeout;
+};
+
+jestLite.jest.setTimeout(200000);
+
+function isBigInt(a) {
+ return typeof a === 'bigint' || a instanceof BigInt;
+}
+
+function compareBigInt(a, b) {
+ const bigA = BigInt(a);
+ const bigB = BigInt(b);
+ if (typeof bigA !== 'bigint') {
+ return bigA.compare(bigB);
+ }
+ if (bigA < bigB) {
+ return -1;
+ }
+ if (bigA > bigB) {
+ return 1;
+ }
+ return 0;
+}
+
+const bigIntMatchers = {
+ toBeGreaterThan(received, other) {
+ return {
+ pass: compareBigInt(received, other) > 0,
+ message: () => `${received} must be greater than ${other}`,
+ };
+ },
+ toBeGreaterThanOrEqual(received, other) {
+ return {
+ pass: compareBigInt(received, other) >= 0,
+ message: () => `${received} must be greater than or equal to ${other}`,
+ };
+ },
+ toBeLessThan(received, other) {
+ return {
+ pass: compareBigInt(received, other) < 0,
+ message: () => `${received} must be less than ${other}`,
+ }
+ },
+ toBeLessThanOrEqual(received, other) {
+ return {
+ pass: compareBigInt(received, other) <= 0,
+ message: () => `${received} must be less than or equal to ${other}`,
+ }
+ },
+ toEqual(received, other) {
+ return {
+ pass: compareBigInt(received, other) === 0,
+ message: () => `${received} must be equal to ${other}`,
+ }
+ },
+ // toBe: null,
+ // toBeCloseTo: null,
+ // toBeDefined: null,
+ // toBeFalsy: null,
+ // toBeInstanceOf: null,
+ // toBeNaN: null,
+ // toBeNull: null,
+ // toBeTruthy: null,
+ // toBeUndefined: null,
+ // toContain: null,
+ // toContainEqual: null,
+ // toHaveLength: null,
+ // toHaveProperty: null,
+ // toMatch: null,
+ // toMatchObject: null,
+ // lastCalledWith: null,
+ // toBeCalled: null,
+ // toBeCalledWith: null,
+ // toHaveBeenCalled: null,
+ // toHaveBeenCalledTimes: null,
+ // toHaveBeenCalledWith: null,
+ // toHaveBeenLastCalledWith: null,
+ // toThrow: null,
+ // toThrowError: null,
+};
+const defaultExpect = jestLite.jest.expect;
+global.expect = jestLite.jest.expect = (received) => {
+ const wrapped = defaultExpect(received);
+ if (isBigInt(received)) {
+ Object.entries(bigIntMatchers).forEach(([name, fn]) => {
+ wrapped[name] = (...args) => fn(received, ...args);
+ })
+ }
+ return wrapped;
+}
+
+
+// platform
+
+async function findGiverKeys() {
+ return assets.giverKeys;
+}
+
+async function writeGiverKeys(keys) {
+}
+
+function createJaegerTracer(endpoint) {
+ return null;
+}
+
+async function initTONClient(tonClientClass) {
+ return initTC(tonClientClass);
+}
+
+async function loadContractPackage(name, version) {
+ const contract = assets.contracts[`${name}-${version}`];
+ if (!contract) {
+ throw new Error(`Contract not found: ${name}-${version}`)
+ }
+ return contract;
+}
+
+const env = assets.env;
+export {
+ findGiverKeys,
+ writeGiverKeys,
+ createJaegerTracer,
+ initTONClient,
+ loadContractPackage,
+ env,
+};
diff --git a/example/web/index.html b/testApp/web/index.html
similarity index 100%
rename from example/web/index.html
rename to testApp/web/index.html
diff --git a/example/webpack.config.js b/testApp/webpack.config.js
similarity index 96%
rename from example/webpack.config.js
rename to testApp/webpack.config.js
index a918d61..6844bc1 100644
--- a/example/webpack.config.js
+++ b/testApp/webpack.config.js
@@ -31,9 +31,11 @@ module.exports = {
test: /\.jsx?$/,
include: [
path.resolve('index.js'),
+ path.resolve(__dirname, './suite'),
path.resolve(__dirname, './node_modules/webpack-dev-server'),
path.resolve(__dirname, './node_modules/ton-client-js'),
path.resolve(__dirname, './node_modules/ton-client-web-js'),
+ path.resolve(__dirname, './node_modules/jest-lite'),
],
loader: 'babel-loader',
query: {