- Introduce `AppIcon.vue`, offering improved performance over the previous `fort-awesome` dependency. This implementation reduces bundle size by 67.31KB (tested for web using `npm run build -- --mode prod`). - Migrate Font Awesome 5 icons to Font Awesome 6. This commit facilitates migration to Vue 3.0 (#230) and ensures no Vue component remains tightly bound to a specific Vue version, enhancing code portability. Font Awesome license is not included because Font Awesome revokes its right: > "Attribution is no longer required as of Font Awesome 3.0" > > Sources: > > - https://fontawesome.com/v4/license/ (archived: https://web.archive.org/web/20231003213441/https://fontawesome.com/v4/license/, https://archive.ph/Yy9j5) > - https://github.com/FortAwesome/Font-Awesome/wiki (archived: https://web.archive.org/web/20231003214646/https://github.com/FortAwesome/Font-Awesome/wiki, https://archive.ph/C6sXv) This commit removes following third-party production dependencies: - `@fortawesome/vue-fontawesome` - `@fortawesome/free-solid-svg-icons` - `@fortawesome/free-regular-svg-icons` - `@fortawesome/free-brands-svg-icons` - `@fortawesome/fontawesome-svg-core`
161 lines
5.8 KiB
TypeScript
161 lines
5.8 KiB
TypeScript
import {
|
|
describe, it, expect, beforeEach,
|
|
} from 'vitest';
|
|
import { ref } from 'vue';
|
|
import { IconName } from '@/presentation/components/Shared/Icon/IconName';
|
|
import { FileLoaders, clearIconCache, useSvgLoader } from '@/presentation/components/Shared/Icon/UseSvgLoader';
|
|
import { waitForValueChange } from '@tests/shared/WaitForValueChange';
|
|
|
|
describe('useSvgLoader', () => {
|
|
beforeEach(() => {
|
|
clearIconCache();
|
|
});
|
|
describe('SVG loading', () => {
|
|
it('renders initial SVG content based on icon name', async () => {
|
|
// arrange
|
|
const expectedIconName: IconName = 'magnifying-glass';
|
|
const expectedIconContent = '<svg id="expected-content"/>';
|
|
const { loaders, addIcon } = useSvgMock();
|
|
addIcon(expectedIconName, expectedIconContent);
|
|
|
|
// act
|
|
const { svgContent } = useSvgLoader(() => expectedIconName, loaders);
|
|
await waitForValueChange(svgContent);
|
|
|
|
// assert
|
|
expect(svgContent.value).to.equal(expectedIconContent);
|
|
});
|
|
it('updates SVG content when icon name changes', async () => {
|
|
// arrange
|
|
const initialIconName: IconName = 'magnifying-glass';
|
|
const iconName = ref<IconName>(initialIconName);
|
|
const initialIconContent = '<svg id="initial"/>';
|
|
const updatedIconName: IconName = 'copy';
|
|
const updatedIconContent = '<svg id="updated"/>';
|
|
const { addIcon, loaders } = useSvgMock();
|
|
addIcon(initialIconName, initialIconContent);
|
|
addIcon(updatedIconName, updatedIconContent);
|
|
|
|
// act
|
|
const { svgContent } = useSvgLoader(() => iconName.value, loaders);
|
|
await waitForValueChange(svgContent);
|
|
iconName.value = updatedIconName;
|
|
await waitForValueChange(svgContent);
|
|
|
|
// assert
|
|
expect(svgContent.value).to.equal(updatedIconContent);
|
|
});
|
|
it('lazy loads SVG icons and does not preload', async () => {
|
|
// arrange
|
|
const expectedIconName: IconName = 'magnifying-glass';
|
|
const unexpectedIconName: IconName = 'copy';
|
|
const { addIcon, getSvgFetchCount, loaders } = useSvgMock();
|
|
addIcon(expectedIconName);
|
|
addIcon(unexpectedIconName);
|
|
|
|
// act
|
|
const { svgContent } = useSvgLoader(() => expectedIconName, loaders);
|
|
await waitForValueChange(svgContent);
|
|
|
|
// assert
|
|
expect(getSvgFetchCount(expectedIconName)).to.equal(1);
|
|
expect(getSvgFetchCount(unexpectedIconName)).to.equal(0);
|
|
});
|
|
it('avoids loading same SVG content multiple times for concurrent calls', async () => {
|
|
// arrange
|
|
const expectedIconName: IconName = 'magnifying-glass';
|
|
const { addIcon, getSvgFetchCount, loaders } = useSvgMock();
|
|
addIcon(expectedIconName);
|
|
|
|
// act
|
|
const { svgContent: svgContent1 } = useSvgLoader(() => expectedIconName, loaders);
|
|
const { svgContent: svgContent2 } = useSvgLoader(() => expectedIconName, loaders);
|
|
await Promise.all([
|
|
waitForValueChange(svgContent1),
|
|
waitForValueChange(svgContent2),
|
|
]);
|
|
|
|
// assert
|
|
expect(getSvgFetchCount(expectedIconName)).to.equal(1);
|
|
});
|
|
});
|
|
describe('SVG content manipulation', () => {
|
|
it('sets path fill color to currentColor', async () => {
|
|
// arrange
|
|
const expectedIconName: IconName = 'magnifying-glass';
|
|
const { addIcon, loaders } = useSvgMock();
|
|
addIcon(expectedIconName, '<svg id="svg-with-paths"><path /><path /></svg>');
|
|
|
|
// act
|
|
const { svgContent } = useSvgLoader(() => expectedIconName, loaders);
|
|
await waitForValueChange(svgContent);
|
|
|
|
// assert
|
|
const svgElement = new DOMParser().parseFromString(svgContent.value, 'image/svg+xml');
|
|
const pathElements = Array.from(svgElement.querySelectorAll('path'));
|
|
expect(pathElements).to.have.lengthOf(2, svgContent.value);
|
|
const fillAttributeValues = pathElements.map((el: Element) => el.getAttribute('fill'));
|
|
expect(fillAttributeValues).to.have.members(['currentColor', 'currentColor']);
|
|
});
|
|
it('removes comments from loaded SVG', async () => {
|
|
// arrange
|
|
const commentLine = '<!-- This is a comment -->';
|
|
const expectedIconName: IconName = 'magnifying-glass';
|
|
const { addIcon, loaders } = useSvgMock();
|
|
addIcon(expectedIconName, `<svg>${commentLine}<path></path></svg>`);
|
|
|
|
// act
|
|
const { svgContent } = useSvgLoader(() => expectedIconName, loaders);
|
|
await waitForValueChange(svgContent);
|
|
|
|
// assert
|
|
expect(svgContent.value).not.to.include(commentLine);
|
|
});
|
|
});
|
|
describe('icon cache management', () => {
|
|
it('reloads SVG content after clearing cache', async () => {
|
|
// arrange
|
|
const expectedIconName: IconName = 'magnifying-glass';
|
|
const { addIcon, getSvgFetchCount, loaders } = useSvgMock();
|
|
addIcon(expectedIconName);
|
|
|
|
// act
|
|
const { svgContent } = useSvgLoader(() => expectedIconName, loaders);
|
|
await waitForValueChange(svgContent);
|
|
expect(getSvgFetchCount(expectedIconName)).to.equal(1);
|
|
clearIconCache();
|
|
const { svgContent: newSvgContent } = useSvgLoader(() => expectedIconName, loaders);
|
|
await waitForValueChange(newSvgContent);
|
|
|
|
// assert
|
|
expect(getSvgFetchCount(expectedIconName)).to.equal(2);
|
|
});
|
|
});
|
|
});
|
|
|
|
function useSvgMock() {
|
|
const ICON_PATH_PREFIX = '/assets/icons/';
|
|
function getPath(iconName: IconName) {
|
|
return `${ICON_PATH_PREFIX}${iconName}.svg`;
|
|
}
|
|
const svgFetchCount = {} as Record<IconName, number>;
|
|
const loaders = {} as FileLoaders;
|
|
function addIcon(iconName: IconName, svgContent = '<svg id="stub" />') {
|
|
const path = getPath(iconName);
|
|
svgFetchCount[iconName] = 0;
|
|
loaders[path] = () => {
|
|
svgFetchCount[iconName] += 1;
|
|
return Promise.resolve(svgContent);
|
|
};
|
|
}
|
|
function getSvgFetchCount(iconName: IconName): number {
|
|
return svgFetchCount[iconName];
|
|
}
|
|
return {
|
|
loaders,
|
|
getSvgFetchCount,
|
|
getPath,
|
|
addIcon,
|
|
};
|
|
}
|