Skip to content

Commit db0b932

Browse files
committed
chore: add unit testing
refs #49
1 parent 0e01c51 commit db0b932

9 files changed

Lines changed: 870 additions & 15 deletions

File tree

.github/workflows/netlify.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ jobs:
3131
- name: Install
3232
run: pnpm i
3333

34+
- name: Test
35+
run: pnpm test
36+
3437
- name: Build DAVINCI UI
3538
run: pnpm build
3639
env:

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
"dev": "vite",
88
"build": "tsc -b && vite build",
99
"preview": "vite preview",
10-
"lint": "tsc -b && eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
10+
"lint": "tsc -b && eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
11+
"test": "vitest run",
12+
"test:watch": "vitest",
13+
"test:coverage": "vitest run --coverage"
1114
},
1215
"dependencies": {
1316
"@dnd-kit/core": "^6.3.1",
@@ -81,11 +84,13 @@
8184
"@typescript-eslint/eslint-plugin": "^8.37.0",
8285
"@typescript-eslint/parser": "^8.37.0",
8386
"@vitejs/plugin-react": "^4.3.3",
87+
"@vitest/coverage-v8": "^4.0.16",
8488
"autoprefixer": "^10.4.21",
8589
"eslint": "^9.31.0",
8690
"eslint-plugin-react-hooks": "^5.2.0",
8791
"eslint-plugin-react-refresh": "^0.4.20",
8892
"globals": "^16.3.0",
93+
"jsdom": "^27.3.0",
8994
"postcss": "^8.5",
9095
"prettier": "3.5.3",
9196
"tailwindcss": "^3.4.17",
@@ -94,6 +99,7 @@
9499
"vite": "^7.0.5",
95100
"vite-plugin-pwa": "^1.0.1",
96101
"vite-tsconfig-paths": "^5.1.2",
102+
"vitest": "^4.0.16",
97103
"workbox-window": "^7.3.0"
98104
},
99105
"packageManager": "pnpm@10.12.1"

pnpm-lock.yaml

Lines changed: 713 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/vote-display.tsx

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { useElection } from '~contexts/election-context'
1717
import { useVocdoniApi } from '~contexts/vocdoni-api-context'
1818
import { usePersistedVote } from '~hooks/use-persisted-vote'
1919
import { useUnifiedWallet } from '~hooks/use-unified-wallet'
20+
import { getBinaryArray, padTo } from '~lib/vote-utils'
2021
import { truncateAddress } from '~lib/web3-utils'
2122
import { NetworkValidationBanner } from './network-validation-banner'
2223
import RelativeTimeRemaining from './relative-time-remaining'
@@ -1057,17 +1058,3 @@ export const WalletEligibilityStatus = () => {
10571058
</div>
10581059
)
10591060
}
1060-
1061-
export function getBinaryArray(positions: string[], value: number = 1): number[] {
1062-
const result = Array(8).fill(0)
1063-
positions.forEach((posStr) => {
1064-
const pos = parseInt(posStr, 10)
1065-
if (!isNaN(pos) && pos >= 0 && pos < 8) {
1066-
result[pos] = value
1067-
}
1068-
})
1069-
return result
1070-
}
1071-
1072-
const padTo = (arr: number[], length: number = 8): number[] =>
1073-
arr.concat(Array(length - arr.length).fill(0)).slice(0, length)

src/lib/utils.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { cn, enumToReverseObject, formatInterval, formatNanosecondsInterval, truncateText } from './utils'
4+
5+
describe('utils', () => {
6+
describe('cn', () => {
7+
it('merges tailwind conflicting classes', () => {
8+
expect(cn('p-2', 'p-4')).toBe('p-4')
9+
expect(cn('text-red-500', 'text-blue-500')).toBe('text-blue-500')
10+
})
11+
12+
it('supports conditional values', () => {
13+
const shouldInclude = false
14+
expect(cn('a', shouldInclude && 'b', undefined, null, 'c')).toBe('a c')
15+
})
16+
})
17+
18+
describe('truncateText', () => {
19+
it('returns the original string when under the limit', () => {
20+
expect(truncateText('hello', 10)).toBe('hello')
21+
})
22+
23+
it('truncates and appends ellipsis by default', () => {
24+
expect(truncateText('hello world', 8)).toBe('hello...')
25+
})
26+
27+
it('uses a custom ellipsis', () => {
28+
expect(truncateText('hello world', 8, '…')).toBe('hello w…')
29+
})
30+
31+
it('truncates ellipsis itself when limit is shorter than ellipsis', () => {
32+
expect(truncateText('hello world', 2, '...')).toBe('..')
33+
})
34+
})
35+
36+
describe('formatInterval', () => {
37+
it('formats milliseconds into a human readable string', () => {
38+
expect(formatInterval(0)).toBe('')
39+
expect(formatInterval(1000)).toBe('1 second')
40+
expect(formatInterval(61_000)).toBe('1 minute 1 second')
41+
})
42+
})
43+
44+
describe('formatNanosecondsInterval', () => {
45+
it('converts nanoseconds to milliseconds before formatting', () => {
46+
expect(formatNanosecondsInterval(1_000_000_000)).toBe('1 second')
47+
})
48+
})
49+
50+
describe('enumToReverseObject', () => {
51+
it('builds a numeric reverse map from a TS numeric enum-like object', () => {
52+
const enumLike = { 0: 'A', 1: 'B', A: 0, B: 1 } as const
53+
expect(enumToReverseObject(enumLike)).toEqual({ 0: 'A', 1: 'B' })
54+
})
55+
})
56+
})

src/lib/vote-utils.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { getBinaryArray, padTo } from './vote-utils'
4+
5+
describe('vote-utils', () => {
6+
describe('getBinaryArray', () => {
7+
it('returns an array of length 8 with 1s in the selected positions', () => {
8+
expect(getBinaryArray(['0', '3'])).toEqual([1, 0, 0, 1, 0, 0, 0, 0])
9+
})
10+
11+
it('uses the provided numeric value', () => {
12+
expect(getBinaryArray(['1', '7'], 5)).toEqual([0, 5, 0, 0, 0, 0, 0, 5])
13+
})
14+
15+
it('ignores invalid positions', () => {
16+
expect(getBinaryArray(['-1', '8', 'foo', '2'])).toEqual([0, 0, 1, 0, 0, 0, 0, 0])
17+
})
18+
})
19+
20+
describe('padTo', () => {
21+
it('pads an array with zeros up to length', () => {
22+
expect(padTo([1, 2], 5)).toEqual([1, 2, 0, 0, 0])
23+
})
24+
25+
it('truncates an array down to length', () => {
26+
expect(padTo([1, 2, 3, 4], 2)).toEqual([1, 2])
27+
})
28+
})
29+
})
30+

src/lib/vote-utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export function getBinaryArray(positions: string[], value: number = 1): number[] {
2+
const result = Array(8).fill(0)
3+
positions.forEach((posStr) => {
4+
const pos = parseInt(posStr, 10)
5+
if (!Number.isNaN(pos) && pos >= 0 && pos < 8) {
6+
result[pos] = value
7+
}
8+
})
9+
return result
10+
}
11+
12+
export function padTo(arr: number[], length: number = 8): number[] {
13+
return arr.concat(Array(Math.max(0, length - arr.length)).fill(0)).slice(0, length)
14+
}
15+

src/lib/web3-utils.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { getNetworkName, truncateAddress } from './web3-utils'
4+
5+
describe('web3-utils', () => {
6+
describe('truncateAddress', () => {
7+
it('returns empty string for empty input', () => {
8+
expect(truncateAddress('')).toBe('')
9+
})
10+
11+
it('truncates long addresses with defaults', () => {
12+
expect(truncateAddress('0x1234567890abcdef')).toBe('0x1234...cdef')
13+
})
14+
15+
it('returns the original address if it is shorter than the requested truncation', () => {
16+
expect(truncateAddress('0x1234', 6, 4)).toBe('0x1234')
17+
})
18+
})
19+
20+
describe('getNetworkName', () => {
21+
it('matches known hex chain ids', () => {
22+
expect(getNetworkName('0xaa36a7')).toBe('Sepolia')
23+
})
24+
25+
it('converts decimal ids to hex', () => {
26+
expect(getNetworkName(1)).toBe('Ethereum Mainnet')
27+
})
28+
29+
it('returns Unknown Network for unknown ids', () => {
30+
expect(getNetworkName('0xdeadbeef')).toBe('Unknown Network')
31+
})
32+
})
33+
})
34+

vite.config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ const viteconfig = defineConfig(({ mode }) => {
99
process.env = { ...process.env, ...loadEnv(mode, process.cwd(), '') }
1010

1111
return {
12+
test: {
13+
environment: 'jsdom',
14+
globals: true,
15+
include: ['src/**/*.test.{ts,tsx}'],
16+
coverage: {
17+
provider: 'v8',
18+
reporter: ['text', 'html'],
19+
include: ['src/lib/**/*.ts'],
20+
exclude: ['src/lib/**/*.d.ts'],
21+
},
22+
},
1223
plugins: [
1324
react(),
1425
tsconfigPaths(),

0 commit comments

Comments
 (0)