import { parse } from 'node-html-parser' import { checkIcoFavicon } from "./ico"; import { CheckerMessage, CheckerStatus, DesktopSingleReport, FetchResponse, MessageId } from '../types'; import { filePathToReadableStream } from '../helper'; import { testFetcher } from '../test-helper'; type TestOutput = { messages: Pick[]; icon: DesktopSingleReport['icon']; } const runIcoTest = async ( headFragment: string | null, output: TestOutput, fetchDatabase: { [url: string]: FetchResponse } = {}, checkContent = true ) => { const root = headFragment ? parse(headFragment) : null; const result = await checkIcoFavicon('https://example.com/', root, testFetcher(fetchDatabase)); const filteredMessages = result.messages.map(m => ({ status: m.status, id: m.id })); expect(filteredMessages).toEqual(output.messages); // Check icon properties - icon is always returned by checkIcoFavicon const resultIcon = result.icon!; const outputIcon = output.icon!; expect(resultIcon.url).toEqual(outputIcon.url); expect(resultIcon.width).toEqual(outputIcon.width); expect(resultIcon.height).toEqual(outputIcon.height); // For content, just check if it's null or not null unless exact match is needed if (checkContent && outputIcon.content === null) { expect(resultIcon.content).toBeNull(); } else if (checkContent && outputIcon.content !== null) { expect(resultIcon.content).not.toBeNull(); expect(resultIcon.content).toMatch(/^data:image\/(png|bmp);base64,/); } } test('checkIcoFavicon - noHead', async () => { await runIcoTest(null, { messages: [{ status: CheckerStatus.Error, id: MessageId.noHead, }], icon: { content: null, url: null, width: null, height: null, } }); }) test('checkIcoFavicon - noIcoFavicon', async () => { await runIcoTest(`Some text`, { messages: [{ status: CheckerStatus.Error, id: MessageId.noIcoFavicon, }], icon: { content: null, url: null, width: null, height: null, } }); }) test('checkIcoFavicon - implicit /favicon.ico when not declared', async () => { const testIconPath = './fixtures/simple-ico.ico'; await runIcoTest(`Some text`, { messages: [{ status: CheckerStatus.Ok, id: MessageId.icoFaviconImplicitInRoot, },{ status: CheckerStatus.Ok, id: MessageId.icoFaviconDownloadable, }, { status: CheckerStatus.Ok, id: MessageId.icoFaviconExpectedSizes, }], icon: { content: "data:image/png;base64,placeholder", // Will be checked for format only url: 'https://example.com/favicon.ico', width: 48, height: 48, } }, { 'https://example.com/favicon.ico': { status: 200, contentType: 'image/x-icon', readableStream: await filePathToReadableStream(testIconPath) } }); }) test('checkIcoFavicon - multipleIcoFavicons with shortcut icon', async () => { await runIcoTest(` `, { messages: [{ status: CheckerStatus.Error, id: MessageId.multipleIcoFavicons, }], icon: { content: null, url: null, width: null, height: null, } }); }) test('checkIcoFavicon - multipleIcoFavicons with type="image/x-icon"', async () => { await runIcoTest(` `, { messages: [{ status: CheckerStatus.Error, id: MessageId.multipleIcoFavicons, }], icon: { content: null, url: null, width: null, height: null, } }); }) test('checkIcoFavicon - icoFaviconDeclared & noIcoFaviconHref', async () => { await runIcoTest(``, { messages: [{ status: CheckerStatus.Ok, id: MessageId.icoFaviconDeclared, }, { status: CheckerStatus.Error, id: MessageId.noIcoFaviconHref, }], icon: { content: null, url: null, width: null, height: null, } }); }) test('checkIcoFavicon - icoFaviconDeclared & icoFavicon404', async () => { await runIcoTest(``, { messages: [{ status: CheckerStatus.Ok, id: MessageId.icoFaviconDeclared, }, { status: CheckerStatus.Error, id: MessageId.icoFavicon404, }], icon: { content: null, url: 'https://example.com/favicon.ico', width: null, height: null, } }); }) test('checkIcoFavicon - icoFaviconDeclared & icoFaviconCannotGet', async () => { await runIcoTest(``, { messages: [{ status: CheckerStatus.Ok, id: MessageId.icoFaviconDeclared, }, { status: CheckerStatus.Error, id: MessageId.icoFaviconCannotGet, }], icon: { content: null, url: 'https://example.com/favicon.ico', width: null, height: null, } }, { 'https://example.com/favicon.ico': { status: 403, contentType: 'image/x-icon' } }); }) test('checkIcoFavicon - icoFaviconDeclared & icoFaviconDownloadable & icoFaviconExpectedSizes', async () => { const testIconPath = './fixtures/simple-ico.ico'; await runIcoTest(``, { messages: [{ status: CheckerStatus.Ok, id: MessageId.icoFaviconDeclared, }, { status: CheckerStatus.Ok, id: MessageId.icoFaviconDownloadable, }, { status: CheckerStatus.Ok, id: MessageId.icoFaviconExpectedSizes, }], icon: { content: "data:image/png;base64,placeholder", // Will be checked for format only url: 'https://example.com/favicon.ico', width: 48, height: 48, } }, { 'https://example.com/favicon.ico': { status: 200, contentType: 'image/x-icon', readableStream: await filePathToReadableStream(testIconPath) } }); }) test('checkIcoFavicon - using type="image/x-icon"', async () => { const testIconPath = './fixtures/simple-ico.ico'; await runIcoTest(``, { messages: [{ status: CheckerStatus.Ok, id: MessageId.icoFaviconDeclared, }, { status: CheckerStatus.Ok, id: MessageId.icoFaviconDownloadable, }, { status: CheckerStatus.Ok, id: MessageId.icoFaviconExpectedSizes, }], icon: { content: "data:image/png;base64,placeholder", // Will be checked for format only url: 'https://example.com/favicon.ico', width: 48, height: 48, } }, { 'https://example.com/favicon.ico': { status: 200, contentType: 'image/x-icon', readableStream: await filePathToReadableStream(testIconPath) } }); }) // For https://github.com/RealFaviconGenerator/core/issues/2 test('checkIcoFavicon - Protocol-relative URL', async () => { const testIconPath = './fixtures/simple-ico.ico'; await runIcoTest(``, { messages: [{ status: CheckerStatus.Ok, id: MessageId.icoFaviconDeclared, }, { status: CheckerStatus.Ok, id: MessageId.icoFaviconDownloadable, }, { status: CheckerStatus.Ok, id: MessageId.icoFaviconExpectedSizes, }], icon: { content: "data:image/png;base64,placeholder", // Will be checked for format only url: 'https://example.com/favicon.ico', width: 48, height: 48, } }, { 'https://example.com/favicon.ico': { status: 200, contentType: 'image/x-icon', readableStream: await filePathToReadableStream(testIconPath) } }); })