Repository Public
1import { describe, expect, it } from 'vitest';2import { extOf, nameKey, nextFreeName, previewKind, safePathSegments, sanitizeName, splitExt } from '../src/index.ts';34describe('sanitizeName', () => {5 it('neutralises paths, control characters and reserved names', () => {6 expect(sanitizeName('../../etc/passwd')).toBe('_.._etc_passwd');7 expect(sanitizeName('..')).toBe('sans-nom');8 expect(sanitizeName('.htaccess')).toBe('htaccess');9 expect(sanitizeName('a\u0000b\nc.txt')).toBe('a_b_c.txt');10 expect(sanitizeName('C:\\Windows\\cmd.exe')).toBe('C__Windows_cmd.exe');11 expect(sanitizeName('CON.txt')).toBe('_CON.txt');12 expect(sanitizeName(' rapport final .pdf. ')).toBe('rapport final .pdf');13 expect(sanitizeName('facture\u202Efdp.exe')).toBe('facture_fdp.exe'); // right-to-left override trick14 expect(sanitizeName('')).toBe('sans-nom');15 });16 it('never returns a separator or a name over 255 bytes', () => {17 const long = sanitizeName(`${'é'.repeat(400)}.pdf`);18 expect(new TextEncoder().encode(long).length).toBeLessThanOrEqual(255);19 expect(long.endsWith('.pdf')).toBe(true);20 for (const raw of ['a/b', 'a\\b', '/abs', '~/x', 'x/../../y']) expect(sanitizeName(raw)).not.toMatch(/[/\\]/);21 });22 it('normalises Unicode so two spellings of one name collide', () => {23 expect(nameKey(sanitizeName('Cafe\u0301.TXT'))).toBe(nameKey(sanitizeName('Café.txt')));24 });25});26describe('names', () => {27 it('splits extensions', () => {28 expect(splitExt('archive.tar.gz')).toEqual(['archive', '.tar.gz']);29 expect(splitExt('README')).toEqual(['README', '']);30 expect(extOf('Photo.HEIC')).toBe('heic');31 });32 it('finds the next free name', () => {33 const taken = new Set(['a.txt', 'a (2).txt']);34 expect(nextFreeName('a.txt', (k) => taken.has(k))).toBe('a (3).txt');35 expect(nextFreeName('b.txt', (k) => taken.has(k))).toBe('b.txt');36 });37 it('keeps relative paths inside the tree', () => {38 expect(safePathSegments('../../a/./b/../c.txt')).toEqual(['a', 'b', 'c.txt']);39 expect(safePathSegments('/etc/passwd')).toEqual(['etc', 'passwd']);40 });41 it('classifies previews by name', () => {42 expect(previewKind('x.pdf')).toBe('pdf'); expect(previewKind('x.HEIC')).toBe('heic'); expect(previewKind('x.tar.gz')).toBe('archive');43 expect(previewKind('x.gz')).toBe('none'); expect(previewKind('x.docx')).toBe('office'); expect(previewKind('x.xlsx')).toBe('sheet'); expect(previewKind('x.bin')).toBe('none');44 });45});46