mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-15 12:45:11 +08:00
Enable tsc alwaysStrict, strictBindCallApply, noImplicitThis (#9600)
* Enable tsc alwaysStrict * Enable tsc strictBindCallApply * Enable tsc noImplicitThis * Add d.ts * Improve types * Add ? * Increase coverage * Improve coverage
This commit is contained in:
parent
0b54699829
commit
8c0d202df4
@ -212,7 +212,7 @@
|
|||||||
"stylelint": "^14.9.1",
|
"stylelint": "^14.9.1",
|
||||||
"stylelint-config-standard": "^26.0.0",
|
"stylelint-config-standard": "^26.0.0",
|
||||||
"stylelint-scss": "^4.2.0",
|
"stylelint-scss": "^4.2.0",
|
||||||
"typescript": "4.7.4",
|
"typescript": "4.8.4",
|
||||||
"walk": "^2.3.14"
|
"walk": "^2.3.14"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
|
@ -24,15 +24,16 @@ export type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
|||||||
export type ComponentClass = keyof JSX.IntrinsicElements | JSXElementConstructor<any>;
|
export type ComponentClass = keyof JSX.IntrinsicElements | JSXElementConstructor<any>;
|
||||||
export type ReactAnyComponent = React.Component | React.ExoticComponent;
|
export type ReactAnyComponent = React.Component | React.ExoticComponent;
|
||||||
|
|
||||||
|
// Utility type for string dot notation for accessing nested object properties
|
||||||
// Based on https://stackoverflow.com/a/58436959
|
// Based on https://stackoverflow.com/a/58436959
|
||||||
type Join<K, P> = K extends string | number ?
|
type Join<K, P> = K extends string | number ?
|
||||||
P extends string | number ?
|
P extends string | number ?
|
||||||
`${K}${"" extends P ? "" : "."}${P}`
|
`${K}${"" extends P ? "" : "."}${P}`
|
||||||
: never : never;
|
: never : never;
|
||||||
|
|
||||||
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...0[]];
|
type Prev = [never, 0, 1, 2, 3, ...0[]];
|
||||||
|
|
||||||
export type Leaves<T, D extends number = 5> = [D] extends [never] ? never : T extends object ?
|
export type Leaves<T, D extends number = 3> = [D] extends [never] ? never : T extends object ?
|
||||||
{ [K in keyof T]-?: Join<K, Leaves<T[K], Prev[D]>> }[keyof T] : "";
|
{ [K in keyof T]-?: Join<K, Leaves<T[K], Prev[D]>> }[keyof T] : "";
|
||||||
|
|
||||||
export type RecursivePartial<T> = {
|
export type RecursivePartial<T> = {
|
||||||
|
52
src/@types/commonmark.ts
Normal file
52
src/@types/commonmark.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
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 * as commonmark from "commonmark";
|
||||||
|
|
||||||
|
declare module "commonmark" {
|
||||||
|
export type Attr = [key: string, value: string];
|
||||||
|
|
||||||
|
export interface HtmlRenderer {
|
||||||
|
// As far as @types/commonmark is concerned, these are not public, so add them
|
||||||
|
// https://github.com/commonmark/commonmark.js/blob/master/lib/render/html.js#L272-L296
|
||||||
|
text: (this: commonmark.HtmlRenderer, node: commonmark.Node) => void;
|
||||||
|
html_inline: (this: commonmark.HtmlRenderer, node: commonmark.Node) => void;
|
||||||
|
html_block: (this: commonmark.HtmlRenderer, node: commonmark.Node) => void;
|
||||||
|
// softbreak: () => void; // This one can't be correctly specified as it is wrongly defined in @types/commonmark
|
||||||
|
linebreak: (this: commonmark.HtmlRenderer) => void;
|
||||||
|
link: (this: commonmark.HtmlRenderer, node: commonmark.Node, entering: boolean) => void;
|
||||||
|
image: (this: commonmark.HtmlRenderer, node: commonmark.Node, entering: boolean) => void;
|
||||||
|
emph: (this: commonmark.HtmlRenderer, node: commonmark.Node, entering: boolean) => void;
|
||||||
|
strong: (this: commonmark.HtmlRenderer, node: commonmark.Node, entering: boolean) => void;
|
||||||
|
paragraph: (this: commonmark.HtmlRenderer, node: commonmark.Node, entering: boolean) => void;
|
||||||
|
heading: (this: commonmark.HtmlRenderer, node: commonmark.Node, entering: boolean) => void;
|
||||||
|
code: (this: commonmark.HtmlRenderer, node: commonmark.Node) => void;
|
||||||
|
code_block: (this: commonmark.HtmlRenderer, node: commonmark.Node) => void;
|
||||||
|
thematic_break: (this: commonmark.HtmlRenderer, node: commonmark.Node) => void;
|
||||||
|
block_quote: (this: commonmark.HtmlRenderer, node: commonmark.Node, entering: boolean) => void;
|
||||||
|
list: (this: commonmark.HtmlRenderer, node: commonmark.Node, entering: boolean) => void;
|
||||||
|
item: (this: commonmark.HtmlRenderer, node: commonmark.Node, entering: boolean) => void;
|
||||||
|
custom_inline: (this: commonmark.HtmlRenderer, node: commonmark.Node, entering: boolean) => void;
|
||||||
|
custom_block: (this: commonmark.HtmlRenderer, node: commonmark.Node, entering: boolean) => void;
|
||||||
|
esc: (s: string) => string;
|
||||||
|
out: (this: commonmark.HtmlRenderer, text: string) => void;
|
||||||
|
tag: (this: commonmark.HtmlRenderer, name: string, attrs?: Attr[], selfClosing?: boolean) => void;
|
||||||
|
attrs: (this: commonmark.HtmlRenderer, node: commonmark.Node) => Attr[];
|
||||||
|
// These are inherited from the base Renderer
|
||||||
|
lit: (this: commonmark.HtmlRenderer, text: string) => void;
|
||||||
|
cr: (this: commonmark.HtmlRenderer) => void;
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./@types/commonmark"; // import better types than @types/commonmark
|
||||||
import * as commonmark from 'commonmark';
|
import * as commonmark from 'commonmark';
|
||||||
import { escape } from "lodash";
|
import { escape } from "lodash";
|
||||||
import { logger } from 'matrix-js-sdk/src/logger';
|
import { logger } from 'matrix-js-sdk/src/logger';
|
||||||
@ -26,17 +27,6 @@ const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u'];
|
|||||||
// These types of node are definitely text
|
// These types of node are definitely text
|
||||||
const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document'];
|
const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document'];
|
||||||
|
|
||||||
// As far as @types/commonmark is concerned, these are not public, so add them
|
|
||||||
interface CommonmarkHtmlRendererInternal extends commonmark.HtmlRenderer {
|
|
||||||
paragraph: (node: commonmark.Node, entering: boolean) => void;
|
|
||||||
link: (node: commonmark.Node, entering: boolean) => void;
|
|
||||||
html_inline: (node: commonmark.Node) => void; // eslint-disable-line camelcase
|
|
||||||
html_block: (node: commonmark.Node) => void; // eslint-disable-line camelcase
|
|
||||||
text: (node: commonmark.Node) => void;
|
|
||||||
out: (text: string) => void;
|
|
||||||
emph: (node: commonmark.Node) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAllowedHtmlTag(node: commonmark.Node): boolean {
|
function isAllowedHtmlTag(node: commonmark.Node): boolean {
|
||||||
if (node.literal != null &&
|
if (node.literal != null &&
|
||||||
node.literal.match('^<((div|span) data-mx-maths="[^"]*"|/(div|span))>$') != null) {
|
node.literal.match('^<((div|span) data-mx-maths="[^"]*"|/(div|span))>$') != null) {
|
||||||
@ -248,7 +238,7 @@ export default class Markdown {
|
|||||||
isPlainText(): boolean {
|
isPlainText(): boolean {
|
||||||
const walker = this.parsed.walker();
|
const walker = this.parsed.walker();
|
||||||
|
|
||||||
let ev;
|
let ev: commonmark.NodeWalkingStep;
|
||||||
while (ev = walker.next()) {
|
while (ev = walker.next()) {
|
||||||
const node = ev.node;
|
const node = ev.node;
|
||||||
if (TEXT_NODES.indexOf(node.type) > -1) {
|
if (TEXT_NODES.indexOf(node.type) > -1) {
|
||||||
@ -278,7 +268,7 @@ export default class Markdown {
|
|||||||
// block quote ends up all on one line
|
// block quote ends up all on one line
|
||||||
// (https://github.com/vector-im/element-web/issues/3154)
|
// (https://github.com/vector-im/element-web/issues/3154)
|
||||||
softbreak: '<br />',
|
softbreak: '<br />',
|
||||||
}) as CommonmarkHtmlRendererInternal;
|
});
|
||||||
|
|
||||||
// Trying to strip out the wrapping <p/> causes a lot more complication
|
// Trying to strip out the wrapping <p/> causes a lot more complication
|
||||||
// than it's worth, i think. For instance, this code will go and strip
|
// than it's worth, i think. For instance, this code will go and strip
|
||||||
@ -356,7 +346,7 @@ export default class Markdown {
|
|||||||
* which has no formatting. Otherwise it emits HTML(!).
|
* which has no formatting. Otherwise it emits HTML(!).
|
||||||
*/
|
*/
|
||||||
toPlaintext(): string {
|
toPlaintext(): string {
|
||||||
const renderer = new commonmark.HtmlRenderer({ safe: false }) as CommonmarkHtmlRendererInternal;
|
const renderer = new commonmark.HtmlRenderer({ safe: false });
|
||||||
|
|
||||||
renderer.paragraph = function(node: commonmark.Node, entering: boolean) {
|
renderer.paragraph = function(node: commonmark.Node, entering: boolean) {
|
||||||
// as with toHTML, only append lines to paragraphs if there are
|
// as with toHTML, only append lines to paragraphs if there are
|
||||||
|
@ -210,7 +210,7 @@ export const Notifier = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
start: function() {
|
start: function(this: typeof Notifier) {
|
||||||
// do not re-bind in the case of repeated call
|
// do not re-bind in the case of repeated call
|
||||||
this.boundOnEvent = this.boundOnEvent || this.onEvent.bind(this);
|
this.boundOnEvent = this.boundOnEvent || this.onEvent.bind(this);
|
||||||
this.boundOnSyncStateChange = this.boundOnSyncStateChange || this.onSyncStateChange.bind(this);
|
this.boundOnSyncStateChange = this.boundOnSyncStateChange || this.onSyncStateChange.bind(this);
|
||||||
@ -225,7 +225,7 @@ export const Notifier = {
|
|||||||
this.isSyncing = false;
|
this.isSyncing = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
stop: function() {
|
stop: function(this: typeof Notifier) {
|
||||||
if (MatrixClientPeg.get()) {
|
if (MatrixClientPeg.get()) {
|
||||||
MatrixClientPeg.get().removeListener(ClientEvent.Event, this.boundOnEvent);
|
MatrixClientPeg.get().removeListener(ClientEvent.Event, this.boundOnEvent);
|
||||||
MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.boundOnRoomReceipt);
|
MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.boundOnRoomReceipt);
|
||||||
@ -322,7 +322,7 @@ export const Notifier = {
|
|||||||
return SettingsStore.getValue("audioNotificationsEnabled");
|
return SettingsStore.getValue("audioNotificationsEnabled");
|
||||||
},
|
},
|
||||||
|
|
||||||
setPromptHidden: function(hidden: boolean, persistent = true) {
|
setPromptHidden: function(this: typeof Notifier, hidden: boolean, persistent = true) {
|
||||||
this.toolbarHidden = hidden;
|
this.toolbarHidden = hidden;
|
||||||
|
|
||||||
hideNotificationsToast();
|
hideNotificationsToast();
|
||||||
@ -343,7 +343,7 @@ export const Notifier = {
|
|||||||
!this.isEnabled() && !this._isPromptHidden();
|
!this.isEnabled() && !this._isPromptHidden();
|
||||||
},
|
},
|
||||||
|
|
||||||
_isPromptHidden: function() {
|
_isPromptHidden: function(this: typeof Notifier) {
|
||||||
// Check localStorage for any such meta data
|
// Check localStorage for any such meta data
|
||||||
if (global.localStorage) {
|
if (global.localStorage) {
|
||||||
return global.localStorage.getItem("notifications_hidden") === "true";
|
return global.localStorage.getItem("notifications_hidden") === "true";
|
||||||
@ -352,7 +352,7 @@ export const Notifier = {
|
|||||||
return this.toolbarHidden;
|
return this.toolbarHidden;
|
||||||
},
|
},
|
||||||
|
|
||||||
onSyncStateChange: function(state: SyncState, prevState?: SyncState, data?: ISyncStateData) {
|
onSyncStateChange: function(this: typeof Notifier, state: SyncState, prevState?: SyncState, data?: ISyncStateData) {
|
||||||
if (state === SyncState.Syncing) {
|
if (state === SyncState.Syncing) {
|
||||||
this.isSyncing = true;
|
this.isSyncing = true;
|
||||||
} else if (state === SyncState.Stopped || state === SyncState.Error) {
|
} else if (state === SyncState.Stopped || state === SyncState.Error) {
|
||||||
@ -368,7 +368,7 @@ export const Notifier = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onEvent: function(ev: MatrixEvent) {
|
onEvent: function(this: typeof Notifier, ev: MatrixEvent) {
|
||||||
if (!this.isSyncing) return; // don't alert for any messages initially
|
if (!this.isSyncing) return; // don't alert for any messages initially
|
||||||
if (ev.getSender() === MatrixClientPeg.get().getUserId()) return;
|
if (ev.getSender() === MatrixClientPeg.get().getUserId()) return;
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ export const CommandCategories = {
|
|||||||
|
|
||||||
export type RunResult = XOR<{ error: Error | ITranslatableError }, { promise: Promise<IContent | undefined> }>;
|
export type RunResult = XOR<{ error: Error | ITranslatableError }, { promise: Promise<IContent | undefined> }>;
|
||||||
|
|
||||||
type RunFn = ((roomId: string, args: string, cmd: string) => RunResult);
|
type RunFn = ((this: Command, roomId: string, args: string) => RunResult);
|
||||||
|
|
||||||
interface ICommandOpts {
|
interface ICommandOpts {
|
||||||
command: string;
|
command: string;
|
||||||
@ -129,9 +129,9 @@ interface ICommandOpts {
|
|||||||
export class Command {
|
export class Command {
|
||||||
public readonly command: string;
|
public readonly command: string;
|
||||||
public readonly aliases: string[];
|
public readonly aliases: string[];
|
||||||
public readonly args: undefined | string;
|
public readonly args?: string;
|
||||||
public readonly description: string;
|
public readonly description: string;
|
||||||
public readonly runFn: undefined | RunFn;
|
public readonly runFn?: RunFn;
|
||||||
public readonly category: string;
|
public readonly category: string;
|
||||||
public readonly hideCompletionAfterSpace: boolean;
|
public readonly hideCompletionAfterSpace: boolean;
|
||||||
public readonly renderingTypes?: TimelineRenderingType[];
|
public readonly renderingTypes?: TimelineRenderingType[];
|
||||||
@ -143,7 +143,7 @@ export class Command {
|
|||||||
this.aliases = opts.aliases || [];
|
this.aliases = opts.aliases || [];
|
||||||
this.args = opts.args || "";
|
this.args = opts.args || "";
|
||||||
this.description = opts.description;
|
this.description = opts.description;
|
||||||
this.runFn = opts.runFn;
|
this.runFn = opts.runFn?.bind(this);
|
||||||
this.category = opts.category || CommandCategories.other;
|
this.category = opts.category || CommandCategories.other;
|
||||||
this.hideCompletionAfterSpace = opts.hideCompletionAfterSpace || false;
|
this.hideCompletionAfterSpace = opts.hideCompletionAfterSpace || false;
|
||||||
this._isEnabled = opts.isEnabled;
|
this._isEnabled = opts.isEnabled;
|
||||||
@ -188,7 +188,7 @@ export class Command {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.runFn.bind(this)(roomId, args);
|
return this.runFn(roomId, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getUsage() {
|
public getUsage() {
|
||||||
@ -1114,7 +1114,7 @@ export const Commands = [
|
|||||||
description: _td("Sends the given message coloured as a rainbow"),
|
description: _td("Sends the given message coloured as a rainbow"),
|
||||||
args: '<message>',
|
args: '<message>',
|
||||||
runFn: function(roomId, args) {
|
runFn: function(roomId, args) {
|
||||||
if (!args) return reject(this.getUserId());
|
if (!args) return reject(this.getUsage());
|
||||||
return successSync(ContentHelpers.makeHtmlMessage(args, textToHtmlRainbow(args)));
|
return successSync(ContentHelpers.makeHtmlMessage(args, textToHtmlRainbow(args)));
|
||||||
},
|
},
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
@ -1124,7 +1124,7 @@ export const Commands = [
|
|||||||
description: _td("Sends the given emote coloured as a rainbow"),
|
description: _td("Sends the given emote coloured as a rainbow"),
|
||||||
args: '<message>',
|
args: '<message>',
|
||||||
runFn: function(roomId, args) {
|
runFn: function(roomId, args) {
|
||||||
if (!args) return reject(this.getUserId());
|
if (!args) return reject(this.getUsage());
|
||||||
return successSync(ContentHelpers.makeHtmlEmote(args, textToHtmlRainbow(args)));
|
return successSync(ContentHelpers.makeHtmlEmote(args, textToHtmlRainbow(args)));
|
||||||
},
|
},
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
@ -1207,7 +1207,7 @@ export const Commands = [
|
|||||||
|
|
||||||
return success((async () => {
|
return success((async () => {
|
||||||
if (isPhoneNumber) {
|
if (isPhoneNumber) {
|
||||||
const results = await LegacyCallHandler.instance.pstnLookup(this.state.value);
|
const results = await LegacyCallHandler.instance.pstnLookup(userId);
|
||||||
if (!results || results.length === 0 || !results[0].userid) {
|
if (!results || results.length === 0 || !results[0].userid) {
|
||||||
throw newTranslatableError("Unable to find Matrix ID for phone number");
|
throw newTranslatableError("Unable to find Matrix ID for phone number");
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ interface IOptions<T extends {}> {
|
|||||||
* @param {function[]} options.funcs List of functions that when called with the
|
* @param {function[]} options.funcs List of functions that when called with the
|
||||||
* object as an arg will return a string to use as an index
|
* object as an arg will return a string to use as an index
|
||||||
*/
|
*/
|
||||||
export default class QueryMatcher<T extends Object> {
|
export default class QueryMatcher<T extends {}> {
|
||||||
private _options: IOptions<T>;
|
private _options: IOptions<T>;
|
||||||
private _items: Map<string, {object: T, keyWeight: number}[]>;
|
private _items: Map<string, {object: T, keyWeight: number}[]>;
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ export default class Draggable extends React.Component<IProps, IState> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private onMouseDown = (event: MouseEvent): void => {
|
private onMouseDown = (event: React.MouseEvent): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
location: {
|
location: {
|
||||||
currentX: event.clientX,
|
currentX: event.clientX,
|
||||||
@ -74,6 +74,6 @@ export default class Draggable extends React.Component<IProps, IState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div className={this.props.className} onMouseDown={this.onMouseDown.bind(this)} />;
|
return <div className={this.props.className} onMouseDown={this.onMouseDown} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ export default class IRCTimelineProfileResizer extends React.Component<IProps, I
|
|||||||
}, () => this.updateCSSWidth(this.state.width));
|
}, () => this.updateCSSWidth(this.state.width));
|
||||||
}
|
}
|
||||||
|
|
||||||
private dragFunc = (location: ILocationState, event: React.MouseEvent<Element, MouseEvent>): ILocationState => {
|
private dragFunc = (location: ILocationState, event: MouseEvent): ILocationState => {
|
||||||
const offset = event.clientX - location.currentX;
|
const offset = event.clientX - location.currentX;
|
||||||
const newWidth = this.state.width + offset;
|
const newWidth = this.state.width + offset;
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ export default class IRCTimelineProfileResizer extends React.Component<IProps, I
|
|||||||
this.state.IRCLayoutRoot.style.setProperty("--name-width", newWidth + "px");
|
this.state.IRCLayoutRoot.style.setProperty("--name-width", newWidth + "px");
|
||||||
}
|
}
|
||||||
|
|
||||||
private onMoueUp(event: MouseEvent) {
|
private onMoueUp = () => {
|
||||||
if (this.props.roomId) {
|
if (this.props.roomId) {
|
||||||
SettingsStore.setValue(
|
SettingsStore.setValue(
|
||||||
"ircDisplayNameWidth",
|
"ircDisplayNameWidth",
|
||||||
@ -86,13 +86,13 @@ export default class IRCTimelineProfileResizer extends React.Component<IProps, I
|
|||||||
this.state.width,
|
this.state.width,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Draggable
|
return <Draggable
|
||||||
className="mx_ProfileResizer"
|
className="mx_ProfileResizer"
|
||||||
dragFunc={this.dragFunc.bind(this)}
|
dragFunc={this.dragFunc}
|
||||||
onMouseUp={this.onMoueUp.bind(this)}
|
onMouseUp={this.onMoueUp}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-invalid-this */
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ interface IArgs<T, D = void> {
|
|||||||
export interface IFieldState {
|
export interface IFieldState {
|
||||||
value: string;
|
value: string;
|
||||||
focused: boolean;
|
focused: boolean;
|
||||||
allowEmpty: boolean;
|
allowEmpty?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IValidationResult {
|
export interface IValidationResult {
|
||||||
@ -80,10 +79,13 @@ export interface IValidationResult {
|
|||||||
* A validation function that takes in the current input value and returns
|
* A validation function that takes in the current input value and returns
|
||||||
* the overall validity and a feedback UI that can be rendered for more detail.
|
* the overall validity and a feedback UI that can be rendered for more detail.
|
||||||
*/
|
*/
|
||||||
export default function withValidation<T = undefined, D = void>({
|
export default function withValidation<T = void, D = void>({
|
||||||
description, hideDescriptionIfValid, deriveData, rules,
|
description, hideDescriptionIfValid, deriveData, rules,
|
||||||
}: IArgs<T, D>) {
|
}: IArgs<T, D>) {
|
||||||
return async function onValidate({ value, focused, allowEmpty = true }: IFieldState): Promise<IValidationResult> {
|
return async function onValidate(
|
||||||
|
this: T,
|
||||||
|
{ value, focused, allowEmpty = true }: IFieldState,
|
||||||
|
): Promise<IValidationResult> {
|
||||||
if (!value && allowEmpty) {
|
if (!value && allowEmpty) {
|
||||||
return {
|
return {
|
||||||
valid: null,
|
valid: null,
|
||||||
@ -96,7 +98,7 @@ export default function withValidation<T = undefined, D = void>({
|
|||||||
|
|
||||||
const results: IResult[] = [];
|
const results: IResult[] = [];
|
||||||
let valid = true;
|
let valid = true;
|
||||||
if (rules && rules.length) {
|
if (rules?.length) {
|
||||||
for (const rule of rules) {
|
for (const rule of rules) {
|
||||||
if (!rule.key || !rule.test) {
|
if (!rule.key || !rule.test) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -31,7 +31,7 @@ function arrayBufferReadInt(arr: ArrayBuffer, start: number): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function arrayBufferReadStr(arr: ArrayBuffer, start: number, len: number): string {
|
function arrayBufferReadStr(arr: ArrayBuffer, start: number, len: number): string {
|
||||||
return String.fromCharCode.apply(null, arrayBufferRead(arr, start, len));
|
return String.fromCharCode.apply(null, Array.from(arrayBufferRead(arr, start, len)));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function blobIsAnimated(mimeType: string | undefined, blob: Blob): Promise<boolean> {
|
export async function blobIsAnimated(mimeType: string | undefined, blob: Blob): Promise<boolean> {
|
||||||
|
@ -356,7 +356,7 @@ function packMegolmKeyFile(data: Uint8Array): ArrayBuffer {
|
|||||||
function encodeBase64(uint8Array: Uint8Array): string {
|
function encodeBase64(uint8Array: Uint8Array): string {
|
||||||
// Misinterpt the Uint8Array as Latin-1.
|
// Misinterpt the Uint8Array as Latin-1.
|
||||||
// window.btoa expects a unicode string with codepoints in the range 0-255.
|
// window.btoa expects a unicode string with codepoints in the range 0-255.
|
||||||
const latin1String = String.fromCharCode.apply(null, uint8Array);
|
const latin1String = String.fromCharCode.apply(null, Array.from(uint8Array));
|
||||||
// Use the builtin base64 encoder.
|
// Use the builtin base64 encoder.
|
||||||
return window.btoa(latin1String);
|
return window.btoa(latin1String);
|
||||||
}
|
}
|
||||||
|
@ -131,9 +131,9 @@ describe("ContentMessages", () => {
|
|||||||
jest.spyOn(document, "createElement").mockImplementation(tagName => {
|
jest.spyOn(document, "createElement").mockImplementation(tagName => {
|
||||||
const element = createElement(tagName);
|
const element = createElement(tagName);
|
||||||
if (tagName === "video") {
|
if (tagName === "video") {
|
||||||
element.load = jest.fn();
|
(<HTMLVideoElement>element).load = jest.fn();
|
||||||
element.play = () => element.onloadeddata(new Event("loadeddata"));
|
(<HTMLVideoElement>element).play = () => element.onloadeddata(new Event("loadeddata"));
|
||||||
element.pause = jest.fn();
|
(<HTMLVideoElement>element).pause = jest.fn();
|
||||||
Object.defineProperty(element, 'videoHeight', {
|
Object.defineProperty(element, 'videoHeight', {
|
||||||
get() { return 600; },
|
get() { return 600; },
|
||||||
});
|
});
|
||||||
|
@ -433,4 +433,11 @@ describe("Notifier", () => {
|
|||||||
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1);
|
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("setPromptHidden", () => {
|
||||||
|
it("should persist by default", () => {
|
||||||
|
Notifier.setPromptHidden(true);
|
||||||
|
expect(localStorage.getItem("notifications_hidden")).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -225,4 +225,19 @@ describe('SlashCommands', () => {
|
|||||||
expect(client.leaveRoomChain).toHaveBeenCalledWith("room-id", expect.anything());
|
expect(client.leaveRoomChain).toHaveBeenCalledWith("room-id", expect.anything());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe.each([
|
||||||
|
"rainbow",
|
||||||
|
"rainbowme",
|
||||||
|
])("/%s", (commandName: string) => {
|
||||||
|
const command = findCommand(commandName);
|
||||||
|
|
||||||
|
it("should return usage if no args", () => {
|
||||||
|
expect(command.run(roomId, null, null).error).toBe(command.getUsage());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should make things rainbowy", () => {
|
||||||
|
return expect(command.run(roomId, null, "this is a test message").promise).resolves.toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
19
test/__snapshots__/SlashCommands-test.tsx.snap
Normal file
19
test/__snapshots__/SlashCommands-test.tsx.snap
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`SlashCommands /rainbow should make things rainbowy 1`] = `
|
||||||
|
{
|
||||||
|
"body": "this is a test message",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "<font color="#ff00be">t</font><font color="#ff0080">h</font><font color="#ff0041">i</font><font color="#ff5f00">s</font> <font color="#faa900">i</font><font color="#c3bf00">s</font> <font color="#00d800">a</font> <font color="#00e371">t</font><font color="#00e6b6">e</font><font color="#00e7f8">s</font><font color="#00e7ff">t</font> <font color="#00deff">m</font><font color="#00d2ff">e</font><font color="#00c0ff">s</font><font color="#44a4ff">s</font><font color="#e87dff">a</font><font color="#ff42ff">g</font><font color="#ff00fe">e</font>",
|
||||||
|
"msgtype": "m.text",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SlashCommands /rainbowme should make things rainbowy 1`] = `
|
||||||
|
{
|
||||||
|
"body": "this is a test message",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "<font color="#ff00be">t</font><font color="#ff0080">h</font><font color="#ff0041">i</font><font color="#ff5f00">s</font> <font color="#faa900">i</font><font color="#c3bf00">s</font> <font color="#00d800">a</font> <font color="#00e371">t</font><font color="#00e6b6">e</font><font color="#00e7f8">s</font><font color="#00e7ff">t</font> <font color="#00deff">m</font><font color="#00d2ff">e</font><font color="#00c0ff">s</font><font color="#44a4ff">s</font><font color="#e87dff">a</font><font color="#ff42ff">g</font><font color="#ff00fe">e</font>",
|
||||||
|
"msgtype": "m.emote",
|
||||||
|
}
|
||||||
|
`;
|
31
test/components/views/Validation-test.ts
Normal file
31
test/components/views/Validation-test.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
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 withValidation from "../../../src/components/views/elements/Validation";
|
||||||
|
|
||||||
|
describe("Validation", () => {
|
||||||
|
it("should handle 0 rules", () => {
|
||||||
|
const handler = withValidation({
|
||||||
|
rules: [],
|
||||||
|
});
|
||||||
|
return expect(handler({
|
||||||
|
value: "value",
|
||||||
|
focused: true,
|
||||||
|
})).resolves.toEqual(expect.objectContaining({
|
||||||
|
valid: true,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
@ -46,20 +46,20 @@ export const createTestPlayback = (): Playback => {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
// EventEmitter
|
// EventEmitter
|
||||||
on: eventEmitter.on.bind(eventEmitter),
|
on: eventEmitter.on.bind(eventEmitter) as Playback["on"],
|
||||||
once: eventEmitter.once.bind(eventEmitter),
|
once: eventEmitter.once.bind(eventEmitter) as Playback["once"],
|
||||||
off: eventEmitter.off.bind(eventEmitter),
|
off: eventEmitter.off.bind(eventEmitter) as Playback["off"],
|
||||||
addListener: eventEmitter.addListener.bind(eventEmitter),
|
addListener: eventEmitter.addListener.bind(eventEmitter) as Playback["addListener"],
|
||||||
removeListener: eventEmitter.removeListener.bind(eventEmitter),
|
removeListener: eventEmitter.removeListener.bind(eventEmitter) as Playback["removeListener"],
|
||||||
removeAllListeners: eventEmitter.removeAllListeners.bind(eventEmitter),
|
removeAllListeners: eventEmitter.removeAllListeners.bind(eventEmitter) as Playback["removeAllListeners"],
|
||||||
getMaxListeners: eventEmitter.getMaxListeners.bind(eventEmitter),
|
getMaxListeners: eventEmitter.getMaxListeners.bind(eventEmitter) as Playback["getMaxListeners"],
|
||||||
setMaxListeners: eventEmitter.setMaxListeners.bind(eventEmitter),
|
setMaxListeners: eventEmitter.setMaxListeners.bind(eventEmitter) as Playback["setMaxListeners"],
|
||||||
listeners: eventEmitter.listeners.bind(eventEmitter),
|
listeners: eventEmitter.listeners.bind(eventEmitter) as Playback["listeners"],
|
||||||
rawListeners: eventEmitter.rawListeners.bind(eventEmitter),
|
rawListeners: eventEmitter.rawListeners.bind(eventEmitter) as Playback["rawListeners"],
|
||||||
listenerCount: eventEmitter.listenerCount.bind(eventEmitter),
|
listenerCount: eventEmitter.listenerCount.bind(eventEmitter) as Playback["listenerCount"],
|
||||||
eventNames: eventEmitter.eventNames.bind(eventEmitter),
|
eventNames: eventEmitter.eventNames.bind(eventEmitter) as Playback["eventNames"],
|
||||||
prependListener: eventEmitter.prependListener.bind(eventEmitter),
|
prependListener: eventEmitter.prependListener.bind(eventEmitter) as Playback["prependListener"],
|
||||||
prependOnceListener: eventEmitter.prependOnceListener.bind(eventEmitter),
|
prependOnceListener: eventEmitter.prependOnceListener.bind(eventEmitter) as Playback["prependOnceListener"],
|
||||||
liveData: new SimpleObservable<number[]>(),
|
liveData: new SimpleObservable<number[]>(),
|
||||||
durationSeconds: 31415,
|
durationSeconds: 31415,
|
||||||
timeSeconds: 3141,
|
timeSeconds: 3141,
|
||||||
|
@ -17,7 +17,10 @@
|
|||||||
"es2020",
|
"es2020",
|
||||||
"dom",
|
"dom",
|
||||||
"dom.iterable"
|
"dom.iterable"
|
||||||
]
|
],
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"strictBindCallApply": true,
|
||||||
|
"noImplicitThis": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./src/**/*.ts",
|
"./src/**/*.ts",
|
||||||
|
@ -9451,10 +9451,10 @@ typedarray-to-buffer@^3.1.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-typedarray "^1.0.0"
|
is-typedarray "^1.0.0"
|
||||||
|
|
||||||
typescript@4.7.4:
|
typescript@4.8.4:
|
||||||
version "4.7.4"
|
version "4.8.4"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
|
||||||
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
|
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
|
||||||
|
|
||||||
ua-parser-js@^0.7.30:
|
ua-parser-js@^0.7.30:
|
||||||
version "0.7.31"
|
version "0.7.31"
|
||||||
|
Loading…
Reference in New Issue
Block a user