Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pnpm-lock.*.yaml
# local v4 upgrade workflow (personal, not for commit)
.cursor/skills/nutui-component-v4-upgrade/
.claude/commands/nutui-v4-upgrade.md
.claude/nutui-*.json

# Harmony CSS files
src/packages/**/*.harmony.css
2 changes: 1 addition & 1 deletion src/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,7 @@
"v15": 2,
"author": "vickyYe",
"dd": false,
"v16": false
"v16": true
},
{
"version": "3.0.0",
Expand Down
16 changes: 16 additions & 0 deletions src/packages/configprovider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,14 +594,30 @@ export type NutCSSVariables =
| 'nutuiNoticebarHeight'
| 'nutuiNoticebarBackground'
| 'nutuiNoticebarColor'
| 'nutuiNoticebarIconColor'
| 'nutuiNoticebarFontSize'
| 'nutuiNoticebarLineHeight'
| 'nutuiNoticebarBoxPadding'
| 'nutuiNoticebarBorderRadius'
| 'nutuiNoticebarWrapablePadding'
| 'nutuiNoticebarIconGap'
| 'nutuiNoticebarLeftIconWidth'
| 'nutuiNoticebarLeftIconWrapWidth'
| 'nutuiNoticebarRightIconWidth'
| 'nutuiNoticebarCloseSize'
| 'nutuiNoticebarTagSize'
| 'nutuiNoticebarTagGap'
| 'nutuiNoticebarActionMaxWidth'
| 'nutuiNoticebarActionGap'
| 'nutuiNoticebarActionFontSize'
| 'nutuiNoticebarDescriptionFontSize'
| 'nutuiNoticebarDescriptionColor'
| 'nutuiNoticebarLeftIconBorderRadius'
| 'nutuiNoticebarDescriptionLineHeight'
| 'nutuiNoticebarCloseColor'
| 'nutuiNoticebarCloseIconSize'
| 'nutuiNoticebarCloseRingColor'
| 'nutuiNoticebarCloseRingShadowColor'
| 'nutuiTimeselectDateWidth'
| 'nutuiTimeselectDateHeight'
| 'nutuiTimeselectDateActiveColor'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,21 @@ exports[`align center test 1`] = `
</svg>
</div>
<div
class="nut-noticebar-box-wrap"
class="nut-noticebar-box-content-wrapper"
>
<div
class="nut-noticebar-box-wrap-content nut-ellipsis"
style="animation-delay: 1s; animation-duration: 0s; transform: translateX(0);"
class="nut-noticebar-box-content-main"
>
NutUI 是京东风格的移动端组件库,使用 Vue 语言来编写可以在 H5,小程序平台上的应用,帮助研发人员提升开发效率,改善开发体验。
<div
class="nut-noticebar-box-wrap"
>
<div
class="nut-noticebar-box-wrap-content nut-ellipsis"
style="animation-delay: 1s; animation-duration: 0s; transform: translateX(0);"
>
NutUI 是京东风格的移动端组件库,使用 Vue 语言来编写可以在 H5,小程序平台上的应用,帮助研发人员提升开发效率,改善开发体验。
</div>
</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -60,13 +68,21 @@ exports[`noticebar base test 1`] = `
</svg>
</div>
<div
class="nut-noticebar-box-wrap"
class="nut-noticebar-box-content-wrapper"
>
<div
class="nut-noticebar-box-wrap-content "
style="animation-delay: 1s; animation-duration: 0s; transform: translateX(0);"
class="nut-noticebar-box-content-main"
>
NutUI 是京东风格的移动端组件库,使用 Vue 语言来编写可以在 H5,小程序平台上的应用,帮助研发人员提升开发效率,改善开发体验。
<div
class="nut-noticebar-box-wrap"
>
<div
class="nut-noticebar-box-wrap-content "
style="animation-delay: 1s; animation-duration: 0s; transform: translateX(0);"
>
NutUI 是京东风格的移动端组件库,使用 Vue 语言来编写可以在 H5,小程序平台上的应用,帮助研发人员提升开发效率,改善开发体验。
</div>
</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -97,13 +113,21 @@ exports[`scrollable test 1`] = `
</svg>
</div>
<div
class="nut-noticebar-box-wrap"
class="nut-noticebar-box-content-wrapper"
>
<div
class="nut-noticebar-box-wrap-content nut-ellipsis"
style="animation-delay: 1s; animation-duration: 0s; transform: translateX(0);"
class="nut-noticebar-box-content-main"
>
NutUI 是京东风格的移动端组件库,使用 Vue 语言来编写可以在 H5,小程序平台上的应用,帮助研发人员提升开发效率,改善开发体验。
<div
class="nut-noticebar-box-wrap"
>
<div
class="nut-noticebar-box-wrap-content nut-ellipsis"
style="animation-delay: 1s; animation-duration: 0s; transform: translateX(0);"
>
NutUI 是京东风格的移动端组件库,使用 Vue 语言来编写可以在 H5,小程序平台上的应用,帮助研发人员提升开发效率,改善开发体验。
</div>
</div>
</div>
</div>
</div>
Expand Down
78 changes: 77 additions & 1 deletion src/packages/noticebar/__test__/noticebar.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react'
import { useState } from 'react'
import { render, fireEvent, waitFor, act } from '@testing-library/react'
import '@testing-library/jest-dom'
import { Fabulous } from '@nutui/icons-react'
import { Fabulous, Notice } from '@nutui/icons-react'
import NoticeBar from '@/packages/noticebar'
import Image from '@/packages/image'

Expand Down Expand Up @@ -352,3 +352,79 @@ test('dynamic children update test', async () => {
})
})
})

test('wrap mode applies wrapable class with left-icon', () => {
const { container } = render(
<NoticeBar content="双行文本内容" wrap leftIcon={<Notice />} />
)
expect(container.querySelector('.nut-noticebar-box')).toHaveClass(
'nut-noticebar-box-wrapable'
)
expect(container.querySelector('.nut-noticebar-box-left-icon')).toBeTruthy()
})

test('description prop renders sub text', () => {
const { container } = render(
<NoticeBar content="主文本内容" description="副文本内容" />
)
const desc = container.querySelector('.nut-noticebar-box-description')
expect(desc).toBeTruthy()
expect(desc?.innerHTML).toBe('副文本内容')
})

test('tag prop renders info tag', () => {
const { container } = render(
<NoticeBar content="提示文案" tag={<Notice width={12} height={12} />} />
)
const tagEl = container.querySelector('.nut-noticebar-box-tag')
expect(tagEl).toBeTruthy()
expect(tagEl?.querySelector('.nut-icon')).toBeTruthy()
})

test('action prop renders action button', () => {
const { container } = render(
<NoticeBar content="提示文案" action={<span>强行动点</span>} />
)
const actionEl = container.querySelector('.nut-noticebar-box-action')
expect(actionEl).toBeTruthy()
expect(actionEl?.innerHTML).toContain('强行动点')
})

test('content-wrapper is rendered in horizontal mode', () => {
const { container } = render(
<NoticeBar content="测试内容" description="副文本" />
)
expect(
container.querySelector('.nut-noticebar-box-content-wrapper')
).toBeTruthy()
})

test('closeable renders Close icon by default', () => {
const { container } = render(<NoticeBar content="测试内容" closeable />)
const closeIcon = container.querySelector('.nut-noticebar-box-right-icon')
expect(closeIcon).toBeTruthy()
expect(closeIcon?.querySelector('.nut-icon-Close')).toBeTruthy()
})

test('autoClose renders countdown ring and closes after delay', async () => {
vi.useFakeTimers()
const handleClose = vi.fn()
const { container } = render(
<NoticeBar content="自动关闭" autoClose={3000} onClose={handleClose} />
)

expect(container.querySelector('.nut-noticebar-box')).toBeTruthy()
expect(
container.querySelector('.nut-noticebar-box-close-countdown')
).toBeTruthy()
expect(container.querySelector('.nut-noticebar-box-close-icon')).toBeTruthy()

act(() => {
vi.advanceTimersByTime(3000)
})

expect(container.querySelector('.nut-noticebar-box')).toBeFalsy()
expect(handleClose).toHaveBeenCalled()

vi.useRealTimers()
})
Comment on lines +409 to +430

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Fake timer 恢复应放入 finally,避免测试污染。

Line 409-430 里若任一断言抛错,vi.useRealTimers() 不会执行,后续用例会被 fake timer 污染。建议用 try/finally 包裹。

🔧 建议修复
 test('autoClose renders countdown ring and closes after delay', async () => {
-  vi.useFakeTimers()
-  const handleClose = vi.fn()
-  const { container } = render(
-    <NoticeBar content="自动关闭" autoClose={3000} onClose={handleClose} />
-  )
-
-  expect(container.querySelector('.nut-noticebar-box')).toBeTruthy()
-  expect(
-    container.querySelector('.nut-noticebar-box-close-countdown')
-  ).toBeTruthy()
-  expect(container.querySelector('.nut-noticebar-box-close-icon')).toBeTruthy()
-
-  act(() => {
-    vi.advanceTimersByTime(3000)
-  })
-
-  expect(container.querySelector('.nut-noticebar-box')).toBeFalsy()
-  expect(handleClose).toHaveBeenCalled()
-
-  vi.useRealTimers()
+  vi.useFakeTimers()
+  try {
+    const handleClose = vi.fn()
+    const { container } = render(
+      <NoticeBar content="自动关闭" autoClose={3000} onClose={handleClose} />
+    )
+
+    expect(container.querySelector('.nut-noticebar-box')).toBeTruthy()
+    expect(
+      container.querySelector('.nut-noticebar-box-close-countdown')
+    ).toBeTruthy()
+    expect(container.querySelector('.nut-noticebar-box-close-icon')).toBeTruthy()
+
+    act(() => {
+      vi.advanceTimersByTime(3000)
+    })
+
+    expect(container.querySelector('.nut-noticebar-box')).toBeFalsy()
+    expect(handleClose).toHaveBeenCalled()
+  } finally {
+    vi.useRealTimers()
+  }
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/packages/noticebar/__test__/noticebar.spec.tsx` around lines 409 - 430,
The autoClose test function uses vi.useFakeTimers() at the start and
vi.useRealTimers() at the end, but if any assertion fails, vi.useRealTimers()
will not execute, causing fake timers to persist and pollute subsequent tests.
Wrap the test logic between vi.useFakeTimers() and vi.useRealTimers() in a
try/finally block, placing vi.useRealTimers() in the finally clause to ensure it
always executes regardless of whether assertions pass or fail.

15 changes: 15 additions & 0 deletions src/packages/noticebar/demo.taro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import Demo8 from './demos/taro/demo8'
import Demo9 from './demos/taro/demo9'
import Demo10 from './demos/taro/demo10'
import Demo11 from './demos/taro/demo11'
import Demo12 from './demos/taro/demo12'
import Demo13 from './demos/taro/demo13'
import Demo14 from './demos/taro/demo14'

const NoticeBarDemo = () => {
const [translated] = useTranslate({
Expand All @@ -31,6 +34,9 @@ const NoticeBarDemo = () => {
complexAm: '纵向模式:自定义左侧图标',
customAm: '纵向模式:自定义滚动内容,动态变更滚动内容',
customRightIcon: '纵向模式:自定义右侧图标',
tagAndAction: '信息标与操作按钮',
autoClose: '自动关闭',
imageIcon: '自定义配图',
},
'en-US': {
basic: 'Basic Usage',
Expand All @@ -44,6 +50,9 @@ const NoticeBarDemo = () => {
complexAm: 'Vertical Scroll Complex Animation',
customAm: 'Vertical Scroll Custom Style,Dynamic Change Scroll Content',
customRightIcon: 'Vertical Scroll Custom Right Icon',
tagAndAction: 'Tag & Action Button',
autoClose: 'Auto Close',
imageIcon: 'Custom Image',
},
})

Expand Down Expand Up @@ -82,6 +91,12 @@ const NoticeBarDemo = () => {
<View className="interstroll-list">
<Demo11 />
</View>
<View className="h2">{translated.tagAndAction}</View>
<Demo12 />
<View className="h2">{translated.imageIcon}</View>
<Demo14 />
<View className="h2">{translated.autoClose}</View>
<Demo13 />
</ScrollView>
</>
)
Expand Down
15 changes: 15 additions & 0 deletions src/packages/noticebar/demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import Demo8 from './demos/h5/demo8'
import Demo9 from './demos/h5/demo9'
import Demo10 from './demos/h5/demo10'
import Demo11 from './demos/h5/demo11'
import Demo12 from './demos/h5/demo12'
import Demo13 from './demos/h5/demo13'
import Demo14 from './demos/h5/demo14'

const NoticeBarDemo = () => {
const [translated] = useTranslate({
Expand All @@ -27,6 +30,9 @@ const NoticeBarDemo = () => {
complexAm: '纵向模式:自定义左侧图标',
customAm: '纵向模式:自定义滚动内容,动态变更滚动内容',
customRightIcon: '纵向模式:自定义右侧图标',
tagAndAction: '信息标与操作按钮',
autoClose: '自动关闭',
imageIcon: '自定义配图',
},
'en-US': {
basic: 'Basic Usage',
Expand All @@ -40,6 +46,9 @@ const NoticeBarDemo = () => {
complexAm: 'Vertical Scroll Complex Animation',
customAm: 'Vertical Scroll Custom Style,Dynamic Change Scroll Content',
customRightIcon: 'Vertical Scroll Custom Right Icon',
tagAndAction: 'Tag & Action Button',
autoClose: 'Auto Close',
imageIcon: 'Custom Image',
},
})

Expand Down Expand Up @@ -72,6 +81,12 @@ const NoticeBarDemo = () => {
<div className="interstroll-list">
<Demo11 />
</div>
<h2>{translated.tagAndAction}</h2>
<Demo12 />
<h2>{translated.imageIcon}</h2>
<Demo14 />
<h2>{translated.autoClose}</h2>
<Demo13 />
</div>
</>
)
Expand Down
43 changes: 43 additions & 0 deletions src/packages/noticebar/demos/h5/demo12.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'
import { NoticeBar, Button } from '@nutui/nutui-react'
import { Notice } from '@nutui/icons-react'

const Demo12 = () => {
return (
<>
<NoticeBar
content="提示文案描述文案描文案"
tag={<Notice width={12} height={12} />}
action={
<Button size="small" type="primary">
强行动点
</Button>
}
wrap
closeable
/>
<br />
<NoticeBar
content="提示文案描述文案描文案"
description="文案提示描述描述描述"
tag={<Notice width={12} height={12} />}
action={
<Button size="small" type="primary">
强行动点
</Button>
}
wrap
closeable
/>
<br />
<NoticeBar
wrap
content="文案展示字"
description="最大支持十五个汉字汉字汉字最大支持十五个汉字汉字汉字最大支持十五个汉字汉字汉字"
tag={<Notice width={12} height={12} />}
action={<span>弱行动点 &gt;</span>}
/>
</>
)
}
export default Demo12
35 changes: 35 additions & 0 deletions src/packages/noticebar/demos/h5/demo13.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { useState } from 'react'
import { NoticeBar, Button } from '@nutui/nutui-react'

const Demo13 = () => {
const [visible, setVisible] = useState(true)

const reset = () => {
setVisible(false)
setTimeout(() => setVisible(true), 100)
}

return (
<>
{visible && (
<NoticeBar
content="提示文案描述文案描文案"
description="文案提示描述描述描述"
action={
<Button size="small" type="primary">
强行动点
</Button>
}
autoClose={5000}
onClose={() => {}}
wrap
/>
)}
<br />
<Button size="small" onClick={reset}>
重新展示
</Button>
</>
)
}
export default Demo13
Loading
Loading