diff --git a/.github/workflows/lint.yml b/.github/workflows/test.yml similarity index 62% rename from .github/workflows/lint.yml rename to .github/workflows/test.yml index 95647826..76e34464 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: lint and type checks +name: lint and test on: push: @@ -7,7 +7,7 @@ on: branches: [main] jobs: - lint: + test: runs-on: ubuntu-latest steps: @@ -18,8 +18,7 @@ jobs: node-version: 18 - name: Install dependencies run: yarn - - name: Run prettier in whole project + - name: Run all lint tasks run: yarn lint - - name: Run type checks in packages - run: yarn lint - working-directory: packages/chakra-components + - name: Test everything + run: yarn test --verbose diff --git a/jest.config.mjs b/jest.config.mjs new file mode 100644 index 00000000..d82960f7 --- /dev/null +++ b/jest.config.mjs @@ -0,0 +1,19 @@ +const config = { + testEnvironment: 'jsdom', + collectCoverageFrom: ['packages/**/*.{ts,tsx}'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + modulePathIgnorePatterns: ['/templates'], + transform: { + '^.+\\.(ts|tsx|js|jsx)?$': '@swc-node/jest', + }, + transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'], + setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect', './setup-tests.ts'], + globals: { + 'ts-jest': { + tsconfig: 'tsconfig/react-library.json', + }, + }, + watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'], +} + +export default config diff --git a/mocks/handlers.ts b/mocks/handlers.ts new file mode 100644 index 00000000..d45e79c2 --- /dev/null +++ b/mocks/handlers.ts @@ -0,0 +1,119 @@ +import { rest } from 'msw' + +export const handlers = [ + rest.get('https://api*.vocdoni.net/v2/accounts/*', (req, res, ctx) => { + const id = req.params[1] + return res( + ctx.status(200), + ctx.json({ + address: id, + nonce: 62, + balance: 4987085, + electionIndex: 42, + infoURL: 'ipfs://bafybeiebhal46gmhx3y7qbrfsgm5zsmrio5bmzwutg6fodkfysxr2e5t34', + metadata: { + version: '1.0', + name: { default: 'testing account' }, + description: { default: '' }, + newsFeed: { default: '' }, + media: { avatar: 'https://pbs.twimg.com/profile_images/1546527030329606150/BmyiyT2I_400x400.jpg' }, + meta: {}, + }, + }) + ) + }), + rest.get('https://api*.vocdoni.net/v2/elections/*', (req, res, ctx) => { + const id = req.params[1] + return res( + ctx.status(200), + ctx.json({ + electionId: id, + organizationId: '389b35ebcf34f9e0e8b32b5468216a699fde0c97', + status: 'RESULTS', + startDate: '2023-08-03T15:10:57.829010914Z', + endDate: '2023-09-01T01:25:08.672843671Z', + voteCount: 1, + finalResults: true, + result: [ + ['0', '1', '0'], + ['0', '1', '0'], + ], + census: { + censusOrigin: 'OFF_CHAIN_TREE_WEIGHTED', + censusRoot: 'cc6c247592db36bbe2b7ecee18716ef28605c97f3c5a52479c154bc20ecafa8f', + postRegisterCensusRoot: '', + censusURL: 'ipfs://bafybeia4w63agvgs5x5zhmcxtvfzlnahltzzvbubragtne26e6ua7rivvm', + maxCensusSize: 2, + }, + metadataURL: 'ipfs://bafybeiaszamrceo4n5kcipcr5cnr5iozbg5zgj3nys2s5tjae7kubyl2fe', + creationTime: '2023-08-03T15:10:47Z', + voteMode: { + serial: false, + anonymous: false, + encryptedVotes: false, + uniqueValues: false, + costFromWeight: false, + }, + electionMode: { + autoStart: true, + interruptible: true, + dynamicCensus: false, + encryptedMetaData: false, + preRegister: false, + }, + tallyMode: { maxCount: 2, maxValue: 2, maxVoteOverwrites: 1, maxTotalCost: 0, costExponent: 10000 }, + metadata: { + title: { default: 'mocked process' }, + version: '1.1', + description: { default: '' }, + media: {}, + meta: { sdk: { version: '0.0.16' } }, + questions: [ + { + choices: [ + { title: { default: '一' }, value: 0 }, + { title: { default: '二' }, value: 1 }, + { title: { default: '三' }, value: 2 }, + ], + description: { default: '' }, + title: { default: '最初の質問' }, + }, + { + choices: [ + { title: { default: 'eins' }, value: 0 }, + { title: { default: 'zwei' }, value: 1 }, + ], + description: { default: '' }, + title: { default: 'das ist eine andere frage' }, + }, + ], + results: { aggregation: 'discrete-counting', display: 'multiple-question' }, + }, + }) + ) + }), + rest.get('https://api*.vocdoni.net/v2/censuses/*/size', (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + size: 2, + }) + ) + }), + rest.get('https://api*.vocdoni.net/v2/censuses/*/weight', (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + weight: 2, + }) + ) + }), + rest.get('https://api*.vocdoni.net/v2/censuses/*/type', (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + type: 'weighted', + }) + ) + }), +] diff --git a/mocks/server.ts b/mocks/server.ts new file mode 100644 index 00000000..86f7d615 --- /dev/null +++ b/mocks/server.ts @@ -0,0 +1,4 @@ +import { setupServer } from 'msw/node' +import { handlers } from './handlers' + +export const server = setupServer(...handlers) diff --git a/package.json b/package.json index 87cf594b..9cb3e5ca 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,20 @@ "templates/*" ], "devDependencies": { + "@swc-node/jest": "^1.6.7", + "@swc/core": "^1.3.78", + "@swc/helpers": "^0.5.1", + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "14.0.0", + "@types/jest": "^29.5.3", + "@types/testing-library__jest-dom": "5.14.8", + "axios": "^0.27.0", + "eslint-plugin-testing-library": "5.5.1", + "ethers": "^5.7.0", + "jest": "^29.6.2", + "jest-environment-jsdom": "^29.6.2", + "jest-watch-typeahead": "^2.2.2", + "msw": "^1.2.5", "prettier": "^2.8.7", "turbo": "^1.8.6" }, @@ -21,8 +35,9 @@ "scripts": { "build": "turbo build --filter=@vocdoni*", "lint:fix": "prettier -c . --write", - "lint": "prettier -c .", - "clean": "turbo clean --filter=@vocdoni* && rm -fr node_modules" + "lint": "turbo lint && prettier -c .", + "clean": "turbo clean --filter=@vocdoni* && rm -fr node_modules", + "test": "jest" }, "dependencies": { "tsup": "^7.2.0" diff --git a/packages/react-providers/.eslintrc.yml b/packages/react-providers/.eslintrc.yml index f1a35772..1a43d60c 100644 --- a/packages/react-providers/.eslintrc.yml +++ b/packages/react-providers/.eslintrc.yml @@ -7,4 +7,6 @@ extends: parserOptions: ecmaVersion: latest sourceType: module +plugins: + - testing-library rules: {} diff --git a/packages/react-providers/src/client/ClientProvider.test.tsx b/packages/react-providers/src/client/ClientProvider.test.tsx new file mode 100644 index 00000000..052b9654 --- /dev/null +++ b/packages/react-providers/src/client/ClientProvider.test.tsx @@ -0,0 +1,143 @@ +import { Wallet } from '@ethersproject/wallet' +import { act, render, renderHook, waitFor } from '@testing-library/react' +import { VocdoniCensus3Client } from '@vocdoni/sdk' +import { ApiUrl, CensusUrls, properProps } from '../test-utils' +import { ClientProvider, useClient } from './ClientProvider' + +describe('', () => { + it('renders child elements', () => { + const { getByText } = render( + +

is rendered

+
+ ) + + expect(getByText('is rendered')).toBeInTheDocument() + }) + + it('has expected defaults defined if no props are passed', () => { + const wrapper = (props: any) => + const { result } = renderHook(() => useClient(), { + wrapper, + }) + + expect(result.current.env).toBe('prod') + + // no signer is passed, so none should be defined + expect(result.current.signer).toBeUndefined() + expect(result.current.client.wallet).toBeUndefined() + expect(result.current.connected).toBeFalsy() + + // and there should be no account connected either + expect(result.current.account).toBeUndefined() + + // ensures prod is taken by default in the client + expect(result.current.client.url).toEqual(ApiUrl.prod) + + // check census3 client is setup + expect(result.current.census3).not.toBeUndefined() + expect(result.current.census3).toBeInstanceOf(VocdoniCensus3Client) + expect(result.current.census3.url).not.toBeUndefined() + }) + + it('sets proper environment', () => { + const wrapper = (props: any) => + const { result, rerender } = renderHook(() => useClient(), { + wrapper, + initialProps: { env: 'stg' }, + }) + + expect(result.current.env).toBe('stg') + expect(result.current.client.url).toEqual(ApiUrl.stg) + expect(result.current.census3.url).toEqual(CensusUrls.stg) + + // change it to dev + rerender({ env: 'dev' }) + + expect(result.current.env).toBe('dev') + // ensure other related instances are updated too + expect(result.current.client.url).toEqual(ApiUrl.dev) + expect(result.current.census3.url).toEqual(CensusUrls.dev) + }) + + it('sets proper signer', async () => { + const signer = Wallet.createRandom() + const wrapper = (props: any) => + const { result, rerender } = renderHook(() => useClient(), { + wrapper, + initialProps: { signer, env: 'dev' }, + }) + + await waitFor(() => { + expect(result.current.connected).toBeTruthy() + }) + + expect(result.current.signer).toEqual(signer) + expect(result.current.client.wallet).toEqual(signer) + + const newsigner = Wallet.createRandom() + + // @ts-ignore + rerender({ signer: newsigner }) + + expect(result.current.signer).not.toEqual(signer) + expect(result.current.signer).toEqual(newsigner) + expect(result.current.client.wallet).toEqual(newsigner) + // ensure env has not changed after changing the signer + expect(result.current.env).toEqual('dev') + expect(result.current.client.url).toEqual(ApiUrl.dev) + }) + + it('"creates" an account', async () => { + const wrapper = (props: any) => + const signer = Wallet.createRandom() + const { result } = renderHook(() => useClient(), { + wrapper, + initialProps: { env: 'dev', signer }, + }) + + // fetch account request is mocked, so we're actually not creating accounts, just fetching them + await act(async () => { + await result.current.createAccount() + }) + + await waitFor(() => { + expect(result.current.loaded.account).toBeTruthy() + }) + + expect(result.current.loading.account).toBeFalsy() + expect(result.current.account).not.toBeUndefined() + expect(result.current.errors.account).toBeNull() + }) + + it('properly clears session data', async () => { + const wrapper = (props: any) => + const signer = Wallet.createRandom() + const { result } = renderHook(() => useClient(), { + wrapper, + initialProps: { env: 'dev', signer }, + }) + + // fetch account request is mocked, so we're actually not creating accounts, just fetching them + await act(async () => { + await result.current.createAccount() + }) + + await waitFor(() => { + expect(result.current.loaded.account).toBeTruthy() + }) + + expect(result.current.connected).toBeTruthy() + expect(result.current.balance).not.toEqual(-1) + + // clear session data + await act(() => { + result.current.clear() + }) + + expect(result.current.connected).toBeFalsy() + expect(result.current.client.wallet).toBeUndefined() + expect(result.current.signer).toStrictEqual({}) + expect(result.current.balance).toEqual(-1) + }) +}) diff --git a/packages/react-providers/src/client/use-client-reducer.ts b/packages/react-providers/src/client/use-client-reducer.ts index 63da569a..78a66f19 100644 --- a/packages/react-providers/src/client/use-client-reducer.ts +++ b/packages/react-providers/src/client/use-client-reducer.ts @@ -84,14 +84,14 @@ export interface ClientState { } export const clientStateEmpty = (env: EnvOptions, client: VocdoniSDKClient, signer: Wallet | Signer): ClientState => ({ - env, + env: env || EnvOptions.PROD, signer, client: client || new VocdoniSDKClient({ - env, + env: env || EnvOptions.PROD, }), - census3: new VocdoniCensus3Client({ env }), + census3: new VocdoniCensus3Client({ env: env || EnvOptions.PROD }), account: undefined, balance: -1, connected: false, diff --git a/packages/react-providers/src/election/ElectionProvider.test.tsx b/packages/react-providers/src/election/ElectionProvider.test.tsx new file mode 100644 index 00000000..67d7a7ab --- /dev/null +++ b/packages/react-providers/src/election/ElectionProvider.test.tsx @@ -0,0 +1,301 @@ +import { Wallet } from '@ethersproject/wallet' +import { act, render, renderHook, waitFor } from '@testing-library/react' +import { EnvOptions, PublishedElection, VocdoniSDKClient, WeightedCensus } from '@vocdoni/sdk' +import { ClientProvider, useClient } from '../client' +import { onlyProps, properProps } from '../test-utils' +import { ElectionProvider, useElection } from './ElectionProvider' + +describe('', () => { + it('renders child elements', () => { + const { getByText } = render( + + +

is rendered

+
+
+ ) + + expect(getByText('is rendered')).toBeInTheDocument() + }) + + it('fetches an election given an ID to the provider', async () => { + const wrapper = (props: any) => { + return ( + + + + ) + } + + const { result } = renderHook(() => useElection(), { + wrapper, + initialProps: { id: '0xc5d2460186f73b259cfcf953772238dccf1d21605f00517933be020000000001' }, + }) + + await waitFor(() => { + expect(result.current.loaded.election).toBeTruthy() + }) + + expect(result.current.election?.title.default).toEqual('mocked process') + }) + + it('sets proper client from ClientProvider by default', async () => { + const signer = Wallet.createRandom() + const wrapper = (props: any) => { + return ( + + {props.children} + + ) + } + + const { result } = renderHook(() => useElection(), { wrapper, initialProps: { signer } }) + + await waitFor(() => { + expect(result.current.connected).toBeTruthy() + }) + + expect(result.current.client.wallet).toStrictEqual(signer) + }) + + it('can set and change signer at election level', () => { + const signer = Wallet.createRandom() + const client = new VocdoniSDKClient({ + env: EnvOptions.STG, + wallet: signer, + }) + const wrapper = (props: any) => { + return ( + + + + ) + } + + // we want to make checks using both hooks + const useBothHooks = () => { + return { + election: useElection(), + client: useClient(), + } + } + + const { result } = renderHook(() => useBothHooks(), { wrapper, initialProps: { client } }) + + expect(result.current.election.client.wallet).toStrictEqual(signer) + + const newsigner = Wallet.createRandom() + const newclient = new VocdoniSDKClient({ + env: EnvOptions.STG, + wallet: newsigner, + }) + + // change the client at election level + act(() => { + result.current.election.setClient(newclient) + }) + + // only the signer at election level should be affected + expect(result.current.election.client.wallet).toStrictEqual(newsigner) + // while the one at client level should remain the same + expect(result.current.client.client.wallet).toStrictEqual(signer) + }) + + it('does not update client at election level for spreadsheet type census', async () => { + const signer = Wallet.createRandom() + const client = new VocdoniSDKClient({ + env: EnvOptions.STG, + wallet: signer, + }) + const wrapper = (props: any) => { + return ( + + + + ) + } + + // we want to make checks using both hooks, again + const useBothHooks = () => { + return { + election: useElection(), + client: useClient(), + } + } + + const { result, rerender } = renderHook(() => useBothHooks(), { wrapper, initialProps: { client, signer } }) + + await waitFor(() => { + expect(result.current.client.connected).toBeTruthy() + }) + + // both should be properly defined to the base signer + expect(result.current.client.client.wallet).toStrictEqual(signer) + expect(result.current.election.client.wallet).toStrictEqual(signer) + + const newsigner = Wallet.createRandom() + const newclient = new VocdoniSDKClient({ + env: EnvOptions.STG, + wallet: newsigner, + }) + + // rerender with new client and signer + rerender({ client: newclient, signer: newsigner }) + + // client level has to change + expect(result.current.client.client.wallet).toStrictEqual(newsigner) + // but election level hasn't, since it's of type spreadsheet and we want to maintain the original one + expect(result.current.election.client.wallet).toStrictEqual(signer) + }) + + it('properly sets and updates election metadata', () => { + const wrapper = (props: any) => { + return ( + + + + ) + } + + const election = PublishedElection.from({ + title: 'test', + description: 'test', + endDate: new Date(), + census: new WeightedCensus(), + }) + // @ts-ignore + election.id = 'test' + + const { result, rerender } = renderHook(() => useElection(), { wrapper, initialProps: { election } }) + + expect(result.current.election?.title.default).toBe('test') + expect(result.current.election?.description.default).toBe('test') + + const newelection = PublishedElection.from({ + title: 'test2', + description: 'test2', + endDate: new Date(), + census: new WeightedCensus(), + }) + // ids must change in order for the data to be actually updated + // @ts-ignore + election.id = 'test2' + + rerender({ election: newelection }) + + expect(result.current.election?.title.default).toBe('test2') + expect(result.current.election?.description.default).toBe('test2') + }) + + it('clears session data on logout', async () => { + const signer = Wallet.createRandom() + const client = new VocdoniSDKClient({ + env: EnvOptions.STG, + wallet: signer, + }) + const wrapper = (props: any) => { + return ( + + + + ) + } + const election = PublishedElection.from({ + title: 'test', + description: 'test', + endDate: new Date(), + census: new WeightedCensus(), + meta: { census: { type: 'spreadsheet' } }, + }) + // @ts-ignore + election.id = 'test' + + // we need both hooks for this test + const useBothHooks = () => { + return { + election: useElection(), + client: useClient(), + } + } + + var { result, rerender } = renderHook(() => useBothHooks(), { wrapper, initialProps: { election } }) + + await waitFor(() => { + expect(result.current.client.connected).toBeTruthy() + }) + + // client should be connected + expect(result.current.client.connected).toBeTruthy() + // but not the local one, since is of type spreadsheet + expect(result.current.election.connected).toBeFalsy() + + await act(() => { + // now we set the local one + result.current.election.setClient(client) + }) + + // and then we should have the local session connected + expect(result.current.election.connected).toBeTruthy() + + act(() => { + result.current.client.clear() + }) + + // client should be cleared + expect(result.current.client.connected).toBeFalsy() + // but not election, since it's a spreadsheet one + expect(result.current.election.connected).toBeTruthy() + + const nelection = PublishedElection.from({ + title: 'test2', + description: 'test2', + endDate: new Date(), + census: new WeightedCensus(), + meta: { census: { type: 'token' } }, + }) + // @ts-ignore + election.id = 'test2' + + rerender({ election: nelection }) + + // initialize again + await act(() => { + result.current.client.setClient(client) + result.current.election.setClient(client) + }) + + expect(result.current.client.connected).toBeTruthy() + expect(result.current.election.connected).toBeTruthy() + + // clear session + await act(() => { + result.current.client.clear() + }) + + // in this case both should be disconnected, since is not a spreadsheet type election + expect(result.current.client.connected).toBeFalsy() + expect(result.current.election.connected).toBeFalsy() + }) +}) diff --git a/packages/react-providers/src/election/use-election-reducer.ts b/packages/react-providers/src/election/use-election-reducer.ts index eb59932c..71bdc6ad 100644 --- a/packages/react-providers/src/election/use-election-reducer.ts +++ b/packages/react-providers/src/election/use-election-reducer.ts @@ -433,6 +433,11 @@ export const useElectionReducer = (client: VocdoniSDKClient, election?: Publishe ) return + // if there's no meta census information, avoid clearing it from state (process does not follow our way to create them) + if (!state.election?.meta?.census) { + return + } + clear() }, [state.connected, connected, state.election]) diff --git a/packages/react-providers/src/organization/OrganizationProvider.test.tsx b/packages/react-providers/src/organization/OrganizationProvider.test.tsx new file mode 100644 index 00000000..f184762b --- /dev/null +++ b/packages/react-providers/src/organization/OrganizationProvider.test.tsx @@ -0,0 +1,81 @@ +import { render, renderHook, waitFor } from '@testing-library/react' +import { Account, AccountData } from '@vocdoni/sdk' +import { ClientProvider } from '../client' +import { properProps } from '../test-utils' +import { OrganizationProvider, useOrganization } from './OrganizationProvider' + +describe('', () => { + it('renders child elements', () => { + const { getByText } = render( + + +

is rendered

+
+
+ ) + + expect(getByText('is rendered')).toBeInTheDocument() + }) + + it('properly sets and updates organization metadata', () => { + const wrapper = (props: any) => { + return ( + + + + ) + } + + const organization: AccountData = { + account: new Account({ + name: 'testing', + }), + address: '0xB38492889D136054A29EC37B238Cc8bBF4f3DEe7', + balance: 0, + electionIndex: 0, + nonce: 0, + } + + const { result, rerender } = renderHook(() => useOrganization(), { wrapper, initialProps: { organization } }) + + expect(result.current.organization?.account.name.default).toBe('testing') + expect(result.current.organization?.address).toEqual('0xB38492889D136054A29EC37B238Cc8bBF4f3DEe7') + + const neworg: AccountData = { + account: new Account({ + name: 'testing2', + }), + address: '0xde0F66E999db9927cc31acABED5cEd80a926d4b7', + balance: 0, + electionIndex: 0, + nonce: 0, + } + + rerender({ organization: neworg }) + + expect(result.current.organization?.account.name.default).toBe('testing2') + expect(result.current.organization?.address).toEqual('0xde0F66E999db9927cc31acABED5cEd80a926d4b7') + }) + + it('fetches an account given an ID to the provider', async () => { + const wrapper = (props: any) => { + return ( + + + + ) + } + + const { result } = renderHook(() => useOrganization(), { + wrapper, + initialProps: { id: '0xde0F66E999db9927cc31acABED5cEd80a926d4b7' }, + }) + + await waitFor(() => { + expect(result.current.loaded).toBeTruthy() + }) + + expect(result.current.errors.load).toBeNull() + expect(result.current.organization?.account.name.default).toEqual('testing account') + }) +}) diff --git a/packages/react-providers/src/test-utils.ts b/packages/react-providers/src/test-utils.ts new file mode 100644 index 00000000..5edad0b1 --- /dev/null +++ b/packages/react-providers/src/test-utils.ts @@ -0,0 +1,28 @@ +// workaround for https://github.com/testing-library/react-testing-library/issues/1233#issuecomment-1686160909 +export const onlyProps = (props: any) => { + const { + children: { + props: { renderCallbackProps }, + }, + } = props + + return { + ...renderCallbackProps, + } +} + +export const properProps = (props: any) => ({ + ...onlyProps(props), + children: props.children, +}) + +export const CensusUrls = { + dev: 'https://census3.dev.vocdoni.net/api', + stg: 'https://census3.stg.vocdoni.net/api', + prod: 'https://census3.vocdoni.net/api', +} +export const ApiUrl = { + dev: 'https://api-dev.vocdoni.net/v2', + stg: 'https://api-stg.vocdoni.net/v2', + prod: 'https://api.vocdoni.net/v2', +} diff --git a/setup-tests.ts b/setup-tests.ts new file mode 100644 index 00000000..2b801822 --- /dev/null +++ b/setup-tests.ts @@ -0,0 +1,34 @@ +import '@testing-library/jest-dom/extend-expect' +import React from 'react' +import { server } from './mocks/server' + +// Establish API mocking before all tests. +beforeAll(() => + server.listen({ + onUnhandledRequest: 'warn', + }) +) +// Reset any request handlers that we may add during the tests, +// so they don't affect other tests. +afterEach(() => server.resetHandlers()) +// Clean up after the tests are finished. +afterAll(() => server.close()) + +class Worker { + private url + private onmessage + constructor(stringUrl) { + this.url = stringUrl + this.onmessage = () => {} + } + + postMessage(msg) { + this.onmessage(msg) + } +} + +// required due to SDK dependency +Object.defineProperty(window, 'Worker', Worker) + +// required by any react component (almost all of them) +global.React = React diff --git a/templates/chakra/package.json b/templates/chakra/package.json index 7ec1edb6..e53ab646 100644 --- a/templates/chakra/package.json +++ b/templates/chakra/package.json @@ -16,7 +16,7 @@ "@emotion/styled": "^11.10.6", "@rainbow-me/rainbowkit": "^0.12.4", "@vocdoni/chakra-components": "*", - "@vocdoni/sdk": "^0.0.18", + "@vocdoni/sdk": "^0.1.1", "date-fns": "^2.29.3", "ethers": "^5.7.2", "formik": "^2.2.9", diff --git a/templates/chakra/src/App.tsx b/templates/chakra/src/App.tsx index 0976ca10..6131bc4e 100644 --- a/templates/chakra/src/App.tsx +++ b/templates/chakra/src/App.tsx @@ -9,7 +9,7 @@ export const App = () => { const { data: signer } = useSigner() return ( - +