diff --git a/src/notice-bar/README.md b/src/notice-bar/README.md
index 3365b7c0c..4b528761a 100644
--- a/src/notice-bar/README.md
+++ b/src/notice-bar/README.md
@@ -5,6 +5,7 @@ spline: message
isComponent: true
---
+
## 引入
全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。
@@ -51,6 +52,7 @@ isComponent: true
| 名称 | 类型 | 默认值 | 说明 | 必传 |
| ---- | ---- | ------ | ---- | -- |
+external-classes | Array | - | 组件类名,分别用于设置 组件外层元素、文本内容、前缀图标、右侧额外信息、后缀图标 等元素类名。`['t-class', 't-class-content', 't-class-prefix-icon', 't-class-extra', 't-class-suffix-icon']` | N
| content | String / Slot | - | 文本内容 | N |
| extra | String / Slot | - | 右侧额外信息| N |
| marquee | Boolean / Object | false | 跑马灯效果。speed 指速度控制;loop 指循环播放次数,值为 -1 表示循环播放,值为 0 表示不循环播放;delay 表示延迟多久开始播放。TS 类型:`boolean | DrawMarquee` `interface DrawMarquee { speed?: number; loop?: number; delay?: number }`。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/notice-bar/type.ts) | N |
diff --git a/src/notice-bar/__test__/__snapshots__/index.test.js.snap b/src/notice-bar/__test__/__snapshots__/index.test.js.snap
new file mode 100644
index 000000000..e4bb016e6
--- /dev/null
+++ b/src/notice-bar/__test__/__snapshots__/index.test.js.snap
@@ -0,0 +1,47 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`notice-bar props : marquee 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ 提示文字描述提示文字描述提示文字描述提示文字描述文
+
+
+
+
+
+
+
+
+`;
diff --git a/src/notice-bar/__test__/index.test.js b/src/notice-bar/__test__/index.test.js
new file mode 100644
index 000000000..d27ca7619
--- /dev/null
+++ b/src/notice-bar/__test__/index.test.js
@@ -0,0 +1,297 @@
+import simulate from 'miniprogram-simulate';
+import path from 'path';
+import similateApi from 'miniprogram-simulate/src/api';
+import * as Util from '../../common/utils';
+
+// simulate提供一些基本的api,如getSystemInfoSync, createAnimation, 如果涉及到复杂的链式调用需要自行 mock
+global.wx = {
+ ...similateApi,
+};
+
+const mockGetAnimationFrame = jest.spyOn(Util, 'getAnimationFrame');
+const mockGetRect = jest.spyOn(Util, 'getRect');
+
+// 设置每次调用函数的值
+mockGetAnimationFrame.mockImplementation((cb) => {
+ return cb();
+});
+
+// 调用函数第1次的返回值 nodeRect
+mockGetRect.mockImplementationOnce(() => {
+ return {
+ width: 350,
+ };
+});
+// 调用函数第2次的返回值 warpID
+mockGetRect.mockImplementationOnce(() => {
+ return {
+ width: 313,
+ };
+});
+
+describe('notice-bar', () => {
+ const noticeBar = simulate.load(path.resolve(__dirname, `../notice-bar`), 't-notice-bar', {
+ less: true,
+ rootPath: path.resolve(__dirname, '../..'),
+ });
+ jest.resetModules();
+ const icon = simulate.load(path.resolve(__dirname, `../../icon/icon`), 't-icon', {
+ less: true,
+ rootPath: path.resolve(__dirname, '../..'),
+ });
+
+ describe('props', () => {
+ it(': visible', () => {
+ const id = simulate.load({
+ template: ``,
+ data: {
+ visible: true,
+ },
+ usingComponents: {
+ 't-notice-bar': noticeBar,
+ },
+ });
+ const comp = simulate.render(id);
+ comp.attach(document.createElement('parent-wrapper'));
+ expect(comp.querySelector('.base >>> .t-notice-bar')).toBeDefined();
+
+ comp.setData({
+ visible: false,
+ });
+ expect(comp.querySelector('.base >>> .t-notice-bar')).not.toBeDefined();
+
+ comp.detach();
+ });
+
+ it(': theme', () => {
+ const id = simulate.load({
+ template: ``,
+ data: {
+ visible: true,
+ theme: 'info',
+ },
+ usingComponents: {
+ 't-notice-bar': noticeBar,
+ },
+ });
+ const comp = simulate.render(id);
+ comp.attach(document.createElement('parent-wrapper'));
+ const $component = comp.querySelector('.base >>> .t-notice-bar');
+ expect($component.dom.getAttribute('class').includes('t-notice-bar--info')).toBeTruthy();
+
+ comp.setData({
+ theme: 'success',
+ });
+ expect($component.dom.getAttribute('class').includes('t-notice-bar--success')).toBeTruthy();
+ });
+
+ it(': content', () => {
+ const id = simulate.load({
+ template: ``,
+ data: {
+ visible: true,
+ content: 'notice-bar content',
+ },
+ usingComponents: {
+ 't-notice-bar': noticeBar,
+ },
+ });
+ const comp = simulate.render(id);
+ comp.attach(document.createElement('parent-wrapper'));
+ const $noticeBar = comp.querySelector('.base');
+ const $content = comp.querySelector('.base >>> .t-notice-bar__content');
+
+ expect($content.dom.textContent.trim()).toBe($noticeBar.instance.data.content);
+ });
+
+ it(': extra', () => {
+ const id = simulate.load({
+ template: ``,
+ data: {
+ visible: true,
+ extra: 'notice-bar extra',
+ },
+ usingComponents: {
+ 't-notice-bar': noticeBar,
+ },
+ });
+ const comp = simulate.render(id);
+ comp.attach(document.createElement('parent-wrapper'));
+ const $noticeBar = comp.querySelector('.base');
+ const $extra = comp.querySelector('.base >>> .t-notice-bar__extra');
+
+ expect($extra.dom.textContent.trim()).toBe($noticeBar.instance.data.extra);
+ });
+
+ it(': prefix-icon', () => {
+ const id = simulate.load({
+ template: ``,
+ data: {
+ visible: true,
+ prefixIcon: 'null',
+ },
+ usingComponents: {
+ 't-notice-bar': noticeBar,
+ },
+ });
+
+ const comp = simulate.render(id);
+ comp.attach(document.createElement('parent-wrapper'));
+ const $prefixIcon = comp.querySelector('.base >>> .t-notice-bar__prefix-icon');
+ expect($prefixIcon.dom.innerHTML).toBe('');
+
+ comp.setData({
+ prefixIcon: 'add',
+ });
+
+ const iconId = simulate.load({
+ template: ``,
+ data: {
+ name: 'add',
+ },
+ usingComponents: {
+ 't-icon': icon,
+ },
+ });
+ const iconComp = simulate.render(iconId);
+ expect($prefixIcon.dom.innerHTML).toContain(iconComp.dom.innerHTML);
+ });
+
+ const delay = 7100;
+ it(
+ ': marquee',
+ async () => {
+ const id = simulate.load({
+ template: ``,
+ data: {
+ visible: true,
+ content: '提示文字描述提示文字描述提示文字描述提示文字描述文',
+ // marquee: true,
+ marquee: {
+ speed: 80,
+ loop: -1,
+ delay: 0,
+ },
+ },
+ usingComponents: {
+ 't-notice-bar': noticeBar,
+ },
+ });
+
+ const comp = simulate.render(id);
+ comp.attach(document.createElement('parent-wrapper'));
+ await simulate.sleep(delay); // 等待 delay 后再继续后续代码的执行
+
+ expect(comp.toJSON()).toMatchSnapshot();
+ const $instance = comp.querySelector('.base');
+ const $content = comp.querySelector('.base >>> .t-notice-bar__content');
+ expect($content.dom.textContent.trim()).toBe($instance.data.content);
+
+ // TODO: marquee 需要支持 Object && Boolean, 单测环境中,marquee值会固定为默认值 false
+ // marquee = false, content内容开启超出换行
+ expect($content.dom.getAttribute('class')).not.toContain('t-notice-bar__content-wrapable');
+ // const componentInstance = simulate.render(noticeBar).instance;
+ // expect(componentInstance.data.marquee).toEqual(comp.instance.data.marquee);
+ },
+ delay,
+ );
+ });
+
+ describe('slots', () => {
+ it(': content', () => {
+ const text = 'content 内容';
+ const id = simulate.load({
+ template: `
+ {{text}}
+ `,
+ data: {
+ visible: true,
+ text,
+ },
+ usingComponents: {
+ 't-notice-bar': noticeBar,
+ },
+ });
+ const comp = simulate.render(id);
+ comp.attach(document.createElement('parent-wrapper'));
+ const $content = comp.querySelector('.base >>> .t-notice-bar__content');
+ expect($content.dom.textContent).toBe(text);
+ });
+ });
+
+ describe('event', () => {
+ let triggerName = null;
+ const click = jest.fn((e) => {
+ const { trigger } = e.detail;
+ triggerName = trigger;
+ });
+
+ it(': click', async () => {
+ const id = simulate.load({
+ template: ``,
+ data: {
+ visible: true,
+ },
+ methods: {
+ click,
+ },
+ usingComponents: {
+ 't-notice-bar': noticeBar,
+ },
+ });
+ const comp = simulate.render(id);
+ comp.attach(document.createElement('parent-wrapper'));
+ const $prefixIcon = comp.querySelector('.base >>> .t-notice-bar__prefix-icon');
+ const $content = comp.querySelector('.base >>> .t-notice-bar__content');
+ const $extra = comp.querySelector('.base >>> .t-notice-bar__extra');
+ const $suffixIcon = comp.querySelector('.base >>> .t-notice-bar__suffix-icon');
+ $prefixIcon.dispatchEvent('tap');
+ await simulate.sleep(0);
+ expect(triggerName).toBe('prefix-icon');
+ expect(click).toHaveBeenCalledTimes(1);
+ $content.dispatchEvent('tap');
+ await simulate.sleep(0);
+ expect(triggerName).toBe('content');
+ expect(click).toHaveBeenCalledTimes(2);
+ $extra.dispatchEvent('tap');
+ await simulate.sleep(0);
+ expect(triggerName).toBe('extra');
+ expect(click).toHaveBeenCalledTimes(3);
+
+ $suffixIcon.dispatchEvent('tap');
+ await simulate.sleep(0);
+ expect(triggerName).toBe('suffix-icon');
+ expect(click).toHaveBeenCalledTimes(4);
+ });
+ });
+});
diff --git a/src/notice-bar/notice-bar.ts b/src/notice-bar/notice-bar.ts
index f0c967dcc..e93afa8e8 100644
--- a/src/notice-bar/notice-bar.ts
+++ b/src/notice-bar/notice-bar.ts
@@ -12,7 +12,7 @@ export default class NoticeBar extends SuperComponent {
`${prefix}-class`,
`${prefix}-class-content`,
`${prefix}-class-prefix-icon`,
- `${prefix}-class-extre`,
+ `${prefix}-class-extra`,
`${prefix}-class-suffix-icon`,
];
@@ -76,17 +76,17 @@ export default class NoticeBar extends SuperComponent {
getAnimationFrame(() => {
Promise.all([getRect(this, nodeID), getRect(this, warpID)]).then(([nodeRect, wrapRect]) => {
const { marquee } = this.properties;
- const speeding = marquee.speed;
- const delaying = marquee.delay ? marquee.delay : 0;
- const loops = marquee.loop - 1;
if (nodeRect == null || wrapRect == null || !nodeRect.width || !wrapRect.width) {
return;
}
if (marquee || wrapRect.width < nodeRect.width) {
+ const speeding = marquee.speed || 50;
+ const delaying = marquee.delay || 0;
+ const loops = marquee.loop - 1 || -1;
const animationDuration = ((wrapRect.width + nodeRect.width) / speeding) * 1000;
- const firstanimationDuration = (nodeRect.width / speeding) * 1000;
+ const firstAnimationDuration = (nodeRect.width / speeding) * 1000;
this.setData({
wrapWidth: Number(wrapRect.width),
@@ -94,7 +94,7 @@ export default class NoticeBar extends SuperComponent {
animationDuration: animationDuration,
delay: delaying,
loop: loops,
- firstanimationDuration: firstanimationDuration,
+ firstAnimationDuration: firstAnimationDuration,
});
this.startScrollAnimation(true);
@@ -106,9 +106,9 @@ export default class NoticeBar extends SuperComponent {
startScrollAnimation(isFirstScroll = false) {
this.clearNoticeBarAnimation();
- const { wrapWidth, nodeWidth, firstanimationDuration, animationDuration, delay } = this.data;
+ const { wrapWidth, nodeWidth, firstAnimationDuration, animationDuration, delay } = this.data;
const delayTime = isFirstScroll ? delay : 0;
- const durationTime = isFirstScroll ? firstanimationDuration : animationDuration;
+ const durationTime = isFirstScroll ? firstAnimationDuration : animationDuration;
// 滚动内容: 初始位置
this.setData({
diff --git a/src/notice-bar/props.ts b/src/notice-bar/props.ts
index 903b5eea2..2a8485bb1 100644
--- a/src/notice-bar/props.ts
+++ b/src/notice-bar/props.ts
@@ -16,8 +16,8 @@ const props: TdNoticeBarProps = {
},
/** 跑马灯效果。speed 指速度控制;loop 指循环播放次数,值为 -1 表示循环播放,值为 0 表示不循环播放;delay 表示延迟多久开始播放 */
marquee: {
- type: Boolean,
- optionalTypes: [Object],
+ type: Object,
+ optionalTypes: [Boolean],
value: false,
},
/** 左边图标 */
diff --git a/src/notice-bar/type.ts b/src/notice-bar/type.ts
index 4b16b4e56..770b2fb9e 100644
--- a/src/notice-bar/type.ts
+++ b/src/notice-bar/type.ts
@@ -24,9 +24,9 @@ export interface TdNoticeBarProps {
* @default false
*/
marquee?: {
- type: BooleanConstructor;
- optionalTypes: Array;
- value?: boolean | DrawMarquee;
+ type: ObjectConstructor;
+ optionalTypes: Array;
+ value?: DrawMarquee | boolean;
};
/**
* 左边图标