Skip to content

Commit b96f459

Browse files
committed
add: tests and enhance typing
- Add tests for ImageLoader and DimensionUtils - Use invariant pattern to make sure only correct and expected input will be received and used - converted ImageLoader class to loadImage utility function
1 parent 3dc7f8c commit b96f459

8 files changed

+754
-509
lines changed

src/AbstractQRCodeWithImage.ts

+121-91
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,149 @@
11
import QRCodeRaw from './QRCodeRaw';
2-
import type { OptionsType as ParentOptionsType, QRCodeDataType } from './QRCodeRaw';
2+
import type {
3+
OptionsType as ParentOptionsType,
4+
QRCodeDataType,
5+
} from './QRCodeRaw';
36
import DimensionUtils from './utils/DimensionUtils';
47

58
export type ImageConfigType = {
6-
source: string | typeof Image | HTMLCanvasElement | Promise<any>,
7-
width: number | string, // 20 | 20%
8-
height: number | string, // 20 | 20%
9-
x: number | string, // 20 | 20% | center | left 20% | right 20%
10-
y: number | string, // 20 | 20% | center | top 20% | bottom 20%,
11-
border: number|null,
9+
source: string | typeof Image | HTMLCanvasElement | Promise<unknown>;
10+
width: number | string; // 20 | 20%
11+
height: number | string; // 20 | 20%
12+
x: number | string; // 20 | 20% | center | left 20% | right 20%
13+
y: number | string; // 20 | 20% | center | top 20% | bottom 20%,
14+
border: number | null;
1215
};
1316

1417
export type OptionsType = ParentOptionsType & {
15-
image?: ImageConfigType,
16-
}
18+
image?: ImageConfigType;
19+
};
1720

1821
const DEFAULT_OPTIONS = {
19-
image: null,
22+
image: null,
2023
};
2124

2225
const DEFAULT_IMAGE_BORDER = 1;
2326

2427
export default class AbstractQRCodeWithImage extends QRCodeRaw {
25-
26-
image: ImageConfigType|null = null;
27-
imageConfig: ImageConfigType|null = null;
28-
29-
constructor(value: string, options: Partial<OptionsType> = {}) {
30-
super(value, options);
31-
const params = { ...DEFAULT_OPTIONS, ...options };
32-
this.image = params.image;
28+
image: ImageConfigType | null = null;
29+
imageConfig: ImageConfigType | null = null;
30+
31+
constructor(value: string, options: Partial<OptionsType> = {}) {
32+
super(value, options);
33+
const params = { ...DEFAULT_OPTIONS, ...options };
34+
this.image = params.image;
35+
}
36+
37+
_clearCache(): void {
38+
super._clearCache();
39+
this.imageConfig = null;
40+
}
41+
42+
_getImageSource(imageConfig: ImageConfigType): string | null {
43+
const source = imageConfig.source;
44+
if (typeof source === 'string') {
45+
return source;
3346
}
34-
35-
_clearCache(): void {
36-
super._clearCache();
37-
this.imageConfig = null;
47+
if (source instanceof Image) {
48+
return source.src;
3849
}
39-
40-
_getImageSource(imageConfig: ImageConfigType): string|null {
41-
const source = imageConfig.source;
42-
if (typeof source === 'string') {
43-
return source;
44-
}
45-
if (source instanceof Image) {
46-
return source.src;
47-
}
48-
if (source instanceof HTMLCanvasElement) {
49-
return source.toDataURL();
50-
}
51-
return null;
50+
if (source instanceof HTMLCanvasElement) {
51+
return source.toDataURL();
5252
}
53+
return null;
54+
}
5355

54-
_getImageConfig(): ImageConfigType|null {
55-
if (this.imageConfig) {
56-
return this.imageConfig;
57-
}
58-
if (!this.image || !this.image.source || !this.image.width || !this.image.height) {
59-
return null;
60-
}
61-
const dataSize = this.getDataSize();
62-
if (!dataSize) {
63-
return null;
64-
}
65-
const source = this._getImageSource(this.image);
66-
if (!source) {
67-
return null;
68-
}
56+
_getImageConfig(): ImageConfigType | null {
57+
if (this.imageConfig) {
58+
return this.imageConfig;
59+
}
60+
if (
61+
!this.image ||
62+
!this.image.source ||
63+
!this.image.width ||
64+
!this.image.height
65+
) {
66+
return null;
67+
}
68+
const dataSize = this.getDataSize();
69+
if (!dataSize) {
70+
return null;
71+
}
72+
const source = this._getImageSource(this.image);
73+
if (!source) {
74+
return null;
75+
}
6976

70-
const dataSizeWithoutPadding = dataSize - this.padding * 2;
71-
const width = DimensionUtils.calculateDimension(this.image.width, dataSizeWithoutPadding);
72-
const height = DimensionUtils.calculateDimension(this.image.height, dataSizeWithoutPadding);
73-
const x = DimensionUtils.calculatePosition(this.image.x, width, dataSizeWithoutPadding) + this.padding;
74-
const y = DimensionUtils.calculatePosition(this.image.y, height, dataSizeWithoutPadding) + this.padding;
77+
const dataSizeWithoutPadding = dataSize - this.padding * 2;
78+
const width = DimensionUtils.calculateDimension(
79+
this.image.width,
80+
dataSizeWithoutPadding,
81+
);
82+
const height = DimensionUtils.calculateDimension(
83+
this.image.height,
84+
dataSizeWithoutPadding,
85+
);
86+
const x =
87+
DimensionUtils.calculatePosition(
88+
// @ts-expect-error make types stronger
89+
this.image.x,
90+
width,
91+
dataSizeWithoutPadding,
92+
) + this.padding;
93+
const y =
94+
DimensionUtils.calculatePosition(
95+
// @ts-expect-error make types stronger
96+
this.image.y,
97+
height,
98+
dataSizeWithoutPadding,
99+
) + this.padding;
100+
101+
let border: number | null = DEFAULT_IMAGE_BORDER;
102+
if (typeof this.image.border === 'number' || this.image.border === null) {
103+
border = this.image.border;
104+
}
75105

76-
let border:number|null = DEFAULT_IMAGE_BORDER;
77-
if (typeof this.image.border === 'number' || this.image.border === null) {
78-
border = this.image.border;
79-
}
106+
this.imageConfig = { source, border, x, y, width, height };
107+
return this.imageConfig;
108+
}
80109

81-
this.imageConfig = { source, border, x, y, width, height };
82-
return this.imageConfig;
110+
getData(): QRCodeDataType | null {
111+
if (this.qrCodeData) {
112+
return this.qrCodeData;
83113
}
84114

85-
getData(): QRCodeDataType|null {
86-
if (this.qrCodeData) {
87-
return this.qrCodeData;
88-
}
89-
90-
const data = super.getData();
91-
if (!data) {
92-
return data;
93-
}
115+
const data = super.getData();
116+
if (!data) {
117+
return data;
118+
}
94119

95-
// FIXME better handle string and number types
96-
// @ts-expect-error
97-
const imageConfig: ImageConfigType = this._getImageConfig();
98-
if (imageConfig !== null && imageConfig.width && imageConfig.height) {
99-
if (typeof imageConfig.border === 'number') {
100-
// @ts-expect-error
101-
const begX = Math.max(imageConfig.x - imageConfig.border, 0);
102-
// @ts-expect-error
103-
const begY = Math.max(imageConfig.y - imageConfig.border, 0);
104-
// @ts-expect-error
105-
const endX = Math.min(begX + imageConfig.width + imageConfig.border * 2, data.length);
106-
// @ts-expect-error
107-
const endY = Math.min(begY + imageConfig.height + imageConfig.border * 2, data.length);
108-
for (let y = begY; y < endY; y += 1) {
109-
for (let x = begX; x < endX; x += 1) {
110-
data[y][x] = this.invert ? true : false;
111-
}
112-
}
113-
}
120+
// FIXME better handle string and number types
121+
// @ts-expect-error make types stronger
122+
const imageConfig: ImageConfigType = this._getImageConfig();
123+
if (imageConfig !== null && imageConfig.width && imageConfig.height) {
124+
if (typeof imageConfig.border === 'number') {
125+
// @ts-expect-error make types stronger
126+
const begX = Math.max(imageConfig.x - imageConfig.border, 0);
127+
// @ts-expect-error make types stronger
128+
const begY = Math.max(imageConfig.y - imageConfig.border, 0);
129+
const endX = Math.min(
130+
// @ts-expect-error make types stronger
131+
begX + imageConfig.width + imageConfig.border * 2,
132+
data.length,
133+
);
134+
const endY = Math.min(
135+
// @ts-expect-error make types stronger
136+
begY + imageConfig.height + imageConfig.border * 2,
137+
data.length,
138+
);
139+
for (let y = begY; y < endY; y += 1) {
140+
for (let x = begX; x < endX; x += 1) {
141+
data[y][x] = this.invert ? true : false;
142+
}
114143
}
115-
116-
return data;
144+
}
117145
}
118146

147+
return data;
148+
}
119149
}

0 commit comments

Comments
 (0)