Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion __tests__/commands/insert-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { EditorState, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { WaterproofSchema } from "../../src/schema";
import { getCmdInsertCode } from "../../src/commands/insert-command";
import { getCmdInsertCode, getCmdInsertMarkdown, getCmdInsertLatex } from "../../src/commands/insert-command";
import { InsertionPlace } from "../../src/commands";
import { configuration } from "../../src/markdown-defaults";

Expand Down Expand Up @@ -53,7 +53,78 @@ test("Insert code below twice (selection static)", () => {
expect(view.state.toJSON()).toStrictEqual(newState2);
});

// States for testing insertion adjacent to code blocks using the real configuration
// (code has openRequiresNewline: true, closeRequiresNewline: true)
//
// doc: [code("Content.")][newline][math_display("Content.")]
// Positions: code size=10 (pos 0-10), newline size=1 (pos 10), math_display starts at pos 11
const stateCodeNewlineMath_mathSelected = {"doc":{"type":"doc","content":[{"type":"code","content":[{"type":"text","text":"Content."}]},{"type":"newline"},{"type":"math_display","content":[{"type":"text","text":"Content."}]}]},"selection":{"type":"node","anchor":11}};

// doc: [math_display("Content.")][newline][code("Content.")]
// math_display size=10 (pos 0-10), newline size=1 (pos 10), code starts at pos 11
const stateMathNewlineCode_mathSelected = {"doc":{"type":"doc","content":[{"type":"math_display","content":[{"type":"text","text":"Content."}]},{"type":"newline"},{"type":"code","content":[{"type":"text","text":"Content."}]}]},"selection":{"type":"node","anchor":0}};

test("Insert markdown above math_display when code is before the newline adds extra newline", () => {
const view = new EditorView(null, {state: EditorState.fromJSON({schema: WaterproofSchema}, stateCodeNewlineMath_mathSelected)});
const cmd = getCmdInsertMarkdown(InsertionPlace.Above, tagConf);
expect(cmd(view.state, view.dispatch, view)).toBe(true);

const content = view.state.toJSON().doc.content;
expect(content).toStrictEqual([
{"type":"code","content":[{"type":"text","text":"Content."}]},
{"type":"newline"},
{"type":"markdown"},
{"type":"newline"},
{"type":"math_display","content":[{"type":"text","text":"Content."}]}
]);
});

test("Insert math above math_display when code is before the newline adds extra newline", () => {
const view = new EditorView(null, {state: EditorState.fromJSON({schema: WaterproofSchema}, stateCodeNewlineMath_mathSelected)});
const cmd = getCmdInsertLatex(InsertionPlace.Above, tagConf);
expect(cmd(view.state, view.dispatch, view)).toBe(true);

const content = view.state.toJSON().doc.content;
expect(content).toStrictEqual([
{"type":"code","content":[{"type":"text","text":"Content."}]},
{"type":"newline"},
{"type":"math_display"},
{"type":"newline"},
{"type":"math_display","content":[{"type":"text","text":"Content."}]}
]);
});

test("Insert markdown below math_display when code is after the newline adds extra newline", () => {
const view = new EditorView(null, {state: EditorState.fromJSON({schema: WaterproofSchema}, stateMathNewlineCode_mathSelected)});
const cmd = getCmdInsertMarkdown(InsertionPlace.Below, tagConf);
expect(cmd(view.state, view.dispatch, view)).toBe(true);

const content = view.state.toJSON().doc.content;
expect(content).toStrictEqual([
{"type":"math_display","content":[{"type":"text","text":"Content."}]},
{"type":"newline"},
{"type":"markdown"},
{"type":"newline"},
{"type":"code","content":[{"type":"text","text":"Content."}]}
]);
});

test("Insert math below math_display when code is after the newline adds extra newline", () => {
const view = new EditorView(null, {state: EditorState.fromJSON({schema: WaterproofSchema}, stateMathNewlineCode_mathSelected)});
const cmd = getCmdInsertLatex(InsertionPlace.Below, tagConf);
expect(cmd(view.state, view.dispatch, view)).toBe(true);

const content = view.state.toJSON().doc.content;
expect(content).toStrictEqual([
{"type":"math_display","content":[{"type":"text","text":"Content."}]},
{"type":"newline"},
{"type":"math_display"},
{"type":"newline"},
{"type":"code","content":[{"type":"text","text":"Content."}]}
]);
});
test("Insert code below twice (selection moves down)", () => {

const view = new EditorView(null, {state: EditorState.fromJSON({schema: WaterproofSchema}, state)});

const cmd = getCmdInsertCode(InsertionPlace.Below, tagConf);
Expand Down
106 changes: 106 additions & 0 deletions __tests__/mapping/mapping-update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,110 @@ test("Mapping.update text insert inside input shifts wrapper and later blocks",
expect(updatedInput.contentRange.to).toBe(inputContentEnd + 1);
expect(updatedAfter.contentRange.from).toBe(afterContentStart + 1);
expect(updatedAfter.tagRange.from).toBe(afterTagStart + 1);

// Inserting "X" (no newlines) should preserve the code block's lineStart
// The parser sets the code block lineStart to 2 for this document structure
const updatedCode = findFirstCodeNode(updatedTree.root);
expect(updatedCode).not.toBeNull();
expect(updatedCode!.lineStart).toBe(2);
expect(updatedTree.computeLineNumbers()).toStrictEqual([2]);
});

test("Mapping.update node insert shifts lineStart of subsequent code blocks", () => {
// Document: ```coq\nFirst\n```\n```coq\nSecond\n```
// Two code blocks: first at line 1, second at line 4
const doc = "```coq\nFirst\n```\n```coq\nSecond\n```";

const blocks = parse(doc, {language: "coq"});
const mapping = new Mapping(blocks, 0, config, serializer);
const proseDoc = constructDocument(blocks);

const tree = mapping.getMapping();
const codeNodes = tree.root.children.filter(node => node.type === "code");
expect(codeNodes.length).toBe(2);
const firstLineStart = codeNodes[0].lineStart;
const secondLineStart = codeNodes[1].lineStart;
expect(firstLineStart).toBe(1);
expect(secondLineStart).toBe(4);

// Insert a new code block before the first code block (at position 0)
const slice = new Slice(Fragment.from([
WaterproofSchema.nodes.code.create(null, Fragment.from(WaterproofSchema.text("New"))),
WaterproofSchema.nodes.newline.create()
]), 0, 0);
const step = new ReplaceStep(0, 0, slice);

mapping.update(step, proseDoc);

const updatedTree = mapping.getMapping();
sanityCheckTree(updatedTree.root);

const updatedCodeNodes: TreeNode[] = [];
updatedTree.traverseDepthFirst(node => {
if (node.type === "code") updatedCodeNodes.push(node);
});

expect(updatedCodeNodes.length).toBe(3);

// The newly inserted code block should have a computed lineStart
// ```coq\nNew\n``` starts at the beginning, so lineStart = 1
expect(updatedCodeNodes[0].lineStart).toBe(1);

// The original first code block should now be shifted by the newlines in the inserted content
// Inserted text: "```coq\nNew\n```\n" = 3 newlines, so original first shifts from 1 to 1+3 = 4
expect(updatedCodeNodes[1].lineStart).toBe(firstLineStart + 3);

// The original second code block should also shift by the same amount
expect(updatedCodeNodes[2].lineStart).toBe(secondLineStart + 3);

expect(updatedTree.computeLineNumbers()).toStrictEqual([1, 4, 7]);
});

test("Mapping.update node insert in the middle shifts lineStart of later code blocks", () => {
// Document: ```coq\nFirst\n```\n```coq\nSecond\n```
// Two code blocks: first at line 1, second at line 4
const doc = "```coq\nFirst\n```\n```coq\nSecond\n```";

const blocks = parse(doc, {language: "coq"});
const mapping = new Mapping(blocks, 0, config, serializer);
const proseDoc = constructDocument(blocks);

const tree = mapping.getMapping();
const codeNodes = tree.root.children.filter(node => node.type === "code");
expect(codeNodes.length).toBe(2);
expect(codeNodes[0].lineStart).toBe(1);
expect(codeNodes[1].lineStart).toBe(4);

// Insert a new code block between the two existing ones
// The newline between them is at pmRange {7, 8}, so inserting at position 8
// places the new node right before the second code block
const slice = new Slice(Fragment.from([
WaterproofSchema.nodes.code.create(null, Fragment.from(WaterproofSchema.text("Middle"))),
WaterproofSchema.nodes.newline.create()
]), 0, 0);
const step = new ReplaceStep(8, 8, slice);

mapping.update(step, proseDoc);

const updatedTree = mapping.getMapping();
sanityCheckTree(updatedTree.root);

const updatedCodeNodes: TreeNode[] = [];
updatedTree.traverseDepthFirst(node => {
if (node.type === "code") updatedCodeNodes.push(node);
});

expect(updatedCodeNodes.length).toBe(3);

// First code block is unchanged
expect(updatedCodeNodes[0].lineStart).toBe(1);

// Inserted code block: after "```coq\nFirst\n```\n" (3 newlines), so lineStart = 4
// The open tag ```coq\n adds 1 more, content starts at line 5
expect(updatedCodeNodes[1].lineStart).toBe(4);

// Second code block shifted by 3 newlines (```coq\nMiddle\n```\n)
expect(updatedCodeNodes[2].lineStart).toBe(4 + 3);

expect(updatedTree.computeLineNumbers()).toStrictEqual([1, 4, 7]);
});
81 changes: 54 additions & 27 deletions __tests__/mapping/newmapping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ function createTestMapping(blocks: WaterproofDocument) {
return tree;
}

const PLACEHOLDER_LINENR = 0;

test("testMapping markdown only", () => {
const blocks = [new MarkdownBlock("Hello", {from: 0, to: 5}, {from: 0, to: 5}, 0)];
const nodes = createTestMapping(blocks);
Expand All @@ -30,11 +28,15 @@ test("testMapping markdown only", () => {
expect(markdownNode.prosemirrorEnd).toBe(6);
expect(markdownNode.pmRange).toStrictEqual<Range>({from: 0, to: 7});
expect(markdownNode.lineStart).toBe(0);

// No code blocks → no line numbers
expect(nodes.computeLineNumbers()).toStrictEqual([]);
});

test("testMapping code", () => {
const blocks = [new CodeBlock("Lemma test", {from: 0, to: 21}, {from: 7, to: 17}, 0)];
const nodes = createTestMapping(blocks).root.children;
const tree = createTestMapping(blocks);
const nodes = tree.root.children;

expect(nodes.length).toBe(1);

Expand All @@ -46,20 +48,23 @@ test("testMapping code", () => {
expect(codeNode.prosemirrorEnd).toBe(11);
expect(codeNode.pmRange).toStrictEqual<Range>({from: 0, to: 12});
expect(codeNode.lineStart).toBe(0);

expect(tree.computeLineNumbers()).toStrictEqual([0]);
});

// TODO: Test for line nrs
test("Input-area with nested code", () => {
// <input-area>\n```lan\nTest\n```\n</input-area>Hello
// Line counting: \n at pos 12 (line 1), \n at pos 19 in ```lan\n (line 2) → code starts at line 2
const blocks = [
new InputAreaBlock("```lan\nTest\n```", {from: 0, to: 42}, {from: 12, to: 29}, PLACEHOLDER_LINENR, [
new NewlineBlock({from: 12, to: 13}, {from: 12, to: 13}, PLACEHOLDER_LINENR),
new CodeBlock("Test", {from: 13, to: 28}, {from: 20, to: 24}, PLACEHOLDER_LINENR),
new NewlineBlock({from: 28, to: 29}, {from: 28, to: 29}, PLACEHOLDER_LINENR)
new InputAreaBlock("```lan\nTest\n```", {from: 0, to: 42}, {from: 12, to: 29}, 0, [
new NewlineBlock({from: 12, to: 13}, {from: 12, to: 13}, 0),
new CodeBlock("Test", {from: 13, to: 28}, {from: 20, to: 24}, 2),
new NewlineBlock({from: 28, to: 29}, {from: 28, to: 29}, 0)
]),
new MarkdownBlock("Hello", {from: 42, to: 47}, {from: 42, to: 47}, PLACEHOLDER_LINENR)
new MarkdownBlock("Hello", {from: 42, to: 47}, {from: 42, to: 47}, 0)
];
const nodes = createTestMapping(blocks).root.children;
const tree = createTestMapping(blocks);
const nodes = tree.root.children;

expect(nodes.length).toBe(2);

Expand Down Expand Up @@ -96,27 +101,32 @@ test("Input-area with nested code", () => {
expect(second.prosemirrorStart).toBe(3);
expect(second.prosemirrorEnd).toBe(7);
expect(second.pmRange).toStrictEqual<Range>({from: 2, to: 8});
expect(second.lineStart).toBe(2);

expect(third.type).toBe("newline");
expect(third.contentRange).toStrictEqual<Range>({from: 28, to: 29});
expect(third.tagRange).toStrictEqual<Range>({from: 28, to: 29});
expect(third.prosemirrorStart).toBe(8);
expect(third.prosemirrorEnd).toBe(8);
expect(third.pmRange).toStrictEqual<Range>({from: 8, to: 9});

// One code block at line 2
expect(tree.computeLineNumbers()).toStrictEqual([2]);
});

// TODO: Test for line nrs
test("Hint block with code and markdown inside", () => {
// <hint title=\"Import libraries\">\n```lan\nRequire Import Rbase.\n```\n</hint>
// Line counting: \n at pos 31 (line 1), \n at pos 38 in ```lan\n (line 2) → code starts at line 2
const blocks = [
new HintBlock("\n```lan\nRequire Import Rbase.\n```\n", "Import libraries", {from: 0, to: 72}, {from: 31, to: 65}, PLACEHOLDER_LINENR, [
new NewlineBlock({from: 31, to: 32}, {from: 31, to: 32}, PLACEHOLDER_LINENR),
new CodeBlock("Require Import Rbase.", {from: 32, to: 64}, {from: 39, to: 60}, PLACEHOLDER_LINENR),
new NewlineBlock({from: 60, to: 61}, {from: 60, to: 61}, PLACEHOLDER_LINENR)
new HintBlock("\n```lan\nRequire Import Rbase.\n```\n", "Import libraries", {from: 0, to: 72}, {from: 31, to: 65}, 0, [
new NewlineBlock({from: 31, to: 32}, {from: 31, to: 32}, 0),
new CodeBlock("Require Import Rbase.", {from: 32, to: 64}, {from: 39, to: 60}, 2),
new NewlineBlock({from: 60, to: 61}, {from: 60, to: 61}, 0)
])
];

const nodes = createTestMapping(blocks).root.children;
const tree = createTestMapping(blocks);
const nodes = tree.root.children;

expect(nodes.length).toBe(1);

Expand Down Expand Up @@ -146,30 +156,39 @@ test("Hint block with code and markdown inside", () => {
expect(second.prosemirrorStart).toBe(3);
expect(second.prosemirrorEnd).toBe(24);
expect(second.pmRange).toStrictEqual<Range>({from: 2, to: 25});
expect(second.lineStart).toBe(2);

expect(third.type).toBe("newline");
expect(third.contentRange).toStrictEqual<Range>({from: 60, to: 61});
expect(third.tagRange).toStrictEqual<Range>({from: 60, to: 61});
expect(third.prosemirrorStart).toBe(25);
expect(third.prosemirrorEnd).toBe(25);
expect(third.pmRange).toStrictEqual<Range>({from: 25, to: 26});

// One code block at line 2
expect(tree.computeLineNumbers()).toStrictEqual([2]);
});

// TODO: Test for line nrs
test("Mixed content: markdown, code, input-area, markdown", () => {
// ### Example:\n```lan\nLemma\nTest\n```\n<input-area>\n```lan\n(* Your solution here *)\n```\n</input-area>
// Line counting:
// \n at pos 12 (line 1), \n at pos 19 in ```lan\n (line 2) → first code starts at line 2
// Content "Lemma\nTest" has \n at pos 25 (line 3), then \n``` at pos 30 (line 4), trailing \n at pos 34 (line 5)
// <input-area> at pos 35 has no newlines, \n at pos 47 (line 6), \n at pos 54 in ```lan\n (line 7)
// → second code starts at line 7
const blocks = [
new MarkdownBlock("### Example:", {from: 0, to: 12}, {from: 0, to: 12}, PLACEHOLDER_LINENR),
new NewlineBlock({from: 12, to: 13}, {from: 12, to: 13}, PLACEHOLDER_LINENR),
new CodeBlock("Lemma\nTest", {from: 13, to: 34}, {from: 20, to: 30}, PLACEHOLDER_LINENR),
new NewlineBlock({from: 34, to: 35}, {from: 34, to: 35}, PLACEHOLDER_LINENR),
new InputAreaBlock("```lan\n(* Your solution here *)\n```", {from: 35, to: 97}, {from: 47, to: 84}, PLACEHOLDER_LINENR, [
new NewlineBlock({from: 47, to: 48}, {from: 47, to: 48}, PLACEHOLDER_LINENR),
new CodeBlock("(* Your solution here *)", {from: 48, to: 83}, {from: 55, to: 79}, PLACEHOLDER_LINENR),
new NewlineBlock({from: 83, to: 84}, {from: 83, to: 84}, PLACEHOLDER_LINENR)
new MarkdownBlock("### Example:", {from: 0, to: 12}, {from: 0, to: 12}, 0),
new NewlineBlock({from: 12, to: 13}, {from: 12, to: 13}, 0),
new CodeBlock("Lemma\nTest", {from: 13, to: 34}, {from: 20, to: 30}, 2),
new NewlineBlock({from: 34, to: 35}, {from: 34, to: 35}, 0),
new InputAreaBlock("```lan\n(* Your solution here *)\n```", {from: 35, to: 97}, {from: 47, to: 84}, 0, [
new NewlineBlock({from: 47, to: 48}, {from: 47, to: 48}, 0),
new CodeBlock("(* Your solution here *)", {from: 48, to: 83}, {from: 55, to: 79}, 7),
new NewlineBlock({from: 83, to: 84}, {from: 83, to: 84}, 0)
])
];
const nodes = createTestMapping(blocks).root.children;
const tree = createTestMapping(blocks);
const nodes = tree.root.children;

expect(nodes.length).toBe(5);

Expand Down Expand Up @@ -198,6 +217,7 @@ test("Mixed content: markdown, code, input-area, markdown", () => {
expect(code1.prosemirrorStart).toBe(16);
expect(code1.prosemirrorEnd).toBe(26);
expect(code1.pmRange).toStrictEqual<Range>({from: 15, to: 27});
expect(code1.lineStart).toBe(2);

// Newline node
expect(nl2.type).toBe("newline");
Expand Down Expand Up @@ -232,19 +252,24 @@ test("Mixed content: markdown, code, input-area, markdown", () => {
expect(ia_code.prosemirrorStart).toBe(31);
expect(ia_code.prosemirrorEnd).toBe(55);
expect(ia_code.pmRange).toStrictEqual<Range>({from: 30, to: 56});
expect(ia_code.lineStart).toBe(7);

expect(ia_nl2.type).toBe("newline");
expect(ia_nl2.contentRange).toStrictEqual<Range>({from: 83, to: 84});
expect(ia_nl2.tagRange).toStrictEqual<Range>({from: 83, to: 84});
expect(ia_nl2.prosemirrorStart).toBe(56);
expect(ia_nl2.prosemirrorEnd).toBe(56);
expect(ia_nl2.pmRange).toStrictEqual<Range>({from: 56, to: 57});

// Two code blocks: first at line 2, second at line 7
expect(tree.computeLineNumbers()).toStrictEqual([2, 7]);
});

test("Empty codeblock", () => {
// ```lan\n\n```
const blocks = [new CodeBlock("", {from: 0, to: 11}, {from: 7, to: 7}, 0)];
const nodes = createTestMapping(blocks).root.children;
const tree = createTestMapping(blocks);
const nodes = tree.root.children;
expect(nodes.length).toBe(1);

const code = nodes[0];
Expand All @@ -255,4 +280,6 @@ test("Empty codeblock", () => {
expect(code.prosemirrorEnd).toBe(1);
expect(code.pmRange).toStrictEqual<Range>({from: 0, to: 2});
expect(code.lineStart).toBe(0);

expect(tree.computeLineNumbers()).toStrictEqual([0]);
});
Loading