auto: PR for feat/export-zip-validation #22
@@ -55,7 +55,7 @@ jobs:
|
||||
done
|
||||
|
||||
if [ -n "${PR_NUMBER:-}" ]; then
|
||||
echo "Resolving existing label id for 'automerge' (no creation)"
|
||||
echo "Resolving existing label id for 'automerge' in $OWNER/$NAME (no creation)"
|
||||
curl -sS -o /tmp/labels.json -H "Authorization: token ${CI_TOKEN}" \
|
||||
"${GITEA_BASE_URL}/api/v1/repos/${OWNER}/${NAME}/labels?limit=1000" || true
|
||||
LID=$(tr '\n' ' ' < /tmp/labels.json | \
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import JSZip from 'jszip'
|
||||
import { exportPage } from '@/lib/exporter/html'
|
||||
import { buildZip } from '@/lib/exporter/zip'
|
||||
import { AssetManager } from '@/lib/assets/assetManager'
|
||||
import { pageSchema, type Page } from '@/lib/schema/page'
|
||||
|
||||
function makePageWithLocalAsset(): { page: Page; am: AssetManager } {
|
||||
const am = new AssetManager({ maxBytes: 1024 * 1024, allowedExts: ['png','svg','jpg','jpeg','webp'] })
|
||||
// simulate adding a local image and referencing it by original name via local: prefix
|
||||
const bytes = new Uint8Array([7,8,9])
|
||||
// add as "hero.png"
|
||||
am.add({ name: 'hero.png', type: 'image/png', bytes })
|
||||
|
||||
const base: Page = pageSchema.parse({
|
||||
title: 'Integrated ZIP',
|
||||
locale: 'en',
|
||||
theme: { primaryColor: '#2563eb', fontFamily: 'Inter' },
|
||||
sections: [
|
||||
{ type: 'hero', props: { heading: 'Hello', subheading: 'World', imageUrl: 'local:hero.png' } },
|
||||
{ type: 'cta', props: { text: 'Go', href: '#' } },
|
||||
],
|
||||
form: { actionUrl: '', fields: [], spamProtection: { honeypotFieldName: '_hp', minSubmitSeconds: 2 } },
|
||||
analytics: { ga4MeasurementId: 'G-TEST', metaPixelId: '', customHeadHtml: '' },
|
||||
seo: { title: 't', description: 'd' },
|
||||
})
|
||||
return { page: base, am }
|
||||
}
|
||||
|
||||
describe('Export ZIP 통합 검증', () => {
|
||||
it('exportPage 출력 + buildZip 결과에서 assets 경로/메타가 보존된다', async () => {
|
||||
const { page, am } = makePageWithLocalAsset()
|
||||
const out = exportPage(page, { assetManager: am })
|
||||
const assets = am.toZipStructure()
|
||||
const zipBytes = await buildZip({ html: out.html, css: out.css, js: out.js, assets })
|
||||
|
||||
const zip = await JSZip.loadAsync(zipBytes)
|
||||
const names = Object.keys(zip.files)
|
||||
// assets 파일이 존재해야 함
|
||||
const assetKey = Object.keys(assets)[0]
|
||||
expect(names).toContain(assetKey)
|
||||
|
||||
// index.html에 canonical/meta/ga4 스니펫 보존
|
||||
const html = await zip.files['index.html'].async('string')
|
||||
expect(html).toMatch(/<link rel="canonical" href="\//)
|
||||
expect(html).toMatch(/<meta name="description" content="d"/)
|
||||
expect(html).toMatch(/gtag\('config', 'G-TEST'\)/)
|
||||
|
||||
// hero 이미지가 재작성된 경로를 사용한다
|
||||
const match = html.match(/<img[^>]*src="([^"]+)"/)
|
||||
expect(match && match[1]).toBe(assetKey)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import JSZip from 'jszip'
|
||||
import { buildZip } from '@/lib/exporter/zip'
|
||||
|
||||
describe('Export ZIP 메타 보존 검증', () => {
|
||||
it('index.html의 SEO/OG/Analytics 메타가 그대로 보존된다', async () => {
|
||||
const html = `<!DOCTYPE html><html><head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="canonical" href="/" />
|
||||
<meta name="description" content="desc" />
|
||||
<meta property="og:title" content="OT" />
|
||||
<meta property="og:description" content="OD" />
|
||||
<script>window.dataLayer=[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());</script>
|
||||
</head><body>ok</body></html>`
|
||||
const css = 'body{color:#222}'
|
||||
const js = 'console.log("ok")'
|
||||
|
||||
const zipBytes = await buildZip({ html, css, js, assets: {} })
|
||||
const zip = await JSZip.loadAsync(zipBytes)
|
||||
const htmlContent = await zip.files['index.html'].async('string')
|
||||
|
||||
expect(htmlContent).toMatch(/<link rel="canonical" href="\//)
|
||||
expect(htmlContent).toMatch(/<meta name="description" content="desc"/)
|
||||
expect(htmlContent).toMatch(/<meta property="og:title" content="OT"/)
|
||||
expect(htmlContent).toMatch(/<meta property="og:description" content="OD"/)
|
||||
expect(htmlContent).toMatch(/window\.dataLayer=\[\];/)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,44 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import JSZip from 'jszip'
|
||||
import { buildZip } from '@/lib/exporter/zip'
|
||||
|
||||
const TE = new TextEncoder()
|
||||
|
||||
describe('Export ZIP 검증 강화', () => {
|
||||
it('assets가 비어있을 때 index.html/styles.css/app.js만 포함한다', async () => {
|
||||
const zipBytes = await buildZip({
|
||||
html: '<!DOCTYPE html><html><head><title>a</title></head><body>ok</body></html>',
|
||||
css: 'body{color:#111}',
|
||||
js: 'console.log("ok")',
|
||||
assets: {},
|
||||
})
|
||||
const zip = await JSZip.loadAsync(zipBytes)
|
||||
const names = Object.keys(zip.files).sort()
|
||||
expect(names).toEqual(['app.js','index.html','styles.css'])
|
||||
})
|
||||
|
||||
it('assets가 있으면 모든 파일을 그대로 포함한다(서브폴더 포함)', async () => {
|
||||
const assets = {
|
||||
'assets/img/logo.png': new Uint8Array([0,1,2,3]),
|
||||
'assets/svg/icon.svg': TE.encode('<svg></svg>'),
|
||||
'assets/fonts/inter.woff2': new Uint8Array([4,5,6]),
|
||||
}
|
||||
const zipBytes = await buildZip({
|
||||
html: '<!DOCTYPE html><html><head><title>a</title></head><body>ok</body></html>',
|
||||
css: 'body{color:#111}',
|
||||
js: 'console.log("ok")',
|
||||
assets,
|
||||
})
|
||||
const zip = await JSZip.loadAsync(zipBytes)
|
||||
const names = Object.keys(zip.files)
|
||||
expect(names).toContain('index.html')
|
||||
expect(names).toContain('styles.css')
|
||||
expect(names).toContain('app.js')
|
||||
expect(names).toContain('assets/img/logo.png')
|
||||
expect(names).toContain('assets/svg/icon.svg')
|
||||
expect(names).toContain('assets/fonts/inter.woff2')
|
||||
|
||||
const logo = await zip.files['assets/img/logo.png'].async('uint8array')
|
||||
expect(Array.from(logo)).toEqual([0,1,2,3])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user