108 lines
3.8 KiB
JavaScript
108 lines
3.8 KiB
JavaScript
'use strict';
|
|
|
|
const { describe, it } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const {
|
|
getPayableAmount,
|
|
normalizeDiscount,
|
|
sanitizeNotes,
|
|
NOTES_MAX_LENGTH,
|
|
rialsToToman,
|
|
isPaidTransaction,
|
|
sumPaidTransactions,
|
|
remainingPayable,
|
|
formatPrice
|
|
} = require('./paymentAmount');
|
|
|
|
describe('payment amount helpers', () => {
|
|
it('returns the original amount when there is no discount', () => {
|
|
assert.equal(getPayableAmount({ amount: 1_000_000 }), 1_000_000);
|
|
assert.equal(getPayableAmount({ amount: 1_000_000, discount: 0 }), 1_000_000);
|
|
});
|
|
|
|
it('subtracts a discount from the total', () => {
|
|
assert.equal(getPayableAmount({ amount: 1_000_000, discount: 150_000 }), 850_000);
|
|
});
|
|
|
|
it('never returns a negative payable amount', () => {
|
|
assert.equal(getPayableAmount({ amount: 100, discount: 250 }), 0);
|
|
});
|
|
|
|
it('treats missing or invalid values as zero', () => {
|
|
assert.equal(getPayableAmount({}), 0);
|
|
assert.equal(getPayableAmount({ amount: 'abc', discount: 'x' }), 0);
|
|
assert.equal(normalizeDiscount(-50, 1000), 0);
|
|
assert.equal(normalizeDiscount('not-a-number', 1000), 0);
|
|
});
|
|
|
|
it('clamps discount so it cannot exceed the total', () => {
|
|
assert.equal(normalizeDiscount(2_000, 1_000), 1_000);
|
|
assert.equal(normalizeDiscount(200, 1_000), 200);
|
|
});
|
|
});
|
|
|
|
describe('formatPrice', () => {
|
|
it('formats numbers and numeric strings with decimal thousand separators', () => {
|
|
assert.equal(formatPrice(8000000), '8,000,000');
|
|
assert.equal(formatPrice('8000000'), '8,000,000');
|
|
assert.equal(formatPrice(500000), '500,000');
|
|
assert.equal(formatPrice(0), '0');
|
|
assert.equal(formatPrice(''), '');
|
|
assert.equal(formatPrice(null), '');
|
|
});
|
|
});
|
|
|
|
describe('rialsToToman', () => {
|
|
it('drops one zero so spreadsheet Rials become website Toman', () => {
|
|
assert.equal(rialsToToman(110_000_000), 11_000_000);
|
|
assert.equal(rialsToToman(5_000_000), 500_000);
|
|
assert.equal(rialsToToman(0), 0);
|
|
});
|
|
});
|
|
|
|
describe('paid vs pending transactions', () => {
|
|
it('counts only paid transactions toward the paid total', () => {
|
|
const transactions = [
|
|
{ amount: 8_000_000, status: 'paid', date: '2026-08-03' },
|
|
{ amount: 3_000_000, status: 'pending', dueDate: '2026-08-23' }
|
|
];
|
|
assert.equal(sumPaidTransactions(transactions), 8_000_000);
|
|
assert.equal(
|
|
remainingPayable({ amount: 11_000_000, discount: 0 }, transactions),
|
|
3_000_000
|
|
);
|
|
});
|
|
|
|
it('treats a 10m billed course as 1m off an 11m tuition', () => {
|
|
assert.equal(getPayableAmount({ amount: 11_000_000, discount: 1_000_000 }), 10_000_000);
|
|
});
|
|
|
|
it('treats an 8m billed course as 3m off an 11m tuition', () => {
|
|
assert.equal(getPayableAmount({ amount: 11_000_000, discount: 3_000_000 }), 8_000_000);
|
|
});
|
|
|
|
it('does not treat pending remainder rows as paid', () => {
|
|
assert.equal(isPaidTransaction({ status: 'pending', amount: 2_000_000 }), false);
|
|
assert.equal(isPaidTransaction({ status: 'paid', amount: 8_000_000 }), true);
|
|
});
|
|
|
|
it('ignores cancelled transactions in paid totals', () => {
|
|
const transactions = [
|
|
{ amount: 5_000_000, status: 'paid', date: '2026-08-03' },
|
|
{ amount: 5_000_000, status: 'cancelled' },
|
|
{ amount: 2_000_000, status: 'pending', dueDate: '2026-08-23' }
|
|
];
|
|
assert.equal(sumPaidTransactions(transactions), 5_000_000);
|
|
assert.equal(isPaidTransaction({ status: 'cancelled', amount: 5_000_000, date: '2026-08-03' }), false);
|
|
});
|
|
});
|
|
|
|
describe('sanitizeNotes', () => {
|
|
it('trims notes and caps length', () => {
|
|
assert.equal(sanitizeNotes(' hello '), 'hello');
|
|
assert.equal(sanitizeNotes(null), '');
|
|
assert.equal(sanitizeNotes(undefined), '');
|
|
assert.equal(sanitizeNotes('a'.repeat(NOTES_MAX_LENGTH + 10)).length, NOTES_MAX_LENGTH);
|
|
});
|
|
});
|