mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-08-07 03:38:30 +08:00
* Redact common credential patterns from published run output * Handle color codes and escape sequences ahead of redacted values Vendor-prefixed formats no longer require a leading word boundary, so a value that follows an ANSI SGR terminator or a serialized JSON escape is still matched. AWS key ids keep a boundary but also accept those cases. sanitizeContent goes back to GitHub-only redaction for inbound content, and the failure annotation is redacted like the tracking comment. * Coerce non-string text content before redacting tool results No-Verification-Needed: one-line coercion in a formatting helper plus regression test
521 lines
18 KiB
TypeScript
521 lines
18 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import {
|
|
stripInvisibleCharacters,
|
|
stripMarkdownImageAltText,
|
|
stripMarkdownLinkTitles,
|
|
stripHiddenAttributes,
|
|
normalizeHtmlEntities,
|
|
sanitizeContent,
|
|
stripHtmlComments,
|
|
redactGitHubTokens,
|
|
redactSecrets,
|
|
} from "../src/github/utils/sanitizer";
|
|
|
|
describe("stripInvisibleCharacters", () => {
|
|
it("should remove zero-width characters", () => {
|
|
expect(stripInvisibleCharacters("Hello\u200BWorld")).toBe("HelloWorld");
|
|
expect(stripInvisibleCharacters("Text\u200C\u200D")).toBe("Text");
|
|
expect(stripInvisibleCharacters("\uFEFFStart")).toBe("Start");
|
|
});
|
|
|
|
it("should remove control characters", () => {
|
|
expect(stripInvisibleCharacters("Hello\u0000World")).toBe("HelloWorld");
|
|
expect(stripInvisibleCharacters("Text\u001F\u007F")).toBe("Text");
|
|
});
|
|
|
|
it("should preserve common whitespace", () => {
|
|
expect(stripInvisibleCharacters("Hello\nWorld")).toBe("Hello\nWorld");
|
|
expect(stripInvisibleCharacters("Tab\there")).toBe("Tab\there");
|
|
expect(stripInvisibleCharacters("Carriage\rReturn")).toBe(
|
|
"Carriage\rReturn",
|
|
);
|
|
});
|
|
|
|
it("should remove soft hyphens", () => {
|
|
expect(stripInvisibleCharacters("Soft\u00ADHyphen")).toBe("SoftHyphen");
|
|
});
|
|
|
|
it("should remove Unicode direction overrides", () => {
|
|
expect(stripInvisibleCharacters("Text\u202A\u202BMore")).toBe("TextMore");
|
|
expect(stripInvisibleCharacters("\u2066Isolated\u2069")).toBe("Isolated");
|
|
});
|
|
});
|
|
|
|
describe("stripMarkdownImageAltText", () => {
|
|
it("should remove alt text from markdown images", () => {
|
|
expect(stripMarkdownImageAltText("")).toBe(
|
|
"",
|
|
);
|
|
expect(
|
|
stripMarkdownImageAltText("Text  more text"),
|
|
).toBe("Text  more text");
|
|
});
|
|
|
|
it("should handle multiple images", () => {
|
|
expect(stripMarkdownImageAltText(" ")).toBe(
|
|
" ",
|
|
);
|
|
});
|
|
|
|
it("should handle empty alt text", () => {
|
|
expect(stripMarkdownImageAltText("")).toBe("");
|
|
});
|
|
|
|
it("should remove alt text from reference-style images", () => {
|
|
expect(stripMarkdownImageAltText("![example alt text][img1]")).toBe(
|
|
"![][img1]",
|
|
);
|
|
expect(
|
|
stripMarkdownImageAltText("Text ![description][ref] more text"),
|
|
).toBe("Text ![][ref] more text");
|
|
});
|
|
|
|
it("should preserve the reference label of a reference-style image", () => {
|
|
// the [ref] label must survive so the image definition still resolves;
|
|
// only the alt text (the injection channel) is removed
|
|
expect(stripMarkdownImageAltText("![alt][my-ref]")).toBe("![][my-ref]");
|
|
expect(stripMarkdownImageAltText("![][keep]")).toBe("![][keep]");
|
|
});
|
|
});
|
|
|
|
describe("stripMarkdownLinkTitles", () => {
|
|
it("should remove titles from markdown links", () => {
|
|
expect(stripMarkdownLinkTitles('[Link](url.com "example title")')).toBe(
|
|
"[Link](url.com)",
|
|
);
|
|
expect(stripMarkdownLinkTitles("[Link](url.com 'example title')")).toBe(
|
|
"[Link](url.com)",
|
|
);
|
|
});
|
|
|
|
it("should handle multiple links", () => {
|
|
expect(
|
|
stripMarkdownLinkTitles('[One](1.com "first") [Two](2.com "second")'),
|
|
).toBe("[One](1.com) [Two](2.com)");
|
|
});
|
|
|
|
it("should preserve links without titles", () => {
|
|
expect(stripMarkdownLinkTitles("[Link](url.com)")).toBe("[Link](url.com)");
|
|
});
|
|
});
|
|
|
|
describe("stripHiddenAttributes", () => {
|
|
it("should remove alt attributes", () => {
|
|
expect(
|
|
stripHiddenAttributes('<img alt="example text" src="pic.jpg">'),
|
|
).toBe('<img src="pic.jpg">');
|
|
expect(stripHiddenAttributes("<img alt='example' src=\"pic.jpg\">")).toBe(
|
|
'<img src="pic.jpg">',
|
|
);
|
|
expect(stripHiddenAttributes('<img alt=example src="pic.jpg">')).toBe(
|
|
'<img src="pic.jpg">',
|
|
);
|
|
});
|
|
|
|
it("should remove title attributes", () => {
|
|
expect(
|
|
stripHiddenAttributes('<a title="example text" href="#">Link</a>'),
|
|
).toBe('<a href="#">Link</a>');
|
|
expect(stripHiddenAttributes("<div title='example'>Content</div>")).toBe(
|
|
"<div>Content</div>",
|
|
);
|
|
});
|
|
|
|
it("should remove aria-label attributes", () => {
|
|
expect(
|
|
stripHiddenAttributes('<button aria-label="example">Click</button>'),
|
|
).toBe("<button>Click</button>");
|
|
});
|
|
|
|
it("should remove data-* attributes", () => {
|
|
expect(
|
|
stripHiddenAttributes(
|
|
'<div data-test="example" data-info="more example">Text</div>',
|
|
),
|
|
).toBe("<div>Text</div>");
|
|
});
|
|
|
|
it("should remove placeholder attributes", () => {
|
|
expect(
|
|
stripHiddenAttributes('<input placeholder="example text" type="text">'),
|
|
).toBe('<input type="text">');
|
|
});
|
|
|
|
it("should handle multiple attributes", () => {
|
|
expect(
|
|
stripHiddenAttributes(
|
|
'<img alt="example" title="test" src="pic.jpg" class="image">',
|
|
),
|
|
).toBe('<img src="pic.jpg" class="image">');
|
|
});
|
|
|
|
it("should not corrupt content when an attribute value contains the other quote type", () => {
|
|
// Regression for #1366: an apostrophe inside a double-quoted attribute
|
|
// (or a double quote inside a single-quoted attribute) must not cause the
|
|
// closing quote to be mismatched, which previously mangled later text.
|
|
expect(
|
|
stripHiddenAttributes(`<Tooltip title="We'll do it" placement="top">`),
|
|
).toBe('<Tooltip placement="top">');
|
|
expect(
|
|
stripHiddenAttributes(`<img alt="Bob's avatar" src="pic.jpg">`),
|
|
).toBe('<img src="pic.jpg">');
|
|
expect(stripHiddenAttributes(`<div title='say "hi"'>Content</div>`)).toBe(
|
|
"<div>Content</div>",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("normalizeHtmlEntities", () => {
|
|
it("should decode numeric entities", () => {
|
|
expect(normalizeHtmlEntities("Hello")).toBe(
|
|
"Hello",
|
|
);
|
|
expect(normalizeHtmlEntities("ABC")).toBe("ABC");
|
|
});
|
|
|
|
it("should decode hex entities", () => {
|
|
expect(normalizeHtmlEntities("Hello")).toBe(
|
|
"Hello",
|
|
);
|
|
expect(normalizeHtmlEntities("ABC")).toBe("ABC");
|
|
});
|
|
|
|
it("should remove non-printable entities", () => {
|
|
expect(normalizeHtmlEntities("�")).toBe("");
|
|
expect(normalizeHtmlEntities("�")).toBe("");
|
|
});
|
|
|
|
it("should preserve normal text", () => {
|
|
expect(normalizeHtmlEntities("Normal text")).toBe("Normal text");
|
|
});
|
|
});
|
|
|
|
describe("sanitizeContent", () => {
|
|
it("should apply all sanitization measures", () => {
|
|
const testContent = `
|
|
<!-- This is a comment -->
|
|
<img alt="example alt text" src="image.jpg">
|
|

|
|
[click here](https://example.com "example title")
|
|
<div data-prompt="example data" aria-label="example label">
|
|
Normal text with hidden\u200Bcharacters
|
|
</div>
|
|
Hidden message
|
|
`;
|
|
|
|
const sanitized = sanitizeContent(testContent);
|
|
|
|
expect(sanitized).not.toContain("<!-- This is a comment -->");
|
|
expect(sanitized).not.toContain("example alt text");
|
|
expect(sanitized).not.toContain("example image description");
|
|
expect(sanitized).not.toContain("example title");
|
|
expect(sanitized).not.toContain("example data");
|
|
expect(sanitized).not.toContain("example label");
|
|
expect(sanitized).not.toContain("\u200B");
|
|
expect(sanitized).not.toContain("alt=");
|
|
expect(sanitized).not.toContain("data-prompt=");
|
|
expect(sanitized).not.toContain("aria-label=");
|
|
|
|
expect(sanitized).toContain("Normal text with hiddencharacters");
|
|
expect(sanitized).toContain("Hidden message");
|
|
expect(sanitized).toContain('<img src="image.jpg">');
|
|
expect(sanitized).toContain("");
|
|
expect(sanitized).toContain("[click here](https://example.com)");
|
|
});
|
|
|
|
it("should handle complex nested patterns", () => {
|
|
const complexContent = `
|
|
Text with  and more.
|
|
<a href="#" title="example\u00ADtitle">Link</a>
|
|
<div data-x="Hi">Content</div>
|
|
`;
|
|
|
|
const sanitized = sanitizeContent(complexContent);
|
|
|
|
expect(sanitized).not.toContain("\u200B");
|
|
expect(sanitized).not.toContain("\u00AD");
|
|
expect(sanitized).not.toContain("alt ");
|
|
expect(sanitized).not.toContain('title="');
|
|
expect(sanitized).not.toContain('data-x="');
|
|
expect(sanitized).toContain("");
|
|
expect(sanitized).toContain('<a href="#">Link</a>');
|
|
});
|
|
|
|
it("should preserve legitimate markdown and HTML", () => {
|
|
const legitimateContent = `
|
|
# Heading
|
|
|
|
This is **bold** and *italic* text.
|
|
|
|
Here's a normal image: 
|
|
And a normal link: [Click here](https://example.com)
|
|
|
|
<div class="container">
|
|
<p id="para">Normal paragraph</p>
|
|
<input type="text" name="field">
|
|
</div>
|
|
`;
|
|
|
|
const sanitized = sanitizeContent(legitimateContent);
|
|
|
|
expect(sanitized).toBe(legitimateContent);
|
|
});
|
|
|
|
it("should handle entity-encoded text", () => {
|
|
const encodedText = `
|
|
Hidden message
|
|
<div title="example">Test</div>
|
|
`;
|
|
|
|
const sanitized = sanitizeContent(encodedText);
|
|
|
|
expect(sanitized).toContain("Hidden message");
|
|
expect(sanitized).not.toContain('title="');
|
|
expect(sanitized).toContain("<div>Test</div>");
|
|
});
|
|
});
|
|
|
|
describe("redactGitHubTokens", () => {
|
|
it("should redact personal access tokens (ghp_)", () => {
|
|
const token = "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";
|
|
expect(redactGitHubTokens(`Token: ${token}`)).toBe(
|
|
"Token: [REDACTED_GITHUB_TOKEN]",
|
|
);
|
|
expect(redactGitHubTokens(`Here's a token: ${token} in text`)).toBe(
|
|
"Here's a token: [REDACTED_GITHUB_TOKEN] in text",
|
|
);
|
|
});
|
|
|
|
it("should redact OAuth tokens (gho_)", () => {
|
|
const token = "gho_16C7e42F292c6912E7710c838347Ae178B4a";
|
|
expect(redactGitHubTokens(`OAuth: ${token}`)).toBe(
|
|
"OAuth: [REDACTED_GITHUB_TOKEN]",
|
|
);
|
|
});
|
|
|
|
it("should redact user-to-server tokens (ghu_)", () => {
|
|
const token = "ghu_16C7e42F292c6912E7710c838347Ae178B4a";
|
|
expect(redactGitHubTokens(`User token: ${token}`)).toBe(
|
|
"User token: [REDACTED_GITHUB_TOKEN]",
|
|
);
|
|
expect(
|
|
redactGitHubTokens(`In a URL: x-access-token:${token}@github.com`),
|
|
).toBe("In a URL: x-access-token:[REDACTED_GITHUB_TOKEN]@github.com");
|
|
});
|
|
|
|
it("should redact installation tokens (ghs_)", () => {
|
|
const token = "ghs_xz7yzju2SZjGPa0dUNMAx0SH4xDOCS31LXQW";
|
|
expect(redactGitHubTokens(`Install token: ${token}`)).toBe(
|
|
"Install token: [REDACTED_GITHUB_TOKEN]",
|
|
);
|
|
});
|
|
|
|
it("should redact refresh tokens (ghr_)", () => {
|
|
const token = "ghr_1B4a2e77838347a253e56d7b5253e7d11667";
|
|
expect(redactGitHubTokens(`Refresh: ${token}`)).toBe(
|
|
"Refresh: [REDACTED_GITHUB_TOKEN]",
|
|
);
|
|
});
|
|
|
|
it("should redact fine-grained tokens (github_pat_)", () => {
|
|
const token =
|
|
"github_pat_11ABCDEFG0example5of9_2nVwvsylpmOLboQwTPTLewDcE621dQ0AAaBBCCDDEEFFHH";
|
|
expect(redactGitHubTokens(`Fine-grained: ${token}`)).toBe(
|
|
"Fine-grained: [REDACTED_GITHUB_TOKEN]",
|
|
);
|
|
});
|
|
|
|
it("should handle tokens in code blocks", () => {
|
|
const content = `\`\`\`bash
|
|
export GITHUB_TOKEN=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
|
|
\`\`\``;
|
|
const expected = `\`\`\`bash
|
|
export GITHUB_TOKEN=[REDACTED_GITHUB_TOKEN]
|
|
\`\`\``;
|
|
expect(redactGitHubTokens(content)).toBe(expected);
|
|
});
|
|
|
|
it("should handle multiple tokens in one text", () => {
|
|
const content =
|
|
"Token 1: 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 and token 2: gho_16C7e42F292c6912E7710c838347Ae178B4a";
|
|
expect(redactGitHubTokens(content)).toBe(
|
|
"Token 1: [REDACTED_GITHUB_TOKEN] and token 2: [REDACTED_GITHUB_TOKEN]",
|
|
);
|
|
});
|
|
|
|
it("should handle tokens in URLs", () => {
|
|
const content =
|
|
"https://api.github.com/user?access_token=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";
|
|
expect(redactGitHubTokens(content)).toBe(
|
|
"https://api.github.com/user?access_token=[REDACTED_GITHUB_TOKEN]",
|
|
);
|
|
});
|
|
|
|
it("should not redact partial matches or invalid tokens", () => {
|
|
const content =
|
|
"This is not a token: ghp_short or gho_toolong1234567890123456789012345678901234567890";
|
|
expect(redactGitHubTokens(content)).toBe(content);
|
|
});
|
|
|
|
it("should preserve normal text", () => {
|
|
const content = "Normal text with no tokens";
|
|
expect(redactGitHubTokens(content)).toBe(content);
|
|
});
|
|
|
|
it("should handle edge cases", () => {
|
|
expect(redactGitHubTokens("")).toBe("");
|
|
expect(redactGitHubTokens("ghp_")).toBe("ghp_");
|
|
expect(redactGitHubTokens("github_pat_short")).toBe("github_pat_short");
|
|
});
|
|
});
|
|
|
|
describe("redactSecrets", () => {
|
|
it("should still redact GitHub tokens", () => {
|
|
expect(
|
|
redactSecrets("Token: ghs_xz7yzju2SZjGPa0dUNMAx0SH4xDOCS31LXQW"),
|
|
).toBe("Token: [REDACTED_GITHUB_TOKEN]");
|
|
});
|
|
|
|
it("should redact Anthropic API keys (sk-ant-)", () => {
|
|
const key = "sk-ant-api03-AbCdEfGhIjKlMnOpQrStUvWxYz0123456789_-abcdefgh";
|
|
expect(redactSecrets(`ANTHROPIC_API_KEY=${key}`)).toBe(
|
|
"ANTHROPIC_API_KEY=[REDACTED_ANTHROPIC_KEY]",
|
|
);
|
|
});
|
|
|
|
it("should not redact sk- strings that are not sk-ant-", () => {
|
|
const content =
|
|
"sk-proj-abcdefghijklmnopqrstuvwxyz0123456789 and sk-ant-short";
|
|
expect(redactSecrets(content)).toBe(content);
|
|
});
|
|
|
|
it("should redact AWS access key ids", () => {
|
|
expect(redactSecrets("aws_access_key_id = AKIAIOSFODNN7EXAMPLE")).toBe(
|
|
"aws_access_key_id = [REDACTED_AWS_KEY_ID]",
|
|
);
|
|
expect(redactSecrets("temp creds ASIAIOSFODNN7EXAMPLE end")).toBe(
|
|
"temp creds [REDACTED_AWS_KEY_ID] end",
|
|
);
|
|
});
|
|
|
|
it("should not redact AWS-like strings that do not fit the format", () => {
|
|
const content =
|
|
"AKIAtest AKIA123 AKIAIOSFODNN7EXAMPLEXYZ akiaiosfodnn7example";
|
|
expect(redactSecrets(content)).toBe(content);
|
|
});
|
|
|
|
it("should redact Slack tokens", () => {
|
|
expect(redactSecrets("token=xoxb-1234567890-abcdefghijkl")).toBe(
|
|
"token=[REDACTED_SLACK_TOKEN]",
|
|
);
|
|
expect(redactSecrets("xoxp-1234567890-1234567890-abc")).toBe(
|
|
"[REDACTED_SLACK_TOKEN]",
|
|
);
|
|
});
|
|
|
|
it("should not redact xox strings that do not fit the format", () => {
|
|
const content = "xoxo-1234567890abc xoxz-1234567890abc xoxb-short";
|
|
expect(redactSecrets(content)).toBe(content);
|
|
});
|
|
|
|
it("should redact JWT-shaped strings", () => {
|
|
const jwt =
|
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
|
|
expect(redactSecrets(`Authorization: Bearer ${jwt}`)).toBe(
|
|
"Authorization: Bearer [REDACTED_JWT]",
|
|
);
|
|
});
|
|
|
|
it("should redact tokens that follow a JSON escape sequence", () => {
|
|
const serialized = JSON.stringify({
|
|
content:
|
|
"line one\nGITHUB_TOKEN=ghs_xz7yzju2SZjGPa0dUNMAx0SH4xDOCS31LXQW\tsk-ant-api03-AbCdEfGhIjKlMnOpQrStUvWx",
|
|
});
|
|
const redacted = redactSecrets(serialized);
|
|
expect(redacted).toContain("[REDACTED_GITHUB_TOKEN]");
|
|
expect(redacted).toContain("[REDACTED_ANTHROPIC_KEY]");
|
|
expect(redacted).not.toContain("ghs_xz7yzju2SZjGPa0dUNMAx0SH4xDOCS31LXQW");
|
|
});
|
|
|
|
it("should redact tokens preceded by ANSI color codes", () => {
|
|
const ghp = "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";
|
|
const anthropic =
|
|
"sk-ant-api03-AbCdEfGhIjKlMnOpQrStUvWxYz0123456789_-abcdefgh";
|
|
expect(redactSecrets(`\x1b[31m${ghp}\x1b[0m`)).toBe(
|
|
"\x1b[31m[REDACTED_GITHUB_TOKEN]\x1b[0m",
|
|
);
|
|
expect(redactSecrets(`key=\x1b[32m${anthropic}\x1b[39m`)).toBe(
|
|
"key=\x1b[32m[REDACTED_ANTHROPIC_KEY]\x1b[39m",
|
|
);
|
|
expect(redactSecrets(`\x1b[1mAKIAIOSFODNN7EXAMPLE\x1b[0m`)).toBe(
|
|
"\x1b[1m[REDACTED_AWS_KEY_ID]\x1b[0m",
|
|
);
|
|
});
|
|
|
|
it("should redact tokens that follow other JSON escapes", () => {
|
|
const ghp = "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";
|
|
const serialized = JSON.stringify({
|
|
colored: `\x1b[31m${ghp}`,
|
|
formfeed: `\f${ghp}`,
|
|
quoted: `"AKIAIOSFODNN7EXAMPLE"`,
|
|
});
|
|
const redacted = redactSecrets(serialized);
|
|
expect(redacted).not.toContain(ghp);
|
|
expect(redacted).not.toContain("AKIAIOSFODNN7EXAMPLE");
|
|
expect(redacted).toContain("[REDACTED_GITHUB_TOKEN]");
|
|
expect(redacted).toContain("[REDACTED_AWS_KEY_ID]");
|
|
});
|
|
|
|
it("should not redact base64 blobs that are not JWTs", () => {
|
|
// Long base64 without dots, and two-segment strings, are left alone
|
|
const content =
|
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJzdWIiOiIxMjM0NTY3ODkw " +
|
|
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0 " +
|
|
"aGVsbG8gd29ybGQgdGhpcyBpcyBub3QgYSBqd3Q=";
|
|
expect(redactSecrets(content)).toBe(content);
|
|
});
|
|
});
|
|
|
|
describe("sanitizeContent with token redaction", () => {
|
|
it("should only redact GitHub tokens from inbound content", () => {
|
|
const content =
|
|
"docs example key AKIAIOSFODNN7EXAMPLE and token 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";
|
|
expect(sanitizeContent(content)).toBe(
|
|
"docs example key AKIAIOSFODNN7EXAMPLE and token [REDACTED_GITHUB_TOKEN]",
|
|
);
|
|
});
|
|
|
|
it("should redact tokens as part of full sanitization", () => {
|
|
const content = `
|
|
<!-- Hidden comment with token: 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 -->
|
|
Here's some text with a token: gho_16C7e42F292c6912E7710c838347Ae178B4a
|
|
And invisible chars: test\u200Btoken
|
|
`;
|
|
|
|
const sanitized = sanitizeContent(content);
|
|
|
|
expect(sanitized).not.toContain("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");
|
|
expect(sanitized).not.toContain("gho_16C7e42F292c6912E7710c838347Ae178B4a");
|
|
expect(sanitized).not.toContain("<!-- Hidden comment");
|
|
expect(sanitized).not.toContain("\u200B");
|
|
expect(sanitized).toContain("[REDACTED_GITHUB_TOKEN]");
|
|
expect(sanitized).toContain("Here's some text with a token:");
|
|
});
|
|
});
|
|
|
|
describe("stripHtmlComments (legacy)", () => {
|
|
it("should remove HTML comments", () => {
|
|
expect(stripHtmlComments("Hello <!-- example -->World")).toBe(
|
|
"Hello World",
|
|
);
|
|
expect(stripHtmlComments("<!-- comment -->Text")).toBe("Text");
|
|
expect(stripHtmlComments("Text<!-- comment -->")).toBe("Text");
|
|
});
|
|
|
|
it("should handle multiline comments", () => {
|
|
expect(stripHtmlComments("Hello <!-- \nexample\n -->World")).toBe(
|
|
"Hello World",
|
|
);
|
|
});
|
|
});
|