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

Fix #9763. long string tooltip applied statically to components #9793

Merged
merged 3 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -6,72 +6,120 @@
* LICENSE file in the root directory of this source tree.
*/
import React from "react";
import ReactDOM from "react-dom";
import ReactDOM, {unmountComponentAtNode} from "react-dom";
import expect from "expect";
import { act } from "react-dom/test-utils";

import NumberFormat from "../../../../I18N/Number";
import { getFormatter, registerFormatter, unregisterFormatter } from "../index";
import {
getFormatter,
registerFormatter,
unregisterFormatter
} from "../index";
let container = null;

describe("Tests for the formatter functions", () => {
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});

afterEach(() => {
// cleanup on exiting
unmountComponentAtNode(container);
container.remove();
container = null;
});
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);
act(() => {
ReactDOM.render(formatter({ value: true }), container);
});
expect(container.textContent).toBe("true");

act(() => {
ReactDOM.render(formatter({ value: false }), container);
});
expect(container.textContent).toBe("false");
});
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);
const Formatter = getFormatter({ localType: "string" });
act(() => {
ReactDOM.render(<Formatter value={value} />, container);
});
expect(container.textContent).toBe(value);
});
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);
const Formatter = getFormatter({ localType: "number" });
act(() => {
ReactDOM.render(<Formatter />, container);
});
expect(container.textContent).toBe("");
act(() => {
ReactDOM.render(<Formatter value={44.3333434353535} />, container);
});
expect(container.textContent).toBe("44.3333434353535");
act(() => {
ReactDOM.render(<Formatter value={null} />, container);
});
expect(container.textContent).toBe("");
act(() => {
ReactDOM.render(<Formatter value={undefined} />, container);
});
expect(container.textContent).toBe("");
act(() => {
ReactDOM.render(<Formatter value={0} />, container);
});
expect(container.textContent).toBe("0");
});
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);
const Formatter = getFormatter({ localType: "int" });
act(() => {
ReactDOM.render(<Formatter />, container);
});
expect(container.textContent).toBe("");
act(() => {
ReactDOM.render(<Formatter value={2455567} />, container);
});
expect(container.textContent).toBe("2455567");
act(() => {
ReactDOM.render(<Formatter value={null} />, container);
});
expect(container.textContent).toBe("");
act(() => {
ReactDOM.render(<Formatter value={undefined} />, container);
});
expect(container.textContent).toBe("");
act(() => {
ReactDOM.render(<Formatter value={0} />, container);
});
expect(container.textContent).toBe("0");
});

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);
const Formatter = getFormatter({ localType: "Geometry" });
act(() => {
ReactDOM.render(<Formatter />, container);
});
expect(container.textContent).toBe("");
act(() => {
ReactDOM.render(<Formatter value={{
properties: {},
geometry: { type: "Point", coordinates: [1, 2] }
}} />, container);
});
expect(container.textContent).toBe("");
act(() => {
ReactDOM.render(<Formatter value={null} />, container);
});
expect(container.textContent).toBe("");
act(() => {
ReactDOM.render(<Formatter value={undefined} />, container);
});
expect(container.textContent).toBe("");
});

describe("test featureGridFormatter", () => {
beforeEach((done) => {
document.body.innerHTML = '<div id="container"></div>';
Expand Down Expand Up @@ -132,51 +180,55 @@ describe("Tests for the formatter functions", () => {
"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
});
expect(typeof dateFormatter).toBe("function");
expect(dateFormatter()).toBe(null);
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(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)
const DateFormatter = getFormatter({ localType: "date" }, undefined, { dateFormats });
const DateTimeFormatter = getFormatter({ localType: "date-time" }, undefined, { dateFormats });
const TimeFormatter = getFormatter({ localType: "time" }, undefined, { dateFormats });

act(() => {
ReactDOM.render(<DateFormatter value="2015-02-01T12:45:00Z" />, container);
});
expect(container.textContent).toBe("2015");

act(() => {
ReactDOM.render(<DateTimeFormatter value="2015-02-01Z" />, container);
});
expect(container.textContent).toBe("2015 01");

act(() => {
ReactDOM.render(<TimeFormatter value="12:45:00Z" />, container);
});
expect(container.textContent).toBe("12:45");

act(() => {
ReactDOM.render(<TimeFormatter value="1970-01-01T02:30:00Z" />, container);
});
expect(container.textContent).toBe("02:30");
});

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');
const DateTimeWithZFormatter = getFormatter({ localType: "date-time" }, undefined, { dateFormats });

act(() => {
ReactDOM.render(<DateTimeWithZFormatter value="2015-02-01Z" />, container);
});
expect(container.textContent).toBe("2015-02-01Z");

act(() => {
ReactDOM.render(<DateTimeWithZFormatter value="2015-02-01" />, container);
});
expect(container.textContent).toBe("2015-02-01Z");

act(() => {
ReactDOM.render(<DateTimeWithZFormatter value="2015/02/01 03:20:10" />, container);
});
expect(container.textContent).toBe("2015-02-01Z");

act(() => {
ReactDOM.render(<DateTimeWithZFormatter value="2015-02-01T12:45:00Z" />, container);
});
expect(container.textContent).toBe("2015-02-01Z");
});
});
23 changes: 15 additions & 8 deletions web/client/components/data/featuregrid/formatters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,33 @@ import reactStringReplace from "react-string-replace";
import moment from "moment";

import NumberFormat from '../../../I18N/Number';
import { handleLongTextEnhancer } from '../../../misc/enhancers/handleLongTextEnhancer';

import { dateFormats as defaultDateFormats } from "../../../../utils/FeatureGridUtils";

const BooleanFormatter = ({value} = {}) => !isNil(value) ? <span>{value.toString()}</span> : null;
export const BooleanFormatter = ({value} = {}) => !isNil(value) ? <span>{value.toString()}</span> : null;
export const StringFormatter = ({value} = {}) => !isNil(value) ? reactStringReplace(value, /(https?:\/\/\S+)/g, (match, i) => (
<a key={match + i} href={match} target={"_blank"}>{match}</a>
)) : null;
const NumberFormatter = ({value} = {}) => !isNil(value) ? <NumberFormat value={value} numberParams={{maximumFractionDigits: 17}}/> : null;
export const NumberFormatter = ({value} = {}) => !isNil(value) ? <NumberFormat value={value} numberParams={{maximumFractionDigits: 17}}/> : null;
const DEFAULT_DATE_PART = "1970-01-01";
const DATE_INPUT_FORAMAT = "YYYY-MM-DD[Z]";
const dateTimeFormatter = ({value, format, type}) => {
const DATE_INPUT_FORMAT = "YYYY-MM-DD[Z]";
export const DateTimeFormatter = ({value, format, type}) => {
return !isNil(value)
? moment.utc(value).isValid() // geoserver sometimes returns UTC for time.
? moment.utc(value).format(format)
: type === 'time'
? moment(`${DEFAULT_DATE_PART}T${value}`).utc().format(format) // time format append default date part
: type === "date" && value?.toLowerCase()?.endsWith("z") // in case: date format and value ends with z
? moment(value, DATE_INPUT_FORAMAT).format(format)
? moment(value, DATE_INPUT_FORMAT).format(format)
: moment(value).format(format)
: null;
};
// add long text handling to formatters of string, date and number
const EnhancedStringFormatter = handleLongTextEnhancer(StringFormatter);
const EnhancedNumberFormatter = handleLongTextEnhancer(NumberFormatter);
const enhancedDateTimeFormatter = handleLongTextEnhancer(DateTimeFormatter);

export const register = {};

/**
Expand Down Expand Up @@ -83,16 +90,16 @@ export const getFormatter = (desc, {featureGridFormatter} = {}, {dateFormats} =
return BooleanFormatter;
case 'int':
case 'number':
return NumberFormatter;
return EnhancedNumberFormatter;
case 'string':
return StringFormatter;
return EnhancedStringFormatter;
case 'Geometry':
return () => null;
case 'time':
case 'date':
case 'date-time':
const format = get(dateFormats, desc.localType) ?? defaultDateFormats[desc.localType];
return ({value} = {}) => dateTimeFormatter({value, format, type: desc.localType});
return ({value} = {}) => enhancedDateTimeFormatter({value, format, type: desc.localType});
default:
return null;
}
Expand Down
3 changes: 1 addition & 2 deletions web/client/utils/FeatureGridUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
} from './ogc/WFS/base';

import { applyDefaultToLocalizedString } from '../components/I18N/LocalizedString';
import { handleLongTextEnhancer } from '../components/misc/enhancers/handleLongTextEnhancer';

const getGeometryName = (describe) => get(findGeometryProperty(describe), "name");
const getPropertyName = (name, describe) => name === "geometry" ? getGeometryName(describe) : name;
Expand Down Expand Up @@ -148,7 +147,7 @@ export const featureTypeToGridColumns = (
editable,
filterable,
editor: getEditor(desc, field),
formatter: handleLongTextEnhancer(getFormatter(desc, field)),
formatter: getFormatter(desc, field),
filterRenderer: getFilterRenderer(desc, field)
};
});
Expand Down
2 changes: 0 additions & 2 deletions web/client/utils/__tests__/FeatureGridUtils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -363,8 +363,6 @@ describe('FeatureGridUtils', () => {
document.getElementById("container")
);
expect(document.getElementById("container").innerHTML).toExist();
expect(document.getElementsByTagName('span').length).toEqual(2);
expect(document.getElementsByTagName('span')[1].innerHTML).toExist();
});

});
Expand Down
Loading