Skip to content

Commit f717d60

Browse files
authored
test(tables): cover the views realtime signal (emit + on-success-only) (#6101)
- events.test.ts: signalTableViewsChanged appends a single 'views' event carrying the tableId (through the real memory buffer). - views/service.test.ts: create/update/delete emit signalTableViewsChanged on real success, and DON'T on a no-op (a PATCH/DELETE targeting a missing view changes nothing, so it must not signal). Mirrors delete-runner's signal-path coverage; drives the DB via the shared dbChainMock. - Add tableViews to the comprehensive @sim/db/schema test mock so the service tests can queue the in-transaction existence row.
1 parent 9b72801 commit f717d60

3 files changed

Lines changed: 139 additions & 3 deletions

File tree

apps/sim/lib/table/events.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ beforeAll(() => {
1111
afterAll(resetEnvMock)
1212

1313
import type { TableEvent } from '@/lib/table/events'
14-
import { appendTableEvent, getLatestTableEventId, readTableEventsSince } from '@/lib/table/events'
14+
import {
15+
appendTableEvent,
16+
getLatestTableEventId,
17+
readTableEventsSince,
18+
signalTableViewsChanged,
19+
} from '@/lib/table/events'
1520

1621
/** Module-level memory buffer can't be reset without vi.resetModules — use a
1722
* unique tableId per test to avoid cross-test bleed. */
@@ -75,3 +80,23 @@ describe('getLatestTableEventId (memory buffer)', () => {
7580
}
7681
})
7782
})
83+
84+
describe('signalTableViewsChanged', () => {
85+
beforeEach(() => {
86+
vi.clearAllMocks()
87+
})
88+
89+
it('appends a single views event carrying the tableId', async () => {
90+
const tableId = uniqueTableId()
91+
// The memory-buffer append is synchronous, so the fire-and-forget signal is
92+
// observable immediately without awaiting the (unreturned) append promise.
93+
signalTableViewsChanged(tableId)
94+
95+
const result = await readTableEventsSince(tableId, 0)
96+
expect(result.status).toBe('ok')
97+
if (result.status === 'ok') {
98+
expect(result.events).toHaveLength(1)
99+
expect(result.events[0].event).toEqual({ kind: 'views', tableId })
100+
}
101+
})
102+
})

apps/sim/lib/table/views/service.test.ts

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
/**
22
* @vitest-environment node
33
*/
4-
import { describe, expect, it } from 'vitest'
4+
import { tableViews } from '@sim/db/schema'
5+
import { dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing'
6+
import { beforeEach, describe, expect, it, vi } from 'vitest'
57
import type { ColumnDefinition, TableViewConfig } from '@/lib/table/types'
6-
import { normalizeStoredViewConfig, pruneViewConfig } from '@/lib/table/views/service'
8+
9+
const { mockSignalTableViewsChanged } = vi.hoisted(() => ({
10+
mockSignalTableViewsChanged: vi.fn(),
11+
}))
12+
vi.mock('@/lib/table/events', () => ({
13+
signalTableViewsChanged: mockSignalTableViewsChanged,
14+
}))
15+
16+
import {
17+
createTableView,
18+
deleteTableView,
19+
normalizeStoredViewConfig,
20+
pruneViewConfig,
21+
updateTableView,
22+
} from '@/lib/table/views/service'
723

824
const columns: ColumnDefinition[] = [
925
{ id: 'col_a', name: 'Name', type: 'text' },
@@ -84,3 +100,87 @@ describe('normalizeStoredViewConfig', () => {
84100
expect(out.filter).toBeNull()
85101
})
86102
})
103+
104+
describe('table-view mutations signal collaborators', () => {
105+
const columns: ColumnDefinition[] = []
106+
const viewRow = {
107+
id: 'view-1',
108+
tableId: 'table-1',
109+
workspaceId: 'ws-1',
110+
name: 'My View',
111+
config: {},
112+
isDefault: false,
113+
createdBy: 'user-1',
114+
createdAt: new Date('2026-01-01T00:00:00.000Z'),
115+
updatedAt: new Date('2026-01-01T00:00:00.000Z'),
116+
}
117+
118+
beforeEach(() => {
119+
vi.clearAllMocks()
120+
resetDbChainMock()
121+
})
122+
123+
it('createTableView signals the table after inserting', async () => {
124+
dbChainMockFns.returning.mockResolvedValueOnce([viewRow])
125+
126+
await createTableView({
127+
tableId: 'table-1',
128+
workspaceId: 'ws-1',
129+
name: 'My View',
130+
config: {},
131+
userId: 'user-1',
132+
columns,
133+
})
134+
135+
expect(mockSignalTableViewsChanged).toHaveBeenCalledTimes(1)
136+
expect(mockSignalTableViewsChanged).toHaveBeenCalledWith('table-1')
137+
})
138+
139+
it('updateTableView signals when the target view exists', async () => {
140+
queueTableRows(tableViews, [{ id: 'view-1' }]) // the in-transaction existence pre-check
141+
dbChainMockFns.returning.mockResolvedValueOnce([viewRow]) // the update returning
142+
143+
const result = await updateTableView({
144+
viewId: 'view-1',
145+
tableId: 'table-1',
146+
name: 'Renamed',
147+
columns,
148+
})
149+
150+
expect(result).not.toBeNull()
151+
expect(mockSignalTableViewsChanged).toHaveBeenCalledTimes(1)
152+
expect(mockSignalTableViewsChanged).toHaveBeenCalledWith('table-1')
153+
})
154+
155+
it('updateTableView does NOT signal a no-op update on a missing view', async () => {
156+
// No queued existence row → the pre-check finds nothing → returns null before any write.
157+
const result = await updateTableView({
158+
viewId: 'missing',
159+
tableId: 'table-1',
160+
name: 'Renamed',
161+
columns,
162+
})
163+
164+
expect(result).toBeNull()
165+
expect(mockSignalTableViewsChanged).not.toHaveBeenCalled()
166+
})
167+
168+
it('deleteTableView signals when a row was actually deleted', async () => {
169+
dbChainMockFns.returning.mockResolvedValueOnce([{ id: 'view-1' }])
170+
171+
const deleted = await deleteTableView('view-1', 'table-1')
172+
173+
expect(deleted).toBe(true)
174+
expect(mockSignalTableViewsChanged).toHaveBeenCalledTimes(1)
175+
expect(mockSignalTableViewsChanged).toHaveBeenCalledWith('table-1')
176+
})
177+
178+
it('deleteTableView does NOT signal when nothing was deleted', async () => {
179+
dbChainMockFns.returning.mockResolvedValueOnce([])
180+
181+
const deleted = await deleteTableView('missing', 'table-1')
182+
183+
expect(deleted).toBe(false)
184+
expect(mockSignalTableViewsChanged).not.toHaveBeenCalled()
185+
})
186+
})

packages/testing/src/mocks/schema.mock.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,17 @@ export const schemaMock = {
11361136
updatedAt: 'updatedAt',
11371137
completedAt: 'completedAt',
11381138
},
1139+
tableViews: {
1140+
id: 'id',
1141+
tableId: 'tableId',
1142+
workspaceId: 'workspaceId',
1143+
name: 'name',
1144+
config: 'config',
1145+
isDefault: 'isDefault',
1146+
createdBy: 'createdBy',
1147+
createdAt: 'createdAt',
1148+
updatedAt: 'updatedAt',
1149+
},
11391150
tableRowExecutions: {
11401151
tableId: 'tableId',
11411152
rowId: 'rowId',

0 commit comments

Comments
 (0)