Skip to content

Commit 4a5d1e8

Browse files
committed
fix(chat): keep the longest overlapping mention chip and centralize the integration matcher mock
1 parent 52a7a88 commit 4a5d1e8

8 files changed

Lines changed: 45 additions & 12 deletions

File tree

‎apps/sim/app/o/[organizationId]/home/components/composer/composer.test.tsx‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
deploymentShapeMock,
66
deploymentShapeMockFns,
77
} from '@sim/testing/mocks/deployment-shape.mock'
8+
import { integrationMatcherMock } from '@sim/testing/mocks/integration-matcher.mock'
89
import {
910
organizationProviderMock,
1011
organizationProviderMockFns,
@@ -57,9 +58,7 @@ vi.mock('@/hooks/queries/skills', () => ({
5758
}),
5859
}))
5960
vi.mock('@/hooks/queries/mcp', () => ({ useMcpToolServers: () => ({ data: [] }) }))
60-
vi.mock('@/blocks/integration-matcher', () => ({
61-
getIntegrationMatcher: () => ({ regex: null, byName: new Map() }),
62-
}))
61+
vi.mock('@/blocks/integration-matcher', () => integrationMatcherMock)
6362
vi.mock(
6463
'@/app/workspace/[workspaceId]/home/components/user-input/components/plus-menu-dropdown/plus-menu-dropdown',
6564
async () => {

‎apps/sim/app/o/[organizationId]/home/organization-home.test.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
deploymentShapeMock,
88
deploymentShapeMockFns,
99
} from '@sim/testing/mocks/deployment-shape.mock'
10+
import { integrationMatcherMock } from '@sim/testing/mocks/integration-matcher.mock'
1011
import {
1112
kbConnectorsQueriesMock,
1213
kbConnectorsQueriesMockFns,
@@ -46,7 +47,7 @@ vi.mock('@/app/workspace/[workspaceId]/providers/feature-flags-provider', () =>
4647
useFeatureFlag: (name: string) => (name === 'mothership-plan-mode' ? mocks.plan : false),
4748
}))
4849
vi.mock('@/lib/core/config/deployment-shape', () => deploymentShapeMock)
49-
vi.mock('@/blocks/integration-matcher', () => ({ mentionifyIntegrations: (text: string) => text }))
50+
vi.mock('@/blocks/integration-matcher', () => integrationMatcherMock)
5051
vi.mock('next/navigation', () => nextNavigationMock)
5152
vi.mock('@tanstack/react-query', () => reactQueryMock)
5253
vi.mock('@/app/workspace/[workspaceId]/home/hooks/use-resource-panel', () => ({

‎apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/use-chat-find.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ export function useChatFind({
127127
const [index, setIndex] = useState(0)
128128
if (scope !== chatId) {
129129
setScope(chatId)
130+
// A pending chat adopting its id (undefined → id) is the same conversation.
130131
if (scope !== undefined) {
131132
setIsOpen(false)
132133
setQuery('')

‎apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/prompt-editor/use-prompt-editor.test.tsx‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
* @vitest-environment jsdom
33
*/
44
import { act, type ReactNode } from 'react'
5+
import { integrationMatcherMock } from '@sim/testing/mocks/integration-matcher.mock'
56
import { createRoot, type Root } from 'react-dom/client'
67
import { describe, expect, it, vi } from 'vitest'
78

89
vi.mock('@/hooks/queries/skills', () => ({ useSkills: () => ({ data: [] }) }))
910
vi.mock('@/hooks/queries/mcp', () => ({ useMcpToolServers: () => ({ data: [] }) }))
10-
vi.mock('@/blocks/integration-matcher', () => ({
11-
getIntegrationMatcher: () => ({ regex: null, byName: new Map() }),
12-
}))
11+
vi.mock('@/blocks/integration-matcher', () => integrationMatcherMock)
1312

1413
import { SIM_SELECTION_MIME } from '@/lib/mothership/chat/selection-clipboard'
1514
import {

‎apps/sim/app/workspace/[workspaceId]/home/components/user-message-content/utils.test.ts‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1+
import { integrationMatcherMock } from '@sim/testing/mocks/integration-matcher.mock'
12
import { describe, expect, it, vi } from 'vitest'
23
import { computeMentionRanges } from '@/app/workspace/[workspaceId]/home/components/user-message-content/utils'
34
import type { ChatMessageContext } from '@/app/workspace/[workspaceId]/home/types'
45

5-
vi.mock('@/blocks/integration-matcher', () => ({
6-
getIntegrationMatcher: () => ({ regex: null, byName: new Map() }),
7-
}))
6+
vi.mock('@/blocks/integration-matcher', () => integrationMatcherMock)
87

98
function spans(text: string, contexts: ChatMessageContext[]): string[] {
109
return computeMentionRanges(text, contexts).map((range) => text.slice(range.start, range.end))
@@ -26,6 +25,18 @@ describe('computeMentionRanges', () => {
2625
expect(spans('@Workflow-2', [workflow])).toEqual([])
2726
})
2827

28+
it('keeps only the longest of overlapping mentions', () => {
29+
const report: ChatMessageContext = { kind: 'file', label: 'report' }
30+
const reportPdf: ChatMessageContext = { kind: 'file', label: 'report.pdf' }
31+
expect(spans('compare @report.pdf with @report', [report, reportPdf])).toEqual([
32+
'@report.pdf',
33+
'@report',
34+
])
35+
expect(
36+
spans('@Workflow 2 and @Workflow', [workflow, { kind: 'workflow', label: 'Workflow 2' }])
37+
).toEqual(['@Workflow 2', '@Workflow'])
38+
})
39+
2940
it('matches slash commands by their slash prefix', () => {
3041
expect(spans('/research the topic', [{ kind: 'slash_command', label: 'research' }])).toEqual([
3142
'/research',

‎apps/sim/app/workspace/[workspaceId]/home/components/user-message-content/utils.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,12 @@ export function computeMentionRanges(text: string, contexts: ChatMessageContext[
4545
ranges.push(range)
4646
}
4747

48-
ranges.sort((a, b) => a.start - b.start)
49-
return ranges
48+
ranges.sort((a, b) => a.start - b.start || b.end - a.end)
49+
const merged: MentionRange[] = []
50+
for (const range of ranges) {
51+
if (range.start >= (merged.at(-1)?.end ?? 0)) merged.push(range)
52+
}
53+
return merged
5054
}
5155

5256
/**

‎packages/testing/src/mocks/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ export {
383383
inputValidationMock,
384384
inputValidationMockFns,
385385
} from './input-validation.mock'
386+
export { integrationMatcherMock } from './integration-matcher.mock'
386387
export {
387388
integrationsAvailabilityMock,
388389
integrationsAvailabilityMockFns,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { vi } from 'vitest'
2+
3+
/**
4+
* Static mock module for `@/blocks/integration-matcher` that knows no integrations, so tests never
5+
* load the block registry: the matcher has no regex and `mentionifyIntegrations` returns its input.
6+
*
7+
* @example
8+
* ```ts
9+
* import { integrationMatcherMock } from '@sim/testing/mocks/integration-matcher.mock'
10+
*
11+
* vi.mock('@/blocks/integration-matcher', () => integrationMatcherMock)
12+
* ```
13+
*/
14+
export const integrationMatcherMock = {
15+
getIntegrationMatcher: vi.fn(() => ({ regex: null, byName: new Map() })),
16+
mentionifyIntegrations: vi.fn((text: string) => text),
17+
}

0 commit comments

Comments
 (0)