68 lines
3.1 KiB
JavaScript
68 lines
3.1 KiB
JavaScript
'use strict';
|
|
const assert = require('node:assert/strict');
|
|
const SS = require('../js/skin-score.js');
|
|
const UI = require('../js/report-ui.js');
|
|
let count = 0;
|
|
function check(age, firm, vita, stab, tier, adjustment = 'none') {
|
|
const r = SS.recommendProduct(age, { firm, vita, stab });
|
|
assert.equal(r.tier, tier, `age=${age}; scores=${firm}/${vita}/${stab}`);
|
|
assert.equal(r.adjustment, adjustment);
|
|
assert.equal(r.version, '2026-10-12-skin-age-v1');
|
|
if (tier) assert(Math.abs(r.tier - r.baseTier) <= 1);
|
|
count++;
|
|
}
|
|
// Explicit decision-table expectations, including fractional ages and inclusive endpoints.
|
|
for (const [age, normal, low, high] of [
|
|
[24, 1, 1, 1], [31.99, 1, 1, 1], [32, 1, 2, 1], [34.99, 1, 2, 1],
|
|
[35, 2, 2, 1], [36, 2, 2, 1], [37, 2, 2, 1], [38, 2, 2, 1], [38.01, 2, 2, 2],
|
|
[46.99, 2, 2, 2], [47, 2, 3, 2], [49.99, 2, 3, 2],
|
|
[50, 3, 3, 2], [51, 3, 3, 2], [52, 3, 3, 2], [53, 3, 3, 2], [53.01, 3, 3, 3], [80, 3, 3, 3]
|
|
]) {
|
|
check(age, 8, 8, 8, normal);
|
|
check(age, 7.99, 10, 10, low, low > normal ? 'up' : 'none');
|
|
check(age, 9, 9, 7, high, high < normal ? 'down' : 'none');
|
|
check(age, 9, 7, 9, high, high < normal ? 'down' : 'none');
|
|
}
|
|
// Thresholds are compared before display rounding, not to 1- or 2-decimal scores.
|
|
for (const age of [32, 34.99, 47, 49.99]) {
|
|
const base = age < 35 ? 1 : 2;
|
|
check(age, 7.99999, 10, 10, base + 1, 'up');
|
|
check(age, 8, 10, 10, base);
|
|
check(age, null, 10, 10, base);
|
|
}
|
|
for (const age of [35, 38, 50, 53]) {
|
|
const base = age < 50 ? 2 : 3;
|
|
check(age, 8.99999, 10, 10, base);
|
|
check(age, 9, 8.99999, 8.99999, base);
|
|
check(age, 9, null, null, base);
|
|
check(age, null, 10, 10, base);
|
|
check(age, 9, null, 9, base - 1, 'down');
|
|
check(age, 9, 9, null, base - 1, 'down');
|
|
}
|
|
check(37, 9, 7, 9, 1, 'down'); // Only dullness: the third dimension may be arbitrarily low.
|
|
check(35, 8.99, 10, 10, 2); // Firmness is necessary, other two alone do not downgrade.
|
|
check(24, null, null, null, 1);
|
|
check(40, null, null, null, 2);
|
|
for (const invalid of [null, undefined, NaN, Infinity, -Infinity, 0, -5, '37', {}]) {
|
|
check(invalid, 10, 10, 10, null, 'unavailable');
|
|
}
|
|
const raw = JSON.parse(JSON.stringify(SS.MOCK_RESULT));
|
|
raw.skin_age = { value: 24 };
|
|
for (const realAge of [24, 40, 60, undefined]) {
|
|
const vm = SS.buildViewModel(raw, SS.MOCK_SIDE, { realAge });
|
|
assert.equal(vm.essence.tier, 1); // Weak wrinkles never upgrade under32.
|
|
assert.equal(vm.recommendation.skinAge, 24);
|
|
assert(Math.abs(vm.recommendation.scores.firm - 7.69) < 1e-12);
|
|
assert.equal(UI.build(vm, raw).product.tier, 1);
|
|
assert.equal(vm.skinAgeDisplay, realAge == null ? 24 : Math.max(realAge - 5, Math.min(realAge + 5, 24))); // Display clamp does not change raw-age recommendation.
|
|
count++;
|
|
}
|
|
delete raw.skin_age;
|
|
const missing = SS.buildViewModel(raw, SS.MOCK_SIDE, { realAge: 60 });
|
|
assert.equal(missing.group, null);
|
|
assert.equal(missing.essence.tier, null);
|
|
assert.equal(UI.build(missing, raw).product, null);
|
|
assert(missing.recommendation.reason.includes('未获取有效原始肌龄'));
|
|
count++;
|
|
console.log(`PASS: ${count} recommendation cases; boundary/threshold/missing/raw-age/actual-age isolation`);
|