import { jest } from '@jest/globals'; import { optimize } from './svgo.js'; import { SvgoParserError } from './parser.js'; test('allow to setup default preset', () => { const svg = ` Created with love `; 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 = ` Not standard description `; 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 = ` Created with love `; const { data } = optimize(svg, { js2svg: { eol: 'lf', pretty: true, indent: 2 }, }); expect(data).toBe( '\n \n\n', ); }); test('should respect EOL set to CRLF', () => { const svg = ` Created with love `; const { data } = optimize(svg, { js2svg: { eol: 'crlf', pretty: true, indent: 2 }, }); expect(data).toBe( '\r\n \r\n\r\n', ); }); test('should default to LF line break for any other EOL values', () => { const svg = ` Created with love `; 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 \n\n', ); }); }); describe('allow to configure final newline', () => { test('should not add final newline when unset', () => { const svg = ` Created with love `; const { data } = optimize(svg, { js2svg: { eol: 'lf' } }); expect(data).toBe( '', ); }); test('should add final newline when set', () => { const svg = ` Created with love `; 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 = ` Created with love `; const { data } = optimize(svg, { js2svg: { finalNewline: true, pretty: true, indent: 2, eol: 'lf' }, }); expect(data).toBe( '\n \n\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 | > 2 | | ^ 3 | 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"`, ); });