Skip to content

Commit

Permalink
🧠 redesign Rewrite App.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
Lord-Valen committed Jan 22, 2023
1 parent ea079d1 commit 62a7c1c
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 146 deletions.
48 changes: 24 additions & 24 deletions src/App.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import "reflect-metadata";
import { Container } from "inversify";
import { bindings } from "./bindings.js";
import { IApp, INixService, IRenderService, ITestFinder } from "./interfaces.js";
import { CliArgs, Schema, TestFile } from "./types.js";
import { CliArgs, Schema, schemaVer, TestFile } from "./types.js";

const nixService = {
run: vi.fn(() => { return {} })
Expand All @@ -25,6 +25,7 @@ describe("App", () => {

beforeAll(() => {
container = new Container();

container.load(bindings);
container.rebind(INixService).toConstantValue(nixService);
container.rebind(IRenderService).toConstantValue(renderService);
Expand All @@ -45,7 +46,7 @@ describe("App", () => {
};

registry = {
__schema: "v0.0",
__schema: schemaVer,
settings: {
list: false,
watch: false,
Expand Down Expand Up @@ -76,66 +77,65 @@ describe("App", () => {
expect(sut).toBeDefined();
});

it("runs in standalone mode when a path is given", () => {
it("runs in standalone mode when a path is given", async () => {
args.paths = ["."];

sut.run(args);
await sut.run(args);

expect(testFinder.run).toHaveBeenCalledOnce();
});

it("runs in flake mode when no path is given", () => {
it("runs in flake mode when no path is given", async () => {
nixService.run.mockReturnValueOnce(registry);
const spy = vi.spyOn(sut, "reporting").mockImplementation(() => {});

sut.run(args);
await sut.run(args);

expect(testFinder.run).toHaveBeenCalledTimes(0);
expect(spy).toHaveBeenCalledWith(args, registry.testSpec);
expect(renderService.run).toHaveBeenCalledWith(args, registry.testSpec);
});

it("runs in standalone mode when the nixt registry is inaccessible", () => {
const spy = vi.spyOn(sut, "reporting").mockImplementation(() => {});
it("runs in standalone mode when the nixt registry is inaccessible", async () => {
nixService.run.mockImplementationOnce(() => {
throw new Error("error: Dummy error");
})

sut.run(args);
await sut.run(args);

expect(testFinder.run).toHaveBeenCalledOnce();
expect(spy).toHaveBeenCalledWith(args, []);
expect(renderService.run).toHaveBeenCalledWith(args, []);
});

it("runs in standalone mode when the nixt registry is malformed", () => {
nixService.run.mockReturnValueOnce({ testSpec: "This isn't an array." });
const spy = vi.spyOn(sut, "reporting").mockImplementation(() => {});
it("runs in standalone mode when the nixt registry is malformed", async () => {
nixService.run.mockReturnValueOnce({ __schema: schemaVer, testSpec: "This isn't an array." });

sut.run(args);
await sut.run(args);

expect(testFinder.run).toHaveBeenCalledOnce();
expect(spy).toHaveBeenCalledWith(args, []);
expect(renderService.run).toHaveBeenCalledWith(args, []);
});

it("runs in standalone mode when the nixt registry uses an unsupported schema", () => {
it("runs in standalone mode when the nixt registry uses an unsupported schema", async () => {
registry.__schema = "v9001"
nixService.run.mockReturnValueOnce(registry);
const spy = vi.spyOn(sut, "reporting").mockImplementation(() => {});

sut.run(args);
await sut.run(args);

expect(testFinder.run).toHaveBeenCalledOnce();
expect(spy).toHaveBeenCalledWith(args, []);
expect(renderService.run).toHaveBeenCalledWith(args, []);
});

it("calls renderService once when watch is false", () => {
it("calls renderService once when watch is false", async () => {
args.watch = false;

sut.run(args);
await sut.run(args);

expect(renderService.run).toHaveBeenCalledOnce();
})

it("calls renderService for an initial run when watch is true", async () => {
args.watch = true;

sut.run(args);
await sut.run(args);

expect(renderService.run).toHaveBeenCalled();
})
Expand Down
88 changes: 50 additions & 38 deletions src/App.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import chokidar from "chokidar";
import chokidar, { FSWatcher } from "chokidar";
import { inject, tagged } from "inversify";
import { provide } from "inversify-binding-decorators";
import { IApp, INixService, IRenderService, ITestFinder } from "./interfaces.js";
import { CliArgs, NixOptions, schema, TestFile } from "./types.js";
import { CliArgs, Schema, schema, schemaVer, TestFile } from "./types.js";

@provide(IApp)
export class App implements IApp {
private _nixService: INixService;
private _renderService: IRenderService;
private _testFinder: ITestFinder;
private watcher: FSWatcher;

public constructor(
@inject(INixService) nixService: INixService,
Expand All @@ -18,53 +19,64 @@ export class App implements IApp {
this._nixService = nixService;
this._renderService = renderService;
this._testFinder = testFinder;
this.watcher = chokidar.watch([], false)
}

public run(args: CliArgs) {
let standalone: boolean = false;
let standaloneCause: string = "Unknown cause";
const schemaVer: string = "v0.0";
let spec: TestFile[] = [];
// TODO: Watch both test and non-test files when using registry.
// This probably entails adding `settings.watchFiles` to the schema.
// Currently, only test files are watched.
public async run(args: CliArgs) {
const schema = await this.fetchSchema(args)

const registry = schema.safeParse(this._nixService.run(".#__nixt", {} as NixOptions));
this.watcher.unwatch(args.paths);

// Run in standalone mode?
if (args.paths.length > 0) {
standalone = true;
standaloneCause = "Path provided";
} else if (registry.success === false) {
standalone = true;
args.paths = ["."];
standaloneCause = "nixt registry does not contain expected values";
} else if (registry.data.__schema !== schemaVer) {
standalone = true;
args.paths = ["."];
standaloneCause = `nixt schema version ${registry.data.__schema} is not ${schemaVer}`;
} else {
spec = registry.data.testSpec
args.paths = [];
for (const testFile of schema.testSpec) {
args.paths.push(testFile.path)
}

if (standalone === true) {
console.log(`${standaloneCause}: running in standalone mode.`);
this._testFinder.run(args)
.then((testSpec: TestFile[]) => spec = testSpec);
if (args.watch === true || schema.settings.watch === true) {
this.watcher.add(args.paths)
this.watcher.on("all", () => {
this.run(args);
});
}

// Watch?
args.watch
? this.watching(args, spec)
: this.reporting(args, spec)
this._renderService.run(args, schema.testSpec);
}

public watching(args: CliArgs, spec: TestFile[]) {
const watcher = chokidar.watch(args.paths, { ignoreInitial: true });
this.reporting(args, spec);
watcher.on("all", () => {
this.reporting(args, spec);
});
private async fetchSchema(args: CliArgs): Promise<Schema> {
let result: Schema;

try {
const registry = schema.safeParse(this._nixService.run(".#__nixt", false));

if (args.paths.length > 0 || registry.success === false || registry.data.__schema !== schemaVer) {
if (args.paths.length > 0) console.log("Path provided: standalone mode")
if (registry.success === false) console.log("Registry non-conformant: standalone mode")
else if (registry.data.__schema !== schemaVer) console.log("Schema mismatch: standalone mode")
result = await this.buildSchema(args)
} else {
result = registry.data;
}
} catch (error: any) {
console.log("Failed to access registry: standalone mode")
result = await this.buildSchema(args)
}

return result
}

public reporting(args: CliArgs, spec: TestFile[]) {
this._renderService.run(args, spec);
private async buildSchema(args: CliArgs): Promise<Schema> {
return schema.parse({
__schema: schemaVer,
settings: {
list: false,
watch: false,
verbose: false,
trace: false
},
testSpec: await this._testFinder.run(args)
})
}
}
62 changes: 38 additions & 24 deletions src/components/nix/NixService.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import "reflect-metadata";

import { Container } from "inversify";
import { resolve } from "node:path";
import { bindings } from "../../bindings.js";
import { INixService } from "../../interfaces.js";
import { execSync } from "node:child_process";

vi.mock("node:child_process", () => ({
execSync: vi.fn()
}));

describe("NixService", () => {
let container: Container;
Expand All @@ -21,38 +25,48 @@ describe("NixService", () => {

afterEach(() => {
container.restore();
vi.restoreAllMocks();
})

it("is defined", () => {
expect(sut).toBeDefined();
})

it("returns nix results", () => {
const path = resolve("examples/valid.nixt");
const expected = {
"path": path,
"suites": {
"Valid Tests": ["always passes"]
}
}

const result = sut.run("get-testspec.nix", {
trace: false,
debug: false,
args: { path: path }
it("returns an object", () => {
const expected = `{
"__schema": "v0.0",
"settings": {
"list": false,
"watch": false,
"verbose": false,
"trace": false
},
"testSpec": [{
"path": "./dummy.nix",
"suites": [{
"name": "Dummy",
"cases": [{
"name": "is a dummy suite",
"expressions": [true]
}]
}]
}]
}`

execSync.mockImplementationOnce((command: string) => {
if (command.includes(".#__nixt") === true) return expected
return {};
})

expect(result).toStrictEqual(expected)
const result = sut.run(".#__nixt", false)

expect(result).toStrictEqual(JSON.parse(expected))
})

it("throws error on invalid path", () => {
function testSut() {
sut.run("somePathWhichDoesNotExist", {
trace: false,
debug: false,
args: { path: resolve("__mocks__/valid.nixt") }
})
}
expect(testSut).toThrow(Error);
it("throws on invalid target", () => {
const dummyError = "error: Dummy error";
execSync.mockReturnValueOnce(dummyError);

expect(sut.run).toThrowError(dummyError);
})
})
17 changes: 10 additions & 7 deletions src/components/nix/NixService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { provide } from "inversify-binding-decorators";
import { execSync } from "node:child_process";
import { INixService } from "../../interfaces.js";
import { NixOptions } from "../../types.js";

const generateCallArgs = (args: {}): string[] => {
return Object
Expand All @@ -11,16 +10,20 @@ const generateCallArgs = (args: {}): string[] => {

@provide(INixService)
export class NixService implements INixService {
public run(target: string, options: NixOptions): any {
const args = options.args ? generateCallArgs(options.args).join(" ") : '';
const traceString = options.trace ? `--show-trace` : '';
const command = `nix eval --json ${traceString} ${target} ${args}'`;
public run(target: string, trace: boolean, args?: {}): any {
const _args = args ? generateCallArgs(args).join(" ") : "";
const traceString = trace ? `--show-trace` : '';
const command = `nix eval --json ${traceString} ${target} ${_args}'`;

const result = execSync(command, {
stdio: ["pipe", "pipe", "pipe"]
});
}).toString();

const parsed = JSON.parse(result.toString());
if (result.startsWith("error:") === true) {
throw new Error(result)
}

const parsed = JSON.parse(result);

return parsed;
}
Expand Down
1 change: 0 additions & 1 deletion src/components/test/TestFinder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { ITestFinder } from "../../interfaces.js";
import { CliArgs } from "../../types.js";

const defaultArgs = {
standalone: true,
paths: ["."],
watch: false,
verbose: [false, false],
Expand Down
1 change: 0 additions & 1 deletion src/components/test/TestRunner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ describe("TestRunner", () => {
beforeEach(() => {
container.snapshot();
args = {
standalone: false,
paths: [],
watch: false,
verbose: [false, false],
Expand Down
8 changes: 3 additions & 5 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { injectable } from "inversify";
import { CliArgs, NixOptions, TestFile } from "./types.js";
import { CliArgs, TestFile } from "./types.js";

@injectable()
export abstract class IApp {
abstract run(args: CliArgs): void;
abstract watching(args: CliArgs, spec: TestFile[]): void;
abstract reporting(args: CliArgs, spec: TestFile[]): void;
abstract run(args: CliArgs): Promise<void>;
}

@injectable()
Expand All @@ -25,7 +23,7 @@ export abstract class ITestRunner {

@injectable()
export abstract class INixService {
abstract run(target: string, options: NixOptions): any;
abstract run(target: string, trace: boolean, args?: {}): any;
}

@injectable()
Expand Down
Loading

0 comments on commit 62a7c1c

Please sign in to comment.