From f3ad801cbc57737bfa5b1df229778d4f7cd6fe1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Pintos=20L=C3=B3pez?= Date: Sat, 7 Sep 2024 00:08:24 +0200 Subject: [PATCH 1/5] refactor(web-ui): add GameWithSpecPair module --- .../frontend/web-ui/src/game/models/GameWithSpecPair.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 packages/frontend/web-ui/src/game/models/GameWithSpecPair.ts diff --git a/packages/frontend/web-ui/src/game/models/GameWithSpecPair.ts b/packages/frontend/web-ui/src/game/models/GameWithSpecPair.ts new file mode 100644 index 000000000..bf7cabb40 --- /dev/null +++ b/packages/frontend/web-ui/src/game/models/GameWithSpecPair.ts @@ -0,0 +1,6 @@ +import { models as apiModels } from '@cornie-js/api-models'; + +export interface GameWithSpecPair { + game: apiModels.GameV1; + spec: apiModels.GameSpecV1; +} From 2442da29039f308e2042e648878e778157103064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Pintos=20L=C3=B3pez?= Date: Sat, 7 Sep 2024 00:08:47 +0200 Subject: [PATCH 2/5] refactor(web-ui): add buildGameWithSpecPairArrayResult --- .../buildGameWithSpecPairArrayResult.spec.ts | 129 ++++++++++++++++++ .../buildGameWithSpecPairArrayResult.ts | 52 +++++++ 2 files changed, 181 insertions(+) create mode 100644 packages/frontend/web-ui/src/game/helpers/buildGameWithSpecPairArrayResult.spec.ts create mode 100644 packages/frontend/web-ui/src/game/helpers/buildGameWithSpecPairArrayResult.ts diff --git a/packages/frontend/web-ui/src/game/helpers/buildGameWithSpecPairArrayResult.spec.ts b/packages/frontend/web-ui/src/game/helpers/buildGameWithSpecPairArrayResult.spec.ts new file mode 100644 index 000000000..9c1d85820 --- /dev/null +++ b/packages/frontend/web-ui/src/game/helpers/buildGameWithSpecPairArrayResult.spec.ts @@ -0,0 +1,129 @@ +import { beforeAll, describe, expect, it } from '@jest/globals'; + +import { models as apiModels } from '@cornie-js/api-models'; + +import { Left, Right } from '../../common/models/Either'; +import { GameWithSpecPair } from '../models/GameWithSpecPair'; +import { buildGameWithSpecPairArrayResult } from './buildGameWithSpecPairArrayResult'; + +describe(buildGameWithSpecPairArrayResult.name, () => { + describe('having gamesV1Result null or gamesSpecsV1Result null', () => { + describe('when called', () => { + let result: unknown; + + beforeAll(() => { + result = buildGameWithSpecPairArrayResult(null, null); + }); + + it('should return null', () => { + expect(result).toBeNull(); + }); + }); + }); + + describe('having Left gamesV1Result or Left gamesSpecsV1Result', () => { + let gamesV1Result: Left; + let gamesSpecsV1Result: Left; + + beforeAll(() => { + gamesV1Result = { + isRight: false, + value: 'games-v1-result-fixture', + }; + + gamesSpecsV1Result = { + isRight: false, + value: 'games-specs-v1-result-fixture', + }; + }); + + describe('when called', () => { + let result: unknown; + + beforeAll(() => { + result = buildGameWithSpecPairArrayResult( + gamesV1Result, + gamesSpecsV1Result, + ); + }); + + it('should return Left', () => { + const expected: Left = { + isRight: false, + value: `${gamesV1Result.value}\n${gamesSpecsV1Result.value}`, + }; + + expect(result).toStrictEqual(expected); + }); + }); + }); + + describe('having Right gamesV1Result and Right gamesSpecsV1Result', () => { + let gamesV1Result: Right<[apiModels.GameV1]>; + let gamesSpecsV1Result: Right<[apiModels.GameSpecV1]>; + + beforeAll(() => { + gamesV1Result = { + isRight: true, + value: [ + { + id: 'game-id', + isPublic: true, + state: { + slots: [], + status: 'nonStarted', + }, + }, + ], + }; + + gamesSpecsV1Result = { + isRight: true, + value: [ + { + cardSpecs: [], + gameId: 'game-id', + gameSlotsAmount: 2, + options: { + chainDraw2Draw2Cards: false, + chainDraw2Draw4Cards: false, + chainDraw4Draw2Cards: false, + chainDraw4Draw4Cards: false, + playCardIsMandatory: false, + playMultipleSameCards: false, + playWildDraw4IfNoOtherAlternative: true, + }, + }, + ], + }; + }); + + describe('when called', () => { + let result: unknown; + + beforeAll(() => { + result = buildGameWithSpecPairArrayResult( + gamesV1Result, + gamesSpecsV1Result, + ); + }); + + it('should return Right', () => { + const [game]: [apiModels.GameV1] = gamesV1Result.value; + const [spec]: [apiModels.GameSpecV1] = gamesSpecsV1Result.value; + + const expected: Right = { + isRight: true, + value: [ + { + game, + spec, + }, + ], + }; + + expect(result).toStrictEqual(expected); + }); + }); + }); +}); diff --git a/packages/frontend/web-ui/src/game/helpers/buildGameWithSpecPairArrayResult.ts b/packages/frontend/web-ui/src/game/helpers/buildGameWithSpecPairArrayResult.ts new file mode 100644 index 000000000..4ff604155 --- /dev/null +++ b/packages/frontend/web-ui/src/game/helpers/buildGameWithSpecPairArrayResult.ts @@ -0,0 +1,52 @@ +import { models as apiModels } from '@cornie-js/api-models'; + +import { Either, Left } from '../../common/models/Either'; +import { GameWithSpecPair } from '../models/GameWithSpecPair'; + +export function buildGameWithSpecPairArrayResult( + gamesV1Result: Either | null, + gamesSpecsV1Result: Either | null, +): Either | null { + if (gamesSpecsV1Result === null || gamesV1Result === null) { + return null; + } + + if (!gamesSpecsV1Result.isRight || !gamesV1Result.isRight) { + const leftovers: string[] = [gamesV1Result, gamesSpecsV1Result] + .filter( + (result: Either): result is Left => + !result.isRight, + ) + .map((result: Left): string => result.value); + + return { + isRight: false, + value: leftovers.join('\n'), + }; + } + + if (gamesV1Result.value.length !== gamesSpecsV1Result.value.length) { + return { + isRight: false, + value: 'Unable to fetch games data', + }; + } + + const gameWithSpecPairArray: GameWithSpecPair[] = gamesV1Result.value.map( + (gameV1: apiModels.GameV1, index: number): GameWithSpecPair => { + const gameSpecV1: apiModels.GameSpecV1 = gamesSpecsV1Result.value[ + index + ] as apiModels.GameSpecV1; + + return { + game: gameV1, + spec: gameSpecV1, + }; + }, + ); + + return { + isRight: true, + value: gameWithSpecPairArray, + }; +} From 9132aacf7402ceb77040100cbcbce9afef4163eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Pintos=20L=C3=B3pez?= Date: Sat, 7 Sep 2024 00:09:11 +0200 Subject: [PATCH 3/5] refactor(web-ui): add useGetGameSpecsV1ForGames --- .../hooks/useGetGameSpecsV1ForGames.spec.ts | 438 ++++++++++++++++++ .../game/hooks/useGetGameSpecsV1ForGames.ts | 57 +++ 2 files changed, 495 insertions(+) create mode 100644 packages/frontend/web-ui/src/game/hooks/useGetGameSpecsV1ForGames.spec.ts create mode 100644 packages/frontend/web-ui/src/game/hooks/useGetGameSpecsV1ForGames.ts diff --git a/packages/frontend/web-ui/src/game/hooks/useGetGameSpecsV1ForGames.spec.ts b/packages/frontend/web-ui/src/game/hooks/useGetGameSpecsV1ForGames.spec.ts new file mode 100644 index 000000000..4f8ba201c --- /dev/null +++ b/packages/frontend/web-ui/src/game/hooks/useGetGameSpecsV1ForGames.spec.ts @@ -0,0 +1,438 @@ +import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; + +jest.mock('../../common/helpers/mapUseQueryHookResult'); +jest.mock('../../common/http/services/cornieApi'); + +import { models as apiModels } from '@cornie-js/api-models'; +import { GetGamesSpecsV1Args } from '@cornie-js/frontend-api-rtk-query'; +import { SubscriptionOptions } from '@reduxjs/toolkit/query'; +import { renderHook, RenderHookResult } from '@testing-library/react'; + +import { + mapUseQueryHookResult, + UseQueryStateResult, +} from '../../common/helpers/mapUseQueryHookResult'; +import { cornieApi } from '../../common/http/services/cornieApi'; +import { Either, Left, Right } from '../../common/models/Either'; +import { + useGetGameSpecsV1ForGames, + UseGetGameSpecsV1ForGamesResult, +} from './useGetGameSpecsV1ForGames'; + +type UseQuerySubscriptionOptions = SubscriptionOptions & { + skip?: boolean; + refetchOnMountOrArgChange?: boolean | number; +}; + +describe(useGetGameSpecsV1ForGames.name, () => { + describe('having gamesV1Result null and subscriptionOptions', () => { + let gamesV1ResultFixture: null; + let subscriptionOptionsFixture: UseQuerySubscriptionOptions; + + beforeAll(() => { + gamesV1ResultFixture = null; + subscriptionOptionsFixture = { + pollingInterval: 10000, + }; + }); + + describe('when called', () => { + let useQueryStateResultFixture: UseQueryStateResult & { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + refetch: () => any; + }; + + let gameSpecsV1ResultFixture: Either< + string, + apiModels.GameSpecArrayV1 + > | null; + + let renderResult: RenderHookResult< + UseGetGameSpecsV1ForGamesResult, + unknown + >; + + beforeAll(() => { + useQueryStateResultFixture = { + data: undefined, + error: undefined, + isLoading: true, + refetch: jest.fn(), + }; + + gameSpecsV1ResultFixture = null; + + ( + cornieApi.useGetGamesSpecsV1Query as jest.Mock< + typeof cornieApi.useGetGamesSpecsV1Query + > + ).mockReturnValueOnce(useQueryStateResultFixture); + + ( + mapUseQueryHookResult as jest.Mock + ).mockReturnValueOnce(gameSpecsV1ResultFixture); + + renderResult = renderHook(() => + useGetGameSpecsV1ForGames( + gamesV1ResultFixture, + subscriptionOptionsFixture, + ), + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call cornieApi.useGetGamesSpecsV1Query()', () => { + const gameSpecSortOptionV1: apiModels.GameSpecSortOptionV1 = 'gameIds'; + const expectedSubscriptionOptions: UseQuerySubscriptionOptions = { + ...subscriptionOptionsFixture, + skip: true, + }; + + const expectedGetGamesSpecsV1Args: GetGamesSpecsV1Args = { + params: [ + { + gameId: [], + sort: gameSpecSortOptionV1, + }, + ], + }; + expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledTimes(1); + expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledWith( + expectedGetGamesSpecsV1Args, + expectedSubscriptionOptions, + ); + }); + + it('should call mapUseQueryHookResult()', () => { + expect(mapUseQueryHookResult).toHaveBeenCalledTimes(1); + expect(mapUseQueryHookResult).toHaveBeenCalledWith( + useQueryStateResultFixture, + ); + }); + + it('should return UseGetGameSpecsV1ForGamesResult', () => { + const expected: UseGetGameSpecsV1ForGamesResult = { + result: gameSpecsV1ResultFixture, + }; + + expect(renderResult.result.current).toStrictEqual(expected); + }); + }); + }); + + describe('having Left gamesV1Result and subscriptionOptions', () => { + let gamesV1ResultFixture: Left; + let subscriptionOptionsFixture: UseQuerySubscriptionOptions; + + beforeAll(() => { + gamesV1ResultFixture = { + isRight: false, + value: 'value-fixture', + }; + subscriptionOptionsFixture = { + pollingInterval: 10000, + }; + }); + + describe('when called', () => { + let useQueryStateResultFixture: UseQueryStateResult & { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + refetch: () => any; + }; + + let gameSpecsV1ResultFixture: Either< + string, + apiModels.GameSpecArrayV1 + > | null; + + let renderResult: RenderHookResult< + UseGetGameSpecsV1ForGamesResult, + unknown + >; + + beforeAll(() => { + useQueryStateResultFixture = { + data: undefined, + error: undefined, + isLoading: true, + refetch: jest.fn(), + }; + + gameSpecsV1ResultFixture = null; + + ( + cornieApi.useGetGamesSpecsV1Query as jest.Mock< + typeof cornieApi.useGetGamesSpecsV1Query + > + ).mockReturnValueOnce(useQueryStateResultFixture); + + ( + mapUseQueryHookResult as jest.Mock + ).mockReturnValueOnce(gameSpecsV1ResultFixture); + + renderResult = renderHook(() => + useGetGameSpecsV1ForGames( + gamesV1ResultFixture, + subscriptionOptionsFixture, + ), + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call cornieApi.useGetGamesSpecsV1Query()', () => { + const gameSpecSortOptionV1: apiModels.GameSpecSortOptionV1 = 'gameIds'; + const expectedSubscriptionOptions: UseQuerySubscriptionOptions = { + ...subscriptionOptionsFixture, + skip: true, + }; + + const expectedGetGamesSpecsV1Args: GetGamesSpecsV1Args = { + params: [ + { + gameId: [], + sort: gameSpecSortOptionV1, + }, + ], + }; + expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledTimes(1); + expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledWith( + expectedGetGamesSpecsV1Args, + expectedSubscriptionOptions, + ); + }); + + it('should call mapUseQueryHookResult()', () => { + expect(mapUseQueryHookResult).toHaveBeenCalledTimes(1); + expect(mapUseQueryHookResult).toHaveBeenCalledWith( + useQueryStateResultFixture, + ); + }); + + it('should return UseGetGameSpecsV1ForGamesResult', () => { + const expected: UseGetGameSpecsV1ForGamesResult = { + result: gameSpecsV1ResultFixture, + }; + + expect(renderResult.result.current).toStrictEqual(expected); + }); + }); + }); + + describe('having Right gamesV1Result with no games and subscriptionOptions', () => { + let gamesV1ResultFixture: Right; + let subscriptionOptionsFixture: UseQuerySubscriptionOptions; + + beforeAll(() => { + gamesV1ResultFixture = { + isRight: true, + value: [], + }; + subscriptionOptionsFixture = { + pollingInterval: 10000, + }; + }); + + describe('when called', () => { + let useQueryStateResultFixture: UseQueryStateResult & { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + refetch: () => any; + }; + + let gameSpecsV1ResultFixture: Either< + string, + apiModels.GameSpecArrayV1 + > | null; + + let renderResult: RenderHookResult< + UseGetGameSpecsV1ForGamesResult, + unknown + >; + + beforeAll(() => { + useQueryStateResultFixture = { + data: undefined, + error: undefined, + isLoading: true, + refetch: jest.fn(), + }; + + gameSpecsV1ResultFixture = null; + + ( + cornieApi.useGetGamesSpecsV1Query as jest.Mock< + typeof cornieApi.useGetGamesSpecsV1Query + > + ).mockReturnValueOnce(useQueryStateResultFixture); + + ( + mapUseQueryHookResult as jest.Mock + ).mockReturnValueOnce(gameSpecsV1ResultFixture); + + renderResult = renderHook(() => + useGetGameSpecsV1ForGames( + gamesV1ResultFixture, + subscriptionOptionsFixture, + ), + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call cornieApi.useGetGamesSpecsV1Query()', () => { + const gameSpecSortOptionV1: apiModels.GameSpecSortOptionV1 = 'gameIds'; + const expectedSubscriptionOptions: UseQuerySubscriptionOptions = { + ...subscriptionOptionsFixture, + skip: true, + }; + + const expectedGetGamesSpecsV1Args: GetGamesSpecsV1Args = { + params: [ + { + gameId: [], + sort: gameSpecSortOptionV1, + }, + ], + }; + expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledTimes(1); + expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledWith( + expectedGetGamesSpecsV1Args, + expectedSubscriptionOptions, + ); + }); + + it('should call mapUseQueryHookResult()', () => { + expect(mapUseQueryHookResult).toHaveBeenCalledTimes(1); + expect(mapUseQueryHookResult).toHaveBeenCalledWith( + useQueryStateResultFixture, + ); + }); + + it('should return UseGetGameSpecsV1ForGamesResult', () => { + const expected: UseGetGameSpecsV1ForGamesResult = { + result: gameSpecsV1ResultFixture, + }; + + expect(renderResult.result.current).toStrictEqual(expected); + }); + }); + }); + + describe('having Right gamesV1Result with a game and subscriptionOptions', () => { + let gameV1Fixture: apiModels.GameV1; + let gamesV1ResultFixture: Right; + let subscriptionOptionsFixture: UseQuerySubscriptionOptions; + + beforeAll(() => { + gameV1Fixture = { + id: 'game-id', + isPublic: true, + state: { + slots: [], + status: 'nonStarted', + }, + }; + + gamesV1ResultFixture = { + isRight: true, + value: [gameV1Fixture], + }; + subscriptionOptionsFixture = { + pollingInterval: 10000, + }; + }); + + describe('when called', () => { + let useQueryStateResultFixture: UseQueryStateResult & { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + refetch: () => any; + }; + + let gameSpecsV1ResultFixture: Either< + string, + apiModels.GameSpecArrayV1 + > | null; + + let renderResult: RenderHookResult< + UseGetGameSpecsV1ForGamesResult, + unknown + >; + + beforeAll(() => { + useQueryStateResultFixture = { + data: undefined, + error: undefined, + isLoading: true, + refetch: jest.fn(), + }; + + gameSpecsV1ResultFixture = null; + + ( + cornieApi.useGetGamesSpecsV1Query as jest.Mock< + typeof cornieApi.useGetGamesSpecsV1Query + > + ).mockReturnValueOnce(useQueryStateResultFixture); + + ( + mapUseQueryHookResult as jest.Mock + ).mockReturnValueOnce(gameSpecsV1ResultFixture); + + renderResult = renderHook(() => + useGetGameSpecsV1ForGames( + gamesV1ResultFixture, + subscriptionOptionsFixture, + ), + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call cornieApi.useGetGamesSpecsV1Query()', () => { + const gameSpecSortOptionV1: apiModels.GameSpecSortOptionV1 = 'gameIds'; + const expectedSubscriptionOptions: UseQuerySubscriptionOptions = { + ...subscriptionOptionsFixture, + skip: false, + }; + + const expectedGetGamesSpecsV1Args: GetGamesSpecsV1Args = { + params: [ + { + gameId: [gameV1Fixture.id], + sort: gameSpecSortOptionV1, + }, + ], + }; + expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledTimes(1); + expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledWith( + expectedGetGamesSpecsV1Args, + expectedSubscriptionOptions, + ); + }); + + it('should call mapUseQueryHookResult()', () => { + expect(mapUseQueryHookResult).toHaveBeenCalledTimes(1); + expect(mapUseQueryHookResult).toHaveBeenCalledWith( + useQueryStateResultFixture, + ); + }); + + it('should return UseGetGameSpecsV1ForGamesResult', () => { + const expected: UseGetGameSpecsV1ForGamesResult = { + result: gameSpecsV1ResultFixture, + }; + + expect(renderResult.result.current).toStrictEqual(expected); + }); + }); + }); +}); diff --git a/packages/frontend/web-ui/src/game/hooks/useGetGameSpecsV1ForGames.ts b/packages/frontend/web-ui/src/game/hooks/useGetGameSpecsV1ForGames.ts new file mode 100644 index 000000000..222f1f333 --- /dev/null +++ b/packages/frontend/web-ui/src/game/hooks/useGetGameSpecsV1ForGames.ts @@ -0,0 +1,57 @@ +import { models as apiModels } from '@cornie-js/api-models'; +import { GetGamesSpecsV1Args } from '@cornie-js/frontend-api-rtk-query'; +import { SubscriptionOptions } from '@reduxjs/toolkit/query'; + +import { mapUseQueryHookResult } from '../../common/helpers/mapUseQueryHookResult'; +import { cornieApi } from '../../common/http/services/cornieApi'; +import { Either } from '../../common/models/Either'; + +type UseQuerySubscriptionOptions = SubscriptionOptions & { + skip?: boolean; + refetchOnMountOrArgChange?: boolean | number; +}; + +function useGetGamesSpecsV1( + getGamesSpecsV1Args: GetGamesSpecsV1Args, + subscriptionOptions: UseQuerySubscriptionOptions, +): { + result: Either | null; +} { + const result = cornieApi.useGetGamesSpecsV1Query( + getGamesSpecsV1Args, + subscriptionOptions, + ); + + return { result: mapUseQueryHookResult(result) }; +} + +export interface UseGetGameSpecsV1ForGamesResult { + result: Either | null; +} + +export const useGetGameSpecsV1ForGames = ( + gamesV1Result: Either | null, + subscriptionOptions: UseQuerySubscriptionOptions, +): UseGetGameSpecsV1ForGamesResult => { + const gameIds: string[] = + gamesV1Result?.isRight === true + ? gamesV1Result.value.map((gameV1: apiModels.GameV1): string => gameV1.id) + : []; + + const gameSpecSortOptionV1: apiModels.GameSpecSortOptionV1 = 'gameIds'; + + return useGetGamesSpecsV1( + { + params: [ + { + gameId: gameIds, + sort: gameSpecSortOptionV1, + }, + ], + }, + { + ...subscriptionOptions, + skip: gameIds.length === 0, + }, + ); +}; From 209d2dc1060ee72287ce30c57b1fdb959d783cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Pintos=20L=C3=B3pez?= Date: Sat, 7 Sep 2024 00:09:53 +0200 Subject: [PATCH 4/5] refactor(web-ui): update useGetGamesWithSpecsV1 to rely on extracted modules --- .../game/hooks/useGetGamesWithSpecsV1.spec.ts | 315 ++++-------------- .../src/game/hooks/useGetGamesWithSpecsV1.ts | 100 +----- 2 files changed, 66 insertions(+), 349 deletions(-) diff --git a/packages/frontend/web-ui/src/game/hooks/useGetGamesWithSpecsV1.spec.ts b/packages/frontend/web-ui/src/game/hooks/useGetGamesWithSpecsV1.spec.ts index 2f551bccf..58ac4f911 100644 --- a/packages/frontend/web-ui/src/game/hooks/useGetGamesWithSpecsV1.spec.ts +++ b/packages/frontend/web-ui/src/game/hooks/useGetGamesWithSpecsV1.spec.ts @@ -2,15 +2,26 @@ import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; jest.mock('../../common/helpers/mapUseQueryHookResult'); jest.mock('../../common/http/services/cornieApi'); +jest.mock('../helpers/buildGameWithSpecPairArrayResult'); +jest.mock('./useGetGameSpecsV1ForGames'); import { models as apiModels } from '@cornie-js/api-models'; import { GetGamesV1Args } from '@cornie-js/frontend-api-rtk-query'; import { SubscriptionOptions } from '@reduxjs/toolkit/query'; import { renderHook, RenderHookResult } from '@testing-library/react'; -import { mapUseQueryHookResult } from '../../common/helpers/mapUseQueryHookResult'; +import { + mapUseQueryHookResult, + UseQueryStateResult, +} from '../../common/helpers/mapUseQueryHookResult'; import { cornieApi } from '../../common/http/services/cornieApi'; -import { Right } from '../../common/models/Either'; +import { Either } from '../../common/models/Either'; +import { buildGameWithSpecPairArrayResult } from '../helpers/buildGameWithSpecPairArrayResult'; +import { GameWithSpecPair } from '../models/GameWithSpecPair'; +import { + useGetGameSpecsV1ForGames, + UseGetGameSpecsV1ForGamesResult, +} from './useGetGameSpecsV1ForGames'; import { useGetGamesWithSpecsV1, UseGetGamesWithSpecsV1Result, @@ -22,13 +33,25 @@ type UseQuerySubscriptionOptions = SubscriptionOptions & { }; describe(useGetGamesWithSpecsV1.name, () => { - describe('when called, and useGetGamesV1() returns null and useGetGamesSpecsV1 returns null', () => { + describe('when called', () => { + let gamesV1ResultFixture: Either | null; + let gameWithSpecV1PairArrayResultFixture: Either< + string, + GameWithSpecPair[] + > | null; let getGamesV1ArgsFixture: GetGamesV1Args; let subscriptionOptionsFixture: UseQuerySubscriptionOptions; + let useGetGameSpecsV1ForGamesResultFixture: UseGetGameSpecsV1ForGamesResult; + let useQueryStateResultFixture: UseQueryStateResult & { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + refetch: () => any; + }; let renderResult: RenderHookResult; beforeAll(() => { + gamesV1ResultFixture = null; + gameWithSpecV1PairArrayResultFixture = null; getGamesV1ArgsFixture = { params: [ { @@ -41,152 +64,36 @@ describe(useGetGamesWithSpecsV1.name, () => { pollingInterval: 10000, }; - (mapUseQueryHookResult as jest.Mock) - .mockReturnValueOnce(null) - .mockReturnValueOnce(null); - - renderResult = renderHook(() => - useGetGamesWithSpecsV1( - getGamesV1ArgsFixture, - subscriptionOptionsFixture, - ), - ); - }); - - afterAll(() => { - jest.clearAllMocks(); - }); - - it('should call cornieApi.useGetUsersV1MeQuery()', () => { - const expectedParams: Parameters = [ - getGamesV1ArgsFixture, - subscriptionOptionsFixture, - ]; - - expect(cornieApi.useGetGamesV1Query).toHaveBeenCalledTimes(1); - expect(cornieApi.useGetGamesV1Query).toHaveBeenCalledWith( - ...expectedParams, - ); - }); - - it('should call cornieApi.useGetGamesSpecsV1Query()', () => { - const gameSpecSortOptionV1: apiModels.GameSpecSortOptionV1 = 'gameIds'; - - const expectedParams: Parameters< - typeof cornieApi.useGetGamesSpecsV1Query - > = [ - { - params: [ - { - gameId: [], - sort: gameSpecSortOptionV1, - }, - ], - }, - { - ...subscriptionOptionsFixture, - skip: true, - }, - ]; - - expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledTimes(1); - expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledWith( - ...expectedParams, - ); - }); - - it('should return UseGetGamesWithSpecsV1Result', () => { - const expectedResult: UseGetGamesWithSpecsV1Result = { + useGetGameSpecsV1ForGamesResultFixture = { result: null, }; - expect(renderResult.result.current).toStrictEqual(expectedResult); - }); - }); - - describe('when called, and useGetGamesV1() returns a Right with a game and useGetGamesSpecsV1 returns Right with a game spec', () => { - let getGamesV1ArgsFixture: GetGamesV1Args; - let subscriptionOptionsFixture: UseQuerySubscriptionOptions; - - let gameV1Fixture: apiModels.GameV1; - let gameSpecV1Fixture: apiModels.GameSpecV1; - - let renderResult: RenderHookResult; - - beforeAll(() => { - getGamesV1ArgsFixture = { - params: [ - { - isPublic: 'false', - }, - ], - }; - - subscriptionOptionsFixture = { - pollingInterval: 10000, - }; - - gameV1Fixture = { - id: 'game-id-fixture', - isPublic: true, - state: { - slots: [], - status: 'nonStarted', - }, - }; - - gameSpecV1Fixture = { - cardSpecs: [], - gameId: 'game-id-fixture', - gameSlotsAmount: 2, - options: { - chainDraw2Draw2Cards: false, - chainDraw2Draw4Cards: false, - chainDraw4Draw2Cards: false, - chainDraw4Draw4Cards: false, - playCardIsMandatory: false, - playMultipleSameCards: false, - playWildDraw4IfNoOtherAlternative: true, - }, + useQueryStateResultFixture = { + data: undefined, + error: undefined, + isLoading: true, + refetch: jest.fn(), }; ( cornieApi.useGetGamesV1Query as jest.Mock< typeof cornieApi.useGetGamesV1Query > - ).mockReturnValueOnce({ - data: undefined, - error: undefined, - isLoading: true, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - refetch: jest.fn(), - }); + ).mockReturnValueOnce(useQueryStateResultFixture); ( - cornieApi.useGetGamesSpecsV1Query as jest.Mock< - typeof cornieApi.useGetGamesSpecsV1Query - > - ).mockReturnValueOnce({ - data: undefined, - error: undefined, - isLoading: true, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - refetch: jest.fn(), - }); - - const getGamesV1Result: Right = { - isRight: true, - value: [gameV1Fixture], - }; + mapUseQueryHookResult as jest.Mock + ).mockReturnValueOnce(gamesV1ResultFixture); - const getGamesSpecsV1Result: Right = { - isRight: true, - value: [gameSpecV1Fixture], - }; + ( + useGetGameSpecsV1ForGames as jest.Mock + ).mockReturnValueOnce(useGetGameSpecsV1ForGamesResultFixture); - (mapUseQueryHookResult as jest.Mock) - .mockReturnValueOnce(getGamesV1Result) - .mockReturnValueOnce(getGamesSpecsV1Result); + ( + buildGameWithSpecPairArrayResult as jest.Mock< + typeof buildGameWithSpecPairArrayResult + > + ).mockReturnValueOnce(gameWithSpecV1PairArrayResultFixture); renderResult = renderHook(() => useGetGamesWithSpecsV1( @@ -200,7 +107,7 @@ describe(useGetGamesWithSpecsV1.name, () => { jest.clearAllMocks(); }); - it('should call cornieApi.useGetUsersV1MeQuery()', () => { + it('should call cornieApi.useGetGamesV1Query()', () => { const expectedParams: Parameters = [ getGamesV1ArgsFixture, subscriptionOptionsFixture, @@ -212,138 +119,32 @@ describe(useGetGamesWithSpecsV1.name, () => { ); }); - it('should call cornieApi.useGetGamesSpecsV1Query()', () => { - const gameSpecSortOptionV1: apiModels.GameSpecSortOptionV1 = 'gameIds'; - - const expectedParams: Parameters< - typeof cornieApi.useGetGamesSpecsV1Query - > = [ - { - params: [ - { - gameId: [gameV1Fixture.id], - sort: gameSpecSortOptionV1, - }, - ], - }, - { - ...subscriptionOptionsFixture, - skip: false, - }, - ]; - - expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledTimes(1); - expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledWith( - ...expectedParams, + it('should call mapUseQueryHookResult()', () => { + expect(mapUseQueryHookResult).toHaveBeenCalledTimes(1); + expect(mapUseQueryHookResult).toHaveBeenCalledWith( + useQueryStateResultFixture, ); }); - it('should return UseGetGamesWithSpecsV1Result', () => { - const expectedResult: UseGetGamesWithSpecsV1Result = { - result: { - isRight: true, - value: [ - { - game: gameV1Fixture, - spec: gameSpecV1Fixture, - }, - ], - }, - }; - - expect(renderResult.result.current).toStrictEqual(expectedResult); - }); - }); - - describe('when called, and useGetGamesV1() returns a Right with no games and useGetGamesSpecsV1 returns Right with no game specs', () => { - let getGamesV1ArgsFixture: GetGamesV1Args; - let subscriptionOptionsFixture: UseQuerySubscriptionOptions; - - let renderResult: RenderHookResult; - - beforeAll(() => { - getGamesV1ArgsFixture = { - params: [ - { - isPublic: 'false', - }, - ], - }; - - subscriptionOptionsFixture = { - pollingInterval: 10000, - }; - - const getGamesV1Result: Right = { - isRight: true, - value: [], - }; - - const getGamesSpecsV1Result: Right = { - isRight: true, - value: [], - }; - - (mapUseQueryHookResult as jest.Mock) - .mockReturnValueOnce(getGamesV1Result) - .mockReturnValueOnce(getGamesSpecsV1Result); - - renderResult = renderHook(() => - useGetGamesWithSpecsV1( - getGamesV1ArgsFixture, - subscriptionOptionsFixture, - ), - ); - }); - - afterAll(() => { - jest.clearAllMocks(); - }); - - it('should call cornieApi.useGetUsersV1MeQuery()', () => { - const expectedParams: Parameters = [ - getGamesV1ArgsFixture, + it('should call useGetGameSpecsV1ForGames()', () => { + expect(useGetGameSpecsV1ForGames).toHaveBeenCalledTimes(1); + expect(useGetGameSpecsV1ForGames).toHaveBeenCalledWith( + gamesV1ResultFixture, subscriptionOptionsFixture, - ]; - - expect(cornieApi.useGetGamesV1Query).toHaveBeenCalledTimes(1); - expect(cornieApi.useGetGamesV1Query).toHaveBeenCalledWith( - ...expectedParams, ); }); - it('should call cornieApi.useGetGamesSpecsV1Query()', () => { - const gameSpecSortOptionV1: apiModels.GameSpecSortOptionV1 = 'gameIds'; - - const expectedParams: Parameters< - typeof cornieApi.useGetGamesSpecsV1Query - > = [ - { - params: [ - { - gameId: [], - sort: gameSpecSortOptionV1, - }, - ], - }, - { - ...subscriptionOptionsFixture, - skip: true, - }, - ]; - - expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledTimes(1); - expect(cornieApi.useGetGamesSpecsV1Query).toHaveBeenCalledWith( - ...expectedParams, + it('should call buildGameWithSpecPairArrayResult()', () => { + expect(buildGameWithSpecPairArrayResult).toHaveBeenCalledTimes(1); + expect(buildGameWithSpecPairArrayResult).toHaveBeenCalledWith( + gamesV1ResultFixture, + useGetGameSpecsV1ForGamesResultFixture.result, ); }); it('should return UseGetGamesWithSpecsV1Result', () => { const expectedResult: UseGetGamesWithSpecsV1Result = { - result: { - isRight: true, - value: [], - }, + result: gameWithSpecV1PairArrayResultFixture, }; expect(renderResult.result.current).toStrictEqual(expectedResult); diff --git a/packages/frontend/web-ui/src/game/hooks/useGetGamesWithSpecsV1.ts b/packages/frontend/web-ui/src/game/hooks/useGetGamesWithSpecsV1.ts index 53838b877..d6a9c3ffd 100644 --- a/packages/frontend/web-ui/src/game/hooks/useGetGamesWithSpecsV1.ts +++ b/packages/frontend/web-ui/src/game/hooks/useGetGamesWithSpecsV1.ts @@ -1,76 +1,23 @@ import { models as apiModels } from '@cornie-js/api-models'; -import { - GetGamesSpecsV1Args, - GetGamesV1Args, -} from '@cornie-js/frontend-api-rtk-query'; +import { GetGamesV1Args } from '@cornie-js/frontend-api-rtk-query'; import { SubscriptionOptions } from '@reduxjs/toolkit/query'; import { mapUseQueryHookResult } from '../../common/helpers/mapUseQueryHookResult'; import { cornieApi } from '../../common/http/services/cornieApi'; -import { Either, Left } from '../../common/models/Either'; +import { Either } from '../../common/models/Either'; +import { buildGameWithSpecPairArrayResult } from '../helpers/buildGameWithSpecPairArrayResult'; +import { GameWithSpecPair } from '../models/GameWithSpecPair'; +import { useGetGameSpecsV1ForGames } from './useGetGameSpecsV1ForGames'; type UseQuerySubscriptionOptions = SubscriptionOptions & { skip?: boolean; refetchOnMountOrArgChange?: boolean | number; }; -export interface GameWithSpecPair { - game: apiModels.GameV1; - spec: apiModels.GameSpecV1; -} - export interface UseGetGamesWithSpecsV1Result { result: Either | null; } -function buildGameWithSpecPairArrayResult( - gamesV1Result: Either | null, - gamesSpecsV1Result: Either | null, -): Either | null { - if (gamesSpecsV1Result === null || gamesV1Result === null) { - return null; - } - - if (!gamesSpecsV1Result.isRight || !gamesV1Result.isRight) { - const leftovers: string[] = [gamesV1Result, gamesSpecsV1Result] - .filter( - (result: Either): result is Left => - !result.isRight, - ) - .map((result: Left): string => result.value); - - return { - isRight: false, - value: leftovers.join('\n'), - }; - } - - if (gamesV1Result.value.length !== gamesSpecsV1Result.value.length) { - return { - isRight: false, - value: 'Unable to fetch games data', - }; - } - - const gameWithSpecPairArray: GameWithSpecPair[] = gamesV1Result.value.map( - (gameV1: apiModels.GameV1, index: number): GameWithSpecPair => { - const gameSpecV1: apiModels.GameSpecV1 = gamesSpecsV1Result.value[ - index - ] as apiModels.GameSpecV1; - - return { - game: gameV1, - spec: gameSpecV1, - }; - }, - ); - - return { - isRight: true, - value: gameWithSpecPairArray, - }; -} - function useGetGamesV1( getGamesV1Args: GetGamesV1Args, subscriptionOptions: UseQuerySubscriptionOptions, @@ -85,20 +32,6 @@ function useGetGamesV1( return { result: mapUseQueryHookResult(result) }; } -function useGetGamesSpecsV1( - getGamesV1Args: GetGamesSpecsV1Args, - subscriptionOptions: UseQuerySubscriptionOptions, -): { - result: Either | null; -} { - const result = cornieApi.useGetGamesSpecsV1Query( - getGamesV1Args, - subscriptionOptions, - ); - - return { result: mapUseQueryHookResult(result) }; -} - export const useGetGamesWithSpecsV1 = ( getGamesV1Args: GetGamesV1Args, subscriptionOptions: UseQuerySubscriptionOptions, @@ -108,26 +41,9 @@ export const useGetGamesWithSpecsV1 = ( subscriptionOptions, ); - const gameIds: string[] = - gamesV1Result?.isRight === true - ? gamesV1Result.value.map((gameV1: apiModels.GameV1): string => gameV1.id) - : []; - - const gameSpecSortOptionV1: apiModels.GameSpecSortOptionV1 = 'gameIds'; - - const { result: gamesSpecsV1Result } = useGetGamesSpecsV1( - { - params: [ - { - gameId: gameIds, - sort: gameSpecSortOptionV1, - }, - ], - }, - { - ...subscriptionOptions, - skip: gameIds.length === 0, - }, + const { result: gamesSpecsV1Result } = useGetGameSpecsV1ForGames( + gamesV1Result, + subscriptionOptions, ); return { From 424a4cec9801eec7ab443984a6999d768a8130ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Pintos=20L=C3=B3pez?= Date: Sat, 7 Sep 2024 00:11:03 +0200 Subject: [PATCH 5/5] refactor(web-ui): add useGetGamesV1Mine --- .../hooks/useGetGamesMineWithSpecsV1.spec.ts | 152 ++++++++++++++++++ .../game/hooks/useGetGamesMineWithSpecsV1.ts | 52 ++++++ 2 files changed, 204 insertions(+) create mode 100644 packages/frontend/web-ui/src/game/hooks/useGetGamesMineWithSpecsV1.spec.ts create mode 100644 packages/frontend/web-ui/src/game/hooks/useGetGamesMineWithSpecsV1.ts diff --git a/packages/frontend/web-ui/src/game/hooks/useGetGamesMineWithSpecsV1.spec.ts b/packages/frontend/web-ui/src/game/hooks/useGetGamesMineWithSpecsV1.spec.ts new file mode 100644 index 000000000..2d9242b35 --- /dev/null +++ b/packages/frontend/web-ui/src/game/hooks/useGetGamesMineWithSpecsV1.spec.ts @@ -0,0 +1,152 @@ +import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; + +jest.mock('../../common/helpers/mapUseQueryHookResult'); +jest.mock('../../common/http/services/cornieApi'); +jest.mock('../helpers/buildGameWithSpecPairArrayResult'); +jest.mock('./useGetGameSpecsV1ForGames'); + +import { models as apiModels } from '@cornie-js/api-models'; +import { GetGamesV1MineArgs } from '@cornie-js/frontend-api-rtk-query'; +import { SubscriptionOptions } from '@reduxjs/toolkit/query'; +import { renderHook, RenderHookResult } from '@testing-library/react'; + +import { + mapUseQueryHookResult, + UseQueryStateResult, +} from '../../common/helpers/mapUseQueryHookResult'; +import { cornieApi } from '../../common/http/services/cornieApi'; +import { Either } from '../../common/models/Either'; +import { buildGameWithSpecPairArrayResult } from '../helpers/buildGameWithSpecPairArrayResult'; +import { GameWithSpecPair } from '../models/GameWithSpecPair'; +import { + useGetGamesMineWithSpecsV1, + UseGetGamesWithSpecsV1Result, +} from './useGetGamesMineWithSpecsV1'; +import { + useGetGameSpecsV1ForGames, + UseGetGameSpecsV1ForGamesResult, +} from './useGetGameSpecsV1ForGames'; + +type UseQuerySubscriptionOptions = SubscriptionOptions & { + skip?: boolean; + refetchOnMountOrArgChange?: boolean | number; +}; + +describe(useGetGamesMineWithSpecsV1.name, () => { + describe('when called', () => { + let gamesV1ResultFixture: Either | null; + let gameWithSpecV1PairArrayResultFixture: Either< + string, + GameWithSpecPair[] + > | null; + let getGamesV1MineArgsFixture: GetGamesV1MineArgs; + let subscriptionOptionsFixture: UseQuerySubscriptionOptions; + let useGetGameSpecsV1ForGamesResultFixture: UseGetGameSpecsV1ForGamesResult; + let useQueryStateResultFixture: UseQueryStateResult & { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + refetch: () => any; + }; + + let renderResult: RenderHookResult; + + beforeAll(() => { + gamesV1ResultFixture = null; + gameWithSpecV1PairArrayResultFixture = null; + getGamesV1MineArgsFixture = { + params: [ + { + isPublic: 'false', + }, + ], + }; + + subscriptionOptionsFixture = { + pollingInterval: 10000, + }; + + useGetGameSpecsV1ForGamesResultFixture = { + result: null, + }; + + useQueryStateResultFixture = { + data: undefined, + error: undefined, + isLoading: true, + refetch: jest.fn(), + }; + + ( + cornieApi.useGetGamesV1MineQuery as jest.Mock< + typeof cornieApi.useGetGamesV1MineQuery + > + ).mockReturnValueOnce(useQueryStateResultFixture); + + ( + mapUseQueryHookResult as jest.Mock + ).mockReturnValueOnce(gamesV1ResultFixture); + + ( + useGetGameSpecsV1ForGames as jest.Mock + ).mockReturnValueOnce(useGetGameSpecsV1ForGamesResultFixture); + + ( + buildGameWithSpecPairArrayResult as jest.Mock< + typeof buildGameWithSpecPairArrayResult + > + ).mockReturnValueOnce(gameWithSpecV1PairArrayResultFixture); + + renderResult = renderHook(() => + useGetGamesMineWithSpecsV1( + getGamesV1MineArgsFixture, + subscriptionOptionsFixture, + ), + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call cornieApi.useGetGamesV1MineQuery()', () => { + const expectedParams: Parameters< + typeof cornieApi.useGetGamesV1MineQuery + > = [getGamesV1MineArgsFixture, subscriptionOptionsFixture]; + + expect(cornieApi.useGetGamesV1MineQuery).toHaveBeenCalledTimes(1); + expect(cornieApi.useGetGamesV1MineQuery).toHaveBeenCalledWith( + ...expectedParams, + ); + }); + + it('should call mapUseQueryHookResult()', () => { + expect(mapUseQueryHookResult).toHaveBeenCalledTimes(1); + expect(mapUseQueryHookResult).toHaveBeenCalledWith( + useQueryStateResultFixture, + ); + }); + + it('should call useGetGameSpecsV1ForGames()', () => { + expect(useGetGameSpecsV1ForGames).toHaveBeenCalledTimes(1); + expect(useGetGameSpecsV1ForGames).toHaveBeenCalledWith( + gamesV1ResultFixture, + subscriptionOptionsFixture, + ); + }); + + it('should call buildGameWithSpecPairArrayResult()', () => { + expect(buildGameWithSpecPairArrayResult).toHaveBeenCalledTimes(1); + expect(buildGameWithSpecPairArrayResult).toHaveBeenCalledWith( + gamesV1ResultFixture, + useGetGameSpecsV1ForGamesResultFixture.result, + ); + }); + + it('should return UseGetGamesWithSpecsV1Result', () => { + const expectedResult: UseGetGamesWithSpecsV1Result = { + result: gameWithSpecV1PairArrayResultFixture, + }; + + expect(renderResult.result.current).toStrictEqual(expectedResult); + }); + }); +}); diff --git a/packages/frontend/web-ui/src/game/hooks/useGetGamesMineWithSpecsV1.ts b/packages/frontend/web-ui/src/game/hooks/useGetGamesMineWithSpecsV1.ts new file mode 100644 index 000000000..cdc6ba06a --- /dev/null +++ b/packages/frontend/web-ui/src/game/hooks/useGetGamesMineWithSpecsV1.ts @@ -0,0 +1,52 @@ +import { models as apiModels } from '@cornie-js/api-models'; +import { GetGamesV1MineArgs } from '@cornie-js/frontend-api-rtk-query'; +import { SubscriptionOptions } from '@reduxjs/toolkit/query'; + +import { mapUseQueryHookResult } from '../../common/helpers/mapUseQueryHookResult'; +import { cornieApi } from '../../common/http/services/cornieApi'; +import { Either } from '../../common/models/Either'; +import { buildGameWithSpecPairArrayResult } from '../helpers/buildGameWithSpecPairArrayResult'; +import { GameWithSpecPair } from '../models/GameWithSpecPair'; +import { useGetGameSpecsV1ForGames } from './useGetGameSpecsV1ForGames'; + +type UseQuerySubscriptionOptions = SubscriptionOptions & { + skip?: boolean; + refetchOnMountOrArgChange?: boolean | number; +}; + +export interface UseGetGamesWithSpecsV1Result { + result: Either | null; +} + +function useGetGamesV1Mine( + getGamesV1Args: GetGamesV1MineArgs, + subscriptionOptions: UseQuerySubscriptionOptions, +): { + result: Either | null; +} { + const result = cornieApi.useGetGamesV1MineQuery( + getGamesV1Args, + subscriptionOptions, + ); + + return { result: mapUseQueryHookResult(result) }; +} + +export const useGetGamesMineWithSpecsV1 = ( + getGamesV1Args: GetGamesV1MineArgs, + subscriptionOptions: UseQuerySubscriptionOptions, +): UseGetGamesWithSpecsV1Result => { + const { result: gamesV1Result } = useGetGamesV1Mine( + getGamesV1Args, + subscriptionOptions, + ); + + const { result: gamesSpecsV1Result } = useGetGameSpecsV1ForGames( + gamesV1Result, + subscriptionOptions, + ); + + return { + result: buildGameWithSpecPairArrayResult(gamesV1Result, gamesSpecsV1Result), + }; +};