diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index eba59e2..71678d7 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,38 +1,32 @@
-name: Tests
+name: tests
on:
- # push:
- # branches: [ "main" ]
workflow_dispatch:
pull_request:
-
-concurrency:
- group: ${{ github.head_ref || github.run_id }}
- cancel-in-progress: true
+ types: [synchronize, opened, reopened, ready_for_review, unlabeled]
jobs:
- build:
+ test:
runs-on: ubuntu-22.04
- strategy:
- matrix:
- node-version: [18]
-
steps:
- uses: actions/checkout@v3
- - name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+
+ - uses: actions/setup-node@v3
with:
- node-version: ${{ matrix.node-version }}
- - name: Clear npm cache
- run: npm cache clean --force
+ node-version: 18
+
- name: Install dfx
uses: dfinity/setup-dfx@main
+
- name: Install mops
uses: ZenVoich/setup-mops@v1
- - name: Start dfx
- run: |
- dfx cache install
- dfx start --background
- - run: npm run setup
- - run: npm test
+
+ - name: generate declarations
+ run: dfx generate backend
+
+ - name: install dependencies
+ run: npm i
+
+ - name: run tests
+ run: npm run test
diff --git a/README.md b/README.md
index 98b9273..5a3233d 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,6 @@
> Due to current limitations, this template does not work in a Browser Editor when using gitpod or codespaces. Please use VS Code for desktop instead.
>
-
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/letmejustputthishere/vite-sveltekit-motoko)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/letmejustputthishere/vite-sveltekit-motoko?quickstart=1)
@@ -18,7 +17,7 @@ For an example of a real-world dapp built using this starter project, check out
## ๐ฆ Create a New Project
> [!IMPORTANT]
-> Make sure that [Node.js](https://nodejs.org/en/) `>= 18`, [mops](https://docs.mops.one/quick-start) `>=0.39.2` and [`dfx`](https://internetcomputer.org/docs/current/developer-docs/build/install-upgrade-remove) `>= 0.16` are installed on your system.
+> Make sure that [Node.js](https://nodejs.org/en/) `>= 18`, [mops](https://docs.mops.one/quick-start) `>=0.39.2` and [`dfx`](https://internetcomputer.org/docs/current/developer-docs/build/install-upgrade-remove) `>= 0.16.1` are installed on your system.
Run the following commands in a new, empty project directory:
@@ -45,6 +44,11 @@ When ready, run `dfx deploy --network ic` to deploy your application to the Inte
- [mo-dev](https://github.com/dfinity/motoko-dev-server#readme): a live reload development server for Motoko
- [eslint](https://eslint.org/): a static code analysis tool used in software development for identifying problematic patterns or code that doesn't adhere to certain style guidelines in JavaScript and TypeScript
- [Internet Identity](https://github.com/dfinity/internet-identity/tree/main): a decentralized identity provider for the Internet Computer
+- [pic.js](https://github.com/hadronous/pic-js): an Internet Computer Protocol canister testing library for TypeScript and JavaScript
+
+## ๐งช Testing
+
+You can run `npm run test` to run unit tests using [`mops test`](https://docs.mops.one/cli/mops-test) and end-to-end tests using [`pic.js`](https://hadronous.github.io/pic-js/).
## ๐ Documentation
@@ -55,10 +59,11 @@ When ready, run `dfx deploy --network ic` to deploy your application to the Inte
- [Motoko developer docs](https://internetcomputer.org/docs/current/developer-docs/build/cdks/motoko-dfinity/motoko/)
- [Mops usage instructions](https://j4mwm-bqaaa-aaaam-qajbq-cai.ic0.app/#/docs/install)
- [Internet Identity docs](https://internetcomputer.org/docs/current/developer-docs/integrations/internet-identity/overview)
+- [pic-js](https://hadronous.github.io/pic-js/)
## ๐ก Tips and Tricks
- Customize your project's code style by editing the `.prettierrc` file and then running `npm run format`.
- Reduce the latency of update calls by passing the `--emulator` flag to `dfx start`.
-- Install a Motoko package by running `npx ic-mops add `. Here is a [list of available packages](https://mops.one/).
+- Install a Motoko package by running `mops add `. Here is a [list of available packages](https://mops.one/).
- Split your frontend and backend console output by running `npm run frontend` and `npm run backend` in separate terminals.
diff --git a/backend/main.mo b/backend/main.mo
index 74cc1a5..26e62d4 100644
--- a/backend/main.mo
+++ b/backend/main.mo
@@ -1,6 +1,6 @@
-actor class Main() {
+actor class Main(initArgs : { phrase : Text }) {
public query func greet(name : Text) : async Text {
- return "Hello, " # name # "!";
+ return initArgs.phrase # ", " # name # "!";
};
public query ({ caller }) func whoAmI() : async Principal {
diff --git a/backend/tests/main.greet.test.mo b/backend/tests/main.greet.test.mo
index 42371c6..0300c4d 100644
--- a/backend/tests/main.greet.test.mo
+++ b/backend/tests/main.greet.test.mo
@@ -1,5 +1,5 @@
import { Main } "../main";
-let main = await Main();
+let main = await Main({ phrase = "Hello" });
assert (await main.greet("Moritz")) == "Hello, Moritz!";
diff --git a/package-lock.json b/package-lock.json
index 79ba82e..a298ab7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"@dfinity/auth-client": "^0.21.4",
"@dfinity/candid": "^0.21.4",
"@dfinity/principal": "^0.21.4",
+ "@hadronous/pic": "^0.3.0-b0",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.0.0",
@@ -595,6 +596,22 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@hadronous/pic": {
+ "version": "0.3.0-b0",
+ "resolved": "https://registry.npmjs.org/@hadronous/pic/-/pic-0.3.0-b0.tgz",
+ "integrity": "sha512-KPiivseCIpIHtLAQ1sZr0p8R85pS5QNsCvovPzZDgdiBuJddz24Lt2K2YpEOOmSpikqNdYRelTl/HkCtFSb0kQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "bip39": "^3.1.0"
+ },
+ "peerDependencies": {
+ "@dfinity/agent": "~0.21.4",
+ "@dfinity/candid": "~0.21.4",
+ "@dfinity/identity": "~0.21.4",
+ "@dfinity/principal": "~0.21.4"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@@ -1732,6 +1749,15 @@
"node": ">=8"
}
},
+ "node_modules/bip39": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz",
+ "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==",
+ "dev": true,
+ "dependencies": {
+ "@noble/hashes": "^1.2.0"
+ }
+ },
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
diff --git a/package.json b/package.json
index b8dfcb1..ff7b4c2 100644
--- a/package.json
+++ b/package.json
@@ -3,15 +3,16 @@
"version": "0.0.1",
"private": true,
"scripts": {
- "setup": "npm i && npm run init-ii && dfx generate backend && dfx generate internet_identity && dfx deploy backend && dfx deps deploy",
+ "setup": "npm i && npm run init-ii && dfx generate backend && dfx generate internet_identity && dfx deploy backend --argument '(record {phrase = \"Hello\"})' && dfx deps deploy",
"init-ii": "dfx deps pull && dfx deps init internet_identity --argument '(null)'",
"start": "run-p frontend backend",
"frontend": "vite --host",
"backend": "mo-dev --generate --deploy -y",
"build": "vite build",
- "test": "run-s test:backend test:frontend",
- "test:frontend": "vitest run",
- "test:backend": "mops test",
+ "pretest": "dfx generate backend",
+ "test": "run-s test:e2e test:unit",
+ "test:e2e": "vitest run",
+ "test:unit": "mops test",
"format": "prettier --write .",
"sources": "mops sources",
"postinstall": "mops install",
@@ -26,6 +27,7 @@
"@dfinity/auth-client": "^0.21.4",
"@dfinity/candid": "^0.21.4",
"@dfinity/principal": "^0.21.4",
+ "@hadronous/pic": "^0.3.0-b0",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.0.0",
diff --git a/tests/canister.test.ts b/tests/canister.test.ts
new file mode 100644
index 0000000..8fd55ae
--- /dev/null
+++ b/tests/canister.test.ts
@@ -0,0 +1,83 @@
+import { describe, it, expect, afterEach, beforeEach } from 'vitest';
+
+import { AnonymousIdentity } from '@dfinity/agent';
+import { PocketIc, createIdentity, type Actor } from '@hadronous/pic';
+import type { _SERVICE } from '../src/declarations/backend/backend.did';
+import { deployCanister } from './setup';
+
+describe('canister tests', () => {
+ let pic: PocketIc;
+ let actor: Actor<_SERVICE>;
+
+ const alice = createIdentity('superSecretAlicePassword');
+ const bob = createIdentity('superSecretBobPassword');
+
+ afterEach(async () => {
+ await pic.tearDown();
+ });
+
+ describe('when calling greet on the canister deployed with the default init args', () => {
+ beforeEach(async () => {
+ ({ pic, actor } = await deployCanister({
+ deployer: alice.getPrincipal()
+ }));
+ });
+
+ it('the argument should be prefixed with `Hello, `', async () => {
+ await expect(actor.greet('Moritz')).resolves.toEqual('Hello, Moritz!');
+ });
+
+ it('the argument should be prefixed with `Hello, `, even for very long names', async () => {
+ const veryLongName = 'a'.repeat(1000);
+ await expect(actor.greet(veryLongName)).resolves.toEqual('Hello, ' + veryLongName + '!');
+ });
+
+ it('a call to whoami with the anonymous principal should return the anonymous principal', async () => {
+ actor.setIdentity(new AnonymousIdentity());
+ await expect(actor.whoAmI()).resolves.toEqual(new AnonymousIdentity().getPrincipal());
+ });
+
+ it('a call to whoami with the alice principal should return the alice principal', async () => {
+ actor.setIdentity(alice);
+ await expect(actor.whoAmI()).resolves.toEqual(alice.getPrincipal());
+ });
+
+ it('a call to whoami with the bob principal should return the bob principal', async () => {
+ actor.setIdentity(bob);
+ await expect(actor.whoAmI()).resolves.toEqual(bob.getPrincipal());
+ });
+ });
+
+ describe('when calling greet on the canister deployed with `bonjour` as an init arg', () => {
+ beforeEach(async () => {
+ ({ pic, actor } = await deployCanister({
+ initArgs: { phrase: 'bonjour' },
+ deployer: alice.getPrincipal()
+ }));
+ });
+
+ it('the argument should be prefixed with `bonjour, `', async () => {
+ await expect(actor.greet('Moritz')).resolves.toEqual('bonjour, Moritz!');
+ });
+
+ it('the argument should be prefixed with `Hello, `, even for very long names', async () => {
+ const veryLongName = 'a'.repeat(1000);
+ await expect(actor.greet(veryLongName)).resolves.toEqual('bonjour, ' + veryLongName + '!');
+ });
+
+ it('a call to whoami with the anonymous principal should return the anonymous principal', async () => {
+ actor.setIdentity(new AnonymousIdentity());
+ await expect(actor.whoAmI()).resolves.toEqual(new AnonymousIdentity().getPrincipal());
+ });
+
+ it('a call to whoami with the alice principal should return the alice principal', async () => {
+ actor.setIdentity(alice);
+ await expect(actor.whoAmI()).resolves.toEqual(alice.getPrincipal());
+ });
+
+ it('a call to whoami with the bob principal should return the bob principal', async () => {
+ actor.setIdentity(bob);
+ await expect(actor.whoAmI()).resolves.toEqual(bob.getPrincipal());
+ });
+ });
+});
diff --git a/tests/index.test.ts b/tests/index.test.ts
deleted file mode 100644
index e07cbbd..0000000
--- a/tests/index.test.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { describe, it, expect } from 'vitest';
-
-describe('sum test', () => {
- it('adds 1 + 2 to equal 3', () => {
- expect(1 + 2).toBe(3);
- });
-});
diff --git a/tests/setup.ts b/tests/setup.ts
new file mode 100644
index 0000000..ab12c03
--- /dev/null
+++ b/tests/setup.ts
@@ -0,0 +1,37 @@
+import { IDL } from '@dfinity/candid';
+import { idlFactory, init } from '../src/declarations/backend/backend.did.js';
+import type { _SERVICE } from '../src/declarations/backend/backend.did';
+import { resolve } from 'node:path';
+import { PocketIc } from '@hadronous/pic';
+import { Principal } from '@dfinity/principal';
+
+type InitArgs = {
+ phrase: string;
+};
+const defaultInitArgs: InitArgs = {
+ phrase: 'Hello'
+};
+const WASM_PATH = resolve(__dirname, '..', '.dfx', 'local', 'canisters', 'backend', 'backend.wasm');
+
+interface DeployOptions {
+ initArgs?: InitArgs;
+ deployer?: Principal;
+}
+
+export async function deployCanister({
+ initArgs = defaultInitArgs,
+ deployer = Principal.anonymous()
+}: DeployOptions) {
+ const encodedInitArgs = IDL.encode(init({ IDL }), [initArgs]);
+ const pic = await PocketIc.create();
+ const fixture = await pic.setupCanister<_SERVICE>(
+ idlFactory,
+ WASM_PATH,
+ undefined,
+ encodedInitArgs,
+ deployer
+ );
+ const actor = fixture.actor;
+ const canisterId = fixture.canisterId;
+ return { pic, actor, canisterId };
+}