Skip to content

Commit 8d95b11

Browse files
committed
chore: update code and test
1 parent f08a2ae commit 8d95b11

File tree

8 files changed

+1282
-118
lines changed

8 files changed

+1282
-118
lines changed

README.md

Lines changed: 339 additions & 44 deletions
Large diffs are not rendered by default.

jest.config.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'jsdom',
4+
roots: ['<rootDir>/src'],
5+
testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'],
6+
transform: {
7+
'^.+\\.(ts|tsx)$': ['ts-jest', {
8+
tsconfig: {
9+
jsx: 'react',
10+
esModuleInterop: true,
11+
allowSyntheticDefaultImports: true
12+
}
13+
}],
14+
},
15+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
16+
collectCoverageFrom: [
17+
'src/**/*.{ts,tsx}',
18+
'!src/**/*.d.ts',
19+
'!src/**/index.ts',
20+
'!src/**/*.test.{ts,tsx}',
21+
],
22+
coverageDirectory: 'coverage',
23+
coverageReporters: ['text', 'lcov', 'html'],
24+
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
25+
moduleNameMapper: {
26+
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
27+
},
28+
}

jest.setup.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
require('@testing-library/jest-dom')
2+
3+
// Mock canvas for testing
4+
HTMLCanvasElement.prototype.getContext = jest.fn(() => ({
5+
fillStyle: '',
6+
fillRect: jest.fn(),
7+
clearRect: jest.fn(),
8+
getImageData: jest.fn(() => ({
9+
data: new Array(4),
10+
})),
11+
putImageData: jest.fn(),
12+
createImageData: jest.fn(() => []),
13+
setTransform: jest.fn(),
14+
drawImage: jest.fn(),
15+
save: jest.fn(),
16+
restore: jest.fn(),
17+
scale: jest.fn(),
18+
rotate: jest.fn(),
19+
translate: jest.fn(),
20+
transform: jest.fn(),
21+
beginPath: jest.fn(),
22+
closePath: jest.fn(),
23+
moveTo: jest.fn(),
24+
lineTo: jest.fn(),
25+
clip: jest.fn(),
26+
quadraticCurveTo: jest.fn(),
27+
bezierCurveTo: jest.fn(),
28+
arc: jest.fn(),
29+
arcTo: jest.fn(),
30+
isPointInPath: jest.fn(),
31+
stroke: jest.fn(),
32+
fill: jest.fn(),
33+
}))
34+
35+
// Mock Image constructor
36+
global.Image = class {
37+
constructor() {
38+
this.onload = null
39+
this.src = ''
40+
}
41+
}

package.json

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@
4545
"prepare": "husky",
4646
"prepublishOnly": "rm -rf dist/* && NODE_ENV=production rollup -c rollup.config.mjs",
4747
"prettier": "prettier --write '**/*.{ts,tsx,css,scss}'",
48-
"test": "echo \"Error: no test specified\"",
49-
"test-watch": "jest --watch"
48+
"test": "jest",
49+
"test:watch": "jest --watch",
50+
"test:coverage": "jest --coverage"
5051
},
5152
"lint-staged": {
5253
"src/*.{ts,tsx,js,jsx}": "prettier --write",
@@ -58,6 +59,10 @@
5859
"@rollup/plugin-node-resolve": "16.0.1",
5960
"@rollup/plugin-replace": "6.0.2",
6061
"@rollup/plugin-typescript": "12.1.4",
62+
"@testing-library/dom": "10.4.1",
63+
"@testing-library/jest-dom": "6.8.0",
64+
"@testing-library/react": "16.3.0",
65+
"@testing-library/user-event": "14.6.1",
6166
"@types/jasmine": "5.1.9",
6267
"@types/jest": "30.0.0",
6368
"@types/react": "19.1.12",
@@ -67,10 +72,14 @@
6772
"eslint": "9.35.0",
6873
"eslint-config-prettier": "10.1.8",
6974
"husky": "9.1.7",
75+
"identity-obj-proxy": "3.0.0",
7076
"jest": "30.1.3",
77+
"jest-environment-jsdom": "30.1.2",
7178
"lint-staged": "16.1.6",
7279
"prettier": "3.6.2",
7380
"pretty-quick": "4.2.2",
81+
"react": "19.1.1",
82+
"react-dom": "19.1.1",
7483
"rollup": "4.50.0",
7584
"rollup-plugin-uglify": "6.0.4",
7685
"ts-jest": "29.4.1",
@@ -81,5 +90,5 @@
8190
"react": ">=17.0.2",
8291
"react-dom": ">=17.0.2"
8392
},
84-
"packageManager": "[email protected].21"
93+
"packageManager": "[email protected].22"
8594
}

src/index.test.tsx

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import React from 'react'
2+
import { render, screen } from '@testing-library/react'
3+
import { ReactQrCode } from './index'
4+
5+
describe('ReactQrCode', () => {
6+
describe('SVG rendering', () => {
7+
it('should render SVG QR code by default', () => {
8+
const { container } = render(<ReactQrCode value="test" />)
9+
const svg = container.querySelector('svg')
10+
expect(svg).toBeInTheDocument()
11+
expect(svg).toHaveAttribute('width', '256')
12+
expect(svg).toHaveAttribute('height', '256')
13+
})
14+
15+
it('should render with custom size', () => {
16+
const { container } = render(<ReactQrCode value="test" size={512} />)
17+
const svg = container.querySelector('svg')
18+
expect(svg).toHaveAttribute('width', '512')
19+
expect(svg).toHaveAttribute('height', '512')
20+
})
21+
22+
it('should apply custom colors', () => {
23+
const { container } = render(
24+
<ReactQrCode value="test" fgColor="#FF0000" bgColor="#00FF00" />
25+
)
26+
const paths = container.querySelectorAll('path')
27+
const hasRedPath = Array.from(paths).some(
28+
(path) => path.getAttribute('fill') === '#FF0000'
29+
)
30+
expect(hasRedPath).toBe(true)
31+
})
32+
33+
it('should apply margin size', () => {
34+
const { container } = render(<ReactQrCode value="test" marginSize={10} />)
35+
const rect = container.querySelector('rect')
36+
expect(rect).toBeInTheDocument()
37+
})
38+
39+
it('should render with title', () => {
40+
const { container } = render(
41+
<ReactQrCode value="test" title="QR Code Title" />
42+
)
43+
const title = container.querySelector('title')
44+
expect(title).toBeInTheDocument()
45+
expect(title?.textContent).toBe('QR Code Title')
46+
})
47+
48+
it('should apply custom className', () => {
49+
const { container } = render(
50+
<ReactQrCode value="test" className="custom-qr" />
51+
)
52+
const svg = container.querySelector('svg')
53+
expect(svg).toHaveClass('custom-qr')
54+
})
55+
56+
it('should apply custom styles', () => {
57+
const { container } = render(
58+
<ReactQrCode value="test" style={{ border: '1px solid red' }} />
59+
)
60+
const svg = container.querySelector('svg')
61+
expect(svg).toHaveStyle('border: 1px solid red')
62+
})
63+
64+
it('should render images in SVG', () => {
65+
const { container } = render(
66+
<ReactQrCode
67+
value="test"
68+
images={[
69+
{
70+
src: 'https://example.com/logo.png',
71+
height: 24,
72+
width: 24,
73+
},
74+
]}
75+
/>
76+
)
77+
const image = container.querySelector('image')
78+
expect(image).toBeInTheDocument()
79+
expect(image).toHaveAttribute('href', 'https://example.com/logo.png')
80+
expect(image).toHaveAttribute('width', '24')
81+
expect(image).toHaveAttribute('height', '24')
82+
})
83+
84+
it('should render images with excavate', () => {
85+
const { container } = render(
86+
<ReactQrCode
87+
value="test"
88+
images={[
89+
{
90+
src: 'https://example.com/logo.png',
91+
height: 24,
92+
width: 24,
93+
excavate: true,
94+
},
95+
]}
96+
/>
97+
)
98+
const g = container.querySelector('g')
99+
const rect = g?.querySelector('rect')
100+
expect(rect).toBeInTheDocument()
101+
})
102+
103+
it('should position images with custom x and y', () => {
104+
const { container } = render(
105+
<ReactQrCode
106+
value="test"
107+
images={[
108+
{
109+
src: 'https://example.com/logo.png',
110+
x: 50,
111+
y: 50,
112+
height: 24,
113+
width: 24,
114+
},
115+
]}
116+
/>
117+
)
118+
const image = container.querySelector('image')
119+
expect(image).toHaveAttribute('x', '50')
120+
expect(image).toHaveAttribute('y', '50')
121+
})
122+
})
123+
124+
describe('Canvas rendering', () => {
125+
it('should render canvas when renderAs is canvas', () => {
126+
const { container } = render(
127+
<ReactQrCode value="test" renderAs="canvas" />
128+
)
129+
const canvas = container.querySelector('canvas')
130+
expect(canvas).toBeInTheDocument()
131+
expect(canvas).toHaveStyle('width: 256px')
132+
expect(canvas).toHaveStyle('height: 256px')
133+
})
134+
135+
it('should apply custom size to canvas', () => {
136+
const { container } = render(
137+
<ReactQrCode value="test" renderAs="canvas" size={512} />
138+
)
139+
const canvas = container.querySelector('canvas')
140+
expect(canvas).toHaveStyle('width: 512px')
141+
expect(canvas).toHaveStyle('height: 512px')
142+
})
143+
144+
it('should apply custom className to canvas', () => {
145+
const { container } = render(
146+
<ReactQrCode value="test" renderAs="canvas" className="canvas-qr" />
147+
)
148+
const canvas = container.querySelector('canvas')
149+
expect(canvas).toHaveClass('canvas-qr')
150+
})
151+
152+
it('should apply custom id to canvas', () => {
153+
const { container } = render(
154+
<ReactQrCode value="test" renderAs="canvas" id="qr-canvas" />
155+
)
156+
const canvas = container.querySelector('canvas')
157+
expect(canvas).toHaveAttribute('id', 'qr-canvas')
158+
})
159+
})
160+
161+
describe('Error correction levels', () => {
162+
const levels: Array<'L' | 'M' | 'Q' | 'H'> = ['L', 'M', 'Q', 'H']
163+
164+
levels.forEach((level) => {
165+
it(`should render with error correction level ${level}`, () => {
166+
const { container } = render(<ReactQrCode value="test" level={level} />)
167+
const svg = container.querySelector('svg')
168+
expect(svg).toBeInTheDocument()
169+
})
170+
})
171+
})
172+
173+
describe('Value encoding', () => {
174+
it('should encode URLs', () => {
175+
const { container } = render(
176+
<ReactQrCode value="https://github.com/devmehq/react-qr-code" />
177+
)
178+
const svg = container.querySelector('svg')
179+
expect(svg).toBeInTheDocument()
180+
})
181+
182+
it('should encode plain text', () => {
183+
const { container } = render(<ReactQrCode value="Hello, World!" />)
184+
const svg = container.querySelector('svg')
185+
expect(svg).toBeInTheDocument()
186+
})
187+
188+
it('should encode numbers', () => {
189+
const { container } = render(<ReactQrCode value="1234567890" />)
190+
const svg = container.querySelector('svg')
191+
expect(svg).toBeInTheDocument()
192+
})
193+
194+
it('should encode special characters', () => {
195+
const { container } = render(
196+
<ReactQrCode value="!@#$%^&*()_+-=[]{}|;:,.<>?" />
197+
)
198+
const svg = container.querySelector('svg')
199+
expect(svg).toBeInTheDocument()
200+
})
201+
202+
it('should encode long text', () => {
203+
const longText = 'A'.repeat(1000)
204+
const { container } = render(<ReactQrCode value={longText} />)
205+
const svg = container.querySelector('svg')
206+
expect(svg).toBeInTheDocument()
207+
})
208+
})
209+
210+
describe('Default props', () => {
211+
it('should use default value when no value provided', () => {
212+
const { container } = render(<ReactQrCode value="" />)
213+
const svg = container.querySelector('svg')
214+
expect(svg).toBeInTheDocument()
215+
})
216+
217+
it('should use default colors', () => {
218+
const { container } = render(<ReactQrCode value="test" />)
219+
const paths = container.querySelectorAll('path')
220+
const hasBlackPath = Array.from(paths).some(
221+
(path) => path.getAttribute('fill') === '#000000'
222+
)
223+
expect(hasBlackPath).toBe(true)
224+
})
225+
226+
it('should use default size', () => {
227+
const { container } = render(<ReactQrCode value="test" />)
228+
const svg = container.querySelector('svg')
229+
expect(svg).toHaveAttribute('width', '256')
230+
})
231+
232+
it('should use default error correction level', () => {
233+
const { container } = render(<ReactQrCode value="test" />)
234+
const svg = container.querySelector('svg')
235+
expect(svg).toBeInTheDocument()
236+
})
237+
})
238+
})

0 commit comments

Comments
 (0)