@jackwener/opencli 1.7.8 → 1.7.10
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/README.md +49 -14
- package/README.zh-CN.md +30 -10
- package/cli-manifest.json +646 -30
- package/clis/36kr/news.js +1 -1
- package/clis/apple-podcasts/commands.test.js +4 -4
- package/clis/apple-podcasts/episodes.js +1 -1
- package/clis/apple-podcasts/search.js +1 -1
- package/clis/apple-podcasts/top.js +1 -1
- package/clis/arxiv/paper.js +1 -1
- package/clis/arxiv/search.js +1 -1
- package/clis/band/mentions.js +3 -3
- package/clis/bbc/news.js +1 -1
- package/clis/bilibili/subtitle.js +2 -2
- package/clis/bloomberg/businessweek.js +1 -1
- package/clis/bloomberg/economics.js +1 -1
- package/clis/bloomberg/industries.js +1 -1
- package/clis/bloomberg/main.js +1 -1
- package/clis/bloomberg/markets.js +1 -1
- package/clis/bloomberg/opinions.js +1 -1
- package/clis/bloomberg/politics.js +1 -1
- package/clis/bloomberg/tech.js +1 -1
- package/clis/boss/search.js +49 -8
- package/clis/boss/search.test.js +78 -0
- package/clis/boss/send.js +3 -3
- package/clis/chatgpt/image.js +37 -8
- package/clis/chatgpt/image.test.js +92 -0
- package/clis/chatgpt/utils.js +39 -6
- package/clis/chatgpt/utils.test.js +63 -0
- package/clis/chatgpt-app/ask.js +1 -1
- package/clis/chatgpt-app/ax.js +4 -2
- package/clis/chatgpt-app/ax.test.js +12 -0
- package/clis/chatgpt-app/model.js +1 -1
- package/clis/chatgpt-app/new.js +1 -1
- package/clis/chatgpt-app/read.js +1 -1
- package/clis/chatgpt-app/send.js +1 -1
- package/clis/chatgpt-app/status.js +1 -1
- package/clis/chatwise/ask.js +2 -2
- package/clis/chatwise/model.js +2 -2
- package/clis/chatwise/send.js +2 -2
- package/clis/claude/ask.js +128 -0
- package/clis/claude/ask.test.js +338 -0
- package/clis/claude/commands.test.js +118 -0
- package/clis/claude/detail.js +29 -0
- package/clis/claude/history.js +31 -0
- package/clis/claude/new.js +21 -0
- package/clis/claude/read.js +24 -0
- package/clis/claude/send.js +41 -0
- package/clis/claude/status.js +24 -0
- package/clis/claude/utils.js +440 -0
- package/clis/claude/utils.test.js +148 -0
- package/clis/codex/ask.js +2 -2
- package/clis/codex/send.js +2 -2
- package/clis/ctrip/search.js +1 -1
- package/clis/ctrip/search.test.js +4 -4
- package/clis/cursor/ask.js +2 -2
- package/clis/cursor/composer.js +2 -2
- package/clis/cursor/send.js +2 -2
- package/clis/deepseek/ask.js +17 -4
- package/clis/deepseek/ask.test.js +46 -0
- package/clis/deepseek/utils.js +55 -16
- package/clis/deepseek/utils.test.js +124 -5
- package/clis/doubao/utils.js +53 -11
- package/clis/doubao/utils.test.js +22 -2
- package/clis/eastmoney/announcement.js +1 -1
- package/clis/eastmoney/convertible.js +1 -1
- package/clis/eastmoney/etf.js +1 -1
- package/clis/eastmoney/holders.js +1 -1
- package/clis/eastmoney/index-board.js +1 -1
- package/clis/eastmoney/kline.js +1 -1
- package/clis/eastmoney/kuaixun.js +1 -1
- package/clis/eastmoney/longhu.js +1 -1
- package/clis/eastmoney/money-flow.js +1 -1
- package/clis/eastmoney/northbound.js +1 -1
- package/clis/eastmoney/quote.js +1 -1
- package/clis/eastmoney/rank.js +1 -1
- package/clis/eastmoney/sectors.js +1 -1
- package/clis/facebook/marketplace-inbox.js +83 -0
- package/clis/facebook/marketplace-listings.js +83 -0
- package/clis/facebook/marketplace.test.js +91 -0
- package/clis/google/news.js +1 -1
- package/clis/google/suggest.js +1 -1
- package/clis/google/trends.js +1 -1
- package/clis/google-scholar/cite.js +74 -0
- package/clis/google-scholar/cite.test.js +47 -0
- package/clis/google-scholar/profile.js +92 -0
- package/clis/google-scholar/profile.test.js +49 -0
- package/clis/google-scholar/search.js +1 -1
- package/clis/google-scholar/search.test.js +15 -0
- package/clis/hf/top.js +1 -1
- package/clis/instagram/collection-create.js +57 -0
- package/clis/instagram/saved.js +21 -7
- package/clis/jd/item.js +679 -47
- package/clis/jd/item.test.js +318 -7
- package/clis/jd/item.test.ts +517 -0
- package/clis/lesswrong/comments.js +1 -1
- package/clis/lesswrong/curated.js +1 -1
- package/clis/lesswrong/frontpage.js +1 -1
- package/clis/lesswrong/new.js +1 -1
- package/clis/lesswrong/read.js +1 -1
- package/clis/lesswrong/sequences.js +1 -1
- package/clis/lesswrong/shortform.js +1 -1
- package/clis/lesswrong/tag.js +1 -1
- package/clis/lesswrong/tags.js +1 -1
- package/clis/lesswrong/top-month.js +1 -1
- package/clis/lesswrong/top-week.js +1 -1
- package/clis/lesswrong/top-year.js +1 -1
- package/clis/lesswrong/top.js +1 -1
- package/clis/lesswrong/user-posts.js +1 -1
- package/clis/lesswrong/user.js +1 -1
- package/clis/paperreview/commands.test.js +6 -6
- package/clis/paperreview/feedback.js +1 -1
- package/clis/paperreview/review.js +1 -1
- package/clis/paperreview/submit.js +1 -1
- package/clis/producthunt/posts.js +1 -1
- package/clis/producthunt/today.js +1 -1
- package/clis/sinablog/search.js +1 -1
- package/clis/sinafinance/news.js +1 -1
- package/clis/sinafinance/stock.js +1 -1
- package/clis/sinafinance/stock.test.js +2 -2
- package/clis/spotify/spotify.js +6 -6
- package/clis/substack/search.js +1 -1
- package/clis/toutiao/articles.js +5 -6
- package/clis/toutiao/articles.test.js +22 -15
- package/clis/twitter/followers.js +2 -2
- package/clis/twitter/following.js +224 -73
- package/clis/twitter/following.test.js +277 -0
- package/clis/twitter/post.js +184 -47
- package/clis/twitter/post.test.js +114 -34
- package/clis/uiverse/_shared.js +63 -4
- package/clis/uiverse/_shared.test.js +7 -0
- package/clis/uiverse/code.js +1 -0
- package/clis/uiverse/navigation.test.js +12 -0
- package/clis/uiverse/preview.js +1 -0
- package/clis/web/read.js +319 -81
- package/clis/web/read.test.js +221 -5
- package/clis/weibo/favorites.js +169 -0
- package/clis/weibo/favorites.test.js +114 -0
- package/clis/weibo/publish.js +282 -0
- package/clis/weibo/publish.test.js +183 -0
- package/clis/weread/ranking.js +1 -1
- package/clis/weread/search-regression.test.js +8 -8
- package/clis/weread/search.js +1 -1
- package/clis/wikipedia/random.js +1 -1
- package/clis/wikipedia/search.js +1 -1
- package/clis/wikipedia/summary.js +1 -1
- package/clis/wikipedia/trending.js +1 -1
- package/clis/xianyu/chat.js +3 -3
- package/clis/xianyu/item.js +2 -2
- package/clis/xianyu/item.test.js +3 -3
- package/clis/xiaohongshu/search.js +17 -2
- package/clis/xiaohongshu/search.test.js +37 -1
- package/clis/xiaoyuzhou/download.js +1 -1
- package/clis/xiaoyuzhou/download.test.js +3 -3
- package/clis/xiaoyuzhou/episode.js +1 -1
- package/clis/xiaoyuzhou/podcast-episodes.js +1 -1
- package/clis/xiaoyuzhou/podcast-episodes.test.js +2 -2
- package/clis/xiaoyuzhou/podcast.js +1 -1
- package/clis/xiaoyuzhou/transcript.js +1 -1
- package/clis/xiaoyuzhou/transcript.test.js +5 -5
- package/clis/yollomi/models.js +1 -1
- package/clis/youtube/channel.js +24 -1
- package/clis/youtube/channel.test.js +59 -0
- package/clis/zhihu/answer.js +21 -162
- package/clis/zhihu/answer.test.js +26 -53
- package/clis/zhihu/collection.js +197 -0
- package/clis/zhihu/collection.test.js +290 -0
- package/clis/zhihu/collections.js +127 -0
- package/clis/zhihu/collections.test.js +182 -0
- package/clis/zhihu/comment.js +24 -305
- package/clis/zhihu/comment.test.js +31 -35
- package/clis/zhihu/favorite.js +44 -182
- package/clis/zhihu/favorite.test.js +30 -167
- package/clis/zhihu/follow.js +25 -56
- package/clis/zhihu/follow.test.js +20 -23
- package/clis/zhihu/like.js +22 -67
- package/clis/zhihu/like.test.js +19 -42
- package/clis/zhihu/search.js +3 -2
- package/clis/zhihu/write-shared.js +8 -1
- package/clis/zhihu/write-shared.test.js +1 -0
- package/clis/zlibrary/commands.test.js +75 -0
- package/clis/zlibrary/info.js +47 -0
- package/clis/zlibrary/search.js +46 -0
- package/clis/zlibrary/utils.js +136 -0
- package/dist/src/adapter-source.d.ts +11 -0
- package/dist/src/adapter-source.js +24 -0
- package/dist/src/adapter-source.test.js +29 -0
- package/dist/src/browser/base-page.d.ts +3 -1
- package/dist/src/browser/base-page.js +76 -1
- package/dist/src/browser/base-page.test.d.ts +1 -0
- package/dist/src/browser/base-page.test.js +74 -0
- package/dist/src/browser/bridge.d.ts +1 -2
- package/dist/src/browser/bridge.js +40 -41
- package/dist/src/browser/cdp.d.ts +1 -0
- package/dist/src/browser/cdp.js +3 -3
- package/dist/src/browser/daemon-client.d.ts +38 -4
- package/dist/src/browser/daemon-client.js +24 -7
- package/dist/src/browser/daemon-client.test.js +49 -0
- package/dist/src/browser/daemon-lifecycle.d.ts +23 -0
- package/dist/src/browser/daemon-lifecycle.js +67 -0
- package/dist/src/browser/daemon-version.d.ts +4 -0
- package/dist/src/browser/daemon-version.js +12 -0
- package/dist/src/browser/errors.js +3 -0
- package/dist/src/browser/errors.test.js +3 -0
- package/dist/src/browser/network-cache.d.ts +1 -0
- package/dist/src/browser/page.d.ts +3 -1
- package/dist/src/browser/page.js +10 -2
- package/dist/src/browser/profile.d.ts +14 -0
- package/dist/src/browser/profile.js +85 -0
- package/dist/src/build-manifest.d.ts +2 -0
- package/dist/src/build-manifest.js +13 -3
- package/dist/src/build-manifest.test.js +20 -2
- package/dist/src/cli.d.ts +6 -0
- package/dist/src/cli.js +477 -35
- package/dist/src/cli.test.js +303 -2
- package/dist/src/commanderAdapter.js +17 -9
- package/dist/src/commanderAdapter.test.js +67 -2
- package/dist/src/commands/daemon.d.ts +2 -0
- package/dist/src/commands/daemon.js +42 -1
- package/dist/src/commands/daemon.test.js +103 -2
- package/dist/src/completion-shared.js +1 -2
- package/dist/src/completion.test.js +3 -2
- package/dist/src/daemon.js +125 -41
- package/dist/src/doctor.d.ts +5 -6
- package/dist/src/doctor.js +77 -19
- package/dist/src/doctor.test.js +117 -0
- package/dist/src/engine.test.js +6 -5
- package/dist/src/errors.d.ts +14 -8
- package/dist/src/errors.js +36 -30
- package/dist/src/errors.test.js +5 -5
- package/dist/src/execution.d.ts +4 -0
- package/dist/src/execution.js +173 -25
- package/dist/src/execution.test.js +171 -1
- package/dist/src/main.js +10 -0
- package/dist/src/observation/artifact.d.ts +16 -0
- package/dist/src/observation/artifact.js +260 -0
- package/dist/src/observation/artifact.test.d.ts +1 -0
- package/dist/src/observation/artifact.test.js +121 -0
- package/dist/src/observation/events.d.ts +89 -0
- package/dist/src/observation/events.js +1 -0
- package/dist/src/observation/index.d.ts +7 -0
- package/dist/src/observation/index.js +7 -0
- package/dist/src/observation/manager.d.ts +9 -0
- package/dist/src/observation/manager.js +27 -0
- package/dist/src/observation/manager.test.d.ts +1 -0
- package/dist/src/observation/manager.test.js +13 -0
- package/dist/src/observation/redaction.d.ts +11 -0
- package/dist/src/observation/redaction.js +81 -0
- package/dist/src/observation/redaction.test.d.ts +1 -0
- package/dist/src/observation/redaction.test.js +32 -0
- package/dist/src/observation/retention.d.ts +32 -0
- package/dist/src/observation/retention.js +160 -0
- package/dist/src/observation/retention.test.d.ts +1 -0
- package/dist/src/observation/retention.test.js +118 -0
- package/dist/src/observation/ring-buffer.d.ts +22 -0
- package/dist/src/observation/ring-buffer.js +45 -0
- package/dist/src/observation/ring-buffer.test.d.ts +1 -0
- package/dist/src/observation/ring-buffer.test.js +22 -0
- package/dist/src/observation/session.d.ts +25 -0
- package/dist/src/observation/session.js +50 -0
- package/dist/src/pipeline/executor.test.js +1 -0
- package/dist/src/pipeline/steps/download.test.js +1 -0
- package/dist/src/pipeline/steps/fetch.js +1 -21
- package/dist/src/pipeline/steps/fetch.test.js +6 -12
- package/dist/src/plugin-scaffold.js +1 -1
- package/dist/src/plugin-scaffold.test.js +1 -1
- package/dist/src/registry.d.ts +40 -9
- package/dist/src/registry.js +3 -1
- package/dist/src/runtime-detect.d.ts +10 -0
- package/dist/src/runtime-detect.js +19 -0
- package/dist/src/runtime-detect.test.js +12 -1
- package/dist/src/runtime.d.ts +2 -0
- package/dist/src/runtime.js +1 -0
- package/dist/src/types.d.ts +22 -0
- package/dist/src/update-check.d.ts +31 -1
- package/dist/src/update-check.js +62 -16
- package/dist/src/update-check.test.js +86 -1
- package/package.json +1 -1
- package/dist/src/diagnostic.d.ts +0 -63
- package/dist/src/diagnostic.js +0 -292
- package/dist/src/diagnostic.test.js +0 -302
- /package/dist/src/{diagnostic.test.d.ts → adapter-source.test.d.ts} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { getRegistry } from '@jackwener/opencli/registry';
|
|
3
3
|
import './post.js';
|
|
4
|
+
|
|
4
5
|
vi.mock('node:fs', async (importOriginal) => {
|
|
5
6
|
const actual = await importOriginal();
|
|
6
7
|
return {
|
|
@@ -12,6 +13,7 @@ vi.mock('node:fs', async (importOriginal) => {
|
|
|
12
13
|
}),
|
|
13
14
|
};
|
|
14
15
|
});
|
|
16
|
+
|
|
15
17
|
vi.mock('node:path', async (importOriginal) => {
|
|
16
18
|
const actual = await importOriginal();
|
|
17
19
|
return {
|
|
@@ -23,92 +25,170 @@ vi.mock('node:path', async (importOriginal) => {
|
|
|
23
25
|
}),
|
|
24
26
|
};
|
|
25
27
|
});
|
|
26
|
-
|
|
28
|
+
|
|
29
|
+
function makePage(evaluateResults = [], overrides = {}) {
|
|
30
|
+
const evaluate = vi.fn();
|
|
31
|
+
for (const result of evaluateResults) {
|
|
32
|
+
evaluate.mockResolvedValueOnce(result);
|
|
33
|
+
}
|
|
34
|
+
evaluate.mockResolvedValue({ ok: true });
|
|
35
|
+
|
|
27
36
|
return {
|
|
28
37
|
goto: vi.fn().mockResolvedValue(undefined),
|
|
29
38
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
30
|
-
evaluate
|
|
39
|
+
evaluate,
|
|
31
40
|
setFileInput: vi.fn().mockResolvedValue(undefined),
|
|
41
|
+
insertText: vi.fn().mockResolvedValue(undefined),
|
|
32
42
|
...overrides,
|
|
33
43
|
};
|
|
34
44
|
}
|
|
45
|
+
|
|
35
46
|
describe('twitter post command', () => {
|
|
36
47
|
const getCommand = () => getRegistry().get('twitter/post');
|
|
37
|
-
|
|
48
|
+
|
|
49
|
+
it('posts text-only tweet successfully through the current compose route', async () => {
|
|
38
50
|
const command = getCommand();
|
|
39
|
-
const page = makePage(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
const page = makePage([
|
|
52
|
+
{ ok: true }, // focus composer
|
|
53
|
+
{ ok: true }, // verify native insertText
|
|
54
|
+
{ ok: true }, // click post
|
|
55
|
+
{ ok: true, message: 'Tweet posted successfully.' }, // verify submit completed
|
|
56
|
+
]);
|
|
57
|
+
|
|
44
58
|
const result = await command.func(page, { text: 'hello world' });
|
|
59
|
+
|
|
45
60
|
expect(result).toEqual([{ status: 'success', message: 'Tweet posted successfully.', text: 'hello world' }]);
|
|
46
|
-
expect(page.goto).toHaveBeenCalledWith('https://x.com/compose/
|
|
61
|
+
expect(page.goto).toHaveBeenCalledWith('https://x.com/compose/post', { waitUntil: 'load', settleMs: 2500 });
|
|
62
|
+
expect(page.wait).toHaveBeenNthCalledWith(1, { selector: '[data-testid="tweetTextarea_0"]', timeout: 15 });
|
|
63
|
+
expect(page.insertText).toHaveBeenCalledWith('hello world');
|
|
47
64
|
});
|
|
65
|
+
|
|
48
66
|
it('returns failed when text area not found', async () => {
|
|
49
67
|
const command = getCommand();
|
|
50
|
-
const page = makePage(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
const page = makePage([
|
|
69
|
+
{ ok: false, message: 'Could not find the tweet composer text area. Are you logged in?' },
|
|
70
|
+
]);
|
|
71
|
+
|
|
54
72
|
const result = await command.func(page, { text: 'hello' });
|
|
55
|
-
|
|
73
|
+
|
|
74
|
+
expect(result).toEqual([{ status: 'failed', message: 'Could not find the tweet composer text area. Are you logged in?', text: 'hello' }]);
|
|
75
|
+
expect(page.insertText).not.toHaveBeenCalled();
|
|
56
76
|
});
|
|
77
|
+
|
|
57
78
|
it('throws when more than 4 images', async () => {
|
|
58
79
|
const command = getCommand();
|
|
59
80
|
const page = makePage();
|
|
60
81
|
await expect(command.func(page, { text: 'hi', images: 'a.png,b.png,c.png,d.png,e.png' })).rejects.toThrow('Too many images: 5 (max 4)');
|
|
61
82
|
});
|
|
83
|
+
|
|
62
84
|
it('throws when image file does not exist', async () => {
|
|
63
85
|
const command = getCommand();
|
|
64
86
|
const page = makePage();
|
|
65
87
|
await expect(command.func(page, { text: 'hi', images: 'missing.png' })).rejects.toThrow('Not a valid file');
|
|
66
88
|
});
|
|
89
|
+
|
|
67
90
|
it('throws on unsupported image format', async () => {
|
|
68
91
|
const command = getCommand();
|
|
69
92
|
const page = makePage();
|
|
70
93
|
await expect(command.func(page, { text: 'hi', images: 'photo.bmp' })).rejects.toThrow('Unsupported image format');
|
|
71
94
|
});
|
|
95
|
+
|
|
72
96
|
it('throws when page.setFileInput is not available', async () => {
|
|
73
97
|
const command = getCommand();
|
|
74
|
-
const page = makePage({
|
|
75
|
-
evaluate: vi.fn().mockResolvedValueOnce({ ok: true }),
|
|
76
|
-
setFileInput: undefined,
|
|
77
|
-
});
|
|
98
|
+
const page = makePage([], { setFileInput: undefined });
|
|
78
99
|
await expect(command.func(page, { text: 'hi', images: 'a.png' })).rejects.toThrow('Browser extension does not support file upload');
|
|
79
100
|
});
|
|
80
|
-
|
|
101
|
+
|
|
102
|
+
it('uploads images before inserting text so media re-renders cannot erase the tweet text', async () => {
|
|
81
103
|
const command = getCommand();
|
|
82
|
-
const page = makePage(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
104
|
+
const page = makePage([
|
|
105
|
+
{ ok: true, previewCount: 2 }, // upload polling returns true
|
|
106
|
+
{ ok: true }, // focus composer
|
|
107
|
+
{ ok: true }, // verify native insertText
|
|
108
|
+
{ ok: true }, // click post
|
|
109
|
+
{ ok: true, message: 'Tweet posted successfully.' }, // verify submit completed
|
|
110
|
+
]);
|
|
111
|
+
|
|
88
112
|
const result = await command.func(page, { text: 'with images', images: 'a.png,b.png' });
|
|
113
|
+
|
|
89
114
|
expect(result).toEqual([{ status: 'success', message: 'Tweet posted successfully.', text: 'with images' }]);
|
|
90
|
-
expect(page.
|
|
91
|
-
|
|
115
|
+
expect(page.wait).toHaveBeenNthCalledWith(2, { selector: 'input[type="file"][data-testid="fileInput"]', timeout: 20 });
|
|
116
|
+
expect(page.setFileInput).toHaveBeenCalledWith(['/abs/a.png', '/abs/b.png'], 'input[type="file"][data-testid="fileInput"]');
|
|
117
|
+
expect(page.insertText).toHaveBeenCalledWith('with images');
|
|
118
|
+
expect(page.setFileInput.mock.invocationCallOrder[0]).toBeLessThan(page.insertText.mock.invocationCallOrder[0]);
|
|
119
|
+
|
|
120
|
+
const uploadScript = page.evaluate.mock.calls[0][0];
|
|
92
121
|
expect(uploadScript).toContain('[data-testid="attachments"]');
|
|
93
|
-
expect(uploadScript).toContain('
|
|
122
|
+
expect(uploadScript).toContain('buttonReady');
|
|
94
123
|
});
|
|
124
|
+
|
|
125
|
+
it('prefers nativeType when available because bridge insert-text can miss Draft.js after media upload', async () => {
|
|
126
|
+
const command = getCommand();
|
|
127
|
+
const nativeType = vi.fn().mockResolvedValue(undefined);
|
|
128
|
+
const page = makePage([
|
|
129
|
+
{ ok: true }, // focus composer
|
|
130
|
+
{ ok: true }, // verify nativeType
|
|
131
|
+
{ ok: true }, // click post
|
|
132
|
+
{ ok: true, message: 'Tweet posted successfully.' },
|
|
133
|
+
], { nativeType });
|
|
134
|
+
|
|
135
|
+
const result = await command.func(page, { text: 'native type' });
|
|
136
|
+
|
|
137
|
+
expect(result).toEqual([{ status: 'success', message: 'Tweet posted successfully.', text: 'native type' }]);
|
|
138
|
+
expect(nativeType).toHaveBeenCalledWith('native type');
|
|
139
|
+
expect(page.insertText).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('treats X success toast as completed even if stale composer nodes remain', async () => {
|
|
143
|
+
const command = getCommand();
|
|
144
|
+
const page = makePage([
|
|
145
|
+
{ ok: true }, // focus composer
|
|
146
|
+
{ ok: true }, // verify native insertText
|
|
147
|
+
{ ok: true }, // click post
|
|
148
|
+
{ ok: true, message: 'Tweet posted successfully.' }, // verify submit completed
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
await command.func(page, { text: 'toast success' });
|
|
152
|
+
|
|
153
|
+
const submitScript = page.evaluate.mock.calls[3][0];
|
|
154
|
+
expect(submitScript).toContain('successToast');
|
|
155
|
+
expect(submitScript).toContain('your post was sent');
|
|
156
|
+
});
|
|
157
|
+
|
|
95
158
|
it('returns failed when image upload times out', async () => {
|
|
96
159
|
const command = getCommand();
|
|
97
|
-
const page = makePage(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
});
|
|
160
|
+
const page = makePage([
|
|
161
|
+
{ ok: false, message: 'Image upload timed out (30s).' },
|
|
162
|
+
]);
|
|
163
|
+
|
|
102
164
|
const result = await command.func(page, { text: 'timeout', images: 'a.png' });
|
|
165
|
+
|
|
103
166
|
expect(result).toEqual([{ status: 'failed', message: 'Image upload timed out (30s).', text: 'timeout' }]);
|
|
167
|
+
expect(page.insertText).not.toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('falls back to DOM insertion when native insertText is unavailable', async () => {
|
|
171
|
+
const command = getCommand();
|
|
172
|
+
const page = makePage([
|
|
173
|
+
{ ok: true }, // focus composer
|
|
174
|
+
{ ok: true }, // fallback DOM insertion
|
|
175
|
+
{ ok: true }, // click post
|
|
176
|
+
{ ok: true, message: 'Tweet posted successfully.' },
|
|
177
|
+
], { insertText: undefined });
|
|
178
|
+
|
|
179
|
+
const result = await command.func(page, { text: 'fallback text' });
|
|
180
|
+
|
|
181
|
+
expect(result).toEqual([{ status: 'success', message: 'Tweet posted successfully.', text: 'fallback text' }]);
|
|
182
|
+
expect(page.evaluate.mock.calls[1][0]).toContain("execCommand('insertText'");
|
|
104
183
|
});
|
|
184
|
+
|
|
105
185
|
it('validates images before navigating to compose page', async () => {
|
|
106
186
|
const command = getCommand();
|
|
107
187
|
const page = makePage();
|
|
108
188
|
await expect(command.func(page, { text: 'hi', images: 'missing.png' })).rejects.toThrow('Not a valid file');
|
|
109
|
-
// Should NOT have navigated since validation happens first
|
|
110
189
|
expect(page.goto).not.toHaveBeenCalled();
|
|
111
190
|
});
|
|
191
|
+
|
|
112
192
|
it('throws when no browser session', async () => {
|
|
113
193
|
const command = getCommand();
|
|
114
194
|
await expect(command.func(null, { text: 'hi' })).rejects.toThrow('Browser session required for twitter post');
|
package/clis/uiverse/_shared.js
CHANGED
|
@@ -273,13 +273,52 @@ export function parseHtmlRootSignature(html) {
|
|
|
273
273
|
};
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
export function getPreviewFallbackTags(signature) {
|
|
277
|
+
const tag = String(signature?.tag || '').toLowerCase();
|
|
278
|
+
if (!tag) return ['label', 'button', 'a', 'div'];
|
|
279
|
+
if (tag === 'input') return ['input', 'label', 'button', 'a', 'div'];
|
|
280
|
+
return [tag];
|
|
281
|
+
}
|
|
282
|
+
|
|
276
283
|
export async function locatePreviewElement(page, html) {
|
|
277
284
|
const signature = parseHtmlRootSignature(html);
|
|
278
285
|
const raw = await page.evaluate(`(async () => {
|
|
279
286
|
const sig = ${JSON.stringify(signature)};
|
|
280
287
|
const viewportWidth = window.innerWidth;
|
|
281
288
|
const viewportHeight = window.innerHeight;
|
|
282
|
-
const fallbackTags =
|
|
289
|
+
const fallbackTags = ${JSON.stringify(getPreviewFallbackTags(signature))};
|
|
290
|
+
|
|
291
|
+
const getSearchRoots = () => {
|
|
292
|
+
const roots = [document];
|
|
293
|
+
const seen = new Set([document]);
|
|
294
|
+
for (let index = 0; index < roots.length; index += 1) {
|
|
295
|
+
const root = roots[index];
|
|
296
|
+
const nodes = root.querySelectorAll ? Array.from(root.querySelectorAll('*')) : [];
|
|
297
|
+
for (const node of nodes) {
|
|
298
|
+
const shadowRoot = node.shadowRoot;
|
|
299
|
+
if (shadowRoot && !seen.has(shadowRoot)) {
|
|
300
|
+
seen.add(shadowRoot);
|
|
301
|
+
roots.push(shadowRoot);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return roots;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const queryAcrossRoots = (selector, limit = 200) => {
|
|
309
|
+
const results = [];
|
|
310
|
+
const seen = new Set();
|
|
311
|
+
for (const root of getSearchRoots()) {
|
|
312
|
+
const matches = root.querySelectorAll ? Array.from(root.querySelectorAll(selector)) : [];
|
|
313
|
+
for (const match of matches) {
|
|
314
|
+
if (seen.has(match)) continue;
|
|
315
|
+
seen.add(match);
|
|
316
|
+
results.push(match);
|
|
317
|
+
if (results.length >= limit) return results;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return results;
|
|
321
|
+
};
|
|
283
322
|
|
|
284
323
|
const isVisible = (element) => {
|
|
285
324
|
if (!element || !(element instanceof Element)) return false;
|
|
@@ -292,6 +331,7 @@ export async function locatePreviewElement(page, html) {
|
|
|
292
331
|
|
|
293
332
|
const scoreCandidate = (element, source) => {
|
|
294
333
|
const rect = element.getBoundingClientRect();
|
|
334
|
+
const style = window.getComputedStyle(element);
|
|
295
335
|
const centerX = rect.x + rect.width / 2;
|
|
296
336
|
const centerY = rect.y + rect.height / 2;
|
|
297
337
|
const area = rect.width * rect.height;
|
|
@@ -299,10 +339,15 @@ export async function locatePreviewElement(page, html) {
|
|
|
299
339
|
if (sig.tag && element.tagName.toLowerCase() === sig.tag) score += 30;
|
|
300
340
|
if (sig.id && element.id === sig.id) score += 120;
|
|
301
341
|
if (sig.classes.length && sig.classes.every((className) => element.classList.contains(className))) score += 120;
|
|
342
|
+
if (sig.tag === 'input' && source === 'shadow-host') score += 220;
|
|
343
|
+
if (sig.tag === 'input' && element.tagName.toLowerCase() === 'label') score += 80;
|
|
344
|
+
if (element.id && element.id.includes('shadow-root')) score += 80;
|
|
302
345
|
if (centerX <= viewportWidth * 0.65) score += 40;
|
|
303
346
|
if (centerY <= viewportHeight * 0.6) score += 40;
|
|
304
347
|
if (area <= viewportWidth * viewportHeight * 0.2) score += 30;
|
|
305
348
|
if (area <= viewportWidth * viewportHeight * 0.05) score += 20;
|
|
349
|
+
if (style.position === 'fixed') score -= 180;
|
|
350
|
+
if (rect.height <= 24 && rect.width >= viewportWidth * 0.8) score -= 120;
|
|
306
351
|
return {
|
|
307
352
|
source,
|
|
308
353
|
tag: element.tagName.toLowerCase(),
|
|
@@ -327,11 +372,25 @@ export async function locatePreviewElement(page, html) {
|
|
|
327
372
|
candidates.push(scoreCandidate(element, source));
|
|
328
373
|
};
|
|
329
374
|
|
|
330
|
-
if (sig.id)
|
|
331
|
-
|
|
375
|
+
if (sig.id) {
|
|
376
|
+
for (const element of queryAcrossRoots('#' + CSS.escape(sig.id), 20)) {
|
|
377
|
+
collect(element, 'id');
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (sig.classes.length) {
|
|
381
|
+
const classSelector = '.' + sig.classes.map((className) => CSS.escape(className)).join('.');
|
|
382
|
+
for (const element of queryAcrossRoots(classSelector, 20)) {
|
|
383
|
+
collect(element, 'classes');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (sig.tag === 'input') {
|
|
387
|
+
for (const element of queryAcrossRoots('#shadow-root-div-ready,[id*="shadow-root"]', 20)) {
|
|
388
|
+
collect(element, 'shadow-host');
|
|
389
|
+
}
|
|
390
|
+
}
|
|
332
391
|
|
|
333
392
|
for (const tagName of fallbackTags) {
|
|
334
|
-
const tagNodes =
|
|
393
|
+
const tagNodes = queryAcrossRoots(tagName, 200);
|
|
335
394
|
for (const node of tagNodes.slice(0, 200)) {
|
|
336
395
|
collect(node, 'tag:' + tagName);
|
|
337
396
|
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
UIVERSE_BASE_URL,
|
|
4
4
|
parseComponentInput,
|
|
5
5
|
parseHtmlRootSignature,
|
|
6
|
+
getPreviewFallbackTags,
|
|
6
7
|
inferLanguage,
|
|
7
8
|
getCodeLength,
|
|
8
9
|
} from './_shared.js';
|
|
@@ -39,6 +40,12 @@ describe('uiverse shared helpers', () => {
|
|
|
39
40
|
expect(parseHtmlRootSignature('')).toEqual({ tag: null, id: null, classes: [] });
|
|
40
41
|
});
|
|
41
42
|
|
|
43
|
+
it('uses broader preview fallback tags for input roots', () => {
|
|
44
|
+
expect(getPreviewFallbackTags({ tag: 'input' })).toEqual(['input', 'label', 'button', 'a', 'div']);
|
|
45
|
+
expect(getPreviewFallbackTags({ tag: 'label' })).toEqual(['label']);
|
|
46
|
+
expect(getPreviewFallbackTags({ tag: null })).toEqual(['label', 'button', 'a', 'div']);
|
|
47
|
+
});
|
|
48
|
+
|
|
42
49
|
it('infers the language from target and metadata', () => {
|
|
43
50
|
expect(inferLanguage('react', {})).toBe('tsx');
|
|
44
51
|
expect(inferLanguage('vue', {})).toBe('vue');
|
package/clis/uiverse/code.js
CHANGED
|
@@ -14,6 +14,7 @@ cli({
|
|
|
14
14
|
domain: 'uiverse.io',
|
|
15
15
|
strategy: Strategy.PUBLIC,
|
|
16
16
|
browser: true,
|
|
17
|
+
navigateBefore: 'https://uiverse.io',
|
|
17
18
|
args: [
|
|
18
19
|
{ name: 'input', type: 'str', required: true, positional: true, help: 'Uiverse URL or author/slug identifier' },
|
|
19
20
|
{ name: 'target', type: 'str', required: true, choices: ['html', 'css', 'react', 'vue'], help: 'Code target to export' },
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { getRegistry } from '@jackwener/opencli/registry';
|
|
3
|
+
import './code.js';
|
|
4
|
+
import './preview.js';
|
|
5
|
+
|
|
6
|
+
describe('uiverse navigateBefore hardening', () => {
|
|
7
|
+
it.each(['uiverse/code', 'uiverse/preview'])('%s starts from uiverse home instead of about:blank', (name) => {
|
|
8
|
+
const cmd = getRegistry().get(name);
|
|
9
|
+
expect(cmd).toBeDefined();
|
|
10
|
+
expect(cmd.navigateBefore).toBe('https://uiverse.io');
|
|
11
|
+
});
|
|
12
|
+
});
|
package/clis/uiverse/preview.js
CHANGED
|
@@ -14,6 +14,7 @@ cli({
|
|
|
14
14
|
domain: 'uiverse.io',
|
|
15
15
|
strategy: Strategy.PUBLIC,
|
|
16
16
|
browser: true,
|
|
17
|
+
navigateBefore: 'https://uiverse.io',
|
|
17
18
|
args: [
|
|
18
19
|
{ name: 'input', type: 'str', required: true, positional: true, help: 'Uiverse URL or author/slug identifier' },
|
|
19
20
|
{ name: 'output', type: 'str', required: false, help: 'Output image path (defaults to a temp file)' },
|