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:
被雨水过滤的空气-Rairn 2023-07-04 15:30:08 +08:00 committed by GitHub
parent 4557f99949
commit ef54fb0dd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 347 additions and 18 deletions

View File

@ -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'); expect(input).toHaveValue('2023/05/01 00:00:00');
// Read pretty // Read pretty
expect(screen.getByText('2023/05/01 00:00:00', { selector: '.ant-description-date-picker' })).toBeInTheDocument(); expect(screen.getByText('2023/05/01 00:00:00', { selector: '.ant-description-date-picker' })).toBeInTheDocument();
// TODO: 本地没有错,但是 CI 上会报错,暂时注释掉 // TODO: 需要有个方法来固定测试环境的时区
if (!process.env.GITHUB_ACTIONS) {
// Value // Value
// expect(screen.getByText('2023-04-30T16:00:00.000Z')).toBeInTheDocument(); 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(startInput).toHaveValue('2023-05-01');
expect(endInput).toHaveValue('2023-05-02'); expect(endInput).toHaveValue('2023-05-02');
// Read pretty // Read pretty
expect(screen.getByText('2023-05-01~2023-05-02', { selector: '.ant-description-text' })).toBeInTheDocument(); expect(screen.getByText('2023-05-01~2023-05-02', { selector: '.ant-description-text' })).toBeInTheDocument();
// TODO: 本地没有错,但是 CI 上会报错,暂时注释掉 if (!process.env.GITHUB_ACTIONS) {
// Value // Value
// expect(screen.getByText(/2023-04-30t16:00:00\.000z ~ 2023-05-02t15:59:59\.999z/i)).toBeInTheDocument(); 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();
});
});
}); });

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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 的日期字段设置。

View File

@ -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));
} }
}, },

View File

@ -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', {