|
@@ -2,11 +2,12 @@ import assert from 'node:assert/strict'
|
|
|
import { describe, it } from 'node:test'
|
|
import { describe, it } from 'node:test'
|
|
|
import {
|
|
import {
|
|
|
buildContractDocxFileName,
|
|
buildContractDocxFileName,
|
|
|
- injectSxWatermarkHtml,
|
|
|
|
|
inlineRemoteImagesAsDataUrls,
|
|
inlineRemoteImagesAsDataUrls,
|
|
|
|
|
+ normalizePartyFlexLayoutForDocx,
|
|
|
parseContractSupplierCode,
|
|
parseContractSupplierCode,
|
|
|
prepareContractHtmlForDocx,
|
|
prepareContractHtmlForDocx,
|
|
|
stripContractLogoHtml,
|
|
stripContractLogoHtml,
|
|
|
|
|
+ stripWatermarkHtml,
|
|
|
toAbsoluteAssetUrls
|
|
toAbsoluteAssetUrls
|
|
|
} from './contract-html-to-docx'
|
|
} from './contract-html-to-docx'
|
|
|
|
|
|
|
@@ -34,6 +35,17 @@ describe('stripContractLogoHtml', () => {
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+describe('stripWatermarkHtml', () => {
|
|
|
|
|
+ it('removes watermark images', () => {
|
|
|
|
|
+ const html =
|
|
|
|
|
+ '<div style="text-align:center"><img src="/watermask.jpg" alt="水印" /></div><p>正文</p>'
|
|
|
|
|
+ const out = stripWatermarkHtml(html)
|
|
|
|
|
+ assert.equal(out.includes('watermask'), false)
|
|
|
|
|
+ assert.equal(out.includes('水印'), false)
|
|
|
|
|
+ assert.equal(out.includes('正文'), true)
|
|
|
|
|
+ })
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
describe('toAbsoluteAssetUrls', () => {
|
|
describe('toAbsoluteAssetUrls', () => {
|
|
|
it('prefixes root-relative src with origin', () => {
|
|
it('prefixes root-relative src with origin', () => {
|
|
|
const out = toAbsoluteAssetUrls(
|
|
const out = toAbsoluteAssetUrls(
|
|
@@ -45,30 +57,45 @@ describe('toAbsoluteAssetUrls', () => {
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-describe('injectSxWatermarkHtml', () => {
|
|
|
|
|
- it('prepends watermark image with absolute url', () => {
|
|
|
|
|
- const out = injectSxWatermarkHtml('<p>合同</p>', 'http://localhost:3006/watermask.jpg')
|
|
|
|
|
- assert.equal(out.includes('http://localhost:3006/watermask.jpg'), true)
|
|
|
|
|
- assert.equal(out.includes('alt="水印"'), true)
|
|
|
|
|
- assert.equal(out.includes('<p>合同</p>'), true)
|
|
|
|
|
|
|
+describe('normalizePartyFlexLayoutForDocx', () => {
|
|
|
|
|
+ it('converts bottom party flex columns into a two-cell table', () => {
|
|
|
|
|
+ const html = `
|
|
|
|
|
+ <div style="display: flex; justify-content: space-between; margin-top: 10px;">
|
|
|
|
|
+ <div style="flex: 1; border: 1px solid #000; padding: 8px;">
|
|
|
|
|
+ <div style="font-weight: bold;">供方</div>
|
|
|
|
|
+ <div>单位名称(章):甲公司</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div style="flex: 1; border: 1px solid #000; padding: 8px;">
|
|
|
|
|
+ <div style="font-weight: bold;">需方</div>
|
|
|
|
|
+ <div>单位名称(章):乙公司</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>`
|
|
|
|
|
+ const out = normalizePartyFlexLayoutForDocx(html)
|
|
|
|
|
+ assert.equal(out.includes('display: flex'), false)
|
|
|
|
|
+ assert.equal(out.includes('<table'), true)
|
|
|
|
|
+ assert.equal(out.includes('width:50%'), true)
|
|
|
|
|
+ assert.equal(out.includes('供方'), true)
|
|
|
|
|
+ assert.equal(out.includes('需方'), true)
|
|
|
|
|
+ assert.equal(out.includes('甲公司'), true)
|
|
|
|
|
+ assert.equal(out.includes('乙公司'), true)
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
describe('prepareContractHtmlForDocx', () => {
|
|
describe('prepareContractHtmlForDocx', () => {
|
|
|
- it('adds SX watermark and keeps logo', () => {
|
|
|
|
|
|
|
+ it('keeps logo and does not inject watermark for SX', () => {
|
|
|
const out = prepareContractHtmlForDocx({
|
|
const out = prepareContractHtmlForDocx({
|
|
|
html: '<div><img src="/logo.gif" /></div><p>正文</p>',
|
|
html: '<div><img src="/logo.gif" /></div><p>正文</p>',
|
|
|
contractNo: 'SX/X-JT20250926002',
|
|
contractNo: 'SX/X-JT20250926002',
|
|
|
origin: 'http://localhost:3006'
|
|
origin: 'http://localhost:3006'
|
|
|
})
|
|
})
|
|
|
- assert.equal(out.includes('watermask.jpg'), true)
|
|
|
|
|
|
|
+ assert.equal(out.includes('watermask'), false)
|
|
|
assert.equal(out.includes('logo.gif'), true)
|
|
assert.equal(out.includes('logo.gif'), true)
|
|
|
assert.equal(out.includes('http://localhost:3006/logo.gif'), true)
|
|
assert.equal(out.includes('http://localhost:3006/logo.gif'), true)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it('strips logo and skips watermark for non-SX', () => {
|
|
|
|
|
|
|
+ it('strips logo and watermark for non-SX', () => {
|
|
|
const out = prepareContractHtmlForDocx({
|
|
const out = prepareContractHtmlForDocx({
|
|
|
- html: '<div><img src="/logo.gif" /></div><p>正文</p>',
|
|
|
|
|
|
|
+ html: '<div><img src="/logo.gif" /></div><img src="/watermask.jpg" alt="水印" /><p>正文</p>',
|
|
|
contractNo: 'YD/X-JT20250926001',
|
|
contractNo: 'YD/X-JT20250926001',
|
|
|
origin: 'http://localhost:3006'
|
|
origin: 'http://localhost:3006'
|
|
|
})
|
|
})
|
|
@@ -76,6 +103,21 @@ describe('prepareContractHtmlForDocx', () => {
|
|
|
assert.equal(out.includes('logo.gif'), false)
|
|
assert.equal(out.includes('logo.gif'), false)
|
|
|
assert.equal(out.includes('正文'), true)
|
|
assert.equal(out.includes('正文'), true)
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
|
|
+ it('normalizes party flex layout during prepare', () => {
|
|
|
|
|
+ const html = `
|
|
|
|
|
+ <div style="display: flex; justify-content: space-between; margin-top: 10px;">
|
|
|
|
|
+ <div style="flex: 1;"><div>供方</div><div>A</div></div>
|
|
|
|
|
+ <div style="flex: 1;"><div>需方</div><div>B</div></div>
|
|
|
|
|
+ </div>`
|
|
|
|
|
+ const out = prepareContractHtmlForDocx({
|
|
|
|
|
+ html,
|
|
|
|
|
+ contractNo: 'SX/X-JT1',
|
|
|
|
|
+ origin: 'http://localhost:3006'
|
|
|
|
|
+ })
|
|
|
|
|
+ assert.equal(out.includes('<table'), true)
|
|
|
|
|
+ assert.equal(/display:\s*flex/.test(out), false)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
describe('buildContractDocxFileName', () => {
|
|
describe('buildContractDocxFileName', () => {
|