Skip to content

Commit 7ebc871

Browse files
committed
test: increases coverage
1 parent bf9a888 commit 7ebc871

16 files changed

Lines changed: 842 additions & 0 deletions

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"liveServer.settings.port": 5501
3+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2+
import { inlineBackgroundImages } from '../src/modules/background.js';
3+
4+
describe('inlineBackgroundImages', () => {
5+
let source, clone;
6+
beforeEach(() => {
7+
source = document.createElement('div');
8+
clone = document.createElement('div');
9+
document.body.appendChild(source);
10+
document.body.appendChild(clone);
11+
});
12+
afterEach(() => {
13+
document.body.removeChild(source);
14+
document.body.removeChild(clone);
15+
});
16+
17+
it('does not fail if there is no background-image', async () => {
18+
source.style.background = 'none';
19+
await expect(inlineBackgroundImages(source, clone, new WeakMap())).resolves.toBeUndefined();
20+
});
21+
22+
it('processes a valid background-image', async () => {
23+
source.style.backgroundImage = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/w8AAn8B9p6Q2wAAAABJRU5ErkJggg==")';
24+
await expect(inlineBackgroundImages(source, clone, new WeakMap())).resolves.toBeUndefined();
25+
});
26+
});

__tests__/capture.core.test.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { captureDOM } from '../src/core/capture.js';
3+
4+
describe('captureDOM edge cases', () => {
5+
it('throws for unsupported element (unknown nodeType)', async () => {
6+
// Simulate a node with an invalid nodeType
7+
const fakeNode = { nodeType: 999 };
8+
await expect(captureDOM(fakeNode)).rejects.toThrow();
9+
});
10+
11+
it('throws if element is null', async () => {
12+
await expect(captureDOM(null)).rejects.toThrow();
13+
});
14+
15+
it('throws para un comment node', async () => {
16+
const fake = document.createComment('not supported');
17+
await expect(captureDOM(fake)).rejects.toThrow('Only Element nodes are supported');
18+
});
19+
20+
it('throws error if getBoundingClientRect fails', async () => {
21+
const el = document.createElement('div');
22+
vi.spyOn(el, 'getBoundingClientRect').mockImplementation(() => { throw new Error('fail'); });
23+
await expect(captureDOM(el)).rejects.toThrow('fail');
24+
});
25+
});
26+
27+
describe('captureDOM functional', () => {
28+
it('captures a simple div and returns an SVG dataURL', async () => {
29+
const el = document.createElement('div');
30+
el.textContent = 'test';
31+
const url = await captureDOM(el);
32+
expect(url.startsWith('data:image/svg+xml')).toBe(true);
33+
});
34+
35+
it('supports scale and width/height options', async () => {
36+
const el = document.createElement('div');
37+
el.style.width = '100px';
38+
el.style.height = '50px';
39+
await captureDOM(el, { scale: 2 });
40+
await captureDOM(el, { width: 200 });
41+
await captureDOM(el, { height: 100 });
42+
});
43+
44+
it('supports fast=false', async () => {
45+
const el = document.createElement('div');
46+
await captureDOM(el, { fast: false });
47+
});
48+
49+
it('supports embedFonts', async () => {
50+
const el = document.createElement('div');
51+
await captureDOM(el, { embedFonts: true });
52+
});
53+
});

__tests__/clone.core.test.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { deepClone } from '../src/core/clone.js';
3+
4+
describe('deepClone', () => {
5+
it('clones a simple div', () => {
6+
const el = document.createElement('div');
7+
el.textContent = 'hello';
8+
const styleMap = new Map();
9+
const styleCache = new WeakMap();
10+
const nodeMap = new Map();
11+
const clone = deepClone(el, styleMap, styleCache, nodeMap, false);
12+
expect(clone).not.toBe(el);
13+
expect(clone.textContent).toBe('hello');
14+
});
15+
16+
it('clones canvas as an image', () => {
17+
const canvas = document.createElement('canvas');
18+
canvas.width = 10;
19+
canvas.height = 10;
20+
const ctx = canvas.getContext('2d');
21+
ctx.fillStyle = 'red';
22+
ctx.fillRect(0,0,10,10);
23+
const styleMap = new Map();
24+
const styleCache = new WeakMap();
25+
const nodeMap = new Map();
26+
const clone = deepClone(canvas, styleMap, styleCache, nodeMap, false);
27+
expect(clone.tagName).toBe('IMG');
28+
expect(clone.src.startsWith('data:image/')).toBe(true);
29+
});
30+
31+
it('deepClone handles data-capture="exclude"', () => {
32+
const el = document.createElement('div');
33+
el.setAttribute('data-capture', 'exclude');
34+
const clone = deepClone(el, new Map(), new WeakMap(), new Map(), false);
35+
expect(clone).not.toBeNull();
36+
});
37+
38+
it('deepClone handles data-capture="placeholder"', () => {
39+
const el = document.createElement('div');
40+
el.setAttribute('data-capture', 'placeholder');
41+
el.setAttribute('data-placeholder-text', 'Placeholder!');
42+
const clone = deepClone(el, new Map(), new WeakMap(), new Map(), false);
43+
expect(clone.textContent).toContain('Placeholder!');
44+
});
45+
46+
it('deepClone handles iframe', () => {
47+
const iframe = document.createElement('iframe');
48+
iframe.width = 100;
49+
iframe.height = 50;
50+
const clone = deepClone(iframe, new Map(), new WeakMap(), new Map(), false);
51+
expect(clone.tagName).toBe('DIV');
52+
});
53+
54+
it('deepClone handles input, textarea, select', () => {
55+
const input = document.createElement('input');
56+
input.value = 'foo';
57+
input.checked = true;
58+
const textarea = document.createElement('textarea');
59+
textarea.value = 'bar';
60+
const select = document.createElement('select');
61+
const opt = document.createElement('option');
62+
opt.value = 'baz';
63+
select.appendChild(opt);
64+
select.value = 'baz';
65+
[input, textarea, select].forEach(el => {
66+
const clone = deepClone(el, new Map(), new WeakMap(), new Map(), false);
67+
expect(clone.value).toBe(el.value);
68+
});
69+
});
70+
71+
it('deepClone handles shadow DOM', () => {
72+
const el = document.createElement('div');
73+
const shadow = el.attachShadow({mode:'open'});
74+
const span = document.createElement('span');
75+
span.textContent = 'shadow';
76+
shadow.appendChild(span);
77+
const clone = deepClone(el, new Map(), new WeakMap(), new Map(), false);
78+
expect(clone).not.toBeNull();
79+
});
80+
});
81+
82+
describe('deepClone edge cases', () => {
83+
it('clones unsupported node (Comment) as a new Comment', () => {
84+
const fake = document.createComment('not supported');
85+
const result = deepClone(fake, new Map(), new WeakMap(), new Map(), false);
86+
expect(result.nodeType).toBe(Node.COMMENT_NODE);
87+
expect(result.textContent).toBe('not supported');
88+
expect(result).not.toBe(fake); // Es un clon, no el mismo objeto
89+
});
90+
it('handles error in internal logic', () => {
91+
const el = document.createElement('div');
92+
vi.spyOn(el, 'cloneNode').mockImplementation(() => { throw new Error('fail'); });
93+
expect(() => deepClone(el, new Map(), new WeakMap(), new Map(), false)).toThrow('fail');
94+
});
95+
it('clones attributes and children', () => {
96+
const el = document.createElement('div');
97+
el.setAttribute('data-test', '1');
98+
const result = deepClone(el, new Map(), new WeakMap(), new Map(), false);
99+
expect(result.getAttribute('data-test')).toBe('1');
100+
});
101+
});

__tests__/cssTools.utils.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { getStyleKey, collectUsedTagNames, getDefaultStyleForTag } from '../src/utils/cssTools.js';
3+
4+
describe('getStyleKey', () => {
5+
it('generates a non-empty style key', () => {
6+
const snapshot = { color: 'red', 'font-size': '12px' };
7+
const key = getStyleKey(snapshot, 'div');
8+
expect(typeof key).toBe('string');
9+
expect(key.length).toBeGreaterThan(0);
10+
});
11+
12+
it('getStyleKey works with compress true', () => {
13+
const snapshot = { color: 'red', 'font-size': '12px' };
14+
const key = getStyleKey(snapshot, 'div', true);
15+
expect(typeof key).toBe('string');
16+
});
17+
});
18+
19+
describe('collectUsedTagNames', () => {
20+
it('returns unique tag names', () => {
21+
const root = document.createElement('div');
22+
root.innerHTML = '<span></span><p></p><span></span>';
23+
const tags = collectUsedTagNames(root);
24+
expect(tags).toContain('div');
25+
expect(tags).toContain('span');
26+
expect(tags).toContain('p');
27+
});
28+
});
29+
30+
describe('getDefaultStyleForTag', () => {
31+
it('returns a default style object', () => {
32+
const defaults = getDefaultStyleForTag('div');
33+
expect(typeof defaults).toBe('object');
34+
});
35+
36+
it('getDefaultStyleForTag skips special tags', () => {
37+
expect(getDefaultStyleForTag('script')).toEqual({});
38+
});
39+
});

__tests__/fonts.module.test.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { describe, it, expect, beforeEach, vi } from 'vitest';
2+
import { iconToImage, embedCustomFonts } from '../src/modules/fonts.js';
3+
4+
// Utilidad para limpiar estilos y links antes de cada test
5+
function cleanFontEnvironment() {
6+
document.querySelectorAll('style,link[rel="stylesheet"]').forEach(s => s.remove());
7+
}
8+
9+
describe('iconToImage', () => {
10+
it('genera un dataURL para un carácter unicode', async () => {
11+
const url = await iconToImage('★', 'Arial', 'bold', 32, '#000');
12+
expect(url.startsWith('data:image/')).toBe(true);
13+
});
14+
15+
it('maneja diferentes pesos y colores de fuente', async () => {
16+
const url = await iconToImage('★', 'Arial', 700, 40, '#ff0000');
17+
expect(url.startsWith('data:image/')).toBe(true);
18+
});
19+
});
20+
21+
describe('embedCustomFonts', () => {
22+
beforeEach(() => {
23+
cleanFontEnvironment();
24+
vi.restoreAllMocks();
25+
});
26+
27+
it('devuelve un string CSS (puede ser vacío si no hay fuentes)', async () => {
28+
const css = await embedCustomFonts();
29+
expect(typeof css).toBe('string');
30+
});
31+
32+
it('funciona con ignoreIconFonts=false', async () => {
33+
const css = await embedCustomFonts({ ignoreIconFonts: false });
34+
expect(typeof css).toBe('string');
35+
});
36+
37+
it('maneja fuentes ya presentes en el DOM', async () => {
38+
const style = document.createElement('style');
39+
style.textContent = `@font-face { font-family: testfont; src: url("data:font/woff;base64,AAAA"); }`;
40+
document.head.appendChild(style);
41+
await new Promise(r => setTimeout(r, 10));
42+
const css = await embedCustomFonts();
43+
expect(css).toContain('testfont');
44+
document.head.removeChild(style);
45+
});
46+
47+
it('maneja error de fetch para url de fuente', async () => {
48+
const style = document.createElement('style');
49+
style.textContent = `@font-face { font-family: testfont; src: url("https://notfound/font.woff"); }`;
50+
document.head.appendChild(style);
51+
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('fail')));
52+
await new Promise(r => setTimeout(r, 10));
53+
const css = await embedCustomFonts();
54+
expect(css).toContain('testfont');
55+
document.head.removeChild(style);
56+
vi.unstubAllGlobals();
57+
});
58+
59+
it('maneja error de FileReader', async () => {
60+
const style = document.createElement('style');
61+
style.textContent = `@font-face { font-family: testfont; src: url("https://test.com/font.woff"); }`;
62+
document.head.appendChild(style);
63+
const origFetch = window.fetch;
64+
window.fetch = vi.fn().mockResolvedValue({ blob: () => Promise.resolve('blob') });
65+
const origFileReader = window.FileReader;
66+
window.FileReader = class {
67+
readAsDataURL() { throw new Error('fail'); }
68+
set onload(_){}
69+
};
70+
await new Promise(r => setTimeout(r, 10));
71+
const css = await embedCustomFonts();
72+
expect(css).toContain('testfont');
73+
document.head.removeChild(style);
74+
window.fetch = origFetch;
75+
window.FileReader = origFileReader;
76+
});
77+
78+
it('inserta style tag cuando preCached es true', async () => {
79+
const styleEl = document.createElement('style');
80+
styleEl.textContent = `@font-face { font-family: testfont; src: url("data:font/woff;base64,AAAA"); }`;
81+
document.head.appendChild(styleEl);
82+
await new Promise(r => setTimeout(r, 10));
83+
const css = await embedCustomFonts({ preCached: true });
84+
const style = document.head.querySelector('style[data-snapdom="embedFonts"]');
85+
expect(style).not.toBeNull();
86+
expect(style.textContent).toBe(css);
87+
style.remove();
88+
document.head.removeChild(styleEl);
89+
});
90+
91+
it('maneja @import en <style> y error de fetch', async () => {
92+
const style = document.createElement('style');
93+
style.textContent = `@import url('https://test.com/imported.css');`;
94+
document.head.appendChild(style);
95+
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('fail')));
96+
await new Promise(r => setTimeout(r, 10));
97+
const css = await embedCustomFonts();
98+
expect(typeof css).toBe('string');
99+
document.head.removeChild(style);
100+
vi.unstubAllGlobals();
101+
});
102+
103+
it('maneja error de fetch para <link rel="stylesheet">', async () => {
104+
const link = document.createElement('link');
105+
link.rel = 'stylesheet';
106+
link.href = 'https://test.com/bad.css';
107+
document.head.appendChild(link);
108+
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('fail')));
109+
const css = await embedCustomFonts();
110+
expect(typeof css).toBe('string');
111+
document.head.removeChild(link);
112+
vi.unstubAllGlobals();
113+
});
114+
});

0 commit comments

Comments
 (0)