diff options
| author | Shipwreckt <me@shipwreckt.co.uk> | 2025-10-31 20:02:14 +0000 |
|---|---|---|
| committer | Shipwreckt <me@shipwreckt.co.uk> | 2025-10-31 20:02:14 +0000 |
| commit | 7a52ddeba2a68388b544f529d2d92104420f77b0 (patch) | |
| tree | 15ddd47457a2cb4a96060747437d36474e4f6b4e /node_modules/entities/src/decode.spec.ts | |
| parent | 53d6ae2b5568437afa5e4995580a3fb679b7b91b (diff) | |
Changed from static to 11ty!
Diffstat (limited to 'node_modules/entities/src/decode.spec.ts')
| -rw-r--r-- | node_modules/entities/src/decode.spec.ts | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/node_modules/entities/src/decode.spec.ts b/node_modules/entities/src/decode.spec.ts new file mode 100644 index 0000000..6ab37d4 --- /dev/null +++ b/node_modules/entities/src/decode.spec.ts @@ -0,0 +1,320 @@ +import { describe, it, expect, vitest } from "vitest"; +import * as entities from "./decode.js"; + +describe("Decode test", () => { + const testcases = [ + { input: "&amp;", output: "&" }, + { input: "&#38;", output: "&" }, + { input: "&#x26;", output: "&" }, + { input: "&#X26;", output: "&" }, + { input: "&#38;", output: "&" }, + { input: "&#38;", output: "&" }, + { input: "&#38;", output: "&" }, + { input: ":", output: ":" }, + { input: ":", output: ":" }, + { input: ":", output: ":" }, + { input: ":", output: ":" }, + { input: "&#", output: "&#" }, + { input: "&>", output: "&>" }, + { input: "id=770&#anchor", output: "id=770&#anchor" }, + ]; + + for (const { input, output } of testcases) { + it(`should XML decode ${input}`, () => + expect(entities.decodeXML(input)).toBe(output)); + it(`should HTML decode ${input}`, () => + expect(entities.decodeHTML(input)).toBe(output)); + } + + it("should HTML decode partial legacy entity", () => { + expect(entities.decodeHTMLStrict("×bar")).toBe("×bar"); + expect(entities.decodeHTML("×bar")).toBe("×bar"); + }); + + it("should HTML decode legacy entities according to spec", () => + expect(entities.decodeHTML("?&image_uri=1&ℑ=2&image=3")).toBe( + "?&image_uri=1&ℑ=2&image=3", + )); + + it("should back out of legacy entities", () => + expect(entities.decodeHTML("&a")).toBe("&a")); + + it("should not parse numeric entities in strict mode", () => + expect(entities.decodeHTMLStrict("7")).toBe("7")); + + it("should parse   followed by < (#852)", () => + expect(entities.decodeHTML(" <")).toBe("\u00A0<")); + + it("should decode trailing legacy entities", () => { + expect(entities.decodeHTML("⨱×bar")).toBe("⨱×bar"); + }); + + it("should decode multi-byte entities", () => { + expect(entities.decodeHTML("≧̸")).toBe("≧̸"); + }); + + it("should not decode legacy entities followed by text in attribute mode", () => { + expect( + entities.decodeHTML("¬", entities.DecodingMode.Attribute), + ).toBe("¬"); + + expect( + entities.decodeHTML("¬i", entities.DecodingMode.Attribute), + ).toBe("¬i"); + + expect( + entities.decodeHTML("¬=", entities.DecodingMode.Attribute), + ).toBe("¬="); + + expect(entities.decodeHTMLAttribute("¬p")).toBe("¬p"); + expect(entities.decodeHTMLAttribute("¬P")).toBe("¬P"); + expect(entities.decodeHTMLAttribute("¬3")).toBe("¬3"); + }); +}); + +describe("EntityDecoder", () => { + it("should decode decimal entities", () => { + const callback = vitest.fn(); + const decoder = new entities.EntityDecoder( + entities.htmlDecodeTree, + callback, + ); + + expect(decoder.write("", 1)).toBe(-1); + expect(decoder.write("8;", 0)).toBe(5); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(":".charCodeAt(0), 5); + }); + + it("should decode hex entities", () => { + const callback = vitest.fn(); + const decoder = new entities.EntityDecoder( + entities.htmlDecodeTree, + callback, + ); + + expect(decoder.write(":", 1)).toBe(6); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(":".charCodeAt(0), 6); + }); + + it("should decode named entities", () => { + const callback = vitest.fn(); + const decoder = new entities.EntityDecoder( + entities.htmlDecodeTree, + callback, + ); + + expect(decoder.write("&", 1)).toBe(5); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith("&".charCodeAt(0), 5); + }); + + it("should decode legacy entities", () => { + const callback = vitest.fn(); + const decoder = new entities.EntityDecoder( + entities.htmlDecodeTree, + callback, + ); + decoder.startEntity(entities.DecodingMode.Legacy); + + expect(decoder.write("&", 1)).toBe(-1); + + expect(callback).toHaveBeenCalledTimes(0); + + expect(decoder.end()).toBe(4); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith("&".charCodeAt(0), 4); + }); + + it("should decode named entity written character by character", () => { + const callback = vitest.fn(); + const decoder = new entities.EntityDecoder( + entities.htmlDecodeTree, + callback, + ); + + for (const c of "amp") { + expect(decoder.write(c, 0)).toBe(-1); + } + expect(decoder.write(";", 0)).toBe(5); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith("&".charCodeAt(0), 5); + }); + + it("should decode numeric entity written character by character", () => { + const callback = vitest.fn(); + const decoder = new entities.EntityDecoder( + entities.htmlDecodeTree, + callback, + ); + + for (const c of "#x3a") { + expect(decoder.write(c, 0)).toBe(-1); + } + expect(decoder.write(";", 0)).toBe(6); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(":".charCodeAt(0), 6); + }); + + it("should decode hex entities across several chunks", () => { + const callback = vitest.fn(); + const decoder = new entities.EntityDecoder( + entities.htmlDecodeTree, + callback, + ); + + for (const chunk of ["#x", "cf", "ff", "d"]) { + expect(decoder.write(chunk, 0)).toBe(-1); + } + + expect(decoder.write(";", 0)).toBe(9); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(0xc_ff_fd, 9); + }); + + it("should not fail if nothing is written", () => { + const callback = vitest.fn(); + const decoder = new entities.EntityDecoder( + entities.htmlDecodeTree, + callback, + ); + + expect(decoder.end()).toBe(0); + expect(callback).toHaveBeenCalledTimes(0); + }); + + describe("errors", () => { + it("should produce an error for a named entity without a semicolon", () => { + const errorHandlers = { + missingSemicolonAfterCharacterReference: vitest.fn(), + absenceOfDigitsInNumericCharacterReference: vitest.fn(), + validateNumericCharacterReference: vitest.fn(), + }; + const callback = vitest.fn(); + const decoder = new entities.EntityDecoder( + entities.htmlDecodeTree, + callback, + errorHandlers, + ); + + decoder.startEntity(entities.DecodingMode.Legacy); + expect(decoder.write("&", 1)).toBe(5); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith("&".charCodeAt(0), 5); + expect( + errorHandlers.missingSemicolonAfterCharacterReference, + ).toHaveBeenCalledTimes(0); + + decoder.startEntity(entities.DecodingMode.Legacy); + expect(decoder.write("&", 1)).toBe(-1); + expect(decoder.end()).toBe(4); + + expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenLastCalledWith("&".charCodeAt(0), 4); + expect( + errorHandlers.missingSemicolonAfterCharacterReference, + ).toHaveBeenCalledTimes(1); + }); + + it("should produce an error for a numeric entity without a semicolon", () => { + const errorHandlers = { + missingSemicolonAfterCharacterReference: vitest.fn(), + absenceOfDigitsInNumericCharacterReference: vitest.fn(), + validateNumericCharacterReference: vitest.fn(), + }; + const callback = vitest.fn(); + const decoder = new entities.EntityDecoder( + entities.htmlDecodeTree, + callback, + errorHandlers, + ); + + decoder.startEntity(entities.DecodingMode.Legacy); + expect(decoder.write(":", 1)).toBe(-1); + expect(decoder.end()).toBe(5); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(0x3a, 5); + expect( + errorHandlers.missingSemicolonAfterCharacterReference, + ).toHaveBeenCalledTimes(1); + expect( + errorHandlers.absenceOfDigitsInNumericCharacterReference, + ).toHaveBeenCalledTimes(0); + expect( + errorHandlers.validateNumericCharacterReference, + ).toHaveBeenCalledTimes(1); + expect( + errorHandlers.validateNumericCharacterReference, + ).toHaveBeenCalledWith(0x3a); + }); + + it("should produce an error for numeric entities without digits", () => { + const errorHandlers = { + missingSemicolonAfterCharacterReference: vitest.fn(), + absenceOfDigitsInNumericCharacterReference: vitest.fn(), + validateNumericCharacterReference: vitest.fn(), + }; + const callback = vitest.fn(); + const decoder = new entities.EntityDecoder( + entities.htmlDecodeTree, + callback, + errorHandlers, + ); + + decoder.startEntity(entities.DecodingMode.Legacy); + expect(decoder.write("&#", 1)).toBe(-1); + expect(decoder.end()).toBe(0); + + expect(callback).toHaveBeenCalledTimes(0); + expect( + errorHandlers.missingSemicolonAfterCharacterReference, + ).toHaveBeenCalledTimes(0); + expect( + errorHandlers.absenceOfDigitsInNumericCharacterReference, + ).toHaveBeenCalledTimes(1); + expect( + errorHandlers.absenceOfDigitsInNumericCharacterReference, + ).toHaveBeenCalledWith(2); + expect( + errorHandlers.validateNumericCharacterReference, + ).toHaveBeenCalledTimes(0); + }); + + it("should produce an error for hex entities without digits", () => { + const errorHandlers = { + missingSemicolonAfterCharacterReference: vitest.fn(), + absenceOfDigitsInNumericCharacterReference: vitest.fn(), + validateNumericCharacterReference: vitest.fn(), + }; + const callback = vitest.fn(); + const decoder = new entities.EntityDecoder( + entities.htmlDecodeTree, + callback, + errorHandlers, + ); + + decoder.startEntity(entities.DecodingMode.Legacy); + expect(decoder.write("&#x", 1)).toBe(-1); + expect(decoder.end()).toBe(0); + + expect(callback).toHaveBeenCalledTimes(0); + expect( + errorHandlers.missingSemicolonAfterCharacterReference, + ).toHaveBeenCalledTimes(0); + expect( + errorHandlers.absenceOfDigitsInNumericCharacterReference, + ).toHaveBeenCalledTimes(1); + expect( + errorHandlers.validateNumericCharacterReference, + ).toHaveBeenCalledTimes(0); + }); + }); +}); |
