@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.
- package/dist/index.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/smrt-knowledge.json +3 -3
- package/package.json +13 -13
- package/dist/__tests__/chat-test-db.d.ts +0 -8
- package/dist/__tests__/chat-test-db.d.ts.map +0 -1
- package/dist/__tests__/raw-collections.d.ts +0 -35
- package/dist/__tests__/raw-collections.d.ts.map +0 -1
- package/dist/svelte/components/agent/__tests__/ToolCallDisplay.test.js +0 -135
- package/dist/svelte/components/agent/message-blocks.test.js +0 -31
- package/dist/svelte/components/assistant/__tests__/AssistantComposer.test.js +0 -75
- package/dist/svelte/components/assistant/__tests__/AssistantDock.test.js +0 -515
- package/dist/svelte/components/assistant/__tests__/AssistantThreadList.test.js +0 -45
- package/dist/svelte/components/assistant/__tests__/action-status.test.js +0 -25
- package/dist/svelte/components/assistant/__tests__/assistant-composer-dictation.test.js +0 -197
- package/dist/svelte/components/assistant/__tests__/assistant-dock-action-outcome.test.js +0 -320
- package/dist/svelte/components/assistant/__tests__/assistant-dock-choices-pending.test.js +0 -224
- package/dist/svelte/components/assistant/__tests__/assistant-dock-choices-preview.test.js +0 -312
- package/dist/svelte/components/assistant/__tests__/assistant-dock-choices.test.js +0 -338
- package/dist/svelte/components/assistant/__tests__/assistant-dock-client-tools.test.js +0 -442
- package/dist/svelte/components/assistant/__tests__/assistant-dock-draft-settled.test.js +0 -448
- package/dist/svelte/components/assistant/__tests__/assistant-dock-host-actions.test.js +0 -185
- package/dist/svelte/components/assistant/__tests__/assistant-dock-run.test.js +0 -303
- package/dist/svelte/components/assistant/__tests__/assistant-dock-settle.test.js +0 -146
- package/dist/svelte/components/assistant/__tests__/assistant-transport.test.js +0 -253
- package/dist/svelte/components/assistant/__tests__/attachment-href.test.js +0 -31
- package/dist/svelte/components/assistant/__tests__/create-assistant-dock-controller.test.js +0 -2491
- package/dist/svelte/components/layout/__tests__/ChatLayout.test.js +0 -67
- package/dist/svelte/components/messages/__tests__/MessageInput.test.js +0 -38
- package/dist/svelte/components/messages/__tests__/MessageItem.test.js +0 -108
- package/dist/svelte/components/shared/__tests__/Avatar.test.js +0 -20
- package/dist/svelte/components/shared/__tests__/LinkPreview.test.js +0 -29
- package/dist/svelte/components/shared/__tests__/MentionAutocomplete.test.js +0 -66
- package/dist/svelte/components/shared/__tests__/ModelPicker.test.js +0 -36
- package/dist/svelte/components/shared/__tests__/ReactionPicker.test.js +0 -35
- package/dist/svelte/components/shared/__tests__/ReadReceipts.test.js +0 -28
- package/dist/svelte/components/shared/__tests__/UserPresence.test.js +0 -23
package/dist/manifest.json
CHANGED
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-chat",
|
|
6
|
-
"packageVersion": "0.53.
|
|
6
|
+
"packageVersion": "0.53.2",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
10
|
+
"manifest": "3865dce9ea07670b543e39c56ddee4c2244cdb8f6bf3450340c880d3d43d87ac",
|
|
11
|
+
"packageJson": "f6fc0cd19c3d9d6cfeb520da745cc9715e14f39f5fc0e2b8b45b5e05216433fc",
|
|
12
12
|
"agents": "c50538672f9ba892ac0998b58c3a2b497e876df27eff7693c66f3103e730df3c",
|
|
13
13
|
"moduleDoc:agents/dev-workbench.md": "69b6f827d25c8aeb0e7516c90303af23948803213b5d34bc334c1e3376c83a16",
|
|
14
14
|
"agentSurface:src/routes/chat-dev.intents.ts": "ff8f7c19c7eb6e841a08f03c97773c53092ed208004666bfed372bd1ad456b35"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-chat",
|
|
3
|
-
"version": "0.53.
|
|
3
|
+
"version": "0.53.2",
|
|
4
4
|
"smrtJsdoc": "strict",
|
|
5
5
|
"description": "Chat rooms, DMs, threads, and agent conversations for the SMRT framework",
|
|
6
6
|
"type": "module",
|
|
@@ -75,14 +75,14 @@
|
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
77
|
"@happyvertical/ai": "^0.98.0",
|
|
78
|
-
"@happyvertical/smrt-agents": "0.53.
|
|
79
|
-
"@happyvertical/smrt-core": "0.53.
|
|
80
|
-
"@happyvertical/smrt-personas": "0.53.
|
|
81
|
-
"@happyvertical/smrt-profiles": "0.53.
|
|
82
|
-
"@happyvertical/smrt-tenancy": "0.53.
|
|
83
|
-
"@happyvertical/smrt-types": "0.53.
|
|
84
|
-
"@happyvertical/smrt-ui": "0.53.
|
|
85
|
-
"@happyvertical/smrt-users": "0.53.
|
|
78
|
+
"@happyvertical/smrt-agents": "0.53.2",
|
|
79
|
+
"@happyvertical/smrt-core": "0.53.2",
|
|
80
|
+
"@happyvertical/smrt-personas": "0.53.2",
|
|
81
|
+
"@happyvertical/smrt-profiles": "0.53.2",
|
|
82
|
+
"@happyvertical/smrt-tenancy": "0.53.2",
|
|
83
|
+
"@happyvertical/smrt-types": "0.53.2",
|
|
84
|
+
"@happyvertical/smrt-ui": "0.53.2",
|
|
85
|
+
"@happyvertical/smrt-users": "0.53.2",
|
|
86
86
|
"@happyvertical/sql": "^0.98.0"
|
|
87
87
|
},
|
|
88
88
|
"peerDependencies": {
|
|
@@ -94,9 +94,9 @@
|
|
|
94
94
|
}
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
-
"@happyvertical/smrt-playground": "0.53.
|
|
98
|
-
"@happyvertical/smrt-vitest": "0.53.
|
|
99
|
-
"@happyvertical/smrt-web": "0.53.
|
|
97
|
+
"@happyvertical/smrt-playground": "0.53.2",
|
|
98
|
+
"@happyvertical/smrt-vitest": "0.53.2",
|
|
99
|
+
"@happyvertical/smrt-web": "0.53.2",
|
|
100
100
|
"@mlc-ai/web-llm": "^0.2.85",
|
|
101
101
|
"bitgpu": "0.19.1",
|
|
102
102
|
"@sveltejs/kit": "2.69.1",
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"vitest": "4.1.10"
|
|
112
112
|
},
|
|
113
113
|
"scripts": {
|
|
114
|
-
"build": "SMRT_PACKAGE_BUILD=1 vite build --mode library && SMRT_PACKAGE_BUILD=1 svelte-package -i src/svelte -o dist/svelte --tsconfig tsconfig.svelte.json",
|
|
114
|
+
"build": "SMRT_PACKAGE_BUILD=1 vite build --mode library && SMRT_PACKAGE_BUILD=1 svelte-package -i src/svelte -o dist/svelte --tsconfig tsconfig.svelte.json && node ../../scripts/prune-svelte-package-artifacts.js dist",
|
|
115
115
|
"build:watch": "vite build --mode library --watch",
|
|
116
116
|
"check": "svelte-kit sync && pnpm exec svelte-check --tsconfig ./tsconfig.kit.json",
|
|
117
117
|
"clean": "rm -rf dist",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { DatabaseInterface } from '@happyvertical/sql';
|
|
2
|
-
import '../index.js';
|
|
3
|
-
export declare function createChatTestDatabase(url: string): Promise<DatabaseInterface>;
|
|
4
|
-
export declare function seedChatRoom(db: DatabaseInterface, id?: string): Promise<void>;
|
|
5
|
-
export declare function seedChatMessage(db: DatabaseInterface, id?: string, roomId?: string): Promise<void>;
|
|
6
|
-
export declare function seedChatThread(db: DatabaseInterface, id: string, rootMessageId?: string): Promise<void>;
|
|
7
|
-
export declare function seedAgentSession(db: DatabaseInterface, id: string): Promise<void>;
|
|
8
|
-
//# sourceMappingURL=chat-test-db.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"chat-test-db.d.ts","sourceRoot":"","sources":["../../src/__tests__/chat-test-db.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,aAAa,CAAC;AAgBrB,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,iBAAiB,CAAC,CAE5B;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,iBAAiB,EACrB,EAAE,SAAW,GACZ,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAsB,eAAe,CACnC,EAAE,EAAE,iBAAiB,EACrB,EAAE,SAAU,EACZ,MAAM,SAAW,GAChB,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAsB,cAAc,CAClC,EAAE,EAAE,iBAAiB,EACrB,EAAE,EAAE,MAAM,EACV,aAAa,SAA2B,GACvC,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,iBAAiB,EACrB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,CAAC,CASf"}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test-only access to the raw chat collections (S5 #1392).
|
|
3
|
-
*
|
|
4
|
-
* The `ChatService` collections are `#private` and the package index no longer
|
|
5
|
-
* exports the collection classes, so production consumers can only mutate chat
|
|
6
|
-
* state through the authorized facade. Security regression tests, however, must
|
|
7
|
-
* sometimes forge a row that "slipped past" the facade or read a row directly to
|
|
8
|
-
* assert tenant binding. They obtain the collections from the `ObjectRegistry`
|
|
9
|
-
* — the same registry `ChatService.create()` registers into — rather than
|
|
10
|
-
* reaching into the service's private fields. This keeps the production surface
|
|
11
|
-
* closed while letting tests exercise the collection layer.
|
|
12
|
-
*/
|
|
13
|
-
import { type SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
14
|
-
import type { AgentSessionCollection } from '../collections/AgentSessionCollection.js';
|
|
15
|
-
import type { ChatMessageCollection } from '../collections/ChatMessageCollection.js';
|
|
16
|
-
import type { ChatParticipantCollection } from '../collections/ChatParticipantCollection.js';
|
|
17
|
-
import type { ChatReactionCollection } from '../collections/ChatReactionCollection.js';
|
|
18
|
-
import type { ChatRoomCollection } from '../collections/ChatRoomCollection.js';
|
|
19
|
-
import type { ChatThreadCollection } from '../collections/ChatThreadCollection.js';
|
|
20
|
-
import type { VoiceSessionCollection } from '../collections/VoiceSessionCollection.js';
|
|
21
|
-
export interface RawChatCollections {
|
|
22
|
-
rooms: ChatRoomCollection;
|
|
23
|
-
messages: ChatMessageCollection;
|
|
24
|
-
participants: ChatParticipantCollection;
|
|
25
|
-
threads: ChatThreadCollection;
|
|
26
|
-
agentSessions: AgentSessionCollection;
|
|
27
|
-
reactions: ChatReactionCollection;
|
|
28
|
-
voiceSessions: VoiceSessionCollection;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Resolve the raw chat collections from the registry for the given DB options.
|
|
32
|
-
* Must be called after `ChatService.create(options)` has registered them.
|
|
33
|
-
*/
|
|
34
|
-
export declare function getRawChatCollections(options: SmrtObjectOptions): Promise<RawChatCollections>;
|
|
35
|
-
//# sourceMappingURL=raw-collections.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"raw-collections.d.ts","sourceRoot":"","sources":["../../src/__tests__/raw-collections.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;AACvF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AAC7F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;AACvF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AACnF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;AAEvF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,qBAAqB,CAAC;IAChC,YAAY,EAAE,yBAAyB,CAAC;IACxC,OAAO,EAAE,oBAAoB,CAAC;IAC9B,aAAa,EAAE,sBAAsB,CAAC;IACtC,SAAS,EAAE,sBAAsB,CAAC;IAClC,aAAa,EAAE,sBAAsB,CAAC;CACvC;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,kBAAkB,CAAC,CAsC7B"}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { expectNoA11yViolations, render, screen, userEvent, } from '@happyvertical/smrt-vitest/svelte';
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import ToolCallDisplay from '../ToolCallDisplay.svelte';
|
|
4
|
-
function makeToolCall(overrides = {}) {
|
|
5
|
-
return {
|
|
6
|
-
toolName: 'search',
|
|
7
|
-
toolCallId: 'tc-1',
|
|
8
|
-
arguments: { q: 'hello' },
|
|
9
|
-
status: 'success',
|
|
10
|
-
result: ['a', 'b'],
|
|
11
|
-
duration: 1200,
|
|
12
|
-
...overrides,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
describe('ToolCallDisplay', () => {
|
|
16
|
-
it('renders the tool name and a human status label', () => {
|
|
17
|
-
render(ToolCallDisplay, { props: { toolCall: makeToolCall() } });
|
|
18
|
-
expect(screen.getByText('search')).toBeInTheDocument();
|
|
19
|
-
expect(screen.getByText('Completed')).toBeInTheDocument();
|
|
20
|
-
});
|
|
21
|
-
// #2904 review, Copilot PR #2919 jAwua/jAwtb: AssistantDock/the demo route
|
|
22
|
-
// map an in-flight preview/apply action to this 'running' toolCall status
|
|
23
|
-
// (see action-status.test.ts) — assert it actually renders "Running", not
|
|
24
|
-
// "Completed".
|
|
25
|
-
it('renders "Running" for a running tool call', () => {
|
|
26
|
-
render(ToolCallDisplay, {
|
|
27
|
-
props: {
|
|
28
|
-
toolCall: makeToolCall({ status: 'running', result: undefined }),
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
expect(screen.getByText('Running')).toBeInTheDocument();
|
|
32
|
-
expect(screen.queryByText('Completed')).not.toBeInTheDocument();
|
|
33
|
-
});
|
|
34
|
-
it('expands to reveal arguments and result on click', async () => {
|
|
35
|
-
render(ToolCallDisplay, { props: { toolCall: makeToolCall() } });
|
|
36
|
-
const header = screen.getByRole('button');
|
|
37
|
-
expect(header).toHaveAttribute('aria-expanded', 'false');
|
|
38
|
-
await userEvent.click(header);
|
|
39
|
-
expect(header).toHaveAttribute('aria-expanded', 'true');
|
|
40
|
-
expect(screen.getByText('Arguments')).toBeInTheDocument();
|
|
41
|
-
expect(screen.getByText('Result')).toBeInTheDocument();
|
|
42
|
-
});
|
|
43
|
-
it('is axe-clean', async () => {
|
|
44
|
-
const { container } = render(ToolCallDisplay, {
|
|
45
|
-
props: { toolCall: makeToolCall({ status: 'error', error: 'boom' }) },
|
|
46
|
-
});
|
|
47
|
-
await expectNoA11yViolations(container);
|
|
48
|
-
});
|
|
49
|
-
// #2904: additive DataSurfaceActionResult rendering, used by AssistantDock.
|
|
50
|
-
function makeActionResult(overrides = {}) {
|
|
51
|
-
return {
|
|
52
|
-
version: 1,
|
|
53
|
-
requestId: 'req-1',
|
|
54
|
-
identity: {
|
|
55
|
-
surfaceId: 'orders',
|
|
56
|
-
kind: 'table',
|
|
57
|
-
subject: { type: 'tenant', id: 'tenant-a' },
|
|
58
|
-
},
|
|
59
|
-
actionId: 'archive',
|
|
60
|
-
phase: 'preview',
|
|
61
|
-
ok: true,
|
|
62
|
-
...overrides,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
it('a pending preview reads "Awaiting confirmation", starts expanded, and exposes Confirm/Reject', async () => {
|
|
66
|
-
const onconfirmaction = vi.fn();
|
|
67
|
-
const onrejectaction = vi.fn();
|
|
68
|
-
render(ToolCallDisplay, {
|
|
69
|
-
props: {
|
|
70
|
-
toolCall: makeToolCall(),
|
|
71
|
-
actionResult: makeActionResult(),
|
|
72
|
-
onconfirmaction,
|
|
73
|
-
onrejectaction,
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
// #2904 review fix: a preview must never read "Completed" and must be
|
|
77
|
-
// expanded by default so Confirm/Reject are visible without a click.
|
|
78
|
-
expect(screen.queryByText('Completed')).not.toBeInTheDocument();
|
|
79
|
-
expect(screen.getByText('Awaiting confirmation')).toBeInTheDocument();
|
|
80
|
-
const header = screen.getByRole('button', { name: /search/i });
|
|
81
|
-
expect(header).toHaveAttribute('aria-expanded', 'true');
|
|
82
|
-
expect(screen.getByText('Proposed change')).toBeInTheDocument();
|
|
83
|
-
await userEvent.click(screen.getByRole('button', { name: 'Confirm' }));
|
|
84
|
-
expect(onconfirmaction).toHaveBeenCalledOnce();
|
|
85
|
-
await userEvent.click(screen.getByRole('button', { name: 'Reject' }));
|
|
86
|
-
expect(onrejectaction).toHaveBeenCalledOnce();
|
|
87
|
-
});
|
|
88
|
-
it('the header toggle still collapses an awaiting-confirmation card', async () => {
|
|
89
|
-
render(ToolCallDisplay, {
|
|
90
|
-
props: { toolCall: makeToolCall(), actionResult: makeActionResult() },
|
|
91
|
-
});
|
|
92
|
-
const header = screen.getByRole('button', { name: /search/i });
|
|
93
|
-
expect(header).toHaveAttribute('aria-expanded', 'true');
|
|
94
|
-
await userEvent.click(header);
|
|
95
|
-
expect(header).toHaveAttribute('aria-expanded', 'false');
|
|
96
|
-
});
|
|
97
|
-
it('renders "Applied" with a result body and no Confirm/Reject once applied', async () => {
|
|
98
|
-
render(ToolCallDisplay, {
|
|
99
|
-
props: {
|
|
100
|
-
toolCall: makeToolCall(),
|
|
101
|
-
actionResult: makeActionResult({
|
|
102
|
-
phase: 'apply',
|
|
103
|
-
details: { rowId: 'order-2', status: 'shipped' },
|
|
104
|
-
}),
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
expect(screen.getByText('Applied')).toBeInTheDocument();
|
|
108
|
-
const header = screen.getByRole('button', { name: /search/i });
|
|
109
|
-
expect(header).toHaveAttribute('aria-expanded', 'true');
|
|
110
|
-
expect(screen.getByText('Applied change')).toBeInTheDocument();
|
|
111
|
-
expect(screen.getByText(/order-2/)).toBeInTheDocument();
|
|
112
|
-
expect(screen.queryByRole('button', { name: 'Confirm' })).not.toBeInTheDocument();
|
|
113
|
-
});
|
|
114
|
-
it('falls back to a plain confirmation line when an applied result has no details', () => {
|
|
115
|
-
render(ToolCallDisplay, {
|
|
116
|
-
props: {
|
|
117
|
-
toolCall: makeToolCall(),
|
|
118
|
-
actionResult: makeActionResult({ phase: 'apply' }),
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
expect(screen.getByText('Change applied successfully.')).toBeInTheDocument();
|
|
122
|
-
});
|
|
123
|
-
it('renders "Failed" with the reason when the action result is not ok', async () => {
|
|
124
|
-
render(ToolCallDisplay, {
|
|
125
|
-
props: {
|
|
126
|
-
toolCall: makeToolCall(),
|
|
127
|
-
actionResult: makeActionResult({ ok: false, reason: 'stale_revision' }),
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
expect(screen.getByText('Failed')).toBeInTheDocument();
|
|
131
|
-
const header = screen.getByRole('button', { name: /search/i });
|
|
132
|
-
expect(header).toHaveAttribute('aria-expanded', 'true');
|
|
133
|
-
expect(screen.getByText('stale_revision')).toBeInTheDocument();
|
|
134
|
-
});
|
|
135
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { parseAgentMessageBlocks } from './message-blocks';
|
|
3
|
-
describe('parseAgentMessageBlocks', () => {
|
|
4
|
-
it('preserves fenced markdown blocks as readable content', () => {
|
|
5
|
-
expect(parseAgentMessageBlocks('Here is a draft:\n```markdown\n# Heading\n\nBody copy\n```\nThanks.')).toEqual([
|
|
6
|
-
{ type: 'text', content: 'Here is a draft:\n' },
|
|
7
|
-
{ type: 'markdown', content: '# Heading\n\nBody copy' },
|
|
8
|
-
{ type: 'text', content: '\nThanks.' },
|
|
9
|
-
]);
|
|
10
|
-
});
|
|
11
|
-
it('extracts field updates from json blocks', () => {
|
|
12
|
-
expect(parseAgentMessageBlocks('```json\n{"fields":{"title":"New title","body":"Updated body"}}\n```')).toEqual([
|
|
13
|
-
{
|
|
14
|
-
type: 'fields',
|
|
15
|
-
fields: { title: 'New title', body: 'Updated body' },
|
|
16
|
-
},
|
|
17
|
-
]);
|
|
18
|
-
});
|
|
19
|
-
it('falls back to plain text for invalid json blocks', () => {
|
|
20
|
-
expect(parseAgentMessageBlocks('```json\n{not valid json}\n```')).toEqual([
|
|
21
|
-
{ type: 'text', content: '```json\n{not valid json}\n```' },
|
|
22
|
-
]);
|
|
23
|
-
});
|
|
24
|
-
it('supports windows newlines and whitespace after the language tag', () => {
|
|
25
|
-
expect(parseAgentMessageBlocks('Intro\r\n```markdown \r\n# Heading\r\n\r\nBody copy\r\n```\r\nDone')).toEqual([
|
|
26
|
-
{ type: 'text', content: 'Intro\r\n' },
|
|
27
|
-
{ type: 'markdown', content: '# Heading\r\n\r\nBody copy' },
|
|
28
|
-
{ type: 'text', content: '\r\nDone' },
|
|
29
|
-
]);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
/**
|
|
3
|
-
* Component-level coverage for AssistantComposer (#2904 review finding 4).
|
|
4
|
-
*
|
|
5
|
-
* Before this fix, `handleSend` cleared `content`/`stagedAttachments`
|
|
6
|
-
* synchronously before `onsend`'s promise settled, so a transport failure
|
|
7
|
-
* discarded the user's typed message with no visible error.
|
|
8
|
-
*/
|
|
9
|
-
import { expectNoA11yViolations, render, screen, userEvent, } from '@happyvertical/smrt-vitest/svelte';
|
|
10
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
11
|
-
import AssistantComposer from '../AssistantComposer.svelte';
|
|
12
|
-
function fileInput(container) {
|
|
13
|
-
const input = container.querySelector('input[type="file"]');
|
|
14
|
-
if (!input)
|
|
15
|
-
throw new Error('file input not found');
|
|
16
|
-
return input;
|
|
17
|
-
}
|
|
18
|
-
const png = (name = 'photo.png') => new File(['data'], name, { type: 'image/png' });
|
|
19
|
-
describe('AssistantComposer', () => {
|
|
20
|
-
it('keeps the draft and shows an inline error when onsend rejects', async () => {
|
|
21
|
-
const onsend = vi.fn().mockRejectedValue(new Error('network down'));
|
|
22
|
-
const onupload = vi.fn();
|
|
23
|
-
render(AssistantComposer, { props: { onsend, onupload } });
|
|
24
|
-
const textarea = screen.getByLabelText('Message');
|
|
25
|
-
await userEvent.type(textarea, 'hello there');
|
|
26
|
-
await userEvent.click(screen.getByRole('button', { name: 'Send' }));
|
|
27
|
-
expect(onsend).toHaveBeenCalledWith('hello there', []);
|
|
28
|
-
// The draft text must still be in the textarea — not cleared.
|
|
29
|
-
expect(textarea).toHaveValue('hello there');
|
|
30
|
-
expect(await screen.findByText(/Could not send: network down/i)).toBeInTheDocument();
|
|
31
|
-
});
|
|
32
|
-
it('clears the draft only after onsend resolves successfully', async () => {
|
|
33
|
-
const onsend = vi.fn().mockResolvedValue(undefined);
|
|
34
|
-
const onupload = vi.fn();
|
|
35
|
-
render(AssistantComposer, { props: { onsend, onupload } });
|
|
36
|
-
const textarea = screen.getByLabelText('Message');
|
|
37
|
-
await userEvent.type(textarea, 'hi');
|
|
38
|
-
await userEvent.click(screen.getByRole('button', { name: 'Send' }));
|
|
39
|
-
expect(onsend).toHaveBeenCalledWith('hi', []);
|
|
40
|
-
expect(textarea).toHaveValue('');
|
|
41
|
-
expect(screen.queryByText(/Could not send/i)).not.toBeInTheDocument();
|
|
42
|
-
});
|
|
43
|
-
// Cycle-2 third final: a rejecting onupload previously had no catch
|
|
44
|
-
// anywhere in handleFileChange, so the chip row never updated and no
|
|
45
|
-
// error appeared — the rejection escaped as an unhandled promise
|
|
46
|
-
// rejection from the DOM change event instead.
|
|
47
|
-
it('shows an inline error and keeps existing chips when onupload rejects', async () => {
|
|
48
|
-
const onsend = vi.fn();
|
|
49
|
-
const onupload = vi
|
|
50
|
-
.fn()
|
|
51
|
-
.mockResolvedValueOnce([{ id: 'att-1', name: 'existing.png' }])
|
|
52
|
-
.mockRejectedValueOnce(new Error('no writeEndpoint configured'));
|
|
53
|
-
const { container } = render(AssistantComposer, {
|
|
54
|
-
props: { onsend, onupload },
|
|
55
|
-
});
|
|
56
|
-
// First upload succeeds and stages a chip.
|
|
57
|
-
await userEvent.upload(fileInput(container), png('existing.png'));
|
|
58
|
-
expect(await screen.findByText('existing.png')).toBeInTheDocument();
|
|
59
|
-
// Second upload rejects: the existing chip must remain, and the
|
|
60
|
-
// rejection must show as an inline error.
|
|
61
|
-
await userEvent.upload(fileInput(container), png('second.png'));
|
|
62
|
-
expect(await screen.findByText(/Could not attach file: no writeEndpoint configured/i)).toBeInTheDocument();
|
|
63
|
-
expect(screen.getByText('existing.png')).toBeInTheDocument();
|
|
64
|
-
expect(screen.queryByText('second.png')).not.toBeInTheDocument();
|
|
65
|
-
});
|
|
66
|
-
// Cycle-3 second final finding 2: the hidden file input had no accessible
|
|
67
|
-
// name (the visually-hidden `clip: rect(0 0 0 0)` styling keeps it in the
|
|
68
|
-
// a11y tree, unlike ../shared/FileUpload.svelte's wrapping visible label).
|
|
69
|
-
it('is axe-clean', async () => {
|
|
70
|
-
const { container } = render(AssistantComposer, {
|
|
71
|
-
props: { onsend: vi.fn(), onupload: vi.fn() },
|
|
72
|
-
});
|
|
73
|
-
await expectNoA11yViolations(container);
|
|
74
|
-
});
|
|
75
|
-
});
|