some test fixes

This commit is contained in:
Max Leiter 2022-05-14 15:18:06 -07:00
parent b798cfdc64
commit 4c98b81e35
No known key found for this signature in database
GPG key ID: A3512F2F2F17EBDA
44 changed files with 215 additions and 90 deletions

View file

@ -1,3 +1,4 @@
public/
coverage/
src/dist/
dist/

1
.gitignore vendored
View file

@ -8,3 +8,4 @@ coverage/
public/
client/dist
src/dist
dist/

View file

@ -4,6 +4,7 @@ test/fixtures/.thelounge/logs/
test/fixtures/.thelounge/certificates/
test/fixtures/.thelounge/storage/
src/dist/
dist/
*.log
*.png
*.svg

View file

@ -195,7 +195,7 @@
}
</style>
<script lang="ts">
<script>
import Mousetrap from "mousetrap";
import Draggable from "vuedraggable";
import {filter as fuzzyFilter} from "fuzzy";
@ -209,10 +209,7 @@ import isIgnoredKeybind from "../js/helpers/isIgnoredKeybind";
import distance from "../js/helpers/distance";
import eventbus from "../js/eventbus";
import NetworkModel from "../../src/models/network";
import ChannelMode from "../../src/models/chan";
export default {
export default Vue.extend({
name: "NetworkList",
components: {
JoinChannel,
@ -484,5 +481,5 @@ export default {
});
},
},
};
});
</script>

View file

@ -274,10 +274,6 @@ function fuzzyGrep<T>(term: string, array: Array<T>) {
}
function rawNicks() {
if (!store.state.activeChannel) {
return [];
}
if (store.state.activeChannel.channel.users.length > 0) {
const users = store.state.activeChannel.channel.users.slice();

View file

@ -4,7 +4,7 @@ import store from "../store";
function input() {
const messageIds = [];
for (const message of store.state.activeChannel.channel.messages) {
for (const message of store.state.activeChannel?.channel.messages) {
let toggled = false;
for (const preview of message.previews) {
@ -22,7 +22,7 @@ function input() {
// Tell the server we're toggling so it remembers at page reload
if (!document.body.classList.contains("public") && messageIds.length > 0) {
socket.emit("msg:preview:toggle", {
target: store.state.activeChannel.channel.id,
target: store.state.activeChannel?.channel.id,
messageIds: messageIds,
shown: false,
});

View file

@ -22,7 +22,7 @@ function input() {
// Tell the server we're toggling so it remembers at page reload
if (!document.body.classList.contains("public") && messageIds.length > 0) {
socket.emit("msg:preview:toggle", {
target: store.state.activeChannel.channel.id,
target: store.state.activeChannel?.channel.id,
messageIds: messageIds,
shown: true,
});

View file

@ -4,9 +4,9 @@
// directory, so we iterate over its content, which is a map statically built by
// Webpack.
// Second argument says it's recursive, third makes sure we only load javascript.
const commands = require.context("./", true, /\.js$/);
const commands = require.context("./", true, /\.ts$/);
export default commands.keys().reduce((acc, path) => {
export default commands.keys().reduce<Record<string, unknown>>((acc, path) => {
const command = path.substring(2, path.length - 3);
if (command === "index") {

View file

@ -1,6 +1,7 @@
import socket from "../socket";
import eventbus from "../eventbus";
import type {ClientChan, ClientNetwork} from "../types";
import type {Methods} from "../vue";
type ContextMenuItem =
| ({
label: string;
@ -18,7 +19,11 @@ type ContextMenuItem =
type: "divider";
};
export function generateChannelContextMenu($root, channel, network) {
export function generateChannelContextMenu(
$root: Methods,
channel: ClientChan,
network: ClientNetwork
) {
const typeMap = {
lobby: "network",
channel: "chan",

View file

@ -1,8 +1,9 @@
import {ParsedStyle} from "./parseStyle";
// Return true if any section of "a" or "b" parts (defined by their start/end
import {Part} from "./merge";
// markers) intersect each other, false otherwise.
function anyIntersection(a: ParsedStyle, b: ParsedStyle) {
function anyIntersection(a: Part, b: Part) {
return (
(a.start <= b.start && b.start < a.end) ||
(a.start < b.end && b.end <= a.end) ||

View file

@ -1,4 +1,5 @@
const matchFormatting =
// eslint-disable-next-line no-control-regex
/\x02|\x1D|\x1F|\x16|\x0F|\x11|\x1E|\x03(?:[0-9]{1,2}(?:,[0-9]{1,2})?)?|\x04(?:[0-9a-f]{6}(?:,[0-9a-f]{6})?)?/gi;
export default (message: string) => message.replace(matchFormatting, "").trim();

View file

@ -1,15 +1,16 @@
import {ParsedStyle} from "./parseStyle";
// Create plain text entries corresponding to areas of the text that match no
// existing entries. Returns an empty array if all parts of the text have been
import {Part} from "./merge";
// parsed into recognizable entries already.
function fill(existingEntries: ParsedStyle[], text: string) {
function fill(existingEntries: Part[], text: string) {
let position = 0;
// Fill inner parts of the text. For example, if text is `foobarbaz` and both
// `foo` and `baz` have matched into an entry, this will return a dummy entry
// corresponding to `bar`.
const result = existingEntries.reduce((acc: Omit<ParsedStyle, "text">[], textSegment) => {
const result = existingEntries.reduce<Part[]>((acc, textSegment) => {
if (textSegment.start > position) {
acc.push({
start: position,

View file

@ -2,13 +2,14 @@
// ")", "[", "]", "{", "}", and "|" in string.
// See https://lodash.com/docs/#escapeRegExp
import escapeRegExp from "lodash/escapeRegExp";
import {Part} from "./merge";
// Given an array of channel prefixes (such as "#" and "&") and an array of user
// modes (such as "@" and "+"), this function extracts channels and nicks from a
// text.
// It returns an array of objects for each channel found with their start index,
// end index and channel name.
function findChannels(text, channelPrefixes, userModes) {
function findChannels(text: string, channelPrefixes: string[], userModes: string[]) {
// `userModePattern` is necessary to ignore user modes in /whois responses.
// For example, a voiced user in #thelounge will have a /whois response of:
// > foo is on the following channels: +#thelounge
@ -18,7 +19,7 @@ function findChannels(text, channelPrefixes, userModes) {
const channelPattern = `(?:^|\\s)[${userModePattern}]*([${channelPrefixPattern}][^ \u0007]+)`;
const channelRegExp = new RegExp(channelPattern, "g");
const result = [];
const result: ChannelPart[] = [];
let match;
do {
@ -38,4 +39,8 @@ function findChannels(text, channelPrefixes, userModes) {
return result;
}
export type ChannelPart = Part & {
channel: string;
};
export default findChannels;

View file

@ -1,10 +1,13 @@
const emojiRegExp = require("emoji-regex")();
import emojiRegExp from "emoji-regex";
import {Part} from "./merge";
function findEmoji(text) {
const result = [];
const regExp = emojiRegExp();
function findEmoji(text: string) {
const result: EmojiPart[] = [];
let match;
while ((match = emojiRegExp.exec(text))) {
while ((match = regExp.exec(text))) {
result.push({
start: match.index,
end: match.index + match[0].length,
@ -15,4 +18,8 @@ function findEmoji(text) {
return result;
}
export type EmojiPart = Part & {
emoji: string;
};
export default findEmoji;

View file

@ -1,4 +1,5 @@
import LinkifyIt, {Match} from "linkify-it";
import {Part} from "./merge";
type OurMatch = Match & {
noschema?: boolean;
@ -24,7 +25,8 @@ LinkifyIt.prototype.normalize = function normalize(match: OurMatch) {
}
};
const linkify = LinkifyIt().tlds(require("tlds")).tlds("onion", true);
import tlds from "tlds";
const linkify = LinkifyIt().tlds(tlds).tlds("onion", true);
// Known schemes to detect in text
const commonSchemes = [
@ -68,7 +70,7 @@ function findLinksWithSchema(text: string) {
return matches.filter((url) => !url.noschema).map(returnUrl);
}
function returnUrl(url: OurMatch) {
function returnUrl(url: OurMatch): LinkPart {
return {
start: url.index,
end: url.lastIndex,
@ -76,4 +78,8 @@ function returnUrl(url: OurMatch) {
};
}
export type LinkPart = Part & {
link: string;
};
export {findLinks, findLinksWithSchema};

View file

@ -1,17 +1,23 @@
import {Part} from "./merge";
const nickRegExp = /([\w[\]\\`^{|}-]+)/g;
function findNames(text, users) {
const result = [];
export type NamePart = Part & {
nick: string;
};
function findNames(text: string, nicks: string[]): NamePart[] {
const result: NamePart[] = [];
// Return early if we don't have any nicknames to find
if (users.length === 0) {
if (nicks.length === 0) {
return result;
}
let match;
while ((match = nickRegExp.exec(text))) {
if (users.indexOf(match[1]) > -1) {
if (nicks.indexOf(match[1]) > -1) {
result.push({
start: match.index,
end: match.index + match[1].length,

View file

@ -1,8 +1,17 @@
import anyIntersection from "./anyIntersection";
import fill from "./fill";
import {ChannelPart} from "./findChannels";
import {EmojiPart} from "./findEmoji";
import {NamePart} from "./findNames";
type TextPart = Part & {
text: string;
};
type Fragment = TextPart;
// Merge text part information within a styling fragment
function assign(textPart, fragment) {
function assign(textPart: Part, fragment: Fragment) {
const fragStart = fragment.start;
const start = Math.max(fragment.start, textPart.start);
const end = Math.min(fragment.end, textPart.end);
@ -11,10 +20,20 @@ function assign(textPart, fragment) {
return Object.assign({}, fragment, {start, end, text});
}
function sortParts(a, b) {
function sortParts(a: Part, b: Part) {
return a.start - b.start || b.end - a.end;
}
export type Part = {
start: number;
end: number;
fragments?: Fragment;
};
type MergedPart = TextPart | NamePart | EmojiPart | ChannelPart;
type MergedPartWithFragments = MergedPart & {fragments: Fragment[]};
// Merge the style fragments within the text parts, taking into account
// boundaries and text sections that have not matched to links or channels.
// For example, given a string "foobar" where "foo" and "bar" have been
@ -22,9 +41,13 @@ function sortParts(a, b) {
// different styles, the first resulting part will contain fragments "fo" and
// "o", and the second resulting part will contain "b" and "ar". "o" and "b"
// fragments will contain duplicate styling attributes.
function merge(textParts, styleFragments, cleanText) {
function merge(
textParts: MergedPart[],
styleFragments: Fragment[],
cleanText: string
): MergedPart[] {
// Remove overlapping parts
textParts = textParts.sort(sortParts).reduce((prev, curr) => {
textParts = textParts.sort(sortParts).reduce<MergedPart[]>((prev, curr) => {
const intersection = prev.some((p) => anyIntersection(p, curr));
if (intersection) {
@ -37,11 +60,14 @@ function merge(textParts, styleFragments, cleanText) {
// Every section of the original text that has not been captured in a "part"
// is filled with "text" parts, dummy objects with start/end but no extra
// metadata.
const allParts = textParts.concat(fill(textParts, cleanText)).sort(sortParts); // Sort all parts identified based on their position in the original text
const filled = fill(textParts, cleanText) as TextPart[];
const allParts: MergedPart[] = [...textParts, ...filled].sort(sortParts); // Sort all parts identified based on their position in the original text
// Distribute the style fragments within the text parts
return allParts.map((textPart) => {
textPart.fragments = styleFragments
// TODO: remove any type casting.
(textPart as any).fragments = styleFragments
.filter((fragment) => anyIntersection(textPart, fragment))
.map((fragment) => assign(textPart, fragment));

View file

@ -9,11 +9,17 @@ import LinkPreviewToggle from "../../components/LinkPreviewToggle.vue";
import LinkPreviewFileSize from "../../components/LinkPreviewFileSize.vue";
import InlineChannel from "../../components/InlineChannel.vue";
import Username from "../../components/Username.vue";
import {VNode} from "vue";
import Network from "src/models/network";
import {Message} from "src/models/msg";
const emojiModifiersRegex = /[\u{1f3fb}-\u{1f3ff}]|\u{fe0f}/gu;
type createElement = (tag: string, props: any, children: any) => VNode;
// Create an HTML `span` with styling information for a given fragment
function createFragment(fragment, createElement) {
// TODO: remove any
function createFragment(fragment: Record<any, string>, createElement: createElement) {
const classes = [];
if (fragment.bold) {
@ -44,7 +50,7 @@ function createFragment(fragment, createElement) {
classes.push("irc-monospace");
}
const data = {};
const data = {} as any;
let hasData = false;
if (classes.length > 0) {
@ -68,7 +74,7 @@ function createFragment(fragment, createElement) {
// Transform an IRC message potentially filled with styling control codes, URLs,
// nicknames, and channels into a string of HTML elements to display on the client.
function parse(createElement, text, message = undefined, network = undefined) {
function parse(createElement: createElement, text: string, message?: Message, network?: Network) {
// Extract the styling information and get the plain text version from it
const styleFragments = parseStyle(text);
const cleanText = styleFragments.map((fragment) => fragment.text).join("");
@ -76,14 +82,15 @@ function parse(createElement, text, message = undefined, network = undefined) {
// On the plain text, find channels and URLs, returned as "parts". Parts are
// arrays of objects containing start and end markers, as well as metadata
// depending on what was found (channel or link).
const channelPrefixes = network ? network.serverOptions.CHANTYPES : ["#", "&"];
const channelPrefixes = network?.serverOptions?.CHANTYPES || ["#", "&"];
const userModes = network?.serverOptions?.PREFIX.symbols || ["!", "@", "%", "+"];
const channelParts = findChannels(cleanText, channelPrefixes, userModes);
const linkParts = findLinks(cleanText);
const emojiParts = findEmoji(cleanText);
const nameParts = findNames(cleanText, message ? message.users || [] : []);
// TODO: remove type casting.
const nameParts = findNames(cleanText, message ? (message.users as string[]) || [] : []);
const parts = channelParts.concat(linkParts).concat(emojiParts).concat(nameParts);
const parts = [...channelParts, ...linkParts, ...emojiParts, ...nameParts];
// The channel the message belongs to might not exist if the user isn't joined to it.
const messageChannel = message ? message.channel : null;
@ -91,12 +98,12 @@ function parse(createElement, text, message = undefined, network = undefined) {
// Merge the styling information with the channels / URLs / nicks / text objects and
// generate HTML strings with the resulting fragments
return merge(parts, styleFragments, cleanText).map((textPart) => {
const fragments = textPart.fragments.map((fragment) =>
const fragments = textPart.fragments?.map((fragment) =>
createFragment(fragment, createElement)
);
// Wrap these potentially styled fragments with links and channel buttons
if (textPart.link) {
if ("link" in textPart) {
const preview =
message &&
message.previews &&

View file

@ -1,4 +1,4 @@
export default (stringUri) => {
export default (stringUri: string) => {
const data = {};
try {

View file

@ -1,4 +1,4 @@
export default (count) => {
export default (count: number) => {
if (count < 1000) {
return count.toString();
}

View file

@ -5,6 +5,12 @@ import store from "../store";
import location from "../location";
let lastServerHash = null;
declare global {
interface Window {
g_TheLoungeRemoveLoading: () => void;
}
}
socket.on("auth:success", function () {
store.commit("currentUserVisibleError", "Loading messages…");
updateLoadingMessage();
@ -83,10 +89,10 @@ function showSignIn() {
}
}
function reloadPage(message) {
function reloadPage(message: string) {
socket.disconnect();
store.commit("currentUserVisibleError", message);
location.reload(true);
location.reload();
}
function updateLoadingMessage() {

View file

@ -1,6 +1,6 @@
import io, {Socket} from "socket.io-client";
const socket = io({
const socket: Socket = io({
transports: JSON.parse(document.body.dataset.transports || "['polling', 'websocket']"),
path: window.location.pathname + "socket.io/",
autoConnect: false,

View file

@ -2,6 +2,7 @@ import Vue from "vue";
import Vuex from "vuex";
import {createSettingsStore} from "./store-settings";
import storage from "./localStorage";
import {ClientChan, ClientNetwork} from "./types";
const appName = document.title;
@ -21,15 +22,15 @@ function detectDesktopNotificationState() {
export type State = {
appLoaded: boolean;
activeChannel?: {
network: Network;
activeChannel: {
network: ClientNetwork;
channel: ClientChan;
};
currentUserVisibleError: string | null;
desktopNotificationState: "granted" | "blocked" | "nohttps" | "unsupported";
isAutoCompleting: boolean;
isConnected: boolean;
networks: Network[];
networks: ClientNetwork[];
// TODO: type
mentions: any[];
hasServiceWorker: boolean;

View file

@ -1,4 +1,5 @@
import Chan from "../src/models/chan";
import Chan from "../../src/models/chan";
import Network from "../../src/models/network";
declare module "*.vue" {
import Vue from "vue";
@ -10,4 +11,9 @@ interface LoungeWindow extends Window {
type ClientChan = Chan & {
moreHistoryAvailable: boolean;
editTopic: boolean;
};
type ClientNetwork = Network & {
isJoinChannelShown: boolean;
};

View file

@ -12,22 +12,31 @@ import eventbus from "./eventbus";
import "./socket-events";
import "./webpush";
import "./keybinds";
import {ClientChan} from "./types";
const favicon = document.getElementById("favicon");
const faviconNormal = favicon?.getAttribute("href") || "";
const faviconAlerted = favicon?.dataset.other || "";
new Vue({
type Data = {};
export type Methods = {
switchToChannel: (channel: ClientChan) => void;
closeChannel: (channel: ClientChan) => void;
};
type Computed = {};
type Props = {};
new Vue<Data, Methods, Computed, Props>({
el: "#viewport",
router,
mounted() {
socket.open();
},
methods: {
switchToChannel(channel: Channel) {
switchToChannel(channel: ClientChan) {
navigate("RoutedChat", {id: channel.id});
},
closeChannel(channel: Channel) {
closeChannel(channel: ClientChan) {
if (channel.type === "lobby") {
eventbus.emit(
"confirm-dialog",

View file

@ -6,6 +6,7 @@
"files": [
"../package.json"
] /* If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. When a 'files' property is specified, only those files and those specified by 'include' are included. */,
// "exclude": [],
"compilerOptions": {
"sourceMap": false /*Create source map files for emitted JavaScript files. See more: https://www.typescriptlang.org/tsconfig#sourceMap */,
"jsx": "preserve" /* Specify what JSX code is generated. */,
@ -19,6 +20,8 @@
"module": "es2015",
"moduleResolution": "node",
// TODO: Remove eventually, this is due to typescript checking vue files that don't have lang="ts".
"checkJs": false,
// TODO: Remove eventually
"noImplicitAny": false /*Enable error reporting for expressions and declarations with an implied any type. See more: https://www.typescriptlang.org/tsconfig#noImplicitAny */
} /* Instructs the TypeScript compiler how to compile .ts files. */

View file

@ -25,7 +25,7 @@
"lint:tsc": "tsc --noEmit",
"start": "node src/dist/src/index start",
"test": "run-p --aggregate-output --continue-on-error lint:* test:*",
"test:mocha": "NODE_ENV=test webpack --mode=development && nyc --nycrc-path=test/.nycrc-mocha.json mocha --colors --config=test/.mocharc.yml",
"test:mocha": "NODE_ENV=test webpack --mode=development && nyc --nycrc-path=test/.nycrc-mocha.json mocha --require ts-node/register --colors --config=test/.mocharc.yml",
"watch": "webpack --watch"
},
"keywords": [

View file

@ -17,7 +17,7 @@ import SqliteMessageStorage from "./plugins/messageStorage/sqlite";
import TextFileMessageStorage from "./plugins/messageStorage/text";
import Network, {NetworkWithIrcFramework} from "./models/network";
import ClientManager from "./clientManager";
import {MessageStorage} from "./types/plugins/messageStorage";
import {MessageStorage, SearchQuery, SearchResponse} from "./plugins/messageStorage/types";
const events = [
"away",
@ -598,9 +598,15 @@ class Client {
}
}
search(query: string) {
search(query: SearchQuery): Promise<SearchResponse> {
if (this.messageProvider === undefined) {
return Promise.resolve([]);
return Promise.resolve({
results: [],
target: "",
networkUuid: "",
offset: 0,
searchTerm: query?.searchTerm,
});
}
return this.messageProvider.search(query);

View file

@ -1,6 +1,7 @@
import Client from "../../client";
import Chan, {Channel} from "../../models/chan";
import Network, {NetworkWithIrcFramework} from "../../models/network";
import {PackageInfo} from "../packages";
export type PluginInputHandler = (
this: Client,
@ -97,7 +98,7 @@ const getCommands = () =>
.concat(passThroughCommands)
.sort();
const addPluginCommand = (packageInfo, command, func) => {
const addPluginCommand = (packageInfo: PackageInfo, command, func) => {
func.packageInfo = packageInfo;
pluginCommands.set(command, func);
};

View file

@ -7,8 +7,9 @@ import Config from "../../config";
import Msg, {Message} from "../../models/msg";
import Client from "../../client";
import Chan, {Channel} from "../../models/chan";
import type {SqliteMessageStorage as ISqliteMessageStorage} from "../../types/plugins/messageStorage";
import type {SearchResponse, SqliteMessageStorage as ISqliteMessageStorage} from "./types";
import Network from "../../models/network";
import {SearchQuery} from "./types";
// TODO; type
let sqlite3: any;
@ -209,9 +210,15 @@ class SqliteMessageStorage implements ISqliteMessageStorage {
}) as Promise<Message[]>;
}
search(query: {searchTerm: string; networkUuid: string; channelName: string; offset: string}) {
search(query: SearchQuery): Promise<SearchResponse> {
if (!this.isEnabled) {
return Promise.resolve([]);
return Promise.resolve({
results: [],
target: "",
networkUuid: "",
offset: 0,
searchTerm: query?.searchTerm,
});
}
// Using the '@' character to escape '%' and '_' in patterns.
@ -243,7 +250,7 @@ class SqliteMessageStorage implements ISqliteMessageStorage {
if (err) {
reject(err);
} else {
const response = {
const response: SearchResponse = {
searchTerm: query.searchTerm,
target: query.channelName,
networkUuid: query.networkUuid,
@ -263,7 +270,8 @@ class SqliteMessageStorage implements ISqliteMessageStorage {
export default SqliteMessageStorage;
function parseSearchRowsToMessages(id, rows) {
// TODO: type any
function parseSearchRowsToMessages(id: string, rows: any[]) {
const messages: Msg[] = [];
for (const row of rows) {

View file

@ -4,7 +4,7 @@ import filenamify from "filenamify";
import log from "../../log";
import Config from "../../config";
import {MessageStorage} from "../../types/plugins/messageStorage";
import {MessageStorage} from "./types";
import Client from "../../client";
import Channel from "../../models/chan";
import {Message, MessageType} from "../../models/msg";

View file

@ -22,6 +22,22 @@ interface MessageStorage {
canProvideMessages(): boolean;
}
export type SearchQuery = {
searchTerm: string;
networkUuid: string;
channelName: string;
offset: string;
};
export type SearchResponse = Omit<SearchQuery, "channelName" | "offset"> & {
results: Message[];
target: string;
offset: number;
};
type SearchFunction = (query: SearchQuery) => Promise<SearchResponse>;
export interface SqliteMessageStorage extends MessageStorage {
database: Database;
search: SearchFunction;
}

View file

@ -12,12 +12,14 @@ import fs from "fs";
import Utils from "../../command-line/utils";
import Client from "../../client";
type PackageInfo = {
export type PackageInfo = {
packageName: string;
thelounge?: {supports: any};
version: string;
type?: string;
files?: string[];
// Legacy support
name?: string;
};
const stylesheets: string[] = [];

View file

@ -1,12 +1,13 @@
import {PackageInfo} from "./index";
import Client from "../../client";
import Chan from "../../models/chan";
import Msg, {MessageType, UserInMessage} from "../../models/msg";
export default class PublicClient {
private client: Client;
private packageInfo: any;
private packageInfo: PackageInfo;
constructor(client, packageInfo) {
constructor(client: Client, packageInfo: PackageInfo) {
this.client = client;
this.packageInfo = packageInfo;
}
@ -24,7 +25,7 @@ export default class PublicClient {
*
* @param {Object} attributes
*/
createChannel(attributes) {
createChannel(attributes: Partial<Chan>) {
return this.client.createChannel(attributes);
}

View file

@ -1,13 +1,13 @@
{
"extends": "../tsconfig.base.json" /* Path to base configuration file to inherit from. Requires TypeScript version 2.1 or later. */,
"include": [
"**/*"
"**/*",
"../client/js/helpers/ircmessageparser/*.ts"
] /* Specifies a list of glob patterns that match files to be included in compilation. If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. Requires TypeScript version 2.0 or later. */,
"files": [
"../babel.config.cjs",
"../client/js/constants.ts",
"../client/js/helpers/ircmessageparser/cleanIrcMessage.ts",
"../client/js/helpers/ircmessageparser/findLinks.ts",
"../babel.config.cjs",
"../defaults/config.js",
"../package.json",
"../webpack.config.ts"

View file

@ -1,2 +1 @@
import "./modules";
import "./plugins";

View file

@ -1 +0,0 @@
import "./messageStorage";

View file

@ -5,4 +5,4 @@ reporter: dot
interactive: false
spec: "test/**/*.ts"
ignore: "test/client/**"
require: "test/fixtures/env"
require: "test/fixtures/env.ts"

View file

@ -1,13 +1,13 @@
"use strict";
var config = require("../../../defaults/config.js");
import config from "../../../defaults/config.js";
config.defaults.name = "Example IRC Server";
config.defaults.host = "irc.example.com";
config.public = true;
config.prefetch = true;
config.host = config.bind = "127.0.0.1";
config.host = bind = "127.0.0.1";
config.port = 61337;
config.transports = ["websocket"];
module.exports = config;
export default config;

View file

@ -10,7 +10,7 @@ config.setHome(home);
import STSPolicies from "../../src/plugins/sts"; // Must be imported *after* setHome
const mochaGlobalTeardown = async function () {
const mochaGlobalTeardown = function () {
STSPolicies.refresh.cancel(); // Cancel debounced function, so it does not write later
fs.unlinkSync(STSPolicies.stsFile);
};

View file

@ -6,7 +6,8 @@
"../src"
] /* Specifies a list of glob patterns that match files to be included in compilation. If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. Requires TypeScript version 2.0 or later. */,
"files": [
"../babel.config.cjs"
"../babel.config.cjs",
"../src/helper.ts"
] /* If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. When a 'files' property is specified, only those files and those specified by 'include' are included. */,
"ts-node": {
"files": true

View file

@ -47,7 +47,8 @@
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
/* outDir is necessary because otherwise the built output for files like babel.config.cjs would overwrite the input. */
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */

View file

@ -5,6 +5,7 @@
"./babel.config.cjs",
"./src/helper.ts"
] /* If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. When a 'files' property is specified, only those files and those specified by 'include' are included. */,
// "exclude": [],
"references": [
{
"path": "./client" /* Path to referenced tsconfig or to folder containing tsconfig. */

View file

@ -187,6 +187,10 @@ export default (env: any, argv: any) => {
filename: "css/style.css",
}),
new MiniCssExtractPlugin({
filename: "css/style.css",
}),
// Client tests that require Vue may end up requireing socket.io
new webpack.NormalModuleReplacementPlugin(
/js(\/|\\)socket\.js/,