Skip to content

Commit

Permalink
Add first tests, mock file system
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephen Hanson committed Oct 29, 2023
1 parent 34db957 commit d73ba6a
Show file tree
Hide file tree
Showing 19 changed files with 600 additions and 60 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Run linter
- name: Run ESLint, Prettier, tsc
run: yarn lint
- name: Run tests
run: yarn test
run: yarn test:run
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ npm-debug.log
.idea/*
.vscode/*
.DS_Store
/build
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"singleQuote": true
"singleQuote": true
}
25 changes: 25 additions & 0 deletions __mocks__/fs-extra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import actualfs from 'fs-extra';
import { fs } from 'memfs';

const DONT_MOCK_PATTERNS = ['templates/'];

export default {
...fs.promises,
exists(path) {
return new Promise((resolve) => {
fs.exists(path, (exists) => resolve(exists));
});
},
readFile: (...args) => {
const path = args[0];
const useActual = DONT_MOCK_PATTERNS.some((pattern) =>
path.includes(pattern),
);

if (useActual) {
return actualfs.readFile(...args);
}

return fs.promises.readFile(...args);
},
};
3 changes: 0 additions & 3 deletions __mocks__/sfs.js

This file was deleted.

12 changes: 6 additions & 6 deletions bin/belt.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#!/usr/bin/env node

import { spawn } from "node:child_process";
import path from "node:path";
import { URL, fileURLToPath } from "node:url";
import { spawn } from 'node:child_process';
import path from 'node:path';
import { URL, fileURLToPath } from 'node:url';

const dirname = fileURLToPath(new URL(".", import.meta.url));
const dirname = fileURLToPath(new URL('.', import.meta.url));
const args = process.argv.slice(2);

spawn("ts-node", [path.join(dirname, "../src/cli.ts"), ...args], {
stdio: "inherit",
spawn('ts-node', [path.join(dirname, '../src/cli.ts'), ...args], {
stdio: 'inherit',
});
25 changes: 19 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@
"type": "module",
"scripts": {
"belt": "bin/belt.js",
"lint": "eslint --max-warnings=0 --ext js,jsx,ts,tsx .",
"test": "vitest"
"lint": "run-p lint:eslint lint:types lint:prettier",
"lint:eslint": "eslint --max-warnings=0 --ext js,jsx,ts,tsx .",
"lint:prettier": "prettier --check '**/*' --ignore-unknown",
"lint:types": "tsc",
"fix:prettier": "prettier --write '**/*' --ignore-unknown",
"test": "vitest watch",
"test:run": "test run",
"test:all": "yarn lint && yarn test:run"
},
"bin": {
"thoughtbelt": "./bin/belt.js"
Expand All @@ -27,11 +33,12 @@
"ts-node": "^10.9.1"
},
"devDependencies": {
"@types/fs-extra": "^11.0.1",
"@types/fs-extra": "^11.0.3",
"@types/node": "^18.16.3",
"@types/react": "^18.2.6",
"typescript": "^5.0.4",
"memfs": "^4.2.0",
"npm-run-all": "^4.1.5",
"typescript": "^5.0.4",
"vitest": "^0.34.1"
},
"eslintConfig": {
Expand All @@ -41,7 +48,13 @@
"@thoughtbot/eslint-config/typescript"
],
"rules": {
"no-console": "off"
}
"no-console": "off",
"import/order": "off"
},
"ignorePatterns": [
"__mocks__/**/*.js",
"bin/belt.js",
"/build"
]
}
}
79 changes: 70 additions & 9 deletions src/commands/__tests__/typescript.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,75 @@
// import fs from 'fs';
import { fs as memfs } from 'memfs';
import { expect, test } from 'vitest';
import { fs, vol } from 'memfs';
import { Mock, afterEach, expect, test, vi } from 'vitest';
import addDependency from '../../util/addDependency';
import print from '../../util/print';
import addTypescript from '../typescript';

test('mocking file system works', async () => {
memfs.writeFileSync('/hello.txt', 'World!');
expect(memfs.readFileSync('/hello.txt', 'utf8')).toEqual('World!');
vi.mock('fs-extra');
vi.mock('../../util/addDependency');
vi.mock('../../util/print', () => ({
// __esModule: true,
default: vi.fn(),
}));

afterEach(() => {
vol.reset();
(print as Mock).mockReset();
});

test('exits with message if tsconfig.json already exists', async () => {
const json = {
'package.json': '{}',
'tsconfig.json': '1',
};
vol.fromJSON(json, './');

await addTypescript();
expect(print).toHaveBeenCalledWith(
expect.stringMatching(/tsconfig\.json already exists/),
);

// doesn't modify
expect(fs.readFileSync('tsconfig.json', 'utf8')).toEqual('1');
});

test('writes new tsconfig.json, adds dependencies', async () => {
vol.fromJSON({
'package.json': JSON.stringify({
dependencies: {
expo: '1.0.0',
},
}),
});

await addTypescript();

expect(addDependency).toHaveBeenCalledWith('typescript @types/react', {
dev: true,
});

expect(fs.readFileSync('tsconfig.json', 'utf8')).toMatch(
'"extends": "expo/tsconfig.base"',
);

expect(print).not.toHaveBeenCalledWith(
expect.stringMatching(/already exists/),
);
});

test('exits with message if tsconfig.json already exists', () => {
void addTypescript();
expect(true).toBe(true);
test("doesn't extend expo/tsconfig.base if not an Expo project", async () => {
vol.fromJSON({
'package.json': JSON.stringify({
dependencies: {},
}),
});

await addTypescript();

expect(addDependency).toHaveBeenCalledWith('typescript @types/react', {
dev: true,
});

expect(fs.readFileSync('tsconfig.json', 'utf8')).not.toMatch(
'expo/tsconfig.base',
);
});
2 changes: 1 addition & 1 deletion src/commands/templates/tsconfig.json.eta
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@
"include": ["src/**/*", "*.js", ".*.js", "*.ts", "*.tsx"],
"exclude": ["node_modules"],
<% if(it.expo) { %>
"extends": "expo/tsconfig.base"
"extends": "expo/tsconfig.base"
<% } %>
}
10 changes: 5 additions & 5 deletions src/commands/typescript.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { log } from 'console';
import path from 'path';
import { fileURLToPath, URL } from 'url';
import chalk from 'chalk';
import * as eta from 'eta';
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath, URL } from 'url';
import addDependency from '../util/addDependency';
import getProjectDir from '../util/getProjectDir';
import getProjectType from '../util/getProjectType';
import print from '../util/print';
import writeFile from '../util/writeFile';

// for manual testing, change this to another name so doesn't conflict
Expand All @@ -18,7 +18,7 @@ export default async function addTypescript() {
const projectDir = await getProjectDir();

if (await fs.exists(path.join(projectDir, tsConfig))) {
log(
print(
chalk.yellow(
'tsconfig.json already exists, exiting.\nIf you would like to perform a fresh TypeScript install, delete this file and rerun the script.\n',
),
Expand All @@ -40,7 +40,7 @@ export default async function addTypescript() {
format: true,
});

log(
print(
chalk.green(
'\n🎉 TypeScript successfully configured\nConsider renaming your existing JS files as .ts or .tsx.\n',
),
Expand Down
2 changes: 1 addition & 1 deletion src/util/addDependency.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { exec } from 'child_process';
import fs from 'fs-extra';
import * as path from 'path';
import * as fs from 'fs-extra';
import getProjectDir from './getProjectDir';

export default async function addDependency(deps: string, { dev = false }) {
Expand Down
4 changes: 2 additions & 2 deletions src/util/getProjectDir.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import path from 'path';
import fs from 'fs-extra';
import path from 'path';

export default async function getProjectDir(
base: string = process.cwd(),
): Promise<string> {
let previous = null;
let previous: string | null = null;
let dir = base;

do {
Expand Down
14 changes: 7 additions & 7 deletions src/util/getProjectType.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import readPackageJson from "./readPackageJson";
import readPackageJson from './readPackageJson';

type ProjectType = "expo-bare" | "expo-managed" | "react-native";
type ProjectType = 'expo-bare' | 'expo-managed' | 'react-native';

export default async function getProjectType(): Promise<ProjectType> {
const packageJson = await readPackageJson();
const hasExpo = hasProperty(packageJson.dependencies, "expo");
const hasExpo = hasProperty(packageJson.dependencies, 'expo');
const hasReactNativeUnimodules = hasProperty(
packageJson.dependencies,
"react-native-unimodules"
'react-native-unimodules',
);
if (hasExpo) {
return hasReactNativeUnimodules ? "expo-bare" : "expo-managed";
return hasReactNativeUnimodules ? 'expo-bare' : 'expo-managed';
}

return "react-native";
return 'react-native';
}

function hasProperty(
object: Record<string, unknown> | undefined,
property: string
property: string,
) {
return Object.prototype.hasOwnProperty.call(object, property);
}
3 changes: 3 additions & 0 deletions src/util/print.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function print(...args: unknown[]) {
return console.log(...args);
}
2 changes: 1 addition & 1 deletion src/util/readPackageJson.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from 'path';
import fs from 'fs-extra';
import path from 'path';
import { PackageJson } from '../types';
import getProjectDir from './getProjectDir';

Expand Down
11 changes: 6 additions & 5 deletions src/util/writeFile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import chalk from "chalk";
import fs from "fs-extra";
import formatFile from "./formatFile";
import chalk from 'chalk';
import fs from 'fs-extra';
import formatFile from './formatFile';
import print from './print';

type Options = {
format?: boolean;
Expand All @@ -9,9 +10,9 @@ type Options = {
export default async function writeFile(
filePath: string,
contents: string,
{ format = false }: Options = {}
{ format = false }: Options = {},
) {
console.log(chalk.bold(`🔨 Creating ${filePath}`));
print(chalk.bold(`🔨 Creating ${filePath}`));
await fs.writeFile(filePath, contents);

if (format) {
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
"esm": true,
"experimentalSpecifierResolution": "node"
},
"include": ["./**/*"]
"include": ["./**/*.ts"]
}
7 changes: 7 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { configDefaults, defineConfig } from 'vitest/config';

export default defineConfig({
test: {
exclude: [...configDefaults.exclude, 'build/**/*'],
},
});
Loading

0 comments on commit d73ba6a

Please sign in to comment.