comment.test.js
1 import { describe, expect, it, vi } from 'vitest'; 2 import { getRegistry } from '@jackwener/opencli/registry'; 3 import './comment.js'; 4 describe('zhihu comment', () => { 5 it('rejects composer paths that are not proven side-effect free', async () => { 6 const cmd = getRegistry().get('zhihu/comment'); 7 expect(cmd?.func).toBeTypeOf('function'); 8 const page = { 9 goto: vi.fn().mockResolvedValue(undefined), 10 evaluate: vi.fn() 11 .mockResolvedValueOnce({ slug: 'alice' }) 12 .mockResolvedValueOnce({ entryPathSafe: false }) 13 .mockResolvedValueOnce({ wrongAnswer: false, rows: [], commentLinks: [] }), 14 }; 15 await expect(cmd.func(page, { target: 'answer:1:2', text: 'hello', execute: true })).rejects.toMatchObject({ code: 'ACTION_NOT_AVAILABLE' }); 16 }); 17 it('requires exact editor replacement before accepting fallback proof', async () => { 18 const cmd = getRegistry().get('zhihu/comment'); 19 const page = { 20 goto: vi.fn().mockResolvedValue(undefined), 21 evaluate: vi.fn() 22 .mockResolvedValueOnce({ slug: 'alice' }) 23 .mockResolvedValueOnce({ entryPathSafe: true }) 24 .mockResolvedValueOnce({ wrongAnswer: false, rows: [], commentLinks: [] }) 25 .mockResolvedValueOnce({ composerState: 'fresh_top_level' }) 26 .mockResolvedValueOnce({ editorContent: 'hello', mode: 'top_level' }) 27 .mockResolvedValueOnce({ 28 proofType: 'fallback', 29 createdProof: { 30 proof_type: 'comment_fallback', 31 author_scope: 'current_user', 32 target_scope: 'requested_target', 33 comment_scope: 'top_level_only', 34 content_match: 'exact_normalized', 35 observed_after_submit: true, 36 present_in_pre_submit_snapshot: false, 37 new_matching_entries: 1, 38 post_submit_matching_entries: 1, 39 snapshot_scope: 'stabilized_expanded_target_comment_list', 40 }, 41 }), 42 }; 43 await expect(cmd.func(page, { target: 'answer:1:2', text: 'hello', execute: true })).resolves.toEqual([ 44 expect.objectContaining({ outcome: 'created', author_identity: 'alice', created_proof: expect.any(Object) }), 45 ]); 46 expect(page.evaluate.mock.calls[1][0]).toContain('topLevelCandidates.length === 1'); 47 expect(page.evaluate.mock.calls[1][0]).not.toContain('commentTrigger'); 48 expect(page.evaluate.mock.calls[2][0]).toContain("node.getAttribute('data-answerid')"); 49 expect(page.evaluate.mock.calls[2][0]).toContain("node.getAttribute('data-zop-question-answer')"); 50 expect(page.evaluate.mock.calls[5][0]).toContain('const readCommentAuthorSlug = (node) =>'); 51 expect(page.evaluate.mock.calls[5][0]).toContain('const commentAuthorScopeSelector = ".CommentItemV2-head, .CommentItem-head, .CommentItemV2-meta, .CommentItem-meta, .CommentItemV2-metaSibling, [data-comment-author], [itemprop=\\"author\\"]"'); 52 expect(page.evaluate.mock.calls[5][0]).not.toContain("card?.querySelector('a[href^=\"/people/\"]')"); 53 }); 54 });