Spaces:
Paused
Paused
| import { describe, expect, it, beforeEach } from "vitest"; | |
| import { SplicedBlob } from "./SplicedBlob"; | |
| describe("SplicedBlob", () => { | |
| let originalBlob: Blob; | |
| let insertBlob: Blob; | |
| let replaceBlob: Blob; | |
| beforeEach(() => { | |
| // originalBlob: "0123456789" (10 chars) | |
| originalBlob = new Blob(["0123456789"]); | |
| // insertBlob: "ABC" (3 chars) - used in tests where we insert something into the blob | |
| insertBlob = new Blob(["ABC"]); | |
| // replaceBlob: "XY" (2 chars) - used in tests where part of the blob is replaced | |
| replaceBlob = new Blob(["XY"]); | |
| }); | |
| describe("create", () => { | |
| it("should create a SplicedBlob with valid parameters", () => { | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| expect(splicedBlob).toBeInstanceOf(SplicedBlob); | |
| }); | |
| it("should throw error for negative start", () => { | |
| expect(() => SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: -1, end: 5 }])).toThrow( | |
| "Invalid start/end positions for SplicedBlob", | |
| ); | |
| }); | |
| it("should throw error for negative end", () => { | |
| expect(() => SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: -1 }])).toThrow( | |
| "Invalid start/end positions for SplicedBlob", | |
| ); | |
| }); | |
| it("should throw error for start > original.size", () => { | |
| expect(() => SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 15, end: 5 }])).toThrow( | |
| "Invalid start/end positions for SplicedBlob", | |
| ); | |
| }); | |
| it("should throw error for end > original.size", () => { | |
| expect(() => SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 15 }])).toThrow( | |
| "Invalid start/end positions for SplicedBlob", | |
| ); | |
| }); | |
| it("should throw error for start > end", () => { | |
| expect(() => SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 7, end: 5 }])).toThrow( | |
| "Invalid start/end positions for SplicedBlob", | |
| ); | |
| }); | |
| }); | |
| describe("size and type", () => { | |
| it("should calculate size correctly for insertion", () => { | |
| // Insert "ABC" at position 5: "01234ABC56789" = 13 chars | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| expect(splicedBlob.size).toBe(13); | |
| }); | |
| it("should calculate size correctly for replacement", () => { | |
| // Replace "345" with "XY": "012XY6789" = 9 chars | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: replaceBlob, start: 3, end: 6 }]); | |
| expect(splicedBlob.size).toBe(9); | |
| }); | |
| it("should return original blob type", () => { | |
| const typedBlob = new Blob(["test"], { type: "text/plain" }); | |
| const splicedBlob = SplicedBlob.create(typedBlob, [{ insert: insertBlob, start: 2, end: 2 }]); | |
| expect(splicedBlob.type).toBe("text/plain"); | |
| }); | |
| }); | |
| describe("text method", () => { | |
| it("should insert at beginning", async () => { | |
| // Insert "ABC" at start: "ABC0123456789" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 0, end: 0 }]); | |
| const text = await splicedBlob.text(); | |
| expect(text).toBe("ABC0123456789"); | |
| }); | |
| it("should insert at end", async () => { | |
| // Insert "ABC" at end: "0123456789ABC" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 10, end: 10 }]); | |
| const text = await splicedBlob.text(); | |
| expect(text).toBe("0123456789ABC"); | |
| }); | |
| it("should insert in middle", async () => { | |
| // Insert "ABC" at position 5: "01234ABC56789" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const text = await splicedBlob.text(); | |
| expect(text).toBe("01234ABC56789"); | |
| }); | |
| it("should replace content", async () => { | |
| // Replace "345" with "XY": "012XY6789" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: replaceBlob, start: 3, end: 6 }]); | |
| const text = await splicedBlob.text(); | |
| expect(text).toBe("012XY6789"); | |
| }); | |
| it("should replace everything", async () => { | |
| // Replace entire content with "ABC": "ABC" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 0, end: 10 }]); | |
| const text = await splicedBlob.text(); | |
| expect(text).toBe("ABC"); | |
| }); | |
| }); | |
| describe("slice method - basic cases", () => { | |
| it("should return empty blob for start >= end", async () => { | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(5, 5); | |
| expect(slice.size).toBe(0); | |
| expect(await slice.text()).toBe(""); | |
| }); | |
| it("should handle slice beyond size", async () => { | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(10, 20); | |
| const text = await slice.text(); | |
| expect(text).toBe("789"); // Only gets what's available | |
| }); | |
| it("should throw error for negative start/end", () => { | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| expect(() => splicedBlob.slice(-1, 5)).toThrow("Unsupported negative start/end on SplicedBlob.slice"); | |
| expect(() => splicedBlob.slice(0, -1)).toThrow("Unsupported negative start/end on SplicedBlob.slice"); | |
| }); | |
| }); | |
| describe("slice method - before segment only", () => { | |
| it("should slice entirely in before segment", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(1, 4) = "123" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(1, 4); | |
| const text = await slice.text(); | |
| expect(text).toBe("123"); | |
| }); | |
| it("should slice from start of before segment", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(0, 3) = "012" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(0, 3); | |
| const text = await slice.text(); | |
| expect(text).toBe("012"); | |
| }); | |
| it("should slice to end of before segment", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(2, 5) = "234" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(2, 5); | |
| const text = await slice.text(); | |
| expect(text).toBe("234"); | |
| }); | |
| }); | |
| describe("slice method - insert segment only", () => { | |
| it("should slice entirely in insert segment", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(6, 7) = "B" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(6, 7); | |
| const text = await slice.text(); | |
| expect(text).toBe("B"); | |
| }); | |
| it("should slice entire insert segment", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(5, 8) = "ABC" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(5, 8); | |
| const text = await slice.text(); | |
| expect(text).toBe("ABC"); | |
| }); | |
| it("should slice from start of insert segment", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(5, 7) = "AB" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(5, 7); | |
| const text = await slice.text(); | |
| expect(text).toBe("AB"); | |
| }); | |
| it("should slice to end of insert segment", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(6, 8) = "BC" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(6, 8); | |
| const text = await slice.text(); | |
| expect(text).toBe("BC"); | |
| }); | |
| }); | |
| describe("slice method - after segment only", () => { | |
| it("should slice entirely in after segment", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(9, 12) = "678" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(9, 12); | |
| const text = await slice.text(); | |
| expect(text).toBe("678"); | |
| }); | |
| it("should slice from start of after segment", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(8, 11) = "567" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(8, 11); | |
| const text = await slice.text(); | |
| expect(text).toBe("567"); | |
| }); | |
| it("should slice to end of after segment", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(10, 13) = "789" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(10, 13); | |
| const text = await slice.text(); | |
| expect(text).toBe("789"); | |
| }); | |
| }); | |
| describe("slice method - spanning before and insert", () => { | |
| it("should slice spanning before and insert segments", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(3, 7) = "34AB" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(3, 7); | |
| const text = await slice.text(); | |
| expect(text).toBe("34AB"); | |
| }); | |
| it("should slice from start spanning before and insert", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(0, 6) = "01234A" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(0, 6); | |
| const text = await slice.text(); | |
| expect(text).toBe("01234A"); | |
| }); | |
| it("should slice to end of insert spanning before and insert", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(4, 8) = "4ABC" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(4, 8); | |
| const text = await slice.text(); | |
| expect(text).toBe("4ABC"); | |
| }); | |
| }); | |
| describe("slice method - spanning insert and after", () => { | |
| it("should slice spanning insert and after segments", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(6, 10) = "BC56" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(6, 10); | |
| const text = await slice.text(); | |
| expect(text).toBe("BC56"); | |
| }); | |
| it("should slice from start of insert to end", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(5, 13) = "ABC56789" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(5, 13); | |
| const text = await slice.text(); | |
| expect(text).toBe("ABC56789"); | |
| }); | |
| it("should slice from middle of insert spanning to after", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(7, 11) = "C567" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(7, 11); | |
| const text = await slice.text(); | |
| expect(text).toBe("C567"); | |
| }); | |
| }); | |
| describe("slice method - spanning all three segments", () => { | |
| it("should slice spanning all three segments", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(3, 10) = "34ABC56" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(3, 10); | |
| const text = await slice.text(); | |
| expect(text).toBe("34ABC56"); | |
| }); | |
| it("should slice entire spliced blob", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(0, 13) = "01234ABC56789" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(0, 13); | |
| const text = await slice.text(); | |
| expect(text).toBe("01234ABC56789"); | |
| }); | |
| it("should slice most of spliced blob", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(1, 12) = "1234ABC5678" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice = splicedBlob.slice(1, 12); | |
| const text = await slice.text(); | |
| expect(text).toBe("1234ABC5678"); | |
| }); | |
| }); | |
| describe("slice method - with replacement", () => { | |
| it("should slice before replacement", async () => { | |
| // Replace "345" with "XY": "012XY6789", slice(0, 3) = "012" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: replaceBlob, start: 3, end: 6 }]); | |
| const slice = splicedBlob.slice(0, 3); | |
| const text = await slice.text(); | |
| expect(text).toBe("012"); | |
| }); | |
| it("should slice replacement only", async () => { | |
| // Replace "345" with "XY": "012XY6789", slice(3, 5) = "XY" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: replaceBlob, start: 3, end: 6 }]); | |
| const slice = splicedBlob.slice(3, 5); | |
| const text = await slice.text(); | |
| expect(text).toBe("XY"); | |
| }); | |
| it("should slice after replacement", async () => { | |
| // Replace "345" with "XY": "012XY6789", slice(5, 9) = "6789" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: replaceBlob, start: 3, end: 6 }]); | |
| const slice = splicedBlob.slice(5, 9); | |
| const text = await slice.text(); | |
| expect(text).toBe("6789"); | |
| }); | |
| it("should slice spanning before and replacement", async () => { | |
| // Replace "345" with "XY": "012XY6789", slice(1, 4) = "12X" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: replaceBlob, start: 3, end: 6 }]); | |
| const slice = splicedBlob.slice(1, 4); | |
| const text = await slice.text(); | |
| expect(text).toBe("12X"); | |
| }); | |
| it("should slice spanning replacement and after", async () => { | |
| // Replace "345" with "XY": "012XY6789", slice(4, 7) = "Y67" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: replaceBlob, start: 3, end: 6 }]); | |
| const slice = splicedBlob.slice(4, 7); | |
| const text = await slice.text(); | |
| expect(text).toBe("Y67"); | |
| }); | |
| it("should slice spanning all segments with replacement", async () => { | |
| // Replace "345" with "XY": "012XY6789", slice(1, 8) = "12XY678" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: replaceBlob, start: 3, end: 6 }]); | |
| const slice = splicedBlob.slice(1, 8); | |
| const text = await slice.text(); | |
| expect(text).toBe("12XY678"); | |
| }); | |
| }); | |
| describe("slice method - edge cases", () => { | |
| it("should handle empty insert blob", async () => { | |
| const emptyBlob = new Blob([""]); | |
| // Replace "345" with "": "0126789" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: emptyBlob, start: 3, end: 6 }]); | |
| const text = await splicedBlob.text(); | |
| expect(text).toBe("0126789"); | |
| const slice = splicedBlob.slice(2, 5); | |
| expect(await slice.text()).toBe("267"); | |
| }); | |
| it("should handle slice at segment boundaries", async () => { | |
| // SplicedBlob: "01234ABC56789", exact boundary slices | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| // End of before segment | |
| expect(await splicedBlob.slice(4, 5).text()).toBe("4"); | |
| // Start of insert segment | |
| expect(await splicedBlob.slice(5, 6).text()).toBe("A"); | |
| // End of insert segment | |
| expect(await splicedBlob.slice(7, 8).text()).toBe("C"); | |
| // Start of after segment | |
| expect(await splicedBlob.slice(8, 9).text()).toBe("5"); | |
| }); | |
| it("should handle insert at beginning with slice", async () => { | |
| // Insert "ABC" at start: "ABC0123456789" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 0, end: 0 }]); | |
| const slice = splicedBlob.slice(1, 5); | |
| expect(await slice.text()).toBe("BC01"); | |
| }); | |
| it("should handle insert at end with slice", async () => { | |
| // Insert "ABC" at end: "0123456789ABC" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 10, end: 10 }]); | |
| const slice = splicedBlob.slice(8, 12); | |
| expect(await slice.text()).toBe("89AB"); | |
| }); | |
| }); | |
| describe("arrayBuffer method", () => { | |
| it("should return correct ArrayBuffer for insertion", async () => { | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const buffer = await splicedBlob.arrayBuffer(); | |
| const text = new TextDecoder().decode(buffer); | |
| expect(text).toBe("01234ABC56789"); | |
| expect(buffer.byteLength).toBe(13); | |
| }); | |
| it("should return correct ArrayBuffer for replacement", async () => { | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: replaceBlob, start: 3, end: 6 }]); | |
| const buffer = await splicedBlob.arrayBuffer(); | |
| const text = new TextDecoder().decode(buffer); | |
| expect(text).toBe("012XY6789"); | |
| expect(buffer.byteLength).toBe(9); | |
| }); | |
| }); | |
| describe("stream method", () => { | |
| it("should return correct stream for insertion", async () => { | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const stream = splicedBlob.stream(); | |
| const response = new Response(stream); | |
| const text = await response.text(); | |
| expect(text).toBe("01234ABC56789"); | |
| }); | |
| it("should return correct stream for replacement", async () => { | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: replaceBlob, start: 3, end: 6 }]); | |
| const stream = splicedBlob.stream(); | |
| const response = new Response(stream); | |
| const text = await response.text(); | |
| expect(text).toBe("012XY6789"); | |
| }); | |
| it("should handle empty segments in stream", async () => { | |
| const emptyBlob = new Blob([""]); | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: emptyBlob, start: 5, end: 5 }]); | |
| const stream = splicedBlob.stream(); | |
| const response = new Response(stream); | |
| const text = await response.text(); | |
| expect(text).toBe("0123456789"); | |
| }); | |
| }); | |
| describe("nested slicing", () => { | |
| it("should allow slicing of sliced blob", async () => { | |
| // SplicedBlob: "01234ABC56789", slice(3, 10) = "34ABC56", then slice(2, 5) = "ABC" | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const firstSlice = splicedBlob.slice(3, 10); | |
| const secondSlice = firstSlice.slice(2, 5); | |
| const text = await secondSlice.text(); | |
| expect(text).toBe("ABC"); | |
| }); | |
| it("should handle multiple levels of slicing", async () => { | |
| // Complex slicing chain | |
| const splicedBlob = SplicedBlob.create(originalBlob, [{ insert: insertBlob, start: 5, end: 5 }]); | |
| const slice1 = splicedBlob.slice(2, 11); // "234ABC567" | |
| const slice2 = slice1.slice(1, 7); // "34ABC5" | |
| const slice3 = slice2.slice(2, 5); // "ABC" | |
| const text = await slice3.text(); | |
| expect(text).toBe("ABC"); | |
| }); | |
| }); | |
| }); | |