Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,14 @@ jobs:
if: steps.test_units.outcome == 'failure'
run: |
echo "::notice::Run \`npm run test:unit\` in your dev environment for a detailed report about failed unit tests"
exit 1
- name: Ensure all units are covered
if: steps.changed-files.outputs.any_changed == 'true'
id: test_unit_missing
run: npm run test:unit:missing
continue-on-error: true
- name: If there are missing unit tests, highlight debug tools
if: steps.test_unit_missing.outcome == 'failure'
run: |
echo "::notice::Run \`npm run test:unit -- run && npm run test:unit:missing\` in your dev environment to see which units are missing a test companion"
exit 1
21 changes: 21 additions & 0 deletions @internal/Directory/createAt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
mkdirSync,
} from 'node:fs';

import type FilesystemPath from '../FilesystemPath';

import {
serialized,
} from '../FilesystemPath';

const Directory_createAt = (
givenPath: FilesystemPath,
): void => {
mkdirSync(serialized(givenPath), {
recursive: true,
});
};

export {
Directory_createAt,
};
3 changes: 3 additions & 0 deletions @internal/Directory/definition.assembled.members.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export {
Directory_createAt as createAt,
} from './createAt';
1 change: 1 addition & 0 deletions @internal/Directory/definition.assembled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as Directory from './definition.assembled.members.ts';
1 change: 1 addition & 0 deletions @internal/Directory/exports.object.primary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './definition.assembled.ts';
3 changes: 3 additions & 0 deletions @internal/Directory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export {
Directory as default,
} from './exports.object.primary.ts';
25 changes: 25 additions & 0 deletions @internal/File/contentsReadFrom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
readFileSync,
} from 'node:fs';

import type FilesystemPath from '../FilesystemPath';

import {
serialized,
} from '../FilesystemPath';

const File_contentsReadAt = (
givenPath: FilesystemPath,
): string => {
const someAbsolutePath = serialized(givenPath);

const contentsFromGivenPath = readFileSync(someAbsolutePath, {
encoding: 'utf8',
});

return contentsFromGivenPath;
};

export {
File_contentsReadAt as File_contentsReadFrom,
};
11 changes: 11 additions & 0 deletions @internal/File/definition.assembled.members.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export {
File_doesExistAt as doesExistAt,
} from './doesExistAt';

export {
File_contentsReadFrom as contentsReadFrom,
} from './contentsReadFrom';

export {
File_write as write,
} from './write';
1 change: 1 addition & 0 deletions @internal/File/definition.assembled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as File from './definition.assembled.members.ts';
20 changes: 20 additions & 0 deletions @internal/File/doesExistAt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
existsSync,
} from 'node:fs';

import type FilesystemPath from '../FilesystemPath';

import {
serialized,
} from '../FilesystemPath';

const File_doesExistAt = (
givenPath: FilesystemPath,
): boolean => {
const derivedAbsolutePath = serialized(givenPath);
return existsSync(derivedAbsolutePath);
};

export {
File_doesExistAt,
};
1 change: 1 addition & 0 deletions @internal/File/exports.object.primary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './definition.assembled.ts';
3 changes: 3 additions & 0 deletions @internal/File/exports.toolbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './toPath';

export * from './contentsReadFrom';
5 changes: 5 additions & 0 deletions @internal/File/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export {
File as default,
} from './exports.object.primary.ts';

export * from './exports.toolbox.ts';
16 changes: 16 additions & 0 deletions @internal/File/toPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {
fileURLToPath,
} from 'node:url';

import FilesystemPath from '../FilesystemPath';

const toPath = (
givenFile: URL,
): FilesystemPath => {
const absolutePathToFile = fileURLToPath(givenFile);
return FilesystemPath.parsedFrom(absolutePathToFile);
};

export {
toPath,
};
27 changes: 27 additions & 0 deletions @internal/File/write.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
writeFileSync,
} from 'node:fs';

import type FilesystemPath from '../FilesystemPath';

import {
serialized,
} from '../FilesystemPath';

const File_write = (
{
contents: givenContents,
to: givenPath,
}: {
contents: string;
to: FilesystemPath;
},
): void => {
writeFileSync(serialized(givenPath), givenContents, {
encoding: 'utf8',
});
};

export {
File_write,
};
29 changes: 29 additions & 0 deletions @internal/FilesystemPath/definition.declared.augmentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
FilesystemPath,
} from './definition.declared.ts';

import {
FilesystemPath_from,
} from './from.ts';

import {
FilesystemPath_parsedFrom,
} from './parsedFrom.ts';

import {
FilesystemPath_resolved,
} from './resolved.ts';

FilesystemPath.from /* */ = FilesystemPath_from;
FilesystemPath.parsedFrom /**/ = FilesystemPath_parsedFrom;
FilesystemPath.resolved /* */ = FilesystemPath_resolved;

declare module './definition.declared.ts' {
namespace FilesystemPath {
export {
FilesystemPath_from /* */ as from,
FilesystemPath_parsedFrom /**/ as parsedFrom,
FilesystemPath_resolved /* */ as resolved,
};
}
}
17 changes: 17 additions & 0 deletions @internal/FilesystemPath/definition.declared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type {
ParsedPath,
} from 'node:path';

type FilesystemPath = ParsedPath;

function FilesystemPath(
namespaceOnly: never = (() => {
throw new Error(`Unexpected call of module augmentation provision for ${FilesystemPath.name}.`); // eslint-disable-line no-restricted-syntax -- TODO use `Attempt` instead
})(),
) {
return namespaceOnly;
}

export {
FilesystemPath,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import './definition.declared.augmentation.ts';

export * from './definition.declared.ts';
1 change: 1 addition & 0 deletions @internal/FilesystemPath/exports.object.primary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './definition.declared.withAugmentation.ts';
1 change: 1 addition & 0 deletions @internal/FilesystemPath/exports.toolbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './serialized';
39 changes: 39 additions & 0 deletions @internal/FilesystemPath/from.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type {
FilesystemPath,
} from './definition.declared.ts';

const FilesystemPath_from = (
givenPath: FilesystemPath,
{
name: nameMutatedFrom = $0 => $0,
extension: extensionMutatedFrom = $0 => $0,
}: {
name?: (
originalName: string
) => string;
extension?: (
originalExtension: string
) => string;
},
): FilesystemPath => {
const mutablePath = {
...givenPath,
};

mutablePath.name = nameMutatedFrom(mutablePath.name);
const mutatedExtension = extensionMutatedFrom(mutablePath.ext);

if (
(0 < mutatedExtension.length)
&& !mutatedExtension.startsWith('.')
) throw new Error(`Extension must start with a period when not empty: ${mutatedExtension}`); // eslint-disable-line no-restricted-syntax -- TODO replace with `Attempt`

mutablePath.ext = mutatedExtension;
mutablePath.base = `${mutablePath.name}${mutablePath.ext}`;

return mutablePath;
};

export {
FilesystemPath_from,
};
5 changes: 5 additions & 0 deletions @internal/FilesystemPath/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export {
FilesystemPath as default,
} from './exports.object.primary.ts';

export * from './exports.toolbox.ts';
3 changes: 3 additions & 0 deletions @internal/FilesystemPath/parsedFrom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export {
parse as FilesystemPath_parsedFrom,
} from 'node:path';
33 changes: 33 additions & 0 deletions @internal/FilesystemPath/resolved.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
resolve,
} from 'node:path';

import type {
FilesystemPath,
} from './definition.declared.ts';

import {
FilesystemPath_parsedFrom,
} from './parsedFrom';

import {
serialized,
} from './serialized';

const FilesystemPath_resolved = (
{
from: givenRoot,
to: givenPathFromTargetToRoot,
}: {
from: FilesystemPath;
to: string;
},
): FilesystemPath => {
const absolutePathToRoot = serialized(givenRoot);
const absolutePathToTarget = resolve(absolutePathToRoot, givenPathFromTargetToRoot);
return FilesystemPath_parsedFrom(absolutePathToTarget);
};

export {
FilesystemPath_resolved,
};
3 changes: 3 additions & 0 deletions @internal/FilesystemPath/serialized.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export {
format as serialized,
} from 'node:path';
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"test:unit": "vitest",
"test:unit:missing": "jiti vitest/ensure-all-units-have-test-companion.ts",
"prepare": "cypress install && husky",
"test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
Expand Down
74 changes: 74 additions & 0 deletions scripts/stubbed-test-suites/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import Directory from '../../@internal/Directory';
import File from '../../@internal/File';
import FilesystemPath from '../../@internal/FilesystemPath';

// take a given file path and generate a stub for a test suite
function createAndSaveTestCompanionForUnitWith(
givenAbsolutePath: string,
) {
const componentsOfAbsolutePath = givenAbsolutePath.split('/');
const fileNameWithExtension = componentsOfAbsolutePath.pop() ?? '';
const [fileName, fileExtension] = fileNameWithExtension.split('.');

if (
!fileName || !fileExtension
) throw new Error(`Could not parse file name and extension from: ${givenAbsolutePath}`); // eslint-disable-line no-restricted-syntax -- "@/library/Attempt is not accessible outside of src"

const testFileName = `${fileName}.test.${fileExtension}`;
const absolutePathToTestCompanion = [...componentsOfAbsolutePath, testFileName].join('/');
const symbolName = 'someSymbol';

const contentsOfTestCompanion = `import {
describe,
} from 'vitest';

import {
${symbolName},
} from './${fileNameWithExtension}';

describe.todo(${symbolName});
`;

const pathToTestCompanion = FilesystemPath.parsedFrom(absolutePathToTestCompanion);
const pathToDirectoryOfTestCompanion = FilesystemPath.parsedFrom(pathToTestCompanion.dir);

if (
!File.doesExistAt(pathToDirectoryOfTestCompanion)
) Directory.createAt(pathToDirectoryOfTestCompanion);

const testCompanionShouldBeCreated = !File.doesExistAt(pathToTestCompanion);

if (
testCompanionShouldBeCreated
) File.write({
contents: contentsOfTestCompanion,
to : pathToTestCompanion,
});

return {
created : testCompanionShouldBeCreated,
pathForTestFile: absolutePathToTestCompanion,
};
}

const inputPath = process.argv[2];

if (!inputPath) {
console.error('Usage: npx jiti scripts/generate-stub-for-test-suite.ts <path-to-source-file.ts>');
process.exit(1);
}

// eslint-disable-next-line no-restricted-syntax -- "@/library/Attempt is not accessible outside of src"
try {
const result = createAndSaveTestCompanionForUnitWith(inputPath);

const messageForResult = result.created
? `Created test stub: ${result.pathForTestFile}`
: `Test file already exists: ${result.pathForTestFile}`;

console.log(messageForResult);
}
catch (err) {
console.error('Error:', (err as Error).message);
process.exit(1);
}
Loading
Loading