1+ /**
2+ * Cross-platform test utilities for handling filesystem differences
3+ */
4+
5+ import { platform } from 'node:os' ;
6+ import { join , sep , win32 , posix } from 'node:path' ;
7+ import { accessSync , constants , lstatSync } from 'node:fs' ;
8+
9+ export interface PlatformInfo {
10+ isWindows : boolean ;
11+ isMacOS : boolean ;
12+ isLinux : boolean ;
13+ pathSeparator : string ;
14+ caseSensitive : boolean ;
15+ supportsSymlinks : boolean ;
16+ }
17+
18+ /**
19+ * Get current platform information
20+ */
21+ export function getPlatformInfo ( ) : PlatformInfo {
22+ const currentPlatform = platform ( ) ;
23+ const isWindows = currentPlatform === 'win32' ;
24+ const isMacOS = currentPlatform === 'darwin' ;
25+ const isLinux = currentPlatform === 'linux' ;
26+
27+ return {
28+ isWindows,
29+ isMacOS,
30+ isLinux,
31+ pathSeparator : sep ,
32+ caseSensitive : getCaseSensitivity ( ) ,
33+ supportsSymlinks : getSymlinkSupport ( ) ,
34+ } ;
35+ }
36+
37+ /**
38+ * Detect filesystem case sensitivity
39+ */
40+ function getCaseSensitivity ( ) : boolean {
41+ // Check environment variable first (from CI)
42+ const envVar = process . env . MARKMV_TEST_FILESYSTEM_CASE_SENSITIVE ;
43+ if ( envVar !== undefined ) {
44+ return envVar === 'true' ;
45+ }
46+
47+ // Default assumptions based on platform
48+ const currentPlatform = platform ( ) ;
49+ switch ( currentPlatform ) {
50+ case 'win32' :
51+ return false ; // Windows is typically case-insensitive
52+ case 'darwin' :
53+ return false ; // macOS is typically case-insensitive (default APFS/HFS+)
54+ case 'linux' :
55+ return true ; // Linux is typically case-sensitive
56+ default :
57+ return true ; // Default to case-sensitive for unknown platforms
58+ }
59+ }
60+
61+ /**
62+ * Detect symbolic link support
63+ */
64+ function getSymlinkSupport ( ) : boolean {
65+ // Check environment variable first (from CI)
66+ const envVar = process . env . MARKMV_TEST_SUPPORTS_SYMLINKS ;
67+ if ( envVar !== undefined ) {
68+ return envVar === 'true' ;
69+ }
70+
71+ // Default assumptions based on platform
72+ const currentPlatform = platform ( ) ;
73+ switch ( currentPlatform ) {
74+ case 'win32' :
75+ return false ; // Windows has limited symlink support
76+ case 'darwin' :
77+ case 'linux' :
78+ return true ; // Unix-like systems generally support symlinks
79+ default :
80+ return false ; // Default to no symlink support for unknown platforms
81+ }
82+ }
83+
84+ /**
85+ * Normalize path for the current platform
86+ */
87+ export function normalizePath ( path : string ) : string {
88+ const platformInfo = getPlatformInfo ( ) ;
89+
90+ if ( platformInfo . isWindows ) {
91+ return win32 . normalize ( path ) ;
92+ } else {
93+ return posix . normalize ( path ) ;
94+ }
95+ }
96+
97+ /**
98+ * Create a path using the appropriate separator for the current platform
99+ */
100+ export function createPath ( ...segments : string [ ] ) : string {
101+ const platformInfo = getPlatformInfo ( ) ;
102+
103+ if ( platformInfo . isWindows ) {
104+ return win32 . join ( ...segments ) ;
105+ } else {
106+ return posix . join ( ...segments ) ;
107+ }
108+ }
109+
110+ /**
111+ * Convert path separators to the current platform
112+ */
113+ export function convertPathSeparators ( path : string ) : string {
114+ const platformInfo = getPlatformInfo ( ) ;
115+
116+ if ( platformInfo . isWindows ) {
117+ return path . replace ( / \/ / g, '\\' ) ;
118+ } else {
119+ return path . replace ( / \\ / g, '/' ) ;
120+ }
121+ }
122+
123+ /**
124+ * Test if two filenames would conflict on the current filesystem
125+ */
126+ export function wouldFilenamesConflict ( filename1 : string , filename2 : string ) : boolean {
127+ const platformInfo = getPlatformInfo ( ) ;
128+
129+ if ( platformInfo . caseSensitive ) {
130+ return filename1 === filename2 ;
131+ } else {
132+ return filename1 . toLowerCase ( ) === filename2 . toLowerCase ( ) ;
133+ }
134+ }
135+
136+ /**
137+ * Skip test if the current platform doesn't support the required feature
138+ */
139+ export function skipIfUnsupported ( feature : 'symlinks' | 'case-sensitivity' ) : boolean {
140+ const platformInfo = getPlatformInfo ( ) ;
141+
142+ switch ( feature ) {
143+ case 'symlinks' :
144+ return ! platformInfo . supportsSymlinks ;
145+ case 'case-sensitivity' :
146+ return ! platformInfo . caseSensitive ;
147+ default :
148+ return false ;
149+ }
150+ }
151+
152+ /**
153+ * Create test helper that conditionally runs based on platform capabilities
154+ */
155+ export function conditionalTest (
156+ name : string ,
157+ requirement : 'symlinks' | 'case-sensitivity' | 'windows' | 'unix' ,
158+ testFn : ( ) => void | Promise < void >
159+ ) : void {
160+ const platformInfo = getPlatformInfo ( ) ;
161+
162+ let shouldSkip = false ;
163+ let skipReason = '' ;
164+
165+ switch ( requirement ) {
166+ case 'symlinks' :
167+ shouldSkip = ! platformInfo . supportsSymlinks ;
168+ skipReason = 'symbolic links not supported on this platform' ;
169+ break ;
170+ case 'case-sensitivity' :
171+ shouldSkip = ! platformInfo . caseSensitive ;
172+ skipReason = 'filesystem is not case-sensitive' ;
173+ break ;
174+ case 'windows' :
175+ shouldSkip = ! platformInfo . isWindows ;
176+ skipReason = 'test requires Windows' ;
177+ break ;
178+ case 'unix' :
179+ shouldSkip = platformInfo . isWindows ;
180+ skipReason = 'test requires Unix-like system' ;
181+ break ;
182+ }
183+
184+ if ( shouldSkip ) {
185+ test . skip ( `${ name } (skipped: ${ skipReason } )` , testFn ) ;
186+ } else {
187+ test ( name , testFn ) ;
188+ }
189+ }
190+
191+ /**
192+ * Check if a file exists and is accessible
193+ */
194+ export function fileExists ( filePath : string ) : boolean {
195+ try {
196+ accessSync ( filePath , constants . F_OK ) ;
197+ return true ;
198+ } catch {
199+ return false ;
200+ }
201+ }
202+
203+ /**
204+ * Check if a path is a symbolic link
205+ */
206+ export function isSymbolicLink ( filePath : string ) : boolean {
207+ try {
208+ const stats = lstatSync ( filePath ) ;
209+ return stats . isSymbolicLink ( ) ;
210+ } catch {
211+ return false ;
212+ }
213+ }
214+
215+ /**
216+ * Get OS-specific temporary directory patterns
217+ */
218+ export function getTempDirPatterns ( ) : string [ ] {
219+ const platformInfo = getPlatformInfo ( ) ;
220+
221+ if ( platformInfo . isWindows ) {
222+ return [ 'C:\\temp' , 'C:\\tmp' , '%TEMP%' , '%TMP%' ] ;
223+ } else {
224+ return [ '/tmp' , '/var/tmp' , '$TMPDIR' ] ;
225+ }
226+ }
227+
228+ /**
229+ * Platform-specific test data for path testing
230+ */
231+ export const PLATFORM_TEST_PATHS = {
232+ windows : {
233+ absolute : [ 'C:\\Users\\test\\file.txt' , 'D:\\projects\\readme.md' ] ,
234+ relative : [ '..\\parent\\file.txt' , 'subfolder\\document.md' ] ,
235+ invalid : [ 'C:' , 'C:\\con' , 'C:\\prn' , 'C:\\aux' ] ,
236+ } ,
237+ unix : {
238+ absolute : [ '/home/test/file.txt' , '/usr/local/bin/script' ] ,
239+ relative : [ '../parent/file.txt' , 'subfolder/document.md' ] ,
240+ invalid : [ '/dev/null/../file' , '' ] ,
241+ } ,
242+ } ;
243+
244+ /**
245+ * Get platform-appropriate test paths
246+ */
247+ export function getTestPaths ( ) : typeof PLATFORM_TEST_PATHS . windows | typeof PLATFORM_TEST_PATHS . unix {
248+ const platformInfo = getPlatformInfo ( ) ;
249+
250+ if ( platformInfo . isWindows ) {
251+ return PLATFORM_TEST_PATHS . windows ;
252+ } else {
253+ return PLATFORM_TEST_PATHS . unix ;
254+ }
255+ }
0 commit comments