Merged PR 774: 画面実装(csv読み込み部分切り出し)
## 概要 [タスク 3754: 画面実装(csv読み込み部分切り出し)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3754) - CSVファイルの内容を入力として、JSONに変換する処理&テストを実装 - 画面実装時の想定としては、以下の流れと想定しており、本Taskの範囲は1のみ 1. csv->jsonへパースが出来るか?(csvの形式として合っていて読み込み可能か?) 2. json内の各パラメータは問題ないか?(データの制限や組み合わせは問題ないか?) - 使い方を示す & 動作確認のために、client側でもテストを実施できるよう修正(※pipelineでは実行されない) ## レビューポイント - テストケースを見て、使い方は分かるか - CSVの形式自体が想定とズレていた場合は入力を弾く必要がある想定だが、間違っていないか - 利用ライブラリはメジャーかつ便利そうなものを選定したが、問題なさそうか ## 動作確認状況 - npm run testを通過
This commit is contained in:
parent
71127a6db9
commit
363f12f86f
5
dictation_client/jest.config.js
Normal file
5
dictation_client/jest.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
6032
dictation_client/package-lock.json
generated
6032
dictation_client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,8 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"codegen": "sh codegen.sh",
|
||||
"lint": "eslint --cache . --ext .js,.ts,.tsx",
|
||||
"lint:fix": "npm run lint -- --fix"
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/msal-browser": "^2.33.0",
|
||||
@ -25,7 +26,6 @@
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^14.2.1",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^17.0.45",
|
||||
"@types/react": "^18.0.14",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
@ -38,6 +38,7 @@
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.3.0",
|
||||
"papaparse": "^5.4.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-google-recaptcha-v3": "^1.10.0",
|
||||
@ -56,8 +57,10 @@
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||
"@mdx-js/react": "^2.1.2",
|
||||
"@openapitools/openapi-generator-cli": "^2.5.2",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/luxon": "^3.2.0",
|
||||
"@types/papaparse": "^5.3.14",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@types/redux-mock-store": "^1.0.3",
|
||||
@ -67,16 +70,18 @@
|
||||
"babel-loader": "^8.2.5",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"jest": "^29.7.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier": "^2.8.8",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"sass": "^1.58.3",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^4.1.4",
|
||||
"vite-plugin-env-compatible": "^1.1.1",
|
||||
|
||||
134
dictation_client/src/common/parser.test.ts
Normal file
134
dictation_client/src/common/parser.test.ts
Normal file
@ -0,0 +1,134 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
// Jestによるparser.tsのテスト
|
||||
import fs from "fs";
|
||||
import { CSVType, parseCSV } from "./parser";
|
||||
|
||||
describe("parse", () => {
|
||||
it("指定形式のCSV文字列をパースできる", async () => {
|
||||
const text = fs.readFileSync("src/common/test/test_001.csv", "utf-8");
|
||||
const actualData = await parseCSV(text);
|
||||
const expectData: CSVType[] = [
|
||||
{
|
||||
name: "hoge",
|
||||
email: "sample@example.com",
|
||||
role: 1,
|
||||
author_id: "HOGE",
|
||||
auto_renew: 1,
|
||||
notification: 1,
|
||||
encryption: 1,
|
||||
encryption_password: "abcd",
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
expect(actualData).toEqual(expectData);
|
||||
});
|
||||
it("指定形式のヘッダでない場合、例外が送出される | author_id(値がoptionial)がない", async () => {
|
||||
const text = fs.readFileSync("src/common/test/test_002.csv", "utf-8");
|
||||
try {
|
||||
await parseCSV(text);
|
||||
fail("例外が発生しませんでした");
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new Error("Invalid CSV format"));
|
||||
}
|
||||
});
|
||||
it("指定形式のヘッダでない場合、例外が送出される | email(値が必須)がない", async () => {
|
||||
const text = fs.readFileSync("src/common/test/test_003.csv", "utf-8");
|
||||
try {
|
||||
await parseCSV(text);
|
||||
fail("例外が発生しませんでした");
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new Error("Invalid CSV format"));
|
||||
}
|
||||
});
|
||||
it("指定形式のヘッダでない場合、例外が送出される | emailがスペルミス", async () => {
|
||||
const text = fs.readFileSync("src/common/test/test_004.csv", "utf-8");
|
||||
try {
|
||||
await parseCSV(text);
|
||||
fail("例外が発生しませんでした");
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new Error("Invalid CSV format"));
|
||||
}
|
||||
});
|
||||
it("指定形式のCSV文字列をパースできる | 抜けているパラメータ(文字列)はnullとなる", async () => {
|
||||
const text = fs.readFileSync("src/common/test/test_005.csv", "utf-8");
|
||||
const actualData = await parseCSV(text);
|
||||
const expectData: CSVType[] = [
|
||||
{
|
||||
name: "hoge",
|
||||
email: "sample@example.com",
|
||||
role: 1,
|
||||
author_id: null,
|
||||
auto_renew: 1,
|
||||
notification: 1,
|
||||
encryption: 1,
|
||||
encryption_password: "abcd",
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
expect(actualData).toEqual(expectData);
|
||||
});
|
||||
it("指定形式のCSV文字列をパースできる | 抜けているパラメータ(数値)はnullとなる", async () => {
|
||||
const text = fs.readFileSync("src/common/test/test_006.csv", "utf-8");
|
||||
const actualData = await parseCSV(text);
|
||||
const expectData: CSVType[] = [
|
||||
{
|
||||
name: "hoge",
|
||||
email: "sample@example.com",
|
||||
role: null,
|
||||
author_id: "HOGE",
|
||||
auto_renew: 1,
|
||||
notification: 1,
|
||||
encryption: 1,
|
||||
encryption_password: "abcd",
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
expect(actualData).toEqual(expectData);
|
||||
});
|
||||
it("指定形式のCSV文字列をパースできる | 余計なパラメータがあっても問題はない", async () => {
|
||||
const text = fs.readFileSync("src/common/test/test_007.csv", "utf-8");
|
||||
const actualData = await parseCSV(text);
|
||||
const expectData: CSVType[] = [
|
||||
{
|
||||
name: "hoge",
|
||||
email: "sample@example.com",
|
||||
role: 1,
|
||||
author_id: "HOGE",
|
||||
auto_renew: 1,
|
||||
notification: 1,
|
||||
encryption: 1,
|
||||
encryption_password: "abcd",
|
||||
prompt: 0,
|
||||
},
|
||||
{
|
||||
name: "hoge2",
|
||||
email: "sample2@example.com",
|
||||
role: 1,
|
||||
author_id: "HOGE2",
|
||||
auto_renew: 1,
|
||||
notification: 1,
|
||||
encryption: 1,
|
||||
encryption_password: "abcd2",
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
expect(actualData.length).toBe(expectData.length);
|
||||
|
||||
// 余計なパラメータ格納用に __parsed_extra: string[] というプロパティが作られてしまうので、既知のプロパティ毎に比較
|
||||
for (let i = 0; i < actualData.length; i += 1) {
|
||||
const actualValue = actualData[i];
|
||||
const expectValue = expectData[i];
|
||||
expect(actualValue.author_id).toEqual(expectValue.author_id);
|
||||
expect(actualValue.auto_renew).toEqual(expectValue.auto_renew);
|
||||
expect(actualValue.email).toEqual(expectValue.email);
|
||||
expect(actualValue.encryption).toEqual(expectValue.encryption);
|
||||
expect(actualValue.encryption_password).toEqual(
|
||||
expectValue.encryption_password
|
||||
);
|
||||
expect(actualValue.name).toEqual(expectValue.name);
|
||||
expect(actualValue.notification).toEqual(expectValue.notification);
|
||||
expect(actualValue.prompt).toEqual(expectValue.prompt);
|
||||
expect(actualValue.role).toEqual(expectValue.role);
|
||||
}
|
||||
});
|
||||
});
|
||||
57
dictation_client/src/common/parser.ts
Normal file
57
dictation_client/src/common/parser.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import Papa, { ParseResult } from "papaparse";
|
||||
|
||||
export type CSVType = {
|
||||
name: string | null;
|
||||
email: string | null;
|
||||
role: number | null;
|
||||
author_id: string | null;
|
||||
auto_renew: number | null;
|
||||
notification: number;
|
||||
encryption: number | null;
|
||||
encryption_password: string | null;
|
||||
prompt: number | null;
|
||||
};
|
||||
|
||||
// CSVTypeのプロパティ名を文字列の配列で定義する
|
||||
const CSVTypeFields: (keyof CSVType)[] = [
|
||||
"name",
|
||||
"email",
|
||||
"role",
|
||||
"author_id",
|
||||
"auto_renew",
|
||||
"notification",
|
||||
"encryption",
|
||||
"encryption_password",
|
||||
"prompt",
|
||||
];
|
||||
|
||||
// 2つの配列が等しいかどうかを判定する
|
||||
const equals = (lhs: string[], rhs: string[]) => {
|
||||
if (lhs.length !== rhs.length) return false;
|
||||
for (let i = 0; i < lhs.length; i += 1) {
|
||||
if (lhs[i] !== rhs[i]) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/** CSVファイルをCSVType型に変換するパーサー */
|
||||
export const parseCSV = async (csvString: string): Promise<CSVType[]> =>
|
||||
new Promise((resolve, reject) => {
|
||||
Papa.parse<CSVType>(csvString, {
|
||||
download: false,
|
||||
worker: true,
|
||||
header: true,
|
||||
dynamicTyping: true,
|
||||
complete: (results: ParseResult<CSVType>) => {
|
||||
// ヘッダーがCSVTypeFieldsと一致しない場合はエラーを返す
|
||||
if (!equals(results.meta.fields ?? [], CSVTypeFields)) {
|
||||
reject(new Error("Invalid CSV format"));
|
||||
}
|
||||
resolve(results.data);
|
||||
},
|
||||
error: (error: Error) => {
|
||||
reject(error);
|
||||
},
|
||||
});
|
||||
});
|
||||
2
dictation_client/src/common/test/test_001.csv
Normal file
2
dictation_client/src/common/test/test_001.csv
Normal file
@ -0,0 +1,2 @@
|
||||
name,email,role,author_id,auto_renew,notification,encryption,encryption_password,prompt
|
||||
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
|
||||
|
2
dictation_client/src/common/test/test_002.csv
Normal file
2
dictation_client/src/common/test/test_002.csv
Normal file
@ -0,0 +1,2 @@
|
||||
name,email,role,auto_renew,notification,encryption,encryption_password,prompt
|
||||
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
|
||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
2
dictation_client/src/common/test/test_003.csv
Normal file
2
dictation_client/src/common/test/test_003.csv
Normal file
@ -0,0 +1,2 @@
|
||||
name,role,author_id,auto_renew,notification,encryption,encryption_password,prompt
|
||||
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
|
||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
2
dictation_client/src/common/test/test_004.csv
Normal file
2
dictation_client/src/common/test/test_004.csv
Normal file
@ -0,0 +1,2 @@
|
||||
name,emeil,role,author_id,auto_renew,notification,encryption,encryption_password,prompt
|
||||
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
|
||||
|
2
dictation_client/src/common/test/test_005.csv
Normal file
2
dictation_client/src/common/test/test_005.csv
Normal file
@ -0,0 +1,2 @@
|
||||
name,email,role,author_id,auto_renew,notification,encryption,encryption_password,prompt
|
||||
hoge,sample@example.com,1,,1,1,1,abcd,0
|
||||
|
2
dictation_client/src/common/test/test_006.csv
Normal file
2
dictation_client/src/common/test/test_006.csv
Normal file
@ -0,0 +1,2 @@
|
||||
name,email,role,author_id,auto_renew,notification,encryption,encryption_password,prompt
|
||||
hoge,sample@example.com,,"HOGE",1,1,1,abcd,0
|
||||
|
3
dictation_client/src/common/test/test_007.csv
Normal file
3
dictation_client/src/common/test/test_007.csv
Normal file
@ -0,0 +1,3 @@
|
||||
name,email,role,author_id,auto_renew,notification,encryption,encryption_password,prompt
|
||||
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0,x
|
||||
hoge2,sample2@example.com,1,"HOGE2",1,1,1,abcd2,0,1,32,4,aa
|
||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
Loading…
x
Reference in New Issue
Block a user