Skip to content

Commit

Permalink
Merge pull request #592 from hymccord/halloween-2024
Browse files Browse the repository at this point in the history
Halloween '24 upgrades
  • Loading branch information
AardWolf authored Oct 22, 2024
2 parents f64dabf + 488624c commit cce6c38
Show file tree
Hide file tree
Showing 4 changed files with 337 additions and 68 deletions.
15 changes: 9 additions & 6 deletions src/scripts/modules/ajax-handlers/spookyShuffle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {AjaxSuccessHandler} from "./ajaxSuccessHandler";
import {HgItem} from "@scripts/types/mhct";
import {LoggerService} from "@scripts/util/logger";
import {SpookyShuffleResponse, TitleRange} from "./spookyShuffle.types";
import {SpookyShuffleResponse, spookyShuffleResponseSchema, TitleRange} from "./spookyShuffle.types";
import {CustomConvertibleIds} from "@scripts/util/constants";
import {parseHgInt} from "@scripts/util/number";
import * as hgFuncs from "@scripts/util/hgFunctions";
Expand Down Expand Up @@ -30,7 +30,6 @@ export class SpookyShuffleAjaxHandler extends AjaxSuccessHandler {

async execute(responseJSON: unknown): Promise<void> {
if (!this.isSpookyShuffleResponse(responseJSON)) {
this.logger.warn("Unexpected spooky shuffle response.", responseJSON);
return;
}

Expand Down Expand Up @@ -129,10 +128,14 @@ export class SpookyShuffleAjaxHandler extends AjaxSuccessHandler {
* @returns
*/
private isSpookyShuffleResponse(responseJSON: unknown): responseJSON is SpookyShuffleResponse {
const resultKey: keyof SpookyShuffleResponse = 'memory_game';
return responseJSON != null &&
typeof responseJSON === 'object' &&
resultKey in responseJSON;
const response = spookyShuffleResponseSchema.safeParse(responseJSON);

if (!response.success) {
const errorMessage = response.error.message;
this.logger.warn("Unexpected spooky shuffle response object.", errorMessage);
}

return response.success;
}

static ShuffleConvertibleIds: Record<TitleRange, number> = {
Expand Down
87 changes: 48 additions & 39 deletions src/scripts/modules/ajax-handlers/spookyShuffle.types.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,5 @@
import {HgResponse} from "@scripts/types/hg";

export interface SpookyShuffleResponse extends HgResponse {
memory_game: SpookyShuffleStatus;
}

export interface SpookyShuffleStatus {
is_complete: true | null;
is_upgraded: true | null;
has_selected_testing_pair: boolean; // true on selection of the 2nd card
reward_tiers: RewardTier[];
title_range: TitleRange;
cards: Card[];
}

interface RewardTier {
type: TitleRange;
name: string; // A readable/nice english string of the title range
}
import z from "zod";
import {hgResponseSchema} from "@scripts/types/hg";

const TitleRanges = [
'novice_journeyman',
Expand All @@ -25,23 +8,49 @@ const TitleRanges = [
'grand_duke_plus',
] as const;

export type TitleRange = typeof TitleRanges[number];

type Card = {
id: number;
quantity: number | null;
is_matched: true | null;
is_tested_pair: true | null;
} & (KnownCard | UnknownCard);

interface KnownCard {
name: string;
is_revealed: true;
quantity: number;
}

interface UnknownCard {
name: null;
is_revealed: false;
quantity: null;
}
const titleRangeSchema = z.enum(TitleRanges);

export type TitleRange = z.infer<typeof titleRangeSchema>;

// knowns cards have most field filled in except is_matched
const knownCardSchema = z.object({
name: z.string(),
is_revealed: z.literal(true),
quantity: z.coerce.number(),
is_matched: z.union([z.literal(true), z.literal(null)]),
});

// unknown cards have all null fields
const unknownCardSchema = z.object({
name: z.literal(null),
is_revealed: z.literal(null),
quantity: z.literal(null),
is_matched: z.literal(null),
});

// cards can be either known or unknown but will always have an numeric id
const cardSchema = z.object({
id: z.coerce.number(),
}).and(z.discriminatedUnion('is_revealed', [knownCardSchema, unknownCardSchema]));

const rewardTierSchema = z.object({
type: titleRangeSchema,
name: z.string(), // A readable/nice english string of the title range
});

const spookyShuffleStatusSchema = z.object({
is_complete: z.boolean().nullable(),
is_upgraded: z.boolean().nullable(),
has_selected_testing_pair: z.boolean(),
reward_tiers: z.array(rewardTierSchema),
title_range: titleRangeSchema,
cards: z.array(cardSchema),
});

export type SpookyShuffleStatus = z.infer<typeof spookyShuffleStatusSchema>;

export const spookyShuffleResponseSchema = hgResponseSchema.extend({
memory_game: spookyShuffleStatusSchema,
});

export type SpookyShuffleResponse = z.infer<typeof spookyShuffleResponseSchema>;
73 changes: 50 additions & 23 deletions tests/scripts/modules/ajax-handlers/spookyShuffle.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {SpookyShuffleAjaxHandler} from "@scripts/modules/ajax-handlers";
import {SpookyShuffleStatus} from "@scripts/modules/ajax-handlers/spookyShuffle.types";
import {HgResponse} from "@scripts/types/hg";
import {SpookyShuffleResponse, SpookyShuffleStatus} from "@scripts/modules/ajax-handlers/spookyShuffle.types";
import {HgItem} from "@scripts/types/mhct";

jest.mock('@scripts/util/logger');
Expand All @@ -9,6 +8,7 @@ jest.mock('@scripts/util/hgFunctions');
import {ConsoleLogger} from '@scripts/util/logger';
import {getItemsByClass} from "@scripts/util/hgFunctions";
import {CustomConvertibleIds} from "@scripts/util/constants";
import {HgResponseBuilder} from "@tests/utility/builders";

const logger = new ConsoleLogger();
const submitConvertibleCallback = jest.fn() as jest.MockedFunction<(convertible: HgItem, items: HgItem[]) => void>;
Expand All @@ -18,6 +18,9 @@ const mockedGetItemsByClass = jest.mocked(getItemsByClass);
const spookyShuffle_url = "mousehuntgame.com/managers/ajax/events/spooky_shuffle.php";

describe("SpookyShuffleAjaxHandler", () => {

const responseBuilder = new HgResponseBuilder();

beforeEach(() => {
jest.clearAllMocks();
jest.resetModules();
Expand All @@ -35,11 +38,13 @@ describe("SpookyShuffleAjaxHandler", () => {

describe("execute", () => {
it('warns if response is unexpected', async () => {

// memory_game missing here,
const response = {user_id: 4} as unknown as HgResponse;
const response = responseBuilder.build();

await handler.execute(response);

expect(logger.warn).toBeCalledWith('Unexpected spooky shuffle response.', response);
expect(logger.warn).toHaveBeenCalledWith('Unexpected spooky shuffle response object.', expect.anything());
expect(submitConvertibleCallback).toHaveBeenCalledTimes(0);
});

Expand All @@ -52,9 +57,13 @@ describe("SpookyShuffleAjaxHandler", () => {
title_range: 'novice_journeyman',
cards: [],
};
await handler.execute({memory_game: result} as unknown as HgResponse);
const response: SpookyShuffleResponse = {
...responseBuilder.build(),
memory_game: result,
};
await handler.execute(response);

expect(logger.debug).toBeCalledWith('Spooky Shuffle board is not complete yet.');
expect(logger.debug).toHaveBeenCalledWith('Spooky Shuffle board is not complete yet.');
expect(submitConvertibleCallback).toHaveBeenCalledTimes(0);
});

Expand All @@ -67,9 +76,14 @@ describe("SpookyShuffleAjaxHandler", () => {
title_range: 'novice_journeyman',
cards: [],
};
await handler.execute({memory_game: result} as unknown as HgResponse);
const response: SpookyShuffleResponse = {
...responseBuilder.build(),
memory_game: result,
};

expect(logger.debug).toBeCalledWith('Spooky Shuffle board is not complete yet.');
await handler.execute(response);

expect(logger.debug).toHaveBeenCalledWith('Spooky Shuffle board is not complete yet.');
expect(submitConvertibleCallback).toHaveBeenCalledTimes(0);
});

Expand Down Expand Up @@ -98,12 +112,17 @@ describe("SpookyShuffleAjaxHandler", () => {
name: 'Test Item',
is_matched: true,
is_revealed: true,
is_tested_pair: true,
quantity: 567,
},
],
};
await handler.execute({memory_game: result} as unknown as HgResponse);
const response: SpookyShuffleResponse = {
...responseBuilder.build(),
memory_game: result,
};

await handler.execute(response);

const expectedConvertible = {
id: CustomConvertibleIds.HalloweenSpookyShuffleNovice,
name: 'Spooky Shuffle (Test Title Range)',
Expand All @@ -118,9 +137,9 @@ describe("SpookyShuffleAjaxHandler", () => {
},
];

expect(submitConvertibleCallback).toBeCalledWith(
expectedConvertible,
expectedItems
expect(submitConvertibleCallback).toHaveBeenCalledWith(
expect.objectContaining(expectedConvertible),
expect.objectContaining(expectedItems)
);
});

Expand Down Expand Up @@ -150,20 +169,24 @@ describe("SpookyShuffleAjaxHandler", () => {
name: 'Test Item',
is_matched: true,
is_revealed: true,
is_tested_pair: true,
quantity: 567,
},
{
id: 1,
name: 'Gold',
is_matched: true,
is_revealed: true,
is_tested_pair: true,
quantity: 5000,
},
],
};
await handler.execute({memory_game: result} as unknown as HgResponse);
const response: SpookyShuffleResponse = {
...responseBuilder.build(),
memory_game: result,
};

await handler.execute(response);

const expectedConvertible = {
id: CustomConvertibleIds.HalloweenSpookyShuffleGrandDukeDusted,
name: 'Upgraded Spooky Shuffle (Grand Test Title and up)',
Expand All @@ -183,9 +206,9 @@ describe("SpookyShuffleAjaxHandler", () => {
},
];

expect(submitConvertibleCallback).toBeCalledWith(
expectedConvertible,
expectedItems
expect(submitConvertibleCallback).toHaveBeenCalledWith(
expect.objectContaining(expectedConvertible),
expect.objectContaining(expectedItems)
);
});

Expand Down Expand Up @@ -214,15 +237,19 @@ describe("SpookyShuffleAjaxHandler", () => {
name: 'Test Item',
is_matched: true,
is_revealed: true,
is_tested_pair: true,
quantity: 567,
},
],
};
await handler.execute({memory_game: result} as unknown as HgResponse);
const response: SpookyShuffleResponse = {
...responseBuilder.build(),
memory_game: result,
};

await handler.execute(response);

expect(logger.warn).toBeCalledWith(`Item 'Test Item' wasn't found in item map. Check its classification type`);
expect(submitConvertibleCallback).not.toBeCalled();
expect(logger.warn).toHaveBeenCalledWith(`Item 'Test Item' wasn't found in item map. Check its classification type`);
expect(submitConvertibleCallback).not.toHaveBeenCalled();
});

});
Expand Down
Loading

0 comments on commit cce6c38

Please sign in to comment.