From 5b8c7978b32155ceb62e6327965e6bf87a75702b Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 18 Mar 2022 12:42:56 +0100 Subject: [PATCH 1/8] add mocking helpers for platform peg Signed-off-by: Kerry Archibald --- test/test-utils/index.ts | 1 + test/test-utils/platform.ts | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 test/test-utils/platform.ts diff --git a/test/test-utils/index.ts b/test/test-utils/index.ts index 7f6b69f1d71..c6e4cbd182e 100644 --- a/test/test-utils/index.ts +++ b/test/test-utils/index.ts @@ -1,5 +1,6 @@ export * from './beacon'; export * from './client'; +export * from './platform'; export * from './test-utils'; export * from './wrappers'; export * from './utilities'; diff --git a/test/test-utils/platform.ts b/test/test-utils/platform.ts new file mode 100644 index 00000000000..38063b86b98 --- /dev/null +++ b/test/test-utils/platform.ts @@ -0,0 +1,43 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MethodKeysOf, mocked, MockedObject } from "jest-mock"; +import BasePlatform from "../../src/BasePlatform" +import PlatformPeg from "../../src/PlatformPeg" + +// doesn't implement abstract +// @ts-ignore +class MockPlatform extends BasePlatform { + constructor(platformMocks: Partial, unknown>>) { + super(); + Object.assign(this, platformMocks) + } +} +/** + * Mock Platform Peg + * Creates a mock BasePlatform class + * spys on PlatformPeg.get and returns mock platform + * @returns MockPlatform instance + */ +export const mockPlatformPeg = (platformMocks: Partial, unknown>> = {}): MockedObject => { + const mockPlatform = new MockPlatform(platformMocks); + jest.spyOn(PlatformPeg, 'get').mockReturnValue(mockPlatform); + return mocked(mockPlatform); +} + +export const unmockPlatformPeg = () => { + jest.spyOn(PlatformPeg, 'get').mockRestore() +} \ No newline at end of file From ec108ce3606b55211a9076f13160b292bf563d95 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 18 Mar 2022 12:47:04 +0100 Subject: [PATCH 2/8] basic working live duration dropdown Signed-off-by: Kerry Archibald --- res/css/_components.scss | 1 + .../views/location/_LiveDurationDropdown.scss | 19 +++++ res/css/views/location/_LocationPicker.scss | 1 - .../views/location/LiveDurationDropdown.tsx | 69 +++++++++++++++++ .../views/location/LocationPicker.tsx | 14 +++- src/i18n/strings/en_EN.json | 1 + .../location/LiveDurationDropdown-test.tsx | 75 +++++++++++++++++++ test/test-utils/platform.ts | 17 +++-- 8 files changed, 188 insertions(+), 9 deletions(-) create mode 100644 res/css/components/views/location/_LiveDurationDropdown.scss create mode 100644 src/components/views/location/LiveDurationDropdown.tsx create mode 100644 test/components/views/location/LiveDurationDropdown-test.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 4865a8be158..89df41ca08c 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -5,6 +5,7 @@ @import "./_font-weights.scss"; @import "./_spacing.scss"; @import "./components/views/beacon/_LeftPanelLiveShareWarning.scss"; +@import "./components/views/location/_LiveDurationDropdown.scss"; @import "./components/views/location/_LocationShareMenu.scss"; @import "./components/views/location/_MapError.scss"; @import "./components/views/location/_ShareDialogButtons.scss"; diff --git a/res/css/components/views/location/_LiveDurationDropdown.scss b/res/css/components/views/location/_LiveDurationDropdown.scss new file mode 100644 index 00000000000..9b0e39a07c6 --- /dev/null +++ b/res/css/components/views/location/_LiveDurationDropdown.scss @@ -0,0 +1,19 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_LiveDurationDropdown { + margin-bottom: $spacing-16; +} diff --git a/res/css/views/location/_LocationPicker.scss b/res/css/views/location/_LocationPicker.scss index 0b2555abf6a..5a8e75872fa 100644 --- a/res/css/views/location/_LocationPicker.scss +++ b/res/css/views/location/_LocationPicker.scss @@ -19,7 +19,6 @@ limitations under the License. height: 100%; position: relative; - overflow: hidden; // when there are errors loading the map // the canvas is still inserted diff --git a/src/components/views/location/LiveDurationDropdown.tsx b/src/components/views/location/LiveDurationDropdown.tsx new file mode 100644 index 00000000000..537f8ca7f38 --- /dev/null +++ b/src/components/views/location/LiveDurationDropdown.tsx @@ -0,0 +1,69 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; + +import { _t } from '../../../languageHandler'; +import Dropdown from '../elements/Dropdown'; + +const DURATION_MS = { + fifteenMins: 900000, + oneHour: 3600000, + eightHours: 28800000, +}; + +export const DEFAULT_DURATION_MS = DURATION_MS.fifteenMins; + +interface Props { + timeout: number; + onChange: (timeout: number) => void; +} + +const getLabel = (durationMs: number) => { + return _t('Share for %(duration)s', { duration: durationMs }); +}; + +const LiveDurationDropdown: React.FC = ({ timeout, onChange }) => { + const options = Object.values(DURATION_MS).map((duration) => + ({ key: duration.toString(), duration, label: getLabel(duration) }), + ); + + // timeout is not one of our default values + if (!Object.values(DURATION_MS).includes(timeout)) { + options.push({ + key: timeout.toString(), duration: timeout, label: getLabel(timeout), + }); + } + + const onOptionChange = (key: string) => { + onChange(+key); + }; + + return + { options.map(({ key, label }) => +
{ label }
, + ) } +
; +}; + +export default LiveDurationDropdown; diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index 40030637950..794c95b98ad 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -35,6 +35,7 @@ import { LocationShareError } from './LocationShareErrors'; import AccessibleButton from '../elements/AccessibleButton'; import { MapError } from './MapError'; import { getUserNameColorClass } from '../../../utils/FormattingUtils'; +import LiveDurationDropdown, { DEFAULT_DURATION_MS } from './LiveDurationDropdown'; export interface ILocationPickerProps { sender: RoomMember; shareType: LocationShareType; @@ -50,6 +51,7 @@ interface IPosition { timestamp: number; } interface IState { + timeout: number; position?: IPosition; error?: LocationShareError; } @@ -70,6 +72,7 @@ class LocationPicker extends React.Component { this.state = { position: undefined, + timeout: DEFAULT_DURATION_MS, error: undefined, }; } @@ -206,6 +209,10 @@ class LocationPicker extends React.Component { } }; + private onTimeoutChange = (timeout: number): void => { + this.setState({ timeout }); + }; + private onOk = () => { const position = this.state.position; @@ -235,7 +242,12 @@ class LocationPicker extends React.Component { }
- + { this.props.shareType === LocationShareType.Live && + + } ', () => { + const defaultProps = { + timeout: DEFAULT_DURATION_MS, + onChange: jest.fn(), + }; + const getComponent = (props = {}) => + mount(); + + const getOption = (wrapper, timeout) => findById(wrapper, `live-duration__${timeout}`).at(0); + const getSelectedOption = (wrapper) => findById(wrapper, 'live-duration_value'); + const openDropdown = (wrapper) => act(() => { + wrapper.find('[role="button"]').at(0).simulate('click'); + wrapper.setProps({}); + }); + + it('renders timeout as selected option', () => { + const wrapper = getComponent(); + expect(getSelectedOption(wrapper).text()).toEqual('Share for 900000'); + }); + + it('renders non-default timeout as selected option', () => { + const timeout = 1234567; + const wrapper = getComponent({ timeout }); + expect(getSelectedOption(wrapper).text()).toEqual(`Share for 1234567`); + }); + + it('renders a dropdown option for a non-default timeout value', () => { + const timeout = 1234567; + const wrapper = getComponent({ timeout }); + openDropdown(wrapper); + expect(getOption(wrapper, timeout).text()).toEqual(`Share for 1234567`); + }); + + it('updates value on option selection', () => { + const onChange = jest.fn(); + const wrapper = getComponent({ onChange }); + + const ONE_HOUR = 3600000; + + openDropdown(wrapper); + + act(() => { + getOption(wrapper, ONE_HOUR).simulate('click'); + }); + + expect(onChange).toHaveBeenCalledWith(ONE_HOUR); + }); +}); diff --git a/test/test-utils/platform.ts b/test/test-utils/platform.ts index 38063b86b98..7b82a67a564 100644 --- a/test/test-utils/platform.ts +++ b/test/test-utils/platform.ts @@ -15,15 +15,16 @@ limitations under the License. */ import { MethodKeysOf, mocked, MockedObject } from "jest-mock"; -import BasePlatform from "../../src/BasePlatform" -import PlatformPeg from "../../src/PlatformPeg" + +import BasePlatform from "../../src/BasePlatform"; +import PlatformPeg from "../../src/PlatformPeg"; // doesn't implement abstract // @ts-ignore class MockPlatform extends BasePlatform { constructor(platformMocks: Partial, unknown>>) { super(); - Object.assign(this, platformMocks) + Object.assign(this, platformMocks); } } /** @@ -32,12 +33,14 @@ class MockPlatform extends BasePlatform { * spys on PlatformPeg.get and returns mock platform * @returns MockPlatform instance */ -export const mockPlatformPeg = (platformMocks: Partial, unknown>> = {}): MockedObject => { +export const mockPlatformPeg = ( + platformMocks: Partial, unknown>> = {}, +): MockedObject => { const mockPlatform = new MockPlatform(platformMocks); jest.spyOn(PlatformPeg, 'get').mockReturnValue(mockPlatform); return mocked(mockPlatform); -} +}; export const unmockPlatformPeg = () => { - jest.spyOn(PlatformPeg, 'get').mockRestore() -} \ No newline at end of file + jest.spyOn(PlatformPeg, 'get').mockRestore(); +}; From 2e22ce9e5d5b9bfaaf81a818dc4c73b329e7fa53 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 18 Mar 2022 15:22:16 +0100 Subject: [PATCH 3/8] add duration format utility Signed-off-by: Kerry Archibald --- src/DateUtils.ts | 22 ++++++++++++++++++++++ test/utils/DateUtils-test.ts | 24 +++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/DateUtils.ts b/src/DateUtils.ts index c9f33b2eee7..33a0c6fdf48 100644 --- a/src/DateUtils.ts +++ b/src/DateUtils.ts @@ -201,3 +201,25 @@ export function formatRelativeTime(date: Date, showTwelveHour = false): string { return relativeDate; } } + +/** + * Formats duration in ms to human readable string + * Returns value in biggest possible unit (day, hour, min, second) + * Rounds values up until unit threshold + * ie. 23:13:57 -> 23h, 24:13:57 -> 1d, 44:56:56 -> 2d + */ +const MINUTE_MS = 60000; +const HOUR_MS = MINUTE_MS * 60; +const DAY_MS = HOUR_MS * 24; +export const formatDuration = (durationMs: number): string => { + if (durationMs >= DAY_MS) { + return _t('%(value)sd', { value: Math.round(durationMs / DAY_MS) }); + } + if (durationMs >= HOUR_MS) { + return _t('%(value)sh', { value: Math.round(durationMs / HOUR_MS) }); + } + if (durationMs >= MINUTE_MS) { + return _t('%(value)sm', { value: Math.round(durationMs / MINUTE_MS) }); + } + return _t('%(value)ss', { value: Math.round(durationMs / 1000) }); +}; diff --git a/test/utils/DateUtils-test.ts b/test/utils/DateUtils-test.ts index 78b52d34179..7804fd6e9cf 100644 --- a/test/utils/DateUtils-test.ts +++ b/test/utils/DateUtils-test.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { formatSeconds, formatRelativeTime } from "../../src/DateUtils"; +import { formatSeconds, formatRelativeTime, formatDuration } from "../../src/DateUtils"; describe("formatSeconds", () => { it("correctly formats time with hours", () => { @@ -70,3 +70,25 @@ describe("formatRelativeTime", () => { expect(formatRelativeTime(date, true)).toBe("Oct 31, 2020"); }); }); + +describe('formatDuration()', () => { + type TestCase = [string, string, number]; + + const MINUTE_MS = 60000; + const HOUR_MS = MINUTE_MS * 60; + + it.each([ + ['rounds up to nearest day when more than 24h - 40 hours', '2d', 40 * HOUR_MS], + ['rounds down to nearest day when more than 24h - 26 hours', '1d', 26 * HOUR_MS], + ['24 hours', '1d', 24 * HOUR_MS], + ['rounds to nearest hour when less than 24h - 23h', '23h', 23 * HOUR_MS], + ['rounds to nearest hour when less than 24h - 6h and 10min', '6h', 6 * HOUR_MS + 10 * MINUTE_MS], + ['rounds to nearest hours when less than 24h', '2h', 2 * HOUR_MS + 124234], + ['rounds to nearest minute when less than 1h - 59 minutes', '59m', 59 * MINUTE_MS], + ['rounds to nearest minute when less than 1h - 1 minute', '1m', MINUTE_MS], + ['rounds to nearest second when less than 1min - 59 seconds', '59s', 59000], + ['rounds to 0 seconds when less than a second - 123ms', '0s', 123], + ])('%s formats to %s', (_description, expectedResult, input) => { + expect(formatDuration(input)).toEqual(expectedResult); + }); +}); From 048cdad92488abb253b7f7befac7b505b1e44785 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 18 Mar 2022 16:13:44 +0100 Subject: [PATCH 4/8] add duration dropdown to live location picker Signed-off-by: Kerry Archibald --- .../views/location/LiveDurationDropdown.tsx | 3 +- .../views/location/LocationPicker.tsx | 48 +++++++++++------ src/i18n/strings/en_EN.json | 4 ++ .../location/LiveDurationDropdown-test.tsx | 6 +-- .../views/location/LocationPicker-test.tsx | 54 ++++++++++++++++--- .../views/location/LocationShareMenu-test.tsx | 3 +- 6 files changed, 89 insertions(+), 29 deletions(-) diff --git a/src/components/views/location/LiveDurationDropdown.tsx b/src/components/views/location/LiveDurationDropdown.tsx index 537f8ca7f38..71ab1172806 100644 --- a/src/components/views/location/LiveDurationDropdown.tsx +++ b/src/components/views/location/LiveDurationDropdown.tsx @@ -16,6 +16,7 @@ limitations under the License. import React from 'react'; +import { formatDuration } from '../../../DateUtils'; import { _t } from '../../../languageHandler'; import Dropdown from '../elements/Dropdown'; @@ -33,7 +34,7 @@ interface Props { } const getLabel = (durationMs: number) => { - return _t('Share for %(duration)s', { duration: durationMs }); + return _t('Share for %(duration)s', { duration: formatDuration(durationMs) }); }; const LiveDurationDropdown: React.FC = ({ timeout, onChange }) => { diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index 794c95b98ad..ef8dae46667 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -214,9 +214,12 @@ class LocationPicker extends React.Component { }; private onOk = () => { - const position = this.state.position; + const { timeout, position } = this.state; - this.props.onChoose(position ? { uri: getGeoUri(position), timestamp: position.timestamp } : {}); + this.props.onChoose( + position ? { uri: getGeoUri(position), timestamp: position.timestamp, timeout } : { + timeout, + }); this.props.onFinished(); }; @@ -265,21 +268,32 @@ class LocationPicker extends React.Component { `mx_MLocationBody_marker-${this.props.shareType}`, userColorClass, )} - id={this.getMarkerId()}> -
- { isSharingOwnLocation(this.props.shareType) ? - - : - } -
-
+ id={this.getMarkerId()} + > + { /* + maplibregl hijacks the div above to style the marker + it must be in the dom when the map is initialised + and keep a consistent class + we want to hide the marker until it is set in the case of pin drop + so hide the internal visible elements + */ } + + {!!this.marker && <> +
+ {isSharingOwnLocation(this.props.shareType) ? + + : + } +
+
+ }
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6542ad5c954..f464cabc87a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -106,6 +106,10 @@ "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", "%(date)s at %(time)s": "%(date)s at %(time)s", + "%(value)sd": "%(value)sd", + "%(value)sh": "%(value)sh", + "%(value)sm": "%(value)sm", + "%(value)ss": "%(value)ss", "Who would you like to add to this community?": "Who would you like to add to this community?", "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID", "Invite new community members": "Invite new community members", diff --git a/test/components/views/location/LiveDurationDropdown-test.tsx b/test/components/views/location/LiveDurationDropdown-test.tsx index d16b67bda94..864639db065 100644 --- a/test/components/views/location/LiveDurationDropdown-test.tsx +++ b/test/components/views/location/LiveDurationDropdown-test.tsx @@ -42,20 +42,20 @@ describe('', () => { it('renders timeout as selected option', () => { const wrapper = getComponent(); - expect(getSelectedOption(wrapper).text()).toEqual('Share for 900000'); + expect(getSelectedOption(wrapper).text()).toEqual('Share for 15m'); }); it('renders non-default timeout as selected option', () => { const timeout = 1234567; const wrapper = getComponent({ timeout }); - expect(getSelectedOption(wrapper).text()).toEqual(`Share for 1234567`); + expect(getSelectedOption(wrapper).text()).toEqual(`Share for 21m`); }); it('renders a dropdown option for a non-default timeout value', () => { const timeout = 1234567; const wrapper = getComponent({ timeout }); openDropdown(wrapper); - expect(getOption(wrapper, timeout).text()).toEqual(`Share for 1234567`); + expect(getOption(wrapper, timeout).text()).toEqual(`Share for 21m`); }); it('updates value on option selection', () => { diff --git a/test/components/views/location/LocationPicker-test.tsx b/test/components/views/location/LocationPicker-test.tsx index ca056d87a67..93845dc0d35 100644 --- a/test/components/views/location/LocationPicker-test.tsx +++ b/test/components/views/location/LocationPicker-test.tsx @@ -28,7 +28,7 @@ import LocationPicker, { getGeoUri } from "../../../../src/components/views/loca import { LocationShareType } from "../../../../src/components/views/location/shareLocation"; import MatrixClientContext from '../../../../src/contexts/MatrixClientContext'; import { MatrixClientPeg } from '../../../../src/MatrixClientPeg'; -import { findByTestId } from '../../../test-utils'; +import { findById, findByTestId, mockPlatformPeg } from '../../../test-utils'; import { findMapStyleUrl } from '../../../../src/components/views/location/findMapStyleUrl'; import { LocationShareError } from '../../../../src/components/views/location/LocationShareErrors'; @@ -36,6 +36,9 @@ jest.mock('../../../../src/components/views/location/findMapStyleUrl', () => ({ findMapStyleUrl: jest.fn().mockReturnValue('tileserver.com'), })); +// dropdown uses this +mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); + describe("LocationPicker", () => { describe("getGeoUri", () => { it("Renders a URI with only lat and lon", () => { @@ -108,6 +111,7 @@ describe("LocationPicker", () => { }; const mockClient = { on: jest.fn(), + removeListener: jest.fn(), off: jest.fn(), isGuest: jest.fn(), getClientWellKnown: jest.fn(), @@ -142,6 +146,7 @@ describe("LocationPicker", () => { jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient as unknown as MatrixClient); jest.clearAllMocks(); mocked(mockMap).addControl.mockReset(); + console.log('MM', maplibregl.Marker.mock.calls); mocked(findMapStyleUrl).mockReturnValue('tileserver.com'); }); @@ -206,7 +211,7 @@ describe("LocationPicker", () => { }); const testUserLocationShareTypes = (shareType: LocationShareType.Own | LocationShareType.Live) => { - describe(`for ${shareType} location share type`, () => { + describe('user location behaviours', () => { it('closes and displays error when geolocation errors', () => { // suppress expected error log jest.spyOn(logger, 'error').mockImplementation(() => { }); @@ -263,8 +268,42 @@ describe("LocationPicker", () => { }); }; - testUserLocationShareTypes(LocationShareType.Own); - testUserLocationShareTypes(LocationShareType.Live); + describe('for Own location share type', () => { + testUserLocationShareTypes(LocationShareType.Own); + }); + + describe('for Live location share type', () => { + const shareType = LocationShareType.Live; + testUserLocationShareTypes(shareType); + + const getOption = (wrapper, timeout) => findById(wrapper, `live-duration__${timeout}`).at(0); + const getDropdown = wrapper => findByTestId(wrapper, 'live-duration-dropdown'); + const getSelectedOption = (wrapper) => findById(wrapper, 'live-duration_value'); + + const openDropdown = (wrapper) => act(() => { + const dropdown = getDropdown(wrapper); + dropdown.find('[role="button"]').at(0).simulate('click'); + wrapper.setProps({}); + }); + + it('renders live duration dropdown with default option', () => { + const wrapper = getComponent({ shareType }); + expect(getSelectedOption(getDropdown(wrapper)).text()).toEqual('Share for 15m'); + }); + + it('updates selected duration', () => { + const wrapper = getComponent({ shareType }); + + openDropdown(wrapper); + const dropdown = getDropdown(wrapper); + act(() => { + getOption(dropdown, 3600000).simulate('click'); + }); + + // value updated + expect(getSelectedOption(getDropdown(wrapper)).text()).toEqual('Share for 1h'); + }); + }); describe('for Pin drop location share type', () => { const shareType = LocationShareType.Pin; @@ -298,14 +337,15 @@ describe("LocationPicker", () => { }); it('does not set position on geolocate event', () => { - getComponent({ shareType }); + mocked(maplibregl.Marker).mockClear(); + const wrapper = getComponent({ shareType }); act(() => { // @ts-ignore mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition); }); - // marker added - expect(maplibregl.Marker).not.toHaveBeenCalled(); + // marker not added + expect(wrapper.find('.mx_MLocationBody_markerBorder').length).toBeFalsy(); }); it('sets position on click event', () => { diff --git a/test/components/views/location/LocationShareMenu-test.tsx b/test/components/views/location/LocationShareMenu-test.tsx index 1afb28f1833..1fa19201a15 100644 --- a/test/components/views/location/LocationShareMenu-test.tsx +++ b/test/components/views/location/LocationShareMenu-test.tsx @@ -33,6 +33,7 @@ import { MatrixClientPeg } from '../../../../src/MatrixClientPeg'; import { LocationShareType } from '../../../../src/components/views/location/shareLocation'; import { findByTagAndTestId, flushPromises } from '../../../test-utils'; import Modal from '../../../../src/Modal'; +import { DEFAULT_DURATION_MS } from '../../../../src/components/views/location/LiveDurationDropdown'; jest.mock('../../../../src/components/views/location/findMapStyleUrl', () => ({ findMapStyleUrl: jest.fn().mockReturnValue('test'), @@ -316,7 +317,7 @@ describe('', () => { expect(eventContent).toEqual(expect.objectContaining({ [M_BEACON_INFO.name]: { // default timeout - timeout: 300000, + timeout: DEFAULT_DURATION_MS, description: `Ernie's live location`, live: true, }, From bc104a058db6c91f5c2bdc39917286e1c5c19e4b Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 18 Mar 2022 16:23:44 +0100 Subject: [PATCH 5/8] adjust style to allow overflow and variable height chin Signed-off-by: Kerry Archibald --- res/css/views/location/_LocationPicker.scss | 18 ++++++++++-------- .../views/location/LocationPicker.tsx | 8 ++++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/res/css/views/location/_LocationPicker.scss b/res/css/views/location/_LocationPicker.scss index 5a8e75872fa..a60a83be2a1 100644 --- a/res/css/views/location/_LocationPicker.scss +++ b/res/css/views/location/_LocationPicker.scss @@ -19,6 +19,8 @@ limitations under the License. height: 100%; position: relative; + display: flex; + flex-direction: column; // when there are errors loading the map // the canvas is still inserted @@ -31,8 +33,10 @@ limitations under the License. } #mx_LocationPicker_map { - height: 100%; - border-radius: 8px; + // height: 100%; + border-top-left-radius: inherit; + border-top-right-radius: inherit; + flex: 1; .maplibregl-ctrl.maplibregl-ctrl-group, .maplibregl-ctrl.maplibregl-ctrl-attrib { @@ -45,10 +49,6 @@ limitations under the License. margin-top: 50px; } - .maplibregl-ctrl-bottom-right { - bottom: 80px; - } - .maplibregl-user-location-accuracy-circle { display: none; } @@ -92,8 +92,7 @@ limitations under the License. } .mx_LocationPicker_footer { - position: absolute; - bottom: 0px; + flex: 0; width: 100%; box-sizing: border-box; padding: $spacing-16; @@ -101,6 +100,9 @@ limitations under the License. flex-direction: column; justify-content: stretch; + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; + background-color: $header-panel-bg-color; } } diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index ef8dae46667..28b472fcdbb 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -268,7 +268,7 @@ class LocationPicker extends React.Component { `mx_MLocationBody_marker-${this.props.shareType}`, userColorClass, )} - id={this.getMarkerId()} + id={this.getMarkerId()} > { /* maplibregl hijacks the div above to style the marker @@ -278,9 +278,9 @@ class LocationPicker extends React.Component { so hide the internal visible elements */ } - {!!this.marker && <> + { !!this.marker && <>
- {isSharingOwnLocation(this.props.shareType) ? + { isSharingOwnLocation(this.props.shareType) ? {
- } + }
); From 02196d3daaecb7d715b1f454ec3e8e568e7e2f9d Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 18 Mar 2022 16:27:26 +0100 Subject: [PATCH 6/8] tidy comments Signed-off-by: Kerry Archibald --- res/css/views/location/_LocationPicker.scss | 1 - src/components/views/location/LiveDurationDropdown.tsx | 2 ++ test/components/views/location/LocationPicker-test.tsx | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/location/_LocationPicker.scss b/res/css/views/location/_LocationPicker.scss index a60a83be2a1..91c3e02bf83 100644 --- a/res/css/views/location/_LocationPicker.scss +++ b/res/css/views/location/_LocationPicker.scss @@ -33,7 +33,6 @@ limitations under the License. } #mx_LocationPicker_map { - // height: 100%; border-top-left-radius: inherit; border-top-right-radius: inherit; flex: 1; diff --git a/src/components/views/location/LiveDurationDropdown.tsx b/src/components/views/location/LiveDurationDropdown.tsx index 71ab1172806..3c2df61514a 100644 --- a/src/components/views/location/LiveDurationDropdown.tsx +++ b/src/components/views/location/LiveDurationDropdown.tsx @@ -43,6 +43,7 @@ const LiveDurationDropdown: React.FC = ({ timeout, onChange }) => { ); // timeout is not one of our default values + // eg it was set by another client if (!Object.values(DURATION_MS).includes(timeout)) { options.push({ key: timeout.toString(), duration: timeout, label: getLabel(timeout), @@ -50,6 +51,7 @@ const LiveDurationDropdown: React.FC = ({ timeout, onChange }) => { } const onOptionChange = (key: string) => { + // stringified value back to number onChange(+key); }; diff --git a/test/components/views/location/LocationPicker-test.tsx b/test/components/views/location/LocationPicker-test.tsx index 93845dc0d35..914821a53de 100644 --- a/test/components/views/location/LocationPicker-test.tsx +++ b/test/components/views/location/LocationPicker-test.tsx @@ -146,7 +146,6 @@ describe("LocationPicker", () => { jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient as unknown as MatrixClient); jest.clearAllMocks(); mocked(mockMap).addControl.mockReset(); - console.log('MM', maplibregl.Marker.mock.calls); mocked(findMapStyleUrl).mockReturnValue('tileserver.com'); }); From b0de2b289c5bd4b76154e3ebab2451934c41b1b1 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 18 Mar 2022 17:56:42 +0100 Subject: [PATCH 7/8] arrow fn change Signed-off-by: Kerry Archibald --- src/DateUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DateUtils.ts b/src/DateUtils.ts index 33a0c6fdf48..d8a92afafa9 100644 --- a/src/DateUtils.ts +++ b/src/DateUtils.ts @@ -211,7 +211,7 @@ export function formatRelativeTime(date: Date, showTwelveHour = false): string { const MINUTE_MS = 60000; const HOUR_MS = MINUTE_MS * 60; const DAY_MS = HOUR_MS * 24; -export const formatDuration = (durationMs: number): string => { +export function formatDuration(durationMs: number): string { if (durationMs >= DAY_MS) { return _t('%(value)sd', { value: Math.round(durationMs / DAY_MS) }); } From 93c7b806a73c140f38886b235b8f5f53bf5ec37f Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 21 Mar 2022 09:01:06 +0100 Subject: [PATCH 8/8] lint Signed-off-by: Kerry Archibald --- src/DateUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DateUtils.ts b/src/DateUtils.ts index d8a92afafa9..20227354f02 100644 --- a/src/DateUtils.ts +++ b/src/DateUtils.ts @@ -222,4 +222,4 @@ export function formatDuration(durationMs: number): string { return _t('%(value)sm', { value: Math.round(durationMs / MINUTE_MS) }); } return _t('%(value)ss', { value: Math.round(durationMs / 1000) }); -}; +}