@sovovs/bycli 1.8.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli-manifest.json +0 -61
- package/clis/twitter/search.js +3 -3
- package/dist/src/adapter-shadow.js +2 -2
- package/dist/src/browser/analyze.d.ts +10 -99
- package/dist/src/browser/analyze.js +10 -226
- package/dist/src/browser/cdp.js +67 -7
- package/dist/src/browser/daemon-client.d.ts +2 -0
- package/dist/src/browser/daemon-client.js +1 -1
- package/dist/src/browser/extension-capabilities.d.ts +13 -0
- package/dist/src/browser/extension-capabilities.js +22 -0
- package/dist/src/browser/extension-capabilities.test.d.ts +1 -0
- package/dist/src/browser/extension-version-metadata.test.d.ts +1 -0
- package/dist/src/browser/navigation-guard.d.ts +78 -0
- package/dist/src/browser/navigation-guard.js +142 -0
- package/dist/src/browser/navigation-guard.test.d.ts +1 -0
- package/dist/src/browser/page.d.ts +1 -0
- package/dist/src/browser/page.js +20 -1
- package/dist/src/browser/url-policy.d.ts +89 -0
- package/dist/src/browser/url-policy.js +311 -0
- package/dist/src/browser/url-policy.test.d.ts +1 -0
- package/dist/src/browser/verify-fixture.js +2 -2
- package/dist/src/build-manifest.js +4 -2
- package/dist/src/capabilityRouting.d.ts +3 -2
- package/dist/src/capabilityRouting.js +10 -2
- package/dist/src/cli.js +11 -14
- package/dist/src/commanderAdapter.js +5 -5
- package/dist/src/config-paths.d.ts +19 -0
- package/dist/src/config-paths.js +31 -0
- package/dist/src/daemon.js +256 -0
- package/dist/src/discovery.d.ts +10 -3
- package/dist/src/discovery.js +20 -10
- package/dist/src/discovery.test.d.ts +1 -0
- package/dist/src/execution.d.ts +5 -0
- package/dist/src/execution.js +272 -48
- package/dist/src/help.js +8 -8
- package/dist/src/main.js +18 -2
- package/dist/src/manifest-schema.d.ts +9 -0
- package/dist/src/manifest-schema.js +162 -0
- package/dist/src/manifest-schema.test.d.ts +1 -0
- package/dist/src/manifest-types.d.ts +1 -1
- package/dist/src/observation/redaction.js +10 -4
- package/dist/src/plugin.js +11 -8
- package/dist/src/recorder/highlevel/analyze.d.ts +29 -0
- package/dist/src/recorder/highlevel/analyze.js +91 -0
- package/dist/src/recorder/highlevel/init.d.ts +125 -0
- package/dist/src/recorder/highlevel/init.js +279 -0
- package/dist/src/recorder/highlevel/init.test.d.ts +1 -0
- package/dist/src/recorder/highlevel/verify.d.ts +73 -0
- package/dist/src/recorder/highlevel/verify.js +74 -0
- package/dist/src/recorder/http/analyze-runner.d.ts +28 -0
- package/dist/src/recorder/http/analyze-runner.js +78 -0
- package/dist/src/recorder/http/analyze-runner.test.d.ts +1 -0
- package/dist/src/recorder/http/handlers.d.ts +51 -0
- package/dist/src/recorder/http/handlers.js +219 -0
- package/dist/src/recorder/http/wrapper-config.d.ts +20 -0
- package/dist/src/recorder/http/wrapper-config.js +82 -0
- package/dist/src/recorder/http/wrapper-envelope.d.ts +31 -0
- package/dist/src/recorder/http/wrapper-envelope.js +51 -0
- package/dist/src/recorder/http/wrapper-gates.d.ts +16 -0
- package/dist/src/recorder/http/wrapper-gates.js +28 -0
- package/dist/src/recorder/http/wrapper-metrics.d.ts +1 -0
- package/dist/src/recorder/http/wrapper-metrics.js +5 -0
- package/dist/src/recorder/http/wrapper-registry.d.ts +50 -0
- package/dist/src/recorder/http/wrapper-registry.js +98 -0
- package/dist/src/recorder/http/wrapper-server.d.ts +59 -0
- package/dist/src/recorder/http/wrapper-server.js +240 -0
- package/dist/src/recorder/http/wrapper.test.d.ts +1 -0
- package/dist/src/recorder/observability/logger.d.ts +5 -0
- package/dist/src/recorder/observability/logger.js +13 -0
- package/dist/src/recorder/runner/config.d.ts +34 -0
- package/dist/src/recorder/runner/config.js +84 -0
- package/dist/src/recorder/runner/config.test.d.ts +1 -0
- package/dist/src/recorder/runner/reap.d.ts +79 -0
- package/dist/src/recorder/runner/reap.js +230 -0
- package/dist/src/recorder/runner/reap.test.d.ts +1 -0
- package/dist/src/recorder/runner/runner-port.d.ts +95 -0
- package/dist/src/recorder/runner/runner-port.js +518 -0
- package/dist/src/recorder/runner/runner-port.test.d.ts +1 -0
- package/dist/src/recorder/runner/session-keys.d.ts +28 -0
- package/dist/src/recorder/runner/session-keys.js +72 -0
- package/dist/src/recorder/runner/session-keys.test.d.ts +1 -0
- package/dist/src/recorder/runner/verify-runner-main.d.ts +96 -0
- package/dist/src/recorder/runner/verify-runner-main.js +292 -0
- package/dist/src/recorder/runner/verify-runner-main.test.d.ts +1 -0
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry-api.types.test.d.ts +1 -0
- package/dist/src/registry-transaction.d.ts +42 -0
- package/dist/src/registry-transaction.js +194 -0
- package/dist/src/registry-transaction.test.d.ts +1 -0
- package/dist/src/registry.d.ts +58 -16
- package/dist/src/registry.js +131 -15
- package/dist/src/serialization.d.ts +1 -1
- package/dist/src/serialization.js +3 -3
- package/dist/src/types.d.ts +2 -0
- package/package.json +8 -5
- package/scripts/m1-daemon-navigate-drive.mjs +138 -0
- package/scripts/m1-extension-spike.mjs +237 -0
- package/scripts/m1-live-spike.mjs +156 -0
- package/scripts/m6b-connect-back-spike.sh +31 -0
- package/clis/12306/utils.test.js +0 -331
- package/clis/1688/assets.test.js +0 -39
- package/clis/1688/download.test.js +0 -31
- package/clis/1688/item.test.js +0 -67
- package/clis/1688/search.test.js +0 -75
- package/clis/1688/shared.test.js +0 -57
- package/clis/1688/store.test.js +0 -62
- package/clis/36kr/article.test.js +0 -46
- package/clis/36kr/hot.test.js +0 -15
- package/clis/36kr/news.test.js +0 -85
- package/clis/51job/utils.test.js +0 -69
- package/clis/_atlassian/shared.test.js +0 -170
- package/clis/aibase/news.test.js +0 -59
- package/clis/amazon/bestsellers.test.js +0 -29
- package/clis/amazon/discussion.test.js +0 -124
- package/clis/amazon/offer.test.js +0 -29
- package/clis/amazon/product.test.js +0 -24
- package/clis/amazon/rankings.test.js +0 -41
- package/clis/amazon/search.test.js +0 -22
- package/clis/amazon/shared.test.js +0 -44
- package/clis/apple-podcasts/commands.test.js +0 -119
- package/clis/apple-podcasts/utils.test.js +0 -57
- package/clis/arxiv/arxiv.test.js +0 -112
- package/clis/baidu-scholar/search.test.js +0 -23
- package/clis/barchart/greeks.test.js +0 -138
- package/clis/bilibili/comment.test.js +0 -153
- package/clis/bilibili/comments.test.js +0 -142
- package/clis/bilibili/dynamic.test.js +0 -68
- package/clis/bilibili/hot.test.js +0 -17
- package/clis/bilibili/subtitle.test.js +0 -196
- package/clis/bilibili/summary.test.js +0 -210
- package/clis/bilibili/utils.test.js +0 -61
- package/clis/bilibili/video.test.js +0 -132
- package/clis/binance/commands.test.js +0 -70
- package/clis/bloomberg/utils.test.js +0 -129
- package/clis/booking/booking.test.js +0 -356
- package/clis/boss/chatlist.test.js +0 -211
- package/clis/boss/chatmsg.test.js +0 -230
- package/clis/boss/search.test.js +0 -78
- package/clis/boss/utils.test.js +0 -34
- package/clis/brave/search.test.js +0 -76
- package/clis/chaoxing/utils.test.js +0 -45
- package/clis/chatgpt/commands.test.js +0 -45
- package/clis/chatgpt/envelope.test.js +0 -108
- package/clis/chatgpt/image.test.js +0 -188
- package/clis/chatgpt/utils.test.js +0 -330
- package/clis/chatgpt-app/ax.test.js +0 -35
- package/clis/chatwise/composer.test.js +0 -186
- package/clis/chess/analyze.test.js +0 -79
- package/clis/chess/game.test.js +0 -178
- package/clis/chess/games.test.js +0 -164
- package/clis/chess/stats.test.js +0 -79
- package/clis/chess/utils.test.js +0 -230
- package/clis/claude/ask.test.js +0 -338
- package/clis/claude/commands.test.js +0 -118
- package/clis/claude/utils.test.js +0 -148
- package/clis/cnki/search.test.js +0 -18
- package/clis/codex/sidebar.test.js +0 -329
- package/clis/coingecko/coingecko.test.js +0 -109
- package/clis/confluence/commands.test.js +0 -195
- package/clis/coupang/coupang.test.js +0 -159
- package/clis/coupang/utils.test.js +0 -62
- package/clis/ctrip/ctrip.test.js +0 -719
- package/clis/dblp/dblp.test.js +0 -397
- package/clis/deepseek/ask.test.js +0 -312
- package/clis/deepseek/detail.test.js +0 -81
- package/clis/deepseek/send.test.js +0 -107
- package/clis/deepseek/utils.test.js +0 -370
- package/clis/defillama/defillama.test.js +0 -99
- package/clis/devto/devto.test.js +0 -236
- package/clis/dianping/dianping.test.js +0 -578
- package/clis/douban/download.test.js +0 -170
- package/clis/douban/movie-hot.test.js +0 -14
- package/clis/douban/search.test.js +0 -11
- package/clis/douban/subject.test.js +0 -11
- package/clis/douban/utils.test.js +0 -438
- package/clis/doubao/detail.test.js +0 -42
- package/clis/doubao/history.test.js +0 -37
- package/clis/doubao/utils.test.js +0 -344
- package/clis/douyin/_shared/browser-fetch.test.js +0 -64
- package/clis/douyin/_shared/creation-id.test.js +0 -22
- package/clis/douyin/_shared/imagex-upload.test.js +0 -87
- package/clis/douyin/_shared/sts2.test.js +0 -27
- package/clis/douyin/_shared/text-extra.test.js +0 -37
- package/clis/douyin/_shared/timing.test.js +0 -28
- package/clis/douyin/_shared/tos-upload-short-read.test.js +0 -83
- package/clis/douyin/_shared/tos-upload.test.js +0 -229
- package/clis/douyin/_shared/transcode.test.js +0 -91
- package/clis/douyin/_shared/vod-upload.test.js +0 -38
- package/clis/douyin/activities.test.js +0 -55
- package/clis/douyin/collections.test.js +0 -45
- package/clis/douyin/delete.test.js +0 -100
- package/clis/douyin/draft.test.js +0 -335
- package/clis/douyin/drafts.test.js +0 -11
- package/clis/douyin/hashtag.test.js +0 -171
- package/clis/douyin/location.test.js +0 -23
- package/clis/douyin/profile.test.js +0 -46
- package/clis/douyin/publish-upload-id.test.js +0 -170
- package/clis/douyin/publish.test.js +0 -38
- package/clis/douyin/stats.test.js +0 -22
- package/clis/douyin/update.test.js +0 -11
- package/clis/douyin/user-videos.test.js +0 -151
- package/clis/douyin/videos.test.js +0 -54
- package/clis/duckduckgo/search.test.js +0 -128
- package/clis/duckduckgo/suggest.test.js +0 -66
- package/clis/eastmoney/hot-rank.test.js +0 -59
- package/clis/endoflife/endoflife.test.js +0 -51
- package/clis/facebook/feed.test.js +0 -169
- package/clis/facebook/marketplace.test.js +0 -91
- package/clis/facebook/notifications.test.js +0 -458
- package/clis/facebook/search.test.js +0 -55
- package/clis/flathub/flathub.test.js +0 -90
- package/clis/flomo/memos.test.js +0 -144
- package/clis/gemini/ask.test.js +0 -100
- package/clis/gemini/deep-research-result.test.js +0 -155
- package/clis/gemini/deep-research.test.js +0 -181
- package/clis/gemini/reply-state.test.js +0 -671
- package/clis/gemini/utils.test.js +0 -198
- package/clis/geogebra/geogebra.test.js +0 -175
- package/clis/gitee/search.test.js +0 -65
- package/clis/gitee/user.test.js +0 -63
- package/clis/google/utils.test.js +0 -75
- package/clis/google-scholar/cite.test.js +0 -47
- package/clis/google-scholar/profile.test.js +0 -49
- package/clis/google-scholar/search.test.js +0 -71
- package/clis/goproxy/goproxy.test.js +0 -103
- package/clis/gov-law/commands.test.js +0 -39
- package/clis/gov-policy/gov-policy.test.js +0 -224
- package/clis/grok/ask.test.js +0 -29
- package/clis/grok/utils.test.js +0 -103
- package/clis/hackernews/hackernews.test.js +0 -132
- package/clis/hf/hf.test.js +0 -16
- package/clis/hupu/hot.test.js +0 -224
- package/clis/imdb/utils.test.js +0 -88
- package/clis/indeed/indeed.test.js +0 -375
- package/clis/instagram/_shared/private-publish.test.js +0 -705
- package/clis/instagram/_shared/protocol-capture.test.js +0 -114
- package/clis/instagram/download.test.js +0 -118
- package/clis/instagram/note.test.js +0 -53
- package/clis/instagram/post.test.js +0 -1647
- package/clis/instagram/reel.test.js +0 -167
- package/clis/instagram/story.test.js +0 -134
- package/clis/jd/commands.test.js +0 -41
- package/clis/jd/item.test.js +0 -354
- package/clis/jianyu/search.test.js +0 -153
- package/clis/jianyu/shared/procurement-contract.test.js +0 -72
- package/clis/jianyu/shared/procurement-detail.test.js +0 -84
- package/clis/jike/read.test.js +0 -86
- package/clis/jira/commands.test.js +0 -287
- package/clis/juejin/search.js +0 -123
- package/clis/juejin/search.test.js +0 -98
- package/clis/lesswrong/frontpage.test.js +0 -37
- package/clis/lichess/lichess.test.js +0 -85
- package/clis/linkedin/connect.test.js +0 -213
- package/clis/linkedin/inbox.test.js +0 -152
- package/clis/linkedin/job-detail.test.js +0 -38
- package/clis/linkedin/jobs-preferences.test.js +0 -43
- package/clis/linkedin/people-search.test.js +0 -216
- package/clis/linkedin/post-analytics.test.js +0 -40
- package/clis/linkedin/posts.test.js +0 -40
- package/clis/linkedin/profile-analytics.test.js +0 -67
- package/clis/linkedin/profile-experience.test.js +0 -152
- package/clis/linkedin/profile-projects.test.js +0 -111
- package/clis/linkedin/profile-read.test.js +0 -77
- package/clis/linkedin/safe-send.test.js +0 -204
- package/clis/linkedin/salesnav-inbox.test.js +0 -113
- package/clis/linkedin/salesnav-message.test.js +0 -172
- package/clis/linkedin/salesnav-search.test.js +0 -76
- package/clis/linkedin/salesnav-thread.test.js +0 -79
- package/clis/linkedin/search.test.js +0 -222
- package/clis/linkedin/sent-invitations.test.js +0 -62
- package/clis/linkedin/services-read.test.js +0 -105
- package/clis/linkedin/thread-snapshot.test.js +0 -89
- package/clis/linkedin/timeline.test.js +0 -81
- package/clis/linkedin-learning/course.test.js +0 -114
- package/clis/linkedin-learning/search.test.js +0 -144
- package/clis/linkedin-learning/trending.test.js +0 -123
- package/clis/linux-do/feed.test.js +0 -153
- package/clis/linux-do/topic-content.test.js +0 -59
- package/clis/lobsters/lobsters.test.js +0 -169
- package/clis/notebooklm/add-source.test.js +0 -97
- package/clis/notebooklm/compat.test.js +0 -16
- package/clis/notebooklm/create.test.js +0 -58
- package/clis/notebooklm/generate-audio.test.js +0 -63
- package/clis/notebooklm/generate-slides.test.js +0 -75
- package/clis/notebooklm/history.test.js +0 -58
- package/clis/notebooklm/note-list.test.js +0 -56
- package/clis/notebooklm/notes-get.test.js +0 -72
- package/clis/notebooklm/open.test.js +0 -63
- package/clis/notebooklm/rpc.test.js +0 -131
- package/clis/notebooklm/source-fulltext.test.js +0 -106
- package/clis/notebooklm/source-get.test.js +0 -84
- package/clis/notebooklm/source-guide.test.js +0 -104
- package/clis/notebooklm/summary.test.js +0 -78
- package/clis/notebooklm/utils.test.js +0 -449
- package/clis/notebooklm/write-note.test.js +0 -70
- package/clis/nuget/nuget.test.js +0 -111
- package/clis/oeis/oeis.test.js +0 -88
- package/clis/ones/task-helpers.test.js +0 -12
- package/clis/ones/worklog.test.js +0 -20
- package/clis/openfda/openfda.test.js +0 -114
- package/clis/openreview/openreview.test.js +0 -427
- package/clis/osv/osv.test.js +0 -97
- package/clis/paperreview/commands.test.js +0 -243
- package/clis/paperreview/utils.test.js +0 -49
- package/clis/pixiv/download.test.js +0 -87
- package/clis/pixiv/illusts.test.js +0 -99
- package/clis/pixiv/search.test.js +0 -83
- package/clis/powerchina/search.test.js +0 -93
- package/clis/producthunt/utils.test.js +0 -64
- package/clis/pubmed/pubmed.test.js +0 -276
- package/clis/quark/utils.test.js +0 -58
- package/clis/qwen/utils.test.js +0 -45
- package/clis/reddit/extract-media.test.js +0 -149
- package/clis/reddit/frontpage.test.js +0 -34
- package/clis/reddit/home.test.js +0 -170
- package/clis/reddit/hot.test.js +0 -32
- package/clis/reddit/popular.test.js +0 -26
- package/clis/reddit/read.test.js +0 -335
- package/clis/reddit/reply.test.js +0 -89
- package/clis/reddit/search.test.js +0 -26
- package/clis/reddit/subreddit-info.test.js +0 -163
- package/clis/reddit/subreddit.test.js +0 -31
- package/clis/reddit/subscribed.test.js +0 -168
- package/clis/reddit/whoami.test.js +0 -105
- package/clis/rednote/navigation.test.js +0 -26
- package/clis/rednote/rednote.test.js +0 -222
- package/clis/rest-countries/rest-countries.test.js +0 -83
- package/clis/reuters/reuters.test.js +0 -299
- package/clis/rfc/rfc.test.js +0 -74
- package/clis/sinafinance/stock.test.js +0 -59
- package/clis/spotify/utils.test.js +0 -67
- package/clis/stackoverflow/stackoverflow.test.js +0 -404
- package/clis/steam/steam.test.js +0 -46
- package/clis/substack/utils.test.js +0 -44
- package/clis/suno/commands.test.js +0 -188
- package/clis/suno/download.test.js +0 -151
- package/clis/suno/generate.test.js +0 -252
- package/clis/suno/utils.test.js +0 -329
- package/clis/taobao/commands.test.js +0 -41
- package/clis/tdx/hot-rank.test.js +0 -59
- package/clis/ths/hot-rank.test.js +0 -64
- package/clis/tieba/commands.test.js +0 -79
- package/clis/tieba/utils.test.js +0 -290
- package/clis/tiktok/creator-videos.test.js +0 -113
- package/clis/tiktok/refactor.test.js +0 -389
- package/clis/tiktok/write-refactor.test.js +0 -370
- package/clis/toutiao/toutiao.test.js +0 -378
- package/clis/tvmaze/tvmaze.test.js +0 -93
- package/clis/twitter/bookmark-folder.test.js +0 -395
- package/clis/twitter/bookmark-folders.test.js +0 -152
- package/clis/twitter/bookmark.test.js +0 -74
- package/clis/twitter/bookmarks.test.js +0 -205
- package/clis/twitter/delete.test.js +0 -85
- package/clis/twitter/device-follow.test.js +0 -287
- package/clis/twitter/download.test.js +0 -457
- package/clis/twitter/followers.test.js +0 -62
- package/clis/twitter/following.test.js +0 -338
- package/clis/twitter/hide-reply.test.js +0 -76
- package/clis/twitter/like.test.js +0 -73
- package/clis/twitter/likes.test.js +0 -220
- package/clis/twitter/list-add.test.js +0 -133
- package/clis/twitter/list-create.test.js +0 -169
- package/clis/twitter/list-remove.test.js +0 -110
- package/clis/twitter/list-tweets.test.js +0 -196
- package/clis/twitter/lists.test.js +0 -253
- package/clis/twitter/post.test.js +0 -258
- package/clis/twitter/profile.test.js +0 -39
- package/clis/twitter/quote.test.js +0 -194
- package/clis/twitter/reply.test.js +0 -237
- package/clis/twitter/retweet.test.js +0 -73
- package/clis/twitter/search.test.js +0 -349
- package/clis/twitter/shared.test.js +0 -794
- package/clis/twitter/thread.test.js +0 -58
- package/clis/twitter/timeline.test.js +0 -104
- package/clis/twitter/trending.test.js +0 -15
- package/clis/twitter/tweets.test.js +0 -362
- package/clis/twitter/unbookmark.test.js +0 -73
- package/clis/twitter/unlike.test.js +0 -75
- package/clis/twitter/unretweet.test.js +0 -73
- package/clis/twitter/utils.test.js +0 -169
- package/clis/uisdc/news.test.js +0 -66
- package/clis/uiverse/_shared.test.js +0 -62
- package/clis/uiverse/navigation.test.js +0 -12
- package/clis/upwork/upwork.test.js +0 -566
- package/clis/wanfang/search.test.js +0 -23
- package/clis/web/read.test.js +0 -392
- package/clis/weibo/delete.test.js +0 -94
- package/clis/weibo/envelope.test.js +0 -85
- package/clis/weibo/favorites.test.js +0 -114
- package/clis/weibo/publish.test.js +0 -192
- package/clis/weibo/user-posts.test.js +0 -92
- package/clis/weibo/utils.test.js +0 -36
- package/clis/weixin/drafts.test.js +0 -69
- package/clis/weixin/search.test.js +0 -227
- package/clis/weread/ai-outline.test.js +0 -83
- package/clis/weread/book-search.test.js +0 -242
- package/clis/weread/commands.test.js +0 -398
- package/clis/weread/private-api-regression.test.js +0 -369
- package/clis/weread/search-regression.test.js +0 -494
- package/clis/weread/utils.test.js +0 -128
- package/clis/weread-official/commands.test.js +0 -385
- package/clis/weread-official/utils.test.js +0 -242
- package/clis/wikidata/wikidata.test.js +0 -83
- package/clis/wikipedia/trending.test.js +0 -57
- package/clis/wttr/wttr.test.js +0 -84
- package/clis/xianyu/chat.test.js +0 -84
- package/clis/xianyu/im.test.js +0 -253
- package/clis/xianyu/item.test.js +0 -56
- package/clis/xianyu/publish.test.js +0 -220
- package/clis/xianyu/search.test.js +0 -17
- package/clis/xiaoe/xiaoe.test.js +0 -486
- package/clis/xiaohongshu/comments.test.js +0 -258
- package/clis/xiaohongshu/creator-note-detail.test.js +0 -451
- package/clis/xiaohongshu/creator-notes-summary.test.js +0 -94
- package/clis/xiaohongshu/creator-notes.test.js +0 -281
- package/clis/xiaohongshu/creator-stats.test.js +0 -24
- package/clis/xiaohongshu/delete-note.test.js +0 -172
- package/clis/xiaohongshu/download.test.js +0 -316
- package/clis/xiaohongshu/navigation.test.js +0 -34
- package/clis/xiaohongshu/note.test.js +0 -249
- package/clis/xiaohongshu/publish.test.js +0 -608
- package/clis/xiaohongshu/search.test.js +0 -325
- package/clis/xiaohongshu/user-helpers.test.js +0 -160
- package/clis/xiaoyuzhou/auth.test.js +0 -124
- package/clis/xiaoyuzhou/download.test.js +0 -135
- package/clis/xiaoyuzhou/podcast-episodes.test.js +0 -78
- package/clis/xiaoyuzhou/transcript.test.js +0 -195
- package/clis/xiaoyuzhou/utils.test.js +0 -39
- package/clis/xueqiu/comments.test.js +0 -696
- package/clis/xueqiu/danjuan-utils.test.js +0 -41
- package/clis/xueqiu/utils.test.js +0 -26
- package/clis/yahoo/search.test.js +0 -94
- package/clis/youdao/note.test.js +0 -99
- package/clis/youtube/channel.test.js +0 -59
- package/clis/youtube/feed.test.js +0 -131
- package/clis/youtube/transcript-group.test.js +0 -99
- package/clis/youtube/transcript.test.js +0 -296
- package/clis/youtube/utils.test.js +0 -84
- package/clis/yuanbao/ask.test.js +0 -124
- package/clis/yuanbao/new.test.js +0 -30
- package/clis/yuanbao/shared.test.js +0 -80
- package/clis/zhihu/answer-comments.test.js +0 -287
- package/clis/zhihu/answer-detail.test.js +0 -338
- package/clis/zhihu/answer.test.js +0 -54
- package/clis/zhihu/collection.test.js +0 -337
- package/clis/zhihu/collections.test.js +0 -182
- package/clis/zhihu/comment.test.js +0 -50
- package/clis/zhihu/download.test.js +0 -12
- package/clis/zhihu/favorite.test.js +0 -59
- package/clis/zhihu/follow.test.js +0 -42
- package/clis/zhihu/like.test.js +0 -41
- package/clis/zhihu/question.test.js +0 -251
- package/clis/zhihu/recommend.test.js +0 -143
- package/clis/zhihu/search.test.js +0 -198
- package/clis/zhihu/target.test.js +0 -77
- package/clis/zhihu/text.test.js +0 -24
- package/clis/zhihu/write-shared.test.js +0 -176
- package/clis/zlibrary/commands.test.js +0 -65
- package/clis/zsxq/search.test.js +0 -24
- package/clis/zsxq/topic.test.js +0 -30
- package/clis/zsxq/topics.test.js +0 -24
- package/clis/zsxq/utils.test.js +0 -31
- package/dist/src/adapter-shadow.test.js +0 -49
- package/dist/src/adapter-source.test.js +0 -29
- package/dist/src/browser/analyze.test.js +0 -166
- package/dist/src/browser/article-extract.e2e.test.js +0 -105
- package/dist/src/browser/article-extract.test.js +0 -94
- package/dist/src/browser/ax-snapshot.test.js +0 -91
- package/dist/src/browser/base-page.test.js +0 -758
- package/dist/src/browser/cdp-click-fixture.test.js +0 -87
- package/dist/src/browser/cdp.test.js +0 -79
- package/dist/src/browser/compound.test.js +0 -175
- package/dist/src/browser/daemon-client.test.js +0 -199
- package/dist/src/browser/dom-helpers.test.js +0 -106
- package/dist/src/browser/dom-snapshot.test.js +0 -313
- package/dist/src/browser/errors.test.js +0 -60
- package/dist/src/browser/extract.test.js +0 -129
- package/dist/src/browser/find.test.js +0 -180
- package/dist/src/browser/html-tree.test.js +0 -181
- package/dist/src/browser/network-cache.test.js +0 -58
- package/dist/src/browser/network-key.test.js +0 -49
- package/dist/src/browser/page.test.js +0 -378
- package/dist/src/browser/shape-filter.test.js +0 -101
- package/dist/src/browser/shape.test.js +0 -82
- package/dist/src/browser/stealth.test.js +0 -134
- package/dist/src/browser/target-errors.test.js +0 -94
- package/dist/src/browser/target-resolver.test.js +0 -118
- package/dist/src/browser/utils.test.js +0 -29
- package/dist/src/browser/verify-fixture.test.js +0 -219
- package/dist/src/browser.test.js +0 -232
- package/dist/src/build-manifest.test.js +0 -341
- package/dist/src/capabilityRouting.test.js +0 -63
- package/dist/src/cli-argv-preprocess.test.js +0 -209
- package/dist/src/cli.test.js +0 -2803
- package/dist/src/commanderAdapter.test.js +0 -352
- package/dist/src/commands/daemon.test.js +0 -219
- package/dist/src/completion.test.js +0 -24
- package/dist/src/convention-audit.test.js +0 -247
- package/dist/src/daemon.test.js +0 -60
- package/dist/src/doctor.test.js +0 -263
- package/dist/src/download/article-download.test.js +0 -235
- package/dist/src/download/index.test.js +0 -140
- package/dist/src/download/media-download.test.js +0 -112
- package/dist/src/download/progress.test.js +0 -36
- package/dist/src/electron-apps.test.js +0 -49
- package/dist/src/engine.test.js +0 -312
- package/dist/src/errors.test.js +0 -109
- package/dist/src/execution.test.js +0 -623
- package/dist/src/extension-manifest-regression.test.js +0 -12
- package/dist/src/external.test.js +0 -146
- package/dist/src/help.test.js +0 -58
- package/dist/src/hooks.test.js +0 -92
- package/dist/src/interceptor.test.js +0 -81
- package/dist/src/launcher.test.js +0 -138
- package/dist/src/node-network.test.js +0 -55
- package/dist/src/observation/artifact.test.js +0 -121
- package/dist/src/observation/manager.test.js +0 -13
- package/dist/src/observation/redaction.test.js +0 -32
- package/dist/src/observation/retention.test.js +0 -118
- package/dist/src/observation/ring-buffer.test.js +0 -22
- package/dist/src/output.test.js +0 -53
- package/dist/src/package-exports.test.js +0 -112
- package/dist/src/pipeline/executor.test.js +0 -182
- package/dist/src/pipeline/steps/download.test.js +0 -102
- package/dist/src/pipeline/steps/fetch.test.js +0 -117
- package/dist/src/pipeline/template.test.js +0 -175
- package/dist/src/pipeline/transform.test.js +0 -133
- package/dist/src/plugin-manifest.test.js +0 -179
- package/dist/src/plugin-scaffold.test.js +0 -83
- package/dist/src/plugin.test.js +0 -1324
- package/dist/src/registry.test.js +0 -209
- package/dist/src/runtime-detect.test.js +0 -39
- package/dist/src/serialization.test.js +0 -78
- package/dist/src/snapshotFormatter.test.js +0 -521
- package/dist/src/tui.test.js +0 -19
- package/dist/src/update-check.test.js +0 -116
- package/dist/src/utils.test.js +0 -155
- package/dist/src/validate.test.js +0 -90
- package/dist/src/weixin-download.test.js +0 -45
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as os from 'node:os';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import { JSDOM } from 'jsdom';
|
|
5
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
6
|
-
import { ArgumentError, CommandExecutionError } from '@sovovs/bycli/errors';
|
|
7
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
8
|
-
import { __test__ } from './reply.js';
|
|
9
|
-
import { __test__ as utilsTest } from './utils.js';
|
|
10
|
-
import { createPageMock } from '../test-utils.js';
|
|
11
|
-
|
|
12
|
-
describe('twitter reply command', () => {
|
|
13
|
-
it('uses the dedicated reply composer for text-only replies too', async () => {
|
|
14
|
-
const cmd = getRegistry().get('twitter/reply');
|
|
15
|
-
expect(cmd?.func).toBeTypeOf('function');
|
|
16
|
-
const page = createPageMock([
|
|
17
|
-
{ ok: true },
|
|
18
|
-
{ ok: true },
|
|
19
|
-
{ ok: true, message: 'Reply posted successfully.' },
|
|
20
|
-
]);
|
|
21
|
-
const result = await cmd.func(page, {
|
|
22
|
-
url: 'https://x.com/_kop6/status/2040254679301718161?s=20',
|
|
23
|
-
text: 'text-only reply',
|
|
24
|
-
});
|
|
25
|
-
expect(page.goto).toHaveBeenCalledWith('https://x.com/compose/post?in_reply_to=2040254679301718161', { waitUntil: 'load', settleMs: 2500 });
|
|
26
|
-
expect(page.wait).toHaveBeenCalledWith({ selector: '[data-testid="tweetTextarea_0"]', timeout: 15 });
|
|
27
|
-
expect(result).toEqual([
|
|
28
|
-
{
|
|
29
|
-
status: 'success',
|
|
30
|
-
message: 'Reply posted successfully.',
|
|
31
|
-
text: 'text-only reply',
|
|
32
|
-
},
|
|
33
|
-
]);
|
|
34
|
-
});
|
|
35
|
-
it('uploads a local image through the dedicated reply composer when --image is provided', async () => {
|
|
36
|
-
const cmd = getRegistry().get('twitter/reply');
|
|
37
|
-
expect(cmd?.func).toBeTypeOf('function');
|
|
38
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bycli-twitter-reply-'));
|
|
39
|
-
const imagePath = path.join(tempDir, 'qr.png');
|
|
40
|
-
fs.writeFileSync(imagePath, Buffer.from([0x89, 0x50, 0x4e, 0x47]));
|
|
41
|
-
const setFileInput = vi.fn().mockResolvedValue(undefined);
|
|
42
|
-
const page = createPageMock([
|
|
43
|
-
{ ok: true, previewCount: 1 },
|
|
44
|
-
{ ok: true },
|
|
45
|
-
{ ok: true },
|
|
46
|
-
{ ok: true, message: 'Reply posted successfully.' },
|
|
47
|
-
], {
|
|
48
|
-
setFileInput,
|
|
49
|
-
});
|
|
50
|
-
const result = await cmd.func(page, {
|
|
51
|
-
url: 'https://x.com/_kop6/status/2040254679301718161?s=20',
|
|
52
|
-
text: 'reply with image',
|
|
53
|
-
image: imagePath,
|
|
54
|
-
});
|
|
55
|
-
expect(page.goto).toHaveBeenCalledWith('https://x.com/compose/post?in_reply_to=2040254679301718161', { waitUntil: 'load', settleMs: 2500 });
|
|
56
|
-
expect(page.wait).toHaveBeenNthCalledWith(1, { selector: '[data-testid="tweetTextarea_0"]', timeout: 15 });
|
|
57
|
-
expect(page.wait).toHaveBeenNthCalledWith(2, { selector: 'input[type="file"][data-testid="fileInput"]', timeout: 20 });
|
|
58
|
-
expect(setFileInput).toHaveBeenCalledWith([imagePath], 'input[type="file"][data-testid="fileInput"]');
|
|
59
|
-
expect(result).toEqual([
|
|
60
|
-
{
|
|
61
|
-
status: 'success',
|
|
62
|
-
message: 'Reply posted successfully.',
|
|
63
|
-
text: 'reply with image',
|
|
64
|
-
image: imagePath,
|
|
65
|
-
},
|
|
66
|
-
]);
|
|
67
|
-
});
|
|
68
|
-
it('downloads a remote image before uploading when --image-url is provided', async () => {
|
|
69
|
-
const cmd = getRegistry().get('twitter/reply');
|
|
70
|
-
expect(cmd?.func).toBeTypeOf('function');
|
|
71
|
-
const fetchMock = vi.fn().mockResolvedValue({
|
|
72
|
-
ok: true,
|
|
73
|
-
headers: {
|
|
74
|
-
get: vi.fn().mockReturnValue('image/png'),
|
|
75
|
-
},
|
|
76
|
-
arrayBuffer: vi.fn().mockResolvedValue(Uint8Array.from([0x89, 0x50, 0x4e, 0x47]).buffer),
|
|
77
|
-
});
|
|
78
|
-
vi.stubGlobal('fetch', fetchMock);
|
|
79
|
-
const setFileInput = vi.fn().mockResolvedValue(undefined);
|
|
80
|
-
const page = createPageMock([
|
|
81
|
-
{ ok: true, previewCount: 1 },
|
|
82
|
-
{ ok: true },
|
|
83
|
-
{ ok: true },
|
|
84
|
-
{ ok: true, message: 'Reply posted successfully.' },
|
|
85
|
-
], {
|
|
86
|
-
setFileInput,
|
|
87
|
-
});
|
|
88
|
-
const result = await cmd.func(page, {
|
|
89
|
-
url: 'https://x.com/_kop6/status/2040254679301718161?s=20',
|
|
90
|
-
text: 'reply with remote image',
|
|
91
|
-
'image-url': 'https://example.com/qr',
|
|
92
|
-
});
|
|
93
|
-
expect(fetchMock).toHaveBeenCalledWith('https://example.com/qr');
|
|
94
|
-
expect(setFileInput).toHaveBeenCalledTimes(1);
|
|
95
|
-
const uploadedPath = setFileInput.mock.calls[0][0][0];
|
|
96
|
-
// Tmp dir is created by utils.downloadRemoteImage with the
|
|
97
|
-
// 'bycli-twitter-' prefix; final extension comes from Content-Type.
|
|
98
|
-
expect(uploadedPath).toMatch(/bycli-twitter-.*\/image\.png$/);
|
|
99
|
-
// Per-call tmp dir is removed in the adapter's finally block, so the
|
|
100
|
-
// downloaded file no longer exists once the command returns.
|
|
101
|
-
expect(fs.existsSync(uploadedPath)).toBe(false);
|
|
102
|
-
expect(result).toEqual([
|
|
103
|
-
{
|
|
104
|
-
status: 'success',
|
|
105
|
-
message: 'Reply posted successfully.',
|
|
106
|
-
text: 'reply with remote image',
|
|
107
|
-
'image-url': 'https://example.com/qr',
|
|
108
|
-
},
|
|
109
|
-
]);
|
|
110
|
-
vi.unstubAllGlobals();
|
|
111
|
-
});
|
|
112
|
-
it('falls back to the target tweet page when the dedicated composer route does not expose a textarea', async () => {
|
|
113
|
-
const cmd = getRegistry().get('twitter/reply');
|
|
114
|
-
expect(cmd?.func).toBeTypeOf('function');
|
|
115
|
-
const wait = vi.fn()
|
|
116
|
-
.mockRejectedValueOnce(new Error('Selector not found: [data-testid="tweetTextarea_0"]'))
|
|
117
|
-
.mockResolvedValue(undefined);
|
|
118
|
-
const page = createPageMock([
|
|
119
|
-
{ ok: true }, // click target tweet page Reply button
|
|
120
|
-
{ ok: true }, // insert reply text
|
|
121
|
-
{ ok: true }, // click composer Reply button
|
|
122
|
-
{ ok: true, message: 'Reply posted successfully.' }, // submit completed
|
|
123
|
-
], { wait });
|
|
124
|
-
|
|
125
|
-
const url = 'https://x.com/_kop6/status/2040254679301718161?s=20';
|
|
126
|
-
const result = await cmd.func(page, { url, text: 'fallback reply' });
|
|
127
|
-
|
|
128
|
-
expect(page.goto).toHaveBeenNthCalledWith(1, 'https://x.com/compose/post?in_reply_to=2040254679301718161', { waitUntil: 'load', settleMs: 2500 });
|
|
129
|
-
expect(page.goto).toHaveBeenNthCalledWith(2, url, { waitUntil: 'load', settleMs: 2500 });
|
|
130
|
-
expect(page.evaluate.mock.calls[0][0]).toContain('[data-testid="reply"]');
|
|
131
|
-
expect(wait).toHaveBeenLastCalledWith({ selector: '[data-testid="tweetTextarea_0"]', timeout: 15 });
|
|
132
|
-
expect(result).toEqual([{ status: 'success', message: 'Reply posted successfully.', text: 'fallback reply' }]);
|
|
133
|
-
});
|
|
134
|
-
it('treats an X success toast as success after a Promise was collected error', async () => {
|
|
135
|
-
const cmd = getRegistry().get('twitter/reply');
|
|
136
|
-
expect(cmd?.func).toBeTypeOf('function');
|
|
137
|
-
const evaluate = vi.fn()
|
|
138
|
-
.mockResolvedValueOnce({ ok: true }) // insert reply text
|
|
139
|
-
.mockResolvedValueOnce({ ok: true }) // click Reply
|
|
140
|
-
.mockRejectedValueOnce(new Error('{"code":-32000,"message":"Promise was collected"}'))
|
|
141
|
-
.mockResolvedValueOnce({
|
|
142
|
-
ok: true,
|
|
143
|
-
message: 'Reply posted successfully.',
|
|
144
|
-
url: 'https://x.com/me/status/123',
|
|
145
|
-
});
|
|
146
|
-
const page = createPageMock([], { evaluate });
|
|
147
|
-
|
|
148
|
-
const result = await cmd.func(page, {
|
|
149
|
-
url: 'https://x.com/_kop6/status/2040254679301718161?s=20',
|
|
150
|
-
text: 'toast recovery',
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
expect(page.wait).toHaveBeenCalledWith(2);
|
|
154
|
-
expect(result).toEqual([{
|
|
155
|
-
status: 'success',
|
|
156
|
-
message: 'Reply posted successfully.',
|
|
157
|
-
text: 'toast recovery',
|
|
158
|
-
url: 'https://x.com/me/status/123',
|
|
159
|
-
}]);
|
|
160
|
-
});
|
|
161
|
-
it('rejects using --image and --image-url together', async () => {
|
|
162
|
-
const cmd = getRegistry().get('twitter/reply');
|
|
163
|
-
expect(cmd?.func).toBeTypeOf('function');
|
|
164
|
-
const page = createPageMock([]);
|
|
165
|
-
await expect(cmd.func(page, {
|
|
166
|
-
url: 'https://x.com/_kop6/status/2040254679301718161?s=20',
|
|
167
|
-
text: 'nope',
|
|
168
|
-
image: '/tmp/a.png',
|
|
169
|
-
'image-url': 'https://example.com/a.png',
|
|
170
|
-
})).rejects.toThrow(CommandExecutionError);
|
|
171
|
-
});
|
|
172
|
-
it('rejects malformed tweet URLs before any browser interaction', () => {
|
|
173
|
-
// buildReplyComposerUrl runs parseTweetUrl synchronously; substring matches
|
|
174
|
-
// and off-domain hosts now throw ArgumentError instead of silently
|
|
175
|
-
// producing a wrong-host /compose/post URL.
|
|
176
|
-
expect(() => __test__.buildReplyComposerUrl('https://x.com/alice/home')).toThrow(ArgumentError);
|
|
177
|
-
expect(() => __test__.buildReplyComposerUrl('https://x.com.evil.com/alice/status/2040254679301718161')).toThrow(ArgumentError);
|
|
178
|
-
expect(() => __test__.buildReplyComposerUrl('not a url')).toThrow(ArgumentError);
|
|
179
|
-
});
|
|
180
|
-
it('builds the reply composer URL for both /<user>/status/<id> and /i/status/<id> shapes', () => {
|
|
181
|
-
expect(__test__.buildReplyComposerUrl('https://x.com/_kop6/status/2040254679301718161?s=20'))
|
|
182
|
-
.toBe('https://x.com/compose/post?in_reply_to=2040254679301718161');
|
|
183
|
-
expect(__test__.buildReplyComposerUrl('https://x.com/i/status/2040318731105313143'))
|
|
184
|
-
.toBe('https://x.com/compose/post?in_reply_to=2040318731105313143');
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe('twitter image helpers (utils.js)', () => {
|
|
189
|
-
it('rejects invalid image paths early', () => {
|
|
190
|
-
expect(() => utilsTest.resolveImagePath('/tmp/does-not-exist.png'))
|
|
191
|
-
.toThrow(ArgumentError);
|
|
192
|
-
});
|
|
193
|
-
it('prefers content-type when resolving remote image extensions', () => {
|
|
194
|
-
expect(utilsTest.resolveImageExtension('https://example.com/no-ext', 'image/webp')).toBe('.webp');
|
|
195
|
-
expect(utilsTest.resolveImageExtension('https://example.com/a.jpeg?x=1', null)).toBe('.jpeg');
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('classifies CDP NotAllowed file-input failures as recoverable', () => {
|
|
199
|
-
expect(utilsTest.isRecoverableFileInputError(new Error('NotAllowedError: Not allowed'))).toBe(true);
|
|
200
|
-
expect(utilsTest.isRecoverableFileInputError(new Error('ProtocolError: not-allowed'))).toBe(true);
|
|
201
|
-
expect(utilsTest.isRecoverableFileInputError(new Error('Permission denied'))).toBe(false);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('fails closed when a composer image preview never appears', async () => {
|
|
205
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bycli-twitter-helper-'));
|
|
206
|
-
const imagePath = path.join(tempDir, 'missing-preview.png');
|
|
207
|
-
fs.writeFileSync(imagePath, Buffer.from([0x89, 0x50, 0x4e, 0x47]));
|
|
208
|
-
const page = createPageMock([{ ok: false, message: 'Image upload timed out (30s).' }], {
|
|
209
|
-
setFileInput: vi.fn().mockResolvedValue(undefined),
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
await expect(utilsTest.attachComposerImage(page, imagePath)).rejects.toThrow('Image upload timed out');
|
|
213
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it('does not treat an empty attachments container as uploaded media', async () => {
|
|
217
|
-
const runMediaReadyProbe = async (html) => {
|
|
218
|
-
const dom = new JSDOM(`<!doctype html><body>${html}</body>`, {
|
|
219
|
-
url: 'https://x.com/compose/post',
|
|
220
|
-
runScripts: 'outside-only',
|
|
221
|
-
});
|
|
222
|
-
dom.window.setTimeout = (callback) => {
|
|
223
|
-
callback();
|
|
224
|
-
return 0;
|
|
225
|
-
};
|
|
226
|
-
const page = {
|
|
227
|
-
evaluate: vi.fn(async (script) => dom.window.eval(script)),
|
|
228
|
-
};
|
|
229
|
-
return utilsTest.waitForComposerMediaReady(page, 1);
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
await expect(runMediaReadyProbe('<div data-testid="attachments"></div>'))
|
|
233
|
-
.resolves.toMatchObject({ ok: false });
|
|
234
|
-
await expect(runMediaReadyProbe('<div data-testid="attachments"><img src="blob:https://x.com/1"></div>'))
|
|
235
|
-
.resolves.toMatchObject({ ok: true, previewCount: 1 });
|
|
236
|
-
});
|
|
237
|
-
});
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { ArgumentError, CommandExecutionError } from '@sovovs/bycli/errors';
|
|
3
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
4
|
-
import './retweet.js';
|
|
5
|
-
import { createPageMock } from '../test-utils.js';
|
|
6
|
-
|
|
7
|
-
describe('twitter retweet command', () => {
|
|
8
|
-
it('clicks the retweet button then the confirm menu item and reports success', async () => {
|
|
9
|
-
const cmd = getRegistry().get('twitter/retweet');
|
|
10
|
-
expect(cmd?.func).toBeTypeOf('function');
|
|
11
|
-
const page = createPageMock([
|
|
12
|
-
{ ok: true, message: 'Tweet successfully retweeted.' },
|
|
13
|
-
]);
|
|
14
|
-
const result = await cmd.func(page, {
|
|
15
|
-
url: 'https://x.com/alice/status/2040254679301718161',
|
|
16
|
-
});
|
|
17
|
-
expect(page.goto).toHaveBeenCalledWith('https://x.com/alice/status/2040254679301718161');
|
|
18
|
-
expect(page.wait).toHaveBeenNthCalledWith(1, { selector: '[data-testid="primaryColumn"]' });
|
|
19
|
-
expect(page.wait).toHaveBeenNthCalledWith(2, 2);
|
|
20
|
-
const script = page.evaluate.mock.calls[0][0];
|
|
21
|
-
// Two-step UI flow must be present:
|
|
22
|
-
// 1) click the retweet button
|
|
23
|
-
// 2) wait for and click the confirm menu item (data-testid="retweetConfirm")
|
|
24
|
-
expect(script).toContain('retweetBtn.click()');
|
|
25
|
-
expect(script).toContain("document.querySelector('[data-testid=\"retweetConfirm\"]')");
|
|
26
|
-
expect(script).toContain('confirmBtn.click()');
|
|
27
|
-
// Article scoping comes from the shared helper (buildTwitterArticleScopeSource):
|
|
28
|
-
// emits __twHasLinkToTarget + __twGetStatusIdFromHref + the anchored
|
|
29
|
-
// tweet-path regex. JSDOM-level coverage lives in shared.test.js.
|
|
30
|
-
expect(script).toContain('__twHasLinkToTarget');
|
|
31
|
-
expect(script).toContain('__twGetStatusIdFromHref');
|
|
32
|
-
expect(script).toContain("document.querySelectorAll('article')");
|
|
33
|
-
expect(script).toContain("targetArticle?.querySelector('[data-testid=\"retweet\"]')");
|
|
34
|
-
// Idempotency probe: when already retweeted ([data-testid="unretweet"] present),
|
|
35
|
-
// the script returns ok:true with an "already retweeted" message.
|
|
36
|
-
expect(script).toContain("targetArticle?.querySelector('[data-testid=\"unretweet\"]')");
|
|
37
|
-
expect(result).toEqual([
|
|
38
|
-
{ status: 'success', message: 'Tweet successfully retweeted.' },
|
|
39
|
-
]);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('returns a failed row when the confirm menu item never appears', async () => {
|
|
43
|
-
const cmd = getRegistry().get('twitter/retweet');
|
|
44
|
-
expect(cmd?.func).toBeTypeOf('function');
|
|
45
|
-
const page = createPageMock([
|
|
46
|
-
{ ok: false, message: 'Retweet menu opened but the confirm option did not appear.' },
|
|
47
|
-
]);
|
|
48
|
-
const result = await cmd.func(page, {
|
|
49
|
-
url: 'https://x.com/alice/status/2040254679301718161',
|
|
50
|
-
});
|
|
51
|
-
expect(result).toEqual([
|
|
52
|
-
{ status: 'failed', message: 'Retweet menu opened but the confirm option did not appear.' },
|
|
53
|
-
]);
|
|
54
|
-
expect(page.wait).toHaveBeenCalledTimes(1);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('throws CommandExecutionError when no page is provided', async () => {
|
|
58
|
-
const cmd = getRegistry().get('twitter/retweet');
|
|
59
|
-
await expect(cmd.func(undefined, {
|
|
60
|
-
url: 'https://x.com/alice/status/2040254679301718161',
|
|
61
|
-
})).rejects.toThrow(CommandExecutionError);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('rejects invalid tweet URLs before navigation', async () => {
|
|
65
|
-
const cmd = getRegistry().get('twitter/retweet');
|
|
66
|
-
const page = createPageMock([]);
|
|
67
|
-
await expect(cmd.func(page, {
|
|
68
|
-
url: 'https://evil.com/?next=https://x.com/alice/status/2040254679301718161',
|
|
69
|
-
})).rejects.toThrow(ArgumentError);
|
|
70
|
-
expect(page.goto).not.toHaveBeenCalled();
|
|
71
|
-
expect(page.evaluate).not.toHaveBeenCalled();
|
|
72
|
-
});
|
|
73
|
-
});
|
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { getRegistry } from '@sovovs/bycli/registry';
|
|
3
|
-
import { __test__ } from './search.js';
|
|
4
|
-
|
|
5
|
-
const { buildSearchQuery, resolveSearchFParam, resolveSearchProduct, buildSearchTimelineRequest, parseSearchTimeline, HAS_CHOICES, EXCLUDE_CHOICES, PRODUCT_CHOICES, EXCLUDE_TO_OPERATOR, PRODUCT_TO_F_PARAM, FROM_USER_PATTERN } = __test__;
|
|
6
|
-
describe('twitter search command', () => {
|
|
7
|
-
function makeSearchPage(data) {
|
|
8
|
-
return {
|
|
9
|
-
getCookies: vi.fn().mockResolvedValue([{ name: 'ct0', value: 'csrf' }]),
|
|
10
|
-
goto: vi.fn().mockResolvedValue(undefined),
|
|
11
|
-
evaluate: vi.fn()
|
|
12
|
-
.mockResolvedValueOnce(null) // resolveTwitterQueryId fallback
|
|
13
|
-
.mockResolvedValueOnce(data),
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
it('fetches SearchTimeline directly instead of relying on SPA navigation', async () => {
|
|
18
|
-
const command = getRegistry().get('twitter/search');
|
|
19
|
-
expect(command?.func).toBeTypeOf('function');
|
|
20
|
-
const page = makeSearchPage({
|
|
21
|
-
data: {
|
|
22
|
-
search_by_raw_query: {
|
|
23
|
-
search_timeline: {
|
|
24
|
-
timeline: {
|
|
25
|
-
instructions: [
|
|
26
|
-
{
|
|
27
|
-
type: 'TimelineAddEntries',
|
|
28
|
-
entries: [
|
|
29
|
-
{
|
|
30
|
-
entryId: 'tweet-1',
|
|
31
|
-
content: {
|
|
32
|
-
itemContent: {
|
|
33
|
-
tweet_results: {
|
|
34
|
-
result: {
|
|
35
|
-
rest_id: '1',
|
|
36
|
-
legacy: {
|
|
37
|
-
full_text: 'hello world',
|
|
38
|
-
favorite_count: 7,
|
|
39
|
-
created_at: 'Thu Mar 26 10:30:00 +0000 2026',
|
|
40
|
-
},
|
|
41
|
-
core: {
|
|
42
|
-
user_results: {
|
|
43
|
-
result: {
|
|
44
|
-
core: {
|
|
45
|
-
screen_name: 'alice',
|
|
46
|
-
},
|
|
47
|
-
legacy: {
|
|
48
|
-
description: 'Search author bio',
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
views: {
|
|
54
|
-
count: '12',
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
const result = await command.func(page, { query: 'from:alice', filter: 'top', limit: 5 });
|
|
70
|
-
expect(result).toEqual([
|
|
71
|
-
{
|
|
72
|
-
id: '1',
|
|
73
|
-
author: 'alice',
|
|
74
|
-
bio: 'Search author bio',
|
|
75
|
-
text: 'hello world',
|
|
76
|
-
created_at: 'Thu Mar 26 10:30:00 +0000 2026',
|
|
77
|
-
likes: 7,
|
|
78
|
-
views: '12',
|
|
79
|
-
url: 'https://x.com/i/status/1',
|
|
80
|
-
has_media: false,
|
|
81
|
-
media_urls: [],
|
|
82
|
-
card: null,
|
|
83
|
-
quoted_tweet: null,
|
|
84
|
-
},
|
|
85
|
-
]);
|
|
86
|
-
expect(page.getCookies).toHaveBeenCalledWith({ url: 'https://x.com' });
|
|
87
|
-
expect(page.goto).toHaveBeenCalledWith('https://x.com/home', { waitUntil: 'load', settleMs: 1000 });
|
|
88
|
-
const searchFetch = page.evaluate.mock.calls[1][0];
|
|
89
|
-
expect(searchFetch).toContain('/SearchTimeline');
|
|
90
|
-
expect(searchFetch).toContain("method: 'POST'");
|
|
91
|
-
expect(searchFetch).toContain('\\"rawQuery\\":\\"from:alice\\"');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('uses the requested GraphQL product', async () => {
|
|
95
|
-
const command = getRegistry().get('twitter/search');
|
|
96
|
-
const page = makeSearchPage({ data: { search_by_raw_query: { search_timeline: { timeline: { instructions: [] } } } } });
|
|
97
|
-
await command.func(page, { query: 'cats', product: 'videos', limit: 5 });
|
|
98
|
-
expect(page.evaluate.mock.calls[1][0]).toContain('\\"product\\":\\"Videos\\"');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('paginates past the old five-page cap until the requested limit is reached', async () => {
|
|
102
|
-
const command = getRegistry().get('twitter/search');
|
|
103
|
-
let pageIndex = 0;
|
|
104
|
-
const page = {
|
|
105
|
-
getCookies: vi.fn().mockResolvedValue([{ name: 'ct0', value: 'csrf' }]),
|
|
106
|
-
goto: vi.fn().mockResolvedValue(undefined),
|
|
107
|
-
evaluate: vi.fn().mockImplementation(async () => {
|
|
108
|
-
if (pageIndex === 0) {
|
|
109
|
-
pageIndex += 1;
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
const id = String(pageIndex);
|
|
113
|
-
pageIndex += 1;
|
|
114
|
-
return {
|
|
115
|
-
data: {
|
|
116
|
-
search_by_raw_query: {
|
|
117
|
-
search_timeline: {
|
|
118
|
-
timeline: {
|
|
119
|
-
instructions: [
|
|
120
|
-
{
|
|
121
|
-
entries: [
|
|
122
|
-
{
|
|
123
|
-
content: {
|
|
124
|
-
itemContent: {
|
|
125
|
-
tweet_results: {
|
|
126
|
-
result: {
|
|
127
|
-
rest_id: id,
|
|
128
|
-
legacy: { full_text: `tweet ${id}`, created_at: 'now' },
|
|
129
|
-
core: { user_results: { result: { core: { screen_name: 'alice' } } } },
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
content: {
|
|
137
|
-
entryType: 'TimelineTimelineCursor',
|
|
138
|
-
cursorType: 'Bottom',
|
|
139
|
-
value: `cursor-${id}`,
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
},
|
|
144
|
-
],
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
};
|
|
150
|
-
}),
|
|
151
|
-
};
|
|
152
|
-
const result = await command.func(page, { query: 'bycli', limit: 7 });
|
|
153
|
-
expect(result).toHaveLength(7);
|
|
154
|
-
expect(result.map((row) => row.id)).toEqual(['1', '2', '3', '4', '5', '6', '7']);
|
|
155
|
-
expect(page.evaluate).toHaveBeenCalledTimes(8);
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
describe('twitter search filter helpers', () => {
|
|
160
|
-
describe('buildSearchQuery', () => {
|
|
161
|
-
it('returns the trimmed raw query when no filters are set', () => {
|
|
162
|
-
expect(buildSearchQuery(' hello world ', {})).toBe('hello world');
|
|
163
|
-
});
|
|
164
|
-
it('appends from: with leading @ stripped', () => {
|
|
165
|
-
expect(buildSearchQuery('hello', { from: '@alice' })).toBe('hello from:alice');
|
|
166
|
-
});
|
|
167
|
-
it('preserves from: when caller passes a bare username', () => {
|
|
168
|
-
expect(buildSearchQuery('hello', { from: 'alice' })).toBe('hello from:alice');
|
|
169
|
-
});
|
|
170
|
-
it('strips multiple leading @ characters from --from', () => {
|
|
171
|
-
expect(buildSearchQuery('hi', { from: '@@bob' })).toBe('hi from:bob');
|
|
172
|
-
});
|
|
173
|
-
it('drops --from when it is whitespace-only', () => {
|
|
174
|
-
expect(buildSearchQuery('hi', { from: ' ' })).toBe('hi');
|
|
175
|
-
});
|
|
176
|
-
it('rejects invalid --from usernames instead of injecting raw operators', () => {
|
|
177
|
-
expect(() => buildSearchQuery('hi', { from: 'alice filter:links' })).toThrow(/Invalid --from/);
|
|
178
|
-
expect(() => buildSearchQuery('hi', { from: 'alice/bob' })).toThrow(/Invalid --from/);
|
|
179
|
-
expect(() => buildSearchQuery('hi', { from: '@' + 'a'.repeat(16) })).toThrow(/Invalid --from/);
|
|
180
|
-
});
|
|
181
|
-
it('appends filter:<has> for --has', () => {
|
|
182
|
-
expect(buildSearchQuery('q', { has: 'images' })).toBe('q filter:images');
|
|
183
|
-
});
|
|
184
|
-
it('maps --exclude retweets to -filter:nativeretweets', () => {
|
|
185
|
-
expect(buildSearchQuery('q', { exclude: 'retweets' })).toBe('q -filter:nativeretweets');
|
|
186
|
-
});
|
|
187
|
-
it('maps --exclude replies/media/links to their -filter operators', () => {
|
|
188
|
-
expect(buildSearchQuery('q', { exclude: 'replies' })).toBe('q -filter:replies');
|
|
189
|
-
expect(buildSearchQuery('q', { exclude: 'media' })).toBe('q -filter:media');
|
|
190
|
-
expect(buildSearchQuery('q', { exclude: 'links' })).toBe('q -filter:links');
|
|
191
|
-
});
|
|
192
|
-
it('silently ignores unknown --exclude values', () => {
|
|
193
|
-
// choices: ['replies','retweets','media','links'] — unknowns shouldn't appear
|
|
194
|
-
// in real CLI use because the validator rejects them, but the helper still
|
|
195
|
-
// guards via the EXCLUDE_TO_OPERATOR map lookup.
|
|
196
|
-
expect(buildSearchQuery('q', { exclude: 'bogus' })).toBe('q');
|
|
197
|
-
});
|
|
198
|
-
it('composes multiple filter clauses in stable order: query → from → has → exclude', () => {
|
|
199
|
-
expect(buildSearchQuery('hot take', {
|
|
200
|
-
from: '@alice',
|
|
201
|
-
has: 'media',
|
|
202
|
-
exclude: 'retweets',
|
|
203
|
-
})).toBe('hot take from:alice filter:media -filter:nativeretweets');
|
|
204
|
-
});
|
|
205
|
-
it('allows an empty raw query when filters are present', () => {
|
|
206
|
-
expect(buildSearchQuery('', { from: 'alice' })).toBe('from:alice');
|
|
207
|
-
});
|
|
208
|
-
it('returns empty string when nothing useful is supplied', () => {
|
|
209
|
-
expect(buildSearchQuery('', {})).toBe('');
|
|
210
|
-
expect(buildSearchQuery(' ', {})).toBe('');
|
|
211
|
-
});
|
|
212
|
-
it('coerces nullish raw query into empty string', () => {
|
|
213
|
-
expect(buildSearchQuery(null, { from: 'alice' })).toBe('from:alice');
|
|
214
|
-
expect(buildSearchQuery(undefined, { from: 'alice' })).toBe('from:alice');
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
describe('resolveSearchFParam', () => {
|
|
219
|
-
it('defaults to top when neither product nor filter is set', () => {
|
|
220
|
-
expect(resolveSearchFParam({})).toBe('top');
|
|
221
|
-
});
|
|
222
|
-
it('returns top when filter=top', () => {
|
|
223
|
-
expect(resolveSearchFParam({ filter: 'top' })).toBe('top');
|
|
224
|
-
});
|
|
225
|
-
it('returns live when filter=live', () => {
|
|
226
|
-
expect(resolveSearchFParam({ filter: 'live' })).toBe('live');
|
|
227
|
-
});
|
|
228
|
-
it('maps --product photos to image (Twitter URL singular form)', () => {
|
|
229
|
-
expect(resolveSearchFParam({ product: 'photos' })).toBe('image');
|
|
230
|
-
});
|
|
231
|
-
it('maps --product videos to video (Twitter URL singular form)', () => {
|
|
232
|
-
expect(resolveSearchFParam({ product: 'videos' })).toBe('video');
|
|
233
|
-
});
|
|
234
|
-
it('maps --product top|live straight through', () => {
|
|
235
|
-
expect(resolveSearchFParam({ product: 'top' })).toBe('top');
|
|
236
|
-
expect(resolveSearchFParam({ product: 'live' })).toBe('live');
|
|
237
|
-
});
|
|
238
|
-
it('lets --product win when both --product and --filter are set', () => {
|
|
239
|
-
expect(resolveSearchFParam({ product: 'photos', filter: 'live' })).toBe('image');
|
|
240
|
-
expect(resolveSearchFParam({ product: 'top', filter: 'live' })).toBe('top');
|
|
241
|
-
});
|
|
242
|
-
it('falls back to filter when --product is unknown', () => {
|
|
243
|
-
// unknowns are blocked at the CLI validator layer; this is just defence
|
|
244
|
-
expect(resolveSearchFParam({ product: 'bogus', filter: 'live' })).toBe('live');
|
|
245
|
-
expect(resolveSearchFParam({ product: 'bogus' })).toBe('top');
|
|
246
|
-
});
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
describe('choice surface', () => {
|
|
250
|
-
it('exposes the documented HAS_CHOICES set', () => {
|
|
251
|
-
expect(HAS_CHOICES).toEqual(['media', 'images', 'videos', 'links', 'replies']);
|
|
252
|
-
});
|
|
253
|
-
it('exposes the documented EXCLUDE_CHOICES set', () => {
|
|
254
|
-
expect(EXCLUDE_CHOICES).toEqual(['replies', 'retweets', 'media', 'links']);
|
|
255
|
-
});
|
|
256
|
-
it('exposes the documented PRODUCT_CHOICES set', () => {
|
|
257
|
-
expect(PRODUCT_CHOICES).toEqual(['top', 'live', 'photos', 'videos']);
|
|
258
|
-
});
|
|
259
|
-
it('keeps PRODUCT_TO_F_PARAM domain a strict subset of PRODUCT_CHOICES', () => {
|
|
260
|
-
for (const choice of PRODUCT_CHOICES) {
|
|
261
|
-
expect(PRODUCT_TO_F_PARAM[choice]).toBeTypeOf('string');
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
it('keeps EXCLUDE_TO_OPERATOR domain a strict subset of EXCLUDE_CHOICES', () => {
|
|
265
|
-
for (const choice of EXCLUDE_CHOICES) {
|
|
266
|
-
expect(EXCLUDE_TO_OPERATOR[choice]).toMatch(/^-filter:/);
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
it('keeps FROM_USER_PATTERN aligned with X handle syntax', () => {
|
|
270
|
-
expect(FROM_USER_PATTERN.test('alice_123')).toBe(true);
|
|
271
|
-
expect(FROM_USER_PATTERN.test('a'.repeat(15))).toBe(true);
|
|
272
|
-
expect(FROM_USER_PATTERN.test('a'.repeat(16))).toBe(false);
|
|
273
|
-
expect(FROM_USER_PATTERN.test('alice/bob')).toBe(false);
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
describe('twitter search end-to-end with new filters', () => {
|
|
279
|
-
it('encodes the composed query and product=live into the GraphQL request', async () => {
|
|
280
|
-
const command = getRegistry().get('twitter/search');
|
|
281
|
-
const evaluate = vi.fn()
|
|
282
|
-
.mockResolvedValueOnce(null)
|
|
283
|
-
.mockResolvedValueOnce({ data: { search_by_raw_query: { search_timeline: { timeline: { instructions: [] } } } } });
|
|
284
|
-
const page = {
|
|
285
|
-
getCookies: vi.fn().mockResolvedValue([{ name: 'ct0', value: 'csrf' }]),
|
|
286
|
-
goto: vi.fn().mockResolvedValue(undefined),
|
|
287
|
-
evaluate,
|
|
288
|
-
};
|
|
289
|
-
await command.func(page, {
|
|
290
|
-
query: 'breaking news',
|
|
291
|
-
from: '@alice',
|
|
292
|
-
has: 'images',
|
|
293
|
-
exclude: 'retweets',
|
|
294
|
-
product: 'live',
|
|
295
|
-
limit: 5,
|
|
296
|
-
});
|
|
297
|
-
const searchFetch = evaluate.mock.calls[1][0];
|
|
298
|
-
expect(searchFetch).toContain('\\"product\\":\\"Latest\\"');
|
|
299
|
-
expect(searchFetch).toContain('\\"rawQuery\\":\\"breaking news from:alice filter:images -filter:nativeretweets\\"');
|
|
300
|
-
});
|
|
301
|
-
it('throws ArgumentError when query and all filters are empty', async () => {
|
|
302
|
-
const command = getRegistry().get('twitter/search');
|
|
303
|
-
const page = {
|
|
304
|
-
goto: vi.fn().mockResolvedValue(undefined),
|
|
305
|
-
evaluate: vi.fn(),
|
|
306
|
-
};
|
|
307
|
-
await expect(command.func(page, { query: ' ', limit: 5 }))
|
|
308
|
-
.rejects
|
|
309
|
-
.toThrow(/empty/i);
|
|
310
|
-
expect(page.goto).not.toHaveBeenCalled();
|
|
311
|
-
});
|
|
312
|
-
it('throws ArgumentError for invalid --from before navigation', async () => {
|
|
313
|
-
const command = getRegistry().get('twitter/search');
|
|
314
|
-
const page = {
|
|
315
|
-
goto: vi.fn(),
|
|
316
|
-
evaluate: vi.fn(),
|
|
317
|
-
};
|
|
318
|
-
await expect(command.func(page, { query: 'hi', from: 'alice filter:links', limit: 5 }))
|
|
319
|
-
.rejects
|
|
320
|
-
.toThrow(/Invalid --from/);
|
|
321
|
-
expect(page.goto).not.toHaveBeenCalled();
|
|
322
|
-
});
|
|
323
|
-
it('throws ArgumentError for invalid --limit before navigation', async () => {
|
|
324
|
-
const command = getRegistry().get('twitter/search');
|
|
325
|
-
const page = {
|
|
326
|
-
goto: vi.fn(),
|
|
327
|
-
evaluate: vi.fn(),
|
|
328
|
-
};
|
|
329
|
-
await expect(command.func(page, { query: 'hi', limit: 0 }))
|
|
330
|
-
.rejects
|
|
331
|
-
.toThrow(/--limit/);
|
|
332
|
-
expect(page.goto).not.toHaveBeenCalled();
|
|
333
|
-
});
|
|
334
|
-
it('runs with only filters set (empty <query>)', async () => {
|
|
335
|
-
const command = getRegistry().get('twitter/search');
|
|
336
|
-
const evaluate = vi.fn()
|
|
337
|
-
.mockResolvedValueOnce(null)
|
|
338
|
-
.mockResolvedValueOnce({ data: { search_by_raw_query: { search_timeline: { timeline: { instructions: [] } } } } });
|
|
339
|
-
const page = {
|
|
340
|
-
getCookies: vi.fn().mockResolvedValue([{ name: 'ct0', value: 'csrf' }]),
|
|
341
|
-
goto: vi.fn().mockResolvedValue(undefined),
|
|
342
|
-
evaluate,
|
|
343
|
-
};
|
|
344
|
-
const result = await command.func(page, { query: '', from: 'alice', limit: 5 });
|
|
345
|
-
expect(result).toEqual([]);
|
|
346
|
-
const searchFetch = evaluate.mock.calls[1][0];
|
|
347
|
-
expect(searchFetch).toContain('\\"rawQuery\\":\\"from:alice\\"');
|
|
348
|
-
});
|
|
349
|
-
});
|