From 6b3480cd6df57d4131e60daf423fec0d273c7bed Mon Sep 17 00:00:00 2001 From: "heecheol.park" Date: Tue, 12 May 2026 21:36:17 +0900 Subject: [PATCH] fix(client): route createPage/createChildPage html through htmlToConfluenceStorage createComment and updatePage already normalize HTML input via htmlToConfluenceStorage, but createPage and createChildPage were passing HTML straight to storage. Confluence storage is strict XHTML, so inputs with
, , or unclosed tags would be rejected by the server. Bring both create paths in line with the rest of the API surface. The converter is idempotent for storage-quality XHTML (ac:* macros, ri:* references pass through unchanged), so existing callers feeding pre-formed storage via format='html' keep working. --- lib/confluence-client.js | 6 ++---- tests/confluence-client.test.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/confluence-client.js b/lib/confluence-client.js index aac9c83..1a0f60d 100644 --- a/lib/confluence-client.js +++ b/lib/confluence-client.js @@ -1260,8 +1260,7 @@ class ConfluenceClient { if (format === 'markdown') { storageContent = this.markdownToStorage(content); } else if (format === 'html') { - // Convert HTML directly to storage format (no macro wrapper) - storageContent = content; + storageContent = this.htmlToConfluenceStorage(content); } pageData.body = { @@ -1299,8 +1298,7 @@ class ConfluenceClient { if (format === 'markdown') { storageContent = this.markdownToStorage(content); } else if (format === 'html') { - // Convert HTML directly to storage format (no macro wrapper) - storageContent = content; + storageContent = this.htmlToConfluenceStorage(content); } pageData.body = { diff --git a/tests/confluence-client.test.js b/tests/confluence-client.test.js index 603b37d..640d157 100644 --- a/tests/confluence-client.test.js +++ b/tests/confluence-client.test.js @@ -1501,6 +1501,34 @@ describe('ConfluenceClient', () => { expect(requestData.body).toBeUndefined(); mock.restore(); }); + + test('createPage with format="html" routes through htmlToConfluenceStorage', async () => { + const mock = new MockAdapter(client.client); + mock.onPost('/content').reply(200, { id: '444' }); + const spy = jest.spyOn(client, 'htmlToConfluenceStorage'); + + await client.createPage('T', 'TEST', '

x

', 'html'); + + expect(spy).toHaveBeenCalledWith('

x

'); + const body = JSON.parse(mock.history.post[0].data).body.storage; + expect(body.representation).toBe('storage'); + expect(body.value).toBe('

x

'); + + spy.mockRestore(); + mock.restore(); + }); + + test('createChildPage with format="html" routes through htmlToConfluenceStorage', async () => { + const mock = new MockAdapter(client.client); + mock.onPost('/content').reply(200, { id: '555' }); + const spy = jest.spyOn(client, 'htmlToConfluenceStorage'); + + await client.createChildPage('T', 'TEST', '100', '

x

', 'html'); + + expect(spy).toHaveBeenCalledWith('

x

'); + spy.mockRestore(); + mock.restore(); + }); }); describe('deletePage', () => {