Skip to content

Commit 81af571

Browse files
committed
test(archive): add import/export round-trip and store tests
Add unit tests for archive utilities and store actions: - createArchive produces valid ZIP blob - extractArchive reads back model, template, and data - extractArchive throws on missing model or grammar files - Name extraction from description field - Full round-trip data preservation - exportTemplate creates archive and triggers download - importTemplate updates store state with extracted data - Error handling for both store actions Signed-off-by: Shubh-Raj <shubhraj625@gmail.com>
1 parent 5dd97c1 commit 81af571

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { vi, describe, it, expect, beforeEach } from 'vitest';
2+
import useAppStore from '../../store/store';
3+
4+
vi.mock('../../utils/archive/archive', () => ({
5+
createArchive: vi.fn(),
6+
extractArchive: vi.fn(),
7+
downloadBlob: vi.fn(),
8+
}));
9+
10+
import { createArchive, extractArchive, downloadBlob } from '../../utils/archive/archive';
11+
12+
describe('useAppStore - import/export', () => {
13+
beforeEach(() => {
14+
vi.clearAllMocks();
15+
});
16+
17+
describe('exportTemplate', () => {
18+
it('should create archive and trigger download', async () => {
19+
const mockBlob = new Blob(['test']);
20+
vi.mocked(createArchive).mockResolvedValue(mockBlob);
21+
22+
useAppStore.setState({
23+
sampleName: 'Test',
24+
modelCto: 'model',
25+
templateMarkdown: 'template',
26+
data: '{}',
27+
});
28+
29+
await useAppStore.getState().exportTemplate();
30+
31+
expect(createArchive).toHaveBeenCalledWith('Test', 'model', 'template', '{}');
32+
expect(downloadBlob).toHaveBeenCalledWith(mockBlob, 'test.cta');
33+
});
34+
35+
it('should set error on failure', async () => {
36+
vi.mocked(createArchive).mockRejectedValue(new Error('zip failed'));
37+
38+
await useAppStore.getState().exportTemplate();
39+
40+
const state = useAppStore.getState();
41+
expect(state.error).toContain('Failed to export template');
42+
expect(state.isProblemPanelVisible).toBe(true);
43+
});
44+
});
45+
46+
describe('importTemplate', () => {
47+
it('should extract archive and update store state', async () => {
48+
vi.mocked(extractArchive).mockResolvedValue({
49+
name: 'Imported',
50+
model: 'new model',
51+
template: 'new template',
52+
data: '{"key":"value"}',
53+
});
54+
55+
const file = new File(['fake'], 'test.cta');
56+
await useAppStore.getState().importTemplate(file);
57+
58+
const state = useAppStore.getState();
59+
expect(state.sampleName).toBe('Imported');
60+
expect(state.modelCto).toBe('new model');
61+
expect(state.templateMarkdown).toBe('new template');
62+
expect(state.data).toBe('{"key":"value"}');
63+
});
64+
65+
it('should set error on failure', async () => {
66+
vi.mocked(extractArchive).mockRejectedValue(new Error('invalid cta'));
67+
68+
const file = new File(['bad'], 'bad.cta');
69+
await expect(useAppStore.getState().importTemplate(file)).rejects.toThrow('invalid cta');
70+
71+
const state = useAppStore.getState();
72+
expect(state.error).toContain('Failed to import template');
73+
expect(state.isProblemPanelVisible).toBe(true);
74+
});
75+
});
76+
});

src/tests/utils/archive.test.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { createArchive, extractArchive } from '../../utils/archive/archive';
3+
4+
describe('Archive Utilities', () => {
5+
const mockData = {
6+
name: 'Test Template',
7+
model: 'namespace test@1.0.0\nconcept Data { o String value }',
8+
template: '# Hello {{value}}',
9+
data: JSON.stringify({ $class: 'test@1.0.0.Data', value: 'World' }),
10+
};
11+
12+
describe('createArchive', () => {
13+
it('should create a valid ZIP blob', async () => {
14+
const blob = await createArchive(
15+
mockData.name,
16+
mockData.model,
17+
mockData.template,
18+
mockData.data
19+
);
20+
expect(blob).toBeInstanceOf(Blob);
21+
expect(blob.size).toBeGreaterThan(0);
22+
});
23+
});
24+
25+
describe('extractArchive', () => {
26+
it('should extract template data from archive', async () => {
27+
const blob = await createArchive(
28+
mockData.name,
29+
mockData.model,
30+
mockData.template,
31+
mockData.data
32+
);
33+
const file = new File([blob], 'test.cta', { type: 'application/zip' });
34+
const extracted = await extractArchive(file);
35+
36+
expect(extracted.model).toBe(mockData.model);
37+
expect(extracted.template).toBe(mockData.template);
38+
expect(extracted.data).toBe(mockData.data);
39+
});
40+
41+
it('should extract human-readable name from description field', async () => {
42+
const blob = await createArchive(
43+
mockData.name,
44+
mockData.model,
45+
mockData.template,
46+
mockData.data
47+
);
48+
const file = new File([blob], 'test.cta');
49+
const extracted = await extractArchive(file);
50+
expect(extracted.name).toBe('Test Template');
51+
});
52+
});
53+
54+
describe('round-trip', () => {
55+
it('should preserve data through export/import cycle', async () => {
56+
const blob = await createArchive(
57+
mockData.name,
58+
mockData.model,
59+
mockData.template,
60+
mockData.data
61+
);
62+
const file = new File([blob], 'roundtrip.cta');
63+
const result = await extractArchive(file);
64+
65+
expect(result.name).toBe(mockData.name);
66+
expect(result.model).toBe(mockData.model);
67+
expect(result.template).toBe(mockData.template);
68+
expect(result.data).toBe(mockData.data);
69+
});
70+
});
71+
describe('error handling', () => {
72+
it('should throw when archive is missing model file', async () => {
73+
const JSZip = (await import('jszip')).default;
74+
const zip = new JSZip();
75+
zip.file('package.json', '{"name":"test"}');
76+
zip.file('text/grammar.tem.md', 'template');
77+
const blob = await zip.generateAsync({ type: 'blob' });
78+
const file = new File([blob], 'bad.cta');
79+
80+
await expect(extractArchive(file)).rejects.toThrow('Archive missing model .cto file');
81+
});
82+
83+
it('should throw when archive is missing grammar file', async () => {
84+
const JSZip = (await import('jszip')).default;
85+
const zip = new JSZip();
86+
zip.file('package.json', '{"name":"test"}');
87+
zip.file('model/model.cto', 'namespace test');
88+
const blob = await zip.generateAsync({ type: 'blob' });
89+
const file = new File([blob], 'bad.cta');
90+
91+
await expect(extractArchive(file)).rejects.toThrow('Archive missing text/grammar.tem.md');
92+
});
93+
});
94+
});

0 commit comments

Comments
 (0)