Browse Source

Merge branch 'next' of https://github.com/jdf2e/nutui into next

Drjnigfubo 4 years ago
parent
commit
f4acc3e947

+ 5 - 1
src/packages/__VUE/address/index.vue

@@ -9,7 +9,11 @@
     <view class="nut-address">
       <view class="nut-address__header">
         <view class="arrow-back" @click="switchModule">
-          <nut-icon :name="backBtnIcon" color="#cccccc" v-show="privateType == 'custom' && backBtnIcon"></nut-icon>
+          <nut-icon
+            :name="backBtnIcon"
+            color="#cccccc"
+            v-show="type == 'exist' && privateType == 'custom' && backBtnIcon"
+          ></nut-icon>
         </view>
 
         <view class="nut-address__header__title">

+ 1 - 1
src/packages/__VUE/barrage/doc.md

@@ -20,7 +20,7 @@ app.use(Barrage);
 
 ## 代码演示
 
-### 基础用法1
+### 基础用法
 
 `Icon` 的 `name` 属性支持传入图标名称或图片链接。
 :::demo

+ 12 - 0
src/packages/__VUE/button/doc.md

@@ -130,6 +130,16 @@ app.use(Button);
 
 :::
 
+#### 自定义图标
+
+参照 `Icon` 组件说明中的自定义图标,其中 `icon-font-class-name` 对应 Icon 组件的 `font-class-name`,`icon-class-prefix` 对应 Icon 组件的 `class-prefix`。
+
+```html
+<template>
+  <nut-button shape="square" plain type="primary" icon-font-class-name="iconfont" icon-class-prefix="icon" icon="close"></nut-button>
+</template>
+```
+
 ### 按钮尺寸
 
 支持 `large`、`normal`、`small`、`mini` 四种尺寸,默认为 `normal`。
@@ -190,6 +200,8 @@ app.use(Button);
 | disabled | 是否禁用按钮                                                 | Boolean | `false`   |
 | block    | 是否为块级元素                                               | Boolean | `false`   |
 | icon     | 按钮图标,同 Icon 组件 name 属性                             | String  | -         |
+| icon-font-class-name | 自定义 icon 字体基础类名                         | String | `nutui-iconfont` |
+| icon-class-prefix    | 自定义 icon 类名前缀,用于使用自定义图标           | String | `nut-icon`       |
 | loading  | 按钮 loading 状态                                            | Boolean | `false`   |
 
 ### Events

+ 14 - 1
src/packages/__VUE/button/index.taro.vue

@@ -2,7 +2,12 @@
   <button :class="classes" :style="getStyle" @click="handleClick">
     <view class="nut-button__warp">
       <nut-icon class="nut-icon-loading" v-if="loading"></nut-icon>
-      <nut-icon :class="icon" v-if="icon && !loading" :name="icon"></nut-icon>
+      <nut-icon
+        v-if="icon && !loading"
+        :name="icon"
+        :class-prefix="iconClassPrefix"
+        :font-class-name="iconFontClassName"
+      ></nut-icon>
       <view :class="{ text: icon || loading }" v-if="$slots.default">
         <slot></slot>
       </view>
@@ -55,6 +60,14 @@ export default create({
     icon: {
       type: String,
       default: ''
+    },
+    iconClassPrefix: {
+      type: String,
+      default: 'nut-icon'
+    },
+    iconFontClassName: {
+      type: String,
+      default: 'nutui-iconfont'
     }
   },
   emits: ['click'],

+ 14 - 1
src/packages/__VUE/button/index.vue

@@ -2,7 +2,12 @@
   <view :class="classes" :style="getStyle" @click="handleClick">
     <view class="nut-button__warp">
       <nut-icon class="nut-icon-loading" v-if="loading"></nut-icon>
-      <nut-icon :class="icon" v-if="icon && !loading" :name="icon"></nut-icon>
+      <nut-icon
+        v-if="icon && !loading"
+        :name="icon"
+        :class-prefix="iconClassPrefix"
+        :font-class-name="iconFontClassName"
+      ></nut-icon>
       <view :class="{ text: icon || loading }" v-if="$slots.default">
         <slot></slot>
       </view>
@@ -56,6 +61,14 @@ export default create({
     icon: {
       type: String,
       default: ''
+    },
+    iconClassPrefix: {
+      type: String,
+      default: 'nut-icon'
+    },
+    iconFontClassName: {
+      type: String,
+      default: 'nutui-iconfont'
     }
   },
   emits: ['click'],

+ 4 - 1
src/packages/__VUE/datepicker/doc.md

@@ -8,7 +8,10 @@
     
 ```javascript
 import { createApp } from 'vue';
-import { DatePicker } from '@nutui/nutui';
+// vue
+import { DatePicker, Picker } from '@nutui/nutui';
+// taro
+import { DatePicker, Picker } from '@nutui/nutui-taro';
 
 const app = createApp();
 app.use(DatePicker);

+ 102 - 0
src/packages/__VUE/elevator/__tests__/index.spec.ts

@@ -0,0 +1,102 @@
+import { mount, VueWrapper, DOMWrapper } from '@vue/test-utils';
+import Elevator from '../index.vue';
+import { nextTick, ComponentPublicInstance } from 'vue';
+
+const indexList = [
+  {
+    title: 'A',
+    list: [
+      {
+        name: '安徽',
+        id: 1
+      }
+    ]
+  },
+  {
+    title: 'B',
+    list: [
+      {
+        name: '北京',
+        id: 2
+      }
+    ]
+  },
+  {
+    title: 'G',
+    list: [
+      {
+        name: '广西',
+        id: 3
+      },
+      {
+        name: '广东',
+        id: 4
+      }
+    ]
+  },
+  {
+    title: 'H',
+    list: [
+      {
+        name: '湖南',
+        id: 5
+      },
+      {
+        name: '湖北',
+        id: 6
+      }
+    ]
+  }
+];
+
+test('should render elevator list height after height props to be 200', () => {
+  const wrapper = mount(Elevator, {
+    props: {
+      height: 200
+    }
+  });
+
+  const elevatorList = wrapper.find('.nut-elevator__list').element as HTMLElement;
+
+  expect(elevatorList.style.height).toBe('200px');
+});
+
+test('should render list data when indexList props not empty', () => {
+  const wrapper = mount(Elevator, {
+    props: {
+      indexList
+    }
+  });
+
+  expect(wrapper.findAll('.nut-elevator__list__item').length).toBe(indexList.length);
+});
+
+test('should list item highlight when clickItem trigger click', async () => {
+  const wrapper = mount(Elevator, {
+    props: {
+      indexList
+    }
+  });
+
+  const listItem = wrapper.findAll('.nut-elevator__list__item')[1];
+  await listItem.find('.nut-elevator__list__item__name').trigger('click');
+
+  expect(listItem.find('.nut-elevator__list__item__name').classes()).toContain(
+    'nut-elevator__list__item__name--highcolor'
+  );
+
+  expect((wrapper.emitted('click-item') as any)[0][0]).toBe('B');
+});
+
+test('clickIndex trigger click', async () => {
+  const wrapper = mount(Elevator, {
+    props: {
+      indexList
+    }
+  });
+
+  const listItem = wrapper.findAll('.nut-elevator__bars__inner__item')[2];
+  await listItem.trigger('click');
+
+  expect((wrapper.emitted('click-index') as any)[0][0]).toBe('G');
+});

+ 4 - 0
src/packages/__VUE/icon/doc.md

@@ -71,6 +71,10 @@ app.use(Icon);
   <nut-icon name="dou-arrow-up" class="nut-icon-am-jump nut-icon-am-infinite"></nut-icon>
   <nut-icon name="star-fill-n" class="nut-icon-am-blink nut-icon-am-infinite"></nut-icon>
   <nut-icon name="refresh2" class="nut-icon-am-rotate nut-icon-am-infinite"></nut-icon>
+  <nut-icon name="heart-fill" class="nut-icon-am-breathe nut-icon-am-infinite"></nut-icon>
+  <nut-icon name="microphone" class="nut-icon-am-flash nut-icon-am-infinite"></nut-icon>
+  <nut-icon name="download" class="nut-icon-am-bounce nut-icon-am-infinite"></nut-icon>
+  <nut-icon name="message" class="nut-icon-am-shake nut-icon-am-infinite"></nut-icon>
 </template>
 
 <style>

+ 165 - 0
src/packages/__VUE/inputnumber/__tests__/index.spec.ts

@@ -0,0 +1,165 @@
+import { mount, config } from '@vue/test-utils';
+import InputNumber from '../index.vue';
+import { nextTick } from 'vue';
+import NutIcon from '../../icon/index.vue';
+
+beforeAll(() => {
+  config.global.components = {
+    NutIcon
+  };
+});
+
+afterAll(() => {
+  config.global.components = {};
+});
+
+test('should render modelValue', () => {
+  const wrapper = mount(InputNumber, {
+    props: {
+      modelValue: 12
+    }
+  });
+
+  const input = wrapper.find('input').element as HTMLInputElement;
+  expect(input.value).toBe('12');
+});
+
+test('should add step 2 when trigger click plus button', async () => {
+  const wrapper = mount(InputNumber, {
+    props: {
+      modelValue: 1,
+      step: '2'
+    }
+  });
+
+  const iconPlus = wrapper.find('.nut-icon-plus');
+  await iconPlus.trigger('click');
+
+  expect(wrapper.emitted('overlimit')).toBeFalsy();
+  expect(wrapper.emitted('add')).toBeTruthy();
+  expect((wrapper.emitted('change')![0] as any[])[0]).toEqual('3');
+  expect((wrapper.emitted('update:modelValue')![0] as any[])[0]).toEqual('3');
+});
+
+test('should minis step 2 when trigger click minis button', async () => {
+  const wrapper = mount(InputNumber, {
+    props: {
+      modelValue: 3,
+      step: '2'
+    }
+  });
+
+  const iconMinus = wrapper.find('.nut-icon-minus');
+  await iconMinus.trigger('click');
+
+  expect(wrapper.emitted('overlimit')).toBeFalsy();
+  expect(wrapper.emitted('reduce')).toBeTruthy();
+  expect((wrapper.emitted('change')![0] as any[])[0]).toEqual('1');
+  expect((wrapper.emitted('update:modelValue')![0] as any[])[0]).toEqual('1');
+});
+
+test('should render max and min props', async () => {
+  const wrapper = mount(InputNumber, {
+    props: {
+      modelValue: 100,
+      min: '2',
+      max: 100
+    }
+  });
+
+  const iconPlus = wrapper.find('.nut-icon-plus');
+  await iconPlus.trigger('click');
+
+  expect(wrapper.emitted('overlimit')).toBeTruthy();
+  expect(wrapper.emitted('add')).toBeTruthy();
+  expect(wrapper.emitted('change')).toBeFalsy();
+
+  await wrapper.setProps({
+    modelValue: 2
+  });
+
+  const iconMinus = wrapper.find('.nut-icon-minus');
+  await iconMinus.trigger('click');
+  expect(wrapper.emitted('overlimit')).toBeTruthy();
+  expect(wrapper.emitted('reduce')).toBeTruthy();
+  expect(wrapper.emitted('change')).toBeFalsy();
+});
+
+test('should not trigger click when disabled props to be true', async () => {
+  const wrapper = mount(InputNumber, {
+    disabled: true,
+    modelValue: 1
+  });
+
+  const iconPlus = wrapper.find('.nut-icon-plus');
+  await iconPlus.trigger('click');
+  expect((wrapper.emitted('update:modelValue')![0] as any[])[0]).toEqual('1');
+
+  const iconMinus = wrapper.find('.nut-icon-minus');
+  await iconMinus.trigger('click');
+  expect((wrapper.emitted('update:modelValue')![0] as any[])[0]).toEqual('1');
+});
+
+test('should not focus input when readonly props to be true', async () => {
+  const wrapper = mount(InputNumber, {
+    props: {
+      readonly: true,
+      modelValue: 2
+    }
+  });
+
+  const iconMinus = wrapper.find('.nut-icon-minus');
+  await iconMinus.trigger('click');
+  await nextTick();
+
+  expect((wrapper.emitted('update:modelValue')![0] as any[])[0]).toEqual('1');
+
+  expect(wrapper.emitted('focus')).toBeFalsy();
+});
+
+test('should render decimal when step props to be 0.2', async () => {
+  const wrapper = mount(InputNumber, {
+    props: {
+      step: 0.2,
+      decimalPlaces: 1,
+      modelValue: 2
+    }
+  });
+
+  const iconPlus = wrapper.find('.nut-icon-plus');
+  await iconPlus.trigger('click');
+
+  expect((wrapper.emitted('change')![0] as any[])[0]).toEqual('2.2');
+});
+
+test('should render size when buttonSize and inputWidth props setted', async () => {
+  const wrapper = mount(InputNumber, {
+    props: {
+      buttonSize: '30px',
+      inputWidth: '120px',
+      modelValue: 2
+    }
+  });
+
+  const iconPlus = wrapper.find('.nut-icon-plus');
+  const input = wrapper.find('input').element as HTMLInputElement;
+
+  expect((iconPlus.element as HTMLElement).style.fontSize).toEqual('30px');
+  expect(input.style.width).toEqual('120px');
+});
+
+test('should update input value when inputValue overlimit', async () => {
+  const wrapper = mount(InputNumber, {
+    props: {
+      modelValue: 2,
+      max: 100
+    }
+  });
+
+  const input = wrapper.find('input');
+  input.element.value = '200';
+  await input.trigger('change');
+  await input.trigger('blur');
+
+  expect((wrapper.emitted('update:modelValue')![0] as any[])[0]).toEqual('100');
+});

+ 11 - 0
src/packages/__VUE/layout/demo.vue

@@ -112,6 +112,17 @@
           <div class="flex-content">span:6</div>
         </nut-col>
       </nut-row>
+      <nut-row type="flex" justify="space-evenly">
+        <nut-col :span="6">
+          <div class="flex-content">span:6</div>
+        </nut-col>
+        <nut-col :span="6">
+          <div class="flex-content flex-content-light">span:6</div>
+        </nut-col>
+        <nut-col :span="6">
+          <div class="flex-content">span:6</div>
+        </nut-col>
+      </nut-row>
     </div>
   </div>
 </template>

+ 12 - 1
src/packages/__VUE/layout/doc.md

@@ -187,6 +187,17 @@ app.use(Col);
       <div class="flex-content">span:6</div>
     </nut-col>
   </nut-row>
+  <nut-row type="flex" justify="space-evenly">
+    <nut-col :span="6">
+      <div class="flex-content">span:6</div>
+    </nut-col>
+    <nut-col :span="6">
+      <div class="flex-content flex-content-light">span:6</div>
+    </nut-col>
+    <nut-col :span="6">
+      <div class="flex-content">span:6</div>
+    </nut-col>
+  </nut-row>
 </template>
 <style lang="scss" scoped>
 .nut-row {
@@ -220,7 +231,7 @@ app.use(Col);
 |----- | ----- | ----- | ----- 
 | type | 布局方式,可选值为flex | String | -
 | gutter | 列元素之间的间距(单位为px) | String、Number | -
-| justify | Flex 主轴对齐方式,可选值为 start end center space-around space-between | String | start
+| justify | Flex 主轴对齐方式,可选值为 start end center space-around space-between space-evenly | String | start
 | align | Flex 交叉轴对齐方式,可选值为 flex-start center flex-end | String | flex-start
 | flex-wrap | Flex是否换行,可选值为 nowrap wrap reverse | String | nowrap
 

+ 6 - 6
src/packages/__VUE/numberkeyboard/doc.md

@@ -238,13 +238,13 @@ export default{
 </script>
 ```
 :::
-```
 
-## Prop
 
-| 字段 | 说明 | 类型 | 默认值
-|----- | ----- | ----- | ----- 
-| v-model:visible | 是否显示键盘 | Boolean | false
+### Prop
+
+| 字段 | 说明 | 类型 | 默认值 |
+|----- | ----- | ----- | ----- |
+| v-model:visible | 是否显示键盘 | Boolean | false | 
 | title | 键盘标题 | String | - |
 | type | 键盘模式  | String | `default`:默认样式<br>`rightColumn`:带右侧栏 |
 | random-keys | 随机数  | Boolean | false |
@@ -254,7 +254,7 @@ export default{
 | maxlength  | 输入值最大长度,结合 v-model 使用 | number <br> String| 6 |
 
 
-## Event
+### Event
 
 | 字段 | 说明 | 回调参数
 |----- | ----- | -----

+ 3 - 0
src/packages/__VUE/row/index.scss

@@ -30,6 +30,9 @@
   &-justify-space-around {
     justify-content: space-around;
   }
+  &-justify-space-evenly {
+    justify-content: space-evenly;
+  }
   &-align-flex-start {
     align-items: flex-start;
   }

+ 97 - 0
src/packages/__VUE/sidenavbar/__tests__/index.spec.ts

@@ -0,0 +1,97 @@
+import { mount, config } from '@vue/test-utils';
+import SideNavBar from '../index.vue';
+import SideNavBarItem from '../../sidenavbaritem/index.vue';
+import SubSideNavBar from '../../subsidenavbar/index.vue';
+import { nextTick } from 'vue';
+import NutIcon from '../../icon/index.vue';
+
+function sleep(delay = 0): Promise<void> {
+  return new Promise((resolve) => {
+    setTimeout(resolve, delay);
+  });
+}
+
+beforeAll(() => {
+  config.global.components = {
+    NutIcon
+  };
+});
+
+afterAll(() => {
+  config.global.components = {};
+});
+
+const template = `
+<nut-sidenavbar offset="30">
+  <nut-subsidenavbar title="智能城市AI" ikey="6">
+    <nut-subsidenavbar title="人体识别1" ikey="9" :open="false">
+      <nut-sidenavbaritem ikey="10" title="人体检测1"></nut-sidenavbaritem>
+      <nut-sidenavbaritem ikey="11" title="细粒度人像分割1"></nut-sidenavbaritem>
+    </nut-subsidenavbar>
+    <nut-subsidenavbar title="人体识别2" ikey="12">
+      <nut-sidenavbaritem ikey="13" title="人体检测2"></nut-sidenavbaritem>
+      <nut-sidenavbaritem ikey="14" title="细粒度人像分割2"></nut-sidenavbaritem>
+    </nut-subsidenavbar>
+  </nut-subsidenavbar>
+</nut-sidenavbar>
+`;
+
+test('render offset props', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-sidenavbar': SideNavBar,
+      'nut-sidenavbaritem': SideNavBarItem,
+      'nut-subsidenavbar': SubSideNavBar
+    },
+    template,
+    setup: () => {}
+  });
+
+  await nextTick();
+
+  const subSideNavBar = wrapper.findAll('.nut-subsidenavbar__title');
+
+  expect((subSideNavBar[0].element as HTMLElement).style.paddingLeft).toEqual('30px');
+  expect((subSideNavBar[1].element as HTMLElement).style.paddingLeft).toEqual('60px');
+  expect((subSideNavBar[2].element as HTMLElement).style.paddingLeft).toEqual('60px');
+});
+
+test('render subsidenavbar open props', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-sidenavbar': SideNavBar,
+      'nut-sidenavbaritem': SideNavBarItem,
+      'nut-subsidenavbar': SubSideNavBar
+    },
+    template,
+    setup: () => {}
+  });
+
+  await nextTick();
+
+  const subSideNavBar = wrapper.findAll('.nut-subsidenavbar__list');
+
+  expect((subSideNavBar[1].element as HTMLElement).style.height).toEqual('0px');
+});
+
+test('subsidenavbar trigger click', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-sidenavbar': SideNavBar,
+      'nut-sidenavbaritem': SideNavBarItem,
+      'nut-subsidenavbar': SubSideNavBar
+    },
+    template,
+    setup: () => {}
+  });
+
+  await nextTick();
+
+  const subSideNavBarTitle = wrapper.findAll('.nut-subsidenavbar__title');
+  const subSideNavBarList = wrapper.findAll('.nut-subsidenavbar__list');
+
+  await subSideNavBarTitle[0].trigger('click');
+  await sleep(100);
+
+  expect((subSideNavBarList[0].element as HTMLElement).style.height).toEqual('0px');
+});

+ 134 - 0
src/packages/__VUE/steps/__tests__/index.spec.ts

@@ -0,0 +1,134 @@
+import { config, DOMWrapper, mount } from '@vue/test-utils';
+import Steps from '../index.vue';
+import Step from './../../step/index.vue';
+import Button from './../../button/index.vue';
+import NutIcon from '../../icon/index.vue';
+import { nextTick, toRefs, reactive } from 'vue';
+
+beforeAll(() => {
+  config.global.components = {
+    NutIcon
+  };
+});
+
+afterAll(() => {
+  config.global.components = {};
+});
+
+test('should render horizontal class when props direction is to be horizontal', () => {
+  const wrapper = mount(Steps, {
+    props: {
+      direction: 'horizontal'
+    }
+  });
+
+  expect(wrapper.classes()).toContain('nut-steps-horizontal');
+});
+
+test('should first step checked when props current is to be 1', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-steps': Steps,
+      'nut-step': Step
+    },
+    template: `
+      <nut-steps current="1">
+        <nut-step title="步骤一">1</nut-step>
+        <nut-step title="步骤二">2</nut-step>
+        <nut-step title="步骤三">3</nut-step>
+      </nut-steps>
+    `,
+    setup() {}
+  });
+
+  await nextTick();
+
+  const stepItem = wrapper.findAll('.nut-step')[0];
+  expect(stepItem.classes()).toContain('nut-step-process');
+});
+
+test('should render dot class when props progressDot is to be true', async () => {
+  const wrapper = mount(Steps, {
+    props: {
+      progressDot: true
+    }
+  });
+
+  expect(wrapper.classes()).toContain('nut-steps-dot');
+});
+
+test('step props', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-steps': Steps,
+      'nut-step': Step
+    },
+    template: `
+      <nut-steps>
+        <nut-step title="已完成" content="您的订单已经打包完成,商品已发出" icon="service" iconColor="blue" size="14px">1</nut-step>
+        <nut-step title="进行中" content="您的订单正在配送途中" icon="people" iconColor="blue" size="14px">2</nut-step>
+        <nut-step title="未开始" content="收货地址为:北京市经济技术开发区科创十一街18号院京东大厦" icon="location2" iconColor="blue" size="14px">3</nut-step>
+      </nut-steps>
+    `
+  });
+
+  await nextTick();
+
+  const stepItemTitle = wrapper.findAll('.nut-step-title')[0];
+
+  expect(stepItemTitle.element.innerHTML).toEqual('已完成');
+
+  const stepItemContent = wrapper.findAll('.nut-step-content')[1];
+
+  expect(stepItemContent.element.innerHTML).toEqual('您的订单正在配送途中');
+
+  const stepItemIcon = wrapper.findAll('.nutui-iconfont')[2];
+  expect(stepItemIcon.classes()).toContain('nut-icon-location2');
+  expect((stepItemIcon.element as HTMLElement).style.color).toEqual('blue');
+  expect((stepItemIcon.element as HTMLElement).style.width).toEqual('14px');
+});
+
+test('should props current changes when trigger click', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-steps': Steps,
+      'nut-step': Step,
+      'nut-button': Button
+    },
+    template: `
+      <nut-steps :current="current">
+        <nut-step title="已完成" content="您的订单已经打包完成,商品已发出">1</nut-step>
+        <nut-step title="进行中" content="您的订单正在配送途中">2</nut-step>
+        <nut-step title="未开始" content="收货地址为:北京市经济技术开发区科创十一街18号院京东大厦">3</nut-step>
+      </nut-steps>
+      <nut-button @click="handleClick">下一步</nut-button>
+    `,
+    setup() {
+      const state = reactive({
+        current: 1
+      });
+
+      const handleClick = () => {
+        if (state.current >= 3) {
+          state.current = 1;
+        } else {
+          state.current += 1;
+        }
+      };
+
+      return { ...toRefs(state), handleClick };
+    }
+  });
+
+  const button = wrapper.find('.nut-button');
+  await button.trigger('click');
+  await nextTick();
+  expect(wrapper.vm.current).toBe(2);
+
+  const stepItem = wrapper.findAll('.nut-step')[1];
+  expect(stepItem.classes()).toContain('nut-step-process');
+
+  await button.trigger('click');
+  await nextTick();
+  expect(wrapper.vm.current).toBe(3);
+});

+ 0 - 1
src/packages/__VUE/sticky/__tests__/sticky.spec.ts

@@ -1 +0,0 @@
-import { mount } from '@vue/test-utils';

+ 350 - 0
src/packages/__VUE/swiper/__tests__/index.spec.ts

@@ -0,0 +1,350 @@
+import { mount, shallowMount } from '@vue/test-utils';
+import Swiper from '../index.vue';
+import SwiperItem from './../../swiperitem/index.vue';
+import { nextTick, toRefs, reactive } from 'vue';
+
+function sleep(delay = 0): Promise<void> {
+  return new Promise((resolve) => {
+    setTimeout(resolve, delay);
+  });
+}
+
+test('should render width and height', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-swiper': Swiper,
+      'nut-swiper-item': SwiperItem
+    },
+    template: `
+        <nut-swiper :init-page="page" :pagination-visible="true" pagination-color="#426543" auto-play="2000" :height="height">
+          <nut-swiper-item v-for="item in list" :key="item">
+            <img :src="item" alt="" />
+          </nut-swiper-item>
+        </nut-swiper>
+    `,
+    setup() {
+      const state = reactive({
+        page: 1,
+        height: '500',
+        list: [
+          'https://storage.360buyimg.com/jdc-article/NutUItaro34.jpg',
+          'https://storage.360buyimg.com/jdc-article/NutUItaro2.jpg',
+          'https://storage.360buyimg.com/jdc-article/welcomenutui.jpg',
+          'https://storage.360buyimg.com/jdc-article/fristfabu.jpg'
+        ]
+      });
+
+      return { ...toRefs(state) };
+    }
+  });
+
+  await nextTick();
+
+  const swiperItem = wrapper.findAll('.nut-swiper-inner')[0].element as HTMLElement;
+
+  expect(swiperItem.style.height).toEqual(`${wrapper.vm.height}px`);
+  expect(swiperItem.style.width).toEqual(`${window.innerWidth * wrapper.vm.list.length}px`);
+});
+
+test('should render initpage', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-swiper': Swiper,
+      'nut-swiper-item': SwiperItem
+    },
+    template: `
+        <nut-swiper :init-page="page" :width="width">
+          <nut-swiper-item v-for="item in list" :key="item">
+            <img :src="item" alt="" />
+          </nut-swiper-item>
+        </nut-swiper>
+    `,
+    setup() {
+      const state = reactive({
+        page: 2,
+        width: 375,
+        list: [
+          'https://storage.360buyimg.com/jdc-article/NutUItaro34.jpg',
+          'https://storage.360buyimg.com/jdc-article/NutUItaro2.jpg',
+          'https://storage.360buyimg.com/jdc-article/welcomenutui.jpg',
+          'https://storage.360buyimg.com/jdc-article/fristfabu.jpg'
+        ]
+      });
+
+      return { ...toRefs(state) };
+    }
+  });
+
+  await nextTick();
+  await nextTick();
+
+  const swiperItem = wrapper.findAll('.nut-swiper-inner')[0].element as HTMLElement;
+
+  expect(swiperItem.style.transform).toEqual(`translateX(-${wrapper.vm.width * wrapper.vm.page}px)`);
+});
+
+test('should render direction', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-swiper': Swiper,
+      'nut-swiper-item': SwiperItem
+    },
+    template: `
+        <nut-swiper :init-page="page" :height="height" :direction="direction">
+          <nut-swiper-item v-for="item in list" :key="item">
+            <img :src="item" alt="" />
+          </nut-swiper-item>
+        </nut-swiper>
+    `,
+    setup() {
+      const state = reactive({
+        page: 1,
+        height: 300,
+        direction: 'vertical',
+        list: [
+          'https://storage.360buyimg.com/jdc-article/NutUItaro34.jpg',
+          'https://storage.360buyimg.com/jdc-article/NutUItaro2.jpg',
+          'https://storage.360buyimg.com/jdc-article/welcomenutui.jpg',
+          'https://storage.360buyimg.com/jdc-article/fristfabu.jpg'
+        ]
+      });
+
+      return { ...toRefs(state) };
+    }
+  });
+
+  await nextTick();
+  await nextTick();
+
+  const swiperItem = wrapper.findAll('.nut-swiper-inner')[0].element as HTMLElement;
+
+  expect(swiperItem.style.transform).toEqual(`translateY(-${wrapper.vm.height * wrapper.vm.page}px)`);
+});
+
+test('should render pagination', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-swiper': Swiper,
+      'nut-swiper-item': SwiperItem
+    },
+    template: `
+        <nut-swiper :init-page="page" :pagination-visible="paginationVisible" :pagination-color="paginationColor">
+          <nut-swiper-item v-for="item in list" :key="item">
+            <img :src="item" alt="" />
+          </nut-swiper-item>
+        </nut-swiper>
+    `,
+    setup() {
+      const state = reactive({
+        page: 0,
+        paginationVisible: true,
+        paginationColor: 'red',
+        list: [
+          'https://storage.360buyimg.com/jdc-article/NutUItaro34.jpg',
+          'https://storage.360buyimg.com/jdc-article/NutUItaro2.jpg',
+          'https://storage.360buyimg.com/jdc-article/welcomenutui.jpg',
+          'https://storage.360buyimg.com/jdc-article/fristfabu.jpg'
+        ]
+      });
+
+      return { ...toRefs(state) };
+    }
+  });
+
+  await nextTick();
+  await nextTick();
+
+  const swiperPagination = wrapper.find('.nut-swiper-pagination');
+  expect(swiperPagination.exists()).toBe(true);
+  expect(swiperPagination.findAll('i')[0].element.style.backgroundColor).toEqual('red');
+});
+
+test('should render loop and auto-play', async () => {
+  const onChange = jest.fn();
+  const wrapper = mount({
+    props: {
+      onChange
+    },
+    emits: ['change'],
+    components: {
+      'nut-swiper': Swiper,
+      'nut-swiper-item': SwiperItem
+    },
+    template: `
+        <nut-swiper :init-page="page" :auto-play="autoPlay" :width="width" @change="change">
+          <nut-swiper-item v-for="item in list" :key="item">
+            <img :src="item" alt="" />
+          </nut-swiper-item>
+        </nut-swiper>
+    `,
+    setup() {
+      const state = reactive({
+        page: 0,
+        autoPlay: 100,
+        width: 375,
+        list: [
+          'https://storage.360buyimg.com/jdc-article/NutUItaro34.jpg',
+          'https://storage.360buyimg.com/jdc-article/NutUItaro2.jpg',
+          'https://storage.360buyimg.com/jdc-article/welcomenutui.jpg',
+          'https://storage.360buyimg.com/jdc-article/fristfabu.jpg'
+        ]
+      });
+
+      const change = (index: number) => {
+        state.page = index;
+      };
+
+      return { ...toRefs(state), change };
+    }
+  });
+
+  await nextTick();
+  await nextTick();
+
+  await sleep(200);
+  const swiperItem = wrapper.findAll('.nut-swiper-inner')[0].element as HTMLElement;
+  expect(wrapper.vm.page).toBe(1);
+  expect(swiperItem.style.transform).toEqual(`translateX(-${wrapper.vm.width * wrapper.vm.page}px)`);
+  await sleep(200);
+  expect(wrapper.vm.page).toBe(2);
+});
+
+test('should not allow to drag when touchable is false', async () => {
+  const onChange = jest.fn();
+  const wrapper = mount(Swiper, {
+    props: {
+      onChange,
+      touchable: false
+    }
+  });
+
+  const track = wrapper.find('.nut-swiper-inner');
+
+  track.trigger('drag');
+  expect(onChange).toHaveBeenCalledTimes(0);
+});
+
+test('should not allow to drag when loop is false', async () => {
+  const wrapper = mount({
+    emits: ['change'],
+    components: {
+      'nut-swiper': Swiper,
+      'nut-swiper-item': SwiperItem
+    },
+    template: `
+        <nut-swiper :init-page="page" :auto-play="autoPlay" :width="width" :loop="loop" @change="change">
+          <nut-swiper-item v-for="item in list" :key="item">
+            <img :src="item" alt="" />
+          </nut-swiper-item>
+        </nut-swiper>
+    `,
+    setup() {
+      const state = reactive({
+        page: 3,
+        autoPlay: 100,
+        width: 375,
+        loop: false,
+        list: [
+          'https://storage.360buyimg.com/jdc-article/NutUItaro34.jpg',
+          'https://storage.360buyimg.com/jdc-article/NutUItaro2.jpg',
+          'https://storage.360buyimg.com/jdc-article/welcomenutui.jpg',
+          'https://storage.360buyimg.com/jdc-article/fristfabu.jpg'
+        ]
+      });
+
+      const change = (index: number) => {
+        state.page = index;
+      };
+
+      return { ...toRefs(state), change };
+    }
+  });
+
+  await nextTick();
+  await nextTick();
+
+  await sleep(2000);
+  expect(wrapper.vm.page).toBe(3);
+});
+
+test('should swiper to prev swiper after calling prev method', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-swiper': Swiper,
+      'nut-swiper-item': SwiperItem
+    },
+    template: `
+        <nut-swiper :init-page="page" :auto-play="autoPlay" @change="change" ref="swiper">
+          <nut-swiper-item v-for="item in list" :key="item">
+            <img :src="item" alt="" />
+          </nut-swiper-item>
+        </nut-swiper>
+    `,
+    setup() {
+      const state = reactive({
+        page: 3,
+        autoPlay: 0,
+        list: [
+          'https://storage.360buyimg.com/jdc-article/NutUItaro34.jpg',
+          'https://storage.360buyimg.com/jdc-article/NutUItaro2.jpg',
+          'https://storage.360buyimg.com/jdc-article/welcomenutui.jpg',
+          'https://storage.360buyimg.com/jdc-article/fristfabu.jpg'
+        ]
+      });
+
+      const change = (index: number) => {
+        state.page = index;
+      };
+
+      return { ...toRefs(state), change };
+    }
+  });
+
+  await nextTick();
+
+  const { swiper } = wrapper.vm.$refs as any;
+  swiper.prev();
+  await sleep(1000);
+  expect(wrapper.vm.page).toBe(2);
+});
+
+test('should swiper to swiper after calling to method', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-swiper': Swiper,
+      'nut-swiper-item': SwiperItem
+    },
+    template: `
+        <nut-swiper :init-page="page" :auto-play="autoPlay" @change="change" ref="swiper">
+          <nut-swiper-item v-for="item in list" :key="item">
+            <img :src="item" alt="" />
+          </nut-swiper-item>
+        </nut-swiper>
+    `,
+    setup() {
+      const state = reactive({
+        page: 3,
+        autoPlay: 0,
+        list: [
+          'https://storage.360buyimg.com/jdc-article/NutUItaro34.jpg',
+          'https://storage.360buyimg.com/jdc-article/NutUItaro2.jpg',
+          'https://storage.360buyimg.com/jdc-article/welcomenutui.jpg',
+          'https://storage.360buyimg.com/jdc-article/fristfabu.jpg'
+        ]
+      });
+
+      const change = (index: number) => {
+        state.page = index;
+      };
+
+      return { ...toRefs(state), change };
+    }
+  });
+
+  await nextTick();
+
+  const { swiper } = wrapper.vm.$refs as any;
+  swiper.to(1);
+  await sleep(1000);
+  expect(wrapper.vm.page).toBe(1);
+});