diff --git a/config.json b/config.json index 574ec5e8..07466fd4 100644 --- a/config.json +++ b/config.json @@ -892,6 +892,14 @@ "practices": [], "prerequisites": [], "difficulty": 3 + }, + { + "slug": "ledger", + "name": "Ledger", + "uuid": "fb9758db-359a-4839-b37e-7a462906846a", + "practices": [], + "prerequisites": [], + "difficulty": 4 } ], "foregone": [ diff --git a/exercises/practice/ledger/.docs/instructions.append.md b/exercises/practice/ledger/.docs/instructions.append.md new file mode 100644 index 00000000..82563cd0 --- /dev/null +++ b/exercises/practice/ledger/.docs/instructions.append.md @@ -0,0 +1,3 @@ +# Instructions append + +Because Cairo only supports ASCII characters, and the Euro currency symbol (€) is _not_ an ASCII character, we will be using the character `e` as a substitute. diff --git a/exercises/practice/ledger/.docs/instructions.md b/exercises/practice/ledger/.docs/instructions.md new file mode 100644 index 00000000..a53e5c15 --- /dev/null +++ b/exercises/practice/ledger/.docs/instructions.md @@ -0,0 +1,14 @@ +# Instructions + +Refactor a ledger printer. + +The ledger exercise is a refactoring exercise. +There is code that prints a nicely formatted ledger, given a locale (American or Dutch) and a currency (US dollar or euro). +The code however is rather badly written, though (somewhat surprisingly) it consistently passes the test suite. + +Rewrite this code. +Remember that in refactoring the trick is to make small steps that keep the tests passing. +That way you can always quickly go back to a working version. +Version control tools like git can help here as well. + +Please keep a log of what changes you've made and make a comment on the exercise containing that log, this will help reviewers. diff --git a/exercises/practice/ledger/.meta/config.json b/exercises/practice/ledger/.meta/config.json new file mode 100644 index 00000000..fa548014 --- /dev/null +++ b/exercises/practice/ledger/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "Falilah" + ], + "files": { + "solution": [ + "src/lib.cairo" + ], + "test": [ + "tests/ledger.cairo" + ], + "example": [ + ".meta/example.cairo" + ], + "invalidator": [ + "Scarb.toml" + ] + }, + "blurb": "Refactor a ledger printer." +} diff --git a/exercises/practice/ledger/.meta/example.cairo b/exercises/practice/ledger/.meta/example.cairo new file mode 100644 index 00000000..90bfe0ea --- /dev/null +++ b/exercises/practice/ledger/.meta/example.cairo @@ -0,0 +1,259 @@ +// Changes in the Refactored Code + +// 1) format_entries function now uses format_row to create each ledger entry, reducing redundancy. + +// 2) format_row function to format each row, combining date, description, and amount formatting +// into a single string. + +// 3) format_date Refactor split date formatting into a standalone function that uses split_date for +// modular handling. + +// 4) split_date Function introduced to extract year, month, and day components, improving clarity +// and code reuse. + +// 5) format_amount Improvements refactored logic for formatting amounts to make negative handling, +// spacing, and locale-specific formatting cleaner. + +// 6) format_number refinement Handles separators and decimal points for numbers more +// systematically. + +// 7) Enhanced add_sep Split logic into separate steps for readability, making prefix, middle, and +// final result assembly explicit. + +// 8)format_description logic for truncation and padding, aligning with coding standards. + +// 9) Added AMOUNT_COLUMN_WIDTH, MAX_DESCRIPTION_LENGTH and FORMATTED_DESCRIPTION_LENGTH constants +// for readability and maintainability. + +// 10) Imported AppendFormattedToByteArray to handle the conversion of byteArray to integer. + +// Overall, the refactored code is modular, better adheres to single-responsibility principles, +// and improves readability while ensuring maintainability. +use core::to_byte_array::AppendFormattedToByteArray; + +#[derive(Debug, PartialEq, Drop)] +pub enum Currency { + USD, + EUR, +} + +#[derive(Debug, PartialEq, Drop)] +pub enum Locale { + en_US, + nl_NL, +} + +#[derive(Debug, PartialEq, Drop)] +pub struct Entry { + pub date: ByteArray, + pub description: ByteArray, + pub amount_in_cents: i32, +} + +pub fn format_entries( + currency: Currency, locale: Locale, entries: Array, +) -> Array { + let mut ledger: Array = array![]; + + // Step 1: Define the header based on the locale + let header = match @locale { + Locale::en_US => "Date | Description | Change ", + Locale::nl_NL => "Datum | Omschrijving | Verandering ", + }; + ledger.append(header); + // Step 2: Process transactions + for entry in entries { + let row = format_row(@currency, @locale, entry); + ledger.append(row); + }; + + ledger +} + +fn format_row(currency: @Currency, locale: @Locale, entry: Entry) -> ByteArray { + let date = format_date(@entry.date, locale); + let amount_in_cents = format_amount(@entry.amount_in_cents, currency, locale); + format!("{} | {} | {}", date, format_description(entry.description), amount_in_cents) +} + +// format date based on the locale +fn format_date(date: @ByteArray, locale: @Locale) -> ByteArray { + let (mut year, mut month, mut day) = split_date(date); + match locale { + Locale::en_US => { + day += "/"; + month += "/"; + ByteArrayTrait::concat(@month, @ByteArrayTrait::concat(@day, @year)) + }, + Locale::nl_NL => { + day += "-"; + month += "-"; + ByteArrayTrait::concat(@day, @ByteArrayTrait::concat(@month, @year)) + }, + } +} + +// split date into year, month and day +fn split_date(date: @ByteArray) -> (ByteArray, ByteArray, ByteArray) { + let mut year = ""; + let mut month = ""; + let mut day = ""; + let mut sep = 0; + let mut i = 0; + + while i < date.len() { + if sep == 0 && i < 4 && date[i] != '-' { + year.append_byte(date[i]); + } else if date[i] == '-' { + sep += 1; + } else if sep == 1 && i < 7 && date[i] != '-' { + month.append_byte(date[i]); + } else { + day.append_byte(date[i]); + } + i += 1; + }; + + (year, month, day) +} + +fn format_amount(amount_in_cents: @i32, currency: @Currency, locale: @Locale) -> ByteArray { + let amount_in_cents = format!("{amount_in_cents}"); + let mut int_value: u32 = 0; + let mut negative = false; + let mut i = 0; + + if amount_in_cents[i] == '-' { + negative = true; + i += 1; + } + + const AMOUNT_COLUMN_WIDTH: usize = 13; + while i < amount_in_cents.len() { + let zero_ascii = '0'; + if let Option::Some(digit) = Option::Some(amount_in_cents[i] - zero_ascii) { + int_value = int_value * 10 + digit.into(); + } + i += 1; + }; + + let formatted_value = format_number(int_value, negative, currency, locale); + let mut extra = ""; + + if formatted_value.len() < AMOUNT_COLUMN_WIDTH { + let diff = AMOUNT_COLUMN_WIDTH - formatted_value.len(); + let mut i = 0; + while i < diff { + extra += " "; + i += 1; + } + } + ByteArrayTrait::concat(@extra, @formatted_value) +} + +fn format_number(value: u32, negative: bool, currency: @Currency, locale: @Locale) -> ByteArray { + let mut result = ""; + + if negative && locale == @Locale::en_US { + result.append_byte('('); + } + + match currency { + Currency::USD => result.append_byte('$'), + Currency::EUR => result.append_byte('e'), + }; + + if locale == @Locale::nl_NL { + result.append_byte(' '); + + if negative { + result.append_byte('-'); + } + } + + let whole = value / 100; + + result += add_sep(whole, locale); + let fraction = value % 100; + if locale == @Locale::en_US { + result.append_byte('.'); + } else { + result.append_byte(','); + } + + if fraction < 10 { + result.append_byte('0'); + fraction.append_formatted_to_byte_array(ref result, 10); + } else { + fraction.append_formatted_to_byte_array(ref result, 10); + } + + if negative && locale == @Locale::en_US { + result.append_byte(')'); + } else { + result.append_byte(' '); + } + + result +} + + +fn add_sep(whole: u32, locale: @Locale) -> ByteArray { + let mut result = ""; + let mut temp = ""; + @whole.append_formatted_to_byte_array(ref temp, 10); + + // Step 1: Append the first character if length > 3 + let mut prefix = ""; + if temp.len() > 3 { + prefix.append_byte(temp[0]); + } else { + result += temp; // If length <= 3, the result is directly temp + return result; + } + + // Step 2: Build the middle part with separators + let mut middle = ""; + let mut i = 1; + let mut sep = 0; + while i < temp.len() { + if sep == 0 { + if locale == @Locale::nl_NL { + middle.append_byte('.'); + } else { + middle.append_byte(','); + } + sep = 3; + } + middle.append_byte(temp[i]); + i += 1; + sep -= 1; + }; + + // Step 3: Combine prefix and middle + result = ByteArrayTrait::concat(@prefix, @middle); + + result +} + +fn format_description(transaction: ByteArray) -> ByteArray { + let mut formatted = ""; + const MAX_DESCRIPTION_LENGTH: usize = 22; + const FORMATTED_DESCRIPTION_LENGTH: usize = 25; + + if transaction.len() > MAX_DESCRIPTION_LENGTH { + let mut i = 0; + while i < MAX_DESCRIPTION_LENGTH { + formatted.append_byte(transaction[i]); + i += 1; + }; + formatted += "..."; + } else { + formatted += transaction; + while formatted.len() < FORMATTED_DESCRIPTION_LENGTH { + formatted.append_byte(' '); + }; + } + + formatted +} diff --git a/exercises/practice/ledger/.meta/tests.toml b/exercises/practice/ledger/.meta/tests.toml new file mode 100644 index 00000000..4ea45ceb --- /dev/null +++ b/exercises/practice/ledger/.meta/tests.toml @@ -0,0 +1,48 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[d131ecae-a30e-436c-b8f3-858039a27234] +description = "empty ledger" + +[ce4618d2-9379-4eca-b207-9df1c4ec8aaa] +description = "one entry" + +[8d02e9cb-e6ee-4b77-9ce4-e5aec8eb5ccb] +description = "credit and debit" + +[502c4106-0371-4e7c-a7d8-9ce33f16ccb1] +description = "multiple entries on same date ordered by description" +include = false + +[29dd3659-6c2d-4380-94a8-6d96086e28e1] +description = "final order tie breaker is change" + +[9b9712a6-f779-4f5c-a759-af65615fcbb9] +description = "overlong description is truncated" + +[67318aad-af53-4f3d-aa19-1293b4d4c924] +description = "euros" + +[bdc499b6-51f5-4117-95f2-43cb6737208e] +description = "Dutch locale" + +[86591cd4-1379-4208-ae54-0ee2652b4670] +description = "Dutch locale and euros" + +[876bcec8-d7d7-4ba4-82bd-b836ac87c5d2] +description = "Dutch negative number with 3 digits before decimal point" + +[29670d1c-56be-492a-9c5e-427e4b766309] +description = "American negative number with 3 digits before decimal point" + +[9c70709f-cbbd-4b3b-b367-81d7c6101de4] +description = "multiple entries on same date ordered by description" +reimplements = "502c4106-0371-4e7c-a7d8-9ce33f16ccb1" diff --git a/exercises/practice/ledger/Scarb.toml b/exercises/practice/ledger/Scarb.toml new file mode 100644 index 00000000..78db418a --- /dev/null +++ b/exercises/practice/ledger/Scarb.toml @@ -0,0 +1,7 @@ +[package] +name = "ledger" +version = "0.1.0" +edition = "2024_07" + +[dev-dependencies] +cairo_test = "2.9.2" diff --git a/exercises/practice/ledger/src/lib.cairo b/exercises/practice/ledger/src/lib.cairo new file mode 100644 index 00000000..93df9446 --- /dev/null +++ b/exercises/practice/ledger/src/lib.cairo @@ -0,0 +1,376 @@ +#[derive(Debug, PartialEq, Drop)] +pub enum Currency { + USD, + EUR, + JPY, +} + +#[derive(Debug, PartialEq, Drop)] +pub enum Locale { + en_US, + nl_NL, +} + +#[derive(Debug, PartialEq, Drop)] +pub struct Entry { + pub date: ByteArray, + pub description: ByteArray, + pub amount_in_cents: i32, +} + +pub fn format_entries( + currency: Currency, locale: Locale, entries: Array, +) -> Array { + let mut ledger: Array = ArrayTrait::new(); + let mut header = ""; + if locale == Locale::en_US { + header = "Date | Description | Change "; + } else if locale == Locale::nl_NL { + header = "Datum | Omschrijving | Verandering "; + } + // date, transaction, change + ledger.append(header); + for entry in entries { + let mut formatted_date = ""; + if locale == Locale::en_US { + let mut year = "/"; + let mut month = ""; + let mut day = "/"; + + let mut i = 0; + let mut sep = 0; + + while i < entry.date.len() { + if sep == 0 && i < 4 && entry.date[i] != '-' { + year.append_byte(entry.date[i]); + } else if entry.date[i] == '-' { + sep += 1; + } else if sep == 1 && i < 7 && entry.date[i] != '-' { + month.append_byte(entry.date[i]); + } else { + day.append_byte(entry.date[i]); + } + i += 1; + }; + + formatted_date = ByteArrayTrait::concat(@month, @ByteArrayTrait::concat(@day, @year)) + } else { + let mut year = "-"; + let mut month = "-"; + let mut day = ""; + + let mut i = 0; + let mut sep = 0; + + while i < entry.date.len() { + if sep == 0 && i < 4 && entry.date[i] != '-' { + year.append_byte(entry.date[i]); + } else if entry.date[i] == '-' { + sep += 1; + } else if sep == 1 && i < 7 && entry.date[i] != '-' { + month.append_byte(entry.date[i]); + } else { + day.append_byte(entry.date[i]); + } + i += 1; + }; + + formatted_date = ByteArrayTrait::concat(@day, @ByteArrayTrait::concat(@month, @year)); + } + + let mut formatted_change = ""; + let amount_in_cents = entry.amount_in_cents; + let change = format!("{amount_in_cents}"); + if currency == Currency::USD { + if locale == Locale::en_US { + let mut result = "$"; + let mut i = 0; + let mut op = ""; + let mut cl = ""; + if change[i] == '-' { + op += "("; + cl += ")"; + i += 1; + } else { + op += " "; + cl += " "; + } + let mut int: u32 = 0; + while i < change.len() { + let zero_ascii = '0'; + let c = Option::Some(change[i] - zero_ascii); + + match c { + Option::Some(v) => { int = int * 10 + v.into(); }, + Option::None => { break; }, + } + i += 1; + }; + + let val = int / 100; + let mut temp = format!("{val}"); + if temp.len() > 3 { + result.append_byte(temp[0]); + let mut i = 1; + let mut sep = 0; + while i < temp.len() { + if sep == 0 { + result.append_byte(','); + sep = 3; + } + result.append_byte(temp[i]); + i += 1; + sep -= 1; + } + } else { + result += temp; + } + + result += "."; + let mut rem = int % 100; + if int < 10 { + result += "0"; + } + + let rem = format!("{rem}"); + result += rem; + if result[result.len() - 2] == '.' { + result += "0"; + } + + result = ByteArrayTrait::concat(@ByteArrayTrait::concat(@op, @result), @cl); + + let mut extra = ""; + if result.len() < 13 { + let diff = 13 - result.len(); + let mut i = 0; + while i < diff { + extra += " "; + i += 1; + } + } + + formatted_change = ByteArrayTrait::concat(@extra, @result); + } else { + let mut result = " $ "; + let mut i = 0; + let mut op = ""; + let mut cl = ""; + if change[i] == '-' { + result = " $ -"; + i += 1; + } + op += " "; + cl += " "; + + let mut int: u32 = 0; + while i < change.len() { + let zero_ascii = '0'; + let c = Option::Some(change[i] - zero_ascii); + + match c { + Option::Some(v) => { int = int * 10 + v.into(); }, + Option::None => { break; }, + } + i += 1; + }; + + let val = int / 100; + let mut temp = format!("{val}"); + if temp.len() > 3 { + result.append_byte(temp[0]); + let mut i = 1; + let mut sep = 0; + while i < temp.len() { + if sep == 0 { + result.append_byte('.'); + sep = 3; + } + result.append_byte(temp[i]); + i += 1; + sep -= 1; + } + } else { + result += temp; + } + + result += ","; + let mut rem = int % 100; + if int < 10 { + result += "0"; + } + + let rem = format!("{rem}"); + result += rem; + if result[result.len() - 2] == ',' { + result += "0"; + } + + result = ByteArrayTrait::concat(@ByteArrayTrait::concat(@op, @result), @cl); + + let mut extra = ""; + if result.len() < 13 { + let diff = 13 - result.len(); + let mut i = 0; + while i < diff { + extra += " "; + i += 1; + } + } + + formatted_change = ByteArrayTrait::concat(@extra, @result); + } + } else { + if locale == Locale::en_US { + // formatted_change = format_eur(change); + + let mut result = "e"; + let mut i = 0; + let mut op = ""; + let mut cl = ""; + if change[i] == '-' { + op += "("; + cl += ")"; + i += 1; + } else { + op += " "; + cl += " "; + } + let mut int: u32 = 0; + while i < change.len() { + let zero_ascii = '0'; + let c = Option::Some(change[i] - zero_ascii); + + match c { + Option::Some(v) => { int = int * 10 + v.into(); }, + Option::None => { break; }, + } + i += 1; + }; + + let val = int / 100; + let val = format!("{val}"); + result += val; + result += "."; + let rem = int % 100; + if int < 10 { + result += "0"; + } + let mut rem = format!("{rem}"); + if rem.len() < 2 { + rem += "0"; + } + result += rem; + + result = ByteArrayTrait::concat(@ByteArrayTrait::concat(@op, @result), @cl); + + let mut extra = ""; + if result.len() < 13 { + let diff = 13 - result.len(); + let mut i = 0; + while i < diff { + extra += " "; + i += 1; + } + } + + formatted_change = ByteArrayTrait::concat(@extra, @result); + } else { + // formatted_change = format_eur_nil(change); + + let mut result = "e "; + let mut i = 0; + let mut op = ""; + let mut cl = ""; + if change[i] == '-' { + op += "("; + cl += ")"; + i += 1; + } else { + op += " "; + cl += " "; + } + let mut int: u32 = 0; + while i < change.len() { + let zero_ascii = '0'; + let c = Option::Some(change[i] - zero_ascii); + + match c { + Option::Some(v) => { int = int * 10 + v.into(); }, + Option::None => { break; }, + } + i += 1; + }; + let val = int / 100; + let mut temp = format!("{val}"); + if temp.len() > 3 { + result.append_byte(temp[0]); + let mut i = 1; + let mut sep = 0; + while i < temp.len() { + if sep == 0 { + result.append_byte('.'); + sep = 3; + } + result.append_byte(temp[i]); + i += 1; + sep -= 1; + } + } else { + result += temp; + } + result += ","; + let rem = int % 100; + if int < 10 { + result += "0"; + } + let mut rem = format!("{rem}"); + if rem.len() < 2 && int >= 10 { + rem += "0"; + } + result += rem; + + result = ByteArrayTrait::concat(@ByteArrayTrait::concat(@op, @result), @cl); + let mut extra = ""; + if result.len() < 13 { + let diff = 13 - result.len(); + let mut i = 0; + while i < diff { + extra += " "; + i += 1; + } + } + + formatted_change = ByteArrayTrait::concat(@extra, @result); + } + } + + let mut row = formatted_date; + row += " | "; + if entry.description.len() > 22 { + let mut i = 0; + while i < 22 { + row.append_byte(entry.description[i]); + i += 1; + }; + row += "..."; + } else { + row += entry.description; + } + + if row.len() < 38 { + let dif = 38 - row.len(); + let mut i = 0; + while i < dif { + row += " "; + i += 1; + } + } + row += " | "; + row += formatted_change; + + ledger.append(row); + }; + + ledger +} diff --git a/exercises/practice/ledger/tests/ledger.cairo b/exercises/practice/ledger/tests/ledger.cairo new file mode 100644 index 00000000..19077be3 --- /dev/null +++ b/exercises/practice/ledger/tests/ledger.cairo @@ -0,0 +1,209 @@ +use ledger::{format_entries, Currency, Locale, Entry}; + +#[test] +fn empty_ledger() { + let currency = Currency::USD; + let locale = Locale::en_US; + let entries = array![]; + + let expected: Array = array![ + "Date | Description | Change ", + ]; + let result = format_entries(currency, locale, entries); + + assert_eq!(result, expected); +} + +#[test] +#[ignore] +fn one_entry() { + let currency = Currency::USD; + let locale = Locale::en_US; + let entries = array![ + Entry { date: "2015-01-01", description: "Buy present", amount_in_cents: -1000 }, + ]; + + let expected: Array = array![ + "Date | Description | Change ", + "01/01/2015 | Buy present | ($10.00)", + ]; + let result = format_entries(currency, locale, entries); + + assert_eq!(result, expected); +} + +#[test] +#[ignore] +fn credit_and_debit() { + let currency = Currency::USD; + let locale = Locale::en_US; + let entries = array![ + Entry { date: "2015-01-01", description: "Buy present", amount_in_cents: -1000 }, + Entry { date: "2015-01-02", description: "Get present", amount_in_cents: 1000 }, + ]; + + let expected: Array = array![ + "Date | Description | Change ", + "01/01/2015 | Buy present | ($10.00)", + "01/02/2015 | Get present | $10.00 ", + ]; + let result = format_entries(currency, locale, entries); + + assert_eq!(result, expected); +} + +#[test] +#[ignore] +fn final_order_tie_breaker_is_change() { + let currency = Currency::USD; + let locale = Locale::en_US; + + let entries = array![ + Entry { date: "2015-01-01", description: "Something", amount_in_cents: -1 }, + Entry { date: "2015-01-01", description: "Something", amount_in_cents: 0 }, + Entry { date: "2015-01-01", description: "Something", amount_in_cents: 1 }, + ]; + + let expected: Array = array![ + "Date | Description | Change ", + "01/01/2015 | Something | ($0.01)", + "01/01/2015 | Something | $0.00 ", + "01/01/2015 | Something | $0.01 ", + ]; + let result = format_entries(currency, locale, entries); + + assert_eq!(result, expected); +} + + +#[test] +#[ignore] +fn overlong_description_is_truncated() { + let currency = Currency::USD; + let locale = Locale::en_US; + let entries = array![ + Entry { + date: "2015-01-01", + description: "Freude schoner Gotterfunken", + amount_in_cents: -123456, + }, + ]; + + let expected: Array = array![ + "Date | Description | Change ", + "01/01/2015 | Freude schoner Gotterf... | ($1,234.56)", + ]; + let result = format_entries(currency, locale, entries); + + assert_eq!(result, expected); +} + +#[test] +#[ignore] +fn euros() { + let currency = Currency::EUR; + let locale = Locale::en_US; + let entries = array![ + Entry { date: "2015-01-01", description: "Buy present", amount_in_cents: -1000 }, + ]; + + let expected: Array = array![ + "Date | Description | Change ", + "01/01/2015 | Buy present | (e10.00)", + ]; + let result = format_entries(currency, locale, entries); + + assert_eq!(result, expected); +} + +#[test] +#[ignore] +fn dutch_locale() { + let currency = Currency::USD; + let locale = Locale::nl_NL; + let entries = array![ + Entry { date: "2015-03-12", description: "Buy present", amount_in_cents: 123456 }, + ]; + + let expected: Array = array![ + "Datum | Omschrijving | Verandering ", + "12-03-2015 | Buy present | $ 1.234,56 ", + ]; + let result = format_entries(currency, locale, entries); + + assert_eq!(result, expected); +} + +#[test] +#[ignore] +fn dutch_locale_and_euros() { + let currency = Currency::EUR; + let locale = Locale::nl_NL; + let entries = array![ + Entry { date: "2015-03-12", description: "Buy present", amount_in_cents: 123456 }, + ]; + let expected: Array = array![ + "Datum | Omschrijving | Verandering ", + "12-03-2015 | Buy present | e 1.234,56 ", + ]; + let result = format_entries(currency, locale, entries); + + assert_eq!(result, expected); +} + +#[test] +#[ignore] +fn dutch_negative_number_with_3_digits_before_decimal_point() { + let currency = Currency::USD; + let locale = Locale::nl_NL; + let entries = array![ + Entry { date: "2015-03-12", description: "Buy present", amount_in_cents: -12345 }, + ]; + + let expected: Array = array![ + "Datum | Omschrijving | Verandering ", + "12-03-2015 | Buy present | $ -123,45 ", + ]; + let result = format_entries(currency, locale, entries); + + assert_eq!(result, expected); +} + +#[test] +#[ignore] +fn american_negative_number_with_3_digits_before_decimal_point() { + let currency = Currency::USD; + let locale = Locale::en_US; + let entries = array![ + Entry { date: "2015-03-12", description: "Buy present", amount_in_cents: -12345 }, + ]; + + let expected: Array = array![ + "Date | Description | Change ", + "03/12/2015 | Buy present | ($123.45)", + ]; + let result = format_entries(currency, locale, entries); + + assert_eq!(result, expected); +} + + +#[test] +#[ignore] +fn multiple_entries_on_same_date_ordered_by_description() { + let currency = Currency::USD; + let locale = Locale::en_US; + let entries = array![ + Entry { date: "2015-01-01", description: "Buy present", amount_in_cents: -1000 }, + Entry { date: "2015-01-01", description: "Get present", amount_in_cents: 1000 }, + ]; + + let expected: Array = array![ + "Date | Description | Change ", + "01/01/2015 | Buy present | ($10.00)", + "01/01/2015 | Get present | $10.00 ", + ]; + let result = format_entries(currency, locale, entries); + + assert_eq!(result, expected); +}