mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-04-04 07:09:14 +00:00
Add PriceParser and UnitPrices and specs
This is in preparation for removing angular from the variant update page. Converted using https://www.codeconvert.ai/coffeescript-to-javascript-converter
This commit is contained in:
45
app/webpacker/js/services/price_parser.js
Normal file
45
app/webpacker/js/services/price_parser.js
Normal file
@@ -0,0 +1,45 @@
|
||||
export default class PriceParser {
|
||||
parse(price) {
|
||||
if (!price) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// used decimal and thousands separators from currency configuration
|
||||
const decimalSeparator = I18n.toCurrency(0.1, { precision: 1, unit: "" }).substring(1, 2);
|
||||
const thousandsSeparator = I18n.toCurrency(1000, { precision: 1, unit: "" }).substring(1, 2);
|
||||
|
||||
// Replace comma used as a decimal separator and remplace by "."
|
||||
price = this.replaceCommaByFinalPoint(price);
|
||||
|
||||
// Remove configured thousands separator if it is actually a thousands separator
|
||||
price = this.removeThousandsSeparator(price, thousandsSeparator);
|
||||
|
||||
if (decimalSeparator === ",") {
|
||||
price = price.replace(",", ".");
|
||||
}
|
||||
|
||||
price = parseFloat(price);
|
||||
|
||||
if (isNaN(price)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
replaceCommaByFinalPoint(price) {
|
||||
if (price.match(/^[0-9]*(,{1})[0-9]{1,2}$/g)) {
|
||||
return price.replace(",", ".");
|
||||
} else {
|
||||
return price;
|
||||
}
|
||||
}
|
||||
|
||||
removeThousandsSeparator(price, thousandsSeparator) {
|
||||
if (new RegExp(`^([0-9]*(${thousandsSeparator}{1})[0-9]{3}[0-9\.,]*)*$`, "g").test(price)) {
|
||||
return price.replaceAll(thousandsSeparator, "");
|
||||
} else {
|
||||
return price;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
app/webpacker/js/services/unit_prices.js
Normal file
51
app/webpacker/js/services/unit_prices.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import PriceParser from "js/services/price_parser";
|
||||
import VariantUnitManager from "js/services/variant_unit_manager";
|
||||
import localizeCurrency from "js/services/localize_currency";
|
||||
|
||||
export default class UnitPrices {
|
||||
constructor() {
|
||||
this.variantUnitManager = new VariantUnitManager();
|
||||
this.priceParser = new PriceParser();
|
||||
}
|
||||
|
||||
displayableUnitPrice(price, scale, unit_type, unit_value, variant_unit_name) {
|
||||
price = this.priceParser.parse(price);
|
||||
if (price && !isNaN(price) && unit_type && unit_value) {
|
||||
const value = localizeCurrency(
|
||||
this.price(price, scale, unit_type, unit_value, variant_unit_name),
|
||||
);
|
||||
const unit = this.unit(scale, unit_type, variant_unit_name);
|
||||
return `${value} / ${unit}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
price(price, scale, unit_type, unit_value) {
|
||||
return price / this.denominator(scale, unit_type, unit_value);
|
||||
}
|
||||
|
||||
denominator(scale, unit_type, unit_value) {
|
||||
const unit = this.unit(scale, unit_type);
|
||||
if (unit === "lb") {
|
||||
return unit_value / 453.6;
|
||||
} else if (unit === "kg") {
|
||||
return unit_value / 1000;
|
||||
} else {
|
||||
return unit_value;
|
||||
}
|
||||
}
|
||||
|
||||
unit(scale, unit_type, variant_unit_name = "") {
|
||||
if (variant_unit_name.length > 0 && unit_type === "items") {
|
||||
return variant_unit_name;
|
||||
} else if (unit_type === "items") {
|
||||
return "item";
|
||||
} else if (this.variantUnitManager.systemOfMeasurement(scale, unit_type) === "imperial") {
|
||||
return "lb";
|
||||
} else if (unit_type === "weight") {
|
||||
return "kg";
|
||||
} else if (unit_type === "volume") {
|
||||
return "L";
|
||||
}
|
||||
}
|
||||
}
|
||||
150
spec/javascripts/services/price_parser_test.js
Normal file
150
spec/javascripts/services/price_parser_test.js
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import PriceParse from "js/services/price_parser";
|
||||
|
||||
describe("PriceParser service", function () {
|
||||
let priceParser = null;
|
||||
|
||||
beforeEach(() => {
|
||||
priceParser = new PriceParse();
|
||||
});
|
||||
|
||||
describe("test internal method with Regexp", function () {
|
||||
describe("test replaceCommaByFinalPoint() method", function () {
|
||||
it("handle the default case (with two numbers after comma)", function () {
|
||||
expect(priceParser.replaceCommaByFinalPoint("1,00")).toEqual("1.00");
|
||||
});
|
||||
it("doesn't confuse with thousands separator", function () {
|
||||
expect(priceParser.replaceCommaByFinalPoint("1,000")).toEqual("1,000");
|
||||
});
|
||||
it("handle also when there is only one number after the decimal separator", function () {
|
||||
expect(priceParser.replaceCommaByFinalPoint("1,0")).toEqual("1.0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("test removeThousandsSeparator() method", function () {
|
||||
it("handle the default case", function () {
|
||||
expect(priceParser.removeThousandsSeparator("1,000", ",")).toEqual("1000");
|
||||
expect(priceParser.removeThousandsSeparator("1,000,000", ",")).toEqual("1000000");
|
||||
});
|
||||
it("handle the case with decimal separator", function () {
|
||||
expect(priceParser.removeThousandsSeparator("1,000,000.00", ",")).toEqual("1000000.00");
|
||||
});
|
||||
it("handle the case when it is actually a decimal separator (and not a thousands one)", function () {
|
||||
expect(priceParser.removeThousandsSeparator("1,00", ",")).toEqual("1,00");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("with point as decimal separator and comma as thousands separator for I18n service", function () {
|
||||
beforeAll(() => {
|
||||
const mockedToCurrency = jest.fn();
|
||||
mockedToCurrency.mockImplementation((arg) => {
|
||||
if (arg == 0.1) {
|
||||
return "0.1";
|
||||
} else if (arg == 1000) {
|
||||
return "1,000";
|
||||
}
|
||||
});
|
||||
|
||||
global.I18n = { toCurrency: mockedToCurrency };
|
||||
});
|
||||
// (jest still doesn't have aroundEach https://github.com/jestjs/jest/issues/4543 )
|
||||
afterAll(() => {
|
||||
delete global.I18n;
|
||||
});
|
||||
|
||||
it("handle point as decimal separator", function () {
|
||||
expect(priceParser.parse("1.00")).toEqual(1.0);
|
||||
});
|
||||
|
||||
it("handle point as decimal separator", function () {
|
||||
expect(priceParser.parse("1.000")).toEqual(1.0);
|
||||
});
|
||||
|
||||
it("also handle comma as decimal separator", function () {
|
||||
expect(priceParser.parse("1,0")).toEqual(1.0);
|
||||
});
|
||||
|
||||
it("also handle comma as decimal separator", function () {
|
||||
expect(priceParser.parse("1,00")).toEqual(1.0);
|
||||
});
|
||||
|
||||
it("also handle comma as decimal separator", function () {
|
||||
expect(priceParser.parse("11,00")).toEqual(11.0);
|
||||
});
|
||||
|
||||
it("handle comma as decimal separator but not confusing with thousands separator", function () {
|
||||
expect(priceParser.parse("11,000")).toEqual(11000);
|
||||
});
|
||||
|
||||
it("handle point as decimal separator and comma as thousands separator", function () {
|
||||
expect(priceParser.parse("1,000,000.00")).toEqual(1000000);
|
||||
});
|
||||
|
||||
it("handle integer number", function () {
|
||||
expect(priceParser.parse("10")).toEqual(10);
|
||||
});
|
||||
|
||||
it("handle integer number with comma as thousands separator", function () {
|
||||
expect(priceParser.parse("1,000")).toEqual(1000);
|
||||
});
|
||||
|
||||
it("handle integer number with no thousands separator", function () {
|
||||
expect(priceParser.parse("1000")).toEqual(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with comma as decimal separator and final point as thousands separator for I18n service", function () {
|
||||
beforeAll(() => {
|
||||
const mockedToCurrency = jest.fn();
|
||||
mockedToCurrency.mockImplementation((arg) => {
|
||||
if (arg == 0.1) {
|
||||
return "0,1";
|
||||
} else if (arg == 1000) {
|
||||
return "1.000";
|
||||
}
|
||||
});
|
||||
|
||||
global.I18n = { toCurrency: mockedToCurrency };
|
||||
});
|
||||
// (jest still doesn't have aroundEach https://github.com/jestjs/jest/issues/4543 )
|
||||
afterAll(() => {
|
||||
delete global.I18n;
|
||||
});
|
||||
|
||||
it("handle comma as decimal separator", function () {
|
||||
expect(priceParser.parse("1,00")).toEqual(1.0);
|
||||
});
|
||||
|
||||
it("handle comma as decimal separator with one digit after the comma", function () {
|
||||
expect(priceParser.parse("11,0")).toEqual(11.0);
|
||||
});
|
||||
|
||||
it("handle comma as decimal separator with two digit after the comma", function () {
|
||||
expect(priceParser.parse("11,00")).toEqual(11.0);
|
||||
});
|
||||
|
||||
it("handle comma as decimal separator with three digit after the comma", function () {
|
||||
expect(priceParser.parse("11,000")).toEqual(11.0);
|
||||
});
|
||||
|
||||
it("also handle point as decimal separator", function () {
|
||||
expect(priceParser.parse("1.00")).toEqual(1.0);
|
||||
});
|
||||
|
||||
it("also handle point as decimal separator with integer part with two digits", function () {
|
||||
expect(priceParser.parse("11.00")).toEqual(11.0);
|
||||
});
|
||||
|
||||
it("handle point as decimal separator and final point as thousands separator", function () {
|
||||
expect(priceParser.parse("1.000.000,00")).toEqual(1000000);
|
||||
});
|
||||
|
||||
it("handle integer number", function () {
|
||||
expect(priceParser.parse("10")).toEqual(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
170
spec/javascripts/services/unit_prices_test.js
Normal file
170
spec/javascripts/services/unit_prices_test.js
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import UnitPrices from "js/services/unit_prices";
|
||||
|
||||
describe("UnitPrices service", function () {
|
||||
let unitPrices = null;
|
||||
|
||||
beforeAll(() => {
|
||||
// Requires global var from page for VariantUnitManager
|
||||
global.ofn_available_units_sorted = {
|
||||
weight: {
|
||||
"1.0": { name: "g", system: "metric" },
|
||||
28.35: { name: "oz", system: "imperial" },
|
||||
453.6: { name: "lb", system: "imperial" },
|
||||
"1000.0": { name: "kg", system: "metric" },
|
||||
"1000000.0": { name: "T", system: "metric" },
|
||||
},
|
||||
volume: {
|
||||
0.001: { name: "mL", system: "metric" },
|
||||
"1.0": { name: "L", system: "metric" },
|
||||
"1000.0": { name: "kL", system: "metric" },
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
unitPrices = new UnitPrices();
|
||||
});
|
||||
|
||||
describe("get correct unit price duo unit/value for weight", function () {
|
||||
const unit_type = "weight";
|
||||
|
||||
it("with scale: 1", function () {
|
||||
const price = 1;
|
||||
const scale = 1;
|
||||
const unit_value = 1;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(1000);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("kg");
|
||||
});
|
||||
|
||||
it("with scale and unit_value: 1000", function () {
|
||||
const price = 1;
|
||||
const scale = 1000;
|
||||
const unit_value = 1000;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(1);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("kg");
|
||||
});
|
||||
|
||||
it("with scale: 1000 and unit_value: 2000", function () {
|
||||
const price = 1;
|
||||
const scale = 1000;
|
||||
const unit_value = 2000;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(0.5);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("kg");
|
||||
});
|
||||
|
||||
it("with price: 2", function () {
|
||||
const price = 2;
|
||||
const scale = 1;
|
||||
const unit_value = 1;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(2000);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("kg");
|
||||
});
|
||||
|
||||
it("with price: 2, scale and unit_value: 1000", function () {
|
||||
const price = 2;
|
||||
const scale = 1000;
|
||||
const unit_value = 1000;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(2);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("kg");
|
||||
});
|
||||
|
||||
it("with price: 2, scale: 1000 and unit_value: 2000", function () {
|
||||
const price = 2;
|
||||
const scale = 1000;
|
||||
const unit_value = 2000;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(1);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("kg");
|
||||
});
|
||||
|
||||
it("with price: 2, scale: 1000 and unit_value: 500", function () {
|
||||
const price = 2;
|
||||
const scale = 1000;
|
||||
const unit_value = 500;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(4);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("kg");
|
||||
});
|
||||
});
|
||||
|
||||
describe("get correct unit price duo unit/value for volume", function () {
|
||||
const unit_type = "volume";
|
||||
|
||||
it("with scale: 1", function () {
|
||||
const price = 1;
|
||||
const scale = 1;
|
||||
const unit_value = 1;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(1);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("L");
|
||||
});
|
||||
|
||||
it("with price: 2 and unit_value: 0.5", function () {
|
||||
const price = 2;
|
||||
const scale = 1;
|
||||
const unit_value = 0.5;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(4);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("L");
|
||||
});
|
||||
|
||||
it("with price: 2, scale: 0.001 and unit_value: 0.01", function () {
|
||||
const price = 2;
|
||||
const scale = 0.001;
|
||||
const unit_value = 0.01;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(200);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("L");
|
||||
});
|
||||
|
||||
it("with price: 20000, scale: 1000 and unit_value: 10000", function () {
|
||||
const price = 20000;
|
||||
const scale = 1000;
|
||||
const unit_value = 10000;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(2);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("L");
|
||||
});
|
||||
|
||||
it("with price: 2, scale: 1000 and unit_value: 10000 and variant_unit_name: box", function () {
|
||||
const price = 20000;
|
||||
const scale = 1000;
|
||||
const unit_value = 10000;
|
||||
const variant_unit_name = "Box";
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value, variant_unit_name)).toEqual(2);
|
||||
expect(unitPrices.unit(scale, unit_type, variant_unit_name)).toEqual("L");
|
||||
});
|
||||
});
|
||||
|
||||
describe("get correct unit price duo unit/value for items", function () {
|
||||
const unit_type = "items";
|
||||
const scale = null;
|
||||
|
||||
it("with price: 1 and unit_value: 1", function () {
|
||||
const price = 1;
|
||||
const unit_value = 1;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(1);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("item");
|
||||
});
|
||||
|
||||
it("with price: 1 and unit_value: 10", function () {
|
||||
const price = 1;
|
||||
const unit_value = 10;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(0.1);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("item");
|
||||
});
|
||||
|
||||
it("with price: 10 and unit_value: 1", function () {
|
||||
const price = 10;
|
||||
const unit_value = 1;
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value)).toEqual(10);
|
||||
expect(unitPrices.unit(scale, unit_type)).toEqual("item");
|
||||
});
|
||||
|
||||
it("with price: 10 and unit_value: 1 and variant_unit_name: box", function () {
|
||||
const price = 10;
|
||||
const unit_value = 1;
|
||||
const variant_unit_name = "Box";
|
||||
expect(unitPrices.price(price, scale, unit_type, unit_value, variant_unit_name)).toEqual(10);
|
||||
expect(unitPrices.unit(scale, unit_type, variant_unit_name)).toEqual("Box");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user