Skip to content

Commit 3586a53

Browse files
authored
Merge pull request #1706 from notaphplover/refactor/extract-use-game-utils
Extract use game utils
2 parents 4961942 + 45d1970 commit 3586a53

File tree

9 files changed

+915
-1
lines changed

9 files changed

+915
-1
lines changed

packages/frontend/web-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"dependencies": {
2222
"@cornie-js/api-http-client": "workspace:*",
2323
"@cornie-js/api-models": "workspace:*",
24+
"@cornie-js/eventsource": "workspace:*",
2425
"@cornie-js/frontend-api-rtk-query": "workspace:*",
2526
"@cornie-js/frontend-common": "workspace:*",
2627
"@emotion/react": "11.13.3",

packages/frontend/web-ui/src/common/env/services/EnvironmentService.tsx renamed to packages/frontend/web-ui/src/common/env/services/EnvironmentService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Environment } from '../models/Environment';
22

3-
class EnvironmentService {
3+
export class EnvironmentService {
44
readonly #env: Environment;
55

66
constructor() {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { jest } from '@jest/globals';
2+
3+
import { Environment } from '../../models/Environment';
4+
import type { EnvironmentService as OriginalEnvironmentService } from '../EnvironmentService';
5+
6+
export const environmentService: Partial<OriginalEnvironmentService> = {
7+
getEnvironment: jest.fn<() => Environment>(),
8+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { EventSource } from '@cornie-js/eventsource';
2+
3+
export interface CornieEventSourceInit extends EventSourceInit {
4+
defaultLastEventId?: string | undefined;
5+
getAccessToken: () => string;
6+
}
7+
8+
export class CornieEventSource extends EventSource {
9+
readonly #defaultLastEventId: string | undefined;
10+
readonly #getAccessToken: () => string;
11+
12+
constructor(url: string, eventSourceInitDict: CornieEventSourceInit) {
13+
const { defaultLastEventId, getAccessToken, ...eventSourceInit } =
14+
eventSourceInitDict;
15+
16+
super(url, eventSourceInit);
17+
18+
this.#defaultLastEventId = defaultLastEventId;
19+
this.#getAccessToken = getAccessToken;
20+
}
21+
22+
protected override _buildHeaders(): Headers {
23+
const headers: Headers = super._buildHeaders();
24+
25+
headers.set('authorization', `Bearer ${this.#getAccessToken()}`);
26+
27+
if (
28+
!headers.has('last-event-id') &&
29+
this.#defaultLastEventId !== undefined
30+
) {
31+
headers.set('last-event-id', this.#defaultLastEventId);
32+
}
33+
34+
return headers;
35+
}
36+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { jest } from '@jest/globals';
2+
3+
import { EventSource } from '@cornie-js/eventsource';
4+
5+
export const CornieEventSource: jest.Mock<(this: EventSource) => void> = jest
6+
.fn()
7+
.mockImplementation(function (this: EventSource) {
8+
this.addEventListener = jest.fn();
9+
});
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { beforeAll, describe, expect, it, jest } from '@jest/globals';
2+
3+
jest.mock(
4+
'../../app/store/features/authSlice',
5+
() => ({
6+
selectAuthToken: jest.fn(),
7+
}),
8+
{ virtual: true },
9+
);
10+
jest.mock(
11+
require.resolve('../../app/store/features/authSlice'),
12+
() => ({
13+
selectAuthToken: jest.fn(),
14+
}),
15+
{ virtual: true },
16+
);
17+
jest.mock(
18+
'../../app/store/store',
19+
() => ({
20+
store: {
21+
getState: jest.fn(),
22+
},
23+
}),
24+
{ virtual: true },
25+
);
26+
jest.mock(
27+
require.resolve('../../app/store/store'),
28+
() => ({
29+
store: {
30+
getState: jest.fn(),
31+
},
32+
}),
33+
{ virtual: true },
34+
);
35+
jest.mock('../../common/env/services/EnvironmentService');
36+
jest.mock('../../common/http/services/CornieEventSource');
37+
38+
import { models as apiModels } from '@cornie-js/api-models';
39+
40+
import { Environment } from '../../common/env/models/Environment';
41+
import {
42+
EnvironmentService,
43+
environmentService,
44+
} from '../../common/env/services/EnvironmentService';
45+
import {
46+
CornieEventSource,
47+
CornieEventSourceInit,
48+
} from '../../common/http/services/CornieEventSource';
49+
import { buildEventSource } from './buildEventSource';
50+
51+
describe(buildEventSource.name, () => {
52+
describe('having an active game with last event id', () => {
53+
let gameFixture: apiModels.ActiveGameV1;
54+
let setMessageEventsQueueMock: jest.Mock<
55+
React.Dispatch<React.SetStateAction<[string, apiModels.GameEventV2][]>>
56+
>;
57+
58+
beforeAll(() => {
59+
gameFixture = gameFixture = {
60+
id: 'game-id',
61+
isPublic: false,
62+
state: {
63+
currentCard: {
64+
kind: 'wild',
65+
},
66+
currentColor: 'blue',
67+
currentDirection: 'antiClockwise',
68+
currentPlayingSlotIndex: 0,
69+
currentTurnCardsDrawn: true,
70+
currentTurnCardsPlayed: true,
71+
drawCount: 0,
72+
lastEventId: 'event-id-fixture',
73+
slots: [],
74+
status: 'active',
75+
},
76+
};
77+
78+
setMessageEventsQueueMock = jest.fn();
79+
});
80+
81+
describe('when called', () => {
82+
let environmentFixture: Environment;
83+
84+
let result: unknown;
85+
86+
beforeAll(() => {
87+
environmentFixture = {
88+
backendBaseUrl: 'backend-base-url-fixture',
89+
};
90+
91+
(
92+
environmentService as jest.Mocked<EnvironmentService>
93+
).getEnvironment.mockReturnValueOnce(environmentFixture);
94+
95+
result = buildEventSource(gameFixture, setMessageEventsQueueMock);
96+
});
97+
98+
it('should call CornieEventSource()', () => {
99+
const expectedUrl: string = `${environmentFixture.backendBaseUrl}/v2/events/games/${gameFixture.id}`;
100+
const expectedInitDict: CornieEventSourceInit = {
101+
defaultLastEventId: gameFixture.state.lastEventId as string,
102+
getAccessToken: expect.any(Function) as unknown as () => string,
103+
};
104+
105+
expect(CornieEventSource).toHaveBeenCalledTimes(1);
106+
expect(CornieEventSource).toHaveBeenCalledWith(
107+
expectedUrl,
108+
expectedInitDict,
109+
);
110+
});
111+
112+
it('should call a cornieEventSource.addEventListener()', () => {
113+
expect(
114+
(result as CornieEventSource).addEventListener,
115+
).toHaveBeenCalledTimes(1);
116+
expect(
117+
(result as CornieEventSource).addEventListener,
118+
).toHaveBeenCalledWith('message', expect.any(Function));
119+
});
120+
121+
it('should return a CornieEventSource', () => {
122+
const expectedProperties: Partial<CornieEventSource> = {
123+
addEventListener: expect.any(
124+
Function,
125+
) as unknown as CornieEventSource['addEventListener'],
126+
};
127+
128+
expect(result).toStrictEqual(
129+
expect.objectContaining(expectedProperties),
130+
);
131+
});
132+
});
133+
});
134+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { models as apiModels } from '@cornie-js/api-models';
2+
3+
import { selectAuthToken } from '../../app/store/features/authSlice';
4+
import { store } from '../../app/store/store';
5+
import { environmentService } from '../../common/env/services/EnvironmentService';
6+
import { CornieEventSource } from '../../common/http/services/CornieEventSource';
7+
8+
export function buildEventSource(
9+
game: apiModels.ActiveGameV1,
10+
setMessageEventsQueue: React.Dispatch<
11+
React.SetStateAction<[string, apiModels.GameEventV2][]>
12+
>,
13+
): CornieEventSource {
14+
const url: string = `${environmentService.getEnvironment().backendBaseUrl}/v2/events/games/${game.id}`;
15+
16+
const eventSource: CornieEventSource = new CornieEventSource(url, {
17+
defaultLastEventId: game.state.lastEventId ?? undefined,
18+
getAccessToken: () => selectAuthToken(store.getState()) ?? '',
19+
});
20+
21+
const gameEventsListener: (event: MessageEvent<unknown>) => void = (
22+
event: MessageEvent<unknown>,
23+
): void => {
24+
const parsedGameEventV2: apiModels.GameEventV2 = JSON.parse(
25+
event.data as string,
26+
) as apiModels.GameEventV2;
27+
28+
setMessageEventsQueue(
29+
(
30+
messageEventsQueue: [string, apiModels.GameEventV2][],
31+
): [string, apiModels.GameEventV2][] => [
32+
...messageEventsQueue,
33+
[event.lastEventId, parsedGameEventV2],
34+
],
35+
);
36+
};
37+
38+
eventSource.addEventListener('message', gameEventsListener);
39+
40+
return eventSource;
41+
}

0 commit comments

Comments
 (0)