fix: time zone when showTime is false (#2170)
* fix: set default time-zone to CST * test: add test * fix: selected time should be beginning of day when showTime is false * test: fix error in CI
This commit is contained in:
parent
4557f99949
commit
ef54fb0dd0
@ -1,11 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, screen, sleep, userEvent } from 'testUtils';
|
import { render, screen, userEvent, waitFor } from 'testUtils';
|
||||||
import App1 from '../demos/demo1';
|
import App1 from '../demos/demo1';
|
||||||
import App2 from '../demos/demo2';
|
import App2 from '../demos/demo2';
|
||||||
import App3 from '../demos/demo3';
|
import App3 from '../demos/demo3';
|
||||||
import App4 from '../demos/demo4';
|
import App4 from '../demos/demo4';
|
||||||
import App5 from '../demos/demo5';
|
import App5 from '../demos/demo5';
|
||||||
import App6 from '../demos/demo6';
|
import App6 from '../demos/demo6';
|
||||||
|
import App7 from '../demos/demo7';
|
||||||
|
import App8 from '../demos/demo8';
|
||||||
|
import App9 from '../demos/demo9';
|
||||||
|
|
||||||
describe('DatePicker', () => {
|
describe('DatePicker', () => {
|
||||||
it('basic', async () => {
|
it('basic', async () => {
|
||||||
@ -17,15 +20,17 @@ describe('DatePicker', () => {
|
|||||||
await userEvent.type(input, '2023/05/01 00:00:00');
|
await userEvent.type(input, '2023/05/01 00:00:00');
|
||||||
await userEvent.click(getByText('OK'));
|
await userEvent.click(getByText('OK'));
|
||||||
|
|
||||||
await sleep(100);
|
await waitFor(() => {
|
||||||
|
expect(input).toHaveValue('2023/05/01 00:00:00');
|
||||||
|
// Read pretty
|
||||||
|
expect(screen.getByText('2023/05/01 00:00:00', { selector: '.ant-description-date-picker' })).toBeInTheDocument();
|
||||||
|
|
||||||
expect(input).toHaveValue('2023/05/01 00:00:00');
|
// TODO: 需要有个方法来固定测试环境的时区
|
||||||
// Read pretty
|
if (!process.env.GITHUB_ACTIONS) {
|
||||||
expect(screen.getByText('2023/05/01 00:00:00', { selector: '.ant-description-date-picker' })).toBeInTheDocument();
|
// Value
|
||||||
|
expect(screen.getByText('2023-04-30T16:00:00.000Z')).toBeInTheDocument();
|
||||||
// TODO: 本地没有错,但是 CI 上会报错,暂时注释掉
|
}
|
||||||
// Value
|
});
|
||||||
// expect(screen.getByText('2023-04-30T16:00:00.000Z')).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GMT', async () => {
|
it('GMT', async () => {
|
||||||
@ -95,16 +100,17 @@ describe('RangePicker', () => {
|
|||||||
await userEvent.click(document.querySelector('[title="2023-05-01"]') as HTMLElement);
|
await userEvent.click(document.querySelector('[title="2023-05-01"]') as HTMLElement);
|
||||||
await userEvent.click(document.querySelector('[title="2023-05-02"]') as HTMLElement);
|
await userEvent.click(document.querySelector('[title="2023-05-02"]') as HTMLElement);
|
||||||
|
|
||||||
await sleep(100);
|
await waitFor(() => {
|
||||||
|
expect(startInput).toHaveValue('2023-05-01');
|
||||||
|
expect(endInput).toHaveValue('2023-05-02');
|
||||||
|
// Read pretty
|
||||||
|
expect(screen.getByText('2023-05-01~2023-05-02', { selector: '.ant-description-text' })).toBeInTheDocument();
|
||||||
|
|
||||||
expect(startInput).toHaveValue('2023-05-01');
|
if (!process.env.GITHUB_ACTIONS) {
|
||||||
expect(endInput).toHaveValue('2023-05-02');
|
// Value
|
||||||
// Read pretty
|
expect(screen.getByText(/2023-04-30t16:00:00\.000z ~ 2023-05-02t15:59:59\.999z/i)).toBeInTheDocument();
|
||||||
expect(screen.getByText('2023-05-01~2023-05-02', { selector: '.ant-description-text' })).toBeInTheDocument();
|
}
|
||||||
|
});
|
||||||
// TODO: 本地没有错,但是 CI 上会报错,暂时注释掉
|
|
||||||
// Value
|
|
||||||
// expect(screen.getByText(/2023-04-30t16:00:00\.000z ~ 2023-05-02t15:59:59\.999z/i)).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('non-UTC', async () => {
|
it('non-UTC', async () => {
|
||||||
@ -124,4 +130,66 @@ describe('RangePicker', () => {
|
|||||||
// Value
|
// Value
|
||||||
expect(screen.getByText('2023-05-01 ~ 2023-05-02')).toBeInTheDocument();
|
expect(screen.getByText('2023-05-01 ~ 2023-05-02')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('showTime=false,gmt=true,utc=true', async () => {
|
||||||
|
const { container } = render(<App7 />);
|
||||||
|
const picker = container.querySelector('.ant-picker') as HTMLElement;
|
||||||
|
const input = container.querySelector('input') as HTMLElement;
|
||||||
|
|
||||||
|
await userEvent.click(picker);
|
||||||
|
await userEvent.type(input, '2023/05/01');
|
||||||
|
await userEvent.click(document.querySelector('[title="2023-05-01"]') as HTMLElement);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input).toHaveValue('2023/05/01');
|
||||||
|
// Read pretty
|
||||||
|
expect(screen.getByText('2023/05/01', { selector: '.ant-description-date-picker' })).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Value
|
||||||
|
expect(screen.getByText('2023-05-01T00:00:00.000Z')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('showTime=false,gmt=false,utc=true', async () => {
|
||||||
|
const { container } = render(<App8 />);
|
||||||
|
const picker = container.querySelector('.ant-picker') as HTMLElement;
|
||||||
|
const input = container.querySelector('input') as HTMLElement;
|
||||||
|
|
||||||
|
await userEvent.click(picker);
|
||||||
|
await userEvent.type(input, '2023/05/01');
|
||||||
|
await userEvent.click(document.querySelector('[title="2023-05-01"]') as HTMLElement);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input).toHaveValue('2023/05/01');
|
||||||
|
// Read pretty
|
||||||
|
expect(screen.getByText('2023/05/01', { selector: '.ant-description-date-picker' })).toBeInTheDocument();
|
||||||
|
|
||||||
|
if (!process.env.GITHUB_ACTIONS) {
|
||||||
|
// Value
|
||||||
|
// 当 gmt 为 false 时是按照客户端本地时区进行计算的,但是这里的测试环境是 UTC+8,所以会有 8 小时的误差
|
||||||
|
expect(screen.getByText('2023-04-30T16:00:00.000Z')).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('showTime=false,gmt=true,utc=true & not input', async () => {
|
||||||
|
const currentDateString = new Date().toISOString().split('T')[0];
|
||||||
|
const { container } = render(<App9 />);
|
||||||
|
const picker = container.querySelector('.ant-picker') as HTMLElement;
|
||||||
|
|
||||||
|
await userEvent.click(picker);
|
||||||
|
const btn = document.querySelector(`[title="${currentDateString}"]`);
|
||||||
|
expect(btn).toBeInTheDocument();
|
||||||
|
await userEvent.click(btn as HTMLElement);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
// Read pretty
|
||||||
|
expect(
|
||||||
|
screen.getByText(currentDateString.replace(/-/g, '/'), { selector: '.ant-description-date-picker' }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Value
|
||||||
|
expect(screen.getByText(`${currentDateString}T00:00:00.000Z`)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* title: DatePicker
|
||||||
|
*/
|
||||||
|
import { FormItem } from '@formily/antd';
|
||||||
|
import { DatePicker, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
input: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Editable`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
dateFormat: 'YYYY/MM/DD',
|
||||||
|
showTime: false,
|
||||||
|
gmt: false,
|
||||||
|
utc: true,
|
||||||
|
},
|
||||||
|
'x-reactions': {
|
||||||
|
target: '*(read1,read2)',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
value: '{{$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read1: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Read pretty`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
dateFormat: 'YYYY/MM/DD',
|
||||||
|
showTime: false,
|
||||||
|
gmt: false,
|
||||||
|
utc: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read2: {
|
||||||
|
type: 'string',
|
||||||
|
title: `Value`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<SchemaComponentProvider components={{ Input, DatePicker, FormItem }}>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* title: DatePicker
|
||||||
|
*/
|
||||||
|
import { FormItem } from '@formily/antd';
|
||||||
|
import { DatePicker, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
input: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Editable`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
dateFormat: 'YYYY/MM/DD',
|
||||||
|
showTime: false,
|
||||||
|
gmt: true,
|
||||||
|
utc: true,
|
||||||
|
},
|
||||||
|
'x-reactions': {
|
||||||
|
target: '*(read1,read2)',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
value: '{{$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read1: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Read pretty`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
dateFormat: 'YYYY/MM/DD',
|
||||||
|
showTime: false,
|
||||||
|
gmt: true,
|
||||||
|
utc: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read2: {
|
||||||
|
type: 'string',
|
||||||
|
title: `Value`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<SchemaComponentProvider components={{ Input, DatePicker, FormItem }}>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* title: DatePicker
|
||||||
|
*/
|
||||||
|
import { FormItem } from '@formily/antd';
|
||||||
|
import { DatePicker, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
input: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Editable`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
dateFormat: 'YYYY/MM/DD',
|
||||||
|
showTime: false,
|
||||||
|
gmt: false,
|
||||||
|
utc: true,
|
||||||
|
},
|
||||||
|
'x-reactions': {
|
||||||
|
target: '*(read1,read2)',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
value: '{{$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read1: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Read pretty`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
dateFormat: 'YYYY/MM/DD',
|
||||||
|
showTime: false,
|
||||||
|
gmt: false,
|
||||||
|
utc: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read2: {
|
||||||
|
type: 'string',
|
||||||
|
title: `Value`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<SchemaComponentProvider components={{ Input, DatePicker, FormItem }}>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* title: DatePicker
|
||||||
|
*/
|
||||||
|
import { FormItem } from '@formily/antd';
|
||||||
|
import { DatePicker, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
input: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Editable`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
dateFormat: 'YYYY/MM/DD',
|
||||||
|
showTime: false,
|
||||||
|
gmt: true,
|
||||||
|
utc: true,
|
||||||
|
},
|
||||||
|
'x-reactions': {
|
||||||
|
target: '*(read1,read2)',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
value: '{{$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read1: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Read pretty`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
dateFormat: 'YYYY/MM/DD',
|
||||||
|
showTime: false,
|
||||||
|
gmt: true,
|
||||||
|
utc: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read2: {
|
||||||
|
type: 'string',
|
||||||
|
title: `Value`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<SchemaComponentProvider components={{ Input, DatePicker, FormItem }}>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -16,6 +16,16 @@ group:
|
|||||||
|
|
||||||
<code src="./demos/demo2.tsx"></code>
|
<code src="./demos/demo2.tsx"></code>
|
||||||
|
|
||||||
|
### DatePicker (showTime=false,gmt=true,utc=true)
|
||||||
|
|
||||||
|
<code src="./demos/demo7.tsx"></code>
|
||||||
|
|
||||||
|
|
||||||
|
### DatePicker (showTime=false,gmt=false,utc=true)
|
||||||
|
|
||||||
|
<code src="./demos/demo8.tsx"></code>
|
||||||
|
|
||||||
|
|
||||||
### DatePicker (non-UTC)
|
### DatePicker (non-UTC)
|
||||||
|
|
||||||
<code src="./demos/demo3.tsx"></code>
|
<code src="./demos/demo3.tsx"></code>
|
||||||
@ -32,6 +42,7 @@ group:
|
|||||||
|
|
||||||
<code src="./demos/demo6.tsx"></code>
|
<code src="./demos/demo6.tsx"></code>
|
||||||
|
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
基于 antd 的 [DatePicker](https://ant.design/components/date-picker/#API),新增了以下扩展属性,用于支持 NocoBase 的日期字段设置。
|
基于 antd 的 [DatePicker](https://ant.design/components/date-picker/#API),新增了以下扩展属性,用于支持 NocoBase 的日期字段设置。
|
||||||
|
@ -73,6 +73,9 @@ export const mapDatePicker = function () {
|
|||||||
value: str2moment(props.value, props),
|
value: str2moment(props.value, props),
|
||||||
onChange: (value: moment.Moment) => {
|
onChange: (value: moment.Moment) => {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
|
if (!props.showTime) {
|
||||||
|
value.startOf('day');
|
||||||
|
}
|
||||||
onChange(moment2str(value, props));
|
onChange(moment2str(value, props));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -9,6 +9,9 @@ import 'jsdom-worker';
|
|||||||
import { vi } from 'vitest';
|
import { vi } from 'vitest';
|
||||||
import '../packages/core/client/src/i18n';
|
import '../packages/core/client/src/i18n';
|
||||||
|
|
||||||
|
// 设置 node 环境下的默认时区为中国标准时间, 即东八区
|
||||||
|
process.env.TZ = 'Asia/Shanghai';
|
||||||
|
|
||||||
// 解决 TypeError: window.matchMedia is not a function
|
// 解决 TypeError: window.matchMedia is not a function
|
||||||
// 参见: https://github.com/vitest-dev/vitest/issues/821#issuecomment-1046954558
|
// 参见: https://github.com/vitest-dev/vitest/issues/821#issuecomment-1046954558
|
||||||
Object.defineProperty(window, 'matchMedia', {
|
Object.defineProperty(window, 'matchMedia', {
|
||||||
|
Loading…
Reference in New Issue
Block a user