From 85b1c1b298abd8221ce0724502c9a830bc03cacb Mon Sep 17 00:00:00 2001 From: Ren Adachi <49895682+moeyashi@users.noreply.github.com> Date: Fri, 22 Mar 2024 22:16:11 +0900 Subject: [PATCH 1/2] feat(nita-repository): add countExistsNita Signed-off-by: Ren Adachi <49895682+moeyashi@users.noreply.github.com> --- src/infra/repository/nita.js | 10 +++++++ src/infra/repository/nita.test.js | 48 +++++++++++++++++++++++++++++++ src/types.d.ts | 4 +++ 3 files changed, 62 insertions(+) diff --git a/src/infra/repository/nita.js b/src/infra/repository/nita.js index d9e099d..8f60997 100644 --- a/src/infra/repository/nita.js +++ b/src/infra/repository/nita.js @@ -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); + }, }; }; diff --git a/src/infra/repository/nita.test.js b/src/infra/repository/nita.test.js index fe022df..3233841 100644 --- a/src/infra/repository/nita.test.js +++ b/src/infra/repository/nita.test.js @@ -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); + }); + }); }); diff --git a/src/types.d.ts b/src/types.d.ts index 0aaad54..888f48f 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -34,6 +34,10 @@ export type NitaRepository = { discordUserId: string, discordMembers: GuildMember[] ) => Promise; + countExistsNita: ( + trackCode: string, + discordMembers: GuildMember[] + ) => Promise; }; // 参考 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 From ef3accc5bdce45d8f2306368d65edba3b0482a3b Mon Sep 17 00:00:00 2001 From: Ren Adachi <49895682+moeyashi@users.noreply.github.com> Date: Fri, 22 Mar 2024 22:22:57 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat(/ranking):=20=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E3=81=A7=E4=BD=95=E4=BD=8D=E3=81=BE=E3=81=A7=E3=81=82=E3=82=8B?= =?UTF-8?q?=E3=81=8B=E3=82=8F=E3=81=8B=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ren Adachi <49895682+moeyashi@users.noreply.github.com> --- src/presenter/ranking-response.js | 5 +++-- src/presenter/ranking-response.test.js | 29 ++++++++++++++------------ src/slash-command/ranking.js | 3 ++- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/presenter/ranking-response.js b/src/presenter/ranking-response.js index ca987f6..ef1535a 100644 --- a/src/presenter/ranking-response.js +++ b/src/presenter/ranking-response.js @@ -7,9 +7,10 @@ import { displayMilliseconds } from '../util/time.js'; * @param {number} page * @param {import('../types').UnwrapPromise>} 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のタイムが登録されていません。`, @@ -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 = []; diff --git a/src/presenter/ranking-response.test.js b/src/presenter/ranking-response.test.js index cf29d88..3cab9a1 100644 --- a/src/presenter/ranking-response.test.js +++ b/src/presenter/ranking-response.test.js @@ -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位まで'); }); }); }); @@ -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位まで'); }); }); }); @@ -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位まで'); }); }); }); @@ -101,6 +101,7 @@ describe(rankingResponse, () => { { member: makeGuildMember({ nickname: 'user24' }), milliseconds: sampleTrack.nitaVSWRMilliseconds + 7001 }, ], null, + 24, ); test('1落ちのembedが返却されること', () => { expect(actual.embeds?.[0]).toEqual({ @@ -186,6 +187,7 @@ describe(rankingResponse, () => { milliseconds: sampleTrack.nitaVSWRMilliseconds, })), null, + 25, ); expect(actual.embeds?.length).toEqual(1); }); @@ -198,6 +200,7 @@ describe(rankingResponse, () => { milliseconds: sampleTrack.nitaVSWRMilliseconds, })), null, + 26, ); expect(actual.embeds?.length).toEqual(2); }); @@ -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のタイムが登録されていません。', diff --git a/src/slash-command/ranking.js b/src/slash-command/ranking.js index f7511a7..9c861c6 100644 --- a/src/slash-command/ranking.js +++ b/src/slash-command/ranking.js @@ -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)); }, };