Full document, spreadsheet, slideshow, and diagram tooling
1/**
2 * Tests for form response export (CSV generation).
3 */
4import { describe, it, expect } from 'vitest';
5import {
6 createPipelineConfig,
7 pipelineHeaders,
8 responseToRow,
9 type FormResponse,
10} from '../src/forms/responses.js';
11import { escapeField } from '../src/sheets/csv-export.js';
12
13function makeResponse(answers: Record<string, unknown>, submittedAt = Date.now()): FormResponse {
14 return {
15 id: `resp-${submittedAt}`,
16 formId: 'form-1',
17 answers: new Map(Object.entries(answers)),
18 submittedAt,
19 submitterId: '',
20 };
21}
22
23describe('response export pipeline', () => {
24 const config = createPipelineConfig(
25 'form-1',
26 '',
27 ['q1', 'q2', 'q3'],
28 ['Name', 'Email', 'Rating'],
29 );
30
31 it('generates correct headers including timestamp', () => {
32 const headers = pipelineHeaders(config);
33 expect(headers).toEqual(['Name', 'Email', 'Rating', 'Submitted At']);
34 });
35
36 it('generates headers without timestamp when disabled', () => {
37 const noTs = createPipelineConfig('f', '', ['q1'], ['Name'], false);
38 expect(pipelineHeaders(noTs)).toEqual(['Name']);
39 });
40
41 it('converts response to row array', () => {
42 const resp = makeResponse({ q1: 'Alice', q2: 'alice@example.com', q3: 5 }, 1700000000000);
43 const row = responseToRow(resp, config);
44 expect(row[0]).toBe('Alice');
45 expect(row[1]).toBe('alice@example.com');
46 expect(row[2]).toBe(5);
47 expect(row[3]).toBe(new Date(1700000000000).toISOString());
48 });
49
50 it('handles missing answers as empty string', () => {
51 const resp = makeResponse({ q1: 'Bob' });
52 const row = responseToRow(resp, config);
53 expect(row[0]).toBe('Bob');
54 expect(row[1]).toBe('');
55 expect(row[2]).toBe('');
56 });
57
58 it('handles array answers (multiple choice)', () => {
59 const resp = makeResponse({ q1: ['opt-a', 'opt-b'] });
60 const row = responseToRow(resp, config);
61 expect(row[0]).toEqual(['opt-a', 'opt-b']);
62 });
63});
64
65describe('CSV escaping', () => {
66 it('escapes fields with commas', () => {
67 expect(escapeField('hello, world', ',')).toBe('"hello, world"');
68 });
69
70 it('escapes fields with double quotes', () => {
71 expect(escapeField('say "hi"', ',')).toBe('"say ""hi"""');
72 });
73
74 it('escapes fields with newlines', () => {
75 expect(escapeField('line1\nline2', ',')).toBe('"line1\nline2"');
76 });
77
78 it('returns empty string as empty', () => {
79 expect(escapeField('', ',')).toBe('');
80 });
81
82 it('does not escape plain values', () => {
83 expect(escapeField('hello', ',')).toBe('hello');
84 });
85
86 it('uses tab delimiter correctly', () => {
87 expect(escapeField('hello\tworld', '\t')).toBe('"hello\tworld"');
88 expect(escapeField('hello, world', '\t')).toBe('hello, world');
89 });
90});