Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions spec/result.spec.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An edge case that I frequently run into is that "time" is different depending on FMC single vs average. For example 29 is an FMC single score result and 2933 is an FMC average score result. This needs to be taken into account for them to render properly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is true, I based this function on https://github.com/thewca/worldcubeassociation.org/blob/d3338d0175c608eb34bc1d3be60034cf0ac44e46/WcaOnRails/app/webpacker/lib/utils/edit-events.js#L99 where AttemptResults can't be an average. AttemptResultQualification can though, so if we want to use the method for that also I can change it.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
isMultiResultDnf,
encodeMultiResult,
formatMultiResult,
attemptResultToString,
} from '../src/helpers/result';

describe('Result helper', function () {
Expand Down Expand Up @@ -147,4 +148,58 @@ describe('Result helper', function () {
it('Correctly decodes new style multi result', function () {
expect(decodeMultiResult(979999902)).toEqual({ solved: 4, attempted: 6 });
});

it('Correctly Formats FMC Result', function () {
expect(
attemptResultToString({ attemptResult: 24, eventId: '333fm' }),
).toEqual('24 moves');
});

it('Correctly Formats MBLD Result', function () {
expect(
attemptResultToString({
attemptResult: encodeMultiResult({
solved: 6,
attempted: 6,
}),
eventId: '333mbf',
}),
).toEqual('6 points');
});

it('Correctly Formats 333 Result in Second', function () {
expect(
attemptResultToString({ attemptResult: 100, eventId: '333' }),
).toEqual('1 second');
});

it('Correctly Formats 333 Result in Seconds', function () {
expect(
attemptResultToString({ attemptResult: 3000, eventId: '333' }),
).toEqual('30 seconds');
});

it('Correctly Formats 333 Result in Minute', function () {
expect(
attemptResultToString({ attemptResult: 6000, eventId: '333' }),
).toEqual('1 minute');
});

it('Correctly Formats 333 Result in Minutes', function () {
expect(
attemptResultToString({ attemptResult: 9000, eventId: '333' }),
).toEqual('1 minute 30 seconds');
});

it('Correctly Formats 333 Result in hour', function () {
expect(
attemptResultToString({ attemptResult: 360000, eventId: '333' }),
).toEqual('1 hour');
});

it('Correctly Formats 333 Result in hours', function () {
expect(
attemptResultToString({ attemptResult: 369000, eventId: '333' }),
).toEqual('1 hour 1 minute 30 seconds');
});
});
16 changes: 15 additions & 1 deletion spec/time.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { formatCentiseconds } from '../src/helpers/time';
import { formatCentiseconds, pluralize } from '../src/helpers/time';

describe('Time Helper', function () {
it('Correctly formats DNF', function () {
Expand Down Expand Up @@ -52,4 +52,18 @@ describe('Time Helper', function () {
expect(formatCentiseconds(360000)).toBe('60:00.00');
expect(formatCentiseconds(360400)).toBe('60:04.00');
});

it('Correctly pluralizes', function () {
expect(pluralize({ count: 2, word: 'Cube' })).toEqual('2 Cubes');
});

it('Doesnt pluralize if the count is 1', function () {
expect(pluralize({ count: 1, word: 'Cube' })).toEqual('1 Cube');
});

it('Abbreviates Correctly', function () {
expect(
pluralize({ count: 1, word: 'hour', options: { abbreviate: true } }),
).toEqual('1h');
});
});
39 changes: 38 additions & 1 deletion src/helpers/result.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { AttemptResult } from '../models/attemptResult';
import { formatCentiseconds } from './time';
import { centiSecondsToHumanReadable, formatCentiseconds } from './time';
import { EventId } from '../models';
import { getEventResultType } from './event';

type DnfMultiResult = { isDnf: true };
type DnsMultiResult = { isDns: true };
Expand Down Expand Up @@ -120,3 +122,38 @@ function decodeNewMultiResult(result: AttemptResult): DecodedMultiResult {

return res;
}

interface AttemptResultToStringParams {
attemptResult: number;
eventId: EventId;
}

/**
* Returns the number of Points a Multi Result is worth
* @param mbValue
*/
function attemptResultToMbPoints(mbValue: number) {
const { solved, attempted } = decodeMultiResult(mbValue);
const missed = attempted - solved;
return solved - missed;
}

/**
* Formats an Attempt Result for Cutoffs and Qualifications
* attemptResult can be a time in centiseconds, a number or a MBLD encoded string
* @param attemptResult
* @param eventId
*/
export function attemptResultToString({
attemptResult,
eventId,
}: AttemptResultToStringParams) {
const type = getEventResultType(eventId);
if (type === 'time') {
return centiSecondsToHumanReadable({ c: attemptResult });
}
if (type === 'number') {
return `${attemptResult} moves`;
}
return `${attemptResultToMbPoints(attemptResult)} points`;
}
89 changes: 89 additions & 0 deletions src/helpers/time.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* Formats Centiseconds to a string like 3:30 or 0:43
* @param centiTime
*/
export function formatCentiseconds(centiTime: number): string {
if (centiTime === -1) {
return 'DNF';
Expand All @@ -14,9 +18,94 @@ export function formatCentiseconds(centiTime: number): string {
return `${s}.${prefix(cs)}`;
}

/**
* Pads out a number with a 0 if it's under 10
* @param n
*/
function prefix(n: number): string {
if (n < 10) {
return `0${n}`;
}
return `${n}`;
}

export const SECOND_IN_CS = 100;
export const MINUTE_IN_CS = 60 * SECOND_IN_CS;
export const HOUR_IN_CS = 60 * MINUTE_IN_CS;

interface PluralizeParams {
count: number;
word: string;
options?: {
fixed?: number;
abbreviate?: boolean;
};
}

/**
* Adds an s to a word if count is over 1.
* Takes options to pad the count and abbreviate the word with its
* first letter
* @param count
* @param word
* @param options
*/
export function pluralize({ count, word, options = {} }: PluralizeParams) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't export this function, there's libraries out there that specialize in this use-case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just exported it for testing. How do we do that?

const countStr =
options.fixed && count % 1 > 0 ? count.toFixed(options.fixed) : count;
const countDesc = options.abbreviate
? word[0]
: ` ${count === 1 ? word : `${word}s`}`;
return countStr + countDesc;
}

interface CentiSecondsToHumanReadableParams {
c: number;
options?: {
short?: boolean;
};
}

/**
* Converts Centiseconds to a human-readable string like "5:30 minutes"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"5:30 minutes" doesn't actually make much sense...what's the use case here?

I would expect "5 seconds" or "10 minutes" or "5:30" but in English, I would read the original as "5 minutes and 30 seconds minutes"

Localization should somehow be taken into account for a library like this but I'm not sure if localization makes sense here...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah sorry that comment doesn't make sense, it's 5 minutes and 30 seconds

* @param c
* @param options
*/
export function centiSecondsToHumanReadable({
Comment thread
coder13 marked this conversation as resolved.
c,
options = {},
}: CentiSecondsToHumanReadableParams) {
let centiseconds = c;
let str = '';

const hours = centiseconds / HOUR_IN_CS;
centiseconds %= HOUR_IN_CS;
if (hours >= 1) {
str += `${pluralize({
count: Math.floor(hours),
word: 'hour',
options: { abbreviate: options.short },
})} `;
}

const minutes = centiseconds / MINUTE_IN_CS;
centiseconds %= MINUTE_IN_CS;
if (minutes >= 1) {
str += `${pluralize({
count: Math.floor(minutes),
word: 'minute',
options: { abbreviate: options.short },
})} `;
}

const seconds = centiseconds / SECOND_IN_CS;
if (seconds > 0 || str.length === 0) {
str += `${pluralize({
count: seconds,
word: 'second',
options: { fixed: 2, abbreviate: options.short },
})} `;
}

return str.trim();
}