Skip to content

Commit 037d1b5

Browse files
authored
Merge pull request #65 from mizdra/add-unix-option
Add `unixStylePath` option
2 parents 0dc1caa + 2dc2465 commit 037d1b5

8 files changed

+158
-56
lines changed

docs/api/inline-fixture-files.defineiffcreatoroptions.md

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ The options for [defineIFFCreator()](./inline-fixture-files.defineiffcreator.md)
1212
export interface DefineIFFCreatorOptions
1313
```
1414

15+
## Properties
16+
17+
| Property | Modifiers | Type | Description |
18+
| --- | --- | --- | --- |
19+
| [unixStylePath?](./inline-fixture-files.defineiffcreatoroptions.unixstylepath.md) | | boolean \| undefined | _(Optional)_ Use unix-style path separator (<code>/</code>) for paths on windows. |
20+
1521
## Methods
1622

1723
| Method | Description |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
2+
3+
[Home](./index.md) &gt; [@mizdra/inline-fixture-files](./inline-fixture-files.md) &gt; [DefineIFFCreatorOptions](./inline-fixture-files.defineiffcreatoroptions.md) &gt; [unixStylePath](./inline-fixture-files.defineiffcreatoroptions.unixstylepath.md)
4+
5+
## DefineIFFCreatorOptions.unixStylePath property
6+
7+
Use unix-style path separator (`/`<!-- -->) for paths on windows.
8+
9+
**Signature:**
10+
11+
```typescript
12+
unixStylePath?: boolean | undefined;
13+
```

package-lock.json

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"eslint": "^9.14.0",
4040
"eslint-plugin-tsdoc": "^0.3.0",
4141
"npm-run-all": "^4.1.5",
42-
"prettier": "^3.3.3",
42+
"prettier": "^3.5.1",
4343
"ts-expect": "^1.3.0",
4444
"typescript": "^5.2.2",
4545
"vitest": "^2.1.4"

src/get-paths.test.ts

+55-33
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { utimes, writeFile } from 'node:fs/promises';
22
import { join } from 'node:path';
33
import { expectType } from 'ts-expect';
44
import { describe, expect, test } from 'vitest';
5-
import { getPaths, getSelfAndUpperPaths } from './get-paths.js';
5+
import { getPaths, getSelfAndUpperPaths, slash } from './get-paths.js';
66
import { fixtureDir } from './test/util.js';
77

88
test('getSelfAndUpperPaths', () => {
@@ -17,6 +17,7 @@ describe('getPaths', () => {
1717
'b.txt': 'b',
1818
},
1919
fixtureDir,
20+
false,
2021
);
2122
expect(paths).toStrictEqual({
2223
'a.txt': join(fixtureDir, 'a.txt'),
@@ -43,6 +44,7 @@ describe('getPaths', () => {
4344
},
4445
},
4546
fixtureDir,
47+
false,
4648
);
4749
expect(paths).toStrictEqual({
4850
'a.txt': join(fixtureDir, 'a.txt'),
@@ -74,6 +76,7 @@ describe('getPaths', () => {
7476
},
7577
},
7678
fixtureDir,
79+
false,
7780
);
7881
expect(paths).toStrictEqual({
7982
'a': join(fixtureDir, 'a'),
@@ -100,29 +103,29 @@ describe('getPaths', () => {
100103
});
101104

102105
test('throw error when item name starts with separator', () => {
103-
expect(() => getPaths({ '/a.txt': 'a' }, fixtureDir)).toThrowErrorMatchingInlineSnapshot(
106+
expect(() => getPaths({ '/a.txt': 'a' }, fixtureDir, false)).toThrowErrorMatchingInlineSnapshot(
104107
`[Error: Item name must not start with separator: /a.txt]`,
105108
);
106-
expect(() => getPaths({ '/a': {} }, fixtureDir)).toThrowErrorMatchingInlineSnapshot(
109+
expect(() => getPaths({ '/a': {} }, fixtureDir, false)).toThrowErrorMatchingInlineSnapshot(
107110
`[Error: Item name must not start with separator: /a]`,
108111
);
109112
// NOTE: Use of Windows path separator is an undefined behavior.
110-
expect(() => getPaths({ '\\a.txt': 'a' }, fixtureDir)).not.toThrow();
113+
expect(() => getPaths({ '\\a.txt': 'a' }, fixtureDir, false)).not.toThrow();
111114
});
112115

113116
test('throw error when item name ends with separator', () => {
114-
expect(() => getPaths({ 'a.txt/': 'a' }, fixtureDir)).toThrowErrorMatchingInlineSnapshot(
117+
expect(() => getPaths({ 'a.txt/': 'a' }, fixtureDir, false)).toThrowErrorMatchingInlineSnapshot(
115118
`[Error: Item name must not end with separator: a.txt/]`,
116119
);
117-
expect(() => getPaths({ 'a/': {} }, fixtureDir)).toThrowErrorMatchingInlineSnapshot(
120+
expect(() => getPaths({ 'a/': {} }, fixtureDir, false)).toThrowErrorMatchingInlineSnapshot(
118121
`[Error: Item name must not end with separator: a/]`,
119122
);
120123
// NOTE: Use of Windows path separator is an undefined behavior.
121-
expect(() => getPaths({ 'a.txt\\': 'a' }, fixtureDir)).not.toThrow();
124+
expect(() => getPaths({ 'a.txt\\': 'a' }, fixtureDir, false)).not.toThrow();
122125
});
123126

124127
test('throw error when item name contains consecutive separators', () => {
125-
expect(() => getPaths({ 'a//a.txt': 'a--a' }, fixtureDir)).toThrowErrorMatchingInlineSnapshot(
128+
expect(() => getPaths({ 'a//a.txt': 'a--a' }, fixtureDir, false)).toThrowErrorMatchingInlineSnapshot(
126129
`[Error: Item name must not contain consecutive separators: a//a.txt]`,
127130
);
128131
});
@@ -143,6 +146,7 @@ describe('getPaths', () => {
143146
},
144147
},
145148
fixtureDir,
149+
false,
146150
);
147151
expect(paths).toStrictEqual({
148152
'a.txt': join(fixtureDir, 'a.txt'),
@@ -185,6 +189,7 @@ describe('getPaths', () => {
185189
},
186190
},
187191
fixtureDir,
192+
false,
188193
);
189194
expect(paths).toStrictEqual({
190195
'utime.txt': join(fixtureDir, 'utime.txt'),
@@ -196,30 +201,47 @@ describe('getPaths', () => {
196201
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
197202
paths['a.txt'];
198203
});
199-
});
200-
201-
test('allow function and null as items', () => {
202-
const paths = getPaths(
203-
{
204-
'a.txt': null,
205-
// eslint-disable-next-line @typescript-eslint/naming-convention
206-
'b.txt': () => {},
207-
// eslint-disable-next-line @typescript-eslint/naming-convention
208-
'c.txt': async () => {},
209-
},
210-
fixtureDir,
211-
);
212-
expect(paths).toStrictEqual({
213-
'a.txt': join(fixtureDir, 'a.txt'),
214-
'b.txt': join(fixtureDir, 'b.txt'),
215-
'c.txt': join(fixtureDir, 'c.txt'),
204+
test('allow function and null as items', () => {
205+
const paths = getPaths(
206+
{
207+
'a.txt': null,
208+
// eslint-disable-next-line @typescript-eslint/naming-convention
209+
'b.txt': () => {},
210+
// eslint-disable-next-line @typescript-eslint/naming-convention
211+
'c.txt': async () => {},
212+
},
213+
fixtureDir,
214+
false,
215+
);
216+
expect(paths).toStrictEqual({
217+
'a.txt': join(fixtureDir, 'a.txt'),
218+
'b.txt': join(fixtureDir, 'b.txt'),
219+
'c.txt': join(fixtureDir, 'c.txt'),
220+
});
221+
expectType<{
222+
'a.txt': string;
223+
'b.txt': string;
224+
'c.txt': string;
225+
}>(paths);
226+
// @ts-expect-error
227+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
228+
paths['d.txt'];
229+
});
230+
test.runIf(process.platform === 'win32')('convert windows path separator to unix path separator', () => {
231+
const paths = getPaths(
232+
{
233+
'a.txt': 'a',
234+
'b': {
235+
'a.txt': 'b-a',
236+
},
237+
},
238+
fixtureDir,
239+
true,
240+
);
241+
expect(paths).toStrictEqual({
242+
'a.txt': slash(join(fixtureDir, 'a.txt')),
243+
'b': slash(join(fixtureDir, 'b')),
244+
'b/a.txt': slash(join(fixtureDir, 'b/a.txt')),
245+
});
216246
});
217-
expectType<{
218-
'a.txt': string;
219-
'b.txt': string;
220-
'c.txt': string;
221-
}>(paths);
222-
// @ts-expect-error
223-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
224-
paths['d.txt'];
225247
});

src/get-paths.ts

+18-5
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ export function getSelfAndUpperPaths(path: string): string[] {
4343
return [path, ...getSelfAndUpperPaths(parent)];
4444
}
4545

46-
export function getPaths<T extends Directory>(directory: T, rootDir: string, prefix = ''): FlattenDirectory<T> {
46+
export function getPaths<T extends Directory>(
47+
directory: T,
48+
rootDir: string,
49+
unixStylePath: boolean,
50+
prefix = '',
51+
): FlattenDirectory<T> {
4752
let paths: Record<string, string> = {};
4853
for (const [name, item] of Object.entries(directory)) {
4954
// TODO: Extract to `validateDirectory` function
@@ -56,25 +61,33 @@ export function getPaths<T extends Directory>(directory: T, rootDir: string, pre
5661
// NOTE: Use `Object.defineProperty(obj, prop, { value })` instead of `obj[prop] = value` to allow `paths['__proto__']`.
5762
// ref: https://2ality.com/2015/09/proto-es6.html#defining-__proto__
5863
Object.defineProperty(paths, joinForPosix(prefix, n), {
59-
value: join(rootDir, prefix, n),
64+
value: unixStylePath ? slash(join(rootDir, prefix, n)) : join(rootDir, prefix, n),
6065
enumerable: true,
6166
writable: true,
6267
configurable: true,
6368
});
6469
}
6570

6671
if (isDirectory(item)) {
67-
const newPaths = getPaths(item, rootDir, joinForPosix(prefix, name));
72+
const newPaths = getPaths(item, rootDir, unixStylePath, joinForPosix(prefix, name));
6873
paths = { ...paths, ...newPaths };
6974
}
7075
}
7176
return paths as unknown as FlattenDirectory<T>;
7277
}
7378

74-
export function changeRootDirOfPaths<T extends FlattenDirectory<Directory>>(paths: T, newRootDir: string): T {
79+
export function changeRootDirOfPaths<T extends FlattenDirectory<Directory>>(
80+
paths: T,
81+
newRootDir: string,
82+
unixStylePath: boolean,
83+
): T {
7584
const newPaths: Record<string, string> = {};
7685
for (const key of Object.keys(paths)) {
77-
newPaths[key] = join(newRootDir, key);
86+
newPaths[key] = unixStylePath ? slash(join(newRootDir, key)) : join(newRootDir, key);
7887
}
7988
return newPaths as unknown as T;
8089
}
90+
91+
export function slash(path: string): string {
92+
return path.replace(/\\/gu, '/');
93+
}

src/index.test.ts

+51-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { readdir, readFile, rm, writeFile } from 'node:fs/promises';
22
import { join } from 'node:path';
33
import { expectType } from 'ts-expect';
44
import { beforeEach, describe, expect, test, vi } from 'vitest';
5+
import { slash } from './get-paths.js';
56
import { defineIFFCreator } from './index.js';
67
import { exists, fixtureDir } from './test/util.js';
78

@@ -188,12 +189,56 @@ describe('CreateIFFResult', () => {
188189
const iff = await createIFF({});
189190
expect(iff.rootDir).toBe(join(fixtureDir, 'a'));
190191
});
191-
test('join', async () => {
192-
const iff = await createIFF({ 'a.txt': 'a' });
193-
expect(iff.join('a.txt')).toBe(join(fixtureDir, 'a.txt'));
194-
expect(iff.join('/a.txt')).toBe(join(fixtureDir, 'a.txt'));
195-
expect(iff.join('nonexistent-file.txt')).toBe(join(fixtureDir, 'nonexistent-file.txt'));
196-
expect(iff.join('')).toBe(fixtureDir);
192+
describe('paths', () => {
193+
test('basic', async () => {
194+
const iff = await createIFF({
195+
'a.txt': 'a',
196+
'b': {
197+
'a.txt': 'b-a',
198+
},
199+
});
200+
expect(iff.paths).toStrictEqual({
201+
'a.txt': join(fixtureDir, 'a.txt'),
202+
'b': join(fixtureDir, 'b'),
203+
'b/a.txt': join(fixtureDir, 'b/a.txt'),
204+
});
205+
expectType<{
206+
'a.txt': string;
207+
'b': string;
208+
'b/a.txt': string;
209+
}>(iff.paths);
210+
// @ts-expect-error
211+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
212+
iff.paths['b/b.txt'];
213+
});
214+
test.runIf(process.platform === 'win32')('unixStylePath', async () => {
215+
const createIFF = defineIFFCreator({ generateRootDir: () => fixtureDir, unixStylePath: true });
216+
const iff = await createIFF({
217+
'a.txt': 'a',
218+
'b': {
219+
'a.txt': 'b-a',
220+
},
221+
});
222+
expect(iff.paths).toStrictEqual({
223+
'a.txt': slash(join(fixtureDir, 'a.txt')),
224+
'b': slash(join(fixtureDir, 'b')),
225+
'b/a.txt': slash(join(fixtureDir, 'b/a.txt')),
226+
});
227+
});
228+
});
229+
describe('join', () => {
230+
test('basic', async () => {
231+
const iff = await createIFF({ 'a.txt': 'a' });
232+
expect(iff.join('a.txt')).toBe(join(fixtureDir, 'a.txt'));
233+
expect(iff.join('/a.txt')).toBe(join(fixtureDir, 'a.txt'));
234+
expect(iff.join('nonexistent-file.txt')).toBe(join(fixtureDir, 'nonexistent-file.txt'));
235+
expect(iff.join('')).toBe(fixtureDir);
236+
});
237+
test.runIf(process.platform === 'win32')('unixStylePath', async () => {
238+
const createIFF = defineIFFCreator({ generateRootDir: () => fixtureDir, unixStylePath: true });
239+
const iff = await createIFF({});
240+
expect(iff.join('a.txt')).toBe(slash(join(fixtureDir, 'a.txt')));
241+
});
197242
});
198243
test('rmFixtures', async () => {
199244
const iff = await createIFF({

src/index.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { join } from 'node:path';
88
import type { Directory, MergeDirectory } from './create-iff-impl.js';
99
import { createIFFImpl } from './create-iff-impl.js';
1010
import type { FlattenDirectory } from './get-paths.js';
11-
import { changeRootDirOfPaths, getPaths } from './get-paths.js';
11+
import { changeRootDirOfPaths, getPaths, slash } from './get-paths.js';
1212

1313
export type { Directory, DirectoryItem, FileType } from './create-iff-impl.js';
1414
export { IFFFixtureCreationError } from './error.js';
@@ -40,6 +40,8 @@ export interface DefineIFFCreatorOptions {
4040
* ```
4141
*/
4242
generateRootDir(): string;
43+
/** Use unix-style path separator (`/`) for paths on windows. */
44+
unixStylePath?: boolean | undefined;
4345
}
4446

4547
/**
@@ -249,16 +251,17 @@ export function defineIFFCreator(defineIFFCreatorOptions: DefineIFFCreatorOption
249251
__INTERNAL__prevIFF?: CreateIFFResult<U>,
250252
): Promise<CreateIFFResult<MergeDirectory<U, T>>> {
251253
const rootDir = options?.overrideRootDir ?? defineIFFCreatorOptions.generateRootDir();
254+
const unixStylePath = defineIFFCreatorOptions.unixStylePath ?? false;
252255
const paths = {
253-
...changeRootDirOfPaths(__INTERNAL__prevIFF?.paths ?? ({} as FlattenDirectory<U>), rootDir),
254-
...getPaths(directory, rootDir),
256+
...changeRootDirOfPaths(__INTERNAL__prevIFF?.paths ?? ({} as FlattenDirectory<U>), rootDir, unixStylePath),
257+
...getPaths(directory, rootDir, unixStylePath),
255258
} as FlattenDirectory<MergeDirectory<U, T>>;
256259

257260
const iff: CreateIFFResult<MergeDirectory<U, T>> = {
258261
rootDir,
259262
paths,
260263
join(...paths) {
261-
return join(rootDir, ...paths);
264+
return unixStylePath ? slash(join(rootDir, ...paths)) : join(rootDir, ...paths);
262265
},
263266
async rmRootDir() {
264267
await rm(rootDir, { recursive: true, force: true });

0 commit comments

Comments
 (0)