@cemalidev/trate 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,390 @@
1
+ import { program } from 'commander';
2
+ import { configService } from './config/config';
3
+ import {
4
+ convert,
5
+ getFiatList,
6
+ getCryptoList,
7
+ getMetalList,
8
+ getJewelryList,
9
+ getDisplayName,
10
+ isMetal,
11
+ isJewelry,
12
+ cacheApi,
13
+ } from './convert';
14
+ import { colors, divider } from './ui';
15
+ import { getLogo } from './logo';
16
+ import { displayFavoritesDashboard } from './dashboard';
17
+ import { t, setLocale, getSupportedLocales, getLocaleDisplayName, Locale } from './i18n';
18
+ import { logger } from './utils/logger';
19
+
20
+ const STATUS_WIDTH = 55;
21
+
22
+ const getSupportedCurrencies = () => {
23
+ const fiat = getFiatList().join(', ');
24
+ const crypto = getCryptoList().join(', ');
25
+ const metals = getMetalList().join(', ');
26
+ return { fiat, crypto, metals };
27
+ };
28
+
29
+ const helpText = async () => {
30
+ const config = configService.getAll();
31
+ const { crypto, metals } = getSupportedCurrencies();
32
+
33
+ let statusLine = `${colors.yellow(t('ui.base') + ':')} ${colors.cyan(config.baseCurrency)}`;
34
+
35
+ try {
36
+ const usdResult = await convert(1, 'USD', config.baseCurrency);
37
+ if (usdResult.success) {
38
+ statusLine += ` | 1 USD = ${colors.green(usdResult.result!.toFixed(2))} ${config.baseCurrency}`;
39
+ }
40
+ } catch {
41
+ statusLine += ` | ${colors.dim(t('ui.loading'))}`;
42
+ }
43
+
44
+ return `
45
+ ${getLogo()}
46
+
47
+ ${colors.dim(t('app.tagline'))}
48
+
49
+ ${colors.bold(t('help.usage') + ':')}
50
+ ${colors.cyan('trate <amount> <from> <to>')} ${t('help.examples')} (${colors.dim('trate 100 usd try')})
51
+ ${colors.cyan('trate <currency>')} ${t('dashboard.usage')} (${colors.dim('trate btc')})
52
+
53
+ ${colors.bold(t('help.examples') + ':')}
54
+ ${colors.dim('$')} ${colors.yellow('trate 100 usd')} 100 USD → ${config.baseCurrency}
55
+ ${colors.dim('$')} ${colors.yellow('trate btc')} BTC → ${config.baseCurrency}
56
+ ${colors.dim('$')} ${colors.yellow('trate ceyrek_altin')} Çeyrek Altın → ${config.baseCurrency}
57
+
58
+ ${colors.bold(t('help.crypto') + ':')} ${colors.dim(crypto)}
59
+
60
+ ${colors.bold(t('help.metals') + ':')} ${colors.yellow(metals)}
61
+
62
+ ${colors.bold(t('help.jewelry') + ':')}
63
+ ${colors.yellow('ceyrek_altin')} ${colors.yellow('yarim_altin')} ${colors.yellow('tam_altin')} ${colors.yellow('ata_altin')}
64
+
65
+ ${colors.bold(t('help.config') + ':')}
66
+ ${colors.yellow('set-base')} | ${colors.yellow('list')} | ${colors.yellow('add')} | ${colors.yellow('remove')} | ${colors.yellow('set-lang')} | ${colors.yellow('alias')} | ${colors.yellow('help')}
67
+
68
+ ${colors.bold(t('help.alias') + ':')}
69
+ ${colors.dim('$')} ${colors.cyan('trate alias add ceyrek CEYREK_ALTIN')} ${t('help.aliasCreate')}
70
+ ${colors.dim('$')} ${colors.cyan('trate alias list')} ${t('help.aliasView')}
71
+
72
+ ${divider(STATUS_WIDTH)}
73
+ ${statusLine}
74
+ `;
75
+ };
76
+
77
+ const startScreen = () => {
78
+ const config = configService.getAll();
79
+ return `
80
+ ${getLogo()}
81
+
82
+ ${colors.dim(t('app.tagline'))}
83
+
84
+ ${colors.dim('$')} ${colors.cyan('trate 100 usd')} 100 USD → ${config.baseCurrency}
85
+ ${colors.dim('$')} ${colors.cyan('trate btc')} BTC → ${config.baseCurrency}
86
+ ${colors.dim('$')} ${colors.cyan('trate ceyrek_altin')} Çeyrek Altın
87
+ ${colors.dim('$')} ${colors.cyan('trate list')} ${t('dashboard.favorites')}
88
+ ${colors.dim('$')} ${colors.cyan('trate alias list')} View aliases
89
+ ${colors.dim('$')} ${colors.cyan('trate help')} ${t('commands.help')}
90
+ `;
91
+ };
92
+
93
+ const validateCurrency = (symbol: string): boolean => {
94
+ const fiatList = getFiatList().map(s => s.toUpperCase());
95
+ const cryptoList = getCryptoList().map(s => s.toUpperCase());
96
+ const metalList = getMetalList().map(s => s.toUpperCase());
97
+ const jewelryList = getJewelryList().map(s => s.toUpperCase());
98
+ const upperSymbol = symbol.toUpperCase();
99
+ const lowerSymbol = symbol.toLowerCase();
100
+ const aliases = configService.aliases;
101
+ return (
102
+ fiatList.includes(upperSymbol) ||
103
+ cryptoList.includes(upperSymbol) ||
104
+ metalList.includes(upperSymbol) ||
105
+ jewelryList.includes(upperSymbol) ||
106
+ lowerSymbol in aliases ||
107
+ Object.values(aliases).includes(upperSymbol)
108
+ );
109
+ };
110
+
111
+ const resolveAlias = (symbol: string): string => {
112
+ const aliases = configService.aliases;
113
+ const lowerSymbol = symbol.toLowerCase();
114
+ return aliases[lowerSymbol] || symbol.toUpperCase();
115
+ };
116
+
117
+ const convertCommand = async (args: string[]) => {
118
+ const config = configService.getAll();
119
+
120
+ let amount = 1;
121
+ let from = '';
122
+ let to = config.baseCurrency;
123
+
124
+ if (args.length === 1) {
125
+ from = resolveAlias(args[0]);
126
+ to = config.baseCurrency;
127
+ amount = 1;
128
+ } else if (args.length === 2) {
129
+ const first = args[0];
130
+ const second = args[1];
131
+
132
+ if (!isNaN(parseFloat(first))) {
133
+ amount = parseFloat(first);
134
+ from = resolveAlias(second);
135
+ to = config.baseCurrency;
136
+ } else {
137
+ from = resolveAlias(first);
138
+ to = resolveAlias(second);
139
+ }
140
+ } else if (args.length >= 3) {
141
+ amount = parseFloat(args[0]);
142
+ from = resolveAlias(args[1]);
143
+ to = resolveAlias(args[2]);
144
+ }
145
+
146
+ if (from === to) {
147
+ console.log(`\n ${colors.dim(t('ui.info') + ':')} ${t('error.sameCurrency')}`);
148
+ console.log(` ${amount} ${from} = ${amount} ${to} ${colors.dim(t('ui.noConversionNeeded'))}`);
149
+ return;
150
+ }
151
+
152
+ const result = await convert(amount, from, to);
153
+
154
+ if (result.success) {
155
+ const displayFrom = isMetal(from) || isJewelry(from) ? getDisplayName(from) : from;
156
+ const displayTo = isMetal(to) || isJewelry(to) ? getDisplayName(to) : to;
157
+ const resultStr =
158
+ result.result! < 0.01 ? result.result!.toPrecision(6) : result.result!.toLocaleString();
159
+
160
+ console.log(
161
+ `\n ${colors.bold(amount.toLocaleString())} ${displayFrom} = ${colors.green(resultStr)} ${displayTo}`
162
+ );
163
+ console.log(
164
+ ` ${colors.dim(t('ui.rate') + ':')} 1 ${from} = ${result.rate! < 0.0001 ? result.rate!.toPrecision(6) : result.rate!.toFixed(6)} ${to}`
165
+ );
166
+ console.log(
167
+ ` ${colors.dim(t('ui.base') + ':')} ${result.base} | ${colors.dim(t('ui.date') + ':')} ${result.date}`
168
+ );
169
+ } else {
170
+ console.log(`\n ${colors.red('Error:')} ${result.error}`);
171
+ }
172
+ };
173
+
174
+ const listCommand = async () => {
175
+ console.log(await displayFavoritesDashboard());
176
+ };
177
+
178
+ const moonCommand = async () => {
179
+ console.log(`\n ${colors.bold(t('moon.toTheMoon'))}\n`);
180
+ console.log(` ${colors.yellow('🚀')} ${colors.bold(t('moon.goingToMoon'))}`);
181
+ console.log(
182
+ ` ${colors.dim(t('moon.tip'))} ${colors.cyan('trate <crypto>')} ${colors.dim(t('ui.type') + ':')}`
183
+ );
184
+ console.log('');
185
+ };
186
+
187
+ function initLocale() {
188
+ const configLocale = configService.locale;
189
+ if (configLocale) {
190
+ setLocale(configLocale);
191
+ }
192
+ }
193
+
194
+ initLocale();
195
+
196
+ program.name('trate').description(t('app.tagline')).version('1.0.0');
197
+
198
+ program.argument('[args...]', 'Arguments for conversion or command').action(async args => {
199
+ if (!args || args.length === 0) {
200
+ console.log(startScreen());
201
+ return;
202
+ }
203
+
204
+ const cmd = args[0].toLowerCase();
205
+
206
+ if (cmd === 'set-base') {
207
+ const newBase = args[1]?.toUpperCase();
208
+ if (!newBase) {
209
+ console.log(` ${colors.red('Error:')} ${t('cli.specifyCurrency')}`);
210
+ console.log(` ${colors.dim(t('cli.usage') + ':')} trate set-base <currency>`);
211
+ return;
212
+ }
213
+ if (!validateCurrency(newBase)) {
214
+ console.log(` ${colors.red('Error:')} ${t('error.invalidCurrency')}: ${newBase}`);
215
+ console.log(` ${colors.dim(t('help.crypto') + ':')} ${getFiatList().join(', ')}`);
216
+ return;
217
+ }
218
+ configService.baseCurrency = newBase;
219
+ console.log(` ${colors.green('✓')} ${t('success.baseCurrencySet')}: ${newBase}`);
220
+ return;
221
+ }
222
+
223
+ if (cmd === 'set-lang') {
224
+ const newLocale = args[1]?.toLowerCase() as Locale;
225
+ if (!newLocale) {
226
+ console.log(` ${colors.red('Error:')} ${t('cli.specifyCurrency')}`);
227
+ console.log(` ${colors.dim(t('cli.usage') + ':')} trate set-lang <locale>`);
228
+ console.log(
229
+ ` ${colors.dim('Available:')} ${getSupportedLocales()
230
+ .map(l => colors.cyan(l))
231
+ .join(', ')}`
232
+ );
233
+ return;
234
+ }
235
+ if (!getSupportedLocales().includes(newLocale)) {
236
+ console.log(` ${colors.red('Error:')} ${t('error.invalidCurrency')}: ${newLocale}`);
237
+ console.log(
238
+ ` ${colors.dim('Available:')} ${getSupportedLocales()
239
+ .map(l => `${colors.cyan(l)} (${getLocaleDisplayName(l)})`)
240
+ .join(', ')}`
241
+ );
242
+ return;
243
+ }
244
+ configService.locale = newLocale;
245
+ setLocale(newLocale);
246
+ console.log(` ${colors.green('✓')} Language set to: ${getLocaleDisplayName(newLocale)}`);
247
+ return;
248
+ }
249
+
250
+ if (cmd === 'add') {
251
+ const cur = args[1]?.toUpperCase();
252
+ if (!cur) {
253
+ console.log(` ${colors.red('Error:')} ${t('cli.specifyCurrency')}`);
254
+ console.log(` ${colors.dim(t('cli.usage') + ':')} trate add <currency>`);
255
+ return;
256
+ }
257
+ if (!validateCurrency(cur)) {
258
+ console.log(` ${colors.red('Error:')} ${t('error.invalidCurrency')}: ${cur}`);
259
+ return;
260
+ }
261
+ configService.addFavorite(cur);
262
+ console.log(` ${colors.green('✓')} ${t('success.added')}: ${cur}`);
263
+ return;
264
+ }
265
+
266
+ if (cmd === 'remove') {
267
+ const cur = args[1]?.toUpperCase();
268
+ if (!cur) {
269
+ console.log(` ${colors.red('Error:')} ${t('cli.specifyCurrency')}`);
270
+ console.log(` ${colors.dim(t('cli.usage') + ':')} trate remove <currency>`);
271
+ return;
272
+ }
273
+ configService.removeFavorite(cur);
274
+ console.log(` ${colors.green('✓')} ${t('success.removed')}: ${cur}`);
275
+ return;
276
+ }
277
+
278
+ if (cmd === 'list' || cmd === 'favs') {
279
+ await listCommand();
280
+ return;
281
+ }
282
+
283
+ if (cmd === 'moon') {
284
+ await moonCommand();
285
+ return;
286
+ }
287
+
288
+ if (cmd === 'refresh') {
289
+ cacheApi.clear();
290
+ console.log(` ${colors.green('✓')} ${t('success.cacheCleared')}`);
291
+ return;
292
+ }
293
+
294
+ if (cmd === 'reset') {
295
+ configService.reset();
296
+ console.log(` ${colors.green('✓')} ${t('success.settingsReset')}`);
297
+ return;
298
+ }
299
+
300
+ if (cmd === 'help') {
301
+ console.log(await helpText());
302
+ return;
303
+ }
304
+
305
+ if (cmd === 'alias') {
306
+ const subCmd = args[1]?.toLowerCase();
307
+ const alias = args[2]?.toLowerCase();
308
+ const currency = args[3]?.toUpperCase();
309
+
310
+ if (subCmd === 'list') {
311
+ const aliases = configService.aliases;
312
+ const entries = Object.entries(aliases);
313
+ if (entries.length === 0) {
314
+ console.log(`\n ${colors.dim('No aliases defined')}`);
315
+ } else {
316
+ console.log(`\n ${colors.bold('Aliases:')}`);
317
+ for (const [key, val] of entries) {
318
+ console.log(` ${colors.yellow(key)} → ${colors.cyan(val)}`);
319
+ }
320
+ }
321
+ return;
322
+ }
323
+
324
+ if (subCmd === 'add') {
325
+ if (!alias || !currency) {
326
+ console.log(` ${colors.red('Error:')} Specify alias and currency`);
327
+ console.log(
328
+ ` ${colors.dim('Usage:')} ${colors.cyan('trate alias add <alias> <currency>')}`
329
+ );
330
+ console.log(
331
+ ` ${colors.dim('Example:')} ${colors.cyan('trate alias add ceyrek CEYREK_ALTIN')}`
332
+ );
333
+ return;
334
+ }
335
+ const resolvedCurrency = resolveAlias(currency);
336
+ if (!validateCurrency(resolvedCurrency)) {
337
+ console.log(` ${colors.red('Error:')} Invalid currency: ${resolvedCurrency}`);
338
+ return;
339
+ }
340
+ configService.addAlias(alias, resolvedCurrency);
341
+ console.log(
342
+ ` ${colors.green('✓')} Alias added: ${colors.yellow(alias)} → ${colors.cyan(resolvedCurrency)}`
343
+ );
344
+ return;
345
+ }
346
+
347
+ if (subCmd === 'remove') {
348
+ if (!alias) {
349
+ console.log(` ${colors.red('Error:')} Specify alias to remove`);
350
+ console.log(` ${colors.dim('Usage:')} ${colors.cyan('trate alias remove <alias>')}`);
351
+ return;
352
+ }
353
+ const removed = configService.removeAlias(alias);
354
+ if (removed) {
355
+ console.log(` ${colors.green('✓')} Alias removed: ${colors.yellow(alias)}`);
356
+ } else {
357
+ console.log(` ${colors.red('Error:')} Alias not found: ${colors.yellow(alias)}`);
358
+ }
359
+ return;
360
+ }
361
+
362
+ console.log(` ${colors.bold('Alias Commands:')}`);
363
+ console.log(` ${colors.cyan('trate alias list')} ${colors.dim('List all aliases')}`);
364
+ console.log(
365
+ ` ${colors.cyan('trate alias add <alias> <currency>')} ${colors.dim('Add alias')}`
366
+ );
367
+ console.log(
368
+ ` ${colors.cyan('trate alias remove <alias>')} ${colors.dim('Remove alias')}`
369
+ );
370
+ return;
371
+ }
372
+
373
+ if (validateCurrency(cmd)) {
374
+ await convertCommand(args);
375
+ } else {
376
+ await convertCommand(args.slice(1));
377
+ }
378
+ });
379
+
380
+ program.parse();
381
+
382
+ process.on('SIGINT', () => {
383
+ logger.debug('Received SIGINT, shutting down gracefully');
384
+ process.exit(0);
385
+ });
386
+
387
+ process.on('SIGTERM', () => {
388
+ logger.debug('Received SIGTERM, shutting down gracefully');
389
+ process.exit(0);
390
+ });
package/src/logo.ts ADDED
@@ -0,0 +1,33 @@
1
+ import figlet from 'figlet';
2
+
3
+ let cachedLogo: string | null = null;
4
+
5
+ export function getLogo(): string {
6
+ if (cachedLogo) return cachedLogo;
7
+
8
+ const dollarSign = figlet.textSync('$', {
9
+ font: 'Slant',
10
+ horizontalLayout: 'default',
11
+ verticalLayout: 'default',
12
+ });
13
+
14
+ const trateText = figlet.textSync('trate', {
15
+ font: 'Slant',
16
+ horizontalLayout: 'default',
17
+ verticalLayout: 'default',
18
+ });
19
+
20
+ const white = '\x1b[38;5;15m';
21
+ const reset = '\x1b[0m';
22
+
23
+ const dollarLines = dollarSign.split('\n');
24
+ const trateLines = trateText.split('\n');
25
+
26
+ const combined = dollarLines.map((line, i) => {
27
+ const trateLine = trateLines[i] || '';
28
+ return `${white}${line}${reset} ${white}${trateLine}${reset}`;
29
+ });
30
+
31
+ cachedLogo = combined.join('\n');
32
+ return cachedLogo;
33
+ }
@@ -0,0 +1,38 @@
1
+ export enum ErrorCode {
2
+ NETWORK_ERROR = 'NETWORK_ERROR',
3
+ API_ERROR = 'API_ERROR',
4
+ INVALID_CURRENCY = 'INVALID_CURRENCY',
5
+ CURRENCY_NOT_FOUND = 'CURRENCY_NOT_FOUND',
6
+ RATE_NOT_FOUND = 'RATE_NOT_FOUND',
7
+ CONFIG_ERROR = 'CONFIG_ERROR',
8
+ UNKNOWN_ERROR = 'UNKNOWN_ERROR',
9
+ }
10
+
11
+ export class TrateError extends Error {
12
+ public readonly code: ErrorCode;
13
+ public readonly originalError?: Error;
14
+
15
+ constructor(code: ErrorCode, message: string, originalError?: Error) {
16
+ super(message);
17
+ this.name = 'TrateError';
18
+ this.code = code;
19
+ this.originalError = originalError;
20
+
21
+ if (Error.captureStackTrace) {
22
+ Error.captureStackTrace(this, TrateError);
23
+ }
24
+ }
25
+
26
+ toJSON() {
27
+ return {
28
+ name: this.name,
29
+ message: this.message,
30
+ code: this.code,
31
+ stack: this.stack,
32
+ };
33
+ }
34
+ }
35
+
36
+ export function isTrateError(error: unknown): error is TrateError {
37
+ return error instanceof TrateError;
38
+ }
package/src/ui.ts ADDED
@@ -0,0 +1,40 @@
1
+ const COLORS = {
2
+ reset: '\x1b[0m',
3
+ bold: '\x1b[1m',
4
+ dim: '\x1b[2m',
5
+ cyan: '\x1b[36m',
6
+ green: '\x1b[32m',
7
+ yellow: '\x1b[33m',
8
+ red: '\x1b[31m',
9
+ magenta: '\x1b[35m',
10
+ };
11
+
12
+ export const colors = {
13
+ bold: (text: string) => `${COLORS.bold}${text}${COLORS.reset}`,
14
+ dim: (text: string) => `${COLORS.dim}${text}${COLORS.reset}`,
15
+ cyan: (text: string) => `${COLORS.cyan}${text}${COLORS.reset}`,
16
+ green: (text: string) => `${COLORS.green}${text}${COLORS.reset}`,
17
+ yellow: (text: string) => `${COLORS.yellow}${text}${COLORS.reset}`,
18
+ red: (text: string) => `${COLORS.red}${text}${COLORS.reset}`,
19
+ magenta: (text: string) => `${COLORS.magenta}${text}${COLORS.reset}`,
20
+ };
21
+
22
+ export function gradient(text: string): string {
23
+ const gradientColors = [
24
+ '\x1b[38;5;39m', // Cyan
25
+ '\x1b[38;5;45m', // Light cyan
26
+ '\x1b[38;5;50m', // Light green
27
+ '\x1b[38;5;82m', // Green
28
+ ];
29
+
30
+ let result = '';
31
+ for (let i = 0; i < text.length; i++) {
32
+ const colorIndex = Math.floor((i / text.length) * gradientColors.length);
33
+ result += `${gradientColors[Math.min(colorIndex, gradientColors.length - 1)]}${text[i]}`;
34
+ }
35
+ return `${result}${COLORS.reset}`;
36
+ }
37
+
38
+ export function divider(width = 55): string {
39
+ return colors.dim('─'.repeat(width));
40
+ }
@@ -0,0 +1,84 @@
1
+ export enum LogLevel {
2
+ DEBUG = 0,
3
+ INFO = 1,
4
+ WARN = 2,
5
+ ERROR = 3,
6
+ }
7
+
8
+ const LOG_LEVEL_NAMES: Record<LogLevel, string> = {
9
+ [LogLevel.DEBUG]: 'DEBUG',
10
+ [LogLevel.INFO]: 'INFO',
11
+ [LogLevel.WARN]: 'WARN',
12
+ [LogLevel.ERROR]: 'ERROR',
13
+ };
14
+
15
+ let currentLogLevel = LogLevel.INFO;
16
+ let isDebugMode = false;
17
+
18
+ export function setLogLevel(level: LogLevel): void {
19
+ currentLogLevel = level;
20
+ }
21
+
22
+ export function setDebugMode(enabled: boolean): void {
23
+ isDebugMode = enabled;
24
+ if (enabled && currentLogLevel > LogLevel.DEBUG) {
25
+ currentLogLevel = LogLevel.DEBUG;
26
+ }
27
+ }
28
+
29
+ export function isDebugEnabled(): boolean {
30
+ return isDebugMode;
31
+ }
32
+
33
+ function shouldLog(level: LogLevel): boolean {
34
+ return level >= currentLogLevel;
35
+ }
36
+
37
+ function formatMessage(level: LogLevel, message: string, meta?: Record<string, unknown>): string {
38
+ const timestamp = new Date().toISOString();
39
+ const levelName = LOG_LEVEL_NAMES[level];
40
+ const metaStr = meta ? ` ${JSON.stringify(meta)}` : '';
41
+ return `[${timestamp}] [${levelName}] ${message}${metaStr}`;
42
+ }
43
+
44
+ export function debug(message: string, meta?: Record<string, unknown>): void {
45
+ if (shouldLog(LogLevel.DEBUG)) {
46
+ console.debug(formatMessage(LogLevel.DEBUG, message, meta));
47
+ }
48
+ }
49
+
50
+ export function info(message: string, meta?: Record<string, unknown>): void {
51
+ if (shouldLog(LogLevel.INFO)) {
52
+ console.info(formatMessage(LogLevel.INFO, message, meta));
53
+ }
54
+ }
55
+
56
+ export function warn(message: string, meta?: Record<string, unknown>): void {
57
+ if (shouldLog(LogLevel.WARN)) {
58
+ console.warn(formatMessage(LogLevel.WARN, message, meta));
59
+ }
60
+ }
61
+
62
+ export function error(
63
+ message: string,
64
+ error?: Error | unknown,
65
+ meta?: Record<string, unknown>
66
+ ): void {
67
+ if (shouldLog(LogLevel.ERROR)) {
68
+ const errorMeta =
69
+ error instanceof Error
70
+ ? { ...meta, errorName: error.name, errorMessage: error.message }
71
+ : { ...meta, error };
72
+ console.error(formatMessage(LogLevel.ERROR, message, errorMeta));
73
+ }
74
+ }
75
+
76
+ export const logger = {
77
+ debug,
78
+ info,
79
+ warn,
80
+ error,
81
+ setLogLevel,
82
+ setDebugMode,
83
+ isDebugEnabled,
84
+ };
@@ -0,0 +1,95 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { ConfigService } from '../src/config/config';
3
+ import { resetLocale } from '../src/i18n';
4
+
5
+ describe('ConfigService', () => {
6
+ let configService: ConfigService;
7
+
8
+ beforeEach(() => {
9
+ configService = new ConfigService();
10
+ configService.reset();
11
+ resetLocale();
12
+ });
13
+
14
+ describe('baseCurrency', () => {
15
+ it('should have default base currency', () => {
16
+ expect(configService.baseCurrency).toBeDefined();
17
+ expect(typeof configService.baseCurrency).toBe('string');
18
+ });
19
+
20
+ it('should set base currency', () => {
21
+ configService.baseCurrency = 'USD';
22
+ expect(configService.baseCurrency).toBe('USD');
23
+ });
24
+
25
+ it('should uppercase the base currency', () => {
26
+ configService.baseCurrency = 'usd';
27
+ expect(configService.baseCurrency).toBe('USD');
28
+ });
29
+ });
30
+
31
+ describe('favorites', () => {
32
+ it('should have default favorites', () => {
33
+ const favorites = configService.favorites;
34
+ expect(Array.isArray(favorites)).toBe(true);
35
+ expect(favorites.length).toBeGreaterThan(0);
36
+ });
37
+
38
+ it('should add a favorite', () => {
39
+ configService.addFavorite('BTC');
40
+ expect(configService.favorites).toContain('BTC');
41
+ });
42
+
43
+ it('should not add duplicate favorites', () => {
44
+ configService.addFavorite('BTC');
45
+ configService.addFavorite('btc');
46
+ const count = configService.favorites.filter(f => f === 'BTC').length;
47
+ expect(count).toBe(1);
48
+ });
49
+
50
+ it('should remove a favorite', () => {
51
+ configService.addFavorite('BTC');
52
+ configService.removeFavorite('BTC');
53
+ expect(configService.favorites).not.toContain('BTC');
54
+ });
55
+
56
+ it('should handle case-insensitive operations', () => {
57
+ configService.addFavorite('btc');
58
+ configService.removeFavorite('BTC');
59
+ expect(configService.favorites).not.toContain('BTC');
60
+ });
61
+ });
62
+
63
+ describe('locale', () => {
64
+ it('should have a locale property', () => {
65
+ const locale = configService.locale;
66
+ expect(locale).toBeDefined();
67
+ expect(typeof locale).toBe('string');
68
+ });
69
+
70
+ it('should set locale', () => {
71
+ configService.locale = 'tr';
72
+ expect(configService.locale).toBe('tr');
73
+ });
74
+ });
75
+
76
+ describe('getAll', () => {
77
+ it('should return all config values', () => {
78
+ const config = configService.getAll();
79
+ expect(config).toHaveProperty('baseCurrency');
80
+ expect(config).toHaveProperty('favorites');
81
+ expect(config).toHaveProperty('locale');
82
+ });
83
+ });
84
+
85
+ describe('reset', () => {
86
+ it('should reset all settings', () => {
87
+ configService.baseCurrency = 'USD';
88
+ configService.addFavorite('BTC');
89
+ configService.reset();
90
+
91
+ expect(configService.baseCurrency).toBeDefined();
92
+ expect(configService.favorites).toBeDefined();
93
+ });
94
+ });
95
+ });