209 lines
10 KiB
JavaScript
209 lines
10 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const vm = require('vm');
|
|
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
const read = (relativePath) => fs.readFileSync(path.join(ROOT, relativePath), 'utf8');
|
|
|
|
const precisionConfigSrc = read('App_Data/Config/swappriceprecision.js');
|
|
const precisionHelperSrc = read('wwwroot/Scripts/app/swaptrade/swapPricePrecisionHelper.js');
|
|
|
|
function loadConfiguredPrecision() {
|
|
const context = { window: {} };
|
|
vm.runInNewContext(precisionConfigSrc, context);
|
|
return context.window.main.swapPricePrecision;
|
|
}
|
|
|
|
function loadHelper(config) {
|
|
const context = { window: { main: { swapPricePrecision: config } } };
|
|
vm.runInNewContext(precisionHelperSrc, context);
|
|
return context.swapPricePrecision;
|
|
}
|
|
|
|
function activeViewSource(source) {
|
|
return source.replace(/@\*[\s\S]*?\*@/g, '');
|
|
}
|
|
|
|
describe('swap price precision common wiring', () => {
|
|
test('configured and fallback common precision expose 2/2/4', () => {
|
|
const configured = loadConfiguredPrecision();
|
|
expect(configured.common).toEqual({
|
|
amount: { precision: 2, grouping: true },
|
|
quantity: { integerDigits: 8, precision: 2, grouping: true },
|
|
rate: { precision: 4 }
|
|
});
|
|
|
|
const helper = loadHelper(configured);
|
|
expect(helper.getCommonPrecision('amount')).toBe(2);
|
|
expect(helper.getCommonPrecision('quantity')).toBe(2);
|
|
expect(helper.getCommonPrecision('rate')).toBe(4);
|
|
|
|
const expectedQuantityPrecisions = {
|
|
Stock: 2,
|
|
StockIndex: 2,
|
|
StockIF: 2,
|
|
CommodityFutures: 2,
|
|
CommoditySpot: 2,
|
|
NewOtcStock: 2,
|
|
HKStock: 2,
|
|
HKStockIndex: 2,
|
|
Fund: 4,
|
|
Bond: 0,
|
|
TBonds: 0,
|
|
CreditBonds: 0,
|
|
OtherBonds: 0,
|
|
TBFutures: 2,
|
|
OtherFutures: 2,
|
|
GoldSpot: 2,
|
|
OtherSpot: 2,
|
|
AbroadFutures: 2,
|
|
AbroadSpot: 2,
|
|
AbroadStock: 2,
|
|
AbroadStockIndex: 2,
|
|
ExRate: 4,
|
|
Shibor: 2,
|
|
FixingRepoRate: 2,
|
|
RateYield: 2,
|
|
BondIndex: 2
|
|
};
|
|
Object.entries(expectedQuantityPrecisions).forEach(([instrumentType, precision]) => {
|
|
expect(configured[instrumentType].quantityPrecision).toBe(precision);
|
|
expect(helper.getCommonPrecision('quantity', instrumentType)).toBe(precision);
|
|
expect(helper.getCommonInputFormat('quantity', { append: '' }, instrumentType).precision).toBe(precision);
|
|
});
|
|
const expectedQuantityIntegerDigits = {
|
|
Stock: 8, StockIndex: 8, StockIF: 8, CommodityFutures: 8, CommoditySpot: 8,
|
|
NewOtcStock: 8, HKStock: 8, HKStockIndex: 8, Fund: 8, TBFutures: 8,
|
|
OtherFutures: 8, GoldFutures: 8, GoldSpot: 8, OtherSpot: 8, AbroadFutures: 8,
|
|
AbroadSpot: 8, AbroadStock: 8, AbroadStockIndex: 8, Shibor: 8,
|
|
FixingRepoRate: 8, RateYield: 8, BondIndex: 8,
|
|
Bond: 12, TBonds: 12, CreditBonds: 12, OtherBonds: 12, ExRate: 8
|
|
};
|
|
Object.entries(expectedQuantityIntegerDigits).forEach(([instrumentType, integerDigits]) => {
|
|
expect(configured[instrumentType].quantityIntegerDigits).toBe(integerDigits);
|
|
expect(helper.getCommonInputFormat('quantity', { append: '' }, instrumentType).integerDigits).toBe(integerDigits);
|
|
});
|
|
expect(helper.getCommonPrecision('quantity', 'OtherRate')).toBe(2);
|
|
expect(helper.getCommonInputFormat('quantity', { append: '' }, 'Fund').precision).toBe(4);
|
|
expect(helper.getCommonInputFormat('quantity', { append: '' }, 'Fund').stringMode).toBe(true);
|
|
expect(helper.getCommonInputFormat('quantity', { append: '' }, 'Bond').precision).toBe(0);
|
|
expect(helper.getCommonInputFormat('quantity', { append: '' }, 'OtherRate').integerDigits).toBe(8);
|
|
|
|
const input = helper.getCommonInputFormat('amount', { append: '', trimTailZeros: true });
|
|
expect(input).toEqual(expect.objectContaining({ precision: 2, grouping: true, trimTailZeros: false }));
|
|
expect(helper.getCommonInputFormat('rate', { append: '%' })).toEqual(
|
|
expect.objectContaining({ precision: 4, grouping: false }));
|
|
});
|
|
|
|
test('invalid common precision falls back and formatted values keep trailing zeros', () => {
|
|
const helper = loadHelper({
|
|
common: {
|
|
amount: { precision: -1 },
|
|
quantity: { precision: 14 },
|
|
rate: { precision: 'invalid' }
|
|
}
|
|
});
|
|
expect(helper.getCommonPrecision('amount')).toBe(2);
|
|
expect(helper.getCommonPrecision('quantity')).toBe(2);
|
|
expect(helper.getCommonPrecision('rate')).toBe(4);
|
|
|
|
const configured = loadHelper({
|
|
common: {
|
|
amount: { precision: 3 },
|
|
quantity: { precision: 1 },
|
|
rate: { precision: 2 }
|
|
}
|
|
});
|
|
expect(configured.formatCommon('amount', '12.5')).toBe('12.500');
|
|
expect(configured.formatCommon('quantity', 1)).toBe('1.0');
|
|
expect(configured.formatCommon('rate', '0.0123')).toBe('1.23%');
|
|
expect(configured.formatCommon('amount', '1234.5')).toBe('1,234.500');
|
|
expect(configured.normalizeCommon('amount', '1234.5')).toBe('1234.500');
|
|
expect(configured.formatCommon('amount', NaN)).toBe('');
|
|
expect(configured.formatCommon('amount', Infinity)).toBe('');
|
|
});
|
|
|
|
test('common grouping can be disabled independently from precision', () => {
|
|
const helper = loadHelper({
|
|
common: {
|
|
amount: { precision: 2, grouping: false },
|
|
quantity: { precision: 2, grouping: false },
|
|
rate: { precision: 4, grouping: false }
|
|
}
|
|
});
|
|
expect(helper.formatCommon('amount', '1234.5')).toBe('1234.50');
|
|
expect(helper.getCommonInputFormat('quantity', { append: '' }).grouping).toBe(false);
|
|
});
|
|
|
|
test('quantity display can trim trailing zeros without changing configured precision', () => {
|
|
const helper = loadHelper({ common: { quantity: { precision: 8, grouping: true } } });
|
|
expect(helper.formatCommon('quantity', '10000.12345600', { trimTailZeros: true }))
|
|
.toBe('10,000.123456');
|
|
expect(helper.formatCommon('quantity', '10000.12345600')).toBe('10,000.12345600');
|
|
});
|
|
|
|
test('missing or invalid asset quantity precision falls back to common quantity precision', () => {
|
|
const helper = loadHelper({
|
|
common: { quantity: { precision: 3, grouping: true } },
|
|
Stock: { quantityPrecision: 'invalid' },
|
|
Bond: {}
|
|
});
|
|
expect(helper.getCommonPrecision('quantity', 'Stock')).toBe(3);
|
|
expect(helper.getCommonPrecision('quantity', 'Bond')).toBe(3);
|
|
expect(helper.getCommonPrecision('quantity', 'OtherRate')).toBe(3);
|
|
expect(helper.getCommonInputFormat('quantity', { append: '' }, 'Stock').integerDigits).toBe(8);
|
|
expect(loadHelper({
|
|
common: { quantity: { integerDigits: 0, precision: 3 } },
|
|
Stock: { quantityIntegerDigits: 'invalid' }
|
|
}).getCommonInputFormat('quantity', { append: '' }, 'Stock').integerDigits).toBe(8);
|
|
});
|
|
|
|
test('SwapTrade2 common fields use the price helper without changing price responsibilities', () => {
|
|
const scripts = {
|
|
edit: read('wwwroot/Scripts/app/swaptrade/swapTradeEdit.js'),
|
|
income: read('wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js'),
|
|
unwind: read('wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js'),
|
|
view: read('wwwroot/Scripts/app/swaptrade/swapTradeView.js'),
|
|
flow: read('wwwroot/Scripts/app/swaptrade/SwapflowList.js'),
|
|
helper: precisionHelperSrc
|
|
};
|
|
const view = read('Views/SwapTrade2/TradeView.cshtml');
|
|
const activeView = activeViewSource(view);
|
|
|
|
for (const script of Object.values(scripts)) {
|
|
expect(script).not.toMatch(/otcformat\.trading\.swap(?:Amount|Quantity|RateP)/);
|
|
}
|
|
expect(scripts.edit).toMatch(/getCommonInputFormat\('amount'/);
|
|
expect(scripts.edit).toMatch(/getCommonInputFormat\(\s*['"]quantity['"]/);
|
|
expect(scripts.edit).toMatch(/getCommonInputFormat\('rate'/);
|
|
expect(scripts.view).toContain("swapPricePrecision.formatCommon('rate', item.Rate)");
|
|
expect(scripts.flow).toMatch(/name:\s*'ytm'[\s\S]*formatter:\s*otcformat\.trading\.marginRateP/);
|
|
expect(scripts.flow).not.toMatch(/name:\s*'ytm'[\s\S]*formatCommon\('rate'/);
|
|
|
|
expect(scripts.unwind).toContain('ClosePercent = otcformat.fixed6');
|
|
expect(scripts.unwind).toContain('_.round(tradingFee, 2)');
|
|
expect(scripts.unwind).toContain('.toFixed(2)');
|
|
expect(scripts.edit).toContain('observationRatePrecision = 12');
|
|
expect(scripts.edit).toMatch(/roundObservationRate[\s\S]*swapPricePrecision\.roundDecimal/);
|
|
expect(scripts.helper).toContain('getRule: getRule');
|
|
expect(scripts.helper).toContain('getInputFormat: function');
|
|
expect(scripts.helper).toContain('roundForSubmit: function');
|
|
expect(scripts.helper).toContain('normalizeCommon: function');
|
|
|
|
expect(activeView).not.toContain('OtcFormatHelper');
|
|
expect(activeView).not.toMatch(/OtcFormatMoney|OtcFormatNotional/);
|
|
expect(activeView).toContain('SwapCommonData');
|
|
expect(activeView).toMatch(/js-swap-common[\s\S]*data-kind="amount"/);
|
|
expect(activeView).toMatch(/js-swap-common[\s\S]*data-kind="quantity"/);
|
|
expect(activeView).toMatch(/js-swap-common[\s\S]*data-kind="rate"/);
|
|
expect(scripts.view).toContain('formatSwapCommonElements');
|
|
expect(scripts.edit).toContain('getQuantityInputFormat');
|
|
expect(scripts.unwind).toContain('getQuantityInputFormat');
|
|
expect(scripts.flow).toContain('getQuantityInputFormat');
|
|
expect(scripts.flow).toMatch(/formatSwapQuantity = function \(value, options, rowObject\)/);
|
|
expect(scripts.flow).toMatch(/row\.UnderlyingInstrumentType[\s\S]*row\.InstrumentType[\s\S]*row\.position[\s\S]*UnderlyingInstrumentType/);
|
|
expect(scripts.flow).toContain("formatCommon('quantity', value, instrumentType)");
|
|
expect(activeView).toMatch(/data-kind="quantity"[^>]*data-instrument-type=/);
|
|
});
|
|
});
|