Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add tests for heartbit react #61

Merged
merged 11 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: 🚀 Run tests

on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:

concurrency: ${{ github.workflow }}-${{ github.ref }}

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
test:
name: 🚀 Test
strategy:
matrix:
os: [ubuntu-latest]
node-version: [lts/*]
pnpm-version: [latest]
runs-on: ${{ matrix.os }}

steps:
- name: ⬇️ Checkout
uses: actions/[email protected]
with:
token: ${{ env.GITHUB_TOKEN }}
fetch-depth: 0

- name: 🟢 Setup node
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}

- name: 🥡 Setup pnpm
uses: pnpm/[email protected]
with:
version: ${{ matrix.pnpm-version }}
run_install: false

- name: 🔆 Cache pnpm modules
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-

- name: 🧩 Install Dependencies
run: pnpm install
working-directory: ./packages/heartbit-core

- name: 🏗️ Build
run: pnpm run build
working-directory: ./packages/heartbit-core

- name: 🧪 Run Tests
run: pnpm test
working-directory: ./packages/heartbit-core
14 changes: 14 additions & 0 deletions packages/heartbit-core/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
globals: {
'ts-jest': {
useESM: true,
tsconfig: {
verbatimModuleSyntax: false,
},
},
},
preset: 'ts-jest',
testEnvironment: 'node',
transform: {},
};
9 changes: 7 additions & 2 deletions packages/heartbit-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"build": "pnpm run clean && tsup",
"clean": "rm -rf dist",
"test:build": "publint --strict",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"test": "jest --coverage --coverageReporters=text-summary"
},
"files": [
"dist"
Expand All @@ -32,9 +33,13 @@
"author": "",
"license": "ISC",
"dependencies": {
"ethers": "^6.10.0"
"ethers": "^6.10.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.2"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/jest": "^29.5.12",
"tsup": "^8.0.2"
}
}
83 changes: 83 additions & 0 deletions packages/heartbit-core/src/test/core.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { HeartBitCore } from '../index';
import { getMinterContract } from '../utils';
import { describe, expect, jest, it } from '@jest/globals';
import { HEART_BIT_CONFIG } from '../constants';
import { mintData, unsignedMintData } from './mockData';

jest.mock('../utils', () => ({
getMinterContract: jest.fn().mockImplementation(() => {
return {
totalSupply: jest.fn().mockResolvedValue('10' as never),
hashTokenMap: jest.fn().mockResolvedValue('token1' as never),
balanceOf: jest.fn().mockResolvedValue('5' as never),
}
}),
}));

global.fetch = jest.fn(() =>
Promise.resolve(new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}))
);

describe('HeartBitCore', () => {
const chain = '0xaa36a7';
const rpcUrl = HEART_BIT_CONFIG[chain].publicRPCUrl;
let core = new HeartBitCore({ chain, rpcUrl });

it('should throw an error if chain is not provided', () => {
expect(() => new HeartBitCore({} as any)).toThrow("Chain is required");
});

it('should initialize correctly with the provided options', () => {
expect(core).toBeDefined();
expect(getMinterContract).toHaveBeenCalledWith(chain, expect.anything());
});

it('should call fetch with the correct parameters on mintHeartBit', async () => {
await core.mintHeartBit(mintData);

expect(fetch).toHaveBeenCalledWith(`${HEART_BIT_CONFIG[chain].relayerUrl}/signed-mint`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(mintData),
});
});

it('should call fetch with the correct parameters on unSignedMintHeartBit', async () => {

const {apiKey, ...rest} = unsignedMintData
await core.unSignedMintHeartBit(unsignedMintData);

expect(fetch).toHaveBeenCalledWith(HEART_BIT_CONFIG[chain].relayerUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey
},
body: JSON.stringify(rest),
});
});

it('should handle getTotalHeartBitCountByHash correctly', async () => {
const hash = 'dummyHash';
const count = await core.getTotalHeartBitCountByHash({ hash });
expect(count).toBe(10);
});

it('should handle getHeartbitHashTokenMap correctly', async () => {
const hash = 'dummyHash';
const tokenMap = await core.getHeartbitHashTokenMap(hash);

expect(tokenMap).toBe('token1');
});

it('should handle getHeartBitByUser correctly', async () => {
const account = '0x123';
const hash = 'dummyHash';
const balance = await core.getHeartBitByUser({ account, hash });

expect(balance).toBe(5);
});
});
14 changes: 14 additions & 0 deletions packages/heartbit-core/src/test/mockData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const mintData = {
message: "p6f7gr-5173.csb.app wants you to sign in with your Ethereum account:\n0x852bd84A4dcDc150648AE4e8D6D6e59b1797c86f\n\nHello World!\n\nURI: https://p6f7gr-5173.csb.app\nVersion: 1\nChain ID: undefined\nNonce: 6RnPMnIbRS1yQ02ZK\nIssued At: 2024-02-15T09:14:18.897Z",
signature: "0xfff4ce944d488abf2f8b2fd80bf3f585cbd14bbafe37ffd8b88e69ad88bd0c86426898416eb2c9c4f913eaa01430f4cac405c5a5c53a6410652b56874b9bd45e1c",
startTime: 1706898250,
endTime: 1706898251,
hash: "0xba0c379a9b364b41e69a6a00a8652e28cb7dda3ca684309b940807634919a940"
};
export const unsignedMintData = {
startTime: 1706898250,
endTime: 1706898251,
hash: "ipfs://cid",
account: "0x852bd84A4dcDc150648AE4e8D6D6e59b1797c86f",
apiKey: 'hello'
};
14 changes: 14 additions & 0 deletions packages/heartbit-react/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
globals: {
'ts-jest': {
useESM: true,
tsconfig: {
verbatimModuleSyntax: false,
},
},
},
preset: 'ts-jest',
testEnvironment: 'node',
transform: {},
};
13 changes: 10 additions & 3 deletions packages/heartbit-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@
"scripts": {
"dev": "vite build --watch",
"build": "tsc --project tsconfig.json && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"test": "jest"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@testing-library/react": "^14.2.2",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.6",
"@types/node-sass": "^4.11.7",
"@types/react": "^18.2.43",
Expand All @@ -48,14 +52,17 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.70.0",
"tsup": "^8.0.2",
"typescript": "^5.2.2",
"vite": "^5.0.8",
"vite-plugin-css-injected-by-js": "^3.3.1",
"vite-plugin-dts": "^3.7.2",
"vite-plugin-node-polyfills": "^0.19.0"
},
"dependencies": {
"@fileverse/heartbit-core": "^2.4.0",
"classnames": "^2.5.1"
"@fileverse/heartbit-core": "workspace:^",
"classnames": "^2.5.1",
"jest": "^29.7.0",
"ts-jest": "^29.1.2"
}
}
109 changes: 109 additions & 0 deletions packages/heartbit-react/src/app.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useEffect } from 'react';
import { render } from '@testing-library/react';
import { HeartBitContext, HeartBitProvider, IHeartBitContext } from './components';
import { describe, expect, jest, it } from '@jest/globals';
import HeartBitCore from '@fileverse/heartbit-core'; // Adjust the import path
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong import

import { HeartBitCoreOptions } from '.';

jest.mock('@fileverse/heartbit-core', () => ({
HeartBitCore: jest.fn().mockImplementation(() => {
return {
getHeartBitByUser: jest.fn(),
getTotalHeartBitCountByHash: jest.fn(),
mintHeartBit: jest.fn(),
}
})
}));

describe('HeartBitProvider', () => {
const coreOptions: HeartBitCoreOptions = { chain: "0xaa36a7" };
const core: any = {
getHeartBitByUser: jest.fn(),
getTotalHeartBitCountByHash: jest.fn(),
mintHeartBit: jest.fn(),
};
const testCases: [keyof IHeartBitContext, keyof any][] = [
['getTotalHeartMintsByUser', 'getHeartBitByUser'],
['getTotalHeartBitByHash', 'getTotalHeartBitCountByHash'],
['mintHeartBit', 'mintHeartBit']
];

it('initializes HeartBitCore with coreOptions', () => {
render(
<HeartBitProvider coreOptions={coreOptions}>
<div>Testing</div>
</HeartBitProvider>
);

expect(HeartBitCore).toHaveBeenCalledWith(coreOptions);
});

it('provides context functions', () => {
const TestComponent = () => {
const context = React.useContext(HeartBitContext);

expect(context).not.toBeNull();
expect(context?.getTotalHeartMintsByUser).toBeDefined();
expect(context?.getTotalHeartBitByHash).toBeDefined();
expect(context?.mintHeartBit).toBeDefined();

return <div>Testing</div>;
};

render(
<HeartBitProvider coreOptions={coreOptions}>
<TestComponent />
</HeartBitProvider>
);
});


it.each(testCases)('calls %s through the provided context', async (contextFunction: keyof IHeartBitContext, coreFunction: any) => {
const args = { id: 'test' };
const TestComponent = () => {
const context = React.useContext(HeartBitContext);

useEffect(() => {
context?.[contextFunction](args);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different functions have different args this will not run

}, [context]);

return <div>Testing</div>;
};

render(
<HeartBitProvider coreOptions={coreOptions}>
<TestComponent />
</HeartBitProvider>
);

expect(core[coreFunction]).toHaveBeenCalledWith(args);
});

it.each(testCases)('handles errors in %s', async (contextFunction, coreFunction) => {
const error = new Error('Test Error');
core[coreFunction].mockRejectedValue(error);

const args = { id: 'test' };
const TestComponent = () => {
const context: any = React.useContext(HeartBitContext);

useEffect(() => {
const fetch = async () => {
await expect(context?.[contextFunction](args)).rejects.toThrow(error);
};
fetch();
}, [context]);

return <div>Testing</div>;
};

render(
<HeartBitProvider coreOptions={coreOptions}>
<TestComponent />
</HeartBitProvider>
);

expect(core[coreFunction]).toHaveBeenCalledWith(args);
});
});
Loading
Loading