/** * Tests for SPARQL Results Export */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { exportToCsv, exportToJson, exportToJsonLd, exportAndDownload, } from '../../src/lib/sparql/export'; import type { SelectResults } from '../../src/lib/sparql/client'; describe('SPARQL Export', () => { const mockResults: SelectResults = { head: { vars: ['subject', 'predicate', 'object'] }, results: { bindings: [ { subject: { type: 'uri', value: 'http://example.org/s1' }, predicate: { type: 'uri', value: 'http://example.org/p1' }, object: { type: 'literal', value: 'Object 1' }, }, { subject: { type: 'uri', value: 'http://example.org/s2' }, predicate: { type: 'uri', value: 'http://example.org/p2' }, object: { type: 'literal', value: 'Object 2', 'xml:lang': 'en' }, }, { subject: { type: 'bnode', value: '_:b1' }, predicate: { type: 'uri', value: 'http://example.org/p3' }, object: { type: 'literal', value: '42', datatype: 'http://www.w3.org/2001/XMLSchema#integer', }, }, ], }, }; describe('exportToCsv', () => { it('should export results to CSV format', () => { const csv = exportToCsv(mockResults); expect(csv).toContain('subject,predicate,object'); expect(csv).toContain('http://example.org/s1'); expect(csv).toContain('Object 1'); }); it('should escape CSV values with commas', () => { const results: SelectResults = { head: { vars: ['text'] }, results: { bindings: [{ text: { type: 'literal', value: 'value, with, commas' } }], }, }; const csv = exportToCsv(results); expect(csv).toContain('"value, with, commas"'); }); it('should escape CSV values with quotes', () => { const results: SelectResults = { head: { vars: ['text'] }, results: { bindings: [{ text: { type: 'literal', value: 'value "with" quotes' } }], }, }; const csv = exportToCsv(results); expect(csv).toContain('"value ""with"" quotes"'); }); it('should handle empty results', () => { const results: SelectResults = { head: { vars: ['a', 'b'] }, results: { bindings: [] }, }; const csv = exportToCsv(results); expect(csv).toBe(''); }); it('should include language tags in CSV', () => { const csv = exportToCsv(mockResults); expect(csv).toContain('Object 2@en'); }); it('should include datatypes in CSV', () => { const csv = exportToCsv(mockResults); expect(csv).toContain('42^^http://www.w3.org/2001/XMLSchema#integer'); }); it('should handle missing values', () => { const results: SelectResults = { head: { vars: ['a', 'b', 'c'] }, results: { bindings: [ { a: { type: 'literal', value: 'A' } }, // b and c are missing ], }, }; const csv = exportToCsv(results); expect(csv).toContain('A,,'); }); }); describe('exportToJson', () => { it('should export results to JSON format', () => { const json = exportToJson(mockResults); const parsed = JSON.parse(json); expect(parsed).toHaveProperty('head'); expect(parsed).toHaveProperty('results'); expect(parsed.head.vars).toEqual(['subject', 'predicate', 'object']); expect(parsed.results.bindings).toHaveLength(3); }); it('should format JSON with indentation', () => { const json = exportToJson(mockResults); expect(json).toContain('\n '); }); }); describe('exportToJsonLd', () => { it('should export results to JSON-LD format', () => { const jsonld = exportToJsonLd(mockResults); const parsed = JSON.parse(jsonld); expect(parsed).toHaveProperty('@context'); expect(parsed).toHaveProperty('@graph'); expect(parsed['@graph']).toHaveLength(3); }); it('should convert URIs to @id', () => { const jsonld = exportToJsonLd(mockResults); const parsed = JSON.parse(jsonld); const firstResult = parsed['@graph'][0]; expect(firstResult.subject).toHaveProperty('@id'); expect(firstResult.subject['@id']).toBe('http://example.org/s1'); }); it('should convert literals to @value', () => { const jsonld = exportToJsonLd(mockResults); const parsed = JSON.parse(jsonld); const firstResult = parsed['@graph'][0]; expect(firstResult.object).toHaveProperty('@value'); expect(firstResult.object['@value']).toBe('Object 1'); }); it('should include language tags', () => { const jsonld = exportToJsonLd(mockResults); const parsed = JSON.parse(jsonld); const secondResult = parsed['@graph'][1]; expect(secondResult.object).toHaveProperty('@language'); expect(secondResult.object['@language']).toBe('en'); }); it('should include datatypes', () => { const jsonld = exportToJsonLd(mockResults); const parsed = JSON.parse(jsonld); const thirdResult = parsed['@graph'][2]; expect(thirdResult.object).toHaveProperty('@type'); expect(thirdResult.object['@type']).toBe( 'http://www.w3.org/2001/XMLSchema#integer' ); }); it('should assign blank node IDs to results', () => { const jsonld = exportToJsonLd(mockResults); const parsed = JSON.parse(jsonld); expect(parsed['@graph'][0]['@id']).toMatch(/_:result\d+/); expect(parsed['@graph'][0]['@type']).toBe('SparqlResult'); }); }); describe('exportAndDownload', () => { let createElementSpy: any; let linkElement: any; beforeEach(() => { linkElement = { href: '', download: '', click: vi.fn(), }; createElementSpy = vi.spyOn(document, 'createElement').mockReturnValue(linkElement); global.URL.createObjectURL = vi.fn(() => 'blob:mock-url'); global.URL.revokeObjectURL = vi.fn(); global.Blob = vi.fn() as any; }); afterEach(() => { vi.restoreAllMocks(); }); it('should download CSV file', () => { exportAndDownload(mockResults, 'csv', 'test-results'); expect(linkElement.download).toBe('test-results.csv'); expect(linkElement.click).toHaveBeenCalled(); expect(global.URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock-url'); }); it('should download JSON file', () => { exportAndDownload(mockResults, 'json', 'test-results'); expect(linkElement.download).toBe('test-results.json'); expect(linkElement.click).toHaveBeenCalled(); }); it('should download JSON-LD file', () => { exportAndDownload(mockResults, 'jsonld', 'test-results'); expect(linkElement.download).toBe('test-results.jsonld'); expect(linkElement.click).toHaveBeenCalled(); }); it('should use default filename if not provided', () => { exportAndDownload(mockResults, 'csv'); expect(linkElement.download).toBe('sparql-results.csv'); }); }); });