Skip to content

Commit

Permalink
fix: truncate path parts (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
vCaisim authored Sep 28, 2023
1 parent 615d792 commit 5d55028
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 10 deletions.
121 changes: 116 additions & 5 deletions packages/plugin/src/__tests__/lib.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,121 @@
import { describe, expect, it } from '@jest/globals';
import { sanitizeFilename } from '../lib';
import {
MAX_FILE_PATH_LENGTH,
isValidLength,
sanitizeFilename,
truncateMiddle,
truncatePathParts,
} from '../lib';

describe('isValidLength', () => {
const testCases: [string, number | undefined, boolean][] = [
['short.txt', undefined, true], // Valid filename with default maxLength
['a'.repeat(MAX_FILE_PATH_LENGTH), undefined, true], // Valid filename with maximum maxLength
['a'.repeat(MAX_FILE_PATH_LENGTH + 1), undefined, false], // Invalid filename exceeding maxLength
['abcdefghij', 10, true], // Valid filename with specified maxLength
['abcdefghijk', 10, false], // Invalid filename exceeding specified maxLength
['anystring.txt', 0, false], // Invalid filename with maxLength of 0
];

it.each(testCases)(
'should return the exepcted result for the provided string and max length',
(str: string, maxLength: number | undefined, expectedResult: boolean) => {
expect(isValidLength(str, maxLength)).toBe(expectedResult);
}
);
});

describe('truncateMiddle', () => {
const longString =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ac odio ac quam auctor faucibus ut id dolor. Vivamus vel odio eu ligula tempus viverra. Aenean vehicula, ex non varius euismod, elit ex cursus ex, in bibendum quam elit quis tortor. Sed volutpat scelerisque tortor quis blandit. A';
const truncatedString =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ac odio ac quam auctor faucibus ut id dolor. Vivamus vel o..., ex non varius euismod, elit ex cursus ex, in bibendum quam elit quis tortor. Sed volutpat scelerisque tortor quis blandit. A';
const testCases = [
['short.txt', MAX_FILE_PATH_LENGTH, 'short.txt'], // Short filename within maximum length
[longString, MAX_FILE_PATH_LENGTH, truncatedString], // Long filename, truncated with default separator
['short.txt', 5, 's...t'], // Short filename within a custom maximum length
['longname.txt', 8, 'lon...xt'], // Long filename truncated with custom maximum length and default separator
['filename.txt', 10, 'file...txt'], // Medium-length filename truncated with default separator
['middleseparator.txt', 15, 'middle...or.txt'], // Medium-length filename truncated with default separator
['separatoratstart.txt', 15, 'separa...rt.txt'], // Medium-length filename truncated with default separator
] as Array<[string, number, string]>;

it.each(testCases)(
'should truncate the string correctly for the provided filename and max length',
(filename, maxLength, expected) => {
const result = truncateMiddle(filename, maxLength);
expect(result).toBe(expected);
}
);

const truncatedStringWithDashes =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ac odio ac quam auctor faucibus ut id dolor. Vivamus vel od--, ex non varius euismod, elit ex cursus ex, in bibendum quam elit quis tortor. Sed volutpat scelerisque tortor quis blandit. A';

const customSeparatorTestCases = [
['short.txt', MAX_FILE_PATH_LENGTH, 'short.txt'], // Short filename within maximum length
[longString, MAX_FILE_PATH_LENGTH, truncatedStringWithDashes], // Long filename, truncated with default separator
['short.txt', 5, 'sh--t'], // Short filename within a custom maximum length
['longname.txt', 8, 'lon--txt'], // Long filename truncated with custom maximum length and default separator
['filename.txt', 10, 'file--.txt'], // Medium-length filename truncated with default separator
['middleseparator.txt', 15, 'middles--or.txt'], // Medium-length filename truncated with default separator
['separatoratstart.txt', 15, 'separat--rt.txt'], // Medium-length filename truncated with default separator
] as Array<[string, number, string]>;

it.each(customSeparatorTestCases)(
'truncates "%s" to "%s" with max length %d and custom separator',
(filename, maxLength, expected) => {
const result = truncateMiddle(filename, maxLength, '--');
expect(result).toBe(expected);
}
);
});

describe('sanitizeFilename', () => {
it('should sanitize the string', () => {
expect(sanitizeFilename('a/b\\c?d*e:f|g<h>i%jk', ' ')).toEqual(
'a b c d e f g h i jk'
);
const testCases = [
['validFilename.txt', 'validFilename.txt'], // Valid filename with no invalid characters
['file?with?question?mark.txt', 'file-with-question-mark.txt'], // Replace '?' with '-'
['file*with*asterisk.txt', 'file-with-asterisk.txt'], // Replace '*' with '-'
['file<with<less.txt', 'file-with-less.txt'], // Replace '<' with '-'
['file>with>greater.txt', 'file-with-greater.txt'], // Replace '>' with '-'
['file:with:colon.txt', 'file-with-colon.txt'], // Replace ':' with '-'
['file|with|pipe.txt', 'file-with-pipe.txt'], // Replace '|' with '-'
['file%with%percent.txt', 'file-with-percent.txt'], // Replace '%' with '-'
['file"with"doublequote.txt', 'file-with-doublequote.txt'], // Replace '"' with '-'
['file/with/slash.txt', 'file-with-slash.txt'], // Replace '/' with '-'
['file\\with\\backslash.txt', 'file-with-backslash.txt'], // Replace '\\' with '-'
[
'mixed?chars*and<slashes>and:stuff|"here%.txt',
'mixed-chars-and-slashes-and-stuff--here-.txt',
], // Replace multiple invalid characters with '-'
];

it.each(testCases)(
'should sanitize the string as expected',
(input, expected) => {
const result = sanitizeFilename(input);
expect(result).toBe(expected);
}
);
});

describe('truncatePathParts function', () => {
const longFolderName =
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu';
const truncatedLongFolderName =
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu';
const longFilename =
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq.json';
const truncatedLongFilename =
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst...yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq.json';
it.each([
['folder1/file.txt', 'folder1/file.txt'],
[
`${longFolderName}/${longFolderName}/folder3/file.txt`,
`${truncatedLongFolderName}/${truncatedLongFolderName}/folder3/file.txt`,
],
[`folder1/${longFilename}`, `folder1/${truncatedLongFilename}`],
])('should truncate path parts', (input, expected) => {
const result = truncatePathParts(input);
expect(result).toEqual(expected);
});
});
6 changes: 3 additions & 3 deletions packages/plugin/src/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
recordLogs,
} from './browserLogs';
import { createDir, readFile, removeDir, removeFile, writeFile } from './fs';
import { sanitizeFilename } from './lib';
import { sanitizeFilename, truncatePathParts } from './lib';
import { PluginOptions, TestExecutionResult } from './types';

const harDir = 'dump_har';
Expand All @@ -30,7 +30,7 @@ const createDumpFile = (data: TestExecutionResult, dumpDir: string): string => {
specDirPath,
`${sanitizeFilename(filename)}.json`
);
writeFile(resultsPath, JSON.stringify(data, null, 2));
writeFile(truncatePathParts(resultsPath), JSON.stringify(data, null, 2));
return resultsPath;
};

Expand Down Expand Up @@ -83,7 +83,7 @@ function install(
const dumpDir =
options?.targetDirectory &&
path.resolve(options.targetDirectory) !== path.resolve(harDir)
? options.targetDirectory
? truncatePathParts(options.targetDirectory)
: 'dump';

const resultsFilePath = createDumpFile(dumpData, dumpDir);
Expand Down
49 changes: 47 additions & 2 deletions packages/plugin/src/lib.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,48 @@
import path from 'path';

// https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
const invalidChars = /[/?<>\\:*|%"]/g;

export const MAX_FILE_PATH_LENGTH = 255;

export const isValidLength = (str: string, maxLength = MAX_FILE_PATH_LENGTH) =>
Buffer.from(str, 'utf-8').length <= maxLength;

export const truncateMiddle = (
filename: string,
maxLength: number = MAX_FILE_PATH_LENGTH,
separator = '...'
) => {
if (isValidLength(filename, maxLength)) return filename.trim();

const sepLen = separator.length;
const charsToShow = maxLength - sepLen;
const frontChars = Math.ceil(charsToShow / 2);
const backChars = Math.floor(charsToShow / 2);

return (
filename.substring(0, frontChars) +
separator +
filename.substring(filename.length - backChars)
);
};

export const sanitizeFilename = (filename: string, replacement = '-') =>
// https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
filename.replace(/[/?<>\\:*|%"]/g, replacement);
filename.replace(invalidChars, replacement);

export const truncatePathParts = (filename: string): string =>
filename
.split(path.sep)
.map((s, i, arr) => {
if (i === arr.length - 1) {
const ext = path.extname(s);
const filenameWithoutExt = path.parse(path.basename(s)).name;
return truncateMiddle(
filenameWithoutExt,
MAX_FILE_PATH_LENGTH - ext.length
).concat(ext);
}

return truncateMiddle(s);
})
.join(path.sep);

0 comments on commit 5d55028

Please sign in to comment.