Skip to content

Commit 0b08974

Browse files
committed
wip: mess up tests
1 parent 3a12fae commit 0b08974

File tree

3 files changed

+98
-51
lines changed

3 files changed

+98
-51
lines changed

XMLEditor.spec.ts

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import {
1919
import { EditV2, isSetAttributes, isSetTextContent } from "./editv2.js";
2020

2121
import { XMLEditor } from "./XMLEditor.js";
22-
import { Transactor } from "./Transactor.js";
22+
import { Commit, Transactor } from "./Transactor.js";
2323

24-
describe("Utility function to handle EditV2 edits", () => {
24+
describe("XMLEditor", () => {
2525
let editor: Transactor<EditV2>;
2626
let sclDoc: XMLDocument;
2727

@@ -192,43 +192,100 @@ describe("Utility function to handle EditV2 edits", () => {
192192
it("undoes a committed edit on undo() call", () => {
193193
const node = sclDoc.querySelector("Substation")!;
194194

195-
editor.commit({ node });
196-
editor.undo();
195+
const commit = editor.commit({ node });
196+
const undone = editor.undo();
197197

198+
expect(undone).to.exist.and.to.equal(commit);
198199
expect(sclDoc.querySelector("Substation")).to.exist;
199200
});
200201

201202
it("redoes an undone edit on redo() call", () => {
202203
const node = sclDoc.querySelector("Substation")!;
203204

205+
const commit = editor.commit({ node });
206+
editor.undo();
207+
const redone = editor.redo();
208+
209+
expect(redone).to.exist.and.to.equal(commit);
210+
expect(sclDoc.querySelector("Substation")).to.be.null;
211+
});
212+
213+
it("undoes nothing at the beginning of the history", () => {
214+
const node = sclDoc.querySelector("Substation")!;
215+
204216
editor.commit({ node });
205217
editor.undo();
218+
const secondUndo = editor.undo();
219+
220+
expect(secondUndo).to.not.exist;
221+
});
222+
223+
it("redoes nothing at the end of the history", () => {
224+
const node = sclDoc.querySelector("Substation")!;
225+
226+
editor.commit({ node });
227+
const redo = editor.redo();
228+
229+
expect(redo).to.not.exist;
230+
});
231+
232+
it("allows the user to subscribe to commits and to unsubscribe", () => {
233+
const node = sclDoc.querySelector("Substation")!;
234+
const edit = { node };
235+
let committed: Commit<EditV2> | undefined;
236+
let called = 0;
237+
const callback = (commit: Commit<EditV2>) => {
238+
committed = commit;
239+
called++;
240+
};
241+
const unsubscribe = editor.subscribe(callback);
242+
editor.commit(edit, { title: "test" });
243+
expect(committed).to.exist.and.to.have.property("redo").to.include(edit);
244+
expect(committed).to.have.property("title", "test");
245+
expect(called).to.equal(1);
246+
expect(editor.past).to.have.lengthOf(1);
247+
248+
editor.undo();
249+
expect(called).to.equal(1);
250+
expect(editor.past).to.have.lengthOf(0);
251+
expect(editor.future).to.have.lengthOf(1);
252+
206253
editor.redo();
254+
expect(called).to.equal(1);
255+
expect(editor.past).to.have.lengthOf(1);
256+
expect(editor.future).to.have.lengthOf(0);
207257

208-
expect(sclDoc.querySelector("Substation")).to.be.null;
258+
const unsubscribed = unsubscribe();
259+
expect(unsubscribed).to.equal(callback);
260+
261+
editor.commit(edit, { title: "some other title, not test" });
262+
expect(committed).to.have.property("title", "test");
263+
expect(called).to.equal(1);
264+
expect(editor.past).to.have.lengthOf(2);
209265
});
210266

211267
describe("generally", () => {
212-
it("inserts elements on Insert edit events", () =>
268+
it("inserts elements on Insert edits", () =>
213269
assert(
214270
property(
215271
testDocs.chain(([doc1, doc2]) => {
216272
const nodes = doc1.nodes.concat(doc2.nodes);
217273
return insert(nodes);
218274
}),
219275
(edit) => {
220-
editor.commit(edit);
221-
if (isValidInsert(edit))
276+
if (isValidInsert(edit)) {
277+
editor.commit(edit);
222278
return (
223279
edit.node.parentElement === edit.parent &&
224280
edit.node.nextSibling === edit.reference
225281
);
282+
}
226283
return true;
227284
},
228285
),
229286
));
230287

231-
it("set's an element's textContent on SetTextContent edit events", () =>
288+
it("sets an element's textContent on SetTextContent edits", () =>
232289
assert(
233290
property(
234291
testDocs.chain(([doc1, doc2]) => {
@@ -243,7 +300,7 @@ describe("Utility function to handle EditV2 edits", () => {
243300
),
244301
));
245302

246-
it("updates default- and foreign-namespace attributes on UpdateNS events", () =>
303+
it("updates default- and foreign-namespace attributes on SetAttribute edits", () =>
247304
assert(
248305
property(
249306
testDocs.chain(([{ nodes }]) => setAttributes(nodes)),
@@ -279,7 +336,7 @@ describe("Utility function to handle EditV2 edits", () => {
279336
),
280337
)).timeout(20000);
281338

282-
it("removes elements on Remove edit events", () =>
339+
it("removes elements on Remove edits", () =>
283340
assert(
284341
property(
285342
testDocs.chain(([{ nodes }]) => remove(nodes)),
@@ -299,7 +356,11 @@ describe("Utility function to handle EditV2 edits", () => {
299356
doc.cloneNode(true),
300357
);
301358
edits.forEach((a: EditV2) => {
302-
editor.commit(a, { squash });
359+
try {
360+
editor.commit(a, { squash });
361+
} catch (e) {
362+
console.log("error", e);
363+
}
303364
});
304365
while (editor.past.length) editor.undo();
305366
expect(doc1).to.satisfy((doc: XMLDocument) =>

handleEdit.ts

Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,6 @@ function handleSetTextContent({
3030
return [undoTextContent, ...restoreChildNodes];
3131
}
3232

33-
function uniqueNSPrefix(element: Element, ns: string): string {
34-
let i = 1;
35-
const attributes = Array.from(element.attributes);
36-
const hasSamePrefix = (attribute: Attr) =>
37-
attribute.prefix === `ens${i}` && attribute.namespaceURI !== ns;
38-
const nsOrNull = new Set([null, ns]);
39-
const differentNamespace = (prefix: string) =>
40-
!nsOrNull.has(element.lookupNamespaceURI(prefix));
41-
while (differentNamespace(`ens${i}`) || attributes.find(hasSamePrefix))
42-
i += 1;
43-
return `ens${i}`;
44-
}
45-
4633
const xmlAttributeName =
4734
/^(?!xml|Xml|xMl|xmL|XMl|xML|XmL|XML)[A-Za-z_][A-Za-z0-9-_.]*(:[A-Za-z_][A-Za-z0-9-_.]*)?$/;
4835

@@ -58,6 +45,8 @@ function handleSetAttributes({
5845
const oldAttributes = { ...attributes };
5946
const oldAttributesNS = { ...attributesNS };
6047

48+
const errors: unknown[] = [];
49+
6150
// save element's non-prefixed attributes for undo
6251
Object.keys(attributes)
6352
.reverse()
@@ -71,9 +60,10 @@ function handleSetAttributes({
7160
const [name, value] = entry as [string, string | null];
7261
if (value === null) element.removeAttribute(name);
7362
else element.setAttribute(name, value);
74-
} catch (_e) {
63+
} catch (e) {
7564
// undo nothing if update didn't work on this attribute
7665
delete oldAttributes[entry[0]];
66+
errors.push(e);
7767
}
7868
}
7969

@@ -109,20 +99,21 @@ function handleSetAttributes({
10999
if (value === null) {
110100
element.removeAttributeNS(ns, name.split(":").pop()!);
111101
} else {
112-
let qualifiedName = name;
113-
if (!qualifiedName.includes(":")) {
114-
let prefix = element.lookupPrefix(ns);
115-
if (!prefix) prefix = uniqueNSPrefix(element, ns);
116-
qualifiedName = `${prefix}:${name}`;
117-
}
118-
element.setAttributeNS(ns, qualifiedName, value);
102+
element.setAttributeNS(ns, name, value);
119103
}
120-
} catch (_e) {
104+
} catch (e) {
121105
delete oldAttributesNS[ns]![entry[0]];
106+
errors.push(e);
122107
}
123108
}
124109
}
125110

111+
if (errors.length > 0)
112+
throw new Error(
113+
`Failed to set attributes on element ${element.tagName}`,
114+
{ cause: errors },
115+
);
116+
126117
return {
127118
element,
128119
attributes: oldAttributes,
@@ -148,22 +139,17 @@ function handleInsert({
148139
node,
149140
reference,
150141
}: Insert): Insert | Remove | [] {
151-
try {
152-
const { parentNode, nextSibling } = node;
153-
parent.insertBefore(node, reference);
154-
if (parentNode)
155-
// undo: move child node back to original place
156-
return {
157-
node,
158-
parent: parentNode,
159-
reference: nextSibling,
160-
};
161-
// undo: remove orphaned node
162-
return { node };
163-
} catch (_e) {
164-
// undo nothing if insert doesn't work on these nodes
165-
return [];
166-
}
142+
const { parentNode, nextSibling } = node;
143+
parent.insertBefore(node, reference);
144+
if (parentNode)
145+
// undo: move child node back to original place
146+
return {
147+
node,
148+
parent: parentNode,
149+
reference: nextSibling,
150+
};
151+
// undo: remove orphaned node
152+
return { node };
167153
}
168154

169155
/** Applies an EditV2, returning the corresponding "undo" EditV2. */

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"module": "esnext",
55
"moduleResolution": "node",
66
"noEmitOnError": true,
7-
"lib": ["es2018", "dom"],
7+
"lib": ["es2022", "dom"],
88
"strict": true,
99
"esModuleInterop": false,
1010
"allowSyntheticDefaultImports": true,

0 commit comments

Comments
 (0)