Skip to content

Commit 3761adf

Browse files
committed
Add transformer middleware
1 parent 4bbbdf1 commit 3761adf

File tree

8 files changed

+155
-4
lines changed

8 files changed

+155
-4
lines changed

.vscode/launch.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"type": "node",
6+
"request": "launch",
7+
"name": "Jest All",
8+
"program": "${workspaceFolder}/node_modules/.bin/jest",
9+
"args": ["--runInBand"],
10+
"console": "integratedTerminal",
11+
"internalConsoleOptions": "neverOpen",
12+
"disableOptimisticBPs": true,
13+
"windows": {
14+
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
15+
},
16+
"runtimeExecutable": "~/.asdf/shims/node"
17+
},
18+
{
19+
"type": "node",
20+
"request": "launch",
21+
"name": "Jest Current File",
22+
"program": "${workspaceFolder}/node_modules/.bin/jest",
23+
"args": [
24+
"${fileBasenameNoExtension}",
25+
"--config",
26+
"jest.config.js"
27+
],
28+
"console": "integratedTerminal",
29+
"internalConsoleOptions": "neverOpen",
30+
"disableOptimisticBPs": true,
31+
"windows": {
32+
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
33+
},
34+
"runtimeExecutable": "~/.asdf/shims/node"
35+
}
36+
]
37+
}

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ The most important features of this component are:
6363
- ✅ Automatic testing with >90% coverage
6464
- ✅ Input validation
6565
-[Ant Design](https://ant.design/) integration (see storybook)
66-
- Input transformation
66+
- Input transformation (e.g. trim, ...)
6767
-[Material UI](https://material-ui.com/) integration (see storybook)
6868

6969
✅ means the feature is implemented and released. ❌ indicates that a feature is planned.
@@ -91,3 +91,4 @@ The most important features of this component are:
9191
- [Article: Using ESLint and Prettier in a TypeScript Project](https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project)
9292
- [Template: Rollup Starter Lib (TypeScript)](https://github.com/rollup/rollup-starter-lib/tree/typescript)
9393
- [Article: Creating a React Component library using Rollup, Typescript, Sass and Storybook](https://blog.harveydelaney.com/creating-your-own-react-component-library/) <br> Explains how to create a React component library using Rollup
94+
- [Template: Debugging tests in VS Code](https://github.com/microsoft/vscode-recipes/tree/master/debugging-jest-tests)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"react"
88
],
99
"homepage": "https://openscript.github.io/react-dsv-import/",
10-
"version": "0.2.3",
10+
"version": "0.3.0",
1111
"main": "dist/index.js",
1212
"module": "dist/es/index.js",
1313
"types": "dist/index.d.ts",

src/DSVImport.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { State } from './models/state';
66
import { applyMiddlewares } from './middlewares/middleware';
77
import { createValidatorMiddleware } from './middlewares/validatorMiddleware';
88
import { ValidationError } from './models/validation';
9+
import { Transformers } from './models/transformer';
10+
import { createTransformerMiddleware } from './middlewares/transformerMiddleware';
911

1012
interface EventListenerProps<T> {
1113
onChange?: (value: T[]) => void;
@@ -33,14 +35,21 @@ const EventListener = <T extends { [key: string]: string }>(props: EventListener
3335
export interface Props<T> {
3436
onChange?: (value: T[]) => void;
3537
onValidation?: (errors: ValidationError<T>[]) => void;
38+
transformers?: Transformers<T>;
3639
columns: ColumnsType<T>;
3740
}
3841

3942
export const DSVImport = <T extends { [key: string]: string }>(props: PropsWithChildren<Props<T>>) => {
4043
const DSVImportContext = getDSVImportContext<T>();
41-
const initialValues: State<T> = { columns: props.columns };
44+
const initialValues: State<T> = { columns: props.columns, transformers: props.transformers };
4245
const [state, dispatch] = useReducer(createReducer<T>(), initialValues);
43-
const enhancedDispatch = applyMiddlewares(state, dispatch, createParserMiddleware(), createValidatorMiddleware());
46+
const enhancedDispatch = applyMiddlewares(
47+
state,
48+
dispatch,
49+
createParserMiddleware(),
50+
createTransformerMiddleware(),
51+
createValidatorMiddleware()
52+
);
4453

4554
return (
4655
<DSVImportContext.Provider value={[state, enhancedDispatch]}>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { ColumnsType } from '../models/column';
2+
import { State } from '../models/state';
3+
import { createTransformerMiddleware } from './transformerMiddleware';
4+
5+
describe('validatorMiddleware', () => {
6+
type TestType = { forename: string; surname: string; email: string };
7+
const defaultColumns: ColumnsType<TestType> = [
8+
{ key: 'forename', label: 'Forename' },
9+
{ key: 'surname', label: 'Surname' },
10+
{ key: 'email', label: 'Email' }
11+
];
12+
const middleware = createTransformerMiddleware<TestType>();
13+
const parsed: TestType[] = [
14+
{ forename: 'Hans', surname: 'Muster', email: '[email protected]' },
15+
{ forename: 'Heidi', surname: ' Muster', email: '[email protected]' },
16+
{ forename: 'Joe', surname: 'Doe', email: ' [email protected] ' }
17+
];
18+
19+
it('should not dispatch if there are no transformers', () => {
20+
const state: State<TestType> = { columns: defaultColumns };
21+
const dispatchMock = jest.fn();
22+
23+
middleware(state, dispatchMock, { type: 'setParsed', parsed });
24+
expect(dispatchMock).toBeCalledTimes(0);
25+
});
26+
27+
it('should run a transformer on all values', () => {
28+
const trimTransformer = (value: string) => value.trim();
29+
const state: State<TestType> = { columns: defaultColumns, transformers: [{ transformer: trimTransformer }] };
30+
const dispatchMock = jest.fn();
31+
32+
middleware(state, dispatchMock, { type: 'setParsed', parsed });
33+
34+
expect(dispatchMock).toBeCalledWith({
35+
type: 'setParsed',
36+
parsed: [
37+
{ forename: 'Hans', surname: 'Muster', email: '[email protected]' },
38+
{ forename: 'Heidi', surname: 'Muster', email: '[email protected]' },
39+
{ forename: 'Joe', surname: 'Doe', email: '[email protected]' }
40+
]
41+
});
42+
});
43+
44+
it('should run transformers on values of a certain column', () => {
45+
const trimTransformer = (value: string) => value.trim();
46+
const state: State<TestType> = {
47+
columns: defaultColumns,
48+
transformers: [{ transformer: trimTransformer, column: 'surname' }]
49+
};
50+
const dispatchMock = jest.fn();
51+
52+
middleware(state, dispatchMock, { type: 'setParsed', parsed });
53+
54+
expect(dispatchMock).toBeCalledWith({
55+
type: 'setParsed',
56+
parsed: [
57+
{ forename: 'Hans', surname: 'Muster', email: '[email protected]' },
58+
{ forename: 'Heidi', surname: 'Muster', email: '[email protected]' },
59+
{ forename: 'Joe', surname: 'Doe', email: ' [email protected] ' }
60+
]
61+
});
62+
});
63+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { State } from '../models/state';
2+
import { Dispatch } from 'react';
3+
import { Actions } from '../models/actions';
4+
import { TransformerConfiguration } from '../models/transformer';
5+
import { ColumnsType } from '../models/column';
6+
7+
const executeTransformer = <T>(
8+
values: T[],
9+
transformerConfiguration: TransformerConfiguration<T>,
10+
columns: ColumnsType<T>
11+
) => {
12+
const currentColumns = transformerConfiguration.column
13+
? columns.filter((c) => c.key === transformerConfiguration.column)
14+
: columns;
15+
return values.map<T>((r) => {
16+
const transformed = { ...r };
17+
currentColumns.forEach((c) => {
18+
transformed[c.key] = (transformerConfiguration.transformer(
19+
new String(r[c.key]).toString()
20+
) as unknown) as T[keyof T];
21+
});
22+
return transformed;
23+
});
24+
};
25+
26+
export const createTransformerMiddleware = <T>() => {
27+
return (state: State<T>, next: Dispatch<Actions<T>>, action: Actions<T>) => {
28+
if (action.type === 'setParsed' && state.transformers) {
29+
const parsed = state.transformers.reduce<T[]>(
30+
(acc, t) => executeTransformer(acc, t, state.columns),
31+
action.parsed
32+
);
33+
next({ type: 'setParsed', parsed });
34+
}
35+
};
36+
};

src/models/state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { ColumnsType } from './column';
22
import { ValidationError } from './validation';
3+
import { Transformers } from './transformer';
34

45
export interface State<T> {
56
raw?: string;
67
parsed?: T[];
78
validation?: ValidationError<T>[];
9+
transformers?: Transformers<T>;
810
columns: ColumnsType<T>;
911
}
1012

src/models/transformer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type Transformer = (value: string) => string;
2+
export type TransformerConfiguration<T> = { transformer: Transformer; column?: keyof T };
3+
export type Transformers<T> = TransformerConfiguration<T>[];

0 commit comments

Comments
 (0)