beads-ui 0.1.0 → 0.1.2
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/CHANGES.md +8 -0
- package/README.md +7 -3
- package/package.json +12 -2
- package/.beads/issues.jsonl +0 -107
- package/.editorconfig +0 -10
- package/.eslintrc.json +0 -36
- package/.github/workflows/ci.yml +0 -38
- package/.prettierignore +0 -5
- package/AGENTS.md +0 -85
- package/app/data/providers.test.js +0 -126
- package/app/main.board-switch.test.js +0 -94
- package/app/main.deep-link.test.js +0 -64
- package/app/main.live-updates.test.js +0 -229
- package/app/main.test.js +0 -17
- package/app/main.theme.test.js +0 -41
- package/app/main.view-sync.test.js +0 -54
- package/app/protocol.test.js +0 -57
- package/app/router.test.js +0 -34
- package/app/state.test.js +0 -21
- package/app/utils/markdown.test.js +0 -103
- package/app/utils/type-badge.test.js +0 -30
- package/app/views/board.test.js +0 -184
- package/app/views/detail.acceptance-notes.test.js +0 -67
- package/app/views/detail.assignee.test.js +0 -161
- package/app/views/detail.deps.test.js +0 -97
- package/app/views/detail.edits.test.js +0 -146
- package/app/views/detail.labels.test.js +0 -73
- package/app/views/detail.priority.test.js +0 -86
- package/app/views/detail.test.js +0 -188
- package/app/views/detail.ui47.test.js +0 -78
- package/app/views/epics.test.js +0 -283
- package/app/views/list.inline-edits.test.js +0 -84
- package/app/views/list.test.js +0 -479
- package/app/views/nav.test.js +0 -43
- package/app/ws.test.js +0 -168
- package/eslint.config.js +0 -59
- package/media/bdui-board.png +0 -0
- package/media/bdui-epics.png +0 -0
- package/media/bdui-issues.png +0 -0
- package/prettier.config.js +0 -13
- package/server/app.test.js +0 -29
- package/server/bd.test.js +0 -93
- package/server/cli/cli.test.js +0 -109
- package/server/cli/commands.integration.test.js +0 -155
- package/server/cli/commands.unit.test.js +0 -94
- package/server/cli/open.test.js +0 -26
- package/server/db.test.js +0 -70
- package/server/protocol.test.js +0 -87
- package/server/watcher.test.js +0 -100
- package/server/ws.handlers.test.js +0 -174
- package/server/ws.labels.test.js +0 -95
- package/server/ws.mutations.test.js +0 -261
- package/server/ws.subscriptions.test.js +0 -116
- package/server/ws.test.js +0 -52
- package/test/setup-vitest.js +0 -12
- package/tsconfig.json +0 -23
- package/vitest.config.mjs +0 -14
package/server/protocol.test.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
MESSAGE_TYPES,
|
|
4
|
-
PROTOCOL_VERSION,
|
|
5
|
-
decodeReply,
|
|
6
|
-
decodeRequest,
|
|
7
|
-
isMessageType,
|
|
8
|
-
isReply,
|
|
9
|
-
isRequest,
|
|
10
|
-
makeError,
|
|
11
|
-
makeOk,
|
|
12
|
-
makeRequest
|
|
13
|
-
} from './protocol.js';
|
|
14
|
-
|
|
15
|
-
describe('server/protocol', () => {
|
|
16
|
-
test('isMessageType returns true for known type', () => {
|
|
17
|
-
// execution
|
|
18
|
-
const res = isMessageType('list-issues');
|
|
19
|
-
|
|
20
|
-
// assertion
|
|
21
|
-
expect(res).toBe(true);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('isMessageType returns false for unknown type', () => {
|
|
25
|
-
// execution
|
|
26
|
-
const res = isMessageType('not-a-type');
|
|
27
|
-
|
|
28
|
-
// assertion
|
|
29
|
-
expect(res).toBe(false);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('makeRequest and decodeRequest round-trip', () => {
|
|
33
|
-
// setup
|
|
34
|
-
const req = makeRequest('show-issue', { id: 'UI-9' }, 'r-9');
|
|
35
|
-
|
|
36
|
-
// execution
|
|
37
|
-
const decoded = decodeRequest(JSON.parse(JSON.stringify(req)));
|
|
38
|
-
|
|
39
|
-
// assertion
|
|
40
|
-
expect(isRequest(req)).toBe(true);
|
|
41
|
-
expect(decoded.id).toBe('r-9');
|
|
42
|
-
expect(decoded.type).toBe('show-issue');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('makeOk and makeError create valid replies', () => {
|
|
46
|
-
// setup
|
|
47
|
-
const req = makeRequest('list-issues', undefined, 'r-10');
|
|
48
|
-
|
|
49
|
-
// execution
|
|
50
|
-
const ok = makeOk(req, [{ id: 'UI-1' }]);
|
|
51
|
-
const err = makeError(req, 'boom', 'Something went wrong');
|
|
52
|
-
|
|
53
|
-
// assertion
|
|
54
|
-
expect(isReply(ok)).toBe(true);
|
|
55
|
-
expect(isReply(err)).toBe(true);
|
|
56
|
-
expect(ok.ok).toBe(true);
|
|
57
|
-
expect(err.ok).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('decodeReply accepts ok and error envelopes', () => {
|
|
61
|
-
// setup
|
|
62
|
-
const req = makeRequest('edit-text', { id: 'UI-1', text: 'x' }, 'r-11');
|
|
63
|
-
const ok = makeOk(req, { id: 'UI-1' });
|
|
64
|
-
const err = makeError(req, 'validation', 'Invalid');
|
|
65
|
-
|
|
66
|
-
// execution
|
|
67
|
-
const ok2 = decodeReply(JSON.parse(JSON.stringify(ok)));
|
|
68
|
-
const err2 = decodeReply(JSON.parse(JSON.stringify(err)));
|
|
69
|
-
|
|
70
|
-
// assertion
|
|
71
|
-
expect(ok2.ok).toBe(true);
|
|
72
|
-
expect(err2.ok).toBe(false);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('invalid envelopes throw on decode', () => {
|
|
76
|
-
// execution + assertion
|
|
77
|
-
expect(() => decodeRequest({})).toThrow();
|
|
78
|
-
expect(() => decodeReply({ ok: true })).toThrow();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('exports protocol constants', () => {
|
|
82
|
-
// assertion
|
|
83
|
-
expect(typeof PROTOCOL_VERSION).toBe('string');
|
|
84
|
-
expect(Array.isArray(MESSAGE_TYPES)).toBe(true);
|
|
85
|
-
expect(MESSAGE_TYPES.length).toBeGreaterThan(0);
|
|
86
|
-
});
|
|
87
|
-
});
|
package/server/watcher.test.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
-
import { watchDb } from './watcher.js';
|
|
3
|
-
|
|
4
|
-
/** @type {{ dir: string, cb: (event: string, filename?: string) => void, w: { close: () => void } }[]} */
|
|
5
|
-
const watchers = [];
|
|
6
|
-
|
|
7
|
-
vi.mock('node:fs', () => {
|
|
8
|
-
const watch = vi.fn((dir, _opts, cb) => {
|
|
9
|
-
// Minimal event emitter interface for FSWatcher
|
|
10
|
-
const handlers = /** @type {{ close: Array<() => void> }} */ ({
|
|
11
|
-
close: []
|
|
12
|
-
});
|
|
13
|
-
const w = {
|
|
14
|
-
close: () => handlers.close.forEach((fn) => fn())
|
|
15
|
-
};
|
|
16
|
-
watchers.push({ dir, cb, w });
|
|
17
|
-
return /** @type {any} */ (w);
|
|
18
|
-
});
|
|
19
|
-
return { default: { watch }, watch };
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
watchers.length = 0;
|
|
24
|
-
vi.useFakeTimers();
|
|
25
|
-
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
vi.useRealTimers();
|
|
30
|
-
vi.restoreAllMocks();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe('watchDb', () => {
|
|
34
|
-
test('debounces rapid change events', () => {
|
|
35
|
-
const calls = [];
|
|
36
|
-
const handle = watchDb('/repo', (p) => calls.push(p), {
|
|
37
|
-
debounce_ms: 100,
|
|
38
|
-
explicit_db: '/repo/.beads/ui.db'
|
|
39
|
-
});
|
|
40
|
-
expect(watchers.length).toBe(1);
|
|
41
|
-
const { cb } = watchers[0];
|
|
42
|
-
|
|
43
|
-
// Fire multiple changes in quick succession
|
|
44
|
-
cb('change', 'ui.db');
|
|
45
|
-
cb('change', 'ui.db');
|
|
46
|
-
cb('rename', 'ui.db');
|
|
47
|
-
|
|
48
|
-
// Nothing yet until debounce passes
|
|
49
|
-
expect(calls.length).toBe(0);
|
|
50
|
-
vi.advanceTimersByTime(99);
|
|
51
|
-
expect(calls.length).toBe(0);
|
|
52
|
-
vi.advanceTimersByTime(1);
|
|
53
|
-
expect(calls.length).toBe(1);
|
|
54
|
-
|
|
55
|
-
// Cleanup
|
|
56
|
-
handle.close();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('ignores other filenames', () => {
|
|
60
|
-
const calls = [];
|
|
61
|
-
const handle = watchDb('/repo', (p) => calls.push(p), {
|
|
62
|
-
debounce_ms: 50,
|
|
63
|
-
explicit_db: '/repo/.beads/ui.db'
|
|
64
|
-
});
|
|
65
|
-
const { cb } = watchers[0];
|
|
66
|
-
cb('change', 'something-else.db');
|
|
67
|
-
vi.advanceTimersByTime(60);
|
|
68
|
-
expect(calls.length).toBe(0);
|
|
69
|
-
handle.close();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('rebind attaches to new db path', () => {
|
|
73
|
-
const calls = [];
|
|
74
|
-
const handle = watchDb('/repo', (p) => calls.push(p), {
|
|
75
|
-
debounce_ms: 50,
|
|
76
|
-
explicit_db: '/repo/.beads/ui.db'
|
|
77
|
-
});
|
|
78
|
-
expect(watchers.length).toBe(1);
|
|
79
|
-
const first = watchers[0];
|
|
80
|
-
|
|
81
|
-
// Rebind to a different DB path
|
|
82
|
-
handle.rebind({ explicit_db: '/other/.beads/alt.db' });
|
|
83
|
-
|
|
84
|
-
// A new watcher is created
|
|
85
|
-
expect(watchers.length).toBe(2);
|
|
86
|
-
const second = watchers[1];
|
|
87
|
-
|
|
88
|
-
// Old watcher should ignore new file name
|
|
89
|
-
first.cb('change', 'ui.db');
|
|
90
|
-
vi.advanceTimersByTime(60);
|
|
91
|
-
expect(calls.length).toBe(0);
|
|
92
|
-
|
|
93
|
-
// New watcher reacts
|
|
94
|
-
second.cb('change', 'alt.db');
|
|
95
|
-
vi.advanceTimersByTime(60);
|
|
96
|
-
expect(calls.length).toBe(1);
|
|
97
|
-
|
|
98
|
-
handle.close();
|
|
99
|
-
});
|
|
100
|
-
});
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, vi } from 'vitest';
|
|
2
|
-
import { runBdJson } from './bd.js';
|
|
3
|
-
import { handleMessage } from './ws.js';
|
|
4
|
-
|
|
5
|
-
vi.mock('./bd.js', () => ({ runBdJson: vi.fn() }));
|
|
6
|
-
|
|
7
|
-
function makeStubSocket() {
|
|
8
|
-
return {
|
|
9
|
-
sent: /** @type {string[]} */ ([]),
|
|
10
|
-
readyState: 1,
|
|
11
|
-
OPEN: 1,
|
|
12
|
-
/** @param {string} msg */
|
|
13
|
-
send(msg) {
|
|
14
|
-
this.sent.push(String(msg));
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe('ws handlers: list/show', () => {
|
|
20
|
-
test('list-issues forwards payload from bd', async () => {
|
|
21
|
-
const mocked = /** @type {import('vitest').Mock} */ (runBdJson);
|
|
22
|
-
mocked.mockResolvedValueOnce({ code: 0, stdoutJson: [{ id: 'UI-1' }] });
|
|
23
|
-
const ws = makeStubSocket();
|
|
24
|
-
const req = {
|
|
25
|
-
id: 'r1',
|
|
26
|
-
type: 'list-issues',
|
|
27
|
-
payload: { filters: { status: 'open' } }
|
|
28
|
-
};
|
|
29
|
-
await handleMessage(
|
|
30
|
-
/** @type {any} */ (ws),
|
|
31
|
-
Buffer.from(JSON.stringify(req))
|
|
32
|
-
);
|
|
33
|
-
const obj = JSON.parse(ws.sent[ws.sent.length - 1]);
|
|
34
|
-
expect(obj.ok).toBe(true);
|
|
35
|
-
expect(Array.isArray(obj.payload)).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('list-issues with filters.ready uses bd "ready"', async () => {
|
|
39
|
-
const mocked = /** @type {import('vitest').Mock} */ (runBdJson);
|
|
40
|
-
mocked.mockResolvedValueOnce({ code: 0, stdoutJson: [{ id: 'UI-2' }] });
|
|
41
|
-
const ws = makeStubSocket();
|
|
42
|
-
const req = {
|
|
43
|
-
id: 'r1a',
|
|
44
|
-
type: 'list-issues',
|
|
45
|
-
payload: { filters: { ready: true } }
|
|
46
|
-
};
|
|
47
|
-
await handleMessage(
|
|
48
|
-
/** @type {any} */ (ws),
|
|
49
|
-
Buffer.from(JSON.stringify(req))
|
|
50
|
-
);
|
|
51
|
-
// Ensure we called the ready command
|
|
52
|
-
const call = mocked.mock.calls[mocked.mock.calls.length - 1];
|
|
53
|
-
expect(Array.isArray(call[0])).toBe(true);
|
|
54
|
-
expect(call[0][0]).toBe('ready');
|
|
55
|
-
|
|
56
|
-
const obj = JSON.parse(ws.sent[ws.sent.length - 1]);
|
|
57
|
-
expect(obj.ok).toBe(true);
|
|
58
|
-
expect(Array.isArray(obj.payload)).toBe(true);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('show-issue returns error on missing id', async () => {
|
|
62
|
-
const ws = makeStubSocket();
|
|
63
|
-
const req = { id: 'r2', type: 'show-issue', payload: {} };
|
|
64
|
-
await handleMessage(
|
|
65
|
-
/** @type {any} */ (ws),
|
|
66
|
-
Buffer.from(JSON.stringify(req))
|
|
67
|
-
);
|
|
68
|
-
const obj = JSON.parse(ws.sent[ws.sent.length - 1]);
|
|
69
|
-
expect(obj.ok).toBe(false);
|
|
70
|
-
expect(obj.error.code).toBe('bad_request');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('show-issue forwards bd JSON on success', async () => {
|
|
74
|
-
const mocked = /** @type {import('vitest').Mock} */ (runBdJson);
|
|
75
|
-
mocked.mockResolvedValueOnce({
|
|
76
|
-
code: 0,
|
|
77
|
-
stdoutJson: { id: 'UI-9', title: 'X' }
|
|
78
|
-
});
|
|
79
|
-
const ws = makeStubSocket();
|
|
80
|
-
const req = { id: 'r3', type: 'show-issue', payload: { id: 'UI-9' } };
|
|
81
|
-
await handleMessage(
|
|
82
|
-
/** @type {any} */ (ws),
|
|
83
|
-
Buffer.from(JSON.stringify(req))
|
|
84
|
-
);
|
|
85
|
-
const obj = JSON.parse(ws.sent[ws.sent.length - 1]);
|
|
86
|
-
expect(obj.ok).toBe(true);
|
|
87
|
-
expect(obj.payload.id).toBe('UI-9');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('show-issue unwraps single-element arrays from bd', async () => {
|
|
91
|
-
const mocked = /** @type {import('vitest').Mock} */ (runBdJson);
|
|
92
|
-
mocked.mockResolvedValueOnce({
|
|
93
|
-
code: 0,
|
|
94
|
-
stdoutJson: [{ id: 'UI-9', title: 'X' }]
|
|
95
|
-
});
|
|
96
|
-
const ws = makeStubSocket();
|
|
97
|
-
const req = { id: 'r3a', type: 'show-issue', payload: { id: 'UI-9' } };
|
|
98
|
-
await handleMessage(
|
|
99
|
-
/** @type {any} */ (ws),
|
|
100
|
-
Buffer.from(JSON.stringify(req))
|
|
101
|
-
);
|
|
102
|
-
const obj = JSON.parse(ws.sent[ws.sent.length - 1]);
|
|
103
|
-
expect(obj.ok).toBe(true);
|
|
104
|
-
expect(obj.payload && obj.payload.id).toBe('UI-9');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('show-issue returns not_found when bd returns empty array', async () => {
|
|
108
|
-
const mocked = /** @type {import('vitest').Mock} */ (runBdJson);
|
|
109
|
-
mocked.mockResolvedValueOnce({ code: 0, stdoutJson: [] });
|
|
110
|
-
const ws = makeStubSocket();
|
|
111
|
-
const req = { id: 'r3b', type: 'show-issue', payload: { id: 'X' } };
|
|
112
|
-
await handleMessage(
|
|
113
|
-
/** @type {any} */ (ws),
|
|
114
|
-
Buffer.from(JSON.stringify(req))
|
|
115
|
-
);
|
|
116
|
-
const obj = JSON.parse(ws.sent[ws.sent.length - 1]);
|
|
117
|
-
expect(obj.ok).toBe(false);
|
|
118
|
-
expect(obj.error && obj.error.code).toBe('not_found');
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('epic-status forwards bd epic status', async () => {
|
|
122
|
-
const mocked = /** @type {import('vitest').Mock} */ (runBdJson);
|
|
123
|
-
mocked.mockResolvedValueOnce({ code: 0, stdoutJson: [] });
|
|
124
|
-
const ws = makeStubSocket();
|
|
125
|
-
const req = { id: 're', type: /** @type {any} */ ('epic-status') };
|
|
126
|
-
await handleMessage(
|
|
127
|
-
/** @type {any} */ (ws),
|
|
128
|
-
Buffer.from(JSON.stringify(req))
|
|
129
|
-
);
|
|
130
|
-
const call = mocked.mock.calls[mocked.mock.calls.length - 1];
|
|
131
|
-
expect(Array.isArray(call[0])).toBe(true);
|
|
132
|
-
expect(call[0][0]).toBe('epic');
|
|
133
|
-
expect(call[0][1]).toBe('status');
|
|
134
|
-
const obj = JSON.parse(ws.sent[ws.sent.length - 1]);
|
|
135
|
-
expect(obj.ok).toBe(true);
|
|
136
|
-
expect(Array.isArray(obj.payload)).toBe(true);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('list-issues supports limit passthrough', async () => {
|
|
140
|
-
const mocked = /** @type {import('vitest').Mock} */ (runBdJson);
|
|
141
|
-
mocked.mockResolvedValueOnce({ code: 0, stdoutJson: [] });
|
|
142
|
-
const ws = makeStubSocket();
|
|
143
|
-
const req = {
|
|
144
|
-
id: 'rlim',
|
|
145
|
-
type: 'list-issues',
|
|
146
|
-
payload: { filters: { status: 'closed', limit: 10 } }
|
|
147
|
-
};
|
|
148
|
-
await handleMessage(
|
|
149
|
-
/** @type {any} */ (ws),
|
|
150
|
-
Buffer.from(JSON.stringify(req))
|
|
151
|
-
);
|
|
152
|
-
const call = mocked.mock.calls[mocked.mock.calls.length - 1];
|
|
153
|
-
const args = /** @type {string[]} */ (call[0]);
|
|
154
|
-
expect(args.includes('--limit')).toBe(true);
|
|
155
|
-
expect(args.includes('10')).toBe(true);
|
|
156
|
-
const obj = JSON.parse(ws.sent[ws.sent.length - 1]);
|
|
157
|
-
expect(obj.ok).toBe(true);
|
|
158
|
-
expect(Array.isArray(obj.payload)).toBe(true);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test('bd error propagates as bd_error reply', async () => {
|
|
162
|
-
const mocked = /** @type {import('vitest').Mock} */ (runBdJson);
|
|
163
|
-
mocked.mockResolvedValueOnce({ code: 1, stderr: 'boom' });
|
|
164
|
-
const ws = makeStubSocket();
|
|
165
|
-
const req = { id: 'r4', type: 'list-issues', payload: {} };
|
|
166
|
-
await handleMessage(
|
|
167
|
-
/** @type {any} */ (ws),
|
|
168
|
-
Buffer.from(JSON.stringify(req))
|
|
169
|
-
);
|
|
170
|
-
const obj = JSON.parse(ws.sent[ws.sent.length - 1]);
|
|
171
|
-
expect(obj.ok).toBe(false);
|
|
172
|
-
expect(obj.error.code).toBe('bd_error');
|
|
173
|
-
});
|
|
174
|
-
});
|
package/server/ws.labels.test.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, vi } from 'vitest';
|
|
2
|
-
import { runBd, runBdJson } from './bd.js';
|
|
3
|
-
import { handleMessage } from './ws.js';
|
|
4
|
-
|
|
5
|
-
vi.mock('./bd.js', () => ({
|
|
6
|
-
runBd: vi.fn(),
|
|
7
|
-
runBdJson: vi.fn()
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
function makeStubSocket() {
|
|
11
|
-
return {
|
|
12
|
-
sent: /** @type {string[]} */ ([]),
|
|
13
|
-
readyState: 1,
|
|
14
|
-
OPEN: 1,
|
|
15
|
-
/** @param {string} msg */
|
|
16
|
-
send(msg) {
|
|
17
|
-
this.sent.push(String(msg));
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe('ws labels handlers', () => {
|
|
23
|
-
test('label-add validates payload', async () => {
|
|
24
|
-
const ws = makeStubSocket();
|
|
25
|
-
await handleMessage(
|
|
26
|
-
/** @type {any} */ (ws),
|
|
27
|
-
Buffer.from(
|
|
28
|
-
JSON.stringify({
|
|
29
|
-
id: 'x',
|
|
30
|
-
type: /** @type {any} */ ('label-add'),
|
|
31
|
-
payload: {}
|
|
32
|
-
})
|
|
33
|
-
)
|
|
34
|
-
);
|
|
35
|
-
const obj = JSON.parse(ws.sent[0]);
|
|
36
|
-
expect(obj.ok).toBe(false);
|
|
37
|
-
expect(obj.error.code).toBe('bad_request');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('label-add runs bd and replies with show', async () => {
|
|
41
|
-
const rb = /** @type {import('vitest').Mock} */ (runBd);
|
|
42
|
-
const rj = /** @type {import('vitest').Mock} */ (runBdJson);
|
|
43
|
-
rb.mockResolvedValueOnce({ code: 0, stdout: '', stderr: '' });
|
|
44
|
-
rj.mockResolvedValueOnce({
|
|
45
|
-
code: 0,
|
|
46
|
-
stdoutJson: { id: 'UI-1', labels: ['frontend'] }
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const ws = makeStubSocket();
|
|
50
|
-
await handleMessage(
|
|
51
|
-
/** @type {any} */ (ws),
|
|
52
|
-
Buffer.from(
|
|
53
|
-
JSON.stringify({
|
|
54
|
-
id: 'a',
|
|
55
|
-
type: /** @type {any} */ ('label-add'),
|
|
56
|
-
payload: { id: 'UI-1', label: 'frontend' }
|
|
57
|
-
})
|
|
58
|
-
)
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const call = rb.mock.calls[0][0];
|
|
62
|
-
expect(call.slice(0, 3)).toEqual(['label', 'add', 'UI-1']);
|
|
63
|
-
const obj = JSON.parse(ws.sent[ws.sent.length - 1]);
|
|
64
|
-
expect(obj.ok).toBe(true);
|
|
65
|
-
expect(obj.payload && obj.payload.id).toBe('UI-1');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('label-remove runs bd and replies with show', async () => {
|
|
69
|
-
const rb = /** @type {import('vitest').Mock} */ (runBd);
|
|
70
|
-
const rj = /** @type {import('vitest').Mock} */ (runBdJson);
|
|
71
|
-
rb.mockResolvedValueOnce({ code: 0, stdout: '', stderr: '' });
|
|
72
|
-
rj.mockResolvedValueOnce({
|
|
73
|
-
code: 0,
|
|
74
|
-
stdoutJson: { id: 'UI-1', labels: [] }
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const ws = makeStubSocket();
|
|
78
|
-
await handleMessage(
|
|
79
|
-
/** @type {any} */ (ws),
|
|
80
|
-
Buffer.from(
|
|
81
|
-
JSON.stringify({
|
|
82
|
-
id: 'b',
|
|
83
|
-
type: /** @type {any} */ ('label-remove'),
|
|
84
|
-
payload: { id: 'UI-1', label: 'frontend' }
|
|
85
|
-
})
|
|
86
|
-
)
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
const call = rb.mock.calls[rb.mock.calls.length - 1][0];
|
|
90
|
-
expect(call.slice(0, 3)).toEqual(['label', 'remove', 'UI-1']);
|
|
91
|
-
const obj = JSON.parse(ws.sent[ws.sent.length - 1]);
|
|
92
|
-
expect(obj.ok).toBe(true);
|
|
93
|
-
expect(obj.payload && obj.payload.id).toBe('UI-1');
|
|
94
|
-
});
|
|
95
|
-
});
|