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-config-standard": "^26.0.0",
|
||||
"stylelint-scss": "^4.2.0",
|
||||
"typescript": "4.7.4",
|
||||
"typescript": "4.8.4",
|
||||
"walk": "^2.3.14"
|
||||
},
|
||||
"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 ReactAnyComponent = React.Component | React.ExoticComponent;
|
||||
|
||||
// Utility type for string dot notation for accessing nested object properties
|
||||
// Based on https://stackoverflow.com/a/58436959
|
||||
type Join<K, P> = K extends string | number ?
|
||||
P extends string | number ?
|
||||
`${K}${"" extends P ? "" : "."}${P}`
|
||||
: 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] : "";
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import "./@types/commonmark"; // import better types than @types/commonmark
|
||||
import * as commonmark from 'commonmark';
|
||||
import { escape } from "lodash";
|
||||
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
|
||||
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 {
|
||||
if (node.literal != null &&
|
||||
node.literal.match('^<((div|span) data-mx-maths="[^"]*"|/(div|span))>$') != null) {
|
||||
@ -248,7 +238,7 @@ export default class Markdown {
|
||||
isPlainText(): boolean {
|
||||
const walker = this.parsed.walker();
|
||||
|
||||
let ev;
|
||||
let ev: commonmark.NodeWalkingStep;
|
||||
while (ev = walker.next()) {
|
||||
const node = ev.node;
|
||||
if (TEXT_NODES.indexOf(node.type) > -1) {
|
||||
@ -278,7 +268,7 @@ export default class Markdown {
|
||||
// block quote ends up all on one line
|
||||
// (https://github.com/vector-im/element-web/issues/3154)
|
||||
softbreak: '<br />',
|
||||
}) as CommonmarkHtmlRendererInternal;
|
||||
});
|
||||
|
||||
// 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
|
||||
@ -356,7 +346,7 @@ export default class Markdown {
|
||||
* which has no formatting. Otherwise it emits HTML(!).
|
||||
*/
|
||||
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) {
|
||||
// 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
|
||||
this.boundOnEvent = this.boundOnEvent || this.onEvent.bind(this);
|
||||
this.boundOnSyncStateChange = this.boundOnSyncStateChange || this.onSyncStateChange.bind(this);
|
||||
@ -225,7 +225,7 @@ export const Notifier = {
|
||||
this.isSyncing = false;
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
stop: function(this: typeof Notifier) {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener(ClientEvent.Event, this.boundOnEvent);
|
||||
MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.boundOnRoomReceipt);
|
||||
@ -322,7 +322,7 @@ export const Notifier = {
|
||||
return SettingsStore.getValue("audioNotificationsEnabled");
|
||||
},
|
||||
|
||||
setPromptHidden: function(hidden: boolean, persistent = true) {
|
||||
setPromptHidden: function(this: typeof Notifier, hidden: boolean, persistent = true) {
|
||||
this.toolbarHidden = hidden;
|
||||
|
||||
hideNotificationsToast();
|
||||
@ -343,7 +343,7 @@ export const Notifier = {
|
||||
!this.isEnabled() && !this._isPromptHidden();
|
||||
},
|
||||
|
||||
_isPromptHidden: function() {
|
||||
_isPromptHidden: function(this: typeof Notifier) {
|
||||
// Check localStorage for any such meta data
|
||||
if (global.localStorage) {
|
||||
return global.localStorage.getItem("notifications_hidden") === "true";
|
||||
@ -352,7 +352,7 @@ export const Notifier = {
|
||||
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) {
|
||||
this.isSyncing = true;
|
||||
} 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 (ev.getSender() === MatrixClientPeg.get().getUserId()) return;
|
||||
|
||||
|
@ -111,7 +111,7 @@ export const CommandCategories = {
|
||||
|
||||
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 {
|
||||
command: string;
|
||||
@ -129,9 +129,9 @@ interface ICommandOpts {
|
||||
export class Command {
|
||||
public readonly command: string;
|
||||
public readonly aliases: string[];
|
||||
public readonly args: undefined | string;
|
||||
public readonly args?: string;
|
||||
public readonly description: string;
|
||||
public readonly runFn: undefined | RunFn;
|
||||
public readonly runFn?: RunFn;
|
||||
public readonly category: string;
|
||||
public readonly hideCompletionAfterSpace: boolean;
|
||||
public readonly renderingTypes?: TimelineRenderingType[];
|
||||
@ -143,7 +143,7 @@ export class Command {
|
||||
this.aliases = opts.aliases || [];
|
||||
this.args = opts.args || "";
|
||||
this.description = opts.description;
|
||||
this.runFn = opts.runFn;
|
||||
this.runFn = opts.runFn?.bind(this);
|
||||
this.category = opts.category || CommandCategories.other;
|
||||
this.hideCompletionAfterSpace = opts.hideCompletionAfterSpace || false;
|
||||
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() {
|
||||
@ -1114,7 +1114,7 @@ export const Commands = [
|
||||
description: _td("Sends the given message coloured as a rainbow"),
|
||||
args: '<message>',
|
||||
runFn: function(roomId, args) {
|
||||
if (!args) return reject(this.getUserId());
|
||||
if (!args) return reject(this.getUsage());
|
||||
return successSync(ContentHelpers.makeHtmlMessage(args, textToHtmlRainbow(args)));
|
||||
},
|
||||
category: CommandCategories.messages,
|
||||
@ -1124,7 +1124,7 @@ export const Commands = [
|
||||
description: _td("Sends the given emote coloured as a rainbow"),
|
||||
args: '<message>',
|
||||
runFn: function(roomId, args) {
|
||||
if (!args) return reject(this.getUserId());
|
||||
if (!args) return reject(this.getUsage());
|
||||
return successSync(ContentHelpers.makeHtmlEmote(args, textToHtmlRainbow(args)));
|
||||
},
|
||||
category: CommandCategories.messages,
|
||||
@ -1207,7 +1207,7 @@ export const Commands = [
|
||||
|
||||
return success((async () => {
|
||||
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) {
|
||||
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
|
||||
* 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 _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({
|
||||
location: {
|
||||
currentX: event.clientX,
|
||||
@ -74,6 +74,6 @@ export default class Draggable extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
private dragFunc = (location: ILocationState, event: React.MouseEvent<Element, MouseEvent>): ILocationState => {
|
||||
private dragFunc = (location: ILocationState, event: MouseEvent): ILocationState => {
|
||||
const offset = event.clientX - location.currentX;
|
||||
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");
|
||||
}
|
||||
|
||||
private onMoueUp(event: MouseEvent) {
|
||||
private onMoueUp = () => {
|
||||
if (this.props.roomId) {
|
||||
SettingsStore.setValue(
|
||||
"ircDisplayNameWidth",
|
||||
@ -86,13 +86,13 @@ export default class IRCTimelineProfileResizer extends React.Component<IProps, I
|
||||
this.state.width,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <Draggable
|
||||
className="mx_ProfileResizer"
|
||||
dragFunc={this.dragFunc.bind(this)}
|
||||
onMouseUp={this.onMoueUp.bind(this)}
|
||||
dragFunc={this.dragFunc}
|
||||
onMouseUp={this.onMoueUp}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-invalid-this */
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
@ -46,7 +45,7 @@ interface IArgs<T, D = void> {
|
||||
export interface IFieldState {
|
||||
value: string;
|
||||
focused: boolean;
|
||||
allowEmpty: boolean;
|
||||
allowEmpty?: boolean;
|
||||
}
|
||||
|
||||
export interface IValidationResult {
|
||||
@ -80,10 +79,13 @@ export interface IValidationResult {
|
||||
* 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.
|
||||
*/
|
||||
export default function withValidation<T = undefined, D = void>({
|
||||
export default function withValidation<T = void, D = void>({
|
||||
description, hideDescriptionIfValid, deriveData, rules,
|
||||
}: 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) {
|
||||
return {
|
||||
valid: null,
|
||||
@ -96,7 +98,7 @@ export default function withValidation<T = undefined, D = void>({
|
||||
|
||||
const results: IResult[] = [];
|
||||
let valid = true;
|
||||
if (rules && rules.length) {
|
||||
if (rules?.length) {
|
||||
for (const rule of rules) {
|
||||
if (!rule.key || !rule.test) {
|
||||
continue;
|
||||
|
@ -31,7 +31,7 @@ function arrayBufferReadInt(arr: ArrayBuffer, start: number): number {
|
||||
}
|
||||
|
||||
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> {
|
||||
|
@ -356,7 +356,7 @@ function packMegolmKeyFile(data: Uint8Array): ArrayBuffer {
|
||||
function encodeBase64(uint8Array: Uint8Array): string {
|
||||
// Misinterpt the Uint8Array as Latin-1.
|
||||
// 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.
|
||||
return window.btoa(latin1String);
|
||||
}
|
||||
|
@ -131,9 +131,9 @@ describe("ContentMessages", () => {
|
||||
jest.spyOn(document, "createElement").mockImplementation(tagName => {
|
||||
const element = createElement(tagName);
|
||||
if (tagName === "video") {
|
||||
element.load = jest.fn();
|
||||
element.play = () => element.onloadeddata(new Event("loadeddata"));
|
||||
element.pause = jest.fn();
|
||||
(<HTMLVideoElement>element).load = jest.fn();
|
||||
(<HTMLVideoElement>element).play = () => element.onloadeddata(new Event("loadeddata"));
|
||||
(<HTMLVideoElement>element).pause = jest.fn();
|
||||
Object.defineProperty(element, 'videoHeight', {
|
||||
get() { return 600; },
|
||||
});
|
||||
|
@ -433,4 +433,11 @@ describe("Notifier", () => {
|
||||
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());
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
},
|
||||
// EventEmitter
|
||||
on: eventEmitter.on.bind(eventEmitter),
|
||||
once: eventEmitter.once.bind(eventEmitter),
|
||||
off: eventEmitter.off.bind(eventEmitter),
|
||||
addListener: eventEmitter.addListener.bind(eventEmitter),
|
||||
removeListener: eventEmitter.removeListener.bind(eventEmitter),
|
||||
removeAllListeners: eventEmitter.removeAllListeners.bind(eventEmitter),
|
||||
getMaxListeners: eventEmitter.getMaxListeners.bind(eventEmitter),
|
||||
setMaxListeners: eventEmitter.setMaxListeners.bind(eventEmitter),
|
||||
listeners: eventEmitter.listeners.bind(eventEmitter),
|
||||
rawListeners: eventEmitter.rawListeners.bind(eventEmitter),
|
||||
listenerCount: eventEmitter.listenerCount.bind(eventEmitter),
|
||||
eventNames: eventEmitter.eventNames.bind(eventEmitter),
|
||||
prependListener: eventEmitter.prependListener.bind(eventEmitter),
|
||||
prependOnceListener: eventEmitter.prependOnceListener.bind(eventEmitter),
|
||||
on: eventEmitter.on.bind(eventEmitter) as Playback["on"],
|
||||
once: eventEmitter.once.bind(eventEmitter) as Playback["once"],
|
||||
off: eventEmitter.off.bind(eventEmitter) as Playback["off"],
|
||||
addListener: eventEmitter.addListener.bind(eventEmitter) as Playback["addListener"],
|
||||
removeListener: eventEmitter.removeListener.bind(eventEmitter) as Playback["removeListener"],
|
||||
removeAllListeners: eventEmitter.removeAllListeners.bind(eventEmitter) as Playback["removeAllListeners"],
|
||||
getMaxListeners: eventEmitter.getMaxListeners.bind(eventEmitter) as Playback["getMaxListeners"],
|
||||
setMaxListeners: eventEmitter.setMaxListeners.bind(eventEmitter) as Playback["setMaxListeners"],
|
||||
listeners: eventEmitter.listeners.bind(eventEmitter) as Playback["listeners"],
|
||||
rawListeners: eventEmitter.rawListeners.bind(eventEmitter) as Playback["rawListeners"],
|
||||
listenerCount: eventEmitter.listenerCount.bind(eventEmitter) as Playback["listenerCount"],
|
||||
eventNames: eventEmitter.eventNames.bind(eventEmitter) as Playback["eventNames"],
|
||||
prependListener: eventEmitter.prependListener.bind(eventEmitter) as Playback["prependListener"],
|
||||
prependOnceListener: eventEmitter.prependOnceListener.bind(eventEmitter) as Playback["prependOnceListener"],
|
||||
liveData: new SimpleObservable<number[]>(),
|
||||
durationSeconds: 31415,
|
||||
timeSeconds: 3141,
|
||||
|
@ -17,7 +17,10 @@
|
||||
"es2020",
|
||||
"dom",
|
||||
"dom.iterable"
|
||||
]
|
||||
],
|
||||
"alwaysStrict": true,
|
||||
"strictBindCallApply": true,
|
||||
"noImplicitThis": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
|
@ -9451,10 +9451,10 @@ typedarray-to-buffer@^3.1.5:
|
||||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
typescript@4.7.4:
|
||||
version "4.7.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
|
||||
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
|
||||
typescript@4.8.4:
|
||||
version "4.8.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
|
||||
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
|
||||
|
||||
ua-parser-js@^0.7.30:
|
||||
version "0.7.31"
|
||||
|
Loading…
Reference in New Issue
Block a user