Skip to content

Commit

Permalink
refactor(/nita-list): responseを別ファイルに分け、テスト作成
Browse files Browse the repository at this point in the history
Signed-off-by: Ren Adachi <[email protected]>
  • Loading branch information
moeyashi committed Mar 24, 2024
1 parent ef3accc commit 7d79ddb
Show file tree
Hide file tree
Showing 3 changed files with 290 additions and 66 deletions.
77 changes: 77 additions & 0 deletions src/presenter/nita-list-response.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// @ts-check
import { colorByTimeRank } from '../const/color.js';
import { displayMilliseconds } from '../util/time.js';

/**
* @param {{ nita: import('../types').Nita, track: import('../types').Track }[]} nitaTrackList
* @return {import('discord.js').InteractionReplyOptions}
*/
export const nitaListResponse = (nitaTrackList) => {
if (nitaTrackList.length === 0) {
return {
content: 'NITAのタイムが登録されていません。`/nita`コマンドを使って記録を登録してみましょう。',
};
}

const groupedNitaAndTrackList = groupByRank(nitaTrackList.map(({ nita, track }) => {
return {
nita,
track,
diffWRnNita: nita.milliseconds - track.nitaVSWRMilliseconds,
};
}).sort((a, b) => a.diffWRnNita - b.diffWRnNita));
return {
// embedsは最大10個まで、fieldsは最大25個しか指定できないが、track数が96なので最悪のケースを考えても9embedsで済む
// `[1落ち1fields, 2落ち1fields, 3落ち1fields, 4落ち1fields, 5落ち1fields, 6落ち以上25fields, 6落ち以上25fields, 6落ち以上25fields, 6落ち以上16fields]`
embeds: groupedNitaAndTrackList.flatMap(([rank, color, nitaAndTrackList]) => {
/** @type {import('discord.js').APIEmbed[]} */
const embeds = [];
return nitaAndTrackList.reduce((pv, cv, i) => {
if (i % 25 === 0) {
pv.push({
title: `${rank}`,
color,
fields: [],
});
}
pv[pv.length - 1].fields?.push({
name: cv.track?.trackName || cv.nita.trackCode,
value: `${displayMilliseconds(cv.nita.milliseconds)} WR + ${cv.diffWRnNita / 1000}秒`,
});
return pv;
}, embeds);
}),
};
};

/**
* @param {Array<{ nita: import('../types').Nita; track: import('../types').Track | null; diffWRnNita: number; }>} nitaAndTrackList
* @returns {Array<[string, number|undefined, Array<{ nita: import('../types').Nita; track: import('../types').Track | null; diffWRnNita: number; }>]>}
*/
const groupByRank = (nitaAndTrackList) => {
/** @type {Array<[string, number|undefined, Array<{ nita: import('../types').Nita; track: import('../types').Track | null; diffWRnNita: number; }>]>} */
const ret = [
['1落ち', colorByTimeRank(1), []],
['2落ち', colorByTimeRank(2), []],
['3落ち', colorByTimeRank(3), []],
['4落ち', colorByTimeRank(4), []],
['5落ち', colorByTimeRank(5), []],
['6落ち以上', colorByTimeRank(6), []],
];
nitaAndTrackList.forEach((nitaAndTrack) => {
if (nitaAndTrack.diffWRnNita <= 1000) {
ret[0][2].push(nitaAndTrack);
} else if (nitaAndTrack.diffWRnNita <= 2000) {
ret[1][2].push(nitaAndTrack);
} else if (nitaAndTrack.diffWRnNita <= 3000) {
ret[2][2].push(nitaAndTrack);
} else if (nitaAndTrack.diffWRnNita <= 4000) {
ret[3][2].push(nitaAndTrack);
} else if (nitaAndTrack.diffWRnNita <= 5000) {
ret[4][2].push(nitaAndTrack);
} else {
ret[5][2].push(nitaAndTrack);
}
});
return ret.filter(([, , arr]) => arr.length > 0);
};
199 changes: 199 additions & 0 deletions src/presenter/nita-list-response.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// @ts-check
import { describe, expect, test } from 'vitest';
import { nitaListResponse } from './nita-list-response.js';
import { getByCode, trackCodeSet } from '../const/track.js';

describe(nitaListResponse, () => {
describe('nitaListが空の場合', () => {
const actual = nitaListResponse([]);
test('0件メッセージが返却されること', () => {
expect(actual).toEqual({
content: 'NITAのタイムが登録されていません。`/nita`コマンドを使って記録を登録してみましょう。',
});
});
});
describe('nitaListが空でない場合', () => {
const discordUserId = '1';
const trackList = Object.freeze(Array.from(trackCodeSet));
describe('rank毎に色分けされたembedsが返却される', () => {
describe('秒数の境界値テスト', () => {
/**
* @param {string} trackCode
* @param {number} diff
* @returns {{ nita: import('../types').Nita, track: import('../types').Track }}
*/
const makeNitaTrack = (trackCode, diff) => {
const milliseconds = 2 * 60 * 1000;
return {
nita: { trackCode, discordUserId, milliseconds },
track: { trackName: trackCode, code: trackCode, nitaVSWRMilliseconds: milliseconds - diff, nitaAllCombinationWRMilliseconds: 0, nitaVSWRUrl: '', aliases: [] },
};
};
const nitaList = [
makeNitaTrack(trackList[0], -1),
makeNitaTrack(trackList[1], 0),
makeNitaTrack(trackList[2], 1),
makeNitaTrack(trackList[3], 999),
makeNitaTrack(trackList[4], 1000),
makeNitaTrack(trackList[5], 1001),
makeNitaTrack(trackList[6], 1999),
makeNitaTrack(trackList[7], 2000),
makeNitaTrack(trackList[8], 2001),
makeNitaTrack(trackList[9], 2999),
makeNitaTrack(trackList[10], 3000),
makeNitaTrack(trackList[11], 3001),
makeNitaTrack(trackList[12], 3999),
makeNitaTrack(trackList[13], 4000),
makeNitaTrack(trackList[14], 4001),
makeNitaTrack(trackList[15], 4999),
makeNitaTrack(trackList[16], 5000),
makeNitaTrack(trackList[17], 5001),
makeNitaTrack(trackList[18], 5999),
makeNitaTrack(trackList[19], 6000),
makeNitaTrack(trackList[20], 6001),
makeNitaTrack(trackList[21], 6999),
makeNitaTrack(trackList[22], 7000),
makeNitaTrack(trackList[23], 7001),
];
const actual = nitaListResponse(nitaList);
test('1落ちのembedが返却されること', () => {
expect(actual.embeds?.[0]).toEqual({
title: '1落ち',
color: 13632027,
fields: [
{ name: nitaList[0].track.trackName, value: '2:00.000 WR + -0.001秒' },
{ name: nitaList[1].track.trackName, value: '2:00.000 WR + 0秒' },
{ name: nitaList[2].track.trackName, value: '2:00.000 WR + 0.001秒' },
{ name: nitaList[3].track.trackName, value: '2:00.000 WR + 0.999秒' },
{ name: nitaList[4].track.trackName, value: '2:00.000 WR + 1秒' },
],
});
});
test('2落ちのembedが返却されること', () => {
expect(actual.embeds?.[1]).toEqual({
title: '2落ち',
color: 16312092,
fields: [
{ name: nitaList[5].track.trackName, value: '2:00.000 WR + 1.001秒' },
{ name: nitaList[6].track.trackName, value: '2:00.000 WR + 1.999秒' },
{ name: nitaList[7].track.trackName, value: '2:00.000 WR + 2秒' },
],
});
});
test('3落ちのembedが返却されること', () => {
expect(actual.embeds?.[2]).toEqual({
title: '3落ち',
color: 8311585,
fields: [
{ name: nitaList[8].track.trackName, value: '2:00.000 WR + 2.001秒' },
{ name: nitaList[9].track.trackName, value: '2:00.000 WR + 2.999秒' },
{ name: nitaList[10].track.trackName, value: '2:00.000 WR + 3秒' },
],
});
});
test('4落ちのembedが返却されること', () => {
expect(actual.embeds?.[3]).toEqual({
title: '4落ち',
color: 4886754,
fields: [
{ name: nitaList[11].track.trackName, value: '2:00.000 WR + 3.001秒' },
{ name: nitaList[12].track.trackName, value: '2:00.000 WR + 3.999秒' },
{ name: nitaList[13].track.trackName, value: '2:00.000 WR + 4秒' },
],
});
});
test('5落ちのembedが返却されること', () => {
expect(actual.embeds?.[4]).toEqual({
title: '5落ち',
color: 12390624,
fields: [
{ name: nitaList[14].track.trackName, value: '2:00.000 WR + 4.001秒' },
{ name: nitaList[15].track.trackName, value: '2:00.000 WR + 4.999秒' },
{ name: nitaList[16].track.trackName, value: '2:00.000 WR + 5秒' },
],
});
});
test('6落ち以上のembedが返却されること', () => {
expect(actual.embeds?.[5]).toEqual({
title: '6落ち以上',
color: undefined,
fields: [
{ name: nitaList[17].track.trackName, value: '2:00.000 WR + 5.001秒' },
{ name: nitaList[18].track.trackName, value: '2:00.000 WR + 5.999秒' },
{ name: nitaList[19].track.trackName, value: '2:00.000 WR + 6秒' },
{ name: nitaList[20].track.trackName, value: '2:00.000 WR + 6.001秒' },
{ name: nitaList[21].track.trackName, value: '2:00.000 WR + 6.999秒' },
{ name: nitaList[22].track.trackName, value: '2:00.000 WR + 7秒' },
{ name: nitaList[23].track.trackName, value: '2:00.000 WR + 7.001秒' },
],
});
});
});
});
describe('同一ランクに26コース以上存在する場合はembedが分割される', () => {
// 96コースの場合をテストするので、26, 51, 76コースの場合はスキップする
describe.skip('26コースの場合');
describe.skip('51コースの場合');
describe.skip('76コースの場合');
describe('96コースの場合', () => {
const actual = nitaListResponse(trackList.map((trackCode) => {
const track = getByCode(trackCode);
if (!track) {
throw new Error(`track not found: ${trackCode}`);
}
const nita = {
trackCode,
discordUserId,
milliseconds: track?.nitaVSWRMilliseconds || 0,
};
return { nita, track };
}));
describe('25コース毎に4embedsが返却されること', () => {
test('4embedsが返却されること', () => {
expect(actual.embeds).toHaveLength(4);
});
test('1つ目のembedには25コースが含まれること', () => {
const embed = actual.embeds?.[0];
if (!embed) {
throw new Error('embed is undefined');
}
if (!('fields' in embed)) {
throw new Error('fields is undefined');
}
expect(embed.fields).toHaveLength(25);
});
test('2つ目のembedには25コースが含まれること', () => {
const embed = actual.embeds?.[1];
if (!embed) {
throw new Error('embed is undefined');
}
if (!('fields' in embed)) {
throw new Error('fields is undefined');
}
expect(embed.fields).toHaveLength(25);
});
test('3つ目のembedには25コースが含まれること', () => {
const embed = actual.embeds?.[2];
if (!embed) {
throw new Error('embed is undefined');
}
if (!('fields' in embed)) {
throw new Error('fields is undefined');
}
expect(embed.fields).toHaveLength(25);
});
test('4つ目のembedには21コースが含まれること', () => {
const embed = actual.embeds?.[3];
if (!embed) {
throw new Error('embed is undefined');
}
if (!('fields' in embed)) {
throw new Error('fields is undefined');
}
expect(embed.fields).toHaveLength(21);
});
});
});
});
});
});
80 changes: 14 additions & 66 deletions src/slash-command/nita-list.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// @ts-check
import { SlashCommandBuilder } from 'discord.js';
import { nitaListResponse } from '../presenter/nita-list-response.js';
import { getByCode } from '../const/track.js';
import { displayMilliseconds } from '../util/time.js';
import { colorByTimeRank } from '../const/color.js';

/** @type { import('../types').SlashCommand } */
export default {
Expand All @@ -16,75 +15,24 @@ export default {
}

const nitaList = await nitaRepository.selectNitaByUser(discordUserId);
if (nitaList.length === 0) {
await interaction.reply({
content: 'NITAのタイムが登録されていません。',
});
return;
}

const groupedNitaAndTrackList = groupByRank(nitaList.map((nita) => {
const track = getByCode(nita.trackCode);
return {
nita,
track,
diffWRnNita: track?.nitaVSWRMilliseconds ? nita.milliseconds - track.nitaVSWRMilliseconds : Number.MAX_SAFE_INTEGER,
};
}).sort((a, b) => a.diffWRnNita - b.diffWRnNita));

await interaction.reply({
// embedsは最大10個まで、fieldsは最大25個しか指定できないが、track数が98なので最悪のケースを考えても9embedsで済む
// `[1落ち1fields, 2落ち1fields, 3落ち1fields, 4落ち1fields, 5落ち1fields, 6落ち以上25fields, 6落ち以上25fields, 6落ち以上25fields, 6落ち以上18fields]`
embeds: groupedNitaAndTrackList.flatMap(([rank, color, nitaAndTrackList]) => {
/** @type {import('discord.js').APIEmbed[]} */
const embeds = [];
return nitaAndTrackList.reduce((pv, cv, i) => {
if (i % 25 === 0) {
pv.push({
title: `${rank}`,
color,
fields: [],
});
}
pv[pv.length - 1].fields?.push({
name: cv.track?.trackName || cv.nita.trackCode,
value: `${displayMilliseconds(cv.nita.milliseconds)} WR + ${cv.diffWRnNita / 1000}秒`,
});
return pv;
}, embeds);
}),
});
const nitaTrackList = joinTrack(nitaList);
await interaction.reply(nitaListResponse(nitaTrackList));
},
};

/**
* @param {Array<{ nita: import('../types').Nita; track: import('../types').Track | null; diffWRnNita: number; }>} nitaAndTrackList
* @returns {Array<[string, number|undefined, Array<{ nita: import('../types').Nita; track: import('../types').Track | null; diffWRnNita: number; }>]>}
* @param {import('../types.js').Nita[]} nitaList
* @returns {Parameters<typeof import('../presenter/nita-list-response.js').nitaListResponse>[0]}
*/
const groupByRank = (nitaAndTrackList) => {
/** @type {Array<[string, number|undefined, Array<{ nita: import('../types').Nita; track: import('../types').Track | null; diffWRnNita: number; }>]>} */
const ret = [
['1落ち', colorByTimeRank(1), []],
['2落ち', colorByTimeRank(2), []],
['3落ち', colorByTimeRank(3), []],
['4落ち', colorByTimeRank(4), []],
['5落ち', colorByTimeRank(5), []],
['6落ち以上', colorByTimeRank(6), []],
];
nitaAndTrackList.forEach((nitaAndTrack) => {
if (nitaAndTrack.diffWRnNita < 1000) {
ret[0][2].push(nitaAndTrack);
} else if (nitaAndTrack.diffWRnNita < 2000) {
ret[1][2].push(nitaAndTrack);
} else if (nitaAndTrack.diffWRnNita < 3000) {
ret[2][2].push(nitaAndTrack);
} else if (nitaAndTrack.diffWRnNita < 4000) {
ret[3][2].push(nitaAndTrack);
} else if (nitaAndTrack.diffWRnNita < 5000) {
ret[4][2].push(nitaAndTrack);
} else {
ret[5][2].push(nitaAndTrack);
const joinTrack = (nitaList) => {
return nitaList.map((nita) => {
const track = getByCode(nita.trackCode);
if (!track) {
throw new Error(`track not found: ${nita.trackCode}`);
}
return {
nita,
track,
};
});
return ret.filter(([, , arr]) => arr.length > 0);
};

0 comments on commit 7d79ddb

Please sign in to comment.