import { jest } from '@jest/globals';
import { optimize } from './svgo.js';
import { SvgoParserError } from './parser.js';
test('allow to setup default preset', () => {
const svg = `
`;
const { data } = optimize(svg, {
plugins: ['preset-default'],
js2svg: { pretty: true, indent: 2 },
});
expect(data).toMatchInlineSnapshot(`
"
"
`);
});
test('allow to disable and customize plugins in preset', () => {
const svg = `
`;
const { data } = optimize(svg, {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeXMLProcInst: false,
removeDesc: {
removeAny: true,
},
},
},
},
],
js2svg: { pretty: true, indent: 2 },
});
expect(data).toMatchInlineSnapshot(`
"
"
`);
});
test('warn when user tries enable plugins in preset', () => {
const svg = `
`;
const warn = jest.spyOn(console, 'warn');
optimize(svg, {
plugins: [
// @ts-expect-error Testing if we receive config that diverges from type definitions.
{
name: 'preset-default',
params: {
overrides: {
cleanupListOfValues: true,
},
},
},
],
js2svg: { pretty: true, indent: 2 },
});
expect(warn)
.toBeCalledWith(`You are trying to configure cleanupListOfValues which is not part of preset-default.
Try to put it before or after, for example
plugins: [
{
name: 'preset-default',
},
'cleanupListOfValues'
]
`);
warn.mockRestore();
});
describe('allow to configure EOL', () => {
test('should respect EOL set to LF', () => {
const svg = `
`;
const { data } = optimize(svg, {
js2svg: { eol: 'lf', pretty: true, indent: 2 },
});
expect(data).toBe(
'\n',
);
});
test('should respect EOL set to CRLF', () => {
const svg = `
`;
const { data } = optimize(svg, {
js2svg: { eol: 'crlf', pretty: true, indent: 2 },
});
expect(data).toBe(
'\r\n',
);
});
test('should default to LF line break for any other EOL values', () => {
const svg = `
`;
const { data } = optimize(svg, {
// @ts-expect-error Testing if we receive config that diverges from type definitions.
js2svg: { eol: 'invalid', pretty: true, indent: 2 },
});
expect(data).toBe(
'\n',
);
});
});
describe('allow to configure final newline', () => {
test('should not add final newline when unset', () => {
const svg = `
`;
const { data } = optimize(svg, { js2svg: { eol: 'lf' } });
expect(data).toBe(
'',
);
});
test('should add final newline when set', () => {
const svg = `
`;
const { data } = optimize(svg, {
js2svg: { finalNewline: true, eol: 'lf' },
});
expect(data).toBe(
'\n',
);
});
test('should not add extra newlines when using pretty: true', () => {
const svg = `
`;
const { data } = optimize(svg, {
js2svg: { finalNewline: true, pretty: true, indent: 2, eol: 'lf' },
});
expect(data).toBe(
'\n',
);
});
});
test('allow to customize precision for preset', () => {
const svg = `
`;
const { data } = optimize(svg, {
plugins: [
{
name: 'preset-default',
params: {
floatPrecision: 4,
},
},
],
js2svg: { pretty: true, indent: 2 },
});
expect(data).toMatchInlineSnapshot(`
"
"
`);
});
test('plugin precision should override preset precision', () => {
const svg = `
`;
const { data } = optimize(svg, {
plugins: [
{
name: 'preset-default',
params: {
floatPrecision: 4,
overrides: {
cleanupNumericValues: {
floatPrecision: 5,
},
},
},
},
],
js2svg: { pretty: true, indent: 2 },
});
expect(data).toMatchInlineSnapshot(`
"
"
`);
});
test('provides informative error in result', () => {
const svg = `
`;
const error = new SvgoParserError(
'Unquoted attribute value',
2,
33,
svg,
'test.svg',
);
expect(() => optimize(svg, { path: 'test.svg' })).toThrow(error);
expect(error.name).toBe('SvgoParserError');
expect(error.message).toBe('test.svg:2:33: Unquoted attribute value');
});
test('provides code snippet in rendered error', () => {
const svg = `
`;
const error = new SvgoParserError(
'Unquoted attribute value',
2,
29,
svg,
'test.svg',
);
expect(() => optimize(svg, { path: 'test.svg' })).toThrow(error);
expect(error.toString())
.toBe(`SvgoParserError: test.svg:2:29: Unquoted attribute value
1 |
4 |
`);
});
test('supports errors without path', () => {
const svg = `
`;
const error = new SvgoParserError('Unquoted attribute value', 11, 29, svg);
expect(() => optimize(svg)).toThrow(error);
expect(error.toString())
.toBe(`SvgoParserError: :11:29: Unquoted attribute value
9 |
10 |
> 11 |
| ^
12 |
13 |
`);
});
test('slices long line in error code snippet', () => {
const svg = `
`;
const error = new SvgoParserError('Invalid attribute name', 2, 211, svg);
expect(() => optimize(svg)).toThrow(error);
expect(error.toString())
.toBe(`SvgoParserError: :2:211: Invalid attribute name
1 | …-0.dtd" viewBox="0 0 230 120">
> 2 | …7.334 250.955 222.534t579.555 155.292z stroke-width="1.5" fill="red" strok…
| ^
3 |
4 |
`);
});
test('multipass option should trigger plugins multiple times', () => {
const svg = ``;
/** @type {number[]} */
const list = [];
/** @type {import('./types.js').CustomPlugin} */
const testPlugin = {
name: 'testPlugin',
fn: (_root, _params, info) => {
list.push(info.multipassCount);
return {
element: {
enter: (node) => {
node.attributes.id = node.attributes.id.slice(1);
},
},
};
},
};
const { data } = optimize(svg, { multipass: true, plugins: [testPlugin] });
expect(list).toStrictEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
expect(data).toBe(``);
});
test('encode as datauri', () => {
const input = `
`;
const { data: dataSinglePass } = optimize(input, {
datauri: 'enc',
plugins: ['convertTransform'],
});
expect(dataSinglePass).toMatchInlineSnapshot(
`"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20transform%3D%22rotate(-45%20261.757%20-252.243)scale(2)%22%2F%3E%3C%2Fsvg%3E"`,
);
const { data: dataMultiPass } = optimize(input, {
multipass: true,
datauri: 'enc',
plugins: ['convertTransform'],
});
expect(dataMultiPass).toMatchInlineSnapshot(
`"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20transform%3D%22rotate(-45%20261.757%20-252.243)scale(2)%22%2F%3E%3C%2Fsvg%3E"`,
);
});