Files
v-iole-t--tab-harbor/extension/icon-utils.test.js
T
2026-07-13 12:34:04 +08:00

192 lines
7.0 KiB
JavaScript

'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const {
escapeHtmlAttribute,
getFallbackLabel,
getPageOriginFaviconUrl,
getGoogleFaviconUrl,
getGroupIcon,
getPrimaryDomain,
getIconSources,
getFaviconUrl,
} = require('./icon-utils.js');
test('getIconSources prefers real favicon before chrome, origin, and Google fallbacks', () => {
const savedChrome = globalThis.chrome;
globalThis.chrome = {
runtime: {
getURL: (path) => `chrome-extension://abc/${path}`,
},
};
try {
const iconData = getIconSources({
favIconUrl: 'https://example.com/favicon.ico',
url: 'https://www.example.com/page',
}, 32);
assert.equal(iconData.hostname, 'www.example.com');
assert.equal(iconData.primaryDomain, 'example.com');
assert.deepEqual(iconData.sources, [
'https://example.com/favicon.ico',
'chrome-extension://abc/_favicon/?pageUrl=https%3A%2F%2Fwww.example.com%2Fpage&size=32',
'https://www.example.com/favicon.ico',
'https://www.google.com/s2/favicons?domain=example.com&sz=32',
'https://www.google.com/s2/favicons?domain=www.example.com&sz=32',
]);
} finally {
if (savedChrome === undefined) {
delete globalThis.chrome;
} else {
globalThis.chrome = savedChrome;
}
}
});
test('getGroupIcon falls back through origin and Google sources when tab has no real favicon', () => {
const iconData = getGroupIcon({
tabs: [{ url: 'https://chatgpt.com/c/test' }],
}, 'ChatGPT', 32);
assert.equal(iconData.src, 'https://chatgpt.com/favicon.ico');
assert.equal(iconData.fallbackSrc, 'https://www.google.com/s2/favicons?domain=chatgpt.com&sz=32');
assert.deepEqual(iconData.fallbackSources, [
'https://www.google.com/s2/favicons?domain=chatgpt.com&sz=32',
]);
assert.equal(iconData.fallbackLabel, 'C');
});
test('getFallbackLabel derives stable initials from labels and hosts', () => {
assert.equal(getFallbackLabel('GitHub Issues', 'github.com'), 'GI');
assert.equal(getFallbackLabel('', 'www.wikipedia.org'), 'WI');
assert.equal(getGoogleFaviconUrl('github.com', 16), 'https://www.google.com/s2/favicons?domain=github.com&sz=16');
assert.equal(getPageOriginFaviconUrl('https://www.ict.ac.cn/yjsjy/zsxx/sszs/202605/t20260511_8199682.html'), 'https://www.ict.ac.cn/favicon.ico');
});
test('getIconSources uses chrome, origin, and Google fallback chain when no real favicon is available', () => {
const savedChrome = globalThis.chrome;
globalThis.chrome = {
runtime: {
getURL: (path) => `chrome-extension://abc/${path}`,
},
};
try {
const iconData = getIconSources({
url: 'https://www.example.com/page',
}, 32);
assert.deepEqual(iconData.sources, [
'chrome-extension://abc/_favicon/?pageUrl=https%3A%2F%2Fwww.example.com%2Fpage&size=32',
'https://www.example.com/favicon.ico',
'https://www.google.com/s2/favicons?domain=example.com&sz=32',
'https://www.google.com/s2/favicons?domain=www.example.com&sz=32',
]);
} finally {
if (savedChrome === undefined) {
delete globalThis.chrome;
} else {
globalThis.chrome = savedChrome;
}
}
});
test('getPrimaryDomain collapses regular subdomains but preserves common hosted tenants', () => {
assert.equal(getPrimaryDomain('foo.example.com'), 'example.com');
assert.equal(getPrimaryDomain('foo.bar.example.co.uk'), 'example.co.uk');
assert.equal(getPrimaryDomain('ict.ac.cn'), 'ict.ac.cn');
assert.equal(getPrimaryDomain('myproject.github.io'), 'myproject.github.io');
assert.equal(getPrimaryDomain('writer.substack.com'), 'writer.substack.com');
});
test('getIconSources skips unstable browser internal favicon urls but keeps Google fallback URLs', () => {
const iconData = getIconSources({
favIconUrl: 'chrome://favicon2/?pageUrl=https%3A%2F%2Fwww.ict.ac.cn',
url: 'https://www.ict.ac.cn/yjsjy/zsxx/sszs/202605/t20260511_8199682.html',
}, 32);
assert.deepEqual(iconData.sources, [
'https://www.ict.ac.cn/favicon.ico',
'https://www.google.com/s2/favicons?domain=ict.ac.cn&sz=32',
'https://www.google.com/s2/favicons?domain=www.ict.ac.cn&sz=32',
]);
});
test('getFaviconUrl builds fallback URL from URL string input (no chrome runtime)', () => {
const result = getFaviconUrl('https://www.example.com/page');
assert.equal(result.source, '');
assert.equal(result.url, '');
assert.equal(result.fallback, 'https://www.example.com/favicon.ico');
});
test('getFaviconUrl returns empty result for empty input', () => {
const result = getFaviconUrl({});
assert.equal(result.url, '');
assert.equal(result.source, '');
assert.equal(result.fallback, '');
});
test('getFaviconUrl returns empty result for non-HTTP URL', () => {
const result = getFaviconUrl('chrome://extensions/');
assert.equal(result.url, '');
assert.equal(result.source, '');
assert.equal(result.fallback, '');
});
test('getFaviconUrl builds fallback URL from options object', () => {
const result = getFaviconUrl({ domain: 'github.com', size: 64 });
assert.equal(result.fallback, 'https://github.com/favicon.ico');
});
test('getFaviconUrl preserves http protocol and localhost port in fallback URL', () => {
const result = getFaviconUrl('http://localhost:3000/app');
assert.equal(result.fallback, 'http://localhost:3000/favicon.ico');
});
test('getFaviconUrl preserves custom https port in fallback URL', () => {
const result = getFaviconUrl('https://example.com:8443/path');
assert.equal(result.fallback, 'https://example.com:8443/favicon.ico');
});
test('getFaviconUrl defaults size to 128 for string input', () => {
const result = getFaviconUrl('https://example.com');
assert.ok(result.url === '' || result.url.includes('size=128'),
'Chrome URL should use size=128 when available');
});
test('getFaviconUrl respects custom size in options', () => {
const result = getFaviconUrl({ domain: 'example.com', size: 32 });
assert.ok(result.url === '' || result.url.includes('size=32'),
'Chrome URL should use custom size when available');
});
test('getFaviconUrl generates chrome _favicon URL when chrome runtime is available', () => {
const savedGetURL = globalThis.chrome?.runtime?.getURL;
globalThis.chrome = {
runtime: {
getURL: (path) => `chrome-extension://abc/${path}`,
},
};
try {
const result = getFaviconUrl({ domain: 'https://example.com/path', size: 32 });
assert.ok(result.url.startsWith('chrome-extension://abc/_favicon/'), 'Chrome URL should use _favicon path');
assert.ok(result.url.includes('pageUrl='), 'Chrome URL should include pageUrl param');
assert.ok(result.url.includes('size=32'), 'Chrome URL should include size param');
assert.equal(result.fallback, 'https://example.com/favicon.ico');
} finally {
if (savedGetURL) {
globalThis.chrome = { runtime: { getURL: savedGetURL } };
} else {
delete globalThis.chrome;
}
}
});
test('escapeHtmlAttribute protects custom tooltip text', () => {
assert.equal(
escapeHtmlAttribute('ChatGPT "Projects" & Notes'),
'ChatGPT "Projects" & Notes'
);
});