Fix translation fallbacks, start menu lifecycle, add user last name, and verifier certificate print

This commit is contained in:
2026-08-24 08:52:50 -07:00
parent 7a6c840988
commit eafcde8143
9 changed files with 431 additions and 117 deletions
+19 -8
View File
@@ -90,6 +90,16 @@ async function runAllTests() {
assert(res.includes('NexaCore'));
});
await test('Default embedded English catalog contains all cert.* keys without missing labels', async () => {
const rawI18n = new I18nService({ defaultLocale: 'en' });
const certTitle = rawI18n.t('cert.title');
const compTitle = rawI18n.t('cert.competencyTitle');
const env = rawI18n.t('cert.environment');
assert(!certTitle.startsWith('[missing:'));
assert(!compTitle.startsWith('[missing:'));
assert(!env.startsWith('[missing:'));
});
console.log('\n--- 2. Corporate Branding Tests ---');
const customConfig = {
organization: {
@@ -117,25 +127,26 @@ async function runAllTests() {
});
console.log('\n--- 3. Personalization & Safe Template Interpolation Tests ---');
await test('ActionDispatcher resolves {{learner.firstName}} and {{learner.name}}', async () => {
await test('ActionDispatcher resolves {{learner.firstName}}, {{learner.lastName}}, and {{learner.name}}', async () => {
const mockScenario = {
learner: { firstName: 'Alex', name: 'Alex Rivera', role: 'Specialist' },
learner: { firstName: 'Chris', lastName: 'Morgan', name: 'Chris Morgan', role: 'Specialist' },
organizations: [{ name: 'NexaCore' }]
};
const dispatcher = new ActionDispatcher({ scenario: mockScenario });
const ctx = dispatcher._buildInterpolationContext();
assert.equal(ctx['learner.firstName'], 'Alex');
assert.equal(ctx['learner.name'], 'Alex Rivera');
assert.equal(ctx['learner.firstName'], 'Chris');
assert.equal(ctx['learner.lastName'], 'Morgan');
assert.equal(ctx['learner.name'], 'Chris Morgan');
const tmpl = 'Hello {{learner.firstName}}, your full name is {{learner.name}}.';
const tmpl = 'Hello {{learner.firstName}} {{learner.lastName}}, full: {{learner.name}}.';
const interpolated = dispatcher.interpolate(tmpl, ctx);
assert.equal(interpolated, 'Hello Alex, your full name is Alex Rivera.');
assert.equal(interpolated, 'Hello Chris Morgan, full: Chris Morgan.');
});
await test('Malicious HTML entered as first name is escaped harmlessly', async () => {
await test('Malicious HTML entered as first/last name is escaped harmlessly', async () => {
const xssPayload = '<script>alert("pwned")</script><img src=x onerror=alert(1)>';
const mockScenario = {
learner: { firstName: xssPayload, name: xssPayload },
learner: { firstName: xssPayload, lastName: xssPayload, name: xssPayload },
organizations: [{ name: 'NexaCore' }]
};
const dispatcher = new ActionDispatcher({ scenario: mockScenario });