emoemu 0.1.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/.claude/settings.local.json +77 -0
- package/.node-version +1 -0
- package/CLAUDE.md +435 -0
- package/README.md +404 -0
- package/TODO.md +655 -0
- package/dist/index.cjs +25108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +25085 -0
- package/docs/building-libretro-cores-arm-mac.md +237 -0
- package/docs/config-file-format.md +488 -0
- package/docs/cores-trd.md +425 -0
- package/docs/headless-hardware-rendering-trd.md +676 -0
- package/docs/libretro-cores-trd.md +997 -0
- package/docs/mupen64-software-rendering-trd.md +751 -0
- package/docs/n64-support-trd.md +306 -0
- package/docs/native-rendering-trd.md +540 -0
- package/docs/native-ui-rendering-trd.md +1195 -0
- package/docs/netplay-trd.md +665 -0
- package/docs/retroarch-netplay-docs.md +277 -0
- package/docs/save-state-format.md +666 -0
- package/eslint.config.js +111 -0
- package/icon/icon.png +0 -0
- package/icon/icon.pxd +0 -0
- package/package.json +63 -0
- package/pnpm-workspace.yaml +10 -0
- package/src/Emulator/consts.ts +14 -0
- package/src/Emulator/index.ts +2496 -0
- package/src/Emulator/saveState/index.ts +155 -0
- package/src/Emulator/screenshot/index.ts +160 -0
- package/src/Emulator/terminalDimensions/index.ts +79 -0
- package/src/Emulator/types.ts +83 -0
- package/src/cli/commands/consts.ts +10 -0
- package/src/cli/commands/index.ts +462 -0
- package/src/cli/parseArgs/consts.ts +17 -0
- package/src/cli/parseArgs/index.ts +457 -0
- package/src/cli/parseArgs/types.ts +61 -0
- package/src/cli/runEmulator/index.ts +406 -0
- package/src/cli/runEmulator/types.ts +7 -0
- package/src/consts.ts +19 -0
- package/src/core/button/consts.ts +35 -0
- package/src/core/button/index.ts +123 -0
- package/src/core/core.ts +300 -0
- package/src/core/index.ts +19 -0
- package/src/cores/libretro/api/index.ts +334 -0
- package/src/cores/libretro/api/types.ts +148 -0
- package/src/cores/libretro/callbacks/consts.ts +41 -0
- package/src/cores/libretro/callbacks/index.ts +456 -0
- package/src/cores/libretro/consts.ts +45 -0
- package/src/cores/libretro/coreOptions/consts.ts +36 -0
- package/src/cores/libretro/coreOptions/index.ts +222 -0
- package/src/cores/libretro/environment/consts.ts +118 -0
- package/src/cores/libretro/environment/index.ts +1095 -0
- package/src/cores/libretro/index.ts +937 -0
- package/src/cores/libretro/loader/index.ts +496 -0
- package/src/cores/libretro/pixelFormat/consts.ts +43 -0
- package/src/cores/libretro/pixelFormat/index.ts +397 -0
- package/src/cores/libretro/types.ts +339 -0
- package/src/frontend/AudioManager/index.ts +420 -0
- package/src/frontend/SettingsManager/index.ts +250 -0
- package/src/frontend/config/index.ts +608 -0
- package/src/frontend/config/tests.ts +354 -0
- package/src/frontend/config/types.ts +36 -0
- package/src/frontend/consts.ts +114 -0
- package/src/frontend/coreBuilder/index.ts +644 -0
- package/src/frontend/coreBuilder/types.ts +15 -0
- package/src/frontend/coreDownloader/index.ts +620 -0
- package/src/frontend/coreDownloader/types.ts +17 -0
- package/src/frontend/corePreferences/index.ts +69 -0
- package/src/frontend/coreRegistry/index.ts +276 -0
- package/src/frontend/directoryCache/index.ts +75 -0
- package/src/frontend/index.ts +79 -0
- package/src/frontend/notifications/consts.ts +14 -0
- package/src/frontend/notifications/index.ts +250 -0
- package/src/frontend/playlist/consts.ts +168 -0
- package/src/frontend/playlist/index.ts +899 -0
- package/src/frontend/playlist/labelFormatter/consts.ts +15 -0
- package/src/frontend/playlist/labelFormatter/index.ts +155 -0
- package/src/frontend/playlist/labelFormatter/tests.ts +153 -0
- package/src/frontend/playlist/reader/index.ts +559 -0
- package/src/frontend/playlist/sync/index.ts +511 -0
- package/src/frontend/playlist/systemLookup/index.ts +233 -0
- package/src/frontend/playlist/utils/index.ts +50 -0
- package/src/frontend/romScanner/consts.ts +348 -0
- package/src/frontend/romScanner/index.ts +1957 -0
- package/src/frontend/saveServices/consts.ts +2 -0
- package/src/frontend/saveServices/index.ts +313 -0
- package/src/frontend/serviceProvider/index.ts +108 -0
- package/src/frontend/serviceProvider/types.ts +13 -0
- package/src/index.ts +428 -0
- package/src/input/Controller/consts.ts +50 -0
- package/src/input/Controller/index.ts +81 -0
- package/src/input/GamepadManager/consts.ts +22 -0
- package/src/input/GamepadManager/index.ts +418 -0
- package/src/input/InputManager/consts.ts +86 -0
- package/src/input/InputManager/index.ts +593 -0
- package/src/input/InputMapper/consts.ts +33 -0
- package/src/input/InputMapper/index.ts +436 -0
- package/src/input/consts.ts +410 -0
- package/src/input/gamepadProfiles/index.ts +1100 -0
- package/src/input/index.ts +38 -0
- package/src/input/inputUtils/index.ts +31 -0
- package/src/netplay/FrameBuffer/consts.ts +2 -0
- package/src/netplay/FrameBuffer/index.ts +364 -0
- package/src/netplay/FrameBuffer/tests.ts +286 -0
- package/src/netplay/InputBuffer/consts.ts +7 -0
- package/src/netplay/InputBuffer/index.ts +347 -0
- package/src/netplay/InputBuffer/tests.ts +166 -0
- package/src/netplay/NetplayClient/index.ts +976 -0
- package/src/netplay/NetplayConnection/index.ts +536 -0
- package/src/netplay/NetplayDiscovery/consts.ts +41 -0
- package/src/netplay/NetplayDiscovery/index.ts +525 -0
- package/src/netplay/NetplayServer/index.ts +1407 -0
- package/src/netplay/SyncManager/index.ts +984 -0
- package/src/netplay/SyncManager/tests.ts +419 -0
- package/src/netplay/consts.ts +371 -0
- package/src/netplay/crc32/consts.ts +14 -0
- package/src/netplay/crc32/index.ts +97 -0
- package/src/netplay/crc32/tests.ts +40 -0
- package/src/netplay/index.ts +41 -0
- package/src/netplay/netplayLogger/consts.ts +30 -0
- package/src/netplay/netplayLogger/index.ts +345 -0
- package/src/netplay/protocol/consts.ts +86 -0
- package/src/netplay/protocol/index.ts +1280 -0
- package/src/netplay/protocol/tests.ts +606 -0
- package/src/netplay/protocol/types.ts +20 -0
- package/src/netplay/types.ts +395 -0
- package/src/rendering/KittyRenderer/index.ts +616 -0
- package/src/rendering/NativeRenderer/index.ts +279 -0
- package/src/rendering/NativeRenderer/tests.ts +133 -0
- package/src/rendering/TerminalRenderer/index.ts +770 -0
- package/src/rendering/consts.ts +401 -0
- package/src/rendering/fonts/CozetteVector.ttf +0 -0
- package/src/rendering/index.ts +26 -0
- package/src/rendering/nativeUi/NativeWindowManager/index.ts +158 -0
- package/src/rendering/nativeUi/NativeWindowManager/tests.ts +81 -0
- package/src/rendering/nativeUi/consts.ts +6 -0
- package/src/rendering/nativeUi/index.ts +20 -0
- package/src/rendering/postProcessing/consts.ts +38 -0
- package/src/rendering/postProcessing/index.ts +923 -0
- package/src/rendering/shared/ansi/consts.ts +12 -0
- package/src/rendering/shared/ansi/index.ts +104 -0
- package/src/rendering/shared/consts.ts +2 -0
- package/src/rendering/shared/fitToTerminal/index.ts +67 -0
- package/src/ui/AddRomsPrompt/consts.ts +13 -0
- package/src/ui/AddRomsPrompt/index.tsx +781 -0
- package/src/ui/App/consts.ts +2 -0
- package/src/ui/App/index.tsx +456 -0
- package/src/ui/AppCapabilities/index.tsx +67 -0
- package/src/ui/ConfigContext/index.tsx +56 -0
- package/src/ui/CoreManager/consts.ts +11 -0
- package/src/ui/CoreManager/index.tsx +779 -0
- package/src/ui/CoreSelector/consts.ts +2 -0
- package/src/ui/CoreSelector/index.tsx +251 -0
- package/src/ui/DialogContainer/index.tsx +42 -0
- package/src/ui/DialogOptionsList/index.tsx +61 -0
- package/src/ui/DuplicateCrcPrompt/consts.ts +5 -0
- package/src/ui/DuplicateCrcPrompt/index.tsx +146 -0
- package/src/ui/GamepadContext/consts.ts +15 -0
- package/src/ui/GamepadContext/index.tsx +295 -0
- package/src/ui/NativeDialog/index.tsx +120 -0
- package/src/ui/NetplayDisconnectedDialog/index.tsx +93 -0
- package/src/ui/NetplayPauseMenu/consts.ts +2 -0
- package/src/ui/NetplayPauseMenu/index.tsx +97 -0
- package/src/ui/RomBrowser/NetplayPanel/consts.ts +24 -0
- package/src/ui/RomBrowser/NetplayPanel/index.tsx +520 -0
- package/src/ui/RomBrowser/SettingsPanel/index.tsx +478 -0
- package/src/ui/RomBrowser/consts.ts +61 -0
- package/src/ui/RomBrowser/index.tsx +1164 -0
- package/src/ui/RomBrowser/settingsConfig/index.ts +320 -0
- package/src/ui/RomBrowser/types.ts +67 -0
- package/src/ui/SaveStateDialog/consts.ts +2 -0
- package/src/ui/SaveStateDialog/index.tsx +225 -0
- package/src/ui/WarningDialog/index.tsx +113 -0
- package/src/ui/consts.ts +27 -0
- package/src/ui/hooks/useClearTerminal/consts.ts +2 -0
- package/src/ui/hooks/useClearTerminal/index.ts +37 -0
- package/src/ui/hooks/useDialogNavigation/index.ts +99 -0
- package/src/ui/hooks/useGamepad/consts.ts +21 -0
- package/src/ui/hooks/useGamepad/index.ts +194 -0
- package/src/ui/index.ts +27 -0
- package/src/utils/buffer/consts.ts +17 -0
- package/src/utils/buffer/index.ts +129 -0
- package/src/utils/color/consts.ts +58 -0
- package/src/utils/color/index.ts +183 -0
- package/src/utils/compression/consts.ts +50 -0
- package/src/utils/compression/index.ts +101 -0
- package/src/utils/consts.ts +2 -0
- package/src/utils/crc32/consts.ts +22 -0
- package/src/utils/crc32/index.ts +83 -0
- package/src/utils/ensureDirectory/index.ts +10 -0
- package/src/utils/format/consts.ts +8 -0
- package/src/utils/format/index.ts +53 -0
- package/src/utils/getErrorMessage/index.ts +10 -0
- package/src/utils/index.ts +113 -0
- package/src/utils/ini/index.ts +200 -0
- package/src/utils/kitty/consts.ts +13 -0
- package/src/utils/kitty/index.ts +181 -0
- package/src/utils/logger/consts.ts +35 -0
- package/src/utils/logger/index.ts +217 -0
- package/src/utils/paths/consts.ts +18 -0
- package/src/utils/paths/index.ts +151 -0
- package/src/utils/png/consts.ts +34 -0
- package/src/utils/png/index.ts +131 -0
- package/src/utils/readJsonFile/index.ts +16 -0
- package/src/utils/rotateLogFile/index.ts +44 -0
- package/src/utils/safeClose/index.ts +10 -0
- package/src/utils/terminal/consts.ts +8 -0
- package/src/utils/terminal/index.ts +102 -0
- package/src/utils/thumbnailRenderer/consts.ts +2 -0
- package/src/utils/thumbnailRenderer/index.ts +147 -0
- package/src/utils/typedError/index.ts +26 -0
- package/tsconfig.json +31 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Articles that can appear at end of title (case-insensitive matching) */
|
|
2
|
+
export const TRAILING_ARTICLES = ['The', 'A', 'An'];
|
|
3
|
+
|
|
4
|
+
/** Words that should be lowercase in title case (unless first word) */
|
|
5
|
+
export const LOWERCASE_WORDS = new Set([
|
|
6
|
+
'a', 'an', 'the', // articles
|
|
7
|
+
'and', 'but', 'or', 'nor', // conjunctions
|
|
8
|
+
'for', 'yet', 'so', // FANBOYS conjunctions
|
|
9
|
+
'at', 'by', 'in', 'of', // short prepositions
|
|
10
|
+
'on', 'to', 'up', 'as',
|
|
11
|
+
'vs', 'vs.', // versus
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
/** Roman numeral pattern (I, II, III, IV, V, etc.) */
|
|
15
|
+
export const ROMAN_NUMERAL_PATTERN = /^(M{0,3})(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/i;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ROM Label Formatting Utility
|
|
3
|
+
*
|
|
4
|
+
* Fixes common formatting issues in ROM titles:
|
|
5
|
+
* - Converts ALL CAPS to Title Case
|
|
6
|
+
* - Moves trailing articles (The, A, An) to the front
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
TRAILING_ARTICLES,
|
|
11
|
+
LOWERCASE_WORDS,
|
|
12
|
+
ROMAN_NUMERAL_PATTERN,
|
|
13
|
+
} from './consts';
|
|
14
|
+
import { getSupportedExtensions } from '../../coreRegistry';
|
|
15
|
+
|
|
16
|
+
export * from './consts';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check if a word is a Roman numeral.
|
|
20
|
+
*/
|
|
21
|
+
const isRomanNumeral = (word: string): boolean => {
|
|
22
|
+
const cleaned = word.replace(/[^a-zA-Z]/g, '');
|
|
23
|
+
return cleaned.length > 0 && ROMAN_NUMERAL_PATTERN.test(cleaned);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a string is all uppercase (ignoring non-letter characters).
|
|
28
|
+
* Returns false for strings with no letters.
|
|
29
|
+
*/
|
|
30
|
+
const isAllCaps = (str: string): boolean => {
|
|
31
|
+
const letters = str.replace(/[^a-zA-Z]/g, '');
|
|
32
|
+
return letters.length > 0 && letters === letters.toUpperCase();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Capitalize the first letter of a word, preserving the rest.
|
|
37
|
+
*/
|
|
38
|
+
const capitalizeFirst = (word: string): string => {
|
|
39
|
+
if (word.length === 0) { return word; }
|
|
40
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Convert a string to title case.
|
|
45
|
+
* - First word is always capitalized
|
|
46
|
+
* - Articles, short prepositions, and conjunctions are lowercase (unless first word)
|
|
47
|
+
* - Roman numerals stay uppercase
|
|
48
|
+
*/
|
|
49
|
+
const toTitleCase = (str: string): string => {
|
|
50
|
+
const words = str.split(/(\s+)/); // Split but keep whitespace
|
|
51
|
+
|
|
52
|
+
let isFirstWord = true;
|
|
53
|
+
return words.map((segment) => {
|
|
54
|
+
// Preserve whitespace segments
|
|
55
|
+
if (/^\s+$/.test(segment)) {
|
|
56
|
+
return segment;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const word = segment;
|
|
60
|
+
const lowerWord = word.toLowerCase();
|
|
61
|
+
|
|
62
|
+
// Roman numerals stay uppercase
|
|
63
|
+
if (isRomanNumeral(word)) {
|
|
64
|
+
isFirstWord = false;
|
|
65
|
+
return word.toUpperCase();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// First word is always capitalized
|
|
69
|
+
if (isFirstWord) {
|
|
70
|
+
isFirstWord = false;
|
|
71
|
+
return capitalizeFirst(word);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check if this word should stay lowercase
|
|
75
|
+
if (LOWERCASE_WORDS.has(lowerWord)) {
|
|
76
|
+
return lowerWord;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Capitalize the word
|
|
80
|
+
return capitalizeFirst(word);
|
|
81
|
+
}).join('');
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Strip file extension from the end of a label (case-insensitive).
|
|
86
|
+
* Uses extensions supported by loaded cores.
|
|
87
|
+
* "Super Mario Bros.NES" -> "Super Mario Bros"
|
|
88
|
+
*/
|
|
89
|
+
const stripFileExtension = (str: string): string => {
|
|
90
|
+
// Match any extension at the end (e.g., ".NES", ".nes", ".sfc")
|
|
91
|
+
const match = str.match(/(\.[a-zA-Z0-9]+)$/);
|
|
92
|
+
if (match) {
|
|
93
|
+
const ext = match[1].toLowerCase();
|
|
94
|
+
// Get supported extensions from loaded cores (already lowercase with dot)
|
|
95
|
+
const supportedExtensions = new Set(getSupportedExtensions());
|
|
96
|
+
if (supportedExtensions.has(ext)) {
|
|
97
|
+
return str.slice(0, -ext.length).trim();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return str;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Move trailing articles to the front of the title.
|
|
105
|
+
* "Legend of Zelda, The" -> "The Legend of Zelda"
|
|
106
|
+
* "Legend of Zelda. The" -> "The Legend of Zelda"
|
|
107
|
+
* "Boy and His Blob, A" -> "A Boy and His Blob"
|
|
108
|
+
*/
|
|
109
|
+
const moveTrailingArticle = (str: string): string => {
|
|
110
|
+
for (const article of TRAILING_ARTICLES) {
|
|
111
|
+
// Match ", The" or ". The" with any amount of whitespace (case insensitive)
|
|
112
|
+
const pattern = new RegExp(`[,.]\\s*${article}\\s*$`, 'i');
|
|
113
|
+
const match = str.match(pattern);
|
|
114
|
+
if (match) {
|
|
115
|
+
const withoutArticle = str.slice(0, match.index).trim();
|
|
116
|
+
// Use the canonical article casing
|
|
117
|
+
return `${article} ${withoutArticle}`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return str;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Format a ROM label for display.
|
|
125
|
+
* - Strips file extensions (.nes, .sfc, etc.)
|
|
126
|
+
* - Replaces underscores with spaces
|
|
127
|
+
* - Moves trailing articles (The, A, An) to the front
|
|
128
|
+
* - Converts ALL CAPS titles to Title Case
|
|
129
|
+
* - Normalizes whitespace
|
|
130
|
+
*/
|
|
131
|
+
export const formatRomLabel = (label: string): string => {
|
|
132
|
+
let result = label.trim();
|
|
133
|
+
|
|
134
|
+
// Strip file extension first
|
|
135
|
+
result = stripFileExtension(result);
|
|
136
|
+
|
|
137
|
+
// Replace underscores with spaces
|
|
138
|
+
result = result.replace(/_/g, ' ');
|
|
139
|
+
|
|
140
|
+
// Check if all caps BEFORE any transformations
|
|
141
|
+
const wasAllCaps = isAllCaps(result);
|
|
142
|
+
|
|
143
|
+
// Move trailing articles to front
|
|
144
|
+
result = moveTrailingArticle(result);
|
|
145
|
+
|
|
146
|
+
// Convert all caps to title case (based on original check)
|
|
147
|
+
if (wasAllCaps) {
|
|
148
|
+
result = toTitleCase(result);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Normalize whitespace (collapse multiple spaces)
|
|
152
|
+
result = result.replace(/\s+/g, ' ').trim();
|
|
153
|
+
|
|
154
|
+
return result;
|
|
155
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for ROM label formatting utility
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
+
import { formatRomLabel } from '.';
|
|
7
|
+
|
|
8
|
+
// Mock the core registry to provide test extensions (lowercase, matching real behavior)
|
|
9
|
+
vi.mock('../../coreRegistry', () => ({
|
|
10
|
+
getSupportedExtensions: () => ['.nes', '.sfc', '.md', '.gba', '.bin'],
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('formatRomLabel', () => {
|
|
18
|
+
describe('trailing article handling', () => {
|
|
19
|
+
it('moves "The" from end to beginning', () => {
|
|
20
|
+
expect(formatRomLabel('Legend of Zelda, The')).toBe('The Legend of Zelda');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('moves "A" from end to beginning', () => {
|
|
24
|
+
expect(formatRomLabel('Boy and His Blob, A')).toBe('A Boy and His Blob');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('moves "An" from end to beginning', () => {
|
|
28
|
+
expect(formatRomLabel('American Tail, An')).toBe('An American Tail');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('handles no space after comma', () => {
|
|
32
|
+
expect(formatRomLabel('Legend of Zelda,The')).toBe('The Legend of Zelda');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('handles multiple spaces after comma', () => {
|
|
36
|
+
expect(formatRomLabel('Legend of Zelda, The')).toBe('The Legend of Zelda');
|
|
37
|
+
expect(formatRomLabel('Legend of Zelda, The')).toBe('The Legend of Zelda');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('handles period instead of comma', () => {
|
|
41
|
+
expect(formatRomLabel('Legend of Zelda. The')).toBe('The Legend of Zelda');
|
|
42
|
+
expect(formatRomLabel('Legend of Zelda.The')).toBe('The Legend of Zelda');
|
|
43
|
+
expect(formatRomLabel('Legend of Zelda. The')).toBe('The Legend of Zelda');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('is case-insensitive for article matching', () => {
|
|
47
|
+
expect(formatRomLabel('Legend of Zelda, THE')).toBe('The Legend of Zelda');
|
|
48
|
+
expect(formatRomLabel('Legend of Zelda, the')).toBe('The Legend of Zelda');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('does not modify titles without trailing articles', () => {
|
|
52
|
+
expect(formatRomLabel('Super Mario Bros')).toBe('Super Mario Bros');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('does not match articles in the middle', () => {
|
|
56
|
+
expect(formatRomLabel('The Legend of Zelda')).toBe('The Legend of Zelda');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('all caps to title case', () => {
|
|
61
|
+
it('converts all caps to title case', () => {
|
|
62
|
+
expect(formatRomLabel('THE LEGEND OF ZELDA')).toBe('The Legend of Zelda');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('lowercases articles and prepositions in the middle', () => {
|
|
66
|
+
expect(formatRomLabel('SUPER MARIO BROS')).toBe('Super Mario Bros');
|
|
67
|
+
expect(formatRomLabel('STREETS OF RAGE')).toBe('Streets of Rage');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('preserves Roman numerals', () => {
|
|
71
|
+
expect(formatRomLabel('FINAL FANTASY III')).toBe('Final Fantasy III');
|
|
72
|
+
expect(formatRomLabel('MEGA MAN II')).toBe('Mega Man II');
|
|
73
|
+
expect(formatRomLabel('SUPER MARIO BROS IV')).toBe('Super Mario Bros IV');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('does not modify mixed case titles', () => {
|
|
77
|
+
expect(formatRomLabel('The Legend of Zelda')).toBe('The Legend of Zelda');
|
|
78
|
+
expect(formatRomLabel('Super Mario Bros.')).toBe('Super Mario Bros.');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('handles single word all caps', () => {
|
|
82
|
+
expect(formatRomLabel('TETRIS')).toBe('Tetris');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('combined transformations', () => {
|
|
87
|
+
it('moves article and converts case', () => {
|
|
88
|
+
expect(formatRomLabel('LEGEND OF ZELDA, THE')).toBe('The Legend of Zelda');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('handles complex titles', () => {
|
|
92
|
+
expect(formatRomLabel('ADVENTURES OF LOLO III, THE')).toBe('The Adventures of Lolo III');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('file extension stripping', () => {
|
|
97
|
+
it('strips .NES extension (uppercase)', () => {
|
|
98
|
+
expect(formatRomLabel('The Legend of Zelda.NES')).toBe('The Legend of Zelda');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('strips .nes extension (lowercase)', () => {
|
|
102
|
+
expect(formatRomLabel('Super Mario Bros.nes')).toBe('Super Mario Bros');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('strips various ROM extensions', () => {
|
|
106
|
+
expect(formatRomLabel('Sonic.md')).toBe('Sonic');
|
|
107
|
+
expect(formatRomLabel('Zelda.sfc')).toBe('Zelda');
|
|
108
|
+
expect(formatRomLabel('Pokemon.gba')).toBe('Pokemon');
|
|
109
|
+
expect(formatRomLabel('Game.bin')).toBe('Game');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('strips extension and converts all caps', () => {
|
|
113
|
+
expect(formatRomLabel('SUPER MARIO BROS.NES')).toBe('Super Mario Bros');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('does not strip unknown extensions', () => {
|
|
117
|
+
expect(formatRomLabel('Game.xyz')).toBe('Game.xyz');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('handles extension with spaces before it', () => {
|
|
121
|
+
expect(formatRomLabel('Super Mario Bros .nes')).toBe('Super Mario Bros');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('underscore handling', () => {
|
|
126
|
+
it('replaces underscores with spaces (mixed case preserved)', () => {
|
|
127
|
+
expect(formatRomLabel('Super_Mario_Bros')).toBe('Super Mario Bros');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('replaces underscores and converts all caps', () => {
|
|
131
|
+
expect(formatRomLabel('SUPER_MARIO_ALL_STARS')).toBe('Super Mario All Stars');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('handles multiple underscores', () => {
|
|
135
|
+
expect(formatRomLabel('LEGEND__OF__ZELDA')).toBe('Legend of Zelda');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('whitespace handling', () => {
|
|
140
|
+
it('trims leading and trailing whitespace', () => {
|
|
141
|
+
expect(formatRomLabel(' Super Mario Bros ')).toBe('Super Mario Bros');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('normalizes multiple spaces in the middle', () => {
|
|
145
|
+
expect(formatRomLabel('Super Mario Bros')).toBe('Super Mario Bros');
|
|
146
|
+
expect(formatRomLabel('Mario Bros')).toBe('Mario Bros');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('normalizes tabs and other whitespace', () => {
|
|
150
|
+
expect(formatRomLabel('Super\tMario\t\tBros')).toBe('Super Mario Bros');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|