mayoiga 0.0.22 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.eslintrc.js +111 -0
  2. package/.github/workflows/checker.yml +42 -0
  3. package/.gitignore +217 -0
  4. package/.prettierrc +5 -0
  5. package/README.md +21 -45
  6. package/component.d.ts +1 -0
  7. package/component.js +1 -0
  8. package/dist/component.d.ts +7 -0
  9. package/dist/component.js +11 -0
  10. package/dist/component.test.d.ts +1 -0
  11. package/dist/component.test.js +83 -0
  12. package/dist/index.d.ts +92 -0
  13. package/dist/index.js +150 -0
  14. package/dist/index.test.d.ts +1 -0
  15. package/dist/index.test.js +77 -0
  16. package/dist/utils.d.ts +2 -0
  17. package/dist/utils.js +6 -0
  18. package/docs/.nojekyll +0 -0
  19. package/docs/assets/css/main.css +2328 -0
  20. package/docs/assets/images/icons.png +0 -0
  21. package/docs/assets/images/icons@2x.png +0 -0
  22. package/docs/assets/images/widgets.png +0 -0
  23. package/docs/assets/images/widgets@2x.png +0 -0
  24. package/docs/assets/js/main.js +1 -0
  25. package/docs/assets/js/search.js +3 -0
  26. package/docs/globals.html +185 -0
  27. package/docs/index.html +249 -0
  28. package/docs/modules/_actions_.html +398 -0
  29. package/docs/modules/_forms_.html +362 -0
  30. package/docs/modules/_index_.html +170 -0
  31. package/docs/modules/_inputprotocol_.html +319 -0
  32. package/docs/modules/_mayoiga_.html +783 -0
  33. package/docs/modules/_reducer_.html +417 -0
  34. package/example/src/demo-app.tsx +123 -0
  35. package/example/src/index.tsx +15 -0
  36. package/package.json +42 -44
  37. package/setup/vite-global-setup.js +1 -0
  38. package/setup/vite-set-global.js +0 -0
  39. package/src/component.test.tsx +146 -0
  40. package/src/component.tsx +23 -0
  41. package/src/index.test.tsx +122 -0
  42. package/src/index.ts +311 -0
  43. package/src/utils.ts +8 -0
  44. package/tsconfig.json +38 -0
  45. package/vite.config.js +10 -0
  46. package/yarn.lock +2378 -0
  47. package/lib/actions.d.ts +0 -28
  48. package/lib/actions.js +0 -29
  49. package/lib/actions.js.map +0 -1
  50. package/lib/forms.d.ts +0 -21
  51. package/lib/forms.js +0 -82
  52. package/lib/forms.js.map +0 -1
  53. package/lib/index.d.ts +0 -3
  54. package/lib/index.js +0 -7
  55. package/lib/index.js.map +0 -1
  56. package/lib/inputProtocol.d.ts +0 -17
  57. package/lib/inputProtocol.js +0 -24
  58. package/lib/inputProtocol.js.map +0 -1
  59. package/lib/mayoiga.d.ts +0 -110
  60. package/lib/mayoiga.js +0 -131
  61. package/lib/mayoiga.js.map +0 -1
  62. package/lib/reducer.d.ts +0 -20
  63. package/lib/reducer.js +0 -53
  64. package/lib/reducer.js.map +0 -1
package/package.json CHANGED
@@ -1,19 +1,26 @@
1
1
  {
2
2
  "name": "mayoiga",
3
- "version": "0.0.22",
4
- "description": "Form library for React",
5
- "main": "lib/index.js",
6
- "types": "lib/index.d.ts",
3
+ "version": "0.1.3",
4
+ "description": "Type safe hook based form library for React",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
7
  "files": [
8
- "lib"
8
+ "dist",
9
+ "/component.js",
10
+ "/component.d.ts",
11
+ "!*.test.js"
9
12
  ],
10
13
  "scripts": {
11
- "test": "echo \"Error: no test specified\" && exit 1",
12
- "type-check": "tsc example/src/demo-app.tsx --noEmit --jsx preserve",
13
- "clean": "rm -rf lib/",
14
- "docs": "typedoc --name Mayoiga --mode modules --excludePrivate --out docs/",
15
- "compile": "tsc",
16
- "prepublish": "npm run clean && npm run compile"
14
+ "clean": "rm -rf dist",
15
+ "test": "vitest run",
16
+ "prebuild": "npm run clean",
17
+ "build": "tsc",
18
+ "check:type": "tsc --noEmit",
19
+ "check:style": "prettier --check 'src/**/*.{js,ts,jsx,tsx}'",
20
+ "check:lint": "eslint 'src/**/*.{ts,tsx}'",
21
+ "check-all": "npm run check:type && npm run check:style && npm run check:lint && npm run build --noEmit && npm run test",
22
+ "fix-all": "npm run check:style -- --write && npm run check:lint -- --fix",
23
+ "prepublish": "npm run check-all && npm run build"
17
24
  },
18
25
  "repository": {
19
26
  "type": "git",
@@ -26,45 +33,36 @@
26
33
  "validation",
27
34
  "state"
28
35
  ],
29
- "author": "Ogura Daiki",
36
+ "author": "OGURA Daiki",
30
37
  "license": "MIT",
31
38
  "bugs": {
32
39
  "url": "https://github.com/hachibeeDI/mayoiga/issues"
33
40
  },
34
41
  "homepage": "https://github.com/hachibeeDI/mayoiga#readme",
35
- "devDependencies": {
36
- "@babel/cli": "^7.4.4",
37
- "@babel/core": "^7.4.5",
38
- "@babel/plugin-proposal-class-properties": "^7.4.4",
39
- "@babel/plugin-proposal-object-rest-spread": "^7.4.4",
40
- "@babel/preset-env": "^7.4.5",
41
- "@babel/preset-react": "^7.0.0",
42
- "@babel/preset-typescript": "^7.3.3",
43
- "@types/node": "^11.11.4",
44
- "@types/react": "^16.8.19",
45
- "@types/react-dom": "^16.8.2",
46
- "babel-loader": "^8.0.6",
47
- "prettier": "^1.18.2",
48
- "react": "^16.8.6",
49
- "react-dom": "^16.8.6",
50
- "tslint": "^5.14.0",
51
- "tslint-config-prettier": "^1.18.0",
52
- "tslint-immutable": "^6.0.0",
53
- "tslint-plugin-prettier": "^2.0.1",
54
- "tslint-react": "^4.0.0",
55
- "typedoc": "^0.14.2",
56
- "typescript": "^3.5.1",
57
- "typescript-eslint-parser": "^22.0.0",
58
- "webpack": "^4.29.6",
59
- "webpack-cli": "^3.3.3",
60
- "webpack-dev-server": "^3.7.1"
61
- },
62
42
  "peerDependencies": {
63
- "react": "^16.8.0"
43
+ "zod": "^3.14.4"
64
44
  },
65
-
66
- "resolutions": {
67
- "@types/react": "^16.8.19"
45
+ "dependencies": {
46
+ "nozuchi": "^0.0.3"
68
47
  },
69
- "dependencies": {}
48
+ "devDependencies": {
49
+ "@testing-library/react": "^13.0.0",
50
+ "@testing-library/user-event": "^14.1.1",
51
+ "@types/node": "^17.0.23",
52
+ "@types/react": "^18.0.1",
53
+ "@types/use-sync-external-store": "^0.0.3",
54
+ "@typescript-eslint/eslint-plugin": "^5.20.0",
55
+ "@typescript-eslint/parser": "^5.20.0",
56
+ "eslint": "^8.13.0",
57
+ "eslint-plugin-import": "^2.26.0",
58
+ "eslint-plugin-testing-library": "^5.3.1",
59
+ "jsdom": "^19.0.0",
60
+ "prettier": "^2.6.2",
61
+ "react": "^18.0.0",
62
+ "react-dom": "^18.0.0",
63
+ "typescript": "^4.6.3",
64
+ "use-sync-external-store": "^1.0.0",
65
+ "vitest": "^0.9.3",
66
+ "zod": "^3.14.4"
67
+ }
70
68
  }
@@ -0,0 +1 @@
1
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
File without changes
@@ -0,0 +1,146 @@
1
+ import {render, screen, waitFor} from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import React from 'react';
4
+
5
+ import {test, expect} from 'vitest';
6
+
7
+ import * as zod from 'zod';
8
+
9
+ import {Field, Slicer} from './component';
10
+
11
+ import {createFormHook} from './index';
12
+
13
+ import type {Controller} from './index';
14
+
15
+ const testSchema = zod.object({
16
+ name: zod.string(),
17
+ description: zod.string(),
18
+ age: zod
19
+ .string()
20
+ .transform((val) => Number(val))
21
+ .refine((val) => (Number.isNaN(val) ? 0 : val)),
22
+ marked: zod.boolean(),
23
+ });
24
+
25
+ // every fields are optional for test
26
+ type FormStateBeforeValidation = {
27
+ name: string;
28
+ description: string;
29
+ age: string;
30
+ marked: boolean;
31
+ };
32
+
33
+ const createTestHook = () => createFormHook({name: '', description: '', age: '', marked: false} as FormStateBeforeValidation, testSchema);
34
+
35
+ // afterEach(cleanup);
36
+
37
+ test('Field component works fine in basic usage.', async () => {
38
+ const TestFormHook = createTestHook();
39
+
40
+ const Top = () => {
41
+ return (
42
+ <div>
43
+ <Field controller={TestFormHook.controller} name="age">
44
+ {(tool) => <input data-testid="age-input" {...tool} />}
45
+ </Field>
46
+ </div>
47
+ );
48
+ };
49
+
50
+ render(<Top />);
51
+
52
+ const INPUT_AGE = '42';
53
+
54
+ // calling `userEvent` in here will cause race condition...
55
+ await userEvent.type(screen.getByTestId('age-input'), INPUT_AGE);
56
+
57
+ await waitFor(() => {
58
+ const ageInput: HTMLInputElement = screen.getByTestId('age-input');
59
+ expect(ageInput.value).toBe(INPUT_AGE);
60
+ TestFormHook.api.actions.peek((s) => {
61
+ expect(s.value.age).toBe(INPUT_AGE);
62
+ expect(s.errors.age).toBeNull();
63
+ });
64
+ });
65
+ });
66
+
67
+ test('Slice component works fine in basic usage.', async () => {
68
+ const TestFormHook = createTestHook();
69
+
70
+ const Top = () => {
71
+ return (
72
+ <div>
73
+ <Slicer controller={TestFormHook.controller} selector={(s) => [s.value.age] as const}>
74
+ {(tool, age: string) => (
75
+ <input data-testid="age-input" name="age" value={age} onChange={(e) => tool.handleChange('age' as const, e.target.value)} />
76
+ )}
77
+ </Slicer>
78
+ </div>
79
+ );
80
+ };
81
+
82
+ render(<Top />);
83
+
84
+ const INPUT_AGE = '42';
85
+
86
+ // calling `userEvent` in here will cause race condition...
87
+ await userEvent.type(screen.getByTestId('age-input'), INPUT_AGE);
88
+
89
+ await waitFor(() => {
90
+ const ageInput: HTMLInputElement = screen.getByTestId('age-input');
91
+ expect(ageInput.value).toBe(INPUT_AGE);
92
+ TestFormHook.api.actions.peek((s) => {
93
+ expect(s.value.age).toBe(INPUT_AGE);
94
+ expect(s.errors.age).toBeNull();
95
+ });
96
+ });
97
+ });
98
+
99
+ test('A user able to create a reusable component', async () => {
100
+ const TestFormHook = createTestHook();
101
+
102
+ function NarrowTypedExpected(props: {controller: Controller<{age: string}>}) {
103
+ return (
104
+ <Field controller={props.controller} name="age">
105
+ {(tool) => <input data-testid="age-input" {...tool} />}
106
+ </Field>
107
+ );
108
+ }
109
+
110
+ function ErrorDisplayer(props: {controller: Controller<{age: string}>}) {
111
+ const {controller} = props;
112
+ return (
113
+ <Slicer controller={controller} selector={(s) => [s.value.age] as const}>
114
+ {(tool, age: string) => (
115
+ <button
116
+ onClick={(e) => {
117
+ controller.actions.pushFormErrors((_state) => ({age: age === '42' ? 'yes!!!' : 'noooo'}));
118
+ }}
119
+ />
120
+ )}
121
+ </Slicer>
122
+ );
123
+ }
124
+
125
+ render(
126
+ <div>
127
+ <NarrowTypedExpected controller={TestFormHook.controller} />
128
+ <ErrorDisplayer controller={TestFormHook.controller} />
129
+ </div>,
130
+ );
131
+
132
+ const INPUT_AGE = '42';
133
+
134
+ // calling `userEvent` in here will cause race condition...
135
+ await userEvent.type(screen.getByTestId('age-input'), INPUT_AGE);
136
+ await userEvent.click(screen.getByRole('button'));
137
+
138
+ await waitFor(() => {
139
+ const ageInput: HTMLInputElement = screen.getByTestId('age-input');
140
+ expect(ageInput.value).toBe(INPUT_AGE);
141
+ TestFormHook.api.actions.peek((s) => {
142
+ expect(s.value.age).toBe(INPUT_AGE);
143
+ expect(s.errors.age).toBe('yes!!!');
144
+ });
145
+ });
146
+ });
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+
3
+ import type {SliceProps, FieldProps, StateRestriction, Controller} from './index';
4
+
5
+ type ControlledComponentProps<State, Props> = Props & {
6
+ controller: Controller<State>;
7
+ };
8
+
9
+ export function Slicer<State extends StateRestriction, R extends ReadonlyArray<unknown>>(
10
+ props: ControlledComponentProps<State, SliceProps<State, R>>,
11
+ ) {
12
+ const {controller, ...restProps} = props;
13
+ const {Slicer: Delegate} = controller.components;
14
+ return <Delegate {...restProps} />;
15
+ }
16
+
17
+ export function Field<State extends StateRestriction, Name extends keyof State>(
18
+ props: ControlledComponentProps<State, FieldProps<State, Name>>,
19
+ ) {
20
+ const {controller, ...restProps} = props;
21
+ const {Field: Delegate} = controller.components;
22
+ return <Delegate {...restProps} />;
23
+ }
@@ -0,0 +1,122 @@
1
+ import {render, screen, waitFor} from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import React from 'react';
4
+
5
+ import {test, expect} from 'vitest';
6
+
7
+ import * as zod from 'zod';
8
+
9
+ import {createFormHook, useFormSlice} from './index';
10
+
11
+ import type {Controller} from './index';
12
+
13
+ const testSchema = zod.object({
14
+ name: zod.string(),
15
+ description: zod.string(),
16
+ age: zod
17
+ .string()
18
+ .transform((val) => Number(val))
19
+ .refine((val) => (Number.isNaN(val) ? 0 : val)),
20
+ marked: zod.boolean(),
21
+ });
22
+
23
+ // every fields are optional for test
24
+ type FormStateBeforeValidation = {
25
+ name: string;
26
+ description: string;
27
+ age: string;
28
+ marked: boolean;
29
+ };
30
+
31
+ const createTestHook = () => createFormHook({name: '', description: '', age: '', marked: false} as FormStateBeforeValidation, testSchema);
32
+
33
+ const AgeInputComponent = (props: {controller: Controller<FormStateBeforeValidation>}) => {
34
+ const {controller} = props;
35
+ const [[age, errorMessage], {handleChange}] = useFormSlice(controller, (s) => [s.value.age, s.errors.age] as const);
36
+ return (
37
+ <div>
38
+ <input
39
+ data-testid="age-input"
40
+ value={age}
41
+ name="age"
42
+ onChange={(e) => {
43
+ handleChange('age' as const, e.currentTarget.value);
44
+ }}
45
+ />
46
+ <span data-testid="age-error">{errorMessage}</span>
47
+ </div>
48
+ );
49
+ };
50
+
51
+ // afterEach(cleanup);
52
+
53
+ test('useSelector can select the fields', async () => {
54
+ const TestFormHook = createTestHook();
55
+
56
+ const Top = () => {
57
+ return (
58
+ <div>
59
+ <AgeInputComponent controller={TestFormHook.controller} />
60
+ </div>
61
+ );
62
+ };
63
+
64
+ render(<Top />);
65
+
66
+ const INPUT_AGE = '4';
67
+
68
+ // fireEvent.change(screen.getByTestId('age-input') , {target: {value: INPUT_AGE}})
69
+ // calling `userEvent` in here will cause race condition...
70
+ await userEvent.type(screen.getByTestId('age-input'), INPUT_AGE);
71
+ // TestFormHook.actions.handleBulkChange(prev => ({...prev, name: 'test', description: 'desc'}));
72
+
73
+ await waitFor(() => {
74
+ const ageInput: HTMLInputElement = screen.getByTestId('age-input');
75
+ expect(ageInput.value).toBe(INPUT_AGE);
76
+ });
77
+ });
78
+
79
+ test('zod parse value before submit', async () => {
80
+ const TestFormHook = createTestHook();
81
+
82
+ const INPUT_AGE = '9';
83
+ const PARSED_RESULT = Number(INPUT_AGE);
84
+
85
+ const handleSubmitTester = TestFormHook.handleSubmit((e) => (result) => {
86
+ if (result.success) {
87
+ expect(result.data.age).toBe(PARSED_RESULT);
88
+ } else {
89
+ expect('should not called').toBe(result.error);
90
+ }
91
+ });
92
+
93
+ const Top = () => {
94
+ return (
95
+ <div>
96
+ <AgeInputComponent controller={TestFormHook} />
97
+ <button data-testid="submit-button" onClick={handleSubmitTester} />
98
+ </div>
99
+ );
100
+ };
101
+
102
+ render(<Top />);
103
+
104
+ const ageInput: HTMLInputElement = screen.getByTestId('age-input');
105
+ await userEvent.type(ageInput, INPUT_AGE);
106
+
107
+ await waitFor(() => {
108
+ const ageInput: HTMLInputElement = screen.getByTestId('age-input');
109
+ expect(ageInput.value).toBe(INPUT_AGE);
110
+ });
111
+
112
+ const submitButton = screen.getByTestId('submit-button');
113
+ await userEvent.click(submitButton);
114
+
115
+ await waitFor(() => {
116
+ console.log('waiting');
117
+ TestFormHook.api.actions.peek((s) => {
118
+ expect(s.value.age).toBe(INPUT_AGE);
119
+ expect(s.errors.age).toBeNull();
120
+ });
121
+ });
122
+ });