/** * @vitest-environment jsdom */ import { JSDOM } from 'jsdom'; import { describe, expect, it } from 'vitest'; import snapshot, { _isBlockedElement, serializeNodeWithId, } from '../src/snapshot'; import { elementNode, serializedNodeWithId } from '../src/types'; import { Mirror, absolutifyURLs } from '../src/utils'; const serializeNode = (node: Node): serializedNodeWithId | null => { return serializeNodeWithId(node, { doc: document, mirror: new Mirror(), blockClass: 'blockblock', blockSelector: null, maskTextClass: 'maskmask', maskTextSelector: null, skipChild: false, inlineStylesheet: true, maskTextFn: undefined, maskInputFn: undefined, slimDOMOptions: {}, }); }; describe('absolute url to stylesheet', () => { const href = 'http://localhost/css/style.css'; it('can handle relative path', () => { expect(absolutifyURLs('url(a.jpg)', href)).toEqual( `url(http://localhost/css/a.jpg)`, ); }); it('can handle same level path', () => { expect(absolutifyURLs('url("./a.jpg")', href)).toEqual( `url("http://localhost/css/a.jpg")`, ); }); it('can handle parent level path', () => { expect(absolutifyURLs('url("../a.jpg")', href)).toEqual( `url("http://localhost/a.jpg")`, ); }); it('can handle absolute path', () => { expect(absolutifyURLs('url("/a.jpg")', href)).toEqual( `url("http://localhost/a.jpg")`, ); }); it('can handle external path', () => { expect(absolutifyURLs('url("http://localhost/a.jpg")', href)).toEqual( `url("http://localhost/a.jpg")`, ); }); it('can handle single quote path', () => { expect(absolutifyURLs(`url('./a.jpg')`, href)).toEqual( `url('http://localhost/css/a.jpg')`, ); }); it('can handle no quote path', () => { expect(absolutifyURLs('url(./a.jpg)', href)).toEqual( `url(http://localhost/css/a.jpg)`, ); }); it('can handle multiple no quote paths', () => { expect( absolutifyURLs( 'background-image: url(images/b.jpg);background: #aabbcc url(images/a.jpg) 50% 50% repeat;', href, ), ).toEqual( `background-image: url(http://localhost/css/images/b.jpg);` + `background: #aabbcc url(http://localhost/css/images/a.jpg) 50% 50% repeat;`, ); }); it('can handle data url image', () => { expect(absolutifyURLs('url(data:image/gif;base64,ABC)', href)).toEqual( 'url(data:image/gif;base64,ABC)', ); expect( absolutifyURLs( 'url(data:application/font-woff;base64,d09GMgABAAAAAAm)', href, ), ).toEqual('url(data:application/font-woff;base64,d09GMgABAAAAAAm)'); }); it('preserves quotes around inline svgs with spaces', () => { expect( absolutifyURLs( "url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%2328a745' d='M3'/%3E%3C/svg%3E\")", href, ), ).toEqual( "url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%2328a745' d='M3'/%3E%3C/svg%3E\")", ); expect( absolutifyURLs( 'url(\'data:image/svg+xml;utf8,\')', href, ), ).toEqual( 'url(\'data:image/svg+xml;utf8,\')', ); expect( absolutifyURLs( 'url("data:image/svg+xml;utf8,")', href, ), ).toEqual( 'url("data:image/svg+xml;utf8,")', ); }); it('can handle empty path', () => { expect(absolutifyURLs(`url('')`, href)).toEqual(`url('')`); }); }); describe('isBlockedElement()', () => { const subject = (html: string, opt: any = {}) => _isBlockedElement(render(html), 'rr-block', opt.blockSelector); const render = (html: string): HTMLElement => JSDOM.fragment(html).querySelector('div')!; it('can handle empty elements', () => { expect(subject('
')).toEqual(false); }); it('blocks prohibited className', () => { expect(subject('')).toEqual(true); }); it('does not block random data selector', () => { expect(subject('')).toEqual(false); }); it('blocks blocked selector', () => { expect( subject('', { blockSelector: '[data-rr-block]' }), ).toEqual(true); }); }); describe('style elements', () => { const render = (html: string): HTMLStyleElement => { document.write(html); return document.querySelector('style')!; }; it('should serialize all rules of stylesheet when the sheet has a single child node', () => { const styleEl = render(``); styleEl.sheet?.insertRule('section { color: blue; }'); expect(serializeNode(styleEl)).toMatchObject({ rootId: undefined, attributes: { _cssText: 'section {color: blue;}body {color: red;}', }, type: 2, }); }); it('should serialize all rules on stylesheets with mix of insertion type', () => { const styleEl = render(``); styleEl.sheet?.insertRule('section.lost { color: unseeable; }'); // browser throws this away after append styleEl.append(document.createTextNode('section { color: blue; }')); styleEl.sheet?.insertRule('section.working { color: pink; }'); expect(serializeNode(styleEl)).toMatchObject({ rootId: undefined, attributes: { _cssText: 'section.working {color: pink;}body {color: red;}/* rr_split */section {color: blue;}', }, type: 2, }); }); }); describe('scrollTop/scrollLeft', () => { const render = (html: string): HTMLDivElement => { document.write(html); return document.querySelector('div')!; }; it('should serialize scroll positions', () => { const el = render(`Hello world
`); const sn = snapshot(doc, { // JSDOM Error: Not implemented: HTMLCanvasElement.prototype.toDataURL (without installing the canvas npm package) //recordCanvas: true, }); expect(sn).toMatchObject({ type: 0, }); }); });