Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add integration tests #38

Merged
merged 14 commits into from
Nov 27, 2021
Merged
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
3 changes: 1 addition & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@
"parent",
"sibling",
"index"
],
"newlines-between": "always-and-inside-groups"
]
}
],
"import/no-cycle": "off",
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,5 @@ dist
.devcontainer
build

bin
bin
e2e-runtime
31 changes: 31 additions & 0 deletions Dockerfile.integration
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
FROM node:14 as base

WORKDIR /usr/src/app

COPY package.json package.json
COPY yarn.lock yarn.lock
COPY src src
COPY tsconfig*.json .

# Build and pack agrippa

RUN yarn install --frozen-lockfile
RUN yarn build
RUN yarn pack

FROM node:14 as test
WORKDIR /usr/src/app

# Install agrippa as a package

COPY --from=base /usr/src/app/agrippa-v1.2.1.tgz ./agrippa.tgz
RUN npm i -g agrippa.tgz

# Setup and run tests

RUN yarn init -y
RUN yarn add --dev fast-glob jest typescript ts-jest @types/jest

COPY test/integration integration

CMD ["yarn", "jest", "--config", "integration/jest.integration.config.js", "integration"]
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
},
"license": "MIT",
"scripts": {
"dev": "tsc -p tsconfig.build.json -w",
"dev": "tsc -w",
"prebuild": "rimraf bin",
"build": "tsc -p tsconfig.build.json",
"build": "tsc",
"test": "jest",
"test:integration": "(docker build -f ./Dockerfile.integration -t agrippa_test .) && (docker run -it --name agrippa_test --rm agrippa_test)",
"lint": "eslint src --ext .ts --color",
"preversion": "yarn lint && yarn build && yarn test"
},
Expand Down Expand Up @@ -44,6 +45,7 @@
"eslint-import-resolver-typescript": "^2.4.0",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-unused-imports": "^1.1.5",
"fast-glob": "^3.2.7",
"jest": "^27.2.4",
"rimraf": "^3.0.2",
"ts-jest": "^27.0.5",
Expand Down
6 changes: 3 additions & 3 deletions src/generate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ const builder = async (yargs: yargs.Argv<CommonConfig>) => {
.options({
props: {
choices: ['ts', 'jsdoc', 'prop-types', 'none'],
desc: 'Which prop declaration method to use',
default: tsConfig ? 'ts' : 'none'
desc: 'Which prop declaration method to use'
},
children: {
type: 'boolean',
Expand Down Expand Up @@ -153,7 +152,8 @@ export const generateCommand: GenerateCommand = {
handler: async argv => {
const config: Config = {
name: argv.name as string,
...pick(['props', 'children', 'typescript', 'flat', 'styling', 'debug', 'overwrite', 'destination', 'declaration', 'memo'], argv),
...pick(['children', 'typescript', 'flat', 'styling', 'debug', 'overwrite', 'destination', 'declaration', 'memo'], argv),
props: argv.props ?? (argv.typescript ? 'ts' : 'none'),
stylingModule: argv['styling-module'],
importReact: argv['import-react'],
postCommand: argv['post-command'],
Expand Down
8 changes: 8 additions & 0 deletions test/integration/button-basic/solution/Button/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';

export const Button = () => {

return (
<div></div>
);
};
5 changes: 5 additions & 0 deletions test/integration/button-basic/testinfo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "button-basic",
"description": "A bare test - no config, no options, just plain generation.",
"command": "agrippa gen button"
}
11 changes: 11 additions & 0 deletions test/integration/button-css-ts/solution/CssTsButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import classes from './css-ts-button.module.css';

export interface CssTsButtonProps {}

export const CssTsButton: React.VFC<CssTsButtonProps> = () => {

return (
<div></div>
);
};
5 changes: 5 additions & 0 deletions test/integration/button-css-ts/testinfo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "button-css-ts",
"description": "A simple Button test in a CSS & TS project",
"command": "agrippa gen css-ts-button --styling css --typescript"
}
Empty file.
9 changes: 9 additions & 0 deletions test/integration/button-css/solution/CssButton/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import classes from './css-button.module.css';

export const CssButton = () => {

return (
<div></div>
);
};
5 changes: 5 additions & 0 deletions test/integration/button-css/testinfo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "button-css",
"description": "A simple CSS styling test",
"command": "agrippa gen css-button --styling css"
}
11 changes: 11 additions & 0 deletions test/integration/button-scss-ts/solution/ScssTsButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import classes from './scss-ts-button.module.scss';

export interface ScssTsButtonProps {}

export const ScssTsButton: React.VFC<ScssTsButtonProps> = () => {

return (
<div></div>
);
};
5 changes: 5 additions & 0 deletions test/integration/button-scss-ts/testinfo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "button-scss-ts",
"description": "A simple Button test in an SCSS & TS project",
"command": "agrippa gen scss-ts-button --styling scss --typescript"
}
9 changes: 9 additions & 0 deletions test/integration/button-scss/solution/ScssButton/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import classes from './scss-button.module.scss';

export const ScssButton = () => {

return (
<div></div>
);
};
Empty file.
5 changes: 5 additions & 0 deletions test/integration/button-scss/testinfo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "button-scss",
"description": "A simple Button test in an SCSS project",
"command": "agrippa gen scss-button --styling scss"
}
10 changes: 10 additions & 0 deletions test/integration/button-typescript/solution/TsButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

export interface TsButtonProps {}

export const TsButton: React.VFC<TsButtonProps> = () => {

return (
<div></div>
);
};
5 changes: 5 additions & 0 deletions test/integration/button-typescript/testinfo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "button-typescript",
"description": "A simple Typescript test",
"command": "agrippa gen ts-button --typescript"
}
144 changes: 144 additions & 0 deletions test/integration/integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { join } from 'path';
import { mkdir } from 'fs/promises';
import { execSync } from 'child_process';
import { readdirSync, readFileSync } from 'fs';
import { sync as fgSync } from 'fast-glob';

const safeTry = <T>(fn: () => T): { ok: true, data: T } | { ok: false, err: Error } => {
try {
return { ok: true, data: fn() };
}
catch (e) {
return { ok: false, err: e };
}
};

/** Simple representation of a file found in an integration test case */
interface TestFile {
path: string;
data: string;
}
/** Simple representation of a directory found in an integration test case */
interface TestDir {
path: string;
}

/** The files and dirs returned from scanDir(). */
interface ScannedDir {
files: TestFile[];
dirs: TestDir[];
}
/** The data returned from runCase(); contains the scanned solution and output directories, ready to be compared. */
interface TestCase {
name: string;
solution: ScannedDir;
output: ScannedDir;
}
/** The structure of a testinfo.json file for a given test case. */
interface TestInfo {
name: string;
description?: string;
command: string;
}

const isTestFile = (ent: TestFile | TestDir): ent is TestFile => 'data' in ent;

/**
* Scans the directory resolved from the given path.
* Returns all files and folders in that directory, as a `ScannedDir` object.
*/
const scanDir = (dirPath: string): ScannedDir =>
fgSync('**/*', {
cwd: dirPath,
onlyFiles: false,
markDirectories: true,
})
.map(path =>
path.endsWith('/')
? { path }
: {
path,
data: readFileSync(join(dirPath, path), 'utf-8')
}
)
.reduce(({ files, dirs }, ent) =>
isTestFile(ent)
? { files: [...files, ent], dirs }
: { files, dirs: [...dirs, ent] },
{ files: [] as TestFile[], dirs: [] as TestDir[] }
);

/**
* Runs a test case, specified by its name.
* This entails:
* - Scanning the solution dir & testinfo.json
* - Creating an output directory and running the Agrippa command there
* - Scanning the output directory after the command finished
*/
const runCase = (caseName: string): TestCase => {
const casePath = join(__dirname, caseName);

//Scan solution files & dirs

const solutionDir = join(casePath, 'solution');
const solution = scanDir(solutionDir);

const testInfo = safeTry(() =>
JSON.parse(
readFileSync(join(casePath, 'testinfo.json'), 'utf-8')
) as TestInfo
);

if (!testInfo.ok) {
throw new Error(`Reading testinfo.json failed for case ${caseName}. Please make sure the file exists.`);
}

const { name, command } = testInfo.data;

// Run Agrippa & scan output files

const outputDir = join(casePath, 'output');
mkdir(outputDir);

const logs = safeTry(() => execSync(command, { cwd: outputDir }));
if (logs.ok) {
console.log(logs.data.toString());
}
else {
console.error((logs as any).err);
}

const output = scanDir(outputDir);

return { name, solution, output };
};

const caseNames = readdirSync(__dirname, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);

describe.each(caseNames)('Case %#: %s', (name) => {
const { solution, output } = runCase(name);

// Compare solution & output

test('Solution and output have the same number of directories', () => {
expect(output.dirs.length).toBe(solution.dirs.length);
});

test('Solution and output have the same number of files', () => {
expect(output.files.length).toBe(solution.files.length);
});

const outputDirPaths = output.dirs.map(d => d.path);
solution.dirs.length && test.each(solution.dirs)('Dir $path generated', ({ path }) => {
expect(outputDirPaths).toContain(path);
});

solution.files.length && test.each(solution.files)('File $path generated', ({ path, data }) => {
const outputFile = output.files.find(f => f.path === path);

expect(outputFile).toBeTruthy();
expect(outputFile?.data).toBe(data);
});
});
6 changes: 6 additions & 0 deletions test/integration/jest.integration.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: '.'
};
3 changes: 2 additions & 1 deletion test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"baseUrl": "../src",
"noEmit": true
"noEmit": true,
"rootDir": ".."
},
"include": [
"src",
Expand Down
9 changes: 0 additions & 9 deletions tsconfig.build.json

This file was deleted.

1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"lib": [
"ESNext"
],
"rootDir": "src",
"outDir": "bin",
"baseUrl": "src",
"module": "commonjs",
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1654,7 +1654,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==

fast-glob@^3.1.1:
fast-glob@^3.1.1, fast-glob@^3.2.7:
version "3.2.7"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==
Expand Down