Skip to content

Commit

Permalink
Merge pull request #81 from moeyashi/feature/62
Browse files Browse the repository at this point in the history
Feature/62
  • Loading branch information
moeyashi authored Mar 22, 2024
2 parents 03a7460 + 12ff875 commit b4bd987
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 16 deletions.
10 changes: 10 additions & 0 deletions src/infra/repository/nita.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,15 @@ export const postgresNitaRepository = () => {
const rank = Number(results[0].rank);
return rank || null;
},
async countExistsNita(trackCode, discordMembers) {
const discordUserIds = discordMembers.map((member) => member.user.id);
const results = await sql`
SELECT COUNT(*) as count
FROM nita
WHERE discord_user_id IN ${sql(discordUserIds)}
AND track_code = ${trackCode}
`;
return Number(results[0].count);
},
};
};
48 changes: 48 additions & 0 deletions src/infra/repository/nita.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,52 @@ describe('postgresNitaRepository', () => {
expect(actual).toBeNull();
});
});
describe('countExistsNita', async () => {
const trackCode = 'MKS';
beforeAll(async () => {
execSync('npm run db:reset');
});

test('0件の場合0を返却する', async () => {
const users = Array.from({ length: 21 }).map((_, i) => ({
user: { id: `${i + 1}` },
}));
const actual = await repo.countExistsNita(trackCode, users);
expect(actual).toEqual(0);
});

test('1件の場合1を返却する', async () => {
const users = Array.from({ length: 21 }).map((_, i) => ({
user: { id: `${i + 1}` },
}));
await repo.insertNita({
discordUserId: '1',
trackCode,
milliseconds: 1000,
});
const actual = await repo.countExistsNita(trackCode, users);
expect(actual).toEqual(1);
await repo.deleteNita('1', trackCode);
});

test('2件の場合2を返却する', async () => {
const users = Array.from({ length: 21 }).map((_, i) => ({
user: { id: `${i + 1}` },
}));
await repo.insertNita({
discordUserId: '1',
trackCode,
milliseconds: 1000,
});
await repo.insertNita({
discordUserId: '2',
trackCode,
milliseconds: 1000,
});
const actual = await repo.countExistsNita(trackCode, users);
expect(actual).toEqual(2);
await repo.deleteNita('1', trackCode);
await repo.deleteNita('2', trackCode);
});
});
});
5 changes: 3 additions & 2 deletions src/presenter/ranking-response.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { displayMilliseconds } from '../util/time.js';
* @param {number} page
* @param {import('../types').UnwrapPromise<ReturnType<import('../types').NitaRepository['selectRanking']>>} ranking
* @param {number | null} myRank
* @param {number} rankingSize
* @return {import('discord.js').InteractionReplyOptions}
*/
export const rankingResponse = (track, page, ranking, myRank) => {
export const rankingResponse = (track, page, ranking, myRank, rankingSize) => {
if (ranking.length === 0) {
return {
content: `まだ${track.trackName}のNITAのタイムが登録されていません。`,
Expand All @@ -18,7 +19,7 @@ export const rankingResponse = (track, page, ranking, myRank) => {

const groupedRanking = groupByRank(track, ranking);
return {
content: `### NITAランキング - ${track.trackName}\n${(page - 1) * 20 + 1}位から${(page - 1) * 20 + ranking.length}位まで${myRank ? `\n\nあなたの順位: ${myRank}位` : ''}`,
content: `### NITAランキング - ${track.trackName}\n全${rankingSize}位のうち ${(page - 1) * 20 + 1}位から${(page - 1) * 20 + ranking.length}位まで${myRank ? `\n\nあなたの順位: ${myRank}位` : ''}`,
embeds: groupedRanking.flatMap(([rank, color, nita]) => {
/** @type {import('discord.js').APIEmbed[]} */
const embeds = [];
Expand Down
29 changes: 16 additions & 13 deletions src/presenter/ranking-response.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ describe(rankingResponse, () => {
milliseconds: sampleTrack.nitaVSWRMilliseconds + i,
}));
describe('1ページ目の場合', () => {
const actual = rankingResponse(sampleTrack, 1, ranking, null);
const actual = rankingResponse(sampleTrack, 1, ranking, null, 50);
test('1位から20位までのメッセージが返却されること', () => {
expect(actual.content).toEqual('### NITAランキング - マリオカートスタジアム\n1位から20位まで');
expect(actual.content).toEqual('### NITAランキング - マリオカートスタジアム\n全50位のうち 1位から20位まで');
});
});
describe('2ページ目の場合', () => {
const actual = rankingResponse(sampleTrack, 2, ranking, null);
const actual = rankingResponse(sampleTrack, 2, ranking, null, 50);
test('21位から40位までのメッセージが返却されること', () => {
expect(actual.content).toEqual('### NITAランキング - マリオカートスタジアム\n21位から40位まで');
expect(actual.content).toEqual('### NITAランキング - マリオカートスタジアム\n全50位のうち 21位から40位まで');
});
});
});
Expand All @@ -37,15 +37,15 @@ describe(rankingResponse, () => {
milliseconds: sampleTrack.nitaVSWRMilliseconds + i,
}));
describe('1ページ目の場合', () => {
const actual = rankingResponse(sampleTrack, 1, ranking, null);
const actual = rankingResponse(sampleTrack, 1, ranking, null, 19);
test('1位から19位までのメッセージが返却されること', () => {
expect(actual.content).toEqual('### NITAランキング - マリオカートスタジアム\n1位から19位まで');
expect(actual.content).toEqual('### NITAランキング - マリオカートスタジアム\n全19位のうち 1位から19位まで');
});
});
describe('2ページ目の場合', () => {
const actual = rankingResponse(sampleTrack, 2, ranking, null);
const actual = rankingResponse(sampleTrack, 2, ranking, null, 39);
test('21位から39位までのメッセージが返却されること', () => {
expect(actual.content).toEqual('### NITAランキング - マリオカートスタジアム\n21位から39位まで');
expect(actual.content).toEqual('### NITAランキング - マリオカートスタジアム\n全39位のうち 21位から39位まで');
});
});
});
Expand All @@ -56,15 +56,15 @@ describe(rankingResponse, () => {
milliseconds: sampleTrack.nitaVSWRMilliseconds,
}];
describe('自分の順位が1位の場合', () => {
const actual = rankingResponse(sampleTrack, 1, ranking, 1);
const actual = rankingResponse(sampleTrack, 1, ranking, 1, 1);
test('自分の順位が含まれるメッセージが返却されること', () => {
expect(actual.content).toEqual('### NITAランキング - マリオカートスタジアム\n1位から1位まで\n\nあなたの順位: 1位');
expect(actual.content).toEqual('### NITAランキング - マリオカートスタジアム\n全1位のうち 1位から1位まで\n\nあなたの順位: 1位');
});
});
describe('自分の記録が未登録の場合', () => {
const actual = rankingResponse(sampleTrack, 1, ranking, null);
const actual = rankingResponse(sampleTrack, 1, ranking, null, 1);
test('自分の順位が含まれないメッセージが返却されること', () => {
expect(actual.content).toEqual('### NITAランキング - マリオカートスタジアム\n1位から1位まで');
expect(actual.content).toEqual('### NITAランキング - マリオカートスタジアム\n全1位のうち 1位から1位まで');
});
});
});
Expand Down Expand Up @@ -101,6 +101,7 @@ describe(rankingResponse, () => {
{ member: makeGuildMember({ nickname: 'user24' }), milliseconds: sampleTrack.nitaVSWRMilliseconds + 7001 },
],
null,
24,
);
test('1落ちのembedが返却されること', () => {
expect(actual.embeds?.[0]).toEqual({
Expand Down Expand Up @@ -186,6 +187,7 @@ describe(rankingResponse, () => {
milliseconds: sampleTrack.nitaVSWRMilliseconds,
})),
null,
25,
);
expect(actual.embeds?.length).toEqual(1);
});
Expand All @@ -198,6 +200,7 @@ describe(rankingResponse, () => {
milliseconds: sampleTrack.nitaVSWRMilliseconds,
})),
null,
26,
);
expect(actual.embeds?.length).toEqual(2);
});
Expand All @@ -206,7 +209,7 @@ describe(rankingResponse, () => {
});

describe('ランキングが空の場合', () => {
const actual = rankingResponse(sampleTrack, 1, [], null);
const actual = rankingResponse(sampleTrack, 1, [], null, 0);
test('タイムが登録されていないメッセージが返却されること', () => {
const expected = {
content: 'まだマリオカートスタジアムのNITAのタイムが登録されていません。',
Expand Down
3 changes: 2 additions & 1 deletion src/slash-command/ranking.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export default {
const serverMembers = Array.from((await interaction.guild.members.fetch({ limit: 200 })).values());
const ranking = await nitaRepository.selectRanking(track.code, serverMembers, 20, (page - 1) * 20);
const myRank = await nitaRepository.selectRankByUser(track.code, interaction.user.id, serverMembers);
const rankingSize = await nitaRepository.countExistsNita(track.code, serverMembers);

await interaction.followUp(rankingResponse(track, page, ranking, myRank));
await interaction.followUp(rankingResponse(track, page, ranking, myRank, rankingSize));
},
};
4 changes: 4 additions & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export type NitaRepository = {
discordUserId: string,
discordMembers: GuildMember[]
) => Promise<number | null>;
countExistsNita: (
trackCode: string,
discordMembers: GuildMember[]
) => Promise<number>;
};

// 参考 https://typescriptbook.jp/reference/functions/overload-functions#%E3%82%A2%E3%83%AD%E3%83%BC%E9%96%A2%E6%95%B0%E3%81%A8%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%AD%E3%83%BC%E3%83%89
Expand Down

0 comments on commit b4bd987

Please sign in to comment.