-
-
Notifications
You must be signed in to change notification settings - Fork 617
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding scaffolding, tests and solution
- Loading branch information
Showing
4 changed files
with
377 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
class LedgerEntry { | ||
constructor(date, description, change) { | ||
this.date = new Date(date); | ||
this.description = description; | ||
this.change = change; | ||
} | ||
} | ||
|
||
const headerRow = (locale) => | ||
locale === 'en-US' | ||
? `${`Date`.padEnd(10, ` `)} | ${`Description`.padEnd( | ||
25, | ||
` `, | ||
)} | ${`Change`.padEnd(13, ` `)}` | ||
: `${`Datum`.padEnd(10, ` `)} | ${`Omschrijving`.padEnd( | ||
25, | ||
` `, | ||
)} | ${`Verandering`.padEnd(13, ` `)}`; | ||
|
||
export const createEntry = (date, description, change) => | ||
new LedgerEntry(date, description, change); | ||
|
||
export function formatEntries(currency, locale, entries) { | ||
// Generate Header Row | ||
let table = headerRow(locale); | ||
// Set Formatting Options | ||
let formattingOptions = { | ||
style: 'currency', | ||
currency: currency, | ||
currencySign: locale === 'en-US' ? 'accounting' : 'standard', | ||
currencyDisplay: locale === 'en-US' ? 'symbol' : 'narrowSymbol', | ||
minimumFractionDigits: 2, | ||
maximumFractionDigits: 2, | ||
}; | ||
// Sort Entries | ||
entries.sort( | ||
(a, b) => | ||
a.date - b.date || | ||
a.change - b.change || | ||
a.description.localeCompare(b.description), | ||
); | ||
// Writing each entry to table | ||
entries.forEach((entry) => { | ||
table += '\n'; | ||
// Write entry date to table | ||
table += `${entry.date.toLocaleDateString(locale, { | ||
day: '2-digit', | ||
month: '2-digit', | ||
year: 'numeric', | ||
})} | `; | ||
// Write entry description to table | ||
let truncatedDescription = | ||
entry.description.length > 25 | ||
? `${entry.description.substring(0, 22)}...` | ||
: entry.description.padEnd(25, ' '); | ||
table += `${truncatedDescription} | `; | ||
// Write entry change to table | ||
let changeStr = (entry.change / 100).toLocaleString( | ||
locale, | ||
formattingOptions, | ||
); | ||
let trailingSpace = changeStr.includes(')') ? '' : ' '; | ||
table += `${changeStr}${trailingSpace}`.padStart(13, ' '); | ||
}); | ||
|
||
return table; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
class LedgerEntry { | ||
constructor() { | ||
this.date = undefined; | ||
this.description = undefined; | ||
this.change = undefined; | ||
} | ||
} | ||
|
||
export function createEntry(date, description, change) { | ||
let entry = new LedgerEntry(); | ||
entry.date = new Date(date); | ||
entry.description = description; | ||
entry.change = change; | ||
return entry; | ||
} | ||
|
||
export function formatEntries(currency, locale, entries) { | ||
let table = ''; | ||
if (locale === 'en-US') { | ||
// Generate Header Row | ||
table += | ||
'Date'.padEnd(10, ' ') + | ||
' | ' + | ||
'Description'.padEnd(25, ' ') + | ||
' | ' + | ||
'Change'.padEnd(13, ' ') + | ||
'\n'; | ||
|
||
// Sort entries | ||
entries.sort( | ||
(a, b) => | ||
a.date - b.date || | ||
a.change - b.change || | ||
a.description.localeCompare(b.description), | ||
); | ||
|
||
entries.forEach((entry) => { | ||
// Write entry date to table | ||
const dateStr = `${(entry.date.getMonth() + 1) | ||
.toString() | ||
.padStart(2, '0')}/${entry.date | ||
.getDate() | ||
.toString() | ||
.padStart(2, '0')}/${entry.date.getFullYear()}`; | ||
table += `${dateStr} | `; | ||
|
||
// Write entry description to table | ||
const truncatedDescription = | ||
entry.description.length > 25 | ||
? `${entry.description.substring(0, 22)}...` | ||
: entry.description.padEnd(25, ' '); | ||
table += `${truncatedDescription} | `; | ||
|
||
// Write entry change to table | ||
let changeStr = ''; | ||
if (currency === 'USD') { | ||
let formatingOptions = { | ||
style: 'currency', | ||
currency: 'USD', | ||
//currencySign: 'accounting', | ||
minimumFractionDigits: 2, | ||
maximumFractionDigits: 2, | ||
}; | ||
if (entry.change < 0) { | ||
changeStr = `(${Math.abs(entry.change / 100).toLocaleString( | ||
'en-US', | ||
formatingOptions, | ||
)})`; | ||
} else { | ||
changeStr = `${(entry.change / 100).toLocaleString( | ||
'en-US', | ||
formatingOptions, | ||
)} `; | ||
} | ||
} else if (currency === 'EUR') { | ||
let formatingOptions = { | ||
style: 'currency', | ||
currency: 'EUR', | ||
minimumFractionDigits: 2, | ||
maximumFractionDigits: 2, | ||
}; | ||
if (entry.change < 0) { | ||
changeStr = `(${Math.abs(entry.change / 100).toLocaleString( | ||
'en-US', | ||
formatingOptions, | ||
)})`; | ||
} else { | ||
changeStr = `${(entry.change / 100).toLocaleString( | ||
'en-US', | ||
formatingOptions, | ||
)} `; | ||
} | ||
} | ||
table += changeStr.padStart(13, ' '); | ||
table += '\n'; | ||
}); | ||
} else if (locale === 'nl-NL') { | ||
// Generate Header Row | ||
table += | ||
'Datum'.padEnd(10, ' ') + | ||
' | ' + | ||
'Omschrijving'.padEnd(25, ' ') + | ||
' | ' + | ||
'Verandering'.padEnd(13, ' ') + | ||
'\n'; | ||
|
||
// Sort entries | ||
entries.sort( | ||
(a, b) => | ||
a.date - b.date || | ||
a.change - b.change || | ||
a.description.localeCompare(b.description), | ||
); | ||
|
||
entries.forEach((entry) => { | ||
// Write entry date to table | ||
const dateStr = `${entry.date.getDate().toString().padStart(2, '0')}-${( | ||
entry.date.getMonth() + 1 | ||
) | ||
.toString() | ||
.padStart(2, '0')}-${entry.date.getFullYear()}`; | ||
table += `${dateStr} | `; | ||
|
||
// Write entry description to table | ||
const truncatedDescription = | ||
entry.description.length > 25 | ||
? `${entry.description.substring(0, 22)}...` | ||
: entry.description.padEnd(25, ' '); | ||
table += `${truncatedDescription} | `; | ||
|
||
// Write entry change to table | ||
let changeStr = ''; | ||
if (currency === 'USD') { | ||
let formatingOptions = { | ||
style: 'currency', | ||
currency: 'USD', | ||
currencyDisplay: 'narrowSymbol', | ||
minimumFractionDigits: 2, | ||
maximumFractionDigits: 2, | ||
}; | ||
changeStr = `${(entry.change / 100).toLocaleString( | ||
'nl-NL', | ||
formatingOptions, | ||
)} `; | ||
} else if (currency === 'EUR') { | ||
let formatingOptions = { | ||
style: 'currency', | ||
currency: 'EUR', | ||
currencyDisplay: 'narrowSymbol', | ||
minimumFractionDigits: 2, | ||
maximumFractionDigits: 2, | ||
}; | ||
changeStr = `${(entry.change / 100).toLocaleString( | ||
'nl-NL', | ||
formatingOptions, | ||
)} `; | ||
} | ||
table += changeStr.padStart(13, ' '); | ||
table += '\n'; | ||
}); | ||
} | ||
return table.replace(/\n$/, ''); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import { createEntry, formatEntries } from './ledger'; | ||
|
||
describe('Ledger', () => { | ||
test('empty ledger', () => { | ||
let currency = 'USD'; | ||
let locale = 'en-US'; | ||
let entries = []; | ||
let expected = [ | ||
'Date | Description | Change ', | ||
].join('\n'); | ||
expect(formatEntries(currency, locale, entries)).toEqual(expected); | ||
}); | ||
|
||
xtest('one entry', () => { | ||
let currency = 'USD'; | ||
let locale = 'en-US'; | ||
let entries = [createEntry('2015-01-01', 'Buy present', -1000)]; | ||
let expected = [ | ||
'Date | Description | Change ', | ||
'01/01/2015 | Buy present | ($10.00)', | ||
].join('\n'); | ||
expect(formatEntries(currency, locale, entries)).toEqual(expected); | ||
}); | ||
|
||
xtest('credit and debit', () => { | ||
let currency = 'USD'; | ||
let locale = 'en-US'; | ||
let entries = [ | ||
createEntry('2015-01-02', 'Get present', 1000), | ||
createEntry('2015-01-01', 'Buy present', -1000), | ||
]; | ||
let expected = [ | ||
'Date | Description | Change ', | ||
'01/01/2015 | Buy present | ($10.00)', | ||
'01/02/2015 | Get present | $10.00 ', | ||
].join('\n'); | ||
expect(formatEntries(currency, locale, entries)).toEqual(expected); | ||
}); | ||
|
||
xtest('final order tie breaker is change', () => { | ||
let currency = 'USD'; | ||
let locale = 'en-US'; | ||
let entries = [ | ||
createEntry('2015-01-01', 'Something', 0), | ||
createEntry('2015-01-01', 'Something', -1), | ||
createEntry('2015-01-01', 'Something', 1), | ||
]; | ||
let expected = [ | ||
'Date | Description | Change ', | ||
'01/01/2015 | Something | ($0.01)', | ||
'01/01/2015 | Something | $0.00 ', | ||
'01/01/2015 | Something | $0.01 ', | ||
].join('\n'); | ||
expect(formatEntries(currency, locale, entries)).toEqual(expected); | ||
}); | ||
|
||
xtest('overlong description is truncated', () => { | ||
let currency = 'USD'; | ||
let locale = 'en-US'; | ||
let entries = [ | ||
createEntry('2015-01-01', 'Freude schoner Gotterfunken', -123456), | ||
]; | ||
let expected = [ | ||
'Date | Description | Change ', | ||
'01/01/2015 | Freude schoner Gotterf... | ($1,234.56)', | ||
].join('\n'); | ||
expect(formatEntries(currency, locale, entries)).toEqual(expected); | ||
}); | ||
|
||
xtest('euros', () => { | ||
let currency = 'EUR'; | ||
let locale = 'en-US'; | ||
let entries = [createEntry('2015-01-01', 'Buy present', -1000)]; | ||
let expected = [ | ||
'Date | Description | Change ', | ||
'01/01/2015 | Buy present | (€10.00)', | ||
].join('\n'); | ||
expect(formatEntries(currency, locale, entries)).toEqual(expected); | ||
}); | ||
|
||
xtest('Dutch locale', () => { | ||
let currency = 'USD'; | ||
let locale = 'nl-NL'; | ||
let entries = [createEntry('2015-03-12', 'Buy present', 123456)]; | ||
let expected = [ | ||
'Datum | Omschrijving | Verandering ', | ||
'12-03-2015 | Buy present | $ 1.234,56 ', | ||
].join('\n'); | ||
expect(formatEntries(currency, locale, entries)).toEqual(expected); | ||
}); | ||
|
||
xtest('Dutch locale and euros', () => { | ||
let currency = 'EUR'; | ||
let locale = 'nl-NL'; | ||
let entries = [createEntry('2015-03-12', 'Buy present', 123456)]; | ||
let expected = [ | ||
'Datum | Omschrijving | Verandering ', | ||
'12-03-2015 | Buy present | € 1.234,56 ', | ||
].join('\n'); | ||
expect(formatEntries(currency, locale, entries)).toEqual(expected); | ||
}); | ||
|
||
xtest('Dutch negative number with 3 digits before decimal point', () => { | ||
let currency = 'USD'; | ||
let locale = 'nl-NL'; | ||
let entries = [createEntry('2015-03-12', 'Buy present', -12345)]; | ||
let expected = [ | ||
'Datum | Omschrijving | Verandering ', | ||
'12-03-2015 | Buy present | $ -123,45 ', | ||
].join('\n'); | ||
expect(formatEntries(currency, locale, entries)).toEqual(expected); | ||
}); | ||
|
||
xtest('American negative number with 3 digits before decimal point', () => { | ||
let currency = 'USD'; | ||
let locale = 'en-US'; | ||
let entries = [createEntry('2015-03-12', 'Buy present', -12345)]; | ||
let expected = [ | ||
'Date | Description | Change ', | ||
'03/12/2015 | Buy present | ($123.45)', | ||
].join('\n'); | ||
expect(formatEntries(currency, locale, entries)).toEqual(expected); | ||
}); | ||
|
||
xtest('multiple entries on same date ordered by description', () => { | ||
let currency = 'USD'; | ||
let locale = 'en-US'; | ||
let entries = [ | ||
createEntry('2015-01-01', 'Get present', 1000), | ||
createEntry('2015-01-01', 'Buy present', -1000), | ||
]; | ||
let expected = [ | ||
'Date | Description | Change ', | ||
'01/01/2015 | Buy present | ($10.00)', | ||
'01/01/2015 | Get present | $10.00 ', | ||
].join('\n'); | ||
expect(formatEntries(currency, locale, entries)).toEqual(expected); | ||
}); | ||
}); |