diff --git a/src/stats/progression.test.ts b/src/stats/progression.test.ts index ee4c946a..3b9736c3 100644 --- a/src/stats/progression.test.ts +++ b/src/stats/progression.test.ts @@ -165,24 +165,6 @@ await test("computeStatProgression - error cases", async (t) => { history: [new PlayerDataBuilder(TEST_UUID, startDate).build()], reason: ERR_TRACKING_STARTED, }, - { - name: "no progress", - history: [ - new PlayerDataBuilder(TEST_UUID, startDate) - .withGamemodeStats( - "overall", - new StatsBuilder().withStat("wins", 100).build(), - ) - .build(), - new PlayerDataBuilder(TEST_UUID, endDate) - .withGamemodeStats( - "overall", - new StatsBuilder().withStat("wins", 100).build(), - ) - .build(), - ], - reason: "No progress", - }, { name: "three element history", history: [ @@ -352,6 +334,59 @@ await test("computeStatProgression - linear gamemode stats", async (t) => { }); }); + await t.test("edge case - zero current value", () => { + const startDate = new Date("2024-01-01T00:00:00Z"); + const endDate = new Date("2024-01-06T00:00:00Z"); + const startBuilder = new PlayerDataBuilder( + TEST_UUID, + startDate, + ); + const endBuilder = new PlayerDataBuilder( + TEST_UUID, + endDate, + ); + startBuilder.withGamemodeStats( + gamemode, + new StatsBuilder().withStat(stat, 0).build(), + ); + endBuilder.withGamemodeStats( + gamemode, + new StatsBuilder().withStat(stat, 0).build(), + ); + + const history: History = [ + startBuilder.build(), + endBuilder.build(), + ]; + + const result = computeStatProgression( + history, + endDate, + stat, + gamemode, + ); + + if (result.error) { + assert.fail( + `Expected success but got error: ${result.reason}`, + ); + } + + // When endValue is 0, the milestone should be 1 + assert.deepStrictEqual(result, { + stat, + endValue: 0, + progressPerDay: 0, + nextMilestoneValue: 1, + daysUntilMilestone: Infinity, + trendingUpward: true, + trackingDataTimeInterval: { + start: startDate, + end: endDate, + }, + }); + }); + await t.test("edge case - large values", () => { const startDate = new Date("2025-01-01T00:00:00Z"); const endDate = new Date("2025-01-21T00:00:00Z"); @@ -679,6 +714,65 @@ await test("computeStatProgression - quotient stats", async (t) => { }); }); + await t.test("edge case - zero current value", () => { + // When the current quotient is zero (0 dividend, 0 divisor) + const startDate = new Date("2024-01-01T00:00:00Z"); + const endDate = new Date("2024-01-11T00:00:00Z"); + const startBuilder = new StatsBuilder(); + const endBuilder = new StatsBuilder(); + + // Start: 0/0 = 0 + // End: 0/0 = 0 (zero current value) + startBuilder + .withStat(dividendStat, 0) + .withStat(divisorStat, 0); + endBuilder + .withStat(dividendStat, 0) + .withStat(divisorStat, 0); + + const history: History = [ + new PlayerDataBuilder(TEST_UUID, startDate) + .withGamemodeStats( + gamemode, + startBuilder.build(), + ) + .build(), + new PlayerDataBuilder(TEST_UUID, endDate) + .withGamemodeStats(gamemode, endBuilder.build()) + .build(), + ]; + + const result = computeStatProgression( + history, + endDate, + stat, + gamemode, + ); + + if (result.error) { + assert.fail( + `Expected success but got error: ${result.reason}`, + ); + } + + // When endValue is 0, milestone should be 1 + assert.deepStrictEqual(result, { + stat, + endValue: 0, + sessionQuotient: 0, + progressPerDay: 0, + dividendPerDay: 0, + divisorPerDay: 0, + nextMilestoneValue: 1, + daysUntilMilestone: Infinity, + trendingUpward: true, + trackingDataTimeInterval: { + start: startDate, + end: endDate, + }, + }); + }); + await t.test( "edge case - zero session divisor", async (t) => { @@ -1134,6 +1228,86 @@ await test("computeStatProgression - stars/experience stat", async (t) => { }); }); }); + + await t.test("edge case - zero current value", async (t) => { + // Note: This is a constructed case as new players have 500 exp/1 star + const startDate = new Date("2024-01-01T00:00:00Z"); + const endDate = new Date("2024-01-11T00:00:00Z"); // 10 days + // Start at 0 exp, stay at 0 (constructed case) + const history: History = [ + new PlayerDataBuilder(TEST_UUID, startDate) + .withExperience(0) + .build(), + new PlayerDataBuilder(TEST_UUID, endDate) + .withExperience(0) + .build(), + ]; + + await t.test("stars", () => { + const result = computeStatProgression( + history, + endDate, + "stars", + gamemode, + ); + + if (result.error) { + assert.fail( + `Expected success but got error: ${result.reason}`, + ); + } + + const { + daysUntilMilestone, + progressPerDay, + ...resultWithoutTrickyFloats + } = result; + + assert.deepStrictEqual(resultWithoutTrickyFloats, { + stat: "stars", + endValue: 0, + nextMilestoneValue: 100, + trendingUpward: true, + trackingDataTimeInterval: { + start: startDate, + end: endDate, + }, + }); + + // No progress in exp + assert.strictEqual(progressPerDay, 0); + assert.strictEqual(daysUntilMilestone, Infinity); + }); + + await t.test("experience", () => { + const result = computeStatProgression( + history, + endDate, + "experience", + gamemode, + ); + + if (result.error) { + assert.fail( + `Expected success but got error: ${result.reason}`, + ); + } + + // When endValue is 0, milestone should be 1 + assert.deepStrictEqual(result, { + stat: "experience", + endValue: 0, + progressPerDay: 0, + nextMilestoneValue: 1, + daysUntilMilestone: Infinity, + trendingUpward: true, + trackingDataTimeInterval: { + start: startDate, + end: endDate, + }, + }); + }); + }); }); } }); diff --git a/src/stats/progression.ts b/src/stats/progression.ts index a8352b6d..21ec85cb 100644 --- a/src/stats/progression.ts +++ b/src/stats/progression.ts @@ -277,10 +277,41 @@ export const computeStatProgression = ( if (increasePerDay === 0) { // Will make no progress - // TODO: Display upward milestone and infinite time + // Special case: when endValue is 0, set milestone to 1 + const nextMilestoneValue = endValue === 0 ? 1 : NaN; + const daysUntilMilestone = Infinity; + + return { + stat, + trackingDataTimeInterval: { + start: startDate, + end: endDate, + }, + endValue, + nextMilestoneValue, + daysUntilMilestone, + progressPerDay: increasePerDay, + trendingUpward: true, + }; + } + + // Special case: when endValue is 0, set milestone to 1 + if (endValue === 0) { + const nextMilestoneValue = 1; + const daysUntilMilestone = + (nextMilestoneValue - endValue) / increasePerDay; + return { - error: true, - reason: "No progress", + stat, + trackingDataTimeInterval: { + start: startDate, + end: endDate, + }, + endValue, + nextMilestoneValue, + daysUntilMilestone, + progressPerDay: increasePerDay, + trendingUpward: true, }; }