@happyvertical/smrt-chat 0.53.1 → 0.53.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.
Files changed (37) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/manifest.json +1 -1
  3. package/dist/smrt-knowledge.json +3 -3
  4. package/package.json +13 -13
  5. package/dist/__tests__/chat-test-db.d.ts +0 -8
  6. package/dist/__tests__/chat-test-db.d.ts.map +0 -1
  7. package/dist/__tests__/raw-collections.d.ts +0 -35
  8. package/dist/__tests__/raw-collections.d.ts.map +0 -1
  9. package/dist/svelte/components/agent/__tests__/ToolCallDisplay.test.js +0 -135
  10. package/dist/svelte/components/agent/message-blocks.test.js +0 -31
  11. package/dist/svelte/components/assistant/__tests__/AssistantComposer.test.js +0 -75
  12. package/dist/svelte/components/assistant/__tests__/AssistantDock.test.js +0 -515
  13. package/dist/svelte/components/assistant/__tests__/AssistantThreadList.test.js +0 -45
  14. package/dist/svelte/components/assistant/__tests__/action-status.test.js +0 -25
  15. package/dist/svelte/components/assistant/__tests__/assistant-composer-dictation.test.js +0 -197
  16. package/dist/svelte/components/assistant/__tests__/assistant-dock-action-outcome.test.js +0 -320
  17. package/dist/svelte/components/assistant/__tests__/assistant-dock-choices-pending.test.js +0 -224
  18. package/dist/svelte/components/assistant/__tests__/assistant-dock-choices-preview.test.js +0 -312
  19. package/dist/svelte/components/assistant/__tests__/assistant-dock-choices.test.js +0 -338
  20. package/dist/svelte/components/assistant/__tests__/assistant-dock-client-tools.test.js +0 -442
  21. package/dist/svelte/components/assistant/__tests__/assistant-dock-draft-settled.test.js +0 -448
  22. package/dist/svelte/components/assistant/__tests__/assistant-dock-host-actions.test.js +0 -185
  23. package/dist/svelte/components/assistant/__tests__/assistant-dock-run.test.js +0 -303
  24. package/dist/svelte/components/assistant/__tests__/assistant-dock-settle.test.js +0 -146
  25. package/dist/svelte/components/assistant/__tests__/assistant-transport.test.js +0 -253
  26. package/dist/svelte/components/assistant/__tests__/attachment-href.test.js +0 -31
  27. package/dist/svelte/components/assistant/__tests__/create-assistant-dock-controller.test.js +0 -2491
  28. package/dist/svelte/components/layout/__tests__/ChatLayout.test.js +0 -67
  29. package/dist/svelte/components/messages/__tests__/MessageInput.test.js +0 -38
  30. package/dist/svelte/components/messages/__tests__/MessageItem.test.js +0 -108
  31. package/dist/svelte/components/shared/__tests__/Avatar.test.js +0 -20
  32. package/dist/svelte/components/shared/__tests__/LinkPreview.test.js +0 -29
  33. package/dist/svelte/components/shared/__tests__/MentionAutocomplete.test.js +0 -66
  34. package/dist/svelte/components/shared/__tests__/ModelPicker.test.js +0 -36
  35. package/dist/svelte/components/shared/__tests__/ReactionPicker.test.js +0 -35
  36. package/dist/svelte/components/shared/__tests__/ReadReceipts.test.js +0 -28
  37. package/dist/svelte/components/shared/__tests__/UserPresence.test.js +0 -23
@@ -1,197 +0,0 @@
1
- // @vitest-environment jsdom
2
- /**
3
- * Speaking into the assistant composer (smrt-ui `Dictation` over a mocked
4
- * speech source; real speech recognition cannot run headless): a named
5
- * microphone button, press-and-hold on the message box, heard words at the
6
- * cursor, and Send / Escape stopping it. No source means no microphone.
7
- */
8
- import { fireEvent, render, screen, userEvent, } from '@happyvertical/smrt-vitest/svelte';
9
- import { afterEach, describe, expect, it, vi } from 'vitest';
10
- import AssistantChoiceCards from '../AssistantChoiceCards.svelte';
11
- import AssistantComposer from '../AssistantComposer.svelte';
12
- function mockSpeech() {
13
- const results = new Set();
14
- const ends = new Set();
15
- const source = {
16
- start: vi.fn(async () => { }),
17
- stop: vi.fn(async () => {
18
- for (const cb of ends)
19
- cb();
20
- }),
21
- onResult: (cb) => {
22
- results.add(cb);
23
- return () => results.delete(cb);
24
- },
25
- onError: () => () => { },
26
- onEnd: (cb) => {
27
- ends.add(cb);
28
- return () => ends.delete(cb);
29
- },
30
- };
31
- return {
32
- source,
33
- say(text) {
34
- for (const cb of results)
35
- cb({ text, isFinal: true });
36
- },
37
- };
38
- }
39
- function pointerEvent(type, init = {}) {
40
- const event = new Event(type, { bubbles: true, cancelable: true });
41
- Object.assign(event, {
42
- isPrimary: true,
43
- pointerId: 3,
44
- pointerType: 'touch',
45
- button: 0,
46
- clientX: 20,
47
- clientY: 20,
48
- ...init,
49
- });
50
- return event;
51
- }
52
- describe('AssistantComposer dictation', () => {
53
- afterEach(() => vi.useRealTimers());
54
- it('has no microphone without a speech source', () => {
55
- render(AssistantComposer, {
56
- props: { onsend: vi.fn(), onupload: vi.fn() },
57
- });
58
- expect(screen.queryByRole('button', { name: 'Speak instead of typing' })).toBeNull();
59
- });
60
- it('the microphone listens and puts heard words at the cursor; Send stops it', async () => {
61
- const speech = mockSpeech();
62
- const onsend = vi.fn().mockResolvedValue(undefined);
63
- render(AssistantComposer, {
64
- props: { onsend, onupload: vi.fn(), dictation: () => speech.source },
65
- });
66
- const field = screen.getByLabelText('Message');
67
- await userEvent.type(field, 'Find a picture of');
68
- await userEvent.click(screen.getByRole('button', { name: 'Speak instead of typing' }));
69
- expect(await screen.findByRole('button', { name: 'Stop listening' })).toHaveAttribute('aria-pressed', 'true');
70
- expect(screen.getByRole('status')).toHaveTextContent('Listening');
71
- speech.say('the arena');
72
- expect(field).toHaveValue('Find a picture of the arena');
73
- await userEvent.click(screen.getByRole('button', { name: 'Send' }));
74
- expect(onsend).toHaveBeenCalledWith('Find a picture of the arena', []);
75
- expect(speech.source.stop).toHaveBeenCalled();
76
- });
77
- it('pressing and holding the message box starts listening; Escape stops', async () => {
78
- vi.useFakeTimers({ shouldAdvanceTime: true });
79
- const speech = mockSpeech();
80
- render(AssistantComposer, {
81
- props: {
82
- onsend: vi.fn(),
83
- onupload: vi.fn(),
84
- dictation: () => speech.source,
85
- },
86
- });
87
- const field = screen.getByLabelText('Message');
88
- field.dispatchEvent(pointerEvent('pointerdown'));
89
- vi.advanceTimersByTime(520);
90
- window.dispatchEvent(pointerEvent('pointerup'));
91
- await vi.waitFor(() => expect(speech.source.start).toHaveBeenCalled());
92
- expect(await screen.findByRole('button', { name: 'Stop listening' })).toBeInTheDocument();
93
- await fireEvent.keyDown(field, { key: 'Escape' });
94
- await vi.waitFor(() => expect(speech.source.stop).toHaveBeenCalled());
95
- });
96
- it('a short tap on the message box does not listen', async () => {
97
- vi.useFakeTimers({ shouldAdvanceTime: true });
98
- const speech = mockSpeech();
99
- render(AssistantComposer, {
100
- props: {
101
- onsend: vi.fn(),
102
- onupload: vi.fn(),
103
- dictation: () => speech.source,
104
- },
105
- });
106
- const field = screen.getByLabelText('Message');
107
- field.dispatchEvent(pointerEvent('pointerdown'));
108
- vi.advanceTimersByTime(150);
109
- window.dispatchEvent(pointerEvent('pointerup'));
110
- vi.advanceTimersByTime(800);
111
- expect(speech.source.start).not.toHaveBeenCalled();
112
- });
113
- });
114
- describe('AssistantChoiceCards', () => {
115
- it('renders offered options and reports the pick and "None of these"', async () => {
116
- const onchoose = vi.fn();
117
- const ondismiss = vi.fn();
118
- render(AssistantChoiceCards, {
119
- props: {
120
- choices: [
121
- {
122
- id: 's1',
123
- sourceId: 'crop',
124
- title: 'Pick a shape',
125
- status: 'waiting',
126
- options: [
127
- { id: 'wide', label: 'Wide' },
128
- { id: 'square', label: 'Square' },
129
- ],
130
- },
131
- ],
132
- onchoose,
133
- ondismiss,
134
- },
135
- });
136
- await userEvent.click(screen.getByRole('button', { name: 'Square' }));
137
- expect(onchoose).toHaveBeenCalledWith('s1', 'square');
138
- await userEvent.click(screen.getByRole('button', { name: 'None of these' }));
139
- expect(ondismiss).toHaveBeenCalledWith('s1');
140
- });
141
- });
142
- describe('AssistantComposer recording fallback', () => {
143
- afterEach(() => vi.unstubAllGlobals());
144
- function stubRecorder() {
145
- class FakeMediaRecorder extends EventTarget {
146
- static isTypeSupported = (type) => type === 'audio/webm;codecs=opus';
147
- state = 'inactive';
148
- mimeType;
149
- constructor(_stream, options) {
150
- super();
151
- this.mimeType = options?.mimeType ?? '';
152
- }
153
- start() {
154
- this.state = 'recording';
155
- }
156
- stop() {
157
- this.state = 'inactive';
158
- this.dispatchEvent(Object.assign(new Event('dataavailable'), {
159
- data: new Blob(['opus'], { type: this.mimeType }),
160
- }));
161
- this.dispatchEvent(new Event('stop'));
162
- }
163
- }
164
- const track = { stop: vi.fn() };
165
- vi.stubGlobal('MediaRecorder', FakeMediaRecorder);
166
- Object.defineProperty(navigator, 'mediaDevices', {
167
- configurable: true,
168
- value: {
169
- getUserMedia: vi.fn(async () => ({ getTracks: () => [track] })),
170
- },
171
- });
172
- return track;
173
- }
174
- it('records with only `transcribe` (Firefox), writes it down, and puts the text in the box', async () => {
175
- const track = stubRecorder();
176
- let finish;
177
- const transcribe = vi.fn(() => new Promise((resolve) => (finish = resolve)));
178
- render(AssistantComposer, {
179
- props: { onsend: vi.fn(), onupload: vi.fn(), transcribe },
180
- });
181
- const mic = screen.getByRole('button', { name: 'Speak instead of typing' });
182
- await userEvent.click(mic);
183
- await screen.findByText(/Listening/);
184
- await userEvent.click(screen.getByRole('button', { name: 'Stop listening' }));
185
- await screen.findByText('Writing it down…');
186
- expect(track.stop).toHaveBeenCalled();
187
- expect(transcribe).toHaveBeenCalledWith(expect.any(Blob), {
188
- mimeType: 'audio/webm;codecs=opus',
189
- language: expect.any(String),
190
- durationMs: expect.any(Number),
191
- signal: expect.any(AbortSignal),
192
- });
193
- finish('Show me the arena');
194
- const box = screen.getByRole('textbox', { name: /message/i });
195
- await vi.waitFor(() => expect(box.value).toBe('Show me the arena'));
196
- });
197
- });
@@ -1,320 +0,0 @@
1
- // @vitest-environment jsdom
2
- /**
3
- * #2990: an apply that reached no server decision ("unknown outcome") is kept
4
- * apart from a refusal, keeps its idempotency key, and can only be retried
5
- * with that same key — never discarded or re-proposed under a fresh one.
6
- *
7
- * `idempotentServer` models the server adapter's contract: the first apply
8
- * for a key commits and records its result; any later apply with the same
9
- * key replays that result without committing again, and a different key is
10
- * a new mutation. `commits` is the double-apply oracle.
11
- */
12
- import { createDataSurfaceRegistry, } from '@happyvertical/smrt-ui/data-surface';
13
- import { render, screen, userEvent } from '@happyvertical/smrt-vitest/svelte';
14
- import { describe, expect, it, vi } from 'vitest';
15
- import AssistantDock from '../AssistantDock.svelte';
16
- import { createInMemoryAssistantTransport } from '../assistant-transport.js';
17
- import { ASSISTANT_ACTION_UNKNOWN_OUTCOME_REASONS, createAssistantDockController, } from '../create-assistant-dock-controller.svelte.js';
18
- const identity = {
19
- surfaceId: 'orders',
20
- kind: 'table',
21
- subject: { type: 'tenant', id: 'tenant-a' },
22
- };
23
- function registryWithSurface() {
24
- const registry = createDataSurfaceRegistry();
25
- const descriptor = {
26
- version: 1,
27
- identity,
28
- schemaVersion: 1,
29
- label: 'Orders',
30
- rowKey: 'id',
31
- columns: [
32
- { id: 'id', label: 'ID', capabilities: ['read'], role: 'row-key' },
33
- ],
34
- query: {
35
- modes: ['rows'],
36
- projectableColumnIds: ['id'],
37
- searchableColumnIds: [],
38
- filterableColumnIds: [],
39
- sortableColumnIds: [],
40
- },
41
- actions: [],
42
- controls: [],
43
- limits: { maxQueryRows: 10, maxQueryBytes: 10_000, maxSelectionSize: 10 },
44
- };
45
- registry.register({
46
- descriptor,
47
- getSnapshot: () => ({ revision: 1, state: {} }),
48
- });
49
- return registry;
50
- }
51
- const REQUEST_ID = 'req-ship';
52
- function proposal() {
53
- return {
54
- version: 1,
55
- requestId: REQUEST_ID,
56
- identity,
57
- actionId: 'ship',
58
- phase: 'preview',
59
- selection: { scope: 'explicit-ids', rowIds: ['order-1'] },
60
- };
61
- }
62
- function result(request, phase, ok, reason) {
63
- return {
64
- version: 1,
65
- requestId: request.requestId,
66
- identity: request.identity,
67
- actionId: request.actionId,
68
- phase,
69
- ok,
70
- ...(reason ? { reason } : {}),
71
- };
72
- }
73
- function idempotentServer() {
74
- const byKey = new Map();
75
- const server = {
76
- commits: 0,
77
- applyCalls: 0,
78
- keys: [],
79
- mode: 'commit',
80
- gate: undefined,
81
- preview: vi.fn(async (request) => result(request, 'preview', true)),
82
- client: undefined,
83
- };
84
- server.client = {
85
- preview: server.preview,
86
- apply: async (request, key) => {
87
- server.applyCalls += 1;
88
- server.keys.push(key);
89
- if (server.gate)
90
- await server.gate;
91
- const replay = byKey.get(key);
92
- if (replay)
93
- return replay;
94
- if (server.mode === 'in-progress') {
95
- return result(request, 'apply', false, 'idempotency_in_progress');
96
- }
97
- server.commits += 1;
98
- const applied = result(request, 'apply', true);
99
- byKey.set(key, applied);
100
- if (server.mode === 'commit-then-lose-response') {
101
- throw new Error('network: response lost');
102
- }
103
- return applied;
104
- },
105
- };
106
- return server;
107
- }
108
- function setup(server = idempotentServer()) {
109
- const onActionApplied = vi.fn();
110
- const controller = createAssistantDockController({
111
- transport: createInMemoryAssistantTransport(),
112
- registry: registryWithSurface(),
113
- actionClient: server.client,
114
- onActionApplied,
115
- });
116
- return { controller, server, onActionApplied };
117
- }
118
- async function previewed(controller) {
119
- await controller.previewAction(proposal());
120
- expect(controller.actions.get(REQUEST_ID)?.status).toBe('previewed');
121
- return controller.actions.get(REQUEST_ID)?.idempotencyKey;
122
- }
123
- describe('unknown apply outcome vs refusal (#2990)', () => {
124
- it('marks a rejected apply (transport failure) as an unknown outcome and keeps its key', async () => {
125
- const { controller, server } = setup();
126
- const key = await previewed(controller);
127
- server.mode = 'commit-then-lose-response';
128
- await controller.applyAction(REQUEST_ID);
129
- const state = controller.actions.get(REQUEST_ID);
130
- expect(state?.status).toBe('failed');
131
- expect(state?.outcomeUnknown).toBe(true);
132
- expect(state?.retryable).toBe(true);
133
- expect(state?.idempotencyKey).toBe(key);
134
- controller.dispose();
135
- });
136
- it.each(ASSISTANT_ACTION_UNKNOWN_OUTCOME_REASONS)('treats ok:false with reason %s as an unknown outcome', async (reason) => {
137
- const controller = createAssistantDockController({
138
- transport: createInMemoryAssistantTransport(),
139
- registry: registryWithSurface(),
140
- actionClient: {
141
- preview: async (request) => result(request, 'preview', true),
142
- apply: async (request) => result(request, 'apply', false, reason),
143
- },
144
- });
145
- await previewed(controller);
146
- await controller.applyAction(REQUEST_ID);
147
- expect(controller.actions.get(REQUEST_ID)?.outcomeUnknown).toBe(true);
148
- controller.dispose();
149
- });
150
- it.each([
151
- 'denied',
152
- 'not_found',
153
- 'idempotency_conflict',
154
- 'stale_revision',
155
- 'confirmation_required',
156
- 'confirmation_mismatch',
157
- ])('treats ok:false with reason %s as a refusal', async (reason) => {
158
- const controller = createAssistantDockController({
159
- transport: createInMemoryAssistantTransport(),
160
- registry: registryWithSurface(),
161
- actionClient: {
162
- preview: async (request) => result(request, 'preview', true),
163
- apply: async (request) => result(request, 'apply', false, reason),
164
- },
165
- });
166
- await previewed(controller);
167
- await controller.applyAction(REQUEST_ID);
168
- const state = controller.actions.get(REQUEST_ID);
169
- expect(state?.status).toBe('failed');
170
- expect(state?.outcomeUnknown).toBe(false);
171
- // A refusal is terminal, so discarding it is allowed.
172
- controller.rejectAction(REQUEST_ID);
173
- expect(controller.actions.has(REQUEST_ID)).toBe(false);
174
- controller.dispose();
175
- });
176
- });
177
- describe('an unknown outcome cannot be discarded or re-keyed (#2990)', () => {
178
- it('refuses rejectAction and keeps the entry and key', async () => {
179
- const { controller, server } = setup();
180
- const key = await previewed(controller);
181
- server.mode = 'commit-then-lose-response';
182
- await controller.applyAction(REQUEST_ID);
183
- controller.rejectAction(REQUEST_ID);
184
- expect(controller.actions.get(REQUEST_ID)?.idempotencyKey).toBe(key);
185
- expect(controller.actions.get(REQUEST_ID)?.outcomeUnknown).toBe(true);
186
- expect(controller.error).toMatch(/rejectAction refused/);
187
- controller.dispose();
188
- });
189
- it('refuses a new previewAction for the same request id', async () => {
190
- const { controller, server } = setup();
191
- const key = await previewed(controller);
192
- server.mode = 'commit-then-lose-response';
193
- await controller.applyAction(REQUEST_ID);
194
- server.preview.mockClear();
195
- await controller.previewAction(proposal());
196
- expect(server.preview).not.toHaveBeenCalled();
197
- expect(controller.actions.get(REQUEST_ID)?.idempotencyKey).toBe(key);
198
- expect(controller.actions.get(REQUEST_ID)?.outcomeUnknown).toBe(true);
199
- expect(controller.error).toMatch(/previewAction refused/);
200
- controller.dispose();
201
- });
202
- it('refuses a new previewAction while an apply is in flight', async () => {
203
- const { controller, server } = setup();
204
- const key = await previewed(controller);
205
- let release;
206
- server.gate = new Promise((resolve) => {
207
- release = resolve;
208
- });
209
- const applying = controller.applyAction(REQUEST_ID);
210
- await controller.previewAction(proposal());
211
- expect(controller.actions.get(REQUEST_ID)?.status).toBe('applying');
212
- expect(controller.actions.get(REQUEST_ID)?.idempotencyKey).toBe(key);
213
- release();
214
- await applying;
215
- expect(controller.actions.get(REQUEST_ID)?.status).toBe('applied');
216
- expect(server.commits).toBe(1);
217
- controller.dispose();
218
- });
219
- });
220
- describe('same-key retry after an unknown outcome (#2990)', () => {
221
- it('replays with the same key, applies exactly once, and clears the flag', async () => {
222
- const { controller, server, onActionApplied } = setup();
223
- const key = await previewed(controller);
224
- server.mode = 'commit-then-lose-response';
225
- await controller.applyAction(REQUEST_ID);
226
- expect(server.commits).toBe(1);
227
- // The operator tries every discard/re-propose path, then retries.
228
- controller.rejectAction(REQUEST_ID);
229
- await controller.previewAction(proposal());
230
- server.mode = 'commit';
231
- await controller.applyAction(REQUEST_ID);
232
- const state = controller.actions.get(REQUEST_ID);
233
- expect(state?.status).toBe('applied');
234
- expect(state?.outcomeUnknown).toBe(false);
235
- expect(server.keys).toEqual([key, key]);
236
- expect(server.commits).toBe(1);
237
- expect(onActionApplied).toHaveBeenCalledTimes(1);
238
- controller.dispose();
239
- });
240
- it('keeps the key through idempotency_in_progress until the first attempt settles', async () => {
241
- const { controller, server } = setup();
242
- const key = await previewed(controller);
243
- server.mode = 'in-progress';
244
- await controller.applyAction(REQUEST_ID);
245
- await controller.applyAction(REQUEST_ID);
246
- expect(controller.actions.get(REQUEST_ID)?.outcomeUnknown).toBe(true);
247
- server.mode = 'commit';
248
- await controller.applyAction(REQUEST_ID);
249
- expect(controller.actions.get(REQUEST_ID)?.status).toBe('applied');
250
- expect(new Set(server.keys)).toEqual(new Set([key]));
251
- expect(server.commits).toBe(1);
252
- controller.dispose();
253
- });
254
- it('coalesces concurrent duplicate retries into one in-flight apply', async () => {
255
- const { controller, server } = setup();
256
- const key = await previewed(controller);
257
- server.mode = 'commit-then-lose-response';
258
- await controller.applyAction(REQUEST_ID);
259
- server.mode = 'commit';
260
- let release;
261
- server.gate = new Promise((resolve) => {
262
- release = resolve;
263
- });
264
- const callsBefore = server.applyCalls;
265
- const retries = [
266
- controller.applyAction(REQUEST_ID),
267
- controller.applyAction(REQUEST_ID),
268
- controller.applyAction(REQUEST_ID),
269
- ];
270
- controller.rejectAction(REQUEST_ID);
271
- release();
272
- await Promise.all(retries);
273
- expect(server.applyCalls - callsBefore).toBe(1);
274
- expect(server.keys.every((k) => k === key)).toBe(true);
275
- expect(server.commits).toBe(1);
276
- expect(controller.actions.get(REQUEST_ID)?.status).toBe('applied');
277
- controller.dispose();
278
- });
279
- it('refuses a duplicate retry once the action is applied', async () => {
280
- const { controller, server } = setup();
281
- await previewed(controller);
282
- server.mode = 'commit-then-lose-response';
283
- await controller.applyAction(REQUEST_ID);
284
- server.mode = 'commit';
285
- await controller.applyAction(REQUEST_ID);
286
- const calls = server.applyCalls;
287
- await controller.applyAction(REQUEST_ID);
288
- expect(server.applyCalls).toBe(calls);
289
- expect(server.commits).toBe(1);
290
- controller.dispose();
291
- });
292
- });
293
- describe('AssistantDock unknown-outcome affordance (#2990)', () => {
294
- it('withdraws Reject and offers a same-key Check again', async () => {
295
- const server = idempotentServer();
296
- let dock;
297
- render(AssistantDock, {
298
- props: {
299
- transport: createInMemoryAssistantTransport(),
300
- registry: registryWithSurface(),
301
- actionClient: server.client,
302
- oncontroller: (c) => {
303
- dock = c;
304
- },
305
- },
306
- });
307
- await dock?.previewAction(proposal());
308
- server.mode = 'commit-then-lose-response';
309
- await userEvent.click(await screen.findByRole('button', { name: 'Confirm' }));
310
- const check = await screen.findByRole('button', { name: 'Check again' });
311
- expect(screen.getByRole('status')).toHaveTextContent(/couldn't confirm whether this change was applied/);
312
- expect(screen.queryByRole('button', { name: 'Reject' })).toBeNull();
313
- expect(screen.queryByRole('button', { name: 'Confirm' })).toBeNull();
314
- server.mode = 'commit';
315
- await userEvent.click(check);
316
- await vi.waitFor(() => expect(screen.queryByRole('button', { name: 'Check again' })).toBeNull());
317
- expect(dock?.actions.get(REQUEST_ID)?.status).toBe('applied');
318
- expect(server.commits).toBe(1);
319
- });
320
- });