WIP: A simple cli for daily tangled use cases and AI integration. This is for my personal use right now, but happy if others get mileage from it! :)
1import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2import { formatDate, outputJson } from '../../src/utils/formatting.js';
3
4describe('formatDate', () => {
5 beforeEach(() => {
6 // Mock current date to 2024-06-15 12:00:00 UTC for consistent testing
7 vi.useFakeTimers();
8 vi.setSystemTime(new Date('2024-06-15T12:00:00.000Z'));
9 });
10
11 afterEach(() => {
12 vi.useRealTimers();
13 });
14
15 it('should return "today" for current day', () => {
16 const today = new Date('2024-06-15T10:00:00.000Z').toISOString();
17 expect(formatDate(today)).toBe('today');
18 });
19
20 it('should return "yesterday" for previous day', () => {
21 const yesterday = new Date('2024-06-14T10:00:00.000Z').toISOString();
22 expect(formatDate(yesterday)).toBe('yesterday');
23 });
24
25 it('should return days ago for 2-6 days', () => {
26 const twoDaysAgo = new Date('2024-06-13T10:00:00.000Z').toISOString();
27 expect(formatDate(twoDaysAgo)).toBe('2 days ago');
28
29 const threeDaysAgo = new Date('2024-06-12T10:00:00.000Z').toISOString();
30 expect(formatDate(threeDaysAgo)).toBe('3 days ago');
31
32 const sixDaysAgo = new Date('2024-06-09T10:00:00.000Z').toISOString();
33 expect(formatDate(sixDaysAgo)).toBe('6 days ago');
34 });
35
36 it('should return weeks ago for 7-29 days', () => {
37 const oneWeekAgo = new Date('2024-06-08T10:00:00.000Z').toISOString();
38 expect(formatDate(oneWeekAgo)).toBe('1 weeks ago');
39
40 const twoWeeksAgo = new Date('2024-06-01T10:00:00.000Z').toISOString();
41 expect(formatDate(twoWeeksAgo)).toBe('2 weeks ago');
42
43 const threeWeeksAgo = new Date('2024-05-25T10:00:00.000Z').toISOString();
44 expect(formatDate(threeWeeksAgo)).toBe('3 weeks ago');
45 });
46
47 it('should return months ago for 30-364 days', () => {
48 const oneMonthAgo = new Date('2024-05-16T10:00:00.000Z').toISOString();
49 expect(formatDate(oneMonthAgo)).toBe('1 months ago');
50
51 const threeMonthsAgo = new Date('2024-03-16T10:00:00.000Z').toISOString();
52 expect(formatDate(threeMonthsAgo)).toBe('3 months ago');
53
54 const sixMonthsAgo = new Date('2023-12-16T10:00:00.000Z').toISOString();
55 expect(formatDate(sixMonthsAgo)).toBe('6 months ago');
56 });
57
58 it('should return locale date string for 365+ days', () => {
59 const oneYearAgo = new Date('2023-06-15T10:00:00.000Z').toISOString();
60 const formatted = formatDate(oneYearAgo);
61
62 // The exact format depends on locale, but it should be a date string
63 expect(formatted).toMatch(/\d{1,2}\/\d{1,2}\/\d{4}/);
64 });
65
66 it('should handle edge case at exactly 7 days', () => {
67 const sevenDaysAgo = new Date('2024-06-08T12:00:00.000Z').toISOString();
68 expect(formatDate(sevenDaysAgo)).toBe('1 weeks ago');
69 });
70
71 it('should handle edge case at exactly 30 days', () => {
72 const thirtyDaysAgo = new Date('2024-05-16T12:00:00.000Z').toISOString();
73 expect(formatDate(thirtyDaysAgo)).toBe('1 months ago');
74 });
75
76 it('should handle edge case at exactly 365 days', () => {
77 const oneYearAgo = new Date('2023-06-15T12:00:00.000Z').toISOString();
78 const formatted = formatDate(oneYearAgo);
79
80 // Should use locale date string
81 expect(formatted).toMatch(/\d{1,2}\/\d{1,2}\/\d{4}/);
82 });
83});
84
85describe('outputJson', () => {
86 let consoleLogSpy: ReturnType<typeof vi.spyOn>;
87
88 beforeEach(() => {
89 consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
90 });
91
92 afterEach(() => {
93 vi.restoreAllMocks();
94 });
95
96 it('should output all fields of an object when no fields specified', () => {
97 const data = { title: 'Test', state: 'open', author: 'did:plc:abc' };
98 outputJson(data);
99 expect(consoleLogSpy).toHaveBeenCalledWith(JSON.stringify(data, null, 2));
100 });
101
102 it('should output only specified fields of an object', () => {
103 const data = { title: 'Test', state: 'open', author: 'did:plc:abc', cid: 'bafyrei1' };
104 outputJson(data, 'title,state');
105 const output = JSON.parse(consoleLogSpy.mock.calls[0][0] as string);
106 expect(output).toEqual({ title: 'Test', state: 'open' });
107 expect(output).not.toHaveProperty('author');
108 expect(output).not.toHaveProperty('cid');
109 });
110
111 it('should output all fields of an array when no fields specified', () => {
112 const data = [
113 { title: 'First', state: 'open' },
114 { title: 'Second', state: 'closed' },
115 ];
116 outputJson(data);
117 expect(consoleLogSpy).toHaveBeenCalledWith(JSON.stringify(data, null, 2));
118 });
119
120 it('should output only specified fields of an array', () => {
121 const data = [
122 { title: 'First', state: 'open', author: 'did:plc:abc' },
123 { title: 'Second', state: 'closed', author: 'did:plc:xyz' },
124 ];
125 outputJson(data, 'title,state');
126 const output = JSON.parse(consoleLogSpy.mock.calls[0][0] as string);
127 expect(output).toEqual([
128 { title: 'First', state: 'open' },
129 { title: 'Second', state: 'closed' },
130 ]);
131 });
132
133 it('should silently omit fields not present in the object', () => {
134 const data = { title: 'Test', state: 'open' };
135 outputJson(data, 'title,nonexistent');
136 const output = JSON.parse(consoleLogSpy.mock.calls[0][0] as string);
137 expect(output).toEqual({ title: 'Test' });
138 });
139
140 it('should trim whitespace from field names', () => {
141 const data = { title: 'Test', state: 'open' };
142 outputJson(data, ' title , state ');
143 const output = JSON.parse(consoleLogSpy.mock.calls[0][0] as string);
144 expect(output).toEqual({ title: 'Test', state: 'open' });
145 });
146});