Skip to content

Commit

Permalink
Fix #9763. long string tooltip applied statically to components (#9793)
Browse files Browse the repository at this point in the history
* Fix #9763. long string tooltip applied statically to components

* Fixed tests
  • Loading branch information
offtherailz authored Dec 6, 2023
1 parent eca6b14 commit eefad42
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 102 deletions.
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

0 comments on commit eefad42

Please sign in to comment.