Tabellen-Umbenennung schreibt Referenzen dokumentweit um

Bisher speicherte eine Formel den Tabellennamen nur als Text im
Qualifier ("Umsatz!B2") – beim Umbenennen der Tabelle wurde nirgendwo
danach gesucht, sodass jede andere Formel, die noch den alten Namen
nannte, beim nächsten Neuberechnen #REF zeigte.

renameObject() ruft jetzt vor der eigentlichen Umbenennung eine neue
renameDocumentRefs()/rewriteRefsForRename() auf, die dieselbe
Token-Splice-Technik wiederverwendet, die für das dokumentweite
Umschreiben bei Zeilen-/Spalten-Einfügen bereits existiert: jede Formel
im Dokument wird tokenisiert, jeder Qualifier über die echte
wb.resolveTableName-Auflösung (nicht per String-Vergleich) auf die
umbenannte Tabelle geprüft, und nur die Tabellen-Namens-Textspanne
ersetzt – bei der 3-teiligen "Blatt!Tabelle::Zelle"-Form bleibt der
Blatt-Teil unangetastet, ein neuer Name mit Leerzeichen wird korrekt
gequotet. Umbenennen + Umschreiben zählen als ein Undo-Schritt.

Bewusst auf Tabellenzellen begrenzt, wie schon beim Zeilen-/Spalten-
Umschreiben (keine Makro-Aktionen, keine Funktionskörper).

Live per Playwright verifiziert: "Umsatz" per Doppelklick zu "Verkauf"
umbenannt, =SUM(Umsatz!B2:E2) in einer anderen Tabelle wurde automatisch
zu =SUM(Verkauf!B2:E2), Wert blieb unverändert (kein #REF).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wkh
2026-09-09 12:10:23 +02:00
co-authored by Claude Sonnet 5
parent 3ae3d81066
commit 47c99bbeb6
3 changed files with 304 additions and 13 deletions
+123
View File
@@ -1244,6 +1244,129 @@ describe('insertRow/deleteRow/insertColumn/deleteColumn (mid-grid, not just edge
});
});
describe('renaming a table rewrites every other formula\'s qualifier (document-wide, not just names re-resolving)', () => {
const rawInput = (s: DocumentStore, table: string, a1: string) => {
const { col, row } = coords(a1);
return s.cellInput(tableId(s, table), col, row);
};
it('a plain "Alt!B2" reference in ANOTHER table becomes "Neu!B2" and keeps evaluating correctly', () => {
const s = fresh();
const src = s.addTable(s.doc.activeSheetId);
const srcName = s.tableById(src)!.name;
const dst = s.addTable(s.doc.activeSheetId);
const dstName = s.tableById(dst)!.name;
setCell(s, srcName, 'B2', '10');
setCell(s, dstName, 'A1', `=${srcName}!B2`);
expect(val(s, dstName, 'A1')).toBe(10);
s.renameObject(src, 'Neu');
expect(rawInput(s, dstName, 'A1')).toBe('=Neu!B2');
expect(val(s, dstName, 'A1')).toBe(10); // still resolves -- no #REF
});
it('the "::" qualifier form is rewritten too', () => {
const s = fresh();
const src = s.addTable(s.doc.activeSheetId);
const srcName = s.tableById(src)!.name;
const dst = s.addTable(s.doc.activeSheetId);
const dstName = s.tableById(dst)!.name;
setCell(s, dstName, 'A1', `=${srcName}::B2+1`);
s.renameObject(src, 'Neu');
expect(rawInput(s, dstName, 'A1')).toBe('=Neu::B2+1');
});
it('a range reference keeps its range, only the qualifier changes', () => {
const s = fresh();
const src = s.addTable(s.doc.activeSheetId);
const srcName = s.tableById(src)!.name;
const dst = s.addTable(s.doc.activeSheetId);
const dstName = s.tableById(dst)!.name;
setCell(s, dstName, 'A1', `=SUM(${srcName}!B2:B6)`);
s.renameObject(src, 'Neu');
expect(rawInput(s, dstName, 'A1')).toBe('=SUM(Neu!B2:B6)');
});
it('only the TABLE part of a 3-part "Blatt!Tabelle::Zelle" reference is rewritten, the sheet stays', () => {
const s = fresh();
const sheet2 = s.addSheet('Zweites');
const src = s.addTable(sheet2);
const srcName = s.tableById(src)!.name;
s.setActiveSheet(s.doc.sheets[0].id);
const dst = s.addTable(s.doc.sheets[0].id);
const dstName = s.tableById(dst)!.name;
const sheet2Name = s.sheetById(sheet2)!.name;
setCell(s, dstName, 'A1', `=${sheet2Name}!${srcName}::B2`);
s.renameObject(src, 'Neu');
expect(rawInput(s, dstName, 'A1')).toBe(`=${sheet2Name}!Neu::B2`);
});
it('renaming to a name that needs quoting produces a properly quoted qualifier', () => {
const s = fresh();
const src = s.addTable(s.doc.activeSheetId);
const srcName = s.tableById(src)!.name;
const dst = s.addTable(s.doc.activeSheetId);
const dstName = s.tableById(dst)!.name;
setCell(s, dstName, 'A1', `=${srcName}!B2`);
s.renameObject(src, 'Neuer Name');
expect(rawInput(s, dstName, 'A1')).toBe("='Neuer Name'!B2");
});
it('a formula referencing a DIFFERENT, un-renamed table stays byte-identical', () => {
const s = fresh();
const src = s.addTable(s.doc.activeSheetId);
const other = s.addTable(s.doc.activeSheetId);
const otherName = s.tableById(other)!.name;
const dst = s.addTable(s.doc.activeSheetId);
const dstName = s.tableById(dst)!.name;
const untouched = `=${otherName}!B2 + 1`;
setCell(s, dstName, 'A1', untouched);
s.renameObject(src, 'Neu');
expect(rawInput(s, dstName, 'A1')).toBe(untouched);
});
it('a redundant self-qualified reference inside the renamed table itself is rewritten too', () => {
const s = fresh();
const id = s.addTable(s.doc.activeSheetId);
const name = s.tableById(id)!.name;
setCell(s, name, 'B2', '10');
setCell(s, name, 'A1', `=${name}!B2`); // redundant explicit self-qualifier
s.renameObject(id, 'Neu');
expect(rawInput(s, 'Neu', 'A1')).toBe('=Neu!B2');
expect(val(s, 'Neu', 'A1')).toBe(10);
});
it('the rewrite and the rename coalesce into a single undo step', () => {
vi.useFakeTimers();
try {
const s = fresh();
const src = s.addTable(s.doc.activeSheetId);
const srcName = s.tableById(src)!.name;
const dst = s.addTable(s.doc.activeSheetId);
const dstName = s.tableById(dst)!.name;
setCell(s, dstName, 'A1', `=${srcName}!B2`);
vi.advanceTimersByTime(600); // let the setup settle first
s.renameObject(src, 'Neu');
vi.advanceTimersByTime(600);
expect(rawInput(s, dstName, 'A1')).toBe('=Neu!B2');
s.undo(); // one Ctrl+Z undoes BOTH the rename and the ref rewrite together
expect(s.tableById(src)!.name).toBe(srcName);
expect(rawInput(s, dstName, 'A1')).toBe(`=${srcName}!B2`);
} finally {
vi.runOnlyPendingTimers();
vi.useRealTimers();
}
});
});
describe('dependency-graph view (cellGraphSelf/cellGraphNeighbors)', () => {
it('cellGraphSelf describes a formula cell: label, raw text, value, table/sheet name', () => {
const s = fresh();