@matheuspuel/state-machine 0.4.0 → 0.5.0

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.
@@ -1,21 +1,20 @@
1
1
  import { describe, expect, it } from '@effect/vitest'
2
- import { StateMachine } from '@matheuspuel/state-machine'
3
- import { Form } from '@matheuspuel/state-machine/form'
2
+ import { Form, StateMachine } from '@matheuspuel/state-machine'
4
3
  import { Effect } from 'effect'
4
+ import { NoSuchElementException } from 'effect/Cause'
5
+ import { ValidationError } from './Form.js'
5
6
 
6
7
  describe('Form', () => {
7
8
  it('should work', () => {
8
- const formField = Form.field({
9
- initial: 0,
9
+ const formField = Form.transformField(Form.field(0), {
10
10
  validate: _ =>
11
11
  _ > 1 ? Effect.succeed({ n: _ }) : Effect.fail('low' as const),
12
12
  fromData: (data: { n: number }) => data.n,
13
13
  })
14
- const form = Form.Struct({
14
+ const machine = Form.Struct({
15
15
  a: formField,
16
16
  b: Form.Struct({ c: formField }),
17
17
  })
18
- const machine = StateMachine.Form(form)
19
18
  const instance = StateMachine.run(machine)
20
19
  const getState = () => instance.ref.get.pipe(Effect.runSync)
21
20
  expect(getState()).toStrictEqual<ReturnType<typeof getState>>({
@@ -64,4 +63,122 @@ describe('Form', () => {
64
63
  b: { c: { value: 3, error: null } },
65
64
  })
66
65
  })
66
+
67
+ it('TaggedUnion', () => {
68
+ const stateMachine = Form.TaggedUnion('_tag', {
69
+ A: Form.Struct({ a: Form.field(0), x: Form.field('') }),
70
+ B: Form.Struct({ b: Form.field(0), x: Form.field('') }),
71
+ })
72
+ const form = StateMachine.run(stateMachine)
73
+ const getState = () => form.ref.get.pipe(Effect.runSync)
74
+
75
+ form.actions.A.a.set(1)
76
+ form.actions.A.x.set('a')
77
+ expect(getState()).toStrictEqual<ReturnType<typeof getState>>({
78
+ _tag: { value: 'A', error: null },
79
+ A: { a: { value: 1, error: null }, x: { value: 'a', error: null } },
80
+ B: { b: { value: 0, error: null }, x: { value: '', error: null } },
81
+ })
82
+ const dataA = form.actions.validate().pipe(Effect.runSync)
83
+ expect(dataA).toStrictEqual<typeof dataA>({
84
+ _tag: 'A',
85
+ a: 1,
86
+ x: 'a',
87
+ })
88
+ form.actions._tag.set('B')
89
+ const dataB = form.actions.validate().pipe(Effect.runSync)
90
+ expect(dataB).toStrictEqual<typeof dataB>({
91
+ _tag: 'B',
92
+ b: 0,
93
+ x: '',
94
+ })
95
+ })
96
+
97
+ it('Array', () => {
98
+ const stateMachine = Form.Struct({
99
+ list: Form.Array(Form.Struct({ a: Form.NonEmptyString })),
100
+ })
101
+ const form = StateMachine.run(stateMachine)
102
+ const getState = () => form.ref.get.pipe(Effect.runSync)
103
+
104
+ form.actions.validate().pipe(Effect.runSyncExit)
105
+ expect(getState()).toStrictEqual<ReturnType<typeof getState>>({
106
+ list: [],
107
+ })
108
+ form.actions.list.addItem()
109
+ expect(getState()).toStrictEqual<ReturnType<typeof getState>>({
110
+ list: [{ a: { value: '', error: null } }],
111
+ })
112
+ form.actions.validate().pipe(Effect.runSyncExit)
113
+ expect(getState()).toStrictEqual<ReturnType<typeof getState>>({
114
+ list: [{ a: { value: '', error: new NoSuchElementException() } }],
115
+ })
116
+ form.actions.list.index(0).a.set('aaa')
117
+ const data = form.actions.validate().pipe(Effect.runSync)
118
+ expect(getState()).toStrictEqual<ReturnType<typeof getState>>({
119
+ list: [{ a: { value: 'aaa', error: null } }],
120
+ })
121
+ expect(data).toStrictEqual<typeof data>({
122
+ list: [{ a: 'aaa' }],
123
+ })
124
+ form.actions.list.addItem()
125
+ form.actions.list.removeItem(0)
126
+ expect(getState()).toStrictEqual<ReturnType<typeof getState>>({
127
+ list: [{ a: { value: '', error: null } }],
128
+ })
129
+ form.actions.setStateFromData({ list: [{ a: '1' }, { a: '2' }] })
130
+ expect(getState()).toStrictEqual<ReturnType<typeof getState>>({
131
+ list: [
132
+ { a: { value: '1', error: null } },
133
+ { a: { value: '2', error: null } },
134
+ ],
135
+ })
136
+ })
137
+
138
+ it('change password example', () => {
139
+ const stateMachine = Form.Struct({
140
+ oldPassword: Form.field(''),
141
+ newPassword: StateMachine.mapActions(
142
+ Form.Struct({
143
+ password: Form.field(''),
144
+ confirmation: Form.fieldWithError<'not-match'>()(Form.field('')),
145
+ }),
146
+ actions => ({
147
+ ...actions,
148
+ validate: () =>
149
+ Effect.gen(function* () {
150
+ const result = yield* actions.validate()
151
+ if (result.password !== result.confirmation) {
152
+ actions.confirmation.error.set('not-match' as const)
153
+ return yield* new ValidationError({
154
+ error: 'not-match' as const,
155
+ })
156
+ }
157
+ return result.password
158
+ }),
159
+ setStateFromData: (password: string) =>
160
+ actions.setStateFromData({ password, confirmation: password }),
161
+ }),
162
+ ),
163
+ })
164
+ const form = StateMachine.run(stateMachine)
165
+ const getState = () => form.ref.get.pipe(Effect.runSync)
166
+
167
+ form.actions.newPassword.password.set('123456')
168
+ form.actions.newPassword.confirmation.set('1234567')
169
+ form.actions.validate().pipe(Effect.runSyncExit)
170
+ expect(getState()).toStrictEqual<ReturnType<typeof getState>>({
171
+ oldPassword: { value: '', error: null },
172
+ newPassword: {
173
+ password: { value: '123456', error: null },
174
+ confirmation: { value: '1234567', error: 'not-match' },
175
+ },
176
+ })
177
+ form.actions.newPassword.confirmation.set('123456')
178
+ const data = form.actions.validate().pipe(Effect.runSync)
179
+ expect(data).toStrictEqual<typeof data>({
180
+ oldPassword: '',
181
+ newPassword: '123456',
182
+ })
183
+ })
67
184
  })