Skip to content

Commit cabda79

Browse files
committed
Unit test IconUtils; Add excludes of test files and stories from jest coverage report
1 parent cf6b821 commit cabda79

4 files changed

Lines changed: 340 additions & 5 deletions

File tree

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@
8484
"dist"
8585
],
8686
"jest": {
87+
"collectCoverageFrom": [
88+
"src/**/*.ts*",
89+
"!src/**/*.stories.ts*",
90+
"!src/tests/**/*"
91+
],
8792
"moduleNameMapper": {
8893
"react-spring": "<rootDir>/node_modules/react-spring/web.cjs",
8994
"react-spring/renderprops": "<rootDir>/node_modules/react-spring/renderprops.cjs"
Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import { ReactComponent as Share } from "../../assets/icons/16px/Share.svg";
2525
import { ReactComponent as Trashcan } from "../../assets/icons/16px/Trashcan.svg";
2626
import { ReactComponent as WarningLarge } from "../../assets/icons/24px/Warning.svg";
2727

28+
// -----------------------------------------------------------------------------------------
29+
// #region Constants
30+
// -----------------------------------------------------------------------------------------
31+
2832
const SvgIcons: SvgIcon[] = [
2933
{ type: Icons.Checkmark, base: Checkmark, large: CheckmarkLarge },
3034
{ type: Icons.ChevronDown, base: ChevronDown, large: ChevronDownLarge },
@@ -44,4 +48,20 @@ const SvgIcons: SvgIcon[] = [
4448
{ type: Icons.Warning, base: WarningLarge, large: WarningLarge },
4549
];
4650

47-
export { SvgIcons };
51+
// #endregion Constants
52+
53+
// -----------------------------------------------------------------------------------------
54+
// #region Functions
55+
// -----------------------------------------------------------------------------------------
56+
57+
const getSvgIconByType = (type: Icons) => SvgIcons.find((i) => i.type === type);
58+
59+
// #endregion Functions
60+
61+
// -----------------------------------------------------------------------------------------
62+
// #region Exports
63+
// -----------------------------------------------------------------------------------------
64+
65+
export { getSvgIconByType, SvgIcons };
66+
67+
// #endregion Exports

src/utilities/icon-utils.test.ts

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
import { SvgIcon } from "./../../dist/atoms/interfaces/svg-icon.d";
2+
import { icon } from "./../atoms/icons/icon.stories";
3+
import { Icons } from "../atoms/constants/icons";
4+
import { IconUtils } from "./icon-utils";
5+
import { getSvgIconByType } from "../atoms/constants/svg-icons";
6+
import { IconSizes } from "../atoms/constants/icon-sizes";
7+
import { Icon } from "../atoms/icons/icon";
8+
9+
describe("IconUtils", () => {
10+
beforeEach(() => {
11+
IconUtils.clearRegistry();
12+
});
13+
14+
// -----------------------------------------------------------------------------------------
15+
// #region clearRegistry
16+
// -----------------------------------------------------------------------------------------
17+
18+
describe("clearRegistry", () => {
19+
test("given registry has values, empties registry", () => {
20+
// Arrange
21+
IconUtils.registerSvgIcon(getSvgIconByType(Icons.ChevronDown));
22+
23+
// Act
24+
IconUtils.clearRegistry();
25+
26+
// Assert
27+
expect(Object.keys(IconUtils.getRegistry)).toHaveLength(0);
28+
});
29+
});
30+
31+
// #endregion clearRegistry
32+
33+
// -----------------------------------------------------------------------------------------
34+
// #region getSvg
35+
// -----------------------------------------------------------------------------------------
36+
37+
describe("getSvg", () => {
38+
test("given no icons registered, returns undefined", () => {
39+
expect(IconUtils.getSvg(Icons.Checkmark)).toBeUndefined();
40+
});
41+
42+
test("given no match in registry, returns undefined", () => {
43+
// Arrange
44+
IconUtils.registerSvgIcon(getSvgIconByType(Icons.ChevronDown));
45+
46+
// Act & Assert
47+
expect(IconUtils.getSvg(Icons.Checkmark)).toBeUndefined();
48+
});
49+
50+
test("given match in registry, when no size provided, returns base Svg", () => {
51+
// Arrange
52+
const ChevronDown = getSvgIconByType(Icons.ChevronDown);
53+
IconUtils.registerSvgIcon(ChevronDown);
54+
55+
// Act
56+
const result = IconUtils.getSvg(Icons.ChevronDown);
57+
58+
// Assert
59+
expect(result).toBe(ChevronDown.base);
60+
});
61+
62+
test.each`
63+
size
64+
${IconSizes.Base}
65+
${IconSizes.Large}
66+
`(
67+
"given match in registry, when size of '$size' provided, returns correct Svg",
68+
({ size }) => {
69+
// Arrange
70+
const ChevronDown = getSvgIconByType(Icons.ChevronDown);
71+
IconUtils.registerSvgIcon(ChevronDown);
72+
73+
// Act
74+
const result = IconUtils.getSvg(Icons.ChevronDown, size);
75+
76+
// Assert
77+
expect(result).toBe(ChevronDown[size]);
78+
}
79+
);
80+
});
81+
82+
// #endregion getSvg
83+
84+
// -----------------------------------------------------------------------------------------
85+
// #region getSvgIcon
86+
// -----------------------------------------------------------------------------------------
87+
88+
describe("getSvgIcon", () => {
89+
test("given no icons registered, returns undefined", () => {
90+
expect(IconUtils.getSvgIcon(Icons.Checkmark)).toBeUndefined();
91+
});
92+
93+
test("given no match in registry, returns undefined", () => {
94+
// Arrange
95+
IconUtils.registerSvgIcon(getSvgIconByType(Icons.ChevronDown));
96+
97+
// Act & Assert
98+
expect(IconUtils.getSvgIcon(Icons.Checkmark)).toBeUndefined();
99+
});
100+
101+
test("given match in registry, returns SvgIcon", () => {
102+
// Arrange
103+
const ChevronDown = getSvgIconByType(Icons.ChevronDown);
104+
IconUtils.registerSvgIcon(ChevronDown);
105+
106+
// Act
107+
const result = IconUtils.getSvgIcon(Icons.ChevronDown);
108+
109+
// Assert
110+
expect(result.type).toBe(ChevronDown.type);
111+
});
112+
});
113+
114+
// #endregion getSvgIcon
115+
116+
// -----------------------------------------------------------------------------------------
117+
// #region getRegistry
118+
// -----------------------------------------------------------------------------------------
119+
120+
describe("getRegistry", () => {
121+
test("given no icons registered, returns empty object", () => {
122+
// Arrange & Act
123+
const result = IconUtils.getRegistry();
124+
125+
// Assert
126+
expect(Object.keys(result)).toHaveLength(0);
127+
});
128+
129+
test("given icons registered, returns new instance of registry", () => {
130+
// Arrange
131+
const ChevronDown = getSvgIconByType(Icons.ChevronDown);
132+
IconUtils.registerSvgIcon(ChevronDown);
133+
134+
// Act
135+
const firstResult = IconUtils.getRegistry();
136+
const secondResult = IconUtils.getRegistry();
137+
138+
// Assert
139+
expect(firstResult[ChevronDown.type]).not.toBeUndefined();
140+
expect(secondResult[ChevronDown.type]).not.toBeUndefined();
141+
expect(firstResult).not.toBe(secondResult);
142+
});
143+
});
144+
145+
// #endregion getRegistry
146+
147+
// -----------------------------------------------------------------------------------------
148+
// #region register
149+
// -----------------------------------------------------------------------------------------
150+
151+
describe("register", () => {
152+
test.each`
153+
value
154+
${undefined}
155+
${null}
156+
${[]}
157+
`(
158+
"when icons $value, skips registration and returns registry",
159+
({ value }) => {
160+
// Arrange
161+
const ChevronDown = getSvgIconByType(Icons.ChevronDown);
162+
IconUtils.registerSvgIcon(ChevronDown);
163+
164+
// Act
165+
const registry = IconUtils.register(value);
166+
167+
// Assert
168+
expect(Object.keys(registry)).toHaveLength(1);
169+
expect(registry[ChevronDown.type]).not.toBeUndefined();
170+
}
171+
);
172+
173+
test("given no icons registered, registers provided icons", () => {
174+
// Arrange
175+
const ChevronDown = getSvgIconByType(Icons.ChevronDown);
176+
const ChevronUp = getSvgIconByType(Icons.ChevronUp);
177+
178+
// Act
179+
const registry = IconUtils.register([ChevronDown, ChevronUp]);
180+
181+
// Assert
182+
expect(registry[ChevronDown.type]).not.toBeUndefined();
183+
expect(registry[ChevronUp.type]).not.toBeUndefined();
184+
});
185+
});
186+
187+
// #endregion register
188+
189+
// -----------------------------------------------------------------------------------------
190+
// #region registerSvgIcon
191+
// -----------------------------------------------------------------------------------------
192+
193+
describe("registerSvgIcon", () => {
194+
test.each`
195+
value
196+
${undefined}
197+
${null}
198+
`(
199+
"when icon $value, skip registration and returns empty object",
200+
({ value }) => {
201+
// Arrange & Act
202+
const result = IconUtils.registerSvgIcon(value);
203+
204+
// assert
205+
expect(Object.keys(result)).toHaveLength(0);
206+
}
207+
);
208+
209+
test.each`
210+
value
211+
${undefined}
212+
${null}
213+
`(
214+
"when icon.type $value, skip registration and returns empty object",
215+
({ value }) => {
216+
// Arrange
217+
const ChevronDown = getSvgIconByType(Icons.ChevronDown);
218+
const iconWithoutType: SvgIcon = {
219+
type: null,
220+
base: ChevronDown.base,
221+
large: ChevronDown.large,
222+
};
223+
224+
// Arrange & Act
225+
const result = IconUtils.registerSvgIcon(iconWithoutType);
226+
227+
// assert
228+
expect(Object.keys(result)).toHaveLength(0);
229+
}
230+
);
231+
232+
test("given registry empty, when icon valid, registers icon by type", () => {
233+
// Arrange
234+
const ChevronDown = getSvgIconByType(Icons.ChevronDown);
235+
236+
// Act
237+
const result = IconUtils.registerSvgIcon(ChevronDown);
238+
239+
// Assert
240+
expect(result[ChevronDown.type]).not.toBeUndefined();
241+
});
242+
243+
describe("given registry exists", () => {
244+
test("given no match, when icon valid, registers icon by type", () => {
245+
// Arrange
246+
const ChevronUp = getSvgIconByType(Icons.ChevronUp);
247+
IconUtils.register([ChevronUp]);
248+
249+
const ChevronDown = getSvgIconByType(Icons.ChevronDown);
250+
251+
// Act
252+
const result = IconUtils.registerSvgIcon(ChevronDown);
253+
254+
// Assert
255+
expect(Object.keys(result)).toHaveLength(2);
256+
expect(result[ChevronDown.type]).not.toBeUndefined();
257+
});
258+
259+
test("given match, when icon valid, when throwIfDuplicate false, overwrites match", () => {
260+
// Arrange
261+
const ChevronDown = getSvgIconByType(Icons.ChevronDown);
262+
const ChevronUp = getSvgIconByType(Icons.ChevronUp);
263+
const original: SvgIcon = {
264+
type: ChevronDown.type, // <--------- matching key
265+
base: ChevronUp.base,
266+
large: ChevronUp.large,
267+
};
268+
IconUtils.register([original]);
269+
270+
// Act
271+
const result = IconUtils.registerSvgIcon(ChevronDown, false);
272+
273+
// Assert
274+
expect(Object.keys(result)).toHaveLength(1);
275+
expect(result[ChevronDown.type]).not.toBeUndefined();
276+
expect(result[ChevronDown.type].base).toBe(ChevronDown.base);
277+
expect(result[ChevronDown.type].large).toBe(ChevronDown.large);
278+
});
279+
280+
test("given match, when icon valid, when throwIfDuplicate true, throws an error", () => {
281+
// Arrange
282+
const ChevronDown = getSvgIconByType(Icons.ChevronDown);
283+
const ChevronUp = getSvgIconByType(Icons.ChevronUp);
284+
const original: SvgIcon = {
285+
type: ChevronDown.type, // <--------- matching key
286+
base: ChevronUp.base,
287+
large: ChevronUp.large,
288+
};
289+
IconUtils.register([original]);
290+
291+
// Act & Assert
292+
expect(() =>
293+
IconUtils.registerSvgIcon(ChevronDown, true)
294+
).toThrow();
295+
});
296+
}); // end 'given registry exists'
297+
});
298+
299+
// #endregion registerSvgIcon
300+
});

src/utilities/icon-utils.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,24 @@ let _globalIcons: any = {};
1616
// -----------------------------------------------------------------------------------------
1717

1818
/**
19-
* Retrieve an SVG Icon by type
20-
* @param type Type of SVG Icon
19+
* Empties the global icon registry
2120
*/
22-
const _getSvgIcon = (type: string): SvgIcon => _getRegistry()?.[type];
21+
const _clearRegistry = () => (_globalIcons = {});
2322

2423
/**
2524
* Retrieve a specific SVG Icon size
2625
* @param type Type of SVG Icon
2726
* @param size Size being requested
2827
*/
29-
const _getSvg = (type: string, size: IconSizes): Svg =>
28+
const _getSvg = (type: string, size: IconSizes = IconSizes.Base): Svg =>
3029
_getSvgIcon(type)?.[size] as Svg;
3130

31+
/**
32+
* Retrieve an SVG Icon by type
33+
* @param type Type of SVG Icon
34+
*/
35+
const _getSvgIcon = (type: string): SvgIcon => _getRegistry()?.[type];
36+
3237
/**
3338
* Retrieve a full (immutable) copy of the icon registry
3439
*/
@@ -54,6 +59,10 @@ const _register = (icons: SvgIcon[]) => {
5459
* @param throwIfDuplicate Throw an error if a duplicate for this icon type exists
5560
*/
5661
const _registerSvgIcon = (icon: SvgIcon, throwIfDuplicate: boolean = false) => {
62+
if (icon?.type == null) {
63+
return {};
64+
}
65+
5766
const type = icon.type;
5867

5968
if (throwIfDuplicate && _globalIcons.hasOwnProperty(type)) {
@@ -74,6 +83,7 @@ const _registerSvgIcon = (icon: SvgIcon, throwIfDuplicate: boolean = false) => {
7483
// -----------------------------------------------------------------------------------------
7584

7685
const IconUtils = {
86+
clearRegistry: _clearRegistry,
7787
getSvg: _getSvg,
7888
getSvgIcon: _getSvgIcon,
7989
getRegistry: _getRegistry,

0 commit comments

Comments
 (0)