Add Prettier and ESLint tooling (Refs #77)

This commit is contained in:
2026-10-09 15:56:13 -04:00
parent 8ed059b8d3
commit 16469e2d43
711 changed files with 99737 additions and 28286 deletions
+37 -19
View File
@@ -2,10 +2,18 @@ import { describe, expect, it, vi } from 'vitest';
import { selectGameFolder } from '../../src/lib/ui/game-folder.js';
function selectedFile(path: string, size = 10) {
const read = vi.fn(() => { throw new Error('Folder selection must not read file bodies.'); });
const read = vi.fn(() => {
throw new Error('Folder selection must not read file bodies.');
});
const file = {
name: path.split('/').at(-1), webkitRelativePath: path, size, lastModified: 1_700_000_000_000,
arrayBuffer: read, text: read, stream: read, slice: read
name: path.split('/').at(-1),
webkitRelativePath: path,
size,
lastModified: 1_700_000_000_000,
arrayBuffer: read,
text: read,
stream: read,
slice: read
} as unknown as File;
return { file, read };
}
@@ -30,43 +38,53 @@ describe('game folder selection', () => {
const empty = selectedFile('Game/empty.txt', 0);
const result = selectGameFolder([large.file, empty.file]);
expect(result.size).toBe(7 * 1024 ** 3);
expect(result.files.map(item => item.path)).toEqual(['empty.txt', 'zone/base.ff']);
expect(result.files.map((item) => item.path)).toEqual(['empty.txt', 'zone/base.ff']);
expect(result.files[1].file).toBe(large.file);
expect(large.read).not.toHaveBeenCalled();
expect(empty.read).not.toHaveBeenCalled();
});
it('rejects case-insensitive duplicate file names', () => {
expect(() => selectGameFolder([
selectedFile('Game/Zone/base.ff').file,
selectedFile('Game/zone/BASE.ff').file
])).toThrow('duplicate game paths');
expect(() =>
selectGameFolder([
selectedFile('Game/Zone/base.ff').file,
selectedFile('Game/zone/BASE.ff').file
])
).toThrow('duplicate game paths');
});
it('rejects a path used as both a file and a directory regardless of case', () => {
expect(() => selectGameFolder([
selectedFile('Game/zone/english/base.ff').file,
selectedFile('Game/ZONE').file
])).toThrow('same game path for a file and a directory');
expect(() =>
selectGameFolder([
selectedFile('Game/zone/english/base.ff').file,
selectedFile('Game/ZONE').file
])
).toThrow('same game path for a file and a directory');
});
it.each(['../base.ff', './base.ff', '/Game/base.ff', 'base.ff'])(
'rejects an absent or parent root in %s', path => {
'rejects an absent or parent root in %s',
(path) => {
expect(() => selectGameFolder([selectedFile(path).file])).toThrow('one game folder');
}
);
it.each(['Game/../base.ff', 'Game/zone/./base.ff', 'Game/zone//base.ff', 'Game/zone\\base.ff'])(
'rejects traversal and non-normalized content paths in %s', path => {
expect(() => selectGameFolder([selectedFile(path).file])).toThrow('unsupported game path');
'rejects traversal and non-normalized content paths in %s',
(path) => {
expect(() => selectGameFolder([selectedFile(path).file])).toThrow(
'unsupported game path'
);
}
);
it('rejects files from multiple selected roots', () => {
expect(() => selectGameFolder([
selectedFile('T5/main/base.ff').file,
selectedFile('T6/main/base.ff').file
])).toThrow('one game folder');
expect(() =>
selectGameFolder([
selectedFile('T5/main/base.ff').file,
selectedFile('T6/main/base.ff').file
])
).toThrow('one game folder');
});
it('rejects empty selections', () => {