takt/src/__tests__/i18n.test.ts
nrs 680f0a6df5
github-issue-237-fix-ctrl-c-provider-graceful-timeout-force (#253)
* dist-tag 検証をリトライ付きに変更(npm レジストリの結果整合性対策)

* takt run 実行時に蓋閉じスリープを抑制

* takt: github-issue-237-fix-ctrl-c-provider-graceful-timeout-force
2026-02-12 11:52:07 +09:00

143 lines
5.1 KiB
TypeScript

/**
* Tests for UI label loader utility (src/shared/i18n/index.ts)
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { getLabel, getLabelObject, _resetLabelCache } from '../shared/i18n/index.js';
beforeEach(() => {
_resetLabelCache();
});
describe('getLabel', () => {
it('returns a label by key (defaults to en)', () => {
const result = getLabel('interactive.ui.intro');
expect(result).toContain('Interactive mode');
});
it('returns an English label when lang is "en"', () => {
const result = getLabel('interactive.ui.intro', 'en');
expect(result).toContain('Interactive mode');
});
it('returns a Japanese label when lang is "ja"', () => {
const result = getLabel('interactive.ui.intro', 'ja');
expect(result).toContain('対話モード');
});
it('throws for a non-existent key', () => {
expect(() => getLabel('nonexistent.key')).toThrow('Label key not found: nonexistent.key');
});
it('throws for a non-existent key with language', () => {
expect(() => getLabel('nonexistent.key', 'en')).toThrow('Label key not found: nonexistent.key (lang: en)');
});
describe('template variable substitution', () => {
it('replaces {variableName} placeholders with provided values', () => {
const result = getLabel('piece.iterationLimit.maxReached', undefined, {
currentIteration: '5',
maxMovements: '10',
});
expect(result).toContain('(5/10)');
});
it('replaces single variable', () => {
const result = getLabel('piece.notifyComplete', undefined, {
iteration: '3',
});
expect(result).toContain('3 iterations');
});
it('leaves unmatched placeholders as-is', () => {
const result = getLabel('piece.notifyAbort', undefined, {});
expect(result).toContain('{reason}');
});
});
});
describe('getLabelObject', () => {
it('returns interactive UI text object', () => {
const result = getLabelObject<{ intro: string }>('interactive.ui', 'en');
expect(result.intro).toContain('Interactive mode');
});
it('returns Japanese interactive UI text object', () => {
const result = getLabelObject<{ intro: string }>('interactive.ui', 'ja');
expect(result.intro).toContain('対話モード');
});
it('throws for a non-existent key', () => {
expect(() => getLabelObject('nonexistent.key')).toThrow('Label key not found: nonexistent.key');
});
});
describe('caching', () => {
it('returns the same data on repeated calls', () => {
const first = getLabel('interactive.ui.intro');
const second = getLabel('interactive.ui.intro');
expect(first).toBe(second);
});
it('reloads after cache reset', () => {
const first = getLabel('interactive.ui.intro');
_resetLabelCache();
const second = getLabel('interactive.ui.intro');
expect(first).toBe(second);
});
});
describe('label integrity', () => {
it('contains all expected interactive UI keys in en', () => {
const ui = getLabelObject<Record<string, string>>('interactive.ui', 'en');
expect(ui).toHaveProperty('intro');
expect(ui).toHaveProperty('resume');
expect(ui).toHaveProperty('noConversation');
expect(ui).toHaveProperty('summarizeFailed');
expect(ui).toHaveProperty('continuePrompt');
expect(ui).toHaveProperty('proposed');
expect(ui).toHaveProperty('actionPrompt');
expect(ui).toHaveProperty('actions');
expect(ui).toHaveProperty('cancelled');
});
it('contains all expected piece keys in en', () => {
expect(() => getLabel('piece.iterationLimit.maxReached')).not.toThrow();
expect(() => getLabel('piece.iterationLimit.currentMovement')).not.toThrow();
expect(() => getLabel('piece.iterationLimit.continueQuestion')).not.toThrow();
expect(() => getLabel('piece.iterationLimit.continueLabel')).not.toThrow();
expect(() => getLabel('piece.iterationLimit.continueDescription')).not.toThrow();
expect(() => getLabel('piece.iterationLimit.stopLabel')).not.toThrow();
expect(() => getLabel('piece.iterationLimit.inputPrompt')).not.toThrow();
expect(() => getLabel('piece.iterationLimit.invalidInput')).not.toThrow();
expect(() => getLabel('piece.iterationLimit.userInputPrompt')).not.toThrow();
expect(() => getLabel('piece.notifyComplete')).not.toThrow();
expect(() => getLabel('piece.notifyAbort')).not.toThrow();
expect(() => getLabel('piece.sigintGraceful')).not.toThrow();
expect(() => getLabel('piece.sigintTimeout')).not.toThrow();
expect(() => getLabel('piece.sigintForce')).not.toThrow();
});
it('en and ja have the same key structure', () => {
const stringKeys = [
'interactive.ui.intro',
'interactive.ui.cancelled',
'piece.iterationLimit.maxReached',
'piece.notifyComplete',
'piece.sigintGraceful',
];
for (const key of stringKeys) {
expect(() => getLabel(key, 'en')).not.toThrow();
expect(() => getLabel(key, 'ja')).not.toThrow();
}
const objectKeys = [
'interactive.ui',
];
for (const key of objectKeys) {
expect(() => getLabelObject(key, 'en')).not.toThrow();
expect(() => getLabelObject(key, 'ja')).not.toThrow();
}
});
});