@realfavicongenerator/check-favicon 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
File without changes
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFDC5D" d="M32 20c0-2.209-1.119-4-2.5-4-.012 0-.021.005-.033.005C27.955 9.704 23.394 5.125 18 5.125s-9.956 4.58-11.467 10.88C6.521 16.004 6.511 16 6.5 16 5.119 16 4 17.791 4 20c0 2.107 1.021 3.815 2.314 3.97C7.537 30.619 12.299 35 18 35c5.7 0 10.463-4.381 11.685-11.03C30.979 23.815 32 22.107 32 20z"/><path fill="#662113" d="M13 21c-.552 0-1-.447-1-1v-2c0-.552.448-1 1-1s1 .448 1 1v2c0 .553-.448 1-1 1zm10 0c-.553 0-1-.447-1-1v-2c0-.552.447-1 1-1s1 .448 1 1v2c0 .553-.447 1-1 1z"/><path fill="#C1694F" d="M18 31c-4.201 0-5.491-1.077-5.707-1.293-.391-.391-.391-1.023 0-1.414.378-.379.984-.39 1.376-.036.08.058 1.1.743 4.331.743 3.355 0 4.326-.739 4.336-.747.39-.389 1.001-.37 1.393.021.391.391.369 1.043-.021 1.434C23.491 29.923 22.201 31 18 31zm1-5h-2c-.552 0-1-.447-1-1s.448-1 1-1h2c.553 0 1 .447 1 1s-.447 1-1 1z"/><path fill="#FFAC33" d="M33 12.65c0-.891-.469-1.667-1.171-2.11.106-.277.171-.575.171-.89 0-1.276-.959-2.316-2.193-2.469.118-.323.193-.667.193-1.031 0-1.657-1.343-3-3-3-.395 0-.77.081-1.115.22-.346-1.276-1.5-2.22-2.885-2.22-.87 0-1.646.376-2.194.967C20.384.971 19.292.15 18 .15s-2.384.821-2.806 1.967C14.646 1.526 13.87 1.15 13 1.15c-1.385 0-2.539.944-2.884 2.22-.346-.139-.721-.22-1.116-.22-1.657 0-3 1.343-3 3 0 .364.075.708.193 1.031C4.959 7.334 4 8.374 4 9.65c0 .315.065.613.171.89C3.469 10.983 3 11.759 3 12.65c0 .878.455 1.648 1.141 2.094-.088.281-.141.585-.141.906 0 1.381.895 2.5 2 2.5.327 0 .63-.107.903-.281C7.347 19.312 8 20.132 8 18.15c0-1.585.013-2.552.76-4.081 1.189.403 2.908.159 4.533-.779.282-.163.545-.34.793-.525.378 1.362 1.986 2.385 3.914 2.385 1.296 0 2.437-.47 3.167-1.187.476 1.259 2.002 2.187 3.833 2.187 1.076 0 2.049-.322 2.767-.841.262.914.272 1.723.272 2.841 0 2.348.674 1.358 1.119-.24.256.15.54.24.842.24 1.105 0 2-1.119 2-2.5 0-.321-.053-.625-.14-.906.685-.446 1.14-1.216 1.14-2.094z"/></svg>
Binary file
Binary file
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+ };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@realfavicongenerator/check-favicon",
3
+ "version": "0.0.1",
4
+ "description": "Check the favicon of a website",
5
+ "main": "index.ts",
6
+ "scripts": {
7
+ "test": "jest"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/RealFaviconGenerator/check-favicon.git"
12
+ },
13
+ "keywords": [
14
+ "favicon",
15
+ "realfavicongenerator",
16
+ "checker",
17
+ "check",
18
+ "RFG"
19
+ ],
20
+ "author": "Philippe Bernard",
21
+ "license": "ISC",
22
+ "bugs": {
23
+ "url": "https://github.com/RealFaviconGenerator/check-favicon/issues"
24
+ },
25
+ "homepage": "https://github.com/RealFaviconGenerator/check-favicon#readme",
26
+ "devDependencies": {
27
+ "@types/jest": "^29.5.12",
28
+ "@types/node": "^20.11.30",
29
+ "jest": "^29.7.0",
30
+ "ts-jest": "^29.1.2",
31
+ "typescript": "^5.4.3"
32
+ },
33
+ "dependencies": {
34
+ "decode-ico": "^0.4.1",
35
+ "node-html-parser": "^6.1.13",
36
+ "sharp": "^0.33.3"
37
+ }
38
+ }
@@ -0,0 +1,101 @@
1
+ import { parse } from 'node-html-parser'
2
+ import { checkSvgFavicon } from "./desktop";
3
+ import { CheckerMessage, CheckerStatus, FetchResponse, MessageId } from '..';
4
+ import { filePathToReadableStream, filePathToString, stringToReadableStream } from '../helper';
5
+ import { testFetcher } from '../test-helper';
6
+
7
+ type TestOutput = Pick<CheckerMessage, 'id' | 'status'>[];
8
+
9
+ const runTest = async (
10
+ headFragment: string | null,
11
+ output: TestOutput,
12
+ fetchDatabase: { [url: string]: FetchResponse } = {}
13
+ ) => {
14
+ const root = headFragment ? parse(headFragment) : null;
15
+ const result = await checkSvgFavicon('https://example.com/', root, testFetcher(fetchDatabase));
16
+ const filteredMessages = result.map(m => ({ status: m.status, id: m.id }));
17
+ expect(filteredMessages).toEqual(output);
18
+ }
19
+
20
+ test('checkSvgFavicon - noHead', async () => {
21
+ await runTest(null, [{
22
+ status: CheckerStatus.Error,
23
+ id: MessageId.noHead,
24
+ }]);
25
+ })
26
+
27
+ test('checkSvgFavicon - noSvgFavicon', async () => {
28
+ await runTest(`<title>SOme text</title>`, [{
29
+ status: CheckerStatus.Error,
30
+ id: MessageId.noSvgFavicon,
31
+ }]);
32
+ })
33
+
34
+ test('checkSvgFavicon - multipleSvgFavicons', async () => {
35
+ await runTest(`
36
+ <link rel="icon" type="image/svg+xml" href="/the-icon.svg" />
37
+ <link rel="icon" type="image/svg+xml" href="/another-icon.svg" />
38
+ `, [{
39
+ status: CheckerStatus.Error,
40
+ id: MessageId.multipleSvgFavicons,
41
+ }]);
42
+ })
43
+
44
+ test('checkSvgFavicon - svgFaviconDeclared & noSvgFaviconHref', async () => {
45
+ await runTest(`<link rel="icon" type="image/svg+xml" />`, [{
46
+ status: CheckerStatus.Ok,
47
+ id: MessageId.svgFaviconDeclared,
48
+ }, {
49
+ status: CheckerStatus.Error,
50
+ id: MessageId.noSvgFaviconHref,
51
+ }]);
52
+ })
53
+
54
+ test('checkSvgFavicon - svgFaviconDeclared & svgFavicon404', async () => {
55
+ await runTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`, [{
56
+ status: CheckerStatus.Ok,
57
+ id: MessageId.svgFaviconDeclared,
58
+ }, {
59
+ status: CheckerStatus.Error,
60
+ id: MessageId.svgFavicon404,
61
+ }]);
62
+ })
63
+
64
+ test('checkSvgFavicon - svgFaviconDeclared & svgFaviconCannotGet', async () => {
65
+ await runTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`, [{
66
+ status: CheckerStatus.Ok,
67
+ id: MessageId.svgFaviconDeclared,
68
+ }, {
69
+ status: CheckerStatus.Error,
70
+ id: MessageId.svgFaviconCannotGet,
71
+ }], {
72
+ 'https://example.com/the-icon.svg': {
73
+ status: 403,
74
+ contentType: 'image/svg+xml'
75
+ }
76
+ });
77
+ })
78
+
79
+ test('checkSvgFavicon - svgFaviconDeclared & svgFaviconDownloadable & svgFaviconSquare', async () => {
80
+ const testIconPath = './fixtures/happy-face.svg';
81
+
82
+ const serpIcon = await filePathToString(testIconPath);
83
+
84
+ await runTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`, [
85
+ {
86
+ status: CheckerStatus.Ok,
87
+ id: MessageId.svgFaviconDeclared,
88
+ }, {
89
+ status: CheckerStatus.Ok,
90
+ id: MessageId.svgFaviconDownloadable,
91
+ }, {
92
+ status: CheckerStatus.Ok,
93
+ id: MessageId.svgFaviconSquare,
94
+ }], {
95
+ 'https://example.com/the-icon.svg': {
96
+ status: 200,
97
+ contentType: 'image/svg+xml',
98
+ readableStream: await filePathToReadableStream(testIconPath)
99
+ }
100
+ });
101
+ })
@@ -0,0 +1,207 @@
1
+ import { CheckerMessage, CheckerStatus, DesktopFaviconReport, Fetcher, MessageId, fetchFetcher } from "..";
2
+ import { HTMLElement } from 'node-html-parser'
3
+ import sharp from 'sharp'
4
+ import { CheckIconProcessor, checkIcon, mergeUrlAndPath, readableStreamToString } from "../helper";
5
+ import { checkIcoFavicon } from "./ico";
6
+
7
+ export const PngFaviconFileSize = 48;
8
+
9
+ export const checkSvgFavicon = async (baseUrl: string, head: HTMLElement | null, fetcher: Fetcher = fetchFetcher): Promise<CheckerMessage[]> => {
10
+ const messages: CheckerMessage[] = [];
11
+
12
+ if (!head) {
13
+ messages.push({
14
+ status: CheckerStatus.Error,
15
+ id: MessageId.noHead,
16
+ text: 'No <head> element'
17
+ });
18
+
19
+ return messages;
20
+ }
21
+
22
+ const svgs = head?.querySelectorAll("link[rel='icon'][type='image/svg+xml']");
23
+ if (svgs.length === 0) {
24
+ messages.push({
25
+ status: CheckerStatus.Error,
26
+ id: MessageId.noSvgFavicon,
27
+ text: 'There is no SVG favicon'
28
+ });
29
+ } else if (svgs.length > 1) {
30
+ messages.push({
31
+ status: CheckerStatus.Error,
32
+ id: MessageId.multipleSvgFavicons,
33
+ text: `There are ${svgs.length} SVG favicons`
34
+ });
35
+ } else {
36
+ messages.push({
37
+ status: CheckerStatus.Ok,
38
+ id: MessageId.svgFaviconDeclared,
39
+ text: 'The SVG favicon is declared'
40
+ });
41
+
42
+ const href = svgs[0].attributes.href;
43
+ if (!href) {
44
+ messages.push({
45
+ status: CheckerStatus.Error,
46
+ id: MessageId.noSvgFaviconHref,
47
+ text: 'The SVG markup has no href attribute'
48
+ });
49
+ } else {
50
+ const iconMessages = await checkSvgFaviconFile(baseUrl, href, fetcher)
51
+ return [ ...messages, ...iconMessages ];
52
+ }
53
+ }
54
+
55
+ return messages;
56
+ }
57
+
58
+ export const checkSvgFaviconFile = async (baseUrl: string, url: string, fetcher: Fetcher): Promise<CheckerMessage[]> => {
59
+ const messages: CheckerMessage[] = [];
60
+
61
+ const svgUrl = mergeUrlAndPath(baseUrl, url);
62
+
63
+ const res = await fetcher(svgUrl, 'image/svg+xml');
64
+ if (res.status === 404) {
65
+ messages.push({
66
+ status: CheckerStatus.Error,
67
+ id: MessageId.svgFavicon404,
68
+ text: `The SVG icon file \`${url}\` does not exist (404 error)`
69
+ });
70
+ } else if (res.status >= 300) {
71
+ messages.push({
72
+ status: CheckerStatus.Error,
73
+ id: MessageId.svgFaviconCannotGet,
74
+ text: `Cannot get the SVG icon file at \`${url}\` (${res.status} error)`
75
+ });
76
+ } else if (res.readableStream) {
77
+ messages.push({
78
+ status: CheckerStatus.Ok,
79
+ id: MessageId.svgFaviconDownloadable,
80
+ text: `The SVG favicon is accessible at \`${url}\``
81
+ });
82
+
83
+ const content = await readableStreamToString(res.readableStream);
84
+ const meta = await sharp(Buffer.from(content)).metadata();
85
+
86
+ if (meta.width !== meta.height) {
87
+ messages.push({
88
+ status: CheckerStatus.Error,
89
+ id: MessageId.svgFaviconNotSquare,
90
+ text: `The SVG is not square (${meta.width}x${meta.height})`
91
+ });
92
+ } else {
93
+ messages.push({
94
+ status: CheckerStatus.Ok,
95
+ id: MessageId.svgFaviconSquare,
96
+ text: `The SVG is square (${meta.width}x${meta.height})`
97
+ });
98
+ }
99
+ }
100
+
101
+ return messages;
102
+ }
103
+
104
+ export const checkPngFavicon = async (baseUrl: string, head: HTMLElement | null, fetcher: Fetcher = fetchFetcher): Promise<DesktopFaviconReport> => {
105
+ const messages: CheckerMessage[] = [];
106
+
107
+ if (!head) {
108
+ messages.push({
109
+ status: CheckerStatus.Error,
110
+ id: MessageId.noHead,
111
+ text: 'No <head> element'
112
+ });
113
+
114
+ return { messages, icon: null };
115
+ }
116
+
117
+ const icons = head?.querySelectorAll("link[rel='icon'][type='image/png']");
118
+ if (icons.length === 0) {
119
+ messages.push({
120
+ status: CheckerStatus.Error,
121
+ id: MessageId.noDesktopPngFavicon,
122
+ text: 'There is no desktop PNG favicon'
123
+ });
124
+ } else {
125
+ const size = `${PngFaviconFileSize}x${PngFaviconFileSize}`;
126
+ const sizedIconMarkup = icons.filter(icon => icon.attributes.sizes === size);
127
+
128
+ if (sizedIconMarkup.length === 0) {
129
+ messages.push({
130
+ status: CheckerStatus.Error,
131
+ id: MessageId.no48x48DesktopPngFavicon,
132
+ text: `There is no ${size} desktop PNG favicon`
133
+ });
134
+ } else {
135
+ messages.push({
136
+ status: CheckerStatus.Ok,
137
+ id: MessageId.desktopPngFaviconDeclared,
138
+ text: `The ${size} desktop PNG favicon is declared`
139
+ });
140
+
141
+ const href = icons[0].attributes.href;
142
+ if (!href) {
143
+ messages.push({
144
+ status: CheckerStatus.Error,
145
+ id: MessageId.noDesktopPngFaviconHref,
146
+ text: `The ${size} desktop favicon markup has no href attribute`
147
+ });
148
+ } else {
149
+ const iconUrl = mergeUrlAndPath(baseUrl, href);
150
+ const processor: CheckIconProcessor = {
151
+ cannotGet: (httpStatus) => {
152
+ messages.push({
153
+ status: CheckerStatus.Error,
154
+ id: MessageId.desktopPngFaviconCannotGet,
155
+ text: `Cannot get the ${size} desktop PNG favicon at \`${iconUrl}\` (${httpStatus} error)`
156
+ });
157
+ },
158
+ downloadable: () => {
159
+ messages.push({
160
+ status: CheckerStatus.Ok,
161
+ id: MessageId.desktopPngFaviconDownloadable,
162
+ text: `The ${size} desktop PNG favicon is accessible`
163
+ });
164
+ },
165
+ icon404: () => {
166
+ messages.push({
167
+ status: CheckerStatus.Error,
168
+ id: MessageId.desktopPngFavicon404,
169
+ text: `The ${size} desktop PNG favicon does not exist (404 error)`
170
+ });
171
+ },
172
+ notSquare: (width, height) => {}, // Ignore this message
173
+ wrongSize: (widthHeight) => {
174
+ messages.push({
175
+ status: CheckerStatus.Error,
176
+ id: MessageId.desktopPngFaviconWrongSize,
177
+ text: `The ${size} desktop PNG favicon has the wrong size (${widthHeight}x${widthHeight})`
178
+ });
179
+ },
180
+ rightSize(widthHeight) {
181
+ messages.push({
182
+ status: CheckerStatus.Ok,
183
+ id: MessageId.desktopPngFaviconRightSize,
184
+ text: `The ${size} desktop PNG favicon has the right size`
185
+ });
186
+ },
187
+ square: (widthHeight) => {}, // Ignore this message,
188
+ noHref: () => {} // Ignore this message
189
+ };
190
+ const icon = await checkIcon(iconUrl, processor, fetcher, icons[0].attributes.mimeType || 'image/png', PngFaviconFileSize);
191
+ return { messages, icon }
192
+ }
193
+ }
194
+ }
195
+
196
+ return { messages, icon: null };
197
+ }
198
+
199
+ export const checkDesktopFavicon = async (baseUrl: string, head: HTMLElement | null, fetcher: Fetcher = fetchFetcher): Promise<DesktopFaviconReport> => {
200
+ const svgMessages = await checkSvgFavicon(baseUrl, head, fetcher);
201
+ const pngReport = await checkPngFavicon(baseUrl, head, fetcher);
202
+ const icoReport = await checkIcoFavicon(baseUrl, head, fetcher);
203
+ return {
204
+ messages: [ ...svgMessages, ...pngReport.messages, ...icoReport ],
205
+ icon: pngReport.icon
206
+ };
207
+ }
@@ -0,0 +1,110 @@
1
+ import { CheckerMessage, CheckerStatus, Fetcher, MessageId } from "..";
2
+ import { HTMLElement } from 'node-html-parser'
3
+ import { mergeUrlAndPath, readableStreamToBuffer } from "../helper";
4
+ import decodeIco from "decode-ico";
5
+
6
+ export const IcoFaviconSizes = [ 48, 32, 16 ];
7
+
8
+ export const checkIcoFavicon = async (url: string, head: HTMLElement | null, fetcher: Fetcher): Promise<CheckerMessage[]> => {
9
+ const messages: CheckerMessage[] = [];
10
+
11
+ if (!head) {
12
+ messages.push({
13
+ status: CheckerStatus.Error,
14
+ id: MessageId.noHead,
15
+ text: 'No <head> element'
16
+ });
17
+
18
+ return messages;
19
+ }
20
+
21
+ const icos =
22
+ head.querySelectorAll('link[rel="shortcut icon"]') ||
23
+ head.querySelectorAll('link[rel="icon"][type="image/x-icon"]');
24
+
25
+ if (icos.length === 0) {
26
+ messages.push({
27
+ status: CheckerStatus.Error,
28
+ id: MessageId.noIcoFavicon,
29
+ text: 'There is no ICO favicon'
30
+ });
31
+ } else if (icos.length > 1) {
32
+ messages.push({
33
+ status: CheckerStatus.Error,
34
+ id: MessageId.multipleIcoFavicons,
35
+ text: `There are ${icos.length} ICO favicons`
36
+ });
37
+ } else {
38
+ messages.push({
39
+ status: CheckerStatus.Ok,
40
+ id: MessageId.icoFaviconDeclared,
41
+ text: 'The ICO favicon is declared'
42
+ });
43
+
44
+ const href = icos[0].attributes.href;
45
+ if (!href) {
46
+ messages.push({
47
+ status: CheckerStatus.Error,
48
+ id: MessageId.noIcoFaviconHref,
49
+ text: 'The ICO markup has no href attribute'
50
+ });
51
+ } else {
52
+ const iconUrl = mergeUrlAndPath(url, href);
53
+ const iconResponse = await fetcher(iconUrl, 'image/x-icon');
54
+ if (iconResponse.status === 404) {
55
+ messages.push({
56
+ status: CheckerStatus.Error,
57
+ id: MessageId.icoFavicon404,
58
+ text: `ICO favicon not found at ${iconUrl}`
59
+ });
60
+ } else if (iconResponse.status >= 300 || !iconResponse.readableStream) {
61
+ messages.push({
62
+ status: CheckerStatus.Error,
63
+ id: MessageId.icoFaviconCannotGet,
64
+ text: `Error fetching ICO favicon at ${iconUrl} (status ${iconResponse.status})`
65
+ });
66
+ } else {
67
+ messages.push({
68
+ status: CheckerStatus.Ok,
69
+ id: MessageId.icoFaviconDownloadable,
70
+ text: 'ICO favicon found'
71
+ });
72
+
73
+ const iconBuffer = await readableStreamToBuffer(iconResponse.readableStream);
74
+ const images = await decodeIco(iconBuffer);
75
+
76
+ const imageSizes = images.map(image => `${image.width}x${image.height}`);
77
+
78
+ const expectedSizes = IcoFaviconSizes.map(size => `${size}x${size}`);
79
+
80
+ const extraSizes = imageSizes.filter(size => !expectedSizes.includes(size));
81
+ if (extraSizes.length > 0) {
82
+ messages.push({
83
+ status: CheckerStatus.Warning,
84
+ id: MessageId.icoFaviconExtraSizes,
85
+ text: `Extra sizes found in ICO favicon: ${extraSizes.join(', ')}`
86
+ });
87
+ }
88
+
89
+ const missingSizes = expectedSizes.filter(size => !imageSizes.includes(size));
90
+ if (missingSizes.length > 0) {
91
+ messages.push({
92
+ status: CheckerStatus.Warning,
93
+ id: MessageId.icoFaviconMissingSizes,
94
+ text: `Missing sizes in ICO favicon: ${missingSizes.join(', ')}`
95
+ });
96
+ }
97
+
98
+ if (extraSizes.length === 0 && missingSizes.length === 0) {
99
+ messages.push({
100
+ status: CheckerStatus.Ok,
101
+ id: MessageId.icoFaviconExpectedSizes,
102
+ text: `The ICO favicon has the expected sizes (${imageSizes.join(', ')})`
103
+ });
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ return messages;
110
+ }
@@ -0,0 +1,128 @@
1
+ import sharp from "sharp";
2
+ import { CheckIconProcessor, checkIcon, filePathToReadableStream, mergeUrlAndPath, parseSizesAttribute } from "./helper";
3
+ import { testFetcher } from "./test-helper";
4
+
5
+ const getTestProcessor = () => {
6
+ const messages: string[] = [];
7
+
8
+ const processor: CheckIconProcessor = {
9
+ noHref: () => { messages.push('noHref') },
10
+ icon404: () => { messages.push('icon404') },
11
+ cannotGet: (httpStatusCode: number) => { messages.push(`cannotGet ${httpStatusCode}`) },
12
+ downloadable: () => { messages.push('downloadable') },
13
+ square: (widthHeight: number) => { messages.push(`square ${widthHeight}`) },
14
+ notSquare: (width: number, Height: number) => { messages.push(`notSquare ${width}x${Height}`) },
15
+ rightSize: (width: number) => { messages.push(`rightSize ${width}x${width}`) },
16
+ wrongSize: (widthHeight: number) => { messages.push(`wrongSize ${widthHeight}x${widthHeight}`) }
17
+ }
18
+
19
+ return { processor, messages };
20
+ }
21
+
22
+ const testIcon = './fixtures/logo-transparent.png';
23
+ const nonSquareIcon = './fixtures/non-square.png';
24
+
25
+ test('checkIcon - noHref', async () => {
26
+ const processor = getTestProcessor();
27
+ expect(await checkIcon(undefined, processor.processor, testFetcher({}), 'image/png')).toBeNull();
28
+ expect(processor.messages).toEqual(['noHref']);
29
+ })
30
+
31
+ test('checkIcon - icon404', async () => {
32
+ const processor = getTestProcessor();
33
+ expect(await checkIcon('/does-not-exist.png', processor.processor, testFetcher({}), 'image/png')).toBeNull();
34
+ expect(processor.messages).toEqual(['icon404']);
35
+ })
36
+
37
+ test('checkIcon - icon404', async () => {
38
+ const processor = getTestProcessor();
39
+ expect(await checkIcon('/bad-icon.png', processor.processor, testFetcher({
40
+ '/bad-icon.png': {
41
+ contentType: 'image/png',
42
+ status: 500
43
+ }
44
+ }), 'image/png')).toBeNull();
45
+ expect(processor.messages).toEqual(['cannotGet 500']);
46
+ })
47
+
48
+ test('checkIcon - downloadable & square', async () => {
49
+ const processor = getTestProcessor();
50
+ expect(await checkIcon('/some-icon.png', processor.processor, testFetcher({
51
+ '/some-icon.png': {
52
+ status: 200,
53
+ contentType: 'image/png',
54
+ readableStream: await filePathToReadableStream(testIcon)
55
+ }
56
+ }), 'image/png')).not.toBeNull();
57
+ expect(processor.messages).toEqual([
58
+ 'downloadable',
59
+ 'square 754'
60
+ ]);
61
+ })
62
+
63
+ test('checkIcon - downloadable & rightSize', async () => {
64
+ const processor = getTestProcessor();
65
+ expect(await checkIcon('/some-icon.png', processor.processor, testFetcher({
66
+ '/some-icon.png': {
67
+ status: 200,
68
+ contentType: 'image/png',
69
+ readableStream: await filePathToReadableStream(testIcon)
70
+ }
71
+ }), 'image/png', 754)).not.toBeNull();
72
+ expect(processor.messages).toEqual([
73
+ 'downloadable',
74
+ 'square 754',
75
+ 'rightSize 754x754'
76
+ ]);
77
+ })
78
+
79
+ test('checkIcon - downloadable & wrongSize', async () => {
80
+ const processor = getTestProcessor();
81
+ expect(await checkIcon('/some-icon.png', processor.processor, testFetcher({
82
+ '/some-icon.png': {
83
+ status: 200,
84
+ contentType: 'image/png',
85
+ readableStream: await filePathToReadableStream(testIcon)
86
+ }
87
+ }), 'image/png', 500)).not.toBeNull();
88
+ expect(processor.messages).toEqual([
89
+ 'downloadable',
90
+ 'square 754',
91
+ 'wrongSize 754x754'
92
+ ]);
93
+ })
94
+
95
+ test('checkIcon - downloadable & notSquare', async () => {
96
+ const processor = getTestProcessor();
97
+ expect(await checkIcon('/non-square-icon.png', processor.processor, testFetcher({
98
+ '/non-square-icon.png': {
99
+ status: 200,
100
+ contentType: 'image/png',
101
+ readableStream: await filePathToReadableStream(nonSquareIcon)
102
+ }
103
+ }), 'image/png', 500)).toBeNull();
104
+ expect(processor.messages).toEqual([
105
+ 'downloadable',
106
+ 'notSquare 240x180'
107
+ ]);
108
+ })
109
+
110
+ test('mergeUrlAndPath', () => {
111
+ expect(mergeUrlAndPath('https://example.com', '/some-path')).toBe('https://example.com/some-path');
112
+ expect(mergeUrlAndPath('https://example.com', 'some/path')).toBe('https://example.com/some/path');
113
+
114
+ expect(mergeUrlAndPath('https://example.com', 'some/path?some=param&and=other-param')).toBe('https://example.com/some/path?some=param&and=other-param');
115
+
116
+ expect(mergeUrlAndPath('https://example.com/sub-page', '/some-path')).toBe('https://example.com/some-path');
117
+ expect(mergeUrlAndPath('https://example.com/sub-page', 'some/path')).toBe('https://example.com/sub-page/some/path');
118
+
119
+ expect(mergeUrlAndPath('https://example.com', 'https://elsewhere.com/some-path')).toBe('https://elsewhere.com/some-path');
120
+ })
121
+
122
+ test('parseSizesAttribute', () => {
123
+ expect(parseSizesAttribute(null)).toEqual(null);
124
+ expect(parseSizesAttribute('dummy')).toEqual(null);
125
+
126
+ expect(parseSizesAttribute("16x16")).toEqual(16);
127
+ expect(parseSizesAttribute("50x170")).toEqual(null);
128
+ })