Skip to content
This repository was archived by the owner on Apr 13, 2025. It is now read-only.

Commit 666bb63

Browse files
committed
Test generate module
1 parent 2818e44 commit 666bb63

File tree

7 files changed

+189
-16
lines changed

7 files changed

+189
-16
lines changed

src/generate/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,15 @@ export const generateModule: CommandModule = {
6565
* is not a dev install and has some services installed that can be used.
6666
* Throws an error if the installation cannot be used to generate a bundle with an explanation.
6767
*/
68-
function ensureValidInstallation(install: Installation | undefined): install is ProductionInstallation {
68+
export function ensureValidInstallation(install: Installation | undefined): install is ProductionInstallation {
6969
if (install === undefined) {
7070
throw new Error(
7171
"nodecg-io is not installed to your local nodecg install.\n" +
7272
`Please install it first using this command: ${yellowInstallCommand}`,
7373
);
7474
} else if (install.dev) {
7575
throw new Error(`You cannot use ${yellowGenerateCommand} together with a development installation.`);
76-
} else if (install.packages.length === corePackages.length) {
76+
} else if (install.packages.length <= corePackages.length) {
7777
// just has core packages without any services installed.
7878
throw new Error(
7979
`You first need to have at least one service installed to generate a bundle.\n` +

src/generate/prompt.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,10 @@ function validateServiceSelection(services: string[]): true | string {
136136
}
137137
}
138138

139-
function computeGenOptsFields(opts: PromptedGenerationOptions, install: ProductionInstallation): GenerationOptions {
139+
export function computeGenOptsFields(
140+
opts: PromptedGenerationOptions,
141+
install: ProductionInstallation,
142+
): GenerationOptions {
140143
const corePkg = install.packages.find((pkg) => pkg.name === corePackage);
141144
if (corePkg === undefined) {
142145
throw new Error("Core package in installation info could not be found.");

src/utils/npm.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,10 @@ export async function downloadNpmPackage(pkg: NpmPackage, nodecgIODir: string):
172172
/**
173173
* Installs npm production dependencies in the passed path by running npm install --prod in the directory.
174174
* @param path the path where a package.json is present
175-
* @param prod whether to only install production dependencies or also devDependencies.
175+
* @param onlyProd whether to only install production dependencies or also devDependencies.
176176
*/
177-
export async function runNpmInstall(path: string, prod: boolean): Promise<void> {
178-
const prodArg = prod ? ["--prod"] : [];
177+
export async function runNpmInstall(path: string, onlyProd: boolean): Promise<void> {
178+
const prodArg = onlyProd ? ["--prod"] : [];
179179
await executeCommand("npm", ["install", ...prodArg], path);
180180
}
181181

test/generate/index.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { vol } from "memfs";
2+
import {
3+
corePkg,
4+
fsRoot,
5+
nodecgPackageJson,
6+
nodecgPackageJsonStr,
7+
twitchChatPkg,
8+
validDevInstall,
9+
validProdInstall,
10+
} from "../testUtils";
11+
import { SemVer } from "semver";
12+
import * as path from "path";
13+
import * as installation from "../../src/utils/installation";
14+
import * as fsUtils from "../../src/utils/fs";
15+
import * as npm from "../../src/utils/npm";
16+
import { ensureValidInstallation, generateBundle } from "../../src/generate";
17+
import { computeGenOptsFields, GenerationOptions, PromptedGenerationOptions } from "../../src/generate/prompt";
18+
19+
const defaultOptsPrompt: PromptedGenerationOptions = {
20+
bundleName: "test-bundle",
21+
bundleDir: path.join(fsRoot, "bundles"),
22+
description: "Hello, this is a description for a test bundle.",
23+
version: new SemVer("0.1.0"),
24+
services: [twitchChatPkg.path.replace("nodecg-io-", "")],
25+
language: "typescript",
26+
graphic: false,
27+
dashboard: false,
28+
};
29+
30+
const defaultOpts = computeGenOptsFields(defaultOptsPrompt, validProdInstall);
31+
const jsOpts: GenerationOptions = { ...defaultOpts, language: "javascript" };
32+
const nodecgPackageJsonPath = path.join(fsRoot, "package.json");
33+
const packageJsonPath = path.join(defaultOpts.bundlePath, "package.json");
34+
const tsConfigPath = path.join(defaultOpts.bundlePath, "tsconfig.json");
35+
36+
jest.spyOn(installation, "readInstallInfo").mockResolvedValue(validProdInstall);
37+
jest.spyOn(fsUtils, "executeCommand").mockResolvedValue();
38+
jest.spyOn(npm, "getLatestPackageVersion").mockResolvedValue(new SemVer("1.2.3"));
39+
40+
jest.mock("fs", () => vol);
41+
afterEach(() => vol.reset());
42+
beforeEach(async () => {
43+
await vol.promises.mkdir(defaultOpts.bundleDir);
44+
await vol.promises.mkdir(defaultOpts.bundlePath);
45+
await vol.promises.writeFile(nodecgPackageJsonPath, nodecgPackageJsonStr);
46+
});
47+
48+
describe("ensureValidInstallation", () => {
49+
test("should not throw when passing install capable of generating bundles", () => {
50+
expect(ensureValidInstallation(validProdInstall)).toBe(true);
51+
});
52+
53+
test("should throw when passing undefined", () => {
54+
expect(() => ensureValidInstallation(undefined)).toThrow("not installed");
55+
});
56+
57+
test("should throw when passing a development installation", () => {
58+
expect(() => ensureValidInstallation(validDevInstall)).toThrow("development installation");
59+
});
60+
61+
test("should throw when passing install with no services", () => {
62+
expect(() => ensureValidInstallation({ ...validProdInstall, packages: [corePkg] })).toThrow(
63+
"at least one service",
64+
);
65+
});
66+
});
67+
68+
describe("generateBundle", () => {
69+
test("should fail if bundle directory already contains files", async () => {
70+
// Create some file inside the directory in which the bundle would be generated.
71+
await vol.promises.writeFile(packageJsonPath, "");
72+
await expect(generateBundle(fsRoot, defaultOpts, validProdInstall)).rejects.toThrow(
73+
"already exists and contains files",
74+
);
75+
});
76+
77+
test("should install dependencies", async () => {
78+
const installMock = jest.spyOn(npm, "runNpmInstall").mockResolvedValue();
79+
await generateBundle(fsRoot, defaultOpts, validProdInstall);
80+
81+
expect(installMock).toHaveBeenCalled();
82+
expect(installMock).toHaveBeenCalledWith(defaultOpts.bundlePath, false);
83+
});
84+
85+
test("should run build if typescript", async () => {
86+
const buildMock = jest.spyOn(npm, "runNpmBuild").mockClear().mockResolvedValue();
87+
await generateBundle(fsRoot, defaultOpts, validProdInstall);
88+
expect(buildMock).toHaveBeenCalledTimes(1);
89+
expect(buildMock).toHaveBeenCalledWith(defaultOpts.bundlePath);
90+
});
91+
92+
test("should not run build if javascript", async () => {
93+
const buildMock = jest.spyOn(npm, "runNpmBuild").mockClear().mockResolvedValue();
94+
await generateBundle(fsRoot, jsOpts, validProdInstall);
95+
expect(buildMock).toHaveBeenCalledTimes(0);
96+
});
97+
});
98+
99+
describe("genTSConfig", () => {
100+
test("should generate tsconfig if typescript", async () => {
101+
await generateBundle(fsRoot, defaultOpts, validProdInstall);
102+
expect(vol.existsSync(tsConfigPath)).toBe(true);
103+
});
104+
105+
test("should not generate tsconfig if javascript", async () => {
106+
await generateBundle(fsRoot, jsOpts, validProdInstall);
107+
expect(vol.existsSync(tsConfigPath)).toBe(false);
108+
});
109+
});
110+
111+
describe("genPackageJson", () => {
112+
// We don't have a good type for a package.json and this is only testing code so this should be fine.
113+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
114+
async function genPackageJSON(opts: GenerationOptions = defaultOpts): Promise<any> {
115+
await generateBundle(fsRoot, opts, validProdInstall);
116+
const packageJsonStr = vol.toJSON()[packageJsonPath];
117+
if (!packageJsonStr) throw new Error("package.json does not exist");
118+
return JSON.parse(packageJsonStr.toString());
119+
}
120+
121+
test("should have correct basic information", async () => {
122+
const packageJson = await genPackageJSON();
123+
124+
expect(packageJson["name"]).toBe(defaultOpts.bundleName);
125+
expect(packageJson["version"]).toBe(defaultOpts.version.toString());
126+
expect(packageJson["nodecg"]["compatibleRange"]).toBeDefined();
127+
expect(packageJson["nodecg"]["bundleDependencies"][twitchChatPkg.name]).toBe(`^${twitchChatPkg.version}`);
128+
});
129+
130+
test("should have only nodecg-io-core dependency if javascript", async () => {
131+
const deps = (await genPackageJSON(jsOpts))["dependencies"];
132+
133+
expect(Object.keys(deps).length).toBe(1);
134+
expect(Object.entries(deps)[0]).toStrictEqual([corePkg.name, `^${corePkg.version}`]);
135+
});
136+
137+
test("should have all required typing packages as dependency if typescript", async () => {
138+
const deps = (await genPackageJSON(defaultOpts))["dependencies"];
139+
const e = Object.entries(deps);
140+
expect(e).toEqual(expect.arrayContaining([["nodecg", `^${nodecgPackageJson.version}`]]));
141+
expect(e).toEqual(expect.arrayContaining([[twitchChatPkg.name, `^${twitchChatPkg.version}`]]));
142+
143+
// These dependencies should always have the latest version which is fetched by the mocked getLatestPackageVersion
144+
expect(e).toEqual(expect.arrayContaining([["typescript", `^1.2.3`]]));
145+
expect(e).toEqual(expect.arrayContaining([["@types/node", `^1.2.3`]]));
146+
});
147+
148+
test("should have build scripts if typescript", async () => {
149+
const packageJson = await genPackageJSON(defaultOpts);
150+
expect(packageJson["scripts"]).toBeDefined();
151+
expect(Object.keys(packageJson["scripts"])).toStrictEqual(["build", "watch", "clean"]);
152+
});
153+
154+
test("should have no build scripts if javascript", async () => {
155+
const packageJson = await genPackageJSON(jsOpts);
156+
expect(packageJson["scripts"]).toBeUndefined();
157+
});
158+
159+
test("should generate graphic if graphic is enabled", async () => {
160+
const packageJson = await genPackageJSON({ ...defaultOpts, graphic: true });
161+
expect(packageJson["nodecg"]["graphics"]).toBeDefined();
162+
expect(packageJson["nodecg"]["graphics"].length).toBe(1);
163+
});
164+
165+
test("should generate dashboard if dashboard is enabled", async () => {
166+
const packageJson = await genPackageJSON({ ...defaultOpts, dashboard: true });
167+
expect(packageJson["nodecg"]["dashboardPanels"]).toBeDefined();
168+
expect(packageJson["nodecg"]["dashboardPanels"].length).toBe(1);
169+
});
170+
});

test/install/production.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe("removePackages", () => {
7070
const i = { ...validProdInstall, packages: [...packages] };
7171
await removePackages(packages, i, nodecgIODir);
7272
expect(writeInstallInfoMock).toHaveBeenCalledTimes(2);
73-
expect(writeInstallInfoMock).toHaveBeenLastCalledWith(nodecgIODir, validProdInstall);
73+
expect(writeInstallInfoMock).toHaveBeenLastCalledWith(nodecgIODir, { ...validProdInstall, packages: [] });
7474
});
7575
});
7676

test/testUtils.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,16 @@ export const validDevInstall: DevelopmentInstallation = {
4949
};
5050
export const validProdInstall: ProductionInstallation = {
5151
dev: false,
52-
version: "0.1.0",
53-
packages: [],
52+
version: "0.1",
53+
packages: [corePkg, dashboardPkg, twitchChatPkg],
5454
};
5555
export const testDir = path.join(fsRoot, "testDir");
5656
export const nodecgIODir = path.join(fsRoot, "nodecg-io");
57+
export const nodecgPackageJson = {
58+
name: "nodecg",
59+
version: "1.8.0",
60+
};
61+
export const nodecgPackageJsonStr = JSON.stringify(nodecgPackageJson);
5762

5863
temp.track();
5964
afterEach(() => temp.cleanup());

test/utils/nodecgInstallation.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import * as path from "path";
22
import { createFsFromVolume, vol } from "memfs";
3-
import { testDir } from "../testUtils";
3+
import { nodecgPackageJsonStr, testDir } from "../testUtils";
44
import { findNodeCGDirectory, getNodeCGVersion } from "../../src/utils/nodecgInstallation";
55
import { SemVer } from "semver";
66

77
jest.mock("fs", () => createFsFromVolume(vol));
88
afterEach(() => vol.reset());
99

10-
const examplePackageJsonStr = JSON.stringify({
11-
name: "nodecg",
12-
version: "1.8.0",
13-
});
14-
1510
const nodecgDir = path.join(testDir, "nodecg");
1611
const nodecgSubDir = path.join(nodecgDir, "subDirectory");
1712
const packageJsonFile = path.join(nodecgDir, "package.json");
@@ -22,7 +17,7 @@ beforeEach(async () => {
2217
await vol.promises.mkdir(nodecgSubDir);
2318

2419
// Fake package.json of a real nodecg install
25-
await vol.promises.writeFile(packageJsonFile, examplePackageJsonStr);
20+
await vol.promises.writeFile(packageJsonFile, nodecgPackageJsonStr);
2621
});
2722

2823
describe("findNodeCGDirectory", () => {

0 commit comments

Comments
 (0)