Full document, spreadsheet, slideshow, and diagram tooling
1import { describe, it, expect } from 'vitest';
2import {
3 extractFormat,
4 applyFormat,
5 FORMAT_PROPERTIES,
6} from '../src/sheets/format-painter.js';
7
8// ============================================================
9// FORMAT_PROPERTIES constant
10// ============================================================
11
12describe('FORMAT_PROPERTIES', () => {
13 it('includes all expected formatting properties', () => {
14 expect(FORMAT_PROPERTIES).toContain('bold');
15 expect(FORMAT_PROPERTIES).toContain('italic');
16 expect(FORMAT_PROPERTIES).toContain('align');
17 expect(FORMAT_PROPERTIES).toContain('color');
18 expect(FORMAT_PROPERTIES).toContain('bg');
19 expect(FORMAT_PROPERTIES).toContain('borders');
20 expect(FORMAT_PROPERTIES).toContain('format');
21 expect(FORMAT_PROPERTIES).toContain('wrap');
22 });
23
24 it('includes new formatting properties (underline, strikethrough, verticalAlign, fontSize, fontFamily)', () => {
25 expect(FORMAT_PROPERTIES).toContain('underline');
26 expect(FORMAT_PROPERTIES).toContain('strikethrough');
27 expect(FORMAT_PROPERTIES).toContain('verticalAlign');
28 expect(FORMAT_PROPERTIES).toContain('fontSize');
29 expect(FORMAT_PROPERTIES).toContain('fontFamily');
30 });
31});
32
33// ============================================================
34// extractFormat — new properties
35// ============================================================
36
37describe('extractFormat — new properties', () => {
38 it('extracts underline and strikethrough', () => {
39 const cellData = { v: 'x', f: '', s: { underline: true, strikethrough: true } };
40 const result = extractFormat(cellData);
41 expect(result.underline).toBe(true);
42 expect(result.strikethrough).toBe(true);
43 });
44
45 it('extracts fontSize and fontFamily', () => {
46 const cellData = { v: 'x', f: '', s: { fontSize: 14, fontFamily: 'serif' } };
47 const result = extractFormat(cellData);
48 expect(result.fontSize).toBe(14);
49 expect(result.fontFamily).toBe('serif');
50 });
51
52 it('extracts verticalAlign', () => {
53 const cellData = { v: 'x', f: '', s: { verticalAlign: 'bottom' } };
54 const result = extractFormat(cellData);
55 expect(result.verticalAlign).toBe('bottom');
56 });
57});
58
59// ============================================================
60// applyFormat — new properties
61// ============================================================
62
63describe('applyFormat — new properties', () => {
64 it('applies new properties to a cell', () => {
65 const existing = { bold: true };
66 const format = { underline: true, fontSize: 16, fontFamily: 'monospace', verticalAlign: 'middle', strikethrough: true };
67 const result = applyFormat(existing, format);
68 expect(result.bold).toBe(true);
69 expect(result.underline).toBe(true);
70 expect(result.fontSize).toBe(16);
71 expect(result.fontFamily).toBe('monospace');
72 expect(result.verticalAlign).toBe('middle');
73 expect(result.strikethrough).toBe(true);
74 });
75});
76
77// ============================================================
78// extractFormat — extracts formatting from cell data
79// ============================================================
80
81describe('extractFormat', () => {
82 it('extracts all style properties from cell data', () => {
83 const cellData = {
84 v: 42,
85 f: 'A1+B1',
86 s: {
87 bold: true,
88 italic: true,
89 align: 'center',
90 color: '#ff0000',
91 bg: '#eeeeee',
92 borders: { top: '1px solid #999' },
93 format: 'currency',
94 wrap: true,
95 },
96 };
97 const result = extractFormat(cellData);
98 expect(result.bold).toBe(true);
99 expect(result.italic).toBe(true);
100 expect(result.align).toBe('center');
101 expect(result.color).toBe('#ff0000');
102 expect(result.bg).toBe('#eeeeee');
103 expect(result.borders).toEqual({ top: '1px solid #999' });
104 expect(result.format).toBe('currency');
105 expect(result.wrap).toBe(true);
106 });
107
108 it('returns empty object for cell with no style', () => {
109 const cellData = { v: 'hello', f: '', s: {} };
110 const result = extractFormat(cellData);
111 expect(result).toEqual({});
112 });
113
114 it('returns empty object for null cell data', () => {
115 const result = extractFormat(null);
116 expect(result).toEqual({});
117 });
118
119 it('returns empty object for undefined cell data', () => {
120 const result = extractFormat(undefined);
121 expect(result).toEqual({});
122 });
123
124 it('only extracts known format properties, ignores others', () => {
125 const cellData = {
126 v: 10,
127 f: '',
128 s: {
129 bold: true,
130 someRandomProp: 'test',
131 anotherProp: 123,
132 },
133 };
134 const result = extractFormat(cellData);
135 expect(result.bold).toBe(true);
136 expect(result).not.toHaveProperty('someRandomProp');
137 expect(result).not.toHaveProperty('anotherProp');
138 });
139
140 it('extracts partial style (only some properties set)', () => {
141 const cellData = { v: '', f: '', s: { bold: true, bg: '#fff' } };
142 const result = extractFormat(cellData);
143 expect(result.bold).toBe(true);
144 expect(result.bg).toBe('#fff');
145 expect(result).not.toHaveProperty('italic');
146 expect(result).not.toHaveProperty('color');
147 });
148});
149
150// ============================================================
151// applyFormat — merges format into existing cell data
152// ============================================================
153
154describe('applyFormat', () => {
155 it('applies all format properties to cell with existing style', () => {
156 const existingStyle = { bold: false, color: '#000' };
157 const format = { bold: true, italic: true, bg: '#eee' };
158 const result = applyFormat(existingStyle, format);
159 expect(result.bold).toBe(true);
160 expect(result.italic).toBe(true);
161 expect(result.bg).toBe('#eee');
162 // Original non-format properties preserved
163 expect(result.color).toBe('#000');
164 });
165
166 it('replaces existing format properties', () => {
167 const existingStyle = { bold: true, align: 'left', color: '#f00' };
168 const format = { bold: false, align: 'center', color: '#00f' };
169 const result = applyFormat(existingStyle, format);
170 expect(result.bold).toBe(false);
171 expect(result.align).toBe('center');
172 expect(result.color).toBe('#00f');
173 });
174
175 it('handles null existing style', () => {
176 const format = { bold: true, bg: '#eee' };
177 const result = applyFormat(null, format);
178 expect(result.bold).toBe(true);
179 expect(result.bg).toBe('#eee');
180 });
181
182 it('handles undefined existing style', () => {
183 const format = { italic: true };
184 const result = applyFormat(undefined, format);
185 expect(result.italic).toBe(true);
186 });
187
188 it('handles empty format (clears nothing)', () => {
189 const existingStyle = { bold: true, color: '#f00' };
190 const format = {};
191 const result = applyFormat(existingStyle, format);
192 expect(result.bold).toBe(true);
193 expect(result.color).toBe('#f00');
194 });
195
196 it('does not mutate original objects', () => {
197 const existingStyle = { bold: true };
198 const format = { italic: true };
199 const result = applyFormat(existingStyle, format);
200 expect(existingStyle).toEqual({ bold: true });
201 expect(format).toEqual({ italic: true });
202 expect(result).toEqual({ bold: true, italic: true });
203 });
204
205 it('handles borders object deep copy', () => {
206 const format = { borders: { top: '1px solid #000', bottom: '1px solid #000' } };
207 const result = applyFormat({}, format);
208 expect(result.borders).toEqual(format.borders);
209 // Verify it's a copy not a reference
210 expect(result.borders).not.toBe(format.borders);
211 });
212
213 it('clears format properties when value is undefined', () => {
214 const existingStyle = { bold: true, italic: true, bg: '#fff' };
215 const format = { bold: undefined, italic: false };
216 const result = applyFormat(existingStyle, format);
217 // undefined properties are removed
218 expect(result).not.toHaveProperty('bold');
219 expect(result.italic).toBe(false);
220 expect(result.bg).toBe('#fff');
221 });
222});