Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions clis/twitter/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,12 @@ cli({
return { ok: false, message: e.toString() };
}
})()`);
if (result.ok)
await page.wait(2);
if (!result.ok) {
throw new CommandExecutionError(result.message, 'Nothing changed. Open the profile in the browser and retry.');
}
await page.wait(2);
return [{
status: result.ok ? 'success' : 'failed',
status: 'success',
message: result.message
}];
}
Expand Down
58 changes: 58 additions & 0 deletions clis/twitter/block.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { describe, expect, it } from 'vitest';
import { CommandExecutionError } from '@jackwener/opencli/errors';
import { getRegistry } from '@jackwener/opencli/registry';
import './block.js';
import { createPageMock } from '../test-utils.js';

describe('twitter block command', () => {
it('navigates to the profile URL and reports success when the block script confirms', async () => {
const cmd = getRegistry().get('twitter/block');
expect(cmd?.func).toBeTypeOf('function');
const page = createPageMock([
{ ok: true, message: 'Successfully blocked @alice.' },
]);
const result = await cmd.func(page, {
username: 'alice',
});
expect(page.goto).toHaveBeenCalledWith('https://x.com/alice');
expect(page.wait).toHaveBeenNthCalledWith(1, { selector: '[data-testid="primaryColumn"]' });
expect(page.wait).toHaveBeenNthCalledWith(2, 2);
const script = page.evaluate.mock.calls[0][0];
// Idempotency probe: when already blocking ([data-testid$="-unblock"] present),
// the script returns ok:true with an "already blocking" message.
expect(script).toContain('[data-testid$="-unblock"]');
expect(script).toContain('[data-testid="userActions"]');
expect(script).toContain("includes('Block')");
expect(script).toContain('blockItem.click()');
expect(script).toContain('[data-testid="confirmationSheetConfirm"]');
expect(result).toEqual([
{ status: 'success', message: 'Successfully blocked @alice.' },
]);
});

it('typed-fails without re-waiting when the block script reports a UI mismatch', async () => {
const cmd = getRegistry().get('twitter/block');
const page = createPageMock([
{
ok: false,
message: 'Could not find user actions menu. Are you logged in?',
},
]);
await expect(cmd.func(page, {
username: 'alice',
})).rejects.toMatchObject({
name: 'CommandExecutionError',
code: 'COMMAND_EXEC',
exitCode: 1,
message: 'Could not find user actions menu. Are you logged in?',
});
expect(page.wait).toHaveBeenCalledTimes(1);
});

it('throws CommandExecutionError when no page is provided', async () => {
const cmd = getRegistry().get('twitter/block');
await expect(cmd.func(undefined, {
username: 'alice',
})).rejects.toThrow(CommandExecutionError);
});
});
8 changes: 5 additions & 3 deletions clis/twitter/bookmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,12 @@ cli({
return { ok: false, message: e.toString() };
}
})()`);
if (result.ok)
await page.wait(2);
if (!result.ok) {
throw new CommandExecutionError(result.message, 'Nothing changed. Open the tweet in the browser and retry.');
}
await page.wait(2);
return [{
status: result.ok ? 'success' : 'failed',
status: 'success',
message: result.message
}];
}
Expand Down
15 changes: 7 additions & 8 deletions clis/twitter/bookmark.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,22 @@ describe('twitter bookmark command', () => {
]);
});

it('returns a failed row without re-waiting when the bookmark script reports a UI mismatch', async () => {
it('typed-fails without re-waiting when the bookmark script reports a UI mismatch', async () => {
const cmd = getRegistry().get('twitter/bookmark');
const page = createPageMock([
{
ok: false,
message: 'Could not find Bookmark button on the requested tweet. Are you logged in?',
},
]);
const result = await cmd.func(page, {
await expect(cmd.func(page, {
url: 'https://x.com/alice/status/2040254679301718161',
})).rejects.toMatchObject({
name: 'CommandExecutionError',
code: 'COMMAND_EXEC',
exitCode: 1,
message: 'Could not find Bookmark button on the requested tweet. Are you logged in?',
});
expect(result).toEqual([
{
status: 'failed',
message: 'Could not find Bookmark button on the requested tweet. Are you logged in?',
},
]);
expect(page.wait).toHaveBeenCalledTimes(1);
});

Expand Down
8 changes: 4 additions & 4 deletions clis/twitter/delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ cli({
await page.goto(target.url);
await page.wait({ selector: '[data-testid="primaryColumn"]' }); // Wait for tweet to load completely
const result = unwrapBrowserResult(await page.evaluate(buildDeleteScript(target.id)));
if (result.ok) {
// Wait for the deletion request to be processed
await page.wait(2);
if (!result.ok) {
throw new CommandExecutionError(result.message, 'Nothing changed. Open the tweet in the browser and retry.');
}
await page.wait(2);
return [{
status: result.ok ? 'success' : 'failed',
status: 'success',
message: result.message
}];
}
Expand Down
15 changes: 7 additions & 8 deletions clis/twitter/delete.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('twitter delete command', () => {
},
]);
});
it('passes through matched-tweet lookup failures', async () => {
it('typed-fails on matched-tweet lookup failures', async () => {
const cmd = getRegistry().get('twitter/delete');
expect(cmd?.func).toBeTypeOf('function');
const page = {
Expand All @@ -60,15 +60,14 @@ describe('twitter delete command', () => {
message: 'Could not find the tweet card matching the requested URL.',
}),
};
const result = await cmd.func(page, {
await expect(cmd.func(page, {
url: 'https://x.com/alice/status/2040254679301718161',
})).rejects.toMatchObject({
name: 'CommandExecutionError',
code: 'COMMAND_EXEC',
exitCode: 1,
message: 'Could not find the tweet card matching the requested URL.',
});
expect(result).toEqual([
{
status: 'failed',
message: 'Could not find the tweet card matching the requested URL.',
},
]);
expect(page.wait).toHaveBeenCalledTimes(1);
});
it('unwraps Browser Bridge evaluate envelopes before checking delete success', async () => {
Expand Down
8 changes: 5 additions & 3 deletions clis/twitter/follow.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ cli({
return { ok: false, message: e.toString() };
}
})()`);
if (result.ok)
await page.wait(2);
if (!result.ok) {
throw new CommandExecutionError(result.message, 'Nothing changed. Open the profile in the browser and retry.');
}
await page.wait(2);
return [{
status: result.ok ? 'success' : 'failed',
status: 'success',
message: result.message
}];
}
Expand Down
56 changes: 56 additions & 0 deletions clis/twitter/follow.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe, expect, it } from 'vitest';
import { CommandExecutionError } from '@jackwener/opencli/errors';
import { getRegistry } from '@jackwener/opencli/registry';
import './follow.js';
import { createPageMock } from '../test-utils.js';

describe('twitter follow command', () => {
it('navigates to the profile URL and reports success when the follow script confirms', async () => {
const cmd = getRegistry().get('twitter/follow');
expect(cmd?.func).toBeTypeOf('function');
const page = createPageMock([
{ ok: true, message: 'Successfully followed @alice.' },
]);
const result = await cmd.func(page, {
username: 'alice',
});
expect(page.goto).toHaveBeenCalledWith('https://x.com/alice');
expect(page.wait).toHaveBeenNthCalledWith(1, { selector: '[data-testid="primaryColumn"]' });
expect(page.wait).toHaveBeenNthCalledWith(2, 2);
const script = page.evaluate.mock.calls[0][0];
// Idempotency probe: when already following ([data-testid$="-unfollow"] present),
// the script returns ok:true with an "already following" message.
expect(script).toContain('[data-testid$="-unfollow"]');
expect(script).toContain('[data-testid$="-follow"]');
expect(script).toContain('followBtn.click()');
expect(result).toEqual([
{ status: 'success', message: 'Successfully followed @alice.' },
]);
});

it('typed-fails without re-waiting when the follow script reports a UI mismatch', async () => {
const cmd = getRegistry().get('twitter/follow');
const page = createPageMock([
{
ok: false,
message: 'Could not find Follow button. Are you logged in?',
},
]);
await expect(cmd.func(page, {
username: 'alice',
})).rejects.toMatchObject({
name: 'CommandExecutionError',
code: 'COMMAND_EXEC',
exitCode: 1,
message: 'Could not find Follow button. Are you logged in?',
});
expect(page.wait).toHaveBeenCalledTimes(1);
});

it('throws CommandExecutionError when no page is provided', async () => {
const cmd = getRegistry().get('twitter/follow');
await expect(cmd.func(undefined, {
username: 'alice',
})).rejects.toThrow(CommandExecutionError);
});
});
8 changes: 5 additions & 3 deletions clis/twitter/hide-reply.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@ cli({
return { ok: false, message: e.toString() };
}
})()`);
if (result.ok)
await page.wait(2);
if (!result.ok) {
throw new CommandExecutionError(result.message, 'Nothing changed. Open the tweet in the browser and retry.');
}
await page.wait(2);
return [{
status: result.ok ? 'success' : 'failed',
status: 'success',
message: result.message
}];
}
Expand Down
15 changes: 7 additions & 8 deletions clis/twitter/hide-reply.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,22 @@ describe('twitter hide-reply command', () => {
]);
});

it('returns a failed row without re-waiting when the hide-reply script reports a UI mismatch', async () => {
it('typed-fails without re-waiting when the hide-reply script reports a UI mismatch', async () => {
const cmd = getRegistry().get('twitter/hide-reply');
const page = createPageMock([
{
ok: false,
message: 'Could not find "Hide reply" option. This may not be a reply on your tweet.',
},
]);
const result = await cmd.func(page, {
await expect(cmd.func(page, {
url: 'https://x.com/alice/status/2040254679301718161',
})).rejects.toMatchObject({
name: 'CommandExecutionError',
code: 'COMMAND_EXEC',
exitCode: 1,
message: 'Could not find "Hide reply" option. This may not be a reply on your tweet.',
});
expect(result).toEqual([
{
status: 'failed',
message: 'Could not find "Hide reply" option. This may not be a reply on your tweet.',
},
]);
expect(page.wait).toHaveBeenCalledTimes(1);
});

Expand Down
8 changes: 4 additions & 4 deletions clis/twitter/like.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ cli({
return { ok: false, message: e.toString() };
}
})()`);
if (result.ok) {
// Wait for the like network request to be processed
await page.wait(2);
if (!result.ok) {
throw new CommandExecutionError(result.message, 'Nothing changed. Open the tweet in the browser and retry.');
}
await page.wait(2);
return [{
status: result.ok ? 'success' : 'failed',
status: 'success',
message: result.message
}];
}
Expand Down
23 changes: 15 additions & 8 deletions clis/twitter/like.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,30 @@ describe('twitter like command', () => {
]);
});

it('returns a failed row without re-waiting when the like script reports a UI mismatch', async () => {
it('keeps an already-liked tweet a success rather than a failure', async () => {
const cmd = getRegistry().get('twitter/like');
const page = createPageMock([{ ok: true, message: 'Tweet is already liked.' }]);

await expect(cmd.func(page, { url: 'https://x.com/alice/status/2040254679301718161' }))
.resolves.toEqual([{ status: 'success', message: 'Tweet is already liked.' }]);
});

it('typed-fails without re-waiting when the like script reports a UI mismatch', async () => {
const cmd = getRegistry().get('twitter/like');
const page = createPageMock([
{
ok: false,
message: 'Could not find the Like button on this tweet after waiting 10 seconds. Are you logged in?',
},
]);
const result = await cmd.func(page, {
await expect(cmd.func(page, {
url: 'https://x.com/alice/status/2040254679301718161',
})).rejects.toMatchObject({
name: 'CommandExecutionError',
code: 'COMMAND_EXEC',
exitCode: 1,
message: 'Could not find the Like button on this tweet after waiting 10 seconds. Are you logged in?',
});
expect(result).toEqual([
{
status: 'failed',
message: 'Could not find the Like button on this tweet after waiting 10 seconds. Are you logged in?',
},
]);
// Only the primaryColumn wait should run when ok is false.
expect(page.wait).toHaveBeenCalledTimes(1);
});
Expand Down
Loading