Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions code/core/assets/browser/favicon-wrapper.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion code/core/assets/browser/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 17 additions & 25 deletions code/core/src/core-server/presets/common-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ export const favicon = async (
? staticDirsValue.map((dir) => (typeof dir === 'string' ? dir : `${dir.from}:${dir.to}`))
: [];

if (statics.length > 0) {
const lists = statics.map((dir) => {
const faviconPaths = statics
.map((dir) => {
const results = [];
const normalizedDir =
staticDirsValue && !isAbsolute(dir)
Expand All @@ -72,37 +72,29 @@ export const favicon = async (

const { staticPath, targetEndpoint } = parseStaticDir(normalizedDir);

if (targetEndpoint === '/') {
const url = 'favicon.svg';
const path = join(staticPath, url);
if (existsSync(path)) {
results.push(path);
}
// Direct favicon references (e.g. `staticDirs: ['favicon.svg']`)
if (['/favicon.svg', '/favicon.ico'].includes(targetEndpoint)) {
results.push(staticPath);
}
// Favicon files in a static directory (e.g. `staticDirs: ['static']`)
if (targetEndpoint === '/') {
const url = 'favicon.ico';
const path = join(staticPath, url);
if (existsSync(path)) {
results.push(path);
}
results.push(join(staticPath, 'favicon.svg'));
results.push(join(staticPath, 'favicon.ico'));
}

return results;
});
const flatlist = lists.reduce((l1, l2) => l1.concat(l2), []);

if (flatlist.length > 1) {
logger.warn(dedent`
Looks like multiple favicons were detected. Using the first one.
return results.filter((path) => existsSync(path));
})
.reduce((l1, l2) => l1.concat(l2), []);

${flatlist.join(', ')}
`);
}
if (faviconPaths.length > 1) {
logger.warn(dedent`
Looks like multiple favicons were detected. Using the first one.

return flatlist[0] || defaultFavicon;
${faviconPaths.join(', ')}
`);
}

return defaultFavicon;
return faviconPaths[0] || defaultFavicon;
};

export const babel = async (_: unknown, options: Options) => {
Expand Down
21 changes: 21 additions & 0 deletions code/core/src/core-server/presets/favicon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,36 @@ vi.mock('node:fs', async (importOriginal) => ({
existsSync: vi.fn((p: string) => {
return false;
}),
statSync: vi.fn((p: string) => {
return {
isFile: () => false,
};
}),
}));
const existsSyncMock = vi.mocked(fs.existsSync);
const statSyncMock = vi.mocked(fs.statSync);

it('with no staticDirs favicon should return default', async () => {
const options = createOptions([]);

expect(await m.favicon(undefined, options)).toBe(defaultFavicon);
});

it('with staticDirs referencing a favicon.ico directly should return the found favicon', async () => {
const location = 'favicon.ico';
existsSyncMock.mockImplementation((p) => {
return p === createPath(location);
});
statSyncMock.mockImplementation((p) => {
return {
isFile: () => p === createPath('favicon.ico'),
} as any;
});
const options = createOptions([location]);

expect(await m.favicon(undefined, options)).toBe(createPath('favicon.ico'));
});

it('with staticDirs containing a single favicon.ico should return the found favicon', async () => {
const location = 'static';
existsSyncMock.mockImplementation((p) => {
Expand Down
58 changes: 57 additions & 1 deletion code/core/src/core-server/utils/__tests__/server-statics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,70 @@ import { resolve } from 'node:path';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { onlyWindows, skipWindows } from '../../../../../vitest.helpers';
import { parseStaticDir } from '../server-statics';
import { parseStaticDir, prepareNestedSvg } from '../server-statics';

vi.mock('node:fs');
const existsSyncMock = vi.mocked(fs.existsSync);
const statSyncMock = vi.mocked(fs.statSync);

describe('prepareNestedSvg', () => {
it('should remove xml declaration and add preserveAspectRatio', () => {
const fixedSvg = prepareNestedSvg(`
<?xml version="1.0" encoding="UTF-8"?>
<svg><g /></svg>
`);
expect(fixedSvg).toMatchInlineSnapshot(
`"<svg preserveAspectRatio="xMidYMid meet"><g /></svg>"`
);
});

it('should update width and height', () => {
const fixedSvg = prepareNestedSvg(`
<svg width="64px" height="64px" viewBox="0 0 64 64" fill="none"><g /></svg>
`);
expect(fixedSvg).toMatchInlineSnapshot(
`"<svg width="32px" height="32px" viewBox="0 0 64 64" fill="none" preserveAspectRatio="xMidYMid meet"><g /></svg>"`
);
});

it('should add viewBox if none is present', () => {
const fixedSvg = prepareNestedSvg(`
<svg width="64px" height="64px" fill="none"><g /></svg>
`);
expect(fixedSvg).toMatchInlineSnapshot(
`"<svg width="32px" height="32px" fill="none" viewBox="0 0 64 64" preserveAspectRatio="xMidYMid meet"><g /></svg>"`
);
});

it('handles a full svg', () => {
const fixedSvg = prepareNestedSvg(`
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none">
<circle cx="23" cy="35" r="8" fill="#fff" />
<circle cx="41" cy="35" r="8" fill="#fff" />
<path fill="#9FA628"
d="M62.6 5.8c-2.8 0-4.4-1.6-4.4-4.4 0-.8-.6-1.4-1.4-1.4-.8 0-1.4.6-1.4 1.4 0 1.6.4 3 1 4.2l-3.8 4C49.6 7.2 45.8 6 40.8 6H23.4c-5 0-8.8 1.2-11.8 3.6L7.8 5.8c.8-1.2 1-2.6 1-4.2C8.8.6 8 0 7.2 0S5.8.6 5.8 1.4c0 2.8-1.6 4.4-4.4 4.4-.8 0-1.4.6-1.4 1.4 0 .8.6 1.4 1.4 1.4 1.6 0 3-.4 4.2-1l4 3.8C7.2 14.4 6 18.2 6 23.2V48c0 5.6 4.4 10 10 10h32c5.6 0 10-4.4 10-10V23.2c0-5-1.2-8.8-3.6-11.8l3.8-3.8c1.2.8 2.6 1 4.2 1 .8 0 1.4-.6 1.4-1.4 0-.8-.4-1.4-1.2-1.4zM23 42c-3.8 0-7-3.2-7-7s3.2-7 7-7 7 3.2 7 7-3.2 7-7 7zm18 0c-3.8 0-7-3.2-7-7s3.2-7 7-7 7 3.2 7 7-3.2 7-7 7z" />
<circle cx="23" cy="35" r="4" fill="#BF6C35" />
<circle cx="41" cy="35" r="4" fill="#BF6C35" />
</svg>
`);
expect(fixedSvg).toMatchInlineSnapshot(`
"<svg xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" fill="none" viewBox="0 0 64 64" preserveAspectRatio="xMidYMid meet">
<circle cx="23" cy="35" r="8" fill="#fff" />
<circle cx="41" cy="35" r="8" fill="#fff" />
<path fill="#9FA628"
d="M62.6 5.8c-2.8 0-4.4-1.6-4.4-4.4 0-.8-.6-1.4-1.4-1.4-.8 0-1.4.6-1.4 1.4 0 1.6.4 3 1 4.2l-3.8 4C49.6 7.2 45.8 6 40.8 6H23.4c-5 0-8.8 1.2-11.8 3.6L7.8 5.8c.8-1.2 1-2.6 1-4.2C8.8.6 8 0 7.2 0S5.8.6 5.8 1.4c0 2.8-1.6 4.4-4.4 4.4-.8 0-1.4.6-1.4 1.4 0 .8.6 1.4 1.4 1.4 1.6 0 3-.4 4.2-1l4 3.8C7.2 14.4 6 18.2 6 23.2V48c0 5.6 4.4 10 10 10h32c5.6 0 10-4.4 10-10V23.2c0-5-1.2-8.8-3.6-11.8l3.8-3.8c1.2.8 2.6 1 4.2 1 .8 0 1.4-.6 1.4-1.4 0-.8-.4-1.4-1.2-1.4zM23 42c-3.8 0-7-3.2-7-7s3.2-7 7-7 7 3.2 7 7-3.2 7-7 7zm18 0c-3.8 0-7-3.2-7-7s3.2-7 7-7 7 3.2 7 7-3.2 7-7 7z" />
<circle cx="23" cy="35" r="4" fill="#BF6C35" />
<circle cx="41" cy="35" r="4" fill="#BF6C35" />
</svg>"
`);
});
});

describe('parseStaticDir', () => {
beforeEach(() => {
existsSyncMock.mockReturnValue(true);
statSyncMock.mockReturnValue({ isFile: () => false } as fs.Stats);
});

it('returns the static dir/path and default target', async () => {
Expand Down
Loading
Loading