const test = require('node:test'); const assert = require('node:assert/strict'); const path = require('node:path'); const createLoader = require('./load-module.cjs'); function setup(api = {}) { let definition; const patches = []; let hidden = 0; createLoader({ Page: value => definition = value, wx: { hideKeyboard: () => hidden++, showToast() {} }, console, Date, Promise }, { '../../utils/page-cache': { withDataCache: value => value }, '../../utils/layout': { getLayout: () => ({}) }, '../../utils/request': { api, getImageUrl: value => value } })(path.join(__dirname, '../pages/diary_detail/diary_detail.js')); const page = { ...definition, data: { ...definition.data, diaryId: 1, diary: { comment_count: 0 } }, setData(patch) { patches.push(patch); for (const [key, value] of Object.entries(patch)) { if (key === 'diary.comment_count') this.data.diary.comment_count = value; else this.data[key] = value; } } }; return { page, patches, hidden: () => hidden }; } test('composer follows keyboard without rewriting text, and caps long input height', () => { const { page, patches } = setup(); page.onCommentTap(); patches.length = 0; page.onCommentInput({ detail: { value: '字'.repeat(300) } }); page.onCommentKeyboardHeight({ detail: { height: 360 } }); page.onCommentLineChange({ detail: { height: 400 } }); assert.equal(page.data.commentKeyboardHeight, 360); assert.equal(page.data.commentEditorHeight, 160); assert.equal(page.data.commentText.length, 300); assert.ok(patches.every(p => !('commentInitialText' in p) && !('commentText' in p))); page.onCommentKeyboardHeight({ detail: { height: 0 } }); assert.equal(page.data.commentKeyboardHeight, 0); }); test('closing and reopening preserves draft while changing reply target resets it', () => { const { page } = setup(); page.onCommentTap(); page.onCommentInput({ detail: { value: '未发送' } }); page.cancelComment(); page.onCommentTap(); assert.equal(page.data.commentInitialText, '未发送'); page.onReplyTap({ currentTarget: { dataset: { item: { id: 2, user_id: 3, nickname: 'TA' } } } }); assert.equal(page.data.commentText, ''); assert.equal(page.data.replyTo.id, 2); page.onHide(); assert.equal(page.data.commentKeyboardHeight, 0); assert.equal(page.data.commentFocus, false); }); test('failed send preserves draft and allows retry; concurrent send is ignored', async () => { let release; let calls = 0; const { page } = setup({ diaryCommentAdd: () => { calls++; return new Promise((resolve, reject) => release = reject); } }); page.onCommentTap(); page.onCommentInput({ detail: { value: '保留这句话' } }); const pending = page.submitComment(); await page.submitComment(); assert.equal(calls, 1); release({ error: '网络错误' }); await pending; assert.equal(page.data.commentSending, false); assert.equal(page.data.commentText, '保留这句话'); assert.equal(page.data.showCommentInput, true); }); test('successful reply sends reply metadata and clears composer', async () => { let params; const { page, hidden } = setup({ diaryCommentAdd: async value => { params = value; return { success: true, comment: { id: 5 } }; } }); page.onReplyTap({ currentTarget: { dataset: { item: { id: 2, user_id: 3, nickname: 'TA' } } } }); page.onCommentInput({ detail: { value: '回复内容' } }); await page.submitComment(); assert.equal(params.parent_id, 2); assert.equal(params.reply_to_user_id, 3); assert.equal(page.data.commentText, ''); assert.equal(page.data.commentKeyboardHeight, 0); assert.equal(page.data.showCommentInput, false); assert.equal(page.data.diary.comment_count, 1); assert.equal(hidden(), 1); });