import { describe, it, expect } from 'vitest' import JSZip from 'jszip' import { buildZip } from '@/lib/exporter/zip' import type { BuildZipInput } from '@/lib/exporter/zip' const TE = new TextEncoder() describe('Export ZIP 검증 강화 4차: 루트 파일(favicon/manifest/robots)', () => { it('루트 파일들을 포함한다: favicon.ico / site.webmanifest / robots.txt', async () => { const manifest = { name: 'Landing Builder', short_name: 'LB', start_url: '/', display: 'standalone', icons: [{ src: '/favicon.png', sizes: '512x512', type: 'image/png' }], } const input: BuildZipInput = { html: 'aok', css: 'body{color:#111}', js: 'console.log("ok")', assets: {}, extras: { 'favicon.ico': new Uint8Array([0, 1, 2, 3]), 'site.webmanifest': TE.encode(JSON.stringify(manifest)), 'robots.txt': TE.encode('User-agent: *\nDisallow:'), }, } const zipBytes = await buildZip(input) const zip = await JSZip.loadAsync(zipBytes) const names = Object.keys(zip.files) expect(names).toContain('favicon.ico') expect(names).toContain('site.webmanifest') expect(names).toContain('robots.txt') const robots = await zip.files['robots.txt'].async('string') expect(robots).toMatch(/User-agent: \*/) const parsed = JSON.parse(await zip.files['site.webmanifest'].async('string')) expect(parsed.name).toBe('Landing Builder') expect(parsed.icons[0].type).toBe('image/png') }) it('잘못된 manifest JSON이면 실패한다', async () => { const badInput: BuildZipInput = { html: 'aok', css: 'body{color:#111}', js: 'console.log("ok")', extras: { 'site.webmanifest': TE.encode('{ invalid json') }, } await expect(buildZip(badInput)).rejects.toThrow() }) })