@heojeongbo/log-palette 0.2.0
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/dist/index.cjs +301 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +292 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types/color.d.ts +1 -0
- package/dist/types/config.d.ts +40 -0
- package/dist/types/formatter.d.ts +3 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/logger.d.ts +97 -0
- package/dist/types/registry.d.ts +64 -0
- package/dist/types/types.d.ts +68 -0
- package/package.json +67 -0
- package/src/__tests__/color.test.ts +31 -0
- package/src/__tests__/config.test.ts +41 -0
- package/src/__tests__/formatter.test.ts +82 -0
- package/src/__tests__/logger.test.ts +204 -0
- package/src/__tests__/registry.test.ts +88 -0
- package/src/color.ts +13 -0
- package/src/config.ts +58 -0
- package/src/formatter.ts +26 -0
- package/src/index.ts +5 -0
- package/src/logger.ts +169 -0
- package/src/registry.ts +93 -0
- package/src/types.ts +80 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global format configuration for log-palette.
|
|
3
|
+
* Change these settings once to affect all loggers.
|
|
4
|
+
*/
|
|
5
|
+
export interface GlobalConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Number of inner characters inside the prefix brackets.
|
|
8
|
+
* Total prefix width = innerWidth + 2 (for the `[` and `]`).
|
|
9
|
+
*
|
|
10
|
+
* - Short domains are left-padded with dots to fill this width.
|
|
11
|
+
* - Domains longer than this are truncated with `…`.
|
|
12
|
+
*
|
|
13
|
+
* @default 8 → prefix is always `[XXXXXXXX]` (10 chars total)
|
|
14
|
+
*/
|
|
15
|
+
innerWidth: number;
|
|
16
|
+
/**
|
|
17
|
+
* Whether to include a timestamp in each log line.
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
timestamp: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Read the current global configuration.
|
|
24
|
+
* Returns a snapshot — mutating the returned object has no effect.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getConfig(): Readonly<GlobalConfig>;
|
|
27
|
+
/**
|
|
28
|
+
* Update one or more global format settings.
|
|
29
|
+
* Changes apply immediately to all subsequent log calls across all loggers.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* // Wider prefix column (good when you have long domain names)
|
|
34
|
+
* configure({ innerWidth: 12 })
|
|
35
|
+
*
|
|
36
|
+
* // Strip timestamps (cleaner in environments that add their own)
|
|
37
|
+
* configure({ timestamp: false })
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function configure(options: Partial<GlobalConfig>): void;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { GlobalConfig } from './config.js';
|
|
2
|
+
export { configure, getConfig } from './config.js';
|
|
3
|
+
export { createLogger, LogPalette } from './logger.js';
|
|
4
|
+
export { disableAll, enableAll, getAllLoggers, getLogger, setGlobalLevel } from './registry.js';
|
|
5
|
+
export type { Logger, LoggerOptions, LogLevel } from './types.js';
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Logger, LoggerOptions, LogLevel } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A logger instance tied to a specific domain.
|
|
4
|
+
* Outputs colored prefix badges to the browser console.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const log = new LogPalette('auth', { color: '#7c3aed', level: 'info' })
|
|
9
|
+
* log.info('User signed in', { userId: 42 })
|
|
10
|
+
* // → [....auth] 05:35:41.039 User signed in { userId: 42 }
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare class LogPalette implements Logger {
|
|
14
|
+
readonly domain: string;
|
|
15
|
+
readonly color: string;
|
|
16
|
+
private _minLevel;
|
|
17
|
+
private _enabled;
|
|
18
|
+
/**
|
|
19
|
+
* @param domain Service/feature name shown in the prefix badge (e.g. 'auth', 'api').
|
|
20
|
+
* @param options Optional configuration for color, level filtering, and enabled state.
|
|
21
|
+
*/
|
|
22
|
+
constructor(domain: string, options?: LoggerOptions);
|
|
23
|
+
/**
|
|
24
|
+
* Dynamically change the minimum log level for this logger.
|
|
25
|
+
* Useful for adjusting verbosity at runtime without recreating the logger.
|
|
26
|
+
*/
|
|
27
|
+
setLevel(level: LogLevel): void;
|
|
28
|
+
/**
|
|
29
|
+
* Enable or disable this logger at runtime.
|
|
30
|
+
* When disabled, all log calls are silently dropped.
|
|
31
|
+
*/
|
|
32
|
+
setEnabled(enabled: boolean): void;
|
|
33
|
+
private _log;
|
|
34
|
+
/** @inheritdoc */
|
|
35
|
+
log(...args: unknown[]): void;
|
|
36
|
+
/** @inheritdoc */
|
|
37
|
+
info(...args: unknown[]): void;
|
|
38
|
+
/** @inheritdoc */
|
|
39
|
+
warn(...args: unknown[]): void;
|
|
40
|
+
/** @inheritdoc */
|
|
41
|
+
error(...args: unknown[]): void;
|
|
42
|
+
/** @inheritdoc */
|
|
43
|
+
debug(...args: unknown[]): void;
|
|
44
|
+
/**
|
|
45
|
+
* Open a DevTools console group with the domain prefix applied to the label.
|
|
46
|
+
* Always call `groupEnd()` to close it.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* apiLog.group('Fetch /users')
|
|
51
|
+
* apiLog.info('response', data)
|
|
52
|
+
* apiLog.groupEnd()
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
group(label: string): void;
|
|
56
|
+
/**
|
|
57
|
+
* Open a collapsed DevTools console group with the domain prefix applied to the label.
|
|
58
|
+
* Always call `groupEnd()` to close it.
|
|
59
|
+
*/
|
|
60
|
+
groupCollapsed(label: string): void;
|
|
61
|
+
/** Close the most recently opened group. */
|
|
62
|
+
groupEnd(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Start a named performance timer, namespaced to this domain.
|
|
65
|
+
* Use `timeEnd(label)` to stop it and log the elapsed time.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* authLog.time('login')
|
|
70
|
+
* await performLogin()
|
|
71
|
+
* authLog.timeEnd('login')
|
|
72
|
+
* // DevTools: [....auth] login: 42.1ms
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
time(label: string): void;
|
|
76
|
+
/**
|
|
77
|
+
* Stop the named timer started with `time(label)` and log the elapsed duration.
|
|
78
|
+
* @param label Must match the label passed to `time()`.
|
|
79
|
+
*/
|
|
80
|
+
timeEnd(label: string): void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create a new logger for the given domain.
|
|
84
|
+
* Each call creates a fresh instance; use `getLogger()` for singleton behavior.
|
|
85
|
+
*
|
|
86
|
+
* @param domain Service/feature name (e.g. 'auth', 'api', 'payments').
|
|
87
|
+
* @param options Optional color, level, and enabled settings.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* const log = createLogger('auth')
|
|
92
|
+
* log.info('App started')
|
|
93
|
+
*
|
|
94
|
+
* const payLog = createLogger('payments', { color: '#16a34a', level: 'warn' })
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare function createLogger(domain: string, options?: LoggerOptions): Logger;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Logger, LoggerOptions, LogLevel } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Get or create a logger for the given domain.
|
|
4
|
+
* Unlike `createLogger()`, this returns the same instance every time it is
|
|
5
|
+
* called with the same domain — making it safe to call from multiple modules.
|
|
6
|
+
*
|
|
7
|
+
* Options are only applied on first call for a domain; subsequent calls with
|
|
8
|
+
* the same domain return the existing instance regardless of options.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // In module A
|
|
13
|
+
* const log = getLogger('api')
|
|
14
|
+
*
|
|
15
|
+
* // In module B — same instance
|
|
16
|
+
* const log = getLogger('api')
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function getLogger(domain: string, options?: LoggerOptions): Logger;
|
|
20
|
+
/**
|
|
21
|
+
* Return a read-only snapshot of all registered loggers, keyed by domain.
|
|
22
|
+
* Useful for debugging or building DevTools integrations.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const loggers = getAllLoggers()
|
|
27
|
+
* console.log([...loggers.keys()]) // ['api', 'auth', 'payments']
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function getAllLoggers(): ReadonlyMap<string, Logger>;
|
|
31
|
+
/**
|
|
32
|
+
* Set the minimum log level for every registered logger at once.
|
|
33
|
+
* Loggers created after this call are not affected — they use their own
|
|
34
|
+
* options or the default ('debug').
|
|
35
|
+
*
|
|
36
|
+
* Call this early in your app entry point to control verbosity globally:
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* // Only show warnings and errors in production
|
|
41
|
+
* if (import.meta.env.PROD) setGlobalLevel('warn')
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function setGlobalLevel(level: LogLevel): void;
|
|
45
|
+
/**
|
|
46
|
+
* Enable all registered loggers.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* enableAll()
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function enableAll(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Disable all registered loggers. All log calls will be silently dropped
|
|
56
|
+
* until `enableAll()` or individual `setEnabled(true)` is called.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* // Silence everything in production builds
|
|
61
|
+
* if (import.meta.env.PROD) disableAll()
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function disableAll(): void;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export type LogLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
|
|
2
|
+
export interface LoggerOptions {
|
|
3
|
+
/**
|
|
4
|
+
* CSS color string for this domain's prefix badge.
|
|
5
|
+
* If omitted, a color is auto-derived from the domain name via djb2 hash.
|
|
6
|
+
* Accepts any valid CSS color value: '#f06', 'hsl(200,80%,55%)', 'coral', etc.
|
|
7
|
+
*/
|
|
8
|
+
color?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Minimum log level to output. Levels below this are silently dropped.
|
|
11
|
+
* Order (lowest→highest): debug < log < info < warn < error
|
|
12
|
+
* Defaults to 'debug' (all levels pass through).
|
|
13
|
+
*/
|
|
14
|
+
level?: LogLevel;
|
|
15
|
+
/**
|
|
16
|
+
* When false, suppress all output regardless of level.
|
|
17
|
+
* Defaults to true.
|
|
18
|
+
*/
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface Logger {
|
|
22
|
+
/** Log at 'log' level. */
|
|
23
|
+
log(...args: unknown[]): void;
|
|
24
|
+
/** Log at 'info' level. */
|
|
25
|
+
info(...args: unknown[]): void;
|
|
26
|
+
/** Log at 'warn' level. */
|
|
27
|
+
warn(...args: unknown[]): void;
|
|
28
|
+
/** Log at 'error' level. */
|
|
29
|
+
error(...args: unknown[]): void;
|
|
30
|
+
/** Log at 'debug' level. */
|
|
31
|
+
debug(...args: unknown[]): void;
|
|
32
|
+
/**
|
|
33
|
+
* Open a collapsible DevTools group with the domain prefix.
|
|
34
|
+
* Must be closed with `groupEnd()`.
|
|
35
|
+
*/
|
|
36
|
+
group(label: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Open a collapsed DevTools group with the domain prefix.
|
|
39
|
+
* Must be closed with `groupEnd()`.
|
|
40
|
+
*/
|
|
41
|
+
groupCollapsed(label: string): void;
|
|
42
|
+
/** Close the most recently opened group. */
|
|
43
|
+
groupEnd(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Start a named timer prefixed with the domain.
|
|
46
|
+
* Use `timeEnd(label)` to stop and print elapsed time.
|
|
47
|
+
*/
|
|
48
|
+
time(label: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Stop a named timer and log the elapsed time to the console.
|
|
51
|
+
* @param label Must match the label passed to `time()`.
|
|
52
|
+
*/
|
|
53
|
+
timeEnd(label: string): void;
|
|
54
|
+
/**
|
|
55
|
+
* Dynamically change the minimum log level for this logger.
|
|
56
|
+
* Useful for adjusting verbosity at runtime.
|
|
57
|
+
*/
|
|
58
|
+
setLevel(level: LogLevel): void;
|
|
59
|
+
/**
|
|
60
|
+
* Enable or disable this logger at runtime.
|
|
61
|
+
* When disabled, all log calls are silently dropped.
|
|
62
|
+
*/
|
|
63
|
+
setEnabled(enabled: boolean): void;
|
|
64
|
+
/** The domain name this logger was created with. */
|
|
65
|
+
readonly domain: string;
|
|
66
|
+
/** The resolved CSS color string for this logger's prefix. */
|
|
67
|
+
readonly color: string;
|
|
68
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@heojeongbo/log-palette",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Browser logging library with domain-based colored prefixes",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/types/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"types": "./dist/types/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "rolldown -c rolldown.config.ts && tsc --emitDeclarationOnly",
|
|
22
|
+
"dev": "rolldown -c rolldown.config.ts --watch",
|
|
23
|
+
"check": "tsc --noEmit",
|
|
24
|
+
"lint": "biome check src/ example/src/",
|
|
25
|
+
"lint:fix": "biome check --write src/ example/src/",
|
|
26
|
+
"format": "biome format --write src/ example/src/",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"release": "release-it",
|
|
30
|
+
"release:minor": "release-it minor",
|
|
31
|
+
"release:major": "release-it major",
|
|
32
|
+
"release:patch": "release-it patch",
|
|
33
|
+
"release:dry": "release-it --dry-run"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"logger",
|
|
37
|
+
"browser",
|
|
38
|
+
"console",
|
|
39
|
+
"color",
|
|
40
|
+
"debug"
|
|
41
|
+
],
|
|
42
|
+
"author": "heojeongbo <heojungbo@gmail.com> (https://github.com/HeoJeongBo)",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/HeoJeongBo/log-palette.git"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/HeoJeongBo/log-palette#readme",
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/HeoJeongBo/log-palette/issues"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"packageManager": "bun@1.3.11",
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@biomejs/biome": "^2.4.8",
|
|
58
|
+
"@release-it/conventional-changelog": "^10.0.6",
|
|
59
|
+
"release-it": "^19.2.4",
|
|
60
|
+
"rolldown": "^1.0.0-rc.11",
|
|
61
|
+
"typescript": "^5.8.3",
|
|
62
|
+
"vitest": "^3.1.1"
|
|
63
|
+
},
|
|
64
|
+
"workspaces": [
|
|
65
|
+
"example"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { domainToColor } from '../color.js'
|
|
3
|
+
|
|
4
|
+
describe('domainToColor', () => {
|
|
5
|
+
it('returns a valid hsl color string', () => {
|
|
6
|
+
const color = domainToColor('auth')
|
|
7
|
+
expect(color).toMatch(/^hsl\(\d+,\s*75%,\s*60%\)$/)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('returns the same color for the same domain', () => {
|
|
11
|
+
expect(domainToColor('auth')).toBe(domainToColor('auth'))
|
|
12
|
+
expect(domainToColor('api')).toBe(domainToColor('api'))
|
|
13
|
+
expect(domainToColor('payments')).toBe(domainToColor('payments'))
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('returns different colors for different domains', () => {
|
|
17
|
+
const colors = new Set(['auth', 'api', 'ui', 'payments', 'websocket'].map(domainToColor))
|
|
18
|
+
expect(colors.size).toBeGreaterThan(1)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('hue is within 0-359 range', () => {
|
|
22
|
+
for (const domain of ['a', 'test', 'some-long-domain-name', '']) {
|
|
23
|
+
const color = domainToColor(domain)
|
|
24
|
+
const match = color.match(/^hsl\((\d+),/)
|
|
25
|
+
expect(match).not.toBeNull()
|
|
26
|
+
const hue = parseInt(match?.[1] ?? '', 10)
|
|
27
|
+
expect(hue).toBeGreaterThanOrEqual(0)
|
|
28
|
+
expect(hue).toBeLessThan(360)
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { configure, getConfig } from '../config.js'
|
|
3
|
+
|
|
4
|
+
describe('configure / getConfig', () => {
|
|
5
|
+
// Restore default config after each test
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
configure({ innerWidth: 8, timestamp: true })
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('returns default config', () => {
|
|
11
|
+
const config = getConfig()
|
|
12
|
+
expect(config.innerWidth).toBe(8)
|
|
13
|
+
expect(config.timestamp).toBe(true)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('updates innerWidth', () => {
|
|
17
|
+
configure({ innerWidth: 12 })
|
|
18
|
+
expect(getConfig().innerWidth).toBe(12)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('updates timestamp', () => {
|
|
22
|
+
configure({ timestamp: false })
|
|
23
|
+
expect(getConfig().timestamp).toBe(false)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('partial update leaves other fields unchanged', () => {
|
|
27
|
+
configure({ innerWidth: 10 })
|
|
28
|
+
expect(getConfig().timestamp).toBe(true)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('throws RangeError for innerWidth < 1', () => {
|
|
32
|
+
expect(() => configure({ innerWidth: 0 })).toThrow(RangeError)
|
|
33
|
+
expect(() => configure({ innerWidth: -1 })).toThrow(RangeError)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('getConfig returns a snapshot (mutating it has no effect)', () => {
|
|
37
|
+
const snap = getConfig() as { innerWidth: number }
|
|
38
|
+
snap.innerWidth = 999
|
|
39
|
+
expect(getConfig().innerWidth).toBe(8)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { configure } from '../config.js'
|
|
3
|
+
import { formatPrefix, formatTimestamp, prefixStyle } from '../formatter.js'
|
|
4
|
+
|
|
5
|
+
describe('formatTimestamp', () => {
|
|
6
|
+
it('returns HH:mm:ss.SSS format', () => {
|
|
7
|
+
const date = new Date('2024-01-01T05:35:41.039Z')
|
|
8
|
+
// Use local time representation
|
|
9
|
+
const ts = formatTimestamp(date)
|
|
10
|
+
expect(ts).toMatch(/^\d{2}:\d{2}:\d{2}\.\d{3}$/)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('pads single-digit values with zeros', () => {
|
|
14
|
+
const date = new Date(2024, 0, 1, 1, 2, 3, 4)
|
|
15
|
+
const ts = formatTimestamp(date)
|
|
16
|
+
expect(ts).toBe('01:02:03.004')
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('formatPrefix', () => {
|
|
21
|
+
it('pads short domains with dots on the left', () => {
|
|
22
|
+
expect(formatPrefix('auth')).toBe('[....auth]')
|
|
23
|
+
expect(formatPrefix('api')).toBe('[.....api]')
|
|
24
|
+
expect(formatPrefix('ui')).toBe('[......ui]')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('uses no dots for exactly 8-char domains', () => {
|
|
28
|
+
expect(formatPrefix('payments')).toBe('[payments]')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('all prefixes have consistent bracket width (10 chars)', () => {
|
|
32
|
+
const domains = ['a', 'ui', 'auth', 'api', 'payments']
|
|
33
|
+
for (const domain of domains) {
|
|
34
|
+
expect(formatPrefix(domain)).toHaveLength(10)
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('truncates long domains to 7 chars + ellipsis', () => {
|
|
39
|
+
const result = formatPrefix('websocket')
|
|
40
|
+
expect(result).toBe('[websock\u2026]')
|
|
41
|
+
expect(result).toHaveLength(10)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('truncates very long domains consistently', () => {
|
|
45
|
+
const result = formatPrefix('verylongdomainname')
|
|
46
|
+
expect(result).toHaveLength(10)
|
|
47
|
+
expect(result).toMatch(/^\[.{8}\]$/)
|
|
48
|
+
expect(result).toContain('\u2026')
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('formatPrefix with custom innerWidth', () => {
|
|
53
|
+
afterEach(() => configure({ innerWidth: 8 }))
|
|
54
|
+
|
|
55
|
+
it('respects innerWidth: 4', () => {
|
|
56
|
+
configure({ innerWidth: 4 })
|
|
57
|
+
expect(formatPrefix('ui')).toBe('[..ui]')
|
|
58
|
+
expect(formatPrefix('auth')).toBe('[auth]')
|
|
59
|
+
expect(formatPrefix('login')).toBe('[log\u2026]')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('all prefixes match innerWidth + 2', () => {
|
|
63
|
+
configure({ innerWidth: 12 })
|
|
64
|
+
const domains = ['a', 'auth', 'payments', 'verylongdomainname']
|
|
65
|
+
for (const domain of domains) {
|
|
66
|
+
expect(formatPrefix(domain)).toHaveLength(14)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('prefixStyle', () => {
|
|
72
|
+
it('returns a CSS string with the given color', () => {
|
|
73
|
+
const style = prefixStyle('#ff0000')
|
|
74
|
+
expect(style).toContain('#ff0000')
|
|
75
|
+
expect(style).toContain('font-weight: bold')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('works with hsl colors', () => {
|
|
79
|
+
const style = prefixStyle('hsl(200, 75%, 60%)')
|
|
80
|
+
expect(style).toContain('hsl(200, 75%, 60%)')
|
|
81
|
+
})
|
|
82
|
+
})
|