Fix even even even more bugs

This commit is contained in:
Kalle
2026-08-05 16:33:23 +03:00
parent e65dd86683
commit b3e723e245
17 changed files with 517 additions and 29 deletions

View File

@@ -0,0 +1,34 @@
import { describe, expect, test } from "vitest";
import * as CalendarEventFactory from "~/db/seed/factories/CalendarEventFactory";
import * as UserFactory from "~/db/seed/factories/UserFactory";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import { wrappedLoader } from "~/utils/Test";
import type { GetCalendarWeekResponse } from "../schema";
import { loader } from "./calendar.$year.$week";
const weekLoader = wrappedLoader<Response>({ loader });
const fetchWeek = async (year: number, week: number) => {
const response = await weekLoader({
params: { year: String(year), week: String(week) },
});
return (await response.json()) as GetCalendarWeekResponse;
};
describe("GET /api/calendar/:year/:week", () => {
test("an event starting exactly at the week boundary is returned for exactly one week", async () => {
const user = await UserFactory.createRegular();
// Monday 2025-01-13 00:00 UTC, i.e. Sunday 7 PM EST — the boundary
// between ISO weeks 2 and 3 of 2025
await CalendarEventFactory.create({
authorId: user.id,
startTimes: [dateToDatabaseTimestamp(new Date("2025-01-13T00:00:00Z"))],
});
const weekTwoEvents = await fetchWeek(2025, 2);
const weekThreeEvents = await fetchWeek(2025, 3);
expect(weekTwoEvents.length + weekThreeEvents.length).toBe(1);
});
});

View File

@@ -55,7 +55,7 @@ function fetchEventsOfWeek(args: { week: number; year: number }) {
">=",
dateToDatabaseTimestamp(startTime),
)
.where("CalendarEventDate.startsAt", "<=", dateToDatabaseTimestamp(endTime))
.where("CalendarEventDate.startsAt", "<", dateToDatabaseTimestamp(endTime))
.where("CalendarEvent.hidden", "=", 0)
.orderBy("CalendarEventDate.startsAt", "asc")
.execute();

View File

@@ -17,6 +17,16 @@ describe("chatCodeVisible", () => {
expect(result).toBe(true);
});
test("not visible when just past expiration window", () => {
const result = chatAccessible({
isStaff: false,
expiresAfterDays: 1,
comparedTo: sub(new Date(), { days: 1, hours: 12 }),
});
expect(result).toBe(false);
});
test("not visible when past expiration window", () => {
const result = chatAccessible({
isStaff: false,

View File

@@ -12,7 +12,7 @@ export function chatAccessible(args: {
}): boolean {
const extraDays = args.isStaff ? STAFF_EXTRA_DAYS : 0;
return (
differenceInDays(new Date(), args.comparedTo) <=
differenceInDays(new Date(), args.comparedTo) <
args.expiresAfterDays + extraDays
);
}

View File

@@ -0,0 +1,24 @@
import { describe, expect, test } from "vitest";
import type { LFGLoaderPost } from "../routes/lfg";
import { filterPosts } from "./filtering";
const postOfType = (type: LFGLoaderPost["type"]) =>
({
type,
author: { weaponPool: [] },
team: null,
}) as unknown as LFGLoaderPost;
describe("filterPosts", () => {
test("a weapon filter with no weapons selected shows every post", () => {
const posts = [postOfType("PLAYER_FOR_TEAM"), postOfType("COACH_FOR_TEAM")];
const filtered = filterPosts(
posts,
[{ _tag: "Weapon", weaponSplIds: [] }],
new Map(),
);
expect(filtered).toHaveLength(2);
});
});

View File

@@ -31,7 +31,7 @@ function filterMatchesPost(
if (post.type === "COACH_FOR_TEAM") {
// not visible in the UI
if (
filter._tag === "Weapon" ||
(filter._tag === "Weapon" && filter.weaponSplIds.length > 0) ||
filter._tag === "MaxTier" ||
filter._tag === "MinTier"
) {

View File

@@ -14,12 +14,16 @@ const plusTierFilter: LFGFilter = { _tag: "PlusTier", tier: 1 };
const maxTierFilter: LFGFilter = { _tag: "MaxTier", tier: "GOLD" };
const minTierFilter: LFGFilter = { _tag: "MinTier", tier: "BRONZE" };
// the filter LFGAddFilterButton inserts when the user picks "Weapon"
const emptyWeaponFilter: LFGFilter = { _tag: "Weapon", weaponSplIds: [] };
describe("lfgSearchParams", () => {
it("round-trips", () => {
assertRoundTrips(lfgSearchParams, {
q: [
[],
[weaponFilter],
[emptyWeaponFilter],
[typeFilter],
[timezoneFilter],
[languageFilter],

View File

@@ -92,15 +92,16 @@ export function filterToSmallStr(filter: LFGFilter): string {
export function smallStrToFilter(s: string): LFGFilter | null {
const [tag, val] = s.split(".");
if (!tag || !val) return null;
if (!tag || val === undefined) return null;
switch (tag) {
case "w": {
// an empty weapon filter is valid, it's what the add filter button inserts
const weaponIds = val
.split(",")
.filter(Boolean)
.map((x) => Number.parseInt(x, 10) as MainWeaponId)
.filter((x) => x !== null && x !== undefined);
if (weaponIds.length === 0) return null;
.filter((x) => !Number.isNaN(x));
return {
_tag: "Weapon",
weaponSplIds: weaponIds,

View File

@@ -91,6 +91,71 @@ describe("swiss standings - losses against tied", () => {
expect(standing.stats?.lossesAgainstTied).toBe(0); // they lost against "Tidy Tidings" but that team dropped out before final round
});
it("should ignore a dropped out team with an identical record (losses against tied)", () => {
const data = Engine.create({
type: "swiss",
seeding: [1, 2, 3, 4, 5, 6],
settings: { groupCount: 1, roundCount: 3 },
});
const playedMatch = (
id: number,
roundIdx: number,
number: number,
winnerId: number,
loserId: number,
): MatchData => ({
id,
stageId: data.stage[0].id,
groupId: data.group[0].id,
roundId: data.round[roundIdx].id,
number,
opponent1: { id: winnerId },
opponent2: { id: loserId },
winnerSide: "opponent1",
});
// teams 1 and 6 both finish 2-1; team 1's only loss is to team 6,
// who dropped out after the swiss ended
data.match = [
playedMatch(0, 0, 1, 1, 2),
playedMatch(1, 0, 2, 3, 4),
playedMatch(2, 0, 3, 5, 6),
playedMatch(3, 1, 1, 1, 3),
playedMatch(4, 1, 2, 2, 5),
playedMatch(5, 1, 3, 6, 4),
playedMatch(6, 2, 1, 6, 1),
playedMatch(7, 2, 2, 3, 5),
playedMatch(8, 2, 3, 2, 4),
];
const tournament = testTournament({
data,
ctx: {
settings: {
bracketProgression: [
{
type: "swiss",
name: "Main Bracket",
requiresCheckIn: false,
settings: {},
},
],
},
teams: [1, 2, 3, 4, 5, 6].map((teamId) =>
tournamentCtxTeam(teamId, { droppedOut: teamId === 6 ? 1 : 0 }),
),
},
});
const standing = tournament
.bracketByIdx(0)
?.standings.find((standing) => standing.team.id === 1);
invariant(standing, "Standing not found");
expect(standing.stats?.lossesAgainstTied).toBe(0);
});
const inProgressSwissTestTournament = () => {
const data = Engine.create({
type: "swiss",

View File

@@ -34,7 +34,9 @@ export class SwissBracket extends Bracket {
const relevantMatchesFinished = this.standingsAreFinal;
if (advanceThreshold) {
// explicit placements override the threshold, e.g. a consolation
// bracket for the teams that did not advance
if (advanceThreshold && placements.length === 0) {
return {
relevantMatchesFinished,
teams: standings
@@ -316,10 +318,17 @@ export class SwissBracket extends Bracket {
});
}
// wins against tied
const droppedOutTeams = this.tournament.ctx.teams
.filter((t) => t.droppedOut)
.map((t) => t.id);
// wins against tied, results against dropped out teams don't count
for (const team of teams) {
if (droppedOutTeams.includes(team.id)) continue;
for (const team2 of teams) {
if (team.id === team2.id) continue;
if (droppedOutTeams.includes(team2.id)) continue;
if (
team.setWins !== team2.setWins ||
// check also set losses to account for dropped teams
@@ -358,9 +367,6 @@ export class SwissBracket extends Bracket {
}
}
const droppedOutTeams = this.tournament.ctx.teams
.filter((t) => t.droppedOut)
.map((t) => t.id);
placements.push(
...teams
.sort((a, b) => {

View File

@@ -1,5 +1,12 @@
import { describe, expect, it, test } from "vitest";
import type { MatchData } from "~/features/tournament-bracket/core/engine/types";
import type {
BracketData,
GeneratedRound,
MatchData,
} from "~/features/tournament-bracket/core/engine/types";
import { unwrap } from "~/utils/result";
import * as Engine from "./engine";
import type * as Progression from "./Progression";
import { Tournament } from "./Tournament";
import {
IN_THE_ZONE_32,
@@ -498,6 +505,101 @@ describe("Resolving the team a user is a member of", () => {
});
});
describe("teamMemberOfProgressStatus in swiss", () => {
const teamsWithMembers = [1, 2, 3, 4].map((teamId) =>
tournamentCtxTeam(teamId, { memberUserIds: [100 + teamId] }),
);
it("resolves an early advanced team as waiting for the follow-up bracket", () => {
const data = playOutEarlyAdvanceSwiss(progressions.swissEarlyAdvance);
const tournament = testTournament({
data,
ctx: {
settings: { bracketProgression: progressions.swissEarlyAdvance },
teams: teamsWithMembers,
},
});
expect(
tournament.bracketByIdx(1)?.seeding,
"test setup: the advanced team should be in the top cut preview",
).toContain(1);
expect(tournament.teamMemberOfProgressStatus({ id: 101 })?.type).toBe(
"WAITING_FOR_BRACKET",
);
});
it("resolves a dropped out team's status as thanks for playing", () => {
const data = Engine.create({
type: "swiss",
seeding: [1, 2, 3, 4],
settings: {},
});
finishPendingMatches(data);
const tournament = testTournament({
data,
ctx: {
settings: { bracketProgression: progressions.swissOneGroup },
teams: [1, 2, 3, 4].map((teamId) =>
tournamentCtxTeam(teamId, {
memberUserIds: [100 + teamId],
droppedOut: teamId === 4 ? 1 : 0,
}),
),
},
});
expect(tournament.teamMemberOfProgressStatus({ id: 104 })?.type).toBe(
"THANKS_FOR_PLAYING",
);
});
});
describe("Swiss early advance bracket sourcing", () => {
const progressionWithConsolation: Progression.ParsedBracket[] = [
{
name: "Main Bracket",
type: "swiss",
requiresCheckIn: false,
settings: { advanceThreshold: 3 },
},
{
name: "Top Cut",
type: "single_elimination",
requiresCheckIn: false,
settings: {},
sources: [{ bracketIdx: 0, placements: [] }],
},
{
name: "Consolation",
type: "single_elimination",
requiresCheckIn: false,
settings: {},
sources: [{ bracketIdx: 0, placements: [2, 3, 4] }],
},
];
it("sources a consolation bracket by its placements instead of the advance threshold", () => {
const data = playOutEarlyAdvanceSwiss(progressionWithConsolation);
const tournament = testTournament({
data,
ctx: { settings: { bracketProgression: progressionWithConsolation } },
});
expect(
tournament.bracketByIdx(1)?.seeding,
"test setup: the swiss winner should be in the top cut",
).toContain(1);
expect(
tournament.bracketByIdx(2)?.seeding,
"the swiss winner advanced to the top cut and should not also be in the consolation bracket",
).not.toContain(1);
});
});
describe("teamById division seeds", () => {
it("assigns unique seeds within a division when a late registrant has null startingBracketIdx", () => {
const tournament = testTournament({
@@ -540,3 +642,62 @@ describe("teamById division seeds", () => {
expect(new Set(divATeamSeeds).size).toBe(3);
});
});
/**
* Plays a 4 team, 5 round, advance threshold 3 swiss all the way to its end.
* Team 1 wins rounds 1-3 locking their top cut spot, after which the pairing
* excludes them from the remaining rounds.
*/
function playOutEarlyAdvanceSwiss(
progression: Progression.ParsedBracket[],
): BracketData {
const data = Engine.create({
type: "swiss",
seeding: [1, 2, 3, 4],
settings: { advanceThreshold: 3 },
});
const groupId = data.group[0].id;
finishPendingMatches(data);
for (let roundNumber = 2; roundNumber <= 5; roundNumber++) {
const bracket = testTournament({
data,
ctx: { settings: { bracketProgression: progression } },
}).bracketByIdx(0)!;
const generated = Engine.generateRound(bracket.data, {
groupId,
standings: bracket.standings,
settings: bracket.settings,
});
if (!generated.ok) break;
appendGeneratedRound(data, unwrap(generated));
finishPendingMatches(data);
}
return data;
}
/** Finishes every pending match, team 1 always winning theirs and otherwise the home side. */
function finishPendingMatches(data: BracketData) {
for (const match of data.match) {
if (match.winnerSide !== null || !match.opponent2) continue;
match.winnerSide = match.opponent2.id === 1 ? "opponent2" : "opponent1";
}
}
function appendGeneratedRound(data: BracketData, round: GeneratedRound) {
let id = Math.max(...data.match.map((match) => match.id)) + 1;
for (const match of round.matches) {
data.match.push({
id: id++,
stageId: data.stage[0].id,
groupId: round.groupId,
roundId: round.roundId,
number: match.number,
opponent1: match.opponent1,
opponent2: match.opponent2,
winnerSide: null,
});
}
}

View File

@@ -0,0 +1,47 @@
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import { testTournament } from "./tests/test-utils";
// in its own file because changing the process timezone affects every Date
// operation of the worker while these tests run
describe("regularCheckInStartsAt in a DST observing timezone", () => {
const ORIGINAL_TZ = process.env.TZ;
beforeAll(() => {
process.env.TZ = "America/New_York";
});
afterAll(() => {
if (ORIGINAL_TZ === undefined) {
delete process.env.TZ;
} else {
process.env.TZ = ORIGINAL_TZ;
}
});
it("check-in opens one hour of real time before the start also on the spring forward night", () => {
// 3:30 AM EDT on the night the USA moves to daylight saving time
const startsAt = new Date("2025-03-09T07:30:00Z");
const tournament = testTournament({
ctx: { startsAt: dateToDatabaseTimestamp(startsAt) },
});
expect(tournament.regularCheckInStartsAt.getTime()).toBe(
startsAt.getTime() - 60 * 60 * 1000,
);
});
it("check-in opens one hour of real time before the start also on the fall back night", () => {
// 1:30 AM EST on the night the USA moves off daylight saving time
const startsAt = new Date("2025-11-02T06:30:00Z");
const tournament = testTournament({
ctx: { startsAt: dateToDatabaseTimestamp(startsAt) },
});
expect(tournament.regularCheckInStartsAt.getTime()).toBe(
startsAt.getTime() - 60 * 60 * 1000,
);
});
});

View File

@@ -24,6 +24,7 @@ import { logger } from "~/utils/logger";
import { assertUnreachable } from "~/utils/types";
import { groupNumberToLetters } from "../tournament-bracket-utils";
import { type Bracket, createBracket } from "./Bracket";
import { calculateTeamStatus } from "./engine/swiss/team-status";
import { getRounds } from "./rounds";
import * as Seeding from "./Seeding";
import type { TournamentData } from "./Tournament.server";
@@ -855,9 +856,9 @@ export class Tournament {
/** Date when the regular check-in is scheduled to start. */
get regularCheckInStartsAt() {
const result = new Date(this.ctx.startsAt);
result.setMinutes(result.getMinutes() - 60);
return result;
// elapsed time math instead of wall clock math so that the window
// stays one hour long across a DST transition
return new Date(this.ctx.startsAt.getTime() - 60 * 60 * 1000);
}
/** Date when the regular check-in is scheduled to start. */
@@ -1176,20 +1177,32 @@ export class Tournament {
for (const bracket of startedBrackets) {
if (bracket.type !== "swiss") continue;
// dropped out teams and teams whose run ended early via the advance
// threshold are excluded from the pairing, so no round is coming for them
if (bracket.everyMatchOver || team.droppedOut) continue;
// TODO: both seeding and participantTournamentTeamIds are used for the same thing
const isParticipant = bracket.participantTournamentTeamIds.includes(
team.id,
);
const setsGeneratedCount = bracket.data.match.filter(
const teamsMatches = bracket.data.match.filter(
(match) =>
match.opponent1?.id === team.id || match.opponent2?.id === team.id,
).length;
);
const notAllRoundsGenerated =
setsGeneratedCount !== bracket.swissRoundCount;
teamsMatches.length !== bracket.swissRoundCount;
if (isParticipant && notAllRoundsGenerated) {
const advanceThreshold = bracket.settings?.advanceThreshold;
const runEndedEarly = advanceThreshold
? calculateTeamStatus({
...swissTeamRecord(teamsMatches, team.id),
advanceThreshold,
roundCount: bracket.swissRoundCount,
}) !== "active"
: false;
if (isParticipant && notAllRoundsGenerated && !runEndedEarly) {
return { type: "WAITING_FOR_ROUND" } as const;
}
}
@@ -1446,6 +1459,36 @@ export class Tournament {
}
}
/** A team's swiss set record off its match data, a BYE counting as a win. */
function swissTeamRecord(matches: MatchData[], teamId: number) {
let wins = 0;
let losses = 0;
for (const match of matches) {
const side =
match.opponent1?.id === teamId
? "opponent1"
: match.opponent2?.id === teamId
? "opponent2"
: null;
if (!side) continue;
if (!match.opponent1 || !match.opponent2) {
wins++;
continue;
}
if (!match.winnerSide) continue;
if (match.winnerSide === side) {
wins++;
} else {
losses++;
}
}
return { wins, losses };
}
/** The parts of a tournament that decide who organizes it. */
export type TournamentOrganizerCtx = Pick<
TournamentData["ctx"],

View File

@@ -1,6 +1,8 @@
import { beforeEach, describe, expect, test } from "vitest";
import * as CalendarEventFactory from "~/db/seed/factories/CalendarEventFactory";
import * as TournamentOrganizationFactory from "~/db/seed/factories/TournamentOrganizationFactory";
import * as UserFactory from "~/db/seed/factories/UserFactory";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import * as TournamentOrganizationRepository from "./TournamentOrganizationRepository.server";
import { seedOrgEventWithParticipants } from "./test-utils";
@@ -59,6 +61,51 @@ describe("findByUserId", () => {
});
});
describe("findEventsByMonth", () => {
beforeEach(async () => {
await users.create(1);
});
const seedOrgEventAt = async (startTime: Date) => {
const org = await TournamentOrganizationFactory.create({
ownerId: users.id(1),
});
await CalendarEventFactory.create({
authorId: users.id(1),
organizationId: org.id,
startTimes: [dateToDatabaseTimestamp(startTime)],
});
return org;
};
test("includes an event starting within the timezone margin before the month", async () => {
const org = await seedOrgEventAt(new Date("2024-12-31T22:00:00Z"));
const events = await TournamentOrganizationRepository.findEventsByMonth({
month: 0,
year: 2025,
organizationId: org.id,
});
expect(events).toHaveLength(1);
});
test("includes an event starting within the timezone margin after the month", async () => {
// Jan 31, 6 PM in America/Los_Angeles — the org page calendar renders
// this into the January grid for viewers west of UTC
const org = await seedOrgEventAt(new Date("2025-02-01T02:00:00Z"));
const events = await TournamentOrganizationRepository.findEventsByMonth({
month: 0,
year: 2025,
organizationId: org.id,
});
expect(events).toHaveLength(1);
});
});
describe("countActiveParticipants", () => {
const WINDOW_START = 1_700_000_000;
const WINDOW_END = WINDOW_START + 60 * 60 * 24 * 31;

View File

@@ -359,11 +359,11 @@ export async function findEventsByMonth({
organizationId,
}: FindEventsByMonthArgs) {
const firstDayOfTheMonth = new Date(Date.UTC(year, month, 1));
const lastDayOfTheMonth = new Date(Date.UTC(year, month + 1, 0));
const firstDayOfTheNextMonth = new Date(Date.UTC(year, month + 1, 1));
// a bit of margin for timezones, filtered in the frontend code
firstDayOfTheMonth.setUTCDate(firstDayOfTheMonth.getUTCDate() - 1);
lastDayOfTheMonth.setUTCDate(lastDayOfTheMonth.getUTCDate() + 1);
firstDayOfTheNextMonth.setUTCDate(firstDayOfTheNextMonth.getUTCDate() + 1);
const events = await findEventsBaseQuery(organizationId)
.where(
@@ -374,7 +374,7 @@ export async function findEventsByMonth({
.where(
"CalendarEventDate.startsAt",
"<=",
dateToDatabaseTimestamp(lastDayOfTheMonth),
dateToDatabaseTimestamp(firstDayOfTheNextMonth),
)
.orderBy("CalendarEventDate.startsAt", "asc")
.execute();

View File

@@ -21,6 +21,11 @@ describe("pathnameFromPotentialURL()", () => {
"FW4dKrY",
);
});
test("Resolves a scheme-less URL paste to the path", () => {
// otherwise the generated discordUrl becomes https://discord.gg/discord.gg/FW4dKrY
expect(pathnameFromPotentialURL("discord.gg/FW4dKrY")).toBe("FW4dKrY");
});
});
describe("truncateBySentence()", () => {
@@ -53,6 +58,17 @@ describe("truncateBySentence()", () => {
const text = "First line\nSecond line\nThird line";
expect(truncateBySentence(text, 20)).toBe("First line");
});
test("Does not treat a period inside a time like 18.00 as a sentence end", () => {
const text =
"Doors at 18.00, we will be playing five rounds of swiss followed by a top cut.";
expect(truncateBySentence(text, 40)).not.toBe("Doors at 18.");
});
test("Does not return a tiny fraction of the budget when a long sentence follows a short one", () => {
const text = `Hi. ${"x".repeat(400)}. Everyone is welcome.`;
expect(truncateBySentence(text, 300)).not.toBe("Hi.");
});
});
describe("removeMarkdown()", () => {
@@ -71,6 +87,11 @@ describe("removeMarkdown()", () => {
expect(removeMarkdown("caf&#233; &#x26; tea")).toBe("café & tea");
});
test("Does not throw on an out of range numeric entity", () => {
// e.g. an organizer writing a hex color code in the description
expect(() => removeMarkdown("background: &#xFFFFFF; here")).not.toThrow();
});
test("Leaves unknown named entities untouched", () => {
expect(removeMarkdown("AT&amp;T &fakeentity; rules")).toBe(
"AT&T &fakeentity; rules",

View File

@@ -50,19 +50,38 @@ export function gearTypeToInitial(gearType: GearType) {
}
export function pathnameFromPotentialURL(maybeUrl: string) {
const parsed = safeParseUrl(maybeUrl);
if (parsed) return stripEdgeSlashes(parsed.pathname);
// handle a URL pasted without a protocol, e.g. "discord.gg/FW4dKrY"
const parsedWithProtocol = safeParseUrl(`https://${maybeUrl}`);
const pathname = parsedWithProtocol
? stripEdgeSlashes(parsedWithProtocol.pathname)
: "";
return pathname || maybeUrl;
}
function safeParseUrl(value: string) {
try {
return new URL(maybeUrl).pathname.replace(/^\/+|\/+$/g, "");
return new URL(value);
} catch {
return maybeUrl;
return null;
}
}
function stripEdgeSlashes(pathname: string) {
return pathname.replace(/^\/+|\/+$/g, "");
}
export function truncateBySentence(value: string, max: number) {
if (value.length <= max) {
return value;
}
const sentences = value.match(/[^.!?\n]+[.!?\n]*/g) || [];
// a sentence only ends at a terminator followed by whitespace, so that
// e.g. "18.00" does not split in the middle
const sentences = value.match(/[\s\S]+?(?:[.!?](?=\s|$)|\n|$)/g) || [];
let result = "";
for (const sentence of sentences) {
@@ -72,7 +91,13 @@ export function truncateBySentence(value: string, max: number) {
result += sentence;
}
return result.length > 0 ? result.trim() : value.slice(0, max).trim();
// when cutting at a sentence boundary would leave most of the budget
// unused, a mid-sentence cut that fills it is more informative
if (result.length < max / 2) {
return value.slice(0, max).trim();
}
return result.trim();
}
// based on https://github.com/zuchka/remove-markdown
@@ -101,9 +126,9 @@ export function removeMarkdown(value: string) {
const codePoint = code.startsWith("x")
? Number.parseInt(code.slice(1), 16)
: Number.parseInt(code, 10);
return Number.isFinite(codePoint)
? String.fromCodePoint(codePoint)
: "";
const isValidCodePoint =
Number.isFinite(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff;
return isValidCodePoint ? String.fromCodePoint(codePoint) : "";
})
// Remove setext-style headers
.replace(/^[=-]{2,}\s*$/g, "")