|
1 | 1 | import { describe, it, expect, vi, afterEach } from 'vitest';
|
2 |
| -import { wrapText, generateFeaturedImage } from '../generateFeaturedImage'; |
| 2 | +import { getWrappedLines, drawTitleAndSubtitle, generateFeaturedImage } from '../generateFeaturedImage'; |
3 | 3 | import fs from 'fs-extra';
|
4 | 4 | import path from 'path';
|
5 | 5 |
|
6 |
| -describe('wrapText', () => { |
| 6 | +describe('getWrappedLines', () => { |
7 | 7 | it('should wrap text into multiple lines when exceeding max width', () => {
|
8 |
| - // Create a fake canvas context. |
9 |
| - const fillTextMock = vi.fn(); |
| 8 | + // Create a fake canvas context that simulates measureText. |
10 | 9 | const fakeCtx = {
|
11 |
| - // Fake measureText: each character counts for 10 pixels. |
12 | 10 | measureText: (text: string) => ({ width: text.length * 10 }),
|
13 |
| - fillText: fillTextMock, |
14 |
| - textAlign: '', // not used by our helper |
15 | 11 | } as unknown as import('canvas').CanvasRenderingContext2D;
|
16 | 12 |
|
17 |
| - const maxWidth = 100; // Allow up to 100 pixels per line |
18 |
| - const lineHeight = 20; |
19 |
| - const x = 50; |
20 |
| - const y = 50; |
| 13 | + const maxWidth = 100; // 100 pixels maximum |
21 | 14 | const text = "This is a long text that should be wrapped";
|
22 | 15 |
|
23 |
| - wrapText(fakeCtx, text, x, y, maxWidth, lineHeight); |
| 16 | + // Call getWrappedLines to get an array of wrapped lines. |
| 17 | + const lines = getWrappedLines(fakeCtx, text, maxWidth); |
24 | 18 |
|
25 |
| - // Based on our fake measureText, the helper should break the text into lines. |
26 |
| - // For this specific text and maxWidth, one possible expected split is: |
| 19 | + // Based on our fake measureText (each character = 10px), one expected split is: |
27 | 20 | // 1. "This is a"
|
28 | 21 | // 2. "long text"
|
29 | 22 | // 3. "that"
|
30 | 23 | // 4. "should be"
|
31 | 24 | // 5. "wrapped"
|
32 |
| - // (Depending on spacing, your exact split might vary.) |
33 | 25 | const expectedLines = ["This is a", "long text", "that", "should be", "wrapped"];
|
| 26 | + expect(lines).toEqual(expectedLines); |
| 27 | + }); |
| 28 | +}); |
| 29 | + |
| 30 | +describe('drawTitleAndSubtitle', () => { |
| 31 | + it('should draw title and subtitle with correct dynamic spacing for single-line texts', () => { |
| 32 | + // Create a fake canvas context with spy functions for fillText. |
| 33 | + const fillTextMock = vi.fn(); |
| 34 | + const fakeCtx = { |
| 35 | + measureText: (text: string) => ({ width: text.length * 10 }), |
| 36 | + fillText: fillTextMock, |
| 37 | + save: vi.fn(), |
| 38 | + restore: vi.fn(), |
| 39 | + font: '', |
| 40 | + } as unknown as import('canvas').CanvasRenderingContext2D; |
| 41 | + |
| 42 | + const x = 50; // center x-coordinate |
| 43 | + const canvasHeight = 200; // total canvas height for vertical centering |
| 44 | + const maxWidth = 100; |
| 45 | + const titleFont = "bold 60px Nunito, sans-serif"; |
| 46 | + const subtitleFont = "normal 40px Nunito, sans-serif"; |
| 47 | + const titleLineHeight = 70; // approximate height for each title line |
| 48 | + const subtitleLineHeight = 50; // approximate height for each subtitle line |
| 49 | + const baseGap = 20; |
| 50 | + const gapExtraPerLine = 10; |
| 51 | + |
| 52 | + // Use simple one-word texts so that each wraps into a single line. |
| 53 | + const title = "Hello"; |
| 54 | + const subtitle = "World"; |
| 55 | + |
| 56 | + // Expected calculations: |
| 57 | + // - getWrappedLines("Hello") returns ["Hello"] → titleBlockHeight = 1 * 70 = 70. |
| 58 | + // - getWrappedLines("World") returns ["World"] → subtitleBlockHeight = 1 * 50 = 50. |
| 59 | + // - Gap = baseGap + (1 - 1) * gapExtraPerLine = 20. |
| 60 | + // - Total block height = 70 + 20 + 50 = 140. |
| 61 | + // - startY = (canvasHeight - totalBlockHeight) / 2 = (200 - 140) / 2 = 30. |
| 62 | + // Then: |
| 63 | + // Title is drawn at: 30 + (70 / 2) = 65. |
| 64 | + // Subtitle is drawn at: 30 + 70 + 20 + (50 / 2) = 145. |
| 65 | + |
| 66 | + drawTitleAndSubtitle( |
| 67 | + fakeCtx, |
| 68 | + title, |
| 69 | + subtitle, |
| 70 | + x, |
| 71 | + canvasHeight, |
| 72 | + maxWidth, |
| 73 | + titleFont, |
| 74 | + subtitleFont, |
| 75 | + titleLineHeight, |
| 76 | + subtitleLineHeight, |
| 77 | + baseGap, |
| 78 | + gapExtraPerLine |
| 79 | + ); |
34 | 80 |
|
35 |
| - // Check that fillText was called the expected number of times |
36 |
| - expect(fillTextMock).toHaveBeenCalledTimes(expectedLines.length); |
37 |
| - expectedLines.forEach((line, index) => { |
38 |
| - expect(fillTextMock).toHaveBeenNthCalledWith( |
39 |
| - index + 1, |
40 |
| - line, |
41 |
| - x, |
42 |
| - y + index * lineHeight |
43 |
| - ); |
44 |
| - }); |
| 81 | + expect(fillTextMock).toHaveBeenCalledTimes(2); |
| 82 | + expect(fillTextMock).toHaveBeenNthCalledWith(1, "Hello", x, 65); |
| 83 | + expect(fillTextMock).toHaveBeenNthCalledWith(2, "World", x, 145); |
45 | 84 | });
|
46 | 85 | });
|
47 | 86 |
|
48 | 87 | describe('generateFeaturedImage', () => {
|
49 |
| - // Use a temporary directory for output |
| 88 | + // Use a temporary directory for output during tests. |
50 | 89 | const tempOutputDir = path.join(__dirname, 'temp-test-generated');
|
51 | 90 |
|
52 | 91 | afterEach(async () => {
|
53 |
| - // Remove the temporary directory after each test to clean up. |
| 92 | + // Clean up the temporary directory after each test. |
54 | 93 | await fs.remove(tempOutputDir);
|
55 | 94 | });
|
56 | 95 |
|
57 | 96 | it('should generate an image file', async () => {
|
58 |
| - // For testing, you can either ensure that the background file exists at the |
59 |
| - // provided path or you can mock loadImage. For this example, we assume the |
60 |
| - // background image exists in the expected location. |
61 | 97 | const title = "Test Title";
|
62 | 98 | const subtitle = "Test Subtitle";
|
63 | 99 | const outputPath = path.join(tempOutputDir, 'test.png');
|
64 | 100 |
|
65 | 101 | await generateFeaturedImage(title, subtitle, outputPath);
|
66 | 102 |
|
67 |
| - // Check that the file was created. |
| 103 | + // Verify that the file was created. |
68 | 104 | const fileExists = await fs.pathExists(outputPath);
|
69 | 105 | expect(fileExists).toBe(true);
|
70 | 106 |
|
71 |
| - // Optionally, verify the file is not empty. |
| 107 | + // Optionally, check that the file is not empty. |
72 | 108 | const stats = await fs.stat(outputPath);
|
73 | 109 | expect(stats.size).toBeGreaterThan(0);
|
74 | 110 | });
|
|
0 commit comments