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

Configure Feed

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

Merge pull request 'test: share-dialog pure function coverage (26 tests)' (#253) from test/batch12-share-dialog into main

scott 9890c880 dee5a919

+162
+162
tests/share-dialog.test.ts
··· 1 + import { describe, it, expect } from 'vitest'; 2 + import { 3 + buildShareUrl, 4 + parseViewMode, 5 + getExpiryLabel, 6 + computeExpiryDate, 7 + } from '../src/lib/share-dialog.js'; 8 + 9 + // ===================================================================== 10 + // buildShareUrl 11 + // ===================================================================== 12 + 13 + describe('buildShareUrl', () => { 14 + const base = 'https://tools.example.com'; 15 + 16 + it('builds edit URL (no query param)', () => { 17 + const url = buildShareUrl(base, 'doc', 'abc123', 'keyXYZ', 'edit'); 18 + expect(url).toBe('https://tools.example.com/doc/abc123#keyXYZ'); 19 + }); 20 + 21 + it('builds view URL (appends ?mode=view)', () => { 22 + const url = buildShareUrl(base, 'doc', 'abc123', 'keyXYZ', 'view'); 23 + expect(url).toBe('https://tools.example.com/doc/abc123#keyXYZ?mode=view'); 24 + }); 25 + 26 + it('works with sheet doc type', () => { 27 + const url = buildShareUrl(base, 'sheet', 'sid', 'k', 'edit'); 28 + expect(url).toBe('https://tools.example.com/sheet/sid#k'); 29 + }); 30 + 31 + it('preserves base URL trailing content', () => { 32 + const url = buildShareUrl('https://tools.example.com/app', 'doc', 'd1', 'k1', 'edit'); 33 + expect(url).toBe('https://tools.example.com/app/doc/d1#k1'); 34 + }); 35 + 36 + it('handles empty key string', () => { 37 + const url = buildShareUrl(base, 'doc', 'd1', '', 'edit'); 38 + expect(url).toBe('https://tools.example.com/doc/d1#'); 39 + }); 40 + }); 41 + 42 + // ===================================================================== 43 + // parseViewMode 44 + // ===================================================================== 45 + 46 + describe('parseViewMode', () => { 47 + it('returns true for "view"', () => { 48 + expect(parseViewMode('view')).toBe(true); 49 + }); 50 + 51 + it('returns false for "edit"', () => { 52 + expect(parseViewMode('edit')).toBe(false); 53 + }); 54 + 55 + it('returns false for null', () => { 56 + expect(parseViewMode(null)).toBe(false); 57 + }); 58 + 59 + it('returns false for empty string', () => { 60 + expect(parseViewMode('')).toBe(false); 61 + }); 62 + 63 + it('returns false for "VIEW" (case-sensitive)', () => { 64 + expect(parseViewMode('VIEW')).toBe(false); 65 + }); 66 + 67 + it('returns false for "View"', () => { 68 + expect(parseViewMode('View')).toBe(false); 69 + }); 70 + }); 71 + 72 + // ===================================================================== 73 + // getExpiryLabel 74 + // ===================================================================== 75 + 76 + describe('getExpiryLabel', () => { 77 + it('returns "No expiry" for "none"', () => { 78 + expect(getExpiryLabel('none')).toBe('No expiry'); 79 + }); 80 + 81 + it('returns "1 hour" for "1h"', () => { 82 + expect(getExpiryLabel('1h')).toBe('1 hour'); 83 + }); 84 + 85 + it('returns "1 day" for "1d"', () => { 86 + expect(getExpiryLabel('1d')).toBe('1 day'); 87 + }); 88 + 89 + it('returns "7 days" for "7d"', () => { 90 + expect(getExpiryLabel('7d')).toBe('7 days'); 91 + }); 92 + 93 + it('returns "30 days" for "30d"', () => { 94 + expect(getExpiryLabel('30d')).toBe('30 days'); 95 + }); 96 + 97 + it('returns "No expiry" for unknown option', () => { 98 + expect(getExpiryLabel('99d')).toBe('No expiry'); 99 + }); 100 + 101 + it('returns "No expiry" for empty string', () => { 102 + expect(getExpiryLabel('')).toBe('No expiry'); 103 + }); 104 + }); 105 + 106 + // ===================================================================== 107 + // computeExpiryDate 108 + // ===================================================================== 109 + 110 + describe('computeExpiryDate', () => { 111 + it('returns null for "none"', () => { 112 + expect(computeExpiryDate('none')).toBeNull(); 113 + }); 114 + 115 + it('returns null for null', () => { 116 + expect(computeExpiryDate(null)).toBeNull(); 117 + }); 118 + 119 + it('returns null for unknown option', () => { 120 + expect(computeExpiryDate('99d')).toBeNull(); 121 + }); 122 + 123 + it('returns ISO date string for "1h"', () => { 124 + const before = Date.now(); 125 + const result = computeExpiryDate('1h'); 126 + const after = Date.now(); 127 + expect(result).not.toBeNull(); 128 + const date = new Date(result!).getTime(); 129 + // Should be ~1 hour in the future 130 + expect(date).toBeGreaterThanOrEqual(before + 3600000); 131 + expect(date).toBeLessThanOrEqual(after + 3600000); 132 + }); 133 + 134 + it('returns ISO date string for "1d"', () => { 135 + const result = computeExpiryDate('1d'); 136 + expect(result).not.toBeNull(); 137 + const date = new Date(result!).getTime(); 138 + const expected = Date.now() + 86400000; 139 + expect(Math.abs(date - expected)).toBeLessThan(100); // within 100ms 140 + }); 141 + 142 + it('returns ISO date string for "7d"', () => { 143 + const result = computeExpiryDate('7d'); 144 + expect(result).not.toBeNull(); 145 + const date = new Date(result!).getTime(); 146 + const expected = Date.now() + 7 * 86400000; 147 + expect(Math.abs(date - expected)).toBeLessThan(100); 148 + }); 149 + 150 + it('returns ISO date string for "30d"', () => { 151 + const result = computeExpiryDate('30d'); 152 + expect(result).not.toBeNull(); 153 + const date = new Date(result!).getTime(); 154 + const expected = Date.now() + 30 * 86400000; 155 + expect(Math.abs(date - expected)).toBeLessThan(100); 156 + }); 157 + 158 + it('returns a valid ISO 8601 string', () => { 159 + const result = computeExpiryDate('1h'); 160 + expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/); 161 + }); 162 + });