@pyreon/core 0.24.4 → 0.24.6

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 (56) hide show
  1. package/lib/analysis/index.js.html +1 -1
  2. package/lib/index.js +53 -31
  3. package/package.json +2 -6
  4. package/src/compat-marker.ts +0 -79
  5. package/src/compat-shared.ts +0 -80
  6. package/src/component.ts +0 -98
  7. package/src/context.ts +0 -349
  8. package/src/defer.ts +0 -279
  9. package/src/dynamic.ts +0 -32
  10. package/src/env.d.ts +0 -6
  11. package/src/error-boundary.ts +0 -90
  12. package/src/for.ts +0 -51
  13. package/src/h.ts +0 -80
  14. package/src/index.ts +0 -80
  15. package/src/jsx-dev-runtime.ts +0 -2
  16. package/src/jsx-runtime.ts +0 -747
  17. package/src/lazy.ts +0 -25
  18. package/src/lifecycle.ts +0 -152
  19. package/src/manifest.ts +0 -579
  20. package/src/map-array.ts +0 -42
  21. package/src/portal.ts +0 -39
  22. package/src/props.ts +0 -269
  23. package/src/ref.ts +0 -32
  24. package/src/show.ts +0 -121
  25. package/src/style.ts +0 -102
  26. package/src/suspense.ts +0 -52
  27. package/src/telemetry.ts +0 -120
  28. package/src/tests/compat-marker.test.ts +0 -96
  29. package/src/tests/compat-shared.test.ts +0 -99
  30. package/src/tests/component.test.ts +0 -281
  31. package/src/tests/context.test.ts +0 -629
  32. package/src/tests/core.test.ts +0 -1290
  33. package/src/tests/cx.test.ts +0 -70
  34. package/src/tests/defer.test.ts +0 -359
  35. package/src/tests/dynamic.test.ts +0 -87
  36. package/src/tests/error-boundary.test.ts +0 -181
  37. package/src/tests/extract-props-overloads.types.test.ts +0 -135
  38. package/src/tests/for.test.ts +0 -117
  39. package/src/tests/h.test.ts +0 -221
  40. package/src/tests/jsx-compat.test.tsx +0 -86
  41. package/src/tests/lazy.test.ts +0 -100
  42. package/src/tests/lifecycle.test.ts +0 -350
  43. package/src/tests/manifest-snapshot.test.ts +0 -100
  44. package/src/tests/map-array.test.ts +0 -313
  45. package/src/tests/native-marker-error-boundary.test.ts +0 -12
  46. package/src/tests/portal.test.ts +0 -48
  47. package/src/tests/props-extended.test.ts +0 -157
  48. package/src/tests/props.test.ts +0 -250
  49. package/src/tests/reactive-context.test.ts +0 -69
  50. package/src/tests/reactive-props.test.ts +0 -157
  51. package/src/tests/ref.test.ts +0 -70
  52. package/src/tests/show.test.ts +0 -314
  53. package/src/tests/style.test.ts +0 -157
  54. package/src/tests/suspense.test.ts +0 -139
  55. package/src/tests/telemetry.test.ts +0 -297
  56. package/src/types.ts +0 -116
@@ -1,250 +0,0 @@
1
- import { createUniqueId, mergeProps, splitProps } from '../props'
2
-
3
- describe('splitProps', () => {
4
- test('splits known keys from rest', () => {
5
- const props = { label: 'Hi', icon: 'star', class: 'btn', id: 'x' }
6
- const [own, html] = splitProps(props, ['label', 'icon'])
7
- expect(own).toEqual({ label: 'Hi', icon: 'star' })
8
- expect(html).toEqual({ class: 'btn', id: 'x' })
9
- })
10
-
11
- test('preserves getters', () => {
12
- let count = 0
13
- const props = Object.defineProperty({} as Record<string, unknown>, 'value', {
14
- get: () => ++count,
15
- enumerable: true,
16
- configurable: true,
17
- })
18
- const [own] = splitProps(props, ['value'])
19
- expect(own.value).toBe(1)
20
- expect(own.value).toBe(2) // getter called again
21
- })
22
-
23
- test('handles empty keys array', () => {
24
- const props = { a: 1, b: 2 }
25
- const [own, rest] = splitProps(props, [])
26
- expect(own).toEqual({})
27
- expect(rest).toEqual({ a: 1, b: 2 })
28
- })
29
- })
30
-
31
- describe('mergeProps', () => {
32
- test('later sources override earlier', () => {
33
- const result = mergeProps({ a: 1, b: 2 }, { b: 3, c: 4 })
34
- expect(result).toEqual({ a: 1, b: 3, c: 4 })
35
- })
36
-
37
- test("undefined values don't override defined", () => {
38
- const result = mergeProps({ size: 'md' }, { size: undefined as string | undefined })
39
- expect(result.size).toBe('md')
40
- })
41
-
42
- test('preserves getters from sources', () => {
43
- let count = 0
44
- const source = Object.defineProperty({} as Record<string, unknown>, 'val', {
45
- get: () => ++count,
46
- enumerable: true,
47
- configurable: true,
48
- })
49
- const result = mergeProps({ val: 0 }, source)
50
- expect(result.val).toBe(1)
51
- expect(result.val).toBe(2)
52
- })
53
-
54
- test('getter returning undefined falls back to previous value', () => {
55
- let override: string | undefined
56
- const source = Object.defineProperty({} as Record<string, unknown>, 'size', {
57
- get: () => override,
58
- enumerable: true,
59
- configurable: true,
60
- })
61
- const result = mergeProps({ size: 'md' }, source)
62
- expect(result.size).toBe('md') // getter returns undefined, fallback
63
-
64
- override = 'lg'
65
- expect(result.size).toBe('lg') // getter returns value
66
- })
67
- })
68
-
69
- describe('mergeProps — edge cases', () => {
70
- test('mixed getter/static value merging across 3+ sources', () => {
71
- let dynamicSize = 'sm'
72
- const source1 = { size: 'md', variant: 'primary' }
73
- const source2 = Object.defineProperty({} as Record<string, unknown>, 'size', {
74
- get: () => dynamicSize,
75
- enumerable: true,
76
- configurable: true,
77
- })
78
- const source3 = { color: 'red' }
79
- const result = mergeProps(source1, source2, source3)
80
- expect(result.size).toBe('sm')
81
- dynamicSize = 'xl'
82
- expect(result.size).toBe('xl')
83
- expect((result as Record<string, unknown>).variant).toBe('primary')
84
- expect((result as Record<string, unknown>).color).toBe('red')
85
- })
86
-
87
- test('getter returning undefined falling through multiple levels of defaults', () => {
88
- let level2: string | undefined
89
- let level1: string | undefined
90
- const defaults = { theme: 'light' }
91
- const mid = Object.defineProperty({} as Record<string, unknown>, 'theme', {
92
- get: () => level1,
93
- enumerable: true,
94
- configurable: true,
95
- })
96
- const top = Object.defineProperty({} as Record<string, unknown>, 'theme', {
97
- get: () => level2,
98
- enumerable: true,
99
- configurable: true,
100
- })
101
- const result = mergeProps(defaults, mid, top)
102
- // Both getters return undefined — falls back to "light"
103
- expect(result.theme).toBe('light')
104
- // Mid-level getter returns value — top still undefined, falls to mid
105
- level1 = 'dark'
106
- expect(result.theme).toBe('dark')
107
- // Top-level getter returns value — wins
108
- level2 = 'system'
109
- expect(result.theme).toBe('system')
110
- })
111
-
112
- test('mergeProps with no sources (empty call)', () => {
113
- const result = mergeProps()
114
- expect(result).toEqual({})
115
- })
116
-
117
- test('later source static value overriding earlier getter', () => {
118
- const dynamic = 'from-getter'
119
- const getterSource = Object.defineProperty({} as Record<string, unknown>, 'val', {
120
- get: () => dynamic,
121
- enumerable: true,
122
- configurable: true,
123
- })
124
- const staticSource = { val: 'static-wins' }
125
- const result = mergeProps(getterSource, staticSource)
126
- // Static value should override getter
127
- expect(result.val).toBe('static-wins')
128
- })
129
-
130
- test('later source static undefined does not override earlier getter', () => {
131
- let dynamic = 'from-getter'
132
- const getterSource = Object.defineProperty({} as Record<string, unknown>, 'val', {
133
- get: () => dynamic,
134
- enumerable: true,
135
- configurable: true,
136
- })
137
- const staticSource = { val: undefined }
138
- const result = mergeProps(getterSource, staticSource)
139
- // Static undefined — getter should still be used
140
- expect(result.val).toBe('from-getter')
141
- dynamic = 'updated'
142
- expect(result.val).toBe('updated')
143
- })
144
- })
145
-
146
- describe('splitProps — getter reactivity', () => {
147
- test('preserves getter reactivity through multiple reads', () => {
148
- let count = 0
149
- const props = Object.defineProperty({ other: 'x' } as Record<string, unknown>, 'value', {
150
- get: () => ++count,
151
- enumerable: true,
152
- configurable: true,
153
- })
154
- const [own, rest] = splitProps(props, ['value'])
155
- expect(own.value).toBe(1)
156
- expect(own.value).toBe(2)
157
- expect(own.value).toBe(3)
158
- // rest should not include the getter key
159
- expect(rest).toEqual({ other: 'x' })
160
- })
161
- })
162
-
163
- describe('mergeProps — non-configurable getter sources (regression)', () => {
164
- // Regression: when a source object has a getter defined via
165
- // Object.defineProperty without an explicit `configurable: true`,
166
- // configurable defaults to false. mergeProps forwarded the descriptor as-is,
167
- // so a later source overriding the same key crashed with
168
- // "Cannot redefine property". Real-world trigger: defineProperty-based
169
- // observables, some test mocks, some Proxy getter wrappers.
170
- test('mergeProps handles getters without explicit configurable flag', () => {
171
- const a: Record<string, unknown> = {}
172
- Object.defineProperty(a, 'x', { get: () => 1, enumerable: true })
173
- const b = { x: 2 }
174
- expect(() => mergeProps(a, b)).not.toThrow()
175
- expect(mergeProps(a, b).x).toBe(2)
176
- })
177
-
178
- test('mergeProps result properties are configurable (can be overridden later)', () => {
179
- const a: Record<string, unknown> = {}
180
- Object.defineProperty(a, 'x', { get: () => 1, enumerable: true })
181
- const b = { y: 'b' }
182
- const merged = mergeProps(a, b)
183
- expect(() =>
184
- Object.defineProperty(merged, 'x', { value: 42, enumerable: true, configurable: true }),
185
- ).not.toThrow()
186
- expect(() =>
187
- Object.defineProperty(merged, 'y', { value: 99, enumerable: true, configurable: true }),
188
- ).not.toThrow()
189
- })
190
-
191
- test('splitProps result properties are configurable (can be overridden later)', () => {
192
- const a: Record<string, unknown> = { other: 'x' }
193
- Object.defineProperty(a, 'v', { get: () => 1, enumerable: true })
194
- const [own, rest] = splitProps(a, ['v'])
195
- expect(() =>
196
- Object.defineProperty(own, 'v', { value: 42, enumerable: true, configurable: true }),
197
- ).not.toThrow()
198
- expect(() =>
199
- Object.defineProperty(rest, 'other', {
200
- value: 'y',
201
- enumerable: true,
202
- configurable: true,
203
- }),
204
- ).not.toThrow()
205
- })
206
- })
207
-
208
- describe('splitProps / mergeProps — symbol keys (regression)', () => {
209
- // Regression: Object.keys silently drops symbol-keyed properties, so any
210
- // Symbol.for('pyreon.reactiveProp')-branded prop or user-supplied symbol
211
- // key would disappear from both picked and rest. Use Reflect.ownKeys.
212
- test('splitProps preserves symbol-keyed properties in rest', () => {
213
- const SYM = Symbol('marker')
214
- const props = { label: 'x', [SYM]: 'branded' } as Record<string | symbol, unknown>
215
- const [own, rest] = splitProps(props as { label: string }, ['label'])
216
- expect(own.label).toBe('x')
217
- expect((rest as Record<symbol, unknown>)[SYM]).toBe('branded')
218
- })
219
-
220
- test('splitProps moves a symbol key to picked when it is named in keys', () => {
221
- const SYM = Symbol('marker')
222
- const props = { label: 'x', [SYM]: 'branded' } as Record<string | symbol, unknown>
223
- const [own, rest] = splitProps(
224
- props as { label: string; [SYM]: string },
225
- ['label', SYM] as Array<'label' | typeof SYM>,
226
- )
227
- expect((own as Record<symbol, unknown>)[SYM]).toBe('branded')
228
- expect((rest as Record<symbol, unknown>)[SYM]).toBeUndefined()
229
- })
230
-
231
- test('mergeProps preserves symbol-keyed properties from every source', () => {
232
- const A = Symbol('a')
233
- const B = Symbol('b')
234
- const src1 = { [A]: 1 } as Record<string | symbol, unknown>
235
- const src2 = { [B]: 2 } as Record<string | symbol, unknown>
236
- const merged = mergeProps(src1, src2) as Record<string | symbol, unknown>
237
- expect(merged[A]).toBe(1)
238
- expect(merged[B]).toBe(2)
239
- })
240
- })
241
-
242
- describe('createUniqueId', () => {
243
- test('returns incrementing IDs', () => {
244
- const id1 = createUniqueId()
245
- const id2 = createUniqueId()
246
- expect(id1).toMatch(/^pyreon-\d+$/)
247
- expect(id2).toMatch(/^pyreon-\d+$/)
248
- expect(id1).not.toBe(id2)
249
- })
250
- })
@@ -1,69 +0,0 @@
1
- import { signal } from '@pyreon/reactivity'
2
- import { runWithHooks } from '../component'
3
- import { createReactiveContext, popContext, provide, useContext } from '../context'
4
-
5
- // Coverage for `createReactiveContext` — previously untested public API.
6
- describe('createReactiveContext', () => {
7
- test('default accessor returns the default value when no provider', () => {
8
- const Ctx = createReactiveContext<'light' | 'dark'>('light')
9
- const getter = useContext(Ctx)
10
- expect(typeof getter).toBe('function')
11
- expect(getter()).toBe('light')
12
- })
13
-
14
- test('consumer re-reads the latest signal value through the provided accessor', () => {
15
- const Ctx = createReactiveContext<'light' | 'dark'>('light')
16
- const mode = signal<'light' | 'dark'>('light')
17
-
18
- const seen: Array<'light' | 'dark'> = []
19
-
20
- // provide() registers an onUnmount, so it must run under runWithHooks.
21
- const { hooks } = runWithHooks(() => {
22
- provide(Ctx, () => mode())
23
- const getter = useContext(Ctx)
24
- seen.push(getter())
25
- mode.set('dark')
26
- seen.push(getter())
27
- return null
28
- }, {})
29
-
30
- expect(seen).toEqual(['light', 'dark'])
31
-
32
- // Unmount the provider — outside the scope, useContext returns the default.
33
- for (const fn of hooks.unmount!) fn()
34
- const outerGetter = useContext(Ctx)
35
- expect(outerGetter()).toBe('light')
36
- })
37
-
38
- test('nested providers shadow the outer provider, and the outer is restored after unmount', () => {
39
- const Ctx = createReactiveContext<string>('default')
40
-
41
- const outerRun = runWithHooks(() => {
42
- provide(Ctx, () => 'outer')
43
- const outerGetter = useContext(Ctx)
44
- expect(outerGetter()).toBe('outer')
45
-
46
- const innerRun = runWithHooks(() => {
47
- provide(Ctx, () => 'inner')
48
- const innerGetter = useContext(Ctx)
49
- expect(innerGetter()).toBe('inner')
50
- return null
51
- }, {})
52
- // Run the inner provider's unmount to pop its frame.
53
- for (const fn of innerRun.hooks.unmount!) fn()
54
-
55
- // Outer provider is restored.
56
- expect(outerGetter()).toBe('outer')
57
- return null
58
- }, {})
59
-
60
- // Clean up outer frame.
61
- for (const fn of outerRun.hooks.unmount!) fn()
62
-
63
- // After full teardown, useContext falls back to the default accessor.
64
- expect(useContext(Ctx)()).toBe('default')
65
-
66
- // Sanity — popContext on an already-empty stack should not crash.
67
- popContext()
68
- })
69
- })
@@ -1,157 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { makeReactiveProps, REACTIVE_PROP, _rp, _wrapSpread } from '../props'
3
-
4
- describe('makeReactiveProps', () => {
5
- it('returns raw object when no reactive props exist (fast path)', () => {
6
- const raw = { a: 1, b: 'hello', c: true }
7
- const result = makeReactiveProps(raw)
8
- // Must return the exact same object reference — no allocation
9
- expect(result).toBe(raw)
10
- })
11
-
12
- it('returns raw when props contain non-branded functions', () => {
13
- const fn = () => 42
14
- const raw = { handler: fn, name: 'test' }
15
- const result = makeReactiveProps(raw)
16
- expect(result).toBe(raw)
17
- // Non-branded function is preserved as-is
18
- expect(result.handler).toBe(fn)
19
- })
20
-
21
- it('converts branded _rp() props to getter properties', () => {
22
- let value = 10
23
- const accessor = _rp(() => value)
24
- const raw = { count: accessor, label: 'hello' }
25
- const result = makeReactiveProps(raw)
26
-
27
- // Must NOT be the same object (a new getter-backed object is created)
28
- expect(result).not.toBe(raw)
29
- // Getter reads the current value
30
- expect(result.count).toBe(10)
31
- value = 20
32
- expect(result.count).toBe(20)
33
- // Static props are copied as-is
34
- expect(result.label).toBe('hello')
35
- })
36
-
37
- it('handles mixed reactive and static props', () => {
38
- let x = 'a'
39
- const raw = {
40
- static1: 42,
41
- reactive1: _rp(() => x),
42
- static2: true,
43
- nonBranded: () => 'plain function',
44
- }
45
- const result = makeReactiveProps(raw)
46
-
47
- expect(result).not.toBe(raw)
48
- expect(result.static1).toBe(42)
49
- expect(result.static2).toBe(true)
50
- expect(result.reactive1).toBe('a')
51
- // Non-branded function is copied as a value, not converted to getter
52
- expect(typeof result.nonBranded).toBe('function')
53
- expect((result.nonBranded as () => string)()).toBe('plain function')
54
-
55
- x = 'b'
56
- expect(result.reactive1).toBe('b')
57
- })
58
-
59
- it('reactive prop getters are enumerable and configurable', () => {
60
- const raw = { x: _rp(() => 1) }
61
- const result = makeReactiveProps(raw)
62
- const desc = Object.getOwnPropertyDescriptor(result, 'x')
63
- expect(desc?.enumerable).toBe(true)
64
- expect(desc?.configurable).toBe(true)
65
- expect(typeof desc?.get).toBe('function')
66
- })
67
-
68
- it('handles empty props object', () => {
69
- const raw = {}
70
- const result = makeReactiveProps(raw)
71
- expect(result).toBe(raw)
72
- })
73
- })
74
-
75
- describe('_rp', () => {
76
- it('brands a function with REACTIVE_PROP', () => {
77
- const fn = () => 42
78
- const branded = _rp(fn)
79
- expect(branded).toBe(fn) // same function reference
80
- expect((branded as any)[REACTIVE_PROP]).toBe(true)
81
- })
82
-
83
- it('branded function still callable', () => {
84
- const branded = _rp(() => 'hello')
85
- expect(branded()).toBe('hello')
86
- })
87
- })
88
-
89
- describe('_wrapSpread', () => {
90
- it('returns null/undefined unchanged (primitive guard)', () => {
91
- expect(_wrapSpread(null)).toBe(null)
92
- expect(_wrapSpread(undefined)).toBe(undefined)
93
- })
94
-
95
- it('returns source unchanged when no getter descriptors exist (fast path)', () => {
96
- const source = { a: 1, b: 'x', c: true }
97
- expect(_wrapSpread(source)).toBe(source)
98
- })
99
-
100
- it('returns source unchanged for empty objects', () => {
101
- const source = {}
102
- expect(_wrapSpread(source)).toBe(source)
103
- })
104
-
105
- it('wraps getter-shaped reactive props as _rp-branded thunks', () => {
106
- let liveValue = 'a'
107
- const source = {} as Record<string, unknown>
108
- Object.defineProperty(source, 'x', {
109
- get: () => liveValue,
110
- enumerable: true,
111
- configurable: true,
112
- })
113
-
114
- const result = _wrapSpread(source) as Record<string, unknown>
115
- expect(result).not.toBe(source) // new object allocated
116
-
117
- const wrappedX = result.x as () => unknown
118
- expect(typeof wrappedX).toBe('function')
119
- expect((wrappedX as unknown as Record<symbol, unknown>)[REACTIVE_PROP]).toBe(true)
120
-
121
- // Lazy read — each call reads the current source[x] getter value
122
- expect(wrappedX()).toBe('a')
123
- liveValue = 'b'
124
- expect(wrappedX()).toBe('b') // live re-read, not captured
125
- })
126
-
127
- it('preserves data properties as-is when mixed with getters', () => {
128
- const source = { plain: 'data' } as Record<string, unknown>
129
- Object.defineProperty(source, 'reactive', {
130
- get: () => 'live',
131
- enumerable: true,
132
- configurable: true,
133
- })
134
-
135
- const result = _wrapSpread(source) as Record<string, unknown>
136
- expect(result.plain).toBe('data') // copied through
137
- expect(typeof result.reactive).toBe('function') // wrapped as thunk
138
- })
139
-
140
- it('preserves Reflect.ownKeys symbol-keyed properties', () => {
141
- const sym = Symbol('marker')
142
- const source = { regular: 'x' } as Record<string | symbol, unknown>
143
- Object.defineProperty(source, 'reactive', {
144
- get: () => 'live',
145
- enumerable: true,
146
- configurable: true,
147
- })
148
- source[sym] = 'symbol-value'
149
-
150
- const result = _wrapSpread(source) as Record<string | symbol, unknown>
151
- expect(result.regular).toBe('x')
152
- // Note: symbol keys go through Reflect.ownKeys; the wrap path indexes
153
- // via `key as string` for type narrowing but the runtime carries them
154
- // forward as data properties.
155
- expect(typeof result.reactive).toBe('function')
156
- })
157
- })
@@ -1,70 +0,0 @@
1
- import type { Ref, RefCallback, RefProp } from '../ref'
2
- import { createRef } from '../ref'
3
-
4
- describe('createRef', () => {
5
- test('returns object with current = null', () => {
6
- const ref = createRef()
7
- expect(ref.current).toBeNull()
8
- })
9
-
10
- test('current is mutable', () => {
11
- const ref = createRef<number>()
12
- ref.current = 42
13
- expect(ref.current).toBe(42)
14
- })
15
-
16
- test('typed ref — HTMLElement', () => {
17
- const ref = createRef<HTMLDivElement>()
18
- expect(ref.current).toBeNull()
19
- // In real code, runtime-dom sets this after mount
20
- ref.current = {} as HTMLDivElement
21
- expect(ref.current).not.toBeNull()
22
- })
23
-
24
- test('typed ref — string', () => {
25
- const ref = createRef<string>()
26
- ref.current = 'hello'
27
- expect(ref.current).toBe('hello')
28
- })
29
-
30
- test('can be reset to null', () => {
31
- const ref = createRef<number>()
32
- ref.current = 99
33
- expect(ref.current).toBe(99)
34
- ref.current = null
35
- expect(ref.current).toBeNull()
36
- })
37
-
38
- test('each createRef returns a unique object', () => {
39
- const ref1 = createRef()
40
- const ref2 = createRef()
41
- expect(ref1).not.toBe(ref2)
42
- })
43
-
44
- test('ref object has exactly one property', () => {
45
- const ref = createRef()
46
- expect(Object.keys(ref)).toEqual(['current'])
47
- })
48
-
49
- test('ref object shape matches Ref interface', () => {
50
- const ref: Ref<number> = createRef<number>()
51
- expect('current' in ref).toBe(true)
52
- expect(ref.current).toBeNull()
53
- })
54
- })
55
-
56
- describe('RefCallback type (type-level verification)', () => {
57
- test('callback ref can be assigned to RefProp', () => {
58
- const callback: RefCallback<HTMLElement> = (_el) => {}
59
- // Type-level test: RefProp accepts both object ref and callback ref
60
- const prop: RefProp<HTMLElement> = callback
61
- expect(typeof prop).toBe('function')
62
- })
63
-
64
- test('object ref can be assigned to RefProp', () => {
65
- const ref = createRef<HTMLElement>()
66
- const prop: RefProp<HTMLElement> = ref
67
- expect(typeof prop).toBe('object')
68
- expect(prop).toBe(ref)
69
- })
70
- })