Skip to content

Commit c94f835

Browse files
authored
Merge pull request #43 from KnightNiwrem/copilot/fix-42
feat: Implement startsWith and endsWith instance and static methods for FormattedString
2 parents fa5ca60 + 80b173b commit c94f835

File tree

2 files changed

+281
-0
lines changed

2 files changed

+281
-0
lines changed

src/format.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,83 @@ export class FormattedString
816816
concat(...formattedStrings: FormattedString[]): FormattedString {
817817
return FormattedString.join([this, ...formattedStrings]);
818818
}
819+
820+
/**
821+
* Checks whether this FormattedString starts with the specified pattern.
822+
* Both the raw text and raw entities must match exactly.
823+
* @param pattern The FormattedString pattern to check for at the beginning
824+
* @returns true if this FormattedString starts with the pattern, false otherwise
825+
*/
826+
startsWith(pattern: FormattedString): boolean {
827+
return FormattedString.startsWith(this, pattern);
828+
}
829+
830+
/**
831+
* Checks whether this FormattedString ends with the specified pattern.
832+
* Both the raw text and raw entities must match exactly.
833+
* @param pattern The FormattedString pattern to check for at the end
834+
* @returns true if this FormattedString ends with the pattern, false otherwise
835+
*/
836+
endsWith(pattern: FormattedString): boolean {
837+
return FormattedString.endsWith(this, pattern);
838+
}
839+
840+
/**
841+
* Static method to check whether a FormattedString starts with the specified pattern.
842+
* Both the raw text and raw entities must match exactly.
843+
* @param source The FormattedString to check
844+
* @param pattern The FormattedString pattern to check for at the beginning
845+
* @returns true if the source starts with the pattern, false otherwise
846+
*/
847+
static startsWith(
848+
source: FormattedString,
849+
pattern: FormattedString,
850+
): boolean {
851+
// Pattern cannot be longer than source
852+
if (pattern.rawText.length > source.rawText.length) {
853+
return false;
854+
}
855+
856+
// Handle empty pattern - always matches at the beginning
857+
if (pattern.rawText.length === 0) {
858+
return true;
859+
}
860+
861+
// Extract the beginning of the source with the same length as pattern
862+
const candidate = source.slice(0, pattern.rawText.length);
863+
864+
// Compare both text and entities for exact match
865+
return candidate.rawText === pattern.rawText &&
866+
isEntitiesEqual(candidate.rawEntities, pattern.rawEntities);
867+
}
868+
869+
/**
870+
* Static method to check whether a FormattedString ends with the specified pattern.
871+
* Both the raw text and raw entities must match exactly.
872+
* @param source The FormattedString to check
873+
* @param pattern The FormattedString pattern to check for at the end
874+
* @returns true if the source ends with the pattern, false otherwise
875+
*/
876+
static endsWith(source: FormattedString, pattern: FormattedString): boolean {
877+
// Pattern cannot be longer than source
878+
if (pattern.rawText.length > source.rawText.length) {
879+
return false;
880+
}
881+
882+
// Handle empty pattern - always matches at the end
883+
if (pattern.rawText.length === 0) {
884+
return true;
885+
}
886+
887+
// Extract the end of the source with the same length as pattern
888+
const candidate = source.slice(
889+
source.rawText.length - pattern.rawText.length,
890+
);
891+
892+
// Compare both text and entities for exact match
893+
return candidate.rawText === pattern.rawText &&
894+
isEntitiesEqual(candidate.rawEntities, pattern.rawEntities);
895+
}
819896
}
820897

821898
function buildFormatter<T extends Array<unknown> = never>(

test/format.test.ts

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2030,3 +2030,207 @@ describe("FormattedString - Replace methods", () => {
20302030
assertEquals(result.rawEntities[0]?.length, 5);
20312031
});
20322032
});
2033+
2034+
describe("FormattedString - startsWith and endsWith methods", () => {
2035+
it("Static startsWith - basic functionality", () => {
2036+
const source = new FormattedString("Hello World");
2037+
const pattern1 = new FormattedString("Hello");
2038+
const pattern2 = new FormattedString("World");
2039+
const pattern3 = new FormattedString("Hi");
2040+
2041+
assertEquals(FormattedString.startsWith(source, pattern1), true);
2042+
assertEquals(FormattedString.startsWith(source, pattern2), false);
2043+
assertEquals(FormattedString.startsWith(source, pattern3), false);
2044+
});
2045+
2046+
it("Static startsWith - empty pattern", () => {
2047+
const source = new FormattedString("Hello World");
2048+
const emptyPattern = new FormattedString("");
2049+
2050+
assertEquals(FormattedString.startsWith(source, emptyPattern), true);
2051+
});
2052+
2053+
it("Static startsWith - empty source", () => {
2054+
const emptySource = new FormattedString("");
2055+
const pattern = new FormattedString("Hello");
2056+
const emptyPattern = new FormattedString("");
2057+
2058+
assertEquals(FormattedString.startsWith(emptySource, pattern), false);
2059+
assertEquals(FormattedString.startsWith(emptySource, emptyPattern), true);
2060+
});
2061+
2062+
it("Static startsWith - pattern longer than source", () => {
2063+
const source = new FormattedString("Hi");
2064+
const pattern = new FormattedString("Hello World");
2065+
2066+
assertEquals(FormattedString.startsWith(source, pattern), false);
2067+
});
2068+
2069+
it("Static startsWith - exact match", () => {
2070+
const source = new FormattedString("Hello World");
2071+
const pattern = new FormattedString("Hello World");
2072+
2073+
assertEquals(FormattedString.startsWith(source, pattern), true);
2074+
});
2075+
2076+
it("Static startsWith - with entities matching", () => {
2077+
const source = FormattedString.bold("Hello").concat(
2078+
new FormattedString(" World"),
2079+
);
2080+
const pattern = FormattedString.bold("Hello");
2081+
2082+
assertEquals(FormattedString.startsWith(source, pattern), true);
2083+
});
2084+
2085+
it("Static startsWith - with entities not matching", () => {
2086+
const source = FormattedString.bold("Hello World");
2087+
const pattern = new FormattedString("Hello"); // no bold entity
2088+
2089+
assertEquals(FormattedString.startsWith(source, pattern), false);
2090+
});
2091+
2092+
it("Static startsWith - complex entities matching", () => {
2093+
const boldHello = FormattedString.bold("Hello");
2094+
const space = new FormattedString(" ");
2095+
const italicWorld = FormattedString.italic("World");
2096+
const source = FormattedString.join([boldHello, space, italicWorld]);
2097+
2098+
const pattern1 = FormattedString.join([boldHello, space]);
2099+
const pattern2 = FormattedString.join([
2100+
new FormattedString("Hello"),
2101+
space,
2102+
]);
2103+
2104+
assertEquals(FormattedString.startsWith(source, pattern1), true);
2105+
assertEquals(FormattedString.startsWith(source, pattern2), false);
2106+
});
2107+
2108+
it("Instance startsWith - basic functionality", () => {
2109+
const source = new FormattedString("Hello World");
2110+
const pattern1 = new FormattedString("Hello");
2111+
const pattern2 = new FormattedString("World");
2112+
2113+
assertEquals(source.startsWith(pattern1), true);
2114+
assertEquals(source.startsWith(pattern2), false);
2115+
});
2116+
2117+
it("Static endsWith - basic functionality", () => {
2118+
const source = new FormattedString("Hello World");
2119+
const pattern1 = new FormattedString("World");
2120+
const pattern2 = new FormattedString("Hello");
2121+
const pattern3 = new FormattedString("Earth");
2122+
2123+
assertEquals(FormattedString.endsWith(source, pattern1), true);
2124+
assertEquals(FormattedString.endsWith(source, pattern2), false);
2125+
assertEquals(FormattedString.endsWith(source, pattern3), false);
2126+
});
2127+
2128+
it("Static endsWith - empty pattern", () => {
2129+
const source = new FormattedString("Hello World");
2130+
const emptyPattern = new FormattedString("");
2131+
2132+
assertEquals(FormattedString.endsWith(source, emptyPattern), true);
2133+
});
2134+
2135+
it("Static endsWith - empty source", () => {
2136+
const emptySource = new FormattedString("");
2137+
const pattern = new FormattedString("World");
2138+
const emptyPattern = new FormattedString("");
2139+
2140+
assertEquals(FormattedString.endsWith(emptySource, pattern), false);
2141+
assertEquals(FormattedString.endsWith(emptySource, emptyPattern), true);
2142+
});
2143+
2144+
it("Static endsWith - pattern longer than source", () => {
2145+
const source = new FormattedString("Hi");
2146+
const pattern = new FormattedString("Hello World");
2147+
2148+
assertEquals(FormattedString.endsWith(source, pattern), false);
2149+
});
2150+
2151+
it("Static endsWith - exact match", () => {
2152+
const source = new FormattedString("Hello World");
2153+
const pattern = new FormattedString("Hello World");
2154+
2155+
assertEquals(FormattedString.endsWith(source, pattern), true);
2156+
});
2157+
2158+
it("Static endsWith - with entities matching", () => {
2159+
const source = new FormattedString("Hello ").concat(
2160+
FormattedString.italic("World"),
2161+
);
2162+
const pattern = FormattedString.italic("World");
2163+
2164+
assertEquals(FormattedString.endsWith(source, pattern), true);
2165+
});
2166+
2167+
it("Static endsWith - with entities not matching", () => {
2168+
const source = FormattedString.italic("Hello World");
2169+
const pattern = new FormattedString("World"); // no italic entity
2170+
2171+
assertEquals(FormattedString.endsWith(source, pattern), false);
2172+
});
2173+
2174+
it("Static endsWith - complex entities matching", () => {
2175+
const boldHello = FormattedString.bold("Hello");
2176+
const space = new FormattedString(" ");
2177+
const italicWorld = FormattedString.italic("World");
2178+
const source = FormattedString.join([boldHello, space, italicWorld]);
2179+
2180+
const pattern1 = FormattedString.join([space, italicWorld]);
2181+
const pattern2 = FormattedString.join([
2182+
space,
2183+
new FormattedString("World"),
2184+
]);
2185+
2186+
assertEquals(FormattedString.endsWith(source, pattern1), true);
2187+
assertEquals(FormattedString.endsWith(source, pattern2), false);
2188+
});
2189+
2190+
it("Instance endsWith - basic functionality", () => {
2191+
const source = new FormattedString("Hello World");
2192+
const pattern1 = new FormattedString("World");
2193+
const pattern2 = new FormattedString("Hello");
2194+
2195+
assertEquals(source.endsWith(pattern1), true);
2196+
assertEquals(source.endsWith(pattern2), false);
2197+
});
2198+
2199+
it("startsWith and endsWith - single character", () => {
2200+
const source = new FormattedString("Hello");
2201+
const hPattern = new FormattedString("H");
2202+
const oPattern = new FormattedString("o");
2203+
2204+
assertEquals(source.startsWith(hPattern), true);
2205+
assertEquals(source.endsWith(oPattern), true);
2206+
assertEquals(source.startsWith(oPattern), false);
2207+
assertEquals(source.endsWith(hPattern), false);
2208+
});
2209+
2210+
it("startsWith and endsWith - with link entities", () => {
2211+
const linkText = FormattedString.link("click here", "https://example.com");
2212+
const source = new FormattedString("Visit ").concat(linkText).concat(
2213+
new FormattedString(" now"),
2214+
);
2215+
2216+
const startsPattern = new FormattedString("Visit ");
2217+
const endsPattern = new FormattedString(" now");
2218+
const linkPattern = FormattedString.link(
2219+
"click here",
2220+
"https://example.com",
2221+
);
2222+
2223+
assertEquals(source.startsWith(startsPattern), true);
2224+
assertEquals(source.endsWith(endsPattern), true);
2225+
assertEquals(source.startsWith(linkPattern), false);
2226+
assertEquals(source.endsWith(linkPattern), false);
2227+
});
2228+
2229+
it("startsWith and endsWith - overlapping patterns", () => {
2230+
const source = new FormattedString("abcabc");
2231+
const abcPattern = new FormattedString("abc");
2232+
2233+
assertEquals(source.startsWith(abcPattern), true);
2234+
assertEquals(source.endsWith(abcPattern), true);
2235+
});
2236+
});

0 commit comments

Comments
 (0)