Full document, spreadsheet, slideshow, and diagram tooling
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 337 lines 12 kB view raw
1import { describe, it, expect } from 'vitest'; 2import { 3 createDeck, 4 addSlide, 5 removeSlide, 6 duplicateSlide, 7 goToSlide, 8 moveSlide, 9 addElement, 10 removeElement, 11 currentSlide, 12 slideCount, 13 elementCount, 14} from '../src/slides/canvas-engine.js'; 15 16// ===================================================================== 17// 1. ADD SLIDE — currentSlide index behavior 18// ===================================================================== 19 20describe('Canvas engine integrity — addSlide updates', () => { 21 it('adding a slide does not change currentSlide index', () => { 22 let deck = createDeck(); 23 expect(deck.currentSlide).toBe(0); 24 25 deck = addSlide(deck); 26 // currentSlide should still be 0 (pointing to the original first slide) 27 expect(deck.currentSlide).toBe(0); 28 expect(slideCount(deck)).toBe(2); 29 }); 30 31 it('adding a slide at index 0 pushes the original slide to index 1', () => { 32 let deck = createDeck(); 33 const originalId = deck.slides[0]!.id; 34 35 deck = addSlide(deck, 0); 36 expect(deck.slides[1]!.id).toBe(originalId); 37 expect(slideCount(deck)).toBe(2); 38 }); 39 40 it('currentSlide still points to a valid slide after adding at end', () => { 41 let deck = createDeck(); 42 deck = goToSlide(deck, 0); 43 deck = addSlide(deck); 44 deck = addSlide(deck); 45 46 expect(deck.currentSlide).toBeLessThan(slideCount(deck)); 47 expect(currentSlide(deck)).toBeDefined(); 48 expect(currentSlide(deck).id).toBeTruthy(); 49 }); 50}); 51 52// ===================================================================== 53// 2. DELETE ONLY SLIDE — cannot delete the last slide 54// ===================================================================== 55 56describe('Canvas engine integrity — delete only slide', () => { 57 it('removing the only slide returns unchanged state', () => { 58 const deck = createDeck(); 59 expect(slideCount(deck)).toBe(1); 60 61 const updated = removeSlide(deck, 0); 62 expect(slideCount(updated)).toBe(1); 63 // State reference should be the same (no mutation) 64 expect(updated).toBe(deck); 65 }); 66 67 it('after removing one of two slides, currentSlide is clamped', () => { 68 let deck = createDeck(); 69 deck = addSlide(deck); 70 deck = goToSlide(deck, 1); // on the second slide 71 72 const updated = removeSlide(deck, 1); 73 expect(slideCount(updated)).toBe(1); 74 expect(updated.currentSlide).toBe(0); 75 }); 76 77 it('removing the first slide when on first slide clamps to 0', () => { 78 let deck = createDeck(); 79 deck = addSlide(deck); 80 deck = goToSlide(deck, 0); 81 82 const updated = removeSlide(deck, 0); 83 expect(updated.currentSlide).toBe(0); 84 expect(slideCount(updated)).toBe(1); 85 }); 86}); 87 88// ===================================================================== 89// 3. ADD ELEMENT TO EMPTY DECK — works on the default first slide 90// ===================================================================== 91 92describe('Canvas engine integrity — add element to empty deck', () => { 93 it('addElement works on the default first slide of a new deck', () => { 94 let deck = createDeck(); 95 expect(elementCount(deck)).toBe(0); 96 97 deck = addElement(deck, 'text', 10, 20, 200, 100, 'Hello'); 98 expect(elementCount(deck)).toBe(1); 99 100 const el = currentSlide(deck).elements[0]!; 101 expect(el.type).toBe('text'); 102 expect(el.content).toBe('Hello'); 103 }); 104 105 it('element is added to the current slide, not all slides', () => { 106 let deck = createDeck(); 107 deck = addSlide(deck); 108 109 // Current slide is 0 110 deck = addElement(deck, 'text', 0, 0, 100, 50, 'On slide 0'); 111 112 expect(deck.slides[0]!.elements).toHaveLength(1); 113 expect(deck.slides[1]!.elements).toHaveLength(0); 114 }); 115 116 it('adding element to second slide only affects that slide', () => { 117 let deck = createDeck(); 118 deck = addSlide(deck); 119 deck = goToSlide(deck, 1); 120 121 deck = addElement(deck, 'image', 0, 0, 300, 200, 'image.png'); 122 123 expect(deck.slides[0]!.elements).toHaveLength(0); 124 expect(deck.slides[1]!.elements).toHaveLength(1); 125 }); 126}); 127 128// ===================================================================== 129// 4. ELEMENT POSITIONING AFTER SLIDE REORDER 130// ===================================================================== 131 132describe('Canvas engine integrity — element positioning after reorder', () => { 133 it('moving a slide preserves its elements', () => { 134 let deck = createDeck(); 135 deck = addElement(deck, 'text', 10, 20, 100, 50, 'Slide 0 text'); 136 deck = addSlide(deck); 137 deck = goToSlide(deck, 1); 138 deck = addElement(deck, 'image', 30, 40, 200, 150, 'image.png'); 139 140 // Slide 0 has text, Slide 1 has image 141 expect(deck.slides[0]!.elements[0]!.content).toBe('Slide 0 text'); 142 expect(deck.slides[1]!.elements[0]!.content).toBe('image.png'); 143 144 // Move slide 0 to position 1 145 deck = moveSlide(deck, 0, 1); 146 147 // Now slide at index 0 was the old slide 1 (image), and slide at index 1 is old slide 0 (text) 148 expect(deck.slides[0]!.elements[0]!.content).toBe('image.png'); 149 expect(deck.slides[1]!.elements[0]!.content).toBe('Slide 0 text'); 150 }); 151 152 it('reordering slides does not lose any elements', () => { 153 let deck = createDeck(); 154 deck = addElement(deck, 'text', 0, 0, 100, 50, 'el1'); 155 deck = addSlide(deck); 156 deck = goToSlide(deck, 1); 157 deck = addElement(deck, 'text', 0, 0, 100, 50, 'el2'); 158 deck = addSlide(deck); 159 deck = goToSlide(deck, 2); 160 deck = addElement(deck, 'text', 0, 0, 100, 50, 'el3'); 161 162 // Move slide 2 to position 0 163 deck = moveSlide(deck, 2, 0); 164 165 // Count total elements across all slides 166 const totalElements = deck.slides.reduce((sum, s) => sum + s.elements.length, 0); 167 expect(totalElements).toBe(3); 168 169 // Verify all content is preserved 170 const contents = deck.slides.flatMap(s => s.elements.map(e => e.content)).sort(); 171 expect(contents).toEqual(['el1', 'el2', 'el3']); 172 }); 173}); 174 175// ===================================================================== 176// 5. DUPLICATE SLIDE PRESERVES ALL ELEMENTS 177// ===================================================================== 178 179describe('Canvas engine integrity — duplicate slide', () => { 180 it('duplicated slide has same number of elements', () => { 181 let deck = createDeck(); 182 deck = addElement(deck, 'text', 10, 20, 100, 50, 'Hello'); 183 deck = addElement(deck, 'image', 30, 40, 200, 150, 'photo.jpg'); 184 deck = addElement(deck, 'shape', 50, 60, 80, 80, '', { fill: 'red' }); 185 186 deck = duplicateSlide(deck, 0); 187 expect(slideCount(deck)).toBe(2); 188 expect(deck.slides[1]!.elements).toHaveLength(3); 189 }); 190 191 it('duplicated elements have different IDs from originals', () => { 192 let deck = createDeck(); 193 deck = addElement(deck, 'text', 0, 0, 100, 50, 'content'); 194 deck = addElement(deck, 'image', 0, 0, 100, 50, 'img'); 195 196 deck = duplicateSlide(deck, 0); 197 198 const originalIds = deck.slides[0]!.elements.map(e => e.id); 199 const duplicateIds = deck.slides[1]!.elements.map(e => e.id); 200 201 for (const oid of originalIds) { 202 expect(duplicateIds).not.toContain(oid); 203 } 204 }); 205 206 it('duplicated elements preserve content, type, position, and style', () => { 207 let deck = createDeck(); 208 deck = addElement(deck, 'text', 10, 20, 200, 100, 'Test Content', { color: 'blue', fontSize: '24px' }); 209 210 deck = duplicateSlide(deck, 0); 211 212 const original = deck.slides[0]!.elements[0]!; 213 const copy = deck.slides[1]!.elements[0]!; 214 215 expect(copy.content).toBe(original.content); 216 expect(copy.type).toBe(original.type); 217 expect(copy.x).toBe(original.x); 218 expect(copy.y).toBe(original.y); 219 expect(copy.width).toBe(original.width); 220 expect(copy.height).toBe(original.height); 221 expect(copy.style).toEqual(original.style); 222 }); 223 224 it('duplicated slide preserves notes', () => { 225 let deck = createDeck(); 226 deck = { 227 ...deck, 228 slides: deck.slides.map(s => ({ ...s, notes: 'Speaker notes here' })), 229 }; 230 231 deck = duplicateSlide(deck, 0); 232 expect(deck.slides[1]!.notes).toBe('Speaker notes here'); 233 }); 234 235 it('duplicated slide preserves background color', () => { 236 let deck = createDeck(); 237 deck = addSlide(deck, undefined, '#ff0000'); 238 239 deck = duplicateSlide(deck, 1); 240 expect(deck.slides[2]!.background).toBe('#ff0000'); 241 }); 242}); 243 244// ===================================================================== 245// 6. LARGE NUMBER OF ELEMENTS PER SLIDE (100+) 246// ===================================================================== 247 248describe('Canvas engine integrity — many elements per slide', () => { 249 it('adding 100 elements to a single slide works correctly', () => { 250 let deck = createDeck(); 251 for (let i = 0; i < 100; i++) { 252 deck = addElement(deck, 'text', i * 10, i * 5, 50, 30, `Element ${i}`); 253 } 254 expect(elementCount(deck)).toBe(100); 255 }); 256 257 it('all 100 elements have unique IDs', () => { 258 let deck = createDeck(); 259 for (let i = 0; i < 100; i++) { 260 deck = addElement(deck, 'text', i * 10, i * 5, 50, 30); 261 } 262 263 const ids = currentSlide(deck).elements.map(e => e.id); 264 const uniqueIds = new Set(ids); 265 expect(uniqueIds.size).toBe(100); 266 }); 267 268 it('removing one element from 100 leaves 99', () => { 269 let deck = createDeck(); 270 for (let i = 0; i < 100; i++) { 271 deck = addElement(deck, 'text', i * 10, i * 5, 50, 30, `Element ${i}`); 272 } 273 274 const firstId = currentSlide(deck).elements[0]!.id; 275 deck = removeElement(deck, firstId); 276 expect(elementCount(deck)).toBe(99); 277 278 // Verify the removed element is really gone 279 const remaining = currentSlide(deck).elements.find(e => e.id === firstId); 280 expect(remaining).toBeUndefined(); 281 }); 282 283 it('element zIndex increments correctly for 100 elements', () => { 284 let deck = createDeck(); 285 for (let i = 0; i < 100; i++) { 286 deck = addElement(deck, 'text', 0, 0, 50, 30); 287 } 288 289 const elements = currentSlide(deck).elements; 290 for (let i = 0; i < elements.length; i++) { 291 expect(elements[i]!.zIndex).toBe(i); 292 } 293 }); 294}); 295 296// ===================================================================== 297// 7. EDGE CASES — goToSlide clamping, removeElement on wrong slide 298// ===================================================================== 299 300describe('Canvas engine integrity — edge cases', () => { 301 it('goToSlide with negative index clamps to 0', () => { 302 const deck = createDeck(); 303 const updated = goToSlide(deck, -10); 304 expect(updated.currentSlide).toBe(0); 305 }); 306 307 it('goToSlide with index beyond last slide clamps to last', () => { 308 let deck = createDeck(); 309 deck = addSlide(deck); 310 const updated = goToSlide(deck, 999); 311 expect(updated.currentSlide).toBe(1); 312 }); 313 314 it('removeElement with non-existent ID does not change element count', () => { 315 let deck = createDeck(); 316 deck = addElement(deck, 'text', 0, 0, 100, 50, 'test'); 317 const before = elementCount(deck); 318 319 deck = removeElement(deck, 'non-existent-id'); 320 expect(elementCount(deck)).toBe(before); 321 }); 322 323 it('duplicateSlide with invalid index returns unchanged state', () => { 324 const deck = createDeck(); 325 const updated = duplicateSlide(deck, 99); 326 expect(updated).toBe(deck); 327 }); 328 329 it('removeSlide with out-of-range index still works gracefully', () => { 330 let deck = createDeck(); 331 deck = addSlide(deck); 332 // Index 5 is out of range for a 2-slide deck 333 const updated = removeSlide(deck, 5); 334 // filter will not find index 5, so all slides remain 335 expect(slideCount(updated)).toBe(2); 336 }); 337});