allow snapshotshots to be save to a given file
MODIFY
packages/@romejs-runtime/rome/test.ts
packages/@romejs-runtime/rome/test.ts
* LICENSE file in the root directory of this source tree.
*/
-import {TestRunnerOptions} from '@romejs/core/master/testing/types';
-
export type AsyncFunc = () => undefined | Promise<void>;
export type SyncThrower = () => void;
@@ -38,10 +36,9 @@ export interface TestHelper {
notThrowsAsync(nonThrower: AsyncFunc, message?: string): Promise<void>;
regex(contents: string, regex: RegExp, message?: string): void;
notRegex(contents: string, regex: RegExp, message?: string): void;
- snapshot(expected: unknown, message?: string): string;
- snapshotNamed(name: string, expected: unknown, message?: string): string;
+ snapshot(expected: unknown, message?: string, fileName?: string): string;
+ snapshotNamed(name: string, expected: unknown, message?: string, fileName?: string): string;
getSnapshot(snapshotName: string): unknown;
- getOptions(): TestRunnerOptions;
}
export type TestName = string | Array<string>;

MODIFY
packages/@romejs/core/test-worker/SnapshotManager.ts
packages/@romejs/core/test-worker/SnapshotManager.ts
* LICENSE file in the root directory of this source tree.
*/
-import {AbsoluteFilePath} from '@romejs/path';
-import {writeFile, readFileText, exists, unlink} from '@romejs/fs';
+import {AbsoluteFilePath, AbsoluteFilePathSet, createAbsoluteFilePath} from '@romejs/path';
+import {writeFile, readFileText, exists, unlink, readdir, lstat} from '@romejs/fs';
import {TestRunnerOptions} from '../master/testing/types';
import TestWorkerRunner from './TestWorkerRunner';
import {descriptions, DiagnosticDescription} from '@romejs/diagnostics';
@@ -24,6 +24,11 @@ function cleanHeading(key: string): string {
return key.trim();
}
+type SnapshotEntry = Map<string, Map<string, {
+ language: undefined | string;
+ value: string;
+}>>;
+
export default class SnapshotManager {
constructor(runner: TestWorkerRunner, testPath: AbsoluteFilePath) {
this.path = testPath.getParent().append(
@@ -38,14 +43,14 @@ export default class SnapshotManager {
this.raw = '';
this.entries = new Map();
+ this.outputFiles = new Map();
+ this.outputFiles.set(this.path.join(), this.path);
}
testPath: AbsoluteFilePath;
path: AbsoluteFilePath;
- entries: Map<string, Map<string, {
- language: undefined | string;
- value: string;
- }>>;
+ entries: Map<string, SnapshotEntry>;
+ outputFiles: Map<string, AbsoluteFilePath>;
runner: TestWorkerRunner;
options: TestRunnerOptions;
@@ -62,9 +67,33 @@ export default class SnapshotManager {
});
}
+ async loadSnapshotFixtures(dir: AbsoluteFilePath) {
+ const filenames: AbsoluteFilePathSet = await readdir(dir);
+ for (const filename of filenames) {
+ const stats = await lstat(filename);
+ if (stats.isFile() && filename.join().endsWith('.test.md')) {
+ await this.loadSnapshot(filename);
+ } else if (stats.isDirectory()) {
+ await this.loadSnapshotFixtures(filename);
+ }
+ }
+ }
+
async load() {
- const {path: snapshotFilename} = this;
- if (!(await exists(snapshotFilename))) {
+ const { path: snapshotFile } = this;
+ if ((await exists(snapshotFile))) {
+ await this.loadSnapshot(snapshotFile);
+ } else {
+ // checks if there are fixtures and load them
+ const fixtureDir = snapshotFile.getParent().append('test-fixtures');
+ if ((await exists(fixtureDir))) {
+ await this.loadSnapshotFixtures(fixtureDir);
+ }
+ }
+ }
+
+ async loadSnapshot(snapshotFile: AbsoluteFilePath) {
+ if (!(await exists(snapshotFile))) {
return;
}
@@ -75,9 +104,9 @@ export default class SnapshotManager {
return;
}
- const file = await readFileText(snapshotFilename);
+ const file = await readFileText(snapshotFile);
const parser = createSnapshotParser({
- path: snapshotFilename,
+ path: snapshotFile,
input: file,
});
this.raw = parser.input;
@@ -121,6 +150,7 @@ export default class SnapshotManager {
snapshotName,
language: codeBlock.language,
value: codeBlock.text,
+ snapshotFile,
});
continue;
}
@@ -133,6 +163,7 @@ export default class SnapshotManager {
snapshotName: '0',
language: node.language,
value: node.text,
+ snapshotFile,
});
}
@@ -144,27 +175,7 @@ export default class SnapshotManager {
}
}
- async save() {
- const {path} = this;
-
- // If there'a s focused test then we don't write or validate a snapshot
- if (this.runner.hasFocusedTest) {
- return;
- }
-
- // No point producing an empty snapshot file
- if (this.entries.size === 0) {
- if (this.exists) {
- if (this.options.freezeSnapshots) {
- await this.emitDiagnostic(descriptions.SNAPSHOTS.REDUNDANT);
- } else {
- // Remove the snapshot file as there were none ran
- await unlink(path);
- }
- }
- return;
- }
-
+ buildSnapShot(snapshotEntries: SnapshotEntry): Array<string> {
// Build the snapshot
let lines: Array<string> = [];
@@ -183,10 +194,10 @@ export default class SnapshotManager {
pushNewline();
// Get test names and sort them so they are in a predictable
- const testNames = Array.from(this.entries.keys()).sort();
+ const testNames = Array.from(snapshotEntries.keys()).sort();
for (const testName of testNames) {
- const entries = this.entries.get(testName);
+ const entries = snapshotEntries.get(testName);
if (entries === undefined) {
throw new Error('Impossible');
}
@@ -219,7 +230,35 @@ export default class SnapshotManager {
pushNewline();
}
}
+ return lines;
+ }
+
+ async save() {
+
+ // If there'a s focused test then we don't write or validate a snapshot
+ if (this.runner.hasFocusedTest) {
+ return;
+ }
+ // No point producing an empty snapshot file
+ if (this.entries.size === 0) {
+ if (this.exists) {
+ if (this.options.freezeSnapshots) {
+ await this.emitDiagnostic(descriptions.SNAPSHOTS.REDUNDANT);
+ } else {
+ // Remove the snapshot file as there were none ran
+ await unlink(this.path);
+ }
+ }
+ return;
+ }
+
+ for (const snapshotFilename of this.entries.keys()) {
+ const entries = this.entries.get(snapshotFilename);
+ if (entries === undefined) {
+ continue;
+ }
+ const lines = this.buildSnapShot(entries);
const formatted = lines.join('\n');
if (this.options.freezeSnapshots) {
@@ -233,18 +272,28 @@ export default class SnapshotManager {
}
} else if (formatted !== this.raw) {
// Save the file
- await writeFile(path, formatted);
+ const snapshotFile = this.outputFiles.get(snapshotFilename);
+ if (snapshotFile !== undefined) {
+ await writeFile(snapshotFile, formatted);
+ }
+ }
}
}
- get(testName: string, snapshotName: string): undefined | string {
- const entries = this.entries.get(testName);
+ get(testName: string, snapshotName: string, snapshotFile?: AbsoluteFilePath): undefined | string {
+ if (snapshotFile === undefined) {
+ snapshotFile = this.path;
+ }
+ const entriesByFiles = this.entries.get(snapshotFile.join());
+ if (entriesByFiles !== undefined) {
+ const entries = entriesByFiles.get(testName);
if (entries !== undefined) {
const entry = entries.get(snapshotName);
if (entry !== undefined) {
return entry.value;
}
}
+ }
return undefined;
}
@@ -253,18 +302,34 @@ export default class SnapshotManager {
language,
testName,
snapshotName,
+ snapshotFile,
}: {
value: string;
language: undefined | string;
testName: string;
snapshotName: string;
+ snapshotFile?: AbsoluteFilePath;
}) {
- let entries = this.entries.get(testName);
+ if (snapshotFile === undefined) {
+ snapshotFile = this.path;
+ }
+ const snapshotFileName = snapshotFile.join();
+ let snapshotsForFile = this.entries.get(snapshotFileName);
+ if (snapshotsForFile === undefined) {
+ snapshotsForFile = new Map();
+ this.entries.set(snapshotFileName, snapshotsForFile);
+ }
+
+ let entries = snapshotsForFile.get(testName);
if (entries === undefined) {
entries = new Map();
- this.entries.set(testName, entries);
+ snapshotsForFile.set(testName, entries);
}
entries.set(snapshotName, {value, language});
+
+ if (!this.outputFiles.has(snapshotFileName)) {
+ this.outputFiles.set(snapshotFileName, snapshotFile);
+ }
}
}

MODIFY
packages/@romejs/core/test-worker/TestAPI.ts
packages/@romejs/core/test-worker/TestAPI.ts
@@ -24,6 +24,8 @@ import {
SyncThrower,
ExpectedError,
} from '@romejs-runtime/rome/test';
+import { maybeCreateAbsoluteFilePath, createAbsoluteFilePath } from '@romejs/path';
+import { existsSync } from '@romejs/fs';
function formatExpectedError(expected: ExpectedError): string {
if (typeof expected === 'string') {
@@ -428,13 +430,13 @@ export default class TestAPI implements TestHelper {
throw new Error('unimplemented');
}
- snapshot(expected: unknown, message?: string): string {
+ snapshot(expected: unknown, message?: string, fileName?: string): string {
const id = this.snapshotCounter++;
- return this._snapshotNamed(String(id), expected, message, 2);
+ return this._snapshotNamed(String(id), expected, message, 2, fileName);
}
- snapshotNamed(name: string, expected: unknown, message?: string): string {
- return this._snapshotNamed(name, expected, message, 1);
+ snapshotNamed(name: string, expected: unknown, message?: string, fileName?: string): string {
+ return this._snapshotNamed(name, expected, message, 1, fileName);
}
getSnapshot(snapshotName: string): unknown {
@@ -445,11 +447,26 @@ export default class TestAPI implements TestHelper {
return this.options;
}
+ _normalizeFileName(fileName: string): string {
+ if (!fileName.endsWith('test.md')) {
+ const lastIndex = fileName.lastIndexOf('.');
+ let baseName = undefined;
+ if (lastIndex === - 1) { // extensionless file
+ baseName = fileName;
+ } else {
+ baseName = fileName.substring(0, lastIndex);
+ }
+ return `${baseName}.test.md`;
+ }
+ return fileName;
+ }
+
_snapshotNamed(
name: string,
expected: unknown,
message?: string,
framesToPop?: number,
+ fileName?: string,
): string {
let language: undefined | string;
@@ -461,8 +478,14 @@ export default class TestAPI implements TestHelper {
formatted = prettyFormat(expected);
}
+ let snapshotFile = undefined;
+ if (fileName !== undefined) {
+ fileName = this._normalizeFileName(fileName);
+ snapshotFile = createAbsoluteFilePath(fileName);
+ }
+
// Get the current snapshot
- const existingSnapshot = this.snapshotManager.get(this.testName, name);
+ const existingSnapshot = this.snapshotManager.get(this.testName, name, snapshotFile);
if (existingSnapshot === undefined) {
// No snapshot exists, let's save this one!
this.snapshotManager.set({
@@ -470,6 +493,7 @@ export default class TestAPI implements TestHelper {
snapshotName: String(name),
value: formatted,
language,
+ snapshotFile,
});
return name;
}

MODIFY
packages/@romejs/js-parser/index.test.ts
packages/@romejs/js-parser/index.test.ts
* LICENSE file in the root directory of this source tree.
*/
-import {escapeMarkup} from '@romejs/string-markup';
import {parseJS} from '@romejs/js-parser';
import {createFixtureTests} from '@romejs/test-helpers';
-import prettyFormat from '@romejs/pretty-format';
import {ConstProgramSyntax} from '@romejs/js-ast';
-import {writeFileSync} from '@romejs/fs';
import {removeCarriageReturn} from '@romejs/string-utils';
+import { createAbsoluteFilePath } from '@romejs/path';
const promise = createFixtureTests(
(fixture, t) => {
@@ -26,13 +24,6 @@ const promise = createFixtureTests(
);
}
- // Normalize the AST file
- const outputFile = files.get('output.txt');
- let outputContent = undefined;
- if (outputFile !== undefined) {
- outputContent = removeCarriageReturn(outputFile.content.toString());
- }
-
const sourceTypeProp = options.get('sourceType');
const sourceType = sourceTypeProp.asString('script');
if (sourceType !== 'module' && sourceType !== 'script') {
@@ -69,15 +60,6 @@ const promise = createFixtureTests(
});
const inputContent = removeCarriageReturn(inputFile.content.toString());
- t.addToAdvice({
- type: 'log',
- category: 'info',
- message: 'Input',
- });
- t.addToAdvice({
- type: 'code',
- code: inputContent,
- });
const ast = parseJS({
input: inputContent,
@@ -87,80 +69,10 @@ const promise = createFixtureTests(
syntax,
});
- let expectError = options.get('throws').asStringOrVoid();
- if (expectError !== undefined) {
- expectError = escapeMarkup(expectError);
- }
-
- let {diagnostics} = ast;
- if (diagnostics.length > 0) {
- diagnostics = diagnostics.map((diag) => {
- return {
- ...diag,
- code: inputContent,
- };
- });
-
- if (expectError === undefined) {
- /*throw new DiagnosticsError(
- "Parser has diagnostics when we didn't expect any",
- diagnostics,
- );*/} else {
- // TODO
- if (expectError === 'Unexpected token') {
- return;
- }
-
- let matches = false;
- for (const diag of diagnostics) {
- if (escapeMarkup(diag.description.message.value).includes(expectError)) {
- matches = true;
- break;
- }
- }
-
- if (matches) {
- return;
- } else {
- let msg = `No diagnostic matched expected message of "${escapeMarkup(
- expectError,
- )}"`;
- if (diagnostics.length === 1) {
- msg += ` but got "${escapeMarkup(
- diagnostics[0].description.message.value,
- )}"`;
- }
- msg;
- //throw new DiagnosticsError(msg, diagnostics);
- return;
- }
- }
- }
-
- // If we have an output then compare it with the parsed input
- const runnerOptions = t.getOptions();
- if (outputContent !== undefined && !runnerOptions.updateSnapshots) {
- t.is(prettyFormat(ast), outputContent.trim());
- }
-
- // If we expected an error to thrown it should have already
- if (expectError !== undefined) {
- // TODO
-
- /*throw new Error(
- `Expected an error to be thrown of "${escapeMarkup(expectError)}" in ${
- inputFile.absolute
- } but there was none`,
- );*/
- return;
- }
-
- if (outputContent === undefined || runnerOptions.updateSnapshots) {
- // If we didn't expect an error to be thrown and we have no expected output AST
-
- // then we should write it
- writeFileSync(fixture.dir.append('output.txt'), prettyFormat(ast));
- }
+ const outputFile = createAbsoluteFilePath(inputFile.absolute.getParent().append(
+ `${inputFile.absolute.getExtensionlessBasename()}.test.md`,
+ ).join());
+ t.snapshot(ast, undefined, outputFile.join());
},
);

allow snapshotshots to be save to a given file