Skip to content
Closed
10 changes: 7 additions & 3 deletions scripts/harmony/update-taro-entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ const isShow = (item) => {

// 更新 app.config.ts 文件
const createConfig = async () => {
const configRef = []
let configRef = []

return new Promise((res, rej) => {
config.nav.map((item) => {
let co = {
root: item.enName,
pages: [],
}
if(paramG) {
if(paramG === item.enName){
if (paramG) {
if (paramG === item.enName) {
item.packages.map((it) => {
if (isShow(it)) {
co.pages.push(`pages/${it.name.toLowerCase()}/index`)
Expand All @@ -49,6 +49,10 @@ const createConfig = async () => {
co = { ...co, pages: co.pages.sort() }
configRef.push(co)
})

// 如果 pages 数据为空,则删除该项
configRef = configRef.filter((item) => item.pages.length !== 0)

res(configRef)
})
}
Expand Down
3 changes: 2 additions & 1 deletion src/packages/configprovider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -655,11 +655,12 @@ export type NutCSSVariables =
| 'nutuiSearchbarBackground'
| 'nutuiSearchbarColor'
| 'nutuiSearchbarGap'
| 'nutuiSearchbarInnerGap'
| 'nutuiSearchbarFontSize'
| 'nutuiSearchbarContentPadding'
| 'nutuiSearchbarContentBackground'
| 'nutuiSearchbarContentBorderRadius'
| 'nutuiSearchbarContentRoundBorderRadius'
| 'nutuiSearchbarIconSize'
| 'nutuiSearchbarInputHeight'
| 'nutuiSearchbarInputPadding'
| 'nutuiSearchbarInputTextColor'
Expand Down
95 changes: 70 additions & 25 deletions src/packages/searchbar/__tests__/searchbar.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,89 @@
import * as React from 'react'
import { fireEvent, render } from '@testing-library/react'
import { render, fireEvent, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom'
import { useState } from 'react'
import SearchBar from '@/packages/searchbar'

test('basic usage', () => {
const { container } = render(<SearchBar placeholder="请输入文字" />)
expect(container.querySelector('.nut-searchbar-input')).toHaveAttribute(
'placeholder',
'请输入文字'
test('should render with placeholder', () => {
const { getByPlaceholderText } = render(
<SearchBar placeholder="请输入文字" />
)
expect(getByPlaceholderText('请输入文字')).toBeInTheDocument()
})

test('should limit maxlength of input value when using maxlength prop', () => {
const { container } = render(<SearchBar shape="round" maxLength={5} />)
expect(container.querySelector('.nut-searchbar-input')).toHaveAttribute(
'maxlength',
'5'
)
expect(container.querySelector('.nut-searchbar-content')).toHaveClass(
'nut-searchbar-round'
)
const input = container.querySelector('.nut-searchbar-input')
expect(input).toHaveAttribute('maxlength', '5')
expect(input?.parentNode).toHaveClass('nut-searchbar-round')
})

test('Search box text settings', () => {
test('should display left and right text', () => {
const { container } = render(<SearchBar left="文本" right="确定" />)
expect(container.querySelector('.nut-searchbar-left')?.innerHTML).toBe('文本')
expect(container.querySelector('.nut-searchbar-right')?.innerHTML).toBe(
'确定'
)
})

test('Search clear & change', () => {
const change = vi.fn()
const { container } = render(
<SearchBar value="123" onChange={change} maxLength={10} />
)
const input = container.querySelector('.nut-searchbar-input')
expect(input?.getAttribute('value')).toBe('123')
const clear = container.querySelector('.nut-searchbar-clear')
fireEvent.click(clear as Element)
expect(change).toBeCalledWith('')
expect(input?.getAttribute('value')).toBe('')
test('should render with tags', () => {
const { container } = render(<SearchBar tag value="add,add3" />)
const dvalues = container.querySelectorAll('.nut-searchbar-value')
expect(dvalues.length).toBe(2)
})

test('should render right-in element', () => {
const { container, rerender } = render(<SearchBar rightIn="搜索" />)
const rightin = container.querySelectorAll('.nut-searchbar-rightin')
expect(rightin.length).toBe(1)
rerender(<SearchBar rightIn={<div className="test">搜索</div>} />)
const test = container.querySelectorAll('.test')
expect(test.length).toBe(1)
})

test('should handle all events correctly', async () => {
const handleChange = vi.fn()
const handleFocus = vi.fn()
const handleBlur = vi.fn()
const handleClick = vi.fn()
const handleClear = vi.fn()
const Demo = () => {
const [value, setValue] = useState('奶茶')
const onChange = (newValue: string) => {
setValue(newValue) // 更新状态
handleChange(newValue) // 调用传入的 onChange 处理函数
}
return (
<SearchBar
value={value}
autoFocus
onChange={onChange}
onFocus={handleFocus}
onBlur={handleBlur}
onInputClick={handleClick}
onClear={handleClear}
/>
)
}

const { container } = render(<Demo />)
const inputEl = container.querySelector('.nut-searchbar-input') as Element
expect(inputEl).toHaveValue('奶茶')
fireEvent.click(inputEl)
expect(handleClick).toHaveBeenCalledTimes(1)
fireEvent.change(inputEl, { target: { value: '冰激凌' } })

await waitFor(() => {
expect(handleFocus).toHaveBeenCalledTimes(1)
expect(handleChange).toHaveBeenCalledTimes(1)
expect(inputEl).toHaveValue('冰激凌')
fireEvent.blur(inputEl)
expect(handleBlur).toHaveBeenCalled()
})

const clear = container.querySelector('.nut-searchbar-clear') as Element
fireEvent.click(clear)
await waitFor(() => {
expect(inputEl).toHaveValue('')
})
})
Comment on lines +44 to 89

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

事件处理测试需要细化和完善

这个测试综合验证了多个事件,但有几点可以改进:

  1. 使用了autoFocus属性但没有断言它是否生效
  2. 在一个waitFor块中验证多个不同时间发生的事件,可能导致测试不稳定
  3. 缺少对PR主要功能「tag模式」的测试

建议添加以下测试:

+test('should render with defaultValue', () => {
+  const { container } = render(<SearchBar defaultValue="123" />)
+  const input = container.querySelector('.nut-searchbar-input') as HTMLInputElement
+  expect(input).toHaveValue('123')
+})

+test('should render tags when tag prop is true', () => {
+  const { container } = render(<SearchBar defaultValue="标签1,标签2" tag />)
+  const tags = container.querySelectorAll('.nut-searchbar-value')
+  expect(tags.length).toBe(2)
+  expect(tags[0].textContent).toContain('标签1')
+  expect(tags[1].textContent).toContain('标签2')
+})

+test('should trigger onItemClick when tag close icon is clicked', async () => {
+  const handleItemClick = vi.fn()
+  const { container } = render(
+    <SearchBar defaultValue="标签1,标签2" tag onItemClick={handleItemClick} />
+  )
+  const closeIcon = container.querySelector('.nut-searchbar-value-item-close') as Element
+  fireEvent.click(closeIcon)
+  
+  await waitFor(() => {
+    expect(handleItemClick).toHaveBeenCalledTimes(1)
+  })
+})

另外,建议将事件处理测试细分为多个独立的waitFor块:

 fireEvent.change(inputEl, { target: { value: '冰激凌' } })

 await waitFor(() => {
   expect(handleFocus).toHaveBeenCalledTimes(1)
   expect(handleChange).toHaveBeenCalledTimes(1)
   expect(inputEl).toHaveValue('冰激凌')
-  fireEvent.blur(inputEl)
-  expect(handleBlur).toHaveBeenCalled()
 })
+
+fireEvent.blur(inputEl)
+await waitFor(() => {
+  expect(handleBlur).toHaveBeenCalled()
+})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('should handle all events correctly', async () => {
const handleChange = vi.fn()
const handleFocus = vi.fn()
const handleBlur = vi.fn()
const handleClick = vi.fn()
const handleClear = vi.fn()
const Demo = () => {
const [value, setValue] = useState('奶茶')
const onChange = (newValue: string) => {
setValue(newValue) // 更新状态
handleChange(newValue) // 调用传入的 onChange 处理函数
}
return (
<SearchBar
value={value}
autoFocus
onChange={onChange}
onFocus={handleFocus}
onBlur={handleBlur}
onInputClick={handleClick}
onClear={handleClear}
/>
)
}
const { container } = render(<Demo />)
const inputEl = container.querySelector('.nut-searchbar-input') as Element
expect(inputEl).toHaveValue('奶茶')
fireEvent.click(inputEl)
expect(handleClick).toHaveBeenCalledTimes(1)
fireEvent.change(inputEl, { target: { value: '冰激凌' } })
await waitFor(() => {
expect(handleFocus).toHaveBeenCalledTimes(1)
expect(handleChange).toHaveBeenCalledTimes(1)
expect(inputEl).toHaveValue('冰激凌')
fireEvent.blur(inputEl)
expect(handleBlur).toHaveBeenCalled()
})
const clear = container.querySelector('.nut-searchbar-clear') as Element
fireEvent.click(clear)
await waitFor(() => {
expect(inputEl).toHaveValue('')
})
})
// ——— New tests to cover defaultValue and tag mode ———
test('should render with defaultValue', () => {
const { container } = render(<SearchBar defaultValue="123" />)
const input = container.querySelector('.nut-searchbar-input') as HTMLInputElement
expect(input).toHaveValue('123')
})
test('should render tags when tag prop is true', () => {
const { container } = render(<SearchBar defaultValue="标签1,标签2" tag />)
const tags = container.querySelectorAll('.nut-searchbar-value')
expect(tags.length).toBe(2)
expect(tags[0].textContent).toContain('标签1')
expect(tags[1].textContent).toContain('标签2')
})
test('should trigger onItemClick when tag close icon is clicked', async () => {
const handleItemClick = vi.fn()
const { container } = render(
<SearchBar defaultValue="标签1,标签2" tag onItemClick={handleItemClick} />
)
const closeIcon = container.querySelector(
'.nut-searchbar-value-item-close'
) as Element
fireEvent.click(closeIcon)
await waitFor(() => {
expect(handleItemClick).toHaveBeenCalledTimes(1)
})
})
// ——— Updated existing composite event test ———
test('should handle all events correctly', async () => {
const handleChange = vi.fn()
const handleFocus = vi.fn()
const handleBlur = vi.fn()
const handleClick = vi.fn()
const handleClear = vi.fn()
const Demo = () => {
const [value, setValue] = useState('奶茶')
const onChange = (newValue: string) => {
setValue(newValue) // 更新状态
handleChange(newValue) // 调用传入的 onChange
}
return (
<SearchBar
value={value}
autoFocus
onChange={onChange}
onFocus={handleFocus}
onBlur={handleBlur}
onInputClick={handleClick}
onClear={handleClear}
/>
)
}
const { container } = render(<Demo />)
const inputEl = container.querySelector(
'.nut-searchbar-input'
) as HTMLElement
expect(inputEl).toHaveValue('奶茶')
fireEvent.click(inputEl)
expect(handleClick).toHaveBeenCalledTimes(1)
fireEvent.change(inputEl, { target: { value: '冰激凌' } })
// 1. focus/change/value assertions
await waitFor(() => {
expect(handleFocus).toHaveBeenCalledTimes(1)
expect(handleChange).toHaveBeenCalledTimes(1)
expect(inputEl).toHaveValue('冰激凌')
})
// 2. blur assertion in its own waitFor
fireEvent.blur(inputEl)
await waitFor(() => {
expect(handleBlur).toHaveBeenCalled()
})
const clear = container.querySelector(
'.nut-searchbar-clear'
) as HTMLElement
fireEvent.click(clear)
await waitFor(() => {
expect(inputEl).toHaveValue('')
})
})

15 changes: 6 additions & 9 deletions src/packages/searchbar/demo.taro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ 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 Demo10 from './demos/taro/demo10'

const SearchBarDemo = () => {
const [translated] = useTranslate({
'zh-CN': {
title1: '基础用法',
title10: '默认值、受控',
title2: '搜索框形状及最大长度',
title3: '搜索框内外背景设置',
title4: '搜索框文本设置',
Expand All @@ -26,6 +28,7 @@ const SearchBarDemo = () => {
},
'zh-TW': {
title1: '基礎用法',
title10: '默認值、受控',
title2: '蒐索框形狀及最大長度',
title3: '蒐索框內外背景設定',
title4: '蒐索框文字設定',
Expand All @@ -35,22 +38,14 @@ const SearchBarDemo = () => {
},
'en-US': {
title1: 'Basic Usage',
title10: 'DefaultValue and controlled mode',
title2: 'Search Box Shape And Maximum Length',
title3: 'Background Settings Inside And Outside The Search Box',
title4: 'Search Box Text Settings',
title5: 'Custom Icon Settings',
title6: 'Data Change Monitoring',
title7: 'Custom Settings',
},
'id-ID': {
title1: 'penggunaan dasar',
title2: 'bentuk kotak pencarian dan panjang maksimum',
title3: 'pengaturan latar belakang di dalam dan diluar kotak pencarian',
title4: 'tetapan teks kotak pencarian',
title5: 'pengaturan ikon suai',
title6: 'Monitor perubahan data',
title7: 'pengaturan suai',
},
})

return (
Expand All @@ -59,6 +54,8 @@ const SearchBarDemo = () => {
<ScrollView className={`demo ${Taro.getEnv() === 'WEB' ? 'web' : ''}`}>
<View className="h2">{translated.title1}</View>
<Demo1 />
<View className="h2">{translated.title10}</View>
<Demo10 />
<View className="h2">{translated.title2}</View>
<Demo2 />
<View className="h2">{translated.title3}</View>
Expand Down
15 changes: 6 additions & 9 deletions src/packages/searchbar/demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import Demo4 from './demos/h5/demo4'
import Demo5 from './demos/h5/demo5'
import Demo6 from './demos/h5/demo6'
import Demo7 from './demos/h5/demo7'
import Demo10 from './demos/h5/demo10'

const SearchBarDemo = () => {
const [translated] = useTranslate({
'zh-CN': {
title1: '基础用法',
title10: '默认值、受控',
title2: '搜索框形状及最大长度',
title3: '搜索框内外背景设置',
title4: '搜索框文本设置',
Expand All @@ -22,6 +24,7 @@ const SearchBarDemo = () => {
},
'zh-TW': {
title1: '基礎用法',
title10: '默認值、受控',
title2: '蒐索框形狀及最大長度',
title3: '蒐索框內外背景設定',
title4: '蒐索框文字設定',
Expand All @@ -31,29 +34,23 @@ const SearchBarDemo = () => {
},
'en-US': {
title1: 'Basic Usage',
title10: 'DefaultValue and controlled mode',
title2: 'Search Box Shape And Maximum Length',
title3: 'Background Settings Inside And Outside The Search Box',
title4: 'Search Box Text Settings',
title5: 'Custom Icon Settings',
title6: 'Data Change Monitoring',
title7: 'Custom Settings',
},
'id-ID': {
title1: 'penggunaan dasar',
title2: 'bentuk kotak pencarian dan panjang maksimum',
title3: 'pengaturan latar belakang di dalam dan diluar kotak pencarian',
title4: 'tetapan teks kotak pencarian',
title5: 'pengaturan ikon suai',
title6: 'Monitor perubahan data',
title7: 'pengaturan suai',
},
})

return (
<>
<div className="demo">
<h2>{translated.title1}</h2>
<Demo1 />
<h2>{translated.title10}</h2>
<Demo10 />
<h2>{translated.title2}</h2>
<Demo2 />
<h2>{translated.title3}</h2>
Expand Down
16 changes: 14 additions & 2 deletions src/packages/searchbar/demos/h5/demo1.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import React from 'react'
import { SearchBar } from '../../searchbar'
import { Photograph, Scan } from '@nutui/icons-react'
import { SearchBar, Divider } from '@nutui/nutui-react'

const Demo1 = () => {
return (
<>
<SearchBar backable placeholder="上京东,购好物" />
<SearchBar placeholder="麻辣烫" rightIn="搜索" />
<SearchBar
leftIn={<Scan />}
placeholder="华为Mate 70"
rightIn={
<div style={{ display: 'flex', alignItems: 'center' }}>
<Photograph color="#888B94" onClick={() => console.log('拍照购')} />
<Divider direction="vertical" />
<span style={{ color: '#ff0f23' }}>搜索</span>
</div>
}
/>
</>
)
}
Expand Down
57 changes: 57 additions & 0 deletions src/packages/searchbar/demos/h5/demo10.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { useState } from 'react'
import { SearchBar, Button } from '@nutui/nutui-react'
import { Photograph, Category } from '@nutui/icons-react'

const Demo = () => {
const [value, setValue] = useState('醋溜土豆丝')
const [value1, setValue1] = useState('西红柿,铁皮')
return (
<>
<SearchBar
backable
leftIn={null}
value={value1}
tag
onItemClick={(val: string) => {
console.log('click', val)
const arr = value1.split(',')
const newArr = arr.filter((item: string) => item !== val)
const newVal = newArr.length > 1 ? newArr.join(',') : newArr.join('')
setValue1(newVal)
}}
onFocus={(val: string) => {
console.log('focus value', val)
setValue1(val.split(',').join(''))
}}
onChange={(val) => {
console.log('onChange', val)
setValue1(val)
}}
rightIn={
<div style={{ display: 'flex', alignItems: 'center' }}>
<Photograph color="#505259" />
</div>
}
right={<Category />}
/>
<SearchBar
backable
leftIn={null}
value={value}
onChange={(val) => {
setValue(val)
}}
autoFocus
rightIn={
<div style={{ display: 'flex', alignItems: 'center' }}>
<Photograph color="#505259" style={{ marginRight: '12px' }} />
<Button type="primary" size="mini">
搜索
</Button>
</div>
}
/>
</>
)
}
export default Demo
2 changes: 1 addition & 1 deletion src/packages/searchbar/demos/h5/demo2.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { SearchBar } from '../../searchbar'
import { SearchBar } from '@nutui/nutui-react'

const Demo2 = () => {
return (
Expand Down
6 changes: 2 additions & 4 deletions src/packages/searchbar/demos/h5/demo3.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import React from 'react'
import { SearchBar } from '../../searchbar'
import ConfigProvider from '../../../configprovider'
import Toast from '../../../toast'
import { SearchBar, ConfigProvider, Toast } from '@nutui/nutui-react'

const Demo3 = () => {
return (
<>
<ConfigProvider
theme={{
nutuiSearchbarBackground: 'var(--nutui-color-primary)',
nutuiSearchbarContentBackground: '#eee',
nutuiSearchbarContentBackground: '#fff',
nutuiSearchbarInputTextAlign: 'right',
}}
>
Expand Down
3 changes: 1 addition & 2 deletions src/packages/searchbar/demos/h5/demo4.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react'
import { SearchBar } from '../../searchbar'
import Toast from '../../../toast'
import { SearchBar, Toast } from '@nutui/nutui-react'

const Demo4 = () => {
return (
Expand Down
Loading