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; }; /** * 左边图标