Skip to content

Commit

Permalink
Merge branch 'next' into fix-ip
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoyatong authored Jan 22, 2025
2 parents 7444846 + 36220ef commit 37eecab
Show file tree
Hide file tree
Showing 18 changed files with 715 additions and 43 deletions.
2 changes: 1 addition & 1 deletion src/packages/empty/empty.taro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const Empty: FunctionComponent<

useEffect(() => {
setImgStyle(() => {
if (!imageSize) {
if (typeof imageSize !== 'number' && typeof imageSize !== 'string') {
return {}
}
if (typeof imageSize === 'number') {
Expand Down
2 changes: 1 addition & 1 deletion src/packages/empty/empty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const Empty: FunctionComponent<

useEffect(() => {
setImgStyle(() => {
if (!imageSize) {
if (typeof imageSize !== 'number' && typeof imageSize !== 'string') {
return {}
}
if (typeof imageSize === 'number') {
Expand Down
71 changes: 71 additions & 0 deletions src/packages/form/__tests__/form.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react'
import { useEffect } from 'react'
import { fireEvent, render, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom'
import { Button, Radio, Space } from '@nutui/nutui-react'
import Form, { FormInstance } from '@/packages/form'
import Input from '@/packages/input'

Expand Down Expand Up @@ -312,3 +313,73 @@ test('no-style and render function', async () => {
expect(relatedInput).toBeTruthy()
})
})

test('useWatch', async () => {
const Demo = () => {
const [form] = Form.useForm()
const account = Form.useWatch('account', form)
const loginMethod = Form.useWatch('loginMethod', form)
return (
<Form
form={form}
initialValues={{ loginMethod: 'mobile', account: '123' }}
footer={
<>
<div
style={{
width: '100%',
}}
>
<div
className="result"
style={{
fontSize: '12px',
textAlign: 'center',
marginBottom: '20px',
}}
>
你将使用 {loginMethod === 'mobile' ? '手机号' : '邮箱'}{' '}
{account} 登录
</div>
<Button block type="primary" size="large" nativeType="submit">
提交
</Button>
</div>
</>
}
>
<Form.Item name="loginMethod" label="登录方式">
<Radio.Group>
<Space>
<Radio value="mobile">手机号</Radio>
<Radio className="clickTest" value="email">
邮箱
</Radio>
</Space>
</Radio.Group>
</Form.Item>

<>
{loginMethod === 'mobile' && (
<Form.Item name="account" label="手机号">
<Input placeholder="请输入手机号" />
</Form.Item>
)}
{loginMethod === 'email' && (
<Form.Item name="account" label="邮箱">
<Input placeholder="请输入邮箱" />
</Form.Item>
)}
</>
</Form>
)
}

const { container } = render(<Demo />)
const clickTest = container.querySelector('.clickTest')
if (clickTest) {
fireEvent.click(clickTest)
const result = container.querySelector('.result')
expect(result).toHaveTextContent('你将使用 邮箱 123 登录')
}
})
263 changes: 263 additions & 0 deletions src/packages/form/__tests__/merge.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import { merge, clone, recursive, isPlainObject } from '@/utils/merge'

describe('merge', () => {
it('merges two objects', () => {
expect(merge({ a: 1 }, { b: 2 })).toStrictEqual({ a: 1, b: 2 })
})

it('merges nested levels', () => {
expect(merge({ a: 1 }, { b: { c: { d: 2 } } })).toStrictEqual({
a: 1,
b: { c: { d: 2 } },
})
})
it('clones the target', () => {
let input = {
a: 1,
b: {
c: {
d: 2,
e: ['x', 'y', { z: { w: ['k'] } }],
},
},
f: null,
g: undefined,
h: true,
}

const original = {
a: 1,
b: {
c: {
d: 2,
e: ['x', 'y', { z: { w: ['k'] } }],
},
},
f: null,
g: undefined,
h: true,
}

let output = merge(true, input)

input.b.c.d++
;(input.b.c.e[2] as any).z.w = null
;(input as any).h = null

expect(output).toStrictEqual(original)

input = original

output = merge(true, input, { a: 2 })

expect(output.a).toBe(2)
expect(input.a).toBe(1)
})

it('ignores the sources', () => {
const values = createNonPlainObjects()
const $merge = vi.fn().mockImplementation(merge)

for (const value of values) expect($merge(value)).toStrictEqual({})

expect(values.length).toBeGreaterThan(0)
expect($merge).toBeCalledTimes(values.length)
expect(
merge(...values, [0, 1, 2], ...values, { a: 1 }, ...values, {
b: 2,
})
).toStrictEqual({ a: 1, b: 2 })
})

it('does not merge non plain objects', () => {
const values = createNonPlainObjects()
expect(values.length).toBeGreaterThan(0)
const input: any = {}

for (const [index, value] of Object.entries(values)) {
input[`value${index}`] = value
}

const output = merge({}, input)

for (const [index] of Object.entries(values)) {
const key = `value${index}`
const inputValue = input[key]
const outputValue = output[key]

// eslint-disable-next-line no-restricted-globals
if (typeof outputValue === 'number' && isNaN(outputValue)) {
// eslint-disable-next-line no-restricted-globals
expect(isNaN(inputValue), key).toBeTruthy()
} else {
expect(inputValue === outputValue, key).toBeTruthy()
}
}
})

it('is safe', () => {
expect(
merge({}, JSON.parse('{"__proto__": {"evil": true}}'))
).toStrictEqual({})
expect(({} as any).evil).toBeUndefined()
})
})

describe('clone', () => {
it('clones the input', () => {
const object1 = { a: 1, b: { c: 2 } }
const object2 = clone(object1)

expect(object1).toStrictEqual(object2)
expect(object1 === object2).toBeFalsy()
expect(object1.b === object2.b).toBeFalsy()
})

it('clones each item of the array', () => {
const object1 = [{ a: 1, b: { c: 2 } }]
const object2 = clone(object1)

expect(object1).toStrictEqual(object2)
expect(object1 === object2).toBeFalsy()
expect(object1[0] === object2[0]).toBeFalsy()
expect(object1[0].b === object2[0].b).toBeFalsy()
})

it('returns the same input', () => {
const values = createNonPlainObjects()
const $clone = vi.fn().mockImplementation(clone)
for (const value of values) {
const cloned = $clone(value)
// eslint-disable-next-line no-restricted-globals
if (typeof cloned === 'number' && isNaN(cloned)) {
// eslint-disable-next-line no-restricted-globals
expect(isNaN(value)).toBeTruthy()
} else if (Array.isArray(cloned)) {
expect(Array.isArray(value)).toBeTruthy()
} else {
expect(cloned === value).toBeTruthy()
}
}
expect(values.length).toBeGreaterThan(0)
expect($clone).toBeCalledTimes(values.length)
})
})

describe('recursive', () => {
it('merges recursively', () => {
expect(recursive({ a: { b: 1 } }, { a: { c: 1 } })).toStrictEqual({
a: { b: 1, c: 1 },
})

expect(recursive({ a: { b: 1, c: 1 } }, { a: { b: 2 } })).toStrictEqual({
a: { b: 2, c: 1 },
})

expect(
recursive({ a: { b: [1, 2, 3], c: 1 } }, { a: { b: ['a'] } })
).toStrictEqual({ a: { b: ['a'], c: 1 } })

expect(
recursive({ a: { b: { b: 2 }, c: 1 } }, { a: { b: 2 } })
).toStrictEqual({
a: { b: 2, c: 1 },
})
})

it('clones recursively', () => {
const test1 = { a: { b: 1 } }

expect(recursive(true, test1, { a: { c: 1 } })).toStrictEqual({
a: { b: 1, c: 1 },
})

expect(test1).toStrictEqual({ a: { b: 1 } })

const test2 = { a: { b: 1, c: 1 } }

expect(recursive(true, test2, { a: { b: 2 } })).toStrictEqual({
a: { b: 2, c: 1 },
})

expect(test2).toStrictEqual({ a: { b: 1, c: 1 } })

const test3 = { a: { b: [1, 2, 3], c: 1 } }

expect(recursive(true, test3, { a: { b: ['a'] } })).toStrictEqual({
a: { b: ['a'], c: 1 },
})

expect(test3).toStrictEqual({ a: { b: [1, 2, 3], c: 1 } })

const test4 = { a: { b: { b: 2 }, c: 1 } }

expect(recursive(true, test4, { a: { b: 2 } })).toStrictEqual({
a: { b: 2, c: 1 },
})

expect(test4).toStrictEqual({ a: { b: { b: 2 }, c: 1 } })
})

it('does not merge non plain objects', () => {
const object = recursive({ map: { length: 1 } }, { map: new Map() })
expect(object.map).toBeInstanceOf(Map)
})

it('is safe', () => {
const payload = '{"__proto__": {"a": true}}'
expect(recursive({}, JSON.parse(payload))).toStrictEqual({})
expect(({} as any).a).toBeUndefined()
expect(recursive({ deep: {} }, JSON.parse(payload))).toStrictEqual({
deep: {},
})
expect(({} as any).b).toBeUndefined()
})
})

describe('isPlainObject', () => {
it('returns true', () => {
expect(isPlainObject({})).toBeTruthy()
expect(isPlainObject({ v: 1 })).toBeTruthy()
expect(isPlainObject(Object.create(null))).toBeTruthy()
expect(isPlainObject({})).toBeTruthy()
})
it('returns false', () => {
const values = createNonPlainObjects()
const $isPlainObject = vi.fn().mockImplementation(isPlainObject)
for (const value of values) expect($isPlainObject(value)).toBeFalsy()
expect(values.length).toBeGreaterThan(0)
expect($isPlainObject).toBeCalledTimes(values.length)
})
})

function createNonPlainObjects(): any[] {
class SubObject extends Object {}

return [
null,
undefined,
1,
'',
'str',
[],
[1],
() => {},
function () {},
true,
false,
NaN,
Infinity,
class {},
new (class {})(),
new Map(),
new Set(),
new Date(),
[],
new Date(),
/./,
/./,
SubObject,
new SubObject(),
Symbol(''),
]
}
5 changes: 5 additions & 0 deletions src/packages/form/demo.taro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Demo4 from './demos/taro/demo4'
import Demo5 from './demos/taro/demo5'
import Demo6 from './demos/taro/demo6'
import Demo7 from './demos/taro/demo7'
import Demo8 from './demos/taro/demo8'

const FormDemo = () => {
const [translated] = useTranslate({
Expand All @@ -20,6 +21,7 @@ const FormDemo = () => {
title4: 'Form.useForm 对表单数据域进行交互。',
title5: '表单类型',
validateTrigger: '校验触发时机',
useWatch: 'useWatch',
},
'en-US': {
basic: 'Basic Usage',
Expand All @@ -29,6 +31,7 @@ const FormDemo = () => {
title4: 'Interact with form data fields via Form.useForm',
title5: 'Form Type',
validateTrigger: 'Validate Trigger',
useWatch: 'useWatch',
},
})

Expand All @@ -50,6 +53,8 @@ const FormDemo = () => {
<Demo6 />
<h2>{translated.title5}</h2>
<Demo7 />
<h2>{translated.useWatch}</h2>
<Demo8 />
</div>
</>
)
Expand Down
Loading

0 comments on commit 37eecab

Please sign in to comment.