Skip to content

Commit

Permalink
feat: Add getSelf and getOthers method to cursors
Browse files Browse the repository at this point in the history
  • Loading branch information
Arti M authored and artismarti committed Aug 1, 2023
1 parent 751469d commit 3d4c7bb
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 9 deletions.
28 changes: 28 additions & 0 deletions docs/class-definitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,34 @@ Example:
const lastPositions = space.cursors.getAll();
```

### getSelf

Get the last CursorUpdate for self.

```ts
type getSelf = () => <CursorUpdate | undefined>;
```

Example:

```ts
const selfPosition = space.cursors.getSelf();
```

### getOthers

Get the last CursorUpdate for each connection.

```ts
type getOthers = () => Record<ConnectionId, CursorUpdate>;
```

Example:

```ts
const otherPositions = space.cursors.getOthers();
```

### subscribe

Listen to `CursorUpdate` events. See [EventEmitter](/docs/usage.md#event-emitters) for overloading usage.
Expand Down
5 changes: 1 addition & 4 deletions src/CursorHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ import { Types } from 'ably';
import type { CursorUpdate } from './Cursors.js';
import type { StrictCursorsOptions } from './options/CursorsOptions.js';

type LastPosition = null | CursorUpdate;
type CursorName = string;
type CursorsLastPostion = Record<CursorName, LastPosition>;
type ConnectionId = string;
type ConnectionsLastPosition = Record<ConnectionId, null | CursorUpdate | CursorsLastPostion>;
type ConnectionsLastPosition = Record<ConnectionId, null | CursorUpdate>;

export default class CursorHistory {
constructor() {}
Expand Down
128 changes: 127 additions & 1 deletion src/Cursors.mockClient.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { it, describe, expect, vi, beforeEach, vitest, afterEach } from 'vitest';
import { Realtime, Types } from 'ably/promises';

import Space from './Space.js';
import Space, { SpaceMember } from './Space.js';
import Cursors from './Cursors.js';
import { createPresenceMessage } from './utilities/test/fakes.js';
import CursorBatching from './CursorBatching.js';
import { CURSOR_UPDATE } from './utilities/Constants.js';
import CursorDispensing from './CursorDispensing.js';
import CursorHistory from './CursorHistory.js';
import type { CursorUpdate } from './Cursors.js';

interface CursorsTestContext {
client: Types.RealtimePromise;
Expand All @@ -18,6 +19,8 @@ interface CursorsTestContext {
dispensing: CursorDispensing;
history: CursorHistory;
fakeMessageStub: Types.Message;
selfStub: SpaceMember;
lastCursorPositionsStub: Record<string, CursorUpdate>;
}

vi.mock('ably/promises');
Expand Down Expand Up @@ -335,6 +338,49 @@ describe('Cursors (mockClient)', () => {
});

describe('CursorHistory', () => {
beforeEach<CursorsTestContext>((context) => {
context.selfStub = {
connectionId: 'connectionId1',
clientId: 'clientId1',
isConnected: true,
profileData: {},
location: {},
lastEvent: { name: 'enter', timestamp: 0 },
};

context.lastCursorPositionsStub = {
connectionId1: {
connectionId: 'connectionId1',
clientId: 'clientId1',
data: {
color: 'blue',
},
position: {
x: 2,
y: 3,
},
},
connectionId2: {
connectionId: 'connectionId2',
clientId: 'clientId2',
data: undefined,
position: {
x: 25,
y: 44,
},
},
connectionId3: {
connectionId: 'connectionId3',
clientId: 'clientId3',
data: undefined,
position: {
x: 225,
y: 244,
},
},
};
});

it<CursorsTestContext>('returns an empty object if there is no members in the space', async ({
space,
channel,
Expand Down Expand Up @@ -410,5 +456,85 @@ describe('Cursors (mockClient)', () => {
expect(currentSpy).toHaveBeenCalledOnce();
expect(nextSpy).toHaveBeenCalledTimes(cursors.options.paginationLimit - 1);
});

it<CursorsTestContext>('returns undefined if self is not present in cursors', async ({ space }) => {
vi.spyOn(space.cursors, 'getAll').mockImplementation(async () => ({}));

const self = await space.cursors.getSelf();
expect(self).toBeUndefined();
});

it<CursorsTestContext>('returns the cursor update for self', async ({
space,
lastCursorPositionsStub,
selfStub,
}) => {
vi.spyOn(space.cursors, 'getAll').mockImplementation(async () => lastCursorPositionsStub);
vi.spyOn(space, 'getSelf').mockReturnValue(selfStub);

const selfCursor = await space.cursors.getSelf();
expect(selfCursor).toEqual(lastCursorPositionsStub['connectionId1']);
});

it<CursorsTestContext>('returns an empty object if self is not present in cursors', async ({ space }) => {
vi.spyOn(space.cursors, 'getAll').mockResolvedValue({});
vi.spyOn(space, 'getSelf').mockReturnValue(undefined);

const others = await space.cursors.getOthers();
expect(others).toEqual({});
});

it<CursorsTestContext>('returns an empty object if there are no other cursors', async ({ space, selfStub }) => {
const onlyMyCursor = {
connectionId1: {
connectionId: 'connectionId1',
clientId: 'clientId1',
data: {
color: 'blue',
},
position: {
x: 2,
y: 3,
},
},
};

vi.spyOn(space.cursors, 'getAll').mockResolvedValue(onlyMyCursor);
vi.spyOn(space, 'getSelf').mockReturnValue(selfStub);

const others = await space.cursors.getOthers();
expect(others).toEqual({});
});

it<CursorsTestContext>('returns an object of other cursors', async ({
space,
selfStub,
lastCursorPositionsStub,
}) => {
vi.spyOn(space.cursors, 'getAll').mockResolvedValue(lastCursorPositionsStub);
vi.spyOn(space, 'getSelf').mockReturnValue(selfStub);

const others = await space.cursors.getOthers();
expect(others).toEqual({
connectionId2: {
connectionId: 'connectionId2',
clientId: 'clientId2',
position: {
x: 25,
y: 44,
},
data: undefined,
},
connectionId3: {
connectionId: 'connectionId3',
clientId: 'clientId3',
position: {
x: 225,
y: 244,
},
data: undefined,
},
});
});
});
});
23 changes: 19 additions & 4 deletions src/Cursors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,15 @@ import CursorHistory from './CursorHistory.js';
import { CURSOR_UPDATE } from './utilities/Constants.js';

import type { CursorsOptions, StrictCursorsOptions } from './options/CursorsOptions.js';

type ConnectionId = string;
type CursorPosition = { x: number; y: number };

type CursorData = Record<string, unknown>;

type CursorUpdate = {
clientId: string;
connectionId: string;
position: CursorPosition;
data?: CursorData;
};

type CursorsEventMap = {
cursorsUpdate: Record<string, CursorUpdate>;
};
Expand Down Expand Up @@ -161,6 +158,24 @@ export default class Cursors extends EventEmitter<CursorsEventMap> {
const channel = this.getChannel();
return await this.cursorHistory.getLastCursorUpdate(channel, this.options.paginationLimit);
}

async getSelf(): Promise<CursorUpdate | undefined> {
const self = this.space.getSelf();
if (!self) return;

const allCursors = await this.getAll();
return allCursors[self.connectionId] as CursorUpdate;
}

async getOthers(): Promise<Record<ConnectionId, null | CursorUpdate>> {
const self = this.space.getSelf();
if (!self) return {};

const allCursors = await this.getAll();
const allCursorsFiltered = allCursors;
delete allCursorsFiltered[self.connectionId];
return allCursorsFiltered;
}
}

export { type CursorPosition, type CursorData, type CursorUpdate };

0 comments on commit 3d4c7bb

Please sign in to comment.