diff --git a/src/js/core/RowManager.js b/src/js/core/RowManager.js index 9c699dfa8..c0dccf0df 100644 --- a/src/js/core/RowManager.js +++ b/src/js/core/RowManager.js @@ -893,10 +893,19 @@ export default class RowManager extends CoreFeature{ } if(renderClass){ - this.renderMode = this.table.options.renderVertical; - this.renderer = new renderClass(this.table, this.element, this.tableElement); this.renderer.initialize(); + + //renderMode must be a STRING: it is written into the + //tabulator-render-mode DOM attribute (see _showPlaceholder) and + //returned by getRenderMode(). options.renderVertical may be a custom + //renderer CLASS rather than a "virtual"/"basic" key; assigning it + //verbatim stringified the whole class source into the attribute. + //Prefer the resolved string key, else the renderer's own declared + //string renderMode, else fall back to "virtual". + this.renderMode = typeof this.table.options.renderVertical === "string" + ? this.table.options.renderVertical + : (typeof this.renderer.renderMode === "string" ? this.renderer.renderMode : "virtual"); if((this.table.element.clientHeight || this.table.options.height) && !(this.table.options.minHeight && this.table.options.maxHeight)){ this.fixedHeight = true; diff --git a/test/unit/core/RowManager.spec.js b/test/unit/core/RowManager.spec.js new file mode 100644 index 000000000..5d3ce4ba0 --- /dev/null +++ b/test/unit/core/RowManager.spec.js @@ -0,0 +1,62 @@ +import TabulatorFull from "../../../src/js/core/TabulatorFull"; +import VirtualDomVertical from "../../../src/js/core/rendering/renderers/VirtualDomVertical.js"; + +// Regression: RowManager stored options.renderVertical verbatim in renderMode. +// When a custom renderer CLASS was passed (renderVertical: SomeRenderer), the +// class was later stringified into the tabulator-render-mode DOM attribute +// (RowManager._showPlaceholder) and returned by getRenderMode() as a function +// rather than a mode string. +describe("RowManager renderMode", () => { + let el; + + beforeEach(() => { + el = document.createElement("div"); + document.body.appendChild(el); + }); + + afterEach(() => { + el.remove(); + }); + + const build = (options) => + new Promise((resolve) => { + const table = new TabulatorFull(el, options); + table.on("tableBuilt", () => resolve(table)); + }); + + test("string renderVertical is recorded verbatim", async () => { + const table = await build({ + renderVertical: "virtual", + placeholder: "No Data", + data: [], + columns: [{ title: "A", field: "a" }], + }); + + expect(table.rowManager.getRenderMode()).toBe("virtual"); + }); + + test("custom renderer class resolves to a string mode, not the class source", async () => { + class CustomRenderer extends VirtualDomVertical {} + + const table = await build({ + renderVertical: CustomRenderer, + placeholder: "No Data", + data: [], + columns: [{ title: "A", field: "a" }], + }); + + const mode = table.rowManager.getRenderMode(); + expect(typeof mode).toBe("string"); + expect(mode).toBe("virtual"); + + // The mode is written into the tabulator-render-mode DOM attribute; before + // the fix this held the stringified class body. Show the placeholder so + // the attribute is actually written. + table.rowManager.tableEmpty(); + const placeholder = table.element.querySelector("[tabulator-render-mode]"); + expect(placeholder).not.toBeNull(); + const attr = placeholder.getAttribute("tabulator-render-mode"); + expect(attr).toBe("virtual"); + expect(attr).not.toMatch(/class |function |=>/); + }); +});