Skip to content

Commit a6e4ab3

Browse files
committedMar 18, 2025
Add test to load more rooms
1 parent 9cb49b5 commit a6e4ab3

File tree

7 files changed

+195
-11
lines changed

7 files changed

+195
-11
lines changed
 

‎packages/host/app/components/ai-assistant/past-sessions.gts

+4-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ export default class AiAssistantPastSessionsList extends Component<Signature> {
6969
<PastSessionItem @session={{session}} @actions={{@roomActions}} />
7070
{{/each}}
7171
{{#if this.matrixService.loadingMoreAIRooms}}
72-
<li class='loading-indicator-container'>
72+
<li
73+
class='loading-indicator-container'
74+
data-test-loading-more-rooms
75+
>
7376
<LoadingIndicator
7477
@color='var(--boxel-dark)'
7578
class='loading-indicator'

‎packages/host/app/services/matrix-sdk-loader.ts

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Service from '@ember/service';
33
import { service } from '@ember/service';
44

55
import * as MatrixSDK from 'matrix-js-sdk';
6+
import { SlidingSync } from 'matrix-js-sdk/lib/sliding-sync';
67

78
import { RealmAuthClient } from '@cardstack/runtime-common/realm-auth-client';
89

@@ -23,6 +24,11 @@ export default class MatrixSDKLoader extends Service {
2324
}
2425
return this.#extended;
2526
}
27+
28+
// For testing purposes, we need to mock the SlidingSync class
29+
get SlidingSync() {
30+
return SlidingSync;
31+
}
2632
}
2733

2834
export class ExtendedMatrixSDK {

‎packages/host/app/services/matrix-service.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ import {
1616
type ISendEventResponse,
1717
} from 'matrix-js-sdk';
1818
import {
19-
SlidingSync,
20-
MSC3575List,
19+
type SlidingSync,
20+
type MSC3575List,
2121
SlidingSyncEvent,
2222
SlidingSyncState,
23-
MSC3575SlidingSyncResponse,
23+
type MSC3575SlidingSyncResponse,
2424
} from 'matrix-js-sdk/lib/sliding-sync';
2525
import stringify from 'safe-stable-stringify';
2626
import { md5 } from 'super-fast-md5';
@@ -252,7 +252,7 @@ export default class MatrixService extends Service {
252252
e.event.content.realms,
253253
);
254254
await this.loginToRealms();
255-
await this.maybeLoadMoreAuthRooms(e.event.content.realms);
255+
await this.loadMoreAuthRooms(e.event.content.realms);
256256
}
257257
},
258258
],
@@ -549,14 +549,14 @@ export default class MatrixService extends Service {
549549
timeline_limit: 1,
550550
required_state: [['*', '*']],
551551
});
552-
this.slidingSync = new SlidingSync(
552+
this.slidingSync = new this.matrixSdkLoader.SlidingSync(
553553
this.client.baseUrl,
554554
lists,
555555
{
556556
timeline_limit: SLIDING_SYNC_LIST_TIMELINE_LIMIT,
557557
},
558558
this.client as any,
559-
environment === 'test' ? 200 : 30000,
559+
environment === 'test' ? 200 : 2000,
560560
);
561561
this.slidingSync.on(
562562
SlidingSyncEvent.Lifecycle,
@@ -1675,7 +1675,7 @@ export default class MatrixService extends Service {
16751675
return this.isLoadingMoreAIRooms;
16761676
}
16771677

1678-
async maybeLoadMoreAuthRooms(realms: string[]) {
1678+
async loadMoreAuthRooms(realms: string[]) {
16791679
if (!this.slidingSync) return;
16801680

16811681
let currentList = this.slidingSync.getListParams(

‎packages/host/tests/acceptance/ai-assistant-test.gts

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { click, fillIn, waitFor } from '@ember/test-helpers';
1+
import { click, fillIn, waitFor, waitUntil } from '@ember/test-helpers';
22

33
import { module, test } from 'qunit';
44

@@ -431,4 +431,41 @@ module('Acceptance | AI Assistant tests', function (hooks) {
431431
assert.dom('[data-test-autoattached-file]').exists();
432432
assert.dom(`[data-test-autoattached-file]`).hasText('person.gts');
433433
});
434+
435+
test('loads more AI rooms when scrolling', async function (assert) {
436+
for (let i = 1; i <= 15; i++) {
437+
createAndJoinRoom({
438+
sender: '@testuser:localhost',
439+
name: `AI Room ${i}`,
440+
});
441+
}
442+
443+
await visitOperatorMode({
444+
stacks: [
445+
[
446+
{
447+
id: `${testRealmURL}index`,
448+
format: 'isolated',
449+
},
450+
],
451+
],
452+
});
453+
454+
await click('[data-test-open-ai-assistant]');
455+
await click('[data-test-past-sessions-button]');
456+
457+
assert.dom('[data-test-past-sessions]').exists();
458+
assert.dom('[data-test-joined-room]').exists({ count: 10 });
459+
460+
let pastSessionsElement = document.querySelector(
461+
'[data-test-past-sessions] .body ul',
462+
);
463+
if (pastSessionsElement) {
464+
pastSessionsElement.scrollTop = pastSessionsElement.scrollHeight;
465+
}
466+
await waitUntil(
467+
() => document.querySelectorAll('[data-test-joined-room]').length === 16,
468+
);
469+
assert.dom('[data-test-joined-room]').exists({ count: 16 });
470+
});
434471
});

‎packages/host/tests/helpers/mock-matrix.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type MatrixService from '@cardstack/host/services/matrix-service';
66
import MessageService from '@cardstack/host/services/message-service';
77

88
import { MockSDK } from './mock-matrix/_sdk';
9+
import { MockSlidingSync } from './mock-matrix/_sliding-sync';
910
import { MockUtils } from './mock-matrix/_utils';
1011

1112
export interface Config {
@@ -23,10 +24,16 @@ export function setupMockMatrix(
2324
hooks: NestedHooks,
2425
opts: Config = {},
2526
): MockUtils {
26-
let testState: { owner?: Owner; sdk?: MockSDK; opts?: Config } = {
27+
let testState: {
28+
owner?: Owner;
29+
sdk?: MockSDK;
30+
opts?: Config;
31+
slidingSync?: SlidingSync;
32+
} = {
2733
owner: undefined,
2834
sdk: undefined,
2935
opts: undefined,
36+
slidingSync: undefined,
3037
};
3138

3239
let mockUtils = new MockUtils(testState);
@@ -77,6 +84,7 @@ export function setupMockMatrix(
7784
async load() {
7885
return sdk;
7986
},
87+
SlidingSync: MockSlidingSync,
8088
},
8189
{
8290
instantiate: false,

‎packages/host/tests/helpers/mock-matrix/_client.ts

+66-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import { MatrixEvent } from 'matrix-js-sdk';
22

33
import * as MatrixSDK from 'matrix-js-sdk';
44

5+
import {
6+
MSC3575SlidingSyncRequest,
7+
MSC3575SlidingSyncResponse,
8+
} from 'matrix-js-sdk/lib/sliding-sync';
9+
510
import { baseRealm, unixTime } from '@cardstack/runtime-common';
611

712
import {
@@ -57,8 +62,12 @@ export class MockClient implements ExtendedClient {
5762
}
5863

5964
async startClient(
60-
_opts?: MatrixSDK.IStartClientOpts | undefined,
65+
opts?: MatrixSDK.IStartClientOpts | undefined,
6166
): Promise<void> {
67+
if (opts?.slidingSync) {
68+
await opts.slidingSync.start();
69+
}
70+
6271
this.serverState.onEvent((serverEvent: IEvent) => {
6372
this.emitEvent(new MatrixEvent(serverEvent));
6473
});
@@ -619,4 +628,60 @@ export class MockClient implements ExtendedClient {
619628
mxcUrlToHttp(mxcUrl: string): string {
620629
return mxcUrl.replace('mxc://', 'http://mock-server/');
621630
}
631+
632+
async slidingSync(
633+
req: MSC3575SlidingSyncRequest,
634+
_proxyBaseUrl: string,
635+
_signal: AbortSignal,
636+
): Promise<MSC3575SlidingSyncResponse> {
637+
let listKey = Object.keys(req.lists || {})[0];
638+
if (!listKey || !req.lists?.[listKey]?.ranges?.[0]) {
639+
return Promise.resolve({
640+
pos: '0',
641+
lists: {},
642+
rooms: {},
643+
extensions: {},
644+
});
645+
}
646+
647+
let [start, end] = req.lists[listKey].ranges[0];
648+
let roomsInRange = this.serverState.rooms
649+
.filter((r) => r.id.includes('mock'))
650+
.slice(start, end + 1);
651+
652+
let response: MSC3575SlidingSyncResponse = {
653+
pos: String(Date.now()),
654+
lists: {
655+
[listKey]: {
656+
count: this.serverState.rooms.length,
657+
ops: [
658+
{
659+
op: 'SYNC',
660+
range: [start, end],
661+
room_ids: roomsInRange.map((r) => r.id),
662+
},
663+
],
664+
},
665+
},
666+
rooms: {},
667+
extensions: {},
668+
};
669+
670+
roomsInRange.forEach((room) => {
671+
response.rooms[room.id] = {
672+
name:
673+
this.serverState.getRoomState(room.id, 'm.room.name', '')?.content
674+
?.name ?? 'room',
675+
required_state: [],
676+
timeline: this.serverState.getRoomEvents(room.id),
677+
notification_count: 0,
678+
highlight_count: 0,
679+
joined_count: 1,
680+
invited_count: 0,
681+
initial: true,
682+
};
683+
});
684+
685+
return Promise.resolve(response);
686+
}
622687
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { MatrixEvent } from 'matrix-js-sdk';
2+
import * as MatrixSDK from 'matrix-js-sdk';
3+
import {
4+
SlidingSync,
5+
SlidingSyncEvent,
6+
SlidingSyncState,
7+
} from 'matrix-js-sdk/lib/sliding-sync';
8+
9+
export class MockSlidingSync extends SlidingSync {
10+
private lifecycleCallbacks: Function[] = [];
11+
private listCallbacks: Function[] = [];
12+
13+
on(event: string, callback: Function) {
14+
if (event === SlidingSyncEvent.Lifecycle) {
15+
this.lifecycleCallbacks.push(callback);
16+
}
17+
if (event === SlidingSyncEvent.List) {
18+
this.listCallbacks.push(callback);
19+
}
20+
return this;
21+
}
22+
23+
emit(event: string, ...args: any[]) {
24+
if (event === SlidingSyncEvent.Lifecycle) {
25+
this.lifecycleCallbacks.forEach((cb) => cb(...args));
26+
}
27+
if (event === SlidingSyncEvent.List) {
28+
this.listCallbacks.forEach((cb) => cb(...args));
29+
}
30+
return true;
31+
}
32+
33+
async start() {
34+
let aiRoomList = this.getListParams('ai-room');
35+
if (!aiRoomList) {
36+
return;
37+
}
38+
let slidingResponse = await this.client.slidingSync(
39+
{
40+
lists: { ['ai-room']: aiRoomList },
41+
room_subscriptions: undefined,
42+
},
43+
'',
44+
{} as any,
45+
);
46+
47+
this.emit(
48+
SlidingSyncEvent.Lifecycle,
49+
SlidingSyncState.Complete,
50+
slidingResponse,
51+
);
52+
53+
Object.values(slidingResponse.rooms ?? {}).forEach(
54+
(room: MSC3575RoomData) => {
55+
room.timeline.forEach((event: MatrixSDK.IRoomEvent) => {
56+
this.client.emitEvent(new MatrixEvent(event));
57+
});
58+
},
59+
);
60+
}
61+
62+
resend() {
63+
this.start();
64+
}
65+
}

0 commit comments

Comments
 (0)