crnc
Version:
currency conversion and functionality for the web
441 lines • 20 kB
JavaScript
import { expect } from "cynic";
import { nap } from "./toolbox/nap.js";
import { cache } from "./toolbox/cache.js";
import { clone } from "./toolbox/clone.js";
import { mockPersistence } from "./mocks/mock-persistence.js";
import { makeCurrencyConverter } from "./currency-converter.js";
import { exchangeRates } from "./currency-tools/testing-tools.js";
import { mockExchangeRateDownloaders } from "./mocks/mock-download-exchange-rates.js";
const locale = "en-us";
const currencies = Object.keys(exchangeRates);
const listenForStorageChange = () => { };
export default {
"fresh startups": {
async "fresh startup can display in base currency"() {
const converter = makeCurrencyConverter({
locale,
currencies,
baseCurrency: "USD",
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
const value = 1;
const result = converter.display(value);
expect(result.value).equals(value);
},
async "fresh startup can convert USD to CAD"() {
const converter = makeCurrencyConverter({
locale,
currencies,
baseCurrency: "USD",
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
await converter.exchangeRatesDownload;
converter.setCurrencyPreference("CAD");
const value = 1;
const result = converter.display(value);
expect(result.value).not.equals(value);
expect(result.value).equals(1.5);
},
async "currency inputs are case-insensitive"() {
const converter = makeCurrencyConverter({
locale,
currencies: currencies.map(code => code === "CAD" ? "Cad" : code),
baseCurrency: "usD",
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
await converter.exchangeRatesDownload;
converter.setCurrencyPreference("cAd");
const value = 1;
const result = converter.display(value);
expect(result.value).not.equals(value);
expect(result.value).equals(1.5);
},
async "fresh startup will guess currency by locale"() {
const converter = makeCurrencyConverter({
locale: "en-gb",
currencies,
baseCurrency: "USD",
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
expect(converter.currencyPreference).equals("GBP");
},
async "don't bother downloading rates when it's just the baseCurrency"() {
const downloadCounter = mockExchangeRateDownloaders.downloadCounter();
const converter = makeCurrencyConverter({
locale,
currencies: ["USD"],
baseCurrency: "USD",
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: downloadCounter.download,
});
await converter.exchangeRatesDownload;
expect(downloadCounter.count).equals(0);
},
},
"persistence": {
async "currency preference is remembered"() {
const persistence = mockPersistence.standard();
{
const converter1 = makeCurrencyConverter({
locale,
currencies,
persistence,
baseCurrency: "USD",
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
await converter1.exchangeRatesDownload;
expect(converter1.currencyPreference).equals("USD");
converter1.setCurrencyPreference("CAD");
expect(converter1.currencyPreference).equals("CAD");
}
{
const converter2 = makeCurrencyConverter({
locale,
currencies,
persistence,
baseCurrency: "USD",
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
await converter2.exchangeRatesDownload;
expect(converter2.currencyPreference).equals("CAD");
}
},
async "change to currency preference in other tab propagates to all tabs"() {
const context = mockPersistence.multipleTabsSharingOneStorage();
const tab1 = context.makeTab();
const converter1 = makeCurrencyConverter({
locale,
currencies,
baseCurrency: "USD",
persistence: tab1.persistence,
listenForStorageChange: tab1.listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
await converter1.exchangeRatesDownload;
expect(converter1.currencyPreference).equals("USD");
converter1.setCurrencyPreference("CAD");
tab1.triggerStorageChangeOnAllOtherTabs();
expect(converter1.currencyPreference).equals("CAD");
const tab2 = context.makeTab();
const converter2 = makeCurrencyConverter({
locale,
currencies,
baseCurrency: "USD",
persistence: tab2.persistence,
listenForStorageChange: tab2.listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
await converter2.exchangeRatesDownload;
expect(converter2.currencyPreference).equals("CAD");
converter2.setCurrencyPreference("GBP");
tab2.triggerStorageChangeOnAllOtherTabs();
expect(converter2.currencyPreference).equals("GBP");
expect(converter1.currencyPreference).equals("GBP");
},
async "exchange rates are cached"() {
const persistence = mockPersistence.standard();
const downloadCounter = mockExchangeRateDownloaders.downloadCounter();
{
const converter1 = makeCurrencyConverter({
locale,
currencies,
persistence,
baseCurrency: "USD",
listenForStorageChange,
downloadExchangeRates: downloadCounter.download,
});
await converter1.exchangeRatesDownload;
converter1.setCurrencyPreference("CAD");
const value = 1;
const result = converter1.display(value);
expect(result.value).not.equals(value);
expect(result.value).equals(1.5);
}
expect(downloadCounter.count).equals(1);
{
const converter2 = makeCurrencyConverter({
locale,
currencies,
persistence,
baseCurrency: "USD",
listenForStorageChange,
downloadExchangeRates: downloadCounter.download,
});
await converter2.exchangeRatesDownload;
converter2.setCurrencyPreference("CAD");
const value = 1;
const result = converter2.display(value);
expect(result.value).not.equals(value);
expect(result.value).equals(1.5);
}
expect(downloadCounter.count).equals(1);
},
async "when exchange rates cache expires, rates are redownloaded"() {
const persistence = mockPersistence.standard();
persistence.cacheLifespan = 1;
const downloadCounter = mockExchangeRateDownloaders.downloadCounter();
{
const converter1 = makeCurrencyConverter({
locale,
currencies,
persistence,
baseCurrency: "USD",
listenForStorageChange,
downloadExchangeRates: downloadCounter.download,
});
await converter1.exchangeRatesDownload;
converter1.setCurrencyPreference("CAD");
const value = 1;
const result = converter1.display(value);
expect(result.value).not.equals(value);
expect(result.value).equals(1.5);
}
expect(downloadCounter.count).equals(1);
await nap(2);
{
const converter2 = makeCurrencyConverter({
locale,
currencies,
persistence,
baseCurrency: "USD",
listenForStorageChange,
downloadExchangeRates: downloadCounter.download,
});
await converter2.exchangeRatesDownload;
converter2.setCurrencyPreference("CAD");
const value = 1;
const result = converter2.display(value);
expect(result.value).not.equals(value);
expect(result.value).equals(1.5);
}
expect(downloadCounter.count).equals(2);
},
async "cached exchange rates with too many currencies are trimmed down"() {
const persistence = mockPersistence.standard();
{
const converter1 = makeCurrencyConverter({
locale,
baseCurrency: "USD",
currencies: ["CAD", "AUD", "GBP", "EUR", "JPY"],
persistence,
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
await converter1.exchangeRatesDownload;
expect(Object.keys(converter1.availableCurrencies).length).equals(6);
}
{
const converter2 = makeCurrencyConverter({
locale,
baseCurrency: "USD",
currencies: ["CAD"],
persistence,
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
await converter2.exchangeRatesDownload;
expect(Object.keys(converter2.availableCurrencies).length).equals(2);
}
},
async "cached exchange rates that are insufficient, are ignored, and a new download happens"() {
const persistence = mockPersistence.standard();
{
const converter1 = makeCurrencyConverter({
locale,
baseCurrency: "USD",
currencies: ["CAD"],
persistence,
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
await converter1.exchangeRatesDownload;
expect(Object.keys(converter1.availableCurrencies).length).equals(2);
}
{
const downloadCounter = mockExchangeRateDownloaders.downloadCounter();
const converter2 = makeCurrencyConverter({
locale,
baseCurrency: "USD",
currencies: ["CAD", "AUD", "GBP", "EUR", "JPY"],
persistence,
listenForStorageChange,
downloadExchangeRates: downloadCounter.download,
});
await converter2.exchangeRatesDownload;
expect(downloadCounter.count).equals(1);
expect(Object.keys(converter2.availableCurrencies).length).equals(6);
}
},
},
"fail gracefully": {
async "failed exchange rate download, results in no conversions"() {
const converter = makeCurrencyConverter({
locale,
currencies,
baseCurrency: "USD",
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.fail(),
});
await converter.exchangeRatesDownload;
expect(converter.display(1).value).equals(1);
converter.setCurrencyPreference("CAD");
expect(converter.currencyPreference).equals("CAD");
expect(converter.targetCurrency).equals("USD");
const money = converter.display(1.00);
expect(money.currency.code).equals("USD");
expect(money.value).equals(1.00);
},
async "remembering insufficient exchange rates, results in fresh download"() {
const downloadCounter = mockExchangeRateDownloaders.downloadCounter();
const persistence = mockPersistence.standard();
const ratesCache = cache({
lifespan: persistence.cacheLifespan,
storage: persistence.storage,
storageKey: persistence.storageKeys.exchangeRatesCache,
load: async () => undefined,
});
const insufficientExchangeRates = clone(exchangeRates);
delete insufficientExchangeRates.GBP;
await ratesCache.write({ exchangeRates: insufficientExchangeRates });
const converter = makeCurrencyConverter({
locale,
currencies: [...currencies],
baseCurrency: "USD",
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: downloadCounter.download,
});
await converter.exchangeRatesDownload;
expect(downloadCounter.count).equals(1);
expect(converter.snap.state.exchangeRates).defined();
expect(converter.display(1).value).equals(1);
},
async "if fresh rates are downloaded, but insufficient, ignore the rates"() {
const insufficientExchangeRates = clone(exchangeRates);
delete insufficientExchangeRates.GBP;
const converter = makeCurrencyConverter({
locale,
currencies,
baseCurrency: "USD",
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders
.useTheseRates(insufficientExchangeRates),
});
await converter.exchangeRatesDownload;
expect(converter.snap.state.exchangeRates).not.defined();
expect(converter.display(1).value).equals(1);
},
async "setting an unknown currency preference, falls back on baseCurrency"() {
const converter = makeCurrencyConverter({
locale,
currencies,
baseCurrency: "USD",
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
await converter.exchangeRatesDownload;
converter.setCurrencyPreference("LOL");
const value = 1;
const result = converter.display(value);
expect(converter.targetCurrency).equals("USD");
expect(result.value).equals(value);
expect(result.currency.code).equals("USD");
},
async "remembering an unknown currency preference, falls back on baseCurrency"() {
const persistence = mockPersistence.standard();
persistence.storage.setItem(persistence.storageKeys.currencyPreference, "LOL");
const converter = makeCurrencyConverter({
locale,
currencies,
persistence,
baseCurrency: "USD",
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
await converter.exchangeRatesDownload;
expect(converter.currencyPreference).equals("USD");
const value = 1;
const result = converter.display(value);
expect(result.value).equals(value);
},
async "setting currency preference without rates, falls back on baseCurrency"() {
const converter = makeCurrencyConverter({
locale,
baseCurrency: "USD",
currencies: ["CAD"],
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.useTheseRates({
USD: 1,
}),
});
await converter.exchangeRatesDownload;
converter.setCurrencyPreference("CAD");
expect(converter.targetCurrency).equals(converter.baseCurrency);
},
async "remembering currency preference without rates, falls back on baseCurrency"() {
const persistence = mockPersistence.standard();
persistence.storage.setItem(persistence.storageKeys.currencyPreference, "CAD");
const converter = makeCurrencyConverter({
locale,
persistence,
currencies: ["USD", "CAD"],
baseCurrency: "USD",
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.fail(),
});
await converter.exchangeRatesDownload;
expect(converter.targetCurrency).equals(converter.baseCurrency);
},
async "specifying to display an unavailable currency, falls back on baseCurrency"() {
const converter = makeCurrencyConverter({
locale,
baseCurrency: "USD",
currencies: ["USD", "CAD"],
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
});
await converter.exchangeRatesDownload;
const money = converter.display(1234.56, { currency: "ROFL" });
expect(money.currency.code).equals(converter.baseCurrency);
},
},
"fail hard": {
async "unknown baseCurrency throws an error"() {
await expect(async () => makeCurrencyConverter({
locale,
currencies,
baseCurrency: "LOL",
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
})).throws();
},
async "unsupported currency throws an error"() {
expect(() => makeCurrencyConverter({
locale,
currencies: [...currencies, "LOL"],
baseCurrency: "USD",
persistence: mockPersistence.standard(),
listenForStorageChange,
downloadExchangeRates: mockExchangeRateDownloaders.success(),
})).throws();
},
},
};
//# sourceMappingURL=currency-converter.test.js.map