From d5fdab55ccf158e56d86522229dfe528cfa003e4 Mon Sep 17 00:00:00 2001
From: neverland <chenjiahan@youzan.com>
Date: Sat, 11 Dec 2021 11:45:19 +0800
Subject: [PATCH] fix(DatetimePicker): should update value after calling picker
 methods (#10029)

---
 .../vant/src/datetime-picker/DatePicker.tsx   |  4 +-
 .../vant/src/datetime-picker/TimePicker.tsx   | 10 +++-
 .../datetime-picker.spec.tsx.snap             | 34 +++++------
 .../test/datetime-picker.spec.tsx             | 58 ++++++++++++++++---
 packages/vant/src/datetime-picker/utils.ts    | 25 ++++++++
 5 files changed, 102 insertions(+), 29 deletions(-)

diff --git a/packages/vant/src/datetime-picker/DatePicker.tsx b/packages/vant/src/datetime-picker/DatePicker.tsx
index 621cec848..3bebf8809 100644
--- a/packages/vant/src/datetime-picker/DatePicker.tsx
+++ b/packages/vant/src/datetime-picker/DatePicker.tsx
@@ -23,6 +23,7 @@ import {
   getTrueValue,
   getMonthEndDay,
   pickerInheritKeys,
+  proxyPickerMethods,
 } from './utils';
 
 // Composables
@@ -316,7 +317,8 @@ export default defineComponent({
     );
 
     useExpose({
-      getPicker: () => picker.value,
+      getPicker: () =>
+        picker.value && proxyPickerMethods(picker.value, updateInnerValue),
     });
 
     return () => (
diff --git a/packages/vant/src/datetime-picker/TimePicker.tsx b/packages/vant/src/datetime-picker/TimePicker.tsx
index d3ecf692c..aac16aec1 100644
--- a/packages/vant/src/datetime-picker/TimePicker.tsx
+++ b/packages/vant/src/datetime-picker/TimePicker.tsx
@@ -16,7 +16,12 @@ import {
   createNamespace,
   makeNumericProp,
 } from '../utils';
-import { times, sharedProps, pickerInheritKeys } from './utils';
+import {
+  times,
+  sharedProps,
+  pickerInheritKeys,
+  proxyPickerMethods,
+} from './utils';
 
 // Composables
 import { useExpose } from '../composables/use-expose';
@@ -160,7 +165,8 @@ export default defineComponent({
     );
 
     useExpose({
-      getPicker: () => picker.value,
+      getPicker: () =>
+        picker.value && proxyPickerMethods(picker.value, updateInnerValue),
     });
 
     return () => (
diff --git a/packages/vant/src/datetime-picker/test/__snapshots__/datetime-picker.spec.tsx.snap b/packages/vant/src/datetime-picker/test/__snapshots__/datetime-picker.spec.tsx.snap
index 3b614dcc8..c69cff11f 100644
--- a/packages/vant/src/datetime-picker/test/__snapshots__/datetime-picker.spec.tsx.snap
+++ b/packages/vant/src/datetime-picker/test/__snapshots__/datetime-picker.spec.tsx.snap
@@ -1,22 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`should render title slot correctly 1`] = `
-<div class="van-picker__toolbar">
-  <button type="button"
-          class="van-picker__cancel van-haptics-feedback"
-  >
-    Cancel
-  </button>
-  Custom title
-  <button type="button"
-          class="van-picker__confirm van-haptics-feedback"
-  >
-    Confirm
-  </button>
-</div>
-`;
-
-exports[`time type 1`] = `
+exports[`should render time type correctly 1`] = `
 <div class="van-picker van-datetime-picker">
   <div class="van-picker__toolbar">
     <button type="button"
@@ -92,3 +76,19 @@ exports[`time type 1`] = `
   </div>
 </div>
 `;
+
+exports[`should render title slot correctly 1`] = `
+<div class="van-picker__toolbar">
+  <button type="button"
+          class="van-picker__cancel van-haptics-feedback"
+  >
+    Cancel
+  </button>
+  Custom title
+  <button type="button"
+          class="van-picker__confirm van-haptics-feedback"
+  >
+    Confirm
+  </button>
+</div>
+`;
diff --git a/packages/vant/src/datetime-picker/test/datetime-picker.spec.tsx b/packages/vant/src/datetime-picker/test/datetime-picker.spec.tsx
index 929b4931c..76c0978b5 100644
--- a/packages/vant/src/datetime-picker/test/datetime-picker.spec.tsx
+++ b/packages/vant/src/datetime-picker/test/datetime-picker.spec.tsx
@@ -3,25 +3,29 @@ import { mount, later } from '../../../test';
 import { reactive } from 'vue';
 import { useExpose } from '../../composables/use-expose';
 
-test('confirm & cancel event', () => {
+test('should emit confirm event after clicking the confirm button', () => {
   const onConfirm = jest.fn();
-  const onCancel = jest.fn();
-
   const wrapper = mount(DatetimePicker, {
     props: {
       onConfirm,
+    },
+  });
+  wrapper.find('.van-picker__confirm').trigger('click');
+  expect(onConfirm).toHaveBeenCalledTimes(1);
+});
+
+test('should emit cancel event after clicking the confirm button', () => {
+  const onCancel = jest.fn();
+  const wrapper = mount(DatetimePicker, {
+    props: {
       onCancel,
     },
   });
-
-  wrapper.find('.van-picker__confirm').trigger('click');
-  expect(onConfirm).toHaveBeenCalledTimes(1);
-
   wrapper.find('.van-picker__cancel').trigger('click');
   expect(onCancel).toHaveBeenCalledTimes(1);
 });
 
-test('time type', () => {
+test('should render time type correctly', () => {
   const wrapper = mount(DatetimePicker, {
     props: {
       type: 'time',
@@ -33,7 +37,7 @@ test('time type', () => {
   expect(wrapper.html()).toMatchSnapshot();
 });
 
-test('getPicker method', () => {
+test('should allow to call getPicker method', () => {
   const wrapper = mount(DatetimePicker);
 
   expect(wrapper.vm.getPicker()).toBeTruthy();
@@ -86,3 +90,39 @@ test('should emit value correctly when dynamic change min-date', async () => {
   wrapper.find('.van-picker__confirm').trigger('click');
   expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(defaultValue);
 });
+
+test('should update value correctly after calling setColumnIndex method', async () => {
+  const onConfirm = jest.fn();
+  const defaultDate = new Date(2020, 0, 1);
+  const wrapper = mount(DatetimePicker, {
+    props: {
+      type: 'date',
+      minDate: defaultDate,
+      maxDate: new Date(2020, 0, 30),
+      modelValue: defaultDate,
+      onConfirm,
+    },
+  });
+
+  wrapper.vm.getPicker().setColumnIndex(2, 14);
+  await wrapper.find('.van-picker__confirm').trigger('click');
+  expect(onConfirm.mock.calls[0]).toEqual([new Date(2020, 0, 15)]);
+});
+
+test('should update value correctly after calling setColumnValue method', async () => {
+  const onConfirm = jest.fn();
+  const defaultDate = new Date(2020, 0, 1);
+  const wrapper = mount(DatetimePicker, {
+    props: {
+      type: 'date',
+      minDate: defaultDate,
+      maxDate: new Date(2020, 0, 30),
+      modelValue: defaultDate,
+      onConfirm,
+    },
+  });
+
+  wrapper.vm.getPicker().setColumnValue(2, '15');
+  await wrapper.find('.van-picker__confirm').trigger('click');
+  expect(onConfirm.mock.calls[0]).toEqual([new Date(2020, 0, 15)]);
+});
diff --git a/packages/vant/src/datetime-picker/utils.ts b/packages/vant/src/datetime-picker/utils.ts
index ee09f1387..f78e90d06 100644
--- a/packages/vant/src/datetime-picker/utils.ts
+++ b/packages/vant/src/datetime-picker/utils.ts
@@ -1,6 +1,7 @@
 import { PropType } from 'vue';
 import { extend } from '../utils';
 import { pickerSharedProps } from '../picker/Picker';
+import type { PickerInstance } from '../picker';
 import type { DatetimePickerColumnType } from './types';
 
 export const sharedProps = extend({}, pickerSharedProps, {
@@ -45,3 +46,27 @@ export function getTrueValue(value: string | undefined): number {
 
 export const getMonthEndDay = (year: number, month: number): number =>
   32 - new Date(year, month - 1, 32).getDate();
+
+// https://github.com/youzan/vant/issues/10013
+export const proxyPickerMethods = (
+  picker: PickerInstance,
+  callback: () => void
+) => {
+  const methods = [
+    'setValues',
+    'setIndexes',
+    'setColumnIndex',
+    'setColumnValue',
+  ];
+  return new Proxy(picker, {
+    get: (target, prop: keyof PickerInstance) => {
+      if (methods.includes(prop)) {
+        return (...args: unknown[]) => {
+          target[prop](...args);
+          callback();
+        };
+      }
+      return target[prop];
+    },
+  });
+};