/ clis / zhihu / comment.test.js
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  });