Skip to content

Commit

Permalink
test: outputExtensionReplaceのテストコードを追加
Browse files Browse the repository at this point in the history
  • Loading branch information
yanagi0602 committed Nov 20, 2024
1 parent 0d9ac81 commit b0db9cd
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 40 deletions.
8 changes: 6 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
},
"env": {
"browser": true,
"node": true
"node": true,
"jest": true
},
"overrides": [
{
Expand All @@ -26,5 +27,8 @@
"react/prop-types": "off"
}
}
]
],
"rules": {
"@typescript-eslint/no-var-requires": "off"
}
}
79 changes: 44 additions & 35 deletions bin/outputExtensionReplace.ts → bin/outputExtensionReplace.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { readdir, readFile, writeFile, stat, access } from 'fs/promises';
import { join, dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
import glob from 'glob-promise';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const { readdir, readFile, writeFile, stat, access } = require('fs').promises;
const { join, dirname, resolve } = require('path');
const glob = require('glob-promise');
const process = require('process');

/**
* 指定されたディレクトリ配下のすべての.mjsファイルを再帰的に取得する
* @param {string} dir - ディレクトリのパス
* @param {string[]} [fileList=[]] - ファイルリスト
* @return {Promise<string[]>}
*/
async function getMjsFiles(dir: string, fileList: string[] = []): Promise<string[]> {
async function getMjsFiles(dir, fileList = []) {
const files = await readdir(dir);
for (const file of files) {
const filePath = join(dir, file);
Expand All @@ -33,48 +30,50 @@ async function getMjsFiles(dir: string, fileList: string[] = []): Promise<string
* @param {(match: string, ...args: any[]) => Promise<string>} asyncFn - 非同期関数
* @return {Promise<string>}
*/
async function replaceAsync(
str: string,
regex: RegExp,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
asyncFn: (match: string, ...args: any[]) => Promise<string>
): Promise<string> {
const promises: Promise<string>[] = [];
async function replaceAsync(str, regex, asyncFn) {
const promises = [];
str.replace(regex, (match, ...args) => {
const promise = asyncFn(match, ...args);
promises.push(promise);
return match; // 置換のために必要
});
const data = await Promise.all(promises);
return str.replace(regex, () => data.shift() as string);
return str.replace(regex, () => data.shift());
}

/**
* 指定されたファイルのimport文を置換する
* @param {string} filePath - ファイルのパス
* @return {Promise<void>}
*/
async function replaceImportsInFile(filePath: string): Promise<void> {
async function replaceImportsInFile(filePath) {
try {
const data = await readFile(filePath, 'utf8');
const importRegex = /from\s+['"](\.\/|\.\.\/)(?!.*\.mjs['"])([^'"]+)(['"])/g;
const result = await replaceAsync(data, importRegex, async (_match, p1, p2, p3) => {
const importPath = resolve(dirname(filePath), p1 + p2);
const mjsPath = importPath + '.mjs';
const indexPath = join(importPath, 'index.mjs');
try {
await access(mjsPath);
return `from '${p1}${p2}.mjs${p3}`;
} catch {
const importRegex =
/from\s+['"](\.\/|\.\.\/)(?!.*\.mjs['"])([^'"]+)(['"])/g;
const result = await replaceAsync(
data,
importRegex,
async (_match, p1, p2, p3) => {
const importPath = resolve(dirname(filePath), p1 + p2);
const mjsPath = importPath + '.mjs';
const indexPath = join(importPath, 'index.mjs');
try {
await access(indexPath);
return `from '${p1}${p2}/index.mjs${p3}`;
await access(mjsPath);
return `from '${p1}${p2}.mjs${p3}`;
} catch {
console.error(`.mjs または /index.mjs が存在しません: ${importPath}`);
process.exit(1);
try {
await access(indexPath);
return `from '${p1}${p2}/index.mjs${p3}`;
} catch {
console.error(
`.mjs または /index.mjs が存在しません: ${importPath}`,
);
process.exit(1);
}
}
}
});
},
);
await writeFile(filePath, result, 'utf8');
if (data !== result) {
console.log(`置換処理が完了しました: ${filePath}`);
Expand All @@ -85,17 +84,27 @@ async function replaceImportsInFile(filePath: string): Promise<void> {
}
}

async function main(): Promise<void> {
async function main() {
try {
const directories = await glob(join(__dirname, '../packages/*/dist'));
for (const dir of directories) {
const mjsFiles = await getMjsFiles(dir);
await Promise.all(mjsFiles.map(filePath => replaceImportsInFile(filePath)));
await Promise.all(
mjsFiles.map((filePath) => replaceImportsInFile(filePath)),
);
}
} catch (err) {
console.error('エラーが発生しました', err);
process.exit(1);
}
}

main();
if (require.main === module) {
main();
}

module.exports = {
getMjsFiles,
replaceAsync,
replaceImportsInFile,
};
108 changes: 108 additions & 0 deletions bin/outputExtensionReplace.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
const fs = require('fs').promises;
const {
replaceImportsInFile,
getMjsFiles,
replaceAsync,
} = require('./outputExtensionReplace'); // モジュールのパスを変更してください

jest.mock('fs', () => {
const originalModule = jest.requireActual('fs');
return {
...originalModule,
promises: {
readdir: jest.fn(),
readFile: jest.fn(),
writeFile: jest.fn(),
stat: jest.fn(),
access: jest.fn(),
},
};
});

describe('replaceImportsInFile', () => {
let originalExit;
let originalConsoleError;

beforeAll(() => {
originalExit = process.exit;
process.exit = jest.fn();
originalConsoleError = console.error;
console.error = jest.fn();
});

afterAll(() => {
process.exit = originalExit;
console.error = originalConsoleError;
});

beforeEach(() => {
jest.clearAllMocks();
});

it('should replace imports in a file', async () => {
const filePath = '/file.mjs';
const fileContent = `
import React, { forwardRef } from 'react';
import module from './module';
import hasIndexDirectory from '../hasIndexDirectory';
`;
const expectedContent = `
import React, { forwardRef } from 'react';
import module from './module.mjs';
import hasIndexDirectory from '../hasIndexDirectory/index.mjs';
`;
fs.readFile.mockResolvedValue(fileContent);
fs.writeFile.mockResolvedValue();
fs.access.mockImplementation((path) => {
if (path.endsWith('module.mjs')) {
return Promise.resolve();
} else if (path.endsWith('hasIndexDirectory/index.mjs')) {
return Promise.resolve();
}
return Promise.reject();
});
await replaceImportsInFile(filePath);
expect(fs.readFile).toHaveBeenCalledWith(filePath, 'utf8');
expect(fs.writeFile).toHaveBeenCalledWith(
filePath,
expectedContent,
'utf8',
);
});

it('should exit the process if an error occurs', async () => {
const filePath = '/file.mjs';
fs.readFile.mockRejectedValue(new Error('error'));
await replaceImportsInFile(filePath);
expect(process.exit).toHaveBeenCalledWith(1);
});
});

describe('getMjsFiles', () => {
it('should only return an array of .mjs files', () => {
const files = [
'packages/spindle-ui/dist/Button/Button.mjs',
'bin/outputExtensionReplace.js',
];
fs.readdir.mockResolvedValue(files);
fs.stat.mockImplementation((path) => {
return Promise.resolve({
isDirectory: () => path === 'packages/spindle-ui/dist/Button',
});
});
return expect(getMjsFiles('/')).resolves.toEqual([
'/packages/spindle-ui/dist/Button/Button.mjs',
]);
});
});

describe('replaceAsync', () => {
it('should replace a string asynchronously', async () => {
const result = await replaceAsync(
"import module from './module",
/.\/module/g,
async () => './module.mjs',
);
expect(result).toBe("import module from './module.mjs");
});
});
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
"name": "spindle",
"private": true,
"scripts": {
"test": "run-p lint format test:packages",
"test": "run-p lint format test:*",
"test:packages": "lerna run test",
"test:bin": "jest bin",
"lint": "run-p lint:*",
"lint:script": "eslint --cache 'packages/**/src/**/*.{js,ts,tsx}'",
"lint:yaml": "yamllint .github/workflows/*.yml",
Expand All @@ -18,7 +19,7 @@
"bootstrap": "lerna bootstrap",
"build": "run-s build:*",
"build:package": "lerna run build",
"build:extensionReplace": "npx tsx bin/outputExtensionReplace.ts",
"build:extensionReplace": "node bin/outputExtensionReplace.js",
"prepare": "is-ci || husky install"
},
"devDependencies": {
Expand All @@ -34,6 +35,7 @@
"glob-promise": "^6.0.7",
"husky": "^9.1.5",
"is-ci": "^3.0.0",
"jest": "^29.7.0",
"lerna": "^8.0.0",
"npm-run-all2": "^6.0.0",
"prettier": "3.3.3",
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15704,7 +15704,7 @@ jest-worker@^29.7.0:
merge-stream "^2.0.0"
supports-color "^8.0.0"

jest@^29.0.0:
jest@^29.0.0, jest@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613"
integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==
Expand Down

0 comments on commit b0db9cd

Please sign in to comment.