ソースを参照

test(picker,address,audio...): add test (#1083)

yangxiaolu1993 3 年 前
コミット
6986f36933

+ 123 - 0
src/packages/__VUE/address/__tests__/address.spec.ts

@@ -0,0 +1,123 @@
+import { config, mount } from '@vue/test-utils';
+import { nextTick, toRefs, reactive } from 'vue';
+import NutIcon from '../../icon/index.vue';
+import NutPopup from '../../popup/index.vue';
+import NutElevator from '../../elevator/index.vue';
+import Address from '../index.vue';
+import { addressListData, addressExistData } from '../address-list';
+
+beforeAll(() => {
+  config.global.components = {
+    NutIcon,
+    NutPopup,
+    NutElevator
+  };
+});
+
+afterAll(() => {
+  config.global.components = {};
+});
+
+test('address render', async () => {
+  const wrapper = mount(Address, {
+    props: {
+      visible: true,
+      province: addressListData.province,
+      city: addressListData.city,
+      country: addressListData.country,
+      town: addressListData.town,
+      isWrapTeleport: false
+    }
+  });
+  await nextTick();
+  expect(wrapper.find('.arrow-close').exists()).toBeTruthy();
+  expect(wrapper.findAll('.region-item').length).toBe(5);
+});
+
+test('choose address item', async () => {
+  const wrapper = mount(Address, {
+    props: {
+      visible: true,
+      province: addressListData.province,
+      city: addressListData.city,
+      country: addressListData.country,
+      town: addressListData.town,
+      isWrapTeleport: false
+    }
+  });
+  wrapper.find('.region-item').trigger('click');
+  await nextTick();
+  expect(wrapper.find('.tab-item').text()).toEqual('北京');
+});
+
+test('default choose address', async () => {
+  const wrapper = mount(Address, {
+    props: {
+      modelValue: [1, 7, 3],
+      visible: true,
+      province: addressListData.province,
+      city: addressListData.city,
+      country: addressListData.country,
+      town: addressListData.town,
+      isWrapTeleport: false
+    }
+  });
+  wrapper.vm.initCustomSelected();
+  await nextTick();
+  expect(wrapper.findAll('.tab-item')[0].text()).toEqual('北京');
+  expect(wrapper.findAll('.tab-item')[1].text()).toEqual('朝阳区');
+  expect(wrapper.findAll('.tab-item')[2].text()).toEqual('八里庄街道');
+});
+
+test('Exist address', async () => {
+  const wrapper = mount(Address, {
+    props: {
+      type: 'exist',
+      visible: true,
+      existAddress: addressExistData,
+      isShowCustomAddress: false,
+      isWrapTeleport: false
+    }
+  });
+  await nextTick();
+  expect(wrapper.find('.choose-other').exists()).toBeFalsy();
+  expect(wrapper.findAll('.exist-item').length).toBe(3);
+});
+
+test('Exist address choose event', async () => {
+  const wrapper = mount(Address, {
+    props: {
+      type: 'exist',
+      visible: true,
+      existAddress: addressExistData,
+      isWrapTeleport: false
+    }
+  });
+  await nextTick();
+  wrapper.find('.exist-item').trigger('click');
+  const chooseAddress = wrapper.emitted().selected[0];
+  expect((chooseAddress as []).length).toBe(3);
+});
+
+test('Exist address & list address', async () => {
+  const wrapper = mount(Address, {
+    props: {
+      type: 'exist',
+      visible: true,
+      existAddress: addressExistData,
+      isWrapTeleport: false,
+      province: addressListData.province,
+      city: addressListData.city,
+      country: addressListData.country,
+      town: addressListData.town
+    }
+  });
+  await nextTick();
+  const changeBtn = wrapper.find('.choose-other');
+  expect(changeBtn.exists()).toBeTruthy();
+
+  changeBtn.trigger('click');
+  await nextTick();
+  expect(wrapper.find('.arrow-back').exists()).toBeTruthy();
+  expect(wrapper.find('.custom-address').exists()).toBeTruthy();
+});

+ 60 - 0
src/packages/__VUE/address/address-list.ts

@@ -0,0 +1,60 @@
+const addressListData = {
+  province: [
+    { id: 1, name: '北京', title: 'B' },
+    { id: 2, name: '广西', title: 'G' },
+    { id: 3, name: '江西', title: 'J' },
+    { id: 4, name: '四川', title: 'S' },
+    { id: 5, name: '浙江', title: 'Z' }
+  ],
+  city: [
+    { id: 7, name: '朝阳区', title: 'C' },
+    { id: 8, name: '崇文区', title: 'C' },
+    { id: 9, name: '昌平区', title: 'C' },
+    { id: 6, name: '石景山区', title: 'S' },
+    { id: 3, name: '八里庄街道', title: 'B' },
+    { id: 9, name: '北苑', title: 'B' }
+  ],
+  country: [
+    { id: 3, name: '八里庄街道', title: 'B' },
+    { id: 9, name: '北苑', title: 'B' },
+    { id: 4, name: '常营乡', title: 'C' }
+  ],
+  town: []
+};
+
+const addressExistData = [
+  {
+    id: 1,
+    addressDetail: '',
+    cityName: '次渠镇',
+    countyName: '通州区',
+    provinceName: '北京市',
+    selectedAddress: true,
+    townName: '',
+    name: '探探鱼',
+    phone: '182****1718'
+  },
+  {
+    id: 2,
+    addressDetail: '',
+    cityName: '钓鱼岛全区',
+    countyName: '',
+    provinceName: '钓鱼岛',
+    selectedAddress: false,
+    townName: '',
+    name: '探探鱼',
+    phone: '182****1718'
+  },
+  {
+    id: 3,
+    addressDetail: '京东大厦',
+    cityName: '大兴区',
+    countyName: '科创十一街18号院',
+    provinceName: '北京市',
+    selectedAddress: false,
+    townName: '',
+    name: '探探鱼',
+    phone: '182****1718'
+  }
+];
+export { addressListData, addressExistData };

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

@@ -5,6 +5,8 @@
     @click-overlay="clickOverlay"
     @open="closeWay = 'self'"
     v-model:visible="showPopup"
+    :isWrapTeleport="isWrapTeleport"
+    :teleport="teleport"
   >
     <view class="nut-address">
       <view class="nut-address__header">
@@ -127,6 +129,7 @@
 <script lang="ts">
 import { reactive, ref, toRefs, watch, nextTick, computed, Ref, onMounted } from 'vue';
 import { createComponent } from '../../utils/create';
+import { popupProps } from '../popup/index.vue';
 const { componentName, create } = createComponent('address');
 interface RegionData {
   name: string;
@@ -149,14 +152,11 @@ interface AddressList {
 export default create({
   inheritAttrs: false,
   props: {
+    ...popupProps,
     modelValue: {
       type: Array,
       default: () => []
     },
-    visible: {
-      type: Boolean,
-      default: false
-    },
     type: {
       type: String,
       default: 'custom'
@@ -584,6 +584,7 @@ export default create({
       clickOverlay,
       handClose,
       handleElevatorItem,
+      initCustomSelected,
       ...toRefs(props)
     };
   }

+ 86 - 0
src/packages/__VUE/audio/__tests__/audio.spec.ts

@@ -0,0 +1,86 @@
+import { config, mount } from '@vue/test-utils';
+import { nextTick, ref, toRefs, reactive, onMounted } from 'vue';
+import NutIcon from '../../icon/index.vue';
+import NutRange from '../../range/index.vue';
+import Audio from '../index.vue';
+import AudioOperate from '../../audiooperate/index.vue';
+import NutButton from '../../button/index.vue';
+
+function sleep(delay = 0): Promise<void> {
+  return new Promise((resolve) => {
+    setTimeout(resolve, delay);
+  });
+}
+
+beforeAll(() => {
+  config.global.components = {
+    NutIcon,
+    NutRange,
+    AudioOperate,
+    NutButton
+  };
+});
+
+afterAll(() => {
+  config.global.components = {};
+});
+
+test('audio init render', async () => {
+  const wrapper = mount(Audio, {
+    props: {
+      type: 'icon',
+      url: '//storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav'
+    }
+  });
+  await nextTick();
+  expect(wrapper.find('.nut-audio-icon').exists()).toBeTruthy();
+});
+
+test('audio init render', async () => {
+  const wrapper = mount({
+    components: {
+      'nut-audio': Audio,
+      'nut-icon': NutIcon,
+      'nut-range': NutRange,
+      'nut-audio-operate': AudioOperate,
+      'nut-button': NutButton
+    },
+    template: `
+    <nut-audio
+        url="//storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+        :muted="muted"
+        :autoplay="autoplay"
+        :loop="false"
+        type="progress"
+        @forward="forward"
+      >
+      <div class="nut-audio-operate-group">
+        <nut-audio-operate type="forward"></nut-audio-operate>
+        <nut-audio-operate type="back"></nut-audio-operate>
+        <nut-audio-operate type="play"></nut-audio-operate>
+        
+        <nut-audio-operate type="mute"></nut-audio-operate>
+      </div>
+      </nut-audio>
+
+      <div>{{time}}</div>
+    `,
+    setup() {
+      const data = reactive({
+        muted: false,
+        autoplay: false,
+        time: 0
+      });
+      const forward = (p: any) => {
+        data.time = p;
+      };
+
+      return { ...toRefs(data), forward };
+    }
+  });
+  await nextTick();
+  expect(wrapper.find('.nut-audio-operate-item').exists()).toBeTruthy();
+  wrapper.find('.nut-audio-operate-item').trigger('click');
+  await nextTick();
+  expect(wrapper.vm.time).toBe(1);
+});

+ 3 - 3
src/packages/__VUE/audio/demo.vue

@@ -3,7 +3,7 @@
     <h2>基础用法</h2>
     <nut-audio
       style="margin-left: 20px"
-      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      url="//storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
       :muted="muted"
       :autoplay="autoplay"
       :loop="true"
@@ -13,7 +13,7 @@
     <h2>语音播放</h2>
     <nut-audio
       style="margin-left: 20px"
-      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      url="//storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
       :muted="muted"
       :autoplay="autoplay"
       :loop="false"
@@ -29,7 +29,7 @@
     <h2>进度条展示</h2>
     <nut-audio
       style="margin-left: 20px"
-      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      url="//storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
       :muted="muted"
       :autoplay="autoplay"
       :loop="true"

+ 8 - 6
src/packages/__VUE/audio/doc.md

@@ -23,7 +23,7 @@ app.use(Audio);
 ```html
 <template>
     <nut-audio
-      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      url="//storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
       :muted="muted"
       :autoplay="autoplay"
       :loop="true"
@@ -54,11 +54,12 @@ export default {
 ```html
 <template>
     <nut-audio
-      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      url="//storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
       :muted="muted"
       :autoplay="autoplay"
       :loop="false"
       type="none"
+      ref="audioDemo"
     >
       <div class="nut-voice">
         <div><nut-icon name="voice"></nut-icon></div>
@@ -70,13 +71,13 @@ export default {
 import { reactive, toRefs, onMounted } from 'vue';
 export default {
   setup() {
+    const audioDemo = ref(null);
     const data = reactive({
       muted: false,
       autoplay: false
     });
     const duration = ref(0);
     onMounted(() => {
-      console.log(audioDemo.value);
       setTimeout(() => {
         duration.value = audioDemo.value.second.toFixed();
       }, 500);
@@ -84,7 +85,8 @@ export default {
 
     return {
       ...toRefs(data),
-      duration
+      duration,
+      audioDemo
     };
   }
 };
@@ -111,7 +113,7 @@ export default {
 ```html
 <template>
     <nut-audio
-      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      url="//storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
       :muted="muted"
       :autoplay="autoplay"
       :loop="true"
@@ -149,7 +151,7 @@ export default {
 ```html
 <template>
     <nut-audio
-      url="http://storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
+      url="//storage.360buyimg.com/jdcdkh/SMB/VCG231024564.wav"
       :muted="muted"
       :autoplay="autoplay"
       :loop="false"

+ 1 - 1
src/packages/__VUE/audiooperate/index.vue

@@ -28,7 +28,7 @@ export default create({
   props: {
     // 展示的形式   back 倒退   play 开始 or 暂停  forward 快进 mute 静音
     type: {
-      type: Array,
+      type: String,
       default() {
         return 'play';
       }

+ 66 - 0
src/packages/__VUE/datepicker/__tests__/datepicker.spec.ts

@@ -0,0 +1,66 @@
+import { config, mount } from '@vue/test-utils';
+import { nextTick, ref, toRefs, reactive, onMounted } from 'vue';
+import NutIcon from '../../icon/index.vue';
+import NutRange from '../../range/index.vue';
+import DatePicker from '../../datepicker/index.vue';
+
+function sleep(delay = 0): Promise<void> {
+  return new Promise((resolve) => {
+    setTimeout(resolve, delay);
+  });
+}
+
+beforeAll(() => {
+  config.global.components = {
+    NutIcon,
+    NutRange
+  };
+});
+
+afterAll(() => {
+  config.global.components = {};
+});
+
+test('Do not display Chinese', async () => {
+  const wrapper = mount(DatePicker, {
+    props: {
+      modelValue: new Date(2020, 0, 1),
+      visible: true,
+      isWrapTeleport: false,
+      isShowChinese: false
+    }
+  });
+  await nextTick();
+  expect(wrapper.find('.nut-picker__confirm').exists()).toBeTruthy();
+  const confirm = wrapper.find('.nut-picker__confirm');
+  confirm.trigger('click');
+  expect(wrapper.emitted().confirm[0]).toEqual([[2020, 1, 1]]);
+});
+
+test('min date & max date', async () => {
+  const wrapper = mount(DatePicker, {
+    props: {
+      minDate: new Date(2020, 0, 1),
+      maxDate: new Date(2022, 10, 1),
+      visible: true,
+      isWrapTeleport: false
+    }
+  });
+  await nextTick();
+  const yearItem = wrapper.find('.nut-picker__list').findAll('.nut-picker-roller-item');
+  expect(yearItem.length).toBe(3);
+});
+
+test('Increment step setting', async () => {
+  const wrapper = mount(DatePicker, {
+    props: {
+      type: 'time',
+      minuteStep: 5,
+      visible: true,
+      isWrapTeleport: false
+    }
+  });
+  await nextTick();
+  const yearItem = wrapper.findAll('.nut-picker__list')[1].findAll('.nut-picker-roller-item');
+  expect(yearItem.length).toBe(12);
+});

+ 4 - 4
src/packages/__VUE/datepicker/index.vue

@@ -6,13 +6,16 @@
     @change="changeHandler"
     :title="title"
     @confirm="confirm"
+    :isWrapTeleport="isWrapTeleport"
   ></nut-picker>
 </template>
 <script lang="ts">
 import { toRefs, watch, computed, reactive, onMounted } from 'vue';
 import picker from '../picker/index.vue';
+import { popupProps } from '../popup/index.vue';
 import { createComponent } from '../../utils/create';
 const { componentName, create } = createComponent('datepicker');
+
 const currentYear = new Date().getFullYear();
 function isDate(val: Date): val is Date {
   return Object.prototype.toString.call(val) === '[object Date]' && !isNaN(val.getTime());
@@ -31,11 +34,8 @@ export default create({
     [picker.name]: picker
   },
   props: {
+    ...popupProps,
     modelValue: null,
-    visible: {
-      type: Boolean,
-      default: false
-    },
     title: {
       type: String,
       default: ''

+ 10 - 12
src/packages/__VUE/noticebar/__tests__/noticebar.spec.ts

@@ -3,6 +3,12 @@ import { nextTick, ref, reactive } from 'vue';
 import NoticeBar from '../index.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
@@ -13,6 +19,10 @@ afterAll(() => {
   config.global.components = {};
 });
 
+Object.defineProperty(window.HTMLElement.prototype, 'clientWidth', {
+  value: 375
+});
+
 test('close event', async () => {
   const wrapper = mount(NoticeBar, {
     props: {
@@ -26,18 +36,6 @@ test('close event', async () => {
   expect(wrapper.emitted('close')).toBeTruthy();
 });
 
-test('scrollable props', async () => {
-  const wrapper = mount(NoticeBar, {
-    props: {
-      text: '华为畅享9新品即将上市,活动期间0元预约可参与抽奖,赢HUAWEI WATCH等好礼,更多产品信息请持续关注!',
-      direction: 'across',
-      scrollable: false
-    }
-  });
-  const closeDom = wrapper.find('.nut-ellipsis');
-  expect(closeDom.exists()).toBeTruthy();
-});
-
 test('icon custom', async () => {
   const wrapper = mount({
     components: {

+ 4 - 5
src/packages/__VUE/noticebar/index.vue

@@ -14,8 +14,7 @@
       <view ref="wrap" class="wrap">
         <view
           ref="content"
-          class="content"
-          :class="[animationClass, { 'nut-ellipsis': isEllipsis }]"
+          :class="['content', animationClass, { 'nut-ellipsis': isEllipsis }]"
           :style="contentStyle"
           @animationend="onAnimationEnd"
           @webkitAnimationEnd="onAnimationEnd"
@@ -197,7 +196,7 @@ export default create({
 
     const isEllipsis = computed(() => {
       if (state.isCanScroll == null) {
-        return false && !props.wrapable;
+        return props.wrapable;
       } else {
         return !state.isCanScroll && !props.wrapable;
       }
@@ -284,7 +283,7 @@ export default create({
         const offsetWidth = content.value.getBoundingClientRect().width;
 
         state.isCanScroll = props.scrollable == null ? offsetWidth > wrapWidth : props.scrollable;
-
+        console.log(111, state.isCanScroll);
         if (state.isCanScroll) {
           state.wrapWidth = wrapWidth;
           state.offsetWidth = offsetWidth;
@@ -294,7 +293,7 @@ export default create({
         } else {
           state.animationClass = '';
         }
-      });
+      }, 0);
     };
     const handleClick = (event: Event) => {
       emit('click', event);

+ 16 - 2
src/packages/__VUE/picker/Column.vue

@@ -39,7 +39,20 @@ export default create({
       type: Boolean,
       default: false
     },
-    ...commonProps
+    listData: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    },
+    readonly: {
+      type: Boolean,
+      default: false
+    },
+    defaultIndex: {
+      type: [Number, String],
+      default: 0
+    }
   },
 
   emits: ['click', 'change'],
@@ -225,7 +238,8 @@ export default create({
       onTouchMove,
       onTouchEnd,
       touchRollerStyle,
-      touchListStyle
+      touchListStyle,
+      setMove
     };
   }
 });

+ 0 - 8
src/packages/__VUE/picker/__tests__/__snapshots__/picker.spec.ts.snap

@@ -1,8 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`base 1`] = `
-"<view class=\\"nut-picker\\" modelvisible=\\"true\\">
-  <!--teleport start-->
-  <!--teleport end-->
-</view>"
-`;

+ 97 - 9
src/packages/__VUE/picker/__tests__/picker.spec.ts

@@ -4,6 +4,7 @@ import NutIcon from '../../icon/index.vue';
 import NutPupup from '../../popup/index.vue';
 import NutPickerColumn from '../Column.vue';
 import { nextTick, toRefs, reactive, ref, onMounted } from 'vue';
+import { triggerDrag } from '../../../utils/test/event';
 
 beforeAll(() => {
   config.global.components = {
@@ -17,24 +18,111 @@ afterAll(() => {
   config.global.components = {};
 });
 
-const simpleColumn = ['1990', '1991', '1992', '1993', '1994', '1995'];
-const columns = [
+const simpleColumns = ['南京市', '无锡市', '海北藏族自治区', '北京市', '连云港市', '浙江市', '江苏市'];
+const multipleColumns = [
   {
-    values: ['vip', 'normal'],
-    className: 'column1'
+    values: ['周一', '周二', '周三', '周四', '周五'],
+    defaultIndex: 2
   },
+  // 第二列
   {
-    values: simpleColumn,
-    className: 'column2'
+    values: ['上午', '下午', '晚上'],
+    defaultIndex: 1
   }
 ];
+const multistageColumns = [
+  {
+    text: '浙江',
+    children: [
+      {
+        text: '杭州',
+        children: [{ text: '西湖区' }, { text: '余杭区' }]
+      },
+      {
+        text: '温州',
+        children: [{ text: '鹿城区' }, { text: '瓯海区' }]
+      }
+    ]
+  },
+  {
+    text: '福建',
+    children: [
+      {
+        text: '福州',
+        children: [{ text: '鼓楼区' }, { text: '台江区' }]
+      },
+      {
+        text: '厦门',
+        children: [{ text: '思明区' }, { text: '海沧区' }]
+      }
+    ]
+  }
+];
+
+test('first render', async () => {
+  const wrapper = mount(Picker, {
+    props: {
+      visible: true,
+      listData: simpleColumns,
+      isWrapTeleport: false
+    }
+  });
+  await nextTick();
+  expect(wrapper.find('.nut-picker__cancel').exists()).toBeTruthy();
+  expect(wrapper.find('.nut-picker__confirm').exists()).toBeTruthy();
+});
+
+test('simple list-data confirm & close event', async () => {
+  const wrapper = mount(Picker, {
+    props: {
+      visible: true,
+      listData: simpleColumns,
+      isWrapTeleport: false
+    }
+  });
+  await nextTick();
+  wrapper.find('.nut-picker__cancel').trigger('click');
+  wrapper.find('.nut-picker__confirm').trigger('click');
+  expect(wrapper.emitted('confirm')).toEqual([['南京市']]);
+  expect(wrapper.emitted('close')).toEqual([[]]);
+});
+
+test('simple columns default checked item', async () => {
+  const wrapper = mount(Picker, {
+    props: {
+      visible: true,
+      listData: simpleColumns,
+      isWrapTeleport: false,
+      defaultIndex: 2
+    }
+  });
+  await nextTick();
+  wrapper.find('.nut-picker__confirm').trigger('click');
+  expect(wrapper.emitted('confirm')).toEqual([['海北藏族自治区']]);
+});
+
+test('multiple columns render', async () => {
+  const wrapper = mount(Picker, {
+    props: {
+      visible: true,
+      listData: multipleColumns,
+      isWrapTeleport: false
+    }
+  });
+  await nextTick();
+  const columnItems = wrapper.findAll('.nut-picker__columnitem');
+  expect(columnItems.length).toEqual(2);
+});
 
-test('base', async () => {
+test('Multistage Tandem', async () => {
   const wrapper = mount(Picker, {
     props: {
-      modelVisible: true,
-      listData: []
+      visible: true,
+      listData: multistageColumns,
+      isWrapTeleport: false
     }
   });
   await nextTick();
+  wrapper.find('.nut-picker__confirm').trigger('click');
+  expect(wrapper.emitted().confirm[0]).toEqual([['浙江', '杭州', '西湖区']]);
 });

+ 9 - 10
src/packages/__VUE/picker/demo.vue

@@ -15,30 +15,27 @@
       v-model:visible="show"
       :list-data="listData1"
       title="城市选择"
+      :isWrapTeleport="false"
       @confirm="(val) => confirm(0, val)"
-      @close="close"
     >
     </nut-picker>
-    <!-- <nut-picker
+    <nut-picker
       v-model:visible="show1"
       :list-data="listData1"
       title="城市选择"
       :defaultIndex="2"
       @confirm="(val) => confirm(1, val)"
-      @close="close"
     >
     </nut-picker>
-    <nut-picker v-model:visible="show2" :list-data="listData2" title="多列选择" @confirm="confirm2" @close="close">
-    </nut-picker>
+    <nut-picker v-model:visible="show2" :list-data="listData2" title="多列选择" @confirm="confirm2"></nut-picker>
     <nut-picker v-model:visible="show3" :list-data="listData3" title="地址选择" @confirm="confirm3"></nut-picker>
     <nut-picker
       v-model:visible="show4"
       :list-data="listData4"
-      :demoIndex="demoIndex"
       title="地址选择"
       @change="onChange"
       @confirm="(val) => confirm(4, val)"
-    ></nut-picker> -->
+    ></nut-picker>
   </div>
 </template>
 <script lang="ts">
@@ -48,7 +45,7 @@ const { createDemo } = createComponent('picker');
 export default createDemo({
   props: {},
   setup() {
-    const listData1 = ['南京市', '无锡市', '海北藏族自治区', '北京市', '连云港市', '浙江市', '江苏市'];
+    const listData1 = ref(['南京市', '无锡市', '海北藏族自治区', '北京市', '连云港市', '浙江市', '江苏市']);
 
     const listData2 = [
       {
@@ -110,8 +107,8 @@ export default createDemo({
     const show3 = ref(false);
     const show4 = ref(false);
     const showList = [show, show1, show2, show3, show4];
-    const desc = ref(listData1[0]);
-    const desc1 = ref(listData1[2]);
+    const desc = ref(listData1.value[0]);
+    const desc1 = ref(listData1.value[2]);
     const desc2 = ref(
       `${listData2[0].values[listData2[0].defaultIndex]} ${listData2[1].values[listData2[1].defaultIndex]}`
     );
@@ -141,6 +138,7 @@ export default createDemo({
         showList[index].value = true;
       },
       confirm: (type: number, res: any) => {
+        console.log(res);
         if (type == 4) {
           descList[type].value = res[0] + ' ' + res[1];
         } else {
@@ -148,6 +146,7 @@ export default createDemo({
         }
       },
       confirm2: (res: any) => {
+        console.log(res);
         desc2.value = res.join(' ');
       },
       confirm3: (res: any) => {

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

@@ -1,7 +1,4 @@
 .nut-picker {
-  view {
-    display: block;
-  }
   &__content {
     display: block;
     position: relative;

+ 37 - 6
src/packages/__VUE/picker/index.vue

@@ -8,11 +8,12 @@
       :close-on-click-overlay="closeOnClickOverlay"
       @close="close"
       :round="true"
+      :isWrapTeleport="isWrapTeleport"
     >
       <view class="nut-picker__bar">
-        <view class="nut-picker__button nut-picker__left" @click="close">{{ cancelText }}</view>
+        <view class="nut-picker__cancel nut-picker__left nut-picker__button" @click="close">{{ cancelText }}</view>
         <view class="nut-picker__title"> {{ title }}</view>
-        <view class="nut-picker__button nut-picker__right" @click="confirm()">{{ okText }}</view>
+        <view class="nut-picker__confirm nut-picker__button nut-picker__right" @click="confirm()">{{ okText }}</view>
       </view>
 
       <view class="nut-picker__column">
@@ -23,7 +24,6 @@
             :list-data="item"
             :readonly="readonly"
             :default-index="item.defaultIndex"
-            :visible-item-count="visibleItemCount"
             :data-type="dataType"
             @change="
               (dataIndex) => {
@@ -37,7 +37,18 @@
   </view>
 </template>
 <script lang="ts">
-import { reactive, watch, computed, toRaw, toRefs } from 'vue';
+import {
+  onMounted,
+  onBeforeMount,
+  onBeforeUnmount,
+  onActivated,
+  onDeactivated,
+  reactive,
+  watch,
+  computed,
+  toRaw,
+  toRefs
+} from 'vue';
 import { createComponent } from '../../utils/create';
 import column from './Column.vue';
 import popup, { popupProps } from '../popup/index.vue';
@@ -65,7 +76,20 @@ export default create({
       type: String,
       default: '确定'
     },
-    ...commonProps
+    listData: {
+      type: Array,
+      default: () => {
+        return [];
+      }
+    },
+    readonly: {
+      type: Boolean,
+      default: false
+    },
+    defaultIndex: {
+      type: [Number, String],
+      default: 0
+    }
   },
   emits: ['close', 'change', 'confirm', 'update:visible'],
   setup(props, { emit }) {
@@ -101,7 +125,6 @@ export default create({
     });
 
     const columnList = computed(() => {
-      console.log('初始化', dataType.value);
       if (dataType.value === 'text') {
         return [{ values: state.formattedColumns, defaultIndex: state.defaultIndex }];
       } else if (dataType.value === 'multipleColumns') {
@@ -199,6 +222,14 @@ export default create({
       emit('update:visible', false);
     };
 
+    onMounted(() => {
+      if (props.visible) state.show = props.visible;
+    });
+
+    onBeforeUnmount(() => {
+      if (props.visible) state.show = false;
+    });
+
     watch(
       () => props.visible,
       (val) => {

+ 80 - 0
src/packages/__VUE/sku/__tests__/sku.spec.ts

@@ -0,0 +1,80 @@
+import { config, mount } from '@vue/test-utils';
+import { nextTick, toRefs, reactive } from 'vue';
+import NutIcon from '../../icon/index.vue';
+import NutPopup from '../../popup/index.vue';
+import NutInputnumber from '../../inputnumber/index.vue';
+import NutPrice from '../../price/index.vue';
+import Sku from '../index.vue';
+import { Sku as SkuData, Goods } from '../data';
+
+beforeAll(() => {
+  config.global.components = {
+    NutIcon,
+    NutPopup,
+    NutInputnumber,
+    NutPrice
+  };
+});
+
+afterAll(() => {
+  config.global.components = {};
+});
+
+test('sku init render', async () => {
+  const wrapper = mount(Sku, {
+    props: {
+      visible: true,
+      isWrapTeleport: false,
+      sku: SkuData,
+      goods: Goods
+    }
+  });
+  await nextTick();
+  expect(wrapper.find('.nut-sku-select').exists()).toBeTruthy();
+});
+
+test('sku select event', async () => {
+  const wrapper = mount(Sku, {
+    props: {
+      visible: true,
+      isWrapTeleport: false,
+      sku: SkuData,
+      goods: Goods
+    }
+  });
+  await nextTick();
+  const skuItem = wrapper.findAll('.nut-sku-select-item-skus-sku');
+  skuItem[1].trigger('click');
+  const selectSkuObj = wrapper.emitted().selectSku[0] as [];
+  expect(selectSkuObj.length).toBe(1);
+});
+
+test('do not sell', async () => {
+  const wrapper = mount(Sku, {
+    props: {
+      visible: true,
+      isWrapTeleport: false,
+      sku: SkuData,
+      goods: Goods,
+      btnExtraText: '抱歉,此商品在所选区域暂无存货'
+    }
+  });
+  await nextTick();
+  expect(wrapper.find('.nut-sku-operate-desc').exists()).toBeTruthy();
+  expect(wrapper.find('.nut-sku-operate-desc').text()).toEqual('抱歉,此商品在所选区域暂无存货');
+});
+
+test('button event', async () => {
+  const wrapper = mount(Sku, {
+    props: {
+      visible: true,
+      isWrapTeleport: false,
+      sku: SkuData,
+      goods: Goods
+    }
+  });
+  await nextTick();
+  expect(wrapper.find('.nut-sku-operate-btn-confirm').exists()).toBeTruthy();
+  wrapper.find('.nut-sku-operate-btn-confirm').trigger('click');
+  expect(wrapper.emitted().clickBtnOperate[0]).toEqual([{ type: 'confirm', value: 1 }]);
+});

+ 0 - 4
src/packages/__VUE/sku/components/SkuOperate.vue

@@ -62,10 +62,6 @@ export default create({
       return mapD[type];
     };
 
-    onMounted(() => {
-      console.log(slots);
-    });
-
     const getSlots = (name: string) => slots[name];
 
     const clickBtnOperate = (btn: string) => {

+ 0 - 1
src/packages/__VUE/sku/components/SkuSelect.vue

@@ -36,7 +36,6 @@ export default create({
     watch(
       () => props.sku,
       (value) => {
-        // console.log('发生变化');
         skuInfo.value = [].slice.call(value);
       },
       { deep: true }

src/packages/__VUE/sku/data.js → src/packages/__VUE/sku/data.ts


+ 1 - 1
src/packages/__VUE/sku/demo.vue

@@ -207,7 +207,7 @@ export default createDemo({
     });
 
     const getData = () => {
-      fetch('http://storage.360buyimg.com/nutui/3x/data.js')
+      fetch('//storage.360buyimg.com/nutui/3x/data.js')
         .then((response) => response.json())
         .then((res) => {
           const { Sku, Goods, imagePathMap } = res;

+ 4 - 4
src/packages/__VUE/sku/index.vue

@@ -8,6 +8,8 @@
     @click-overlay="closePopup('overlay')"
     @close="closePopup('close')"
     style="height: 75%"
+    :isWrapTeleport="isWrapTeleport"
+    :teleport="teleport"
   >
     <view class="nut-sku">
       <slot name="sku-header"></slot>
@@ -66,14 +68,12 @@ import SkuSelect from './components/SkuSelect.vue';
 import SkuStepper from './components/SkuStepper.vue';
 import SkuOperate from './components/SkuOperate.vue';
 import { createComponent } from '../../utils/create';
+import { popupProps } from '../popup/index.vue';
 const { componentName, create } = createComponent('sku');
 
 export default create({
   props: {
-    visible: {
-      type: Boolean,
-      default: false
-    },
+    ...popupProps,
 
     sku: {
       type: Array,