diff --git a/web/client/components/data/featuregrid/formatters/__tests__/index-test.jsx b/web/client/components/data/featuregrid/formatters/__tests__/index-test.jsx index 5e0a27287e..8dd17b2033 100644 --- a/web/client/components/data/featuregrid/formatters/__tests__/index-test.jsx +++ b/web/client/components/data/featuregrid/formatters/__tests__/index-test.jsx @@ -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(, 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(, container); + }); + expect(container.textContent).toBe(""); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe("44.3333434353535"); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe(""); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe(""); + act(() => { + ReactDOM.render(, 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(, container); + }); + expect(container.textContent).toBe(""); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe("2455567"); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe(""); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe(""); + act(() => { + ReactDOM.render(, 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(, container); + }); + expect(container.textContent).toBe(""); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe(""); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe(""); + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe(""); }); + describe("test featureGridFormatter", () => { beforeEach((done) => { document.body.innerHTML = '
'; @@ -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(, container); + }); + expect(container.textContent).toBe("2015"); + + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe("2015 01"); + + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe("12:45"); + + act(() => { + ReactDOM.render(, 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(, container); + }); + expect(container.textContent).toBe("2015-02-01Z"); + + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe("2015-02-01Z"); + + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe("2015-02-01Z"); + + act(() => { + ReactDOM.render(, container); + }); + expect(container.textContent).toBe("2015-02-01Z"); }); }); diff --git a/web/client/components/data/featuregrid/formatters/index.js b/web/client/components/data/featuregrid/formatters/index.js index 5f2e4104e4..a08d393e1e 100644 --- a/web/client/components/data/featuregrid/formatters/index.js +++ b/web/client/components/data/featuregrid/formatters/index.js @@ -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) ? {value.toString()} : null; +export const BooleanFormatter = ({value} = {}) => !isNil(value) ? {value.toString()} : null; export const StringFormatter = ({value} = {}) => !isNil(value) ? reactStringReplace(value, /(https?:\/\/\S+)/g, (match, i) => ( {match} )) : null; -const NumberFormatter = ({value} = {}) => !isNil(value) ? : null; +export const NumberFormatter = ({value} = {}) => !isNil(value) ? : 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 = {}; /** @@ -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; } diff --git a/web/client/utils/FeatureGridUtils.js b/web/client/utils/FeatureGridUtils.js index 190f62cb91..16f9e3851f 100644 --- a/web/client/utils/FeatureGridUtils.js +++ b/web/client/utils/FeatureGridUtils.js @@ -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; @@ -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) }; }); diff --git a/web/client/utils/__tests__/FeatureGridUtils-test.js b/web/client/utils/__tests__/FeatureGridUtils-test.js index aafbbac3ea..677017bf1e 100644 --- a/web/client/utils/__tests__/FeatureGridUtils-test.js +++ b/web/client/utils/__tests__/FeatureGridUtils-test.js @@ -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(); }); });