Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#9275 Fix issue Invalid date on Firefox #9393

Merged
merged 3 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,118 +5,178 @@
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import expect from 'expect';
import React from "react";
import ReactDOM from "react-dom";
import expect from "expect";

import NumberFormat from '../../../../I18N/Number';
import {getFormatter, registerFormatter, unregisterFormatter} from '../index';
import NumberFormat from "../../../../I18N/Number";
import { getFormatter, registerFormatter, unregisterFormatter } from "../index";

describe('Tests for the formatter functions', () => {
it('test getFormatter for booleans', () => {
const formatter = getFormatter({localType: "boolean"});
describe("Tests for the formatter functions", () => {
it("test getFormatter for booleans", () => {
const formatter = getFormatter({ localType: "boolean" });
expect(typeof formatter).toBe("function");
expect(formatter()).toBe(null);
expect(formatter({value: true}).type).toBe("span");
expect(formatter({value: true}).props.children).toBe("true");
expect(formatter({value: false}).props.children).toBe("false");
expect(formatter({value: null})).toBe(null);
expect(formatter({value: undefined})).toBe(null);
expect(formatter({ value: true }).type).toBe("span");
expect(formatter({ value: true }).props.children).toBe("true");
expect(formatter({ value: false }).props.children).toBe("false");
expect(formatter({ value: null })).toBe(null);
expect(formatter({ value: undefined })).toBe(null);
});
it('test getFormatter for strings', () => {
const value = 'Test https://google.com with google link';
const formatter = getFormatter({localType: "string"});
it("test getFormatter for strings", () => {
const value = "Test https://google.com with google link";
const formatter = getFormatter({ localType: "string" });
expect(typeof formatter).toBe("function");
expect(formatter()).toBe(null);
expect(formatter({value: 'Test no links'})[0]).toBe('Test no links');
expect(formatter({value})[0]).toBe('Test ');
expect(formatter({value})[1].props.href).toBe('https://google.com');
expect(formatter({value})[2]).toBe(' with google link');
expect(formatter({value: null})).toBe(null);
expect(formatter({value: undefined})).toBe(null);
expect(formatter({ value: "Test no links" })[0]).toBe("Test no links");
expect(formatter({ value })[0]).toBe("Test ");
expect(formatter({ value })[1].props.href).toBe("https://google.com");
expect(formatter({ value })[2]).toBe(" with google link");
expect(formatter({ value: null })).toBe(null);
expect(formatter({ value: undefined })).toBe(null);
});
it('test getFormatter for number', () => {
const formatter = getFormatter({localType: "number"});
it("test getFormatter for number", () => {
const formatter = getFormatter({ localType: "number" });
expect(typeof formatter).toBe("function");
expect(formatter()).toBe(null);
expect(formatter({value: 44.3333434353535}).type).toBe(NumberFormat);
expect(formatter({value: 44.3333434353535}).props.value).toBe(44.3333434353535);
expect(formatter({value: null})).toBe(null);
expect(formatter({value: undefined})).toBe(null);
expect(formatter({value: 0}).props.value).toBe(0);
expect(formatter({ value: 44.3333434353535 }).type).toBe(NumberFormat);
expect(formatter({ value: 44.3333434353535 }).props.value).toBe(
44.3333434353535
);
expect(formatter({ value: null })).toBe(null);
expect(formatter({ value: undefined })).toBe(null);
expect(formatter({ value: 0 }).props.value).toBe(0);
});
it('test getFormatter for int', () => {
const formatter = getFormatter({localType: "int"});
it("test getFormatter for int", () => {
const formatter = getFormatter({ localType: "int" });
expect(typeof formatter).toBe("function");
expect(formatter()).toBe(null);
expect(formatter({value: 2455567}).type).toBe(NumberFormat);
expect(formatter({value: 2455567}).props.value).toBe(2455567);
expect(formatter({value: null})).toBe(null);
expect(formatter({value: undefined})).toBe(null);
expect(formatter({value: 0}).props.value).toBe(0);
expect(formatter({ value: 2455567 }).type).toBe(NumberFormat);
expect(formatter({ value: 2455567 }).props.value).toBe(2455567);
expect(formatter({ value: null })).toBe(null);
expect(formatter({ value: undefined })).toBe(null);
expect(formatter({ value: 0 }).props.value).toBe(0);
});
it('test getFormatter for geometry', () => {
const formatter = getFormatter({localType: "Geometry"});
it("test getFormatter for geometry", () => {
const formatter = getFormatter({ localType: "Geometry" });
expect(typeof formatter).toBe("function");
expect(formatter()).toBe(null);
expect(formatter({value: {properties: {}, geometry: {type: "Point", coordinates: [1, 2]}}})).toBe(null);
expect(formatter({value: null})).toBe(null);
expect(formatter({value: undefined})).toBe(null);
expect(
formatter({
value: {
properties: {},
geometry: { type: "Point", coordinates: [1, 2] }
}
})
).toBe(null);
expect(formatter({ value: null })).toBe(null);
expect(formatter({ value: undefined })).toBe(null);
});
describe('test featureGridFormatter', () => {
describe("test featureGridFormatter", () => {
beforeEach((done) => {
document.body.innerHTML = '<div id="container"></div>';
setTimeout(done);
});

afterEach((done) => {
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
document.body.innerHTML = '';
ReactDOM.unmountComponentAtNode(
document.getElementById("container")
);
document.body.innerHTML = "";
setTimeout(done);
});
it('base', () => {
it("base", () => {
try {
registerFormatter("test", ({config, value}) => {
registerFormatter("test", ({ config, value }) => {
expect(config).toExist();
expect(value).toBe("test");
return <div>test</div>;
});
const Formatter = getFormatter({localType: "test"}, {featureGridFormatter: {name: "test"}});
ReactDOM.render(<Formatter value="test"/>, document.getElementById("container"));
expect(document.getElementById("container").innerHTML).toBe('<div>test</div>');
const Formatter = getFormatter(
{ localType: "test" },
{ featureGridFormatter: { name: "test" } }
);
ReactDOM.render(
<Formatter value="test" />,
document.getElementById("container")
);
expect(document.getElementById("container").innerHTML).toBe(
"<div>test</div>"
);
} finally {
unregisterFormatter("test");
}
});
it('with directRender option', () => {
it("with directRender option", () => {
try {
const TEST_FUNC = () => <div>test</div>;
registerFormatter("test", TEST_FUNC);
const formatter = getFormatter({localType: "test"}, {featureGridFormatter: {name: "test", directRender: true}});
const formatter = getFormatter(
{ localType: "test" },
{
featureGridFormatter: {
name: "test",
directRender: true
}
}
);
expect(formatter).toBe(TEST_FUNC);
} finally {
unregisterFormatter("test");
}
});
});
it('test getFormatter for date / date-time / time', () => {
it("test getFormatter for date / date-time / time", () => {
const dateFormats = {
date: 'YYYY',
"date-time": 'YYYY DD',
time: 'HH:mm'
date: "YYYY",
"date-time": "YYYY DD",
time: "HH:mm"
};
const dateFormatter = getFormatter({localType: "date"}, undefined, {dateFormats});
const dateTimeFormatter = getFormatter({localType: "date-time"}, undefined, {dateFormats});
const timeFormatter = getFormatter({localType: "time"}, undefined, {dateFormats});
const dateFormatter = getFormatter({ localType: "date" }, undefined, {
dateFormats
});
const dateTimeFormatter = getFormatter(
{ localType: "date-time" },
undefined,
{ dateFormats }
);
const timeFormatter = getFormatter({ localType: "time" }, undefined, {
dateFormats
});
expect(typeof dateFormatter).toBe("function");
expect(dateFormatter()).toBe(null);
expect(dateFormatter({value: '2015-02-01T12:45:00Z'})).toBe('2015');
expect(dateFormatter({ value: "2015-02-01T12:45:00Z" })).toBe("2015");
expect(typeof dateTimeFormatter).toBe("function");
expect(dateTimeFormatter()).toBe(null);
expect(dateTimeFormatter({value: '2015-02-01Z'})).toBe('2015 01');
expect(dateTimeFormatter({ value: "2015-02-01Z" })).toBe("2015 01");
expect(typeof timeFormatter).toBe("function");
expect(timeFormatter()).toBe(null);
expect(timeFormatter({value: '12:45:00Z'})).toBe('12:45');
expect(timeFormatter({ value: '1970-01-01T02:30:00Z' })).toBe('02:30'); // still able to format time even when found a full date (sometimes GeoServer returns full date instead of time only)
expect(timeFormatter({ value: "12:45:00Z" })).toBe("12:45");
expect(timeFormatter({ value: "1970-01-01T02:30:00Z" })).toBe("02:30"); // still able to format time even when found a full date (sometimes GeoServer returns full date instead of time only)
});

it("test getFormatter for invalid date-time YYYY-MM-DD[Z]", () => {
const dateFormats = {
"date-time": "YYYY-MM-DD[Z]"
};
const dateTimeWithZFormatter = getFormatter(
{ localType: "date-time" },
undefined,
{ dateFormats }
);
expect(typeof dateTimeWithZFormatter).toBe("function");
expect(dateTimeWithZFormatter({ value: "2015-02-01Z" })).toBe(
"2015-02-01Z"
);
expect(dateTimeWithZFormatter({ value: "2015-02-01" })).toBe(
"2015-02-01Z"
);
expect(dateTimeWithZFormatter({ value: "2015/02/01" })).toBe(
"2015-02-01Z"
);
expect(dateTimeWithZFormatter({ value: "2015/02/01 03:20:10" })).toBe(
"2015-02-01Z"
);
expect(dateTimeWithZFormatter({ value: "2015-02-01T12:45:00Z"})).toBe('2015-02-01Z');
});
});
2 changes: 1 addition & 1 deletion web/client/components/data/featuregrid/formatters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const dateTimeFormatter = ({value, format, type}) => {
? moment.utc(value).format(format)
: type === 'time'
? moment(`${DEFAULT_DATE_PART}T${value}`).utc().format(format) // time format append default date part
: moment(value).format(format) // date or date-time formats
: moment(value, format).format(format) // date or date-time formats
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The input format should not be the same as the output format. This function is taking a raw date/date-time/time value from feature and convert it to configured format for visualization so we expect:

moment(value, 'YYYY-MM-DD[Z]').format(format)

We should apply the input format when the type is only date and the value ends with Z, in general is a good practice to apply the input format when known as suggested here.
The current implementation is not working if you try to replace the localConfig configuration of FeatureEditor with custom formats:

- "FeatureEditor",
+ {
+  "name": "FeatureEditor",
+  "cfg": {
+    "dateFormats": {
+      "date-time": "MM DD YYYY - HH:mm:ss",
+      "date": "MM DD YYYY",
+      "time": "HH:mm:ss"
+    }
+  }
+ },

Copy link
Contributor Author

@mahmoudadel54 mahmoudadel54 Sep 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I check momentjs documentation for this point and I saw something I think it may be helpful.
If I don't know the exact input format, I can pass all the available date formats in an array (list) and it will match the correct one from the list.
check this image:
image

image

ref: https://momentjs.com/docs/#/parsing/string-formats/

: null;
};
export const register = {};
Expand Down